Core/AI: Finally move the "is creature engaged" flag to be a property of the creature AI, where it honestly always belonged.

Port From (https://github.com/TrinityCore/TrinityCore/commit/c13d83796f7b2111c5dcf8546bdd84eccd232ae3)
This commit is contained in:
hondacrx
2022-01-01 17:05:29 -05:00
parent 9b3fe829d6
commit 2842cc9c91
13 changed files with 142 additions and 71 deletions
+55 -5
View File
@@ -29,6 +29,7 @@ namespace Game.AI
{ {
public class CreatureAI : UnitAI public class CreatureAI : UnitAI
{ {
bool _isEngaged;
bool _moveInLOSLocked; bool _moveInLOSLocked;
List<AreaBoundary> _boundary = new(); List<AreaBoundary> _boundary = new();
bool _negateBoundary; bool _negateBoundary;
@@ -201,6 +202,12 @@ namespace Game.AI
} }
} }
public override void JustEnteredCombat(Unit who)
{
if (!IsEngaged() && !me.CanHaveThreatList())
EngagementStart(who);
}
// Called for reaction at stopping attack at no attackers or targets // Called for reaction at stopping attack at no attackers or targets
public virtual void EnterEvadeMode(EvadeReason why = EvadeReason.Other) public virtual void EnterEvadeMode(EvadeReason why = EvadeReason.Other)
{ {
@@ -231,7 +238,7 @@ namespace Game.AI
public bool UpdateVictim() public bool UpdateVictim()
{ {
if (!me.IsEngaged()) if (!IsEngaged())
return false; return false;
if (!me.HasReactState(ReactStates.Passive)) if (!me.HasReactState(ReactStates.Passive))
@@ -254,23 +261,53 @@ namespace Game.AI
return true; return true;
} }
public void EngagementStart(Unit who)
{
if (_isEngaged)
{
//Log.outError(LogFilter.ScriptsAi, $"CreatureAI::EngagementStart called even though creature is already engaged. Creature debug info:\n{me.GetDebugInfo()}");
return;
}
_isEngaged = true;
me.AtEngage(who);
}
public void EngagementOver()
{
if (!_isEngaged)
{
//Log.outError(LogFilter.ScriptsAi, $"CreatureAI::EngagementOver called even though creature is not currently engaged. Creature debug info:\n{me.GetDebugInfo()}");
return;
}
_isEngaged = false;
me.AtDisengage();
}
public bool _EnterEvadeMode(EvadeReason why = EvadeReason.Other) public bool _EnterEvadeMode(EvadeReason why = EvadeReason.Other)
{ {
if (!me.IsAlive()) if (me.IsInEvadeMode())
return false; return false;
if (!me.IsAlive())
{
EngagementOver();
return false;
}
me.RemoveAurasOnEvade(); me.RemoveAurasOnEvade();
// sometimes bosses stuck in combat? // sometimes bosses stuck in combat?
me.CombatStop(true); me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged();
me.SetLootRecipient(null); me.SetLootRecipient(null);
me.ResetPlayerDamageReq(); me.ResetPlayerDamageReq();
me.SetLastDamagedTime(0); me.SetLastDamagedTime(0);
me.SetCannotReachTarget(false); me.SetCannotReachTarget(false);
me.DoNotReacquireTarget(); me.DoNotReacquireTarget();
EngagementOver();
return !me.IsInEvadeMode(); return true;
} }
public CypherStrings VisualizeBoundary(int duration, Unit owner = null, bool fill = false) public CypherStrings VisualizeBoundary(int duration, Unit owner = null, bool fill = false)
@@ -404,11 +441,22 @@ namespace Game.AI
me.DoImmediateBoundaryCheck(); me.DoImmediateBoundaryCheck();
} }
// Called for reaction whenever a new non-offline unit is added to the threat list
public virtual void JustStartedThreateningMe(Unit who)
{
if (!IsEngaged())
EngagementStart(who);
}
// Called for reaction when initially engaged - this will always happen _after_ JustEnteredCombat // Called for reaction when initially engaged - this will always happen _after_ JustEnteredCombat
public virtual void JustEngagedWith(Unit who) { } public virtual void JustEngagedWith(Unit who) { }
// Called when the creature is killed // Called when the creature is killed
public virtual void JustDied(Unit killer) { } public virtual void JustDied(Unit killer)
{
if (IsEngaged())
EngagementOver();
}
// Called when the creature kills a unit // Called when the creature kills a unit
public virtual void KilledUnit(Unit victim) { } public virtual void KilledUnit(Unit victim) { }
@@ -521,6 +569,8 @@ namespace Game.AI
public virtual bool IsEscortNPC(bool onlyIfActive) { return false; } 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 class AISpellInfoType public class AISpellInfoType
+2 -2
View File
@@ -48,7 +48,7 @@ namespace Game.AI
{ {
me.GetMotionMaster().MoveIdle(); me.GetMotionMaster().MoveIdle();
me.CombatStop(true); me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged(); EngagementOver();
return; return;
} }
@@ -56,7 +56,7 @@ namespace Game.AI
me.RemoveAllAuras(); me.RemoveAllAuras();
me.CombatStop(true); me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged(); EngagementOver();
me.GetMotionMaster().MoveTargetedHome(); me.GetMotionMaster().MoveTargetedHome();
} }
+17
View File
@@ -77,6 +77,21 @@ namespace Game.AI
public override void MoveInLineOfSight(Unit who) { } public override void MoveInLineOfSight(Unit who) { }
public override void JustEnteredCombat(Unit who)
{
EngagementStart(who);
}
public override void JustExitedCombat()
{
EngagementOver();
}
public override void JustStartedThreateningMe(Unit who)
{
}
public override void EnterEvadeMode(EvadeReason why) { } public override void EnterEvadeMode(EvadeReason why) { }
} }
@@ -89,6 +104,8 @@ namespace Game.AI
public override void MoveInLineOfSight(Unit unit) { } public override void MoveInLineOfSight(Unit unit) { }
public override void AttackStart(Unit unit) { } public override void AttackStart(Unit unit) { }
public override void JustStartedThreateningMe(Unit unit) { }
public override void JustEnteredCombat(Unit who) { }
public override void UpdateAI(uint diff) { } public override void UpdateAI(uint diff) { }
public override void JustAppeared() { } public override void JustAppeared() { }
public override void EnterEvadeMode(EvadeReason why) { } public override void EnterEvadeMode(EvadeReason why) { }
+1 -10
View File
@@ -21,7 +21,7 @@ using Game.Maps;
namespace Game.AI namespace Game.AI
{ {
public class TotemAI : CreatureAI public class TotemAI : NullCreatureAI
{ {
ObjectGuid _victimGuid; ObjectGuid _victimGuid;
@@ -30,15 +30,6 @@ namespace Game.AI
_victimGuid = ObjectGuid.Empty; _victimGuid = ObjectGuid.Empty;
} }
public override void MoveInLineOfSight(Unit who) { }
public override void JustAppeared() { }
public override void EnterEvadeMode(EvadeReason why)
{
me.CombatStop(true);
}
public override void UpdateAI(uint diff) public override void UpdateAI(uint diff)
{ {
if (me.ToTotem().GetTotemType() != TotemType.Active) if (me.ToTotem().GetTotemType() != TotemType.Active)
+1 -1
View File
@@ -536,7 +536,7 @@ namespace Game.AI
_me = unit; _me = unit;
_dist = dist; _dist = dist;
_playerOnly = playerOnly; _playerOnly = playerOnly;
_exception = !withTank ? _me.GetThreatManager().GetCurrentVictim() : null; _exception = !withTank ? unit.GetThreatManager().GetLastVictim() : null;
_aura = aura; _aura = aura;
} }
@@ -135,9 +135,10 @@ namespace Game.AI
{ {
me.RemoveAllAuras(); me.RemoveAllAuras();
me.CombatStop(true); me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged();
me.SetLootRecipient(null); me.SetLootRecipient(null);
EngagementOver();
if (HasEscortState(EscortState.Escorting)) if (HasEscortState(EscortState.Escorting))
{ {
AddEscortState(EscortState.Returning); AddEscortState(EscortState.Returning);
@@ -123,15 +123,19 @@ namespace Game.AI
public override void EnterEvadeMode(EvadeReason why) public override void EnterEvadeMode(EvadeReason why)
{ {
if (!me.IsAlive()) if (!me.IsAlive())
{
EngagementOver();
return; return;
}
me.RemoveAllAuras(); me.RemoveAllAuras();
me.CombatStop(true); me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged();
me.SetLootRecipient(null); me.SetLootRecipient(null);
me.SetCannotReachTarget(false); me.SetCannotReachTarget(false);
me.DoNotReacquireTarget(); me.DoNotReacquireTarget();
EngagementOver();
if (HasFollowState(FollowState.Inprogress)) if (HasFollowState(FollowState.Inprogress))
{ {
Log.outDebug(LogFilter.Scripts, "FollowerAI left combat, returning to CombatStartPosition."); Log.outDebug(LogFilter.Scripts, "FollowerAI left combat, returning to CombatStartPosition.");
+5 -11
View File
@@ -234,15 +234,9 @@ namespace Game.Combat
public static void NotifyAICombat(Unit me, Unit other) public static void NotifyAICombat(Unit me, Unit other)
{ {
if (!me.IsAIEnabled()) UnitAI ai = me.GetAI();
return; if (ai != null)
ai.JustEnteredCombat(other);
me.GetAI().JustEnteredCombat(other);
Creature cMe = me.ToCreature();
if (cMe != null)
if (!cMe.CanHaveThreatList())
cMe.GetAI().JustEngagedWith(other);
} }
void PutReference(ObjectGuid guid, CombatReference refe) void PutReference(ObjectGuid guid, CombatReference refe)
@@ -277,14 +271,14 @@ namespace Game.Combat
{ {
_owner.AddUnitFlag(UnitFlags.InCombat); _owner.AddUnitFlag(UnitFlags.InCombat);
_owner.AtEnterCombat(); _owner.AtEnterCombat();
if (!_owner.CanHaveThreatList() && !_owner.IsEngaged()) if (_owner.IsCreature())
_owner.AtEngage(GetAnyTarget()); _owner.AtEngage(GetAnyTarget());
} }
else else
{ {
_owner.RemoveUnitFlag(UnitFlags.InCombat); _owner.RemoveUnitFlag(UnitFlags.InCombat);
_owner.AtExitCombat(); _owner.AtExitCombat();
if (_owner.IsEngaged() && !(_owner.IsCreature() && _owner.CanHaveThreatList() && _owner.ToCreature().IsAIEnabled())) if (!_owner.IsCreature())
_owner.AtDisengage(); _owner.AtDisengage();
} }
+37 -29
View File
@@ -37,6 +37,7 @@ namespace Game.Combat
uint _updateTimer; uint _updateTimer;
List<ThreatReference> _sortedThreatList = new(); List<ThreatReference> _sortedThreatList = new();
Dictionary<ObjectGuid, ThreatReference> _myThreatListEntries = new(); Dictionary<ObjectGuid, ThreatReference> _myThreatListEntries = new();
List<ThreatReference> _needsAIUpdate = new();
ThreatReference _currentVictimRef; ThreatReference _currentVictimRef;
ThreatReference _fixateRef; ThreatReference _fixateRef;
@@ -98,6 +99,12 @@ namespace Game.Combat
if (_currentVictimRef == null || _currentVictimRef.ShouldBeOffline()) if (_currentVictimRef == null || _currentVictimRef.ShouldBeOffline())
UpdateVictim(); UpdateVictim();
Cypher.Assert(_currentVictimRef == null || _currentVictimRef.IsAvailable());
return _currentVictimRef?.GetVictim();
}
public Unit GetLastVictim()
{
if (_currentVictimRef != null && !_currentVictimRef.ShouldBeOffline()) if (_currentVictimRef != null && !_currentVictimRef.ShouldBeOffline())
return _currentVictimRef.GetVictim(); return _currentVictimRef.GetVictim();
@@ -294,7 +301,7 @@ namespace Game.Combat
} }
} }
if (targetRefe.IsOnline()) if (targetRefe.IsOnline())
targetRefe.AddThreat(amount); targetRefe.AddThreat(amount);
return; return;
} }
@@ -306,14 +313,13 @@ namespace Game.Combat
// afterwards, we evaluate whether this is an online reference (it might not be an acceptable target, but we need to add it to our threat list before we check!) // afterwards, we evaluate whether this is an online reference (it might not be an acceptable target, but we need to add it to our threat list before we check!)
newRefe.UpdateOffline(); newRefe.UpdateOffline();
if (newRefe.IsOnline()) // ...and if the ref is online it also gets the threat it should have if (newRefe.IsOnline()) // we only add the threat if the ref is currently available
newRefe.AddThreat(amount); newRefe.AddThreat(amount);
if (!_owner.IsEngaged()) if (_currentVictimRef == null)
{
_owner.AtEngage(target);
UpdateVictim(); UpdateVictim();
} else
ProcessAIUpdates();
} }
void ScaleThreat(Unit target, float factor) void ScaleThreat(Unit target, float factor)
@@ -398,18 +404,6 @@ namespace Game.Combat
} }
} }
/// <summary>
/// THIS SHOULD ONLY BE CALLED FROM A CREATURE'S AI (typically in EnterEvadeMode)
/// notify the unit that the AI has disengaged
/// </summary>
public void NotifyDisengaged()
{
// note: i don't really like having this here
// (maybe engage flag should be in creature ai? it's inherently an AI property...)
if (_owner.IsEngaged())
_owner.AtDisengage();
}
public void FixateTarget(Unit target) public void FixateTarget(Unit target)
{ {
if (target) if (target)
@@ -446,6 +440,7 @@ namespace Game.Combat
NeedClientUpdate = false; NeedClientUpdate = false;
} }
ProcessAIUpdates();
} }
ThreatReference ReselectVictim() ThreatReference ReselectVictim()
@@ -454,7 +449,7 @@ namespace Game.Combat
return null; return null;
foreach (var pair in _myThreatListEntries) foreach (var pair in _myThreatListEntries)
pair.Value.UpdateOffline(); pair.Value.UpdateOffline(); // AI notifies are processed in ::UpdateVictim caller
// fixated target is always preferred // fixated target is always preferred
if (_fixateRef != null && _fixateRef.IsAvailable()) if (_fixateRef != null && _fixateRef.IsAvailable())
@@ -509,6 +504,16 @@ namespace Game.Combat
return null; return null;
} }
void ProcessAIUpdates()
{
CreatureAI ai = _owner.ToCreature().GetAI();
List<ThreatReference> v = new(_needsAIUpdate); // _needClientUpdate is now empty in case this triggers a recursive call
if (ai == null)
return;
foreach (ThreatReference refe in v)
ai.JustStartedThreateningMe(refe.GetVictim());
}
// returns true if a is LOWER on the threat list than b // returns true if a is LOWER on the threat list than b
public static bool CompareReferencesLT(ThreatReference a, ThreatReference b, float aWeight) public static bool CompareReferencesLT(ThreatReference a, ThreatReference b, float aWeight)
{ {
@@ -563,17 +568,17 @@ namespace Game.Combat
threat *= victimMgr._singleSchoolModifiers[(int)SpellSchools.Arcane]; threat *= victimMgr._singleSchoolModifiers[(int)SpellSchools.Arcane];
break; break;
default: default:
{
if (victimMgr._multiSchoolModifiers.TryGetValue(mask, out float value))
{ {
if (victimMgr._multiSchoolModifiers.TryGetValue(mask, out float value)) threat *= value;
{
threat *= value;
break;
}
float mod = victim.GetTotalAuraMultiplierByMiscMask(AuraType.ModThreat, (uint)mask);
victimMgr._multiSchoolModifiers[mask] = mod;
threat *= mod;
break; break;
} }
float mod = victim.GetTotalAuraMultiplierByMiscMask(AuraType.ModThreat, (uint)mask);
victimMgr._multiSchoolModifiers[mask] = mod;
threat *= mod;
break;
}
} }
return threat; return threat;
} }
@@ -671,7 +676,7 @@ namespace Game.Combat
threatClear.UnitGUID = _owner.GetGUID(); threatClear.UnitGUID = _owner.GetGUID();
_owner.SendMessageToSet(threatClear, false); _owner.SendMessageToSet(threatClear, false);
} }
public void SendRemoveToClients(Unit victim) public void SendRemoveToClients(Unit victim)
{ {
ThreatRemove threatRemove = new(); ThreatRemove threatRemove = new();
@@ -797,6 +802,8 @@ namespace Game.Combat
// Resets the specified unit's threat to zero // Resets the specified unit's threat to zero
public void ResetThreat(Unit target) { ScaleThreat(target, 0.0f); } public void ResetThreat(Unit target) { ScaleThreat(target, 0.0f); }
public void RegisterForAIUpdate(ThreatReference refe) { _needsAIUpdate.Add(refe); }
} }
public enum TauntState public enum TauntState
@@ -827,7 +834,7 @@ namespace Game.Combat
_owner = mgr._owner as Creature; _owner = mgr._owner as Creature;
_mgr = mgr; _mgr = mgr;
_victim = victim; _victim = victim;
Online = ShouldBeSuppressed() ? OnlineState.Suppressed : OnlineState.Online; Online = OnlineState.Offline;
} }
public void AddThreat(float amount) public void AddThreat(float amount)
@@ -866,6 +873,7 @@ namespace Game.Combat
{ {
Online = ShouldBeSuppressed() ? OnlineState.Suppressed : OnlineState.Online; Online = ShouldBeSuppressed() ? OnlineState.Suppressed : OnlineState.Online;
ListNotifyChanged(); ListNotifyChanged();
_mgr.RegisterForAIUpdate(this);
} }
} }
+11 -3
View File
@@ -1047,6 +1047,14 @@ namespace Game.Entities
&& !GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoXpAtKill); && !GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoXpAtKill);
} }
public override bool IsEngaged()
{
CreatureAI ai = GetAI();
if (ai != null)
return ai.IsEngaged();
return false;
}
public override void AtEngage(Unit target) public override void AtEngage(Unit target)
{ {
base.AtEngage(target); base.AtEngage(target);
@@ -1353,7 +1361,7 @@ namespace Game.Entities
CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(level, cInfo.UnitClass); CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(level, cInfo.UnitClass);
// health // health
float healthmod = _GetHealthMod(rank); float healthmod = GetHealthMod(rank);
uint basehp = (uint)GetMaxHealthByLevel(level); uint basehp = (uint)GetMaxHealthByLevel(level);
uint health = (uint)(basehp * healthmod); uint health = (uint)(basehp * healthmod);
@@ -1416,7 +1424,7 @@ namespace Game.Entities
} }
} }
float _GetHealthMod(CreatureEliteType Rank) public float GetHealthMod(CreatureEliteType Rank)
{ {
switch (Rank) // define rates for each elite rank switch (Rank) // define rates for each elite rank
{ {
@@ -1567,7 +1575,7 @@ namespace Game.Entities
curhealth = m_creatureData.curhealth; curhealth = m_creatureData.curhealth;
if (curhealth != 0) if (curhealth != 0)
{ {
curhealth = (uint)(curhealth * _GetHealthMod(GetCreatureTemplate().Rank)); curhealth = (uint)(curhealth * GetHealthMod(GetCreatureTemplate().Rank));
if (curhealth < 1) if (curhealth < 1)
curhealth = 1; curhealth = 1;
} }
+5 -5
View File
@@ -57,8 +57,9 @@ namespace Game.Entities
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.LeavingCombat); RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.LeavingCombat);
} }
public virtual void AtEngage(Unit target) { m_isEngaged = true; } public virtual void AtEngage(Unit target) { }
public virtual void AtDisengage() { m_isEngaged = false; }
public virtual void AtDisengage() { }
public void CombatStop(bool includingCast = false, bool mutualPvP = true) public void CombatStop(bool includingCast = false, bool mutualPvP = true)
{ {
@@ -147,10 +148,10 @@ namespace Game.Entities
public bool CanHaveThreatList() { return m_threatManager.CanHaveThreatList(); } public bool CanHaveThreatList() { return m_threatManager.CanHaveThreatList(); }
// This value can be different from IsInCombat: // This value can be different from IsInCombat, for example:
// - when a projectile spell is midair against a creature (combat on launch - threat+aggro on impact) // - when a projectile spell is midair against a creature (combat on launch - threat+aggro on impact)
// - when the creature has no targets left, but the AI has not yet ceased engaged logic // - when the creature has no targets left, but the AI has not yet ceased engaged logic
public bool IsEngaged() { return m_isEngaged; } public virtual bool IsEngaged() { return IsInCombat(); }
public bool IsEngagedBy(Unit who) { return CanHaveThreatList() ? IsThreatenedBy(who) : IsInCombatWith(who); } public bool IsEngagedBy(Unit who) { return CanHaveThreatList() ? IsThreatenedBy(who) : IsInCombatWith(who); }
@@ -230,7 +231,6 @@ namespace Game.Entities
minion.StopAttackFaction(factionId); minion.StopAttackFaction(factionId);
} }
public void HandleProcExtraAttackFor(Unit victim) public void HandleProcExtraAttackFor(Unit victim)
{ {
while (ExtraAttacks != 0) while (ExtraAttacks != 0)
-2
View File
@@ -56,8 +56,6 @@ namespace Game.Entities
protected uint[] m_attackTimer = new uint[(int)WeaponAttackType.Max]; protected uint[] m_attackTimer = new uint[(int)WeaponAttackType.Max];
// Threat+combat management // Threat+combat management
bool m_isEngaged;
CombatManager m_combatManager; CombatManager m_combatManager;
ThreatManager m_threatManager; ThreatManager m_threatManager;
+1 -1
View File
@@ -45,7 +45,7 @@ namespace Scripts.Pets
return; return;
me.CombatStop(true); me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged(); EngagementOver();
me.ResetPlayerDamageReq(); me.ResetPlayerDamageReq();
} }
} }