aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/game/AI/CoreAI/PetAI.cpp11
-rw-r--r--src/server/game/AI/CoreAI/UnitAI.h1
-rw-r--r--src/server/game/AI/CreatureAI.cpp66
-rw-r--r--src/server/game/AI/PlayerAI/PlayerAI.h1
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.cpp11
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.h2
-rw-r--r--src/server/game/AI/SmartScripts/SmartScript.cpp2
-rw-r--r--src/server/game/Battlegrounds/Battleground.cpp3
-rw-r--r--src/server/game/Combat/CombatManager.cpp350
-rw-r--r--src/server/game/Combat/CombatManager.h143
-rw-r--r--src/server/game/Combat/HostileRefManager.cpp216
-rw-r--r--src/server/game/Combat/HostileRefManager.h76
-rw-r--r--src/server/game/Combat/ThreatManager.cpp1011
-rw-r--r--src/server/game/Combat/ThreatManager.h517
-rw-r--r--src/server/game/Combat/UnitEvents.h121
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp134
-rw-r--r--src/server/game/Entities/Creature/Creature.h17
-rw-r--r--src/server/game/Entities/Object/Object.h4
-rw-r--r--src/server/game/Entities/Pet/Pet.cpp2
-rw-r--r--src/server/game/Entities/Player/Player.cpp41
-rw-r--r--src/server/game/Entities/Player/Player.h4
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp713
-rw-r--r--src/server/game/Entities/Unit/Unit.h119
-rw-r--r--src/server/game/Grids/ObjectGridLoader.cpp2
-rw-r--r--src/server/game/Handlers/PetHandler.cpp3
-rw-r--r--src/server/game/Maps/Map.cpp21
-rw-r--r--src/server/game/Movement/MovementGenerators/FlightPathMovementGenerator.cpp3
-rw-r--r--src/server/game/Server/WorldSession.cpp2
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp80
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.h1
-rw-r--r--src/server/game/Spells/Spell.cpp11
-rw-r--r--src/server/game/Spells/SpellEffects.cpp54
-rw-r--r--src/server/game/Spells/SpellInfo.cpp4
-rw-r--r--src/server/scripts/Commands/cs_debug.cpp97
-rw-r--r--src/server/scripts/Commands/cs_misc.cpp1
-rw-r--r--src/server/scripts/EasternKingdoms/BaradinHold/boss_occuthar.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp52
-rw-r--r--src/server/scripts/EasternKingdoms/MagistersTerrace/boss_priestess_delrissa.cpp26
-rw-r--r--src/server/scripts/EasternKingdoms/TheStockade/boss_randolph_moloch.cpp1
-rw-r--r--src/server/scripts/EasternKingdoms/ZulGurub/boss_mandokir.cpp4
-rw-r--r--src/server/scripts/Kalimdor/Firelands/boss_alysrazor.cpp2
-rw-r--r--src/server/scripts/Kalimdor/Firelands/firelands.cpp2
-rw-r--r--src/server/scripts/Maelstrom/Stonecore/boss_corborus.cpp8
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp74
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp10
-rw-r--r--src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp2
-rw-r--r--src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp12
-rw-r--r--src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp2
-rw-r--r--src/server/scripts/Northrend/Gundrak/boss_drakkari_colossus.cpp5
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp18
-rw-r--r--src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp9
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp6
-rw-r--r--src/server/scripts/Northrend/VaultOfArchavon/boss_toravon.cpp2
-rw-r--r--src/server/scripts/Spells/spell_hunter.cpp11
-rw-r--r--src/server/scripts/Spells/spell_rogue.cpp119
-rw-r--r--src/server/scripts/Spells/spell_warlock.cpp8
-rw-r--r--src/server/scripts/World/npcs_special.cpp17
57 files changed, 2042 insertions, 2194 deletions
diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp
index 2a2b8c62bd3..50cad20bae1 100644
--- a/src/server/game/AI/CoreAI/PetAI.cpp
+++ b/src/server/game/AI/CoreAI/PetAI.cpp
@@ -69,7 +69,6 @@ void PetAI::_stopAttack()
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MoveIdle();
me->CombatStop();
- me->getHostileRefManager().deleteReferences();
return;
}
@@ -459,8 +458,7 @@ void PetAI::HandleReturnMovement()
me->GetMotionMaster()->MoveFollow(me->GetCharmerOrOwner(), PET_FOLLOW_DIST, me->GetFollowAngle());
}
}
-
- me->ClearInPetCombat();
+ me->RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); // on player pets, this flag indicates that we're actively going after a target - we're returning, so remove it
}
void PetAI::DoAttack(Unit* target, bool chase)
@@ -470,12 +468,7 @@ void PetAI::DoAttack(Unit* target, bool chase)
if (me->Attack(target, true))
{
- // properly fix fake combat after pet is sent to attack
- if (Unit* owner = me->GetOwner())
- owner->AddUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
-
- me->AddUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
-
+ me->AddUnitFlag(UNIT_FLAG_PET_IN_COMBAT); // 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(REACT_AGGRESSIVE) && !me->GetCharmInfo()->IsCommandAttack())
me->SendPetAIReaction(me->GetGUID());
diff --git a/src/server/game/AI/CoreAI/UnitAI.h b/src/server/game/AI/CoreAI/UnitAI.h
index b2cf0564546..30fa8a56958 100644
--- a/src/server/game/AI/CoreAI/UnitAI.h
+++ b/src/server/game/AI/CoreAI/UnitAI.h
@@ -19,6 +19,7 @@
#define TRINITY_UNITAI_H
#include "Containers.h"
+#include "Errors.h"
#include "EventMap.h"
#include "ObjectGuid.h"
#include "SpellDefines.h"
diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp
index 1b7234dd939..2c07dcd551d 100644
--- a/src/server/game/AI/CreatureAI.cpp
+++ b/src/server/game/AI/CreatureAI.cpp
@@ -67,61 +67,49 @@ void CreatureAI::DoZoneInCombat(Creature* creature /*= nullptr*/, float maxRange
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())
{
- TC_LOG_ERROR("misc", "DoZoneInCombat call for map that isn't an instance (creature entry = %d)", creature->GetTypeId() == TYPEID_UNIT ? creature->ToCreature()->GetEntry() : 0);
- return;
- }
+ if (!map->IsDungeon()) //use IsDungeon instead of Instanceable, in case battlegrounds will be instantiated
+ {
+ TC_LOG_ERROR("misc", "DoZoneInCombat call for map that isn't an instance (creature entry = %d)", creature->GetTypeId() == TYPEID_UNIT ? creature->ToCreature()->GetEntry() : 0);
+ return;
+ }
- if (!creature->HasReactState(REACT_PASSIVE) && !creature->GetVictim())
- {
- if (Unit* nearTarget = creature->SelectNearestTarget(maxRangeToNearestTarget))
- creature->AI()->AttackStart(nearTarget);
- else if (creature->IsSummon())
+ if (!creature->HasReactState(REACT_PASSIVE) && !creature->GetVictim())
{
- if (Unit* summoner = creature->ToTempSummon()->GetSummoner())
+ if (Unit* nearTarget = creature->SelectNearestTarget(maxRangeToNearestTarget))
+ creature->AI()->AttackStart(nearTarget);
+ else if (creature->IsSummon())
{
- Unit* target = summoner->getAttackerForHelper();
- if (!target && summoner->CanHaveThreatList() && !summoner->GetThreatManager().IsThreatListEmpty())
- target = summoner->GetThreatManager().GetAnyTarget();
- if (target && (creature->IsFriendlyTo(summoner) || creature->IsHostileTo(target)))
- creature->AI()->AttackStart(target);
+ if (Unit* summoner = creature->ToTempSummon()->GetSummoner())
+ {
+ Unit* target = summoner->getAttackerForHelper();
+ if (!target && !summoner->GetThreatManager().IsThreatListEmpty())
+ target = summoner->GetThreatManager().GetAnyTarget();
+ if (target && (creature->IsFriendlyTo(summoner) || creature->IsHostileTo(target)))
+ creature->AI()->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(REACT_PASSIVE) && !creature->GetVictim())
- {
- TC_LOG_ERROR("misc.dozoneincombat", "DoZoneInCombat called for creature that has empty threat list (creature entry = %u)", 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(REACT_PASSIVE) && !creature->GetVictim())
+ {
+ TC_LOG_ERROR("misc.dozoneincombat", "DoZoneInCombat called for creature that has empty threat list (creature entry = %u)", creature->GetEntry());
+ return;
+ }
}
Map::PlayerList const& playerList = map->GetPlayers();
-
if (playerList.isEmpty())
return;
for (Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr)
- {
if (Player* player = itr->GetSource())
- {
- if (player->IsGameMaster())
- continue;
-
if (player->IsAlive())
- {
creature->SetInCombatWith(player);
- player->SetInCombatWith(creature);
- creature->GetThreatManager().AddThreat(player, 0.0f, nullptr, true, true);
- }
- }
- }
}
// scripts does not take care about MoveInLineOfSight loops
@@ -249,11 +237,13 @@ bool CreatureAI::UpdateVictim()
return me->GetVictim() != nullptr;
}
- else if (me->GetThreatManager().IsThreatListEmpty())
+ else if (me->GetThreatManager().IsThreatListEmpty(true))
{
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
return false;
}
+ else
+ me->AttackStop();
return true;
}
diff --git a/src/server/game/AI/PlayerAI/PlayerAI.h b/src/server/game/AI/PlayerAI/PlayerAI.h
index 600713e9680..195f7cb03b2 100644
--- a/src/server/game/AI/PlayerAI/PlayerAI.h
+++ b/src/server/game/AI/PlayerAI/PlayerAI.h
@@ -20,6 +20,7 @@
#include "UnitAI.h"
+class Creature;
class Spell;
class TC_GAME_API PlayerAI : public UnitAI
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
index fc5e30d0ff1..d502e70fa7e 100644
--- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
+++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
@@ -504,11 +504,12 @@ void BossAI::TeleportCheaters()
float x, y, z;
me->GetPosition(x, y, z);
- ThreatContainer::StorageType threatList = me->GetThreatManager().getThreatList();
- for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
- if (Unit* target = (*itr)->getTarget())
- if (target->GetTypeId() == TYPEID_PLAYER && !CheckBoundary(target))
- target->NearTeleportTo(x, y, z, 0);
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
+ {
+ Unit* target = pair.second->GetOther(me);
+ if (target->IsControlledByPlayer() && !CheckBoundary(target))
+ target->NearTeleportTo(x, y, z, 0);
+ }
}
void BossAI::JustSummoned(Creature* summon)
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h
index 5556f24872b..4e41082f561 100644
--- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h
+++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h
@@ -184,7 +184,7 @@ struct TC_GAME_API ScriptedAI : public CreatureAI
void Reset() override { }
//Called at creature aggro either by MoveInLOS or Attack Start
- void JustEngagedWith(Unit* /*victim*/) override { }
+ void JustEngagedWith(Unit* /*who*/) override { }
// Called before JustEngagedWith even before the creature is in combat.
void AttackStart(Unit* /*target*/) override;
diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp
index 4192a243367..36084e06989 100644
--- a/src/server/game/AI/SmartScripts/SmartScript.cpp
+++ b/src/server/game/AI/SmartScripts/SmartScript.cpp
@@ -2254,6 +2254,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
case SMART_ACTION_ADD_THREAT:
{
+ if (!me->CanHaveThreatList())
+ break;
for (WorldObject* const target : targets)
if (IsUnit(target))
me->GetThreatManager().AddThreat(target->ToUnit(), float(e.action.threatPCT.threatINC) - float(e.action.threatPCT.threatDEC), nullptr, true, true);
diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp
index b9cac4f0254..91871e1edd8 100644
--- a/src/server/game/Battlegrounds/Battleground.cpp
+++ b/src/server/game/Battlegrounds/Battleground.cpp
@@ -768,11 +768,8 @@ void Battleground::EndBattleground(uint32 winner)
player->SpawnCorpseBones();
}
else
- {
//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
player->RemoveAura(SPELL_HONORABLE_DEFENDER_25Y);
diff --git a/src/server/game/Combat/CombatManager.cpp b/src/server/game/Combat/CombatManager.cpp
new file mode 100644
index 00000000000..3bd5f70cd7e
--- /dev/null
+++ b/src/server/game/Combat/CombatManager.cpp
@@ -0,0 +1,350 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * 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 2 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/>.
+ */
+
+#include "CombatManager.h"
+#include "Creature.h"
+#include "CreatureAI.h"
+#include "Player.h"
+
+/*static*/ bool CombatManager::CanBeginCombat(Unit const* a, Unit const* 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(UNIT_STATE_EVADE) || b->HasUnitState(UNIT_STATE_EVADE))
+ return false;
+ if (a->HasUnitState(UNIT_STATE_IN_FLIGHT) || b->HasUnitState(UNIT_STATE_IN_FLIGHT))
+ 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 const* playerA = a->GetCharmerOrOwnerPlayerOrPlayerItself();
+ Player const* 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;
+}
+
+void CombatReference::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 const needFirstAI = first->GetCombatManager().UpdateOwnerCombatState();
+ bool const 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();
+
+ // ...and finally clean up the reference object
+ delete this;
+}
+
+bool PvPCombatReference::Update(uint32 tdiff)
+{
+ if (_combatTimer <= tdiff)
+ return false;
+ _combatTimer -= tdiff;
+ return true;
+}
+
+void PvPCombatReference::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 PvPCombatReference::SuppressFor(Unit* who)
+{
+ Suppress(who);
+ if (who->GetCombatManager().UpdateOwnerCombatState())
+ if (who->IsAIEnabled)
+ who->GetAI()->JustExitedCombat();
+}
+
+void CombatManager::Update(uint32 tdiff)
+{
+ auto it = _pvpRefs.begin(), end = _pvpRefs.end();
+ while (it != end)
+ {
+ PvPCombatReference* const ref = it->second;
+ if (ref->first == _owner && !ref->Update(tdiff)) // only update if we're the first unit involved (otherwise double decrement)
+ {
+ it = _pvpRefs.erase(it), end = _pvpRefs.end(); // remove it from our refs first to prevent invalidation
+ ref->EndCombat(); // this will remove it from the other side
+ }
+ else
+ ++it;
+ }
+}
+
+bool CombatManager::HasPvPCombat() const
+{
+ for (auto const& pair : _pvpRefs)
+ if (!pair.second->IsSuppressedFor(_owner))
+ return true;
+ return false;
+}
+
+Unit* CombatManager::GetAnyTarget() const
+{
+ if (!_pveRefs.empty())
+ return _pveRefs.begin()->second->GetOther(_owner);
+ for (auto const& pair : _pvpRefs)
+ if (!pair.second->IsSuppressedFor(_owner))
+ return pair.second->GetOther(_owner);
+ return nullptr;
+}
+
+bool CombatManager::SetInCombatWith(Unit* who)
+{
+ // Are we already in combat? If yes, refresh pvp combat
+ auto it = _pvpRefs.find(who->GetGUID());
+ if (it != _pvpRefs.end())
+ {
+ it->second->Refresh();
+ return true;
+ }
+ else if (_pveRefs.find(who->GetGUID()) != _pveRefs.end())
+ return true;
+
+ // Otherwise, check validity...
+ if (!CombatManager::CanBeginCombat(_owner, who))
+ return false;
+
+ // ...then create new reference
+ CombatReference* ref;
+ if (_owner->IsControlledByPlayer() && who->IsControlledByPlayer())
+ ref = new PvPCombatReference(_owner, who);
+ else
+ ref = new CombatReference(_owner, who);
+
+ // ...and insert it into both managers
+ PutReference(who->GetGUID(), ref);
+ who->GetCombatManager().PutReference(_owner->GetGUID(), ref);
+
+ // 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 const needSelfAI = UpdateOwnerCombatState();
+ bool const 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;
+}
+
+bool CombatManager::IsInCombatWith(ObjectGuid const& guid) const
+{
+ return (_pveRefs.find(guid) != _pveRefs.end()) || (_pvpRefs.find(guid) != _pvpRefs.end());
+}
+
+bool CombatManager::IsInCombatWith(Unit const* who) const
+{
+ return IsInCombatWith(who->GetGUID());
+}
+
+void CombatManager::InheritCombatStatesFrom(Unit const* who)
+{
+ CombatManager const& mgr = who->GetCombatManager();
+ for (auto& ref : mgr._pveRefs)
+ {
+ if (!IsInCombatWith(ref.first))
+ {
+ Unit* target = ref.second->GetOther(who);
+ if ((_owner->IsImmuneToPC() && target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)) ||
+ (_owner->IsImmuneToNPC() && !target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)))
+ continue;
+ SetInCombatWith(target);
+ }
+ }
+ for (auto& ref : mgr._pvpRefs)
+ {
+ if (!IsInCombatWith(ref.first))
+ {
+ Unit* target = ref.second->GetOther(who);
+ if ((_owner->IsImmuneToPC() && target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)) ||
+ (_owner->IsImmuneToNPC() && !target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)))
+ continue;
+ SetInCombatWith(target);
+ }
+ }
+}
+
+void CombatManager::EndCombatBeyondRange(float range, bool includingPvP)
+{
+ auto it = _pveRefs.begin(), end = _pveRefs.end();
+ while (it != end)
+ {
+ CombatReference* const ref = it->second;
+ if (!ref->first->IsWithinDistInMap(ref->second, range))
+ {
+ it = _pveRefs.erase(it), end = _pveRefs.end(); // erase manually here to avoid iterator invalidation
+ ref->EndCombat();
+ }
+ else
+ ++it;
+ }
+
+ if (!includingPvP)
+ return;
+
+ auto it2 = _pvpRefs.begin(), end2 = _pvpRefs.end();
+ while (it2 != end2)
+ {
+ CombatReference* const ref = it2->second;
+ if (!ref->first->IsWithinDistInMap(ref->second, range))
+ {
+ it2 = _pvpRefs.erase(it2), end2 = _pvpRefs.end(); // erase manually here to avoid iterator invalidation
+ ref->EndCombat();
+ }
+ else
+ ++it2;
+ }
+}
+
+void CombatManager::SuppressPvPCombat()
+{
+ for (auto const& pair : _pvpRefs)
+ pair.second->Suppress(_owner);
+ if (UpdateOwnerCombatState())
+ if (_owner->IsAIEnabled)
+ _owner->GetAI()->JustExitedCombat();
+}
+
+void CombatManager::EndAllPvECombat()
+{
+ // cannot have threat without combat
+ _owner->GetThreatManager().RemoveMeFromThreatLists();
+ _owner->GetThreatManager().ClearAllThreat();
+ while (!_pveRefs.empty())
+ _pveRefs.begin()->second->EndCombat();
+}
+
+void CombatManager::EndAllPvPCombat()
+{
+ while (!_pvpRefs.empty())
+ _pvpRefs.begin()->second->EndCombat();
+}
+
+/*static*/ void CombatManager::NotifyAICombat(Unit* me, Unit* other)
+{
+ if (!me->IsAIEnabled)
+ return;
+ me->GetAI()->JustEnteredCombat(other);
+
+ if (Creature* cMe = me->ToCreature())
+ if (!cMe->CanHaveThreatList())
+ cMe->AI()->JustEngagedWith(other);
+}
+
+void CombatManager::PutReference(ObjectGuid const& guid, CombatReference* ref)
+{
+ if (ref->_isPvP)
+ {
+ auto& inMap = _pvpRefs[guid];
+ ASSERT(!inMap && "Duplicate combat state detected - memory leak!");
+ inMap = static_cast<PvPCombatReference*>(ref);
+ }
+ else
+ {
+ auto& inMap = _pveRefs[guid];
+ ASSERT(!inMap && "Duplicate combat state detected - memory leak!");
+ inMap = ref;
+ }
+}
+
+void CombatManager::PurgeReference(ObjectGuid const& guid, bool pvp)
+{
+ if (pvp)
+ _pvpRefs.erase(guid);
+ else
+ _pveRefs.erase(guid);
+}
+
+bool CombatManager::UpdateOwnerCombatState() const
+{
+ bool const combatState = HasCombat();
+ if (combatState == _owner->IsInCombat())
+ return false;
+
+ if (combatState)
+ {
+ _owner->AddUnitFlag(UNIT_FLAG_IN_COMBAT);
+ _owner->AtEnterCombat();
+ }
+ else
+ {
+ _owner->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
+ _owner->AtExitCombat();
+ }
+
+ if (Unit* master = _owner->GetCharmerOrOwner())
+ master->UpdatePetCombatState();
+
+ return true;
+}
diff --git a/src/server/game/Combat/CombatManager.h b/src/server/game/Combat/CombatManager.h
new file mode 100644
index 00000000000..2b8e95ad220
--- /dev/null
+++ b/src/server/game/Combat/CombatManager.h
@@ -0,0 +1,143 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * 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 2 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/>.
+ */
+
+#ifndef TRINITY_COMBATMANAGER_H
+#define TRINITY_COMBATMANAGER_H
+
+#include "Common.h"
+#include "ObjectGuid.h"
+#include <unordered_map>
+
+class Unit;
+
+/********************************************************************************************************************************************************\
+ * DEV DOCUMENTATION: COMBAT SYSTEM *
+ * (future devs: please keep this up-to-date if you change the system) *
+ * CombatManager maintains a list of dynamically allocated CombatReference entries. Each entry represents a combat state between two distinct units. *
+ * A unit is "in combat" iff it has one or more non-suppressed CombatReference entries in its CombatManager. No exceptions. *
+ * - Note: Only PvP combat references can be suppressed, and only because Vanish is a very silly spell. Sue Blizzard. *
+ * *
+ * A CombatReference object carries the following implicit guarantees by existing: *
+ * - Both CombatReference.first and CombatReference.second are valid Units, distinct, not nullptr and currently in the world. *
+ * - If the CombatReference was retrieved from the CombatManager of Unit* A, then exactly one of .first and .second is equal to A. *
+ * - Note: Use CombatReference::GetOther to quickly get the other unit for a given reference. *
+ * - Both .first and .second are currently in combat (IsInCombat will always be true) if either of the following hold: *
+ * - The reference is a PvE combat reference (_isPvP is false) *
+ * - IsSuppressedFor returns false for the respective unit *
+ * *
+ * To end combat between two units, find their CombatReference and call EndCombat. *
+ * - Keep in mind that this modifies the CombatRefs maps on both ends, which may cause iterators to be invalidated. *
+ * *
+ * To put two units in combat with each other, call SetInCombatWith. Note that this is not guaranteed to succeed. *
+ * - The return value of SetInCombatWith is the new combat state between the units (identical to calling IsInCombatWith at that time). *
+ * *
+ * Note that (threat => combat) is a strong guarantee provided in conjunction with ThreatManager. Thus: *
+ * - Ending combat between two units will also delete any threat references that may exist between them. *
+ * - Adding threat will also create a combat reference between the units if one doesn't exist yet. *
+\********************************************************************************************************************************************************/
+
+// Please check Game/Combat/CombatManager.h for documentation on how this class works!
+struct TC_GAME_API CombatReference
+{
+ Unit* const first;
+ Unit* const second;
+ bool const _isPvP;
+ Unit* GetOther(Unit const* me) const { return (first == me) ? second : first; }
+
+ void EndCombat();
+
+ CombatReference(CombatReference const&) = delete;
+ CombatReference& operator=(CombatReference const&) = delete;
+
+protected:
+ CombatReference(Unit* a, Unit* b, bool pvp = false) : first(a), second(b), _isPvP(pvp) { }
+
+ friend class CombatManager;
+};
+
+// Please check Game/Combat/CombatManager.h for documentation on how this class works!
+struct TC_GAME_API PvPCombatReference : public CombatReference
+{
+ static const uint32 PVP_COMBAT_TIMEOUT = 5 * IN_MILLISECONDS;
+
+ // suppressed combat refs do not generate a combat state for one side of the relation
+ // (used by: vanish, feign death)
+ void SuppressFor(Unit* who);
+ bool IsSuppressedFor(Unit const* who) const { return (who == first) ? _suppressFirst : _suppressSecond; }
+
+private:
+ PvPCombatReference(Unit* first, Unit* second) : CombatReference(first, second, true) { }
+
+ bool Update(uint32 tdiff);
+ void Refresh();
+ void Suppress(Unit* who) { (who == first ? _suppressFirst : _suppressSecond) = true; }
+
+ uint32 _combatTimer = PVP_COMBAT_TIMEOUT;
+ bool _suppressFirst = false;
+ bool _suppressSecond = false;
+
+ friend class CombatManager;
+};
+
+// please check Game/Combat/CombatManager.h for documentation on how this class works!
+class TC_GAME_API CombatManager
+{
+ public:
+ static bool CanBeginCombat(Unit const* a, Unit const* b);
+
+ CombatManager(Unit* owner) : _owner(owner) { }
+ void Update(uint32 tdiff); // called from Unit::Update
+
+ Unit* GetOwner() const { return _owner; }
+ bool HasCombat() const { return HasPvECombat() || HasPvPCombat(); }
+ bool HasPvECombat() const { return !_pveRefs.empty(); }
+ std::unordered_map<ObjectGuid, CombatReference*> const& GetPvECombatRefs() const { return _pveRefs; }
+ bool HasPvPCombat() const;
+ std::unordered_map<ObjectGuid, PvPCombatReference*> const& GetPvPCombatRefs() const { return _pvpRefs; }
+ // If the Unit is in combat, returns an arbitrary Unit that it's in combat with. Otherwise, returns nullptr.
+ Unit* GetAnyTarget() const;
+
+ // return value is the same as calling IsInCombatWith immediately after this returns
+ bool SetInCombatWith(Unit* who);
+ bool IsInCombatWith(ObjectGuid const& who) const;
+ bool IsInCombatWith(Unit const* who) const;
+ void InheritCombatStatesFrom(Unit const* who);
+ void EndCombatBeyondRange(float range, bool includingPvP = false);
+ // flags any pvp refs for suppression on owner's side - these refs will not generate combat until refreshed
+ void SuppressPvPCombat();
+ void EndAllPvECombat();
+ void EndAllPvPCombat();
+ void EndAllCombat() { EndAllPvECombat(); EndAllPvPCombat(); }
+
+ CombatManager(CombatManager const&) = delete;
+ CombatManager& operator=(CombatManager const&) = delete;
+
+ private:
+ static void NotifyAICombat(Unit* me, Unit* other);
+ void PutReference(ObjectGuid const& guid, CombatReference* ref);
+ void PurgeReference(ObjectGuid const& guid, bool pvp);
+ bool UpdateOwnerCombatState() const;
+ Unit* const _owner;
+ std::unordered_map<ObjectGuid, CombatReference*> _pveRefs;
+ std::unordered_map<ObjectGuid, PvPCombatReference*> _pvpRefs;
+
+
+ friend struct CombatReference;
+ friend struct PvPCombatReference;
+};
+
+#endif
diff --git a/src/server/game/Combat/HostileRefManager.cpp b/src/server/game/Combat/HostileRefManager.cpp
deleted file mode 100644
index 503091cf573..00000000000
--- a/src/server/game/Combat/HostileRefManager.cpp
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
- *
- * 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 2 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/>.
- */
-
-#include "HostileRefManager.h"
-#include "DB2Structure.h"
-#include "SpellInfo.h"
-#include "ThreatManager.h"
-#include "Unit.h"
-
-HostileRefManager::~HostileRefManager()
-{
- deleteReferences();
-}
-
-//=================================================
-// 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
-
-void HostileRefManager::threatAssist(Unit* victim, float baseThreat, SpellInfo const* threatSpell)
-{
- if (getSize() == 0)
- return;
-
- float threat = ThreatCalcHelper::calcThreat(victim, iOwner, baseThreat, (threatSpell ? threatSpell->GetSchoolMask() : SPELL_SCHOOL_MASK_NORMAL), threatSpell);
- threat /= getSize();
-
- HostileReference* ref = getFirst();
- while (ref)
- {
- if (ThreatCalcHelper::isValidProcess(victim, ref->GetSource()->GetOwner(), threatSpell))
- ref->GetSource()->doAddThreat(victim, threat);
-
- ref = ref->next();
- }
-}
-
-//=================================================
-
-void HostileRefManager::addTempThreat(float threat, bool apply)
-{
- HostileReference* ref = getFirst();
- while (ref)
- {
- if (apply)
- {
- if (ref->getTempThreatModifier() == 0.0f)
- ref->addTempThreat(threat);
- }
- else
- ref->resetTempThreat();
-
- ref = ref->next();
- }
-}
-
-//=================================================
-
-void HostileRefManager::addThreatPercent(int32 percent)
-{
- HostileReference* ref = getFirst();
- while (ref)
- {
- ref->addThreatPercent(percent);
- ref = ref->next();
- }
-}
-
-//=================================================
-// The online / offline status is given to the method. The calculation has to be done before
-
-void HostileRefManager::setOnlineOfflineState(bool isOnline)
-{
- HostileReference* ref = getFirst();
- while (ref)
- {
- ref->setOnlineOfflineState(isOnline);
- ref = ref->next();
- }
-}
-
-//=================================================
-// The online / offline status is calculated and set
-
-void HostileRefManager::updateThreatTables()
-{
- HostileReference* ref = getFirst();
- while (ref)
- {
- ref->updateOnlineStatus();
- ref = ref->next();
- }
-}
-
-//=================================================
-// The references are not needed anymore
-// tell the source to remove them from the list and free the mem
-
-void HostileRefManager::deleteReferences()
-{
- HostileReference* ref = getFirst();
- while (ref)
- {
- HostileReference* nextRef = ref->next();
- ref->removeReference();
- delete ref;
- ref = nextRef;
- }
-}
-
-//=================================================
-// delete one reference, defined by faction
-
-void HostileRefManager::deleteReferencesForFaction(uint32 faction)
-{
- HostileReference* ref = getFirst();
- while (ref)
- {
- HostileReference* nextRef = ref->next();
- if (ref->GetSource()->GetOwner()->GetFactionTemplateEntry()->Faction == faction)
- {
- ref->removeReference();
- delete ref;
- }
- ref = nextRef;
- }
-}
-
-//=================================================
-// delete all references out of specified range
-
-void HostileRefManager::deleteReferencesOutOfRange(float range)
-{
- HostileReference* ref = getFirst();
- range = range*range;
- while (ref)
- {
- HostileReference* nextRef = ref->next();
- Unit* owner = ref->GetSource()->GetOwner();
- if (!owner->isActiveObject() && owner->GetExactDist2dSq(GetOwner()) > range)
- {
- ref->removeReference();
- delete ref;
- }
- ref = nextRef;
- }
-}
-
-//=================================================
-// delete one reference, defined by Unit
-
-void HostileRefManager::deleteReference(Unit* creature)
-{
- HostileReference* ref = getFirst();
- while (ref)
- {
- HostileReference* nextRef = ref->next();
- if (ref->GetSource()->GetOwner() == creature)
- {
- ref->removeReference();
- delete ref;
- break;
- }
- ref = nextRef;
- }
-}
-
-//=================================================
-// set state for one reference, defined by Unit
-
-void HostileRefManager::setOnlineOfflineState(Unit* creature, bool isOnline)
-{
- HostileReference* ref = getFirst();
- while (ref)
- {
- HostileReference* nextRef = ref->next();
- if (ref->GetSource()->GetOwner() == creature)
- {
- ref->setOnlineOfflineState(isOnline);
- break;
- }
- ref = nextRef;
- }
-}
-
-//=================================================
-
-void HostileRefManager::UpdateVisibility()
-{
- HostileReference* ref = getFirst();
- while (ref)
- {
- HostileReference* nextRef = ref->next();
- if (!ref->GetSource()->GetOwner()->CanSeeOrDetect(GetOwner()))
- {
- nextRef = ref->next();
- ref->removeReference();
- delete ref;
- }
- ref = nextRef;
- }
-}
diff --git a/src/server/game/Combat/HostileRefManager.h b/src/server/game/Combat/HostileRefManager.h
deleted file mode 100644
index 36d4c82b2fc..00000000000
--- a/src/server/game/Combat/HostileRefManager.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
- *
- * 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 2 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/>.
- */
-
-#ifndef _HOSTILEREFMANAGER
-#define _HOSTILEREFMANAGER
-
-#include "Common.h"
-#include "RefManager.h"
-
-class Unit;
-class ThreatManager;
-class HostileReference;
-class SpellInfo;
-
-//=================================================
-
-class TC_GAME_API HostileRefManager : public RefManager<Unit, ThreatManager>
-{
- public:
- explicit HostileRefManager(Unit* owner) : iOwner(owner) { }
- ~HostileRefManager();
-
- Unit* GetOwner() const { return iOwner; }
-
- // send threat to all my hateres for the victim
- // The victim is hated than by them as well
- // use for buffs and healing threat functionality
- void threatAssist(Unit* victim, float baseThreat, SpellInfo const* threatSpell = nullptr);
-
- void addTempThreat(float threat, bool apply);
-
- void addThreatPercent(int32 percent);
-
- // The references are not needed anymore
- // tell the source to remove them from the list and free the mem
- void deleteReferences();
-
- // Remove specific faction references
- void deleteReferencesForFaction(uint32 faction);
-
- // for combat bugs
- void deleteReferencesOutOfRange(float range);
-
- HostileReference* getFirst() { return ((HostileReference*) RefManager<Unit, ThreatManager>::getFirst()); }
-
- void updateThreatTables();
-
- void setOnlineOfflineState(bool isOnline);
-
- // set state for one reference, defined by Unit
- void setOnlineOfflineState(Unit* creature, bool isOnline);
-
- // delete one reference, defined by Unit
- void deleteReference(Unit* creature);
-
- void UpdateVisibility();
-
- private:
- Unit* iOwner;
-};
-//=================================================
-#endif
diff --git a/src/server/game/Combat/ThreatManager.cpp b/src/server/game/Combat/ThreatManager.cpp
index e3d5f054abb..16511b14a3b 100644
--- a/src/server/game/Combat/ThreatManager.cpp
+++ b/src/server/game/Combat/ThreatManager.cpp
@@ -16,623 +16,728 @@
*/
#include "ThreatManager.h"
-#include "Unit.h"
#include "Creature.h"
-#include "Map.h"
-#include "Player.h"
+#include "CombatPackets.h"
+#include "CreatureAI.h"
+#include "MotionMaster.h"
#include "ObjectAccessor.h"
-#include "UnitEvents.h"
-#include "SpellAuras.h"
+#include "Player.h"
+#include "SpellAuraEffects.h"
#include "SpellMgr.h"
-#include "TemporarySummon.h"
-
-//==============================================================
-//================= ThreatCalcHelper ===========================
-//==============================================================
-
-// The hatingUnit is not used yet
-float ThreatCalcHelper::calcThreat(Unit* hatedUnit, Unit* /*hatingUnit*/, float threat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell /*= nullptr*/)
-{
- if (threatSpell)
- {
- if (SpellThreatEntry const* threatEntry = sSpellMgr->GetSpellThreatEntry(threatSpell->Id))
- if (threatEntry->pctMod != 1.0f)
- threat *= threatEntry->pctMod;
- // Energize is not affected by Mods
- for (SpellEffectInfo const* effect : threatSpell->GetEffects())
- if (effect && (effect->Effect == SPELL_EFFECT_ENERGIZE || effect->ApplyAuraName == SPELL_AURA_PERIODIC_ENERGIZE))
- return threat;
+#include "Hacks/boost_1_74_fibonacci_heap.h"
+BOOST_1_74_FIBONACCI_HEAP_MSVC_COMPILE_FIX(ThreatManager::threat_list_heap::value_type)
- if (Player* modOwner = hatedUnit->GetSpellModOwner())
- modOwner->ApplySpellMod(threatSpell, SpellModOp::Hate, threat);
- }
+const CompareThreatLessThan ThreatManager::CompareThreat;
- return hatedUnit->ApplyTotalThreatModifier(threat, schoolMask);
+void ThreatReference::AddThreat(float amount)
+{
+ if (amount == 0.0f)
+ return;
+ _baseAmount = std::max<float>(_baseAmount + amount, 0.0f);
+ if (amount > 0.0f)
+ HeapNotifyIncreased();
+ else
+ HeapNotifyDecreased();
}
-bool ThreatCalcHelper::isValidProcess(Unit* hatedUnit, Unit* hatingUnit, SpellInfo const* threatSpell /*= nullptr*/)
+void ThreatReference::ScaleThreat(float factor)
{
- //function deals with adding threat and adding players and pets into ThreatList
- //mobs, NPCs, guards have ThreatList and HateOfflineList
- //players and pets have only InHateListOf
- //HateOfflineList is used co contain unattackable victims (in-flight, in-water, GM etc.)
-
- if (!hatedUnit || !hatingUnit)
- return false;
-
- // not to self
- if (hatedUnit == hatingUnit)
- return false;
-
- // not to GM
- if (hatedUnit->GetTypeId() == TYPEID_PLAYER && hatedUnit->ToPlayer()->IsGameMaster())
- return false;
-
- // not to dead and not for dead
- if (!hatedUnit->IsAlive() || !hatingUnit->IsAlive())
- return false;
-
- // not in same map or phase
- if (!hatedUnit->IsInMap(hatingUnit) || !hatedUnit->IsInPhase(hatingUnit))
- return false;
-
- // spell not causing threat
- if (threatSpell && threatSpell->HasAttribute(SPELL_ATTR1_NO_THREAT))
- return false;
+ if (factor == 1.0f)
+ return;
+ _baseAmount *= factor;
+ if (factor > 1.0f)
+ HeapNotifyIncreased();
+ else
+ HeapNotifyDecreased();
+}
- ASSERT(hatingUnit->GetTypeId() == TYPEID_UNIT);
+void ThreatReference::UpdateOnlineState()
+{
+ OnlineState onlineState = SelectOnlineState();
+ if (onlineState == _online)
+ return;
+ bool increase = (onlineState > _online);
+ _online = onlineState;
+ if (increase)
+ HeapNotifyIncreased();
+ else
+ HeapNotifyDecreased();
- return true;
+ if (!IsAvailable())
+ _owner->GetThreatManager().SendRemoveToClients(_victim);
}
-//============================================================
-//================= HostileReference ==========================
-//============================================================
-
-HostileReference::HostileReference(Unit* refUnit, ThreatManager* threatManager, float threat)
+/*static*/ bool ThreatReference::FlagsAllowFighting(Unit const* a, Unit const* b)
{
- iThreat = threat;
- iTempThreatModifier = 0.0f;
- link(refUnit, threatManager);
- iUnitGuid = refUnit->GetGUID();
- iOnline = true;
- iAccessible = true;
+ if (a->GetTypeId() == TYPEID_UNIT && a->ToCreature()->IsTrigger())
+ return false;
+ if (a->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ {
+ if (b->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC))
+ return false;
+ }
+ else
+ {
+ if (b->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC))
+ return false;
+ }
+ return true;
}
-//============================================================
-// Tell our refTo (target) object that we have a link
-void HostileReference::targetObjectBuildLink()
+ThreatReference::OnlineState ThreatReference::SelectOnlineState()
{
- getTarget()->addHatedBy(this);
+ // first, check all offline conditions
+ if (!_owner->CanSeeOrDetect(_victim)) // not in map/phase, or stealth/invis
+ return ONLINE_STATE_OFFLINE;
+ if (_victim->HasUnitState(UNIT_STATE_DIED)) // feign death
+ return ONLINE_STATE_OFFLINE;
+ if (!FlagsAllowFighting(_owner, _victim) || !FlagsAllowFighting(_victim, _owner))
+ return ONLINE_STATE_OFFLINE;
+ // next, check suppression (immunity to chosen melee attack school)
+ if (_victim->IsImmunedToDamage(_owner->GetMeleeDamageSchoolMask()))
+ return ONLINE_STATE_SUPPRESSED;
+ // or any form of CC that will break on damage - disorient, polymorph, blind etc
+ if (_victim->HasBreakableByDamageCrowdControlAura())
+ return ONLINE_STATE_SUPPRESSED;
+ // no suppression - we're online
+ return ONLINE_STATE_ONLINE;
}
-//============================================================
-// Tell our refTo (taget) object, that the link is cut
-void HostileReference::targetObjectDestroyLink()
+void ThreatReference::UpdateTauntState(bool victimIsTaunting)
{
- getTarget()->removeHatedBy(this);
-}
+ if (victimIsTaunting)
+ {
+ _taunted = TAUNT_STATE_TAUNT;
+ HeapNotifyIncreased();
+ return;
+ }
-//============================================================
-// Tell our refFrom (source) object, that the link is cut (Target destroyed)
+ // Check for SPELL_AURA_MOD_DETAUNT (applied from owner to victim)
+ for (AuraEffect const* eff : _victim->GetAuraEffectsByType(SPELL_AURA_MOD_DETAUNT))
+ if (eff->GetCasterGUID() == _owner->GetGUID())
+ {
+ _taunted = TAUNT_STATE_DETAUNT;
+ HeapNotifyDecreased();
+ return;
+ }
-void HostileReference::sourceObjectDestroyLink()
-{
- setOnlineOfflineState(false);
+ _taunted = TAUNT_STATE_NONE;
+ HeapNotifyChanged();
}
-//============================================================
-// Inform the source, that the status of the reference changed
-
-void HostileReference::fireStatusChanged(ThreatRefStatusChangeEvent& threatRefStatusChangeEvent)
+void ThreatReference::ClearThreat(bool sendRemove)
{
- if (GetSource())
- GetSource()->processThreatEvent(&threatRefStatusChangeEvent);
+ _owner->GetThreatManager().PurgeThreatListRef(_victim->GetGUID(), sendRemove);
+ _victim->GetThreatManager().PurgeThreatenedByMeRef(_owner->GetGUID());
+ delete this;
}
-// -- compatibility layer for combat rewrite (PR #19930)
-Unit* HostileReference::GetOwner() const { return GetSource()->GetOwner(); }
-
-//============================================================
-
-void HostileReference::addThreat(float modThreat)
+/*static*/ bool ThreatManager::CanHaveThreatList(Unit const* who)
{
- if (!modThreat)
- return;
-
- iThreat += modThreat;
+ // only creatures can have threat list
+ if (who->GetTypeId() != TYPEID_UNIT)
+ return false;
- // the threat is changed. Source and target unit have to be available
- // if the link was cut before relink it again
- if (!isOnline())
- updateOnlineStatus();
+ // pets and totems cannot have threat list
+ if (who->IsPet() || who->IsTotem())
+ return false;
- ThreatRefStatusChangeEvent event(UEV_THREAT_REF_THREAT_CHANGE, this, modThreat);
- fireStatusChanged(event);
+ // summons cannot have a threat list, unless they are controlled by a creature
+ if (who->HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN) && !who->GetOwnerGUID().IsCreature())
+ return false;
- if (isValid() && modThreat > 0.0f)
- {
- Unit* victimOwner = getTarget()->GetCharmerOrOwner();
- if (victimOwner && victimOwner->IsAlive())
- GetSource()->addThreat(victimOwner, 0.0f); // create a threat to the owner of a pet, if the pet attacks
- }
+ return true;
}
-void HostileReference::addThreatPercent(int32 percent)
+ThreatManager::ThreatManager(Unit* owner) : _owner(owner), _ownerCanHaveThreatList(false), _ownerEngaged(false), _updateClientTimer(CLIENT_THREAT_UPDATE_INTERVAL), _currentVictimRef(nullptr)
{
- addThreat(CalculatePct(iThreat, percent));
+ for (int8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
+ _singleSchoolModifiers[i] = 1.0f;
}
-//============================================================
-// check, if source can reach target and set the status
-
-void HostileReference::updateOnlineStatus()
+void ThreatManager::Initialize()
{
- bool online = false;
- bool accessible = false;
-
- if (!isValid())
- if (Unit* target = ObjectAccessor::GetUnit(*GetSourceUnit(), getUnitGuid()))
- link(target, GetSource());
+ _ownerCanHaveThreatList = ThreatManager::CanHaveThreatList(_owner);
+}
- // only check for online status if
- // ref is valid
- // target is no player or not gamemaster
- // target is not in flight
- if (isValid()
- && (getTarget()->GetTypeId() != TYPEID_PLAYER || !getTarget()->ToPlayer()->IsGameMaster())
- && !getTarget()->HasUnitState(UNIT_STATE_IN_FLIGHT)
- && getTarget()->IsInMap(GetSourceUnit())
- && getTarget()->IsInPhase(GetSourceUnit())
- )
+void ThreatManager::Update(uint32 tdiff)
+{
+ if (_updateClientTimer <= tdiff)
{
- 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;
+ _updateClientTimer = CLIENT_THREAT_UPDATE_INTERVAL;
+ SendThreatListToClients();
}
-
- setAccessibleState(accessible);
- setOnlineOfflineState(online);
+ else
+ _updateClientTimer -= tdiff;
}
-//============================================================
-// set the status and fire the event on status change
-
-void HostileReference::setOnlineOfflineState(bool isOnline)
+Unit* ThreatManager::GetCurrentVictim() const
{
- if (iOnline != isOnline)
- {
- iOnline = isOnline;
- if (!iOnline)
- setAccessibleState(false); // if not online that not accessable as well
-
- ThreatRefStatusChangeEvent event(UEV_THREAT_REF_ONLINE_STATUS, this);
- fireStatusChanged(event);
- }
+ if (_currentVictimRef)
+ return _currentVictimRef->GetVictim();
+ return nullptr;
}
-//============================================================
+Unit* ThreatManager::GetAnyTarget() const
+{
+ for (ThreatReference const* ref : _sortedThreatList)
+ if (!ref->IsOffline())
+ return ref->GetVictim();
+ return nullptr;
+}
-void HostileReference::setAccessibleState(bool isAccessible)
+Unit* ThreatManager::SelectVictim()
{
- if (iAccessible != isAccessible)
+ if (_sortedThreatList.empty())
+ return nullptr;
+
+ ThreatReference const* newVictimRef = ReselectVictim();
+ if (newVictimRef != _currentVictimRef)
{
- iAccessible = isAccessible;
+ if (newVictimRef)
+ SendNewVictimToClients(newVictimRef);
- ThreatRefStatusChangeEvent event(UEV_THREAT_REF_ACCESSIBLE_STATUS, this);
- fireStatusChanged(event);
+ _currentVictimRef = newVictimRef;
}
+ return newVictimRef ? newVictimRef->GetVictim() : nullptr;
}
-//============================================================
-// prepare the reference for deleting
-// this is called be the target
-
-void HostileReference::removeReference()
+bool ThreatManager::IsThreatListEmpty(bool includeOffline) const
{
- invalidate();
-
- ThreatRefStatusChangeEvent event(UEV_THREAT_REF_REMOVE_FROM_LIST, this);
- fireStatusChanged(event);
+ if (includeOffline)
+ return _sortedThreatList.empty();
+ for (ThreatReference const* ref : _sortedThreatList)
+ if (ref->IsAvailable())
+ return false;
+ return true;
}
-//============================================================
-
-Unit* HostileReference::GetSourceUnit()
+bool ThreatManager::IsThreatenedBy(ObjectGuid const& who, bool includeOffline) const
{
- return (GetSource()->GetOwner());
+ auto it = _myThreatListEntries.find(who);
+ if (it == _myThreatListEntries.end())
+ return false;
+ return (includeOffline || it->second->IsAvailable());
}
+bool ThreatManager::IsThreatenedBy(Unit const* who, bool includeOffline) const { return IsThreatenedBy(who->GetGUID(), includeOffline); }
-//============================================================
-//================ ThreatContainer ===========================
-//============================================================
-
-void ThreatContainer::clearReferences()
+float ThreatManager::GetThreat(Unit const* who, bool includeOffline) const
{
- for (ThreatContainer::StorageType::const_iterator i = iThreatList.begin(); i != iThreatList.end(); ++i)
- {
- (*i)->unlink();
- delete (*i);
- }
-
- iThreatList.clear();
+ auto it = _myThreatListEntries.find(who->GetGUID());
+ if (it == _myThreatListEntries.end())
+ return 0.0f;
+ return (includeOffline || it->second->IsAvailable()) ? it->second->GetThreat() : 0.0f;
}
-//============================================================
-// Return the HostileReference of NULL, if not found
-HostileReference* ThreatContainer::getReferenceByTarget(Unit const* victim) const
+std::vector<ThreatReference*> ThreatManager::GetModifiableThreatList() const
{
- if (!victim)
- return nullptr;
-
- ObjectGuid guid = victim->GetGUID();
- for (ThreatContainer::StorageType::const_iterator i = iThreatList.begin(); i != iThreatList.end(); ++i)
- {
- HostileReference* ref = (*i);
- if (ref && ref->getUnitGuid() == guid)
- return ref;
- }
-
- return nullptr;
+ std::vector<ThreatReference*> list;
+ list.reserve(_myThreatListEntries.size());
+ for (auto it = _sortedThreatList.ordered_begin(), end = _sortedThreatList.ordered_end(); it != end; ++it)
+ list.push_back(const_cast<ThreatReference*>(*it));
+ return list;
}
-//============================================================
-// Add the threat, if we find the reference
-
-HostileReference* ThreatContainer::addThreat(Unit* victim, float threat)
+bool ThreatManager::IsThreateningAnyone(bool includeOffline) const
{
- HostileReference* ref = getReferenceByTarget(victim);
- if (ref)
- ref->addThreat(threat);
- return ref;
+ if (includeOffline)
+ return !_threatenedByMe.empty();
+ for (auto const& pair : _threatenedByMe)
+ if (pair.second->IsAvailable())
+ return true;
+ return false;
}
-//============================================================
-
-void ThreatContainer::ModifyThreatByPercent(Unit* victim, int32 percent)
+bool ThreatManager::IsThreateningTo(ObjectGuid const& who, bool includeOffline) const
{
- if (HostileReference* ref = getReferenceByTarget(victim))
- ref->addThreatPercent(percent);
+ auto it = _threatenedByMe.find(who);
+ if (it == _threatenedByMe.end())
+ return false;
+ return (includeOffline || it->second->IsAvailable());
}
+bool ThreatManager::IsThreateningTo(Unit const* who, bool includeOffline) const { return IsThreateningTo(who->GetGUID(), includeOffline); }
-//============================================================
-// Check if the list is dirty and sort if necessary
-
-void ThreatContainer::update()
+void ThreatManager::UpdateOnlineStates(bool meThreateningOthers, bool othersThreateningMe)
{
- if (iDirty && iThreatList.size() > 1)
- iThreatList.sort(Trinity::ThreatOrderPred());
-
- iDirty = false;
+ if (othersThreateningMe)
+ for (auto const& pair : _myThreatListEntries)
+ pair.second->UpdateOnlineState();
+ if (meThreateningOthers)
+ for (auto const& pair : _threatenedByMe)
+ pair.second->UpdateOnlineState();
}
-//============================================================
-// return the next best victim
-// could be the current victim
-
-HostileReference* ThreatContainer::selectNextVictim(Creature* attacker, HostileReference* currentVictim) const
+static void SaveCreatureHomePositionIfNeed(Creature* c)
{
- HostileReference* currentRef = nullptr;
- bool found = false;
- bool noPriorityTargetFound = false;
-
- ThreatContainer::StorageType::const_iterator lastRef = iThreatList.end();
- --lastRef;
+ MovementGeneratorType const movetype = c->GetMotionMaster()->GetCurrentMovementGeneratorType();
+ if (movetype == WAYPOINT_MOTION_TYPE || movetype == POINT_MOTION_TYPE || (c->IsAIEnabled && c->AI()->IsEscorted()))
+ c->SetHomePosition(c->GetPosition());
+}
- for (ThreatContainer::StorageType::const_iterator iter = iThreatList.begin(); iter != iThreatList.end() && !found;)
+void ThreatManager::AddThreat(Unit* target, float amount, SpellInfo const* spell, bool ignoreModifiers, bool ignoreRedirects)
+{
+ // step 1: we can shortcut if the spell has one of the NO_THREAT attrs set - nothing will happen
+ if (spell)
{
- currentRef = (*iter);
+ if (spell->HasAttribute(SPELL_ATTR1_NO_THREAT))
+ return;
+ if (!_owner->IsEngaged() && spell->HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO))
+ return;
+ }
- Unit* target = currentRef->getTarget();
- ASSERT(target); // if the ref has status online the target must be there !
+ // while riding a vehicle, all threat goes to the vehicle, not the pilot
+ if (Unit* vehicle = target->GetVehicleBase())
+ {
+ AddThreat(vehicle, amount, spell, ignoreModifiers, ignoreRedirects);
+ if (target->HasUnitTypeMask(UNIT_MASK_ACCESSORY)) // accessories are fully treated as components of the parent and cannot have threat
+ return;
+ amount = 0.0f;
+ }
- // some units are prefered in comparison to others
- if (!noPriorityTargetFound && (target->IsImmunedToDamage(attacker->GetMeleeDamageSchoolMask()) || target->HasNegativeAuraWithInterruptFlag(SpellAuraInterruptFlags::Damage)))
+ // If victim is personal spawn, redirect all aggro to summoner
+ if (target->IsPrivateObject() && (!GetOwner()->IsPrivateObject() || !GetOwner()->CheckPrivateObjectOwnerVisibility(target)))
+ {
+ if (Unit* privateObjectOwner = ObjectAccessor::GetUnit(*GetOwner(), target->GetPrivateObjectOwner()))
{
- if (iter != lastRef)
- {
- // current victim is a second choice target, so don't compare threat with it below
- if (currentRef == currentVictim)
- currentVictim = nullptr;
- ++iter;
- continue;
- }
- else
- {
- // if we reached to this point, everyone in the threatlist is a second choice target. In such a situation the target with the highest threat should be attacked.
- noPriorityTargetFound = true;
- iter = iThreatList.begin();
- continue;
- }
+ AddThreat(privateObjectOwner, amount, spell, ignoreModifiers, ignoreRedirects);
+ amount = 0.0f;
}
+ }
+
+ // if we cannot actually have a threat list, we instead just set combat state and avoid creating threat refs altogether
+ if (!CanHaveThreatList())
+ {
+ CombatManager& combatMgr = _owner->GetCombatManager();
+ if (!combatMgr.SetInCombatWith(target))
+ return;
+ // traverse redirects and put them in combat, too
+ for (auto const& pair : target->GetThreatManager()._redirectInfo)
+ if (!combatMgr.IsInCombatWith(pair.first))
+ if (Unit* redirTarget = ObjectAccessor::GetUnit(*_owner, pair.first))
+ combatMgr.SetInCombatWith(redirTarget);
+ return;
+ }
- if (attacker->CanCreatureAttack(target)) // skip non attackable currently targets
+ // apply threat modifiers to the amount
+ if (!ignoreModifiers)
+ amount = CalculateModifiedThreat(amount, target, spell);
+
+ // if we're increasing threat, send some/all of it to redirection targets instead if applicable
+ if (!ignoreRedirects && amount > 0.0f)
+ {
+ auto const& redirInfo = target->GetThreatManager()._redirectInfo;
+ if (!redirInfo.empty())
{
- if (currentVictim) // select 1.3/1.1 better target in comparison current target
+ float const origAmount = amount;
+ for (auto const& pair : redirInfo) // (victim,pct)
{
- // list sorted and and we check current target, then this is best case
- if (currentVictim == currentRef || currentRef->getThreat() <= 1.1f * currentVictim->getThreat())
+ Unit* redirTarget = nullptr;
+ auto it = _myThreatListEntries.find(pair.first); // try to look it up in our threat list first (faster)
+ if (it != _myThreatListEntries.end())
+ redirTarget = it->second->_victim;
+ else
+ redirTarget = ObjectAccessor::GetUnit(*_owner, pair.first);
+
+ if (redirTarget)
{
- if (currentVictim != currentRef && attacker->CanCreatureAttack(currentVictim->getTarget()))
- currentRef = currentVictim; // for second case, if currentvictim is attackable
-
- found = true;
- break;
- }
-
- if (currentRef->getThreat() > 1.3f * currentVictim->getThreat() ||
- (currentRef->getThreat() > 1.1f * currentVictim->getThreat() &&
- attacker->IsWithinMeleeRange(target)))
- { //implement 110% threat rule for targets in melee range
- found = true; //and 130% rule for targets in ranged distances
- break; //for selecting alive targets
+ float amountRedirected = CalculatePct(origAmount, pair.second);
+ AddThreat(redirTarget, amountRedirected, spell, true, true);
+ amount -= amountRedirected;
}
}
- else // select any
- {
- found = true;
- break;
- }
}
- ++iter;
}
- if (!found)
- currentRef = nullptr;
- return currentRef;
-}
+ // ok, now we actually apply threat
+ // check if we already have an entry - if we do, just increase threat for that entry and we're done
+ auto it = _myThreatListEntries.find(target->GetGUID());
+ if (it != _myThreatListEntries.end())
+ {
+ it->second->AddThreat(amount);
+ return;
+ }
-//============================================================
-//=================== ThreatManager ==========================
-//============================================================
+ // otherwise, ensure we're in combat (threat implies combat!)
+ if (!_owner->GetCombatManager().SetInCombatWith(target)) // if this returns false, we're not actually in combat, and thus cannot have threat!
+ return; // typical causes: bad scripts trying to add threat to GMs, dead targets etc
-ThreatManager::ThreatManager(Unit* owner) : iCurrentVictim(nullptr), iOwner(owner), iUpdateTimer(THREAT_UPDATE_INTERVAL) { }
+ // ok, we're now in combat - create the threat list reference and push it to the respective managers
+ ThreatReference* ref = new ThreatReference(this, target, amount);
+ PutThreatListRef(target->GetGUID(), ref);
+ target->GetThreatManager().PutThreatenedByMeRef(_owner->GetGUID(), ref);
+ if (!ref->IsOffline() && !_ownerEngaged)
+ {
+ _ownerEngaged = true;
+
+ Creature* cOwner = _owner->ToCreature();
+ assert(cOwner); // if we got here the owner can have a threat list, and must be a creature!
+ SaveCreatureHomePositionIfNeed(cOwner);
+ if (cOwner->IsAIEnabled)
+ cOwner->AI()->JustEngagedWith(target);
+ }
+}
-// -- compatibility layer for combat rewrite (PR #19930)
-void ThreatManager::ForwardThreatForAssistingMe(Unit* victim, float amount, SpellInfo const* spell, bool ignoreModifiers, bool ignoreRedirection)
+void ThreatManager::ScaleThreat(Unit* target, float factor)
{
- (void)ignoreModifiers; (void)ignoreRedirection;
- GetOwner()->getHostileRefManager().threatAssist(victim, amount, spell);
+ auto it = _myThreatListEntries.find(target->GetGUID());
+ if (it != _myThreatListEntries.end())
+ it->second->ScaleThreat(std::max<float>(factor,0.0f));
}
-void ThreatManager::AddThreat(Unit* victim, float amount, SpellInfo const* spell, bool ignoreModifiers, bool ignoreRedirection)
+void ThreatManager::MatchUnitThreatToHighestThreat(Unit* target)
{
- (void)ignoreModifiers; (void)ignoreRedirection;
- if (!iOwner->CanHaveThreatList() || iOwner->HasUnitState(UNIT_STATE_EVADE))
+ if (_sortedThreatList.empty())
return;
- if (iOwner->IsControlledByPlayer() || victim->IsControlledByPlayer())
+ auto it = _sortedThreatList.begin(), end = _sortedThreatList.end();
+ ThreatReference const* highest = *it;
+ if (!highest->IsOnline())
+ return;
+
+ if (highest->_taunted) // might need to skip this - new max could be one of the preceding elements (heap property) since there is only one taunt element
{
- if (iOwner->IsFriendlyTo(victim) || victim->IsFriendlyTo(iOwner))
- return;
+ if ((++it) != end)
+ {
+ ThreatReference const* a = *it;
+ if (a->IsOnline() && a->GetThreat() > highest->GetThreat())
+ highest = a;
+
+ if ((++it) != end)
+ {
+ ThreatReference const* a = *it;
+ if (a->IsOnline() && a->GetThreat() > highest->GetThreat())
+ highest = a;
+ }
+ }
}
- else if (!iOwner->IsHostileTo(victim) && !victim->IsHostileTo(iOwner))
- return;
- iOwner->SetInCombatWith(victim);
- victim->SetInCombatWith(iOwner);
- addThreat(victim, amount, spell ? spell->GetSchoolMask() : victim->GetMeleeDamageSchoolMask(), spell);
+ AddThreat(target, highest->GetThreat() - GetThreat(target, true), nullptr, true, true);
}
-void ThreatManager::ClearAllThreat()
+void ThreatManager::TauntUpdate()
{
- if (iOwner->CanHaveThreatList(true) && !isThreatListEmpty())
- iOwner->SendClearThreatListOpcode();
- clearReferences();
+ std::list<AuraEffect*> const& tauntEffects = _owner->GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
+ auto threatEnd = _myThreatListEntries.end();
+ ThreatReference* tauntRef = nullptr;
+ // Only the last taunt effect applied by something still on our threat list is considered
+ for (auto it = tauntEffects.rbegin(), end = tauntEffects.rend(); it != end; ++it)
+ {
+ auto threatIt = _myThreatListEntries.find((*it)->GetCasterGUID());
+ if (threatIt == threatEnd)
+ continue;
+ if (!threatIt->second->IsOnline())
+ continue;
+ tauntRef = threatIt->second;
+ break;
+ }
+ for (auto const& pair : _myThreatListEntries)
+ pair.second->UpdateTauntState(pair.second == tauntRef);
}
-//============================================================
-
-void ThreatManager::clearReferences()
+void ThreatManager::ResetAllThreat()
{
- iThreatContainer.clearReferences();
- iThreatOfflineContainer.clearReferences();
- iCurrentVictim = nullptr;
- iUpdateTimer = THREAT_UPDATE_INTERVAL;
+ for (auto const& pair : _myThreatListEntries)
+ pair.second->SetThreat(0.0f);
}
-//============================================================
+void ThreatManager::ClearThreat(Unit* target)
+{
+ auto it = _myThreatListEntries.find(target->GetGUID());
+ if (it != _myThreatListEntries.end())
+ it->second->ClearThreat();
+}
-void ThreatManager::addThreat(Unit* victim, float threat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell)
+void ThreatManager::ClearAllThreat()
{
- if (!ThreatCalcHelper::isValidProcess(victim, GetOwner(), threatSpell))
+ _ownerEngaged = false;
+ if (_myThreatListEntries.empty())
return;
- doAddThreat(victim, ThreatCalcHelper::calcThreat(victim, iOwner, threat, schoolMask, threatSpell));
+ SendClearAllThreatToClients();
+ do
+ _myThreatListEntries.begin()->second->ClearThreat(false);
+ while (!_myThreatListEntries.empty());
}
-void ThreatManager::doAddThreat(Unit* victim, float threat)
+ThreatReference const* ThreatManager::ReselectVictim()
{
- uint32 redirectThreadPct = victim->GetRedirectThreatPercent();
- Unit* redirectTarget = victim->GetRedirectThreatTarget();
+ ThreatReference const* oldVictimRef = _currentVictimRef;
+ if (oldVictimRef && !oldVictimRef->IsAvailable())
+ oldVictimRef = nullptr;
+ // in 99% of cases - we won't need to actually look at anything beyond the first element
+ ThreatReference const* highest = _sortedThreatList.top();
+ // if the highest reference is offline, the entire list is offline, and we indicate this
+ if (!highest->IsAvailable())
+ return nullptr;
+ // if we have no old victim, or old victim is still highest, then highest is our target and we're done
+ if (!oldVictimRef || highest == oldVictimRef)
+ return highest;
+ // if highest threat doesn't break 110% of old victim, nothing below it is going to do so either; new victim = old victim and done
+ if (!ThreatManager::CompareReferencesLT(oldVictimRef, highest, 1.1f))
+ return oldVictimRef;
+ // if highest threat breaks 130%, it's our new target regardless of range (and we're done)
+ if (ThreatManager::CompareReferencesLT(oldVictimRef, highest, 1.3f))
+ return highest;
+ // if it doesn't break 130%, we need to check if it's melee - if yes, it breaks 110% (we checked earlier) and is our new target
+ if (_owner->IsWithinMeleeRange(highest->_victim))
+ return highest;
+ // If we get here, highest threat is ranged, but below 130% of current - there might be a melee that breaks 110% below us somewhere, so now we need to actually look at the next highest element
+ // luckily, this is a heap, so getting the next highest element is O(log n), and we're just gonna do that repeatedly until we've seen enough targets (or find a target)
+ auto it = _sortedThreatList.ordered_begin(), end = _sortedThreatList.ordered_end();
+ while (it != end)
+ {
+ ThreatReference const* next = *it;
+ // if we've found current victim, we're done (nothing above is higher, and nothing below can be higher)
+ if (next == oldVictimRef)
+ return next;
+ // if next isn't above 110% threat, then nothing below it can be either - we're done, old victim stays
+ if (!ThreatManager::CompareReferencesLT(oldVictimRef, next, 1.1f))
+ return oldVictimRef;
+ // if next is melee, he's above 110% and our new victim
+ if (_owner->IsWithinMeleeRange(next->_victim))
+ return next;
+ // otherwise the next highest target may still be a melee above 110% and we need to look further
+ ++it;
+ }
+ // we should have found the old victim at some point in the loop above, so execution should never get to this point
+ ASSERT(false && "Current victim not found in sorted threat list even though it has a reference - manager desync!");
+ return nullptr;
+}
- // If victim is personal spawn, redirect all aggro to summoner
- if (victim->IsPrivateObject() && (!GetOwner()->IsPrivateObject() || !GetOwner()->CheckPrivateObjectOwnerVisibility(victim)))
+// returns true if a is LOWER on the threat list than b
+/*static*/ bool ThreatManager::CompareReferencesLT(ThreatReference const* a, ThreatReference const* b, float aWeight)
+{
+ if (a->_online != b->_online) // online state precedence (ONLINE > SUPPRESSED > OFFLINE)
+ return a->_online < b->_online;
+ if (a->_taunted != b->_taunted) // taunt state precedence (TAUNT > NONE > DETAUNT)
+ return a->_taunted < b->_taunted;
+ return (a->GetThreat()*aWeight < b->GetThreat());
+}
+
+/*static*/ float ThreatManager::CalculateModifiedThreat(float threat, Unit const* victim, SpellInfo const* spell)
+{
+ // modifiers by spell
+ if (spell)
{
- redirectThreadPct = 100;
- redirectTarget = ObjectAccessor::GetUnit(*GetOwner(), victim->GetPrivateObjectOwner());
+ if (SpellThreatEntry const* threatEntry = sSpellMgr->GetSpellThreatEntry(spell->Id))
+ if (threatEntry->pctMod != 1.0f) // flat/AP modifiers handled in Spell::HandleThreatSpells
+ threat *= threatEntry->pctMod;
+
+ if (Player* modOwner = victim->GetSpellModOwner())
+ modOwner->ApplySpellMod(spell, SpellModOp::Hate, threat);
}
- // must check > 0.0f, otherwise dead loop
- if (threat > 0.0f && redirectThreadPct)
+ // modifiers by effect school
+ ThreatManager const& victimMgr = victim->GetThreatManager();
+ SpellSchoolMask const mask = spell ? spell->GetSchoolMask() : SPELL_SCHOOL_MASK_NORMAL;
+ switch (mask)
{
- if (redirectTarget)
+ case SPELL_SCHOOL_MASK_NORMAL:
+ threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NORMAL];
+ break;
+ case SPELL_SCHOOL_MASK_HOLY:
+ threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_HOLY];
+ break;
+ case SPELL_SCHOOL_MASK_FIRE:
+ threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FIRE];
+ break;
+ case SPELL_SCHOOL_MASK_NATURE:
+ threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NATURE];
+ break;
+ case SPELL_SCHOOL_MASK_FROST:
+ threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FROST];
+ break;
+ case SPELL_SCHOOL_MASK_SHADOW:
+ threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_SHADOW];
+ break;
+ case SPELL_SCHOOL_MASK_ARCANE:
+ threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_ARCANE];
+ break;
+ default:
{
- float redirectThreat = CalculatePct(threat, redirectThreadPct);
- threat -= redirectThreat;
- if (ThreatCalcHelper::isValidProcess(redirectTarget, GetOwner()))
- _addThreat(redirectTarget, redirectThreat);
+ auto it = victimMgr._multiSchoolModifiers.find(mask);
+ if (it != victimMgr._multiSchoolModifiers.end())
+ {
+ threat *= it->second;
+ break;
+ }
+ float mod = victim->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, mask);
+ victimMgr._multiSchoolModifiers[mask] = mod;
+ threat *= mod;
+ break;
}
}
-
- _addThreat(victim, threat);
+ return threat;
}
-void ThreatManager::_addThreat(Unit* victim, float threat)
+void ThreatManager::SendClearAllThreatToClients() const
{
- HostileReference* ref = iThreatContainer.addThreat(victim, threat);
- // Ref is not in the online refs, search the offline refs next
- if (!ref)
- ref = iThreatOfflineContainer.addThreat(victim, threat);
+ WorldPackets::Combat::ThreatClear threatClear;
+ threatClear.UnitGUID = _owner->GetGUID();
+ _owner->SendMessageToSet(threatClear.Write(), false);
+}
- if (!ref) // there was no ref => create a new one
+void ThreatManager::SendThreatListToClients() const
+{
+ WorldPackets::Combat::ThreatUpdate threatUpdate;
+ threatUpdate.UnitGUID = _owner->GetGUID();
+ threatUpdate.ThreatList.reserve(_sortedThreatList.size());
+ for (ThreatReference const* ref : _sortedThreatList)
{
- bool isFirst = iThreatContainer.empty();
- // threat has to be 0 here
- HostileReference* hostileRef = new HostileReference(victim, this, 0);
- iThreatContainer.addReference(hostileRef);
- hostileRef->addThreat(threat); // now we add the real threat
- if (victim->GetTypeId() == TYPEID_PLAYER && victim->ToPlayer()->IsGameMaster())
- hostileRef->setOnlineOfflineState(false); // GM is always offline
- else if (isFirst)
- setCurrentVictim(hostileRef);
+ if (!ref->IsAvailable()) // @todo check if suppressed threat should get sent for bubble/iceblock/hop etc
+ continue;
+
+ WorldPackets::Combat::ThreatInfo threatInfo;
+ threatInfo.UnitGUID = ref->GetVictim()->GetGUID();
+ threatInfo.Threat = int64(ref->GetThreat() * 100);
+ threatUpdate.ThreatList.push_back(threatInfo);
}
+ _owner->SendMessageToSet(threatUpdate.Write(), false);
}
-//============================================================
-
-void ThreatManager::ModifyThreatByPercent(Unit* victim, int32 percent)
+void ThreatManager::ForwardThreatForAssistingMe(Unit* assistant, float baseAmount, SpellInfo const* spell, bool ignoreModifiers)
{
- iThreatContainer.ModifyThreatByPercent(victim, percent);
+ if (spell && spell->HasAttribute(SPELL_ATTR1_NO_THREAT)) // shortcut, none of the calls would do anything
+ return;
+ for (auto const& pair : _threatenedByMe)
+ pair.second->GetOwner()->GetThreatManager().AddThreat(assistant, baseAmount, spell, ignoreModifiers);
}
-//============================================================
-
-Unit* ThreatManager::getHostilTarget()
+void ThreatManager::RemoveMeFromThreatLists()
{
- iThreatContainer.update();
- HostileReference* nextVictim = iThreatContainer.selectNextVictim(GetOwner()->ToCreature(), getCurrentVictim());
- setCurrentVictim(nextVictim);
- return getCurrentVictim() != nullptr ? getCurrentVictim()->getTarget() : nullptr;
+ while (!_threatenedByMe.empty())
+ _threatenedByMe.begin()->second->ClearThreat();
}
-//============================================================
-
-float ThreatManager::getThreat(Unit* victim, bool alsoSearchOfflineList)
+void ThreatManager::UpdateMyTempModifiers()
{
- float threat = 0.0f;
- HostileReference* ref = iThreatContainer.getReferenceByTarget(victim);
- if (!ref && alsoSearchOfflineList)
- ref = iThreatOfflineContainer.getReferenceByTarget(victim);
- if (ref)
- threat = ref->getThreat();
- return threat;
+ int32 mod = 0;
+ for (AuraEffect const* eff : _owner->GetAuraEffectsByType(SPELL_AURA_MOD_TOTAL_THREAT))
+ mod += eff->GetAmount();
+
+ for (auto const& pair : _threatenedByMe)
+ {
+ pair.second->_tempModifier = mod;
+ pair.second->HeapNotifyChanged();
+ }
}
-//============================================================
+void ThreatManager::UpdateMySpellSchoolModifiers()
+{
+ for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
+ _singleSchoolModifiers[i] = _owner->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, 1 << i);
+ _multiSchoolModifiers.clear();
+}
-void ThreatManager::tauntApply(Unit* taunter)
+void ThreatManager::RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct)
{
- HostileReference* ref = iThreatContainer.getReferenceByTarget(taunter);
- if (getCurrentVictim() && ref && (ref->getThreat() < getCurrentVictim()->getThreat()))
- {
- if (ref->getTempThreatModifier() == 0.0f) // Ok, temp threat is unused
- ref->setTempThreat(getCurrentVictim()->getThreat());
- }
+ _redirectRegistry[spellId][victim] = pct;
+ UpdateRedirectInfo();
}
-//============================================================
+void ThreatManager::UnregisterRedirectThreat(uint32 spellId)
+{
+ auto it = _redirectRegistry.find(spellId);
+ if (it == _redirectRegistry.end())
+ return;
+ _redirectRegistry.erase(it);
+ UpdateRedirectInfo();
+}
-void ThreatManager::tauntFadeOut(Unit* taunter)
+void ThreatManager::UnregisterRedirectThreat(uint32 spellId, ObjectGuid const& victim)
{
- HostileReference* ref = iThreatContainer.getReferenceByTarget(taunter);
- if (ref)
- ref->resetTempThreat();
+ auto it = _redirectRegistry.find(spellId);
+ if (it == _redirectRegistry.end())
+ return;
+ auto& victimMap = it->second;
+ auto it2 = victimMap.find(victim);
+ if (it2 == victimMap.end())
+ return;
+ victimMap.erase(it2);
+ UpdateRedirectInfo();
}
-//============================================================
+void ThreatManager::SendRemoveToClients(Unit const* victim) const
+{
+ WorldPackets::Combat::ThreatRemove threatRemove;
+ threatRemove.UnitGUID = _owner->GetGUID();
+ threatRemove.AboutGUID = victim->GetGUID();
+ _owner->SendMessageToSet(threatRemove.Write(), false);
+}
-void ThreatManager::setCurrentVictim(HostileReference* pHostileReference)
+void ThreatManager::SendNewVictimToClients(ThreatReference const* victimRef) const
{
- if (pHostileReference && pHostileReference != iCurrentVictim)
+ WorldPackets::Combat::HighestThreatUpdate highestThreatUpdate;
+ highestThreatUpdate.UnitGUID = _owner->GetGUID();
+ highestThreatUpdate.HighestThreatGUID = victimRef->_victim->GetGUID();
+ highestThreatUpdate.ThreatList.reserve(_sortedThreatList.size());
+ for (ThreatReference const* ref : _sortedThreatList)
{
- iOwner->SendChangeCurrentVictimOpcode(pHostileReference);
+ if (!ref->IsAvailable())
+ continue;
+
+ WorldPackets::Combat::ThreatInfo threatInfo;
+ threatInfo.UnitGUID = ref->GetVictim()->GetGUID();
+ threatInfo.Threat = int64(ref->GetThreat() * 100);
+ highestThreatUpdate.ThreatList.push_back(threatInfo);
}
- iCurrentVictim = pHostileReference;
+ _owner->SendMessageToSet(highestThreatUpdate.Write(), false);
}
-//============================================================
-// The hated unit is gone, dead or deleted
-// return true, if the event is consumed
+void ThreatManager::PutThreatListRef(ObjectGuid const& guid, ThreatReference* ref)
+{
+ auto& inMap = _myThreatListEntries[guid];
+ ASSERT(!inMap && "Duplicate threat list entry being inserted - memory leak!");
+ inMap = ref;
+ ref->_handle = _sortedThreatList.push(ref);
+}
-void ThreatManager::processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent)
+void ThreatManager::PurgeThreatListRef(ObjectGuid const& guid, bool sendRemove)
{
- threatRefStatusChangeEvent->setThreatManager(this); // now we can set the threat manager
+ auto it = _myThreatListEntries.find(guid);
+ if (it == _myThreatListEntries.end())
+ return;
+ ThreatReference* ref = it->second;
+ _myThreatListEntries.erase(it);
- HostileReference* hostilRef = threatRefStatusChangeEvent->getReference();
+ if (_currentVictimRef == ref)
+ _currentVictimRef = nullptr;
- switch (threatRefStatusChangeEvent->getType())
- {
- case UEV_THREAT_REF_THREAT_CHANGE:
- if ((getCurrentVictim() == hostilRef && threatRefStatusChangeEvent->getFValue()<0.0f) ||
- (getCurrentVictim() != hostilRef && threatRefStatusChangeEvent->getFValue()>0.0f))
- setDirty(true); // the order in the threat list might have changed
- break;
- case UEV_THREAT_REF_ONLINE_STATUS:
- if (!hostilRef->isOnline())
- {
- if (hostilRef == getCurrentVictim())
- {
- setCurrentVictim(nullptr);
- setDirty(true);
- }
- iOwner->SendRemoveFromThreatListOpcode(hostilRef);
- iThreatContainer.remove(hostilRef);
- iThreatOfflineContainer.addReference(hostilRef);
- }
- else
- {
- if (getCurrentVictim() && hostilRef->getThreat() > (1.1f * getCurrentVictim()->getThreat()))
- setDirty(true);
- iThreatContainer.addReference(hostilRef);
- iThreatOfflineContainer.remove(hostilRef);
- }
- break;
- case UEV_THREAT_REF_REMOVE_FROM_LIST:
- if (hostilRef == getCurrentVictim())
- {
- setCurrentVictim(nullptr);
- setDirty(true);
- }
- iOwner->SendRemoveFromThreatListOpcode(hostilRef);
- if (hostilRef->isOnline())
- iThreatContainer.remove(hostilRef);
- else
- iThreatOfflineContainer.remove(hostilRef);
- break;
- }
+ _sortedThreatList.erase(ref->_handle);
+ if (sendRemove && ref->IsOnline())
+ SendRemoveToClients(ref->_victim);
}
-bool ThreatManager::isNeedUpdateToClient(uint32 time)
+void ThreatManager::PutThreatenedByMeRef(ObjectGuid const& guid, ThreatReference* ref)
{
- if (isThreatListEmpty())
- return false;
-
- if (time >= iUpdateTimer)
- {
- iUpdateTimer = THREAT_UPDATE_INTERVAL;
- return true;
- }
- iUpdateTimer -= time;
- return false;
+ auto& inMap = _threatenedByMe[guid];
+ ASSERT(!inMap && "Duplicate entry being inserted into threatened by me list - potential memory leak!");
+ inMap = ref;
}
-// Reset all aggro without modifying the threatlist.
-void ThreatManager::resetAllAggro()
+void ThreatManager::PurgeThreatenedByMeRef(ObjectGuid const& guid)
{
- ThreatContainer::StorageType &threatList = iThreatContainer.iThreatList;
- if (threatList.empty())
- return;
-
- for (ThreatContainer::StorageType::iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
- (*itr)->setThreat(0);
+ auto it = _threatenedByMe.find(guid);
+ if (it != _threatenedByMe.end())
+ _threatenedByMe.erase(it);
+}
- setDirty(true);
+void ThreatManager::UpdateRedirectInfo()
+{
+ _redirectInfo.clear();
+ uint32 totalPct = 0;
+ for (auto const& pair : _redirectRegistry) // (spellid, victim -> pct)
+ for (auto const& victimPair : pair.second) // (victim,pct)
+ {
+ uint32 thisPct = std::min<uint32>(100 - totalPct, victimPair.second);
+ if (thisPct > 0)
+ {
+ _redirectInfo.push_back({ victimPair.first, thisPct });
+ totalPct += thisPct;
+ ASSERT(totalPct <= 100);
+ if (totalPct == 100)
+ return;
+ }
+ }
}
diff --git a/src/server/game/Combat/ThreatManager.h b/src/server/game/Combat/ThreatManager.h
index 7066794e5b7..486db4d7e13 100644
--- a/src/server/game/Combat/ThreatManager.h
+++ b/src/server/game/Combat/ThreatManager.h
@@ -15,320 +15,261 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef _THREATMANAGER
-#define _THREATMANAGER
+ #ifndef TRINITY_THREATMANAGER_H
+ #define TRINITY_THREATMANAGER_H
#include "Common.h"
-#include "SharedDefines.h"
-#include "LinkedReference/Reference.h"
-#include "UnitEvents.h"
-#include "ObjectGuid.h"
#include "IteratorPair.h"
-
-#include <list>
-
-//==============================================================
+#include "ObjectGuid.h"
+#include "SharedDefines.h"
+#include <boost/heap/fibonacci_heap.hpp>
+#include <unordered_map>
+#include <vector>
class Unit;
-class Creature;
-class ThreatManager;
class SpellInfo;
-#define THREAT_UPDATE_INTERVAL 1 * IN_MILLISECONDS // Server should send threat update to client periodically each second
-
-//==============================================================
-// Class to calculate the real threat based
-
-struct TC_GAME_API ThreatCalcHelper
+/********************************************************************************************************************************************************\
+ * DEV DOCUMENTATION: THREAT SYSTEM *
+ * (future devs: please keep this up-to-date if you change the system) *
+ * The threat system works based on dynamically allocated threat list entries. *
+ * *
+ * Each such entry is a ThreatReference object, which is always stored in exactly three places: *
+ * - The threatened unit's (from now: reference "owner") sorted and unsorted threat lists *
+ * - The threatening unit's (from now: reference "victim") threatened-by-me list *
+ * A ThreatReference object carries the following implicit guarantees: *
+ * - Both owner and victim are valid units, which are currently in the world. Neither can be nullptr. *
+ * - There is an active combat reference between owner and victim. *
+ * *
+ * ThreatManager also keeps track of whether its owner is engaged (a boolean flag). *
+ * - If a (non-offline) threat list entry is added to a not-yet-engaged ThreatManager, it calls JustEngagedWith on its owner's AI. *
+ * - The engaged state is cleared in ClearAllThreat (which is invoked on evade). *
+ * - This flag can be accessed through the IsEngaged method. For creatures that can have a threat list, this is equal to Unit::IsEngaged. *
+ * *
+ * Note that (threat => combat) is a strong guarantee provided in conjunction with CombatManager. Thus: *
+ * - Adding threat will also create a combat reference between the units if one doesn't exist yet (even if the owner can't have a threat list!) *
+ * - Ending combat between two units will also delete any threat references that may exist between them. *
+ * *
+ * To manage a creature's threat list, ThreatManager maintains a heap of threat reference const pointers. *
+ * This heap is kept well-structured in all methods that modify ThreatReference, and is used to select the next target. *
+ * *
+ * Selection uses the following properties on ThreatReference, in order: *
+ * - Online state (one of ONLINE, SUPPRESSED, OFFLINE): *
+ * - ONLINE: Normal threat state, target is valid and attackable *
+ * - SUPPRESSED: Target is attackable, but fully immuned. This is used for targets under HoP, Divine Shield, Ice Block etc. *
+ * Targets with SUPPRESSED threat can still be valid targets, but any target with ONLINE threat will be preferred. *
+ * - OFFLINE: The target is, for whatever reason, not valid at this time (for example, IMMUNE_TO_X flags or game master state). *
+ * These targets can never be selected by SelectVictim, which will return nullptr if all targets are OFFLINE (typically causing evade). *
+ * - Related methods: GetOnlineState, IsOnline, IsAvailable, IsOffline *
+ * - Taunt state (one of TAUNT, NONE, DETAUNT), the names speak for themselves *
+ * - Related methods: GetTauntState, IsTaunting, IsDetaunted *
+ * - Actual threat value (GetThreat) *
+ * *
+ * The current (= last selected) victim can be accessed using GetCurrentVictim. SelectVictim selects a (potentially new) victim. *
+ * Beyond that, ThreatManager has a variety of helpers and notifiers, which are documented inline below. *
+ * *
+ * SPECIAL NOTE: Please be aware that any heap iterator may be invalidated if you modify a ThreatReference. The heap holds const pointers for a reason. *
+ * If you need to modify multiple ThreatReference objects, then use GetModifiableThreatList(), which is safe to modify! *
+\********************************************************************************************************************************************************/
+
+class ThreatReference;
+struct CompareThreatLessThan
{
- static float calcThreat(Unit* hatedUnit, Unit* hatingUnit, float threat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr);
- static bool isValidProcess(Unit* hatedUnit, Unit* hatingUnit, SpellInfo const* threatSpell = nullptr);
+ CompareThreatLessThan() {}
+ bool operator()(ThreatReference const* a, ThreatReference const* b) const;
};
-//==============================================================
-class TC_GAME_API HostileReference : public Reference<Unit, ThreatManager>
+// Please check Game/Combat/ThreatManager.h for documentation on how this class works!
+class TC_GAME_API ThreatManager
{
public:
- HostileReference(Unit* refUnit, ThreatManager* threatManager, float threat);
-
- // -- compatibility layer for combat rewrite (PR #19930)
- Unit* GetOwner() const;
- Unit* GetVictim() const { return getTarget(); }
- void AddThreat(float amt) { addThreat(amt); }
- void SetThreat(float amt) { setThreat(amt); }
- void ModifyThreatByPercent(int32 pct) { addThreatPercent(pct); }
- void ScaleThreat(float factor) { setThreat(iThreat*factor); }
- bool IsOnline() const { return iOnline; }
- bool IsAvailable() const { return iOnline; }
- bool IsOffline() const { return !iOnline; }
- float GetThreat() const { return getThreat(); }
- void ClearThreat() { removeReference(); }
-
- //=================================================
- void addThreat(float modThreat);
-
- void setThreat(float threat) { addThreat(threat - iThreat); }
-
- void addThreatPercent(int32 percent);
-
- float getThreat() const { return iThreat + iTempThreatModifier; }
-
- bool isOnline() const { 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() const { return iAccessible; }
-
- // used for temporary setting a threat and reducing it later again.
- // the threat modification is stored
- void setTempThreat(float threat)
- {
- addTempThreat(threat - iTempThreatModifier);
- }
-
- void addTempThreat(float threat)
- {
- if (!threat)
- return;
-
- iTempThreatModifier += threat;
-
- ThreatRefStatusChangeEvent event(UEV_THREAT_REF_THREAT_CHANGE, this, threat);
- fireStatusChanged(event);
- }
-
- void resetTempThreat()
- {
- addTempThreat(-iTempThreatModifier);
- }
-
- float getTempThreatModifier() { return iTempThreatModifier; }
-
- //=================================================
- // check, if source can reach target and set the status
- void updateOnlineStatus();
-
- void setOnlineOfflineState(bool isOnline);
-
- void setAccessibleState(bool isAccessible);
- //=================================================
-
- bool operator==(HostileReference const& hostileRef) const { return hostileRef.getUnitGuid() == getUnitGuid(); }
-
- //=================================================
-
- ObjectGuid getUnitGuid() const { return iUnitGuid; }
-
- //=================================================
- // reference is not needed anymore. realy delete it !
-
- void removeReference();
-
- //=================================================
-
- HostileReference* next() { return static_cast<HostileReference*>(Reference<Unit, ThreatManager>::next()); }
-
- //=================================================
-
- // Tell our refTo (target) object that we have a link
- void targetObjectBuildLink() override;
-
- // Tell our refTo (taget) object, that the link is cut
- void targetObjectDestroyLink() override;
-
- // Tell our refFrom (source) object, that the link is cut (Target destroyed)
- void sourceObjectDestroyLink() override;
-
- private:
- // Inform the source, that the status of that reference was changed
- void fireStatusChanged(ThreatRefStatusChangeEvent& threatRefStatusChangeEvent);
+ typedef boost::heap::fibonacci_heap<ThreatReference const*, boost::heap::compare<CompareThreatLessThan>> threat_list_heap;
+ static const uint32 CLIENT_THREAT_UPDATE_INTERVAL = 1000u;
+
+ static bool CanHaveThreatList(Unit const* who);
+
+ ThreatManager(Unit* owner);
+ // called from ::Create methods just after construction (once all fields on owner have been populated)
+ // should not be called from anywhere else
+ void Initialize();
+ // called from Creature::Update (only creatures can have their own threat list)
+ // should not be called from anywhere else
+ void Update(uint32 tdiff);
+
+ // never nullptr
+ Unit* GetOwner() const { return _owner; }
+ // can our owner have a threat list?
+ // identical to ThreatManager::CanHaveThreatList(GetOwner())
+ bool CanHaveThreatList() const { return _ownerCanHaveThreatList; }
+ // returns the victim selected by the last SelectVictim call - this can be nullptr
+ Unit* GetCurrentVictim() const;
+ // returns an arbitrary non-offline victim from owner's threat list if one exists, nullptr otherwise
+ Unit* GetAnyTarget() const;
+ // selects a (potentially new) victim from the threat list and returns it - this can be nullptr
+ Unit* SelectVictim();
+
+ bool IsEngaged() const { return _ownerEngaged; }
+ // are there any entries in owner's threat list?
+ bool IsThreatListEmpty(bool includeOffline = false) const;
+ // is there a threat list entry on owner's threat list with victim == who?
+ bool IsThreatenedBy(ObjectGuid const& who, bool includeOffline = false) const;
+ // is there a threat list entry on owner's threat list with victim == who?
+ bool IsThreatenedBy(Unit const* who, bool includeOffline = false) const;
+ // returns ThreatReference amount if a ref exists, 0.0f otherwise
+ float GetThreat(Unit const* who, bool includeOffline = false) const;
+ size_t GetThreatListSize() const { return _sortedThreatList.size(); }
+ // fastest of the three threat list getters - gets the threat list in "arbitrary" order
+ Trinity::IteratorPair<threat_list_heap::const_iterator> GetUnsortedThreatList() const { return { _sortedThreatList.begin(), _sortedThreatList.end() }; }
+ // slightly slower than GetUnsorted, but, well...sorted - only use it if you need the sorted property, of course
+ // note: current tank is NOT guaranteed to be the first entry in this list - check GetCurrentVictim separately if you want that!
+ Trinity::IteratorPair<threat_list_heap::ordered_iterator> GetSortedThreatList() const { return { _sortedThreatList.ordered_begin(), _sortedThreatList.ordered_end() }; }
+ // slowest of the three threat list getters (by far), but lets you modify the threat references
+ std::vector<ThreatReference*> GetModifiableThreatList() const;
+
+ // does any unit have a threat list entry with victim == this.owner?
+ bool IsThreateningAnyone(bool includeOffline = false) const;
+ // is there a threat list entry on who's threat list for this.owner?
+ bool IsThreateningTo(ObjectGuid const& who, bool includeOffline = false) const;
+ // is there a threat list entry on who's threat list for this.owner?
+ bool IsThreateningTo(Unit const* who, bool includeOffline = false) const;
+ auto const& GetThreatenedByMeList() const { return _threatenedByMe; }
+
+ // Notify the ThreatManager that a condition changed that may impact refs' online state so it can re-evaluate
+ void UpdateOnlineStates(bool meThreateningOthers = true, bool othersThreateningMe = true);
+ ///== AFFECT MY THREAT LIST ==
+ void AddThreat(Unit* target, float amount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false, bool ignoreRedirects = false);
+ void ScaleThreat(Unit* target, float factor);
+ // Modify target's threat by +percent%
+ void ModifyThreatByPercent(Unit* target, int32 percent) { if (percent) ScaleThreat(target, 0.01f*float(100 + percent)); }
+ // Resets the specified unit's threat to zero
+ void ResetThreat(Unit* target) { ScaleThreat(target, 0.0f); }
+ // Sets the specified unit's threat to be equal to the highest entry on the threat list
+ void MatchUnitThreatToHighestThreat(Unit* target);
+ // Notify the ThreatManager that we have a new taunt aura (or a taunt aura expired)
+ void TauntUpdate();
+ // Sets all threat refs in owner's threat list to have zero threat
+ void ResetAllThreat();
+ // Removes specified target from the threat list
+ void ClearThreat(Unit* target);
+ // Removes all targets from the threat list (will cause evade in UpdateVictim if called)
+ void ClearAllThreat();
- Unit* GetSourceUnit();
+ // sends SMSG_THREAT_UPDATE to all nearby clients (used by client to forward threat list info to addons)
+ void SendThreatListToClients() const;
+
+ ///== AFFECT OTHERS' THREAT LISTS ==
+ // what it says on the tin - call AddThreat on everything that's threatened by us with the specified params
+ void ForwardThreatForAssistingMe(Unit* assistant, float baseAmount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false);
+ // delete all ThreatReferences with victim == owner
+ void RemoveMeFromThreatLists();
+ // re-calculates the temporary threat modifier from auras on myself
+ void UpdateMyTempModifiers();
+ // re-calculate SPELL_AURA_MOD_THREAT modifiers
+ void UpdateMySpellSchoolModifiers();
+
+ ///== REDIRECT SYSTEM ==
+ // Register a redirection effect that redirects pct% of threat generated by owner to victim
+ void RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct);
+ // Unregister a redirection effort for all victims
+ void UnregisterRedirectThreat(uint32 spellId);
+ // Unregister a redirection effect for a specific victim
+ void UnregisterRedirectThreat(uint32 spellId, ObjectGuid const& victim);
private:
- float iThreat;
- float iTempThreatModifier; // used for SPELL_AURA_MOD_TOTAL_THREAT
-
- ObjectGuid iUnitGuid;
- bool iOnline;
- bool iAccessible;
-};
-
-//==============================================================
-class ThreatManager;
-
-class TC_GAME_API ThreatContainer
-{
- friend class ThreatManager;
+ Unit* const _owner;
+ bool _ownerCanHaveThreatList;
+ bool _ownerEngaged;
+
+ static const CompareThreatLessThan CompareThreat;
+ static bool CompareReferencesLT(ThreatReference const* a, ThreatReference const* b, float aWeight);
+ static float CalculateModifiedThreat(float threat, Unit const* victim, SpellInfo const* spell);
+
+ // send opcodes (all for my own threat list)
+ void SendClearAllThreatToClients() const;
+ void SendRemoveToClients(Unit const* victim) const;
+ void SendNewVictimToClients(ThreatReference const* victimRef) const;
+
+ ///== MY THREAT LIST ==
+ void PutThreatListRef(ObjectGuid const& guid, ThreatReference* ref);
+ void PurgeThreatListRef(ObjectGuid const& guid, bool sendRemove);
+
+ uint32 _updateClientTimer;
+ threat_list_heap _sortedThreatList;
+ std::unordered_map<ObjectGuid, ThreatReference*> _myThreatListEntries;
+ ThreatReference const* _currentVictimRef;
+ ThreatReference const* ReselectVictim();
+
+ ///== OTHERS' THREAT LISTS ==
+ void PutThreatenedByMeRef(ObjectGuid const& guid, ThreatReference* ref);
+ void PurgeThreatenedByMeRef(ObjectGuid const& guid);
+ std::unordered_map<ObjectGuid, ThreatReference*> _threatenedByMe; // these refs are entries for myself on other units' threat lists
+ float _singleSchoolModifiers[MAX_SPELL_SCHOOL]; // most spells are single school - we pre-calculate these and store them
+ mutable std::unordered_map<std::underlying_type<SpellSchoolMask>::type, float> _multiSchoolModifiers; // these are calculated on demand
+
+ // redirect system (is kind of dumb, but that's because none of the redirection spells actually have any aura effect associated with them, so spellscript needs to deal with it)
+ void UpdateRedirectInfo();
+ std::vector<std::pair<ObjectGuid, uint32>> _redirectInfo; // current redirection targets and percentages (updated from registry in ThreatManager::UpdateRedirectInfo)
+ std::unordered_map<uint32, std::unordered_map<ObjectGuid, uint32>> _redirectRegistry; // spellid -> (victim -> pct); all redirection effects on us (removal individually managed by spell scripts because blizzard is dumb)
public:
- typedef std::list<HostileReference*> StorageType;
+ ThreatManager(ThreatManager const&) = delete;
+ ThreatManager& operator=(ThreatManager const&) = delete;
- ThreatContainer(): iDirty(false) { }
-
- ~ThreatContainer() { clearReferences(); }
-
- HostileReference* addThreat(Unit* victim, float threat);
-
- void ModifyThreatByPercent(Unit* victim, int32 percent);
-
- HostileReference* selectNextVictim(Creature* attacker, HostileReference* currentVictim) const;
-
- void setDirty(bool isDirty) { iDirty = isDirty; }
-
- bool isDirty() const { return iDirty; }
-
- bool empty() const
- {
- return iThreatList.empty();
- }
-
- HostileReference* getMostHated() const
- {
- return iThreatList.empty() ? nullptr : iThreatList.front();
- }
-
- HostileReference* getReferenceByTarget(Unit const* victim) const;
-
- StorageType const & getThreatList() const { return iThreatList; }
-
- private:
- void remove(HostileReference* hostileRef)
- {
- iThreatList.remove(hostileRef);
- }
-
- void addReference(HostileReference* hostileRef)
- {
- iThreatList.push_back(hostileRef);
- }
-
- void clearReferences();
-
- // Sort the list if necessary
- void update();
-
- StorageType iThreatList;
- bool iDirty;
+ friend class ThreatReference;
+ friend struct CompareThreatLessThan;
};
-//=================================================
-
-typedef HostileReference ThreatReference;
-class TC_GAME_API ThreatManager
+// Please check Game/Combat/ThreatManager.h for documentation on how this class works!
+class TC_GAME_API ThreatReference
{
public:
- // -- compatibility layer for combat rewrite (PR #19930)
- Trinity::IteratorPair<std::list<ThreatReference*>::const_iterator> GetSortedThreatList() const { auto& list = iThreatContainer.getThreatList(); return { list.cbegin(), list.cend() }; }
- Trinity::IteratorPair<std::list<ThreatReference*>::const_iterator> GetUnsortedThreatList() const { return GetSortedThreatList(); }
- std::list<ThreatReference*> GetModifiableThreatList() const { return iThreatContainer.getThreatList(); }
- Unit* SelectVictim() { return getHostilTarget(); }
- Unit* GetCurrentVictim() const { if (ThreatReference* ref = getCurrentVictim()) return ref->GetVictim(); else return nullptr; }
- bool IsThreatListEmpty(bool includeOffline = false) const { return includeOffline ? areThreatListsEmpty() : isThreatListEmpty(); }
- bool IsThreatenedBy(Unit const* who, bool includeOffline = false) const { return (FindReference(who, includeOffline) != nullptr); }
- size_t GetThreatListSize() const { return iThreatContainer.iThreatList.size(); }
- void ForwardThreatForAssistingMe(Unit* victim, float amount, SpellInfo const* spell, bool ignoreModifiers = false, bool ignoreRedirection = false);
- Unit* GetAnyTarget() const { auto const& list = getThreatList(); if (!list.empty()) return list.front()->getTarget(); return nullptr; }
- void ResetThreat(Unit const* who) { if (auto* ref = FindReference(who, true)) ref->setThreat(0.0f); }
- void ResetAllThreat() { resetAllAggro(); }
- float GetThreat(Unit const* who, bool includeOffline = false) const { if (auto* ref = FindReference(who, includeOffline)) return ref->GetThreat(); return 0.0f; }
- void ClearThreat(Unit const* who) { if (auto* ref = FindReference(who, true)) ref->removeReference(); }
- void ClearAllThreat();
- void AddThreat(Unit* victim, float amount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false, bool ignoreRedirection = false);
+ enum TauntState { TAUNT_STATE_DETAUNT = -1, TAUNT_STATE_NONE = 0, TAUNT_STATE_TAUNT = 1 };
+ enum OnlineState { ONLINE_STATE_ONLINE = 2, ONLINE_STATE_SUPPRESSED = 1, ONLINE_STATE_OFFLINE = 0 };
+
+ Unit* GetOwner() const { return _owner; }
+ Unit* GetVictim() const { return _victim; }
+ float GetThreat() const { return std::max<float>(_baseAmount + (float)_tempModifier, 0.0f); }
+ OnlineState GetOnlineState() const { return _online; }
+ bool IsOnline() const { return (_online >= ONLINE_STATE_ONLINE); }
+ bool IsAvailable() const { return (_online > ONLINE_STATE_OFFLINE); }
+ bool IsOffline() const { return (_online <= ONLINE_STATE_OFFLINE); }
+ TauntState GetTauntState() const { return _taunted; }
+ bool IsTaunting() const { return _taunted == TAUNT_STATE_TAUNT; }
+ bool IsDetaunted() const { return _taunted == TAUNT_STATE_DETAUNT; }
+
+ void SetThreat(float amount) { _baseAmount = amount; HeapNotifyChanged(); }
+ void AddThreat(float amount);
+ void ScaleThreat(float factor);
+ void ModifyThreatByPercent(int32 percent) { if (percent) ScaleThreat(0.01f*float(100 + percent)); }
+ void UpdateOnlineState();
+
+ void ClearThreat(bool sendRemove = true); // dealloc's this
+
private:
- HostileReference* FindReference(Unit const* who, bool includeOffline) const { if (auto* ref = iThreatContainer.getReferenceByTarget(who)) return ref; if (includeOffline) if (auto* ref = iThreatOfflineContainer.getReferenceByTarget(who)) return ref; return nullptr; }
+ ThreatReference(ThreatManager* mgr, Unit* victim, float amount) : _owner(mgr->_owner), _mgr(mgr), _victim(victim), _baseAmount(amount), _tempModifier(0), _online(SelectOnlineState()), _taunted(TAUNT_STATE_NONE) { }
+ static bool FlagsAllowFighting(Unit const* a, Unit const* b);
+ OnlineState SelectOnlineState();
+ void UpdateTauntState(bool victimIsTaunting);
+ Unit* const _owner;
+ ThreatManager* const _mgr;
+ void HeapNotifyIncreased() { _mgr->_sortedThreatList.increase(_handle); }
+ void HeapNotifyDecreased() { _mgr->_sortedThreatList.decrease(_handle); }
+ void HeapNotifyChanged() { _mgr->_sortedThreatList.update(_handle); }
+ Unit* const _victim;
+ float _baseAmount;
+ int32 _tempModifier; // Temporary effects (auras with SPELL_AURA_MOD_TOTAL_THREAT) - set from victim's threatmanager in ThreatManager::UpdateMyTempModifiers
+ OnlineState _online;
+ TauntState _taunted;
+ ThreatManager::threat_list_heap::handle_type _handle;
public:
+ ThreatReference(ThreatReference const&) = delete;
+ ThreatReference& operator=(ThreatReference const&) = delete;
- friend class HostileReference;
-
- explicit ThreatManager(Unit* owner);
-
- ~ThreatManager() { clearReferences(); }
-
- void clearReferences();
-
- void addThreat(Unit* victim, float threat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr);
-
- void doAddThreat(Unit* victim, float threat);
-
- void ModifyThreatByPercent(Unit* victim, int32 percent);
-
- float getThreat(Unit* victim, bool alsoSearchOfflineList = false);
-
- bool isThreatListEmpty() const { return iThreatContainer.empty(); }
- bool areThreatListsEmpty() const { return iThreatContainer.empty() && iThreatOfflineContainer.empty(); }
-
- void processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent);
-
- bool isNeedUpdateToClient(uint32 time);
-
- HostileReference* getCurrentVictim() const { return iCurrentVictim; }
-
- Unit* GetOwner() const { return iOwner; }
-
- Unit* getHostilTarget();
-
- void tauntApply(Unit* taunter);
- void tauntFadeOut(Unit* taunter);
-
- void setCurrentVictim(HostileReference* hostileRef);
-
- void setDirty(bool isDirty) { iThreatContainer.setDirty(isDirty); }
-
- // Reset all aggro without modifying the threadlist.
- void resetAllAggro();
-
- // Reset all aggro of unit in threadlist satisfying the predicate.
- template<class PREDICATE> void resetAggro(PREDICATE predicate)
- {
- ThreatContainer::StorageType &threatList = iThreatContainer.iThreatList;
- if (threatList.empty())
- return;
-
- for (ThreatContainer::StorageType::iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
- {
- HostileReference* ref = (*itr);
-
- if (predicate(ref->getTarget()))
- {
- ref->setThreat(0);
- setDirty(true);
- }
- }
- }
-
- // methods to access the lists from the outside to do some dirty manipulation (scriping and such)
- // I hope they are used as little as possible.
- ThreatContainer::StorageType const & getThreatList() const { return iThreatContainer.getThreatList(); }
- ThreatContainer::StorageType const & getOfflineThreatList() const { return iThreatOfflineContainer.getThreatList(); }
- ThreatContainer& getOnlineContainer() { return iThreatContainer; }
- ThreatContainer& getOfflineContainer() { return iThreatOfflineContainer; }
- private:
- void _addThreat(Unit* victim, float threat);
-
- HostileReference* iCurrentVictim;
- Unit* iOwner;
- uint32 iUpdateTimer;
- ThreatContainer iThreatContainer;
- ThreatContainer iThreatOfflineContainer;
+ friend class ThreatManager;
+ friend struct CompareThreatLessThan;
};
-//=================================================
+inline bool CompareThreatLessThan::operator()(ThreatReference const* a, ThreatReference const* b) const { return ThreatManager::CompareReferencesLT(a, b, 1.0f); }
-namespace Trinity
-{
- // Binary predicate for sorting HostileReferences based on threat value
- class ThreatOrderPred
- {
- public:
- ThreatOrderPred(bool ascending = false) : m_ascending(ascending) { }
- bool operator() (HostileReference const* a, HostileReference const* b) const
- {
- return m_ascending ? a->getThreat() < b->getThreat() : a->getThreat() > b->getThreat();
- }
- private:
- const bool m_ascending;
- };
-}
-#endif
+ #endif
diff --git a/src/server/game/Combat/UnitEvents.h b/src/server/game/Combat/UnitEvents.h
deleted file mode 100644
index fec0f089ac7..00000000000
--- a/src/server/game/Combat/UnitEvents.h
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
- *
- * 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 2 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/>.
- */
-
-#ifndef _UNITEVENTS
-#define _UNITEVENTS
-
-#include "Common.h"
-
-class ThreatContainer;
-class ThreatManager;
-class HostileReference;
-
-//==============================================================
-//==============================================================
-
-enum UNIT_EVENT_TYPE
-{
- // Player/Pet changed on/offline status
- UEV_THREAT_REF_ONLINE_STATUS = 1<<0,
-
- // Threat for Player/Pet changed
- UEV_THREAT_REF_THREAT_CHANGE = 1<<1,
-
- // Player/Pet will be removed from list (dead) [for internal use]
- UEV_THREAT_REF_REMOVE_FROM_LIST = 1<<2,
-
- // Player/Pet entered/left water or some other place where it is/was not accessible for the creature
- UEV_THREAT_REF_ACCESSIBLE_STATUS = 1<<3,
-
- // Threat list is going to be sorted (if dirty flag is set)
- UEV_THREAT_SORT_LIST = 1<<4,
-
- // New target should be fetched, could tbe the current target as well
- UEV_THREAT_SET_NEXT_TARGET = 1<<5,
-
- // A new victim (target) was set. Could be NULL
- UEV_THREAT_VICTIM_CHANGED = 1<<6
-
- // Future use
- //UEV_UNIT_KILLED = 1<<7,
-
- //Future use
- //UEV_UNIT_HEALTH_CHANGE = 1<<8,
-};
-
-#define UEV_THREAT_REF_EVENT_MASK (UEV_THREAT_REF_ONLINE_STATUS | UEV_THREAT_REF_THREAT_CHANGE | UEV_THREAT_REF_REMOVE_FROM_LIST | UEV_THREAT_REF_ACCESSIBLE_STATUS)
-#define UEV_THREAT_MANAGER_EVENT_MASK (UEV_THREAT_SORT_LIST | UEV_THREAT_SET_NEXT_TARGET | UEV_THREAT_VICTIM_CHANGED)
-#define UEV_ALL_EVENT_MASK (0xffffffff)
-
-// Future use
-//#define UEV_UNIT_EVENT_MASK (UEV_UNIT_KILLED | UEV_UNIT_HEALTH_CHANGE)
-
-//==============================================================
-
-class UnitBaseEvent
-{
- public:
- explicit UnitBaseEvent(uint32 pType) { iType = pType; }
- uint32 getType() const { return iType; }
- bool matchesTypeMask(uint32 pMask) const { return (iType & pMask) != 0; }
-
- private:
- uint32 iType;
-
- protected:
- ~UnitBaseEvent() { }
-};
-
-//==============================================================
-
-class TC_GAME_API ThreatRefStatusChangeEvent : public UnitBaseEvent
-{
- private:
- HostileReference* iHostileReference;
- union
- {
- float iFValue;
- int32 iIValue;
- bool iBValue;
- };
- ThreatManager* iThreatManager;
-
- public:
- explicit ThreatRefStatusChangeEvent(uint32 pType) : UnitBaseEvent(pType), iHostileReference(nullptr), iThreatManager(nullptr) { }
-
- ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference) : UnitBaseEvent(pType), iHostileReference(pHostileReference), iThreatManager(nullptr) { }
-
- ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, float pValue) : UnitBaseEvent(pType), iHostileReference(pHostileReference), iFValue(pValue), iThreatManager(nullptr) { }
-
- ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, bool pValue) : UnitBaseEvent(pType), iHostileReference(pHostileReference), iBValue(pValue), iThreatManager(nullptr) { }
-
- int32 getIValue() const { return iIValue; }
-
- float getFValue() const { return iFValue; }
-
- bool getBValue() const { return iBValue; }
-
- void setBValue(bool pValue) { iBValue = pValue; }
-
- HostileReference* getReference() const { return iHostileReference; }
-
- void setThreatManager(ThreatManager* pThreatManager) { iThreatManager = pThreatManager; }
-
- ThreatManager* GetThreatManager() const { return iThreatManager; }
-};
-
-#endif
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index a8fb84f7f47..e6fd20e73aa 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -671,6 +671,8 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/,
UpdateMovementFlags();
LoadCreaturesAddon();
LoadTemplateImmunities();
+
+ GetThreatManager().UpdateOnlineStates(true, true);
return true;
}
@@ -771,6 +773,8 @@ void Creature::Update(uint32 diff)
if (!IsAlive())
break;
+ GetThreatManager().Update(diff);
+
if (m_shouldReacquireTarget && !IsFocusing(nullptr, true))
{
SetTarget(m_suppressedTarget);
@@ -1161,6 +1165,8 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, Posit
ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, true);
}
+ GetThreatManager().Initialize();
+
return true;
}
@@ -1198,6 +1204,103 @@ Creature* Creature::CreateCreatureFromDB(ObjectGuid::LowType spawnId, Map* map,
return creature;
}
+Unit* Creature::SelectVictim()
+{
+ Unit* target = nullptr;
+
+ ThreatManager& mgr = GetThreatManager();
+
+ if (mgr.CanHaveThreatList())
+ {
+ target = mgr.SelectVictim();
+ while (!target)
+ {
+ Unit* newTarget = nullptr;
+ // 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
+ for (auto const& pair : GetCombatManager().GetPvECombatRefs())
+ {
+ newTarget = pair.second->GetOther(this);
+ if (!mgr.IsThreatenedBy(newTarget, true))
+ {
+ mgr.AddThreat(newTarget, 0.0f, nullptr, true, true);
+ break;
+ }
+ else
+ newTarget = nullptr;
+ }
+ if (!newTarget)
+ break;
+ target = mgr.SelectVictim();
+ }
+ }
+ else if (!HasReactState(REACT_PASSIVE))
+ {
+ // We're a player pet, probably
+ target = getAttackerForHelper();
+ if (!target && IsSummon())
+ {
+ if (Unit* owner = ToTempSummon()->GetOwner())
+ {
+ if (owner->IsInCombat())
+ target = owner->getAttackerForHelper();
+ if (!target)
+ {
+ for (ControlList::const_iterator itr = owner->m_Controlled.begin(); itr != owner->m_Controlled.end(); ++itr)
+ {
+ if ((*itr)->IsInCombat())
+ {
+ target = (*itr)->getAttackerForHelper();
+ if (target)
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ return nullptr;
+
+ if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
+ {
+ if (!IsFocusing(nullptr, true))
+ SetInFront(target);
+ return target;
+ }
+
+ /// @todo a vehicle may eat some mob, so mob should not evade
+ if (GetVehicle())
+ return nullptr;
+
+ // search nearby enemy before enter evade mode
+ if (HasReactState(REACT_AGGRESSIVE))
+ {
+ target = SelectNearestTargetInAttackDistance(m_CombatDistance ? m_CombatDistance : ATTACK_DISTANCE);
+
+ if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
+ return target;
+ }
+
+ Unit::AuraEffectList const& iAuras = GetAuraEffectsByType(SPELL_AURA_MOD_INVISIBILITY);
+ if (!iAuras.empty())
+ {
+ for (Unit::AuraEffectList::const_iterator itr = iAuras.begin(); itr != iAuras.end(); ++itr)
+ {
+ if ((*itr)->GetBase()->IsPermanent())
+ {
+ AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_OTHER);
+ break;
+ }
+ }
+ return nullptr;
+ }
+
+ // enter in evade mode in other case
+ AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES);
+
+ return nullptr;
+}
+
void Creature::InitializeReactState()
{
if (IsTotem() || IsTrigger() || IsCritter() || IsSpiritService())
@@ -3286,6 +3389,37 @@ bool Creature::CanGiveExperience() const
&& !(GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_XP_AT_KILL);
}
+void Creature::AtEnterCombat()
+{
+ Unit::AtEnterCombat();
+
+ if (!(GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_MOUNTED_COMBAT_ALLOWED))
+ Dismount();
+
+ if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
+ {
+ UpdateSpeed(MOVE_RUN);
+ UpdateSpeed(MOVE_SWIM);
+ UpdateSpeed(MOVE_FLIGHT);
+ }
+}
+
+void Creature::AtExitCombat()
+{
+ Unit::AtExitCombat();
+
+ ClearUnitState(UNIT_STATE_ATTACK_PLAYER);
+ if (HasDynamicFlag(UNIT_DYNFLAG_TAPPED))
+ SetDynamicFlags(GetCreatureTemplate()->dynamicflags);
+
+ if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
+ {
+ UpdateSpeed(MOVE_RUN);
+ UpdateSpeed(MOVE_SWIM);
+ UpdateSpeed(MOVE_FLIGHT);
+ }
+}
+
bool Creature::IsEscortNPC(bool onlyIfActive)
{
if (!IsAIEnabled)
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index 6c8ebd2db22..4bd28f76e99 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -102,11 +102,23 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
bool IsDungeonBoss() const { return (GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_DUNGEON_BOSS) != 0; }
bool IsAffectedByDiminishingReturns() const override { return Unit::IsAffectedByDiminishingReturns() || (GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_ALL_DIMINISH) != 0; }
+ Unit* SelectVictim();
+
void SetReactState(ReactStates st) { m_reactState = st; }
ReactStates GetReactState() const { return m_reactState; }
bool HasReactState(ReactStates state) const { return (m_reactState == state); }
void InitializeReactState();
+ using Unit::IsImmuneToAll;
+ using Unit::SetImmuneToAll;
+ void SetImmuneToAll(bool apply) override { Unit::SetImmuneToAll(apply, HasReactState(REACT_PASSIVE)); }
+ using Unit::IsImmuneToPC;
+ using Unit::SetImmuneToPC;
+ void SetImmuneToPC(bool apply) override { Unit::SetImmuneToPC(apply, HasReactState(REACT_PASSIVE)); }
+ using Unit::IsImmuneToNPC;
+ using Unit::SetImmuneToNPC;
+ void SetImmuneToNPC(bool apply) override { Unit::SetImmuneToNPC(apply, HasReactState(REACT_PASSIVE)); }
+
/// @todo Rename these properly
bool isCanInteractWithBattleMaster(Player* player, bool msg) const;
bool CanResetTalents(Player* player) const;
@@ -303,8 +315,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
void SignalFormationMovement(Position const& destination, uint32 id = 0, uint32 moveType = 0, bool orientation = false);
bool IsFormationLeaderMoveAllowed() const;
- Unit* SelectVictim();
-
void SetDisableReputationGain(bool disable) { DisableReputationGain = disable; }
bool IsReputationGainDisabled() const { return DisableReputationGain; }
bool IsDamageEnoughForLootingAndReward() const { return (m_creatureInfo->flags_extra & CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ) || (m_PlayerDamageReq == 0); }
@@ -346,6 +356,9 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
bool CanGiveExperience() const;
+ void AtEnterCombat() override;
+ void AtExitCombat() override;
+
protected:
bool CreateFromProto(ObjectGuid::LowType guidlow, uint32 entry, CreatureData const* data = nullptr, uint32 vehId = 0);
bool InitEntry(uint32 entry, CreatureData const* data = nullptr);
diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h
index 530b3aae43f..eb18fa57ee4 100644
--- a/src/server/game/Entities/Object/Object.h
+++ b/src/server/game/Entities/Object/Object.h
@@ -428,6 +428,10 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
{
return GetPhaseShift().CanSee(obj->GetPhaseShift());
}
+ static bool InSamePhase(WorldObject const* a, WorldObject const* b)
+ {
+ return a && b && a->IsInPhase(b);
+ }
PhaseShift& GetPhaseShift() { return _phaseShift; }
PhaseShift const& GetPhaseShift() const { return _phaseShift; }
diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp
index 36ae708e962..d9cdfe8762a 100644
--- a/src/server/game/Entities/Pet/Pet.cpp
+++ b/src/server/game/Entities/Pet/Pet.cpp
@@ -1630,6 +1630,8 @@ bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 Entry)
AddUnitFlag2(UNIT_FLAG2_REGENERATE_POWER);
SetSheath(SHEATH_STATE_MELEE);
+ GetThreatManager().Initialize();
+
return true;
}
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index bf9356f5e6a..aabfbf911ee 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -568,6 +568,8 @@ bool Player::Create(ObjectGuid::LowType guidlow, WorldPackets::Character::Charac
SetPrimarySpecialization(defaultSpec->ID);
}
+ GetThreatManager().Initialize();
+
return true;
}
@@ -946,7 +948,7 @@ void Player::Update(uint32 p_time)
UpdateAfkReport(now);
- if (GetCombatTimer()) // Only set when in pvp combat
+ if (GetCombatManager().HasPvPCombat())
if (Aura* aura = GetAura(SPELL_PVP_RULES_ENABLED))
if (!aura->IsPermanent())
aura->SetDuration(aura->GetSpellInfo()->GetMaxDuration());
@@ -1220,7 +1222,7 @@ void Player::Update(uint32 p_time)
{
m_hostileReferenceCheckTimer = 15 * IN_MILLISECONDS;
if (!GetMap()->IsDungeon())
- getHostileRefManager().deleteReferencesOutOfRange(GetVisibilityRange());
+ GetCombatManager().EndCombatBeyondRange(GetVisibilityRange(), true);
}
else
m_hostileReferenceCheckTimer -= p_time;
@@ -2060,9 +2062,6 @@ void Player::SetInWater(bool inWater)
// Call base
Unit::SetInWater(inWater);
-
- // Update threat tables
- getHostileRefManager().updateThreatTables();
}
bool Player::IsInAreaTriggerRadius(AreaTriggerEntry const* trigger) const
@@ -2104,15 +2103,11 @@ void Player::SetGameMaster(bool on)
AddUnitFlag2(UNIT_FLAG2_ALLOW_CHEAT_SPELLS);
if (Pet* pet = GetPet())
- {
pet->SetFaction(FACTION_FRIENDLY);
- pet->getHostileRefManager().setOnlineOfflineState(false);
- }
RemovePvpFlag(UNIT_BYTE2_FLAG_FFA_PVP);
ResetContestedPvP();
- getHostileRefManager().setOnlineOfflineState(false);
CombatStopWithPets();
PhasingHandler::SetAlwaysVisible(this, true, false);
@@ -2130,7 +2125,7 @@ void Player::SetGameMaster(bool on)
if (Pet* pet = GetPet())
{
pet->SetFaction(GetFaction());
- pet->getHostileRefManager().setOnlineOfflineState(true);
+ pet->GetThreatManager().UpdateOnlineStates();
}
// restore FFA PvP Server state
@@ -2140,7 +2135,6 @@ void Player::SetGameMaster(bool on)
// restore FFA PvP area state, remove not allowed for GM mounts
UpdateArea(m_areaUpdateId);
- getHostileRefManager().setOnlineOfflineState(true);
m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
}
@@ -22666,7 +22660,6 @@ void Player::CleanupAfterTaxiFlight()
m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
Dismount();
RemoveUnitFlag(UnitFlags(UNIT_FLAG_REMOVE_CLIENT_CONTROL | UNIT_FLAG_TAXI_FLIGHT));
- getHostileRefManager().setOnlineOfflineState(true);
}
void Player::ContinueTaxiFlight() const
@@ -25837,6 +25830,20 @@ void Player::ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidDat
m_MirrorTimerFlags &= ~(UNDERWATER_INWATER | UNDERWATER_INLAVA | UNDERWATER_INSLIME | UNDERWATER_INDARKWATER);
}
+void Player::AtEnterCombat()
+{
+ Unit::AtEnterCombat();
+ if (GetCombatManager().HasPvPCombat())
+ EnablePvpRules(true);
+}
+
+void Player::AtExitCombat()
+{
+ Unit::AtExitCombat();
+ UpdatePotionCooldown();
+ m_combatExitTime = getMSTime();
+}
+
float Player::GetBlockPercent(uint8 attackerLevel) const
{
float blockArmor = float(*m_activePlayerData->ShieldBlock);
@@ -26812,7 +26819,7 @@ void Player::DisablePvpRules()
if (IsInAreaThatActivatesPvpTalents())
return;
- if (!GetCombatTimer())
+ if (!GetCombatManager().HasPvPCombat())
{
RemoveAurasDueToSpell(SPELL_PVP_RULES_ENABLED);
UpdateItemLevelAreaBasedScaling();
@@ -27983,14 +27990,6 @@ VoidStorageItem* Player::GetVoidStorageItem(uint64 id, uint8& slot) const
return nullptr;
}
-void Player::OnCombatExit()
-{
- Unit::OnCombatExit();
-
- UpdatePotionCooldown();
- m_combatExitTime = getMSTime();
-}
-
void Player::CreateGarrison(uint32 garrSiteId)
{
std::unique_ptr<Garrison> garrison(new Garrison(this));
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index ad8e9ac1e7a..9e267fdf923 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -2012,6 +2012,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
bool UpdatePosition(float x, float y, float z, float orientation, bool teleport = false) override;
bool UpdatePosition(Position const& pos, bool teleport = false) override { return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport); }
void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> const& liquidData) override;
+ void AtEnterCombat() override;
+ void AtExitCombat() override;
void SendMessageToSet(WorldPacket const* data, bool self) const override { SendMessageToSetInRange(data, GetVisibilityRange(), self); }
void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const override;
@@ -2528,8 +2530,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
VoidStorageItem* GetVoidStorageItem(uint8 slot) const;
VoidStorageItem* GetVoidStorageItem(uint64 id, uint8& slot) const;
- void OnCombatExit() override;
-
void CreateGarrison(uint32 garrSiteId);
void DeleteGarrison();
Garrison* GetGarrison() const { return _garrison.get(); }
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index a606cd14190..ba8ce866e7f 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -295,13 +295,13 @@ SpellNonMeleeDamage::SpellNonMeleeDamage(Unit* _attacker, Unit* _target, SpellIn
Unit::Unit(bool isWorldObject) :
WorldObject(isWorldObject), m_playerMovingMe(nullptr), m_lastSanctuaryTime(0),
- IsAIEnabled(false), NeedChangeAI(false), LastCharmerGUID(),
- m_ControlledByPlayer(false), movespline(new Movement::MoveSpline()),
- i_AI(nullptr), i_disabledAI(nullptr), m_AutoRepeatFirstCast(false), m_procDeep(0),
- m_removedAurasCount(0), m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None),
- i_motionMaster(new MotionMaster(this)), m_regenTimer(0), m_ThreatManager(this),
- m_vehicle(nullptr), m_vehicleKit(nullptr), m_unitTypeMask(UNIT_MASK_NONE), m_Diminishing(),
- m_HostileRefManager(this), _aiAnimKitId(0), _movementAnimKitId(0), _meleeAnimKitId(0),
+ IsAIEnabled(false), NeedChangeAI(false), LastCharmerGUID(), m_ControlledByPlayer(false),
+ movespline(new Movement::MoveSpline()), i_AI(nullptr), i_disabledAI(nullptr),
+ m_AutoRepeatFirstCast(false), m_procDeep(0), m_removedAurasCount(0),
+ m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None),
+ i_motionMaster(new MotionMaster(this)), m_regenTimer(0), m_vehicle(nullptr),
+ m_vehicleKit(nullptr), m_unitTypeMask(UNIT_MASK_NONE), m_Diminishing(), m_combatManager(this),
+ m_threatManager(this), _aiAnimKitId(0), _movementAnimKitId(0), _meleeAnimKitId(0),
_spellHistory(new SpellHistory(this))
{
m_objectType |= TYPEMASK_UNIT;
@@ -377,18 +377,12 @@ Unit::Unit(bool isWorldObject) :
}
m_baseSpellCritChance = 5;
- m_CombatTimer = 0;
-
- for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
- m_threatModifier[i] = 1.0f;
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
m_speed_rate[i] = 1.0f;
m_charmInfo = nullptr;
- _redirectThreadInfo = RedirectThreatInfo();
-
// remove aurastates allowing special moves
for (uint8 i = 0; i < MAX_REACTIVE; ++i)
m_reactiveTimer[i] = 0;
@@ -438,28 +432,6 @@ Unit::~Unit()
ASSERT(m_dynObj.empty());
}
-// Check if unit in combat with specific unit
-bool Unit::IsInCombatWith(Unit const* who) const
-{
- // Check target exists
- if (!who)
- return false;
-
- // Search in threat list
- ObjectGuid guid = who->GetGUID();
- for (ThreatContainer::StorageType::const_iterator i = m_ThreatManager.getThreatList().begin(); i != m_ThreatManager.getThreatList().end(); ++i)
- {
- HostileReference* ref = (*i);
-
- // Return true if the unit matches
- if (ref && ref->getUnitGuid() == guid)
- return true;
- }
-
- // Nothing found, false.
- return false;
-}
-
void Unit::Update(uint32 p_time)
{
// WARNING! Order of execution here is important, do not change.
@@ -476,24 +448,7 @@ void Unit::Update(uint32 p_time)
// Having this would prevent spells from being proced, so let's crash
ASSERT(!m_procDeep);
- if (CanHaveThreatList() && GetThreatManager().isNeedUpdateToClient(p_time))
- SendThreatListUpdate();
-
- // update combat timer only for players and pets (only pets with PetAI)
- if (IsInCombat() && (GetTypeId() == TYPEID_PLAYER || (IsPet() && IsControlledByPlayer())))
- {
- // Check UNIT_STATE_MELEE_ATTACKING or UNIT_STATE_CHASE (without UNIT_STATE_FOLLOW in this case) so pets can reach far away
- // targets without stopping half way there and running off.
- // These flags are reset after target dies or another command is given.
- if (m_HostileRefManager.isEmpty())
- {
- // m_CombatTimer set at aura start and it will be freeze until aura removing
- if (m_CombatTimer <= p_time)
- ClearInCombat();
- else
- m_CombatTimer -= p_time;
- }
- }
+ m_combatManager.Update(p_time);
// not implemented before 3.0.2
if (uint32 base_att = getAttackTimer(BASE_ATTACK))
@@ -2060,7 +2015,7 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extr
if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim))
return;
- CombatStart(victim);
+ AttackedTarget(victim, true);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Attacking);
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
@@ -2144,7 +2099,7 @@ void Unit::FakeAttackerStateUpdate(Unit* victim, WeaponAttackType attType /*= BA
if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim))
return;
- CombatStart(victim);
+ AttackedTarget(victim, true);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Attacking);
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
@@ -5718,20 +5673,20 @@ Unit* Unit::getAttackerForHelper() const // If someone wants to
return nullptr;
if (Unit* victim = GetVictim())
- if ((!IsPet() && !GetPlayerMovingMe()) || IsInCombatWith(victim) || victim->IsInCombatWith(this))
+ if ((!IsPet() && !GetPlayerMovingMe()) || IsInCombatWith(victim))
return victim;
- if (!m_attackers.empty())
- return *(m_attackers.begin());
-
- if (Player* owner = GetCharmerOrOwnerPlayerOrPlayerItself())
- {
- HostileRefManager& refs = owner->getHostileRefManager();
- for (Reference<Unit, ThreatManager> const& ref : refs)
- if (Unit* hostile = ref.GetSource()->GetOwner())
- return hostile;
- }
-
+ CombatManager const& mgr = GetCombatManager();
+ // pick arbitrary targets; our pvp combat > owner's pvp combat > our pve combat > owner's pve combat
+ Unit* owner = GetCharmerOrOwner();
+ if (mgr.HasPvPCombat())
+ return mgr.GetPvPCombatRefs().begin()->second->GetOther(this);
+ if (owner && (owner->GetCombatManager().HasPvPCombat()))
+ return owner->GetCombatManager().GetPvPCombatRefs().begin()->second->GetOther(owner);
+ if (mgr.HasPvECombat())
+ return mgr.GetPvECombatRefs().begin()->second->GetOther(this);
+ if (owner && (owner->GetCombatManager().HasPvECombat()))
+ return owner->GetCombatManager().GetPvECombatRefs().begin()->second->GetOther(owner);
return nullptr;
}
@@ -5822,19 +5777,7 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
if (creature && !IsPet())
{
- // should not let player enter combat by right clicking target - doesn't helps
- GetThreatManager().AddThreat(victim, 0.0f);
- SetInCombatWith(victim);
- if (victim->GetTypeId() == TYPEID_PLAYER)
- victim->SetInCombatWith(this);
-
- if (Unit* owner = victim->GetOwner())
- {
- GetThreatManager().AddThreat(owner, 0.0f);
- SetInCombatWith(owner);
- if (owner->GetTypeId() == TYPEID_PLAYER)
- owner->SetInCombatWith(this);
- }
+ EngageWithTarget(victim); // ensure that anything we're attacking has threat
creature->SendAIReaction(AI_REACTION_HOSTILE);
creature->CallAssistance();
@@ -5915,7 +5858,7 @@ void Unit::ValidateAttackersAndOwnTarget()
AttackStop();
}
-void Unit::CombatStop(bool includingCast)
+void Unit::CombatStop(bool includingCast, bool mutualPvP)
{
if (includingCast && IsNonMeleeSpellCast(false))
InterruptNonMeleeSpells(false);
@@ -5924,19 +5867,22 @@ void Unit::CombatStop(bool includingCast)
RemoveAllAttackers();
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
- ClearInCombat();
- // just in case
- if (IsPetInCombat() && GetTypeId() != TYPEID_PLAYER)
- ClearInPetCombat();
+ if (mutualPvP)
+ ClearInCombat();
+ else
+ { // vanish and brethren are weird
+ m_combatManager.EndAllPvECombat();
+ m_combatManager.SuppressPvPCombat();
+ }
}
void Unit::CombatStopWithPets(bool includingCast)
{
CombatStop(includingCast);
- for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
- (*itr)->CombatStop(includingCast);
+ for (Unit* minion : m_Controlled)
+ minion->CombatStop(includingCast);
}
bool Unit::isAttackingPlayer() const
@@ -6329,6 +6275,7 @@ void Unit::SetMinion(Minion *minion, bool apply)
}
}
}
+ UpdatePetCombatState();
}
void Unit::GetAllMinionsByEntry(std::list<TempSummon*>& Minions, uint32 entry)
@@ -6430,6 +6377,7 @@ void Unit::SetCharm(Unit* charm, bool apply)
m_Controlled.erase(charm);
}
}
+ UpdatePetCombatState();
}
void Unit::DealHeal(HealInfo& healInfo)
@@ -6576,6 +6524,8 @@ void Unit::RemoveAllControlled()
TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its minion %s", GetEntry(), GetMinionGUID().ToString().c_str());
if (!GetCharmGUID().IsEmpty())
TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its charm %s", GetEntry(), GetCharmGUID().ToString().c_str());
+ if (!IsPet()) // pets don't use the flag for this
+ RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); // m_controlled is now empty, so we know none of our minions are in combat
}
bool Unit::isPossessedByPlayer() const
@@ -8143,190 +8093,119 @@ bool Unit::IsServiceProvider() const
UNIT_NPC_FLAG_SPIRITGUIDE | UNIT_NPC_FLAG_TABARDDESIGNER | UNIT_NPC_FLAG_AUCTIONEER));
}
-void Unit::SetInCombatWith(Unit* enemy)
+void Unit::EngageWithTarget(Unit* enemy)
{
- Unit* eOwner = enemy->GetCharmerOrOwnerOrSelf();
- if (eOwner->IsPvP() || eOwner->IsFFAPvP())
- {
- SetInCombatState(true, enemy);
+ if (!enemy)
return;
- }
- // check for duel
- if (eOwner->GetTypeId() == TYPEID_PLAYER && eOwner->ToPlayer()->duel)
- {
- Unit const* myOwner = GetCharmerOrOwnerOrSelf();
- if (((Player const*)eOwner)->duel->opponent == myOwner)
- {
- SetInCombatState(true, enemy);
- return;
- }
- }
- SetInCombatState(false, enemy);
-}
+ if (IsEngagedBy(enemy))
+ return;
-void Unit::SetImmuneToPC(bool apply, bool keepCombat)
-{
- (void)keepCombat;
- if (apply)
- AddUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
+ if (CanHaveThreatList())
+ m_threatManager.AddThreat(enemy, 0.0f, nullptr, true, true);
else
- RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
-}
+ SetInCombatWith(enemy);
-void Unit::SetImmuneToNPC(bool apply, bool keepCombat)
-{
- (void)keepCombat;
- if (apply)
- AddUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
- else
- RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
+ if (Creature* creature = ToCreature())
+ if (CreatureGroup* formation = creature->GetFormation())
+ formation->MemberEngagingTarget(creature, enemy);
}
-void Unit::CombatStart(Unit* target, bool initialAggro)
+void Unit::AttackedTarget(Unit* target, bool canInitialAggro)
{
- if (initialAggro)
- {
- if (!target->IsStandState())
- target->SetStandState(UNIT_STAND_STATE_STAND);
-
- if (!target->IsInCombat() && target->GetTypeId() != TYPEID_PLAYER
- && !target->ToCreature()->HasReactState(REACT_PASSIVE) && target->ToCreature()->IsAIEnabled)
- target->ToCreature()->AI()->AttackStart(this);
-
- SetInCombatWith(target);
- target->SetInCombatWith(this);
- }
+ if (!target->IsEngaged() && !canInitialAggro)
+ return;
+ target->EngageWithTarget(this);
+ if (Unit* targetOwner = target->GetCharmerOrOwner())
+ targetOwner->EngageWithTarget(this);
- Player* me = GetCharmerOrOwnerPlayerOrPlayerItself();
- Unit* who = target->GetCharmerOrOwnerOrSelf();
- if (me && who->GetTypeId() == TYPEID_PLAYER)
- me->SetContestedPvP(who->ToPlayer());
- if (me && who->IsPvP()
- && (who->GetTypeId() != TYPEID_PLAYER
- || !me->duel || me->duel->opponent != who))
+ Player* myPlayerOwner = GetCharmerOrOwnerPlayerOrPlayerItself();
+ Player* targetPlayerOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself();
+ if (myPlayerOwner && targetPlayerOwner && !(myPlayerOwner->duel && myPlayerOwner->duel->opponent == targetPlayerOwner))
{
- me->UpdatePvP(true);
- me->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::PvPActive);
+ myPlayerOwner->UpdatePvP(true);
+ myPlayerOwner->SetContestedPvP(targetPlayerOwner);
+ myPlayerOwner->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::PvPActive);
}
}
-void Unit::SetInCombatState(bool PvP, Unit* enemy)
+void Unit::SetImmuneToAll(bool apply, bool keepCombat)
{
- // only alive units can be in combat
- if (!IsAlive())
- return;
-
- if (PvP)
+ if (apply)
{
- m_CombatTimer = 5000;
- if (Player* me = ToPlayer())
- me->EnablePvpRules(true);
+ AddUnitFlag(UnitFlags(UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC));
+ ValidateAttackersAndOwnTarget();
+ if (keepCombat)
+ m_threatManager.UpdateOnlineStates(true, true);
+ else
+ m_combatManager.EndAllCombat();
}
-
- if (IsInCombat() || HasUnitState(UNIT_STATE_EVADE))
- return;
-
- AddUnitFlag(UNIT_FLAG_IN_COMBAT);
-
- if (Creature* creature = ToCreature())
+ else
{
- // Set home position at place of engaging combat for escorted creatures
- if ((IsAIEnabled && creature->AI()->IsEscorted()) ||
- GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE ||
- GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE)
- creature->SetHomePosition(GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
-
- if (enemy)
- {
- if (IsAIEnabled)
- creature->AI()->JustEngagedWith(enemy);
-
- if (creature->GetFormation())
- creature->GetFormation()->MemberEngagingTarget(creature, enemy);
- }
-
- if (IsPet())
- {
- UpdateSpeed(MOVE_RUN);
- UpdateSpeed(MOVE_SWIM);
- UpdateSpeed(MOVE_FLIGHT);
- }
-
- if (!(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_MOUNTED_COMBAT_ALLOWED))
- Dismount();
+ RemoveUnitFlag(UnitFlags(UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC));
+ m_threatManager.UpdateOnlineStates(true, true);
}
+}
- for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();)
+void Unit::SetImmuneToPC(bool apply, bool keepCombat)
+{
+ if (apply)
{
- Unit* controlled = *itr;
- ++itr;
-
- controlled->SetInCombatState(PvP, enemy);
+ AddUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
+ ValidateAttackersAndOwnTarget();
+ if (keepCombat)
+ m_threatManager.UpdateOnlineStates(true, true);
+ else
+ {
+ std::list<CombatReference*> toEnd;
+ for (auto const& pair : m_combatManager.GetPvECombatRefs())
+ if (pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ toEnd.push_back(pair.second);
+ for (auto const& pair : m_combatManager.GetPvPCombatRefs())
+ if (pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ toEnd.push_back(pair.second);
+ for (CombatReference* ref : toEnd)
+ ref->EndCombat();
+ }
}
-
- for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
+ else
{
- AuraApplication* aurApp = itr->second;
- ++itr;
-
- aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, true);
+ RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
+ m_threatManager.UpdateOnlineStates(true, true);
}
-
- if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL])
- if (spell->getState() == SPELL_STATE_PREPARING
- && spell->m_spellInfo->HasAttribute(SPELL_ATTR0_CANT_USED_IN_COMBAT)
- && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Combat))
- InterruptNonMeleeSpells(false);
-
- RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnteringCombat);
- ProcSkillsAndAuras(enemy, PROC_FLAG_ENTER_COMBAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
-void Unit::ClearInCombat()
+void Unit::SetImmuneToNPC(bool apply, bool keepCombat)
{
- m_CombatTimer = 0;
- RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
-
- // Player's state will be cleared in Player::UpdateContestedPvP
- if (Creature* creature = ToCreature())
+ if (apply)
{
- ClearUnitState(UNIT_STATE_ATTACK_PLAYER);
- if (HasDynamicFlag(UNIT_DYNFLAG_TAPPED))
- SetDynamicFlags(creature->GetCreatureTemplate()->dynamicflags);
-
- if (creature->IsPet() || creature->IsGuardian())
+ AddUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
+ ValidateAttackersAndOwnTarget();
+ if (keepCombat)
+ m_threatManager.UpdateOnlineStates(true, true);
+ else
{
- if (Unit* owner = GetOwner())
- for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
- if (owner->GetSpeedRate(UnitMoveType(i)) > GetSpeedRate(UnitMoveType(i)))
- SetSpeedRate(UnitMoveType(i), owner->GetSpeedRate(UnitMoveType(i)));
+ std::list<CombatReference*> toEnd;
+ for (auto const& pair : m_combatManager.GetPvECombatRefs())
+ if (!pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ toEnd.push_back(pair.second);
+ for (auto const& pair : m_combatManager.GetPvPCombatRefs())
+ if (!pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ toEnd.push_back(pair.second);
+ for (CombatReference* ref : toEnd)
+ ref->EndCombat();
}
- else if (!IsCharmed())
- return;
}
-
- OnCombatExit();
-
- RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeavingCombat);
+ else
+ {
+ RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
+ m_threatManager.UpdateOnlineStates(true, true);
+ }
}
-void Unit::ClearInPetCombat()
+bool Unit::IsThreatened() const
{
- RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
- if (Unit* owner = GetOwner())
- owner->RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
-}
-
-void Unit::OnCombatExit()
-{
- for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
- {
- AuraApplication* aurApp = itr->second;
- ++itr;
-
- aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, false);
- }
+ return !m_threatManager.IsThreatListEmpty();
}
bool Unit::isTargetableForAttack(bool checkFakeDeath) const
@@ -8375,7 +8254,7 @@ bool Unit::_IsValidAttackTarget(Unit const* target, SpellInfo const* bySpell, Wo
{
// ignore stealth for aoe spells. Ignore stealth if target is player and unit in combat with same player
bool const ignoreStealthCheck = (bySpell && bySpell->IsAffectingArea()) ||
- (target->GetTypeId() == TYPEID_PLAYER && target->HasStealthAura() && target->IsInCombat() && IsInCombatWith(target));
+ (target->GetTypeId() == TYPEID_PLAYER && target->HasStealthAura() && IsInCombatWith(target));
if (!CanSeeOrDetect(target, ignoreStealthCheck))
return false;
@@ -8961,7 +8840,6 @@ void Unit::setDeathState(DeathState s)
{
CombatStop();
GetThreatManager().ClearAllThreat();
- getHostileRefManager().deleteReferences();
if (IsNonMeleeSpellCast(false))
InterruptNonMeleeSpells(false);
@@ -9005,243 +8883,60 @@ void Unit::setDeathState(DeathState s)
RemoveUnitFlag(UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground)
}
-/*########################################
-######## ########
-######## AGGRO SYSTEM ########
-######## ########
-########################################*/
-bool Unit::CanHaveThreatList(bool skipAliveCheck) const
-{
- // only creatures can have threat list
- if (GetTypeId() != TYPEID_UNIT)
- return false;
-
- // only alive units can have threat list
- if (!skipAliveCheck && !IsAlive())
- return false;
-
- // totems can not have threat list
- if (IsTotem())
- return false;
-
- // vehicles can not have threat list
- //if (ToCreature()->IsVehicle())
- // return false;
-
- // summons can not have a threat list, unless they are controlled by a creature
- if (HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN | UNIT_MASK_CONTROLABLE_GUARDIAN) && GetOwnerGUID().IsPlayer())
- return false;
-
- return true;
-}
-
-//======================================================================
-
-float Unit::ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask)
-{
- if (!HasAuraType(SPELL_AURA_MOD_THREAT) || fThreat < 0)
- return fThreat;
-
- SpellSchools school = GetFirstSchoolInMask(schoolMask);
-
- return fThreat * m_threatModifier[school];
-}
-
//======================================================================
-void Unit::TauntApply(Unit* taunter)
+void Unit::AtEnterCombat()
{
- ASSERT(GetTypeId() == TYPEID_UNIT);
-
- if (!taunter || (taunter->GetTypeId() == TYPEID_PLAYER && taunter->ToPlayer()->IsGameMaster()))
- return;
-
- if (!CanHaveThreatList())
- return;
-
- Creature* creature = ToCreature();
-
- if (creature->HasReactState(REACT_PASSIVE))
- return;
+ for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
+ {
+ AuraApplication* aurApp = itr->second;
+ ++itr;
- Unit* target = GetVictim();
- if (target && target == taunter)
- return;
+ aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, true);
+ }
- if (!IsFocusing(nullptr, true))
- SetInFront(taunter);
- if (creature->IsAIEnabled)
- creature->AI()->AttackStart(taunter);
+ if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL])
+ if (spell->getState() == SPELL_STATE_PREPARING
+ && spell->m_spellInfo->HasAttribute(SPELL_ATTR0_CANT_USED_IN_COMBAT)
+ && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Combat))
+ InterruptNonMeleeSpells(false);
- //m_ThreatManager.tauntApply(taunter);
+ RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnteringCombat);
+ ProcSkillsAndAuras(nullptr, PROC_FLAG_ENTER_COMBAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
-//======================================================================
-
-void Unit::TauntFadeOut(Unit* taunter)
+void Unit::AtExitCombat()
{
- ASSERT(GetTypeId() == TYPEID_UNIT);
-
- if (!taunter || (taunter->GetTypeId() == TYPEID_PLAYER && taunter->ToPlayer()->IsGameMaster()))
- return;
-
- if (!CanHaveThreatList())
- return;
-
- Creature* creature = ToCreature();
-
- if (creature->HasReactState(REACT_PASSIVE))
- return;
-
- Unit* target = GetVictim();
- if (!target || target != taunter)
- return;
-
- if (m_ThreatManager.isThreatListEmpty())
+ for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
{
- if (creature->IsAIEnabled)
- creature->AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES);
- return;
- }
-
- target = creature->SelectVictim(); // might have more taunt auras remaining
+ AuraApplication* aurApp = itr->second;
+ ++itr;
- if (target && target != taunter)
- {
- if (!IsFocusing(nullptr, true))
- SetInFront(target);
- if (creature->IsAIEnabled)
- creature->AI()->AttackStart(target);
+ aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, false);
}
-}
-//======================================================================
+ RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeavingCombat);
+}
-Unit* Creature::SelectVictim()
+void Unit::UpdatePetCombatState()
{
- // function provides main threat functionality
- // next-victim-selection algorithm and evade mode are called
- // threat list sorting etc.
+ 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
- Unit* target = nullptr;
- // First checking if we have some taunt on us
- AuraEffectList const& tauntAuras = GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
- if (!tauntAuras.empty())
- {
- Unit* caster = tauntAuras.back()->GetCaster();
-
- // The last taunt aura caster is alive an we are happy to attack him
- if (caster && caster->IsAlive())
- return GetVictim();
- else if (tauntAuras.size() > 1)
+ bool state = false;
+ for (Unit* minion : m_Controlled)
+ if (minion->IsInCombat())
{
- // 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
- AuraEffectList::const_iterator aura = --tauntAuras.end();
- do
- {
- --aura;
- caster = (*aura)->GetCaster();
- if (caster && CanSeeOrDetect(caster, true) && IsValidAttackTarget(caster) && caster->isInAccessiblePlaceFor(ToCreature()))
- {
- target = caster;
- break;
- }
- } while (aura != tauntAuras.begin());
+ state = true;
+ break;
}
- else
- target = GetVictim();
- }
- if (CanHaveThreatList())
- {
- if (!target && !m_ThreatManager.isThreatListEmpty())
- // No taunt aura or taunt aura caster is dead standard target selection
- target = m_ThreatManager.getHostilTarget();
- }
- else if (!HasReactState(REACT_PASSIVE))
- {
- // We have player pet probably
- target = getAttackerForHelper();
- if (!target && IsSummon())
- {
- if (Unit* owner = ToTempSummon()->GetOwner())
- {
- if (owner->IsInCombat())
- target = owner->getAttackerForHelper();
- if (!target)
- {
- for (ControlList::const_iterator itr = owner->m_Controlled.begin(); itr != owner->m_Controlled.end(); ++itr)
- {
- if ((*itr)->IsInCombat())
- {
- target = (*itr)->getAttackerForHelper();
- if (target)
- break;
- }
- }
- }
- }
- }
- }
+ if (state)
+ AddUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
else
- return nullptr;
-
- if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
- {
- if (!IsFocusing(nullptr, 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
- for (AttackerSet::const_iterator itr = m_attackers.begin(); itr != m_attackers.end(); ++itr)
- {
- if ((*itr) && CanCreatureAttack(*itr) && (*itr)->GetTypeId() != TYPEID_PLAYER
- && !(*itr)->ToCreature()->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN))
- return nullptr;
- }
-
- /// @todo a vehicle may eat some mob, so mob should not evade
- if (GetVehicle())
- return nullptr;
-
- // search nearby enemy before enter evade mode
- if (HasReactState(REACT_AGGRESSIVE))
- {
- target = SelectNearestTargetInAttackDistance(m_CombatDistance ? m_CombatDistance : ATTACK_DISTANCE);
-
- if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
- return target;
- }
-
- Unit::AuraEffectList const& iAuras = GetAuraEffectsByType(SPELL_AURA_MOD_INVISIBILITY);
- if (!iAuras.empty())
- {
- for (Unit::AuraEffectList::const_iterator itr = iAuras.begin(); itr != iAuras.end(); ++itr)
- {
- if ((*itr)->GetBase()->IsPermanent())
- {
- AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_OTHER);
- break;
- }
- }
- return nullptr;
- }
-
- // enter in evade mode in other case
- AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES);
-
- return nullptr;
+ RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
}
//======================================================================
-//======================================================================
-//======================================================================
float Unit::ApplyEffectModifiers(SpellInfo const* spellProto, uint8 effect_index, float value) const
{
@@ -10274,7 +9969,6 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup)
m_Events.KillAllEvents(false); // non-delatable (currently cast spells) will not deleted now but it will deleted at call in Map::RemoveAllObjectsInRemoveList
CombatStop();
GetThreatManager().ClearAllThreat();
- getHostileRefManager().deleteReferences();
}
void Unit::CleanupsBeforeDelete(bool finalCleanup)
@@ -11701,7 +11395,6 @@ void Unit::Kill(Unit* victim, bool durabilityLoss /*= true*/, bool skipSettingDe
}
// 10% durability loss on death
- // clean InHateListOf
if (Player* plrVictim = victim->ToPlayer())
{
// remember victim PvP death for corpse type and corpse reclaim delay
@@ -12107,7 +11800,6 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au
CastStop();
CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells)
- GetThreatManager().ClearAllThreat();
Player* playerCharmer = charmer->ToPlayer();
@@ -12255,8 +11947,6 @@ void Unit::RemoveCharmedBy(Unit* charmer)
CastStop();
CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells)
- getHostileRefManager().deleteReferences();
- GetThreatManager().ClearAllThreat();
if (_oldFactionId)
{
@@ -12336,6 +12026,8 @@ void Unit::RemoveCharmedBy(Unit* charmer)
player->SetClientControl(this, true);
}
+ EngageWithTarget(charmer);
+
// a guardian should always have charminfo
if (playerCharmer && this != charmer->GetFirstControlled())
playerCharmer->SendRemoveControlBar();
@@ -12363,11 +12055,6 @@ void Unit::RestoreFaction()
}
}
-Unit* Unit::GetRedirectThreatTarget()
-{
- return !_redirectThreadInfo.GetTargetGUID().IsEmpty() ? ObjectAccessor::GetUnit(*this, _redirectThreadInfo.GetTargetGUID()) : nullptr;
-}
-
bool Unit::CreateVehicleKit(uint32 id, uint32 creatureEntry, bool loading /*= false*/)
{
VehicleEntry const* vehInfo = sVehicleStore.LookupEntry(id);
@@ -12748,35 +12435,7 @@ void Unit::OnPhaseChange()
return;
if (GetTypeId() == TYPEID_UNIT || !ToPlayer()->GetSession()->PlayerLogout())
- {
- HostileRefManager& refManager = getHostileRefManager();
- HostileReference* ref = refManager.getFirst();
-
- while (ref)
- {
- if (Unit* unit = ref->GetSource()->GetOwner())
- if (Creature* creature = unit->ToCreature())
- refManager.setOnlineOfflineState(creature, creature->IsInPhase(this));
-
- ref = ref->next();
- }
-
- // modify threat lists for new phasemask
- if (GetTypeId() != TYPEID_PLAYER)
- {
- std::list<HostileReference*> threatList = GetThreatManager().getThreatList();
- std::list<HostileReference*> offlineThreatList = GetThreatManager().getOfflineThreatList();
-
- // merge expects sorted lists
- threatList.sort();
- offlineThreatList.sort();
- threatList.merge(offlineThreatList);
-
- for (std::list<HostileReference*>::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
- if (Unit* unit = (*itr)->getTarget())
- unit->getHostileRefManager().setOnlineOfflineState(ToCreature(), unit->IsInPhase(this));
- }
- }
+ m_threatManager.UpdateOnlineStates(true, true);
}
void Unit::UpdateObjectVisibility(bool forced)
@@ -12785,6 +12444,7 @@ void Unit::UpdateObjectVisibility(bool forced)
AddToNotify(NOTIFY_VISIBILITY_CHANGED);
else
{
+ m_threatManager.UpdateOnlineStates(true, true);
WorldObject::UpdateObjectVisibility(true);
// call MoveInLineOfSight for nearby creatures
Trinity::AIRelocationNotifier notifier(*this);
@@ -13222,7 +12882,9 @@ bool Unit::CanSwim() const
return true;
if (HasUnitFlag2(UnitFlags2(0x1000000)))
return false;
- return HasUnitFlag(UnitFlags(UNIT_FLAG_PET_IN_COMBAT | UNIT_FLAG_RENAME | UNIT_FLAG_UNK_15));
+ if (IsPet() && HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT))
+ return true;
+ return HasUnitFlag(UnitFlags(UNIT_FLAG_RENAME | UNIT_FLAG_UNK_15));
}
void Unit::NearTeleportTo(Position const& pos, bool casting /*= false*/)
@@ -13344,60 +13006,6 @@ void Unit::UpdateHeight(float newZ)
GetVehicleKit()->RelocatePassengers();
}
-void Unit::SendThreatListUpdate()
-{
- if (!GetThreatManager().isThreatListEmpty())
- {
- WorldPackets::Combat::ThreatUpdate packet;
- packet.UnitGUID = GetGUID();
- ThreatContainer::StorageType const& tlist = GetThreatManager().getThreatList();
- packet.ThreatList.reserve(tlist.size());
- for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr)
- {
- WorldPackets::Combat::ThreatInfo info;
- info.UnitGUID = (*itr)->getUnitGuid();
- info.Threat = int64((*itr)->getThreat() * 100);
- packet.ThreatList.push_back(info);
- }
- SendMessageToSet(packet.Write(), false);
- }
-}
-
-void Unit::SendChangeCurrentVictimOpcode(HostileReference* pHostileReference)
-{
- if (!GetThreatManager().isThreatListEmpty())
- {
- WorldPackets::Combat::HighestThreatUpdate packet;
- packet.UnitGUID = GetGUID();
- packet.HighestThreatGUID = pHostileReference->getUnitGuid();
- ThreatContainer::StorageType const& tlist = GetThreatManager().getThreatList();
- packet.ThreatList.reserve(tlist.size());
- for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr)
- {
- WorldPackets::Combat::ThreatInfo info;
- info.UnitGUID = (*itr)->getUnitGuid();
- info.Threat = int64((*itr)->getThreat() * 100);
- packet.ThreatList.push_back(info);
- }
- SendMessageToSet(packet.Write(), false);
- }
-}
-
-void Unit::SendClearThreatListOpcode()
-{
- WorldPackets::Combat::ThreatClear packet;
- packet.UnitGUID = GetGUID();
- SendMessageToSet(packet.Write(), false);
-}
-
-void Unit::SendRemoveFromThreatListOpcode(HostileReference* pHostileReference)
-{
- WorldPackets::Combat::ThreatRemove packet;
- packet.UnitGUID = GetGUID();
- packet.AboutGUID = pHostileReference->getUnitGuid();
- SendMessageToSet(packet.Write(), false);
-}
-
// baseRage means damage taken when attacker = false
void Unit::RewardRage(uint32 baseRage)
{
@@ -13439,10 +13047,15 @@ void Unit::StopAttackFaction(uint32 faction_id)
++itr;
}
- getHostileRefManager().deleteReferencesForFaction(faction_id);
+ std::vector<CombatReference*> refsToEnd;
+ for (auto const& pair : m_combatManager.GetPvECombatRefs())
+ if (pair.second->GetOther(this)->GetFactionTemplateEntry()->Faction == faction_id)
+ refsToEnd.push_back(pair.second);
+ for (CombatReference* ref : refsToEnd)
+ ref->EndCombat();
- for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
- (*itr)->StopAttackFaction(faction_id);
+ for (Unit* minion : m_Controlled)
+ minion->StopAttackFaction(faction_id);
}
void Unit::OutDebugInfo() const
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index 5275677e2e8..7f5f57a621f 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -22,7 +22,7 @@
#include "EventProcessor.h"
#include "FollowerReference.h"
#include "FollowerRefManager.h"
-#include "HostileRefManager.h"
+#include "CombatManager.h"
#include "OptionalFwd.h"
#include "SpellAuraDefines.h"
#include "SpellDefines.h"
@@ -581,28 +581,6 @@ struct SpellPeriodicAuraLogInfo
uint32 createProcHitMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition);
-struct RedirectThreatInfo
-{
- RedirectThreatInfo() : _threatPct(0) { }
- ObjectGuid _targetGUID;
- uint32 _threatPct;
-
- ObjectGuid GetTargetGUID() const { return _targetGUID; }
- uint32 GetThreatPct() const { return _threatPct; }
-
- void Set(ObjectGuid guid, uint32 pct)
- {
- _targetGUID = guid;
- _threatPct = pct;
- }
-
- void ModifyThreatPct(int32 amount)
- {
- amount += _threatPct;
- _threatPct = uint32(std::max(0, amount));
- }
-};
-
enum CurrentSpellTypes : uint8
{
CURRENT_MELEE_SPELL = 0,
@@ -854,7 +832,7 @@ class TC_GAME_API Unit : public WorldObject
}
void ValidateAttackersAndOwnTarget();
- void CombatStop(bool includingCast = false);
+ void CombatStop(bool includingCast = false, bool mutualPvP = true);
void CombatStopWithPets(bool includingCast = false);
void StopAttackFaction(uint32 faction_id);
Unit* SelectNearbyTarget(Unit* exclude = nullptr, float dist = NOMINAL_MELEE_RANGE) const;
@@ -1123,29 +1101,45 @@ class TC_GAME_API Unit : public WorldObject
bool IsInFlight() const { return HasUnitState(UNIT_STATE_IN_FLIGHT); }
- bool IsEngaged() const { return IsInCombat(); }
- bool IsEngagedBy(Unit const* who) const { return IsInCombatWith(who); }
- void EngageWithTarget(Unit* who) { SetInCombatWith(who); who->SetInCombatWith(this); GetThreatManager().AddThreat(who, 0.0f); }
- bool IsThreatened() const { return CanHaveThreatList() && !GetThreatManager().IsThreatListEmpty(); }
- bool IsThreatenedBy(Unit const* who) const { return who && CanHaveThreatList() && GetThreatManager().IsThreatenedBy(who); }
+ /// ====================== THREAT & COMBAT ====================
+ bool CanHaveThreatList() const { return m_threatManager.CanHaveThreatList(); }
+ // For NPCs with threat list: Whether there are any enemies on our threat list
+ // For other units: Whether we're in combat
+ // This value is different from IsInCombat when a projectile spell is midair (combat on launch - threat+aggro on impact)
+ bool IsEngaged() const { return CanHaveThreatList() ? m_threatManager.IsEngaged() : IsInCombat(); }
+ bool IsEngagedBy(Unit const* who) const { return CanHaveThreatList() ? IsThreatenedBy(who) : IsInCombatWith(who); }
+ void EngageWithTarget(Unit* who); // Adds target to threat list if applicable, otherwise just sets combat state
+ // Combat handling
+ CombatManager& GetCombatManager() { return m_combatManager; }
+ CombatManager const& GetCombatManager() const { return m_combatManager; }
+ void AttackedTarget(Unit* target, bool canInitialAggro);
- void SetImmuneToAll(bool apply, bool keepCombat = false) { SetImmuneToPC(apply, keepCombat); SetImmuneToNPC(apply, keepCombat); }
bool IsImmuneToAll() const { return IsImmuneToPC() && IsImmuneToNPC(); }
- void SetImmuneToPC(bool apply, bool keepCombat = false);
+ void SetImmuneToAll(bool apply, bool keepCombat);
+ virtual void SetImmuneToAll(bool apply) { SetImmuneToAll(apply, false); }
bool IsImmuneToPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC); }
- void SetImmuneToNPC(bool apply, bool keepCombat = false);
+ void SetImmuneToPC(bool apply, bool keepCombat);
+ virtual void SetImmuneToPC(bool apply) { SetImmuneToPC(apply, false); }
bool IsImmuneToNPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); }
+ void SetImmuneToNPC(bool apply, bool keepCombat);
+ virtual void SetImmuneToNPC(bool apply) { SetImmuneToNPC(apply, false); }
+
+ bool IsInCombat() const { return HasUnitFlag(UNIT_FLAG_IN_COMBAT); }
+ bool IsInCombatWith(Unit const* who) const { return who && m_combatManager.IsInCombatWith(who); }
+ void SetInCombatWith(Unit* enemy) { if (enemy) m_combatManager.SetInCombatWith(enemy); }
+ void ClearInCombat() { m_combatManager.EndAllCombat(); }
+ void UpdatePetCombatState();
+ // Threat handling
+ bool IsThreatened() const;
+ bool IsThreatenedBy(Unit const* who) const { return who && m_threatManager.IsThreatenedBy(who, true); }
+ // Exposes the threat manager directly - be careful when interfacing with this
+ // As a general rule of thumb, any unit pointer MUST be null checked BEFORE passing it to threatmanager methods
+ // threatmanager will NOT null check your pointers for you - misuse = crash
+ ThreatManager& GetThreatManager() { return m_threatManager; }
+ ThreatManager const& GetThreatManager() const { return m_threatManager; }
- bool IsInCombat() const { return HasUnitFlag(UNIT_FLAG_IN_COMBAT); }
- bool IsPetInCombat() const { return HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT); }
- bool IsInCombatWith(Unit const* who) const;
- void CombatStart(Unit* target, bool initialAggro = true);
- void SetInCombatState(bool PvP, Unit* enemy = nullptr);
- void SetInCombatWith(Unit* enemy);
- void ClearInCombat();
- void ClearInPetCombat();
- uint32 GetCombatTimer() const { return m_CombatTimer; }
- virtual void OnCombatExit();
+ void SendClearTarget();
+ void SendThreatListUpdate() { m_threatManager.SendThreatListToClients(); }
bool HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, flag128 familyFlags) const;
bool virtual HasSpell(uint32 /*spellID*/) const { return false; }
@@ -1251,13 +1245,6 @@ class TC_GAME_API Unit : public WorldObject
void SetFacingTo(float const ori, bool force = true);
void SetFacingToObject(WorldObject const* object, bool force = true);
- void SendChangeCurrentVictimOpcode(HostileReference* pHostileReference);
- void SendClearThreatListOpcode();
- void SendRemoveFromThreatListOpcode(HostileReference* pHostileReference);
- void SendThreatListUpdate();
-
- void SendClearTarget();
-
bool IsAlive() const { return (m_deathState == ALIVE); }
bool isDying() const { return (m_deathState == JUST_DIED); }
bool isDead() const { return (m_deathState == DEAD || m_deathState == CORPSE); }
@@ -1554,7 +1541,6 @@ class TC_GAME_API Unit : public WorldObject
float m_modSpellHitChance;
int32 m_baseSpellCritChance;
- float m_threatModifier[MAX_SPELL_SCHOOL];
uint32 m_baseAttackSpeed[MAX_ATTACK];
float m_modAttackSpeedPct[MAX_ATTACK];
@@ -1632,17 +1618,6 @@ class TC_GAME_API Unit : public WorldObject
SpellImmuneContainer m_spellImmune[MAX_SPELL_IMMUNITY];
uint32 m_lastSanctuaryTime;
- // Threat related methods
- bool CanHaveThreatList(bool skipAliveCheck = false) const;
- float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL);
- void TauntApply(Unit* victim);
- void TauntFadeOut(Unit* taunter);
- ThreatManager& GetThreatManager() { return m_ThreatManager; }
- ThreatManager const& GetThreatManager() const { return m_ThreatManager; }
- void addHatedBy(HostileReference* pHostileReference) { m_HostileRefManager.insertFirst(pHostileReference); }
- void removeHatedBy(HostileReference* /*pHostileReference*/) { /* nothing to do yet */ }
- HostileRefManager& getHostileRefManager() { return m_HostileRefManager; }
-
VisibleAuraContainer const& GetVisibleAuras() const { return m_visibleAuras; }
bool HasVisibleAura(AuraApplication* aurApp) const { return m_visibleAuras.count(aurApp) > 0; }
void SetVisibleAura(AuraApplication* aurApp);
@@ -1724,8 +1699,6 @@ class TC_GAME_API Unit : public WorldObject
uint32 SpellCriticalDamageBonus(SpellInfo const* spellProto, uint32 damage, Unit* victim);
uint32 SpellCriticalHealingBonus(SpellInfo const* spellProto, uint32 damage, Unit* victim);
- void SetContestedPvP(Player* attackedPlayer = nullptr);
-
uint32 GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectType damagetype, uint32 CastingTime) const;
float CalculateDefaultCoefficient(SpellInfo const* spellInfo, DamageEffectType damagetype) const;
@@ -1810,13 +1783,6 @@ class TC_GAME_API Unit : public WorldObject
uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const;
- // Redirect Threat
- void SetRedirectThreat(ObjectGuid guid, uint32 pct) { _redirectThreadInfo.Set(guid, pct); }
- void ResetRedirectThreat() { SetRedirectThreat(ObjectGuid::Empty, 0); }
- void ModifyRedirectThreat(int32 amount) { _redirectThreadInfo.ModifyThreatPct(amount); }
- uint32 GetRedirectThreatPercent() const { return _redirectThreadInfo.GetThreatPct(); }
- Unit* GetRedirectThreatTarget();
-
friend class VehicleJoinEvent;
bool IsAIEnabled, NeedChangeAI;
ObjectGuid LastCharmerGUID;
@@ -1977,8 +1943,6 @@ class TC_GAME_API Unit : public WorldObject
uint32 m_reactiveTimer[MAX_REACTIVE];
uint32 m_regenTimer;
- ThreatManager m_ThreatManager;
-
Vehicle* m_vehicle;
Vehicle* m_vehicleKit;
@@ -1994,6 +1958,9 @@ class TC_GAME_API Unit : public WorldObject
virtual void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> const& liquidData);
virtual void SetInWater(bool inWater);
+ virtual void AtEnterCombat();
+ virtual void AtExitCombat();
+
private:
void UpdateSplineMovement(uint32 t_diff);
@@ -2016,17 +1983,17 @@ class TC_GAME_API Unit : public WorldObject
private:
uint32 m_state; // Even derived shouldn't modify
- uint32 m_CombatTimer;
TimeTrackerSmall m_movesplineTimer;
Diminishing m_Diminishing;
// Manage all Units that are threatened by us
- HostileRefManager m_HostileRefManager;
+ friend class CombatManager;
+ CombatManager m_combatManager;
+ friend class ThreatManager;
+ ThreatManager m_threatManager;
FollowerRefManager m_FollowingRefManager;
- RedirectThreatInfo _redirectThreadInfo;
-
bool m_cleanupDone; // lock made to not add stuff after cleanup before delete
bool m_duringRemoveFromWorld; // lock made to not add stuff after begining removing from world
bool _instantCast;
diff --git a/src/server/game/Grids/ObjectGridLoader.cpp b/src/server/game/Grids/ObjectGridLoader.cpp
index acff2940241..aafe9b65d58 100644
--- a/src/server/game/Grids/ObjectGridLoader.cpp
+++ b/src/server/game/Grids/ObjectGridLoader.cpp
@@ -274,7 +274,7 @@ void ObjectGridStoper::Visit(CreatureMapType &m)
{
iter->GetSource()->RemoveAllDynObjects();
iter->GetSource()->RemoveAllAreaTriggers();
- if (iter->GetSource()->IsInCombat() || !iter->GetSource()->GetThreatManager().areThreatListsEmpty())
+ if (iter->GetSource()->IsInCombat())
{
iter->GetSource()->CombatStop();
iter->GetSource()->GetThreatManager().ClearAllThreat();
diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp
index 9a2863ae12c..c38a63201b1 100644
--- a/src/server/game/Handlers/PetHandler.cpp
+++ b/src/server/game/Handlers/PetHandler.cpp
@@ -134,7 +134,6 @@ void WorldSession::HandlePetStopAttack(WorldPackets::Pet::PetStopAttack& packet)
return;
pet->AttackStop();
- pet->ClearInPetCombat();
}
void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellid, uint16 flag, ObjectGuid guid2, Position const& pos)
@@ -168,7 +167,6 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
case COMMAND_FOLLOW: //spellid=1792 //FOLLOW
pet->AttackStop();
pet->InterruptNonMeleeSpells(false);
- pet->ClearInPetCombat();
pet->GetMotionMaster()->MoveFollow(_player, PET_FOLLOW_DIST, pet->GetFollowAngle());
charmInfo->SetCommandState(COMMAND_FOLLOW);
@@ -282,7 +280,6 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
{
case REACT_PASSIVE: //passive
pet->AttackStop();
- pet->ClearInPetCombat();
/* fallthrough */
case REACT_DEFENSIVE: //recovery
case REACT_AGGRESSIVE: //activete
diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp
index 91138fc043e..8d6d2e8f663 100644
--- a/src/server/game/Maps/Map.cpp
+++ b/src/server/game/Maps/Map.cpp
@@ -875,21 +875,10 @@ void Map::Update(uint32 t_diff)
// Handle updates for creatures in combat with player and are more than 60 yards away
if (player->IsInCombat())
{
- std::vector<Creature*> updateList;
- HostileReference* ref = player->getHostileRefManager().getFirst();
-
- while (ref)
- {
- if (Unit* unit = ref->GetSource()->GetOwner())
- if (unit->ToCreature() && unit->GetMapId() == player->GetMapId() && !unit->IsWithinDistInMap(player, GetVisibilityRange(), false))
- updateList.push_back(unit->ToCreature());
-
- ref = ref->next();
- }
-
- // Process deferred update list for player
- for (Creature* c : updateList)
- VisitNearbyCellsOf(c, grid_object_update, world_object_update);
+ for (auto const& pair : player->GetCombatManager().GetPvECombatRefs())
+ if (Creature* unit = pair.second->GetOther(player)->ToCreature())
+ if (unit->GetMapId() == player->GetMapId() && !unit->IsWithinDistInMap(player, GetVisibilityRange(), false))
+ VisitNearbyCellsOf(unit, grid_object_update, world_object_update);
}
}
@@ -1040,7 +1029,7 @@ void Map::RemovePlayerFromMap(Player* player, bool remove)
player->UpdateZone(MAP_INVALID_ZONE, 0);
sScriptMgr->OnPlayerLeaveMap(this, player);
- player->getHostileRefManager().deleteReferences(); // multithreading crashfix
+ player->CombatStop();
bool const inWorld = player->IsInWorld();
player->RemoveFromWorld();
diff --git a/src/server/game/Movement/MovementGenerators/FlightPathMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/FlightPathMovementGenerator.cpp
index 483ebc4841f..1e07dbd1a31 100644
--- a/src/server/game/Movement/MovementGenerators/FlightPathMovementGenerator.cpp
+++ b/src/server/game/Movement/MovementGenerators/FlightPathMovementGenerator.cpp
@@ -117,7 +117,6 @@ void FlightPathMovementGenerator::DoFinalize(Player* player)
if (player->m_taxi.empty())
{
- player->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
@@ -131,8 +130,8 @@ void FlightPathMovementGenerator::DoFinalize(Player* player)
void FlightPathMovementGenerator::DoReset(Player* player)
{
- player->getHostileRefManager().setOnlineOfflineState(false);
player->AddUnitState(UNIT_STATE_IN_FLIGHT);
+ player->CombatStopWithPets();
player->AddUnitFlag(UnitFlags(UNIT_FLAG_REMOVE_CLIENT_CONTROL | UNIT_FLAG_TAXI_FLIGHT));
Movement::MoveSplineInit init(player);
diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp
index 1cb18499c12..9631b1c007e 100644
--- a/src/server/game/Server/WorldSession.cpp
+++ b/src/server/game/Server/WorldSession.cpp
@@ -508,7 +508,7 @@ void WorldSession::LogoutPlayer(bool save)
///- If the player just died before logging out, make him appear as a ghost
if (_player->GetDeathTimer())
{
- _player->getHostileRefManager().deleteReferences();
+ _player->CombatStop();
_player->BuildPlayerRepop();
_player->RepopAtGraveyard();
}
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
index c3a135b69e5..5653a456002 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
@@ -288,7 +288,7 @@ NonDefaultConstructible<pAuraEffectHandler> AuraEffectHandler[TOTAL_AURAS]=
&AuraEffect::HandleNULL, //218 SPELL_AURA_ADD_PCT_MODIFIER_BY_SPELL_LABEL
&AuraEffect::HandleNULL, //219 SPELL_AURA_ADD_FLAT_MODIFIER_BY_SPELL_LABEL
&AuraEffect::HandleNULL, //220 SPELL_AURA_MOD_ABILITY_SCHOOL_MASK
- &AuraEffect::HandleNULL, //221 SPELL_AURA_MOD_DETAUNT
+ &AuraEffect::HandleModDetaunt, //221 SPELL_AURA_MOD_DETAUNT
&AuraEffect::HandleNoImmediateEffect, //222 SPELL_AURA_REMOVE_TRANSMOG_COST implemented in WorldSession::HandleTransmogrifyItems
&AuraEffect::HandleNoImmediateEffect, //223 SPELL_AURA_REMOVE_BARBER_SHOP_COST implemented in Player::GetBarberShopCost
&AuraEffect::HandleNULL, //224 SPELL_AURA_LEARN_TALENT
@@ -1975,6 +1975,8 @@ void AuraEffect::HandleAuraTransform(AuraApplication const* aurApp, uint8 mode,
}
}
}
+
+ target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModScale(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2045,13 +2047,20 @@ void AuraEffect::HandleFeignDeath(AuraApplication const* aurApp, uint8 mode, boo
}
}
}
- target->CombatStop();
+
+ if (target->GetMap()->IsDungeon()) // feign death does not remove combat in dungeons
+ {
+ target->AttackStop();
+ if (Player* targetPlayer = target->ToPlayer())
+ targetPlayer->SendAttackSwingCancelAttack();
+ }
+ else
+ target->CombatStop(false, false);
// prevent interrupt message
if (GetCasterGUID() == target->GetGUID() && target->GetCurrentSpell(CURRENT_GENERIC_SPELL))
target->FinishSpell(CURRENT_GENERIC_SPELL, false);
target->InterruptNonMeleeSpells(true);
- target->getHostileRefManager().deleteReferences();
// stop handling the effect if it was removed by linked event
if (aurApp->GetRemoveMode())
@@ -2075,6 +2084,7 @@ void AuraEffect::HandleFeignDeath(AuraApplication const* aurApp, uint8 mode, boo
if (Creature* creature = target->ToCreature())
creature->InitializeReactState();
}
+ target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleModUnattackable(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2095,7 +2105,16 @@ void AuraEffect::HandleModUnattackable(AuraApplication const* aurApp, uint8 mode
// call functions which may have additional effects after chainging state of unit
if (apply && (mode & AURA_EFFECT_HANDLE_REAL))
- target->CombatStop();
+ {
+ if (target->GetMap()->IsDungeon())
+ {
+ target->AttackStop();
+ if (Player* targetPlayer = target->ToPlayer())
+ targetPlayer->SendAttackSwingCancelAttack();
+ }
+ else
+ target->CombatStop();
+ }
}
void AuraEffect::HandleAuraModDisarm(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2655,28 +2674,15 @@ void AuraEffect::HandleIgnoreMovementForces(AuraApplication const* aurApp, uint8
/*** THREAT ***/
/****************************/
-void AuraEffect::HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const
+void AuraEffect::HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK))
return;
- Unit* target = aurApp->GetTarget();
- for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
- {
- if (GetMiscValue() & (1 << i))
- {
- if (apply)
- AddPct(target->m_threatModifier[i], GetAmount());
- else
- {
- float amount = target->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, 1 << i);
- target->m_threatModifier[i] = amount;
- }
- }
- }
+ aurApp->GetTarget()->GetThreatManager().UpdateMySpellSchoolModifiers();
}
-void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const
+void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK))
return;
@@ -2688,10 +2694,10 @@ void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 m
Unit* caster = GetCaster();
if (caster && caster->IsAlive())
- target->getHostileRefManager().addTempThreat((float)GetAmount(), apply);
+ caster->GetThreatManager().UpdateMyTempModifiers();
}
-void AuraEffect::HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool apply) const
+void AuraEffect::HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & AURA_EFFECT_HANDLE_REAL))
return;
@@ -2701,17 +2707,21 @@ void AuraEffect::HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool
if (!target->IsAlive() || !target->CanHaveThreatList())
return;
+ target->GetThreatManager().TauntUpdate();
+}
+
+void AuraEffect::HandleModDetaunt(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
+{
+ if (!(mode & AURA_EFFECT_HANDLE_REAL))
+ return;
+
Unit* caster = GetCaster();
- if (!caster || !caster->IsAlive())
+ Unit* target = aurApp->GetTarget();
+
+ if (!caster || !caster->IsAlive() || !target->IsAlive() || !caster->CanHaveThreatList())
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);
- }
+ caster->GetThreatManager().TauntUpdate();
}
/*****************************/
@@ -2726,6 +2736,7 @@ void AuraEffect::HandleModConfuse(AuraApplication const* aurApp, uint8 mode, boo
Unit* target = aurApp->GetTarget();
target->SetControlled(apply, UNIT_STATE_CONFUSED);
+ target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleModFear(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2736,6 +2747,7 @@ void AuraEffect::HandleModFear(AuraApplication const* aurApp, uint8 mode, bool a
Unit* target = aurApp->GetTarget();
target->SetControlled(apply, UNIT_STATE_FLEEING);
+ target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModStun(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2746,6 +2758,7 @@ void AuraEffect::HandleAuraModStun(AuraApplication const* aurApp, uint8 mode, bo
Unit* target = aurApp->GetTarget();
target->SetControlled(apply, UNIT_STATE_STUNNED);
+ target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModRoot(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2756,6 +2769,7 @@ void AuraEffect::HandleAuraModRoot(AuraApplication const* aurApp, uint8 mode, bo
Unit* target = aurApp->GetTarget();
target->SetControlled(apply, UNIT_STATE_ROOT);
+ target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandlePreventFleeing(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -3136,6 +3150,8 @@ void AuraEffect::HandleAuraModSchoolImmunity(AuraApplication const* aurApp, uint
&& GetSpellInfo()->HasAttribute(SPELL_ATTR2_DAMAGE_REDUCED_SHIELD))
target->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::StealthOrInvis);
}
+
+ target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -3145,6 +3161,8 @@ void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 m
Unit* target = aurApp->GetTarget();
m_spellInfo->ApplyAllSpellImmunitiesTo(target, GetSpellEffectInfo(), apply);
+
+ target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModDispelImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -4771,7 +4789,7 @@ void AuraEffect::HandleForceReaction(AuraApplication const* aurApp, uint8 mode,
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 >= REP_FRIENDLY) || (!apply && player->GetReputationRank(factionId) >= REP_FRIENDLY))
player->StopAttackFaction(factionId);
}
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.h b/src/server/game/Spells/Auras/SpellAuraEffects.h
index 8f5f1eb2efb..ff6cba63688 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.h
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.h
@@ -193,6 +193,7 @@ class TC_GAME_API AuraEffect
void HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool apply) const;
+ void HandleModDetaunt(AuraApplication const* aurApp, uint8 mode, bool apply) const;
// control
void HandleModConfuse(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleModFear(AuraApplication const* aurApp, uint8 mode, bool apply) const;
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 54cf9337114..8b0733a4b92 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -2560,7 +2560,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
// spellHitTarget can be null if spell is missed in DoSpellHitOnUnit
if (missInfo != SPELL_MISS_EVADE && spellHitTarget && !m_caster->IsFriendlyTo(unit) && (!IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)))
{
- m_caster->CombatStart(unit, m_spellInfo->HasInitialAggro());
+ m_caster->AttackedTarget(unit, m_spellInfo->HasInitialAggro());
if (!unit->IsStandState())
unit->SetStandState(UNIT_STAND_STATE_STAND);
@@ -2655,7 +2655,8 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask)
}
if (unit->IsInCombat() && m_spellInfo->HasInitialAggro())
{
- m_caster->SetInCombatState(unit->GetCombatTimer() > 0, unit);
+ if (m_caster->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)) // 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, nullptr, true);
}
}
@@ -7459,6 +7460,10 @@ void Spell::DoAllEffectOnLaunchTarget(TargetInfo& targetInfo, float* multiplier)
if (!unit)
return;
+ // This will only cause combat - the target will engage once the projectile hits (in DoAllEffectOnTarget)
+ if (targetInfo.missCondition != SPELL_MISS_EVADE && !m_caster->IsFriendlyTo(unit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (m_spellInfo->HasInitialAggro() || unit->IsEngaged()))
+ m_caster->SetInCombatWith(unit);
+
for (SpellEffectInfo const* effect : m_spellInfo->GetEffects())
{
if (effect && (targetInfo.effectMask & (1<<effect->EffectIndex)))
@@ -8043,7 +8048,7 @@ bool WorldObjectSpellTargetCheck::operator()(WorldObject* target)
return false;
break;
case TARGET_CHECK_THREAT:
- if (_referer->GetThreatManager().getThreat(unitTarget, true) <= 0.0f)
+ if (_referer->GetThreatManager().GetThreat(unitTarget, true) <= 0.0f)
return false;
break;
case TARGET_CHECK_TAP:
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index d44e84529c4..a0a1b6fb26b 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -1090,7 +1090,7 @@ void Spell::EffectPowerDrain(SpellEffIndex effIndex)
int32 gain = int32(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, newDamage, gainMultiplier);
}
@@ -1522,7 +1522,7 @@ void Spell::EffectEnergize(SpellEffIndex /*effIndex*/)
break;
}
- m_caster->EnergizeBySpell(unitTarget, m_spellInfo->Id, damage, power);
+ m_caster->EnergizeBySpell(unitTarget, m_spellInfo, damage, power);
}
void Spell::EffectEnergizePct(SpellEffIndex /*effIndex*/)
@@ -1544,7 +1544,7 @@ void Spell::EffectEnergizePct(SpellEffIndex /*effIndex*/)
return;
uint32 gain = CalculatePct(maxPower, damage);
- m_caster->EnergizeBySpell(unitTarget, m_spellInfo->Id, gain, power);
+ m_caster->EnergizeBySpell(unitTarget, m_spellInfo, gain, power);
}
void Spell::SendLoot(ObjectGuid guid, LootType loottype)
@@ -2700,27 +2700,22 @@ void Spell::EffectTaunt(SpellEffIndex /*effIndex*/)
// 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 || !unitTarget->CanHaveThreatList() || unitTarget->GetVictim() == m_caster)
+ if (!unitTarget || !unitTarget->CanHaveThreatList())
{
SendCastResult(SPELL_FAILED_DONT_REPORT);
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().doAddThreat(m_caster, topThreat - myThreat);
-
- //Set aggro victim to caster
- if (HostileReference* forcedVictim = unitTarget->GetThreatManager().getOnlineContainer().getReferenceByTarget(m_caster))
- unitTarget->GetThreatManager().setCurrentVictim(forcedVictim);
+ SendCastResult(SPELL_FAILED_DONT_REPORT);
+ return;
}
- if (unitTarget->ToCreature()->IsAIEnabled && !unitTarget->ToCreature()->HasReactState(REACT_PASSIVE))
- unitTarget->ToCreature()->AI()->AttackStart(m_caster);
+ if (!mgr.IsThreatListEmpty())
+ // Set threat equal to highest threat currently on target
+ mgr.MatchUnitThreatToHighestThreat(m_caster);
}
void Spell::EffectWeaponDmg(SpellEffIndex effIndex)
@@ -2920,13 +2915,13 @@ void Spell::EffectThreat(SpellEffIndex /*effIndex*/)
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
return;
- if (!unitTarget || !unitTarget->IsAlive() || !m_caster->IsAlive())
+ if (!unitTarget || !m_caster->IsAlive())
return;
if (!unitTarget->CanHaveThreatList())
return;
- unitTarget->GetThreatManager().AddThreat(m_caster, float(damage));
+ unitTarget->GetThreatManager().AddThreat(m_caster, float(damage), m_spellInfo, true);
}
void Spell::EffectHealMaxHealth(SpellEffIndex /*effIndex*/)
@@ -3456,20 +3451,19 @@ void Spell::EffectSanctuary(SpellEffIndex /*effIndex*/)
if (!unitTarget)
return;
- if (unitTarget->GetTypeId() == TYPEID_PLAYER)
- unitTarget->ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
-
- unitTarget->getHostileRefManager().UpdateVisibility();
-
- Unit::AttackerSet const& attackers = unitTarget->getAttackers();
- for (Unit::AttackerSet::const_iterator itr = attackers.begin(); itr != attackers.end();)
+ if (unitTarget->GetTypeId() == TYPEID_PLAYER && !unitTarget->GetMap()->IsDungeon())
{
- if (!(*itr)->CanSeeOrDetect(unitTarget))
- (*(itr++))->AttackStop();
- else
- ++itr;
+ // 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
+ for (auto const& pair : unitTarget->GetThreatManager().GetThreatenedByMeList())
+ pair.second->SetThreat(0.0f);
}
+ // makes spells cast before this time fizzle
unitTarget->m_lastSanctuaryTime = GameTime::GetGameTimeMS();
}
@@ -5015,7 +5009,7 @@ void Spell::EffectRedirectThreat(SpellEffIndex /*effIndex*/)
return;
if (unitTarget)
- m_caster->SetRedirectThreat(unitTarget->GetGUID(), uint32(damage));
+ m_caster->GetThreatManager().RegisterRedirectThreat(m_spellInfo->Id, unitTarget->GetGUID(), uint32(damage));
}
void Spell::EffectGameObjectDamage(SpellEffIndex /*effIndex*/)
diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp
index de90ff466e5..88a598204a1 100644
--- a/src/server/game/Spells/SpellInfo.cpp
+++ b/src/server/game/Spells/SpellInfo.cpp
@@ -2106,8 +2106,8 @@ SpellCastResult SpellInfo::CheckTarget(Unit const* caster, WorldObject const* ta
// creature/player specific target checks
if (unitTarget)
{
- // spells cannot be cast if player is in fake combat also
- if (HasAttribute(SPELL_ATTR1_CANT_TARGET_IN_COMBAT) && (unitTarget->IsInCombat() || unitTarget->IsPetInCombat()))
+ // spells cannot be cast if target has a pet in combat either
+ if (HasAttribute(SPELL_ATTR1_CANT_TARGET_IN_COMBAT) && (unitTarget->IsInCombat() || unitTarget->HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT)))
return SPELL_FAILED_TARGET_AFFECTING_COMBAT;
// only spells with SPELL_ATTR3_ONLY_TARGET_GHOSTS can target ghosts
diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp
index 15372bc0882..b46b8cfd978 100644
--- a/src/server/scripts/Commands/cs_debug.cpp
+++ b/src/server/scripts/Commands/cs_debug.cpp
@@ -846,23 +846,73 @@ public:
static bool HandleDebugThreatListCommand(ChatHandler* handler, char const* /*args*/)
{
- Creature* target = handler->getSelectedCreature();
- if (!target || target->IsTotem() || target->IsPet())
- return false;
+ Unit* target = handler->getSelectedUnit();
+ if (!target)
+ target = handler->GetSession()->GetPlayer();
+
+ ThreatManager& mgr = target->GetThreatManager();
+ if (!target->IsAlive())
+ {
+ handler->PSendSysMessage("%s (%s) is not alive.", target->GetName().c_str(), target->GetGUID().ToString().c_str());
+ return true;
+ }
+ if (!target->CanHaveThreatList())
+ handler->PSendSysMessage("%s (%s) cannot have a threat list.", target->GetName().c_str(), target->GetGUID().ToString().c_str());
- ThreatContainer::StorageType const& threatList = target->GetThreatManager().getThreatList();
- ThreatContainer::StorageType::const_iterator itr;
uint32 count = 0;
- handler->PSendSysMessage("Threat list of %s (%s)", target->GetName().c_str(), target->GetGUID().ToString().c_str());
- for (itr = threatList.begin(); itr != threatList.end(); ++itr)
+ auto const& threatenedByMe = target->GetThreatManager().GetThreatenedByMeList();
+ if (threatenedByMe.empty())
+ handler->PSendSysMessage("%s (%s) does not threaten any units.", target->GetName().c_str(), target->GetGUID().ToString().c_str());
+ else
{
- Unit* unit = (*itr)->getTarget();
- if (!unit)
- continue;
- ++count;
- handler->PSendSysMessage(" %u. %s (%s) - threat %f", count, unit->GetName().c_str(), unit->GetGUID().ToString().c_str(), (*itr)->getThreat());
+ handler->PSendSysMessage("List of units threatened by %s (%s)", target->GetName().c_str(), target->GetGUID().ToString().c_str());
+ for (auto const& pair : threatenedByMe)
+ {
+ Unit* unit = pair.second->GetOwner();
+ handler->PSendSysMessage(" %u. %s (%s, SpawnID %u) - threat %f", ++count, unit->GetName().c_str(), unit->GetGUID().ToString().c_str(), unit->GetTypeId() == TYPEID_UNIT ? unit->ToCreature()->GetSpawnId() : 0, pair.second->GetThreat());
+ }
+ handler->SendSysMessage("End of threatened-by-me list.");
}
- handler->SendSysMessage("End of threat list.");
+
+ if (!mgr.CanHaveThreatList())
+ return true;
+ if (mgr.IsEngaged())
+ {
+ count = 0;
+ handler->PSendSysMessage("Threat list of %s (%s, SpawnID %u)", target->GetName().c_str(), target->GetGUID().ToString().c_str(), target->GetTypeId() == TYPEID_UNIT ? target->ToCreature()->GetSpawnId() : 0);
+ for (ThreatReference const* ref : mgr.GetSortedThreatList())
+ {
+ Unit* unit = ref->GetVictim();
+ char const* onlineStr;
+ switch (ref->GetOnlineState())
+ {
+ case ThreatReference::ONLINE_STATE_SUPPRESSED:
+ onlineStr = " [SUPPRESSED]";
+ break;
+ case ThreatReference::ONLINE_STATE_OFFLINE:
+ onlineStr = " [OFFLINE]";
+ break;
+ default:
+ onlineStr = "";
+ }
+ char const* tauntStr;
+ switch (ref->GetTauntState())
+ {
+ case ThreatReference::TAUNT_STATE_TAUNT:
+ tauntStr = " [TAUNT]";
+ break;
+ case ThreatReference::TAUNT_STATE_DETAUNT:
+ tauntStr = " [DETAUNT]";
+ break;
+ default:
+ tauntStr = "";
+ }
+ handler->PSendSysMessage(" %u. %s (%s) - threat %f%s%s", ++count, unit->GetName().c_str(), unit->GetGUID().ToString().c_str(), ref->GetThreat(), tauntStr, onlineStr);
+ }
+ handler->SendSysMessage("End of threat list.");
+ }
+ else
+ handler->PSendSysMessage("%s (%s, SpawnID %u) is not currently engaged.", target->GetName().c_str(), target->GetGUID().ToString().c_str(), target->GetTypeId() == TYPEID_UNIT ? target->ToCreature()->GetSpawnId() : 0);
return true;
}
@@ -871,19 +921,18 @@ public:
Unit* target = handler->getSelectedUnit();
if (!target)
target = handler->GetSession()->GetPlayer();
- HostileReference* ref = target->getHostileRefManager().getFirst();
- uint32 count = 0;
- handler->PSendSysMessage("Hostil reference list of %s (%s)", target->GetName().c_str(), target->GetGUID().ToString().c_str());
- while (ref)
+
+ handler->PSendSysMessage("Combat refs: (Combat state: %d | Manager state: %d)", target->IsInCombat(), target->GetCombatManager().HasCombat());
+ for (auto const& ref : target->GetCombatManager().GetPvPCombatRefs())
{
- if (Unit* unit = ref->GetSource()->GetOwner())
- {
- ++count;
- handler->PSendSysMessage(" %u. %s (%s, SpawnId: %s) - threat %f", count, unit->GetName().c_str(), unit->GetGUID().ToString().c_str(), unit->GetTypeId() == TYPEID_UNIT ? std::to_string(unit->ToCreature()->GetSpawnId()).c_str() : "0", ref->getThreat());
- }
- ref = ref->next();
+ Unit* unit = ref.second->GetOther(target);
+ handler->PSendSysMessage("[PvP] %s (SpawnID %u)", unit->GetName().c_str(), unit->GetTypeId() == TYPEID_UNIT ? unit->ToCreature()->GetSpawnId() : 0);
+ }
+ for (auto const& ref : target->GetCombatManager().GetPvECombatRefs())
+ {
+ Unit* unit = ref.second->GetOther(target);
+ handler->PSendSysMessage("[PvE] %s (SpawnID %u)", unit->GetName().c_str(), unit->GetTypeId() == TYPEID_UNIT ? unit->ToCreature()->GetSpawnId() : 0);
}
- handler->SendSysMessage("End of hostil reference list.");
return true;
}
diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp
index 0b3547670bc..c8fe798fcaf 100644
--- a/src/server/scripts/Commands/cs_misc.cpp
+++ b/src/server/scripts/Commands/cs_misc.cpp
@@ -2491,7 +2491,6 @@ public:
return false;
target->CombatStop();
- target->getHostileRefManager().deleteReferences();
return true;
}
diff --git a/src/server/scripts/EasternKingdoms/BaradinHold/boss_occuthar.cpp b/src/server/scripts/EasternKingdoms/BaradinHold/boss_occuthar.cpp
index 438230d622a..f2c8ef442ea 100644
--- a/src/server/scripts/EasternKingdoms/BaradinHold/boss_occuthar.cpp
+++ b/src/server/scripts/EasternKingdoms/BaradinHold/boss_occuthar.cpp
@@ -212,7 +212,7 @@ class FocusedFireTargetSelector : public std::unary_function<Unit *, bool>
bool operator() (WorldObject* target)
{
- if (target == _victim && _me->GetThreatManager().getThreatList().size() > 1)
+ if (target == _victim && _me->GetThreatManager().GetThreatListSize() > 1)
return true;
if (target->GetTypeId() != TYPEID_PLAYER)
diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp
index 0338a3ad73f..6e0d047514e 100644
--- a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp
+++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp
@@ -198,30 +198,21 @@ public:
if (!summonedUnit)
return;
- ThreatContainer::StorageType const& threatlist = me->GetThreatManager().getThreatList();
- ThreatContainer::StorageType::const_iterator i = threatlist.begin();
- for (i = threatlist.begin(); i != threatlist.end(); ++i)
+ for (auto* ref : me->GetThreatManager().GetUnsortedThreatList())
{
- Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
+ Unit* unit = ref->GetVictim();
if (unit && unit->IsAlive())
- {
- float threat = me->GetThreatManager().getThreat(unit);
- AddThreat(unit, threat, summonedUnit);
- }
+ AddThreat(unit, ref->GetThreat(), summonedUnit);
}
}
void TeleportPlayersToSelf()
{
- float x = KaelLocations[0][0];
- float y = KaelLocations[0][1];
- me->UpdatePosition(x, y, LOCATION_Z, 0.0f);
- ThreatContainer::StorageType threatlist = me->GetThreatManager().getThreatList();
- ThreatContainer::StorageType::const_iterator i = threatlist.begin();
- for (i = threatlist.begin(); i != threatlist.end(); ++i)
+ me->UpdatePosition(KaelLocations[0][0], KaelLocations[0][1], LOCATION_Z, 0.0f);
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
- Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
- if (unit && (unit->GetTypeId() == TYPEID_PLAYER))
+ Unit* unit = pair.second->GetOther(me);
+ if (unit->GetTypeId() == TYPEID_PLAYER)
unit->CastSpell(unit, SPELL_TELEPORT_CENTER, true);
}
DoCast(me, SPELL_TELEPORT_CENTER, true);
@@ -229,14 +220,11 @@ public:
void CastGravityLapseKnockUp()
{
- ThreatContainer::StorageType threatlist = me->GetThreatManager().getThreatList();
- ThreatContainer::StorageType::const_iterator i = threatlist.begin();
- for (i = threatlist.begin(); i != threatlist.end(); ++i)
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
- Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
- if (unit && (unit->GetTypeId() == TYPEID_PLAYER))
+ Unit* unit = pair.second->GetOther(me);
+ if (unit->GetTypeId() == TYPEID_PLAYER)
{
- // Knockback into the air
CastSpellExtraArgs args;
args.TriggerFlags = TRIGGERED_FULL_MASK;
args.OriginalCaster = me->GetGUID();
@@ -247,12 +235,10 @@ public:
void CastGravityLapseFly() // Use Fly Packet hack for now as players can't cast "fly" spells unless in map 530. Has to be done a while after they get knocked into the air...
{
- ThreatContainer::StorageType threatlist = me->GetThreatManager().getThreatList();
- ThreatContainer::StorageType::const_iterator i = threatlist.begin();
- for (i = threatlist.begin(); i != threatlist.end(); ++i)
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
- Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
- if (unit && (unit->GetTypeId() == TYPEID_PLAYER))
+ Unit* unit = pair.second->GetOther(me);
+ if (unit->GetTypeId() == TYPEID_PLAYER)
{
// Also needs an exception in spell system.
CastSpellExtraArgs args;
@@ -266,12 +252,10 @@ public:
void RemoveGravityLapse()
{
- ThreatContainer::StorageType threatlist = me->GetThreatManager().getThreatList();
- ThreatContainer::StorageType::const_iterator i = threatlist.begin();
- for (i = threatlist.begin(); i != threatlist.end(); ++i)
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
- Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
- if (unit && (unit->GetTypeId() == TYPEID_PLAYER))
+ Unit* unit = pair.second->GetOther(me);
+ if (unit->GetTypeId() == TYPEID_PLAYER)
{
unit->RemoveAurasDueToSpell(SPELL_GRAVITY_LAPSE_FLY);
unit->RemoveAurasDueToSpell(SPELL_GRAVITY_LAPSE_DOT);
@@ -684,8 +668,8 @@ public:
{
if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true))
{
- AddThreat(target, 1.0f);
- me->TauntApply(target);
+ ResetThreatList();
+ AddThreat(target, 1000000.0f);
AttackStart(target);
}
diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_priestess_delrissa.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_priestess_delrissa.cpp
index e50740d8dac..21417362610 100644
--- a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_priestess_delrissa.cpp
+++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_priestess_delrissa.cpp
@@ -850,17 +850,12 @@ public:
if (Blink_Timer <= diff)
{
bool InMeleeRange = false;
- ThreatContainer::StorageType const& t_list = me->GetThreatManager().getThreatList();
- for (ThreatContainer::StorageType::const_iterator itr = t_list.begin(); itr!= t_list.end(); ++itr)
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
- if (Unit* target = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
+ if (pair.second->GetOther(me)->IsWithinMeleeRange(me))
{
- //if in melee range
- if (target->IsWithinDistInMap(me, 5))
- {
- InMeleeRange = true;
- break;
- }
+ InMeleeRange = true;
+ break;
}
}
@@ -944,17 +939,12 @@ public:
if (Intercept_Stun_Timer <= diff)
{
bool InMeleeRange = false;
- ThreatContainer::StorageType const& t_list = me->GetThreatManager().getThreatList();
- for (ThreatContainer::StorageType::const_iterator itr = t_list.begin(); itr!= t_list.end(); ++itr)
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
- if (Unit* target = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
+ if (pair.second->GetOther(me)->IsWithinMeleeRange(me))
{
- //if in melee range
- if (target->IsWithinDistInMap(me, ATTACK_DISTANCE))
- {
- InMeleeRange = true;
- break;
- }
+ InMeleeRange = true;
+ break;
}
}
diff --git a/src/server/scripts/EasternKingdoms/TheStockade/boss_randolph_moloch.cpp b/src/server/scripts/EasternKingdoms/TheStockade/boss_randolph_moloch.cpp
index 901a1522223..573d3c6a4d5 100644
--- a/src/server/scripts/EasternKingdoms/TheStockade/boss_randolph_moloch.cpp
+++ b/src/server/scripts/EasternKingdoms/TheStockade/boss_randolph_moloch.cpp
@@ -115,7 +115,6 @@ struct boss_randolph_moloch : public BossAI
me->RemoveAllAuras();
DoCastSelf(SPELL_VANISH);
me->SetReactState(REACT_PASSIVE);
- me->SetInCombatState(true); // Prevents the boss from resetting
events.ScheduleEvent(EVENT_JUST_VANISHED, 2s);
break;
case EVENT_JUST_VANISHED:
diff --git a/src/server/scripts/EasternKingdoms/ZulGurub/boss_mandokir.cpp b/src/server/scripts/EasternKingdoms/ZulGurub/boss_mandokir.cpp
index 5c63f224ff7..da7fff52dac 100644
--- a/src/server/scripts/EasternKingdoms/ZulGurub/boss_mandokir.cpp
+++ b/src/server/scripts/EasternKingdoms/ZulGurub/boss_mandokir.cpp
@@ -555,7 +555,7 @@ class DevastatingSlamTargetSelector : public std::unary_function<Unit *, bool>
bool operator() (WorldObject* target)
{
- if (target == _victim && _me->GetThreatManager().getThreatList().size() > 1)
+ if (target == _victim && _me->GetThreatManager().GetThreatListSize() > 1)
return true;
if (target->GetTypeId() != TYPEID_PLAYER)
@@ -684,7 +684,7 @@ class spell_mandokir_ohgan_orders_trigger : public SpellScriptLoader
caster->GetMotionMaster()->Clear();
caster->GetThreatManager().ClearAllThreat();
caster->GetThreatManager().AddThreat(target, 50000000.0f);
- caster->TauntApply(target);
+ // TODO: Fixate mechanic
}
}
diff --git a/src/server/scripts/Kalimdor/Firelands/boss_alysrazor.cpp b/src/server/scripts/Kalimdor/Firelands/boss_alysrazor.cpp
index 6c7f00f9b32..e0198304477 100644
--- a/src/server/scripts/Kalimdor/Firelands/boss_alysrazor.cpp
+++ b/src/server/scripts/Kalimdor/Firelands/boss_alysrazor.cpp
@@ -635,7 +635,7 @@ class spell_alysrazor_aggro_closest : public SpellScriptLoader
void HandleEffect(SpellEffIndex effIndex)
{
PreventHitDefaultEffect(effIndex);
- float curThreat = GetCaster()->GetThreatManager().getThreat(GetHitUnit(), true);
+ float curThreat = GetCaster()->GetThreatManager().GetThreat(GetHitUnit(), true);
GetCaster()->GetThreatManager().AddThreat(GetHitUnit(), -curThreat + 50000.0f / std::min(1.0f, GetCaster()->GetDistance(GetHitUnit())));
}
diff --git a/src/server/scripts/Kalimdor/Firelands/firelands.cpp b/src/server/scripts/Kalimdor/Firelands/firelands.cpp
index 572e9a02f9f..6367bc88611 100644
--- a/src/server/scripts/Kalimdor/Firelands/firelands.cpp
+++ b/src/server/scripts/Kalimdor/Firelands/firelands.cpp
@@ -233,7 +233,7 @@ struct npc_firelands_magmakin : public ScriptedAI
return;
AddThreat(target, 50000000.0f);
- me->TauntApply(target);
+ // TODO: Fixate mechanic
}
void UpdateAI(uint32 /*diff*/) override
diff --git a/src/server/scripts/Maelstrom/Stonecore/boss_corborus.cpp b/src/server/scripts/Maelstrom/Stonecore/boss_corborus.cpp
index a491b553763..6824bc68385 100644
--- a/src/server/scripts/Maelstrom/Stonecore/boss_corborus.cpp
+++ b/src/server/scripts/Maelstrom/Stonecore/boss_corborus.cpp
@@ -267,13 +267,13 @@ class npc_rock_borer : public CreatureScript
{
me->SetDisableGravity(true);
me->SetReactState(REACT_PASSIVE);
- events.ScheduleEvent(EVENT_EMERGED, 1200);
- events.ScheduleEvent(EVENT_ROCK_BORE, urand(15000, 20000)); // Need sniffs for this timer
}
- void IsSummonedBy(Unit* summoner) override
+ void IsSummonedBy(Unit* /*summoner*/) override
{
- me->SetInCombatState(false, summoner);
+ events.ScheduleEvent(EVENT_EMERGED, 1200);
+ events.ScheduleEvent(EVENT_ROCK_BORE, urand(15000, 20000)); // Need sniffs for this timer
+ DoZoneInCombat();
DoCast(me, SPELL_ROCK_BORER_EMERGE);
}
diff --git a/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp b/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp
index 86632ed7e99..31d5a9a3e26 100644
--- a/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp
+++ b/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp
@@ -41,6 +41,7 @@ enum Spells
SPELL_FLAME_SPHERE_DEATH_EFFECT = 55947,
SPELL_EMBRACE_OF_THE_VAMPYR = 55959,
SPELL_VANISH = 55964,
+ SPELL_SHADOWSTEP = 55966,
NPC_FLAME_SPHERE_1 = 30106,
NPC_FLAME_SPHERE_2 = 31686,
@@ -77,9 +78,8 @@ enum Events
EVENT_CONJURE_FLAME_SPHERES = 1,
EVENT_BLOODTHIRST,
EVENT_VANISH,
- EVENT_JUST_VANISHED,
- EVENT_VANISHED,
- EVENT_FEEDING,
+ EVENT_START_FEEDING,
+ EVENT_DONE_FEEDING,
// Flame Sphere
EVENT_START_MOVE,
@@ -97,6 +97,7 @@ class boss_prince_taldaram : public CreatureScript
{
me->SetDisableGravity(true);
_embraceTakenDamage = 0;
+ _initialCheckTimer = 3000;
}
void Reset() override
@@ -140,8 +141,30 @@ class boss_prince_taldaram : public CreatureScript
void UpdateAI(uint32 diff) override
{
- if (!UpdateVictim())
- return;
+ if (_initialCheckTimer)
+ {
+ if (_initialCheckTimer <= diff)
+ {
+ CheckSpheres();
+ _initialCheckTimer = 0;
+ }
+ else
+ _initialCheckTimer -= diff;
+ }
+
+ if (me->HasAura(SPELL_VANISH))
+ {
+ if (me->GetThreatManager().IsThreatListEmpty(true))
+ {
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
+ return;
+ }
+ }
+ else
+ {
+ if (!UpdateVictim())
+ return;
+ }
events.Update(diff);
@@ -167,47 +190,29 @@ class boss_prince_taldaram : public CreatureScript
break;
case EVENT_VANISH:
{
- Map::PlayerList const& players = me->GetMap()->GetPlayers();
- uint32 targets = 0;
- for (Map::PlayerList::const_iterator i = players.begin(); i != players.end(); ++i)
- {
- Player* player = i->GetSource();
- if (player && player->IsAlive())
- ++targets;
- }
-
- if (targets > 2)
+ if (me->GetThreatManager().GetThreatListSize() > 1)
{
+ if (Unit* embraceTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true))
+ _embraceTargetGUID = embraceTarget->GetGUID();
Talk(SAY_VANISH);
DoCast(me, SPELL_VANISH);
- me->SetInCombatState(true); // Prevents the boss from resetting
events.DelayEvents(500);
- events.ScheduleEvent(EVENT_JUST_VANISHED, 500);
- if (Unit* embraceTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true))
- _embraceTargetGUID = embraceTarget->GetGUID();
+ events.ScheduleEvent(EVENT_START_FEEDING, 2000);
}
events.ScheduleEvent(EVENT_VANISH, urand(25000, 35000));
break;
}
- case EVENT_JUST_VANISHED:
+ case EVENT_START_FEEDING:
+ me->RemoveAurasDueToSpell(SPELL_VANISH);
if (Unit* embraceTarget = GetEmbraceTarget())
{
- me->GetMotionMaster()->Clear();
- me->SetSpeedRate(MOVE_WALK, 2.0f);
- me->GetMotionMaster()->MoveChase(embraceTarget);
- }
- events.ScheduleEvent(EVENT_VANISHED, 1300);
- break;
- case EVENT_VANISHED:
- if (Unit* embraceTarget = GetEmbraceTarget())
+ DoCast(embraceTarget, SPELL_SHADOWSTEP);
DoCast(embraceTarget, SPELL_EMBRACE_OF_THE_VAMPYR);
- Talk(SAY_FEED);
- me->GetMotionMaster()->Clear();
- me->SetSpeedRate(MOVE_WALK, 1.0f);
- me->GetMotionMaster()->MoveChase(me->GetVictim());
- events.ScheduleEvent(EVENT_FEEDING, 20000);
+ Talk(SAY_FEED);
+ events.ScheduleEvent(EVENT_DONE_FEEDING, 20000);
+ }
break;
- case EVENT_FEEDING:
+ case EVENT_DONE_FEEDING:
_embraceTargetGUID.Clear();
break;
default:
@@ -289,6 +294,7 @@ class boss_prince_taldaram : public CreatureScript
ObjectGuid _flameSphereTargetGUID;
ObjectGuid _embraceTargetGUID;
uint32 _embraceTakenDamage;
+ uint32 _initialCheckTimer;
};
CreatureAI* GetAI(Creature* creature) const override
diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp
index 7b3946364bb..9f99e018345 100644
--- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp
+++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp
@@ -163,13 +163,9 @@ public:
bool IsInCombatWithPlayer() const
{
- std::list<HostileReference*> const& refs = me->GetThreatManager().getThreatList();
- for (HostileReference const* hostileRef : refs)
- {
- if (Unit const* target = hostileRef->getTarget())
- if (target->IsControlledByPlayer())
- return true;
- }
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
+ if (pair.second->GetOther(me)->IsControlledByPlayer())
+ return true;
return false;
}
diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp
index dea9b160e08..7ea2afc6e84 100644
--- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp
+++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp
@@ -820,7 +820,7 @@ class npc_anubarak_spike : public CreatureScript
DoCast(who, SPELL_MARK);
me->SetSpeedRate(MOVE_RUN, 0.5f);
// make sure the Spine will really follow the one he should
- ResetThreatList();
+ me->GetThreatManager().ResetAllThreat();
me->SetInCombatWithZone();
AddThreat(who, 1000000.0f);
me->GetMotionMaster()->Clear(true);
diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp
index 1adc7eb9abf..658d4bb80a6 100644
--- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp
+++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp
@@ -672,14 +672,10 @@ struct boss_faction_championsAI : public BossAI
Unit* SelectEnemyCaster(bool /*casting*/)
{
- std::list<HostileReference*> const& tList = me->GetThreatManager().getThreatList();
- std::list<HostileReference*>::const_iterator iter;
- for (iter = tList.begin(); iter!=tList.end(); ++iter)
- {
- Unit* target = ObjectAccessor::GetUnit(*me, (*iter)->getUnitGuid());
- if (target && target->GetPowerType() == POWER_MANA)
- return target;
- }
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
+ if (Player* player = pair.second->GetOther(me)->ToPlayer())
+ if (player->GetPowerType() == POWER_MANA)
+ return player;
return nullptr;
}
diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp
index 29cb72ebb40..93fe8951d72 100644
--- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp
+++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp
@@ -1342,7 +1342,7 @@ class npc_the_lich_king_escape_hor : public CreatureScript
AttackStart(victim);
return me->GetVictim() != nullptr;
}
- else if (me->GetThreatManager().GetThreatListSize() < 2 && me->HasAura(SPELL_REMORSELESS_WINTER))
+ else if (me->GetCombatManager().GetPvECombatRefs().size() < 2 && me->HasAura(SPELL_REMORSELESS_WINTER))
{
EnterEvadeMode(EVADE_REASON_OTHER);
return false;
diff --git a/src/server/scripts/Northrend/Gundrak/boss_drakkari_colossus.cpp b/src/server/scripts/Northrend/Gundrak/boss_drakkari_colossus.cpp
index 938d3701368..b6cebf1c172 100644
--- a/src/server/scripts/Northrend/Gundrak/boss_drakkari_colossus.cpp
+++ b/src/server/scripts/Northrend/Gundrak/boss_drakkari_colossus.cpp
@@ -148,15 +148,12 @@ class boss_drakkari_colossus : public CreatureScript
if (me->GetReactState() == REACT_AGGRESSIVE)
return;
- me->SetReactState(REACT_AGGRESSIVE);
me->SetImmuneToPC(false);
+ me->SetReactState(REACT_AGGRESSIVE);
me->RemoveAura(SPELL_FREEZE_ANIM);
me->SetInCombatWithZone();
- if (me->GetVictim())
- me->GetMotionMaster()->MoveChase(me->GetVictim(), 0, 0);
-
break;
}
}
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp
index a80874a211e..01213a9009d 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp
@@ -559,25 +559,15 @@ class npc_green_dragon_combat_trigger : public CreatureScript
if (!me->IsInCombat())
return;
- // @TODO check out of bounds on all encounter creatures, evade if matched
-
- std::list<HostileReference*> const& threatList = me->GetThreatManager().getThreatList();
- if (threatList.empty())
- {
- EnterEvadeMode();
- return;
- }
-
// check evade every second tick
_evadeCheck ^= true;
if (!_evadeCheck)
return;
- // check if there is any player on threatlist, if not - evade
- for (std::list<HostileReference*>::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
- if (Unit* target = (*itr)->getTarget())
- if (target->GetTypeId() == TYPEID_PLAYER)
- return; // found any player, return
+ // check if there is any player engaged, if not - evade
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
+ if (pair.second->GetOther(me)->GetTypeId() == TYPEID_PLAYER)
+ return;
EnterEvadeMode();
}
diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp
index 8ddc4d1a7dc..8737e8a2425 100644
--- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp
+++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp
@@ -489,12 +489,11 @@ class boss_hodir : public CreatureScript
if (gettingColdInHereTimer <= diff && gettingColdInHere)
{
- std::list<HostileReference*> ThreatList = me->GetThreatManager().getThreatList();
- for (std::list<HostileReference*>::const_iterator itr = ThreatList.begin(); itr != ThreatList.end(); ++itr)
- if (Unit* target = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
+ for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
+ if (Player* target = pair.second->GetOther(me)->ToPlayer())
if (Aura* BitingColdAura = target->GetAura(SPELL_BITING_COLD_TRIGGERED))
- if ((target->GetTypeId() == TYPEID_PLAYER) && (BitingColdAura->GetStackAmount() > 2))
- SetData(DATA_GETTING_COLD_IN_HERE, 0);
+ if (BitingColdAura->GetStackAmount() > 2)
+ SetData(DATA_GETTING_COLD_IN_HERE, 0);
gettingColdInHereTimer = 1000;
}
else
diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp
index f3b6d1af83e..7dd7fe30571 100644
--- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp
+++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp
@@ -198,7 +198,7 @@ class boss_ingvar_the_plunderer : public CreatureScript
void UpdateAI(uint32 diff) override
{
- if (!events.IsInPhase(PHASE_EVENT) && !UpdateVictim())
+ if (!UpdateVictim())
return;
events.Update(diff);
@@ -231,9 +231,7 @@ class boss_ingvar_the_plunderer : public CreatureScript
ScheduleSecondPhase();
me->RemoveUnitFlag(UnitFlags(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE));
me->SetImmuneToPC(false);
- if (Unit* target = me->GetThreatManager().SelectVictim())
- AttackStart(target);
- else
+ if (!me->IsThreatened())
{
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
return;
diff --git a/src/server/scripts/Northrend/VaultOfArchavon/boss_toravon.cpp b/src/server/scripts/Northrend/VaultOfArchavon/boss_toravon.cpp
index 748e04ba3d0..2ab98e53093 100644
--- a/src/server/scripts/Northrend/VaultOfArchavon/boss_toravon.cpp
+++ b/src/server/scripts/Northrend/VaultOfArchavon/boss_toravon.cpp
@@ -184,7 +184,7 @@ class spell_toravon_random_aggro : public SpellScript
if (!caster->IsAIEnabled)
return;
- caster->GetThreatManager().resetAllAggro();
+ caster->GetThreatManager().ResetAllThreat();
if (Unit* target = caster->AI()->SelectTarget(SELECT_TARGET_RANDOM, 1))
caster->GetThreatManager().AddThreat(target, 1000000);
diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp
index 45a8e390b9c..ed8f1eb38d5 100644
--- a/src/server/scripts/Spells/spell_hunter.cpp
+++ b/src/server/scripts/Spells/spell_hunter.cpp
@@ -38,6 +38,7 @@ enum HunterSpells
SPELL_HUNTER_EXHILARATION_R2 = 231546,
SPELL_HUNTER_LONE_WOLF = 155228,
SPELL_HUNTER_MASTERS_CALL_TRIGGERED = 62305,
+ SPELL_HUNTER_MISDIRECTION = 34477,
SPELL_HUNTER_MISDIRECTION_PROC = 35079,
SPELL_HUNTER_MULTI_SHOT_FOCUS = 213363,
SPELL_HUNTER_PET_LAST_STAND_TRIGGERED = 53479,
@@ -297,12 +298,7 @@ class spell_hun_misdirection : public SpellScriptLoader
return;
if (!GetTarget()->HasAura(SPELL_HUNTER_MISDIRECTION_PROC))
- GetTarget()->ResetRedirectThreat();
- }
-
- bool CheckProc(ProcEventInfo& /*eventInfo*/)
- {
- return GetTarget()->GetRedirectThreatTarget() != nullptr;
+ GetTarget()->GetThreatManager().UnregisterRedirectThreat(SPELL_HUNTER_MISDIRECTION);
}
void HandleProc(AuraEffect* aurEff, ProcEventInfo& /*eventInfo*/)
@@ -314,7 +310,6 @@ class spell_hun_misdirection : public SpellScriptLoader
void Register() override
{
AfterEffectRemove += AuraEffectRemoveFn(spell_hun_misdirection_AuraScript::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
- DoCheckProc += AuraCheckProcFn(spell_hun_misdirection_AuraScript::CheckProc);
OnEffectProc += AuraEffectProcFn(spell_hun_misdirection_AuraScript::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
}
};
@@ -337,7 +332,7 @@ class spell_hun_misdirection_proc : public SpellScriptLoader
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
- GetTarget()->ResetRedirectThreat();
+ GetTarget()->GetThreatManager().UnregisterRedirectThreat(SPELL_HUNTER_MISDIRECTION);
}
void Register() override
diff --git a/src/server/scripts/Spells/spell_rogue.cpp b/src/server/scripts/Spells/spell_rogue.cpp
index 003b55c260e..25ba6f0006d 100644
--- a/src/server/scripts/Spells/spell_rogue.cpp
+++ b/src/server/scripts/Spells/spell_rogue.cpp
@@ -51,6 +51,7 @@ enum RogueSpells
SPELL_ROGUE_STEALTH_STEALTH_AURA = 158185,
SPELL_ROGUE_STEALTH_SHAPESHIFT_AURA = 158188,
SPELL_ROGUE_VANISH_AURA = 11327,
+ SPELL_ROGUE_TRICKS_OF_THE_TRADE = 57934,
SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC = 59628,
SPELL_ROGUE_HONOR_AMONG_THIEVES_ENERGIZE = 51699,
SPELL_ROGUE_T5_2P_SET_BONUS = 37169
@@ -507,82 +508,78 @@ class spell_rog_vanish_aura : public SpellScriptLoader
};
// 57934 - Tricks of the Trade
-class spell_rog_tricks_of_the_trade : public SpellScriptLoader
+class spell_rog_tricks_of_the_trade_aura : public AuraScript
{
- public:
- spell_rog_tricks_of_the_trade() : SpellScriptLoader("spell_rog_tricks_of_the_trade") { }
+ PrepareAuraScript(spell_rog_tricks_of_the_trade_aura);
- class spell_rog_tricks_of_the_trade_AuraScript : public AuraScript
- {
- PrepareAuraScript(spell_rog_tricks_of_the_trade_AuraScript);
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC });
+ }
- bool Validate(SpellInfo const* /*spellInfo*/) override
- {
- return ValidateSpellInfo({ SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC });
- }
+ void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEFAULT || !GetTarget()->HasAura(SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC))
+ GetTarget()->GetThreatManager().UnregisterRedirectThreat(SPELL_ROGUE_TRICKS_OF_THE_TRADE);
+ }
- void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
- {
- if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEFAULT)
- GetTarget()->ResetRedirectThreat();
- }
+ void HandleProc(AuraEffect* aurEff, ProcEventInfo& /*eventInfo*/)
+ {
+ PreventDefaultAction();
- bool CheckProc(ProcEventInfo& /*eventInfo*/)
- {
- _redirectTarget = GetTarget()->GetRedirectThreatTarget();
- return _redirectTarget != nullptr;
- }
+ Unit* rogue = GetTarget();
+ if (ObjectAccessor::GetUnit(*rogue, _redirectTarget))
+ rogue->CastSpell(rogue, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC, aurEff);
+ Remove(AURA_REMOVE_BY_DEFAULT);
+ }
- void HandleProc(AuraEffect* /*aurEff*/, ProcEventInfo& /*eventInfo*/)
- {
- PreventDefaultAction();
+ void Register() override
+ {
+ AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade_aura::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ OnEffectProc += AuraEffectProcFn(spell_rog_tricks_of_the_trade_aura::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
+ }
- Unit* target = GetTarget();
- target->CastSpell(target, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC, true);
- Remove(AURA_REMOVE_BY_DEFAULT); // maybe handle by proc charges
- }
+ ObjectGuid _redirectTarget;
+public:
+ void SetRedirectTarget(ObjectGuid guid) { _redirectTarget = guid; }
+};
- void Register() override
+class spell_rog_tricks_of_the_trade : public SpellScript
+{
+ PrepareSpellScript(spell_rog_tricks_of_the_trade);
+
+ void DoAfterHit()
+ {
+ if (Aura* aura = GetHitAura())
+ if (auto* script = aura->GetScript<spell_rog_tricks_of_the_trade_aura>("spell_rog_tricks_of_the_trade"))
{
- AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade_AuraScript::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
- DoCheckProc += AuraCheckProcFn(spell_rog_tricks_of_the_trade_AuraScript::CheckProc);
- OnEffectProc += AuraEffectProcFn(spell_rog_tricks_of_the_trade_AuraScript::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
+ if (Unit* explTarget = GetExplTargetUnit())
+ script->SetRedirectTarget(explTarget->GetGUID());
+ else
+ script->SetRedirectTarget(ObjectGuid::Empty);
}
+ }
- Unit* _redirectTarget = nullptr;
- };
-
- AuraScript* GetAuraScript() const override
- {
- return new spell_rog_tricks_of_the_trade_AuraScript();
- }
+ void Register() override
+ {
+ AfterHit += SpellHitFn(spell_rog_tricks_of_the_trade::DoAfterHit);
+ }
};
// 59628 - Tricks of the Trade (Proc)
-class spell_rog_tricks_of_the_trade_proc : public SpellScriptLoader
+class spell_rog_tricks_of_the_trade_proc : public AuraScript
{
- public:
- spell_rog_tricks_of_the_trade_proc() : SpellScriptLoader("spell_rog_tricks_of_the_trade_proc") { }
+ PrepareAuraScript(spell_rog_tricks_of_the_trade_proc);
- class spell_rog_tricks_of_the_trade_proc_AuraScript : public AuraScript
- {
- PrepareAuraScript(spell_rog_tricks_of_the_trade_proc_AuraScript);
-
- void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
- {
- GetTarget()->ResetRedirectThreat();
- }
-
- void Register() override
- {
- AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade_proc_AuraScript::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
- }
- };
+ void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ GetTarget()->GetThreatManager().UnregisterRedirectThreat(SPELL_ROGUE_TRICKS_OF_THE_TRADE);
+ }
- AuraScript* GetAuraScript() const override
- {
- return new spell_rog_tricks_of_the_trade_proc_AuraScript();
- }
+ void Register() override
+ {
+ AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade_proc::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ }
};
// 198031 - Honor Among Thieves
@@ -704,8 +701,8 @@ void AddSC_rogue_spell_scripts()
new spell_rog_stealth();
new spell_rog_vanish();
new spell_rog_vanish_aura();
- new spell_rog_tricks_of_the_trade();
- new spell_rog_tricks_of_the_trade_proc();
+ RegisterSpellAndAuraScriptPair(spell_rog_tricks_of_the_trade, spell_rog_tricks_of_the_trade_aura);
+ RegisterAuraScript(spell_rog_tricks_of_the_trade_proc);
new spell_rog_honor_among_thieves();
new spell_rog_eviscerate();
new spell_rog_envenom();
diff --git a/src/server/scripts/Spells/spell_warlock.cpp b/src/server/scripts/Spells/spell_warlock.cpp
index 163c867d71d..1740f3291f9 100644
--- a/src/server/scripts/Spells/spell_warlock.cpp
+++ b/src/server/scripts/Spells/spell_warlock.cpp
@@ -51,7 +51,7 @@ enum WarlockSpells
SPELL_WARLOCK_RAIN_OF_FIRE_DAMAGE = 42223,
SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE = 27285,
SPELL_WARLOCK_SEED_OF_CORRUPTION_GENERIC = 32865,
- SPELL_WARLOCK_SOULSHATTER = 32835,
+ SPELL_WARLOCK_SOULSHATTER_EFFECT = 32835,
SPELL_WARLOCK_SOUL_SWAP_CD_MARKER = 94229,
SPELL_WARLOCK_SOUL_SWAP_OVERRIDE = 86211,
SPELL_WARLOCK_SOUL_SWAP_MOD_COST = 92794,
@@ -815,15 +815,15 @@ class spell_warl_soulshatter : public SpellScriptLoader
bool Validate(SpellInfo const* /*spellInfo*/) override
{
- return ValidateSpellInfo({ SPELL_WARLOCK_SOULSHATTER });
+ return ValidateSpellInfo({ SPELL_WARLOCK_SOULSHATTER_EFFECT });
}
void HandleDummy(SpellEffIndex /*effIndex*/)
{
Unit* caster = GetCaster();
if (Unit* target = GetHitUnit())
- if (target->CanHaveThreatList() && target->GetThreatManager().IsThreatenedBy(caster, true))
- caster->CastSpell(target, SPELL_WARLOCK_SOULSHATTER, true);
+ if (target->GetThreatManager().IsThreatenedBy(caster, true))
+ caster->CastSpell(target, SPELL_WARLOCK_SOULSHATTER_EFFECT, true);
}
void Register() override
diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp
index 3c8dac9d8de..0591f7c4a76 100644
--- a/src/server/scripts/World/npcs_special.cpp
+++ b/src/server/scripts/World/npcs_special.cpp
@@ -1806,21 +1806,28 @@ public:
{
case EVENT_TD_CHECK_COMBAT:
{
- time_t now = GameTime::GetGameTime();
- for (std::unordered_map<ObjectGuid, time_t>::iterator itr = _damageTimes.begin(); itr != _damageTimes.end();)
+ time_t const now = GameTime::GetGameTime();
+ auto const& pveRefs = me->GetCombatManager().GetPvECombatRefs();
+ for (auto itr = _damageTimes.begin(); itr != _damageTimes.end();)
{
// If unit has not dealt damage to training dummy for 5 seconds, remove him from combat
if (itr->second < now - 5)
{
- if (Unit* unit = ObjectAccessor::GetUnit(*me, itr->first))
- unit->getHostileRefManager().deleteReference(me);
+ auto it = pveRefs.find(itr->first);
+ if (it != pveRefs.end())
+ it->second->EndCombat();
itr = _damageTimes.erase(itr);
}
else
++itr;
}
- _events.ScheduleEvent(EVENT_TD_CHECK_COMBAT, 1000);
+
+ for (auto const& pair : pveRefs)
+ if (_damageTimes.find(pair.first) == _damageTimes.end())
+ _damageTimes[pair.first] = now;
+
+ _events.Repeat(1s);
break;
}
case EVENT_TD_DESPAWN: