Files
moonwell-core/src/server/game/Combat/ThreatManager.h
T

332 lines
18 KiB
C++

/*
* 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);
// Immediately reselect victim (bypasses 1-second Update timer)
void UpdateVictimForTesting() { UpdateVictim(); }
///== 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