refactor(Core/Combat): Port TrinityCore heap-based threat system (#24715)

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: Treeston <treeston.mmoc@gmail.com>
Co-authored-by: killerwife <killerwife@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
blinkysc
2026-03-18 13:36:59 -05:00
committed by GitHub
parent c80a0f1fad
commit 984baa92dd
101 changed files with 7045 additions and 2659 deletions
+460
View File
@@ -0,0 +1,460 @@
/*
* This file is part of the AzerothCore 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/>.
*/
/*
* Ported from TrinityCore with modifications for AzerothCore
* Original authors:
* Treeston <treeston.mmoc@gmail.com> - Combat/Threat system rewrite (PR #19930)
* Shauren <shauren.trinity@gmail.com>
* Spp <spp@jorge.gr>
*/
#include "CombatManager.h"
#include "Containers.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "ThreatManager.h"
#include "Unit.h"
#include "UnitAI.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 (!a->InSamePhase(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;
// ... both units must be allowed to enter combat
if (a->IsCombatDisallowed() || b->IsCombatDisallowed())
return false;
if (a->IsFriendlyTo(b) || b->IsFriendlyTo(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->GetThreatMgr().ClearThreat(second);
second->GetThreatMgr().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)
if (UnitAI* firstAI = first->GetAI())
firstAI->JustExitedCombat();
if (needSecondAI)
if (UnitAI* secondAI = second->GetAI())
secondAI->JustExitedCombat();
// ...and finally clean up the reference object
delete this;
}
void CombatReference::Refresh()
{
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 CombatReference::SuppressFor(Unit* who)
{
Suppress(who);
if (who->GetCombatManager().UpdateOwnerCombatState())
if (UnitAI* ai = who->GetAI())
ai->JustExitedCombat();
}
bool PvPCombatReference::Update(uint32 tdiff)
{
if (_combatTimer <= tdiff)
return false;
_combatTimer -= tdiff;
return true;
}
void PvPCombatReference::RefreshTimer()
{
_combatTimer = PVP_COMBAT_TIMEOUT;
}
CombatManager::~CombatManager()
{
ASSERT(_pveRefs.empty(), "CombatManager::~CombatManager - %s: we still have %zu PvE combat references, one of them is with %s", _owner->GetGUID().ToString().c_str(), _pveRefs.size(), _pveRefs.begin()->first.ToString().c_str());
ASSERT(_pvpRefs.empty(), "CombatManager::~CombatManager - %s: we still have %zu PvP combat references, one of them is with %s", _owner->GetGUID().ToString().c_str(), _pvpRefs.size(), _pvpRefs.begin()->first.ToString().c_str());
}
void CombatManager::Update(uint32 tdiff)
{
// Update evade timer
if (_evadeTimer)
{
if (_evadeTimer <= tdiff)
{
_evadeTimer = 0;
// Timer expired - trigger evade check via AI
if (UnitAI* ai = _owner->GetAI())
ai->EvadeTimerExpired();
}
else
_evadeTimer -= 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::HasPvECombat() const
{
for (auto const& [guid, ref] : _pveRefs)
if (!ref->IsSuppressedFor(_owner))
return true;
return false;
}
bool CombatManager::HasPvECombatWithPlayers() const
{
for (std::pair<ObjectGuid const, CombatReference*> const& reference : _pveRefs)
if (!reference.second->IsSuppressedFor(_owner) && reference.second->GetOther(_owner)->IsPlayer())
return true;
return false;
}
bool CombatManager::HasPvPCombat() const
{
for (auto const& pair : _pvpRefs)
if (!pair.second->IsSuppressedFor(_owner))
return true;
return false;
}
Unit* CombatManager::GetAnyTarget() const
{
for (auto const& pair : _pveRefs)
if (!pair.second->IsSuppressedFor(_owner))
return pair.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, bool addSecondUnitSuppressed)
{
// Are we already in combat? If yes, refresh pvp combat
if (PvPCombatReference* existingPvpRef = Acore::Containers::MapGetValuePtr(_pvpRefs, who->GetGUID()))
{
existingPvpRef->RefreshTimer();
existingPvpRef->Refresh();
return true;
}
if (CombatReference* existingPveRef = Acore::Containers::MapGetValuePtr(_pveRefs, who->GetGUID()))
{
existingPveRef->Refresh();
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);
if (addSecondUnitSuppressed)
ref->Suppress(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 IsInCombatWith(who);
}
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_PLAYER_CONTROLLED)) ||
(_owner->IsImmuneToNPC() && !target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)))
continue;
SetInCombatWith(target);
}
}
for (auto& ref : mgr._pvpRefs)
{
Unit* target = ref.second->GetOther(who);
if ((_owner->IsImmuneToPC() && target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) ||
(_owner->IsImmuneToNPC() && !target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)))
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 (UnitAI* ownerAI = _owner->GetAI())
ownerAI->JustExitedCombat();
}
void CombatManager::EndAllPvECombat()
{
// cannot have threat without combat
_owner->GetThreatMgr().RemoveMeFromThreatLists();
_owner->GetThreatMgr().ClearAllThreat();
while (!_pveRefs.empty())
_pveRefs.begin()->second->EndCombat();
}
void CombatManager::RevalidateCombat()
{
auto it = _pveRefs.begin(), end = _pveRefs.end();
while (it != end)
{
CombatReference* const ref = it->second;
if (!CanBeginCombat(_owner, ref->GetOther(_owner)))
{
it = _pveRefs.erase(it), end = _pveRefs.end(); // erase manually here to avoid iterator invalidation
ref->EndCombat();
}
else
++it;
}
auto it2 = _pvpRefs.begin(), end2 = _pvpRefs.end();
while (it2 != end2)
{
CombatReference* const ref = it2->second;
if (!CanBeginCombat(_owner, ref->GetOther(_owner)))
{
it2 = _pvpRefs.erase(it2), end2 = _pvpRefs.end(); // erase manually here to avoid iterator invalidation
ref->EndCombat();
}
else
++it2;
}
}
void CombatManager::EndAllPvPCombat()
{
while (!_pvpRefs.empty())
_pvpRefs.begin()->second->EndCombat();
}
/*static*/ void CombatManager::NotifyAICombat(Unit* me, Unit* other)
{
if (UnitAI* ai = me->GetAI())
ai->JustEnteredCombat(other);
}
void CombatManager::PutReference(ObjectGuid const& guid, CombatReference* ref)
{
if (ref->_isPvP)
{
auto& inMap = _pvpRefs[guid];
ASSERT(!inMap, "Duplicate combat state at %p being inserted for %s vs %s - memory leak!", (void*)ref, _owner->GetGUID().ToString().c_str(), guid.ToString().c_str());
inMap = static_cast<PvPCombatReference*>(ref);
}
else
{
auto& inMap = _pveRefs[guid];
ASSERT(!inMap, "Duplicate combat state at %p being inserted for %s vs %s - memory leak!", (void*)ref, _owner->GetGUID().ToString().c_str(), guid.ToString().c_str());
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->SetUnitFlag(UNIT_FLAG_IN_COMBAT);
_owner->AtEnterCombat();
if (!_owner->IsCreature())
_owner->AtEngage(GetAnyTarget());
if (Player* player = _owner->ToPlayer())
sScriptMgr->OnPlayerEnterCombat(player, GetAnyTarget());
}
else
{
_owner->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
_owner->AtExitCombat();
if (!_owner->IsCreature())
_owner->AtDisengage();
}
if (Unit* master = _owner->GetCharmerOrOwner())
master->UpdatePetCombatState();
return true;
}
void CombatManager::SetEvadeState(EvadeState state)
{
if (_evadeState == state)
return;
if (state == EVADE_STATE_NONE)
_evadeTimer = 0;
_evadeState = state;
// Propagate evade state to controlled units (pets, guardians, etc.)
if (!_owner->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
{
for (Unit* controlled : _owner->m_Controlled)
controlled->GetCombatManager().SetEvadeState(state);
}
}
void CombatManager::StopEvade()
{
_evadeTimer = 0;
SetEvadeState(EVADE_STATE_NONE);
}
+167
View File
@@ -0,0 +1,167 @@
/*
* This file is part of the AzerothCore 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 ACORE_COMBATMANAGER_H
#define ACORE_COMBATMANAGER_H
#include "Common.h"
#include "ObjectGuid.h"
#include "Position.h"
#include <unordered_map>
class Unit;
enum EvadeState : uint8
{
EVADE_STATE_NONE = 0, // Not evading
EVADE_STATE_COMBAT = 1, // In combat evade (target unreachable in dungeon)
EVADE_STATE_HOME = 2, // Running home after combat ends
};
/********************************************************************************************************************************************************\
* 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. *
* *
* 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: *
* - 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. *
\********************************************************************************************************************************************************/
struct AC_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();
// suppressed combat refs do not generate a combat state for one side of the relation
// (used by: vanish, feign death and launched out of combat but not yet landed spell missiles)
void SuppressFor(Unit* who);
bool IsSuppressedFor(Unit const* who) const { return (who == first) ? _suppressFirst : _suppressSecond; }
CombatReference(CombatReference const&) = delete;
CombatReference& operator=(CombatReference const&) = delete;
protected:
CombatReference(Unit* a, Unit* b, bool pvp = false) : first(a), second(b), _isPvP(pvp) { }
void Refresh();
void Suppress(Unit* who) { (who == first ? _suppressFirst : _suppressSecond) = true; }
bool _suppressFirst = false;
bool _suppressSecond = false;
friend class CombatManager;
};
struct AC_GAME_API PvPCombatReference : public CombatReference
{
static const uint32 PVP_COMBAT_TIMEOUT = 5 * IN_MILLISECONDS;
private:
PvPCombatReference(Unit* first, Unit* second) : CombatReference(first, second, true) { }
bool Update(uint32 tdiff);
void RefreshTimer();
uint32 _combatTimer = PVP_COMBAT_TIMEOUT;
friend class CombatManager;
};
class AC_GAME_API CombatManager
{
public:
static bool CanBeginCombat(Unit const* a, Unit const* b);
static constexpr uint32 EVADE_TIMER_DURATION = 10 * IN_MILLISECONDS;
static constexpr uint32 EVADE_REGEN_DELAY = 5 * IN_MILLISECONDS;
CombatManager(Unit* owner) : _owner(owner), _evadeState(EVADE_STATE_NONE), _evadeTimer(0) { }
~CombatManager();
void Update(uint32 tdiff); // called from Unit::Update
Unit* GetOwner() const { return _owner; }
// Evade state handling
EvadeState GetEvadeState() const { return _evadeState; }
void SetEvadeState(EvadeState state);
bool IsEvadingHome() const { return _evadeState == EVADE_STATE_HOME; }
bool IsInEvadeMode() const { return _evadeTimer > 0 || _evadeState != EVADE_STATE_NONE; }
bool IsEvadeRegen() const { return (_evadeTimer > 0 && _evadeTimer <= EVADE_REGEN_DELAY) || _evadeState != EVADE_STATE_NONE; }
void StartEvadeTimer() { _evadeTimer = EVADE_TIMER_DURATION; }
void ContinueEvadeRegen() { _evadeTimer = EVADE_REGEN_DELAY; }
void StopEvade();
bool HasCombat() const { return HasPvECombat() || HasPvPCombat(); }
bool HasPvECombat() const;
bool HasPvECombatWithPlayers() const;
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 addSecondUnitSuppressed = false);
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 RevalidateCombat();
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;
EvadeState _evadeState;
uint32 _evadeTimer;
std::unordered_map<ObjectGuid, CombatReference*> _pveRefs;
std::unordered_map<ObjectGuid, PvPCombatReference*> _pvpRefs;
friend struct CombatReference;
friend struct PvPCombatReference;
};
#endif
-250
View File
@@ -1,250 +0,0 @@
/*
* This file is part of the AzerothCore 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 "HostileRefMgr.h"
#include "CreatureAI.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "ThreatMgr.h"
#include "Unit.h"
HostileRefMgr::~HostileRefMgr()
{
deleteReferences();
}
//=================================================
// 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 HostileRefMgr::threatAssist(Unit* victim, float baseThreat, SpellInfo const* threatSpell)
{
if (getSize() == 0)
return;
HostileReference* ref = getFirst();
float threat = ThreatCalcHelper::calcThreat(victim, baseThreat, (threatSpell ? threatSpell->GetSchoolMask() : SPELL_SCHOOL_MASK_NORMAL), threatSpell);
threat /= getSize();
while (ref)
{
Unit* refOwner = ref->GetSource()->GetOwner();
if (ThreatCalcHelper::isValidProcess(victim, refOwner, threatSpell))
{
if (Creature* hatingCreature = refOwner->ToCreature())
{
if (hatingCreature->IsAIEnabled)
{
hatingCreature->AI()->CalculateThreat(victim, threat, threatSpell);
}
}
ref->GetSource()->DoAddThreat(victim, threat);
}
ref = ref->next();
}
}
//=================================================
void HostileRefMgr::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 HostileRefMgr::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 HostileRefMgr::setOnlineOfflineState(bool isOnline)
{
HostileReference* ref = getFirst();
while (ref)
{
ref->setOnlineOfflineState(isOnline);
ref = ref->next();
}
}
//=================================================
// The online / offline status is calculated and set
void HostileRefMgr::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 HostileRefMgr::deleteReferences(bool removeFromMap /*= false*/)
{
std::vector<Creature*> creaturesToEvade;
HostileReference* ref = getFirst();
while (ref)
{
HostileReference* nextRef = ref->next();
ref->removeReference();
if (removeFromMap)
{
if (ThreatMgr const* threatMgr = ref->GetSource())
{
if (threatMgr->areThreatListsEmpty())
{
if (Creature* creature = threatMgr->GetOwner()->ToCreature())
{
creaturesToEvade.push_back(creature);
}
}
}
}
delete ref;
ref = nextRef;
}
for (Creature* creature : creaturesToEvade)
{
creature->AI()->EnterEvadeMode();
}
}
//=================================================
// delete one reference, defined by faction
void HostileRefMgr::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 one reference, defined by Unit
void HostileRefMgr::deleteReference(Unit* creature)
{
HostileReference* ref = getFirst();
while (ref)
{
HostileReference* nextRef = ref->next();
if (ref->GetSource()->GetOwner() == creature)
{
ref->removeReference();
delete ref;
break;
}
ref = nextRef;
}
}
//=================================================
// delete all references out of specified range
void HostileRefMgr::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;
}
}
//=================================================
// set state for one reference, defined by Unit
void HostileRefMgr::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 HostileRefMgr::UpdateVisibility(bool checkThreat)
{
HostileReference* ref = getFirst();
while (ref)
{
HostileReference* nextRef = ref->next();
if ((!checkThreat || ref->GetSource()->GetThreatListSize() <= 1))
{
nextRef = ref->next();
ref->removeReference();
delete ref;
}
ref = nextRef;
}
}
-74
View File
@@ -1,74 +0,0 @@
/*
* This file is part of the AzerothCore 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 "RefMgr.h"
class Unit;
class ThreatMgr;
class HostileReference;
class SpellInfo;
//=================================================
class HostileRefMgr : public RefMgr<Unit, ThreatMgr>
{
private:
Unit* iOwner;
public:
explicit HostileRefMgr(Unit* owner) { iOwner = owner; }
~HostileRefMgr() override;
Unit* GetOwner() { 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(bool removeFromMap = false);
// Remove specific faction references
void deleteReferencesForFaction(uint32 faction);
// pussywizard: for combat bugs
void deleteReferencesOutOfRange(float range);
HostileReference* getFirst() { return ((HostileReference*) RefMgr<Unit, ThreatMgr>::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(bool checkThreat);
};
//=================================================
#endif
+947
View File
@@ -0,0 +1,947 @@
/*
* This file is part of the AzerothCore 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/>.
*/
/*
* Ported from TrinityCore with modifications for AzerothCore
* Original authors:
* Treeston <treeston.mmoc@gmail.com> - Combat/Threat system rewrite (PR #19930)
* Shauren <shauren.trinity@gmail.com>
* Spp <spp@jorge.gr>
*/
#include "ThreatManager.h"
#include "CombatManager.h"
#include "Containers.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "CreatureGroups.h"
#include "MotionMaster.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "SpellAuraEffects.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "TemporarySummon.h"
#include "Unit.h"
#include "UnitAI.h"
#include "WorldPacket.h"
#include <algorithm>
#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->IsCreature() && 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->GetThreatMgr().PurgeThreatListRef(_victim->GetGUID());
_victim->GetThreatMgr().PurgeThreatenedByMeRef(_owner->GetGUID());
delete this;
}
class ThreatReferenceImpl : public ThreatReference
{
public:
explicit ThreatReferenceImpl(ThreatManager* mgr, Unit* victim) : ThreatReference(mgr, victim)
{
// Only creatures can have threat lists (verified by CanHaveThreatList)
ASSERT(mgr->_owner->ToCreature());
}
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)
{
if (!who)
return false;
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<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() || 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();
}
uint32 ThreatManager::GetThreatListPlayerCount(bool includeOffline/* = false*/) const
{
uint32 returnValue = 0;
for (ThreatReference const* ref : *_sortedThreatList)
{
if (!includeOffline && !ref->IsAvailable())
continue;
if (ref->GetVictim()->IsPlayer())
++returnValue;
}
return returnValue;
}
Acore::IteratorPair<ThreatManager::ThreatListIterator> 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;
};
std::function<ThreatReference const* ()> endGenerator = []() -> ThreatReference const* { return nullptr; };
return { ThreatListIterator{ std::move(generator) }, ThreatListIterator{ std::move(endGenerator) } };
}
Acore::IteratorPair<ThreatManager::ThreatListIterator> 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++);
};
std::function<ThreatReference const* ()> endGenerator = []() -> ThreatReference const* { return nullptr; };
return { ThreatListIterator{ std::move(generator) }, ThreatListIterator{ std::move(endGenerator) } };
}
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_ATTR3_SUPPRESS_TARGET_PROCS))
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 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->GetThreatMgr()._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->GetThreatMgr()._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->GetThreatMgr().PutThreatenedByMeRef(_owner->GetGUID(), ref);
ref->UpdateOffline();
if (ref->IsOnline()) // we only add the threat if the ref is currently available
ref->AddThreat(amount);
// Note: AI update registration is handled inside UpdateOffline() when transitioning from OFFLINE
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 state = ThreatReference::TAUNT_STATE_TAUNT;
std::unordered_map<ObjectGuid, ThreatReference::TauntState> 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::SetTauntStateForTesting(
Unit* target, uint32 state)
{
auto it = _myThreatListEntries.find(target->GetGUID());
if (it != _myThreatListEntries.end())
it->second->UpdateTauntState(
static_cast<ThreatReference::TauntState>(state));
}
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
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
ASSERT(false && "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 = Acore::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->GetThreatMgr();
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<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->GetThreatMgr().AddThreat(assistant, perTarget, spell, ignoreModifiers);
}
for (Creature* threatened : cannotBeThreatened)
threatened->GetThreatMgr().AddThreat(assistant, 0.0f, spell, true);
}
void ThreatManager::ResetAllMyThreatOnOthers()
{
for (auto const& pair : _threatenedByMe)
pair.second->ScaleThreat(0.0f);
}
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);
if (victimMap.empty())
_redirectRegistry.erase(it);
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<uint32>(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!", (void*)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!", (void*)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;
}
}
}
+329
View File
@@ -0,0 +1,329 @@
/*
* This file is part of the AzerothCore 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/>.
*/
/*
* Ported from TrinityCore with modifications for AzerothCore
* Original authors:
* Treeston <treeston.mmoc@gmail.com> - Combat/Threat system rewrite (PR #19930)
* Shauren <shauren.trinity@gmail.com>
* Spp <spp@jorge.gr>
*/
#ifndef ACORE_THREATMANAGER_H
#define ACORE_THREATMANAGER_H
#include "Common.h"
#include "IteratorPair.h"
#include "ObjectGuid.h"
#include "SharedDefines.h"
#include <array>
#include <functional>
#include <memory>
#include <unordered_map>
#include <vector>
class Creature;
class Unit;
class SpellInfo;
/********************************************************************************************************************************************************\
* 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. *
* *
* 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 inopportune. This is used for targets under immunity effects and damage-breaking CC. *
* 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, and GetCurrentVictim 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. *
* Beyond that, ThreatManager has a variety of helpers and notifiers, which are documented inline below. *
* *
* SPECIAL NOTE: Please be aware that any iterator may be invalidated if you modify a ThreatReference. The heap holds const pointers for a reason, but *
* that doesn't mean you're scot free. A variety of actions (casting spells, teleporting units, and so forth) can cause changes to *
* the threat list. Use with care - or default to GetModifiableThreatList(), which inherently copies entries. *
\********************************************************************************************************************************************************/
class ThreatReference;
struct CompareThreatLessThan
{
CompareThreatLessThan() {}
bool operator()(ThreatReference const* a, ThreatReference const* b) const;
};
class AC_GAME_API ThreatManager
{
public:
class Heap;
class ThreatListIterator;
static const uint32 THREAT_UPDATE_INTERVAL = 1000u;
static bool CanHaveThreatList(Unit const* who);
ThreatManager(Unit* owner);
~ThreatManager();
// called from ::Create methods just after construction (once all fields on owner have been populated)
void Initialize();
// called from Creature::Update (only creatures can have their own threat list)
void Update(uint32 tdiff);
// never nullptr
Unit* GetOwner() const { return _owner; }
// can our owner have a threat list?
bool CanHaveThreatList() const { return _ownerCanHaveThreatList; }
// returns the current victim - this can be nullptr if owner's threat list is empty, or has only offline targets
Unit* GetCurrentVictim();
// const-friendly version that doesn't update, returns cached victim
Unit* GetLastVictim() const;
// returns an arbitrary non-offline victim from owner's threat list if one exists, nullptr otherwise
Unit* GetAnyTarget() const;
// 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;
uint32 GetThreatListPlayerCount(bool includeOffline = false) const;
// fastest of the three threat list getters - gets the threat list in "arbitrary" order
Acore::IteratorPair<ThreatListIterator> GetUnsortedThreatList() const;
// slightly slower than GetUnsorted, but sorted - only use it if you need the sorted property
Acore::IteratorPair<ThreatListIterator> GetSortedThreatList() const;
// slowest of the three threat list getters (by far), but lets you modify the threat references - this is also sorted
std::vector<ThreatReference*> GetModifiableThreatList();
// 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 its owner may now be suppressed on others' threat lists
void EvaluateSuppressed(bool canExpire = false);
///== 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);
void ClearThreat(ThreatReference* ref);
// Removes all targets from the threat list (will cause evade in UpdateVictim if called)
void ClearAllThreat();
// Fixate on the passed target; this target will always be selected until the fixate is cleared
void FixateTarget(Unit* target);
void ClearFixate() { FixateTarget(nullptr); }
Unit* GetFixateTarget() const;
///== AFFECT OTHERS' THREAT LISTS ==
// 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);
// zeros this unit's threat on every creature's threat list where it appears (used for Feign Death, Sanctuary)
void ResetAllMyThreatOnOthers();
// 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();
// Test-only: directly set taunt state on a threat ref
// Uses uint32 to avoid incomplete-type issue with ThreatReference
void SetTauntStateForTesting(Unit* target, uint32 state);
///== REDIRECT SYSTEM ==
void RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct);
void UnregisterRedirectThreat(uint32 spellId);
void UnregisterRedirectThreat(uint32 spellId, ObjectGuid const& victim);
bool HasRedirects() const { return !_redirectRegistry.empty(); }
void ResetAllRedirects() { _redirectRegistry.clear(); UpdateRedirectInfo(); }
private:
Unit* const _owner;
bool _ownerCanHaveThreatList;
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);
void SendClearAllThreatToClients() const;
void SendRemoveToClients(Unit const* victim) const;
void SendThreatListToClients(bool newHighest) const;
///== MY THREAT LIST ==
void PutThreatListRef(ObjectGuid const& guid, ThreatReference* ref);
void PurgeThreatListRef(ObjectGuid const& guid);
bool _needClientUpdate;
uint32 _updateTimer;
std::unique_ptr<Heap> _sortedThreatList;
std::unordered_map<ObjectGuid, ThreatReference*> _myThreatListEntries;
void ProcessAIUpdates();
void RegisterForAIUpdate(ObjectGuid const& guid) { _needsAIUpdate.push_back(guid); }
std::vector<ObjectGuid> _needsAIUpdate;
void UpdateVictim();
ThreatReference const* ReselectVictim();
ThreatReference const* _currentVictimRef;
ThreatReference const* _fixateRef;
///== OTHERS' THREAT LISTS ==
void PutThreatenedByMeRef(ObjectGuid const& guid, ThreatReference* ref);
void PurgeThreatenedByMeRef(ObjectGuid const& guid);
std::unordered_map<ObjectGuid, ThreatReference*> _threatenedByMe;
std::array<float, MAX_SPELL_SCHOOL> _singleSchoolModifiers;
mutable std::unordered_map<std::underlying_type<SpellSchoolMask>::type, float> _multiSchoolModifiers;
void UpdateRedirectInfo();
std::vector<std::pair<ObjectGuid, uint32>> _redirectInfo;
std::unordered_map<uint32, std::unordered_map<ObjectGuid, uint32>> _redirectRegistry;
public:
ThreatManager(ThreatManager const&) = delete;
ThreatManager& operator=(ThreatManager const&) = delete;
class ThreatListIterator
{
private:
std::function<ThreatReference const* ()> _generator;
ThreatReference const* _current;
friend ThreatManager;
explicit ThreatListIterator(std::function<ThreatReference const* ()>&& generator)
: _generator(std::move(generator)), _current(_generator()) {}
public:
ThreatReference const* operator*() const { return _current; }
ThreatReference const* operator->() const { return _current; }
ThreatListIterator& operator++() { _current = _generator(); return *this; }
bool operator==(ThreatListIterator const& o) const { return _current == o._current; }
bool operator!=(ThreatListIterator const& o) const { return _current != o._current; }
bool operator==(std::nullptr_t) const { return _current == nullptr; }
bool operator!=(std::nullptr_t) const { return _current != nullptr; }
};
friend class ThreatReference;
friend class ThreatReferenceImpl;
friend struct CompareThreatLessThan;
friend class debug_commandscript;
};
class AC_GAME_API ThreatReference
{
public:
enum TauntState : uint32 { TAUNT_STATE_DETAUNT = 0, TAUNT_STATE_NONE = 1, TAUNT_STATE_TAUNT = 2 };
enum OnlineState { ONLINE_STATE_ONLINE = 2, ONLINE_STATE_SUPPRESSED = 1, ONLINE_STATE_OFFLINE = 0 };
Creature* GetOwner() const { return _owner; }
Unit* GetVictim() const { return _victim; }
float GetThreat() const { return std::max<float>(_baseAmount + static_cast<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 IsSuppressed() const { return (_online == ONLINE_STATE_SUPPRESSED); }
bool IsOffline() const { return (_online <= ONLINE_STATE_OFFLINE); }
TauntState GetTauntState() const { return IsTaunting() ? TAUNT_STATE_TAUNT : _taunted; }
bool IsTaunting() const { return _taunted >= TAUNT_STATE_TAUNT; }
bool IsDetaunted() const { return _taunted == TAUNT_STATE_DETAUNT; }
void AddThreat(float amount);
void ScaleThreat(float factor);
void ModifyThreatByPercent(int32 percent) { if (percent) ScaleThreat(0.01f * float(100 + percent)); }
void UpdateOffline();
void ClearThreat(); // dealloc's this
protected:
static bool FlagsAllowFighting(Unit const* a, Unit const* b);
explicit ThreatReference(ThreatManager* mgr, Unit* victim) :
_owner(reinterpret_cast<Creature*>(mgr->_owner)), _mgr(*mgr), _victim(victim),
_baseAmount(0.0f), _tempModifier(0), _taunted(TAUNT_STATE_NONE)
{
_online = ONLINE_STATE_OFFLINE;
}
virtual ~ThreatReference() = default;
void UnregisterAndFree();
bool ShouldBeOffline() const;
bool ShouldBeSuppressed() const;
void UpdateTauntState(TauntState state = TAUNT_STATE_NONE);
Creature* const _owner;
ThreatManager& _mgr;
void HeapNotifyIncreased();
void HeapNotifyDecreased();
Unit* const _victim;
OnlineState _online;
float _baseAmount;
int32 _tempModifier;
TauntState _taunted;
public:
ThreatReference(ThreatReference const&) = delete;
ThreatReference& operator=(ThreatReference const&) = delete;
friend class ThreatManager;
friend struct CompareThreatLessThan;
};
inline bool CompareThreatLessThan::operator()(ThreatReference const* a, ThreatReference const* b) const
{
return ThreatManager::CompareReferencesLT(a, b, 1.0f);
}
#endif
-733
View File
@@ -1,733 +0,0 @@
/*
* This file is part of the AzerothCore 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 "ThreatMgr.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "Map.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "Unit.h"
#include "UnitEvents.h"
//==============================================================
//================= ThreatCalcHelper ===========================
//==============================================================
// The hatingUnit is not used yet
float ThreatCalcHelper::calcThreat(Unit* hatedUnit, float threat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell)
{
if (threatSpell)
{
if (SpellThreatEntry const* threatEntry = sSpellMgr->GetSpellThreatEntry(threatSpell->Id))
if (threatEntry->pctMod != 1.0f)
threat *= threatEntry->pctMod;
// Energize is not affected by Mods
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; i++)
if (threatSpell->Effects[i].Effect == SPELL_EFFECT_ENERGIZE || threatSpell->Effects[i].ApplyAuraName == SPELL_AURA_PERIODIC_ENERGIZE)
return threat;
if (Player* modOwner = hatedUnit->GetSpellModOwner())
modOwner->ApplySpellMod(threatSpell->Id, SPELLMOD_THREAT, threat);
}
return hatedUnit->ApplyTotalThreatModifier(threat, schoolMask);
}
bool ThreatCalcHelper::isValidProcess(Unit* hatedUnit, Unit* hatingUnit, SpellInfo const* threatSpell)
{
//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->IsPlayer() && hatedUnit->ToPlayer()->IsGameMaster())
return false;
// not to dead and not for dead
if (!hatedUnit->IsAlive() || !hatingUnit->IsAlive())
return false;
// not in same map or phase
if (!hatedUnit->IsInMap(hatingUnit) || !hatedUnit->InSamePhase(hatingUnit))
return false;
// spell not causing threat
if (threatSpell && threatSpell->HasAttribute(SPELL_ATTR1_NO_THREAT))
return false;
ASSERT(hatingUnit->IsCreature());
return true;
}
//============================================================
//================= HostileReference ==========================
//============================================================
HostileReference::HostileReference(Unit* refUnit, ThreatMgr* threatMgr, float threat)
{
iThreat = threat;
iTempThreatModifier = 0.0f;
link(refUnit, threatMgr);
iUnitGuid = refUnit->GetGUID();
iOnline = true;
}
//============================================================
// Tell our refTo (target) object that we have a link
void HostileReference::targetObjectBuildLink()
{
getTarget()->addHatedBy(this);
}
//============================================================
// Tell our refTo (taget) object, that the link is cut
void HostileReference::targetObjectDestroyLink()
{
getTarget()->removeHatedBy(this);
}
//============================================================
// Tell our refFrom (source) object, that the link is cut (Target destroyed)
void HostileReference::sourceObjectDestroyLink()
{
setOnlineOfflineState(false);
}
//============================================================
// Inform the source, that the status of the reference changed
void HostileReference::fireStatusChanged(ThreatRefStatusChangeEvent& threatRefStatusChangeEvent)
{
if (GetSource())
GetSource()->processThreatEvent(&threatRefStatusChangeEvent);
}
// -- compatibility layer for combat rewrite
Unit* HostileReference::GetOwner() const { return GetSource()->GetOwner(); }
//============================================================
void HostileReference::AddThreat(float modThreat)
{
iThreat += modThreat;
// the threat is changed. Source and target unit have to be available
// if the link was cut before relink it again
if (!IsOnline())
updateOnlineStatus();
if (modThreat != 0.0f)
{
ThreatRefStatusChangeEvent event(UEV_THREAT_REF_THREAT_CHANGE, this, modThreat);
fireStatusChanged(event);
}
if (isValid() && modThreat >= 0.0f)
{
Unit* target = getTarget();
if (target->GetEntry() != NPC_EYE_OF_KILROGG) // Excluded Eye of Kilrogg
{
Unit* victimOwner = target->GetCharmerOrOwner();
if (victimOwner && victimOwner->IsAlive())
{
GetSource()->AddThreat(victimOwner, 0.0f); // create a threat to the owner of a pet, if the pet attacks
}
}
}
}
void HostileReference::addThreatPercent(int32 percent)
{
// Xinef: Do not allow to modify threat by percent if threat is negative (forced to big value < 0 by spells adding temporary threat)
// Xinef: When the temporary effect ends, temporary threat is added back which results in huge additional amount of threat
if (iThreat <= 0)
return;
float tmpThreat = iThreat;
AddPct(tmpThreat, percent);
AddThreat(tmpThreat - iThreat);
}
//============================================================
// check, if source can reach target and set the status
void HostileReference::updateOnlineStatus()
{
bool online = false;
if (!isValid())
if (Unit* target = ObjectAccessor::GetUnit(*GetSourceUnit(), getUnitGuid()))
link(target, GetSource());
// only check for online status if
// ref is valid
// target is no player or not gamemaster
// target is not in flight
if (isValid()
&& (!getTarget()->IsPlayer() || !getTarget()->ToPlayer()->IsGameMaster())
&& !getTarget()->IsInFlight()
&& getTarget()->IsInMap(GetSourceUnit())
&& getTarget()->InSamePhase(GetSourceUnit())
)
{
Creature* creature = GetSourceUnit()->ToCreature();
online = getTarget()->isInAccessiblePlaceFor(creature);
if (!online)
{
if (creature->IsWithinCombatRange(getTarget(), creature->m_CombatDistance))
online = true; // not accessible but stays online
}
}
setOnlineOfflineState(online);
}
//============================================================
// set the status and fire the event on status change
void HostileReference::setOnlineOfflineState(bool isOnline)
{
if (iOnline != isOnline)
{
iOnline = isOnline;
ThreatRefStatusChangeEvent event(UEV_THREAT_REF_ONLINE_STATUS, this, isOnline);
fireStatusChanged(event);
}
}
//============================================================
// prepare the reference for deleting
// this is called be the target
void HostileReference::removeReference()
{
invalidate();
ThreatRefStatusChangeEvent event(UEV_THREAT_REF_REMOVE_FROM_LIST, this, false);
fireStatusChanged(event);
}
//============================================================
Unit* HostileReference::GetSourceUnit()
{
return (GetSource()->GetOwner());
}
//============================================================
//================ ThreatContainer ===========================
//============================================================
void ThreatContainer::clearReferences()
{
for (ThreatContainer::StorageType::const_iterator i = iThreatList.begin(); i != iThreatList.end(); ++i)
{
(*i)->unlink();
delete (*i);
}
iThreatList.clear();
}
//============================================================
// Return the HostileReference of nullptr, if not found
HostileReference* ThreatContainer::getReferenceByTarget(Unit const* victim) const
{
if (!victim)
return nullptr;
return getReferenceByTarget(victim->GetGUID());
}
HostileReference* ThreatContainer::getReferenceByTarget(ObjectGuid const& guid) const
{
for (ThreatContainer::StorageType::const_iterator i = iThreatList.begin(); i != iThreatList.end(); ++i)
{
HostileReference* ref = (*i);
if (ref && ref->getUnitGuid() == guid)
{
return ref;
}
}
return nullptr;
}
//============================================================
// Add the threat, if we find the reference
HostileReference* ThreatContainer::AddThreat(Unit* victim, float threat)
{
HostileReference* ref = getReferenceByTarget(victim);
if (ref)
ref->AddThreat(threat);
return ref;
}
//============================================================
void ThreatContainer::ModifyThreatByPercent(Unit* victim, int32 percent)
{
if (HostileReference* ref = getReferenceByTarget(victim))
ref->addThreatPercent(percent);
}
//============================================================
// Check if the list is dirty and sort if necessary
void ThreatContainer::update()
{
if (iDirty && iThreatList.size() > 1)
iThreatList.sort(Acore::ThreatOrderPred());
iDirty = false;
}
//============================================================
// return the next best victim
// could be the current victim
HostileReference* ThreatContainer::SelectNextVictim(Creature* attacker, HostileReference* currentVictim) const
{
// pussywizard: pretty much remade this whole function
HostileReference* currentRef = nullptr;
bool found = false;
bool noPriorityTargetFound = false;
// pussywizard: currentVictim is needed to compare if threat was exceeded by 10%/30% for melee/range targets (only then switching current target)
if (currentVictim)
{
Unit* cvUnit = currentVictim->getTarget();
if (!attacker->CanCreatureAttack(cvUnit)) // pussywizard: if currentVictim is not valid => don't compare the threat with it, just take the highest threat valid target
currentVictim = nullptr;
else if (!IsPreferredTarget(attacker, cvUnit)) // pussywizard: no 10%/30% if currentVictim is immune to damage or has auras breakable by damage
currentVictim = nullptr;
}
ThreatContainer::StorageType::const_iterator lastRef = iThreatList.end();
--lastRef;
// pussywizard: iterate from highest to lowest threat
for (ThreatContainer::StorageType::const_iterator iter = iThreatList.begin(); iter != iThreatList.end() && !found;)
{
currentRef = (*iter);
Unit* target = currentRef->getTarget();
ASSERT(target); // if the ref has status online the target must be there !
// pussywizard: don't go to threat comparison if this ref is immune to damage or has aura breakable on damage (second choice target)
// pussywizard: if this is the last entry on the threat list, then all targets are second choice, set bool to true and loop threat list again, ignoring this section
if (!noPriorityTargetFound && !IsPreferredTarget(attacker, target))
{
if (iter != lastRef)
{
++iter;
continue;
}
else
{
noPriorityTargetFound = true;
iter = iThreatList.begin();
continue;
}
}
// pussywizard: skip not valid targets
if (attacker->CanCreatureAttack(target))
{
if (currentVictim) // pussywizard: if not nullptr then target must have 10%/30% more threat
{
if (currentVictim == currentRef) // pussywizard: nothing found previously was good and enough, currentRef passed all necessary tests, so end now
{
found = true;
break;
}
// pussywizard: implement 110% threat rule for targets in melee range and 130% rule for targets in ranged distances
if (currentRef->GetThreat() > 1.3f * currentVictim->GetThreat()) // pussywizard: enough in all cases, end
{
found = true;
break;
}
else if (currentRef->GetThreat() > 1.1f * currentVictim->GetThreat()) // pussywizard: enought only if target in melee range
{
if (attacker->IsWithinMeleeRange(target))
{
found = true;
break;
}
}
else // pussywizard: nothing found previously was good and enough, this and next entries on the list have less than 110% threat, and currentVictim is present and valid as checked before the loop (otherwise it's nullptr), so end now
{
currentRef = currentVictim;
found = true;
break;
}
}
else // pussywizard: no currentVictim, first passing all checks is chosen (highest threat, list is sorted)
{
// tie-breaker if multiple targets have the same threat: select closest target
currentRef = SelectNextVictimTieBreaker(attacker, iter, noPriorityTargetFound);
found = true;
break;
}
}
++iter;
}
if (!found)
currentRef = nullptr;
return currentRef;
}
// Helper for Tie-breakers
HostileReference* ThreatContainer::SelectNextVictimTieBreaker(Creature* attacker, ThreatContainer::StorageType::const_iterator currentIter, bool noPriorityTargetFound) const
{
HostileReference* bestRef = *currentIter;
float bestThreat = bestRef->GetThreat();
float shortestDistSq = attacker->GetExactDistSq(bestRef->getTarget());
auto tieIter = std::next(currentIter);
while (tieIter != iThreatList.end())
{
HostileReference* nextRef = *tieIter;
if (!nextRef)
{
++tieIter;
continue;
}
// Threatlist is sorted, so we can stop as soon as we find a lower threat
if (bestThreat - nextRef->GetThreat() > 0.01f)
break;
Unit* target = nextRef->getTarget();
if (!target)
{
++tieIter;
continue;
}
if (attacker->CanCreatureAttack(target) && (noPriorityTargetFound || IsPreferredTarget(attacker, target)))
{
float distSq = attacker->GetExactDistSq(target);
if (distSq < shortestDistSq)
{
bestRef = nextRef;
shortestDistSq = distSq;
}
}
++tieIter;
}
return bestRef;
}
// Helper for checking if a target is preferred (not immune, not confused, etc)
bool ThreatContainer::IsPreferredTarget(Creature* attacker, Unit* target) const
{
if (target->IsImmunedToDamageOrSchool(attacker->GetMeleeDamageSchoolMask()))
return false;
if (target->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE))
return false;
if (target->HasUnitState(UNIT_STATE_CONFUSED))
return false;
if (target->HasAuraTypeWithCaster(SPELL_AURA_IGNORED, attacker->GetGUID()))
return false;
if (attacker->HasUnitState(UNIT_STATE_ROOT) && attacker->IsCombatMovementAllowed() && !attacker->IsWithinMeleeRange(target))
return false;
return true;
}
//============================================================
//=================== ThreatMgr ==========================
//============================================================
ThreatMgr::ThreatMgr(Unit* owner) : iCurrentVictim(nullptr), iOwner(owner), iUpdateTimer(THREAT_UPDATE_INTERVAL)
{
}
void ThreatMgr::ClearAllThreat()
{
if (iOwner->CanHaveThreatList(true) && !isThreatListEmpty())
iOwner->SendClearThreatListOpcode();
clearReferences();
}
//============================================================
void ThreatMgr::clearReferences()
{
iThreatContainer.clearReferences();
iThreatOfflineContainer.clearReferences();
iCurrentVictim = nullptr;
iUpdateTimer = THREAT_UPDATE_INTERVAL;
}
//============================================================
void ThreatMgr::AddThreat(Unit* victim, float threat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell)
{
if (!ThreatCalcHelper::isValidProcess(victim, iOwner, threatSpell))
return;
threat = ThreatCalcHelper::calcThreat(victim, threat, schoolMask, threatSpell);
if (Creature* hatingCreature = iOwner->ToCreature())
{
if (hatingCreature->IsAIEnabled)
{
hatingCreature->AI()->CalculateThreat(victim, threat, threatSpell);
}
}
DoAddThreat(victim, threat);
}
void ThreatMgr::DoAddThreat(Unit* victim, float threat)
{
uint32 redirectThreadPct = victim->GetRedirectThreatPercent();
Unit* redirectTarget = victim->GetRedirectThreatTarget();
// Personal Spawns from same summoner can aggro each other
if (TempSummon* tempSummonVictim = victim->ToTempSummon())
{
if (tempSummonVictim->IsVisibleBySummonerOnly())
{
if (!GetOwner()->ToTempSummon() ||
!GetOwner()->ToTempSummon()->IsVisibleBySummonerOnly() ||
tempSummonVictim->GetSummonerGUID() != GetOwner()->ToTempSummon()->GetSummonerGUID())
{
redirectThreadPct = 100;
redirectTarget = tempSummonVictim->GetSummonerUnit();
}
}
}
// must check > 0.0f, otherwise dead loop
if (threat > 0.0f && redirectThreadPct)
{
if (redirectTarget)
{
float redirectThreat = CalculatePct(threat, redirectThreadPct);
threat -= redirectThreat;
if (ThreatCalcHelper::isValidProcess(redirectTarget, GetOwner()))
_addThreat(redirectTarget, redirectThreat);
}
}
_addThreat(victim, threat);
}
void ThreatMgr::_addThreat(Unit* victim, float threat)
{
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);
if (!ref) // there was no ref => create a new one
{
// 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->IsPlayer() && victim->ToPlayer()->IsGameMaster())
hostileRef->setOnlineOfflineState(false); // GM is always offline
}
}
//============================================================
void ThreatMgr::ModifyThreatByPercent(Unit* victim, int32 percent)
{
iThreatContainer.ModifyThreatByPercent(victim, percent);
}
//============================================================
Unit* ThreatMgr::getHostileTarget()
{
iThreatContainer.update();
HostileReference* nextVictim = iThreatContainer.SelectNextVictim(GetOwner()->ToCreature(), getCurrentVictim());
setCurrentVictim(nextVictim);
return getCurrentVictim() != nullptr ? getCurrentVictim()->getTarget() : nullptr;
}
//============================================================
float ThreatMgr::GetThreat(Unit* victim, bool alsoSearchOfflineList)
{
float threat = 0.0f;
HostileReference* ref = iThreatContainer.getReferenceByTarget(victim);
if (!ref && alsoSearchOfflineList)
ref = iThreatOfflineContainer.getReferenceByTarget(victim);
if (ref)
threat = ref->GetThreat();
return threat;
}
//============================================================
float ThreatMgr::getThreatWithoutTemp(Unit* victim, bool alsoSearchOfflineList)
{
float threat = 0.0f;
HostileReference* ref = iThreatContainer.getReferenceByTarget(victim);
if (!ref && alsoSearchOfflineList)
ref = iThreatOfflineContainer.getReferenceByTarget(victim);
if (ref)
threat = ref->GetThreat() - ref->getTempThreatModifier();
return threat;
}
//============================================================
void ThreatMgr::tauntApply(Unit* taunter)
{
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());
}
}
//============================================================
void ThreatMgr::tauntFadeOut(Unit* taunter)
{
HostileReference* ref = iThreatContainer.getReferenceByTarget(taunter);
if (ref)
ref->resetTempThreat();
}
//============================================================
void ThreatMgr::setCurrentVictim(HostileReference* pHostileReference)
{
if (pHostileReference && pHostileReference != iCurrentVictim)
{
iOwner->SendChangeCurrentVictimOpcode(pHostileReference);
}
iCurrentVictim = pHostileReference;
}
//============================================================
// The hated unit is gone, dead or deleted
// return true, if the event is consumed
void ThreatMgr::processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent)
{
threatRefStatusChangeEvent->setThreatMgr(this); // now we can set the threat manager
HostileReference* hostileRef = threatRefStatusChangeEvent->getReference();
switch (threatRefStatusChangeEvent->getType())
{
case UEV_THREAT_REF_THREAT_CHANGE:
if ((getCurrentVictim() == hostileRef && threatRefStatusChangeEvent->getFValue() < 0.0f) ||
(getCurrentVictim() != hostileRef && threatRefStatusChangeEvent->getFValue() > 0.0f))
setDirty(true); // the order in the threat list might have changed
break;
case UEV_THREAT_REF_ONLINE_STATUS:
if (!hostileRef->IsOnline())
{
if (hostileRef == getCurrentVictim())
{
setCurrentVictim(nullptr);
setDirty(true);
}
if (GetOwner() && GetOwner()->IsInWorld())
if (Unit* target = ObjectAccessor::GetUnit(*GetOwner(), hostileRef->getUnitGuid()))
if (GetOwner()->IsInMap(target))
GetOwner()->SendRemoveFromThreatListOpcode(hostileRef);
iThreatContainer.remove(hostileRef);
iThreatOfflineContainer.addReference(hostileRef);
}
else
{
if (getCurrentVictim() && hostileRef->GetThreat() > (1.1f * getCurrentVictim()->GetThreat()))
setDirty(true);
iThreatContainer.addReference(hostileRef);
iThreatOfflineContainer.remove(hostileRef);
}
break;
case UEV_THREAT_REF_REMOVE_FROM_LIST:
if (hostileRef == getCurrentVictim())
{
setCurrentVictim(nullptr);
setDirty(true);
}
iOwner->SendRemoveFromThreatListOpcode(hostileRef);
if (hostileRef->IsOnline())
iThreatContainer.remove(hostileRef);
else
iThreatOfflineContainer.remove(hostileRef);
break;
}
}
bool ThreatMgr::isNeedUpdateToClient(uint32 time)
{
if (isThreatListEmpty())
return false;
if (time >= iUpdateTimer)
{
iUpdateTimer = THREAT_UPDATE_INTERVAL;
return true;
}
iUpdateTimer -= time;
return false;
}
// Reset all aggro without modifying the threatlist.
void ThreatMgr::ResetAllThreat()
{
ThreatContainer::StorageType& threatList = iThreatContainer.iThreatList;
if (threatList.empty())
return;
for (HostileReference* ref : threatList)
{
// Reset temp threat before setting threat back to 0.
ref->resetTempThreat();
ref->SetThreat(0.f);
}
setDirty(true);
}
-335
View File
@@ -1,335 +0,0 @@
/*
* This file is part of the AzerothCore 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 _THREATMANAGER
#define _THREATMANAGER
#include "IteratorPair.h"
#include "ObjectGuid.h"
#include "Reference.h"
#include "SharedDefines.h"
#include "UnitEvents.h"
#include <list>
//==============================================================
class Unit;
class Creature;
class ThreatMgr;
class SpellInfo;
#define THREAT_UPDATE_INTERVAL 2 * IN_MILLISECONDS // Server should send threat update to client periodically each second
//==============================================================
// Class to calculate the real threat based
struct ThreatCalcHelper
{
static float calcThreat(Unit* hatedUnit, float threat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr);
static bool isValidProcess(Unit* hatedUnit, Unit* hatingUnit, SpellInfo const* threatSpell = nullptr);
};
//==============================================================
class HostileReference : public Reference<Unit, ThreatMgr>
{
public:
HostileReference(Unit* refUnit, ThreatMgr* threatMgr, float threat);
Unit* GetOwner() const;
Unit* GetVictim() const { return getTarget(); }
//=================================================
void AddThreat(float modThreat);
void SetThreat(float threat) { AddThreat(threat - GetThreat()); }
void addThreatPercent(int32 percent);
[[nodiscard]] float GetThreat() const { return iThreat; }
void ClearThreat() { removeReference(); }
[[nodiscard]] bool IsOnline() const { return iOnline; }
[[nodiscard]] bool IsAvailable() const { return iOnline; } // unused for now
[[nodiscard]] bool IsOffline() const { return !iOnline; } // unused for now
// used for temporary setting a threat and reducting it later again.
// the threat modification is stored
void setTempThreat(float threat)
{
addTempThreat(threat - GetThreat());
}
void addTempThreat(float threat)
{
iTempThreatModifier = threat;
if (iTempThreatModifier != 0.0f)
AddThreat(iTempThreatModifier);
}
void resetTempThreat()
{
if (iTempThreatModifier != 0.0f)
{
AddThreat(-iTempThreatModifier);
iTempThreatModifier = 0.0f;
}
}
float getTempThreatModifier() { return iTempThreatModifier; }
//=================================================
// check, if source can reach target and set the status
void updateOnlineStatus();
void setOnlineOfflineState(bool isOnline);
//=================================================
bool operator == (const HostileReference& hostileRef) const { return hostileRef.getUnitGuid() == getUnitGuid(); }
//=================================================
[[nodiscard]] ObjectGuid getUnitGuid() const { return iUnitGuid; }
//=================================================
// reference is not needed anymore. realy delete it !
void removeReference();
//=================================================
HostileReference* next() { return ((HostileReference*) Reference<Unit, ThreatMgr>::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);
Unit* GetSourceUnit();
private:
float iThreat;
float iTempThreatModifier; // used for taunt
ObjectGuid iUnitGuid;
bool iOnline;
};
//==============================================================
class ThreatMgr;
class ThreatContainer
{
friend class ThreatMgr;
public:
typedef std::list<HostileReference*> StorageType;
ThreatContainer() = default;
~ThreatContainer() { clearReferences(); }
HostileReference* AddThreat(Unit* victim, float threat);
void ModifyThreatByPercent(Unit* victim, int32 percent);
HostileReference* SelectNextVictim(Creature* attacker, HostileReference* currentVictim) const;
HostileReference* SelectNextVictimTieBreaker(Creature* attacker, ThreatContainer::StorageType::const_iterator currentIter, bool noPriorityTargetFound) const;
bool IsPreferredTarget(Creature* attacker, Unit* target) const;
void setDirty(bool isDirty) { iDirty = isDirty; }
[[nodiscard]] bool isDirty() const { return iDirty; }
[[nodiscard]] bool empty() const
{
return iThreatList.empty();
}
[[nodiscard]] HostileReference* getMostHated() const
{
return iThreatList.empty() ? nullptr : iThreatList.front();
}
HostileReference* getReferenceByTarget(Unit const* victim) const;
HostileReference* getReferenceByTarget(ObjectGuid const& guid) const;
[[nodiscard]] 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{false};
};
//=================================================
typedef HostileReference ThreatReference;
class ThreatMgr
{
public:
friend class HostileReference;
explicit ThreatMgr(Unit* owner);
~ThreatMgr() { clearReferences(); }
Unit* SelectVictim() { return getHostileTarget(); }
Unit* GetCurrentVictim() const { if (ThreatReference* ref = getCurrentVictim()) return ref->GetVictim(); else return nullptr; }
Unit* GetAnyTarget() const { auto const& list = GetThreatList(); if (!list.empty()) return list.front()->getTarget(); return nullptr; }
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);
float GetThreatListSize() const { return GetThreatList().size(); }
float getThreatWithoutTemp(Unit* victim, bool alsoSearchOfflineList = false);
[[nodiscard]] bool isThreatListEmpty() const { return iThreatContainer.empty(); }
[[nodiscard]] bool areThreatListsEmpty() const { return iThreatContainer.empty() && iThreatOfflineContainer.empty(); }
Acore::IteratorPair<std::list<ThreatReference*>::const_iterator> GetSortedThreatList() const { auto& list = iThreatContainer.GetThreatList(); return { list.cbegin(), list.cend() }; }
Acore::IteratorPair<std::list<ThreatReference*>::const_iterator> GetUnsortedThreatList() const { return GetSortedThreatList(); }
void processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent);
bool isNeedUpdateToClient(uint32 time);
[[nodiscard]] HostileReference* getCurrentVictim() const { return iCurrentVictim; }
[[nodiscard]] Unit* GetOwner() const { return iOwner; }
Unit* getHostileTarget();
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 ResetThreat(Unit const* who) { if (auto* ref = FindReference(who, true)) ref->SetThreat(0.0f); }
void ResetAllThreat();
void ClearThreat(Unit const* who) { if (auto* ref = FindReference(who, true)) ref->removeReference(); }
void ClearAllThreat();
// 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 (auto& ref : threatList)
{
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.
[[nodiscard]] ThreatContainer::StorageType const& GetThreatList() const { return iThreatContainer.GetThreatList(); }
[[nodiscard]] ThreatContainer::StorageType const& GetOfflineThreatList() const { return iThreatOfflineContainer.GetThreatList(); }
ThreatContainer& GetOnlineContainer() { return iThreatContainer; }
ThreatContainer& GetOfflineContainer() { return iThreatOfflineContainer; }
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; }
void _addThreat(Unit* victim, float threat);
HostileReference* iCurrentVictim;
Unit* iOwner;
uint32 iUpdateTimer;
ThreatContainer iThreatContainer;
ThreatContainer iThreatOfflineContainer;
};
//=================================================
struct RedirectThreatInfo
{
RedirectThreatInfo() = default;
ObjectGuid _targetGUID;
uint32 _threatPct{ 0 };
[[nodiscard]] ObjectGuid GetTargetGUID() const { return _targetGUID; }
[[nodiscard]] 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));
}
};
//=================================================
namespace Acore
{
// 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
-134
View File
@@ -1,134 +0,0 @@
/*
* This file is part of the AzerothCore 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 ThreatMgr;
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_ASSECCIBLE_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 nullptr
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_ASSECCIBLE_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
{
private:
uint32 iType;
public:
UnitBaseEvent(uint32 pType) { iType = pType; }
[[nodiscard]] uint32 getType() const { return iType; }
[[nodiscard]] bool matchesTypeMask(uint32 pMask) const { return iType & pMask; }
void setType(uint32 pType) { iType = pType; }
};
//==============================================================
class ThreatRefStatusChangeEvent : public UnitBaseEvent
{
private:
HostileReference* iHostileReference;
union
{
float iFValue;
int32 iIValue;
bool iBValue;
};
ThreatMgr* iThreatMgr;
public:
ThreatRefStatusChangeEvent(uint32 pType) : UnitBaseEvent(pType), iThreatMgr(nullptr) { iHostileReference = nullptr; }
ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference) : UnitBaseEvent(pType), iThreatMgr(nullptr) { iHostileReference = pHostileReference; }
ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, float pValue) : UnitBaseEvent(pType), iThreatMgr(nullptr) { iHostileReference = pHostileReference; iFValue = pValue; }
ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, bool pValue) : UnitBaseEvent(pType), iThreatMgr(nullptr) { iHostileReference = pHostileReference; iBValue = pValue; }
[[nodiscard]] int32 getIValue() const { return iIValue; }
[[nodiscard]] float getFValue() const { return iFValue; }
[[nodiscard]] bool getBValue() const { return iBValue; }
void setBValue(bool pValue) { iBValue = pValue; }
[[nodiscard]] HostileReference* getReference() const { return iHostileReference; }
void setThreatMgr(ThreatMgr* pThreatMgr) { iThreatMgr = pThreatMgr; }
[[nodiscard]] ThreatMgr* GetThreatMgr() const { return iThreatMgr; }
};
//==============================================================
class ThreatMgrEvent : public ThreatRefStatusChangeEvent
{
private:
ThreatContainer* iThreatContainer;
public:
ThreatMgrEvent(uint32 pType) : ThreatRefStatusChangeEvent(pType), iThreatContainer(nullptr) {}
ThreatMgrEvent(uint32 pType, HostileReference* pHostileReference) : ThreatRefStatusChangeEvent(pType, pHostileReference), iThreatContainer(nullptr) {}
void setThreatContainer(ThreatContainer* pThreatContainer) { iThreatContainer = pThreatContainer; }
[[nodiscard]] ThreatContainer* getThreatContainer() const { return iThreatContainer; }
};
//==============================================================
#endif