diff --git a/Source/Game/AI/CoreAI/CreatureAI.cs b/Source/Game/AI/CoreAI/CreatureAI.cs index 4e7868418..a9e6dd4f4 100644 --- a/Source/Game/AI/CoreAI/CreatureAI.cs +++ b/Source/Game/AI/CoreAI/CreatureAI.cs @@ -102,16 +102,8 @@ namespace Game.AI { creature.SetInCombatWith(player); player.SetInCombatWith(creature); - creature.AddThreat(player, 0.0f); + creature.GetThreatManager().AddThreat(player, 0.0f, null, true, true); } - - /* Causes certain things to never leave the threat list (Priest Lightwell, etc): - foreach (var unit in player.m_Controlled) - { - me.SetInCombatWith(unit); - unit.SetInCombatWith(me); - me.AddThreat(unit, 0.0f); - }*/ } } @@ -119,6 +111,7 @@ namespace Game.AI { if (MoveInLineOfSight_locked) return; + MoveInLineOfSight_locked = true; MoveInLineOfSight(who); MoveInLineOfSight_locked = false; @@ -126,11 +119,11 @@ namespace Game.AI public virtual void MoveInLineOfSight(Unit who) { - if (me.GetVictim() != null) + if (me.IsEngaged()) return; if (me.HasReactState(ReactStates.Aggressive) && me.CanStartAttack(who, false)) - AttackStart(who); + me.EngageWithTarget(who); } void _OnOwnerCombatInteraction(Unit target) @@ -139,12 +132,7 @@ namespace Game.AI return; if (!me.HasReactState(ReactStates.Passive) && me.CanStartAttack(target, true)) - { - if (me.IsInCombat()) - me.AddThreat(target, 0.0f); - else - AttackStart(target); - } + me.EngageWithTarget(target); } // Distract creature, if player gets too close while stealthed/prowling @@ -154,8 +142,8 @@ namespace Game.AI if (!who || !who.IsTypeId(TypeId.Player)) return; - // If this unit isn't an NPC, is already distracted, is in combat, is confused, stunned or fleeing, do nothing - if (!me.IsTypeId(TypeId.Unit) || me.IsInCombat() || me.HasUnitState(UnitState.Confused | UnitState.Stunned | UnitState.Fleeing | UnitState.Distracted)) + // If this unit isn't an NPC, is already distracted, is fighting, is confused, stunned or fleeing, do nothing + if (!me.IsTypeId(TypeId.Unit) || me.IsEngaged() || me.HasUnitState(UnitState.Confused | UnitState.Stunned | UnitState.Fleeing | UnitState.Distracted)) return; // Only alert for hostiles! @@ -214,7 +202,7 @@ namespace Game.AI public bool UpdateVictimWithGaze() { - if (!me.IsInCombat()) + if (!me.IsEngaged()) return false; if (me.HasReactState(ReactStates.Passive)) @@ -237,7 +225,7 @@ namespace Game.AI public bool UpdateVictim() { - if (!me.IsInCombat()) + if (!me.IsEngaged()) return false; if (!me.HasReactState(ReactStates.Passive)) @@ -266,7 +254,7 @@ namespace Game.AI me.RemoveAurasOnEvade(); // sometimes bosses stuck in combat? - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); me.CombatStop(true); me.SetLootRecipient(null); me.ResetPlayerDamageReq(); @@ -410,7 +398,10 @@ namespace Game.AI me.DoImmediateBoundaryCheck(); } - // Called for reaction at enter to combat if not in combat yet (enemy can be NULL) + /// + /// Called for reaction when initially engaged + /// + /// public virtual void EnterCombat(Unit victim) { } // Called when the creature is killed diff --git a/Source/Game/AI/CoreAI/GuardAI.cs b/Source/Game/AI/CoreAI/GuardAI.cs index 6a360455c..3d5c34764 100644 --- a/Source/Game/AI/CoreAI/GuardAI.cs +++ b/Source/Game/AI/CoreAI/GuardAI.cs @@ -34,12 +34,9 @@ namespace Game.AI public override bool CanSeeAlways(WorldObject obj) { - if (!obj.IsTypeMask(TypeMask.Unit)) - return false; - - var threatList = me.GetThreatManager().GetThreatList(); - foreach (var refe in threatList) - if (refe.GetUnitGuid() == obj.GetGUID()) + Unit unit = obj.ToUnit(); + if (unit != null) + if (unit.IsControlledByPlayer() && me.IsEngagedBy(unit)) return true; return false; @@ -51,14 +48,14 @@ namespace Game.AI { me.GetMotionMaster().MoveIdle(); me.CombatStop(true); - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); return; } Log.outDebug(LogFilter.Unit, "Guard entry: {0} enters evade mode.", me.GetEntry()); me.RemoveAllAuras(); - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); me.CombatStop(true); // Remove ChaseMovementGenerator from MotionMaster stack list, and add HomeMovementGenerator instead diff --git a/Source/Game/AI/CoreAI/UnitAI.cs b/Source/Game/AI/CoreAI/UnitAI.cs index 8553846db..014b43fe6 100644 --- a/Source/Game/AI/CoreAI/UnitAI.cs +++ b/Source/Game/AI/CoreAI/UnitAI.cs @@ -57,9 +57,9 @@ namespace Game.AI return me.GetThreatManager(); } - void SortByDistanceTo(Unit reference, List targets) + void SortByDistance(List targets, bool ascending) { - targets.Sort(new ObjectDistanceOrderPred(reference)); + targets.Sort(new ObjectDistanceOrderPred(me, true)); } public void DoMeleeAttackIfReady() @@ -113,100 +113,187 @@ namespace Game.AI return false; } - public Unit SelectTarget(SelectAggroTarget targetType, uint position = 0, float dist = 0.0f, bool playerOnly = false, int aura = 0) + /// + /// Select the best target (in order) from the threat list that fulfill the following: + /// - Not among the first entries in order (or MAXTHREAT order, if is RANDOM). + /// - Within at most yards (if dist > 0.0f) + /// - At least - yards away (if dist < 0.0f) + /// - Is a player (if playerOnly = true) + /// - Not the current tank (if withTank = false) + /// - Has aura with ID (if aura > 0) + /// - Does not have aura with ID - (if aura < 0) + /// + public Unit SelectTarget(SelectAggroTarget targetType, uint offset = 0, float dist = 0.0f, bool playerOnly = false, bool withTank = true, int aura = 0) { - return SelectTarget(targetType, position, new DefaultTargetSelector(me, dist, playerOnly, aura)); + return SelectTarget(targetType, offset, new DefaultTargetSelector(me, dist, playerOnly, withTank, aura)); } - // Select the targets satifying the predicate. - public Unit SelectTarget(SelectAggroTarget targetType, uint position, ISelector selector) + /// + /// Select the best target (in order) satisfying from the threat list. + /// If is nonzero, the first entries in order (or MAXTHREAT order, if is RANDOM) are skipped. + /// + public Unit SelectTarget(SelectAggroTarget targetType, uint offset, ISelector selector) { - var threatlist = GetThreatManager().GetThreatList(); - if (position >= threatlist.Count) + ThreatManager mgr = GetThreatManager(); + // shortcut: if we ignore the first elements, and there are at most elements, then we ignore ALL elements + if (mgr.GetThreatListSize() <= offset) return null; List targetList = new List(); - Unit currentVictim = null; - - var currentVictimReference = GetThreatManager().GetCurrentVictim(); - if (currentVictimReference != null) + if (targetType == SelectAggroTarget.MaxDistance || targetType == SelectAggroTarget.MinDistance) { - currentVictim = currentVictimReference.GetTarget(); + foreach (HostileReference refe in mgr.GetThreatList()) + { + if (!refe.IsOnline()) + continue; - // Current victim always goes first - if (currentVictim && selector.Check(currentVictim)) + targetList.Add(refe.GetTarget()); + } + } + else + { + Unit currentVictim = mgr.GetCurrentVictim(); + if (currentVictim) targetList.Add(currentVictim); + + foreach (HostileReference refe in mgr.GetThreatList()) + { + if (!refe.IsOnline()) + continue; + + Unit thisTarget = refe.GetTarget(); + if (thisTarget != currentVictim) + targetList.Add(thisTarget); + } } - foreach (var hostileRef in threatlist) - { - if (currentVictim != null && hostileRef.GetTarget() != currentVictim && selector.Check(hostileRef.GetTarget())) - targetList.Add(hostileRef.GetTarget()); - else if (currentVictim == null && selector.Check(hostileRef.GetTarget())) - targetList.Add(hostileRef.GetTarget()); - } - - if (position >= targetList.Count) + // filter by predicate + targetList.RemoveAll(target => { return !selector.Check(target); }); + // shortcut: the list certainly isn't gonna get any larger after this point + if (targetList.Count <= offset) return null; - if (targetType == SelectAggroTarget.Nearest || targetType == SelectAggroTarget.Farthest) - SortByDistanceTo(me, targetList); + // right now, list is unsorted for DISTANCE types - re-sort by MAXDISTANCE + if (targetType == SelectAggroTarget.MaxDistance || targetType == SelectAggroTarget.MinDistance) + SortByDistance(targetList, targetType == SelectAggroTarget.MinDistance); + + // then reverse the sorting for MIN sortings + if (targetType == SelectAggroTarget.MinThreat) + targetList.Reverse(); + + // now pop the first elements + while (offset != 0) + { + targetList.RemoveAt(0); + --offset; + } + + // maybe nothing fulfills the predicate + if (targetList.Empty()) + return null; switch (targetType) { - case SelectAggroTarget.Nearest: - case SelectAggroTarget.TopAggro: - { - return targetList.First(); - } - case SelectAggroTarget.Farthest: - case SelectAggroTarget.BottomAggro: - { - return targetList.Last(); - } + case SelectAggroTarget.MaxThreat: + case SelectAggroTarget.MinThreat: + case SelectAggroTarget.MaxDistance: + case SelectAggroTarget.MinDistance: + return targetList[0]; case SelectAggroTarget.Random: - { return targetList.SelectRandom(); - } default: - break; + return null; } - - return null; } - public List SelectTargetList(uint num, SelectAggroTarget targetType, float dist, bool playerOnly, int aura = 0) + /// + /// Select the best (up to) targets (in order) from the threat list that fulfill the following: + /// - Not among the first entries in order (or MAXTHREAT order, if is RANDOM). + /// - Within at most yards (if dist > 0.0f) + /// - At least - yards away (if dist < 0.0f) + /// - Is a player (if playerOnly = true) + /// - Not the current tank (if withTank = false) + /// - Has aura with ID (if aura > 0) + /// - Does not have aura with ID - (if aura < 0) + /// The resulting targets are stored in (which is cleared first). + /// + public List SelectTargetList(uint num, SelectAggroTarget targetType, uint offset, float dist, bool playerOnly, bool withTank, int aura = 0) { - return SelectTargetList(new DefaultTargetSelector(me, dist, playerOnly, aura), num, targetType); + return SelectTargetList(num, targetType, offset, new DefaultTargetSelector(me, dist, playerOnly, withTank, aura)); } - // Select the targets satifying the predicate. - // predicate shall extend std.unary_function - public List SelectTargetList(ISelector selector, uint maxTargets, SelectAggroTarget targetType) + /// + /// Select the best (up to) targets (in order) satisfying from the threat list and stores them in (which is cleared first). + /// If is nonzero, the first entries in order (or MAXTHREAT order, if is RANDOM) are skipped. + /// + public List SelectTargetList(uint num, SelectAggroTarget targetType, uint offset, ISelector selector) { var targetList = new List(); - var threatlist = GetThreatManager().GetThreatList(); - if (threatlist.Empty()) + ThreatManager mgr = GetThreatManager(); + // shortcut: we're gonna ignore the first elements, and there's at most elements, so we ignore them all - nothing to do here + if (mgr.GetThreatListSize() <= offset) return targetList; - foreach (var hostileRef in threatlist) - if (selector.Check(hostileRef.GetTarget())) - targetList.Add(hostileRef.GetTarget()); + if (targetType == SelectAggroTarget.MaxDistance || targetType == SelectAggroTarget.MinDistance) + { + foreach (HostileReference refe in mgr.GetThreatList()) + { + if (!refe.IsOnline()) + continue; - if (targetList.Count < maxTargets) + targetList.Add(refe.GetTarget()); + } + } + else + { + Unit currentVictim = mgr.GetCurrentVictim(); + if (currentVictim != null) + targetList.Add(currentVictim); + + foreach (HostileReference refe in mgr.GetThreatList()) + { + if (!refe.IsOnline()) + continue; + + Unit thisTarget = refe.GetTarget(); + if (thisTarget != currentVictim) + targetList.Add(thisTarget); + } + } + + // filter by predicate + targetList.RemoveAll(target => { return !selector.Check(target); }); + + // shortcut: the list isn't gonna get any larger + if (targetList.Count <= offset) + { + targetList.Clear(); return targetList; + } - if (targetType == SelectAggroTarget.Nearest || targetType == SelectAggroTarget.Farthest) - SortByDistanceTo(me, targetList); + // right now, list is unsorted for DISTANCE types - re-sort by MAXDISTANCE + if (targetType == SelectAggroTarget.MaxDistance || targetType == SelectAggroTarget.MinDistance) + SortByDistance(targetList, targetType == SelectAggroTarget.MinDistance); - if (targetType == SelectAggroTarget.Farthest || targetType == SelectAggroTarget.BottomAggro) + // now the list is MAX sorted, reverse for MIN types + if (targetType == SelectAggroTarget.MinThreat) targetList.Reverse(); + // ignore the first elements + while (offset != 0) + { + targetList.RemoveAt(0); + --offset; + } + + if (targetList.Count <= num) + return targetList; + if (targetType == SelectAggroTarget.Random) - targetList = targetList.SelectRandom(maxTargets).ToList(); + targetList = targetList.SelectRandom(num).ToList(); else - targetList.Resize(maxTargets); + targetList.Resize(num); return targetList; } @@ -247,7 +334,7 @@ namespace Game.AI bool playerOnly = spellInfo.HasAttribute(SpellAttr3.OnlyTargetPlayers); float range = spellInfo.GetMaxRange(false); - DefaultTargetSelector targetSelector = new DefaultTargetSelector(me, range, playerOnly, -(int)spellId); + DefaultTargetSelector targetSelector = new DefaultTargetSelector(me, range, playerOnly, true, -(int)spellId); if (!spellInfo.HasAuraInterruptFlag(SpellAuraInterruptFlags.NotVictim) && targetSelector.Check(me.GetVictim())) target = me.GetVictim(); @@ -498,11 +585,11 @@ namespace Game.AI public enum SelectAggroTarget { - Random = 0, //Just selects a random target - TopAggro, //Selects targes from top aggro to bottom - BottomAggro, //Selects targets from bottom aggro to top - Nearest, - Farthest + Random = 0, // just pick a random target + MaxThreat, // prefer targets higher in the threat list + MinThreat, // prefer targets lower in the threat list + MaxDistance, // prefer targets further from us + MinDistance // prefer targets closer to us } public interface ISelector @@ -516,29 +603,34 @@ namespace Game.AI Unit me; float m_dist; bool m_playerOnly; + Unit except; int m_aura; - // unit: the reference unit - // dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit - // playerOnly: self explaining - // aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura - public DefaultTargetSelector(Unit unit, float dist, bool playerOnly, int aura) + /// the reference unit + /// if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit + /// self explaining + /// allow current tank to be selected + /// if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura + public DefaultTargetSelector(Unit unit, float dist, bool playerOnly, bool withMainTank, int aura) { me = unit; m_dist = dist; m_playerOnly = playerOnly; + except = withMainTank ? me.GetThreatManager().GetCurrentVictim() : null; m_aura = aura; } public bool Check(Unit target) { - if (me == null) return false; if (target == null) return false; + if (target == except) + return false; + if (m_playerOnly && !target.IsTypeId(TypeId.Player)) return false; @@ -665,9 +757,9 @@ namespace Game.AI if (_playerOnly && !target.IsTypeId(TypeId.Player)) return false; - HostileReference currentVictim = _source.GetThreatManager().GetCurrentVictim(); + Unit currentVictim = _source.GetThreatManager().GetCurrentVictim(); if (currentVictim != null) - return target.GetGUID() != currentVictim.GetUnitGuid(); + return target != currentVictim; return target != _source.GetVictim(); } diff --git a/Source/Game/AI/PlayerAI/PlayerAI.cs b/Source/Game/AI/PlayerAI/PlayerAI.cs index dbb833a81..344ed83d5 100644 --- a/Source/Game/AI/PlayerAI/PlayerAI.cs +++ b/Source/Game/AI/PlayerAI/PlayerAI.cs @@ -1243,7 +1243,7 @@ namespace Game.AI } } - if (charmer.IsInCombat()) + if (charmer.IsEngaged()) { Unit target = me.GetVictim(); if (!target || !charmer.IsValidAttackTarget(target) || target.HasBreakableByDamageCrowdControlAura()) diff --git a/Source/Game/AI/ScriptedAI/ScriptedAI.cs b/Source/Game/AI/ScriptedAI/ScriptedAI.cs index 4ad76ae80..557e5f681 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedAI.cs @@ -111,6 +111,85 @@ namespace Game.AI source.PlayDirectSound(soundId); } + /// + /// Add specified amount of threat directly to victim (ignores redirection effects) - also puts victim in combat and engages them if necessary + /// + /// + /// + /// + public void AddThreat(Unit victim, float amount, Unit who = null) + { + if (!victim) + return; + + if (!who) + who = me; + + who.GetThreatManager().AddThreat(victim, amount, null, true, true); + } + + /// + /// Adds/removes the specified percentage from the specified victim's threat (to who, or me if not specified) + /// + /// + /// + /// + public void ModifyThreatByPercent(Unit victim, int pct, Unit who = null) + { + if (!victim) + return; + + if (!who) + who = me; + + who.GetThreatManager().ModifyThreatByPercent(victim, pct); + } + + /// + /// Resets the victim's threat level to who (or me if not specified) to zero + /// + /// + /// + void ResetThreat(Unit victim, Unit who) + { + if (!victim) + return; + + if (!who) + who = me; + + who.GetThreatManager().ResetThreat(victim); + } + + /// + /// Resets the specified unit's threat list (me if not specified) - does not delete entries, just sets their threat to zero + /// + /// + public void ResetThreatList(Unit who = null) + { + if (!who) + who = me; + + who.GetThreatManager().ResetAllThreat(); + } + + /// + /// Returns the threat level of victim towards who (or me if not specified) + /// + /// + /// + /// + public float GetThreat(Unit victim, Unit who = null) + { + if (!victim) + return 0.0f; + + if (!who) + who = me; + + return who.GetThreatManager().GetThreat(victim); + } + //Spawns a creature relative to me public Creature DoSpawnCreature(uint entry, float offsetX, float offsetY, float offsetZ, float angle, TempSummonType type, uint despawntime) { @@ -182,39 +261,6 @@ namespace Game.AI return apSpell[RandomHelper.IRand(0, (int)(spellCount - 1))]; } - //Drops all threat to 0%. Does not remove players from the threat list - public void DoResetThreat() - { - if (!me.CanHaveThreatList() || me.GetThreatManager().IsThreatListEmpty()) - { - Log.outError(LogFilter.Scripts, "DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = {0})", me.GetEntry()); - return; - } - - var threatlist = me.GetThreatManager().GetThreatList(); - - foreach (var refe in threatlist) - { - Unit unit = Global.ObjAccessor.GetUnit(me, refe.GetUnitGuid()); - if (unit != null && DoGetThreat(unit) != 0) - DoModifyThreatPercent(unit, -100); - } - } - - public float DoGetThreat(Unit unit) - { - if (unit == null) - return 0.0f; - return me.GetThreatManager().GetThreat(unit); - } - - public void DoModifyThreatPercent(Unit unit, int pct) - { - if (unit == null) - return; - me.GetThreatManager().ModifyThreatPercent(unit, pct); - } - void DoTeleportTo(float x, float y, float z, uint time = 0) { me.Relocate(x, y, z); @@ -499,7 +545,7 @@ namespace Game.AI public override void JustSummoned(Creature summon) { summons.Summon(summon); - if (me.IsInCombat()) + if (me.IsEngaged()) DoZoneInCombat(summon); } diff --git a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs index ecb16a65d..2efd91687 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs @@ -88,8 +88,7 @@ namespace Game.AI } else { - who.SetInCombatWith(me); - me.AddThreat(who, 0.0f); + me.EngageWithTarget(who); return true; } } @@ -111,24 +110,7 @@ namespace Game.AI { float fAttackRadius = me.GetAttackDistance(who); if (me.IsWithinDistInMap(who, fAttackRadius) && me.IsWithinLOSInMap(who)) - { - if (!me.GetVictim()) - { - // Clear distracted state on combat - if (me.HasUnitState(UnitState.Distracted)) - { - me.ClearUnitState(UnitState.Distracted); - me.GetMotionMaster().Clear(); - } - - AttackStart(who); - } - else if (me.GetMap().IsDungeon()) - { - who.SetInCombatWith(me); - me.AddThreat(who, 0.0f); - } - } + me.EngageWithTarget(who); } } } @@ -181,7 +163,7 @@ namespace Game.AI public override void EnterEvadeMode(EvadeReason why = EvadeReason.Other) { me.RemoveAllAuras(); - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); me.CombatStop(true); me.SetLootRecipient(null); diff --git a/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs b/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs index 920a25b1d..e4918eae7 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs @@ -49,9 +49,7 @@ namespace Game.AI if (me.Attack(who, true)) { - me.AddThreat(who, 0.0f); - me.SetInCombatWith(who); - who.SetInCombatWith(me); + me.EngageWithTarget(who); // in case it doesn't have threat+combat yet if (me.HasUnitState(UnitState.Follow)) me.ClearUnitState(UnitState.Follow); @@ -84,18 +82,8 @@ namespace Game.AI //too far away and no free sight? if (me.IsWithinDistInMap(who, 100.0f) && me.IsWithinLOSInMap(who)) { - //already fighting someone? - if (!me.GetVictim()) - { - AttackStart(who); - return true; - } - else - { - who.SetInCombatWith(me); - me.AddThreat(who, 0.0f); - return true; - } + me.EngageWithTarget(who); + return true; } return false; @@ -128,10 +116,7 @@ namespace Game.AI AttackStart(who); } else if (me.GetMap().IsDungeon()) - { - who.SetInCombatWith(me); - me.AddThreat(who, 0.0f); - } + me.EngageWithTarget(who); } } } @@ -178,7 +163,7 @@ namespace Game.AI public override void EnterEvadeMode(EvadeReason why) { me.RemoveAllAuras(); - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); me.CombatStop(true); me.SetLootRecipient(null); diff --git a/Source/Game/AI/SmartScripts/SmartAI.cs b/Source/Game/AI/SmartScripts/SmartAI.cs index 340f53862..99cc58c73 100644 --- a/Source/Game/AI/SmartScripts/SmartAI.cs +++ b/Source/Game/AI/SmartScripts/SmartAI.cs @@ -544,18 +544,8 @@ namespace Game.AI //too far away and no free sight? if (me.IsWithinDistInMap(who, SMART_MAX_AID_DIST) && me.IsWithinLOSInMap(who)) { - //already fighting someone? - if (me.GetVictim() == null) - { - AttackStart(who); - return true; - } - else - { - who.SetInCombatWith(me); - me.AddThreat(who, 0.0f); - return true; - } + me.EngageWithTarget(who); + return true; } return false; diff --git a/Source/Game/AI/SmartScripts/SmartScript.cs b/Source/Game/AI/SmartScripts/SmartScript.cs index 58616c69b..dffc900ac 100644 --- a/Source/Game/AI/SmartScripts/SmartScript.cs +++ b/Source/Game/AI/SmartScripts/SmartScript.cs @@ -334,16 +334,10 @@ namespace Game.AI if (me == null) break; - var threatList = me.GetThreatManager().GetThreatList(); - foreach (var refe in threatList) + foreach (var refe in me.GetThreatManager().GetThreatList()) { - Unit target = Global.ObjAccessor.GetUnit(me, refe.GetUnitGuid()); - if (target != null) - { - me.GetThreatManager().ModifyThreatPercent(target, e.Action.threatPCT.threatINC != 0 ? (int)e.Action.threatPCT.threatINC : -(int)e.Action.threatPCT.threatDEC); - Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_THREAT_ALL_PCT: Creature guidLow {0} modify threat for unit {1}, value {2}", - me.GetGUID().ToString(), target.GetGUID().ToString(), e.Action.threatPCT.threatINC != 0 ? (int)e.Action.threatPCT.threatINC : -(int)e.Action.threatPCT.threatDEC); - } + refe.AddThreatPercent(Math.Max(-100, (int)(e.Action.threatPCT.threatINC - e.Action.threatPCT.threatDEC))); + Log.outDebug(LogFilter.ScriptsAi, $"SmartScript.ProcessAction: SMART_ACTION_THREAT_ALL_PCT: Creature {me.GetGUID()} modify threat for {refe.GetTarget().GetGUID()}, value {e.Action.threatPCT.threatINC - e.Action.threatPCT.threatDEC}"); } break; } @@ -356,9 +350,8 @@ namespace Game.AI { if (IsUnit(target)) { - me.GetThreatManager().ModifyThreatPercent(target.ToUnit(), e.Action.threatPCT.threatINC != 0 ? (int)e.Action.threatPCT.threatINC : -(int)e.Action.threatPCT.threatDEC); - Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_THREAT_SINGLE_PCT: Creature guidLow {0} modify threat for unit {1}, value {2}", - me.GetGUID().ToString(), target.GetGUID().ToString(), e.Action.threatPCT.threatINC != 0 ? (int)e.Action.threatPCT.threatINC : -(int)e.Action.threatPCT.threatDEC); + me.GetThreatManager().ModifyThreatByPercent(target.ToUnit(), Math.Max(-100, (int)(e.Action.threatPCT.threatINC - e.Action.threatPCT.threatDEC))); + Log.outDebug(LogFilter.ScriptsAi, $"SmartScript.ProcessAction: SMART_ACTION_THREAT_SINGLE_PCT: Creature {me.GetGUID()} modify threat for {target.GetGUID()}, value {e.Action.threatPCT.threatINC - e.Action.threatPCT.threatDEC}"); } } break; @@ -2094,7 +2087,7 @@ namespace Game.AI { foreach (var target in targets) if (IsUnit(target)) - me.AddThreat(target.ToUnit(), (float)e.Action.threatPCT.threatINC - (float)e.Action.threatPCT.threatDEC); + me.GetThreatManager().AddThreat(target.ToUnit(), (float)(e.Action.threatPCT.threatINC - (float)e.Action.threatPCT.threatDEC), null, true, true); break; } @@ -2377,13 +2370,13 @@ namespace Game.AI { if (e.Target.hostilRandom.powerType != 0) { - Unit u = me.GetAI().SelectTarget(SelectAggroTarget.TopAggro, 1, new PowerUsersSelector(me, (PowerType)(e.Target.hostilRandom.powerType - 1), (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0)); + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.MaxThreat, 1, new PowerUsersSelector(me, (PowerType)(e.Target.hostilRandom.powerType - 1), (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0)); if (u != null) targets.Add(u); } else { - Unit u = me.GetAI().SelectTarget(SelectAggroTarget.TopAggro, 1, (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0); + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.MaxThreat, 1, (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0); if (u != null) targets.Add(u); } @@ -2394,13 +2387,13 @@ namespace Game.AI { if (e.Target.hostilRandom.powerType != 0) { - Unit u = me.GetAI().SelectTarget(SelectAggroTarget.BottomAggro, 1, new PowerUsersSelector(me, (PowerType)(e.Target.hostilRandom.powerType - 1), (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0)); + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.MinThreat, 1, new PowerUsersSelector(me, (PowerType)(e.Target.hostilRandom.powerType - 1), (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0)); if (u != null) targets.Add(u); } else { - Unit u = me.GetAI().SelectTarget(SelectAggroTarget.BottomAggro, 1, (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0); + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.MinThreat, 1, (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0); if (u != null) targets.Add(u); } @@ -2443,7 +2436,7 @@ namespace Game.AI case SmartTargets.Farthest: if (me) { - Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Farthest, 0, new FarthestTargetSelector(me, (float)e.Target.farthest.maxDist, e.Target.farthest.playerOnly != 0, e.Target.farthest.isInLos != 0)); + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.MaxDistance, 0, new FarthestTargetSelector(me, (float)e.Target.farthest.maxDist, e.Target.farthest.playerOnly != 0, e.Target.farthest.isInLos != 0)); if (u != null) targets.Add(u); } @@ -2674,16 +2667,12 @@ namespace Game.AI } case SmartTargets.ThreatList: { - if (me != null) + if (me != null && me.CanHaveThreatList()) { var threatList = me.GetThreatManager().GetThreatList(); - foreach (var refe in threatList) - { - Unit temp = Global.ObjAccessor.GetUnit(me, refe.GetUnitGuid()); - if (temp != null) - if (e.Target.hostilRandom.maxDist == 0 || me.IsWithinCombatRange(temp, (float)e.Target.hostilRandom.maxDist)) - targets.Add(temp); - } + foreach (var refe in me.GetThreatManager().GetThreatList()) + if (e.Target.hostilRandom.maxDist == 0 || me.IsWithinCombatRange(refe.GetTarget(), e.Target.hostilRandom.maxDist)) + targets.Add(refe.GetTarget()); } break; } @@ -2790,18 +2779,18 @@ namespace Game.AI ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); break; case SmartEvents.UpdateOoc: - if (me != null && me.IsInCombat()) + if (me != null && me.IsEngaged()) return; ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); break; case SmartEvents.UpdateIc: - if (me == null || !me.IsInCombat()) + if (me == null || !me.IsEngaged()) return; ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); break; case SmartEvents.HealthPct: { - if (me == null || !me.IsInCombat() || me.GetMaxHealth() == 0) + if (me == null || !me.IsEngaged() || me.GetMaxHealth() == 0) return; uint perc = (uint)me.GetHealthPct(); if (perc > e.Event.minMaxRepeat.max || perc < e.Event.minMaxRepeat.min) @@ -2811,7 +2800,7 @@ namespace Game.AI } case SmartEvents.TargetHealthPct: { - if (me == null || !me.IsInCombat() || me.GetVictim() == null || me.GetVictim().GetMaxHealth() == 0) + if (me == null || !me.IsEngaged() || me.GetVictim() == null || me.GetVictim().GetMaxHealth() == 0) return; uint perc = (uint)me.GetVictim().GetHealthPct(); if (perc > e.Event.minMaxRepeat.max || perc < e.Event.minMaxRepeat.min) @@ -2821,7 +2810,7 @@ namespace Game.AI } case SmartEvents.ManaPct: { - if (me == null || !me.IsInCombat() || me.GetMaxPower(PowerType.Mana) == 0) + if (me == null || !me.IsEngaged() || me.GetMaxPower(PowerType.Mana) == 0) return; uint perc = (uint)me.GetPowerPct(PowerType.Mana); if (perc > e.Event.minMaxRepeat.max || perc < e.Event.minMaxRepeat.min) @@ -2831,7 +2820,7 @@ namespace Game.AI } case SmartEvents.TargetManaPct: { - if (me == null || !me.IsInCombat() || me.GetVictim() == null || me.GetVictim().GetMaxPower(PowerType.Mana) == 0) + if (me == null || !me.IsEngaged() || me.GetVictim() == null || me.GetVictim().GetMaxPower(PowerType.Mana) == 0) return; uint perc = (uint)me.GetVictim().GetPowerPct(PowerType.Mana); if (perc > e.Event.minMaxRepeat.max || perc < e.Event.minMaxRepeat.min) @@ -2841,7 +2830,7 @@ namespace Game.AI } case SmartEvents.Range: { - if (me == null || !me.IsInCombat() || me.GetVictim() == null) + if (me == null || !me.IsEngaged() || me.GetVictim() == null) return; if (me.IsInRange(me.GetVictim(), e.Event.minMaxRepeat.min, e.Event.minMaxRepeat.max)) @@ -2852,7 +2841,7 @@ namespace Game.AI } case SmartEvents.VictimCasting: { - if (me == null || !me.IsInCombat()) + if (me == null || !me.IsEngaged()) return; Unit victim = me.GetVictim(); @@ -2873,7 +2862,7 @@ namespace Game.AI } case SmartEvents.FriendlyHealth: { - if (me == null || !me.IsInCombat()) + if (me == null || !me.IsEngaged()) return; Unit target = DoSelectLowestHpFriendly(e.Event.friendlyHealth.radius, e.Event.friendlyHealth.hpDeficit); @@ -2889,7 +2878,7 @@ namespace Game.AI } case SmartEvents.FriendlyIsCc: { - if (me == null || !me.IsInCombat()) + if (me == null || !me.IsEngaged()) return; List creatures = new List(); @@ -3016,7 +3005,7 @@ namespace Game.AI } case SmartEvents.OocLos: { - if (me == null || me.IsInCombat()) + if (me == null || me.IsEngaged()) return; //can trigger if closer than fMaxAllowedRange float range = e.Event.los.maxDist; @@ -3036,7 +3025,7 @@ namespace Game.AI } case SmartEvents.IcLos: { - if (me == null || !me.IsInCombat()) + if (me == null || !me.IsEngaged()) return; //can trigger if closer than fMaxAllowedRange float range = e.Event.los.maxDist; @@ -3226,7 +3215,7 @@ namespace Game.AI } case SmartEvents.FriendlyHealthPCT: { - if (me == null || !me.IsInCombat()) + if (me == null || !me.IsEngaged()) return; List targets; @@ -3401,10 +3390,10 @@ namespace Game.AI if (e.Event.event_phase_mask != 0 && !IsInPhase(e.Event.event_phase_mask)) return; - if (e.GetEventType() == SmartEvents.UpdateIc && (me == null || !me.IsInCombat())) + if (e.GetEventType() == SmartEvents.UpdateIc && (me == null || !me.IsEngaged())) return; - if (e.GetEventType() == SmartEvents.UpdateOoc && (me != null && me.IsInCombat()))//can be used with me=NULL (go script) + if (e.GetEventType() == SmartEvents.UpdateOoc && (me != null && me.IsEngaged()))//can be used with me=NULL (go script) return; if (e.timer < diff) @@ -3741,15 +3730,10 @@ namespace Game.AI public void OnMoveInLineOfSight(Unit who) { - ProcessEventsFor(SmartEvents.OocLos, who); - if (me == null) return; - if (me.GetVictim() != null) - return; - - ProcessEventsFor(SmartEvents.IcLos, who); + ProcessEventsFor(me.IsEngaged() ? SmartEvents.IcLos : SmartEvents.OocLos, who); } Unit DoSelectLowestHpFriendly(float range, uint MinHPDiff) diff --git a/Source/Game/Combat/HostileRegManager.cs b/Source/Game/Combat/HostileRegManager.cs index bb72adfe5..9f4b858fb 100644 --- a/Source/Game/Combat/HostileRegManager.cs +++ b/Source/Game/Combat/HostileRegManager.cs @@ -209,10 +209,12 @@ namespace Game.Combat { GetTarget().AddHatedBy(this); } + public override void TargetObjectDestroyLink() { GetTarget().RemoveHatedBy(this); } + public override void SourceObjectDestroyLink() { SetOnlineOfflineState(false); @@ -391,4 +393,4 @@ namespace Game.Combat bool iOnline; bool iAccessible; } -} +} \ No newline at end of file diff --git a/Source/Game/Combat/ThreatManager.cs b/Source/Game/Combat/ThreatManager.cs index 2cd600d08..7139ed92a 100644 --- a/Source/Game/Combat/ThreatManager.cs +++ b/Source/Game/Combat/ThreatManager.cs @@ -36,6 +36,28 @@ namespace Game.Combat const int ThreatUpdateInternal = 1 * Time.InMilliseconds; + public void ForwardThreatForAssistingMe(Unit victim, float amount, SpellInfo spell, bool ignoreModifiers = false, bool ignoreRedirection = false) + { + GetOwner().GetHostileRefManager().ThreatAssist(victim, amount, spell); + } + + public void AddThreat(Unit victim, float amount, SpellInfo spell, bool ignoreModifiers = false, bool ignoreRedirection = false) + { + if (!Owner.CanHaveThreatList() || Owner.HasUnitState(UnitState.Evade)) + return; + + Owner.SetInCombatWith(victim); + victim.SetInCombatWith(Owner); + AddThreat(victim, amount, spell != null ? spell.GetSchoolMask() : victim.GetMeleeDamageSchoolMask(), spell); + } + + public void ClearAllThreat() + { + if (Owner.CanHaveThreatList(true) && !IsThreatListEmpty()) + Owner.SendClearThreatList(); + ClearReferences(); + } + public void ClearReferences() { threatContainer.ClearReferences(); @@ -111,17 +133,17 @@ namespace Game.Combat } } - public void ModifyThreatPercent(Unit victim, int percent) + public void ModifyThreatByPercent(Unit victim, int percent) { - threatContainer.ModifyThreatPercent(victim, percent); + threatContainer.ModifyThreatByPercent(victim, percent); } public Unit GetHostilTarget() { threatContainer.Update(); - HostileReference nextVictim = threatContainer.SelectNextVictim(GetOwner().ToCreature(), GetCurrentVictim()); + HostileReference nextVictim = threatContainer.SelectNextVictim(GetOwner().ToCreature(), getCurrentVictim()); SetCurrentVictim(nextVictim); - return GetCurrentVictim() != null ? GetCurrentVictim().GetTarget() : null; + return GetCurrentVictim() != null ? GetCurrentVictim() : null; } public float GetThreat(Unit victim, bool alsoSearchOfflineList = false) @@ -138,10 +160,10 @@ namespace Game.Combat void TauntApply(Unit taunter) { HostileReference refe = threatContainer.GetReferenceByTarget(taunter); - if (GetCurrentVictim() != null && refe != null && (refe.GetThreat() < GetCurrentVictim().GetThreat())) + if (GetCurrentVictim() != null && refe != null && (refe.GetThreat() < getCurrentVictim().GetThreat())) { if (refe.GetTempThreatModifier() == 0.0f) // Ok, temp threat is unused - refe.SetTempThreat(GetCurrentVictim().GetThreat()); + refe.SetTempThreat(getCurrentVictim().GetThreat()); } } @@ -170,14 +192,14 @@ namespace Game.Combat switch (threatRefStatusChangeEvent.GetEventType()) { case UnitEventTypes.ThreatRefThreatChange: - if ((GetCurrentVictim() == hostilRef && threatRefStatusChangeEvent.GetFValue() < 0.0f) || - (GetCurrentVictim() != hostilRef && threatRefStatusChangeEvent.GetFValue() > 0.0f)) + if ((getCurrentVictim() == hostilRef && threatRefStatusChangeEvent.GetFValue() < 0.0f) || + (getCurrentVictim() != hostilRef && threatRefStatusChangeEvent.GetFValue() > 0.0f)) SetDirty(true); // the order in the threat list might have changed break; case UnitEventTypes.ThreatRefOnlineStatus: if (!hostilRef.IsOnline()) { - if (hostilRef == GetCurrentVictim()) + if (hostilRef == getCurrentVictim()) { SetCurrentVictim(null); SetDirty(true); @@ -188,14 +210,14 @@ namespace Game.Combat } else { - if (GetCurrentVictim() != null && hostilRef.GetThreat() > (1.1f * GetCurrentVictim().GetThreat())) + if (GetCurrentVictim() != null && hostilRef.GetThreat() > (1.1f * getCurrentVictim().GetThreat())) SetDirty(true); threatContainer.AddReference(hostilRef); threatOfflineContainer.Remove(hostilRef); } break; case UnitEventTypes.ThreatRefRemoveFromList: - if (hostilRef == GetCurrentVictim()) + if (hostilRef == getCurrentVictim()) { SetCurrentVictim(null); SetDirty(true); @@ -209,7 +231,6 @@ namespace Game.Combat } } - public bool IsNeedUpdateToClient(uint time) { if (IsThreatListEmpty()) @@ -245,11 +266,8 @@ namespace Game.Combat return threatContainer.Empty() && threatOfflineContainer.Empty(); } - public HostileReference GetCurrentVictim() - { - return currentVictim; - } - + public HostileReference getCurrentVictim() { return currentVictim; } + public Unit GetOwner() { return Owner; @@ -264,8 +282,53 @@ namespace Game.Combat public List GetOfflineThreatList() { return threatOfflineContainer.GetThreatList(); } public ThreatContainer GetOnlineContainer() { return threatContainer; } - // The hatingUnit is not used yet - public static float CalcThreat(Unit hatedUnit, Unit hatingUnit, float threat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal, SpellInfo threatSpell = null) + public Unit SelectVictim() { return GetHostilTarget(); } + public Unit GetCurrentVictim() + { + var refe = getCurrentVictim(); + if (refe != null) + return refe.GetTarget(); + else + return null; + } + public bool IsThreatListEmpty(bool includeOffline = false) { return includeOffline ? IsThreatListsEmpty() : IsThreatListEmpty(); } + public bool IsThreatenedBy(Unit who, bool includeOffline = false) { return FindReference(who, includeOffline) != null; } + public int GetThreatListSize() { return threatContainer.threatList.Count; } + public Unit GetAnyTarget() + { + var list = GetThreatList(); + if (!list.Empty()) + return list[0].GetTarget(); + + return null; + } + public void ResetThreat(Unit who) + { + var refe = FindReference(who, true); + if (refe != null) + refe.SetThreat(0.0f); + + } + public void ResetAllThreat() { ResetAllAggro(); } + + HostileReference FindReference(Unit who, bool includeOffline) + { + var refe = threatContainer.GetReferenceByTarget(who); + if (refe != null) + return refe; + + if (includeOffline) + { + var offlineRefe = threatOfflineContainer.GetReferenceByTarget(who); + if (offlineRefe != null) + return offlineRefe; + } + + return null; + } + + // The hatingUnit is not used yet + public static float CalcThreat(Unit hatedUnit, Unit hatingUnit, float threat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal, SpellInfo threatSpell = null) { if (threatSpell != null) { @@ -370,7 +433,7 @@ namespace Game.Combat return reff; } - public void ModifyThreatPercent(Unit victim, int percent) + public void ModifyThreatByPercent(Unit victim, int percent) { HostileReference refe = GetReferenceByTarget(victim); if (refe != null) diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index ff693b112..1783a4364 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -475,7 +475,7 @@ namespace Game.Entities } // periodic check to see if the creature has passed an evade boundary - if (IsAIEnabled && !IsInEvadeMode() && IsInCombat()) + if (IsAIEnabled && !IsInEvadeMode() && IsEngaged()) { if (diff >= m_boundaryCheckTime) { @@ -487,7 +487,7 @@ namespace Game.Entities } // if periodic combat pulse is enabled and we are both in combat and in a dungeon, do this now - if (m_combatPulseDelay > 0 && IsInCombat() && GetMap().IsDungeon()) + if (m_combatPulseDelay > 0 && IsEngaged() && GetMap().IsDungeon()) { if (diff > m_combatPulseTime) m_combatPulseTime = 0; @@ -503,12 +503,7 @@ namespace Game.Entities continue; if (player.IsAlive() && IsHostileTo(player)) - { - if (CanHaveThreatList()) - AddThreat(player, 0.0f); - SetInCombatWith(player); - player.SetInCombatWith(this); - } + EngageWithTarget(player); } m_combatPulseTime = m_combatPulseDelay * Time.InMilliseconds; } @@ -1484,7 +1479,7 @@ namespace Game.Entities if (!_IsTargetAcceptable(who)) return false; - if (who.IsInCombat() && IsWithinDist(who, SharedConst.AttackDistance)) + if (who.IsEngaged() && IsWithinDist(who, SharedConst.AttackDistance)) { Unit victim = who.GetAttackerForHelper(); if (victim != null) @@ -2022,7 +2017,7 @@ namespace Game.Entities return false; // skip fighting creature - if (IsInCombat()) + if (IsEngaged()) return false; // only free creature @@ -2067,7 +2062,7 @@ namespace Game.Entities Unit targetVictim = target.GetAttackerForHelper(); // if I'm already fighting target, or I'm hostile towards the target, the target is acceptable - if (IsInCombatWith(target) || IsHostileTo(target)) + if (IsEngagedBy(target) || IsHostileTo(target)) return true; // if the target's victim is friendly, and the target is neutral, the target is acceptable @@ -2256,7 +2251,6 @@ namespace Game.Entities } var PlList = map.GetPlayers(); - if (PlList.Empty()) return; @@ -2266,12 +2260,7 @@ namespace Game.Entities continue; if (player.IsAlive()) - { - SetInCombatWith(player); - player.SetInCombatWith(this); - AddThreat(player, 0.0f); - } - + EngageWithTarget(player); } } @@ -3270,9 +3259,7 @@ namespace Game.Entities if (assistant != null && assistant.CanAssistTo(m_owner, victim)) { assistant.SetNoCallAssistance(true); - assistant.CombatStart(victim); - if (assistant.IsAIEnabled) - assistant.GetAI().AttackStart(victim); + assistant.EngageWithTarget(victim); } } } diff --git a/Source/Game/Entities/Creature/CreatureGroups.cs b/Source/Game/Entities/Creature/CreatureGroups.cs index 5758c8252..4f49dfa78 100644 --- a/Source/Game/Entities/Creature/CreatureGroups.cs +++ b/Source/Game/Entities/Creature/CreatureGroups.cs @@ -170,7 +170,7 @@ namespace Game.Entities member.SetFormation(null); } - public void MemberAttackStart(Creature member, Unit target) + public void MemberEngagingTarget(Creature member, Unit target) { GroupAIFlags groupAI = (GroupAIFlags)FormationMgr.CreatureGroupMap[member.GetSpawnId()].groupAI; if (groupAI == 0) @@ -186,9 +186,6 @@ namespace Game.Entities foreach (var pair in m_members) { - if (m_leader) // avoid crash if leader was killed and reset. - Log.outDebug(LogFilter.Unit, "GROUP ATTACK: group instance id {0} calls member instid {1}", m_leader.GetInstanceId(), member.GetInstanceId()); - Creature other = pair.Key; // Skip self @@ -198,11 +195,8 @@ namespace Game.Entities if (!other.IsAlive()) continue; - if (other.GetVictim()) - continue; - if (((other != m_leader && groupAI.HasAnyFlag(GroupAIFlags.MembersAssistLeader)) || (other == m_leader && groupAI.HasAnyFlag(GroupAIFlags.LeaderAssistsMember))) && other.IsValidAttackTarget(target)) - other.GetAI().AttackStart(target); + other.EngageWithTarget(target); } } diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index 16d00fc2c..26e817f67 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -1738,9 +1738,7 @@ namespace Game.Entities public void AddToNotify(NotifyFlags f) { m_notifyflags |= f; } public bool IsNeedNotify(NotifyFlags f) { return Convert.ToBoolean(m_notifyflags & f); } NotifyFlags GetNotifyFlags() { return m_notifyflags; } - bool NotifyExecuted(NotifyFlags f) { return Convert.ToBoolean(m_executed_notifies & f); } - void SetNotified(NotifyFlags f) { m_executed_notifies |= f; } - public void ResetAllNotifies() { m_notifyflags = 0; m_executed_notifies = 0; } + public void ResetAllNotifies() { m_notifyflags = 0; } public bool IsActiveObject() { return m_isActive; } public bool IsPermanentWorldObject() { return m_isWorldObject; } @@ -2363,7 +2361,6 @@ namespace Game.Entities public bool IsInWorld { get; set; } NotifyFlags m_notifyflags; - NotifyFlags m_executed_notifies; public FlaggedArray m_stealth = new FlaggedArray(2); public FlaggedArray m_stealthDetect = new FlaggedArray(2); diff --git a/Source/Game/Entities/Player/Player.Combat.cs b/Source/Game/Entities/Player/Player.Combat.cs index 31c9cd275..923e451ad 100644 --- a/Source/Game/Entities/Player/Player.Combat.cs +++ b/Source/Game/Entities/Player/Player.Combat.cs @@ -22,6 +22,7 @@ using Game.Networking.Packets; using Game.Spells; using System; using System.Collections.Generic; +using Game.Maps; namespace Game.Entities { @@ -485,15 +486,38 @@ namespace Game.Entities } } + public void SetContestedPvP(Player attackedPlayer = null) + { + if (attackedPlayer != null && (attackedPlayer == this || (duel != null && duel.opponent == attackedPlayer))) + return; + + SetContestedPvPTimer(30000); + if (!HasUnitState(UnitState.AttackPlayer)) + { + AddUnitState(UnitState.AttackPlayer); + AddPlayerFlag(PlayerFlags.ContestedPVP); + // call MoveInLineOfSight for nearby contested guards + AIRelocationNotifier notifier = new AIRelocationNotifier(this); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + foreach (Unit unit in m_Controlled) + { + if (!unit.HasUnitState(UnitState.AttackPlayer)) + { + unit.AddUnitState(UnitState.AttackPlayer); + AIRelocationNotifier notifier = new AIRelocationNotifier(unit); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + } + } + public void UpdateContestedPvP(uint diff) { if (m_contestedPvPTimer == 0 || IsInCombat()) return; if (m_contestedPvPTimer <= diff) - { ResetContestedPvP(); - } else m_contestedPvPTimer -= diff; } diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index 51f88d9ea..adaec067d 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -125,7 +125,7 @@ namespace Game.Entities public virtual void SetCanDualWield(bool value) { m_canDualWield = value; } - void SendClearThreatList() + public void SendClearThreatList() { ThreatClear packet = new ThreatClear(); packet.UnitGUID = GetGUID(); @@ -158,13 +158,6 @@ namespace Game.Entities } } - public void DeleteThreatList() - { - if (CanHaveThreatList(true) && !threatManager.IsThreatListEmpty()) - SendClearThreatList(); - threatManager.ClearReferences(); - } - public void TauntApply(Unit taunter) { Cypher.Assert(IsTypeId(TypeId.Unit)); @@ -325,12 +318,6 @@ namespace Game.Entities } public void RemoveHatedBy(HostileReference pHostileReference) { } //nothing to do yet - public void AddThreat(Unit victim, float fThreat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal, SpellInfo threatSpell = null) - { - // Only mobs can manage threat lists - if (CanHaveThreatList() && !HasUnitState(UnitState.Evade)) - threatManager.AddThreat(victim, fThreat, schoolMask, threatSpell); - } public float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal) { if (!HasAuraType(AuraType.ModThreat) || fThreat < 0) @@ -360,6 +347,17 @@ namespace Game.Entities return m_deathState; } + public bool IsEngaged() { return IsInCombat(); } + public bool IsEngagedBy(Unit who) { return IsInCombatWith(who); } + public void EngageWithTarget(Unit who) + { + SetInCombatWith(who); + who.SetInCombatWith(this); + GetThreatManager().AddThreat(who, 0.0f); + } + public bool IsThreatened() { return CanHaveThreatList() && !GetThreatManager().IsThreatListEmpty(); } + public bool IsThreatenedBy(Unit who) { return who != null && CanHaveThreatList() && GetThreatManager().IsThreatenedBy(who); } + public bool IsInCombat() { return HasUnitFlag(UnitFlags.InCombat); } public bool IsPetInCombat() { return HasUnitFlag(UnitFlags.PetInCombat); } @@ -444,7 +442,7 @@ namespace Game.Entities if (creature != null && !IsPet()) { // should not let player enter combat by right clicking target - doesn't helps - AddThreat(victim, 0.0f); + GetThreatManager().AddThreat(victim, 0.0f); SetInCombatWith(victim); if (victim.IsTypeId(TypeId.Player)) @@ -453,7 +451,7 @@ namespace Game.Entities Unit owner = victim.GetOwner(); if (owner != null) { - AddThreat(owner, 0.0f); + GetThreatManager().AddThreat(owner, 0.0f); SetInCombatWith(owner); if (owner.GetTypeId() == TypeId.Player) owner.SetInCombatWith(this); @@ -553,6 +551,9 @@ namespace Game.Entities } public Unit GetAttackerForHelper() { + if (!IsEngaged()) + return null; + Unit victim = GetVictim(); if (victim != null) if ((!IsPet() && GetPlayerMovingMe() == null) || IsInCombatWith(victim) || victim.IsInCombatWith(this)) @@ -1143,7 +1144,7 @@ namespace Game.Entities if (damagetype != DamageEffectType.DOT && damage > 0 && !victim.GetOwnerGUID().IsPlayer() && (spellProto == null || !spellProto.HasAura(AuraType.DamageShield))) victim.ToCreature().SetLastDamagedTime(GameTime.GetGameTime() + SharedConst.MaxAggroResetTime); - victim.AddThreat(this, damage, damageSchoolMask, spellProto); + victim.GetThreatManager().AddThreat(this, damage, spellProto); } else // victim is a player { @@ -1338,11 +1339,12 @@ namespace Game.Entities SetInCombatWith(target); target.SetInCombatWith(this); } - Unit who = target.GetCharmerOrOwnerOrSelf(); - if (who.IsTypeId(TypeId.Player)) - SetContestedPvP(who.ToPlayer()); Player me = GetCharmerOrOwnerPlayerOrPlayerItself(); + Unit who = target.GetCharmerOrOwnerOrSelf(); + if (me != null && who.IsTypeId(TypeId.Player)) + me.SetContestedPvP(who.ToPlayer()); + if (me != null && who.IsPvP() && (!who.IsTypeId(TypeId.Player) || me.duel == null || me.duel.opponent != who)) { me.UpdatePvP(true); @@ -1403,7 +1405,7 @@ namespace Game.Entities creature.GetAI().EnterCombat(enemy); if (creature.GetFormation() != null) - creature.GetFormation().MemberAttackStart(creature, enemy); + creature.GetFormation().MemberEngagingTarget(creature, enemy); } if (IsPet()) @@ -1595,7 +1597,7 @@ namespace Game.Entities if (!creature.IsPet()) { - creature.DeleteThreatList(); + creature.GetThreatManager().ClearAllThreat(); // must be after setDeathState which resets dynamic flags if (!creature.loot.IsLooted()) @@ -1709,29 +1711,6 @@ namespace Game.Entities public virtual uint GetBlockPercent() { return 30; } - public void SetContestedPvP(Player attackedPlayer = null) - { - Player player = GetCharmerOrOwnerPlayerOrPlayerItself(); - - if (player == null || (attackedPlayer != null && (attackedPlayer == player || (player.duel != null && player.duel.opponent == attackedPlayer)))) - return; - - player.SetContestedPvPTimer(30000); - if (!player.HasUnitState(UnitState.AttackPlayer)) - { - player.AddUnitState(UnitState.AttackPlayer); - player.AddPlayerFlag(PlayerFlags.ContestedPVP); - // call MoveInLineOfSight for nearby contested guards - UpdateObjectVisibility(); - } - if (!HasUnitState(UnitState.AttackPlayer)) - { - AddUnitState(UnitState.AttackPlayer); - // call MoveInLineOfSight for nearby contested guards - UpdateObjectVisibility(); - } - } - void UpdateReactives(uint p_time) { for (ReactiveType reactive = 0; reactive < ReactiveType.Max; ++reactive) diff --git a/Source/Game/Entities/Unit/Unit.Pets.cs b/Source/Game/Entities/Unit/Unit.Pets.cs index b50d54805..ea2f3f545 100644 --- a/Source/Game/Entities/Unit/Unit.Pets.cs +++ b/Source/Game/Entities/Unit/Unit.Pets.cs @@ -313,7 +313,7 @@ namespace Game.Entities CastStop(); CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells) - DeleteThreatList(); + GetThreatManager().ClearAllThreat(); Player playerCharmer = charmer.ToPlayer(); @@ -456,7 +456,7 @@ namespace Game.Entities CastStop(); CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells) GetHostileRefManager().DeleteReferences(); - DeleteThreatList(); + GetThreatManager().ClearAllThreat(); if (_oldFactionId != 0) { diff --git a/Source/Game/Entities/Unit/Unit.Spells.cs b/Source/Game/Entities/Unit/Unit.Spells.cs index d75ed6b8a..232eedf00 100644 --- a/Source/Game/Entities/Unit/Unit.Spells.cs +++ b/Source/Game/Entities/Unit/Unit.Spells.cs @@ -1508,14 +1508,19 @@ namespace Game.Entities } public void EnergizeBySpell(Unit victim, uint spellId, int damage, PowerType powerType) + { + SpellInfo info = Global.SpellMgr.GetSpellInfo(spellId, GetMap().GetDifficultyID()); + if (info != null) + EnergizeBySpell(victim, info, damage, powerType); + } + + void EnergizeBySpell(Unit victim, SpellInfo spellInfo, int damage, PowerType powerType) { int gain = victim.ModifyPower(powerType, damage); int overEnergize = damage - gain; - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId, GetMap().GetDifficultyID()); - victim.GetHostileRefManager().ThreatAssist(this, damage * 0.5f, spellInfo); - - SendEnergizeSpellLog(victim, spellId, damage, overEnergize, powerType); + victim.GetThreatManager().ForwardThreatForAssistingMe(this, damage / 2, spellInfo, true); + SendEnergizeSpellLog(victim, spellInfo.Id, damage, overEnergize, powerType); } public void ApplySpellImmune(uint spellId, SpellImmunity op, SpellSchoolMask type, bool apply) diff --git a/Source/Game/Entities/Unit/Unit.cs b/Source/Game/Entities/Unit/Unit.cs index 1c7311f96..ff4dc43cc 100644 --- a/Source/Game/Entities/Unit/Unit.cs +++ b/Source/Game/Entities/Unit/Unit.cs @@ -491,7 +491,7 @@ namespace Game.Entities m_Events.KillAllEvents(false); // non-delatable (currently casted spells) will not deleted now but it will deleted at call in Map.RemoveAllObjectsInRemoveList CombatStop(); - DeleteThreatList(); + GetThreatManager().ClearAllThreat(); GetHostileRefManager().DeleteReferences(); } public override void CleanupsBeforeDelete(bool finalCleanup = true) @@ -1609,7 +1609,7 @@ namespace Game.Entities if (s != DeathState.Alive && s != DeathState.JustRespawned) { CombatStop(); - DeleteThreatList(); + GetThreatManager().ClearAllThreat(); GetHostileRefManager().DeleteReferences(); if (IsNonMeleeSpellCast(false)) diff --git a/Source/Game/Maps/ObjectGridLoader.cs b/Source/Game/Maps/ObjectGridLoader.cs index 2f4eed9ad..71cce97f2 100644 --- a/Source/Game/Maps/ObjectGridLoader.cs +++ b/Source/Game/Maps/ObjectGridLoader.cs @@ -168,7 +168,7 @@ namespace Game.Maps if (creature.IsInCombat() || !creature.GetThreatManager().IsThreatListsEmpty()) { creature.CombatStop(); - creature.DeleteThreatList(); + creature.GetThreatManager().ClearAllThreat(); if (creature.IsAIEnabled) creature.GetAI().EnterEvadeMode(); } diff --git a/Source/Game/Spells/Auras/AuraEffect.cs b/Source/Game/Spells/Auras/AuraEffect.cs index f745fde55..49c26aefc 100644 --- a/Source/Game/Spells/Auras/AuraEffect.cs +++ b/Source/Game/Spells/Auras/AuraEffect.cs @@ -4239,7 +4239,7 @@ namespace Game.Spells case 1515: // Tame beast // FIX_ME: this is 2.0.12 threat effect replaced in 2.1.x by dummy aura, must be checked for correctness if (caster != null && target.CanHaveThreatList()) - target.AddThreat(caster, 10.0f); + target.GetThreatManager().AddThreat(caster, 10.0f); break; case 13139: // net-o-matic // root to self part of (root_target.charge.root_self sequence @@ -5597,7 +5597,7 @@ namespace Game.Spells HealInfo healInfo = new HealInfo(caster, caster, heal, GetSpellInfo(), GetSpellInfo().GetSchoolMask()); caster.HealBySpell(healInfo); - caster.GetHostileRefManager().ThreatAssist(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo()); + caster.GetThreatManager().ForwardThreatForAssistingMe(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo()); caster.ProcSkillsAndAuras(caster, ProcFlags.DonePeriodic, ProcFlags.TakenPeriodic, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, hitMask, null, null, healInfo); } @@ -5707,7 +5707,7 @@ namespace Game.Spells SpellPeriodicAuraLogInfo pInfo = new SpellPeriodicAuraLogInfo(this, heal, (uint)damage, heal - healInfo.GetEffectiveHeal(), healInfo.GetAbsorb(), 0, 0.0f, crit); target.SendPeriodicAuraLog(pInfo); - target.GetHostileRefManager().ThreatAssist(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo()); + target.GetThreatManager().ForwardThreatForAssistingMe(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo()); // %-based heal - does not proc auras if (GetAuraType() == AuraType.ObsModHealth) @@ -5751,7 +5751,8 @@ namespace Game.Spells if (gainAmount != 0) { gainedAmount = caster.ModifyPower(powerType, gainAmount); - target.AddThreat(caster, gainedAmount * 0.5f, GetSpellInfo().GetSchoolMask(), GetSpellInfo()); + // energize is not modified by threat modifiers + target.GetThreatManager().AddThreat(caster, gainedAmount * 0.5f, GetSpellInfo(), true); } // Drain Mana @@ -5801,7 +5802,7 @@ namespace Game.Spells int gain = target.ModifyPower(powerType, amount); if (caster != null) - target.GetHostileRefManager().ThreatAssist(caster, gain * 0.5f, GetSpellInfo()); + target.GetThreatManager().ForwardThreatForAssistingMe(caster, gain * 0.5f, GetSpellInfo(), true); target.SendPeriodicAuraLog(pInfo); } @@ -5829,7 +5830,7 @@ namespace Game.Spells int gain = target.ModifyPower(powerType, amount); if (caster != null) - target.GetHostileRefManager().ThreatAssist(caster, gain * 0.5f, GetSpellInfo()); + target.GetThreatManager().ForwardThreatForAssistingMe(caster, gain * 0.5f, GetSpellInfo(), true); target.SendPeriodicAuraLog(pInfo); } diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 4291d1b94..68aa8f735 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -1938,7 +1938,7 @@ namespace Game.Spells HealInfo healInfo = new HealInfo(caster, unitTarget, addhealth, m_spellInfo, m_spellInfo.GetSchoolMask()); caster.HealBySpell(healInfo, crit); - unitTarget.GetHostileRefManager().ThreatAssist(caster, healInfo.GetEffectiveHeal() * 0.5f, m_spellInfo); + unitTarget.GetThreatManager().ForwardThreatForAssistingMe(caster, healInfo.GetEffectiveHeal() * 0.5f, m_spellInfo); m_healing = (int)healInfo.GetEffectiveHeal(); // Do triggers for unit @@ -2007,8 +2007,7 @@ namespace Game.Spells if (missInfo == SpellMissInfo.Resist && m_spellInfo.HasAttribute(SpellCustomAttributes.PickPocket) && unitTarget.IsTypeId(TypeId.Unit)) { m_caster.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Talk); - if (unitTarget.ToCreature().IsAIEnabled) - unitTarget.ToCreature().GetAI().AttackStart(m_caster); + unitTarget.ToCreature().EngageWithTarget(m_caster); } } @@ -2108,14 +2107,17 @@ namespace Game.Spells // assisting case, healing and resurrection if (unit.HasUnitState(UnitState.AttackPlayer)) { - m_caster.SetContestedPvP(); - if (m_caster.IsTypeId(TypeId.Player)) - m_caster.ToPlayer().UpdatePvP(true); + Player playerOwner = m_caster.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (playerOwner != null) + { + playerOwner.SetContestedPvP(); + playerOwner.UpdatePvP(true); + } } if (unit.IsInCombat() && m_spellInfo.HasInitialAggro()) { m_caster.SetInCombatState(unit.GetCombatTimer() > 0, unit); - unit.GetHostileRefManager().ThreatAssist(m_caster, 0.0f); + unit.GetThreatManager().ForwardThreatForAssistingMe(m_caster, 0.0f, null, true); } } } @@ -4377,14 +4379,14 @@ namespace Game.Spells // positive spells distribute threat among all units that are in combat with target, like healing if (m_spellInfo.IsPositive()) - target.GetHostileRefManager().ThreatAssist(m_caster, threatToAdd, m_spellInfo); + target.GetThreatManager().ForwardThreatForAssistingMe(m_caster, threatToAdd, m_spellInfo); // for negative spells threat gets distributed among affected targets else { if (!target.CanHaveThreatList()) continue; - target.AddThreat(m_caster, threatToAdd, m_spellInfo.GetSchoolMask(), m_spellInfo); + target.GetThreatManager().AddThreat(m_caster, threatToAdd, m_spellInfo, true); } } Log.outDebug(LogFilter.Spells, "Spell {0}, added an additional {1} threat for {2} {3} target(s)", m_spellInfo.Id, threat, m_spellInfo.IsPositive() ? "assisting" : "harming", m_UniqueTargetInfo.Count); diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 12c40a1eb..14e923ae9 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -1942,7 +1942,7 @@ namespace Game.Spells return; // Check for possible target - if (unitTarget == null || unitTarget.IsInCombat()) + if (unitTarget == null || unitTarget.IsEngaged()) return; // target must be OK to do this @@ -2701,7 +2701,7 @@ namespace Game.Spells if (!unitTarget.CanHaveThreatList()) return; - unitTarget.AddThreat(m_caster, damage); + unitTarget.GetThreatManager().AddThreat(m_caster, damage); } [SpellEffectHandler(SpellEffectName.HealMaxHealth)] @@ -4408,7 +4408,7 @@ namespace Game.Spells if (unitTarget == null) return; - unitTarget.GetThreatManager().ModifyThreatPercent(m_caster, damage); + unitTarget.GetThreatManager().ModifyThreatByPercent(m_caster, damage); } [SpellEffectHandler(SpellEffectName.TransDoor)] diff --git a/Source/Scripts/EasternKingdoms/Karazhan/Curator.cs b/Source/Scripts/EasternKingdoms/Karazhan/Curator.cs index f6fba2fc5..49f7f07f8 100644 --- a/Source/Scripts/EasternKingdoms/Karazhan/Curator.cs +++ b/Source/Scripts/EasternKingdoms/Karazhan/Curator.cs @@ -102,7 +102,7 @@ namespace Scripts.EasternKingdoms.Karazhan.Curator switch (eventId) { case EventIds.HatefulBolt: - Unit target = SelectTarget(SelectAggroTarget.TopAggro, 1); + Unit target = SelectTarget(SelectAggroTarget.MaxThreat, 1); if (target != null) DoCast(target, SpellIds.HatefulBolt); _events.Repeat(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(15)); diff --git a/Source/Scripts/EasternKingdoms/Karazhan/Moroes.cs b/Source/Scripts/EasternKingdoms/Karazhan/Moroes.cs index dd05930d1..6c3e1fe3c 100644 --- a/Source/Scripts/EasternKingdoms/Karazhan/Moroes.cs +++ b/Source/Scripts/EasternKingdoms/Karazhan/Moroes.cs @@ -278,16 +278,9 @@ namespace Scripts.EasternKingdoms.Karazhan.Moroes if (Blind_Timer <= diff) { - List targets = SelectTargetList(5, SelectAggroTarget.Random, me.GetCombatReach() * 5, true); - foreach (var i in targets) - { - - if (!me.IsWithinMeleeRange(i)) - { - DoCast(i, SpellIds.Blind); - break; - } - } + Unit target = SelectTarget(SelectAggroTarget.MinDistance, 0, 0.0f, true, false); + if (target != null) + DoCast(target, SpellIds.Blind); Blind_Timer = 40000; } else diff --git a/Source/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs b/Source/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs index 10e59cb1f..c76909db8 100644 --- a/Source/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs +++ b/Source/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs @@ -765,11 +765,11 @@ namespace Scripts.EasternKingdoms.Karazhan.OperaEvent { Talk(RedRidingHood.SayWolfHood); DoCast(target, RedRidingHood.SpellLittleRedRidingHood, true); - TempThreat = DoGetThreat(target); + TempThreat = GetThreat(target); if (TempThreat != 0.0f) - DoModifyThreatPercent(target, -100); + ModifyThreatByPercent(target, -100); HoodGUID = target.GetGUID(); - me.AddThreat(target, 1000000.0f); + AddThreat(target, 1000000.0f); ChaseTimer = 20000; IsChasing = true; } @@ -781,9 +781,9 @@ namespace Scripts.EasternKingdoms.Karazhan.OperaEvent if (target) { HoodGUID.Clear(); - if (DoGetThreat(target) != 0f) - DoModifyThreatPercent(target, -100); - me.AddThreat(target, TempThreat); + if (GetThreat(target) != 0f) + ModifyThreatByPercent(target, -100); + AddThreat(target, TempThreat); TempThreat = 0; } @@ -1023,7 +1023,7 @@ namespace Scripts.EasternKingdoms.Karazhan.OperaEvent Romulo.GetMotionMaster().Clear(); Romulo.SetDeathState(DeathState.JustDied); Romulo.CombatStop(true); - Romulo.DeleteThreatList(); + Romulo.GetThreatManager().ClearAllThreat(); Romulo.SetDynamicFlags(UnitDynFlags.Lootable); } @@ -1261,7 +1261,7 @@ namespace Scripts.EasternKingdoms.Karazhan.OperaEvent Julianne.GetMotionMaster().Clear(); Julianne.SetDeathState(DeathState.JustDied); Julianne.CombatStop(true); - Julianne.DeleteThreatList(); + Julianne.GetThreatManager().ClearAllThreat(); Julianne.SetDynamicFlags(UnitDynFlags.Lootable); } return; @@ -1290,7 +1290,7 @@ namespace Scripts.EasternKingdoms.Karazhan.OperaEvent Creature Julianne = (ObjectAccessor.GetCreature(me, JulianneGUID)); if (Julianne && Julianne.GetVictim()) { - me.AddThreat(Julianne.GetVictim(), 1.0f); + AddThreat(Julianne.GetVictim(), 1.0f); AttackStart(Julianne.GetVictim()); } } diff --git a/Source/Scripts/Northrend/AzjolNerub/Ahnkahet/BossJedogaShadowseeker.cs b/Source/Scripts/Northrend/AzjolNerub/Ahnkahet/BossJedogaShadowseeker.cs index 47f0b5b08..f74c3fa14 100644 --- a/Source/Scripts/Northrend/AzjolNerub/Ahnkahet/BossJedogaShadowseeker.cs +++ b/Source/Scripts/Northrend/AzjolNerub/Ahnkahet/BossJedogaShadowseeker.cs @@ -170,7 +170,7 @@ namespace Scripts.Northrend.AzjolNerub.Ahnkahet.JedogaShadowseeker else { who.SetInCombatWith(me); - me.AddThreat(who, 0.0f); + AddThreat(who, 0.0f); } } } diff --git a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs index 4b8599d12..6bd1e6de9 100644 --- a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs +++ b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs @@ -175,8 +175,8 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion { if (player && !player.IsGameMaster() && me.IsInRange(player, 8.0f, 25.0f, false)) { - DoResetThreat(); - me.AddThreat(player, 1.0f); + ResetThreatList(); + me.GetThreatManager().AddThreat(player, 1.0f); DoCast(player, TrialOfChampionSpells.CHARGE); break; } @@ -324,7 +324,7 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion temp.SetReactState(ReactStates.Aggressive); temp.SetInCombatWith(player); player.SetInCombatWith(temp); - temp.AddThreat(player, 0.0f); + temp.GetThreatManager().AddThreat(player, 0.0f); } } } @@ -386,8 +386,8 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion { if (player && !player.IsGameMaster() && me.IsInRange(player, 8.0f, 25.0f, false)) { - DoResetThreat(); - me.AddThreat(player, 5.0f); + ResetThreatList(); + me.GetThreatManager().AddThreat(player, 5.0f); DoCast(player, TrialOfChampionSpells.INTERCEPT); break; } @@ -549,7 +549,7 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion _scheduler.Schedule(TimeSpan.FromSeconds(12), task => { ObjectGuid uiTargetGUID = ObjectGuid.Empty; - Unit target = SelectTarget(SelectAggroTarget.Farthest, 0, 30.0f); + Unit target = SelectTarget(SelectAggroTarget.MaxDistance, 0, 30.0f); if (target) { uiTargetGUID = target.GetGUID(); diff --git a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/TrialOfTheChampion.cs b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/TrialOfTheChampion.cs index 65e58d9c8..3602d7ba0 100644 --- a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/TrialOfTheChampion.cs +++ b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/TrialOfTheChampion.cs @@ -448,7 +448,7 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion temp.SetReactState(ReactStates.Aggressive); temp.SetInCombatWith(player); player.SetInCombatWith(temp); - temp.AddThreat(player, 0.0f); + AddThreat(player, 0.0f, temp); } } } diff --git a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/LordJaraxxus.cs b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/LordJaraxxus.cs index c9f73c79e..0097beb41 100644 --- a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/LordJaraxxus.cs +++ b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/LordJaraxxus.cs @@ -155,7 +155,7 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheCrusader return; case Jaraxxus.EventFelLightning: { - Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true, -Jaraxxus.SpellLordHittin); + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true, true, -Jaraxxus.SpellLordHittin); if (target) DoCast(target, Jaraxxus.SpellFelLighting); _events.ScheduleEvent(Jaraxxus.EventFelLightning, RandomHelper.URand(10 * Time.InMilliseconds, 15 * Time.InMilliseconds)); @@ -163,7 +163,7 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheCrusader } case Jaraxxus.EventIncinerateFlesh: { - Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, -Jaraxxus.SpellLordHittin); + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, true, -Jaraxxus.SpellLordHittin); if (target) { Talk(Jaraxxus.EmoteIncinerate, target); @@ -179,7 +179,7 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheCrusader return; case Jaraxxus.EventLegionFlame: { - Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, -Jaraxxus.SpellLordHittin); + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, true, -Jaraxxus.SpellLordHittin); if (target) { Talk(Jaraxxus.EmoteLegionFlame, target); diff --git a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusader.cs b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusader.cs index 7cf97307e..8f9e55e79 100644 --- a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusader.cs +++ b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusader.cs @@ -1174,8 +1174,7 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheCrusader { //1-shot Fizzlebang temp.CastSpell(me, 67888, false); - me.SetInCombatWith(temp); - temp.AddThreat(me, 1000.0f); + AddThreat(me, 1000.0f, temp); temp.GetAI().AttackStart(me); } _instance.SetData(DataTypes.Event, 1160); diff --git a/Source/Scripts/Northrend/FrozenHalls/PitOfSaron/BossKrickAndIck.cs b/Source/Scripts/Northrend/FrozenHalls/PitOfSaron/BossKrickAndIck.cs index c61351dbf..69d27ecc3 100644 --- a/Source/Scripts/Northrend/FrozenHalls/PitOfSaron/BossKrickAndIck.cs +++ b/Source/Scripts/Northrend/FrozenHalls/PitOfSaron/BossKrickAndIck.cs @@ -174,8 +174,8 @@ namespace Scripts.Northrend.FrozenHalls.PitOfSaron.BossKrickAndIck public void _ResetThreat(Unit target) { - DoModifyThreatPercent(target, -100); - me.AddThreat(target, _tempThreat); + ModifyThreatByPercent(target, -100); + AddThreat(target, _tempThreat); } public override void UpdateAI(uint diff) @@ -617,8 +617,8 @@ namespace Scripts.Northrend.FrozenHalls.PitOfSaron.BossKrickAndIck ick.GetAI().Talk(TextIds.SayIckChase1, target); ick.AddAura(GetSpellInfo().Id, target); ick.GetAI().SetTempThreat(ick.GetThreatManager().GetThreat(target)); - ick.AddThreat(target, GetEffectValue()); - target.AddThreat(ick, GetEffectValue()); + ick.GetThreatManager().AddThreat(target, GetEffectValue(), GetSpellInfo(), true, true); + target.GetThreatManager().AddThreat(ick, GetEffectValue(), GetSpellInfo(), true, true); } } } diff --git a/Source/Scripts/Northrend/IcecrownCitadel/GunshipBattle.cs b/Source/Scripts/Northrend/IcecrownCitadel/GunshipBattle.cs index 0f4a79ff5..1824c4f87 100644 --- a/Source/Scripts/Northrend/IcecrownCitadel/GunshipBattle.cs +++ b/Source/Scripts/Northrend/IcecrownCitadel/GunshipBattle.cs @@ -587,7 +587,7 @@ namespace Scripts.Northrend.IcecrownCitadel if (!me.IsAlive() || !me.IsInCombat()) return; - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); me.CombatStop(true); me.GetMotionMaster().MoveTargetedHome(); } @@ -733,11 +733,10 @@ namespace Scripts.Northrend.IcecrownCitadel foreach (var stalker in creatures) { stalker.RemoveAllAuras(); - stalker.DeleteThreatList(); + stalker.GetThreatManager().ClearAllThreat(); stalker.CombatStop(true); } - uint explosionSpell = isVictory ? GunshipSpells.ExplosionVictory : GunshipSpells.ExplosionWipe; creatures.Clear(); me.GetCreatureListWithEntryInGrid(creatures, CreatureIds.GunshipHull, 200.0f); @@ -872,7 +871,7 @@ namespace Scripts.Northrend.IcecrownCitadel if (!me.IsAlive()) return; - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); me.CombatStop(true); me.GetMotionMaster().MoveTargetedHome(); @@ -1132,7 +1131,7 @@ namespace Scripts.Northrend.IcecrownCitadel if (!me.IsAlive()) return; - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); me.CombatStop(true); me.GetMotionMaster().MoveTargetedHome(); @@ -1442,7 +1441,7 @@ namespace Scripts.Northrend.IcecrownCitadel { players.Sort(new ObjectDistanceOrderPred(me)); foreach (var pl in players) - me.AddThreat(pl, 1.0f); + AddThreat(pl, 1.0f); AttackStart(players.First()); } diff --git a/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs b/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs index cd55cfa24..89c5f175b 100644 --- a/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs +++ b/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs @@ -646,7 +646,7 @@ namespace Scripts.Northrend.IcecrownCitadel Talk(Texts.SaySvalnaAggro); break; case EventTypes.ImpalingSpear: - Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, -(int)InstanceSpells.ImpalingSpear); + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, true, -(int)InstanceSpells.ImpalingSpear); if (target) { DoCast(me, InstanceSpells.AetherShield); diff --git a/Source/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs b/Source/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs index a7b48aaf9..446d34023 100644 --- a/Source/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs +++ b/Source/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs @@ -1509,7 +1509,7 @@ namespace Scripts.Northrend.IcecrownCitadel if (warden) { terenas.GetAI().AttackStart(warden); - warden.AddThreat(terenas, 300000.0f); + warden.GetThreatManager().AddThreat(terenas, 300000.0f); } } break; diff --git a/Source/Scripts/Northrend/IcecrownCitadel/LadyDeathwhisper.cs b/Source/Scripts/Northrend/IcecrownCitadel/LadyDeathwhisper.cs index 47490adc4..102f35497 100644 --- a/Source/Scripts/Northrend/IcecrownCitadel/LadyDeathwhisper.cs +++ b/Source/Scripts/Northrend/IcecrownCitadel/LadyDeathwhisper.cs @@ -313,7 +313,7 @@ namespace Scripts.Northrend.IcecrownCitadel Talk(TextIds.SayDominateMind); for (byte i = 0; i < _dominateMindCount; i++) { - Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, -(int)SpellIds.DominateMind); + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, false, -(int)SpellIds.DominateMind); if (target != null) DoCast(target, SpellIds.DominateMind); } @@ -423,7 +423,7 @@ namespace Scripts.Northrend.IcecrownCitadel Talk(TextIds.SayPhase2); Talk(TextIds.EmotePhase2); DoStartMovement(me.GetVictim()); - DoResetThreat(); + ResetThreatList(); damage -= (uint)me.GetPower(PowerType.Mana); me.SetPower(PowerType.Mana, 0); diff --git a/Source/Scripts/Northrend/IcecrownCitadel/LordMarrowgar.cs b/Source/Scripts/Northrend/IcecrownCitadel/LordMarrowgar.cs index 87d6c6625..ae656c957 100644 --- a/Source/Scripts/Northrend/IcecrownCitadel/LordMarrowgar.cs +++ b/Source/Scripts/Northrend/IcecrownCitadel/LordMarrowgar.cs @@ -470,8 +470,8 @@ namespace Scripts.Northrend.IcecrownCitadel void SelectTarget(List targets) { targets.Clear(); - // select any unit but not the tank (by owners threatlist) - Unit target = GetCaster().GetAI().SelectTarget(SelectAggroTarget.Random, 1, -GetCaster().GetCombatReach(), true, -(int)Spells.Impaled); + // select any unit but not the tank + Unit target = GetCaster().GetAI().SelectTarget(SelectAggroTarget.Random, 1, -GetCaster().GetCombatReach(), true, false, -(int)Spells.Impaled); if (!target) target = GetCaster().GetAI().SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); // or the tank if its solo if (!target) @@ -565,7 +565,7 @@ namespace Scripts.Northrend.IcecrownCitadel CreatureAI marrowgarAI = marrowgar.GetAI(); byte boneSpikeCount = (byte)(Convert.ToBoolean((int)GetCaster().GetMap().GetDifficultyID() & 1) ? 3 : 1); - List targets = marrowgarAI.SelectTargetList(new BoneSpikeTargetSelector(marrowgarAI), boneSpikeCount, SelectAggroTarget.Random); + List targets = marrowgarAI.SelectTargetList(boneSpikeCount, SelectAggroTarget.Random, 1, new BoneSpikeTargetSelector(marrowgarAI)); if (targets.Empty()) return; diff --git a/Source/Scripts/Northrend/Ulduar/Mimiron.cs b/Source/Scripts/Northrend/Ulduar/Mimiron.cs index 61982e07e..f6584f06d 100644 --- a/Source/Scripts/Northrend/Ulduar/Mimiron.cs +++ b/Source/Scripts/Northrend/Ulduar/Mimiron.cs @@ -1352,10 +1352,10 @@ namespace Scripts.Northrend.Ulduar if (me.HasUnitState(UnitState.Root)) { - Unit newTarget = SelectTarget(SelectAggroTarget.Nearest, 0, 30.0f, true); + Unit newTarget = SelectTarget(SelectAggroTarget.MinDistance, 0, 30.0f, true); if (newTarget) { - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); AttackStart(newTarget); } } diff --git a/Source/Scripts/Outlands/HellfirePeninsula.cs b/Source/Scripts/Outlands/HellfirePeninsula.cs index d01a13969..aa79cbef5 100644 --- a/Source/Scripts/Outlands/HellfirePeninsula.cs +++ b/Source/Scripts/Outlands/HellfirePeninsula.cs @@ -73,7 +73,7 @@ namespace Scripts.Outlands me.SetFaction(Aeranas.FactionFriendly); me.AddNpcFlag(NPCFlags.QuestGiver); me.RemoveAllAuras(); - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); me.CombatStop(true); Talk(Aeranas.SayFree); return; diff --git a/Source/Scripts/Pets/Priest.cs b/Source/Scripts/Pets/Priest.cs index c2001da9c..41a2ca698 100644 --- a/Source/Scripts/Pets/Priest.cs +++ b/Source/Scripts/Pets/Priest.cs @@ -42,7 +42,7 @@ namespace Scripts.Pets.Priest if (!me.IsAlive()) return; - me.DeleteThreatList(); + me.GetThreatManager().ClearAllThreat(); me.CombatStop(true); me.ResetPlayerDamageReq(); } diff --git a/Source/Scripts/World/BossEmeraldDragons.cs b/Source/Scripts/World/BossEmeraldDragons.cs index af29e22de..e80a71c16 100644 --- a/Source/Scripts/World/BossEmeraldDragons.cs +++ b/Source/Scripts/World/BossEmeraldDragons.cs @@ -146,7 +146,7 @@ namespace Scripts.World.BossEmeraldDragons _scheduler.Update(diff); - Unit target = SelectTarget(SelectAggroTarget.TopAggro, 0, -50.0f, true); + Unit target = SelectTarget(SelectAggroTarget.MaxThreat, 0, -50.0f, true); if (target) DoCast(target, Spells.SummonPlayer); diff --git a/Source/Scripts/World/NpcSpecial.cs b/Source/Scripts/World/NpcSpecial.cs index a3894a01a..005898afe 100644 --- a/Source/Scripts/World/NpcSpecial.cs +++ b/Source/Scripts/World/NpcSpecial.cs @@ -1559,7 +1559,7 @@ namespace Scripts.World.NpcSpecial public override void DamageTaken(Unit doneBy, ref uint damage) { - me.AddThreat(doneBy, damage); // just to create threat reference + AddThreat(doneBy, damage); // just to create threat reference _damageTimes[doneBy.GetGUID()] = Time.UnixTime; damage = 0; }