Core: Combat/threat system rewrite

Port From (https://github.com/TrinityCore/TrinityCore/commit/34c7810fe507eca1b8b9389630db5d5d26d92e77)
This commit is contained in:
hondacrx
2021-05-18 12:25:40 -04:00
parent 891c3b6478
commit 9851142796
37 changed files with 3454 additions and 3345 deletions
+415
View File
@@ -0,0 +1,415 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Game.Entities;
using System.Collections.Generic;
using System.Linq;
namespace Game.Combat
{
public class CombatManager
{
Unit _owner;
Dictionary<ObjectGuid, CombatReference> _pveRefs = new();
Dictionary<ObjectGuid, PvPCombatReference> _pvpRefs = new();
public CombatManager(Unit owner)
{
_owner = owner;
}
public static bool CanBeginCombat(Unit a, Unit b)
{
// Checks combat validity before initial reference creation.
// For the combat to be valid...
// ...the two units need to be different
if (a == b)
return false;
// ...the two units need to be in the world
if (!a.IsInWorld || !b.IsInWorld)
return false;
// ...the two units need to both be alive
if (!a.IsAlive() || !b.IsAlive())
return false;
// ...the two units need to be on the same map
if (a.GetMap() != b.GetMap())
return false;
// ...the two units need to be in the same phase
if (!WorldObject.InSamePhase(a, b))
return false;
if (a.HasUnitState(UnitState.Evade) || b.HasUnitState(UnitState.Evade))
return false;
if (a.HasUnitState(UnitState.InFlight) || b.HasUnitState(UnitState.InFlight))
return false;
if (a.IsControlledByPlayer() || b.IsControlledByPlayer())
{
// PvSomething, only block friendly fire
if (a.IsFriendlyTo(b) || b.IsFriendlyTo(a))
return false;
}
else
{
// CvC, need hostile reaction to start a fight
if (!a.IsHostileTo(b) && !b.IsHostileTo(a))
return false;
}
Player playerA = a.GetCharmerOrOwnerPlayerOrPlayerItself();
Player playerB = b.GetCharmerOrOwnerPlayerOrPlayerItself();
// ...neither of the two units must be (owned by) a player with .gm on
if ((playerA && playerA.IsGameMaster()) || (playerB && playerB.IsGameMaster()))
return false;
return true;
}
public void Update(uint tdiff)
{
foreach(var pair in _pvpRefs.ToList())
{
PvPCombatReference refe = pair.Value;
if (refe.first == _owner && !refe.Update(tdiff)) // only update if we're the first unit involved (otherwise double decrement)
{
_pvpRefs.Remove(pair.Key);
refe.EndCombat(); // this will remove it from the other side
}
}
}
public bool HasPvPCombat()
{
foreach (var pair in _pvpRefs)
if (!pair.Value.IsSuppressedFor(_owner))
return true;
return false;
}
Unit GetAnyTarget()
{
if (!_pveRefs.Empty())
return _pveRefs.First().Value.GetOther(_owner);
foreach (var pair in _pvpRefs)
if (!pair.Value.IsSuppressedFor(_owner))
return pair.Value.GetOther(_owner);
return null;
}
public bool SetInCombatWith(Unit who)
{
// Are we already in combat? If yes, refresh pvp combat
var pvpRefe = _pvpRefs.LookupByKey(who.GetGUID());
if (pvpRefe != null)
{
pvpRefe.Refresh();
return true;
}
else if (_pveRefs.ContainsKey(who.GetGUID()))
return true;
// Otherwise, check validity...
if (!CombatManager.CanBeginCombat(_owner, who))
return false;
// ...then create new reference
CombatReference refe;
if (_owner.IsControlledByPlayer() && who.IsControlledByPlayer())
refe = new PvPCombatReference(_owner, who);
else
refe = new CombatReference(_owner, who);
// ...and insert it into both managers
PutReference(who.GetGUID(), refe);
who.GetCombatManager().PutReference(_owner.GetGUID(), refe);
// now, sequencing is important - first we update the combat state, which will set both units in combat and do non-AI combat start stuff
bool needSelfAI = UpdateOwnerCombatState();
bool needOtherAI = who.GetCombatManager().UpdateOwnerCombatState();
// then, we finally notify the AI (if necessary) and let it safely do whatever it feels like
if (needSelfAI)
NotifyAICombat(_owner, who);
if (needOtherAI)
NotifyAICombat(who, _owner);
return true;
}
public bool IsInCombatWith(ObjectGuid guid)
{
return _pveRefs.ContainsKey(guid) || _pvpRefs.ContainsKey(guid);
}
public bool IsInCombatWith(Unit who)
{
return IsInCombatWith(who.GetGUID());
}
public void InheritCombatStatesFrom(Unit who)
{
CombatManager mgr = who.GetCombatManager();
foreach (var refe in mgr._pveRefs)
{
if (!IsInCombatWith(refe.Key))
{
Unit target = refe.Value.GetOther(who);
if ((_owner.HasUnitFlag(UnitFlags.ImmuneToPc) && target.HasUnitFlag(UnitFlags.PvpAttackable)) ||
(_owner.HasUnitFlag(UnitFlags.ImmuneToNpc) && !target.HasUnitFlag(UnitFlags.PvpAttackable)))
continue;
SetInCombatWith(target);
}
}
foreach (var refe in mgr._pvpRefs)
{
if (!IsInCombatWith(refe.Key))
{
Unit target = refe.Value.GetOther(who);
if ((_owner.HasUnitFlag(UnitFlags.ImmuneToPc) && target.HasUnitFlag(UnitFlags.PvpAttackable)) ||
(_owner.HasUnitFlag(UnitFlags.ImmuneToNpc) && !target.HasUnitFlag(UnitFlags.PvpAttackable)))
continue;
SetInCombatWith(target);
}
}
}
public void EndCombatBeyondRange(float range, bool includingPvP)
{
foreach (var pair in _pveRefs.ToList())
{
CombatReference refe = pair.Value;
if (!refe.first.IsWithinDistInMap(refe.second, range))
{
_pveRefs.Remove(pair.Key);
refe.EndCombat();
}
}
if (!includingPvP)
return;
foreach (var pair in _pvpRefs.ToList())
{
CombatReference refe = pair.Value;
if (!refe.first.IsWithinDistInMap(refe.second, range))
{
_pvpRefs.Remove(pair.Key);
refe.EndCombat();
}
}
}
public void SuppressPvPCombat()
{
foreach (var pair in _pvpRefs)
pair.Value.Suppress(_owner);
if (UpdateOwnerCombatState())
if (_owner.IsAIEnabled)
_owner.GetAI().JustExitedCombat();
}
public void EndAllPvECombat()
{
// cannot have threat without combat
_owner.GetThreatManager().RemoveMeFromThreatLists();
_owner.GetThreatManager().ClearAllThreat();
while (!_pveRefs.Empty())
_pveRefs.First().Value.EndCombat();
}
void EndAllPvPCombat()
{
while (!_pvpRefs.Empty())
_pvpRefs.First().Value.EndCombat();
}
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);
}
void PutReference(ObjectGuid guid, CombatReference refe)
{
if (refe._isPvP)
{
Cypher.Assert(!_pvpRefs.ContainsKey(guid), "Duplicate combat state detected!");
_pvpRefs[guid] = (PvPCombatReference)refe;
}
else
{
Cypher.Assert(!_pveRefs.ContainsKey(guid), "Duplicate combat state detected!");
_pveRefs[guid] = refe;
}
}
public void PurgeReference(ObjectGuid guid, bool pvp)
{
if (pvp)
_pvpRefs.Remove(guid);
else
_pveRefs.Remove(guid);
}
public bool UpdateOwnerCombatState()
{
bool combatState = HasCombat();
if (combatState == _owner.IsInCombat())
return false;
if (combatState)
{
_owner.AddUnitFlag(UnitFlags.InCombat);
_owner.AtEnterCombat();
}
else
{
_owner.RemoveUnitFlag(UnitFlags.InCombat);
_owner.AtExitCombat();
}
Unit master = _owner.GetCharmerOrOwner();
if (master != null)
master.UpdatePetCombatState();
return true;
}
Unit GetOwner() { return _owner; }
public bool HasCombat() { return HasPvECombat() || HasPvPCombat(); }
public bool HasPvECombat() { return !_pveRefs.Empty(); }
public Dictionary<ObjectGuid, CombatReference> GetPvECombatRefs() { return _pveRefs; }
public Dictionary<ObjectGuid, PvPCombatReference> GetPvPCombatRefs() { return _pvpRefs; }
public void EndAllCombat()
{
EndAllPvECombat();
EndAllPvPCombat();
}
}
public class CombatReference
{
public Unit first;
public Unit second;
public bool _isPvP;
public CombatReference(Unit a, Unit b, bool pvp = false)
{
first = a;
second = b;
_isPvP = pvp;
}
public void EndCombat()
{
// sequencing matters here - AI might do nasty stuff, so make sure refs are in a consistent state before you hand off!
// first, get rid of any threat that still exists...
first.GetThreatManager().ClearThreat(second);
second.GetThreatManager().ClearThreat(first);
// ...then, remove the references from both managers...
first.GetCombatManager().PurgeReference(second.GetGUID(), _isPvP);
second.GetCombatManager().PurgeReference(first.GetGUID(), _isPvP);
// ...update the combat state, which will potentially remove IN_COMBAT...
bool needFirstAI = first.GetCombatManager().UpdateOwnerCombatState();
bool needSecondAI = second.GetCombatManager().UpdateOwnerCombatState();
// ...and if that happened, also notify the AI of it...
if (needFirstAI && first.IsAIEnabled)
first.GetAI().JustExitedCombat();
if (needSecondAI && second.IsAIEnabled)
second.GetAI().JustExitedCombat();
}
public Unit GetOther(Unit me) { return (first == me) ? second : first; }
}
public class PvPCombatReference : CombatReference
{
public static uint PVP_COMBAT_TIMEOUT = 5 * Time.InMilliseconds;
uint _combatTimer = PVP_COMBAT_TIMEOUT;
bool _suppressFirst = false;
bool _suppressSecond = false;
public PvPCombatReference(Unit first, Unit second) : base(first, second, true) { }
public bool Update(uint tdiff)
{
if (_combatTimer <= tdiff)
return false;
_combatTimer -= tdiff;
return true;
}
public void Refresh()
{
_combatTimer = PVP_COMBAT_TIMEOUT;
bool needFirstAI = false, needSecondAI = false;
if (_suppressFirst)
{
_suppressFirst = false;
needFirstAI = first.GetCombatManager().UpdateOwnerCombatState();
}
if (_suppressSecond)
{
_suppressSecond = false;
needSecondAI = second.GetCombatManager().UpdateOwnerCombatState();
}
if (needFirstAI)
CombatManager.NotifyAICombat(first, second);
if (needSecondAI)
CombatManager.NotifyAICombat(second, first);
}
void SuppressFor(Unit who)
{
Suppress(who);
if (who.GetCombatManager().UpdateOwnerCombatState())
if (who.IsAIEnabled)
who.GetAI().JustExitedCombat();
}
// suppressed combat refs do not generate a combat state for one side of the relation
// (used by: vanish, feign death)
public bool IsSuppressedFor(Unit who) { return (who == first) ? _suppressFirst : _suppressSecond; }
public void Suppress(Unit who)
{
if (who == first)
_suppressFirst = true;
else
_suppressSecond = true;
}
}
}
-135
View File
@@ -1,135 +0,0 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
namespace Game.Combat
{
public class UnitBaseEvent
{
public UnitBaseEvent(UnitEventTypes pType)
{
iType = pType;
}
public UnitEventTypes GetEventType()
{
return iType;
}
bool MatchesTypeMask(uint pMask)
{
return Convert.ToBoolean((uint)iType & pMask);
}
void SetEventType(UnitEventTypes pType)
{
iType = pType;
}
UnitEventTypes iType;
}
public class ThreatRefStatusChangeEvent : UnitBaseEvent
{
public ThreatRefStatusChangeEvent(UnitEventTypes pType)
: base(pType)
{
iHostileReference = null;
}
public ThreatRefStatusChangeEvent(UnitEventTypes pType, HostileReference pHostileReference)
: base(pType)
{
iHostileReference = pHostileReference;
}
public ThreatRefStatusChangeEvent(UnitEventTypes pType, HostileReference pHostileReference, float pValue)
: base(pType)
{
iHostileReference = pHostileReference;
iFValue = pValue;
}
public ThreatRefStatusChangeEvent(UnitEventTypes pType, HostileReference pHostileReference, bool pValue)
: base(pType)
{
iHostileReference = pHostileReference;
iBValue = pValue;
}
public float GetFValue()
{
return iFValue;
}
bool GetBValue()
{
return iBValue;
}
void SetBValue(bool pValue)
{
iBValue = pValue;
}
public HostileReference GetReference()
{
return iHostileReference;
}
public void SetThreatManager(ThreatManager pThreatManager)
{
iThreatManager = pThreatManager;
}
ThreatManager GetThreatManager()
{
return iThreatManager;
}
float iFValue;
bool iBValue;
HostileReference iHostileReference;
ThreatManager iThreatManager;
}
public enum UnitEventTypes
{
// Player/Pet Changed On/Offline Status
ThreatRefOnlineStatus = 1 << 0,
// Threat For Player/Pet Changed
ThreatRefThreatChange = 1 << 1,
// Player/Pet Will Be Removed From List (Dead) [For Internal Use]
ThreatRefRemoveFromList = 1 << 2,
// Player/Pet Entered/Left Water Or Some Other Place Where It Is/Was Not Accessible For The Creature
ThreatRefAccessibleStatus = 1 << 3,
// Threat List Is Going To Be Sorted (If Dirty Flag Is Set)
ThreatSortList = 1 << 4,
// New Target Should Be Fetched, Could Tbe The Current Target As Well
ThreatSetNextTarget = 1 << 5,
// A New Victim (Target) Was Set. Could Be Null
ThreatVictimChanged = 1 << 6
// Future Use
//Unit_Killed = 1<<7,
//Future Use
//Unit_Health_Change = 1<<8,
}
}
-398
View File
@@ -1,398 +0,0 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Framework.Dynamic;
using Game.Entities;
using Game.Spells;
namespace Game.Combat
{
public class HostileRefManager : RefManager<Unit, ThreatManager>
{
Unit Owner;
public HostileRefManager(Unit owner)
{
Owner = owner;
}
Unit GetOwner() { return Owner; }
// send threat to all my haters for the victim
// The victim is then hated by them as well
// use for buffs and healing threat functionality
public void ThreatAssist(Unit victim, float baseThreat, SpellInfo threatSpell = null)
{
float threat = ThreatManager.CalcThreat(victim, Owner, baseThreat, (threatSpell != null ? threatSpell.GetSchoolMask() : SpellSchoolMask.Normal), threatSpell);
threat /= GetSize();
HostileReference refe = GetFirst();
while (refe != null)
{
if (ThreatManager.IsValidProcess(victim, refe.GetSource().GetOwner(), threatSpell))
refe.GetSource().DoAddThreat(victim, threat);
refe = refe.Next();
}
}
public void AddTempThreat(float threat, bool apply)
{
HostileReference refe = GetFirst();
while (refe != null)
{
if (apply)
{
if (refe.GetTempThreatModifier() == 0.0f)
refe.AddTempThreat(threat);
}
else
refe.ResetTempThreat();
refe = refe.Next();
}
}
void AddThreatPercent(int percent)
{
HostileReference refe = GetFirst();
while (refe != null)
{
refe.AddThreatPercent(percent);
refe = refe.Next();
}
}
// The references are not needed anymore
// tell the source to remove them from the list and free the mem
public void DeleteReferences()
{
HostileReference refe = GetFirst();
while (refe != null)
{
HostileReference nextRef = refe.Next();
refe.RemoveReference();
refe = nextRef;
}
}
// Remove specific faction references
public void DeleteReferencesForFaction(uint faction)
{
HostileReference refe = GetFirst();
while (refe != null)
{
HostileReference nextRef = refe.Next();
if (refe.GetSource().GetOwner().GetFactionTemplateEntry().Faction == faction)
{
refe.RemoveReference();
}
refe = nextRef;
}
}
// delete all references out of specified range
public void DeleteReferencesOutOfRange(float range)
{
HostileReference refe = GetFirst();
range = range * range;
while (refe != null)
{
HostileReference nextRef = refe.Next();
Unit owner = refe.GetSource().GetOwner();
if (!owner.IsActiveObject() && owner.GetExactDist2dSq(GetOwner()) > range)
{
refe.RemoveReference();
}
refe = nextRef;
}
}
public new HostileReference GetFirst() { return (HostileReference)base.GetFirst(); }
public void UpdateThreatTables()
{
HostileReference refe = GetFirst();
while (refe != null)
{
refe.UpdateOnlineStatus();
refe = refe.Next();
}
}
public void SetOnlineOfflineState(bool isOnline)
{
HostileReference refe = GetFirst();
while (refe != null)
{
refe.SetOnlineOfflineState(isOnline);
refe = refe.Next();
}
}
// set state for one reference, defined by Unit
public void SetOnlineOfflineState(Unit creature, bool isOnline)
{
HostileReference refe = GetFirst();
while (refe != null)
{
HostileReference nextRef = refe.Next();
if (refe.GetSource().GetOwner() == creature)
{
refe.SetOnlineOfflineState(isOnline);
break;
}
refe = nextRef;
}
}
// delete one reference, defined by Unit
public void DeleteReference(Unit creature)
{
HostileReference refe = GetFirst();
while (refe != null)
{
HostileReference nextRef = refe.Next();
if (refe.GetSource().GetOwner() == creature)
{
refe.RemoveReference();
break;
}
refe = nextRef;
}
}
public void UpdateVisibility()
{
HostileReference refe = GetFirst();
while (refe != null)
{
HostileReference nextRef = refe.Next();
if (!refe.GetSource().GetOwner().CanSeeOrDetect(GetOwner()))
{
nextRef = refe.Next();
refe.RemoveReference();
}
refe = nextRef;
}
}
}
public class HostileReference : Reference<Unit, ThreatManager>
{
public HostileReference(Unit refUnit, ThreatManager threatManager, float threat)
{
iThreat = threat;
iTempThreatModifier = 0.0f;
Link(refUnit, threatManager);
iUnitGuid = refUnit.GetGUID();
iOnline = true;
iAccessible = true;
}
public override void TargetObjectBuildLink()
{
GetTarget().AddHatedBy(this);
}
public override void TargetObjectDestroyLink()
{
GetTarget().RemoveHatedBy(this);
}
public override void SourceObjectDestroyLink()
{
SetOnlineOfflineState(false);
}
void FireStatusChanged(ThreatRefStatusChangeEvent threatRefStatusChangeEvent)
{
if (GetSource() != null)
GetSource().ProcessThreatEvent(threatRefStatusChangeEvent);
}
public void AddThreat(float modThreat)
{
if (modThreat == 0.0f)
return;
iThreat += modThreat;
// the threat is changed. Source and target unit have to be available
// if the link was cut before relink it again
if (!IsOnline())
UpdateOnlineStatus();
ThreatRefStatusChangeEvent Event = new(UnitEventTypes.ThreatRefThreatChange, this, modThreat);
FireStatusChanged(Event);
if (IsValid() && modThreat > 0.0f)
{
Unit victimOwner = GetTarget().GetCharmerOrOwner();
if (victimOwner != null && victimOwner.IsAlive())
GetSource().AddThreat(victimOwner, 0.0f); // create a threat to the owner of a pet, if the pet attacks
}
}
public void AddThreatPercent(int percent)
{
AddThreat(MathFunctions.CalculatePct(iThreat, percent));
}
// check, if source can reach target and set the status
public void UpdateOnlineStatus()
{
bool online = false;
bool accessible = false;
if (!IsValid())
{
Unit target = Global.ObjAccessor.GetUnit(GetSourceUnit(), GetUnitGuid());
if (target != null)
Link(target, GetSource());
}
// only check for online status if
// ref is valid
// target is no player or not gamemaster
// target is not in flight
if (IsValid()
&& (GetTarget().IsTypeId(TypeId.Player) || !GetTarget().ToPlayer().IsGameMaster())
&& !GetTarget().HasUnitState(UnitState.InFlight)
&& GetTarget().IsInMap(GetSourceUnit())
&& GetTarget().IsInPhase(GetSourceUnit())
)
{
Creature creature = GetSourceUnit().ToCreature();
online = GetTarget().IsInAccessiblePlaceFor(creature);
if (!online)
{
if (creature.IsWithinCombatRange(GetTarget(), creature.m_CombatDistance))
online = true; // not accessible but stays online
}
else
accessible = true;
}
SetAccessibleState(accessible);
SetOnlineOfflineState(online);
}
public void SetOnlineOfflineState(bool isOnline)
{
if (iOnline != isOnline)
{
iOnline = isOnline;
if (!iOnline)
SetAccessibleState(false); // if not online that not accessable as well
ThreatRefStatusChangeEvent Event = new(UnitEventTypes.ThreatRefOnlineStatus, this);
FireStatusChanged(Event);
}
}
void SetAccessibleState(bool isAccessible)
{
if (iAccessible != isAccessible)
{
iAccessible = isAccessible;
ThreatRefStatusChangeEvent Event = new(UnitEventTypes.ThreatRefAccessibleStatus, this);
FireStatusChanged(Event);
}
}
// reference is not needed anymore. realy delete it !
public void RemoveReference()
{
Invalidate();
ThreatRefStatusChangeEvent Event = new(UnitEventTypes.ThreatRefRemoveFromList, this);
FireStatusChanged(Event);
}
Unit GetSourceUnit()
{
return GetSource().GetOwner();
}
public void SetThreat(float threat)
{
AddThreat(threat - iThreat);
}
public float GetThreat()
{
return iThreat + iTempThreatModifier;
}
public bool IsOnline()
{
return iOnline;
}
// The Unit might be in water and the creature can not enter the water, but has range attack
// in this case online = true, but accessible = false
bool IsAccessible()
{
return iAccessible;
}
// used for temporary setting a threat and reducing it later again.
// the threat modification is stored
public void SetTempThreat(float threat)
{
AddTempThreat(threat - iTempThreatModifier);
}
public void AddTempThreat(float threat)
{
if (threat == 0.0f)
return;
iTempThreatModifier += threat;
ThreatRefStatusChangeEvent Event = new(UnitEventTypes.ThreatRefThreatChange, this, threat);
FireStatusChanged(Event);
}
public void ResetTempThreat()
{
AddTempThreat(-iTempThreatModifier);
}
public float GetTempThreatModifier()
{
return iTempThreatModifier;
}
public ObjectGuid GetUnitGuid()
{
return iUnitGuid;
}
public Unit GetVictim() { return GetTarget(); }
public new HostileReference Next() { return (HostileReference)base.Next(); }
float iThreat;
float iTempThreatModifier; // used for SPELL_AURA_MOD_TOTAL_THREAT
ObjectGuid iUnitGuid;
bool iOnline;
bool iAccessible;
}
}
File diff suppressed because it is too large Load Diff