Scripts/FollowerAI: Some cleanup:

Port From (https://github.com/TrinityCore/TrinityCore/commit/8c3eb07889f72955879c8a0e0881b51befb60067)
This commit is contained in:
hondacrx
2022-01-02 13:06:01 -05:00
parent c4bc8c6362
commit 4c0eb3f2e2
5 changed files with 85 additions and 146 deletions
+1 -8
View File
@@ -493,6 +493,7 @@ namespace Game.AI
public virtual void SpellHitTarget(Unit target, SpellInfo spellInfo) { } public virtual void SpellHitTarget(Unit target, SpellInfo spellInfo) { }
public virtual void SpellHitTargetGameObject(GameObject target, SpellInfo spellInfo) { } public virtual void SpellHitTargetGameObject(GameObject target, SpellInfo spellInfo) { }
// Should return true if the NPC is currently being escorted
public virtual bool IsEscorted() { return false; } public virtual bool IsEscorted() { return false; }
public virtual void MovementInform(MovementGeneratorType type, uint id) { } public virtual void MovementInform(MovementGeneratorType type, uint id) { }
@@ -565,14 +566,6 @@ namespace Game.AI
// Object destruction is handled by Unit::RemoveCharmedBy // Object destruction is handled by Unit::RemoveCharmedBy
public virtual PlayerAI GetAIForCharmedPlayer(Player who) { return null; } public virtual PlayerAI GetAIForCharmedPlayer(Player who) { return null; }
/// <summary>
/// Should return true if the NPC is target of an escort quest
/// If onlyIfActive is set, should return true only if the escort quest is currently active
/// </summary>
/// <param name="onlyIfActive"></param>
/// <returns></returns>
public virtual bool IsEscortNPC(bool onlyIfActive) { return false; }
public List<AreaBoundary> GetBoundary() { return _boundary; } public List<AreaBoundary> GetBoundary() { return _boundary; }
public bool IsEngaged() { return _isEngaged; } public bool IsEngaged() { return _isEngaged; }
+3 -21
View File
@@ -382,15 +382,8 @@ namespace Game.AI
CreatureData cdata = me.GetCreatureData(); CreatureData cdata = me.GetCreatureData();
if (cdata != null) if (cdata != null)
{ {
SpawnGroupTemplateData groupdata = cdata.spawnGroupData; if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && cdata.spawnGroupData.flags.HasFlag(SpawnGroupFlags.EscortQuestNpc))
if (groupdata != null) me.SaveRespawnTime(me.GetRespawnDelay());
{
if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && groupdata.flags.HasAnyFlag(SpawnGroupFlags.EscortQuestNpc) && map.GetCreatureRespawnTime(me.GetSpawnId()) == 0)
{
me.SetRespawnTime(me.GetRespawnDelay());
me.SaveRespawnTime();
}
}
} }
} }
@@ -467,21 +460,10 @@ namespace Game.AI
} }
} }
public override bool IsEscortNPC(bool onlyIfActive)
{
if (!onlyIfActive)
return true;
if (!GetEventStarterGUID().IsEmpty())
return true;
return false;
}
void SetPauseTimer(uint Timer) { _pauseTimer = Timer; } void SetPauseTimer(uint Timer) { _pauseTimer = Timer; }
public bool HasEscortState(EscortState escortState) { return (_escortState & escortState) != 0; } public bool HasEscortState(EscortState escortState) { return (_escortState & escortState) != 0; }
public override bool IsEscorted() { return _escortState.HasAnyFlag(EscortState.Escorting); } public override bool IsEscorted() { return !_playerGUID.IsEmpty(); }
void SetMaxPlayerDistance(float newMax) { _maxPlayerDistance = newMax; } void SetMaxPlayerDistance(float newMax) { _maxPlayerDistance = newMax; }
float GetMaxPlayerDistance() { return _maxPlayerDistance; } float GetMaxPlayerDistance() { return _maxPlayerDistance; }
+73 -111
View File
@@ -18,19 +18,19 @@
using Framework.Constants; using Framework.Constants;
using Game.Entities; using Game.Entities;
using Game.Groups; using Game.Groups;
using Game.Maps;
using System; using System;
namespace Game.AI namespace Game.AI
{ {
enum FollowState enum FollowState
{ {
None = 0x000, None = 0x00,
Inprogress = 0x001, //must always have this state for any follow Inprogress = 0x01, //must always have this state for any follow
Returning = 0x002, //when returning to combat start after being in combat Paused = 0x02, //disables following
Paused = 0x004, //disables following Complete = 0x04, //follow is completed and may end
Complete = 0x008, //follow is completed and may end PreEvent = 0x08, //not implemented (allow pre event to run, before follow is initiated)
PreEvent = 0x010, //not implemented (allow pre event to run, before follow is initiated) PostEvent = 0x10 //can be set at complete and allow post event to run
PostEvent = 0x020 //can be set at complete and allow post event to run
} }
class FollowerAI : ScriptedAI class FollowerAI : ScriptedAI
@@ -38,45 +38,17 @@ namespace Game.AI
ObjectGuid _leaderGUID; ObjectGuid _leaderGUID;
uint _updateFollowTimer; uint _updateFollowTimer;
FollowState _followState; FollowState _followState;
uint _questForFollow;
Quest _questForFollow; //normally we have a quest
public FollowerAI(Creature creature) : base(creature) public FollowerAI(Creature creature) : base(creature)
{ {
_updateFollowTimer = 2500; _updateFollowTimer = 2500;
_followState = FollowState.None; _followState = FollowState.None;
_questForFollow = null;
}
public override void MovementInform(MovementGeneratorType motionType, uint pointId)
{
if (motionType != MovementGeneratorType.Point || !HasFollowState(FollowState.Inprogress))
return;
if (pointId == 0xFFFFFF)
{
if (GetLeaderForFollower())
{
if (!HasFollowState(FollowState.Paused))
AddFollowState(FollowState.Returning);
}
else
me.DespawnOrUnsummon();
}
}
public override void AttackStart(Unit who)
{
base.AttackStart(who);
} }
public override void MoveInLineOfSight(Unit who) public override void MoveInLineOfSight(Unit who)
{ {
// TODO: what in the world is this? if (HasFollowState(FollowState.Inprogress) && !ShouldAssistPlayerInCombatAgainst(who))
if (me.HasReactState(ReactStates.Aggressive) && !me.HasUnitState(UnitState.Stunned) && who.IsTargetableForAttack() && who.IsInAccessiblePlaceFor(me))
return;
if (HasFollowState(FollowState.Inprogress) && AssistPlayerInCombatAgainst(who))
return; return;
base.MoveInLineOfSight(who); base.MoveInLineOfSight(who);
@@ -84,7 +56,7 @@ namespace Game.AI
public override void JustDied(Unit killer) public override void JustDied(Unit killer)
{ {
if (!HasFollowState(FollowState.Inprogress) || _leaderGUID.IsEmpty() || _questForFollow == null) if (!HasFollowState(FollowState.Inprogress) || _leaderGUID.IsEmpty() || _questForFollow == 0)
return; return;
// @todo need a better check for quests with time limit. // @todo need a better check for quests with time limit.
@@ -99,59 +71,39 @@ namespace Game.AI
Player member = groupRef.GetSource(); Player member = groupRef.GetSource();
if (member) if (member)
if (member.IsInMap(player)) if (member.IsInMap(player))
member.FailQuest(_questForFollow.Id); member.FailQuest(_questForFollow);
} }
} }
else else
player.FailQuest(_questForFollow.Id); player.FailQuest(_questForFollow);
} }
} }
public override void JustAppeared() public override void JustReachedHome()
{ {
_followState = FollowState.None; if (!HasFollowState(FollowState.Inprogress))
if (!IsCombatMovementAllowed())
SetCombatMovement(true);
if (me.GetFaction() != me.GetCreatureTemplate().Faction)
me.SetFaction(me.GetCreatureTemplate().Faction);
Reset();
}
public override void EnterEvadeMode(EvadeReason why)
{
if (!me.IsAlive())
{
EngagementOver();
return; return;
}
me.RemoveAllAuras(); Player player = GetLeaderForFollower();
me.CombatStop(true); if (player != null)
me.SetLootRecipient(null);
me.SetCannotReachTarget(false);
me.DoNotReacquireTarget();
EngagementOver();
if (HasFollowState(FollowState.Inprogress))
{ {
Log.outDebug(LogFilter.Scripts, "FollowerAI left combat, returning to CombatStartPosition."); if (HasFollowState(FollowState.Paused))
return;
if (me.HasUnitState(UnitState.Chase)) me.GetMotionMaster().MoveFollow(player, SharedConst.PetFollowDist, SharedConst.PetFollowAngle);
me.GetMotionMaster().Remove(MovementGeneratorType.Chase);
} }
else else
me.GetMotionMaster().MoveTargetedHome(); me.DespawnOrUnsummon();
}
Reset(); public override void OwnerAttackedBy(Unit attacker)
{
if (!me.HasReactState(ReactStates.Passive) && ShouldAssistPlayerInCombatAgainst(attacker))
me.EngageWithTarget(attacker);
} }
public override void UpdateAI(uint uiDiff) public override void UpdateAI(uint uiDiff)
{ {
if (HasFollowState(FollowState.Inprogress) && !me.GetVictim()) if (HasFollowState(FollowState.Inprogress) && !me.IsEngaged())
{ {
if (_updateFollowTimer <= uiDiff) if (_updateFollowTimer <= uiDiff)
{ {
@@ -162,43 +114,49 @@ namespace Game.AI
return; return;
} }
bool bIsMaxRangeExceeded = true; bool maxRangeExceeded = true;
bool questAbandoned = (_questForFollow != 0);
Player player = GetLeaderForFollower(); Player player = GetLeaderForFollower();
if (player) if (player)
{ {
if (HasFollowState(FollowState.Returning))
{
Log.outDebug(LogFilter.Scripts, "FollowerAI is returning to leader.");
RemoveFollowState(FollowState.Returning);
me.GetMotionMaster().MoveFollow(player, SharedConst.PetFollowDist, SharedConst.PetFollowAngle);
return;
}
Group group = player.GetGroup(); Group group = player.GetGroup();
if (group) if (group)
{ {
for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.Next()) for (GroupReference groupRef = group.GetFirstMember(); groupRef != null && (maxRangeExceeded || questAbandoned); groupRef = groupRef.Next())
{ {
Player member = groupRef.GetSource(); Player member = groupRef.GetSource();
if (member && me.IsWithinDistInMap(member, 100.0f)) if (member == null)
continue;
if (maxRangeExceeded && me.IsWithinDistInMap(member, 100.0f))
maxRangeExceeded = false;
if (questAbandoned)
{ {
bIsMaxRangeExceeded = false; QuestStatus status = member.GetQuestStatus(_questForFollow);
break; if ((status == QuestStatus.Complete) || (status == QuestStatus.Incomplete))
questAbandoned = false;
} }
} }
} }
else else
{ {
if (me.IsWithinDistInMap(player, 100.0f)) if (me.IsWithinDistInMap(player, 100.0f))
bIsMaxRangeExceeded = false; maxRangeExceeded = false;
if (questAbandoned)
{
QuestStatus status = player.GetQuestStatus(_questForFollow);
if ((status == QuestStatus.Complete) || (status == QuestStatus.Incomplete))
questAbandoned = false;
}
} }
} }
if (bIsMaxRangeExceeded) if (maxRangeExceeded || questAbandoned)
{ {
Log.outDebug(LogFilter.Scripts, "FollowerAI failed because player/group was to far away or not found"); Log.outDebug(LogFilter.Scripts, $"FollowerAI::UpdateAI: failed because player/group was to far away or not found ({me.GetGUID()})");
me.DespawnOrUnsummon(); me.DespawnOrUnsummon();
return; return;
} }
@@ -222,15 +180,26 @@ namespace Game.AI
public void StartFollow(Player player, uint factionForFollower = 0, Quest quest = null) public void StartFollow(Player player, uint factionForFollower = 0, Quest quest = null)
{ {
if (me.GetVictim()) Map map = me.GetMap();
if (map != null)
{ {
Log.outDebug(LogFilter.Scripts, "FollowerAI attempt to StartFollow while in combat."); CreatureData cdata = me.GetCreatureData();
if (cdata != null)
{
if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && cdata.spawnGroupData.flags.HasFlag(SpawnGroupFlags.EscortQuestNpc))
me.SaveRespawnTime(me.GetRespawnDelay());
}
}
if (me.IsEngaged())
{
Log.outDebug(LogFilter.Scripts, $"FollowerAI::StartFollow: attempt to StartFollow while in combat. ({me.GetGUID()})");
return; return;
} }
if (HasFollowState(FollowState.Inprogress)) if (HasFollowState(FollowState.Inprogress))
{ {
Log.outError(LogFilter.Scenario, "FollowerAI attempt to StartFollow while already following."); Log.outError(LogFilter.Scenario, $"FollowerAI::StartFollow: attempt to StartFollow while already following. ({me.GetGUID()})");
return; return;
} }
@@ -240,7 +209,7 @@ namespace Game.AI
if (factionForFollower != 0) if (factionForFollower != 0)
me.SetFaction(factionForFollower); me.SetFaction(factionForFollower);
_questForFollow = quest; _questForFollow = quest.Id;
me.GetMotionMaster().Clear(MovementGeneratorPriority.Normal); me.GetMotionMaster().Clear(MovementGeneratorPriority.Normal);
me.PauseMovement(); me.PauseMovement();
@@ -252,7 +221,7 @@ namespace Game.AI
me.GetMotionMaster().MoveFollow(player, SharedConst.PetFollowDist, SharedConst.PetFollowAngle); me.GetMotionMaster().MoveFollow(player, SharedConst.PetFollowDist, SharedConst.PetFollowAngle);
Log.outDebug(LogFilter.Scripts, "FollowerAI start follow {0} ({1})", player.GetName(), _leaderGUID.ToString()); Log.outDebug(LogFilter.Scripts, $"FollowerAI::StartFollow: start follow {player.GetName()} - {_leaderGUID} ({me.GetGUID()})");
} }
public void SetFollowPaused(bool paused) public void SetFollowPaused(bool paused)
@@ -310,7 +279,7 @@ namespace Game.AI
Player member = groupRef.GetSource(); Player member = groupRef.GetSource();
if (member && me.IsWithinDistInMap(member, 100.0f) && member.IsAlive()) if (member && me.IsWithinDistInMap(member, 100.0f) && member.IsAlive())
{ {
Log.outDebug(LogFilter.Scripts, "FollowerAI GetLeader changed and returned new leader."); Log.outDebug(LogFilter.Scripts, $"FollowerAI::GetLeaderForFollower: GetLeader changed and returned new leader. ({me.GetGUID()})");
_leaderGUID = member.GetGUID(); _leaderGUID = member.GetGUID();
return member; return member;
} }
@@ -319,29 +288,22 @@ namespace Game.AI
} }
} }
Log.outDebug(LogFilter.Scripts, "FollowerAI GetLeader can not find suitable leader."); Log.outDebug(LogFilter.Scripts, $"FollowerAI::GetLeaderForFollower: GetLeader can not find suitable leader. ({me.GetGUID()})");
return null; return null;
} }
//This part provides assistance to a player that are attacked by who, even if out of normal aggro range //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) //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. //The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate.
bool AssistPlayerInCombatAgainst(Unit who) bool ShouldAssistPlayerInCombatAgainst(Unit who)
{ {
if (!who || !who.GetVictim()) if (!who || !who.GetVictim())
return false; return false;
if (me.HasReactState(ReactStates.Passive))
return false;
//experimental (unknown) flag not present //experimental (unknown) flag not present
if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist)) if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist))
return false; return false;
//not a player
if (!who.GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself())
return false;
if (!who.IsInAccessiblePlaceFor(me)) if (!who.IsInAccessiblePlaceFor(me))
return false; return false;
@@ -361,18 +323,18 @@ namespace Game.AI
return false; return false;
//too far away and no free sight? //too far away and no free sight?
if (me.IsWithinDistInMap(who, 100.0f) && me.IsWithinLOSInMap(who)) if (!me.IsWithinDistInMap(who, 100.0f) || !me.IsWithinLOSInMap(who))
{ return false;
me.EngageWithTarget(who);
return true; return true;
} }
return false; public override bool IsEscorted() { return HasFollowState(FollowState.Inprogress); }
}
bool HasFollowState(FollowState uiFollowState) { return (_followState & uiFollowState) != 0; } bool HasFollowState(FollowState uiFollowState) { return (_followState & uiFollowState) != 0; }
void AddFollowState(FollowState uiFollowState) { _followState |= uiFollowState; } void AddFollowState(FollowState uiFollowState) { _followState |= uiFollowState; }
void RemoveFollowState(FollowState uiFollowState) { _followState &= ~uiFollowState; } void RemoveFollowState(FollowState uiFollowState) { _followState &= ~uiFollowState; }
} }
} }
+5 -3
View File
@@ -1100,11 +1100,11 @@ namespace Game.Entities
} }
} }
public bool IsEscortNPC(bool onlyIfActive = true) public bool IsEscorted()
{ {
CreatureAI ai = GetAI(); CreatureAI ai = GetAI();
if (ai != null) if (ai != null)
return ai.IsEscortNPC(onlyIfActive); return ai.IsEscorted();
return false; return false;
} }
@@ -2302,7 +2302,9 @@ namespace Game.Entities
if (!victim.IsInAccessiblePlaceFor(this)) if (!victim.IsInAccessiblePlaceFor(this))
return false; return false;
if (IsAIEnabled() && !GetAI().CanAIAttack(victim)) CreatureAI ai = GetAI();
if (ai != null)
if (!ai.CanAIAttack(victim))
return false; return false;
// we cannot attack in evade mode // we cannot attack in evade mode
+1 -1
View File
@@ -2348,7 +2348,7 @@ namespace Game.Maps
continue; continue;
// escort NPCs are allowed to respawn as long as all other instances are already escorting // escort NPCs are allowed to respawn as long as all other instances are already escorting
if (isEscort && creature.IsEscortNPC(true)) if (isEscort && creature.IsEscorted())
continue; continue;
doDelete = true; doDelete = true;