mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-25 19:31:59 +01:00
Scripts/FollowerAI: Some cleanup:
- FollowerAI properly resumes follow after evading. - Removed duplicated getters from CreatureAI (IsEscorted vs IsEscortNPC), they were used to do the same thing - FollowerAI properly assists in combat. - FollowerAI properly despawns if quest is abandoned. - FollowerAI now supports dynamic respawning for escort NPCs.
This commit is contained in:
@@ -283,14 +283,8 @@ void EscortAI::Start(bool isActiveAttacker /* = true*/, bool run /* = false */,
|
||||
{
|
||||
if (CreatureData const* cdata = me->GetCreatureData())
|
||||
{
|
||||
if (SpawnGroupTemplateData const* groupdata = cdata->spawnGroupData)
|
||||
{
|
||||
if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (groupdata->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC) && !map->GetCreatureRespawnTime(me->GetSpawnId()))
|
||||
{
|
||||
me->SetRespawnTime(me->GetRespawnDelay());
|
||||
me->SaveRespawnTime();
|
||||
}
|
||||
}
|
||||
if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (cdata->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC))
|
||||
me->SaveRespawnTime(me->GetRespawnDelay());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,17 +372,6 @@ void EscortAI::SetEscortPaused(bool on)
|
||||
}
|
||||
}
|
||||
|
||||
bool EscortAI::IsEscortNPC(bool onlyIfActive) const
|
||||
{
|
||||
if (!onlyIfActive)
|
||||
return true;
|
||||
|
||||
if (GetEventStarterGUID())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Player* EscortAI::GetPlayerForEscort()
|
||||
{
|
||||
return ObjectAccessor::GetPlayer(*me, _playerGUID);
|
||||
|
||||
@@ -56,7 +56,7 @@ struct TC_GAME_API EscortAI : public ScriptedAI
|
||||
void SetEscortPaused(bool on);
|
||||
void SetPauseTimer(uint32 Timer) { _pauseTimer = Timer; }
|
||||
bool HasEscortState(uint32 escortState) { return (_escortState & escortState) != 0; }
|
||||
virtual bool IsEscorted() const override { return (_escortState & STATE_ESCORT_ESCORTING); }
|
||||
bool IsEscorted() const override { return !_playerGUID.IsEmpty(); }
|
||||
void SetMaxPlayerDistance(float newMax) { _maxPlayerDistance = newMax; }
|
||||
float GetMaxPlayerDistance() const { return _maxPlayerDistance; }
|
||||
void SetDespawnAtEnd(bool despawn) { _despawnAtEnd = despawn; }
|
||||
@@ -64,7 +64,6 @@ struct TC_GAME_API EscortAI : public ScriptedAI
|
||||
bool IsActiveAttacker() const { return _activeAttacker; } // obsolete
|
||||
void SetActiveAttacker(bool enable) { _activeAttacker = enable; }
|
||||
ObjectGuid GetEventStarterGUID() const { return _playerGUID; }
|
||||
virtual bool IsEscortNPC(bool isEscorting) const override;
|
||||
|
||||
protected:
|
||||
Player* GetPlayerForEscort();
|
||||
|
||||
@@ -32,37 +32,11 @@ enum Points
|
||||
POINT_COMBAT_START = 0xFFFFFF
|
||||
};
|
||||
|
||||
FollowerAI::FollowerAI(Creature* creature) : ScriptedAI(creature), _updateFollowTimer(2500), _followState(STATE_FOLLOW_NONE), _questForFollow(nullptr) { }
|
||||
|
||||
void FollowerAI::MovementInform(uint32 type, uint32 id)
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE || !HasFollowState(STATE_FOLLOW_INPROGRESS))
|
||||
return;
|
||||
|
||||
if (id == POINT_COMBAT_START)
|
||||
{
|
||||
if (GetLeaderForFollower())
|
||||
{
|
||||
if (!HasFollowState(STATE_FOLLOW_PAUSED))
|
||||
AddFollowState(STATE_FOLLOW_RETURNING);
|
||||
}
|
||||
else
|
||||
me->DespawnOrUnsummon();
|
||||
}
|
||||
}
|
||||
|
||||
void FollowerAI::AttackStart(Unit* who)
|
||||
{
|
||||
ScriptedAI::AttackStart(who);
|
||||
}
|
||||
FollowerAI::FollowerAI(Creature* creature) : ScriptedAI(creature), _updateFollowTimer(2500), _followState(STATE_FOLLOW_NONE), _questForFollow(0) { }
|
||||
|
||||
void FollowerAI::MoveInLineOfSight(Unit* who)
|
||||
{
|
||||
// TODO: what in the world is this?
|
||||
if (me->HasReactState(REACT_AGGRESSIVE) && !me->HasUnitState(UNIT_STATE_STUNNED) && who->isTargetableForAttack() && who->isInAccessiblePlaceFor(me))
|
||||
return;
|
||||
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS) && AssistPlayerInCombatAgainst(who))
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS) && !ShouldAssistPlayerInCombatAgainst(who))
|
||||
return;
|
||||
|
||||
ScriptedAI::MoveInLineOfSight(who);
|
||||
@@ -81,53 +55,32 @@ void FollowerAI::JustDied(Unit* /*killer*/)
|
||||
for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next())
|
||||
if (Player* member = groupRef->GetSource())
|
||||
if (member->IsInMap(player))
|
||||
member->FailQuest(_questForFollow->GetQuestId());
|
||||
member->FailQuest(_questForFollow);
|
||||
}
|
||||
else
|
||||
player->FailQuest(_questForFollow->GetQuestId());
|
||||
player->FailQuest(_questForFollow);
|
||||
}
|
||||
}
|
||||
|
||||
void FollowerAI::JustAppeared()
|
||||
void FollowerAI::JustReachedHome()
|
||||
{
|
||||
_followState = STATE_FOLLOW_NONE;
|
||||
|
||||
if (!IsCombatMovementAllowed())
|
||||
SetCombatMovement(true);
|
||||
|
||||
if (me->GetFaction() != me->GetCreatureTemplate()->faction)
|
||||
me->SetFaction(me->GetCreatureTemplate()->faction);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void FollowerAI::EnterEvadeMode(EvadeReason /*why*/)
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
EngagementOver();
|
||||
if (!HasFollowState(STATE_FOLLOW_INPROGRESS))
|
||||
return;
|
||||
}
|
||||
|
||||
me->RemoveAllAuras();
|
||||
me->CombatStop(true);
|
||||
me->SetLootRecipient(nullptr);
|
||||
me->SetCannotReachTarget(false);
|
||||
me->DoNotReacquireTarget();
|
||||
|
||||
EngagementOver();
|
||||
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS))
|
||||
if (Player* player = GetLeaderForFollower())
|
||||
{
|
||||
TC_LOG_DEBUG("scripts.ai.followerai", "FollowerAI::EnterEvadeMode: left combat, returning to CombatStartPosition. (%s)", me->GetGUID().ToString().c_str());
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CHASE))
|
||||
me->GetMotionMaster()->Remove(CHASE_MOTION_TYPE);
|
||||
if (HasFollowState(STATE_FOLLOW_PAUSED))
|
||||
return;
|
||||
me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
||||
}
|
||||
else
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
me->DespawnOrUnsummon();
|
||||
}
|
||||
|
||||
Reset();
|
||||
void FollowerAI::OwnerAttackedBy(Unit* other)
|
||||
{
|
||||
if (!me->HasReactState(REACT_PASSIVE) && ShouldAssistPlayerInCombatAgainst(other))
|
||||
me->EngageWithTarget(other);
|
||||
}
|
||||
|
||||
void FollowerAI::UpdateAI(uint32 uiDiff)
|
||||
@@ -144,27 +97,23 @@ void FollowerAI::UpdateAI(uint32 uiDiff)
|
||||
}
|
||||
|
||||
bool maxRangeExceeded = true;
|
||||
|
||||
bool questAbandoned = (_questForFollow != 0);
|
||||
if (Player* player = GetLeaderForFollower())
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_RETURNING))
|
||||
{
|
||||
TC_LOG_DEBUG("scripts.ai.followerai", "FollowerAI::UpdateAI: is returning to leader. (%s)", me->GetGUID().ToString().c_str());
|
||||
|
||||
RemoveFollowState(STATE_FOLLOW_RETURNING);
|
||||
me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next())
|
||||
for (GroupReference* groupRef = group->GetFirstMember(); groupRef && (maxRangeExceeded || questAbandoned); groupRef = groupRef->next())
|
||||
{
|
||||
Player* member = groupRef->GetSource();
|
||||
if (member && me->IsWithinDistInMap(member, MAX_PLAYER_DISTANCE))
|
||||
{
|
||||
if (!member)
|
||||
continue;
|
||||
if (maxRangeExceeded && me->IsWithinDistInMap(member, MAX_PLAYER_DISTANCE))
|
||||
maxRangeExceeded = false;
|
||||
break;
|
||||
if (questAbandoned)
|
||||
{
|
||||
QuestStatus status = member->GetQuestStatus(_questForFollow);
|
||||
if ((status == QUEST_STATUS_COMPLETE) || (status == QUEST_STATUS_INCOMPLETE))
|
||||
questAbandoned = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,10 +121,16 @@ void FollowerAI::UpdateAI(uint32 uiDiff)
|
||||
{
|
||||
if (me->IsWithinDistInMap(player, MAX_PLAYER_DISTANCE))
|
||||
maxRangeExceeded = false;
|
||||
if (questAbandoned)
|
||||
{
|
||||
QuestStatus status = player->GetQuestStatus(_questForFollow);
|
||||
if ((status == QUEST_STATUS_COMPLETE) || (status == QUEST_STATUS_INCOMPLETE))
|
||||
questAbandoned = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (maxRangeExceeded)
|
||||
if (maxRangeExceeded || questAbandoned)
|
||||
{
|
||||
TC_LOG_DEBUG("scripts.ai.followerai", "FollowerAI::UpdateAI: failed because player/group was to far away or not found (%s)", me->GetGUID().ToString().c_str());
|
||||
me->DespawnOrUnsummon();
|
||||
@@ -199,8 +154,17 @@ void FollowerAI::UpdateFollowerAI(uint32 /*uiDiff*/)
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
void FollowerAI::StartFollow(Player* player, uint32 factionForFollower, Quest const* quest)
|
||||
void FollowerAI::StartFollow(Player* player, uint32 factionForFollower, uint32 quest)
|
||||
{
|
||||
if (Map* map = me->GetMap())
|
||||
{
|
||||
if (CreatureData const* cdata = me->GetCreatureData())
|
||||
{
|
||||
if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (cdata->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC))
|
||||
me->SaveRespawnTime(me->GetRespawnDelay());
|
||||
}
|
||||
}
|
||||
|
||||
if (me->IsEngaged())
|
||||
{
|
||||
TC_LOG_DEBUG("scripts.ai.followerai", "FollowerAI::StartFollow: attempt to StartFollow while in combat. (%s)", me->GetGUID().ToString().c_str());
|
||||
@@ -301,22 +265,15 @@ Player* FollowerAI::GetLeaderForFollower()
|
||||
// This part provides assistance to a player that are attacked by who, even if out of normal aggro range
|
||||
// It will cause me to attack who 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.
|
||||
bool FollowerAI::AssistPlayerInCombatAgainst(Unit* who)
|
||||
bool FollowerAI::ShouldAssistPlayerInCombatAgainst(Unit* who) const
|
||||
{
|
||||
if (!who || !who->GetVictim())
|
||||
return false;
|
||||
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return false;
|
||||
|
||||
// experimental (unknown) flag not present
|
||||
if (!(me->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_ASSIST))
|
||||
return false;
|
||||
|
||||
// not a player
|
||||
if (!who->EnsureVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
return false;
|
||||
|
||||
if (!who->isInAccessiblePlaceFor(me))
|
||||
return false;
|
||||
|
||||
@@ -336,11 +293,8 @@ bool FollowerAI::AssistPlayerInCombatAgainst(Unit* who)
|
||||
return false;
|
||||
|
||||
// too far away and no free sight
|
||||
if (me->IsWithinDistInMap(who, MAX_PLAYER_DISTANCE) && me->IsWithinLOSInMap(who))
|
||||
{
|
||||
me->EngageWithTarget(who);
|
||||
return true;
|
||||
}
|
||||
if (!me->IsWithinDistInMap(who, MAX_PLAYER_DISTANCE) || !me->IsWithinLOSInMap(who))
|
||||
return false;
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,11 +27,10 @@ enum FollowerState : uint32
|
||||
{
|
||||
STATE_FOLLOW_NONE = 0x000,
|
||||
STATE_FOLLOW_INPROGRESS = 0x001, // must always have this state for any follow
|
||||
STATE_FOLLOW_RETURNING = 0x002, // when returning to combat start after being in combat
|
||||
STATE_FOLLOW_PAUSED = 0x004, // disables following
|
||||
STATE_FOLLOW_COMPLETE = 0x008, // follow is completed and may end
|
||||
STATE_FOLLOW_PREEVENT = 0x010, // not implemented (allow pre event to run, before follow is initiated)
|
||||
STATE_FOLLOW_POSTEVENT = 0x020 // can be set at complete and allow post event to run
|
||||
STATE_FOLLOW_PAUSED = 0x002, // disables following
|
||||
STATE_FOLLOW_COMPLETE = 0x004, // follow is completed and may end
|
||||
STATE_FOLLOW_PREEVENT = 0x008, // not implemented (allow pre event to run, before follow is initiated)
|
||||
STATE_FOLLOW_POSTEVENT = 0x010 // can be set at complete and allow post event to run
|
||||
};
|
||||
|
||||
class TC_GAME_API FollowerAI : public ScriptedAI
|
||||
@@ -40,24 +39,23 @@ class TC_GAME_API FollowerAI : public ScriptedAI
|
||||
explicit FollowerAI(Creature* creature);
|
||||
~FollowerAI() { }
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override;
|
||||
void AttackStart(Unit*) override;
|
||||
void MoveInLineOfSight(Unit*) override;
|
||||
void EnterEvadeMode(EvadeReason /*why*/ = EVADE_REASON_OTHER) override;
|
||||
void JustDied(Unit*) override;
|
||||
void JustAppeared() override;
|
||||
void JustReachedHome() override;
|
||||
void OwnerAttackedBy(Unit* other) override;
|
||||
|
||||
// the "internal" update, calls UpdateFollowerAI()
|
||||
void UpdateAI(uint32) override;
|
||||
|
||||
// used when it's needed to add code in update (abilities, scripted events, etc)
|
||||
virtual void UpdateFollowerAI(uint32);
|
||||
|
||||
void StartFollow(Player* player, uint32 factionForFollower = 0, Quest const* quest = nullptr);
|
||||
void StartFollow(Player* player, uint32 factionForFollower = 0, uint32 quest = 0);
|
||||
// if special event require follow mode to hold/resume during the follow
|
||||
void SetFollowPaused(bool paused);
|
||||
void SetFollowComplete(bool withEndEvent = false);
|
||||
|
||||
bool HasFollowState(uint32 uiFollowState) { return (_followState & uiFollowState) != 0; }
|
||||
bool IsEscorted() const override { return HasFollowState(STATE_FOLLOW_INPROGRESS); }
|
||||
bool HasFollowState(uint32 uiFollowState) const { return (_followState & uiFollowState) != 0; }
|
||||
|
||||
protected:
|
||||
Player* GetLeaderForFollower();
|
||||
@@ -65,13 +63,12 @@ class TC_GAME_API FollowerAI : public ScriptedAI
|
||||
private:
|
||||
void AddFollowState(uint32 followState) { _followState |= followState; }
|
||||
void RemoveFollowState(uint32 followState) { _followState &= ~followState; }
|
||||
bool AssistPlayerInCombatAgainst(Unit* who);
|
||||
bool ShouldAssistPlayerInCombatAgainst(Unit* who) const;
|
||||
|
||||
ObjectGuid _leaderGUID;
|
||||
uint32 _updateFollowTimer;
|
||||
uint32 _followState;
|
||||
|
||||
Quest const* _questForFollow;
|
||||
uint32 _questForFollow;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user