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