/* * 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 . */ #include "ThreatManager.h" #include "Creature.h" #include "CreatureAI.h" #include "CreatureGroups.h" #include "MapUtils.h" #include "MotionMaster.h" #include "Player.h" #include "TemporarySummon.h" #include "Unit.h" #include "UnitAI.h" #include "UnitDefines.h" #include "SpellAuraEffects.h" #include "SpellInfo.h" #include "SpellMgr.h" #include "ObjectAccessor.h" #include "WorldPacket.h" #include #include const CompareThreatLessThan ThreatManager::CompareThreat; class ThreatManager::Heap : public boost::heap::fibonacci_heap> { }; void ThreatReference::AddThreat(float amount) { if (amount == 0.0f) return; _baseAmount = std::max(_baseAmount + amount, 0.0f); if (amount > 0.0f) HeapNotifyIncreased(); else HeapNotifyDecreased(); _mgr._needClientUpdate = true; } void ThreatReference::ScaleThreat(float factor) { if (factor == 1.0f) return; _baseAmount *= factor; if (factor > 1.0f) HeapNotifyIncreased(); else HeapNotifyDecreased(); _mgr._needClientUpdate = true; } void ThreatReference::UpdateOffline() { bool const shouldBeOffline = ShouldBeOffline(); if (shouldBeOffline == IsOffline()) return; if (shouldBeOffline) { _online = ONLINE_STATE_OFFLINE; HeapNotifyDecreased(); _mgr.SendRemoveToClients(_victim); } else { _online = ShouldBeSuppressed() ? ONLINE_STATE_SUPPRESSED : ONLINE_STATE_ONLINE; HeapNotifyIncreased(); _mgr.RegisterForAIUpdate(GetVictim()->GetGUID()); } } /*static*/ bool ThreatReference::FlagsAllowFighting(Unit const* a, Unit const* b) { if (a->GetTypeId() == TYPEID_UNIT && a->ToCreature()->IsTrigger()) return false; if (a->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) { if (b->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC)) return false; } else { if (b->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC)) return false; } return true; } bool ThreatReference::ShouldBeOffline() const { if (!_owner->CanSeeOrDetect(_victim)) return true; if (!_owner->_IsTargetAcceptable(_victim) || !_owner->CanCreatureAttack(_victim)) return true; if (!FlagsAllowFighting(_owner, _victim) || !FlagsAllowFighting(_victim, _owner)) return true; return false; } bool ThreatReference::ShouldBeSuppressed() const { if (IsTaunting()) // a taunting victim can never be suppressed return false; if (_victim->IsImmunedToDamage(_owner->GetMeleeDamageSchoolMask())) return true; if (_victim->HasAuraType(SPELL_AURA_MOD_CONFUSE)) return true; if (_victim->HasBreakableByDamageAuraType(SPELL_AURA_MOD_STUN)) return true; return false; } void ThreatReference::UpdateTauntState(TauntState state) { // Check for SPELL_AURA_MOD_DETAUNT (applied from owner to victim) if (state < TAUNT_STATE_TAUNT && _victim->HasAuraTypeWithCaster(SPELL_AURA_MOD_DETAUNT, _owner->GetGUID())) state = TAUNT_STATE_DETAUNT; if (state == _taunted) return; std::swap(state, _taunted); if (_taunted < state) HeapNotifyDecreased(); else HeapNotifyIncreased(); _mgr._needClientUpdate = true; } void ThreatReference::ClearThreat() { _mgr.ClearThreat(this); } void ThreatReference::UnregisterAndFree() { _owner->GetThreatManager().PurgeThreatListRef(_victim->GetGUID()); _victim->GetThreatManager().PurgeThreatenedByMeRef(_owner->GetGUID()); delete this; } class ThreatReferenceImpl : public ThreatReference { public: explicit ThreatReferenceImpl(ThreatManager* mgr, Unit* victim) : ThreatReference(mgr, victim) { } ThreatManager::Heap::handle_type _handle; }; void ThreatReference::HeapNotifyIncreased() { _mgr._sortedThreatList->increase(static_cast(this)->_handle); } void ThreatReference::HeapNotifyDecreased() { _mgr._sortedThreatList->decrease(static_cast(this)->_handle); } /*static*/ bool ThreatManager::CanHaveThreatList(Unit const* who) { Creature const* cWho = who->ToCreature(); // only creatures can have threat list if (!cWho) return false; // pets, totems and triggers cannot have threat list if (cWho->IsPet() || cWho->IsTotem() || cWho->IsTrigger()) return false; // summons cannot have a threat list if they were summoned by a player if (cWho->HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN)) if (TempSummon const* tWho = cWho->ToTempSummon()) if (tWho->GetSummonerGUID().IsPlayer()) return false; return true; } ThreatManager::ThreatManager(Unit* owner) : _owner(owner), _ownerCanHaveThreatList(false), _needClientUpdate(false), _updateTimer(THREAT_UPDATE_INTERVAL), _sortedThreatList(std::make_unique()), _currentVictimRef(nullptr), _fixateRef(nullptr) { for (int8 i = 0; i < MAX_SPELL_SCHOOL; ++i) _singleSchoolModifiers[i] = 1.0f; } ThreatManager::~ThreatManager() { ASSERT(_myThreatListEntries.empty(), "ThreatManager::~ThreatManager - %s: we still have %zu things threatening us, one of them is %s.", _owner->GetGUID().ToString().c_str(), _myThreatListEntries.size(), _myThreatListEntries.begin()->first.ToString().c_str()); ASSERT(_sortedThreatList->empty(), "ThreatManager::~ThreatManager - %s: we still have %zu things threatening us, one of them is %s.", _owner->GetGUID().ToString().c_str(), _sortedThreatList->size(), (*_sortedThreatList->begin())->GetVictim()->GetGUID().ToString().c_str()); ASSERT(_threatenedByMe.empty(), "ThreatManager::~ThreatManager - %s: we are still threatening %zu things, one of them is %s.", _owner->GetGUID().ToString().c_str(), _threatenedByMe.size(), _threatenedByMe.begin()->first.ToString().c_str()); } void ThreatManager::Initialize() { _ownerCanHaveThreatList = ThreatManager::CanHaveThreatList(_owner); } void ThreatManager::Update(uint32 tdiff) { if (!CanHaveThreatList() || IsThreatListEmpty(true)) return; if (_updateTimer <= tdiff) { UpdateVictim(); _updateTimer = THREAT_UPDATE_INTERVAL; } else _updateTimer -= tdiff; } Unit* ThreatManager::GetCurrentVictim() { if (!_currentVictimRef || _currentVictimRef->ShouldBeOffline()) UpdateVictim(); ASSERT(!_currentVictimRef || _currentVictimRef->IsAvailable()); return _currentVictimRef ? _currentVictimRef->GetVictim() : nullptr; } Unit* ThreatManager::GetLastVictim() const { if (_currentVictimRef && !_currentVictimRef->ShouldBeOffline()) return _currentVictimRef->GetVictim(); return nullptr; } Unit* ThreatManager::GetAnyTarget() const { for (ThreatReference const* ref : *_sortedThreatList) if (!ref->IsOffline()) return ref->GetVictim(); return nullptr; } bool ThreatManager::IsThreatListEmpty(bool includeOffline) const { if (includeOffline) return _sortedThreatList->empty(); for (ThreatReference const* ref : *_sortedThreatList) if (ref->IsAvailable()) return false; return true; } bool ThreatManager::IsThreatenedBy(ObjectGuid const& who, bool includeOffline) const { 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); } float ThreatManager::GetThreat(Unit const* who, bool includeOffline) const { auto it = _myThreatListEntries.find(who->GetGUID()); if (it == _myThreatListEntries.end()) return 0.0f; return (includeOffline || it->second->IsAvailable()) ? it->second->GetThreat() : 0.0f; } size_t ThreatManager::GetThreatListSize() const { return _sortedThreatList->size(); } Trinity::IteratorPair ThreatManager::GetUnsortedThreatList() const { auto itr = _myThreatListEntries.begin(); auto end = _myThreatListEntries.end(); std::function generator = [itr, end]() mutable -> ThreatReference const* { if (itr == end) return nullptr; return (itr++)->second; }; return { ThreatListIterator{ std::move(generator) }, nullptr }; } Trinity::IteratorPair ThreatManager::GetSortedThreatList() const { auto itr = _sortedThreatList->ordered_begin(); auto end = _sortedThreatList->ordered_end(); std::function generator = [itr, end]() mutable -> ThreatReference const* { if (itr == end) return nullptr; return *(itr++); }; return { ThreatListIterator{ std::move(generator) }, nullptr }; } std::vector ThreatManager::GetModifiableThreatList() { std::vector list; list.reserve(_myThreatListEntries.size()); for (auto it = _sortedThreatList->ordered_begin(), end = _sortedThreatList->ordered_end(); it != end; ++it) list.push_back(const_cast(*it)); return list; } bool ThreatManager::IsThreateningAnyone(bool includeOffline) const { if (includeOffline) return !_threatenedByMe.empty(); for (auto const& pair : _threatenedByMe) if (pair.second->IsAvailable()) return true; return false; } bool ThreatManager::IsThreateningTo(ObjectGuid const& who, bool includeOffline) const { 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); } void ThreatManager::EvaluateSuppressed(bool canExpire) { for (auto const& pair : _threatenedByMe) { bool const shouldBeSuppressed = pair.second->ShouldBeSuppressed(); if (pair.second->IsOnline() && shouldBeSuppressed) { pair.second->_online = ThreatReference::ONLINE_STATE_SUPPRESSED; pair.second->HeapNotifyDecreased(); } else if (canExpire && pair.second->IsSuppressed() && !shouldBeSuppressed) { pair.second->_online = ThreatReference::ONLINE_STATE_ONLINE; pair.second->HeapNotifyIncreased(); } } } void ThreatManager::AddThreat(Unit* target, float amount, SpellInfo const* spell, bool ignoreModifiers, bool ignoreRedirects) { // step 1: we can shortcut if the spell has one of the NO_THREAT attrs set - nothing will happen if (spell) { if (spell->HasAttribute(SPELL_ATTR1_NO_THREAT)) return; if (!_owner->IsEngaged() && spell->HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO)) return; } // 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; } // 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())) { 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; } // 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()) { float const origAmount = amount; // intentional iteration by index - there's a nested AddThreat call further down that might cause AI calls which might modify redirect info through spells for (size_t i = 0; i < redirInfo.size(); ++i) { auto const pair = redirInfo[i]; // (victim,pct) 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) { float amountRedirected = CalculatePct(origAmount, pair.second); AddThreat(redirTarget, amountRedirected, spell, true, true); amount -= amountRedirected; } } } } // ensure we're in combat (threat implies combat!) if (!_owner->GetCombatManager().SetInCombatWith(target)) // if this returns false, we're not actually in combat, and thus cannot have threat! return; // typical causes: bad scripts trying to add threat to GMs, dead targets etc // ok, 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()) { ThreatReference* const ref = it->second; // SUPPRESSED threat states don't go back to ONLINE until threat is caused by them (retail behavior) if (ref->GetOnlineState() == ThreatReference::ONLINE_STATE_SUPPRESSED) if (!ref->ShouldBeSuppressed()) { ref->_online = ThreatReference::ONLINE_STATE_ONLINE; ref->HeapNotifyIncreased(); } if (ref->IsOnline()) ref->AddThreat(amount); return; } // ok, we're now in combat - create the threat list reference and push it to the respective managers ThreatReference* ref = new ThreatReferenceImpl(this, target); PutThreatListRef(target->GetGUID(), ref); target->GetThreatManager().PutThreatenedByMeRef(_owner->GetGUID(), ref); ref->UpdateOffline(); if (ref->IsOnline()) // we only add the threat if the ref is currently available ref->AddThreat(amount); if (!_currentVictimRef) UpdateVictim(); else ProcessAIUpdates(); } void ThreatManager::ScaleThreat(Unit* target, float factor) { auto it = _myThreatListEntries.find(target->GetGUID()); if (it != _myThreatListEntries.end()) it->second->ScaleThreat(std::max(factor,0.0f)); } void ThreatManager::MatchUnitThreatToHighestThreat(Unit* target) { if (_sortedThreatList->empty()) return; auto it = _sortedThreatList->ordered_begin(), end = _sortedThreatList->ordered_end(); ThreatReference const* highest = *it; if (!highest->IsAvailable()) return; if (highest->IsTaunting() && ((++it) != end)) // might need to skip this - max threat could be the preceding element (there is only one taunt element) { ThreatReference const* a = *it; if (a->IsAvailable() && a->GetThreat() > highest->GetThreat()) highest = a; } AddThreat(target, highest->GetThreat() - GetThreat(target, true), nullptr, true, true); } void ThreatManager::TauntUpdate() { std::list const& tauntEffects = _owner->GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT); uint32 state = ThreatReference::TAUNT_STATE_TAUNT; std::unordered_map tauntStates; // Only the last taunt effect applied by something still on our threat list is considered for (auto it = tauntEffects.begin(), end = tauntEffects.end(); it != end; ++it) tauntStates[(*it)->GetCasterGUID()] = ThreatReference::TauntState(state++); for (auto const& pair : _myThreatListEntries) { auto it = tauntStates.find(pair.first); if (it != tauntStates.end()) pair.second->UpdateTauntState(it->second); else pair.second->UpdateTauntState(); } // taunt aura update also re-evaluates all suppressed states (retail behavior) EvaluateSuppressed(true); } void ThreatManager::ResetAllThreat() { for (auto const& pair : _myThreatListEntries) pair.second->ScaleThreat(0.0f); } void ThreatManager::ClearThreat(Unit* target) { auto it = _myThreatListEntries.find(target->GetGUID()); if (it != _myThreatListEntries.end()) ClearThreat(it->second); } void ThreatManager::ClearThreat(ThreatReference* ref) { SendRemoveToClients(ref->_victim); ref->UnregisterAndFree(); if (!_currentVictimRef) UpdateVictim(); } void ThreatManager::ClearAllThreat() { if (!_myThreatListEntries.empty()) { SendClearAllThreatToClients(); do _myThreatListEntries.begin()->second->UnregisterAndFree(); while (!_myThreatListEntries.empty()); } } void ThreatManager::FixateTarget(Unit* target) { if (target) { auto it = _myThreatListEntries.find(target->GetGUID()); if (it != _myThreatListEntries.end()) { _fixateRef = it->second; return; } } _fixateRef = nullptr; } Unit* ThreatManager::GetFixateTarget() const { if (_fixateRef) return _fixateRef->GetVictim(); else return nullptr; } void ThreatManager::UpdateVictim() { ThreatReference const* const newVictim = ReselectVictim(); bool const newHighest = newVictim && (newVictim != _currentVictimRef); _currentVictimRef = newVictim; if (newHighest || _needClientUpdate) { SendThreatListToClients(newHighest); _needClientUpdate = false; } ProcessAIUpdates(); } ThreatReference const* ThreatManager::ReselectVictim() { if (_sortedThreatList->empty()) return nullptr; for (auto const& pair : _myThreatListEntries) pair.second->UpdateOffline(); // AI notifies are processed in ::UpdateVictim caller // fixated target is always preferred if (_fixateRef && _fixateRef->IsAvailable()) return _fixateRef; ThreatReference const* oldVictimRef = _currentVictimRef; if (oldVictimRef && oldVictimRef->IsOffline()) 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 ABORT_MSG("Current victim not found in sorted threat list even though it has a reference - manager desync!"); return nullptr; } void ThreatManager::ProcessAIUpdates() { CreatureAI* ai = ASSERT_NOTNULL(_owner->ToCreature())->AI(); std::vector v(std::move(_needsAIUpdate)); // _needsAIUpdate is now empty in case this triggers a recursive call if (!ai) return; for (ObjectGuid const& guid : v) if (ThreatReference const* ref = Trinity::Containers::MapGetValuePtr(_myThreatListEntries, guid)) ai->JustStartedThreateningMe(ref->GetVictim()); } // 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) { if (SpellThreatEntry const* threatEntry = sSpellMgr->GetSpellThreatEntry(spell->Id)) if (threatEntry->pctMod != 1.0f) // flat/AP modifiers handled in Spell::HandleThreatSpells threat *= threatEntry->pctMod; if (Player* modOwner = victim->GetSpellModOwner()) modOwner->ApplySpellMod(spell->Id, SPELLMOD_THREAT, threat); } // modifiers by effect school ThreatManager const& victimMgr = victim->GetThreatManager(); SpellSchoolMask const mask = spell ? spell->GetSchoolMask() : SPELL_SCHOOL_MASK_NORMAL; switch (mask) { case SPELL_SCHOOL_MASK_NORMAL: threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NORMAL]; break; case SPELL_SCHOOL_MASK_HOLY: threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_HOLY]; break; case SPELL_SCHOOL_MASK_FIRE: threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FIRE]; break; case SPELL_SCHOOL_MASK_NATURE: threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NATURE]; break; case SPELL_SCHOOL_MASK_FROST: threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FROST]; break; case SPELL_SCHOOL_MASK_SHADOW: threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_SHADOW]; break; case SPELL_SCHOOL_MASK_ARCANE: threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_ARCANE]; break; default: { auto it = victimMgr._multiSchoolModifiers.find(mask); if (it != victimMgr._multiSchoolModifiers.end()) { threat *= it->second; break; } float mod = victim->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, mask); victimMgr._multiSchoolModifiers[mask] = mod; threat *= mod; break; } } return threat; } void ThreatManager::ForwardThreatForAssistingMe(Unit* assistant, float baseAmount, SpellInfo const* spell, bool ignoreModifiers) { if (spell && spell->HasAttribute(SPELL_ATTR1_NO_THREAT)) // shortcut, none of the calls would do anything return; if (_threatenedByMe.empty()) return; std::vector canBeThreatened, cannotBeThreatened; for (auto const& pair : _threatenedByMe) { Creature* owner = pair.second->GetOwner(); if (!owner->HasUnitState(UNIT_STATE_CONTROLLED)) canBeThreatened.push_back(owner); else cannotBeThreatened.push_back(owner); } if (!canBeThreatened.empty()) // targets under CC cannot gain assist threat - split evenly among the rest { float const perTarget = baseAmount / canBeThreatened.size(); for (Creature* threatened : canBeThreatened) threatened->GetThreatManager().AddThreat(assistant, perTarget, spell, ignoreModifiers); } for (Creature* threatened : cannotBeThreatened) threatened->GetThreatManager().AddThreat(assistant, 0.0f, spell, true); } void ThreatManager::RemoveMeFromThreatLists() { while (!_threatenedByMe.empty()) { auto& ref = _threatenedByMe.begin()->second; ref->_mgr.ClearThreat(_owner); } } void ThreatManager::UpdateMyTempModifiers() { int32 mod = 0; for (AuraEffect const* eff : _owner->GetAuraEffectsByType(SPELL_AURA_MOD_TOTAL_THREAT)) mod += eff->GetAmount(); if (_threatenedByMe.empty()) return; auto it = _threatenedByMe.begin(); bool const isIncrease = (it->second->_tempModifier < mod); do { it->second->_tempModifier = mod; if (isIncrease) it->second->HeapNotifyIncreased(); else it->second->HeapNotifyDecreased(); } while ((++it) != _threatenedByMe.end()); } 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::RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct) { _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::UnregisterRedirectThreat(uint32 spellId, ObjectGuid const& victim) { 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::SendClearAllThreatToClients() const { WorldPacket data(SMSG_THREAT_CLEAR, 8); data << _owner->GetPackGUID(); _owner->SendMessageToSet(&data, false); } void ThreatManager::SendRemoveToClients(Unit const* victim) const { WorldPacket data(SMSG_THREAT_REMOVE, 16); data << _owner->GetPackGUID(); data << victim->GetPackGUID(); _owner->SendMessageToSet(&data, false); } void ThreatManager::SendThreatListToClients(bool newHighest) const { WorldPacket data(newHighest ? SMSG_HIGHEST_THREAT_UPDATE : SMSG_THREAT_UPDATE, (_sortedThreatList->size() + 2) * 8); // guess data << _owner->GetPackGUID(); if (newHighest) data << _currentVictimRef->GetVictim()->GetPackGUID(); size_t countPos = data.wpos(); data << uint32(0); // placeholder uint32 count = 0; for (ThreatReference const* ref : *_sortedThreatList) { if (!ref->IsAvailable()) continue; data << ref->GetVictim()->GetPackGUID(); data << uint32(ref->GetThreat() * 100); ++count; } data.put(countPos, count); _owner->SendMessageToSet(&data, false); } void ThreatManager::PutThreatListRef(ObjectGuid const& guid, ThreatReference* ref) { _needClientUpdate = true; auto& inMap = _myThreatListEntries[guid]; ASSERT(!inMap, "Duplicate threat reference at %p being inserted on %s for %s - memory leak!", ref, _owner->GetGUID().ToString().c_str(), guid.ToString().c_str()); inMap = ref; static_cast(ref)->_handle = _sortedThreatList->push(ref); } void ThreatManager::PurgeThreatListRef(ObjectGuid const& guid) { auto it = _myThreatListEntries.find(guid); if (it == _myThreatListEntries.end()) return; ThreatReference* ref = it->second; _myThreatListEntries.erase(it); _sortedThreatList->erase(static_cast(ref)->_handle); if (_fixateRef == ref) _fixateRef = nullptr; if (_currentVictimRef == ref) _currentVictimRef = nullptr; } void ThreatManager::PutThreatenedByMeRef(ObjectGuid const& guid, ThreatReference* ref) { auto& inMap = _threatenedByMe[guid]; ASSERT(!inMap, "Duplicate threatened-by-me reference at %p being inserted on %s for %s - memory leak!", ref, _owner->GetGUID().ToString().c_str(), guid.ToString().c_str()); inMap = ref; } void ThreatManager::PurgeThreatenedByMeRef(ObjectGuid const& guid) { auto it = _threatenedByMe.find(guid); if (it != _threatenedByMe.end()) _threatenedByMe.erase(it); } void ThreatManager::UpdateRedirectInfo() { _redirectInfo.clear(); uint32 totalPct = 0; for (auto const& pair : _redirectRegistry) // (spellid, victim -> pct) for (auto const& victimPair : pair.second) // (victim,pct) { uint32 thisPct = std::min(100 - totalPct, victimPair.second); if (thisPct > 0) { _redirectInfo.push_back({ victimPair.first, thisPct }); totalPct += thisPct; ASSERT(totalPct <= 100); if (totalPct == 100) return; } } }