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:
@@ -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
|
||||
Reference in New Issue
Block a user