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
{
bool _isEngaged;
bool _moveInLOSLocked;
List<AreaBoundary> _boundary = new();
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
public virtual void EnterEvadeMode(EvadeReason why = EvadeReason.Other)
{
@@ -231,7 +238,7 @@ namespace Game.AI
public bool UpdateVictim()
{
if (!me.IsEngaged())
if (!IsEngaged())
return false;
if (!me.HasReactState(ReactStates.Passive))
@@ -254,23 +261,53 @@ namespace Game.AI
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)
{
if (!me.IsAlive())
if (me.IsInEvadeMode())
return false;
if (!me.IsAlive())
{
EngagementOver();
return false;
}
me.RemoveAurasOnEvade();
// sometimes bosses stuck in combat?
me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged();
me.SetLootRecipient(null);
me.ResetPlayerDamageReq();
me.SetLastDamagedTime(0);
me.SetCannotReachTarget(false);
me.DoNotReacquireTarget();
EngagementOver();
return !me.IsInEvadeMode();
return true;
}
public CypherStrings VisualizeBoundary(int duration, Unit owner = null, bool fill = false)
@@ -404,11 +441,22 @@ namespace Game.AI
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
public virtual void JustEngagedWith(Unit who) { }
// 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
public virtual void KilledUnit(Unit victim) { }
@@ -521,6 +569,8 @@ namespace Game.AI
public virtual bool IsEscortNPC(bool onlyIfActive) { return false; }
public List<AreaBoundary> GetBoundary() { return _boundary; }
public bool IsEngaged() { return _isEngaged; }
}
public class AISpellInfoType
+2 -2
View File
@@ -48,7 +48,7 @@ namespace Game.AI
{
me.GetMotionMaster().MoveIdle();
me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged();
EngagementOver();
return;
}
@@ -56,7 +56,7 @@ namespace Game.AI
me.RemoveAllAuras();
me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged();
EngagementOver();
me.GetMotionMaster().MoveTargetedHome();
}
+17
View File
@@ -77,6 +77,21 @@ namespace Game.AI
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) { }
}
@@ -89,6 +104,8 @@ namespace Game.AI
public override void MoveInLineOfSight(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 JustAppeared() { }
public override void EnterEvadeMode(EvadeReason why) { }
+1 -10
View File
@@ -21,7 +21,7 @@ using Game.Maps;
namespace Game.AI
{
public class TotemAI : CreatureAI
public class TotemAI : NullCreatureAI
{
ObjectGuid _victimGuid;
@@ -30,15 +30,6 @@ namespace Game.AI
_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)
{
if (me.ToTotem().GetTotemType() != TotemType.Active)
+1 -1
View File
@@ -536,7 +536,7 @@ namespace Game.AI
_me = unit;
_dist = dist;
_playerOnly = playerOnly;
_exception = !withTank ? _me.GetThreatManager().GetCurrentVictim() : null;
_exception = !withTank ? unit.GetThreatManager().GetLastVictim() : null;
_aura = aura;
}
@@ -135,9 +135,10 @@ namespace Game.AI
{
me.RemoveAllAuras();
me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged();
me.SetLootRecipient(null);
EngagementOver();
if (HasEscortState(EscortState.Escorting))
{
AddEscortState(EscortState.Returning);
@@ -123,15 +123,19 @@ namespace Game.AI
public override void EnterEvadeMode(EvadeReason why)
{
if (!me.IsAlive())
{
EngagementOver();
return;
}
me.RemoveAllAuras();
me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged();
me.SetLootRecipient(null);
me.SetCannotReachTarget(false);
me.DoNotReacquireTarget();
EngagementOver();
if (HasFollowState(FollowState.Inprogress))
{
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)
{
if (!me.IsAIEnabled())
return;
me.GetAI().JustEnteredCombat(other);
Creature cMe = me.ToCreature();
if (cMe != null)
if (!cMe.CanHaveThreatList())
cMe.GetAI().JustEngagedWith(other);
UnitAI ai = me.GetAI();
if (ai != null)
ai.JustEnteredCombat(other);
}
void PutReference(ObjectGuid guid, CombatReference refe)
@@ -277,14 +271,14 @@ namespace Game.Combat
{
_owner.AddUnitFlag(UnitFlags.InCombat);
_owner.AtEnterCombat();
if (!_owner.CanHaveThreatList() && !_owner.IsEngaged())
if (_owner.IsCreature())
_owner.AtEngage(GetAnyTarget());
}
else
{
_owner.RemoveUnitFlag(UnitFlags.InCombat);
_owner.AtExitCombat();
if (_owner.IsEngaged() && !(_owner.IsCreature() && _owner.CanHaveThreatList() && _owner.ToCreature().IsAIEnabled()))
if (!_owner.IsCreature())
_owner.AtDisengage();
}
+36 -28
View File
@@ -37,6 +37,7 @@ namespace Game.Combat
uint _updateTimer;
List<ThreatReference> _sortedThreatList = new();
Dictionary<ObjectGuid, ThreatReference> _myThreatListEntries = new();
List<ThreatReference> _needsAIUpdate = new();
ThreatReference _currentVictimRef;
ThreatReference _fixateRef;
@@ -98,6 +99,12 @@ namespace Game.Combat
if (_currentVictimRef == null || _currentVictimRef.ShouldBeOffline())
UpdateVictim();
Cypher.Assert(_currentVictimRef == null || _currentVictimRef.IsAvailable());
return _currentVictimRef?.GetVictim();
}
public Unit GetLastVictim()
{
if (_currentVictimRef != null && !_currentVictimRef.ShouldBeOffline())
return _currentVictimRef.GetVictim();
@@ -294,7 +301,7 @@ namespace Game.Combat
}
}
if (targetRefe.IsOnline())
if (targetRefe.IsOnline())
targetRefe.AddThreat(amount);
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!)
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);
if (!_owner.IsEngaged())
{
_owner.AtEngage(target);
if (_currentVictimRef == null)
UpdateVictim();
}
else
ProcessAIUpdates();
}
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)
{
if (target)
@@ -446,6 +440,7 @@ namespace Game.Combat
NeedClientUpdate = false;
}
ProcessAIUpdates();
}
ThreatReference ReselectVictim()
@@ -454,7 +449,7 @@ namespace Game.Combat
return null;
foreach (var pair in _myThreatListEntries)
pair.Value.UpdateOffline();
pair.Value.UpdateOffline(); // AI notifies are processed in ::UpdateVictim caller
// fixated target is always preferred
if (_fixateRef != null && _fixateRef.IsAvailable())
@@ -509,6 +504,16 @@ namespace Game.Combat
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
public static bool CompareReferencesLT(ThreatReference a, ThreatReference b, float aWeight)
{
@@ -563,17 +568,17 @@ namespace Game.Combat
threat *= victimMgr._singleSchoolModifiers[(int)SpellSchools.Arcane];
break;
default:
{
if (victimMgr._multiSchoolModifiers.TryGetValue(mask, out float value))
{
if (victimMgr._multiSchoolModifiers.TryGetValue(mask, out float value))
{
threat *= value;
break;
}
float mod = victim.GetTotalAuraMultiplierByMiscMask(AuraType.ModThreat, (uint)mask);
victimMgr._multiSchoolModifiers[mask] = mod;
threat *= mod;
threat *= value;
break;
}
float mod = victim.GetTotalAuraMultiplierByMiscMask(AuraType.ModThreat, (uint)mask);
victimMgr._multiSchoolModifiers[mask] = mod;
threat *= mod;
break;
}
}
return threat;
}
@@ -797,6 +802,8 @@ namespace Game.Combat
// Resets the specified unit's threat to zero
public void ResetThreat(Unit target) { ScaleThreat(target, 0.0f); }
public void RegisterForAIUpdate(ThreatReference refe) { _needsAIUpdate.Add(refe); }
}
public enum TauntState
@@ -827,7 +834,7 @@ namespace Game.Combat
_owner = mgr._owner as Creature;
_mgr = mgr;
_victim = victim;
Online = ShouldBeSuppressed() ? OnlineState.Suppressed : OnlineState.Online;
Online = OnlineState.Offline;
}
public void AddThreat(float amount)
@@ -866,6 +873,7 @@ namespace Game.Combat
{
Online = ShouldBeSuppressed() ? OnlineState.Suppressed : OnlineState.Online;
ListNotifyChanged();
_mgr.RegisterForAIUpdate(this);
}
}
+11 -3
View File
@@ -1047,6 +1047,14 @@ namespace Game.Entities
&& !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)
{
base.AtEngage(target);
@@ -1353,7 +1361,7 @@ namespace Game.Entities
CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(level, cInfo.UnitClass);
// health
float healthmod = _GetHealthMod(rank);
float healthmod = GetHealthMod(rank);
uint basehp = (uint)GetMaxHealthByLevel(level);
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
{
@@ -1567,7 +1575,7 @@ namespace Game.Entities
curhealth = m_creatureData.curhealth;
if (curhealth != 0)
{
curhealth = (uint)(curhealth * _GetHealthMod(GetCreatureTemplate().Rank));
curhealth = (uint)(curhealth * GetHealthMod(GetCreatureTemplate().Rank));
if (curhealth < 1)
curhealth = 1;
}
+5 -5
View File
@@ -57,8 +57,9 @@ namespace Game.Entities
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.LeavingCombat);
}
public virtual void AtEngage(Unit target) { m_isEngaged = true; }
public virtual void AtDisengage() { m_isEngaged = false; }
public virtual void AtEngage(Unit target) { }
public virtual void AtDisengage() { }
public void CombatStop(bool includingCast = false, bool mutualPvP = true)
{
@@ -147,10 +148,10 @@ namespace Game.Entities
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 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); }
@@ -230,7 +231,6 @@ namespace Game.Entities
minion.StopAttackFaction(factionId);
}
public void HandleProcExtraAttackFor(Unit victim)
{
while (ExtraAttacks != 0)
-2
View File
@@ -56,8 +56,6 @@ namespace Game.Entities
protected uint[] m_attackTimer = new uint[(int)WeaponAttackType.Max];
// Threat+combat management
bool m_isEngaged;
CombatManager m_combatManager;
ThreatManager m_threatManager;
+1 -1
View File
@@ -45,7 +45,7 @@ namespace Scripts.Pets
return;
me.CombatStop(true);
me.GetThreatManager().NotifyDisengaged();
EngagementOver();
me.ResetPlayerDamageReq();
}
}