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
+34 -40
View File
@@ -62,41 +62,42 @@ namespace Game.AI
if (!creature)
creature = me;
if (!creature.CanHaveThreatList())
return;
Map map = creature.GetMap();
if (!map.IsDungeon()) //use IsDungeon instead of Instanceable, in case Battlegrounds will be instantiated
if (creature.CanHaveThreatList())
{
Log.outError(LogFilter.Server, "DoZoneInCombat call for map that isn't an instance (creature entry = {0})", creature.IsTypeId(TypeId.Unit) ? creature.ToCreature().GetEntry() : 0);
return;
}
if (!creature.HasReactState(ReactStates.Passive) && creature.GetVictim() == null)
{
Unit nearTarget = creature.SelectNearestTarget(maxRangeToNearestTarget);
if (nearTarget != null)
creature.GetAI().AttackStart(nearTarget);
else if (creature.IsSummon())
if (!map.IsDungeon()) //use IsDungeon instead of Instanceable, in case Battlegrounds will be instantiated
{
Unit summoner = creature.ToTempSummon().GetSummoner();
if (summoner != null)
Log.outError(LogFilter.Server, "DoZoneInCombat call for map that isn't an instance (creature entry = {0})", creature.IsTypeId(TypeId.Unit) ? creature.ToCreature().GetEntry() : 0);
return;
}
if (!creature.HasReactState(ReactStates.Passive) && creature.GetVictim() == null)
{
Unit nearTarget = creature.SelectNearestTarget(maxRangeToNearestTarget);
if (nearTarget != null)
creature.GetAI().AttackStart(nearTarget);
else if (creature.IsSummon())
{
Unit target = summoner.GetAttackerForHelper();
if (target == null && summoner.CanHaveThreatList() && !summoner.GetThreatManager().IsThreatListEmpty())
target = summoner.GetThreatManager().GetHostilTarget();
if (target != null && (creature.IsFriendlyTo(summoner) || creature.IsHostileTo(target)))
creature.GetAI().AttackStart(target);
Unit summoner = creature.ToTempSummon().GetSummoner();
if (summoner != null)
{
if (creature.IsFriendlyTo(summoner))
{
Unit target = summoner.GetAttackerForHelper();
if (target != null && creature.IsHostileTo(target))
creature.GetAI().AttackStart(target);
}
}
}
}
}
// Intended duplicated check, the code above this should select a victim
// If it can't find a suitable attack target then we should error out.
if (!creature.HasReactState(ReactStates.Passive) && creature.GetVictim() == null)
{
Log.outError(LogFilter.Server, "DoZoneInCombat called for creature that has empty threat list (creature entry = {0})", creature.GetEntry());
return;
// Intended duplicated check, the code above this should select a victim
// If it can't find a suitable attack target then we should error out.
if (!creature.HasReactState(ReactStates.Passive) && creature.GetVictim() == null)
{
Log.outError(LogFilter.Server, "DoZoneInCombat called for creature that has empty threat list (creature entry = {0})", creature.GetEntry());
return;
}
}
var playerList = map.GetPlayers();
@@ -104,17 +105,8 @@ namespace Game.AI
return;
foreach (var player in playerList)
{
if (player.IsGameMaster())
continue;
if (player.IsAlive())
{
creature.SetInCombatWith(player);
player.SetInCombatWith(creature);
creature.GetThreatManager().AddThreat(player, 0.0f, null, true, true);
}
}
if (player != null && player.IsAlive())
creature.EngageWithTarget(player);
}
public virtual void MoveInLineOfSight_Safe(Unit who)
@@ -247,11 +239,13 @@ namespace Game.AI
return me.GetVictim() != null;
}
else if (me.GetThreatManager().IsThreatListEmpty())
else if (me.GetThreatManager().IsThreatListEmpty(true))
{
EnterEvadeMode(EvadeReason.NoHostiles);
return false;
}
else
me.AttackStop();
return true;
}
+3 -10
View File
@@ -56,8 +56,6 @@ namespace Game.AI
me.GetMotionMaster().Clear();
me.GetMotionMaster().MoveIdle();
me.CombatStop();
me.GetHostileRefManager().DeleteReferences();
return;
}
@@ -454,9 +452,9 @@ namespace Game.AI
}
}
me.ClearInPetCombat();
me.RemoveUnitFlag(UnitFlags.PetInCombat); // on player pets, this flag indicates that we're actively going after a target - we're returning, so remove it
}
void DoAttack(Unit target, bool chase)
{
// Handles attack with or without chase and also resets flags
@@ -464,12 +462,7 @@ namespace Game.AI
if (me.Attack(target, true))
{
// properly fix fake combat after pet is sent to attack
Unit owner = me.GetOwner();
if (owner != null)
owner.AddUnitFlag(UnitFlags.PetInCombat);
me.AddUnitFlag(UnitFlags.PetInCombat);
me.AddUnitFlag(UnitFlags.PetInCombat); // on player pets, this flag indicates we're actively going after a target - that's what we're doing, so set it
// Play sound to let the player know the pet is attacking something it picked on its own
if (me.HasReactState(ReactStates.Aggressive) && !me.GetCharmInfo().IsCommandAttack())
+4 -4
View File
@@ -188,12 +188,12 @@ namespace Game.AI
if (targetType == SelectAggroTarget.MaxDistance || targetType == SelectAggroTarget.MinDistance)
{
foreach (HostileReference refe in mgr.GetThreatList())
foreach (ThreatReference refe in mgr.GetSortedThreatList())
{
if (!refe.IsOnline())
continue;
targetList.Add(refe.GetTarget());
targetList.Add(refe.GetVictim());
}
}
else
@@ -202,12 +202,12 @@ namespace Game.AI
if (currentVictim != null)
targetList.Add(currentVictim);
foreach (HostileReference refe in mgr.GetThreatList())
foreach (ThreatReference refe in mgr.GetSortedThreatList())
{
if (!refe.IsOnline())
continue;
Unit thisTarget = refe.GetTarget();
Unit thisTarget = refe.GetVictim();
if (thisTarget != currentVictim)
targetList.Add(thisTarget);
}
+3 -5
View File
@@ -526,12 +526,10 @@ namespace Game.AI
float x, y, z;
me.GetPosition(out x, out y, out z);
var threatList = me.GetThreatManager().GetThreatList();
foreach (var refe in threatList)
foreach (var pair in me.GetCombatManager().GetPvECombatRefs())
{
Unit target = refe.GetTarget();
if (target)
if (target.IsTypeId(TypeId.Player) && !CheckBoundary(target))
Unit target = pair.Value.GetOther(me);
if (target.IsControlledByPlayer() && !CheckBoundary(target))
target.NearTeleportTo(x, y, z, 0);
}
}
+9 -6
View File
@@ -370,10 +370,10 @@ namespace Game.AI
if (_me == null)
break;
foreach (var refe in _me.GetThreatManager().GetThreatList())
foreach (var refe in _me.GetThreatManager().GetModifiableThreatList())
{
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}");
refe.ModifyThreatByPercent(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.GetVictim().GetGUID()}, value {e.Action.threatPCT.threatINC - e.Action.threatPCT.threatDEC}");
}
break;
}
@@ -2245,6 +2245,9 @@ namespace Game.AI
}
case SmartActions.AddThreat:
{
if (!_me.CanHaveThreatList())
break;
foreach (var target in targets)
if (IsUnit(target))
_me.GetThreatManager().AddThreat(target.ToUnit(), (float)(e.Action.threatPCT.threatINC - (float)e.Action.threatPCT.threatDEC), null, true, true);
@@ -2847,9 +2850,9 @@ namespace Game.AI
{
if (_me != null && _me.CanHaveThreatList())
{
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());
foreach (var refe in _me.GetThreatManager().GetSortedThreatList())
if (e.Target.hostilRandom.maxDist == 0 || _me.IsWithinCombatRange(refe.GetVictim(), e.Target.hostilRandom.maxDist))
targets.Add(refe.GetVictim());
}
break;
}
@@ -727,7 +727,6 @@ namespace Game.BattleGrounds
{
//needed cause else in av some creatures will kill the players at the end
player.CombatStop();
player.GetHostileRefManager().DeleteReferences();
}
// remove temporary currency bonus auras before rewarding player
+53 -25
View File
@@ -441,20 +441,19 @@ namespace Game.Chat
Unit target = handler.GetSelectedUnit();
if (!target)
target = handler.GetSession().GetPlayer();
HostileReference refe = target.GetHostileRefManager().GetFirst();
uint count = 0;
handler.SendSysMessage("Hostil reference list of {0} (guid {1})", target.GetName(), target.GetGUID().ToString());
while (refe != null)
handler.SendSysMessage($"Combat refs: (Combat state: {target.IsInCombat()} | Manager state: {target.GetCombatManager().HasCombat()})");
foreach (var refe in target.GetCombatManager().GetPvPCombatRefs())
{
Unit unit = refe.GetSource().GetOwner();
if (unit)
{
++count;
handler.SendSysMessage(" {0}. {1} ({2}, SpawnId: {3}) - threat {4}", count, unit.GetName(), unit.GetGUID().ToString(), unit.IsTypeId(TypeId.Unit) ? unit.ToCreature().GetSpawnId() : 0, refe.GetThreat());
}
refe = refe.Next();
Unit unit = refe.Value.GetOther(target);
handler.SendSysMessage($"[PvP] {unit.GetName()} (SpawnID {(unit.IsCreature() ? unit.ToCreature().GetSpawnId() : 0)})");
}
handler.SendSysMessage("End of hostil reference list.");
foreach (var refe in target.GetCombatManager().GetPvECombatRefs())
{
Unit unit = refe.Value.GetOther(target);
handler.SendSysMessage($"[PvE] {unit.GetName()} (SpawnID {(unit.IsCreature() ? unit.ToCreature().GetSpawnId() : 0)})");
}
return true;
}
@@ -788,22 +787,51 @@ namespace Game.Chat
[Command("threat", RBACPermissions.CommandDebugThreat)]
static bool HandleDebugThreatListCommand(StringArguments args, CommandHandler handler)
{
Creature target = handler.GetSelectedCreature();
if (!target || target.IsTotem() || target.IsPet())
return false;
Unit target = handler.GetSelectedUnit();
if (target == null)
target = handler.GetSession().GetPlayer();
var threatList = target.GetThreatManager().GetThreatList();
uint count = 0;
handler.SendSysMessage("Threat list of {0} (guid {1})", target.GetName(), target.GetGUID().ToString());
foreach (var refe in threatList)
ThreatManager mgr = target.GetThreatManager();
if (!target.IsAlive())
{
Unit unit = refe.GetTarget();
if (!unit)
continue;
++count;
handler.SendSysMessage(" {0}. {1} (guid {2}) - threat {3}", count, unit.GetName(), unit.GetGUID().ToString(), refe.GetThreat());
handler.SendSysMessage($"{target.GetName()} ({target.GetGUID()}) is not alive.");
return true;
}
handler.SendSysMessage("End of threat list.");
if (!target.CanHaveThreatList())
handler.SendSysMessage($"{target.GetName()} ({target.GetGUID()}) cannot have a threat list.");
uint count = 0;
var threatenedByMe = target.GetThreatManager().GetThreatenedByMeList();
if (threatenedByMe.Empty())
handler.SendSysMessage($"{target.GetName()} ({target.GetGUID()}) does not threaten any units.");
else
{
handler.SendSysMessage($"List of units threatened by {target.GetName()} ({target.GetGUID()})");
foreach (var pair in threatenedByMe)
{
Unit unit = pair.Value.GetOwner();
handler.SendSysMessage($" {++count}. {unit.GetName()} ({unit.GetGUID()}, SpawnID {(unit.IsCreature() ? unit.ToCreature().GetSpawnId() : 0)}) - threat {pair.Value.GetThreat()}");
}
handler.SendSysMessage("End of threatened-by-me list.");
}
if (!mgr.CanHaveThreatList())
return true;
if (mgr.IsEngaged())
{
count = 0;
handler.SendSysMessage($"Threat list of {target.GetName()} ({target.GetGUID()}, SpawnID {(target.IsCreature() ? target.ToCreature().GetSpawnId() : 0)})");
foreach (ThreatReference refe in mgr.GetSortedThreatList())
{
Unit unit = refe.GetVictim();
handler.SendSysMessage($" {++count}. {unit.GetName()} ({unit.GetGUID()}) - threat {refe.GetThreat()}[{refe.GetTauntState()}][{refe.GetOnlineState()}]");
}
handler.SendSysMessage("End of threat list.");
}
else
handler.SendSysMessage($"{target.GetName()} ({target.GetGUID()}, SpawnID {(target.IsCreature() ? target.ToCreature().GetSpawnId() : 0)}) is not currently engaged.");
return true;
}
@@ -446,7 +446,6 @@ namespace Game.Chat
return false;
target.CombatStop();
target.GetHostileRefManager().DeleteReferences();
return true;
}
+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
+137 -122
View File
@@ -19,6 +19,7 @@ using Framework.Constants;
using Framework.Database;
using Framework.Dynamic;
using Game.AI;
using Game.Combat;
using Game.DataStorage;
using Game.Groups;
using Game.Loots;
@@ -420,6 +421,8 @@ namespace Game.Entities
UpdateMovementFlags();
LoadCreaturesAddon();
LoadTemplateImmunities();
GetThreatManager().UpdateOnlineStates(true, true);
return true;
}
@@ -511,6 +514,8 @@ namespace Game.Entities
if (!IsAlive())
break;
GetThreatManager().Update(diff);
if (m_shouldReacquireTarget && !IsFocusing(null, true))
{
SetTarget(m_suppressedTarget);
@@ -903,10 +908,110 @@ namespace Game.Entities
ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBackDest, true);
}
GetThreatManager().Initialize();
return true;
}
void InitializeReactState()
public Unit SelectVictim()
{
Unit target = null;
ThreatManager mgr = GetThreatManager();
if (mgr.CanHaveThreatList())
{
target = mgr.SelectVictim();
while (!target)
{
Unit newTarget = null;
// nothing found to attack - try to find something we're in combat with (but don't have a threat entry for yet) and start attacking it
foreach (var pair in GetCombatManager().GetPvECombatRefs())
{
newTarget = pair.Value.GetOther(this);
if (!mgr.IsThreatenedBy(newTarget, true))
{
mgr.AddThreat(newTarget, 0.0f, null, true, true);
break;
}
else
newTarget = null;
}
if (!newTarget)
break;
target = mgr.SelectVictim();
}
}
else if (!HasReactState(ReactStates.Passive))
{
// We're a player pet, probably
target = GetAttackerForHelper();
if (!target && IsSummon())
{
Unit owner = ToTempSummon().GetOwner();
if (owner != null)
{
if (owner.IsInCombat())
target = owner.GetAttackerForHelper();
if (!target)
{
foreach (var itr in owner.m_Controlled)
{
if (itr.IsInCombat())
{
target = itr.GetAttackerForHelper();
if (target)
break;
}
}
}
}
}
}
else
return null;
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
{
if (!IsFocusing(null, true))
SetInFront(target);
return target;
}
/// @todo a vehicle may eat some mob, so mob should not evade
if (GetVehicle())
return null;
// search nearby enemy before enter evade mode
if (HasReactState(ReactStates.Passive))
{
target = SelectNearestTargetInAttackDistance(m_CombatDistance != 0 ? m_CombatDistance : SharedConst.AttackDistance);
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
return target;
}
var iAuras = GetAuraEffectsByType(AuraType.ModInvisibility);
if (!iAuras.Empty())
{
foreach (var itr in iAuras)
{
if (itr.GetBase().IsPermanent())
{
GetAI().EnterEvadeMode(EvadeReason.Other);
break;
}
}
return null;
}
// enter in evade mode in other case
GetAI().EnterEvadeMode(EvadeReason.NoHostiles);
return null;
}
public void InitializeReactState()
{
if (IsTotem() || IsTrigger() || IsCritter() || IsSpiritService())
SetReactState(ReactStates.Passive);
@@ -994,6 +1099,37 @@ namespace Game.Entities
&& !GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoXpAtKill);
}
public override void AtEnterCombat()
{
base.AtEnterCombat();
if (!GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.MountedCombatAllowed))
Dismount();
if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
{
UpdateSpeed(UnitMoveType.Run);
UpdateSpeed(UnitMoveType.Swim);
UpdateSpeed(UnitMoveType.Flight);
}
}
public override void AtExitCombat()
{
base.AtExitCombat();
ClearUnitState(UnitState.AttackPlayer);
if (HasDynamicFlag(UnitDynFlags.Tapped))
SetDynamicFlags((UnitDynFlags)GetCreatureTemplate().DynamicFlags);
if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
{
UpdateSpeed(UnitMoveType.Run);
UpdateSpeed(UnitMoveType.Swim);
UpdateSpeed(UnitMoveType.Flight);
}
}
public bool IsEscortNPC(bool onlyIfActive = true)
{
if (!IsAIEnabled)
@@ -3147,127 +3283,6 @@ namespace Game.Entities
// There's many places not ready for dynamic spawns. This allows them to live on for now.
void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; }
public bool GetRespawnCompatibilityMode() { return m_respawnCompatibilityMode; }
public Unit SelectVictim()
{
// function provides main threat functionality
// next-victim-selection algorithm and evade mode are called
// threat list sorting etc.
Unit target = null;
// First checking if we have some taunt on us
var tauntAuras = GetAuraEffectsByType(AuraType.ModTaunt);
if (!tauntAuras.Empty())
{
Unit caster = tauntAuras.Last().GetCaster();
// The last taunt aura caster is alive an we are happy to attack him
if (caster != null && caster.IsAlive())
return GetVictim();
else if (tauntAuras.Count > 1)
{
// We do not have last taunt aura caster but we have more taunt auras,
// so find first available target
// Auras are pushed_back, last caster will be on the end
for (var i = tauntAuras.Count - 1; i >= 0; i--)
{
caster = tauntAuras[i].GetCaster();
if (caster != null && CanSeeOrDetect(caster, true) && IsValidAttackTarget(caster) && caster.IsInAccessiblePlaceFor(ToCreature()))
{
target = caster;
break;
}
}
}
else
target = GetVictim();
}
if (CanHaveThreatList())
{
if (target == null && !GetThreatManager().IsThreatListEmpty())
// No taunt aura or taunt aura caster is dead standard target selection
target = GetThreatManager().GetHostilTarget();
}
else if (!HasReactState(ReactStates.Passive))
{
// We have player pet probably
target = GetAttackerForHelper();
if (target == null && IsSummon())
{
Unit owner = ToTempSummon().GetOwner();
if (owner != null)
{
if (owner.IsInCombat())
target = owner.GetAttackerForHelper();
if (target == null)
{
foreach (var unit in owner.m_Controlled)
{
if (unit.IsInCombat())
{
target = unit.GetAttackerForHelper();
if (target)
break;
}
}
}
}
}
}
else
return null;
if (target != null && _IsTargetAcceptable(target) && CanCreatureAttack(target))
{
if (!IsFocusing(null, true))
SetInFront(target);
return target;
}
// last case when creature must not go to evade mode:
// it in combat but attacker not make any damage and not enter to aggro radius to have record in threat list
// Note: creature does not have targeted movement generator but has attacker in this case
foreach (var unit in attackerList)
{
if (CanCreatureAttack(unit) && !unit.IsTypeId(TypeId.Player)
&& !unit.ToCreature().HasUnitTypeMask(UnitTypeMask.ControlableGuardian))
return null;
}
// @todo a vehicle may eat some mob, so mob should not evade
if (GetVehicle() != null)
return null;
// search nearby enemy before enter evade mode
if (HasReactState(ReactStates.Aggressive))
{
target = SelectNearestTargetInAttackDistance(m_CombatDistance != 0 ? m_CombatDistance : SharedConst.AttackDistance);
if (target != null && _IsTargetAcceptable(target) && CanCreatureAttack(target))
return target;
}
var iAuras = GetAuraEffectsByType(AuraType.ModInvisibility);
if (!iAuras.Empty())
{
foreach (var aura in iAuras)
{
if (aura.GetBase().IsPermanent())
{
GetAI().EnterEvadeMode();
break;
}
}
return null;
}
// enter in evade mode in other case
GetAI().EnterEvadeMode(EvadeReason.NoHostiles);
return null;
}
}
public class VendorItemCount
@@ -1604,6 +1604,11 @@ namespace Game.Entities
{
return GetPhaseShift().CanSee(obj.GetPhaseShift());
}
public static bool InSamePhase(WorldObject a, WorldObject b)
{
return a != null && b != null && a.IsInPhase(b);
}
public virtual float GetCombatReach() { return 0.0f; } // overridden (only) in Unit
public PhaseShift GetPhaseShift() { return _phaseShift; }
+2
View File
@@ -1343,6 +1343,8 @@ namespace Game.Entities
AddUnitFlag2(UnitFlags2.RegeneratePower);
SetSheath(SheathState.Melee);
GetThreatManager().Initialize();
return true;
}
@@ -342,6 +342,20 @@ namespace Game.Entities
UpdateDamagePhysical(attType);
}
public override void AtEnterCombat()
{
base.AtEnterCombat();
if (GetCombatManager().HasPvPCombat())
EnablePvpRules(true);
}
public override void AtExitCombat()
{
base.AtExitCombat();
UpdatePotionCooldown();
m_combatExitTime = Time.GetMSTime();
}
public override float GetBlockPercent(uint attackerLevel)
{
float blockArmor = (float)m_activePlayerData.ShieldBlock;
+1 -1
View File
@@ -475,7 +475,7 @@ namespace Game.Entities
if (IsInAreaThatActivatesPvpTalents())
return;
if (GetCombatTimer() == 0)
if (!GetCombatManager().HasPvPCombat())
{
RemoveAurasDueToSpell(PlayerConst.SpellPvpRulesEnabled);
UpdateItemLevelAreaBasedScaling();
+4 -22
View File
@@ -316,6 +316,8 @@ namespace Game.Entities
SetPrimarySpecialization(defaultSpec.Id);
}
GetThreatManager().Initialize();
return true;
}
public override void Update(uint diff)
@@ -358,7 +360,7 @@ namespace Game.Entities
UpdateAfkReport(now);
if (GetCombatTimer() != 0) // Only set when in pvp combat
if (GetCombatManager().HasPvPCombat()) // Only set when in pvp combat
{
Aura aura = GetAura(PlayerConst.SpellPvpRulesEnabled);
if (aura != null)
@@ -615,7 +617,7 @@ namespace Game.Entities
{
m_hostileReferenceCheckTimer = 15 * Time.InMilliseconds;
if (!GetMap().IsDungeon())
GetHostileRefManager().DeleteReferencesOutOfRange(GetVisibilityRange());
GetCombatManager().EndCombatBeyondRange(GetVisibilityRange(), true);
}
else
m_hostileReferenceCheckTimer -= diff;
@@ -1908,9 +1910,6 @@ namespace Game.Entities
// Call base
base.SetInWater(inWater);
// Update threat tables
GetHostileRefManager().UpdateThreatTables();
}
public void ValidateMovementInfo(MovementInfo mi)
{
@@ -2151,15 +2150,11 @@ namespace Game.Entities
Pet pet = GetPet();
if (pet != null)
{
pet.SetFaction(35);
pet.GetHostileRefManager().SetOnlineOfflineState(false);
}
RemovePvpFlag(UnitPVPStateFlags.FFAPvp);
ResetContestedPvP();
GetHostileRefManager().SetOnlineOfflineState(false);
CombatStopWithPets();
PhasingHandler.SetAlwaysVisible(this, true, false);
@@ -2176,10 +2171,7 @@ namespace Game.Entities
Pet pet = GetPet();
if (pet != null)
{
pet.SetFaction(GetFaction());
pet.GetHostileRefManager().SetOnlineOfflineState(true);
}
// restore FFA PvP Server state
if (Global.WorldMgr.IsFFAPvPRealm())
@@ -2188,7 +2180,6 @@ namespace Game.Entities
// restore FFA PvP area state, remove not allowed for GM mounts
UpdateArea(m_areaUpdateId);
GetHostileRefManager().SetOnlineOfflineState(true);
m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.GM, AccountTypes.Player);
}
@@ -3918,14 +3909,6 @@ namespace Game.Entities
public static bool IsValidClass(Class _class) { return Convert.ToBoolean((1 << ((int)_class - 1)) & (int)Class.ClassMaskAllPlayable); }
public static bool IsValidRace(Race _race) { return Convert.ToBoolean((ulong)SharedConst.GetMaskForRace(_race) & SharedConst.RaceMaskAllPlayable); }
public override void OnCombatExit()
{
base.OnCombatExit();
UpdatePotionCooldown();
m_combatExitTime = Time.GetMSTime();
}
void LeaveLFGChannel()
{
foreach (var i in m_channels)
@@ -6887,7 +6870,6 @@ namespace Game.Entities
m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
Dismount();
RemoveUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight);
GetHostileRefManager().SetOnlineOfflineState(true);
}
public void ContinueTaxiFlight()
File diff suppressed because it is too large Load Diff
+3 -25
View File
@@ -53,15 +53,14 @@ namespace Game.Entities
protected List<Unit> attackerList = new();
Dictionary<ReactiveType, uint> m_reactiveTimer = new();
protected float[][] m_weaponDamage = new float[(int)WeaponAttackType.Max][];
public float[] m_threatModifier = new float[(int)SpellSchools.Max];
uint[] m_baseAttackSpeed = new uint[(int)WeaponAttackType.Max];
float[] m_modAttackSpeedPct = new float[(int)WeaponAttackType.Max];
protected uint[] m_attackTimer = new uint[(int)WeaponAttackType.Max];
ThreatManager threatManager;
HostileRefManager hostileRefManager;
RedirectThreatInfo _redirectThreatInfo;
CombatManager m_combatManager;
ThreatManager m_threatManager;
protected Unit attacking;
public float ModMeleeHitChance { get; set; }
@@ -520,27 +519,6 @@ namespace Game.Entities
byte _chargesRemoved;
}
public struct RedirectThreatInfo
{
ObjectGuid _targetGUID;
uint _threatPct;
public ObjectGuid GetTargetGUID() { return _targetGUID; }
public uint GetThreatPct() { return _threatPct; }
public void Set(ObjectGuid guid, uint pct)
{
_targetGUID = guid;
_threatPct = pct;
}
public void ModifyThreatPct(int amount)
{
amount += (int)_threatPct;
_threatPct = (uint)(Math.Max(0, amount));
}
}
public class SpellPeriodicAuraLogInfo
{
public SpellPeriodicAuraLogInfo(AuraEffect _auraEff, uint _damage, uint _originalDamage, uint _overDamage, uint _absorb, uint _resist, float _multiplier, bool _critical)
+4 -1
View File
@@ -58,7 +58,10 @@ namespace Game.Entities
return true;
if (HasUnitFlag2((UnitFlags2)0x1000000))
return false;
return HasUnitFlag(UnitFlags.PetInCombat | UnitFlags.Rename | UnitFlags.Unk15);
if (IsPet() && HasUnitFlag(UnitFlags.PetInCombat))
return true;
return HasUnitFlag(UnitFlags.Rename | UnitFlags.Unk15);
}
public virtual bool IsInWater()
{
+28 -3
View File
@@ -274,6 +274,8 @@ namespace Game.Entities
}
}
}
UpdatePetCombatState();
}
public bool SetCharmedBy(Unit charmer, CharmType type, AuraApplication aurApp = null)
@@ -314,7 +316,6 @@ namespace Game.Entities
CastStop();
CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells)
GetThreatManager().ClearAllThreat();
Player playerCharmer = charmer.ToPlayer();
@@ -463,8 +464,6 @@ namespace Game.Entities
CastStop();
CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells)
GetHostileRefManager().DeleteReferences();
GetThreatManager().ClearAllThreat();
if (_oldFactionId != 0)
{
@@ -545,6 +544,8 @@ namespace Game.Entities
player.SetClientControl(this, true);
}
EngageWithTarget(charmer);
// a guardian should always have charminfo
if (playerCharmer && this != charmer.GetFirstControlled())
playerCharmer.SendRemoveControlBar();
@@ -651,6 +652,8 @@ namespace Game.Entities
m_Controlled.Remove(charm);
}
}
UpdatePetCombatState();
}
public Unit GetFirstControlled()
@@ -698,6 +701,8 @@ namespace Game.Entities
Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its minion {1}", GetEntry(), GetMinionGUID());
if (!GetCharmGUID().IsEmpty())
Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its charm {1}", GetEntry(), GetCharmGUID());
if (!IsPet()) // pets don't use the flag for this
RemoveUnitFlag(UnitFlags.PetInCombat); // m_controlled is now empty, so we know none of our minions are in combat
}
public void SendPetActionFeedback(PetActionFeedback msg, uint spellId)
@@ -794,5 +799,25 @@ namespace Game.Entities
pet.SetFullHealth();
return true;
}
public void UpdatePetCombatState()
{
Cypher.Assert(!IsPet()); // player pets do not use UNIT_FLAG_PET_IN_COMBAT for this purpose - but player pets should also never have minions of their own to call this
bool state = false;
foreach (Unit minion in m_Controlled)
{
if (minion.IsInCombat())
{
state = true;
break;
}
}
if (state)
AddUnitFlag(UnitFlags.PetInCombat);
else
RemoveUnitFlag(UnitFlags.PetInCombat);
}
}
}
+1 -8
View File
@@ -1514,14 +1514,7 @@ namespace Game.Entities
SendCombatLogMessage(data);
}
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)
public void EnergizeBySpell(Unit victim, SpellInfo spellInfo, int damage, PowerType powerType)
{
int gain = victim.ModifyPower(powerType, damage, false);
int overEnergize = damage - gain;
File diff suppressed because it is too large Load Diff
-3
View File
@@ -122,7 +122,6 @@ namespace Game
return;
pet.AttackStop();
pet.ClearInPetCombat();
}
void HandlePetActionHelper(Unit pet, ObjectGuid guid1, uint spellid, ActiveStates flag, ObjectGuid guid2, float x, float y, float z)
@@ -156,7 +155,6 @@ namespace Game
case CommandStates.Follow: //spellid=1792 //FOLLOW
pet.AttackStop();
pet.InterruptNonMeleeSpells(false);
pet.ClearInPetCombat();
pet.GetMotionMaster().MoveFollow(GetPlayer(), SharedConst.PetFollowDist, pet.GetFollowAngle());
charmInfo.SetCommandState(CommandStates.Follow);
@@ -271,7 +269,6 @@ namespace Game
{
case ReactStates.Passive: //passive
pet.AttackStop();
pet.ClearInPetCombat();
goto case ReactStates.Defensive;
case ReactStates.Defensive: //recovery
case ReactStates.Aggressive: //activete
+6 -15
View File
@@ -762,22 +762,13 @@ namespace Game.Maps
// Handle updates for creatures in combat with player and are more than 60 yards away
if (player.IsInCombat())
{
List<Creature> updateList = new();
HostileReference refe = player.GetHostileRefManager().GetFirst();
while (refe != null)
foreach (var pair in player.GetCombatManager().GetPvECombatRefs())
{
Unit unit = refe.GetSource().GetOwner();
if (unit)
if (unit.ToCreature() && unit.GetMapId() == player.GetMapId() && !unit.IsWithinDistInMap(player, GetVisibilityRange(), false))
updateList.Add(unit.ToCreature());
refe = refe.Next();
Creature unit = pair.Value.GetOther(player).ToCreature();
if (unit != null)
if (unit.GetMapId() == player.GetMapId() && !unit.IsWithinDistInMap(player, GetVisibilityRange(), false))
VisitNearbyCellsOf(unit, grid_object_update, world_object_update);
}
// Process deferred update list for player
foreach (Creature c in updateList)
VisitNearbyCellsOf(c, grid_object_update, world_object_update);
}
}
@@ -927,7 +918,7 @@ namespace Game.Maps
player.UpdateZone(MapConst.InvalidZone, 0);
Global.ScriptMgr.OnPlayerLeaveMap(this, player);
player.GetHostileRefManager().DeleteReferences(); // multithreading crashfix
player.CombatStop();
bool inWorld = player.IsInWorld;
player.RemoveFromWorld();
+1 -1
View File
@@ -221,7 +221,7 @@ namespace Game.Maps
creature.RemoveAllDynObjects();
creature.RemoveAllAreaTriggers();
if (creature.IsInCombat() || !creature.GetThreatManager().IsThreatListsEmpty())
if (creature.IsInCombat())
{
creature.CombatStop();
creature.GetThreatManager().ClearAllThreat();
@@ -108,7 +108,6 @@ namespace Game.Movement
if (owner.m_taxi.Empty())
{
owner.GetHostileRefManager().SetOnlineOfflineState(true);
// update z position to ground and orientation for landing point
// this prevent cheating with landing point at lags
// when client side flight end early in comparison server side
@@ -122,8 +121,8 @@ namespace Game.Movement
public override void DoReset(Player owner)
{
owner.GetHostileRefManager().SetOnlineOfflineState(false);
owner.AddUnitState(UnitState.InFlight);
owner.CombatStopWithPets();
owner.AddUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight);
MoveSplineInit init = new(owner);
+1 -1
View File
@@ -108,7 +108,7 @@ namespace Game
//FIXME: logout must be delayed in case lost connection with client in time of combat
if (GetPlayer().GetDeathTimer() != 0)
{
_player.GetHostileRefManager().DeleteReferences();
_player.CombatStop();
_player.BuildPlayerRepop();
_player.RepopAtGraveyard();
}
+58 -26
View File
@@ -1522,6 +1522,8 @@ namespace Game.Spells
}
}
}
target.GetThreatManager().UpdateOnlineStates(true, false);
}
[AuraEffectHandler(AuraType.ModScale)]
@@ -1596,13 +1598,21 @@ namespace Game.Spells
}
}
}
target.CombatStop();
if (target.GetMap().IsDungeon()) // feign death does not remove combat in dungeons
{
target.AttackStop();
Player targetPlayer = target.ToPlayer();
if (targetPlayer != null)
targetPlayer.SendAttackSwingCancelAttack();
}
else
target.CombatStop(false, false);
// prevent interrupt message
if (GetCasterGUID() == target.GetGUID() && target.GetCurrentSpell(CurrentSpellTypes.Generic) != null)
target.FinishSpell(CurrentSpellTypes.Generic, false);
target.InterruptNonMeleeSpells(true);
target.GetHostileRefManager().DeleteReferences();
// stop handling the effect if it was removed by linked event
if (aurApp.HasRemoveMode())
@@ -1612,6 +1622,10 @@ namespace Game.Spells
target.AddUnitFlag2(UnitFlags2.FeignDeath);
target.AddDynamicFlag(UnitDynFlags.Dead);
target.AddUnitState(UnitState.Died);
Creature creature = target.ToCreature();
if (creature != null)
creature.SetReactState(ReactStates.Passive);
}
else
{
@@ -1619,7 +1633,13 @@ namespace Game.Spells
target.RemoveUnitFlag2(UnitFlags2.FeignDeath);
target.RemoveDynamicFlag(UnitDynFlags.Dead);
target.ClearUnitState(UnitState.Died);
Creature creature = target.ToCreature();
if (creature != null)
creature.InitializeReactState();
}
target.GetThreatManager().UpdateOnlineStates(true, false);
}
[AuraEffectHandler(AuraType.ModUnattackable)]
@@ -1641,7 +1661,17 @@ namespace Game.Spells
// call functions which may have additional effects after chainging state of unit
if (apply && mode.HasAnyFlag(AuraEffectHandleModes.Real))
target.CombatStop();
{
if (target.GetMap().IsDungeon())
{
target.AttackStop();
Player targetPlayer = target.ToPlayer();
if (targetPlayer != null)
targetPlayer.SendAttackSwingCancelAttack();
}
else
target.CombatStop();
}
}
[AuraEffectHandler(AuraType.ModDisarm)]
@@ -2239,18 +2269,7 @@ namespace Game.Spells
if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask))
return;
Unit target = aurApp.GetTarget();
for (byte i = 0; i < (int)SpellSchools.Max; ++i)
if (Convert.ToBoolean(GetMiscValue() & (1 << i)))
{
if (apply)
MathFunctions.AddPct(ref target.m_threatModifier[i], GetAmount());
else
{
float amount = target.GetTotalAuraMultiplierByMiscMask(AuraType.ModThreat, 1u << i);
target.m_threatModifier[i] = amount;
}
}
aurApp.GetTarget().GetThreatManager().UpdateMySpellSchoolModifiers();
}
[AuraEffectHandler(AuraType.ModTotalThreat)]
@@ -2266,7 +2285,7 @@ namespace Game.Spells
Unit caster = GetCaster();
if (caster != null && caster.IsAlive())
target.GetHostileRefManager().AddTempThreat(GetAmount(), apply);
caster.GetThreatManager().UpdateMyTempModifiers();
}
[AuraEffectHandler(AuraType.ModTaunt)]
@@ -2280,17 +2299,22 @@ namespace Game.Spells
if (!target.IsAlive() || !target.CanHaveThreatList())
return;
Unit caster = GetCaster();
if (caster == null || !caster.IsAlive())
target.GetThreatManager().TauntUpdate();
}
[AuraEffectHandler(AuraType.ModDetaunt)]
void HandleModDetaunt(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply)
{
if (!mode.HasAnyFlag(AuraEffectHandleModes.Real))
return;
if (apply)
target.TauntApply(caster);
else
{
// When taunt aura fades out, mob will switch to previous target if current has less than 1.1 * secondthreat
target.TauntFadeOut(caster);
}
Unit caster = GetCaster();
Unit target = aurApp.GetTarget();
if (!caster || !caster.IsAlive() || !target.IsAlive() || !caster.CanHaveThreatList())
return;
caster.GetThreatManager().TauntUpdate();
}
/*****************************/
@@ -2305,6 +2329,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Confused);
target.GetThreatManager().UpdateOnlineStates(true, false);
}
[AuraEffectHandler(AuraType.ModFear)]
@@ -2316,6 +2341,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Fleeing);
target.GetThreatManager().UpdateOnlineStates(true, false);
}
[AuraEffectHandler(AuraType.ModStun)]
@@ -2327,6 +2353,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Stunned);
target.GetThreatManager().UpdateOnlineStates(true, false);
}
[AuraEffectHandler(AuraType.ModRoot)]
@@ -2339,6 +2366,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Root);
target.GetThreatManager().UpdateOnlineStates(true, false);
}
[AuraEffectHandler(AuraType.PreventsFleeing)]
@@ -2743,6 +2771,8 @@ namespace Game.Spells
&& GetSpellInfo().HasAttribute(SpellAttr2.DamageReducedShield))
target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.StealthOrInvis);
}
target.GetThreatManager().UpdateOnlineStates(true, false);
}
[AuraEffectHandler(AuraType.DamageImmunity)]
@@ -2753,6 +2783,8 @@ namespace Game.Spells
Unit target = aurApp.GetTarget();
m_spellInfo.ApplyAllSpellImmunitiesTo(target, GetSpellEffectInfo(), apply);
target.GetThreatManager().UpdateOnlineStates(true, false);
}
[AuraEffectHandler(AuraType.DispelImmunity)]
@@ -4466,7 +4498,7 @@ namespace Game.Spells
player.GetReputationMgr().ApplyForceReaction(factionId, factionRank, apply);
player.GetReputationMgr().SendForceReactions();
// stop fighting if at apply forced rank friendly or at remove real rank friendly
// stop fighting at apply (if forced rank friendly) or at remove (if real rank friendly)
if ((apply && factionRank >= ReputationRank.Friendly) || (!apply && player.GetReputationRank(factionId) >= ReputationRank.Friendly))
player.StopAttackFaction(factionId);
}
+7 -2
View File
@@ -2033,7 +2033,7 @@ namespace Game.Spells
// spellHitTarget can be null if spell is missed in DoSpellHitOnUnit
if (missInfo != SpellMissInfo.Evade && spellHitTarget && !m_caster.IsFriendlyTo(unit) && (!IsPositive() || m_spellInfo.HasEffect(SpellEffectName.Dispel)))
{
m_caster.CombatStart(unit, m_spellInfo.HasInitialAggro());
m_caster.AttackedTarget(unit, m_spellInfo.HasInitialAggro());
if (!unit.IsStandState())
unit.SetStandState(UnitStandStateType.Stand);
@@ -2133,7 +2133,8 @@ namespace Game.Spells
}
if (unit.IsInCombat() && m_spellInfo.HasInitialAggro())
{
m_caster.SetInCombatState(unit.GetCombatTimer() > 0, unit);
if (m_caster.HasUnitFlag(UnitFlags.PvpAttackable)) // only do explicit combat forwarding for PvP enabled units
m_caster.GetCombatManager().InheritCombatStatesFrom(unit); // for creature v creature combat, the threat forward does it for us
unit.GetThreatManager().ForwardThreatForAssistingMe(m_caster, 0.0f, null, true);
}
}
@@ -6849,6 +6850,10 @@ namespace Game.Spells
if (unit == null)
return;
// This will only cause combat - the target will engage once the projectile hits (in DoAllEffectOnTarget)
if (targetInfo.missCondition != SpellMissInfo.Evade && !m_caster.IsFriendlyTo(unit) && (!m_spellInfo.IsPositive() || m_spellInfo.HasEffect(SpellEffectName.Dispel)) && (m_spellInfo.HasInitialAggro() || unit.IsEngaged()))
m_caster.SetInCombatWith(unit);
foreach (SpellEffectInfo effect in m_spellInfo.GetEffects())
{
if (effect != null && Convert.ToBoolean(targetInfo.effectMask & (1 << (int)effect.EffectIndex)))
+24 -30
View File
@@ -788,7 +788,7 @@ namespace Game.Spells
int gain = (int)(newDamage * gainMultiplier);
m_caster.EnergizeBySpell(m_caster, m_spellInfo.Id, gain, powerType);
m_caster.EnergizeBySpell(m_caster, m_spellInfo, gain, powerType);
}
ExecuteLogEffectTakeTargetPower(effIndex, unitTarget, powerType, (uint)newDamage, gainMultiplier);
}
@@ -1229,7 +1229,7 @@ namespace Game.Spells
break;
}
m_caster.EnergizeBySpell(unitTarget, m_spellInfo.Id, damage, power);
m_caster.EnergizeBySpell(unitTarget, m_spellInfo, damage, power);
}
[SpellEffectHandler(SpellEffectName.EnergizePct)]
@@ -1252,7 +1252,7 @@ namespace Game.Spells
return;
int gain = (int)MathFunctions.CalculatePct(maxPower, damage);
m_caster.EnergizeBySpell(unitTarget, m_spellInfo.Id, gain, power);
m_caster.EnergizeBySpell(unitTarget, m_spellInfo, gain, power);
}
void SendLoot(ObjectGuid guid, LootType loottype)
@@ -2411,28 +2411,22 @@ namespace Game.Spells
// this effect use before aura Taunt apply for prevent taunt already attacking target
// for spell as marked "non effective at already attacking target"
if (unitTarget == null || !unitTarget.CanHaveThreatList() || unitTarget.GetVictim() == m_caster)
if (unitTarget == null || !unitTarget.CanHaveThreatList())
{
SendCastResult(SpellCastResult.DontReport);
return;
}
if (!unitTarget.GetThreatManager().GetOnlineContainer().Empty())
ThreatManager mgr = unitTarget.GetThreatManager();
if (mgr.GetCurrentVictim() == m_caster)
{
// Also use this effect to set the taunter's threat to the taunted creature's highest value
float myThreat = unitTarget.GetThreatManager().GetThreat(m_caster);
float topThreat = unitTarget.GetThreatManager().GetOnlineContainer().GetMostHated().GetThreat();
if (topThreat > myThreat)
unitTarget.GetThreatManager().AddThreat(m_caster, topThreat - myThreat);
//Set aggro victim to caster
HostileReference forcedVictim = unitTarget.GetThreatManager().GetOnlineContainer().GetReferenceByTarget(m_caster);
if (forcedVictim != null)
unitTarget.GetThreatManager().SetCurrentVictim(forcedVictim);
SendCastResult(SpellCastResult.DontReport);
return;
}
if (unitTarget.ToCreature().IsAIEnabled && !unitTarget.ToCreature().HasReactState(ReactStates.Passive))
unitTarget.ToCreature().GetAI().AttackStart(m_caster);
if (!mgr.IsThreatListEmpty())
// Set threat equal to highest threat currently on target
mgr.MatchUnitThreatToHighestThreat(m_caster);
}
[SpellEffectHandler(SpellEffectName.WeaponDamageNoSchool)]
@@ -2654,13 +2648,13 @@ namespace Game.Spells
if (effectHandleMode != SpellEffectHandleMode.HitTarget)
return;
if (unitTarget == null || !unitTarget.IsAlive() || !m_caster.IsAlive())
if (unitTarget == null || !m_caster.IsAlive())
return;
if (!unitTarget.CanHaveThreatList())
return;
unitTarget.GetThreatManager().AddThreat(m_caster, damage);
unitTarget.GetThreatManager().AddThreat(m_caster, damage, m_spellInfo, true);
}
[SpellEffectHandler(SpellEffectName.HealMaxHealth)]
@@ -3241,19 +3235,19 @@ namespace Game.Spells
if (unitTarget == null)
return;
if (unitTarget.IsTypeId(TypeId.Player))
unitTarget.ToPlayer().SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
unitTarget.GetHostileRefManager().UpdateVisibility();
var attackers = unitTarget.GetAttackers();
for (var i = 0; i < attackers.Count; ++i)
if (unitTarget.IsPlayer() && !unitTarget.GetMap().IsDungeon())
{
var unit = attackers[i];
if (!unit.CanSeeOrDetect(unitTarget))
unit.AttackStop();
// stop all pve combat for players outside dungeons, suppress pvp combat
unitTarget.CombatStop(false, false);
}
else
{
// in dungeons (or for nonplayers), reset this unit on all enemies' threat lists
foreach (var pair in unitTarget.GetThreatManager().GetThreatenedByMeList())
pair.Value.SetThreat(0.0f);
}
// makes spells cast before this time fizzle
unitTarget.LastSanctuaryTime = GameTime.GetGameTimeMS();
}
@@ -4808,7 +4802,7 @@ namespace Game.Spells
return;
if (unitTarget != null)
m_caster.SetRedirectThreat(unitTarget.GetGUID(), (uint)damage);
m_caster.GetThreatManager().RegisterRedirectThreat(m_spellInfo.Id, unitTarget.GetGUID(), (uint)damage);
}
[SpellEffectHandler(SpellEffectName.GameObjectDamage)]
+2 -2
View File
@@ -1022,8 +1022,8 @@ namespace Game.Spells
// creature/player specific target checks
if (unitTarget != null)
{
// spells cannot be cast if player is in fake combat also
if (HasAttribute(SpellAttr1.CantTargetInCombat) && (unitTarget.IsInCombat() || unitTarget.IsPetInCombat()))
// spells cannot be cast if target has a pet in combat either
if (HasAttribute(SpellAttr1.CantTargetInCombat) && (unitTarget.IsInCombat() || unitTarget.HasUnitFlag(UnitFlags.PetInCombat)))
return SpellCastResult.TargetAffectingCombat;
// only spells with SPELL_ATTR3_ONLY_TARGET_GHOSTS can target ghosts
+7 -38
View File
@@ -76,21 +76,13 @@ namespace Scripts.Pets
continue;
}
// else compare best fit unit with current unit
var triggers = unit.GetThreatManager().GetThreatList();
foreach (var reference in triggers)
float threat = unit.GetThreatManager().GetThreat(owner);
// Check if best fit hostile unit hs lower threat than this current unit
if (highestThreat < threat)
{
// Try to find threat referenced to owner
if (reference.GetTarget() == owner)
{
// Check if best fit hostile unit hs lower threat than this current unit
if (highestThreat < reference.GetThreat())
{
// If so, update best fit unit
highestThreat = reference.GetThreat();
highestThreatUnit = unit;
break;
}
}
// If so, update best fit unit
highestThreat = threat;
highestThreatUnit = unit;
}
// In case no unit with threat was found so far, always check for nearest unit (only for players)
if (unit.IsTypeId(TypeId.Player))
@@ -112,30 +104,7 @@ namespace Scripts.Pets
bool IsInThreatList(Unit target)
{
Unit owner = me.GetCharmerOrOwner();
List<Unit> targets = new List<Unit>();
var u_check = new AnyUnfriendlyUnitInObjectRangeCheck(me, me, 30.0f);
var searcher = new UnitListSearcher(me, targets, u_check);
Cell.VisitAllObjects(me, searcher, 40.0f);
foreach (var unit in targets)
{
if (unit == target)
{
// Consider only units without CC
if (!unit.HasBreakableByDamageCrowdControlAura(unit))
{
var triggers = unit.GetThreatManager().GetThreatList();
foreach (var reference in triggers)
{
// Try to find threat referenced to owner
if (reference.GetTarget() == owner)
return true;
}
}
}
}
return false;
return owner && target.IsThreatenedBy(owner);
}
public override void InitializeAI()
+1 -1
View File
@@ -2179,7 +2179,7 @@ namespace Scripts.Spells.Generic
if (mana != 0)
{
mana /= 10;
caster.EnergizeBySpell(caster, GetId(), mana, PowerType.Mana);
caster.EnergizeBySpell(caster, GetSpellInfo(), mana, PowerType.Mana);
}
}
+3 -8
View File
@@ -33,6 +33,7 @@ namespace Scripts.Spells.Hunter
public const uint ExhilarationR2 = 231546;
public const uint Lonewolf = 155228;
public const uint MastersCallTriggered = 62305;
public const uint Misdirection = 34477;
public const uint MisdirectionProc = 35079;
public const uint PetLastStandTriggered = 53479;
public const uint PetHeartOfThePhoenixTriggered = 54114;
@@ -205,12 +206,7 @@ namespace Scripts.Spells.Hunter
return;
if (!GetTarget().HasAura(SpellIds.MisdirectionProc))
GetTarget().ResetRedirectThreat();
}
bool CheckProc(ProcEventInfo eventInfo)
{
return GetTarget().GetRedirectThreatTarget();
GetTarget().GetThreatManager().UnregisterRedirectThreat(SpellIds.Misdirection);
}
void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo)
@@ -222,7 +218,6 @@ namespace Scripts.Spells.Hunter
public override void Register()
{
AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 1, AuraType.Dummy, AuraEffectHandleModes.Real));
DoCheckProc.Add(new CheckProcHandler(CheckProc));
OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.Dummy));
}
}
@@ -233,7 +228,7 @@ namespace Scripts.Spells.Hunter
{
void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode)
{
GetTarget().ResetRedirectThreat();
GetTarget().GetThreatManager().UnregisterRedirectThreat(SpellIds.Misdirection);
}
public override void Register()
+10 -4
View File
@@ -1550,18 +1550,24 @@ namespace Scripts.World.NpcSpecial
_scheduler.Schedule(TimeSpan.FromSeconds(1), task =>
{
long now = GameTime.GetGameTime();
foreach (var pair in _damageTimes.ToList())
var pveRefs = me.GetCombatManager().GetPvECombatRefs();
foreach (var pair in _damageTimes)
{
// If unit has not dealt damage to training dummy for 5 seconds, Remove him from combat
if (pair.Value < now - 5)
{
Unit unit = Global.ObjAccessor.GetUnit(me, pair.Key);
if (unit)
unit.GetHostileRefManager().DeleteReference(me);
var refe = pveRefs.LookupByKey(pair.Key);
if (refe != null)
refe.EndCombat();
_damageTimes.Remove(pair.Key);
}
}
foreach (var pair in pveRefs)
if (!_damageTimes.ContainsKey(pair.Key))
_damageTimes[pair.Key] = now;
task.Repeat();
});
}