Core/Threat: Threat system adjustments

Port From (https://github.com/TrinityCore/TrinityCore/commit/392a644dc8562ea54b9c185a2776fceb1006e2cd)
This commit is contained in:
hondacrx
2021-11-01 12:04:49 -04:00
parent 9b99019d44
commit 3f684eadf9
6 changed files with 176 additions and 158 deletions
+163 -124
View File
@@ -28,13 +28,13 @@ namespace Game.Combat
{
public class ThreatManager
{
public static uint CLIENT_THREAT_UPDATE_INTERVAL = 1000u;
public static uint THREAT_UPDATE_INTERVAL = 1000u;
public Unit _owner;
bool _ownerCanHaveThreatList;
bool _ownerEngaged;
uint _updateClientTimer;
uint _updateTimer;
List<ThreatReference> _sortedThreatList = new();
Dictionary<ObjectGuid, ThreatReference> _myThreatListEntries = new();
ThreatReference _currentVictimRef;
@@ -68,7 +68,7 @@ namespace Game.Combat
public ThreatManager(Unit owner)
{
_owner = owner;
_updateClientTimer = CLIENT_THREAT_UPDATE_INTERVAL;
_updateTimer = THREAT_UPDATE_INTERVAL;
for (var i = 0; i < (int)SpellSchools.Max; ++i)
_singleSchoolModifiers[i] = 1.0f;
@@ -84,18 +84,21 @@ namespace Game.Combat
if (!CanHaveThreatList() || !IsEngaged())
return;
if (_updateClientTimer <= tdiff)
if (_updateTimer <= tdiff)
{
_updateClientTimer = CLIENT_THREAT_UPDATE_INTERVAL;
SendThreatListToClients();
UpdateVictim();
_updateTimer = THREAT_UPDATE_INTERVAL;
}
else
_updateClientTimer -= tdiff;
_updateTimer -= tdiff;
}
public Unit GetCurrentVictim()
{
if (_currentVictimRef != null)
if (_currentVictimRef != null || _currentVictimRef.ShouldBeOffline())
UpdateVictim();
if (_currentVictimRef != null && !_currentVictimRef.ShouldBeOffline())
return _currentVictimRef.GetVictim();
return null;
@@ -110,22 +113,6 @@ namespace Game.Combat
return null;
}
public Unit SelectVictim()
{
if (_sortedThreatList.Empty())
return null;
ThreatReference newVictimRef = ReselectVictim();
if (newVictimRef != _currentVictimRef)
{
if (newVictimRef != null)
SendNewVictimToClients(newVictimRef);
_currentVictimRef = newVictimRef;
}
return newVictimRef != null ? newVictimRef.GetVictim() : null;
}
public bool IsThreatListEmpty(bool includeOffline = false)
{
if (includeOffline)
@@ -186,14 +173,11 @@ namespace Game.Combat
public bool IsThreateningTo(Unit who, bool includeOffline = false) { return IsThreateningTo(who.GetGUID(), includeOffline); }
public void UpdateOnlineStates(bool meThreateningOthers, bool othersThreateningMe)
public void EvaluateSuppressed()
{
if (othersThreateningMe)
foreach (var pair in _myThreatListEntries)
pair.Value.UpdateOnlineState();
if (meThreateningOthers)
foreach (var pair in _threatenedByMe)
pair.Value.UpdateOnlineState();
foreach (var pair in _threatenedByMe)
if (pair.Value.IsOnline() && pair.Value.ShouldBeSuppressed())
pair.Value.Online = OnlineState.Suppressed;
}
static void SaveCreatureHomePositionIfNeed(Creature c)
@@ -296,7 +280,13 @@ namespace Game.Combat
var targetRefe = _myThreatListEntries.LookupByKey(target.GetGUID());
if (targetRefe != null)
{
targetRefe.AddThreat(amount);
// causing threat causes SUPPRESSED threat states to stop being suppressed
if (targetRefe.GetOnlineState() == OnlineState.Suppressed)
if (!targetRefe.ShouldBeSuppressed())
targetRefe.Online = OnlineState.Online;
if (targetRefe.IsOnline())
targetRefe.AddThreat(amount);
return;
}
@@ -307,10 +297,10 @@ namespace Game.Combat
if (!_ownerEngaged)
{
Creature cOwner = _owner.ToCreature(); // if we got here the owner can have a threat list, and must be a creature!
_ownerEngaged = true;
Creature cOwner = _owner.ToCreature();
Cypher.Assert(cOwner != null); // if we got here the owner can have a threat list, and must be a creature!
UpdateVictim();
SaveCreatureHomePositionIfNeed(cOwner);
CreatureAI ownerAI = cOwner.GetAI();
if (ownerAI != null)
@@ -368,14 +358,22 @@ namespace Game.Combat
public void ResetAllThreat()
{
foreach (var pair in _myThreatListEntries)
pair.Value.SetThreat(0.0f);
pair.Value.ScaleThreat(0.0f);
}
public void ClearThreat(Unit target)
{
var refe = _myThreatListEntries.LookupByKey(target.GetGUID());
if (refe != null)
refe.ClearThreat();
ClearThreat(refe);
}
public void ClearThreat(ThreatReference refe)
{
SendRemoveToClients(refe.GetVictim());
refe.UnregisterAndFree();
if (_currentVictimRef == null)
UpdateVictim();
}
public void ClearAllThreat()
@@ -386,7 +384,7 @@ namespace Game.Combat
SendClearAllThreatToClients();
do
_myThreatListEntries.First().Value.ClearThreat(false);
_myThreatListEntries.First().Value.UnregisterAndFree();
while (!_myThreatListEntries.Empty());
}
@@ -414,8 +412,24 @@ namespace Game.Combat
public void ClearFixate() { FixateTarget(null); }
void UpdateVictim()
{
ThreatReference newVictim = ReselectVictim();
bool newHighest = (newVictim != _currentVictimRef);
_currentVictimRef = newVictim;
SendThreatListToClients(newVictim != null && newHighest);
}
ThreatReference ReselectVictim()
{
if (_sortedThreatList.Empty())
return null;
foreach (var pair in _myThreatListEntries)
pair.Value.UpdateOffline();
// fixated target is always preferred
if (_fixateRef != null && _fixateRef.IsAvailable())
return _fixateRef;
@@ -538,30 +552,6 @@ namespace Game.Combat
return threat;
}
void SendClearAllThreatToClients()
{
ThreatClear threatClear = new();
threatClear.UnitGUID = _owner.GetGUID();
_owner.SendMessageToSet(threatClear, false);
}
public void SendThreatListToClients()
{
ThreatUpdate threatUpdate = new();
threatUpdate.UnitGUID = _owner.GetGUID();
foreach (ThreatReference refe in _sortedThreatList)
{
if (!refe.IsAvailable()) // @todo check if suppressed threat should get sent for bubble/iceblock/hop etc
continue;
ThreatInfo threatInfo = new();
threatInfo.UnitGUID = refe.GetVictim().GetGUID();
threatInfo.Threat = (long)(refe.GetThreat() * 100);
threatUpdate.ThreatList.Add(threatInfo);
}
_owner.SendMessageToSet(threatUpdate, false);
}
public void ForwardThreatForAssistingMe(Unit assistant, float baseAmount, SpellInfo spell = null, bool ignoreModifiers = false)
{
if (spell != null && spell.HasAttribute(SpellAttr1.NoThreat)) // shortcut, none of the calls would do anything
@@ -570,16 +560,35 @@ namespace Game.Combat
if (_threatenedByMe.Empty())
return;
float perTarget = baseAmount / _threatenedByMe.Count; // Threat is divided evenly among all targets (LibThreat sourced)
List<Creature> canBeThreatened = new();
List<Creature> cannotBeThreatened = new();
foreach (var pair in _threatenedByMe)
pair.Value.GetOwner().GetThreatManager().AddThreat(assistant, perTarget, spell, ignoreModifiers);
{
Creature owner = pair.Value.GetOwner();
if (!owner.HasBreakableByDamageCrowdControlAura())
canBeThreatened.Add(owner);
else
cannotBeThreatened.Add(owner);
}
if (!canBeThreatened.Empty()) // targets under CC cannot gain assist threat - split evenly among the rest
{
float perTarget = baseAmount / canBeThreatened.Count;
foreach (Creature threatened in canBeThreatened)
threatened.GetThreatManager().AddThreat(assistant, perTarget, spell, ignoreModifiers);
}
foreach (Creature threatened in cannotBeThreatened)
threatened.GetThreatManager().AddThreat(assistant, 0.0f, spell, true);
}
public void RemoveMeFromThreatLists()
{
while (!_threatenedByMe.Empty())
_threatenedByMe.First().Value.ClearThreat();
{
var refe = _threatenedByMe.FirstOrDefault().Value;
refe._mgr.ClearThreat(_owner);
}
}
public void UpdateMyTempModifiers()
@@ -588,9 +597,12 @@ namespace Game.Combat
foreach (AuraEffect eff in _owner.GetAuraEffectsByType(AuraType.ModTotalThreat))
mod += eff.GetAmount();
if (_threatenedByMe.Empty())
return;
foreach (var pair in _threatenedByMe)
{
pair.Value._tempModifier = mod;
pair.Value.TempModifier = mod;
pair.Value.ListNotifyChanged();
}
}
@@ -627,6 +639,13 @@ namespace Game.Combat
UpdateRedirectInfo();
}
void SendClearAllThreatToClients()
{
ThreatClear threatClear = new();
threatClear.UnitGUID = _owner.GetGUID();
_owner.SendMessageToSet(threatClear, false);
}
public void SendRemoveToClients(Unit victim)
{
ThreatRemove threatRemove = new();
@@ -635,22 +654,37 @@ namespace Game.Combat
_owner.SendMessageToSet(threatRemove, false);
}
void SendNewVictimToClients(ThreatReference victimRef)
void SendThreatListToClients(bool newHighest)
{
HighestThreatUpdate highestThreatUpdate = new();
highestThreatUpdate.UnitGUID = _owner.GetGUID();
highestThreatUpdate.HighestThreatGUID = victimRef.GetVictim().GetGUID();
foreach (ThreatReference refe in _sortedThreatList)
void fillSharedPacketDataAndSend(dynamic packet)
{
if (!refe.IsAvailable())
continue;
packet.UnitGUID = _owner.GetGUID();
packet.ThreatList.reserve(_sortedThreatList.Count);
ThreatInfo threatInfo = new();
threatInfo.UnitGUID = refe.GetVictim().GetGUID();
threatInfo.Threat = (long)(refe.GetThreat() * 100);
highestThreatUpdate.ThreatList.Add(threatInfo);
foreach (ThreatReference refe in _sortedThreatList)
{
if (!refe.IsAvailable())
continue;
ThreatInfo threatInfo = new();
threatInfo.UnitGUID = refe.GetVictim().GetGUID();
threatInfo.Threat = (long)(refe.GetThreat() * 100);
packet.ThreatList.Add(threatInfo);
}
_owner.SendMessageToSet(packet, false);
}
if (newHighest)
{
HighestThreatUpdate highestThreatUpdate = new();
highestThreatUpdate.HighestThreatGUID = _currentVictimRef.GetVictim().GetGUID();
fillSharedPacketDataAndSend(highestThreatUpdate);
}
else
{
ThreatUpdate threatUpdate = new();
fillSharedPacketDataAndSend(threatUpdate);
}
_owner.SendMessageToSet(highestThreatUpdate, false);
}
void PutThreatListRef(ObjectGuid guid, ThreatReference refe)
@@ -661,23 +695,20 @@ namespace Game.Combat
_sortedThreatList.Sort();
}
public void PurgeThreatListRef(ObjectGuid guid, bool sendRemove)
public void PurgeThreatListRef(ObjectGuid guid)
{
var refe = _myThreatListEntries.LookupByKey(guid);
if (refe == null)
return;
_myThreatListEntries.Remove(guid);
_sortedThreatList.Remove(refe);
if (_currentVictimRef == refe)
_currentVictimRef = null;
if (_fixateRef == refe)
_fixateRef = null;
_sortedThreatList.Remove(refe);
_sortedThreatList.Sort();
if (sendRemove && refe.IsAvailable())
SendRemoveToClients(refe.GetVictim());
if (_currentVictimRef == refe)
_currentVictimRef = null;
}
void PutThreatenedByMeRef(ObjectGuid guid, ThreatReference refe)
@@ -759,21 +790,21 @@ namespace Game.Combat
public class ThreatReference : IComparable<ThreatReference>
{
Unit _owner;
ThreatManager _mgr;
Creature _owner;
public ThreatManager _mgr;
Unit _victim;
public OnlineState Online;
float _baseAmount;
public int _tempModifier; // Temporary effects (auras with SPELL_AURA_MOD_TOTAL_THREAT) - set from victim's threatmanager in ThreatManager::UpdateMyTempModifiers
OnlineState _online;
public int TempModifier; // Temporary effects (auras with SPELL_AURA_MOD_TOTAL_THREAT) - set from victim's threatmanager in ThreatManager::UpdateMyTempModifiers
TauntState _taunted;
public ThreatReference(ThreatManager mgr, Unit victim, float amount)
{
_owner = mgr._owner;
_owner = mgr._owner as Creature;
_mgr = mgr;
_victim = victim;
_baseAmount = amount;
_online = SelectOnlineState();
Online = ShouldBeOffline() ? OnlineState.Offline : ShouldBeSuppressed() ? OnlineState.Suppressed : OnlineState.Online;
_baseAmount = IsOnline() ? amount : 0.0f;
}
public void AddThreat(float amount)
@@ -794,17 +825,23 @@ namespace Game.Combat
ListNotifyChanged();
}
public void UpdateOnlineState()
public void UpdateOffline()
{
OnlineState onlineState = SelectOnlineState();
if (onlineState == _online)
bool shouldBeOffline = ShouldBeOffline();
if (shouldBeOffline == IsOffline())
return;
_online = onlineState;
ListNotifyChanged();
if (!IsAvailable())
_owner.GetThreatManager().SendRemoveToClients(_victim);
if (shouldBeOffline)
{
Online = OnlineState.Offline;
ListNotifyChanged();
_mgr.SendRemoveToClients(_victim);
}
else
{
Online = ShouldBeSuppressed() ? OnlineState.Suppressed : OnlineState.Online;
ListNotifyChanged();
}
}
public static bool FlagsAllowFighting(Unit a, Unit b)
@@ -825,31 +862,28 @@ namespace Game.Combat
return true;
}
OnlineState SelectOnlineState()
public bool ShouldBeOffline()
{
// first, check all offline conditions
if (!_owner.CanSeeOrDetect(_victim)) // not in map/phase, or stealth/invis
return OnlineState.Offline;
if (!_owner.CanSeeOrDetect(_victim))
return true;
if (_victim.HasUnitState(UnitState.Died)) // feign death
return OnlineState.Offline;
if (!_owner._IsTargetAcceptable(_victim) || !_owner.CanCreatureAttack(_victim))
return true;
if (!FlagsAllowFighting(_owner, _victim) || !FlagsAllowFighting(_victim, _owner))
return OnlineState.Offline;
return true;
if (_owner.IsAIEnabled() && !_owner.GetAI().CanAIAttack(_victim))
return OnlineState.Offline;
return false;
}
// next, check suppression (immunity to chosen melee attack school)
public bool ShouldBeSuppressed()
{
if (_victim.IsImmunedToDamage(_owner.GetMeleeDamageSchoolMask()))
return OnlineState.Suppressed;
// or any form of CC that will break on damage - disorient, polymorph, blind etc
return true;
if (_victim.HasBreakableByDamageCrowdControlAura())
return OnlineState.Suppressed;
return true;
// no suppression - we're online
return OnlineState.Online;
return false;
}
public void UpdateTauntState(TauntState state = TauntState.None)
@@ -866,19 +900,24 @@ namespace Game.Combat
ListNotifyChanged();
}
public void ClearThreat(bool sendRemove = true)
public void ClearThreat()
{
_owner.GetThreatManager().PurgeThreatListRef(_victim.GetGUID(), sendRemove);
_mgr.ClearThreat(this);
}
public void UnregisterAndFree()
{
_owner.GetThreatManager().PurgeThreatListRef(_victim.GetGUID());
_victim.GetThreatManager().PurgeThreatenedByMeRef(_owner.GetGUID());
}
public Unit GetOwner() { return _owner; }
public Creature GetOwner() { return _owner; }
public Unit GetVictim() { return _victim; }
public float GetThreat() { return Math.Max(_baseAmount + (float)_tempModifier, 0.0f); }
public OnlineState GetOnlineState() { return _online; }
public bool IsOnline() { return (_online >= OnlineState.Online); }
public bool IsAvailable() { return (_online > OnlineState.Offline); }
public bool IsOffline() { return (_online <= OnlineState.Offline); }
public float GetThreat() { return Math.Max(_baseAmount + (float)TempModifier, 0.0f); }
public OnlineState GetOnlineState() { return Online; }
public bool IsOnline() { return Online >= OnlineState.Online; }
public bool IsAvailable() { return Online > OnlineState.Offline; }
public bool IsOffline() { return Online <= OnlineState.Offline; }
public TauntState GetTauntState() { return IsTaunting() ? TauntState.Taunt : _taunted; }
public bool IsTaunting() { return _taunted >= TauntState.Taunt; }
public bool IsDetaunted() { return _taunted == TauntState.Detaunt; }
+2 -3
View File
@@ -417,7 +417,7 @@ namespace Game.Entities
LoadCreaturesAddon();
LoadTemplateImmunities();
GetThreatManager().UpdateOnlineStates(true, true);
GetThreatManager().EvaluateSuppressed();
return true;
}
@@ -541,7 +541,6 @@ namespace Game.Entities
if (diff >= m_boundaryCheckTime)
{
GetAI().CheckInRoom();
GetThreatManager().UpdateOnlineStates(false, true);
m_boundaryCheckTime = 2500;
}
else
@@ -898,7 +897,7 @@ namespace Game.Entities
Unit target;
if (CanHaveThreatList())
target = GetThreatManager().SelectVictim();
target = GetThreatManager().GetCurrentVictim();
else if (!HasReactState(ReactStates.Passive))
{
// We're a player pet, probably
-2
View File
@@ -152,8 +152,6 @@ namespace Game.Entities
public bool CanHaveThreatList() { return m_threatManager.CanHaveThreatList(); }
void SendThreatListUpdate() { m_threatManager.SendThreatListToClients(); }
// For NPCs with threat list: Whether there are any enemies on our threat list
// For other units: Whether we're in combat
// This value is different from IsInCombat when a projectile spell is midair (combat on launch - threat+aggro on impact)
+3 -19
View File
@@ -419,7 +419,6 @@ namespace Game.Entities
AddToNotify(NotifyFlags.VisibilityChanged);
else
{
m_threatManager.UpdateOnlineStates(true, true);
base.UpdateObjectVisibility(true);
// call MoveInLineOfSight for nearby creatures
AIRelocationNotifier notifier = new(this);
@@ -2882,16 +2881,11 @@ namespace Game.Entities
{
AddUnitFlag(UnitFlags.ImmuneToPc | UnitFlags.ImmuneToNpc);
ValidateAttackersAndOwnTarget();
if (keepCombat)
m_threatManager.UpdateOnlineStates(true, true);
else
if (!keepCombat)
m_combatManager.EndAllCombat();
}
else
{
RemoveUnitFlag(UnitFlags.ImmuneToPc | UnitFlags.ImmuneToNpc);
m_threatManager.UpdateOnlineStates(true, true);
}
}
public virtual void SetImmuneToAll(bool apply) { SetImmuneToAll(apply, false); }
@@ -2904,9 +2898,7 @@ namespace Game.Entities
{
AddUnitFlag(UnitFlags.ImmuneToPc);
ValidateAttackersAndOwnTarget();
if (keepCombat)
m_threatManager.UpdateOnlineStates(true, true);
else
if (!keepCombat)
{
List<CombatReference> toEnd = new();
foreach (var pair in m_combatManager.GetPvECombatRefs())
@@ -2922,10 +2914,7 @@ namespace Game.Entities
}
}
else
{
RemoveUnitFlag(UnitFlags.ImmuneToPc);
m_threatManager.UpdateOnlineStates(true, true);
}
}
public virtual void SetImmuneToPC(bool apply) { SetImmuneToPC(apply, false); }
@@ -2938,9 +2927,7 @@ namespace Game.Entities
{
AddUnitFlag(UnitFlags.ImmuneToNpc);
ValidateAttackersAndOwnTarget();
if (keepCombat)
m_threatManager.UpdateOnlineStates(true, true);
else
if (!keepCombat)
{
List<CombatReference> toEnd = new();
foreach (var pair in m_combatManager.GetPvECombatRefs())
@@ -2956,10 +2943,7 @@ namespace Game.Entities
}
}
else
{
RemoveUnitFlag(UnitFlags.ImmuneToNpc);
m_threatManager.UpdateOnlineStates(true, true);
}
}
public virtual void SetImmuneToNPC(bool apply) { SetImmuneToNPC(apply, false); }
+7 -9
View File
@@ -1581,7 +1581,7 @@ namespace Game.Spells
}
}
target.GetThreatManager().UpdateOnlineStates(true, false);
target.GetThreatManager().EvaluateSuppressed();
}
[AuraEffectHandler(AuraType.ModScale)]
@@ -1696,8 +1696,6 @@ namespace Game.Spells
if (creature != null)
creature.InitializeReactState();
}
target.GetThreatManager().UpdateOnlineStates(true, false);
}
[AuraEffectHandler(AuraType.ModUnattackable)]
@@ -2383,7 +2381,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Confused);
target.GetThreatManager().UpdateOnlineStates(true, false);
target.GetThreatManager().EvaluateSuppressed();
}
[AuraEffectHandler(AuraType.ModFear)]
@@ -2395,7 +2393,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Fleeing);
target.GetThreatManager().UpdateOnlineStates(true, false);
target.GetThreatManager().EvaluateSuppressed();
}
[AuraEffectHandler(AuraType.ModStun)]
@@ -2407,7 +2405,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Stunned);
target.GetThreatManager().UpdateOnlineStates(true, false);
target.GetThreatManager().EvaluateSuppressed();
}
[AuraEffectHandler(AuraType.ModRoot)]
@@ -2420,7 +2418,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Root);
target.GetThreatManager().UpdateOnlineStates(true, false);
target.GetThreatManager().EvaluateSuppressed();
}
[AuraEffectHandler(AuraType.PreventsFleeing)]
@@ -2817,7 +2815,7 @@ namespace Game.Spells
target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.StealthOrInvis);
}
target.GetThreatManager().UpdateOnlineStates(true, false);
target.GetThreatManager().EvaluateSuppressed();
}
[AuraEffectHandler(AuraType.DamageImmunity)]
@@ -2829,7 +2827,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
m_spellInfo.ApplyAllSpellImmunitiesTo(target, GetSpellEffectInfo(), apply);
target.GetThreatManager().UpdateOnlineStates(true, false);
target.GetThreatManager().EvaluateSuppressed();
}
[AuraEffectHandler(AuraType.DispelImmunity)]
+1 -1
View File
@@ -3217,7 +3217,7 @@ namespace Game.Spells
{
// in dungeons (or for nonplayers), reset this unit on all enemies' threat lists
foreach (var pair in unitTarget.GetThreatManager().GetThreatenedByMeList())
pair.Value.SetThreat(0.0f);
pair.Value.ScaleThreat(0.0f);
}
// makes spells cast before this time fizzle