Core: Combat/threat system rewrite
Port From (https://github.com/TrinityCore/TrinityCore/commit/34c7810fe507eca1b8b9389630db5d5d26d92e77)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+798
-461
File diff suppressed because it is too large
Load Diff
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -475,7 +475,7 @@ namespace Game.Entities
|
||||
if (IsInAreaThatActivatesPvpTalents())
|
||||
return;
|
||||
|
||||
if (GetCombatTimer() == 0)
|
||||
if (!GetCombatManager().HasPvPCombat())
|
||||
{
|
||||
RemoveAurasDueToSpell(PlayerConst.SpellPvpRulesEnabled);
|
||||
UpdateItemLevelAreaBasedScaling();
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1567
-66
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user