diff options
53 files changed, 2024 insertions, 2189 deletions
diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index cca80ce5a3a..21ab166a236 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->RemoveFlag(UNIT_FIELD_FLAGS, 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->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); - - me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); - + me->SetFlag(UNIT_FIELD_FLAGS, 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/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 93675fe1692..d8fcb6c95c5 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -63,61 +63,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 @@ -245,11 +233,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/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index 6f0c378f61f..d3aba9f9a04 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -520,11 +520,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 bd3e33307b6..1aeb6a65db7 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -186,7 +186,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 a43e756385a..f1e94fba7e0 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -2179,6 +2179,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 0b79991f7a6..33eaac77962 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -797,11 +797,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(); - } uint32 winner_kills = player->GetRandomWinner() ? sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_HONOR_LAST) : sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_HONOR_FIRST); uint32 loser_kills = player->GetRandomWinner() ? sWorld->getIntConfig(CONFIG_BG_REWARD_LOSER_HONOR_LAST) : sWorld->getIntConfig(CONFIG_BG_REWARD_LOSER_HONOR_FIRST); diff --git a/src/server/game/Combat/CombatManager.cpp b/src/server/game/Combat/CombatManager.cpp new file mode 100644 index 00000000000..0ea271e4016 --- /dev/null +++ b/src/server/game/Combat/CombatManager.cpp @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2008-2018 TrinityCore <http://www.trinitycore.org/> + * + * 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 "Unit.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->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) || + (_owner->IsImmuneToNPC() && !target->HasFlag(UNIT_FIELD_FLAGS, 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->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) || + (_owner->IsImmuneToNPC() && !target->HasFlag(UNIT_FIELD_FLAGS, 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->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT); + _owner->AtEnterCombat(); + } + else + { + _owner->RemoveFlag(UNIT_FIELD_FLAGS, 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..f153c5a65c2 --- /dev/null +++ b/src/server/game/Combat/CombatManager.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2008-2018 TrinityCore <http://www.trinitycore.org/> + * + * 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 eea3e0f8d18..00000000000 --- a/src/server/game/Combat/HostileRefManager.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - * - * 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 "ThreatManager.h" -#include "Unit.h" -#include "DBCStructure.h" -#include "SpellInfo.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 585d96b7443..00000000000 --- a/src/server/game/Combat/HostileRefManager.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - * - * 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 9a6f6093154..ee1fe5683e7 100644 --- a/src/server/game/Combat/ThreatManager.cpp +++ b/src/server/game/Combat/ThreatManager.cpp @@ -1,6 +1,5 @@ /* * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> * * 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 @@ -16,614 +15,722 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "ThreatManager.h" -#include "Unit.h" #include "Creature.h" +#include "CreatureAI.h" +#include "MotionMaster.h" #include "Player.h" -#include "ObjectAccessor.h" -#include "UnitEvents.h" -#include "SpellAuras.h" +#include "ThreatManager.h" +#include "Unit.h" +#include "UnitAI.h" +#include "SpellAuraEffects.h" +#include "SpellInfo.h" #include "SpellMgr.h" +#include "ObjectAccessor.h" +#include "WorldPacket.h" +#include <algorithm> -//============================================================== -//================= ThreatCalcHelper =========================== -//============================================================== +const CompareThreatLessThan ThreatManager::CompareThreat; -// The hatingUnit is not used yet -float ThreatCalcHelper::calcThreat(Unit* hatedUnit, Unit* /*hatingUnit*/, float threat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell /*= nullptr*/) +void ThreatReference::AddThreat(float amount) { - 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 (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - if (threatSpell->Effects[i].Effect == SPELL_EFFECT_ENERGIZE || threatSpell->Effects[i].ApplyAuraName == SPELL_AURA_PERIODIC_ENERGIZE) - return threat; - - if (Player* modOwner = hatedUnit->GetSpellModOwner()) - modOwner->ApplySpellMod(threatSpell->Id, SPELLMOD_THREAT, threat); - } - - return hatedUnit->ApplyTotalThreatModifier(threat, schoolMask); + 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->InSamePhase(hatingUnit)) - return false; - - // spell not causing threat - if (threatSpell && threatSpell->HasAttribute(SPELL_ATTR1_NO_THREAT)) - return false; - - ASSERT(hatingUnit->GetTypeId() == TYPEID_UNIT); - - return true; + if (factor == 1.0f) + return; + _baseAmount *= factor; + if (factor > 1.0f) + HeapNotifyIncreased(); + else + HeapNotifyDecreased(); } -//============================================================ -//================= HostileReference ========================== -//============================================================ - -HostileReference::HostileReference(Unit* refUnit, ThreatManager* threatManager, float threat) +void ThreatReference::UpdateOnlineState() { - iThreat = threat; - iTempThreatModifier = 0.0f; - link(refUnit, threatManager); - iUnitGuid = refUnit->GetGUID(); - iOnline = true; - iAccessible = true; + OnlineState onlineState = SelectOnlineState(); + if (onlineState == _online) + return; + bool increase = (onlineState > _online); + _online = onlineState; + if (increase) + HeapNotifyIncreased(); + else + HeapNotifyDecreased(); + + if (!IsAvailable()) + _owner->GetThreatManager().SendRemoveToClients(_victim); } -//============================================================ -// Tell our refTo (target) object that we have a link -void HostileReference::targetObjectBuildLink() +/*static*/ bool ThreatReference::FlagsAllowFighting(Unit const* a, Unit const* b) { - getTarget()->addHatedBy(this); + if (a->GetTypeId() == TYPEID_UNIT && a->ToCreature()->IsTrigger()) + return false; + if (a->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) + { + if (b->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC)) + return false; + } + else + { + if (b->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC)) + return false; + } + return true; } -//============================================================ -// Tell our refTo (taget) object, that the link is cut -void HostileReference::targetObjectDestroyLink() +ThreatReference::OnlineState ThreatReference::SelectOnlineState() { - getTarget()->removeHatedBy(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 refFrom (source) object, that the link is cut (Target destroyed) - -void HostileReference::sourceObjectDestroyLink() +void ThreatReference::UpdateTauntState(bool victimIsTaunting) { - setOnlineOfflineState(false); -} + if (victimIsTaunting) + { + _taunted = TAUNT_STATE_TAUNT; + HeapNotifyIncreased(); + return; + } + + // 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; + } -//============================================================ -// Inform the source, that the status of the reference changed + _taunted = TAUNT_STATE_NONE; + HeapNotifyChanged(); +} -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()->InSamePhase(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; + MovementGeneratorType const movetype = c->GetMotionMaster()->GetCurrentMovementGeneratorType(); + if (movetype == WAYPOINT_MOTION_TYPE || movetype == POINT_MOTION_TYPE || (c->IsAIEnabled && c->AI()->IsEscorted())) + c->SetHomePosition(c->GetPosition()); +} - ThreatContainer::StorageType::const_iterator lastRef = iThreatList.end(); - --lastRef; +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) + { + if (spell->HasAttribute(SPELL_ATTR1_NO_THREAT)) + return; + if (!_owner->IsEngaged() && spell->HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO)) + return; + } - for (ThreatContainer::StorageType::const_iterator iter = iThreatList.begin(); iter != iThreatList.end() && !found;) + // while riding a vehicle, all threat goes to the vehicle, not the pilot + if (Unit* vehicle = target->GetVehicleBase()) { - currentRef = (*iter); + 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; + } - Unit* target = currentRef->getTarget(); - ASSERT(target); // if the ref has status online the target must be there ! + // 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; + } - // some units are prefered in comparison to others - if (!noPriorityTargetFound && (target->IsImmunedToDamage(attacker->GetMeleeDamageSchoolMask()) || target->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE))) - { - 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; - } - } + // apply threat modifiers to the amount + if (!ignoreModifiers) + amount = CalculateModifiedThreat(amount, target, spell); - if (attacker->CanCreatureAttack(target)) // skip non attackable currently targets + // 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 + + // 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; -ThreatManager::ThreatManager(Unit* owner) : iCurrentVictim(nullptr), iOwner(owner), iUpdateTimer(THREAT_UPDATE_INTERVAL) { } + 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(); - - // must check > 0.0f, otherwise dead loop - if (threat > 0.0f && redirectThreadPct) + 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) { - if (Unit* redirectTarget = victim->GetRedirectThreatTarget()) - { - float redirectThreat = CalculatePct(threat, redirectThreadPct); - threat -= redirectThreat; - if (ThreatCalcHelper::isValidProcess(redirectTarget, GetOwner())) - _addThreat(redirectTarget, redirectThreat); - } + 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; +} - _addThreat(victim, threat); +// 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()); } -void ThreatManager::_addThreat(Unit* victim, float threat) +/*static*/ float ThreatManager::CalculateModifiedThreat(float threat, Unit const* victim, SpellInfo const* spell) { - 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); + // modifiers by spell + if (spell) + { + 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->Id, SPELLMOD_THREAT, threat); + } - if (!ref) // there was no ref => create a new one + // modifiers by effect school + ThreatManager const& victimMgr = victim->GetThreatManager(); + SpellSchoolMask const mask = spell ? spell->GetSchoolMask() : SPELL_SCHOOL_MASK_NORMAL; + switch (mask) { - 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); + 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: + { + 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; + } } + return threat; } -//============================================================ - -void ThreatManager::ModifyThreatByPercent(Unit* victim, int32 percent) +void ThreatManager::SendClearAllThreatToClients() const { - iThreatContainer.ModifyThreatByPercent(victim, percent); + WorldPacket data(SMSG_THREAT_CLEAR, 8); + data << _owner->GetPackGUID(); + _owner->SendMessageToSet(&data, false); } -//============================================================ - -Unit* ThreatManager::getHostilTarget() +void ThreatManager::SendThreatListToClients() const { - iThreatContainer.update(); - HostileReference* nextVictim = iThreatContainer.selectNextVictim(GetOwner()->ToCreature(), getCurrentVictim()); - setCurrentVictim(nextVictim); - return getCurrentVictim() != nullptr ? getCurrentVictim()->getTarget() : nullptr; + WorldPacket data(SMSG_THREAT_UPDATE, (_sortedThreatList.size() + 1) * 8); // guess + data << _owner->GetPackGUID(); + size_t countPos = data.wpos(); + data << uint32(0); // placeholder + uint32 count = 0; + for (ThreatReference const* ref : _sortedThreatList) + { + if (!ref->IsAvailable()) // @todo check if suppressed threat should get sent for bubble/iceblock/hop etc + continue; + data << ref->GetVictim()->GetPackGUID(); + data << uint32(ref->GetThreat() * 100); + ++count; + } + data.put<uint32>(countPos, count); + _owner->SendMessageToSet(&data, false); } -//============================================================ - -float ThreatManager::getThreat(Unit* victim, bool alsoSearchOfflineList) +void ThreatManager::ForwardThreatForAssistingMe(Unit* assistant, float baseAmount, SpellInfo const* spell, bool ignoreModifiers) { - float threat = 0.0f; - HostileReference* ref = iThreatContainer.getReferenceByTarget(victim); - if (!ref && alsoSearchOfflineList) - ref = iThreatOfflineContainer.getReferenceByTarget(victim); - if (ref) - threat = ref->getThreat(); - return threat; + 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); } -//============================================================ +void ThreatManager::RemoveMeFromThreatLists() +{ + while (!_threatenedByMe.empty()) + _threatenedByMe.begin()->second->ClearThreat(); +} -void ThreatManager::tauntApply(Unit* taunter) +void ThreatManager::UpdateMyTempModifiers() { - HostileReference* ref = iThreatContainer.getReferenceByTarget(taunter); - if (getCurrentVictim() && ref && (ref->getThreat() < getCurrentVictim()->getThreat())) + int32 mod = 0; + for (AuraEffect const* eff : _owner->GetAuraEffectsByType(SPELL_AURA_MOD_TOTAL_THREAT)) + mod += eff->GetAmount(); + + for (auto const& pair : _threatenedByMe) { - if (ref->getTempThreatModifier() == 0.0f) // Ok, temp threat is unused - ref->setTempThreat(getCurrentVictim()->getThreat()); + pair.second->_tempModifier = mod; + pair.second->HeapNotifyChanged(); } } -//============================================================ - -void ThreatManager::tauntFadeOut(Unit* taunter) +void ThreatManager::UpdateMySpellSchoolModifiers() { - HostileReference* ref = iThreatContainer.getReferenceByTarget(taunter); - if (ref) - ref->resetTempThreat(); + for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i) + _singleSchoolModifiers[i] = _owner->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, 1 << i); + _multiSchoolModifiers.clear(); } -//============================================================ - -void ThreatManager::setCurrentVictim(HostileReference* pHostileReference) +void ThreatManager::RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct) { - if (pHostileReference && pHostileReference != iCurrentVictim) - { - iOwner->SendChangeCurrentVictimOpcode(pHostileReference); - } - iCurrentVictim = pHostileReference; + _redirectRegistry[spellId][victim] = pct; + UpdateRedirectInfo(); } -//============================================================ -// The hated unit is gone, dead or deleted -// return true, if the event is consumed +void ThreatManager::UnregisterRedirectThreat(uint32 spellId) +{ + auto it = _redirectRegistry.find(spellId); + if (it == _redirectRegistry.end()) + return; + _redirectRegistry.erase(it); + UpdateRedirectInfo(); +} -void ThreatManager::processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent) +void ThreatManager::UnregisterRedirectThreat(uint32 spellId, ObjectGuid const& victim) { - threatRefStatusChangeEvent->setThreatManager(this); // now we can set the threat manager + 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(); +} - HostileReference* hostilRef = threatRefStatusChangeEvent->getReference(); +void ThreatManager::SendRemoveToClients(Unit const* victim) const +{ + WorldPacket data(SMSG_THREAT_REMOVE, 16); + data << _owner->GetPackGUID(); + data << victim->GetPackGUID(); + _owner->SendMessageToSet(&data, false); +} - switch (threatRefStatusChangeEvent->getType()) +void ThreatManager::SendNewVictimToClients(ThreatReference const* victimRef) const +{ + WorldPacket data(SMSG_HIGHEST_THREAT_UPDATE, (_sortedThreatList.size() + 2) * 8); + data << _owner->GetPackGUID(); + data << victimRef->_victim->GetPackGUID(); + size_t countPos = data.wpos(); + data << uint32(0); // placeholder + uint32 count = 0; + for (ThreatReference const* ref : _sortedThreatList) { - 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; + if (!ref->IsAvailable()) + continue; + data << ref->GetVictim()->GetPackGUID(); + data << uint32(ref->GetThreat() * 100); + ++count; } + data.put<uint32>(countPos, count); + _owner->SendMessageToSet(&data, false); } -bool ThreatManager::isNeedUpdateToClient(uint32 time) +void ThreatManager::PutThreatListRef(ObjectGuid const& guid, ThreatReference* ref) { - if (isThreatListEmpty()) - return false; - - if (time >= iUpdateTimer) - { - iUpdateTimer = THREAT_UPDATE_INTERVAL; - return true; - } - iUpdateTimer -= time; - return false; + auto& inMap = _myThreatListEntries[guid]; + ASSERT(!inMap && "Duplicate threat list entry being inserted - memory leak!"); + inMap = ref; + ref->_handle = _sortedThreatList.push(ref); } -// Reset all aggro without modifying the threatlist. -void ThreatManager::resetAllAggro() +void ThreatManager::PurgeThreatListRef(ObjectGuid const& guid, bool sendRemove) { - ThreatContainer::StorageType &threatList = iThreatContainer.iThreatList; - if (threatList.empty()) + auto it = _myThreatListEntries.find(guid); + if (it == _myThreatListEntries.end()) return; + ThreatReference* ref = it->second; + _myThreatListEntries.erase(it); - for (ThreatContainer::StorageType::iterator itr = threatList.begin(); itr != threatList.end(); ++itr) - (*itr)->setThreat(0); + if (_currentVictimRef == ref) + _currentVictimRef = nullptr; + + _sortedThreatList.erase(ref->_handle); + if (sendRemove && ref->IsOnline()) + SendRemoveToClients(ref->_victim); +} - setDirty(true); +void ThreatManager::PutThreatenedByMeRef(ObjectGuid const& guid, ThreatReference* ref) +{ + auto& inMap = _threatenedByMe[guid]; + ASSERT(!inMap && "Duplicate entry being inserted into threatened by me list - potential memory leak!"); + inMap = ref; +} + +void ThreatManager::PurgeThreatenedByMeRef(ObjectGuid const& guid) +{ + auto it = _threatenedByMe.find(guid); + if (it != _threatenedByMe.end()) + _threatenedByMe.erase(it); +} + +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 1613057f46d..4638ef995ec 100644 --- a/src/server/game/Combat/ThreatManager.h +++ b/src/server/game/Combat/ThreatManager.h @@ -1,6 +1,5 @@ /* * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> * * 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 @@ -15,321 +14,262 @@ * 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 _THREATMANAGER -#define _THREATMANAGER + + #ifndef TRINITY_THREATMANAGER_H + #define TRINITY_THREATMANAGER_H #include "Common.h" #include "IteratorPair.h" -#include "SharedDefines.h" -#include "LinkedReference/Reference.h" -#include "UnitEvents.h" #include "ObjectGuid.h" - -#include <list> - -//============================================================== +#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; - - ThreatContainer(): iDirty(false) { } + ThreatManager(ThreatManager const&) = delete; + ThreatManager& operator=(ThreatManager const&) = delete; - ~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; }; -//================================================= - -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 +inline bool CompareThreatLessThan::operator()(ThreatReference const* a, ThreatReference const* b) const { return ThreatManager::CompareReferencesLT(a, b, 1.0f); } + + #endif diff --git a/src/server/game/Combat/UnitEvents.h b/src/server/game/Combat/UnitEvents.h deleted file mode 100644 index 304d5f7f0eb..00000000000 --- a/src/server/game/Combat/UnitEvents.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - * - * 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 5ff7cee6b5a..c6d824ed723 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -616,6 +616,8 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/, UpdateMovementFlags(); LoadCreaturesAddon(); LoadTemplateImmunities(); + + GetThreatManager().UpdateOnlineStates(true, true); return true; } @@ -716,6 +718,8 @@ void Creature::Update(uint32 diff) if (!IsAlive()) break; + GetThreatManager().Update(diff); + if (m_shouldReacquireTarget && !IsFocusing(nullptr, true)) { SetTarget(m_suppressedTarget); @@ -1094,9 +1098,108 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, true); } + GetThreatManager().Initialize(); + return true; } +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()) @@ -3131,6 +3234,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 (HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TAPPED)) + SetUInt32Value(UNIT_DYNAMIC_FLAGS, 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 921b03380a7..89cf8596042 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -97,11 +97,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 isCanTrainingAndResetTalentsOf(Player* player) const; @@ -304,8 +316,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); } @@ -347,6 +357,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.cpp b/src/server/game/Entities/Object/Object.cpp index 839d0ba410b..f7fc60432ac 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2376,11 +2376,6 @@ void WorldObject::SetPhaseMask(uint32 newPhaseMask, bool update) UpdateObjectVisibility(); } -bool WorldObject::InSamePhase(WorldObject const* obj) const -{ - return InSamePhase(obj->GetPhaseMask()); -} - void WorldObject::PlayDistanceSound(uint32 sound_id, Player* target /*= nullptr*/) { WorldPacket data(SMSG_PLAY_OBJECT_SOUND, 4+8); diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 7f33a069fa3..ce846f75ee2 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -289,8 +289,9 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation virtual void SetPhaseMask(uint32 newPhaseMask, bool update); uint32 GetPhaseMask() const { return m_phaseMask; } - bool InSamePhase(WorldObject const* obj) const; bool InSamePhase(uint32 phasemask) const { return (GetPhaseMask() & phasemask) != 0; } + bool InSamePhase(WorldObject const* obj) const { return obj && InSamePhase(obj->GetPhaseMask()); } + static bool InSamePhase(WorldObject const* a, WorldObject const* b) { return a && a->InSamePhase(b); } uint32 GetZoneId() const { return m_zoneId; } uint32 GetAreaId() const { return m_areaId; } diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 6f5084268ea..8651693a417 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -1814,6 +1814,8 @@ bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 SetFlag(UNIT_FIELD_FLAGS_2, 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 ff4e0667ac1..d44db9608f8 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -683,6 +683,8 @@ bool Player::Create(ObjectGuid::LowType guidlow, CharacterCreateInfo* createInfo } // all item positions resolved + GetThreatManager().Initialize(); + return true; } @@ -1371,7 +1373,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; @@ -2329,8 +2331,6 @@ void Player::SetInWater(bool apply) // remove auras that need water/land RemoveAurasWithInterruptFlags(apply ? AURA_INTERRUPT_FLAG_NOT_ABOVEWATER : AURA_INTERRUPT_FLAG_NOT_UNDERWATER); - - getHostileRefManager().updateThreatTables(); } bool Player::IsInAreaTriggerRadius(AreaTriggerEntry const* trigger) const @@ -2365,15 +2365,11 @@ void Player::SetGameMaster(bool on) SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_ALLOW_CHEAT_SPELLS); if (Pet* pet = GetPet()) - { pet->SetFaction(FACTION_FRIENDLY); - pet->getHostileRefManager().setOnlineOfflineState(false); - } RemoveByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP); ResetContestedPvP(); - getHostileRefManager().setOnlineOfflineState(false); CombatStopWithPets(); SetPhaseMask(uint32(PHASEMASK_ANYWHERE), false); // see and visible in all phases @@ -2401,7 +2397,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 @@ -2411,7 +2407,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); } @@ -21323,7 +21318,6 @@ void Player::CleanupAfterTaxiFlight() m_taxi.ClearTaxiDestinations(); // not destinations, clear source node Dismount(); RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_REMOVE_CLIENT_CONTROL | UNIT_FLAG_TAXI_FLIGHT); - getHostileRefManager().setOnlineOfflineState(true); } void Player::ContinueTaxiFlight() const @@ -24122,6 +24116,19 @@ void Player::ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidDat m_MirrorTimerFlags &= ~(UNDERWATER_INWATER | UNDERWATER_INLAVA | UNDERWATER_INSLIME | UNDERWATER_INDARKWATER); } +void Player::AtExitCombat() +{ + Unit::AtExitCombat(); + UpdatePotionCooldown(); + + if (getClass() == CLASS_DEATH_KNIGHT) + for (uint8 i = 0; i < MAX_RUNES; ++i) + { + SetRuneTimer(i, 0xFFFFFFFF); + SetLastRuneGraceTimer(i, 0); + } +} + void Player::SetCanParry(bool value) { if (m_canParry == value) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index f85699fb546..d74b36c1e84 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1647,6 +1647,7 @@ 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 AtExitCombat() override; void SendMessageToSet(WorldPacket const* data, bool self) override { SendMessageToSetInRange(data, GetVisibilityRange(), self); } void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) override; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 41e6d255baf..51be3716f5e 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -278,12 +278,12 @@ bool DispelableAura::RollDispel() const 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), 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), m_comboTarget(nullptr), m_comboPoints(0), m_spellHistory(new SpellHistory(this)) + 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), + 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), m_comboTarget(nullptr), m_comboPoints(0), m_spellHistory(new SpellHistory(this)) { m_objectType |= TYPEMASK_UNIT; m_objectTypeId = TYPEID_UNIT; @@ -345,19 +345,13 @@ Unit::Unit(bool isWorldObject) : m_modSpellHitChance = 0.0f; m_baseSpellCritChance = 5; - m_CombatTimer = 0; m_lastManaUse = 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; @@ -407,28 +401,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. @@ -445,24 +417,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)) @@ -1996,7 +1951,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(AURA_INTERRUPT_FLAG_MELEE_ATTACK); if (attType != BASE_ATTACK && attType != OFF_ATTACK) @@ -2044,7 +1999,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(AURA_INTERRUPT_FLAG_MELEE_ATTACK); if (attType != BASE_ATTACK && attType != OFF_ATTACK) @@ -5680,20 +5635,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; } @@ -5781,19 +5736,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(); @@ -5874,7 +5817,7 @@ void Unit::ValidateAttackersAndOwnTarget() AttackStop(); } -void Unit::CombatStop(bool includingCast) +void Unit::CombatStop(bool includingCast, bool mutualPvP) { if (includingCast && IsNonMeleeSpellCast(false)) InterruptNonMeleeSpells(false); @@ -5883,19 +5826,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 @@ -6288,6 +6234,7 @@ void Unit::SetMinion(Minion *minion, bool apply) } } } + UpdatePetCombatState(); } void Unit::GetAllMinionsByEntry(std::list<Creature*>& Minions, uint32 entry) @@ -6389,6 +6336,7 @@ void Unit::SetCharm(Unit* charm, bool apply) m_Controlled.erase(charm); } } + UpdatePetCombatState(); } void Unit::DealHeal(HealInfo& healInfo) @@ -6527,6 +6475,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()) 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 + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); // m_controlled is now empty, so we know none of our minions are in combat } bool Unit::isPossessedByPlayer() const @@ -8574,168 +8524,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) - SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + if (CanHaveThreatList()) + m_threatManager.AddThreat(enemy, 0.0f, nullptr, true, true); else - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + SetInCombatWith(enemy); + + if (Creature* creature = ToCreature()) + if (CreatureGroup* formation = creature->GetFormation()) + formation->MemberEngagingTarget(creature, enemy); } -void Unit::SetImmuneToNPC(bool apply, bool keepCombat) +void Unit::AttackedTarget(Unit* target, bool canInitialAggro) { - (void)keepCombat; - if (apply) - SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); - else - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); + if (!target->IsEngaged() && !canInitialAggro) + return; + target->EngageWithTarget(this); + if (Unit* targetOwner = target->GetCharmerOrOwner()) + targetOwner->EngageWithTarget(this); + + Player* myPlayerOwner = GetCharmerOrOwnerPlayerOrPlayerItself(); + Player* targetPlayerOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself(); + if (myPlayerOwner && targetPlayerOwner && !(myPlayerOwner->duel && myPlayerOwner->duel->opponent == targetPlayerOwner)) + { + myPlayerOwner->UpdatePvP(true); + myPlayerOwner->SetContestedPvP(targetPlayerOwner); + myPlayerOwner->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + } } -void Unit::CombatStart(Unit* target, bool initialAggro) +void Unit::SetImmuneToAll(bool apply, bool keepCombat) { - if (initialAggro) + if (apply) { - 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); + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); + ValidateAttackersAndOwnTarget(); + if (keepCombat) + m_threatManager.UpdateOnlineStates(true, true); + else + m_combatManager.EndAllCombat(); } - - 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)) + else { - me->UpdatePvP(true); - me->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); + m_threatManager.UpdateOnlineStates(true, true); } } -void Unit::SetInCombatState(bool PvP, Unit* enemy) +void Unit::SetImmuneToPC(bool apply, bool keepCombat) { - // only alive units can be in combat - if (!IsAlive()) - return; - - if (PvP) - m_CombatTimer = 5000; - - if (IsInCombat() || HasUnitState(UNIT_STATE_EVADE)) - return; - - SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT); - - if (Creature* creature = ToCreature()) + if (apply) { - // 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()) + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + ValidateAttackersAndOwnTarget(); + if (keepCombat) + m_threatManager.UpdateOnlineStates(true, true); + else { - UpdateSpeed(MOVE_RUN); - UpdateSpeed(MOVE_SWIM); - UpdateSpeed(MOVE_FLIGHT); + std::list<CombatReference*> toEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) + toEnd.push_back(pair.second); + for (auto const& pair : m_combatManager.GetPvPCombatRefs()) + if (pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) + toEnd.push_back(pair.second); + for (CombatReference* ref : toEnd) + ref->EndCombat(); } - - if (!(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_MOUNTED_COMBAT_ALLOWED)) - Dismount(); } - - for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) + else { - Unit* controlled = *itr; - ++itr; - - controlled->SetInCombatState(PvP, enemy); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + m_threatManager.UpdateOnlineStates(true, true); } } -void Unit::ClearInCombat() +void Unit::SetImmuneToNPC(bool apply, bool keepCombat) { - m_CombatTimer = 0; - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT); - - // Reset rune flags after combat - if (GetTypeId() == TYPEID_PLAYER && getClass() == CLASS_DEATH_KNIGHT) + if (apply) { - for (uint8 i = 0; i < MAX_RUNES; ++i) + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); + ValidateAttackersAndOwnTarget(); + if (keepCombat) + m_threatManager.UpdateOnlineStates(true, true); + else { - ToPlayer()->SetRuneTimer(i, 0xFFFFFFFF); - ToPlayer()->SetLastRuneGraceTimer(i, 0); + std::list<CombatReference*> toEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (!pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) + toEnd.push_back(pair.second); + for (auto const& pair : m_combatManager.GetPvPCombatRefs()) + if (!pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) + toEnd.push_back(pair.second); + for (CombatReference* ref : toEnd) + ref->EndCombat(); } } - - // Player's state will be cleared in Player::UpdateContestedPvP - if (Creature* creature = ToCreature()) + else { - ClearUnitState(UNIT_STATE_ATTACK_PLAYER); - if (HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TAPPED)) - SetUInt32Value(UNIT_DYNAMIC_FLAGS, creature->GetCreatureTemplate()->dynamicflags); - - if (creature->IsPet() || creature->IsGuardian()) - { - 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))); - } - else if (!IsCharmed()) - return; + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); + m_threatManager.UpdateOnlineStates(true, true); } - else - ToPlayer()->UpdatePotionCooldown(); - - RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_LEAVE_COMBAT); } -void Unit::ClearInPetCombat() +bool Unit::IsThreatened() const { - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); - if (Unit* owner = GetOwner()) - owner->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); + return !m_threatManager.IsThreatListEmpty(); } bool Unit::isTargetableForAttack(bool checkFakeDeath) const @@ -8785,7 +8686,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; @@ -9361,7 +9262,6 @@ void Unit::setDeathState(DeathState s) { CombatStop(); GetThreatManager().ClearAllThreat(); - getHostileRefManager().deleteReferences(); ClearComboPointHolders(); // any combo points pointed to unit lost at it death if (IsNonMeleeSpellCast(false)) @@ -9406,243 +9306,32 @@ void Unit::setDeathState(DeathState s) RemoveFlag(UNIT_FIELD_FLAGS, 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::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 (!IsFocusing(nullptr, true)) - SetInFront(taunter); - if (creature->IsAIEnabled) - creature->AI()->AttackStart(taunter); - - //m_ThreatManager.tauntApply(taunter); -} - -//====================================================================== - -void Unit::TauntFadeOut(Unit* taunter) -{ - 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()) - { - if (creature->IsAIEnabled) - creature->AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES); - return; - } - - target = creature->SelectVictim(); // might have more taunt auras remaining - - if (target && target != taunter) - { - if (!IsFocusing(nullptr, true)) - SetInFront(target); - if (creature->IsAIEnabled) - creature->AI()->AttackStart(target); - } + RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_LEAVE_COMBAT); } -//====================================================================== - -Unit* Creature::SelectVictim() +void Unit::UpdatePetCombatState() { - // function provides main threat functionality - // next-victim-selection algorithm and evade mode are called - // threat list sorting etc. - - 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(); + 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 - // 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) + SetFlag(UNIT_FIELD_FLAGS, 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; + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); } //====================================================================== -//====================================================================== -//====================================================================== float Unit::ApplyEffectModifiers(SpellInfo const* spellProto, uint8 effect_index, float value) const { @@ -10595,7 +10284,6 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup) ClearComboPoints(); ClearComboPointHolders(); GetThreatManager().ClearAllThreat(); - getHostileRefManager().deleteReferences(); } void Unit::CleanupsBeforeDelete(bool finalCleanup) @@ -12063,7 +11751,6 @@ void Unit::Kill(Unit* victim, bool durabilityLoss) } // 10% durability loss on death - // clean InHateListOf if (Player* plrVictim = victim->ToPlayer()) { // remember victim PvP death for corpse type and corpse reclaim delay @@ -12489,7 +12176,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(); @@ -12637,8 +12323,6 @@ void Unit::RemoveCharmedBy(Unit* charmer) CastStop(); CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells) - getHostileRefManager().deleteReferences(); - GetThreatManager().ClearAllThreat(); if (_oldFactionId) { @@ -12717,6 +12401,8 @@ void Unit::RemoveCharmedBy(Unit* charmer) } player->SetClientControl(this, true); } + + EngageWithTarget(charmer); // a guardian should always have charminfo if (playerCharmer && this != charmer->GetFirstControlled()) @@ -12745,11 +12431,6 @@ void Unit::RestoreFaction() } } -Unit* Unit::GetRedirectThreatTarget() -{ - return _redirectThreadInfo.GetTargetGUID() ? ObjectAccessor::GetUnit(*this, _redirectThreadInfo.GetTargetGUID()) : nullptr; -} - bool Unit::CreateVehicleKit(uint32 id, uint32 creatureEntry) { VehicleEntry const* vehInfo = sVehicleStore.LookupEntry(id); @@ -13078,39 +12759,7 @@ void Unit::SetPhaseMask(uint32 newPhaseMask, bool update) return; if (IsInWorld()) - { - // modify hostile references for new phasemask, some special cases deal with hostile references themselves - if (GetTypeId() == TYPEID_UNIT || (!ToPlayer()->IsGameMaster() && !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->InSamePhase(newPhaseMask)); - - 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->InSamePhase(newPhaseMask)); - } - } - } + m_threatManager.UpdateOnlineStates(true, true); // Phase player, dont update WorldObject::SetPhaseMask(newPhaseMask, false); @@ -13141,6 +12790,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); @@ -13829,7 +13479,9 @@ bool Unit::CanSwim() const return true; if (HasFlag(UNIT_FIELD_FLAGS_2, 0x1000000)) return false; - return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT | UNIT_FLAG_RENAME | UNIT_FLAG_UNK_15); + if (IsPet() && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT)) + return true; + return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_RENAME | UNIT_FLAG_UNK_15); } void Unit::NearTeleportTo(Position const& pos, bool casting /*= false*/) @@ -13952,64 +13604,6 @@ void Unit::UpdateHeight(float newZ) GetVehicleKit()->RelocatePassengers(); } -void Unit::SendThreatListUpdate() -{ - if (!GetThreatManager().isThreatListEmpty()) - { - uint32 count = GetThreatManager().getThreatList().size(); - - //TC_LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_UPDATE Message"); - WorldPacket data(SMSG_THREAT_UPDATE, 8 + count * 8); - data << GetPackGUID(); - data << uint32(count); - ThreatContainer::StorageType const& tlist = GetThreatManager().getThreatList(); - for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr) - { - data << (*itr)->getUnitGuid().WriteAsPacked(); - data << uint32((*itr)->getThreat() * 100); - } - SendMessageToSet(&data, false); - } -} - -void Unit::SendChangeCurrentVictimOpcode(HostileReference* pHostileReference) -{ - if (!GetThreatManager().isThreatListEmpty()) - { - uint32 count = GetThreatManager().getThreatList().size(); - - TC_LOG_DEBUG("entities.unit", "WORLD: Send SMSG_HIGHEST_THREAT_UPDATE Message"); - WorldPacket data(SMSG_HIGHEST_THREAT_UPDATE, 8 + 8 + count * 8); - data << GetPackGUID(); - data << pHostileReference->getUnitGuid().WriteAsPacked(); - data << uint32(count); - ThreatContainer::StorageType const& tlist = GetThreatManager().getThreatList(); - for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr) - { - data << (*itr)->getUnitGuid().WriteAsPacked(); - data << uint32((*itr)->getThreat() * 100); - } - SendMessageToSet(&data, false); - } -} - -void Unit::SendClearThreatListOpcode() -{ - TC_LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_CLEAR Message"); - WorldPacket data(SMSG_THREAT_CLEAR, 8); - data << GetPackGUID(); - SendMessageToSet(&data, false); -} - -void Unit::SendRemoveFromThreatListOpcode(HostileReference* pHostileReference) -{ - TC_LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_REMOVE Message"); - WorldPacket data(SMSG_THREAT_REMOVE, 8 + 8); - data << GetPackGUID(); - data << pHostileReference->getUnitGuid().WriteAsPacked(); - SendMessageToSet(&data, false); -} - void Unit::RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker) { float addRage; @@ -14069,10 +13663,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 6e4512dfb21..8b8c975597d 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -23,7 +23,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" @@ -555,28 +555,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, @@ -830,7 +808,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; @@ -1030,28 +1008,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); } - - void SetImmuneToAll(bool apply, bool keepCombat = false) { SetImmuneToPC(apply, keepCombat); SetImmuneToNPC(apply, keepCombat); } + /// ====================== 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); + 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 HasFlag(UNIT_FIELD_FLAGS, 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 HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); } + void SetImmuneToNPC(bool apply, bool keepCombat); + virtual void SetImmuneToNPC(bool apply) { SetImmuneToNPC(apply, false); } + + bool IsInCombat() const { return HasFlag(UNIT_FIELD_FLAGS, 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 HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT); } - bool IsPetInCombat() const { return HasFlag(UNIT_FIELD_FLAGS, 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; } + void SendClearTarget(); + void SendThreatListUpdate() { m_threatManager.SendThreatListToClients(); } bool HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint32 familyFlags) const; bool virtual HasSpell(uint32 /*spellID*/) const { return false; } @@ -1138,13 +1133,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(); - void BuildHeartBeatMsg(WorldPacket* data) const; bool IsAlive() const { return (m_deathState == ALIVE); } @@ -1409,7 +1397,6 @@ class TC_GAME_API Unit : public WorldObject float m_modSpellHitChance; int32 m_baseSpellCritChance; - float m_threatModifier[MAX_SPELL_SCHOOL]; float m_modAttackSpeedPct[3]; // Event handler @@ -1473,17 +1460,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; } - VisibleAuraMap const* GetVisibleAuras() { return &m_visibleAuras; } AuraApplication * GetVisibleAura(uint8 slot) const; void SetVisibleAura(uint8 slot, AuraApplication * aur); @@ -1546,8 +1522,6 @@ class TC_GAME_API Unit : public WorldObject void SetLastManaUse(uint32 spellCastTime) { m_lastManaUse = spellCastTime; } bool IsUnderLastManaUseEffect() const; - void SetContestedPvP(Player* attackedPlayer = nullptr); - uint32 GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectType damagetype, uint32 CastingTime) const; float CalculateDefaultCoefficient(SpellInfo const* spellInfo, DamageEffectType damagetype) const; @@ -1648,13 +1622,6 @@ class TC_GAME_API Unit : public WorldObject uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const; uint32 GetModelForTotem(PlayerTotemType totemType); - // 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; @@ -1795,8 +1762,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; @@ -1811,6 +1776,9 @@ class TC_GAME_API Unit : public WorldObject void ProcessPositionDataChanged(PositionFullTerrainStatus const& data) override; virtual void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> const& liquidData); + virtual void AtEnterCombat() { } + virtual void AtExitCombat(); + private: void UpdateSplineMovement(uint32 t_diff); @@ -1830,13 +1798,15 @@ class TC_GAME_API Unit : public WorldObject uint32 m_rootTimes; uint32 m_state; // Even derived shouldn't modify - uint32 m_CombatTimer; uint32 m_lastManaUse; // msecs TimeTrackerSmall m_movesplineTimer; DiminishingReturn m_Diminishing[DIMINISHING_MAX]; // 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; @@ -1844,8 +1814,6 @@ class TC_GAME_API Unit : public WorldObject int8 m_comboPoints; std::unordered_set<Unit*> m_ComboPointHolders; - 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 1166be0477e..ffa619e613e 100644 --- a/src/server/game/Grids/ObjectGridLoader.cpp +++ b/src/server/game/Grids/ObjectGridLoader.cpp @@ -260,7 +260,7 @@ void ObjectGridStoper::Visit(CreatureMapType &m) for (CreatureMapType::iterator iter = m.begin(); iter != m.end(); ++iter) { iter->GetSource()->RemoveAllDynObjects(); - 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 cabb6e753b5..4ed71333ccf 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -141,7 +141,6 @@ void WorldSession::HandlePetStopAttack(WorldPacket &recvData) return; pet->AttackStop(); - pet->ClearInPetCombat(); } void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellid, uint16 flag, ObjectGuid guid2) @@ -175,7 +174,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); @@ -277,7 +275,6 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe { case REACT_PASSIVE: //passive pet->AttackStop(); - pet->ClearInPetCombat(); // no break; 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 1d759054745..8c1cf06a4f5 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -792,21 +792,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); } } @@ -947,7 +936,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 e5f7f92d87c..67a76f1e692 100644 --- a/src/server/game/Movement/MovementGenerators/FlightPathMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/FlightPathMovementGenerator.cpp @@ -115,7 +115,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 @@ -128,8 +127,8 @@ void FlightPathMovementGenerator::DoFinalize(Player* player) void FlightPathMovementGenerator::DoReset(Player* player) { - player->getHostileRefManager().setOnlineOfflineState(false); player->AddUnitState(UNIT_STATE_IN_FLIGHT); + player->CombatStopWithPets(); player->SetFlag(UNIT_FIELD_FLAGS, 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 8a1637e7a58..9b2ce6fe4a9 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -447,7 +447,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 fffa102f7b2..fb0e1d978ca 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -280,7 +280,7 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS]= &AuraEffect::HandleAuraModRangedHaste, //218 SPELL_AURA_HASTE_RANGED &AuraEffect::HandleModManaRegen, //219 SPELL_AURA_MOD_MANA_REGEN_FROM_STAT &AuraEffect::HandleModRatingFromStat, //220 SPELL_AURA_MOD_RATING_FROM_STAT - &AuraEffect::HandleNULL, //221 SPELL_AURA_MOD_DETAUNT + &AuraEffect::HandleModDetaunt, //221 SPELL_AURA_MOD_DETAUNT &AuraEffect::HandleUnused, //222 unused (3.2.0) only for spell 44586 that not used in real spell cast &AuraEffect::HandleNoImmediateEffect, //223 SPELL_AURA_RAID_PROC_FROM_CHARGE &AuraEffect::HandleUnused, //224 unused (3.0.8a) @@ -2083,6 +2083,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 @@ -2160,14 +2162,22 @@ 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); + target->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_IMMUNE_OR_LOST_SELECTION); // 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()) @@ -2198,6 +2208,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 @@ -2216,7 +2227,14 @@ 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(); target->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_IMMUNE_OR_LOST_SELECTION); } } @@ -2678,28 +2696,15 @@ void AuraEffect::HandleForceMoveForward(AuraApplication const* aurApp, uint8 mod /*** 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; @@ -2711,10 +2716,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; @@ -2724,17 +2729,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(); } /*****************************/ @@ -2749,6 +2758,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 @@ -2759,6 +2769,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 @@ -2769,6 +2780,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 @@ -2779,6 +2791,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 @@ -3132,6 +3145,8 @@ void AuraEffect::HandleAuraModSchoolImmunity(AuraApplication const* aurApp, uint if (GetSpellInfo()->HasAttribute(SPELL_ATTR1_DISPEL_AURAS_ON_IMMUNITY) && GetSpellInfo()->HasAttribute(SPELL_ATTR2_DAMAGE_REDUCED_SHIELD)) target->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_IMMUNE_OR_LOST_SELECTION); + + target->GetThreatManager().UpdateOnlineStates(true, false); } void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const @@ -3141,6 +3156,8 @@ void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 m Unit* target = aurApp->GetTarget(); m_spellInfo->ApplyAllSpellImmunitiesTo(target, GetEffIndex(), apply); + + target->GetThreatManager().UpdateOnlineStates(true, false); } void AuraEffect::HandleAuraModDispelImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const @@ -4744,7 +4761,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 cc643dcbfb5..befe0d7a2f6 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.h +++ b/src/server/game/Spells/Auras/SpellAuraEffects.h @@ -185,6 +185,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 b6f368d3a56..3bdce758016 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2480,7 +2480,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); @@ -2582,7 +2582,8 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA } if (unit->IsInCombat() && m_spellInfo->HasInitialAggro()) { - m_caster->SetInCombatState(unit->GetCombatTimer() > 0, unit); + if (m_caster->HasFlag(UNIT_FIELD_FLAGS, 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); } } @@ -7290,6 +7291,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 (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (targetInfo.effectMask & (1<<i)) diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 45ecb4a8e15..59f5d9b764a 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -1229,7 +1229,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); } @@ -1781,7 +1781,7 @@ void Spell::EffectEnergize(SpellEffIndex effIndex) if (damage < 0) return; - m_caster->EnergizeBySpell(unitTarget, m_spellInfo->Id, damage, power); + m_caster->EnergizeBySpell(unitTarget, m_spellInfo, damage, power); } void Spell::EffectEnergizePct(SpellEffIndex effIndex) @@ -1807,7 +1807,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) @@ -3040,30 +3040,26 @@ 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 (m_spellInfo->Id == 62124 && (!unitTarget->IsPet() || !unitTarget->GetOwnerGUID().IsPlayer())) - m_caster->CastSpell(unitTarget, 67485, true); - - 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); + // Hand of Reckoning + if (m_spellInfo->Id == 62124) + m_caster->CastSpell(unitTarget, 67485, true); + + if (!mgr.IsThreatListEmpty()) + // Set threat equal to highest threat currently on target + mgr.MatchUnitThreatToHighestThreat(m_caster); } void Spell::EffectWeaponDmg(SpellEffIndex effIndex) @@ -3354,13 +3350,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*/) @@ -3925,20 +3921,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(); } @@ -5478,7 +5473,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 f17b2481ea3..873bfbefdcf 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1645,8 +1645,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->HasFlag(UNIT_FIELD_FLAGS, 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 9cb8e554372..54f80e6abb4 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -842,23 +842,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 (guid %u) is not alive.", target->GetName().c_str(), target->GetGUID().GetCounter()); + return true; + } + if (!target->CanHaveThreatList()) + handler->PSendSysMessage("%s (guid %u) cannot have a threat list.", target->GetName().c_str(), target->GetGUID().GetCounter()); - ThreatContainer::StorageType const& threatList = target->GetThreatManager().getThreatList(); - ThreatContainer::StorageType::const_iterator itr; uint32 count = 0; - handler->PSendSysMessage("Threat list of %s (guid %u)", target->GetName().c_str(), target->GetGUID().GetCounter()); - for (itr = threatList.begin(); itr != threatList.end(); ++itr) + auto const& threatenedByMe = target->GetThreatManager().GetThreatenedByMeList(); + if (threatenedByMe.empty()) + handler->PSendSysMessage("%s (guid %u) does not threaten any units.", target->GetName().c_str(), target->GetGUID().GetCounter()); + else { - Unit* unit = (*itr)->getTarget(); - if (!unit) - continue; - ++count; - handler->PSendSysMessage(" %u. %s (guid %u) - threat %f", count, unit->GetName().c_str(), unit->GetGUID().GetCounter(), (*itr)->getThreat()); + handler->PSendSysMessage("List of units threatened by %s (guid %u)", target->GetName().c_str(), target->GetGUID().GetCounter()); + for (auto const& pair : threatenedByMe) + { + Unit* unit = pair.second->GetOwner(); + handler->PSendSysMessage(" %u. %s (current guid %u, DB guid %u) - threat %f", ++count, unit->GetName().c_str(), unit->GetGUID().GetCounter(), 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 (guid %u, DB GUID %u)", target->GetName().c_str(), target->GetGUID().GetCounter(), 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 (guid %u) - threat %f%s%s", ++count, unit->GetName().c_str(), unit->GetGUID().GetCounter(), ref->GetThreat(), tauntStr, onlineStr); + } + handler->SendSysMessage("End of threat list."); + } + else + handler->PSendSysMessage("%s (guid %u, DB GUID %u) is not currently engaged.", target->GetName().c_str(), target->GetGUID().GetCounter(), target->GetTypeId() == TYPEID_UNIT ? target->ToCreature()->GetSpawnId() : 0); return true; } @@ -867,19 +917,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 (guid %u)", target->GetName().c_str(), target->GetGUID().GetCounter()); - 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 (current guid %u, DB guid %u) - threat %f", count, unit->GetName().c_str(), unit->GetGUID().GetCounter(), unit->GetTypeId() == TYPEID_UNIT ? unit->ToCreature()->GetSpawnId() : 0, ref->getThreat()); - } - ref = ref->next(); + Unit* unit = ref.second->GetOther(target); + handler->PSendSysMessage("[PvP] %s (DBGUID %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 (DBGUID %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 aecef71a961..cc28b2da8ab 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -2420,7 +2420,6 @@ public: return false; target->CombatStop(); - target->getHostileRefManager().deleteReferences(); return true; } diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp index 6635fc0bc7b..4ffaf1c5f42 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp @@ -199,30 +199,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); @@ -230,14 +221,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(); @@ -248,12 +236,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; @@ -267,12 +253,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 9480bf038a5..6d7816ff0d8 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_priestess_delrissa.cpp +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_priestess_delrissa.cpp @@ -855,17 +855,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; } } @@ -949,17 +944,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/Kalimdor/boss_azuregos.cpp b/src/server/scripts/Kalimdor/boss_azuregos.cpp index 8a98eae734a..e4e12a765ca 100644 --- a/src/server/scripts/Kalimdor/boss_azuregos.cpp +++ b/src/server/scripts/Kalimdor/boss_azuregos.cpp @@ -118,13 +118,10 @@ class boss_azuregos : public CreatureScript case EVENT_TELEPORT: { Talk(SAY_TELEPORT); - ThreatContainer::StorageType const& threatlist = me->GetThreatManager().getThreatList(); - for (ThreatContainer::StorageType::const_iterator i = threatlist.begin(); i != threatlist.end(); ++i) - { - if (Player* player = ObjectAccessor::GetPlayer(*me, (*i)->getUnitGuid())) + for (auto const& pair : me->GetCombatManager().GetPvECombatRefs()) + if (Player* player = pair.second->GetOther(me)->ToPlayer()) DoTeleportPlayer(player, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()+3, player->GetOrientation()); - } - + ResetThreatList(); events.ScheduleEvent(EVENT_TELEPORT, 30000); break; 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 c6cae404478..1d6eed64946 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 9ba7cce3130..8ce416a5eba 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 1c4f444234b..6f77a251f31 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp @@ -822,7 +822,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 7ea01046763..6bc1bdc9e10 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp @@ -673,14 +673,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 e87248ddff0..1a78118b7c1 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 02afc219aa3..4ac7f99e2c2 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 90c9224669f..f940d67f8c7 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp @@ -560,25 +560,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 777b9a8cf1a..3bbb13258f1 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_hodir.cpp @@ -487,12 +487,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 53850b38f54..c67c88a397d 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->RemoveFlag(UNIT_FIELD_FLAGS, 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 eba719e957e..bcc0f7c0c51 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/Pet/pet_hunter.cpp b/src/server/scripts/Pet/pet_hunter.cpp index 5a295c04f05..50c61f4c885 100644 --- a/src/server/scripts/Pet/pet_hunter.cpp +++ b/src/server/scripts/Pet/pet_hunter.cpp @@ -238,8 +238,11 @@ class spell_pet_guard_dog : public SpellScriptLoader Unit* caster = eventInfo.GetActor(); caster->CastSpell(nullptr, SPELL_PET_GUARD_DOG_HAPPINESS, aurEff); + Unit* target = eventInfo.GetProcTarget(); + if (!target->CanHaveThreatList()) + return; float addThreat = CalculatePct(ASSERT_NOTNULL(eventInfo.GetSpellInfo())->Effects[EFFECT_0].CalcValue(caster), aurEff->GetAmount()); - eventInfo.GetProcTarget()->GetThreatManager().AddThreat(caster, addThreat, GetSpellInfo(), false, true); + target->GetThreatManager().AddThreat(caster, addThreat, GetSpellInfo(), false, true); } void Register() override diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index c9ae8848d40..89b14f71ccb 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -45,6 +45,7 @@ enum HunterSpells SPELL_HUNTER_IMPROVED_MEND_PET = 24406, SPELL_HUNTER_INVIGORATION_TRIGGERED = 53398, SPELL_HUNTER_MASTERS_CALL_TRIGGERED = 62305, + SPELL_HUNTER_MISDIRECTION = 34477, SPELL_HUNTER_MISDIRECTION_PROC = 35079, SPELL_HUNTER_PET_LAST_STAND_TRIGGERED = 53479, SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX = 55709, @@ -911,12 +912,7 @@ class spell_hun_misdirection : public SpellScriptLoader void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEFAULT || !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 const* aurEff, ProcEventInfo& /*eventInfo*/) @@ -928,7 +924,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); } }; @@ -951,7 +946,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 fd46173675d..e04c0f76791 100644 --- a/src/server/scripts/Spells/spell_rogue.cpp +++ b/src/server/scripts/Spells/spell_rogue.cpp @@ -43,7 +43,8 @@ enum RogueSpells SPELL_ROGUE_KILLING_SPREE_WEAPON_DMG = 57841, SPELL_ROGUE_KILLING_SPREE_DMG_BUFF = 61851, SPELL_ROGUE_PREY_ON_THE_WEAK = 58670, - SPELL_ROGUE_SHIV_TRIGGERED = 5940, + SPELL_ROGUE_SHIV_TRIGGERED = 5940, + SPELL_ROGUE_TRICKS_OF_THE_TRADE = 57934, SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST = 57933, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC = 59628, SPELL_ROGUE_HONOR_AMONG_THIEVES = 51698, @@ -52,7 +53,7 @@ enum RogueSpells SPELL_ROGUE_T10_2P_BONUS = 70804, SPELL_ROGUE_GLYPH_OF_BACKSTAB_TRIGGER = 63975, SPELL_ROGUE_QUICK_RECOVERY_ENERGY = 31663, - SPELL_ROGUE_CRIPPLING_POISON = 3409, + SPELL_ROGUE_CRIPPLING_POISON = 3409, SPELL_ROGUE_MASTER_OF_SUBTLETY_BUFF = 31665, SPELL_ROGUE_OVERKILL_BUFF = 58427 }; @@ -879,87 +880,86 @@ class spell_rog_shiv : 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 + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( { - PrepareAuraScript(spell_rog_tricks_of_the_trade_AuraScript); + SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST, + SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC + }); + } - bool Validate(SpellInfo const* /*spellInfo*/) override - { - return ValidateSpellInfo( - { - SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST, - 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 const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); - bool CheckProc(ProcEventInfo& /*eventInfo*/) - { - _redirectTarget = GetTarget()->GetRedirectThreatTarget(); - return _redirectTarget != nullptr; - } + Unit* rogue = GetTarget(); + Unit* target = ObjectAccessor::GetUnit(*rogue, _redirectTarget); + if (target) + { + rogue->CastSpell(target, SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST, aurEff); + rogue->CastSpell(rogue, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC, aurEff); + } + Remove(AURA_REMOVE_BY_DEFAULT); + } - void HandleProc(AuraEffect const* /*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(_redirectTarget, SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST, true); - 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 const& 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); + } }; // 51698,51700,51701 - Honor Among Thieves @@ -1135,8 +1135,8 @@ void AddSC_rogue_spell_scripts() new spell_rog_glyph_of_backstab_triggered(); new spell_rog_setup(); new spell_rog_shiv(); - 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_honor_among_thieves_proc(); new spell_rog_turn_the_tables(); diff --git a/src/server/scripts/Spells/spell_warlock.cpp b/src/server/scripts/Spells/spell_warlock.cpp index 4be4717c9e4..38772874e0e 100644 --- a/src/server/scripts/Spells/spell_warlock.cpp +++ b/src/server/scripts/Spells/spell_warlock.cpp @@ -63,7 +63,7 @@ enum WarlockSpells SPELL_WARLOCK_NETHER_PROTECTION_ARCANE = 54373, SPELL_WARLOCK_NETHER_PROTECTION_SHADOW = 54374, SPELL_WARLOCK_NETHER_PROTECTION_NATURE = 54375, - SPELL_WARLOCK_SOULSHATTER = 32835, + SPELL_WARLOCK_SOULSHATTER_EFFECT = 32835, SPELL_WARLOCK_SIPHON_LIFE_HEAL = 63106, SPELL_WARLOCK_UNSTABLE_AFFLICTION_DISPEL = 31117, SPELL_WARLOCK_GLYPH_OF_LIFE_TAP_TRIGGERED = 63321, @@ -1383,17 +1383,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/Spells/spell_warrior.cpp b/src/server/scripts/Spells/spell_warrior.cpp index 3693ccf6eca..929bdb8fac3 100644 --- a/src/server/scripts/Spells/spell_warrior.cpp +++ b/src/server/scripts/Spells/spell_warrior.cpp @@ -1091,7 +1091,7 @@ class spell_warr_vigilance : public SpellScriptLoader target->RemoveAurasDueToSpell(SPELL_GEN_DAMAGE_REDUCTION_AURA); } - target->ResetRedirectThreat(); + target->GetThreatManager().UnregisterRedirectThreat(SPELL_WARRIOR_VIGILANCE_REDIRECT_THREAT, GetCasterGUID()); } bool CheckProc(ProcEventInfo& /*eventInfo*/) diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index 533228bcbb0..402761b9a6c 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -1811,21 +1811,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: |