From a4b91e2cc4a4d0a09bce01c686df1fc7c812a12a Mon Sep 17 00:00:00 2001 From: Kudlaty Date: Sun, 16 Aug 2009 18:34:45 +0200 Subject: Merge [SD2] r1311 Move SetData from Reset to JustReachedHome for channelers and boss. r1312 Added class for FollowerAI. Note this is under development and may have issues in some situations. FollowerAI is generally to be used for escort quests where NPC follow leader instead of using a predefined path. --HG-- branch : trunk --- src/bindings/scripts/base/escortAI.cpp | 409 ------------------------------ src/bindings/scripts/base/escortAI.h | 107 -------- src/bindings/scripts/base/escort_ai.cpp | 409 ++++++++++++++++++++++++++++++ src/bindings/scripts/base/escort_ai.h | 107 ++++++++ src/bindings/scripts/base/follower_ai.cpp | 309 ++++++++++++++++++++++ src/bindings/scripts/base/follower_ai.h | 50 ++++ 6 files changed, 875 insertions(+), 516 deletions(-) delete mode 100644 src/bindings/scripts/base/escortAI.cpp delete mode 100644 src/bindings/scripts/base/escortAI.h create mode 100644 src/bindings/scripts/base/escort_ai.cpp create mode 100644 src/bindings/scripts/base/escort_ai.h create mode 100644 src/bindings/scripts/base/follower_ai.cpp create mode 100644 src/bindings/scripts/base/follower_ai.h (limited to 'src/bindings/scripts/base') diff --git a/src/bindings/scripts/base/escortAI.cpp b/src/bindings/scripts/base/escortAI.cpp deleted file mode 100644 index 2c9b968d281..00000000000 --- a/src/bindings/scripts/base/escortAI.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/* Copyright (C) 2006 - 2009 ScriptDev2 - * This program is free software licensed under GPL version 2 - * Please see the included DOCS/LICENSE.TXT for more information */ - -/* ScriptData -SDName: Npc_EscortAI -SD%Complete: 100 -SDComment: -SDCategory: Npc -EndScriptData */ - -#include "precompiled.h" -#include "escortAI.h" - -enum -{ - POINT_LAST_POINT = 0xFFFFFF, - POINT_HOME = 0xFFFFFE -}; - -extern std::list PointMovementList; - -void npc_escortAI::AttackStart(Unit* pWho) -{ - if (!pWho) - return; - - if (m_creature->Attack(pWho, true)) - { - if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE) - m_creature->GetMotionMaster()->MovementExpired(); - - if (IsCombatMovement()) - m_creature->GetMotionMaster()->MoveChase(pWho); - } -} - -void npc_escortAI::MoveInLineOfSight(Unit* pWho) -{ - if (IsBeingEscorted && !m_bIsActiveAttacker) - return; - - ScriptedAI::MoveInLineOfSight(pWho); -} - -void npc_escortAI::JustDied(Unit* pKiller) -{ - if (!IsBeingEscorted || !PlayerGUID || !m_pQuestForEscort) - return; - - if (Player* pPlayer = Unit::GetPlayer(PlayerGUID)) - { - if (Group* pGroup = pPlayer->GetGroup()) - { - for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) - { - if (Player* pMember = pRef->getSource()) - { - if (pPlayer->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE) - pPlayer->FailQuest(m_pQuestForEscort->GetQuestId()); - } - } - } - else - { - if (pPlayer->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE) - pPlayer->FailQuest(m_pQuestForEscort->GetQuestId()); - } - } -} - -void npc_escortAI::JustRespawned() -{ - IsBeingEscorted = false; - IsOnHold = false; - - if (!IsCombatMovement()) - SetCombatMovement(true); - - //add a small delay before going to first waypoint, normal in near all cases - m_uiWPWaitTimer = 2500; - - if (m_creature->getFaction() != m_creature->GetCreatureInfo()->faction_A) - me->RestoreFaction(); - - Reset(); -} - -void npc_escortAI::ReturnToLastPoint() -{ - float x, y, z, o; - m_creature->GetHomePosition(x, y, z, o); - m_creature->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z); -} - -void npc_escortAI::EnterEvadeMode() -{ - m_creature->RemoveAllAuras(); - m_creature->DeleteThreatList(); - m_creature->CombatStop(true); - m_creature->SetLootRecipient(NULL); - - if (IsBeingEscorted) - { - m_bIsReturning = true; - ReturnToLastPoint(); - debug_log("TSCR: EscortAI has left combat and is now returning to last point"); - } - else - m_creature->GetMotionMaster()->MoveTargetedHome(); - - Reset(); -} - -void npc_escortAI::UpdateAI(const uint32 uiDiff) -{ - //Waypoint Updating - if (IsBeingEscorted && !m_creature->getVictim() && m_uiWPWaitTimer && !m_bIsReturning) - { - if (m_uiWPWaitTimer <= uiDiff) - { - //End of the line - if (CurrentWP == WaypointList.end()) - { - if (DespawnAtEnd) - { - debug_log("TSCR: EscortAI reached end of waypoints"); - - if (m_bCanReturnToStart) - { - float fRetX, fRetY, fRetZ; - m_creature->GetRespawnCoord(fRetX, fRetY, fRetZ); - - m_creature->GetMotionMaster()->MovePoint(POINT_HOME, fRetX, fRetY, fRetZ); - - m_uiWPWaitTimer = 0; - - debug_log("TSCR: EscortAI are returning home to spawn location: %u, %f, %f, %f", POINT_HOME, fRetX, fRetY, fRetZ); - return; - } - - if (m_bCanInstantRespawn) - { - m_creature->setDeathState(JUST_DIED); - m_creature->Respawn(); - } - else - m_creature->ForcedDespawn(); - - return; - } - else - { - debug_log("TSCR: EscortAI reached end of waypoints with Despawn off"); - - return; - } - } - - if (!IsOnHold) - { - m_creature->GetMotionMaster()->MovePoint(CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z); - debug_log("TSCR: EscortAI Next WP is: %u, %f, %f, %f", CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z); - m_uiWPWaitTimer = 0; - } - } - else - m_uiWPWaitTimer -= uiDiff; - } - - //Check if player or any member of his group is within range - if (IsBeingEscorted && PlayerGUID && !m_creature->getVictim() && !m_bIsReturning) - { - if (m_uiPlayerCheckTimer < uiDiff) - { - bool bIsMaxRangeExceeded = true; - - if (Player* pPlayer = Unit::GetPlayer(PlayerGUID)) - { - if (Group* pGroup = pPlayer->GetGroup()) - { - for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) - { - Player* pMember = pRef->getSource(); - - if (pMember && m_creature->IsWithinDistInMap(pMember, GetMaxPlayerDistance())) - { - bIsMaxRangeExceeded = false; - break; - } - } - } - else - { - if (m_creature->IsWithinDistInMap(pPlayer, GetMaxPlayerDistance())) - bIsMaxRangeExceeded = false; - } - } - - if (DespawnAtFar && bIsMaxRangeExceeded) - { - debug_log("TSCR: EscortAI failed because player/group was to far away or not found"); - - if (m_bCanInstantRespawn) - { - m_creature->setDeathState(JUST_DIED); - m_creature->Respawn(); - } - else - m_creature->ForcedDespawn(); - - return; - } - - m_uiPlayerCheckTimer = 1000; - } - else - m_uiPlayerCheckTimer -= uiDiff; - } - - if (CanMelee && UpdateVictim()) - DoMeleeAttackIfReady(); -} - -void npc_escortAI::MovementInform(uint32 uiMoveType, uint32 uiPointId) -{ - if (uiMoveType != POINT_MOTION_TYPE || !IsBeingEscorted) - return; - - //Combat start position reached, continue waypoint movement - if (uiPointId == POINT_LAST_POINT) - { - debug_log("TSCR: EscortAI has returned to original position before combat"); - - if (m_bIsRunning && m_creature->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE)) - m_creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - else if (!m_bIsRunning && !m_creature->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE)) - m_creature->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - - m_bIsReturning = false; - - if (!m_uiWPWaitTimer) - m_uiWPWaitTimer = 1; - } - else if (uiPointId == POINT_HOME) - { - debug_log("TSCR: EscortAI has returned to original home location and will continue from beginning of waypoint list."); - - CurrentWP = WaypointList.begin(); - m_uiWPWaitTimer = 1; - } - else - { - //Make sure that we are still on the right waypoint - if (CurrentWP->id != uiPointId) - { - debug_log("TSCR ERROR: EscortAI reached waypoint out of order %d, expected %d", uiPointId, CurrentWP->id); - return; - } - - debug_log("TSCR: EscortAI Waypoint %d reached", CurrentWP->id); - - //Call WP function - WaypointReached(CurrentWP->id); - - m_uiWPWaitTimer = CurrentWP->WaitTimeMs + 1; - - ++CurrentWP; - } -} - -/* -void npc_escortAI::OnPossess(bool apply) -{ - // We got possessed in the middle of being escorted, store the point - // where we left off to come back to when possess is removed - if (IsBeingEscorted) - { - if (apply) - m_creature->GetPosition(LastPos.x, LastPos.y, LastPos.z); - else - { - Returning = true; - m_creature->GetMotionMaster()->MovementExpired(); - m_creature->GetMotionMaster()->MovePoint(WP_LAST_POINT, LastPos.x, LastPos.y, LastPos.z); - } - } -} -*/ - -void npc_escortAI::AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs) -{ - Escort_Waypoint t(id, x, y, z, WaitTimeMs); - - WaypointList.push_back(t); - - // i think SD2 no longer uses this function - ScriptWP = true; - /*PointMovement wp; - wp.m_uiCreatureEntry = me->GetEntry(); - wp.m_uiPointId = id; - wp.m_fX = x; - wp.m_fY = y; - wp.m_fZ = z; - wp.m_uiWaitTime = WaitTimeMs; - PointMovementMap[wp.m_uiCreatureEntry].push_back(wp);*/ -} - -void npc_escortAI::FillPointMovementListForCreature() -{ - UNORDERED_MAP >::iterator pPointsEntries = PointMovementMap.find(m_creature->GetEntry()); - - if (pPointsEntries != PointMovementMap.end()) - { - std::vector::iterator itr; - - for (itr = pPointsEntries->second.begin(); itr != pPointsEntries->second.end(); ++itr) - { - Escort_Waypoint pPoint(itr->m_uiPointId,itr->m_fX,itr->m_fY,itr->m_fZ,itr->m_uiWaitTime); - WaypointList.push_back(pPoint); - } - } -} - -void npc_escortAI::SetRun(bool bRun) -{ - if (bRun) - { - if (!m_bIsRunning) - m_creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - else - debug_log("TSCR: EscortAI attempt to set run mode, but is already running."); - } - else - { - if (m_bIsRunning) - m_creature->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - else - debug_log("TSCR: EscortAI attempt to set walk mode, but is already walking."); - } - m_bIsRunning = bRun; -} - -//TODO: get rid of this many variables passed in function. -void npc_escortAI::Start(bool bIsActiveAttacker, bool bRun, uint64 uiPlayerGUID, const Quest* pQuest, bool bInstantRespawn, bool bCanLoopPath) -{ - if (m_creature->getVictim()) - { - debug_log("TSCR ERROR: EscortAI attempt to Start while in combat"); - return; - } - - if (IsBeingEscorted) - { - error_log("TSCR: EscortAI attempt to Start while already escorting"); - return; - } - - if(!ScriptWP) // sd2 never adds wp in script, but tc does - { - - if (!WaypointList.empty()) - WaypointList.clear(); - - FillPointMovementListForCreature(); - - } - - if (WaypointList.empty()) - { - error_db_log("TSCR: EscortAI Start with 0 waypoints (possible missing entry in script_waypoint)"); - return; - } - - //set variables - m_bIsActiveAttacker = bIsActiveAttacker; - m_bIsRunning = bRun; - - PlayerGUID = uiPlayerGUID; - m_pQuestForEscort = pQuest; - - m_bCanInstantRespawn = bInstantRespawn; - m_bCanReturnToStart = bCanLoopPath; - - if (m_bCanReturnToStart && m_bCanInstantRespawn) - debug_log("TSCR: EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn."); - - if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) - { - m_creature->GetMotionMaster()->MovementExpired(); - m_creature->GetMotionMaster()->MoveIdle(); - debug_log("TSCR: EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle."); - } - - //disable npcflags - m_creature->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); - - debug_log("TSCR: EscortAI started with %d waypoints. ActiveAttacker = %d, Run = %d, PlayerGUID = %d", WaypointList.size(), m_bIsActiveAttacker, m_bIsRunning, PlayerGUID); - - CurrentWP = WaypointList.begin(); - - //Set initial speed - if (m_bIsRunning) - m_creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - else - m_creature->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); - - IsBeingEscorted = true; -} diff --git a/src/bindings/scripts/base/escortAI.h b/src/bindings/scripts/base/escortAI.h deleted file mode 100644 index 8ce82eb370a..00000000000 --- a/src/bindings/scripts/base/escortAI.h +++ /dev/null @@ -1,107 +0,0 @@ -/* Copyright (C) 2006 - 2009 ScriptDev2 - * This program is free software licensed under GPL version 2 - * Please see the included DOCS/LICENSE.TXT for more information */ - -#ifndef SC_ESCORTAI_H -#define SC_ESCORTAI_H - -#define DEFAULT_MAX_PLAYER_DISTANCE 50 - -extern UNORDERED_MAP > PointMovementMap; - -struct Escort_Waypoint -{ - Escort_Waypoint(uint32 _id, float _x, float _y, float _z, uint32 _w) - { - id = _id; - x = _x; - y = _y; - z = _z; - WaitTimeMs = _w; - } - - uint32 id; - float x; - float y; - float z; - uint32 WaitTimeMs; -}; - -struct TRINITY_DLL_DECL npc_escortAI : public ScriptedAI -{ - public: - explicit npc_escortAI(Creature* pCreature) : ScriptedAI(pCreature), - IsBeingEscorted(false), IsOnHold(false), PlayerGUID(0), MaxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE), CanMelee(true), m_uiPlayerCheckTimer(1000), m_uiWPWaitTimer(2500), m_bIsReturning(false), m_bIsActiveAttacker(true), m_bIsRunning(false), DespawnAtEnd(true), DespawnAtFar(true), m_pQuestForEscort(NULL), m_bCanInstantRespawn(false), m_bCanReturnToStart(false), ScriptWP(false) {} - ~npc_escortAI() {} - - // Pure Virtual Functions - virtual void WaypointReached(uint32) = 0; - - // CreatureAI functions - void AttackStart(Unit* who); - - void MoveInLineOfSight(Unit* who); - - void JustDied(Unit*); - - void JustRespawned(); - - void ReturnToLastPoint(); - - void EnterEvadeMode(); - - void UpdateAI(const uint32); - - void MovementInform(uint32, uint32); - - // EscortAI functions - void AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs = 0); - - void FillPointMovementListForCreature(); - - void Start(bool bIsActiveAttacker = true, bool bRun = false, uint64 uiPlayerGUID = 0, const Quest* pQuest = NULL, bool bInstantRespawn = false, bool bCanLoopPath = false); - - void SetRun(bool bRun = true); - - void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; } - float GetMaxPlayerDistance() { return MaxPlayerDistance; } - - bool IsEscorted() {return IsBeingEscorted;} - - void SetCanMelee(bool usemelee) { CanMelee = usemelee; } - void SetDespawnAtEnd(bool despawn) { DespawnAtEnd = despawn; } - void SetDespawnAtFar(bool despawn) { DespawnAtFar = despawn; } - bool GetAttack() { return m_bIsActiveAttacker; }//used in EnterEvadeMode override - bool GetIsBeingEscorted() { return IsBeingEscorted; }//used in EnterEvadeMode override - void SetReturning(bool returning) { m_bIsReturning = returning; }//used in EnterEvadeMode override - void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; } - uint64 GetEventStarterGUID() { return PlayerGUID; } - - // EscortAI variables - protected: - uint64 PlayerGUID; - bool IsBeingEscorted; - bool IsOnHold; - - private: - uint32 m_uiWPWaitTimer; - uint32 m_uiPlayerCheckTimer; - float MaxPlayerDistance; - - const Quest* m_pQuestForEscort; //generally passed in Start() when regular escort script. - - std::list WaypointList; - std::list::iterator CurrentWP; - - bool m_bIsActiveAttacker; //possible obsolete, and should be determined with db only (civilian) - bool m_bIsReturning; //in use when creature leave combat, and are returning to combat start position - bool m_bIsRunning; //all creatures are walking by default (has flag MOVEMENTFLAG_WALK) - bool m_bCanInstantRespawn; //if creature should respawn instantly after escort over (if not, database respawntime are used) - bool m_bCanReturnToStart; //if creature can walk same path (loop) without despawn. Not for regular escort quests. - bool CanMelee; - bool DespawnAtEnd; - bool DespawnAtFar; - bool ScriptWP; -}; -#endif - diff --git a/src/bindings/scripts/base/escort_ai.cpp b/src/bindings/scripts/base/escort_ai.cpp new file mode 100644 index 00000000000..3f4c8601f90 --- /dev/null +++ b/src/bindings/scripts/base/escort_ai.cpp @@ -0,0 +1,409 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +/* ScriptData +SDName: Npc_EscortAI +SD%Complete: 100 +SDComment: +SDCategory: Npc +EndScriptData */ + +#include "precompiled.h" +#include "escort_ai.h" + +enum +{ + POINT_LAST_POINT = 0xFFFFFF, + POINT_HOME = 0xFFFFFE +}; + +extern std::list PointMovementList; + +void npc_escortAI::AttackStart(Unit* pWho) +{ + if (!pWho) + return; + + if (m_creature->Attack(pWho, true)) + { + if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE) + m_creature->GetMotionMaster()->MovementExpired(); + + if (IsCombatMovement()) + m_creature->GetMotionMaster()->MoveChase(pWho); + } +} + +void npc_escortAI::MoveInLineOfSight(Unit* pWho) +{ + if (IsBeingEscorted && !m_bIsActiveAttacker) + return; + + ScriptedAI::MoveInLineOfSight(pWho); +} + +void npc_escortAI::JustDied(Unit* pKiller) +{ + if (!IsBeingEscorted || !PlayerGUID || !m_pQuestForEscort) + return; + + if (Player* pPlayer = Unit::GetPlayer(PlayerGUID)) + { + if (Group* pGroup = pPlayer->GetGroup()) + { + for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + if (Player* pMember = pRef->getSource()) + { + if (pPlayer->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE) + pPlayer->FailQuest(m_pQuestForEscort->GetQuestId()); + } + } + } + else + { + if (pPlayer->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE) + pPlayer->FailQuest(m_pQuestForEscort->GetQuestId()); + } + } +} + +void npc_escortAI::JustRespawned() +{ + IsBeingEscorted = false; + IsOnHold = false; + + if (!IsCombatMovement()) + SetCombatMovement(true); + + //add a small delay before going to first waypoint, normal in near all cases + m_uiWPWaitTimer = 2500; + + if (m_creature->getFaction() != m_creature->GetCreatureInfo()->faction_A) + me->RestoreFaction(); + + Reset(); +} + +void npc_escortAI::ReturnToLastPoint() +{ + float x, y, z, o; + m_creature->GetHomePosition(x, y, z, o); + m_creature->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z); +} + +void npc_escortAI::EnterEvadeMode() +{ + m_creature->RemoveAllAuras(); + m_creature->DeleteThreatList(); + m_creature->CombatStop(true); + m_creature->SetLootRecipient(NULL); + + if (IsBeingEscorted) + { + m_bIsReturning = true; + ReturnToLastPoint(); + debug_log("TSCR: EscortAI has left combat and is now returning to last point"); + } + else + m_creature->GetMotionMaster()->MoveTargetedHome(); + + Reset(); +} + +void npc_escortAI::UpdateAI(const uint32 uiDiff) +{ + //Waypoint Updating + if (IsBeingEscorted && !m_creature->getVictim() && m_uiWPWaitTimer && !m_bIsReturning) + { + if (m_uiWPWaitTimer <= uiDiff) + { + //End of the line + if (CurrentWP == WaypointList.end()) + { + if (DespawnAtEnd) + { + debug_log("TSCR: EscortAI reached end of waypoints"); + + if (m_bCanReturnToStart) + { + float fRetX, fRetY, fRetZ; + m_creature->GetRespawnCoord(fRetX, fRetY, fRetZ); + + m_creature->GetMotionMaster()->MovePoint(POINT_HOME, fRetX, fRetY, fRetZ); + + m_uiWPWaitTimer = 0; + + debug_log("TSCR: EscortAI are returning home to spawn location: %u, %f, %f, %f", POINT_HOME, fRetX, fRetY, fRetZ); + return; + } + + if (m_bCanInstantRespawn) + { + m_creature->setDeathState(JUST_DIED); + m_creature->Respawn(); + } + else + m_creature->ForcedDespawn(); + + return; + } + else + { + debug_log("TSCR: EscortAI reached end of waypoints with Despawn off"); + + return; + } + } + + if (!IsOnHold) + { + m_creature->GetMotionMaster()->MovePoint(CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z); + debug_log("TSCR: EscortAI Next WP is: %u, %f, %f, %f", CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z); + m_uiWPWaitTimer = 0; + } + } + else + m_uiWPWaitTimer -= uiDiff; + } + + //Check if player or any member of his group is within range + if (IsBeingEscorted && PlayerGUID && !m_creature->getVictim() && !m_bIsReturning) + { + if (m_uiPlayerCheckTimer < uiDiff) + { + bool bIsMaxRangeExceeded = true; + + if (Player* pPlayer = Unit::GetPlayer(PlayerGUID)) + { + if (Group* pGroup = pPlayer->GetGroup()) + { + for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + Player* pMember = pRef->getSource(); + + if (pMember && m_creature->IsWithinDistInMap(pMember, GetMaxPlayerDistance())) + { + bIsMaxRangeExceeded = false; + break; + } + } + } + else + { + if (m_creature->IsWithinDistInMap(pPlayer, GetMaxPlayerDistance())) + bIsMaxRangeExceeded = false; + } + } + + if (DespawnAtFar && bIsMaxRangeExceeded) + { + debug_log("TSCR: EscortAI failed because player/group was to far away or not found"); + + if (m_bCanInstantRespawn) + { + m_creature->setDeathState(JUST_DIED); + m_creature->Respawn(); + } + else + m_creature->ForcedDespawn(); + + return; + } + + m_uiPlayerCheckTimer = 1000; + } + else + m_uiPlayerCheckTimer -= uiDiff; + } + + if (CanMelee && UpdateVictim()) + DoMeleeAttackIfReady(); +} + +void npc_escortAI::MovementInform(uint32 uiMoveType, uint32 uiPointId) +{ + if (uiMoveType != POINT_MOTION_TYPE || !IsBeingEscorted) + return; + + //Combat start position reached, continue waypoint movement + if (uiPointId == POINT_LAST_POINT) + { + debug_log("TSCR: EscortAI has returned to original position before combat"); + + if (m_bIsRunning && m_creature->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE)) + m_creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + else if (!m_bIsRunning && !m_creature->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE)) + m_creature->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + + m_bIsReturning = false; + + if (!m_uiWPWaitTimer) + m_uiWPWaitTimer = 1; + } + else if (uiPointId == POINT_HOME) + { + debug_log("TSCR: EscortAI has returned to original home location and will continue from beginning of waypoint list."); + + CurrentWP = WaypointList.begin(); + m_uiWPWaitTimer = 1; + } + else + { + //Make sure that we are still on the right waypoint + if (CurrentWP->id != uiPointId) + { + debug_log("TSCR ERROR: EscortAI reached waypoint out of order %d, expected %d", uiPointId, CurrentWP->id); + return; + } + + debug_log("TSCR: EscortAI Waypoint %d reached", CurrentWP->id); + + //Call WP function + WaypointReached(CurrentWP->id); + + m_uiWPWaitTimer = CurrentWP->WaitTimeMs + 1; + + ++CurrentWP; + } +} + +/* +void npc_escortAI::OnPossess(bool apply) +{ + // We got possessed in the middle of being escorted, store the point + // where we left off to come back to when possess is removed + if (IsBeingEscorted) + { + if (apply) + m_creature->GetPosition(LastPos.x, LastPos.y, LastPos.z); + else + { + Returning = true; + m_creature->GetMotionMaster()->MovementExpired(); + m_creature->GetMotionMaster()->MovePoint(WP_LAST_POINT, LastPos.x, LastPos.y, LastPos.z); + } + } +} +*/ + +void npc_escortAI::AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs) +{ + Escort_Waypoint t(id, x, y, z, WaitTimeMs); + + WaypointList.push_back(t); + + // i think SD2 no longer uses this function + ScriptWP = true; + /*PointMovement wp; + wp.m_uiCreatureEntry = me->GetEntry(); + wp.m_uiPointId = id; + wp.m_fX = x; + wp.m_fY = y; + wp.m_fZ = z; + wp.m_uiWaitTime = WaitTimeMs; + PointMovementMap[wp.m_uiCreatureEntry].push_back(wp);*/ +} + +void npc_escortAI::FillPointMovementListForCreature() +{ + UNORDERED_MAP >::iterator pPointsEntries = PointMovementMap.find(m_creature->GetEntry()); + + if (pPointsEntries != PointMovementMap.end()) + { + std::vector::iterator itr; + + for (itr = pPointsEntries->second.begin(); itr != pPointsEntries->second.end(); ++itr) + { + Escort_Waypoint pPoint(itr->m_uiPointId,itr->m_fX,itr->m_fY,itr->m_fZ,itr->m_uiWaitTime); + WaypointList.push_back(pPoint); + } + } +} + +void npc_escortAI::SetRun(bool bRun) +{ + if (bRun) + { + if (!m_bIsRunning) + m_creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + else + debug_log("TSCR: EscortAI attempt to set run mode, but is already running."); + } + else + { + if (m_bIsRunning) + m_creature->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + else + debug_log("TSCR: EscortAI attempt to set walk mode, but is already walking."); + } + m_bIsRunning = bRun; +} + +//TODO: get rid of this many variables passed in function. +void npc_escortAI::Start(bool bIsActiveAttacker, bool bRun, uint64 uiPlayerGUID, const Quest* pQuest, bool bInstantRespawn, bool bCanLoopPath) +{ + if (m_creature->getVictim()) + { + debug_log("TSCR ERROR: EscortAI attempt to Start while in combat"); + return; + } + + if (IsBeingEscorted) + { + error_log("TSCR: EscortAI attempt to Start while already escorting"); + return; + } + + if(!ScriptWP) // sd2 never adds wp in script, but tc does + { + + if (!WaypointList.empty()) + WaypointList.clear(); + + FillPointMovementListForCreature(); + + } + + if (WaypointList.empty()) + { + error_db_log("TSCR: EscortAI Start with 0 waypoints (possible missing entry in script_waypoint)"); + return; + } + + //set variables + m_bIsActiveAttacker = bIsActiveAttacker; + m_bIsRunning = bRun; + + PlayerGUID = uiPlayerGUID; + m_pQuestForEscort = pQuest; + + m_bCanInstantRespawn = bInstantRespawn; + m_bCanReturnToStart = bCanLoopPath; + + if (m_bCanReturnToStart && m_bCanInstantRespawn) + debug_log("TSCR: EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn."); + + if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + { + m_creature->GetMotionMaster()->MovementExpired(); + m_creature->GetMotionMaster()->MoveIdle(); + debug_log("TSCR: EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle."); + } + + //disable npcflags + m_creature->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + + debug_log("TSCR: EscortAI started with %d waypoints. ActiveAttacker = %d, Run = %d, PlayerGUID = %d", WaypointList.size(), m_bIsActiveAttacker, m_bIsRunning, PlayerGUID); + + CurrentWP = WaypointList.begin(); + + //Set initial speed + if (m_bIsRunning) + m_creature->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + else + m_creature->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + + IsBeingEscorted = true; +} diff --git a/src/bindings/scripts/base/escort_ai.h b/src/bindings/scripts/base/escort_ai.h new file mode 100644 index 00000000000..8ce82eb370a --- /dev/null +++ b/src/bindings/scripts/base/escort_ai.h @@ -0,0 +1,107 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SC_ESCORTAI_H +#define SC_ESCORTAI_H + +#define DEFAULT_MAX_PLAYER_DISTANCE 50 + +extern UNORDERED_MAP > PointMovementMap; + +struct Escort_Waypoint +{ + Escort_Waypoint(uint32 _id, float _x, float _y, float _z, uint32 _w) + { + id = _id; + x = _x; + y = _y; + z = _z; + WaitTimeMs = _w; + } + + uint32 id; + float x; + float y; + float z; + uint32 WaitTimeMs; +}; + +struct TRINITY_DLL_DECL npc_escortAI : public ScriptedAI +{ + public: + explicit npc_escortAI(Creature* pCreature) : ScriptedAI(pCreature), + IsBeingEscorted(false), IsOnHold(false), PlayerGUID(0), MaxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE), CanMelee(true), m_uiPlayerCheckTimer(1000), m_uiWPWaitTimer(2500), m_bIsReturning(false), m_bIsActiveAttacker(true), m_bIsRunning(false), DespawnAtEnd(true), DespawnAtFar(true), m_pQuestForEscort(NULL), m_bCanInstantRespawn(false), m_bCanReturnToStart(false), ScriptWP(false) {} + ~npc_escortAI() {} + + // Pure Virtual Functions + virtual void WaypointReached(uint32) = 0; + + // CreatureAI functions + void AttackStart(Unit* who); + + void MoveInLineOfSight(Unit* who); + + void JustDied(Unit*); + + void JustRespawned(); + + void ReturnToLastPoint(); + + void EnterEvadeMode(); + + void UpdateAI(const uint32); + + void MovementInform(uint32, uint32); + + // EscortAI functions + void AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs = 0); + + void FillPointMovementListForCreature(); + + void Start(bool bIsActiveAttacker = true, bool bRun = false, uint64 uiPlayerGUID = 0, const Quest* pQuest = NULL, bool bInstantRespawn = false, bool bCanLoopPath = false); + + void SetRun(bool bRun = true); + + void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; } + float GetMaxPlayerDistance() { return MaxPlayerDistance; } + + bool IsEscorted() {return IsBeingEscorted;} + + void SetCanMelee(bool usemelee) { CanMelee = usemelee; } + void SetDespawnAtEnd(bool despawn) { DespawnAtEnd = despawn; } + void SetDespawnAtFar(bool despawn) { DespawnAtFar = despawn; } + bool GetAttack() { return m_bIsActiveAttacker; }//used in EnterEvadeMode override + bool GetIsBeingEscorted() { return IsBeingEscorted; }//used in EnterEvadeMode override + void SetReturning(bool returning) { m_bIsReturning = returning; }//used in EnterEvadeMode override + void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; } + uint64 GetEventStarterGUID() { return PlayerGUID; } + + // EscortAI variables + protected: + uint64 PlayerGUID; + bool IsBeingEscorted; + bool IsOnHold; + + private: + uint32 m_uiWPWaitTimer; + uint32 m_uiPlayerCheckTimer; + float MaxPlayerDistance; + + const Quest* m_pQuestForEscort; //generally passed in Start() when regular escort script. + + std::list WaypointList; + std::list::iterator CurrentWP; + + bool m_bIsActiveAttacker; //possible obsolete, and should be determined with db only (civilian) + bool m_bIsReturning; //in use when creature leave combat, and are returning to combat start position + bool m_bIsRunning; //all creatures are walking by default (has flag MOVEMENTFLAG_WALK) + bool m_bCanInstantRespawn; //if creature should respawn instantly after escort over (if not, database respawntime are used) + bool m_bCanReturnToStart; //if creature can walk same path (loop) without despawn. Not for regular escort quests. + bool CanMelee; + bool DespawnAtEnd; + bool DespawnAtFar; + bool ScriptWP; +}; +#endif + diff --git a/src/bindings/scripts/base/follower_ai.cpp b/src/bindings/scripts/base/follower_ai.cpp new file mode 100644 index 00000000000..eb847c219db --- /dev/null +++ b/src/bindings/scripts/base/follower_ai.cpp @@ -0,0 +1,309 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +/* ScriptData +SDName: FollowerAI +SD%Complete: 50 +SDComment: This AI is under development +SDCategory: Npc +EndScriptData */ + +#include "precompiled.h" +#include "follower_ai.h" + +const float MAX_PLAYER_DISTANCE = 100.0f; + +enum +{ + POINT_COMBAT_START = 0xFFFFFF +}; + +FollowerAI::FollowerAI(Creature* pCreature) : ScriptedAI(pCreature), + m_uiLeaderGUID(0), + m_pQuestForFollow(NULL), + m_uiUpdateFollowTimer(2500), + m_bIsFollowing(false), + m_bIsReturnToLeader(false), + m_bIsFollowComplete(false) +{} + +void FollowerAI::AttackStart(Unit* pWho) +{ + if (!pWho) + return; + + if (m_creature->Attack(pWho, true)) + { + m_creature->AddThreat(pWho, 0.0f); + m_creature->SetInCombatWith(pWho); + pWho->SetInCombatWith(m_creature); + + if (m_creature->hasUnitState(UNIT_STAT_FOLLOW)) + m_creature->clearUnitState(UNIT_STAT_FOLLOW); + + if (IsCombatMovement()) + m_creature->GetMotionMaster()->MoveChase(pWho); + } +} + +void FollowerAI::MoveInLineOfSight(Unit* pWho) +{ + if (!m_creature->hasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack() && + m_creature->IsHostileTo(pWho) && pWho->isInAccessiblePlaceFor(m_creature)) + { + if (!m_creature->canFly() && m_creature->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE) + return; + + //This part provides assistance to a player that are attacked by pWho, even if out of normal aggro range + //It will cause m_creature to attack pWho that are attacking _any_ player (which has been confirmed may happen also on offi) + //The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate. + if (m_creature->hasUnitState(UNIT_STAT_FOLLOW) && + m_creature->GetCreatureInfo()->type_flags & 0x01000 && + pWho->getVictim() && + pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself() && + m_creature->IsWithinDistInMap(pWho, MAX_PLAYER_DISTANCE) && + m_creature->IsWithinLOSInMap(pWho)) + { + pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH); + AttackStart(pWho); + } + else + { + float attackRadius = m_creature->GetAttackDistance(pWho); + if (m_creature->IsWithinDistInMap(pWho, attackRadius) && m_creature->IsWithinLOSInMap(pWho)) + { + if (!m_creature->getVictim()) + { + pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH); + AttackStart(pWho); + } + else if (m_creature->GetMap()->IsDungeon()) + { + pWho->SetInCombatWith(m_creature); + m_creature->AddThreat(pWho, 0.0f); + } + } + } + } +} + +void FollowerAI::JustDied(Unit* pKiller) +{ + if (!m_bIsFollowing || !m_uiLeaderGUID) + return; + + //TODO: need a better check for quests with time limit. + if (Player* pPlayer = GetLeaderForFollower()) + { + if (Group* pGroup = pPlayer->GetGroup()) + { + for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + if (Player* pMember = pRef->getSource()) + { + if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE) + pPlayer->FailQuest(m_pQuestForFollow->GetQuestId()); + } + } + } + else + { + if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE) + pPlayer->FailQuest(m_pQuestForFollow->GetQuestId()); + } + } +} + +void FollowerAI::JustRespawned() +{ + m_bIsFollowing = false; + m_bIsReturnToLeader = false; + m_bIsFollowComplete = false; + + if (!IsCombatMovement()) + SetCombatMovement(true); + + if (m_creature->getFaction() != m_creature->GetCreatureInfo()->faction_A) + m_creature->setFaction(m_creature->GetCreatureInfo()->faction_A); + + Reset(); +} + +void FollowerAI::EnterEvadeMode() +{ + m_creature->RemoveAllAuras(); + m_creature->DeleteThreatList(); + m_creature->CombatStop(true); + m_creature->SetLootRecipient(NULL); + + if (m_bIsFollowing) + { + debug_log("SD2: FollowerAI left combat, returning to CombatStartPosition."); + + if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE) + { + float fPosX, fPosY, fPosZ; + m_creature->GetPosition(fPosX, fPosY, fPosZ); + m_creature->GetMotionMaster()->MovePoint(POINT_COMBAT_START, fPosX, fPosY, fPosZ); + } + } + else + { + if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE) + m_creature->GetMotionMaster()->MoveTargetedHome(); + } + + Reset(); +} + +void FollowerAI::UpdateAI(const uint32 uiDiff) +{ + if (m_bIsFollowing && !m_creature->getVictim()) + { + if (m_uiUpdateFollowTimer < uiDiff) + { + if (m_bIsFollowComplete) + { + debug_log("SD2: FollowerAI is set completed, despawns."); + m_creature->ForcedDespawn(); + return; + } + + bool bIsMaxRangeExceeded = true; + + if (Player* pPlayer = GetLeaderForFollower()) + { + if (m_bIsReturnToLeader) + { + debug_log("SD2: FollowerAI is returning to leader."); + m_creature->GetMotionMaster()->MoveFollow(pPlayer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + m_bIsReturnToLeader = false; + return; + } + + if (Group* pGroup = pPlayer->GetGroup()) + { + for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + Player* pMember = pRef->getSource(); + + if (pMember && m_creature->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE)) + { + bIsMaxRangeExceeded = false; + break; + } + } + } + else + { + if (m_creature->IsWithinDistInMap(pPlayer, MAX_PLAYER_DISTANCE)) + bIsMaxRangeExceeded = false; + } + } + + if (bIsMaxRangeExceeded) + { + debug_log("SD2: FollowerAI failed because player/group was to far away or not found"); + m_creature->ForcedDespawn(); + return; + } + + m_uiUpdateFollowTimer = 1000; + } + else + m_uiUpdateFollowTimer -= uiDiff; + } + + UpdateFollowerAI(uiDiff); +} + +void FollowerAI::UpdateFollowerAI(const uint32 uiDiff) +{ + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); +} + +void FollowerAI::MovementInform(uint32 uiMotionType, uint32 uiPointId) +{ + if (uiMotionType != POINT_MOTION_TYPE || !m_bIsFollowing) + return; + + if (uiPointId == POINT_COMBAT_START) + { + if (GetLeaderForFollower()) + m_bIsReturnToLeader = true; + else + m_creature->ForcedDespawn(); + } +} + +void FollowerAI::StartFollow(Player* pLeader, uint32 uiFactionForFollower, const Quest* pQuest) +{ + if (m_creature->getVictim()) + { + debug_log("SD2: FollowerAI attempt to StartFollow while in combat."); + return; + } + + if (m_bIsFollowing) + { + error_log("SD2: FollowerAI attempt to StartFollow while already following."); + return; + } + + //set variables + m_uiLeaderGUID = pLeader->GetGUID(); + + if (uiFactionForFollower) + m_creature->setFaction(uiFactionForFollower); + + m_pQuestForFollow = pQuest; + + if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + { + m_creature->GetMotionMaster()->Clear(); + m_creature->GetMotionMaster()->MoveIdle(); + debug_log("SD2: FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle."); + } + + m_creature->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + + m_creature->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + + m_bIsFollowing = true; + + debug_log("SD2: FollowerAI start follow %s (GUID %u)", pLeader->GetName(), m_uiLeaderGUID); +} + +Player* FollowerAI::GetLeaderForFollower() +{ + if (Player* pLeader = (Player*)Unit::GetUnit(*m_creature, m_uiLeaderGUID)) + { + if (pLeader->isAlive()) + return pLeader; + else + { + if (Group* pGroup = pLeader->GetGroup()) + { + for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + Player* pMember = pRef->getSource(); + + if (pMember && pMember->isAlive() && m_creature->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE)) + { + debug_log("SD2: FollowerAI GetLeader changed and returned new leader."); + m_uiLeaderGUID = pMember->GetGUID(); + return pMember; + break; + } + } + } + } + } + + debug_log("SD2: FollowerAI GetLeader can not find suitable leader."); + return NULL; +} diff --git a/src/bindings/scripts/base/follower_ai.h b/src/bindings/scripts/base/follower_ai.h new file mode 100644 index 00000000000..d62980d4951 --- /dev/null +++ b/src/bindings/scripts/base/follower_ai.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SC_FOLLOWERAI_H +#define SC_FOLLOWERAI_H + +class TRINITY_DLL_DECL FollowerAI : public ScriptedAI +{ + public: + explicit FollowerAI(Creature* pCreature); + ~FollowerAI() {} + + //virtual void WaypointReached(uint32 uiPointId) = 0; + + void MovementInform(uint32 uiMotionType, uint32 uiPointId); + + void AttackStart(Unit*); + + void MoveInLineOfSight(Unit*); + + void EnterEvadeMode(); + + void JustDied(Unit*); + + void JustRespawned(); + + void UpdateAI(const uint32); //the "internal" update, calls UpdateFollowerAI() + virtual void UpdateFollowerAI(const uint32); //used when it's needed to add code in update (abilities, scripted events, etc) + + void StartFollow(Player* pPlayer, uint32 uiFactionForFollower = 0, const Quest* pQuest = NULL); + + protected: + void SetFollowComplete() { m_bIsFollowComplete = true; } + bool IsFollowComplete() { return m_bIsFollowComplete; } + + Player* GetLeaderForFollower(); + + private: + uint64 m_uiLeaderGUID; + uint32 m_uiUpdateFollowTimer; + + bool m_bIsFollowing; + bool m_bIsReturnToLeader; + bool m_bIsFollowComplete; + + const Quest* m_pQuestForFollow; //normally we have a quest +}; + +#endif -- cgit v1.2.3