mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
945 lines
33 KiB
C++
945 lines
33 KiB
C++
/*
|
|
* 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 "ThreatManager.h"
|
|
#include "Creature.h"
|
|
#include "CombatPackets.h"
|
|
#include "CreatureAI.h"
|
|
#include "CreatureGroups.h"
|
|
#include "MapUtils.h"
|
|
#include "MotionMaster.h"
|
|
#include "ObjectAccessor.h"
|
|
#include "Player.h"
|
|
#include "SpellAuraEffects.h"
|
|
#include "SpellMgr.h"
|
|
#include "TemporarySummon.h"
|
|
#include <boost/heap/fibonacci_heap.hpp>
|
|
|
|
const CompareThreatLessThan ThreatManager::CompareThreat;
|
|
|
|
class ThreatManager::Heap : public boost::heap::fibonacci_heap<ThreatReference const*, boost::heap::compare<CompareThreatLessThan>>
|
|
{
|
|
};
|
|
|
|
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();
|
|
_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<ThreatReferenceImpl*>(this)->_handle);
|
|
}
|
|
|
|
void ThreatReference::HeapNotifyDecreased()
|
|
{
|
|
_mgr._sortedThreatList->decrease(static_cast<ThreatReferenceImpl*>(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), _needThreatClearUpdate(false), _updateTimer(THREAT_UPDATE_INTERVAL),
|
|
_sortedThreatList(std::make_unique<Heap>()), _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())
|
|
return;
|
|
|
|
if (_updateTimer <= tdiff)
|
|
{
|
|
if (_needThreatClearUpdate)
|
|
{
|
|
SendClearAllThreatToClients();
|
|
_needThreatClearUpdate = false;
|
|
}
|
|
|
|
if (!IsThreatListEmpty(true))
|
|
UpdateVictim();
|
|
|
|
_updateTimer = THREAT_UPDATE_INTERVAL;
|
|
}
|
|
else
|
|
_updateTimer -= tdiff;
|
|
}
|
|
|
|
void ThreatManager::ResetUpdateTimer()
|
|
{
|
|
_updateTimer = THREAT_UPDATE_INTERVAL;
|
|
}
|
|
|
|
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::ThreatListIterator, std::nullptr_t> ThreatManager::GetUnsortedThreatList() const
|
|
{
|
|
auto itr = _myThreatListEntries.begin();
|
|
auto end = _myThreatListEntries.end();
|
|
std::function<ThreatReference const* ()> generator = [itr, end]() mutable -> ThreatReference const*
|
|
{
|
|
if (itr == end)
|
|
return nullptr;
|
|
|
|
return (itr++)->second;
|
|
};
|
|
return { ThreatListIterator{ std::move(generator) }, nullptr };
|
|
}
|
|
|
|
Trinity::IteratorPair<ThreatManager::ThreatListIterator, std::nullptr_t> ThreatManager::GetSortedThreatList() const
|
|
{
|
|
auto itr = _sortedThreatList->ordered_begin();
|
|
auto end = _sortedThreatList->ordered_end();
|
|
std::function<ThreatReference const* ()> generator = [itr, end]() mutable -> ThreatReference const*
|
|
{
|
|
if (itr == end)
|
|
return nullptr;
|
|
|
|
return *(itr++);
|
|
};
|
|
return { ThreatListIterator{ std::move(generator) }, nullptr };
|
|
}
|
|
|
|
std::vector<ThreatReference*> ThreatManager::GetModifiableThreatList()
|
|
{
|
|
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;
|
|
}
|
|
|
|
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_ATTR2_NO_INITIAL_THREAT))
|
|
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<float>(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()
|
|
{
|
|
Unit::AuraEffectList const& tauntEffects = _owner->GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
|
|
|
|
uint32 tauntPriority = 0; // lowest is highest
|
|
std::unordered_map<ObjectGuid, uint32> tauntStates;
|
|
// Only the last taunt effect applied by something still on our threat list is considered
|
|
for (AuraEffect const* tauntEffect : tauntEffects)
|
|
tauntStates[tauntEffect->GetCasterGUID()] = ++tauntPriority;
|
|
|
|
for (auto const& pair : _myThreatListEntries)
|
|
{
|
|
auto it = tauntStates.find(pair.first);
|
|
if (it != tauntStates.end())
|
|
pair.second->UpdateTauntState(ThreatReference::TauntState(ThreatReference::TAUNT_STATE_TAUNT + tauntStates.size() - 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())
|
|
{
|
|
_needThreatClearUpdate = true;
|
|
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<ObjectGuid> 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());
|
|
}
|
|
|
|
void ThreatManager::RegisterForAIUpdate(ObjectGuid const& guid)
|
|
{
|
|
_needsAIUpdate.push_back(guid);
|
|
}
|
|
|
|
// 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, SpellModOp::Hate, 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) || spell->HasAttribute(SPELL_ATTR4_NO_HELPFUL_THREAT))) // shortcut, none of the calls would do anything
|
|
return;
|
|
if (_threatenedByMe.empty())
|
|
return;
|
|
|
|
std::vector<Creature*> 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(bool (*unitFilter)(Unit const* otherUnit))
|
|
{
|
|
std::vector<ThreatReference*> threatReferencesToRemove;
|
|
threatReferencesToRemove.reserve(_threatenedByMe.size());
|
|
for (auto const& [guid, ref] : _threatenedByMe)
|
|
if (!unitFilter || unitFilter(ref->GetOwner()))
|
|
threatReferencesToRemove.push_back(ref);
|
|
|
|
for (ThreatReference* ref : threatReferencesToRemove)
|
|
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
|
|
{
|
|
if (Creature const* owner = _owner->ToCreature(); owner && owner->IsThreatFeedbackDisabled())
|
|
return;
|
|
|
|
WorldPackets::Combat::ThreatClear threatClear;
|
|
threatClear.UnitGUID = _owner->GetGUID();
|
|
_owner->SendMessageToSet(threatClear.Write(), false);
|
|
}
|
|
|
|
void ThreatManager::SendRemoveToClients(Unit const* victim) const
|
|
{
|
|
if (Creature const* owner = _owner->ToCreature(); owner && owner->IsThreatFeedbackDisabled())
|
|
return;
|
|
|
|
WorldPackets::Combat::ThreatRemove threatRemove;
|
|
threatRemove.UnitGUID = _owner->GetGUID();
|
|
threatRemove.AboutGUID = victim->GetGUID();
|
|
_owner->SendMessageToSet(threatRemove.Write(), false);
|
|
}
|
|
|
|
void ThreatManager::SendThreatListToClients(bool newHighest) const
|
|
{
|
|
if (Creature const* owner = _owner->ToCreature(); owner && owner->IsThreatFeedbackDisabled())
|
|
return;
|
|
|
|
auto fillSharedPacketDataAndSend = [&](auto& packet)
|
|
{
|
|
packet.UnitGUID = _owner->GetGUID();
|
|
packet.ThreatList.reserve(_sortedThreatList->size());
|
|
for (ThreatReference const* ref : *_sortedThreatList)
|
|
{
|
|
if (!ref->IsAvailable())
|
|
continue;
|
|
|
|
WorldPackets::Combat::ThreatInfo threatInfo;
|
|
threatInfo.UnitGUID = ref->GetVictim()->GetGUID();
|
|
threatInfo.Threat = int64(ref->GetThreat() * 100);
|
|
packet.ThreatList.push_back(threatInfo);
|
|
}
|
|
_owner->SendMessageToSet(packet.Write(), false);
|
|
};
|
|
|
|
if (newHighest)
|
|
{
|
|
WorldPackets::Combat::HighestThreatUpdate highestThreatUpdate;
|
|
highestThreatUpdate.HighestThreatGUID = _currentVictimRef->GetVictim()->GetGUID();
|
|
fillSharedPacketDataAndSend(highestThreatUpdate);
|
|
}
|
|
else
|
|
{
|
|
WorldPackets::Combat::ThreatUpdate threatUpdate;
|
|
fillSharedPacketDataAndSend(threatUpdate);
|
|
}
|
|
}
|
|
|
|
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<ThreatReferenceImpl*>(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<ThreatReferenceImpl*>(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<uint32>(100 - totalPct, victimPair.second);
|
|
if (thisPct > 0)
|
|
{
|
|
_redirectInfo.push_back({ victimPair.first, thisPct });
|
|
totalPct += thisPct;
|
|
ASSERT(totalPct <= 100);
|
|
if (totalPct == 100)
|
|
return;
|
|
}
|
|
}
|
|
}
|