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,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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user