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) if (!creature)
creature = me; creature = me;
if (!creature.CanHaveThreatList())
return;
Map map = creature.GetMap(); 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); if (!map.IsDungeon()) //use IsDungeon instead of Instanceable, in case Battlegrounds will be instantiated
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 summoner = creature.ToTempSummon().GetSummoner(); Log.outError(LogFilter.Server, "DoZoneInCombat call for map that isn't an instance (creature entry = {0})", creature.IsTypeId(TypeId.Unit) ? creature.ToCreature().GetEntry() : 0);
if (summoner != null) 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(); Unit summoner = creature.ToTempSummon().GetSummoner();
if (target == null && summoner.CanHaveThreatList() && !summoner.GetThreatManager().IsThreatListEmpty()) if (summoner != null)
target = summoner.GetThreatManager().GetHostilTarget(); {
if (target != null && (creature.IsFriendlyTo(summoner) || creature.IsHostileTo(target))) if (creature.IsFriendlyTo(summoner))
creature.GetAI().AttackStart(target); {
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 // 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 it can't find a suitable attack target then we should error out.
if (!creature.HasReactState(ReactStates.Passive) && creature.GetVictim() == null) 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()); Log.outError(LogFilter.Server, "DoZoneInCombat called for creature that has empty threat list (creature entry = {0})", creature.GetEntry());
return; return;
}
} }
var playerList = map.GetPlayers(); var playerList = map.GetPlayers();
@@ -104,17 +105,8 @@ namespace Game.AI
return; return;
foreach (var player in playerList) foreach (var player in playerList)
{ if (player != null && player.IsAlive())
if (player.IsGameMaster()) creature.EngageWithTarget(player);
continue;
if (player.IsAlive())
{
creature.SetInCombatWith(player);
player.SetInCombatWith(creature);
creature.GetThreatManager().AddThreat(player, 0.0f, null, true, true);
}
}
} }
public virtual void MoveInLineOfSight_Safe(Unit who) public virtual void MoveInLineOfSight_Safe(Unit who)
@@ -247,11 +239,13 @@ namespace Game.AI
return me.GetVictim() != null; return me.GetVictim() != null;
} }
else if (me.GetThreatManager().IsThreatListEmpty()) else if (me.GetThreatManager().IsThreatListEmpty(true))
{ {
EnterEvadeMode(EvadeReason.NoHostiles); EnterEvadeMode(EvadeReason.NoHostiles);
return false; return false;
} }
else
me.AttackStop();
return true; return true;
} }
+2 -9
View File
@@ -56,8 +56,6 @@ namespace Game.AI
me.GetMotionMaster().Clear(); me.GetMotionMaster().Clear();
me.GetMotionMaster().MoveIdle(); me.GetMotionMaster().MoveIdle();
me.CombatStop(); me.CombatStop();
me.GetHostileRefManager().DeleteReferences();
return; return;
} }
@@ -454,7 +452,7 @@ 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) void DoAttack(Unit target, bool chase)
@@ -464,12 +462,7 @@ namespace Game.AI
if (me.Attack(target, true)) if (me.Attack(target, true))
{ {
// properly fix fake combat after pet is sent to attack 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
Unit owner = me.GetOwner();
if (owner != null)
owner.AddUnitFlag(UnitFlags.PetInCombat);
me.AddUnitFlag(UnitFlags.PetInCombat);
// Play sound to let the player know the pet is attacking something it picked on its own // 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()) 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) if (targetType == SelectAggroTarget.MaxDistance || targetType == SelectAggroTarget.MinDistance)
{ {
foreach (HostileReference refe in mgr.GetThreatList()) foreach (ThreatReference refe in mgr.GetSortedThreatList())
{ {
if (!refe.IsOnline()) if (!refe.IsOnline())
continue; continue;
targetList.Add(refe.GetTarget()); targetList.Add(refe.GetVictim());
} }
} }
else else
@@ -202,12 +202,12 @@ namespace Game.AI
if (currentVictim != null) if (currentVictim != null)
targetList.Add(currentVictim); targetList.Add(currentVictim);
foreach (HostileReference refe in mgr.GetThreatList()) foreach (ThreatReference refe in mgr.GetSortedThreatList())
{ {
if (!refe.IsOnline()) if (!refe.IsOnline())
continue; continue;
Unit thisTarget = refe.GetTarget(); Unit thisTarget = refe.GetVictim();
if (thisTarget != currentVictim) if (thisTarget != currentVictim)
targetList.Add(thisTarget); targetList.Add(thisTarget);
} }
+3 -5
View File
@@ -526,12 +526,10 @@ namespace Game.AI
float x, y, z; float x, y, z;
me.GetPosition(out x, out y, out z); me.GetPosition(out x, out y, out z);
var threatList = me.GetThreatManager().GetThreatList(); foreach (var pair in me.GetCombatManager().GetPvECombatRefs())
foreach (var refe in threatList)
{ {
Unit target = refe.GetTarget(); Unit target = pair.Value.GetOther(me);
if (target) if (target.IsControlledByPlayer() && !CheckBoundary(target))
if (target.IsTypeId(TypeId.Player) && !CheckBoundary(target))
target.NearTeleportTo(x, y, z, 0); target.NearTeleportTo(x, y, z, 0);
} }
} }
+9 -6
View File
@@ -370,10 +370,10 @@ namespace Game.AI
if (_me == null) if (_me == null)
break; 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))); 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.GetTarget().GetGUID()}, value {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; break;
} }
@@ -2245,6 +2245,9 @@ namespace Game.AI
} }
case SmartActions.AddThreat: case SmartActions.AddThreat:
{ {
if (!_me.CanHaveThreatList())
break;
foreach (var target in targets) foreach (var target in targets)
if (IsUnit(target)) if (IsUnit(target))
_me.GetThreatManager().AddThreat(target.ToUnit(), (float)(e.Action.threatPCT.threatINC - (float)e.Action.threatPCT.threatDEC), null, true, true); _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()) if (_me != null && _me.CanHaveThreatList())
{ {
foreach (var refe in _me.GetThreatManager().GetThreatList()) foreach (var refe in _me.GetThreatManager().GetSortedThreatList())
if (e.Target.hostilRandom.maxDist == 0 || _me.IsWithinCombatRange(refe.GetTarget(), e.Target.hostilRandom.maxDist)) if (e.Target.hostilRandom.maxDist == 0 || _me.IsWithinCombatRange(refe.GetVictim(), e.Target.hostilRandom.maxDist))
targets.Add(refe.GetTarget()); targets.Add(refe.GetVictim());
} }
break; break;
} }
@@ -727,7 +727,6 @@ namespace Game.BattleGrounds
{ {
//needed cause else in av some creatures will kill the players at the end //needed cause else in av some creatures will kill the players at the end
player.CombatStop(); player.CombatStop();
player.GetHostileRefManager().DeleteReferences();
} }
// remove temporary currency bonus auras before rewarding player // remove temporary currency bonus auras before rewarding player
+53 -25
View File
@@ -441,20 +441,19 @@ namespace Game.Chat
Unit target = handler.GetSelectedUnit(); Unit target = handler.GetSelectedUnit();
if (!target) if (!target)
target = handler.GetSession().GetPlayer(); target = handler.GetSession().GetPlayer();
HostileReference refe = target.GetHostileRefManager().GetFirst();
uint count = 0; handler.SendSysMessage($"Combat refs: (Combat state: {target.IsInCombat()} | Manager state: {target.GetCombatManager().HasCombat()})");
handler.SendSysMessage("Hostil reference list of {0} (guid {1})", target.GetName(), target.GetGUID().ToString()); foreach (var refe in target.GetCombatManager().GetPvPCombatRefs())
while (refe != null)
{ {
Unit unit = refe.GetSource().GetOwner(); Unit unit = refe.Value.GetOther(target);
if (unit) handler.SendSysMessage($"[PvP] {unit.GetName()} (SpawnID {(unit.IsCreature() ? unit.ToCreature().GetSpawnId() : 0)})");
{
++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();
} }
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; return true;
} }
@@ -788,22 +787,51 @@ namespace Game.Chat
[Command("threat", RBACPermissions.CommandDebugThreat)] [Command("threat", RBACPermissions.CommandDebugThreat)]
static bool HandleDebugThreatListCommand(StringArguments args, CommandHandler handler) static bool HandleDebugThreatListCommand(StringArguments args, CommandHandler handler)
{ {
Creature target = handler.GetSelectedCreature(); Unit target = handler.GetSelectedUnit();
if (!target || target.IsTotem() || target.IsPet()) if (target == null)
return false; target = handler.GetSession().GetPlayer();
var threatList = target.GetThreatManager().GetThreatList(); ThreatManager mgr = target.GetThreatManager();
uint count = 0; if (!target.IsAlive())
handler.SendSysMessage("Threat list of {0} (guid {1})", target.GetName(), target.GetGUID().ToString());
foreach (var refe in threatList)
{ {
Unit unit = refe.GetTarget(); handler.SendSysMessage($"{target.GetName()} ({target.GetGUID()}) is not alive.");
if (!unit) return true;
continue;
++count;
handler.SendSysMessage(" {0}. {1} (guid {2}) - threat {3}", count, unit.GetName(), unit.GetGUID().ToString(), refe.GetThreat());
} }
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; return true;
} }
@@ -446,7 +446,6 @@ namespace Game.Chat
return false; return false;
target.CombatStop(); target.CombatStop();
target.GetHostileRefManager().DeleteReferences();
return true; 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.Database;
using Framework.Dynamic; using Framework.Dynamic;
using Game.AI; using Game.AI;
using Game.Combat;
using Game.DataStorage; using Game.DataStorage;
using Game.Groups; using Game.Groups;
using Game.Loots; using Game.Loots;
@@ -420,6 +421,8 @@ namespace Game.Entities
UpdateMovementFlags(); UpdateMovementFlags();
LoadCreaturesAddon(); LoadCreaturesAddon();
LoadTemplateImmunities(); LoadTemplateImmunities();
GetThreatManager().UpdateOnlineStates(true, true);
return true; return true;
} }
@@ -511,6 +514,8 @@ namespace Game.Entities
if (!IsAlive()) if (!IsAlive())
break; break;
GetThreatManager().Update(diff);
if (m_shouldReacquireTarget && !IsFocusing(null, true)) if (m_shouldReacquireTarget && !IsFocusing(null, true))
{ {
SetTarget(m_suppressedTarget); SetTarget(m_suppressedTarget);
@@ -903,10 +908,110 @@ namespace Game.Entities
ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBackDest, true); ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBackDest, true);
} }
GetThreatManager().Initialize();
return true; 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()) if (IsTotem() || IsTrigger() || IsCritter() || IsSpiritService())
SetReactState(ReactStates.Passive); SetReactState(ReactStates.Passive);
@@ -994,6 +1099,37 @@ namespace Game.Entities
&& !GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoXpAtKill); && !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) public bool IsEscortNPC(bool onlyIfActive = true)
{ {
if (!IsAIEnabled) 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. // 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; } void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; }
public bool GetRespawnCompatibilityMode() { return m_respawnCompatibilityMode; } 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 public class VendorItemCount
@@ -1605,6 +1605,11 @@ namespace Game.Entities
return GetPhaseShift().CanSee(obj.GetPhaseShift()); 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 virtual float GetCombatReach() { return 0.0f; } // overridden (only) in Unit
public PhaseShift GetPhaseShift() { return _phaseShift; } public PhaseShift GetPhaseShift() { return _phaseShift; }
public void SetPhaseShift(PhaseShift phaseShift) { _phaseShift = new PhaseShift(phaseShift); } public void SetPhaseShift(PhaseShift phaseShift) { _phaseShift = new PhaseShift(phaseShift); }
+2
View File
@@ -1343,6 +1343,8 @@ namespace Game.Entities
AddUnitFlag2(UnitFlags2.RegeneratePower); AddUnitFlag2(UnitFlags2.RegeneratePower);
SetSheath(SheathState.Melee); SetSheath(SheathState.Melee);
GetThreatManager().Initialize();
return true; return true;
} }
@@ -342,6 +342,20 @@ namespace Game.Entities
UpdateDamagePhysical(attType); 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) public override float GetBlockPercent(uint attackerLevel)
{ {
float blockArmor = (float)m_activePlayerData.ShieldBlock; float blockArmor = (float)m_activePlayerData.ShieldBlock;
+1 -1
View File
@@ -475,7 +475,7 @@ namespace Game.Entities
if (IsInAreaThatActivatesPvpTalents()) if (IsInAreaThatActivatesPvpTalents())
return; return;
if (GetCombatTimer() == 0) if (!GetCombatManager().HasPvPCombat())
{ {
RemoveAurasDueToSpell(PlayerConst.SpellPvpRulesEnabled); RemoveAurasDueToSpell(PlayerConst.SpellPvpRulesEnabled);
UpdateItemLevelAreaBasedScaling(); UpdateItemLevelAreaBasedScaling();
+4 -22
View File
@@ -316,6 +316,8 @@ namespace Game.Entities
SetPrimarySpecialization(defaultSpec.Id); SetPrimarySpecialization(defaultSpec.Id);
} }
GetThreatManager().Initialize();
return true; return true;
} }
public override void Update(uint diff) public override void Update(uint diff)
@@ -358,7 +360,7 @@ namespace Game.Entities
UpdateAfkReport(now); 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); Aura aura = GetAura(PlayerConst.SpellPvpRulesEnabled);
if (aura != null) if (aura != null)
@@ -615,7 +617,7 @@ namespace Game.Entities
{ {
m_hostileReferenceCheckTimer = 15 * Time.InMilliseconds; m_hostileReferenceCheckTimer = 15 * Time.InMilliseconds;
if (!GetMap().IsDungeon()) if (!GetMap().IsDungeon())
GetHostileRefManager().DeleteReferencesOutOfRange(GetVisibilityRange()); GetCombatManager().EndCombatBeyondRange(GetVisibilityRange(), true);
} }
else else
m_hostileReferenceCheckTimer -= diff; m_hostileReferenceCheckTimer -= diff;
@@ -1908,9 +1910,6 @@ namespace Game.Entities
// Call base // Call base
base.SetInWater(inWater); base.SetInWater(inWater);
// Update threat tables
GetHostileRefManager().UpdateThreatTables();
} }
public void ValidateMovementInfo(MovementInfo mi) public void ValidateMovementInfo(MovementInfo mi)
{ {
@@ -2151,15 +2150,11 @@ namespace Game.Entities
Pet pet = GetPet(); Pet pet = GetPet();
if (pet != null) if (pet != null)
{
pet.SetFaction(35); pet.SetFaction(35);
pet.GetHostileRefManager().SetOnlineOfflineState(false);
}
RemovePvpFlag(UnitPVPStateFlags.FFAPvp); RemovePvpFlag(UnitPVPStateFlags.FFAPvp);
ResetContestedPvP(); ResetContestedPvP();
GetHostileRefManager().SetOnlineOfflineState(false);
CombatStopWithPets(); CombatStopWithPets();
PhasingHandler.SetAlwaysVisible(this, true, false); PhasingHandler.SetAlwaysVisible(this, true, false);
@@ -2176,10 +2171,7 @@ namespace Game.Entities
Pet pet = GetPet(); Pet pet = GetPet();
if (pet != null) if (pet != null)
{
pet.SetFaction(GetFaction()); pet.SetFaction(GetFaction());
pet.GetHostileRefManager().SetOnlineOfflineState(true);
}
// restore FFA PvP Server state // restore FFA PvP Server state
if (Global.WorldMgr.IsFFAPvPRealm()) if (Global.WorldMgr.IsFFAPvPRealm())
@@ -2188,7 +2180,6 @@ namespace Game.Entities
// restore FFA PvP area state, remove not allowed for GM mounts // restore FFA PvP area state, remove not allowed for GM mounts
UpdateArea(m_areaUpdateId); UpdateArea(m_areaUpdateId);
GetHostileRefManager().SetOnlineOfflineState(true);
m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.GM, AccountTypes.Player); 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 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 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() void LeaveLFGChannel()
{ {
foreach (var i in m_channels) foreach (var i in m_channels)
@@ -6887,7 +6870,6 @@ namespace Game.Entities
m_taxi.ClearTaxiDestinations(); // not destinations, clear source node m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
Dismount(); Dismount();
RemoveUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); RemoveUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight);
GetHostileRefManager().SetOnlineOfflineState(true);
} }
public void ContinueTaxiFlight() 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(); protected List<Unit> attackerList = new();
Dictionary<ReactiveType, uint> m_reactiveTimer = new(); Dictionary<ReactiveType, uint> m_reactiveTimer = new();
protected float[][] m_weaponDamage = new float[(int)WeaponAttackType.Max][]; 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]; uint[] m_baseAttackSpeed = new uint[(int)WeaponAttackType.Max];
float[] m_modAttackSpeedPct = new float[(int)WeaponAttackType.Max]; float[] m_modAttackSpeedPct = new float[(int)WeaponAttackType.Max];
protected uint[] m_attackTimer = new uint[(int)WeaponAttackType.Max]; protected uint[] m_attackTimer = new uint[(int)WeaponAttackType.Max];
ThreatManager threatManager; CombatManager m_combatManager;
HostileRefManager hostileRefManager; ThreatManager m_threatManager;
RedirectThreatInfo _redirectThreatInfo;
protected Unit attacking; protected Unit attacking;
public float ModMeleeHitChance { get; set; } public float ModMeleeHitChance { get; set; }
@@ -520,27 +519,6 @@ namespace Game.Entities
byte _chargesRemoved; 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 class SpellPeriodicAuraLogInfo
{ {
public SpellPeriodicAuraLogInfo(AuraEffect _auraEff, uint _damage, uint _originalDamage, uint _overDamage, uint _absorb, uint _resist, float _multiplier, bool _critical) 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; return true;
if (HasUnitFlag2((UnitFlags2)0x1000000)) if (HasUnitFlag2((UnitFlags2)0x1000000))
return false; 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() 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) public bool SetCharmedBy(Unit charmer, CharmType type, AuraApplication aurApp = null)
@@ -314,7 +316,6 @@ namespace Game.Entities
CastStop(); CastStop();
CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells) CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells)
GetThreatManager().ClearAllThreat();
Player playerCharmer = charmer.ToPlayer(); Player playerCharmer = charmer.ToPlayer();
@@ -463,8 +464,6 @@ namespace Game.Entities
CastStop(); CastStop();
CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells) CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells)
GetHostileRefManager().DeleteReferences();
GetThreatManager().ClearAllThreat();
if (_oldFactionId != 0) if (_oldFactionId != 0)
{ {
@@ -545,6 +544,8 @@ namespace Game.Entities
player.SetClientControl(this, true); player.SetClientControl(this, true);
} }
EngageWithTarget(charmer);
// a guardian should always have charminfo // a guardian should always have charminfo
if (playerCharmer && this != charmer.GetFirstControlled()) if (playerCharmer && this != charmer.GetFirstControlled())
playerCharmer.SendRemoveControlBar(); playerCharmer.SendRemoveControlBar();
@@ -651,6 +652,8 @@ namespace Game.Entities
m_Controlled.Remove(charm); m_Controlled.Remove(charm);
} }
} }
UpdatePetCombatState();
} }
public Unit GetFirstControlled() 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()); Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its minion {1}", GetEntry(), GetMinionGUID());
if (!GetCharmGUID().IsEmpty()) if (!GetCharmGUID().IsEmpty())
Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its charm {1}", GetEntry(), GetCharmGUID()); 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) public void SendPetActionFeedback(PetActionFeedback msg, uint spellId)
@@ -794,5 +799,25 @@ namespace Game.Entities
pet.SetFullHealth(); pet.SetFullHealth();
return true; 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); SendCombatLogMessage(data);
} }
public void EnergizeBySpell(Unit victim, uint spellId, int damage, PowerType powerType) public void EnergizeBySpell(Unit victim, SpellInfo spellInfo, int damage, PowerType powerType)
{
SpellInfo info = Global.SpellMgr.GetSpellInfo(spellId, GetMap().GetDifficultyID());
if (info != null)
EnergizeBySpell(victim, info, damage, powerType);
}
void EnergizeBySpell(Unit victim, SpellInfo spellInfo, int damage, PowerType powerType)
{ {
int gain = victim.ModifyPower(powerType, damage, false); int gain = victim.ModifyPower(powerType, damage, false);
int overEnergize = damage - gain; int overEnergize = damage - gain;
File diff suppressed because it is too large Load Diff
-3
View File
@@ -122,7 +122,6 @@ namespace Game
return; return;
pet.AttackStop(); pet.AttackStop();
pet.ClearInPetCombat();
} }
void HandlePetActionHelper(Unit pet, ObjectGuid guid1, uint spellid, ActiveStates flag, ObjectGuid guid2, float x, float y, float z) 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 case CommandStates.Follow: //spellid=1792 //FOLLOW
pet.AttackStop(); pet.AttackStop();
pet.InterruptNonMeleeSpells(false); pet.InterruptNonMeleeSpells(false);
pet.ClearInPetCombat();
pet.GetMotionMaster().MoveFollow(GetPlayer(), SharedConst.PetFollowDist, pet.GetFollowAngle()); pet.GetMotionMaster().MoveFollow(GetPlayer(), SharedConst.PetFollowDist, pet.GetFollowAngle());
charmInfo.SetCommandState(CommandStates.Follow); charmInfo.SetCommandState(CommandStates.Follow);
@@ -271,7 +269,6 @@ namespace Game
{ {
case ReactStates.Passive: //passive case ReactStates.Passive: //passive
pet.AttackStop(); pet.AttackStop();
pet.ClearInPetCombat();
goto case ReactStates.Defensive; goto case ReactStates.Defensive;
case ReactStates.Defensive: //recovery case ReactStates.Defensive: //recovery
case ReactStates.Aggressive: //activete 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 // Handle updates for creatures in combat with player and are more than 60 yards away
if (player.IsInCombat()) if (player.IsInCombat())
{ {
List<Creature> updateList = new(); foreach (var pair in player.GetCombatManager().GetPvECombatRefs())
HostileReference refe = player.GetHostileRefManager().GetFirst();
while (refe != null)
{ {
Unit unit = refe.GetSource().GetOwner(); Creature unit = pair.Value.GetOther(player).ToCreature();
if (unit) if (unit != null)
if (unit.ToCreature() && unit.GetMapId() == player.GetMapId() && !unit.IsWithinDistInMap(player, GetVisibilityRange(), false)) if (unit.GetMapId() == player.GetMapId() && !unit.IsWithinDistInMap(player, GetVisibilityRange(), false))
updateList.Add(unit.ToCreature()); VisitNearbyCellsOf(unit, grid_object_update, world_object_update);
refe = refe.Next();
} }
// 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); player.UpdateZone(MapConst.InvalidZone, 0);
Global.ScriptMgr.OnPlayerLeaveMap(this, player); Global.ScriptMgr.OnPlayerLeaveMap(this, player);
player.GetHostileRefManager().DeleteReferences(); // multithreading crashfix player.CombatStop();
bool inWorld = player.IsInWorld; bool inWorld = player.IsInWorld;
player.RemoveFromWorld(); player.RemoveFromWorld();
+1 -1
View File
@@ -221,7 +221,7 @@ namespace Game.Maps
creature.RemoveAllDynObjects(); creature.RemoveAllDynObjects();
creature.RemoveAllAreaTriggers(); creature.RemoveAllAreaTriggers();
if (creature.IsInCombat() || !creature.GetThreatManager().IsThreatListsEmpty()) if (creature.IsInCombat())
{ {
creature.CombatStop(); creature.CombatStop();
creature.GetThreatManager().ClearAllThreat(); creature.GetThreatManager().ClearAllThreat();
@@ -108,7 +108,6 @@ namespace Game.Movement
if (owner.m_taxi.Empty()) if (owner.m_taxi.Empty())
{ {
owner.GetHostileRefManager().SetOnlineOfflineState(true);
// update z position to ground and orientation for landing point // update z position to ground and orientation for landing point
// this prevent cheating with landing point at lags // this prevent cheating with landing point at lags
// when client side flight end early in comparison server side // when client side flight end early in comparison server side
@@ -122,8 +121,8 @@ namespace Game.Movement
public override void DoReset(Player owner) public override void DoReset(Player owner)
{ {
owner.GetHostileRefManager().SetOnlineOfflineState(false);
owner.AddUnitState(UnitState.InFlight); owner.AddUnitState(UnitState.InFlight);
owner.CombatStopWithPets();
owner.AddUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); owner.AddUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight);
MoveSplineInit init = new(owner); 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 //FIXME: logout must be delayed in case lost connection with client in time of combat
if (GetPlayer().GetDeathTimer() != 0) if (GetPlayer().GetDeathTimer() != 0)
{ {
_player.GetHostileRefManager().DeleteReferences(); _player.CombatStop();
_player.BuildPlayerRepop(); _player.BuildPlayerRepop();
_player.RepopAtGraveyard(); _player.RepopAtGraveyard();
} }
+58 -26
View File
@@ -1522,6 +1522,8 @@ namespace Game.Spells
} }
} }
} }
target.GetThreatManager().UpdateOnlineStates(true, false);
} }
[AuraEffectHandler(AuraType.ModScale)] [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 // prevent interrupt message
if (GetCasterGUID() == target.GetGUID() && target.GetCurrentSpell(CurrentSpellTypes.Generic) != null) if (GetCasterGUID() == target.GetGUID() && target.GetCurrentSpell(CurrentSpellTypes.Generic) != null)
target.FinishSpell(CurrentSpellTypes.Generic, false); target.FinishSpell(CurrentSpellTypes.Generic, false);
target.InterruptNonMeleeSpells(true); target.InterruptNonMeleeSpells(true);
target.GetHostileRefManager().DeleteReferences();
// stop handling the effect if it was removed by linked event // stop handling the effect if it was removed by linked event
if (aurApp.HasRemoveMode()) if (aurApp.HasRemoveMode())
@@ -1612,6 +1622,10 @@ namespace Game.Spells
target.AddUnitFlag2(UnitFlags2.FeignDeath); target.AddUnitFlag2(UnitFlags2.FeignDeath);
target.AddDynamicFlag(UnitDynFlags.Dead); target.AddDynamicFlag(UnitDynFlags.Dead);
target.AddUnitState(UnitState.Died); target.AddUnitState(UnitState.Died);
Creature creature = target.ToCreature();
if (creature != null)
creature.SetReactState(ReactStates.Passive);
} }
else else
{ {
@@ -1619,7 +1633,13 @@ namespace Game.Spells
target.RemoveUnitFlag2(UnitFlags2.FeignDeath); target.RemoveUnitFlag2(UnitFlags2.FeignDeath);
target.RemoveDynamicFlag(UnitDynFlags.Dead); target.RemoveDynamicFlag(UnitDynFlags.Dead);
target.ClearUnitState(UnitState.Died); target.ClearUnitState(UnitState.Died);
Creature creature = target.ToCreature();
if (creature != null)
creature.InitializeReactState();
} }
target.GetThreatManager().UpdateOnlineStates(true, false);
} }
[AuraEffectHandler(AuraType.ModUnattackable)] [AuraEffectHandler(AuraType.ModUnattackable)]
@@ -1641,7 +1661,17 @@ namespace Game.Spells
// call functions which may have additional effects after chainging state of unit // call functions which may have additional effects after chainging state of unit
if (apply && mode.HasAnyFlag(AuraEffectHandleModes.Real)) 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)] [AuraEffectHandler(AuraType.ModDisarm)]
@@ -2239,18 +2269,7 @@ namespace Game.Spells
if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask))
return; return;
Unit target = aurApp.GetTarget(); aurApp.GetTarget().GetThreatManager().UpdateMySpellSchoolModifiers();
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;
}
}
} }
[AuraEffectHandler(AuraType.ModTotalThreat)] [AuraEffectHandler(AuraType.ModTotalThreat)]
@@ -2266,7 +2285,7 @@ namespace Game.Spells
Unit caster = GetCaster(); Unit caster = GetCaster();
if (caster != null && caster.IsAlive()) if (caster != null && caster.IsAlive())
target.GetHostileRefManager().AddTempThreat(GetAmount(), apply); caster.GetThreatManager().UpdateMyTempModifiers();
} }
[AuraEffectHandler(AuraType.ModTaunt)] [AuraEffectHandler(AuraType.ModTaunt)]
@@ -2280,17 +2299,22 @@ namespace Game.Spells
if (!target.IsAlive() || !target.CanHaveThreatList()) if (!target.IsAlive() || !target.CanHaveThreatList())
return; return;
Unit caster = GetCaster(); target.GetThreatManager().TauntUpdate();
if (caster == null || !caster.IsAlive()) }
[AuraEffectHandler(AuraType.ModDetaunt)]
void HandleModDetaunt(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply)
{
if (!mode.HasAnyFlag(AuraEffectHandleModes.Real))
return; return;
if (apply) Unit caster = GetCaster();
target.TauntApply(caster); Unit target = aurApp.GetTarget();
else
{ if (!caster || !caster.IsAlive() || !target.IsAlive() || !caster.CanHaveThreatList())
// When taunt aura fades out, mob will switch to previous target if current has less than 1.1 * secondthreat return;
target.TauntFadeOut(caster);
} caster.GetThreatManager().TauntUpdate();
} }
/*****************************/ /*****************************/
@@ -2305,6 +2329,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget(); Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Confused); target.SetControlled(apply, UnitState.Confused);
target.GetThreatManager().UpdateOnlineStates(true, false);
} }
[AuraEffectHandler(AuraType.ModFear)] [AuraEffectHandler(AuraType.ModFear)]
@@ -2316,6 +2341,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget(); Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Fleeing); target.SetControlled(apply, UnitState.Fleeing);
target.GetThreatManager().UpdateOnlineStates(true, false);
} }
[AuraEffectHandler(AuraType.ModStun)] [AuraEffectHandler(AuraType.ModStun)]
@@ -2327,6 +2353,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget(); Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Stunned); target.SetControlled(apply, UnitState.Stunned);
target.GetThreatManager().UpdateOnlineStates(true, false);
} }
[AuraEffectHandler(AuraType.ModRoot)] [AuraEffectHandler(AuraType.ModRoot)]
@@ -2339,6 +2366,7 @@ namespace Game.Spells
Unit target = aurApp.GetTarget(); Unit target = aurApp.GetTarget();
target.SetControlled(apply, UnitState.Root); target.SetControlled(apply, UnitState.Root);
target.GetThreatManager().UpdateOnlineStates(true, false);
} }
[AuraEffectHandler(AuraType.PreventsFleeing)] [AuraEffectHandler(AuraType.PreventsFleeing)]
@@ -2743,6 +2771,8 @@ namespace Game.Spells
&& GetSpellInfo().HasAttribute(SpellAttr2.DamageReducedShield)) && GetSpellInfo().HasAttribute(SpellAttr2.DamageReducedShield))
target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.StealthOrInvis); target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.StealthOrInvis);
} }
target.GetThreatManager().UpdateOnlineStates(true, false);
} }
[AuraEffectHandler(AuraType.DamageImmunity)] [AuraEffectHandler(AuraType.DamageImmunity)]
@@ -2753,6 +2783,8 @@ namespace Game.Spells
Unit target = aurApp.GetTarget(); Unit target = aurApp.GetTarget();
m_spellInfo.ApplyAllSpellImmunitiesTo(target, GetSpellEffectInfo(), apply); m_spellInfo.ApplyAllSpellImmunitiesTo(target, GetSpellEffectInfo(), apply);
target.GetThreatManager().UpdateOnlineStates(true, false);
} }
[AuraEffectHandler(AuraType.DispelImmunity)] [AuraEffectHandler(AuraType.DispelImmunity)]
@@ -4466,7 +4498,7 @@ namespace Game.Spells
player.GetReputationMgr().ApplyForceReaction(factionId, factionRank, apply); player.GetReputationMgr().ApplyForceReaction(factionId, factionRank, apply);
player.GetReputationMgr().SendForceReactions(); 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)) if ((apply && factionRank >= ReputationRank.Friendly) || (!apply && player.GetReputationRank(factionId) >= ReputationRank.Friendly))
player.StopAttackFaction(factionId); player.StopAttackFaction(factionId);
} }
+7 -2
View File
@@ -2033,7 +2033,7 @@ namespace Game.Spells
// spellHitTarget can be null if spell is missed in DoSpellHitOnUnit // 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))) 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()) if (!unit.IsStandState())
unit.SetStandState(UnitStandStateType.Stand); unit.SetStandState(UnitStandStateType.Stand);
@@ -2133,7 +2133,8 @@ namespace Game.Spells
} }
if (unit.IsInCombat() && m_spellInfo.HasInitialAggro()) 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); unit.GetThreatManager().ForwardThreatForAssistingMe(m_caster, 0.0f, null, true);
} }
} }
@@ -6849,6 +6850,10 @@ namespace Game.Spells
if (unit == null) if (unit == null)
return; 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()) foreach (SpellEffectInfo effect in m_spellInfo.GetEffects())
{ {
if (effect != null && Convert.ToBoolean(targetInfo.effectMask & (1 << (int)effect.EffectIndex))) 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); 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); ExecuteLogEffectTakeTargetPower(effIndex, unitTarget, powerType, (uint)newDamage, gainMultiplier);
} }
@@ -1229,7 +1229,7 @@ namespace Game.Spells
break; break;
} }
m_caster.EnergizeBySpell(unitTarget, m_spellInfo.Id, damage, power); m_caster.EnergizeBySpell(unitTarget, m_spellInfo, damage, power);
} }
[SpellEffectHandler(SpellEffectName.EnergizePct)] [SpellEffectHandler(SpellEffectName.EnergizePct)]
@@ -1252,7 +1252,7 @@ namespace Game.Spells
return; return;
int gain = (int)MathFunctions.CalculatePct(maxPower, damage); 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) 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 // this effect use before aura Taunt apply for prevent taunt already attacking target
// for spell as marked "non effective at 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); SendCastResult(SpellCastResult.DontReport);
return; 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 SendCastResult(SpellCastResult.DontReport);
float myThreat = unitTarget.GetThreatManager().GetThreat(m_caster); return;
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);
} }
if (unitTarget.ToCreature().IsAIEnabled && !unitTarget.ToCreature().HasReactState(ReactStates.Passive)) if (!mgr.IsThreatListEmpty())
unitTarget.ToCreature().GetAI().AttackStart(m_caster); // Set threat equal to highest threat currently on target
mgr.MatchUnitThreatToHighestThreat(m_caster);
} }
[SpellEffectHandler(SpellEffectName.WeaponDamageNoSchool)] [SpellEffectHandler(SpellEffectName.WeaponDamageNoSchool)]
@@ -2654,13 +2648,13 @@ namespace Game.Spells
if (effectHandleMode != SpellEffectHandleMode.HitTarget) if (effectHandleMode != SpellEffectHandleMode.HitTarget)
return; return;
if (unitTarget == null || !unitTarget.IsAlive() || !m_caster.IsAlive()) if (unitTarget == null || !m_caster.IsAlive())
return; return;
if (!unitTarget.CanHaveThreatList()) if (!unitTarget.CanHaveThreatList())
return; return;
unitTarget.GetThreatManager().AddThreat(m_caster, damage); unitTarget.GetThreatManager().AddThreat(m_caster, damage, m_spellInfo, true);
} }
[SpellEffectHandler(SpellEffectName.HealMaxHealth)] [SpellEffectHandler(SpellEffectName.HealMaxHealth)]
@@ -3241,19 +3235,19 @@ namespace Game.Spells
if (unitTarget == null) if (unitTarget == null)
return; return;
if (unitTarget.IsTypeId(TypeId.Player)) if (unitTarget.IsPlayer() && !unitTarget.GetMap().IsDungeon())
unitTarget.ToPlayer().SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
unitTarget.GetHostileRefManager().UpdateVisibility();
var attackers = unitTarget.GetAttackers();
for (var i = 0; i < attackers.Count; ++i)
{ {
var unit = attackers[i]; // stop all pve combat for players outside dungeons, suppress pvp combat
if (!unit.CanSeeOrDetect(unitTarget)) unitTarget.CombatStop(false, false);
unit.AttackStop(); }
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(); unitTarget.LastSanctuaryTime = GameTime.GetGameTimeMS();
} }
@@ -4808,7 +4802,7 @@ namespace Game.Spells
return; return;
if (unitTarget != null) if (unitTarget != null)
m_caster.SetRedirectThreat(unitTarget.GetGUID(), (uint)damage); m_caster.GetThreatManager().RegisterRedirectThreat(m_spellInfo.Id, unitTarget.GetGUID(), (uint)damage);
} }
[SpellEffectHandler(SpellEffectName.GameObjectDamage)] [SpellEffectHandler(SpellEffectName.GameObjectDamage)]
+2 -2
View File
@@ -1022,8 +1022,8 @@ namespace Game.Spells
// creature/player specific target checks // creature/player specific target checks
if (unitTarget != null) if (unitTarget != null)
{ {
// spells cannot be cast if player is in fake combat also // spells cannot be cast if target has a pet in combat either
if (HasAttribute(SpellAttr1.CantTargetInCombat) && (unitTarget.IsInCombat() || unitTarget.IsPetInCombat())) if (HasAttribute(SpellAttr1.CantTargetInCombat) && (unitTarget.IsInCombat() || unitTarget.HasUnitFlag(UnitFlags.PetInCombat)))
return SpellCastResult.TargetAffectingCombat; return SpellCastResult.TargetAffectingCombat;
// only spells with SPELL_ATTR3_ONLY_TARGET_GHOSTS can target ghosts // only spells with SPELL_ATTR3_ONLY_TARGET_GHOSTS can target ghosts
+7 -38
View File
@@ -76,21 +76,13 @@ namespace Scripts.Pets
continue; continue;
} }
// else compare best fit unit with current unit // else compare best fit unit with current unit
var triggers = unit.GetThreatManager().GetThreatList(); float threat = unit.GetThreatManager().GetThreat(owner);
foreach (var reference in triggers) // Check if best fit hostile unit hs lower threat than this current unit
if (highestThreat < threat)
{ {
// Try to find threat referenced to owner // If so, update best fit unit
if (reference.GetTarget() == owner) highestThreat = threat;
{ highestThreatUnit = unit;
// 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;
}
}
} }
// In case no unit with threat was found so far, always check for nearest unit (only for players) // In case no unit with threat was found so far, always check for nearest unit (only for players)
if (unit.IsTypeId(TypeId.Player)) if (unit.IsTypeId(TypeId.Player))
@@ -112,30 +104,7 @@ namespace Scripts.Pets
bool IsInThreatList(Unit target) bool IsInThreatList(Unit target)
{ {
Unit owner = me.GetCharmerOrOwner(); Unit owner = me.GetCharmerOrOwner();
return owner && target.IsThreatenedBy(owner);
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;
} }
public override void InitializeAI() public override void InitializeAI()
+1 -1
View File
@@ -2179,7 +2179,7 @@ namespace Scripts.Spells.Generic
if (mana != 0) if (mana != 0)
{ {
mana /= 10; 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 ExhilarationR2 = 231546;
public const uint Lonewolf = 155228; public const uint Lonewolf = 155228;
public const uint MastersCallTriggered = 62305; public const uint MastersCallTriggered = 62305;
public const uint Misdirection = 34477;
public const uint MisdirectionProc = 35079; public const uint MisdirectionProc = 35079;
public const uint PetLastStandTriggered = 53479; public const uint PetLastStandTriggered = 53479;
public const uint PetHeartOfThePhoenixTriggered = 54114; public const uint PetHeartOfThePhoenixTriggered = 54114;
@@ -205,12 +206,7 @@ namespace Scripts.Spells.Hunter
return; return;
if (!GetTarget().HasAura(SpellIds.MisdirectionProc)) if (!GetTarget().HasAura(SpellIds.MisdirectionProc))
GetTarget().ResetRedirectThreat(); GetTarget().GetThreatManager().UnregisterRedirectThreat(SpellIds.Misdirection);
}
bool CheckProc(ProcEventInfo eventInfo)
{
return GetTarget().GetRedirectThreatTarget();
} }
void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo)
@@ -222,7 +218,6 @@ namespace Scripts.Spells.Hunter
public override void Register() public override void Register()
{ {
AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 1, AuraType.Dummy, AuraEffectHandleModes.Real)); AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 1, AuraType.Dummy, AuraEffectHandleModes.Real));
DoCheckProc.Add(new CheckProcHandler(CheckProc));
OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.Dummy)); OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.Dummy));
} }
} }
@@ -233,7 +228,7 @@ namespace Scripts.Spells.Hunter
{ {
void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode)
{ {
GetTarget().ResetRedirectThreat(); GetTarget().GetThreatManager().UnregisterRedirectThreat(SpellIds.Misdirection);
} }
public override void Register() public override void Register()
+10 -4
View File
@@ -1550,18 +1550,24 @@ namespace Scripts.World.NpcSpecial
_scheduler.Schedule(TimeSpan.FromSeconds(1), task => _scheduler.Schedule(TimeSpan.FromSeconds(1), task =>
{ {
long now = GameTime.GetGameTime(); 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 unit has not dealt damage to training dummy for 5 seconds, Remove him from combat
if (pair.Value < now - 5) if (pair.Value < now - 5)
{ {
Unit unit = Global.ObjAccessor.GetUnit(me, pair.Key); var refe = pveRefs.LookupByKey(pair.Key);
if (unit) if (refe != null)
unit.GetHostileRefManager().DeleteReference(me); refe.EndCombat();
_damageTimes.Remove(pair.Key); _damageTimes.Remove(pair.Key);
} }
} }
foreach (var pair in pveRefs)
if (!_damageTimes.ContainsKey(pair.Key))
_damageTimes[pair.Key] = now;
task.Repeat(); task.Repeat();
}); });
} }