diff --git a/sql/base/auth_database.sql b/sql/base/auth_database.sql index cc57dc7df22..ab69c9b8bb7 100644 --- a/sql/base/auth_database.sql +++ b/sql/base/auth_database.sql @@ -962,9 +962,8 @@ INSERT INTO `rbac_linked_permissions` VALUES (196,843), (196,866), (196,867), -(196,872), -(196,873), -(197,232), +(196,870),(196,872), +(196,873),(197,232), (197,236), (197,237), (197,273), @@ -1847,6 +1846,7 @@ INSERT INTO `rbac_permissions` VALUES (865,'Command: npc showloot'), (866,'Command: list spawnpoints'), (867,'Command: reload quest_greeting_locale'), +(870,'Command: debug threatinfo'); (872,'Command: server debug'), (873,'Command: reload creature_movement_override'); /*!40000 ALTER TABLE `rbac_permissions` ENABLE KEYS */; diff --git a/src/server/game/Combat/ThreatManager.h b/src/server/game/Combat/ThreatManager.h index f4ac832dfad..2a600a922f5 100644 --- a/src/server/game/Combat/ThreatManager.h +++ b/src/server/game/Combat/ThreatManager.h @@ -15,13 +15,11 @@ * with this program. If not, see . */ -#ifndef TRINITY_THREATMANAGER_H -#define TRINITY_THREATMANAGER_H - #include "Common.h" #include "IteratorPair.h" #include "ObjectGuid.h" #include "SharedDefines.h" +#include #include #include #include @@ -82,8 +80,9 @@ struct CompareThreatLessThan // Please check Game/Combat/ThreatManager.h for documentation on how this class works! class TC_GAME_API ThreatManager { -public: - typedef boost::heap::fibonacci_heap> threat_list_heap; + public: + typedef boost::heap::fibonacci_heap> threat_list_heap; + static const uint32 CLIENT_THREAT_UPDATE_INTERVAL = 1000u; static bool CanHaveThreatList(Unit const* who); @@ -99,180 +98,178 @@ public: // never nullptr Unit* GetOwner() const { return _owner; } // can our owner have a threat list? - // never nullptr - Unit* GetOwner() const { return _owner; } - // can our owner have a threat list? - // identical to ThreatManager::CanHaveThreatList(GetOwner()) - bool CanHaveThreatList() const { return _ownerCanHaveThreatList; } - // returns the victim selected by the last SelectVictim call - this can be nullptr - Unit* GetCurrentVictim() const; - // returns an arbitrary non-offline victim from owner's threat list if one exists, nullptr otherwise - Unit* GetAnyTarget() const; - // selects a (potentially new) victim from the threat list and returns it - this can be nullptr - Unit* SelectVictim(); + // identical to ThreatManager::CanHaveThreatList(GetOwner()) + bool CanHaveThreatList() const { return _ownerCanHaveThreatList; } + // returns the victim selected by the last SelectVictim call - this can be nullptr + Unit* GetCurrentVictim() const; + // returns an arbitrary non-offline victim from owner's threat list if one exists, nullptr otherwise + Unit* GetAnyTarget() const; + // selects a (potentially new) victim from the threat list and returns it - this can be nullptr + Unit* SelectVictim(); - bool IsEngaged() const { return _ownerEngaged; } - // 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 { return _sortedThreatList.size(); } - // fastest of the three threat list getters - gets the threat list in "arbitrary" order - Trinity::IteratorPair GetUnsortedThreatList() const { return { _sortedThreatList.begin(), _sortedThreatList.end() }; } - // slightly slower than GetUnsorted, but, well...sorted - only use it if you need the sorted property, of course - // note: current tank is NOT guaranteed to be the first entry in this list - check GetCurrentVictim separately if you want that! - Trinity::IteratorPair GetSortedThreatList() const { return { _sortedThreatList.ordered_begin(), _sortedThreatList.ordered_end() }; } - // slowest of the three threat list getters (by far), but lets you modify the threat references - std::vector GetModifiableThreatList() const; + bool IsEngaged() const { return _ownerEngaged; } + // 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 { return _sortedThreatList.size(); } + // fastest of the three threat list getters - gets the threat list in "arbitrary" order + Trinity::IteratorPair GetUnsortedThreatList() const { return { _sortedThreatList.begin(), _sortedThreatList.end() }; } + // slightly slower than GetUnsorted, but, well...sorted - only use it if you need the sorted property, of course + // note: current tank is NOT guaranteed to be the first entry in this list - check GetCurrentVictim separately if you want that! + Trinity::IteratorPair GetSortedThreatList() const { return { _sortedThreatList.ordered_begin(), _sortedThreatList.ordered_end() }; } + // slowest of the three threat list getters (by far), but lets you modify the threat references + std::vector GetModifiableThreatList() const; - // 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; } + // 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 a condition changed that may impact refs' online state so it can re-evaluate - void UpdateOnlineStates(bool meThreateningOthers = true, bool othersThreateningMe = true); - ///== 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); - // Removes all targets from the threat list (will cause evade in UpdateVictim if called) - void ClearAllThreat(); + // Notify the ThreatManager that a condition changed that may impact refs' online state so it can re-evaluate + void UpdateOnlineStates(bool meThreateningOthers = true, bool othersThreateningMe = true); + ///== 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); + // Removes all targets from the threat list (will cause evade in UpdateVictim if called) + void ClearAllThreat(); - // sends SMSG_THREAT_UPDATE to all nearby clients (used by client to forward threat list info to addons) - void SendThreatListToClients() const; + // sends SMSG_THREAT_UPDATE to all nearby clients (used by client to forward threat list info to addons) + void SendThreatListToClients() const; - ///== AFFECT OTHERS' THREAT LISTS == - // what it says on the tin - 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); - // 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(); + ///== AFFECT OTHERS' THREAT LISTS == + // what it says on the tin - 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); + // 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(); - ///== REDIRECT SYSTEM == - // Register a redirection effect that redirects pct% of threat generated by owner to victim - void RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct); - // Unregister a redirection effort for all victims - void UnregisterRedirectThreat(uint32 spellId); - // Unregister a redirection effect for a specific victim - void UnregisterRedirectThreat(uint32 spellId, ObjectGuid const& victim); + ///== REDIRECT SYSTEM == + // Register a redirection effect that redirects pct% of threat generated by owner to victim + void RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct); + // Unregister a redirection effort for all victims + void UnregisterRedirectThreat(uint32 spellId); + // Unregister a redirection effect for a specific victim + void UnregisterRedirectThreat(uint32 spellId, ObjectGuid const& victim); -private: - Unit* const _owner; - bool _ownerCanHaveThreatList; - bool _ownerEngaged; + private: + Unit* const _owner; + bool _ownerCanHaveThreatList; + bool _ownerEngaged; - 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); + 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); - // send opcodes (all for my own threat list) - void SendClearAllThreatToClients() const; - void SendRemoveToClients(Unit const* victim) const; - void SendNewVictimToClients(ThreatReference const* victimRef) const; + // send opcodes (all for my own threat list) + void SendClearAllThreatToClients() const; + void SendRemoveToClients(Unit const* victim) const; + void SendNewVictimToClients(ThreatReference const* victimRef) const; - ///== MY THREAT LIST == - void PutThreatListRef(ObjectGuid const& guid, ThreatReference* ref); - void PurgeThreatListRef(ObjectGuid const& guid, bool sendRemove); + ///== MY THREAT LIST == + void PutThreatListRef(ObjectGuid const& guid, ThreatReference* ref); + void PurgeThreatListRef(ObjectGuid const& guid, bool sendRemove); - uint32 _updateClientTimer; - threat_list_heap _sortedThreatList; - std::unordered_map _myThreatListEntries; - ThreatReference const* _currentVictimRef; - ThreatReference const* ReselectVictim(); + uint32 _updateClientTimer; + threat_list_heap _sortedThreatList; + std::unordered_map _myThreatListEntries; + ThreatReference const* _currentVictimRef; + ThreatReference const* ReselectVictim(); - ///== OTHERS' THREAT LISTS == - void PutThreatenedByMeRef(ObjectGuid const& guid, ThreatReference* ref); - void PurgeThreatenedByMeRef(ObjectGuid const& guid); - std::unordered_map _threatenedByMe; // these refs are entries for myself on other units' threat lists - float _singleSchoolModifiers[MAX_SPELL_SCHOOL]; // most spells are single school - we pre-calculate these and store them - mutable std::unordered_map::type, float> _multiSchoolModifiers; // these are calculated on demand + ///== OTHERS' THREAT LISTS == + void PutThreatenedByMeRef(ObjectGuid const& guid, ThreatReference* ref); + void PurgeThreatenedByMeRef(ObjectGuid const& guid); + std::unordered_map _threatenedByMe; // these refs are entries for myself on other units' threat lists + std::array _singleSchoolModifiers; // most spells are single school - we pre-calculate these and store them + mutable std::unordered_map::type, float> _multiSchoolModifiers; // these are calculated on demand - // redirect system (is kind of dumb, but that's because none of the redirection spells actually have any aura effect associated with them, so spellscript needs to deal with it) - void UpdateRedirectInfo(); - std::vector> _redirectInfo; // current redirection targets and percentages (updated from registry in ThreatManager::UpdateRedirectInfo) - std::unordered_map> _redirectRegistry; // spellid -> (victim -> pct); all redirection effects on us (removal individually managed by spell scripts because blizzard is dumb) + // redirect system (is kind of dumb, but that's because none of the redirection spells actually have any aura effect associated with them, so spellscript needs to deal with it) + void UpdateRedirectInfo(); + std::vector> _redirectInfo; // current redirection targets and percentages (updated from registry in ThreatManager::UpdateRedirectInfo) + std::unordered_map> _redirectRegistry; // spellid -> (victim -> pct); all redirection effects on us (removal individually managed by spell scripts because blizzard is dumb) -public: - ThreatManager(ThreatManager const&) = delete; - ThreatManager& operator=(ThreatManager const&) = delete; + public: + ThreatManager(ThreatManager const&) = delete; + ThreatManager& operator=(ThreatManager const&) = delete; friend class ThreatReference; friend struct CompareThreatLessThan; + friend class debug_commandscript; }; // Please check Game/Combat/ThreatManager.h for documentation on how this class works! class TC_GAME_API ThreatReference { -public: - enum TauntState { TAUNT_STATE_DETAUNT = -1, TAUNT_STATE_NONE = 0, TAUNT_STATE_TAUNT = 1 }; - enum OnlineState { ONLINE_STATE_ONLINE = 2, ONLINE_STATE_SUPPRESSED = 1, ONLINE_STATE_OFFLINE = 0 }; + public: + enum TauntState { TAUNT_STATE_DETAUNT = -1, TAUNT_STATE_NONE = 0, TAUNT_STATE_TAUNT = 1 }; + enum OnlineState { ONLINE_STATE_ONLINE = 2, ONLINE_STATE_SUPPRESSED = 1, ONLINE_STATE_OFFLINE = 0 }; - Unit* GetOwner() const { return _owner; } - Unit* GetVictim() const { return _victim; } - float GetThreat() const { return std::max(_baseAmount + (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 IsOffline() const { return (_online <= ONLINE_STATE_OFFLINE); } - TauntState GetTauntState() const { return _taunted; } - bool IsTaunting() const { return _taunted == TAUNT_STATE_TAUNT; } - bool IsDetaunted() const { return _taunted == TAUNT_STATE_DETAUNT; } + Unit* GetOwner() const { return _owner; } + Unit* GetVictim() const { return _victim; } + float GetThreat() const { return std::max(_baseAmount + (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 IsOffline() const { return (_online <= ONLINE_STATE_OFFLINE); } + TauntState GetTauntState() const { return _taunted; } + bool IsTaunting() const { return _taunted == TAUNT_STATE_TAUNT; } + bool IsDetaunted() const { return _taunted == TAUNT_STATE_DETAUNT; } - void SetThreat(float amount) { _baseAmount = amount; HeapNotifyChanged(); } - void AddThreat(float amount); - void ScaleThreat(float factor); - void ModifyThreatByPercent(int32 percent) { if (percent) ScaleThreat(0.01f * float(100 + percent)); } - void UpdateOnlineState(); + void SetThreat(float amount) { _baseAmount = amount; HeapNotifyChanged(); } + void AddThreat(float amount); + void ScaleThreat(float factor); + void ModifyThreatByPercent(int32 percent) { if (percent) ScaleThreat(0.01f*float(100 + percent)); } + void UpdateOnlineState(); - void ClearThreat(bool sendRemove = true); // dealloc's this + void ClearThreat(bool sendRemove = true); // dealloc's this -private: - ThreatReference(ThreatManager* mgr, Unit* victim, float amount) : _owner(mgr->_owner), _mgr(mgr), _victim(victim), _baseAmount(amount), _tempModifier(0), _online(SelectOnlineState()), _taunted(TAUNT_STATE_NONE) { } - static bool FlagsAllowFighting(Unit const* a, Unit const* b); - OnlineState SelectOnlineState(); - void UpdateTauntState(bool victimIsTaunting); - Unit* const _owner; - ThreatManager* const _mgr; - void HeapNotifyIncreased() { _mgr->_sortedThreatList.increase(_handle); } - void HeapNotifyDecreased() { _mgr->_sortedThreatList.decrease(_handle); } - void HeapNotifyChanged() { _mgr->_sortedThreatList.update(_handle); } - Unit* const _victim; - float _baseAmount; - int32 _tempModifier; // Temporary effects (auras with SPELL_AURA_MOD_TOTAL_THREAT) - set from victim's threatmanager in ThreatManager::UpdateMyTempModifiers - OnlineState _online; - TauntState _taunted; - ThreatManager::threat_list_heap::handle_type _handle; + private: + ThreatReference(ThreatManager* mgr, Unit* victim, float amount) : _owner(mgr->_owner), _mgr(mgr), _victim(victim), _baseAmount(amount), _tempModifier(0), _online(SelectOnlineState()), _taunted(TAUNT_STATE_NONE) { } + static bool FlagsAllowFighting(Unit const* a, Unit const* b); + OnlineState SelectOnlineState(); + void UpdateTauntState(bool victimIsTaunting); + Unit* const _owner; + ThreatManager* const _mgr; + void HeapNotifyIncreased() { _mgr->_sortedThreatList.increase(_handle); } + void HeapNotifyDecreased() { _mgr->_sortedThreatList.decrease(_handle); } + void HeapNotifyChanged() { _mgr->_sortedThreatList.update(_handle); } + Unit* const _victim; + float _baseAmount; + int32 _tempModifier; // Temporary effects (auras with SPELL_AURA_MOD_TOTAL_THREAT) - set from victim's threatmanager in ThreatManager::UpdateMyTempModifiers + OnlineState _online; + TauntState _taunted; + ThreatManager::threat_list_heap::handle_type _handle; -public: - ThreatReference(ThreatReference const&) = delete; - ThreatReference& operator=(ThreatReference const&) = delete; + 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 + + #endif diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index f90f2c6bd11..6cb39871f10 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -39,6 +39,7 @@ EndScriptData */ #include "PoolMgr.h" #include "QuestPools.h" #include "RBAC.h" +#include "SpellMgr.h" #include "Transport.h" #include "WorldSession.h" #include @@ -78,6 +79,7 @@ public: { { "setbit", rbac::RBAC_PERM_COMMAND_DEBUG_SETBIT, false, &HandleDebugSet32BitCommand, "" }, { "threat", rbac::RBAC_PERM_COMMAND_DEBUG_THREAT, false, &HandleDebugThreatListCommand, "" }, + { "threatinfo", rbac::RBAC_PERM_COMMAND_DEBUG_THREATINFO, false, &HandleDebugThreatInfoCommand, "" }, { "combat", rbac::RBAC_PERM_COMMAND_DEBUG_COMBAT, false, &HandleDebugCombatListCommand, "" }, { "anim", rbac::RBAC_PERM_COMMAND_DEBUG_ANIM, false, &HandleDebugAnimCommand, "" }, { "arena", rbac::RBAC_PERM_COMMAND_DEBUG_ARENA, true, &HandleDebugArenaCommand, "" }, @@ -916,6 +918,79 @@ public: return true; } + static bool HandleDebugThreatInfoCommand(ChatHandler* handler, char const* /*args*/) + { + Unit* target = handler->getSelectedUnit(); + if (!target) + { + handler->SendSysMessage(LANG_SELECT_CHAR_OR_CREATURE); + handler->SetSentErrorMessage(true); + return false; + } + + handler->PSendSysMessage("Threat info for %s (%s):", target->GetName(), target->GetGUID().ToString().c_str()); + + ThreatManager const& mgr = target->GetThreatManager(); + + // _singleSchoolModifiers + { + auto& mods = mgr._singleSchoolModifiers; + handler->SendSysMessage(" - Single-school threat modifiers:"); + handler->PSendSysMessage(" |-- Physical: %.2f%%", mods[SPELL_SCHOOL_NORMAL]*100.0f); + handler->PSendSysMessage(" |-- Holy : %.2f%%", mods[SPELL_SCHOOL_HOLY]*100.0f); + handler->PSendSysMessage(" |-- Fire : %.2f%%", mods[SPELL_SCHOOL_FIRE]*100.0f); + handler->PSendSysMessage(" |-- Nature : %.2f%%", mods[SPELL_SCHOOL_NATURE]*100.0f); + handler->PSendSysMessage(" |-- Frost : %.2f%%", mods[SPELL_SCHOOL_FROST]*100.0f); + handler->PSendSysMessage(" |-- Shadow : %.2f%%", mods[SPELL_SCHOOL_SHADOW]*100.0f); + handler->PSendSysMessage(" |-- Arcane : %.2f%%", mods[SPELL_SCHOOL_ARCANE]*100.0f); + } + + // _multiSchoolModifiers + { + auto& mods = mgr._multiSchoolModifiers; + handler->PSendSysMessage("- Multi-school threat modifiers (%zu entries):", mods.size()); + for (auto const& pair : mods) + handler->PSendSysMessage(" |-- Mask 0x%x: %.2f%%", uint32(pair.first), pair.second); + } + + // _redirectInfo + { + auto const& redirectInfo = mgr._redirectInfo; + if (redirectInfo.empty()) + handler->SendSysMessage(" - No redirects being applied"); + else + { + handler->PSendSysMessage(" - %02zu redirects being applied:", redirectInfo.size()); + for (auto const& pair : redirectInfo) + { + Unit* unit = ObjectAccessor::GetUnit(*target, pair.first); + handler->PSendSysMessage(" |-- %02u%% to %s", pair.second, unit ? unit->GetName().c_str() : pair.first.ToString().c_str()); + } + } + } + + // _redirectRegistry + { + auto const& redirectRegistry = mgr._redirectRegistry; + if (redirectRegistry.empty()) + handler->SendSysMessage(" - No redirects are registered"); + else + { + handler->PSendSysMessage(" - %02zu spells may have redirects registered", redirectRegistry.size()); + for (auto const& outerPair : redirectRegistry) // (spellId, (guid, pct)) + { + SpellInfo const* const spell = sSpellMgr->GetSpellInfo(outerPair.first); + handler->PSendSysMessage(" |-- #%06u %s (%zu entries):", outerPair.first, spell ? spell->SpellName[0] : "", outerPair.second.size()); + for (auto const& innerPair : outerPair.second) // (guid, pct) + { + Unit* unit = ObjectAccessor::GetUnit(*target, innerPair.first); + handler->PSendSysMessage(" |-- %02u%% to %s", innerPair.second, unit ? unit->GetName().c_str() : innerPair.first.ToString().c_str()); + } + } + } + } + } + static bool HandleDebugCombatListCommand(ChatHandler* handler, char const* /*args*/) { Unit* target = handler->getSelectedUnit();