forked from mirror/azerothcore-wotlk
refactor(Core/Combat): Port TrinityCore heap-based threat system (#24715)
Co-authored-by: blinkysc <[email protected]> Co-authored-by: Treeston <[email protected]> Co-authored-by: killerwife <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
blinkysc
Treeston
killerwife
Claude Opus 4.6
parent
c80a0f1fad
commit
984baa92dd
@@ -35,7 +35,7 @@ int32 NullCreatureAI::Permissible(Creature const* creature)
|
||||
|
||||
void PassiveAI::UpdateAI(uint32)
|
||||
{
|
||||
if (me->IsInCombat() && me->getAttackers().empty())
|
||||
if (me->IsEngaged() && !me->IsInCombat())
|
||||
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ public:
|
||||
|
||||
void MoveInLineOfSight(Unit*) override {}
|
||||
void AttackStart(Unit* target) override;
|
||||
void JustEnteredCombat(Unit* who) override { EngagementStart(who); }
|
||||
void JustExitedCombat() override { EngagementOver(); }
|
||||
void JustStartedThreateningMe(Unit*) override {}
|
||||
void UpdateAI(uint32) override;
|
||||
void EnterEvadeMode(EvadeReason /*why*/) override {}
|
||||
|
||||
@@ -56,6 +59,8 @@ public:
|
||||
|
||||
void MoveInLineOfSight(Unit*) override {}
|
||||
void AttackStart(Unit*) override {}
|
||||
void JustStartedThreateningMe(Unit*) override {}
|
||||
void JustEnteredCombat(Unit*) override {}
|
||||
void UpdateAI(uint32) override {}
|
||||
void EnterEvadeMode(EvadeReason /*why*/) override {}
|
||||
void OnCharmed(bool /*apply*/) override {}
|
||||
|
||||
@@ -77,7 +77,7 @@ void PetAI::_stopAttack()
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
me->CombatStop();
|
||||
me->getHostileRefMgr().deleteReferences();
|
||||
me->GetThreatMgr().RemoveMeFromThreatLists();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ Unit* PetAI::SelectNextTarget(bool allowAutoSelect) const
|
||||
if (!tauntAuras.empty())
|
||||
for (Unit::AuraEffectList::const_reverse_iterator itr = tauntAuras.rbegin(); itr != tauntAuras.rend(); ++itr)
|
||||
if (Unit* caster = (*itr)->GetCaster())
|
||||
if (me->CanCreatureAttack(caster) && !caster->HasAuraTypeWithCaster(SPELL_AURA_IGNORED, me->GetGUID()))
|
||||
if (me->CanCreatureAttack(caster) && !caster->HasAuraTypeWithCaster(SPELL_AURA_MOD_DETAUNT, me->GetGUID()))
|
||||
return caster;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "UnitAI.h"
|
||||
#include "Creature.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
#include "Player.h"
|
||||
#include "Spell.h"
|
||||
@@ -135,10 +136,12 @@ SpellCastResult UnitAI::DoAddAuraToAllHostilePlayers(uint32 spellid)
|
||||
{
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
ThreatContainer::StorageType threatlist = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
|
||||
if (ref->IsOffline())
|
||||
continue;
|
||||
|
||||
if (Unit* unit = ref->GetVictim())
|
||||
{
|
||||
if (unit->IsPlayer())
|
||||
{
|
||||
@@ -146,8 +149,6 @@ SpellCastResult UnitAI::DoAddAuraToAllHostilePlayers(uint32 spellid)
|
||||
return SPELL_CAST_OK;
|
||||
}
|
||||
}
|
||||
else
|
||||
return SPELL_FAILED_BAD_TARGETS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,16 +159,16 @@ SpellCastResult UnitAI::DoCastToAllHostilePlayers(uint32 spellid, bool triggered
|
||||
{
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
ThreatContainer::StorageType threatlist = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
|
||||
if (ref->IsOffline())
|
||||
continue;
|
||||
|
||||
if (Unit* unit = ref->GetVictim())
|
||||
{
|
||||
if (unit->IsPlayer())
|
||||
return me->CastSpell(unit, spellid, triggered);
|
||||
}
|
||||
else
|
||||
return SPELL_FAILED_BAD_TARGETS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +377,7 @@ void UnitAI::FillAISpellInfo()
|
||||
}
|
||||
}
|
||||
|
||||
ThreatMgr& UnitAI::GetThreatMgr()
|
||||
ThreatManager& UnitAI::GetThreatMgr()
|
||||
{
|
||||
return me->GetThreatMgr();
|
||||
}
|
||||
@@ -386,6 +387,60 @@ void UnitAI::SortByDistance(std::list<Unit*>& list, bool ascending)
|
||||
list.sort(Acore::ObjectDistanceOrderPred(me, ascending));
|
||||
}
|
||||
|
||||
void UnitAI::EvadeTimerExpired()
|
||||
{
|
||||
Creature* creature = me->ToCreature();
|
||||
if (!creature)
|
||||
return;
|
||||
|
||||
CreatureAI* ai = creature->AI();
|
||||
if (!ai)
|
||||
return;
|
||||
|
||||
// Check if we can teleport an unreachable player first
|
||||
if (ObjectGuid targetGuid = creature->GetCannotReachTarget())
|
||||
{
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*creature, targetGuid))
|
||||
{
|
||||
if (creature->IsEngagedBy(player) && ai->OnTeleportUnreacheablePlayer(player))
|
||||
{
|
||||
creature->SetCannotReachTarget();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (creature->GetMap()->IsRaid())
|
||||
{
|
||||
creature->GetCombatManager().ContinueEvadeRegen();
|
||||
return;
|
||||
}
|
||||
|
||||
// If only one target, enter evade mode
|
||||
if (creature->GetThreatMgr().GetThreatListSize() <= 1)
|
||||
{
|
||||
ai->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_PATH);
|
||||
return;
|
||||
}
|
||||
|
||||
// Multiple targets - clear threat on unreachable target and try another
|
||||
if (ObjectGuid targetGuid = creature->GetCannotReachTarget())
|
||||
{
|
||||
if (Unit* target = ObjectAccessor::GetUnit(*creature, targetGuid))
|
||||
{
|
||||
if (creature->GetThreatMgr().IsThreatenedBy(target))
|
||||
{
|
||||
creature->GetThreatMgr().ClearThreat(target);
|
||||
creature->SetCannotReachTarget();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback - enter evade
|
||||
ai->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_PATH);
|
||||
}
|
||||
|
||||
//Enable PlayerAI when charmed
|
||||
void PlayerAI::OnCharmed(bool apply)
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ struct DefaultTargetSelector : public Acore::unary_function<Unit*, bool>
|
||||
// playerOnly: self explaining
|
||||
// withMainTank: allow current tank to be selected
|
||||
// aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura
|
||||
DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, bool withMainTank, int32 aura) : me(unit), m_dist(dist), except(!withMainTank ? me->GetThreatMgr().GetCurrentVictim() : nullptr), m_playerOnly(playerOnly), m_aura(aura) {}
|
||||
DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, bool withMainTank, int32 aura) : me(unit), m_dist(dist), except(!withMainTank ? me->GetThreatMgr().GetLastVictim() : nullptr), m_playerOnly(playerOnly), m_aura(aura) {}
|
||||
|
||||
bool operator()(Unit const* target) const
|
||||
{
|
||||
@@ -126,7 +126,7 @@ public:
|
||||
bool operator()(Unit const* target) const;
|
||||
|
||||
private:
|
||||
Creature const* _source;
|
||||
Creature* _source;
|
||||
bool _playerOnly;
|
||||
};
|
||||
|
||||
@@ -146,7 +146,7 @@ struct PowerUsersSelector : public Acore::unary_function<Unit*, bool>
|
||||
if (!_me || !target)
|
||||
return false;
|
||||
|
||||
if (!_withTank && target == _me->GetThreatMgr().GetCurrentVictim())
|
||||
if (!_withTank && target == _me->GetThreatMgr().GetLastVictim())
|
||||
return false;
|
||||
|
||||
if (target->getPowerType() != _power)
|
||||
@@ -237,7 +237,7 @@ public:
|
||||
template <class PREDICATE>
|
||||
Unit* SelectTarget(SelectTargetMethod targetType, uint32 position, PREDICATE const& predicate)
|
||||
{
|
||||
ThreatMgr& mgr = GetThreatMgr();
|
||||
ThreatManager& mgr = GetThreatMgr();
|
||||
// shortcut: if we ignore the first <offset> elements, and there are at most <offset> elements, then we ignore ALL elements
|
||||
if (mgr.GetThreatListSize() <= position)
|
||||
return nullptr;
|
||||
@@ -282,7 +282,7 @@ public:
|
||||
void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, uint32 position, PREDICATE const& predicate)
|
||||
{
|
||||
targetList.clear();
|
||||
ThreatMgr& mgr = GetThreatMgr();
|
||||
ThreatManager& mgr = GetThreatMgr();
|
||||
// shortcut: we're gonna ignore the first <offset> elements, and there's at most <offset> elements, so we ignore them all - nothing to do here
|
||||
if (mgr.GetThreatListSize() <= position)
|
||||
return;
|
||||
@@ -352,11 +352,14 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Called when the unit leaves combat
|
||||
*
|
||||
* @todo Never invoked right now. Preparation for Combat Threat refactor
|
||||
*/
|
||||
virtual void JustExitedCombat() { }
|
||||
|
||||
/**
|
||||
* @brief Called when evade timer expires (target unreachable for too long)
|
||||
*/
|
||||
virtual void EvadeTimerExpired();
|
||||
|
||||
/// @brief Called at any Damage to any victim (before damage apply)
|
||||
virtual void DamageDealt(Unit* /*victim*/, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) {}
|
||||
|
||||
@@ -421,7 +424,7 @@ public:
|
||||
virtual std::string GetDebugInfo() const;
|
||||
|
||||
private:
|
||||
ThreatMgr& GetThreatMgr();
|
||||
ThreatManager& GetThreatMgr();
|
||||
void SortByDistance(std::list<Unit*>& list, bool ascending = true);
|
||||
};
|
||||
|
||||
|
||||
@@ -33,9 +33,23 @@
|
||||
#include <functional>
|
||||
|
||||
//Disable CreatureAI when charmed
|
||||
void CreatureAI::OnCharmed(bool /*apply*/)
|
||||
void CreatureAI::OnCharmed(bool /* apply */)
|
||||
{
|
||||
//me->IsAIEnabled = !apply;*/
|
||||
if (!me->IsCharmed() && !me->LastCharmerGUID.IsEmpty())
|
||||
{
|
||||
if (!me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
if (Unit* lastCharmer = ObjectAccessor::GetUnit(*me, me->LastCharmerGUID))
|
||||
me->EngageWithTarget(lastCharmer);
|
||||
}
|
||||
|
||||
me->LastCharmerGUID.Clear();
|
||||
|
||||
if (!me->IsInCombat())
|
||||
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
|
||||
}
|
||||
|
||||
// trigger AI switch
|
||||
me->NeedChangeAI = true;
|
||||
me->IsAIEnabled = false;
|
||||
}
|
||||
@@ -139,13 +153,13 @@ void CreatureAI::DoZoneInCombat(Creature* creature /*= nullptr*/, float maxRange
|
||||
continue;
|
||||
}
|
||||
|
||||
creature->SetInCombatWith(player);
|
||||
player->SetInCombatWith(creature);
|
||||
creature->EngageWithTarget(player);
|
||||
|
||||
if (creature->CanHaveThreatList())
|
||||
{
|
||||
creature->AddThreat(player, 0.0f);
|
||||
}
|
||||
for (Unit* pet : player->m_Controlled)
|
||||
creature->EngageWithTarget(pet);
|
||||
|
||||
if (Unit* vehicle = player->GetVehicleBase())
|
||||
creature->EngageWithTarget(vehicle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,6 +193,15 @@ void CreatureAI::MoveInLineOfSight(Unit* who)
|
||||
AttackStart(who);
|
||||
}
|
||||
|
||||
void CreatureAI::OnOwnerCombatInteraction(Unit* target)
|
||||
{
|
||||
if (!target || !me->IsAlive())
|
||||
return;
|
||||
|
||||
if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target))
|
||||
me->EngageWithTarget(target);
|
||||
}
|
||||
|
||||
// Distract creature, if player gets too close while stealthed/prowling
|
||||
void CreatureAI::TriggerAlert(Unit const* who) const
|
||||
{
|
||||
@@ -241,6 +264,47 @@ void CreatureAI::EnterEvadeMode(EvadeReason why)
|
||||
}
|
||||
}
|
||||
|
||||
void CreatureAI::JustEnteredCombat(Unit* who)
|
||||
{
|
||||
// Creatures without threat list use JustEnteredCombat to trigger engagement
|
||||
if (!IsEngaged() && !me->CanHaveThreatList())
|
||||
EngagementStart(who);
|
||||
}
|
||||
|
||||
void CreatureAI::EngagementStart(Unit* who)
|
||||
{
|
||||
if (_isEngaged)
|
||||
{
|
||||
LOG_ERROR("scripts.ai", "CreatureAI::EngagementStart called even though creature is already engaged. Creature debug info:\n{}", me->GetDebugInfo());
|
||||
return;
|
||||
}
|
||||
_isEngaged = true;
|
||||
|
||||
me->AtEngage(who);
|
||||
}
|
||||
|
||||
void CreatureAI::EngagementOver()
|
||||
{
|
||||
if (!_isEngaged)
|
||||
{
|
||||
LOG_DEBUG("scripts.ai", "CreatureAI::EngagementOver called even though creature is not currently engaged. Creature debug info:\n{}", me->GetDebugInfo());
|
||||
return;
|
||||
}
|
||||
_isEngaged = false;
|
||||
|
||||
me->AtDisengage();
|
||||
}
|
||||
|
||||
void CreatureAI::JustExitedCombat()
|
||||
{
|
||||
EngagementOver();
|
||||
|
||||
// If creature is alive, in world, and not already evading, trigger evade to return home
|
||||
// Check IsInWorld to avoid evade during server shutdown/cleanup
|
||||
if (me->IsAlive() && me->IsInWorld() && !me->IsInEvadeMode())
|
||||
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
|
||||
}
|
||||
|
||||
/*void CreatureAI::AttackedBy(Unit* attacker)
|
||||
{
|
||||
if (!me->GetVictim())
|
||||
@@ -279,38 +343,50 @@ bool CreatureAI::UpdateVictim()
|
||||
if (!me->IsEngaged())
|
||||
return false;
|
||||
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
EngagementOver();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
if (Unit* victim = me->SelectVictim())
|
||||
AttackStart(victim);
|
||||
return me->GetVictim();
|
||||
if (victim != me->GetVictim())
|
||||
AttackStart(victim);
|
||||
return me->GetVictim() != nullptr;
|
||||
}
|
||||
// xinef: if we have any victim, just return true
|
||||
else if (me->GetVictim() && me->GetExactDist(me->GetVictim()) < 30.0f)
|
||||
return true;
|
||||
else if (me->GetThreatMgr().isThreatListEmpty())
|
||||
else if (!me->IsInCombat())
|
||||
{
|
||||
EnterEvadeMode();
|
||||
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
|
||||
return false;
|
||||
}
|
||||
else if (me->GetVictim())
|
||||
me->AttackStop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/)
|
||||
{
|
||||
if (me->IsInEvadeMode())
|
||||
return false;
|
||||
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
EngagementOver();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set evade state early to prevent recursion when CombatStop triggers JustExitedCombat
|
||||
// This will be cleared in MoveTargetedHome for pets/guardians following their owner
|
||||
me->AddUnitState(UNIT_STATE_EVADE);
|
||||
|
||||
// don't remove vehicle auras, passengers aren't supposed to drop off the vehicle
|
||||
// don't remove clone caster on evade (to be verified)
|
||||
me->RemoveEvadeAuras();
|
||||
|
||||
me->ClearComboPointHolders(); // Remove all combo points targeting this unit
|
||||
// sometimes bosses stuck in combat?
|
||||
me->GetThreatMgr().ClearAllThreat();
|
||||
me->CombatStop(true);
|
||||
me->LoadCreaturesAddon(true);
|
||||
me->SetLootRecipient(nullptr);
|
||||
@@ -321,14 +397,8 @@ bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/)
|
||||
if (ZoneScript* zoneScript = me->GetZoneScript() ? me->GetZoneScript() : (ZoneScript*)me->GetInstanceScript())
|
||||
zoneScript->OnCreatureEvade(me);
|
||||
|
||||
if (me->IsInEvadeMode())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (CreatureGroup* formation = me->GetFormation())
|
||||
{
|
||||
if (CreatureGroup* formation = me->GetFormation())
|
||||
formation->MemberEvaded(me);
|
||||
}
|
||||
|
||||
if (TempSummon* summon = me->ToTempSummon())
|
||||
{
|
||||
@@ -345,6 +415,8 @@ bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/)
|
||||
}
|
||||
}
|
||||
|
||||
EngagementOver();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,10 +97,13 @@ public:
|
||||
|
||||
WorldObject* GetSummoner() const;
|
||||
|
||||
explicit CreatureAI(Creature* creature) : UnitAI(creature), me(creature), _boundary(nullptr), _negateBoundary(false), m_MoveInLineOfSight_locked(false) { }
|
||||
explicit CreatureAI(Creature* creature) : UnitAI(creature), me(creature), _boundary(nullptr), _negateBoundary(false), _isEngaged(false), m_MoveInLineOfSight_locked(false) { }
|
||||
|
||||
~CreatureAI() override {}
|
||||
|
||||
/// @brief Check if creature is currently engaged in combat
|
||||
bool IsEngaged() const { return _isEngaged; }
|
||||
|
||||
void MoveCircleChecks();
|
||||
void MoveBackwardsChecks();
|
||||
|
||||
@@ -118,11 +121,24 @@ public:
|
||||
// Called for reaction at stopping attack at no attackers or targets
|
||||
virtual void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER);
|
||||
|
||||
// Called for reaction whenever we start being in combat (overridden from UnitAI)
|
||||
void JustEnteredCombat(Unit* who) override;
|
||||
|
||||
// Called for reaction when a new non-offline unit is added to the threat list
|
||||
virtual void JustStartedThreateningMe(Unit* who) { if (!IsEngaged()) EngagementStart(who); }
|
||||
|
||||
/**
|
||||
* @brief Called for reaction when initially engaged
|
||||
* @brief Called for reaction when initially engaged - this happens _after_ JustEnteredCombat
|
||||
*/
|
||||
virtual void JustEngagedWith(Unit* /*who*/) {}
|
||||
|
||||
// Engagement handling
|
||||
void EngagementStart(Unit* who);
|
||||
void EngagementOver();
|
||||
|
||||
// Called when combat ends - clears engagement state and triggers evade if needed
|
||||
void JustExitedCombat() override;
|
||||
|
||||
// Called when the creature is killed
|
||||
virtual void JustDied(Unit* /*killer*/) {}
|
||||
|
||||
@@ -181,10 +197,13 @@ public:
|
||||
virtual void ReceiveEmote(Player* /*player*/, uint32 /*emoteId*/) {}
|
||||
|
||||
// Called when owner takes damage
|
||||
virtual void OwnerAttackedBy(Unit* /*attacker*/) {}
|
||||
virtual void OwnerAttackedBy(Unit* attacker) { OnOwnerCombatInteraction(attacker); }
|
||||
|
||||
// Called when owner attacks something
|
||||
virtual void OwnerAttacked(Unit* /*target*/) {}
|
||||
virtual void OwnerAttacked(Unit* target) { OnOwnerCombatInteraction(target); }
|
||||
|
||||
// Default handler for owner combat interactions — makes controlled creatures auto-engage
|
||||
void OnOwnerCombatInteraction(Unit* target);
|
||||
|
||||
/// == Triggered Actions Requested ==================
|
||||
|
||||
@@ -248,6 +267,7 @@ protected:
|
||||
|
||||
CreatureBoundary const* _boundary;
|
||||
bool _negateBoundary;
|
||||
bool _isEngaged;
|
||||
|
||||
private:
|
||||
bool m_MoveInLineOfSight_locked;
|
||||
|
||||
@@ -472,7 +472,7 @@ void ScriptedAI::DoResetThreat(Unit* unit)
|
||||
|
||||
void ScriptedAI::DoResetThreatList()
|
||||
{
|
||||
if (!me->CanHaveThreatList() || me->GetThreatMgr().isThreatListEmpty())
|
||||
if (!me->CanHaveThreatList() || me->GetThreatMgr().IsThreatListEmpty())
|
||||
{
|
||||
LOG_ERROR("entities.unit.ai", "DoResetThreatList called for creature that either cannot have threat list or has empty threat list (me entry = {})", me->GetEntry());
|
||||
return;
|
||||
@@ -714,11 +714,12 @@ void BossAI::TeleportCheaters()
|
||||
float x, y, z;
|
||||
me->GetPosition(x, y, z);
|
||||
|
||||
ThreatContainer::StorageType threatList = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
|
||||
if (Unit* target = (*itr)->getTarget())
|
||||
if (target->IsPlayer() && !IsInBoundary(target))
|
||||
target->NearTeleportTo(x, y, z, 0);
|
||||
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
|
||||
{
|
||||
Unit* target = pair.second->GetOther(me);
|
||||
if (target->IsControlledByPlayer() && !IsInBoundary(target))
|
||||
target->NearTeleportTo(x, y, z, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void BossAI::JustSummoned(Creature* summon)
|
||||
|
||||
@@ -523,8 +523,16 @@ void SmartAI::CheckConditions(const uint32 diff)
|
||||
|
||||
void SmartAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
bool hasVictim = UpdateVictim();
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
if (IsEngaged())
|
||||
EngagementOver();
|
||||
return;
|
||||
}
|
||||
|
||||
CheckConditions(diff);
|
||||
|
||||
bool hasVictim = UpdateVictim();
|
||||
GetScript()->OnUpdate(diff);
|
||||
UpdatePath(diff);
|
||||
UpdateDespawn(diff);
|
||||
@@ -689,7 +697,12 @@ void SmartAI::EnterEvadeMode(EvadeReason /*why*/)
|
||||
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_EVADE); //must be after aura clear so we can cast spells from db
|
||||
|
||||
if (HasEscortState(SMART_ESCORT_ESCORTING))
|
||||
if (Unit* owner = me->GetCharmerOrOwner())
|
||||
{
|
||||
me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
me->ClearUnitState(UNIT_STATE_EVADE);
|
||||
}
|
||||
else if (HasEscortState(SMART_ESCORT_ESCORTING))
|
||||
{
|
||||
AddEscortState(SMART_ESCORT_RETURNING);
|
||||
ReturnToLastOOCPos();
|
||||
@@ -1003,13 +1016,24 @@ void SmartAI::OnCharmed(bool /* apply */)
|
||||
|
||||
mIsCharmed = charmed;
|
||||
|
||||
if (charmed && !me->isPossessed() && !me->IsVehicle())
|
||||
me->GetMotionMaster()->MoveFollow(me->GetCharmer(), PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
|
||||
if (!charmed && !me->IsInEvadeMode())
|
||||
{
|
||||
if (mCanRepeatPath)
|
||||
StartPath(mForcedMovement, GetScript()->GetPathId(), true);
|
||||
|
||||
if (Unit* charmer = me->GetCharmer())
|
||||
AttackStart(charmer);
|
||||
if (!me->LastCharmerGUID.IsEmpty())
|
||||
{
|
||||
if (!me->HasReactState(REACT_PASSIVE))
|
||||
if (Unit* lastCharmer = ObjectAccessor::GetUnit(*me, me->LastCharmerGUID))
|
||||
me->EngageWithTarget(lastCharmer);
|
||||
me->LastCharmerGUID.Clear();
|
||||
|
||||
if (!me->IsInCombat())
|
||||
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
|
||||
}
|
||||
}
|
||||
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_CHARMED, nullptr, 0, 0, charmed);
|
||||
|
||||
@@ -585,15 +585,11 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
|
||||
if (!me)
|
||||
break;
|
||||
|
||||
ThreatContainer::StorageType threatList = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator i = threatList.begin(); i != threatList.end(); ++i)
|
||||
for (ThreatReference* ref : me->GetThreatMgr().GetModifiableThreatList())
|
||||
{
|
||||
if (Unit* target = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid()))
|
||||
{
|
||||
me->GetThreatMgr().ModifyThreatByPercent(target, e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC);
|
||||
LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_ALL_PCT: Creature {} modify threat for unit {}, value {}",
|
||||
me->GetGUID().ToString(), target->GetGUID().ToString(), e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC);
|
||||
}
|
||||
ref->ModifyThreatByPercent(std::max<int32>(-100, int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC)));
|
||||
LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_ALL_PCT: Creature {} modify threat for unit {}, value {}",
|
||||
me->GetGUID().ToString(), ref->GetVictim()->GetGUID().ToString(), int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -606,9 +602,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
|
||||
{
|
||||
if (IsUnit(target))
|
||||
{
|
||||
me->GetThreatMgr().ModifyThreatByPercent(target->ToUnit(), e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC);
|
||||
LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_SINGLE_PCT: Creature guidLow {} modify threat for unit {}, value {}",
|
||||
me->GetGUID().ToString(), target->GetGUID().ToString(), e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC);
|
||||
me->GetThreatMgr().ModifyThreatByPercent(target->ToUnit(), std::max<int32>(-100, int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC)));
|
||||
LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_SINGLE_PCT: Creature {} modify threat for unit {}, value {}",
|
||||
me->GetGUID().ToString(), target->GetGUID().ToString(), int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1294,9 +1290,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
|
||||
for (WorldObject* unit : units)
|
||||
if (IsPlayer(unit) && !unit->ToPlayer()->isDead())
|
||||
{
|
||||
me->SetInCombatWith(unit->ToPlayer());
|
||||
unit->ToPlayer()->SetInCombatWith(me);
|
||||
me->AddThreat(unit->ToPlayer(), 0.0f);
|
||||
me->EngageWithTarget(unit->ToPlayer());
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2611,9 +2605,11 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
|
||||
}
|
||||
case SMART_ACTION_ADD_THREAT:
|
||||
{
|
||||
if (!me->CanHaveThreatList())
|
||||
break;
|
||||
for (WorldObject* const target : targets)
|
||||
if (IsUnit(target))
|
||||
me->AddThreat(target->ToUnit(), float(e.action.threatPCT.threatINC) - float(e.action.threatPCT.threatDEC));
|
||||
me->GetThreatMgr().AddThreat(target->ToUnit(), float(e.action.threat.threatINC) - float(e.action.threat.threatDEC), nullptr, true, true);
|
||||
break;
|
||||
}
|
||||
case SMART_ACTION_LOAD_EQUIPMENT:
|
||||
@@ -3900,9 +3896,8 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e,
|
||||
{
|
||||
if (me)
|
||||
{
|
||||
ThreatContainer::StorageType threatList = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator i = threatList.begin(); i != threatList.end(); ++i)
|
||||
if (Unit* temp = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid()))
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
if (Unit* temp = ref->GetVictim())
|
||||
// Xinef: added distance check
|
||||
if (e.target.threatList.maxDist == 0 || me->IsWithinCombatRange(temp, (float)e.target.threatList.maxDist))
|
||||
targets.push_back(temp);
|
||||
@@ -4848,12 +4843,11 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui
|
||||
if (!me || !me->IsEngaged())
|
||||
return;
|
||||
|
||||
ThreatContainer::StorageType threatList = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator i = threatList.begin(); i != threatList.end(); ++i)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (Unit* target = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid()))
|
||||
if (Unit* target = ref->GetVictim())
|
||||
{
|
||||
if (!target || !IsPlayer(target) || !target->IsNonMeleeSpellCast(false, false, true))
|
||||
if (!IsPlayer(target) || !target->IsNonMeleeSpellCast(false, false, true))
|
||||
continue;
|
||||
|
||||
if (!(me->IsInRange(target, (float)e.event.minMaxRepeat.rangeMin, (float)e.event.minMaxRepeat.rangeMax)))
|
||||
@@ -4867,6 +4861,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui
|
||||
|
||||
// No targets found
|
||||
RecalcTimer(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax);
|
||||
|
||||
break;
|
||||
}
|
||||
case SMART_EVENT_AREA_RANGE:
|
||||
@@ -4874,10 +4869,9 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui
|
||||
if (!me || !me->IsEngaged())
|
||||
return;
|
||||
|
||||
ThreatContainer::StorageType threatList = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator i = threatList.begin(); i != threatList.end(); ++i)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (Unit* target = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid()))
|
||||
if (Unit* target = ref->GetVictim())
|
||||
{
|
||||
if (!(me->IsInRange(target, (float)e.event.minMaxRepeat.rangeMin, (float)e.event.minMaxRepeat.rangeMax)))
|
||||
continue;
|
||||
|
||||
@@ -903,7 +903,7 @@ void Battleground::EndBattleground(PvPTeamId winnerTeamId)
|
||||
{
|
||||
//needed cause else in av some creatures will kill the players at the end
|
||||
player->CombatStop();
|
||||
player->getHostileRefMgr().deleteReferences();
|
||||
player->GetThreatMgr().RemoveMeFromThreatLists();
|
||||
}
|
||||
|
||||
uint32 winner_kills = player->GetRandomWinner() ? sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_HONOR_LAST) : sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_HONOR_FIRST);
|
||||
|
||||
@@ -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 <[email protected]> - Combat/Threat system rewrite (PR #19930)
|
||||
* Shauren <[email protected]>
|
||||
* Spp <[email protected]>
|
||||
*/
|
||||
|
||||
#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 <[email protected]> - Combat/Threat system rewrite (PR #19930)
|
||||
* Shauren <[email protected]>
|
||||
* Spp <[email protected]>
|
||||
*/
|
||||
|
||||
#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 <[email protected]> - Combat/Threat system rewrite (PR #19930)
|
||||
* Shauren <[email protected]>
|
||||
* Spp <[email protected]>
|
||||
*/
|
||||
|
||||
#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
|
||||
@@ -221,16 +221,12 @@ bool AssistDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
|
||||
if (assistant && assistant->CanAssistTo(m_owner, victim))
|
||||
{
|
||||
assistant->SetNoCallAssistance(true);
|
||||
assistant->CombatStart(victim);
|
||||
if (assistant->IsAIEnabled)
|
||||
{
|
||||
assistant->AI()->AttackStart(victim);
|
||||
assistant->EngageWithTarget(victim);
|
||||
|
||||
// When nearby mobs aggro from another mob's initial call for assistance
|
||||
// their leash timers become linked and attacking one will keep the rest from evading.
|
||||
if (assistant->GetVictim())
|
||||
assistant->SetLastLeashExtensionTimePtr(m_owner->GetLastLeashExtensionTimePtr());
|
||||
}
|
||||
// When nearby mobs aggro from another mob's initial call for assistance
|
||||
// their leash timers become linked and attacking one will keep the rest from evading.
|
||||
if (assistant->GetVictim())
|
||||
assistant->SetLastLeashExtensionTimePtr(m_owner->GetLastLeashExtensionTimePtr());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,7 +263,7 @@ Creature::Creature(): Unit(), MovableMapObject(), m_groupLootTimer(0), lootingGr
|
||||
m_transportCheckTimer(1000), lootPickPocketRestoreTime(0), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE),
|
||||
m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_alreadyCallForHelp(false), m_AlreadyCallAssistance(false),
|
||||
m_AlreadySearchedAssistance(false), m_regenHealth(true), m_regenPower(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), _gossipMenuId(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false),
|
||||
m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f),_sparringPct(0.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr), m_cannotReachTimer(0),
|
||||
m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f),_sparringPct(0.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr),
|
||||
_isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _isCombatMovementAllowed(true)
|
||||
{
|
||||
m_regenTimer = CREATURE_REGEN_INTERVAL;
|
||||
@@ -529,6 +525,8 @@ bool Creature::InitEntry(uint32 Entry, const CreatureData* data)
|
||||
for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i)
|
||||
m_spells[i] = GetCreatureTemplate()->spells[i];
|
||||
|
||||
GetThreatMgr().Initialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -722,6 +720,8 @@ void Creature::Update(uint32 diff)
|
||||
if (!IsAlive())
|
||||
break;
|
||||
|
||||
GetThreatMgr().Update(diff);
|
||||
|
||||
// if creature is charmed, switch to charmed AI
|
||||
if (NeedChangeAI)
|
||||
{
|
||||
@@ -865,45 +865,7 @@ void Creature::Update(uint32 diff)
|
||||
m_regenTimer += CREATURE_REGEN_INTERVAL;
|
||||
}
|
||||
|
||||
if (CanNotReachTarget() && !IsInEvadeMode())
|
||||
{
|
||||
m_cannotReachTimer += diff;
|
||||
if (m_cannotReachTimer >= (sWorld->getIntConfig(CONFIG_NPC_EVADE_IF_NOT_REACHABLE) * IN_MILLISECONDS))
|
||||
{
|
||||
Player* cannotReachPlayer = ObjectAccessor::GetPlayer(*this, m_cannotReachTarget);
|
||||
if (cannotReachPlayer && IsEngagedBy(cannotReachPlayer) && IsAIEnabled && AI()->OnTeleportUnreacheablePlayer(cannotReachPlayer))
|
||||
{
|
||||
SetCannotReachTarget();
|
||||
}
|
||||
else if (!GetMap()->IsRaid())
|
||||
{
|
||||
auto EnterEvade = [&]()
|
||||
{
|
||||
if (CreatureAI* ai = AI())
|
||||
{
|
||||
ai->EnterEvadeMode(CreatureAI::EvadeReason::EVADE_REASON_NO_PATH);
|
||||
}
|
||||
};
|
||||
|
||||
if (GetThreatMgr().GetThreatListSize() <= 1)
|
||||
{
|
||||
EnterEvade();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HostileReference* ref = GetThreatMgr().GetOnlineContainer().getReferenceByTarget(m_cannotReachTarget))
|
||||
{
|
||||
ref->removeReference();
|
||||
SetCannotReachTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
EnterEvade();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Evade timer is now handled by CombatManager::Update() which calls UnitAI::EvadeTimerExpired()
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -2792,6 +2754,80 @@ void Creature::SetInCombatWithZone()
|
||||
AI()->DoZoneInCombat();
|
||||
}
|
||||
|
||||
void Creature::AtEngage(Unit* target)
|
||||
{
|
||||
Unit::AtEngage(target);
|
||||
|
||||
// If we somehow engage in combat with a player while immune, remove immunity so they can fight back
|
||||
if (target && IsImmuneToPC() && target->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
SetImmuneToPC(false);
|
||||
|
||||
if (!IsStandState())
|
||||
SetStandState(UNIT_STAND_STATE_STAND);
|
||||
|
||||
if (!(GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_ALLOW_MOUNTED_COMBAT))
|
||||
Dismount();
|
||||
|
||||
RefreshSwimmingFlag();
|
||||
|
||||
if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
|
||||
{
|
||||
UpdateSpeed(MOVE_RUN, true);
|
||||
UpdateSpeed(MOVE_SWIM, true);
|
||||
UpdateSpeed(MOVE_FLIGHT, true);
|
||||
}
|
||||
|
||||
MovementGeneratorType const movetype = GetMotionMaster()->GetCurrentMovementGeneratorType();
|
||||
if (movetype == WAYPOINT_MOTION_TYPE || movetype == ESCORT_MOTION_TYPE || (IsAIEnabled && AI()->IsEscorted()))
|
||||
{
|
||||
SetHomePosition(GetPosition());
|
||||
|
||||
// if its a vehicle, set the home position of every creature passenger at engage
|
||||
// so that they are in combat range if hostile
|
||||
if (Vehicle* vehicle = GetVehicleKit())
|
||||
{
|
||||
for (auto& seat : vehicle->Seats)
|
||||
if (Unit* passenger = ObjectAccessor::GetUnit(*this, seat.second.Passenger.Guid))
|
||||
if (Creature* creature = passenger->ToCreature())
|
||||
creature->SetHomePosition(GetPosition());
|
||||
}
|
||||
}
|
||||
|
||||
if (CreatureAI* ai = AI())
|
||||
{
|
||||
ai->JustEngagedWith(target);
|
||||
UpdateLeashExtensionTime();
|
||||
|
||||
if (CreatureGroup* formation = GetFormation())
|
||||
formation->MemberEngagingTarget(this, target);
|
||||
|
||||
sScriptMgr->OnUnitEnterCombat(this, target);
|
||||
}
|
||||
}
|
||||
|
||||
void Creature::AtDisengage()
|
||||
{
|
||||
Unit::AtDisengage();
|
||||
|
||||
ClearUnitState(UNIT_STATE_ATTACK_PLAYER);
|
||||
if (IsAlive() && HasDynamicFlag(UNIT_DYNFLAG_TAPPED))
|
||||
ReplaceAllDynamicFlags(GetCreatureTemplate()->dynamicflags);
|
||||
|
||||
if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
|
||||
{
|
||||
UpdateSpeed(MOVE_RUN, true);
|
||||
UpdateSpeed(MOVE_SWIM, true);
|
||||
UpdateSpeed(MOVE_FLIGHT, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool Creature::IsEngaged() const
|
||||
{
|
||||
if (CreatureAI const* ai = AI())
|
||||
return ai->IsEngaged();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Creature::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs)
|
||||
{
|
||||
for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
|
||||
@@ -3393,7 +3429,9 @@ void Creature::SetDisplayFromModel(uint32 modelIdx)
|
||||
|
||||
void Creature::SetTarget(ObjectGuid guid)
|
||||
{
|
||||
if (!_focusSpell)
|
||||
if (_focusSpell)
|
||||
_spellFocusTarget = guid;
|
||||
else
|
||||
SetGuidValue(UNIT_FIELD_TARGET, guid);
|
||||
}
|
||||
|
||||
@@ -3404,6 +3442,7 @@ void Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target)
|
||||
return;
|
||||
|
||||
_focusSpell = focusSpell;
|
||||
_spellFocusTarget = GetGuidValue(UNIT_FIELD_TARGET);
|
||||
|
||||
SetGuidValue(UNIT_FIELD_TARGET, this == target ? ObjectGuid::Empty : target->GetGUID());
|
||||
if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AI_DOESNT_FACE_TARGET))
|
||||
@@ -3433,10 +3472,8 @@ void Creature::ReleaseFocus(Spell const* focusSpell)
|
||||
return;
|
||||
|
||||
_focusSpell = nullptr;
|
||||
if (Unit* victim = GetVictim())
|
||||
SetGuidValue(UNIT_FIELD_TARGET, victim->GetGUID());
|
||||
else
|
||||
SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
|
||||
SetGuidValue(UNIT_FIELD_TARGET, _spellFocusTarget);
|
||||
_spellFocusTarget.Clear();
|
||||
|
||||
if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AI_DOESNT_FACE_TARGET))
|
||||
ClearUnitState(UNIT_STATE_ROTATING);
|
||||
@@ -3509,16 +3546,19 @@ bool Creature::IsMovementPreventedByCasting() const
|
||||
void Creature::SetCannotReachTarget(ObjectGuid const& cannotReach)
|
||||
{
|
||||
if (cannotReach == m_cannotReachTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_cannotReachTarget = cannotReach;
|
||||
m_cannotReachTimer = 0;
|
||||
|
||||
if (cannotReach)
|
||||
{
|
||||
LOG_DEBUG("entities.unit", "Creature::SetCannotReachTarget() called with true. Details: {}", GetDebugInfo());
|
||||
LOG_DEBUG("entities.unit", "Creature::SetCannotReachTarget() called with target {}. Details: {}", cannotReach.ToString(), GetDebugInfo());
|
||||
if (!GetCombatManager().IsInEvadeMode())
|
||||
GetCombatManager().StartEvadeTimer();
|
||||
}
|
||||
else
|
||||
{
|
||||
GetCombatManager().StopEvade();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3529,12 +3569,7 @@ bool Creature::CanNotReachTarget() const
|
||||
|
||||
bool Creature::IsNotReachableAndNeedRegen() const
|
||||
{
|
||||
if (CanNotReachTarget())
|
||||
{
|
||||
return m_cannotReachTimer >= (sWorld->getIntConfig(CONFIG_NPC_REGEN_TIME_IF_NOT_REACHABLE_IN_RAID) * IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
return false;
|
||||
return CanNotReachTarget() && GetCombatManager().IsEvadeRegen();
|
||||
}
|
||||
|
||||
std::shared_ptr<time_t> const& Creature::GetLastLeashExtensionTimePtr() const
|
||||
|
||||
@@ -313,6 +313,11 @@ public:
|
||||
|
||||
void SetInCombatWithZone();
|
||||
|
||||
// Engagement callbacks (called from CreatureAI::EngagementStart/EngagementOver)
|
||||
void AtEngage(Unit* target) override;
|
||||
void AtDisengage() override;
|
||||
[[nodiscard]] bool IsEngaged() const override;
|
||||
|
||||
[[nodiscard]] bool hasQuest(uint32 quest_id) const override;
|
||||
[[nodiscard]] bool hasInvolvedQuest(uint32 quest_id) const override;
|
||||
|
||||
@@ -330,6 +335,7 @@ public:
|
||||
|
||||
void SetCannotReachTarget(ObjectGuid const& target = ObjectGuid::Empty);
|
||||
[[nodiscard]] bool CanNotReachTarget() const;
|
||||
[[nodiscard]] ObjectGuid const& GetCannotReachTarget() const { return m_cannotReachTarget; }
|
||||
[[nodiscard]] bool IsNotReachableAndNeedRegen() const;
|
||||
|
||||
void SetPosition(float x, float y, float z, float o);
|
||||
@@ -521,9 +527,9 @@ private:
|
||||
mutable std::shared_ptr<time_t> m_lastLeashExtensionTime;
|
||||
|
||||
ObjectGuid m_cannotReachTarget;
|
||||
uint32 m_cannotReachTimer;
|
||||
|
||||
Spell const* _focusSpell; ///> Locks the target during spell cast for proper facing
|
||||
ObjectGuid _spellFocusTarget; ///> Saved target during spell focus for restoration
|
||||
|
||||
CreatureTextRepeatGroup m_textRepeat;
|
||||
|
||||
|
||||
@@ -227,9 +227,9 @@ void CreatureGroup::MemberEngagingTarget(Creature* member, Unit* target)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pMember->IsValidAttackTarget(target) && pMember->AI())
|
||||
if (pMember->IsValidAttackTarget(target))
|
||||
{
|
||||
pMember->AI()->AttackStart(target);
|
||||
pMember->EngageWithTarget(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2318,6 +2318,7 @@ bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32
|
||||
// Force regen flag for player pets, just like we do for players themselves
|
||||
SetUnitFlag2(UNIT_FLAG2_REGENERATE_POWER);
|
||||
SetSheath(SHEATH_STATE_MELEE);
|
||||
GetThreatMgr().Initialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2161,7 +2161,7 @@ void Player::SetInWater(bool apply)
|
||||
// remove auras that need water/land
|
||||
RemoveAurasWithInterruptFlags(apply ? AURA_INTERRUPT_FLAG_NOT_ABOVEWATER : AURA_INTERRUPT_FLAG_NOT_UNDERWATER);
|
||||
|
||||
getHostileRefMgr().updateThreatTables();
|
||||
GetThreatMgr().EvaluateSuppressed();
|
||||
|
||||
if (InstanceScript* instance = GetInstanceScript())
|
||||
instance->OnPlayerInWaterStateUpdate(this, apply);
|
||||
@@ -2203,7 +2203,6 @@ void Player::SetGameMaster(bool on)
|
||||
{
|
||||
if (GetSession()->IsGMAccount())
|
||||
pet->SetFaction(FACTION_FRIENDLY);
|
||||
pet->getHostileRefMgr().setOnlineOfflineState(false);
|
||||
}
|
||||
if (HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP))
|
||||
{
|
||||
@@ -2212,7 +2211,6 @@ void Player::SetGameMaster(bool on)
|
||||
}
|
||||
ResetContestedPvP();
|
||||
|
||||
getHostileRefMgr().setOnlineOfflineState(false);
|
||||
CombatStopWithPets();
|
||||
|
||||
SetPhaseMask(uint32(PHASEMASK_ANYWHERE), false); // see and visible in all phases
|
||||
@@ -2234,10 +2232,7 @@ void Player::SetGameMaster(bool on)
|
||||
RemoveUnitFlag2(UNIT_FLAG2_ALLOW_CHEAT_SPELLS);
|
||||
|
||||
if (Pet* pet = GetPet())
|
||||
{
|
||||
pet->SetFaction(GetFaction());
|
||||
pet->getHostileRefMgr().setOnlineOfflineState(true);
|
||||
}
|
||||
|
||||
// restore FFA PvP Server state
|
||||
if (sWorld->IsFFAPvPRealm())
|
||||
@@ -2251,7 +2246,6 @@ void Player::SetGameMaster(bool on)
|
||||
// restore FFA PvP area state, remove not allowed for GM mounts
|
||||
UpdateArea(m_areaUpdateId);
|
||||
|
||||
getHostileRefMgr().setOnlineOfflineState(true);
|
||||
SetServerSideVisibilityDetect(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
|
||||
}
|
||||
|
||||
@@ -2268,7 +2262,6 @@ void Player::SetGMVisible(bool on)
|
||||
m_ExtraFlags &= ~PLAYER_EXTRA_GM_INVISIBLE;
|
||||
SetServerSideVisibility(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
|
||||
|
||||
getHostileRefMgr().setOnlineOfflineState(false);
|
||||
CombatStopWithPets();
|
||||
}
|
||||
else
|
||||
@@ -10380,7 +10373,6 @@ void Player::CleanupAfterTaxiFlight()
|
||||
m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
|
||||
Dismount();
|
||||
RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_TAXI_FLIGHT);
|
||||
getHostileRefMgr().setOnlineOfflineState(true);
|
||||
}
|
||||
|
||||
void Player::ContinueTaxiFlight()
|
||||
|
||||
@@ -402,13 +402,11 @@ void Player::Update(uint32 p_time)
|
||||
// != GetCharmGUID())))
|
||||
RemovePet(pet, PET_SAVE_NOT_IN_SLOT, true);
|
||||
|
||||
// pussywizard:
|
||||
if (m_hostileReferenceCheckTimer <= p_time)
|
||||
{
|
||||
m_hostileReferenceCheckTimer = 15000;
|
||||
if (!GetMap()->IsDungeon())
|
||||
getHostileRefMgr().deleteReferencesOutOfRange(
|
||||
GetVisibilityRange());
|
||||
GetCombatManager().EndCombatBeyondRange(GetVisibilityRange(), true);
|
||||
}
|
||||
else
|
||||
m_hostileReferenceCheckTimer -= p_time;
|
||||
@@ -1238,7 +1236,8 @@ void Player::UpdateArea(uint32 newArea)
|
||||
{
|
||||
SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY);
|
||||
pvpInfo.IsInNoPvPArea = true;
|
||||
CombatStopWithPets();
|
||||
if (!duel && GetCombatManager().HasPvPCombat())
|
||||
CombatStopWithPets();
|
||||
}
|
||||
else
|
||||
RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY);
|
||||
|
||||
@@ -329,11 +329,11 @@ Unit::Unit() : WorldObject(),
|
||||
m_removedAurasCount(0),
|
||||
i_motionMaster(new MotionMaster(this)),
|
||||
m_regenTimer(0),
|
||||
m_ThreatMgr(this),
|
||||
m_threatManager(this),
|
||||
m_combatManager(this),
|
||||
m_vehicle(nullptr),
|
||||
m_vehicleKit(nullptr),
|
||||
m_unitTypeMask(UNIT_MASK_NONE),
|
||||
m_HostileRefMgr(this),
|
||||
m_comboTarget(nullptr),
|
||||
m_comboPoints(0)
|
||||
{
|
||||
@@ -406,16 +406,11 @@ Unit::Unit() : WorldObject(),
|
||||
m_CombatTimer = 0;
|
||||
m_lastManaUse = 0;
|
||||
|
||||
for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
|
||||
m_threatModifier[i] = 1.0f;
|
||||
|
||||
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
|
||||
m_speed_rate[i] = 1.0f;
|
||||
|
||||
m_charmInfo = nullptr;
|
||||
|
||||
_redirectThreatInfo = RedirectThreatInfo();
|
||||
|
||||
// remove aurastates allowing special moves
|
||||
for (uint8 i = 0; i < MAX_REACTIVE; ++i)
|
||||
m_reactiveTimer[i] = 0;
|
||||
@@ -439,6 +434,7 @@ Unit::Unit() : WorldObject(),
|
||||
_oldFactionId = 0;
|
||||
|
||||
_isWalkingBeforeCharm = false;
|
||||
_isCombatDisallowed = false;
|
||||
|
||||
_lastExtraAttackSpell = 0;
|
||||
}
|
||||
@@ -529,16 +525,13 @@ void Unit::Update(uint32 p_time)
|
||||
|
||||
_UpdateSpells( p_time );
|
||||
|
||||
if (CanHaveThreatList() && GetThreatMgr().isNeedUpdateToClient(p_time))
|
||||
SendThreatListUpdate();
|
||||
|
||||
// update combat timer only for players and pets (only pets with PetAI)
|
||||
if (IsInCombat() && (IsPlayer() || ((IsPet() || HasUnitTypeMask(UNIT_MASK_CONTROLLABLE_GUARDIAN)) && IsControlledByPlayer())))
|
||||
{
|
||||
// Check UNIT_STATE_MELEE_ATTACKING or UNIT_STATE_CHASE (without UNIT_STATE_FOLLOW in this case) so pets can reach far away
|
||||
// targets without stopping half way there and running off.
|
||||
// These flags are reset after target dies or another command is given.
|
||||
if (m_HostileRefMgr.IsEmpty())
|
||||
if (!GetCombatManager().HasCombat())
|
||||
{
|
||||
// m_CombatTimer set at aura start and it will be freeze until aura removing
|
||||
if (m_CombatTimer <= p_time)
|
||||
@@ -548,6 +541,8 @@ void Unit::Update(uint32 p_time)
|
||||
}
|
||||
}
|
||||
|
||||
m_combatManager.Update(p_time);
|
||||
|
||||
_lastDamagedTargetGuid = ObjectGuid::Empty;
|
||||
if (_lastExtraAttackSpell)
|
||||
{
|
||||
@@ -1197,9 +1192,6 @@ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage
|
||||
|
||||
if (attacker && attacker != victim)
|
||||
{
|
||||
if (spellProto && victim->CanHaveThreatList() && !victim->HasUnitState(UNIT_STATE_EVADE) && !victim->IsInCombatWith(attacker))
|
||||
victim->CombatStart(attacker, !(spellProto->AttributesEx3 & SPELL_ATTR3_SUPPRESS_TARGET_PROCS));
|
||||
|
||||
victim->AddThreat(attacker, float(damage), damageSchoolMask, spellProto);
|
||||
}
|
||||
}
|
||||
@@ -2701,7 +2693,7 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType /*= BASE_A
|
||||
// CombatStart puts the target into stand state, so we need to cache sit state here to know if we should crit later
|
||||
const bool sittingVictim = victim->IsPlayer() && (victim->IsSitState() || victim->getStandState() == UNIT_STAND_STATE_SLEEP);
|
||||
|
||||
CombatStart(victim);
|
||||
AtTargetAttacked(victim, true);
|
||||
RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_MELEE_ATTACK);
|
||||
|
||||
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
|
||||
@@ -7348,14 +7340,10 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
|
||||
// set position before any AI calls/assistance
|
||||
//if (IsCreature())
|
||||
// ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ());
|
||||
if (creature && !(IsControllableGuardian() && IsControlledByPlayer()))
|
||||
if (creature && !IsControlledByPlayer())
|
||||
{
|
||||
// should not let player enter combat by right clicking target - doesn't helps
|
||||
SetInCombatWith(victim);
|
||||
if (victim->IsPlayer())
|
||||
victim->SetInCombatWith(this);
|
||||
|
||||
AddThreat(victim, 0.0f);
|
||||
EngageWithTarget(victim);
|
||||
|
||||
creature->SendAIReaction(AI_REACTION_HOSTILE);
|
||||
|
||||
@@ -7415,7 +7403,7 @@ bool Unit::AttackStop()
|
||||
return true;
|
||||
}
|
||||
|
||||
void Unit::CombatStop(bool includingCast)
|
||||
void Unit::CombatStop(bool includingCast, bool mutualPvP)
|
||||
{
|
||||
if (includingCast && IsNonMeleeSpellCast(false))
|
||||
InterruptNonMeleeSpells(false);
|
||||
@@ -7426,7 +7414,14 @@ void Unit::CombatStop(bool includingCast)
|
||||
ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
|
||||
if (Creature* pCreature = ToCreature())
|
||||
pCreature->ClearLastLeashExtensionTimePtr();
|
||||
ClearInCombat();
|
||||
|
||||
if (mutualPvP)
|
||||
ClearInCombat();
|
||||
else
|
||||
{
|
||||
GetCombatManager().EndAllPvECombat();
|
||||
GetCombatManager().SuppressPvPCombat();
|
||||
}
|
||||
|
||||
// xinef: just in case
|
||||
if (IsPetInCombat() && !IsPlayer())
|
||||
@@ -7441,6 +7436,16 @@ void Unit::CombatStopWithPets(bool includingCast)
|
||||
(*itr)->CombatStop(includingCast);
|
||||
}
|
||||
|
||||
void Unit::EngageWithTarget(Unit* who)
|
||||
{
|
||||
if (!who)
|
||||
return;
|
||||
if (CanHaveThreatList())
|
||||
m_threatManager.AddThreat(who, 0.0f, nullptr, true, true);
|
||||
else
|
||||
SetInCombatWith(who);
|
||||
}
|
||||
|
||||
bool Unit::isAttackingPlayer() const
|
||||
{
|
||||
if (HasUnitState(UNIT_STATE_ATTACK_PLAYER))
|
||||
@@ -7460,6 +7465,38 @@ bool Unit::isAttackingPlayer() const
|
||||
return false;
|
||||
}
|
||||
|
||||
void Unit::UpdatePetCombatState()
|
||||
{
|
||||
ASSERT(!IsPet()); // player pets do not use UNIT_FLAG_PET_IN_COMBAT for this purpose
|
||||
|
||||
bool state = false;
|
||||
for (Unit* minion : m_Controlled)
|
||||
if (minion->IsInCombat())
|
||||
{
|
||||
state = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (state)
|
||||
SetUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
|
||||
else
|
||||
RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
|
||||
}
|
||||
|
||||
void Unit::AtExitCombat()
|
||||
{
|
||||
RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_LEAVE_COMBAT);
|
||||
}
|
||||
|
||||
void Unit::AtEngage(Unit* /*target*/)
|
||||
{
|
||||
if (HasUnitState(UNIT_STATE_DISTRACTED))
|
||||
{
|
||||
ClearUnitState(UNIT_STATE_DISTRACTED);
|
||||
GetMotionMaster()->MovementExpiredOnSlot(MOTION_SLOT_CONTROLLED, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove all units in m_attackers list and send them AttackStop()
|
||||
*/
|
||||
@@ -8262,13 +8299,10 @@ void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellID, uint32 damage, Pow
|
||||
|
||||
void Unit::EnergizeBySpell(Unit* victim, uint32 spellID, uint32 damage, Powers powerType)
|
||||
{
|
||||
int32 gainedPower = victim->ModifyPower(powerType, damage, false);
|
||||
victim->ModifyPower(powerType, damage, false);
|
||||
|
||||
if (powerType != POWER_HAPPINESS && gainedPower)
|
||||
{
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID);
|
||||
victim->getHostileRefMgr().threatAssist(this, float(gainedPower) * 0.5f, spellInfo);
|
||||
}
|
||||
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID))
|
||||
victim->GetThreatMgr().ForwardThreatForAssistingMe(this, float(damage) / 2.0f, spellInfo, true);
|
||||
|
||||
SendEnergizeSpellLog(victim, spellID, damage, powerType);
|
||||
}
|
||||
@@ -10519,245 +10553,56 @@ void Unit::Dismount()
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::SetInCombatWith(Unit* enemy, uint32 duration)
|
||||
{
|
||||
// Xinef: Dont allow to start combat with triggers
|
||||
if (enemy->IsCreature() && enemy->ToCreature()->IsTrigger())
|
||||
return;
|
||||
|
||||
Unit* eOwner = enemy->GetCharmerOrOwnerOrSelf();
|
||||
if (eOwner->IsPvP() || eOwner->IsFFAPvP())
|
||||
{
|
||||
SetInCombatState(true, enemy, duration);
|
||||
return;
|
||||
}
|
||||
|
||||
// check for duel
|
||||
if (eOwner->IsPlayer() && eOwner->ToPlayer()->duel)
|
||||
{
|
||||
Unit const* myOwner = GetCharmerOrOwnerOrSelf();
|
||||
if (((Player const*)eOwner)->duel->Opponent == myOwner)
|
||||
{
|
||||
SetInCombatState(true, enemy, duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SetInCombatState(false, enemy, duration);
|
||||
}
|
||||
|
||||
void Unit::SetImmuneToPC(bool apply, bool keepCombat)
|
||||
{
|
||||
(void)keepCombat;
|
||||
if (apply)
|
||||
{
|
||||
SetUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
|
||||
if (!keepCombat)
|
||||
{
|
||||
std::vector<CombatReference*> toEnd;
|
||||
for (auto const& pair : m_combatManager.GetPvECombatRefs())
|
||||
if (pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
|
||||
toEnd.push_back(pair.second);
|
||||
for (auto const& pair : m_combatManager.GetPvPCombatRefs())
|
||||
toEnd.push_back(pair.second);
|
||||
for (CombatReference* ref : toEnd)
|
||||
ref->EndCombat();
|
||||
}
|
||||
}
|
||||
else
|
||||
RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
|
||||
}
|
||||
|
||||
void Unit::SetImmuneToNPC(bool apply, bool keepCombat)
|
||||
{
|
||||
(void)keepCombat;
|
||||
if (apply)
|
||||
{
|
||||
SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
|
||||
if (!keepCombat)
|
||||
{
|
||||
std::vector<CombatReference*> toEnd;
|
||||
for (auto const& pair : m_combatManager.GetPvECombatRefs())
|
||||
if (!pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
|
||||
toEnd.push_back(pair.second);
|
||||
for (CombatReference* ref : toEnd)
|
||||
ref->EndCombat();
|
||||
}
|
||||
}
|
||||
else
|
||||
RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
|
||||
}
|
||||
|
||||
void Unit::CombatStart(Unit* victim, bool initialAggro)
|
||||
{
|
||||
// Xinef: Dont allow to start combat with triggers
|
||||
if (victim->IsCreature() && victim->ToCreature()->IsTrigger())
|
||||
return;
|
||||
|
||||
if (initialAggro)
|
||||
{
|
||||
// Make player victim stand up automatically
|
||||
if (victim->getStandState() && victim->IsPlayer())
|
||||
{
|
||||
victim->SetStandState(UNIT_STAND_STATE_STAND);
|
||||
}
|
||||
|
||||
if (!victim->IsInCombat() && !victim->IsPlayer() && !victim->ToCreature()->HasReactState(REACT_PASSIVE) && victim->ToCreature()->IsAIEnabled)
|
||||
{
|
||||
if (victim->IsPet())
|
||||
victim->ToCreature()->AI()->AttackedBy(this); // PetAI has special handler before AttackStart()
|
||||
else
|
||||
{
|
||||
victim->ToCreature()->AI()->AttackStart(this);
|
||||
// if the target is an NPC with a pet or minion, pet should react.
|
||||
if (Unit* victimControlledUnit = victim->GetFirstControlled())
|
||||
{
|
||||
victimControlledUnit->SetInCombatWith(this);
|
||||
SetInCombatWith(victimControlledUnit);
|
||||
victimControlledUnit->AddThreat(this, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// if unit has an owner, put owner in combat.
|
||||
if (Unit* victimOwner = victim->GetOwner())
|
||||
{
|
||||
if (!(victimOwner->IsInCombatWith(this)))
|
||||
{
|
||||
/* warding off to not take over aggro for no reason
|
||||
Using only AddThreat causes delay in attack */
|
||||
if (!victimOwner->IsInCombat() && victimOwner->IsAIEnabled)
|
||||
{
|
||||
victimOwner->ToCreature()->AI()->AttackStart(this);
|
||||
}
|
||||
victimOwner->SetInCombatWith(this);
|
||||
SetInCombatWith(victimOwner);
|
||||
victimOwner->AddThreat(this, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool alreadyInCombat = IsInCombat();
|
||||
|
||||
SetInCombatWith(victim);
|
||||
victim->SetInCombatWith(this);
|
||||
|
||||
// Update leash timer when attacking creatures
|
||||
if (victim->IsCreature() && this != victim)
|
||||
victim->ToCreature()->UpdateLeashExtensionTime();
|
||||
|
||||
// Xinef: If pet started combat - put owner in combat
|
||||
if (!alreadyInCombat && IsInCombat())
|
||||
{
|
||||
if (Unit* owner = GetOwner())
|
||||
{
|
||||
owner->SetInCombatWith(victim);
|
||||
victim->SetInCombatWith(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Unit* who = victim->GetCharmerOrOwnerOrSelf();
|
||||
if (who->IsPlayer())
|
||||
SetContestedPvP(who->ToPlayer());
|
||||
|
||||
Player* player = GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
if (player && who->IsPvP() && (!who->IsPlayer() || !player->duel || player->duel->Opponent != who))
|
||||
{
|
||||
player->UpdatePvP(true);
|
||||
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::CombatStartOnCast(Unit* target, bool initialAggro, uint32 duration)
|
||||
{
|
||||
// Xinef: Dont allow to start combat with triggers
|
||||
if (target->IsCreature() && target->ToCreature()->IsTrigger())
|
||||
return;
|
||||
|
||||
if (initialAggro)
|
||||
{
|
||||
SetInCombatWith(target, duration);
|
||||
|
||||
// Xinef: If pet started combat - put owner in combat
|
||||
if (Unit* owner = GetOwner())
|
||||
owner->SetInCombatWith(target, duration);
|
||||
|
||||
// Update leash timer when attacking creatures
|
||||
if (target->IsCreature())
|
||||
target->ToCreature()->UpdateLeashExtensionTime();
|
||||
else if (ToCreature()) // Reset leash if it is a spell caster, else it may evade inbetween casts
|
||||
ToCreature()->UpdateLeashExtensionTime();
|
||||
}
|
||||
|
||||
Unit* who = target->GetCharmerOrOwnerOrSelf();
|
||||
if (who->IsPlayer())
|
||||
SetContestedPvP(who->ToPlayer());
|
||||
|
||||
Player* player = GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
if (player && who->IsPvP() && (!who->IsPlayer() || !player->duel || player->duel->Opponent != who))
|
||||
{
|
||||
player->UpdatePvP(true);
|
||||
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::SetInCombatState(bool PvP, Unit* enemy, uint32 duration)
|
||||
{
|
||||
// only alive units can be in combat
|
||||
if (!IsAlive())
|
||||
return;
|
||||
|
||||
if (PvP)
|
||||
m_CombatTimer = std::max<uint32>(GetCombatTimer(), std::max<uint32>(5500, duration));
|
||||
else if (duration)
|
||||
m_CombatTimer = std::max<uint32>(GetCombatTimer(), duration);
|
||||
|
||||
if (HasUnitState(UNIT_STATE_EVADE) || GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET)
|
||||
return;
|
||||
|
||||
// xinef: if we somehow engage in combat (scripts, dunno) with player, remove this flag so he can fight back
|
||||
if (IsCreature() && enemy && IsImmuneToPC() && enemy->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
SetImmuneToPC(false); // unit has engaged in combat, remove immunity so players can fight back
|
||||
|
||||
if (IsInCombat())
|
||||
return;
|
||||
|
||||
SetUnitFlag(UNIT_FLAG_IN_COMBAT);
|
||||
|
||||
if (Creature* creature = ToCreature())
|
||||
{
|
||||
// Set home position at place of engaging combat for escorted creatures
|
||||
if ((IsAIEnabled && creature->AI()->IsEscorted()) ||
|
||||
GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE ||
|
||||
GetMotionMaster()->GetCurrentMovementGeneratorType() == ESCORT_MOTION_TYPE)
|
||||
creature->SetHomePosition(GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
|
||||
|
||||
if (enemy)
|
||||
{
|
||||
creature->UpdateLeashExtensionTime();
|
||||
|
||||
if (IsAIEnabled)
|
||||
creature->AI()->JustEngagedWith(enemy);
|
||||
|
||||
if (creature->GetFormation())
|
||||
creature->GetFormation()->MemberEngagingTarget(creature, enemy);
|
||||
|
||||
sScriptMgr->OnUnitEnterCombat(creature, enemy);
|
||||
}
|
||||
|
||||
creature->RefreshSwimmingFlag();
|
||||
|
||||
if (IsPet())
|
||||
{
|
||||
UpdateSpeed(MOVE_RUN, true);
|
||||
UpdateSpeed(MOVE_SWIM, true);
|
||||
UpdateSpeed(MOVE_FLIGHT, true);
|
||||
}
|
||||
|
||||
if (!(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_ALLOW_MOUNTED_COMBAT))
|
||||
Dismount();
|
||||
if (!IsStandState()) // pussywizard: already done in CombatStart(target, initialAggro) for the target, but when aggro'ing from MoveInLOS CombatStart is not called!
|
||||
SetStandState(UNIT_STAND_STATE_STAND);
|
||||
}
|
||||
|
||||
for (Unit::ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();)
|
||||
{
|
||||
Unit* controlled = *itr;
|
||||
++itr;
|
||||
|
||||
// Xinef: Dont set combat for passive units, they will evade in next update...
|
||||
if (controlled->IsCreature() && controlled->ToCreature()->HasReactState(REACT_PASSIVE))
|
||||
continue;
|
||||
|
||||
controlled->SetInCombatState(PvP, enemy, duration);
|
||||
}
|
||||
|
||||
if (Player* player = this->ToPlayer())
|
||||
{
|
||||
sScriptMgr->OnPlayerEnterCombat(player, enemy);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::ClearInCombat()
|
||||
{
|
||||
m_CombatTimer = 0;
|
||||
RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
|
||||
// Let CombatManager handle flag removal through UpdateOwnerCombatState
|
||||
// so that AtExitCombat/AtDisengage callbacks properly fire.
|
||||
GetCombatManager().EndAllCombat();
|
||||
// If flag is still set (e.g. set by old code path without CombatReference),
|
||||
// remove it directly.
|
||||
if (IsInCombat())
|
||||
RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
|
||||
|
||||
// Player's state will be cleared in Player::UpdateContestedPvP
|
||||
if (Creature* creature = ToCreature())
|
||||
@@ -11454,8 +11299,6 @@ void Unit::setDeathState(DeathState s, bool despawn)
|
||||
if (s != DeathState::Alive && s != DeathState::JustRespawned)
|
||||
{
|
||||
CombatStop();
|
||||
GetThreatMgr().ClearAllThreat();
|
||||
getHostileRefMgr().deleteReferences();
|
||||
ClearComboPointHolders(); // any combo points pointed to unit lost at it death
|
||||
|
||||
if (IsNonMeleeSpellCast(false))
|
||||
@@ -11508,118 +11351,56 @@ void Unit::setDeathState(DeathState s, bool despawn)
|
||||
########################################*/
|
||||
bool Unit::CanHaveThreatList(bool skipAliveCheck) const
|
||||
{
|
||||
// only creatures can have threat list
|
||||
if (!IsCreature())
|
||||
// Delegate to ThreatManager's cached value for consistency
|
||||
if (!m_threatManager.CanHaveThreatList())
|
||||
return false;
|
||||
|
||||
// only alive units can have threat list
|
||||
if (!skipAliveCheck && !IsAlive())
|
||||
return false;
|
||||
|
||||
// totems can not have threat list
|
||||
if (ToCreature()->IsTotem())
|
||||
return false;
|
||||
|
||||
// vehicles can not have threat list
|
||||
// vehicles can not have threat list in battlegrounds
|
||||
if (ToCreature()->IsVehicle() && GetMap()->IsBattlegroundOrArena())
|
||||
return false;
|
||||
|
||||
// summons can not have a threat list, unless they are controlled by a creature
|
||||
if (HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN | UNIT_MASK_CONTROLLABLE_GUARDIAN) && ((Pet*)this)->GetOwnerGUID().IsPlayer())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
||||
float Unit::ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask)
|
||||
{
|
||||
if (!HasThreatAura() || fThreat < 0)
|
||||
return fThreat;
|
||||
|
||||
SpellSchools school = GetFirstSchoolInMask(schoolMask);
|
||||
|
||||
return fThreat * m_threatModifier[school];
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
||||
void Unit::AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell)
|
||||
void Unit::AddThreat(Unit* victim, float fThreat, SpellSchoolMask /*schoolMask*/, SpellInfo const* threatSpell)
|
||||
{
|
||||
// Only mobs can manage threat lists
|
||||
if (CanHaveThreatList() && !HasUnitState(UNIT_STATE_EVADE))
|
||||
{
|
||||
m_ThreatMgr.AddThreat(victim, fThreat, schoolMask, threatSpell);
|
||||
m_threatManager.AddThreat(victim, fThreat, threatSpell);
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
||||
void Unit::TauntApply(Unit* taunter)
|
||||
void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro)
|
||||
{
|
||||
ASSERT(IsCreature());
|
||||
|
||||
if (!taunter || (taunter->IsPlayer() && taunter->ToPlayer()->IsGameMaster()))
|
||||
if (!target->IsEngaged() && !canInitialAggro)
|
||||
return;
|
||||
|
||||
if (!CanHaveThreatList())
|
||||
return;
|
||||
target->EngageWithTarget(this);
|
||||
if (Unit* targetOwner = target->GetCharmerOrOwner())
|
||||
targetOwner->EngageWithTarget(this);
|
||||
|
||||
Creature* creature = ToCreature();
|
||||
// Patch 3.0.8: All player spells which cause a creature to become aggressive
|
||||
// to you will now also immediately cause the creature to be tapped.
|
||||
if (Creature* creature = target->ToCreature())
|
||||
if (!creature->hasLootRecipient() && IsPlayer())
|
||||
creature->SetLootRecipient(this);
|
||||
|
||||
if (creature->HasReactState(REACT_PASSIVE))
|
||||
return;
|
||||
|
||||
Unit* target = GetVictim();
|
||||
if (target && target == taunter)
|
||||
return;
|
||||
|
||||
SetInFront(taunter);
|
||||
SetGuidValue(UNIT_FIELD_TARGET, taunter->GetGUID());
|
||||
|
||||
if (creature->IsAIEnabled)
|
||||
creature->AI()->AttackStart(taunter);
|
||||
|
||||
//m_ThreatMgr.tauntApply(taunter);
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
||||
void Unit::TauntFadeOut(Unit* taunter)
|
||||
{
|
||||
ASSERT(IsCreature());
|
||||
|
||||
if (!taunter || (taunter->IsPlayer() && taunter->ToPlayer()->IsGameMaster()))
|
||||
return;
|
||||
|
||||
if (!CanHaveThreatList())
|
||||
return;
|
||||
|
||||
Creature* creature = ToCreature();
|
||||
|
||||
if (creature->HasReactState(REACT_PASSIVE))
|
||||
return;
|
||||
|
||||
Unit* target = GetVictim();
|
||||
if (!target || target != taunter)
|
||||
return;
|
||||
|
||||
if (m_ThreatMgr.isThreatListEmpty())
|
||||
Player* myPlayerOwner = GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
Player* targetPlayerOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
if (myPlayerOwner && targetPlayerOwner && !(myPlayerOwner->duel && myPlayerOwner->duel->Opponent == targetPlayerOwner))
|
||||
{
|
||||
if (creature->IsAIEnabled)
|
||||
creature->AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES);
|
||||
return;
|
||||
}
|
||||
|
||||
target = creature->SelectVictim(); // might have more taunt auras remaining
|
||||
|
||||
if (target && target != taunter)
|
||||
{
|
||||
SetGuidValue(UNIT_FIELD_TARGET, target->GetGUID());
|
||||
SetInFront(target);
|
||||
if (creature->IsAIEnabled)
|
||||
creature->AI()->AttackStart(target);
|
||||
myPlayerOwner->UpdatePvP(true);
|
||||
myPlayerOwner->SetContestedPvP(targetPlayerOwner);
|
||||
myPlayerOwner->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11633,21 +11414,11 @@ Unit* Creature::SelectVictim()
|
||||
|
||||
Unit* target = nullptr;
|
||||
|
||||
// First checking if we have some taunt on us
|
||||
AuraEffectList const& tauntAuras = GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
|
||||
if (!tauntAuras.empty())
|
||||
for (Unit::AuraEffectList::const_reverse_iterator itr = tauntAuras.rbegin(); itr != tauntAuras.rend(); ++itr)
|
||||
if (Unit* caster = (*itr)->GetCaster())
|
||||
if (CanCreatureAttack(caster) && !caster->HasAuraTypeWithCaster(SPELL_AURA_IGNORED, GetGUID()))
|
||||
{
|
||||
target = caster;
|
||||
break;
|
||||
}
|
||||
|
||||
// Taunt handling is now done inside ThreatManager via TauntState
|
||||
if (CanHaveThreatList())
|
||||
{
|
||||
if (!target && !m_ThreatMgr.isThreatListEmpty())
|
||||
target = m_ThreatMgr.getHostileTarget();
|
||||
if (!m_threatManager.IsThreatListEmpty())
|
||||
target = m_threatManager.GetCurrentVictim();
|
||||
}
|
||||
else if (!HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
@@ -11671,9 +11442,10 @@ Unit* Creature::SelectVictim()
|
||||
else
|
||||
return nullptr;
|
||||
|
||||
if (target && CanCreatureAttack(target))
|
||||
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
|
||||
{
|
||||
SetInFront(target);
|
||||
if (!HasSpellFocus())
|
||||
SetInFront(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -12776,8 +12548,6 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup)
|
||||
CombatStop();
|
||||
ClearComboPoints();
|
||||
ClearComboPointHolders();
|
||||
GetThreatMgr().ClearAllThreat();
|
||||
getHostileRefMgr().deleteReferences();
|
||||
GetMotionMaster()->Clear(false); // remove different non-standard movement generators.
|
||||
}
|
||||
|
||||
@@ -14290,7 +14060,6 @@ void Unit::Kill(Unit* killer, Unit* victim, bool durabilityLoss, WeaponAttackTyp
|
||||
|
||||
// Stop attacks
|
||||
victim->CombatStop();
|
||||
victim->getHostileRefMgr().deleteReferences();
|
||||
|
||||
// restore for use at real death
|
||||
victim->SetUInt32Value(PLAYER_SELF_RES_SPELL, ressSpellId);
|
||||
@@ -14352,8 +14121,6 @@ void Unit::Kill(Unit* killer, Unit* victim, bool durabilityLoss, WeaponAttackTyp
|
||||
|
||||
if (!creature->IsPet() && creature->GetLootMode() > 0)
|
||||
{
|
||||
creature->GetThreatMgr().ClearAllThreat();
|
||||
|
||||
// must be after setDeathState which resets dynamic flags
|
||||
if (!creature->loot.isLooted())
|
||||
{
|
||||
@@ -14814,10 +14581,6 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au
|
||||
CastStop();
|
||||
AttackStop();
|
||||
|
||||
// Xinef: dont reset threat and combat, put them on offline list, moved down after faction changes
|
||||
// CombatStop(); /// @todo: CombatStop(true) may cause crash (interrupt spells)
|
||||
// DeleteThreatList();
|
||||
|
||||
Player* playerCharmer = charmer->ToPlayer();
|
||||
|
||||
// Charmer stop charming
|
||||
@@ -15027,6 +14790,10 @@ void Unit::RemoveCharmedBy(Unit* charmer)
|
||||
ASSERT(type != CHARM_TYPE_POSSESS || charmer->IsPlayer());
|
||||
ASSERT(type != CHARM_TYPE_VEHICLE || (IsCreature() && IsVehicle()));
|
||||
|
||||
// Vehicle should not attack its passenger after he exits the seat
|
||||
if (type != CHARM_TYPE_VEHICLE)
|
||||
LastCharmerGUID = charmer->GetGUID();
|
||||
|
||||
charmer->SetCharm(this, false);
|
||||
|
||||
StopAttackingInvalidTarget();
|
||||
@@ -15478,41 +15245,29 @@ void Unit::SetPhaseMask(uint32 newPhaseMask, bool update)
|
||||
if (!sScriptMgr->CanSetPhaseMask(this, newPhaseMask, update))
|
||||
return;
|
||||
|
||||
// modify hostile references for new phasemask, some special cases deal with hostile references themselves
|
||||
if (IsCreature() || (!ToPlayer()->IsGameMaster() && !ToPlayer()->GetSession()->PlayerLogout()))
|
||||
{
|
||||
HostileRefMgr& refMgr = getHostileRefMgr();
|
||||
HostileReference* ref = refMgr.getFirst();
|
||||
|
||||
while (ref)
|
||||
{
|
||||
if (Unit* unit = ref->GetSource()->GetOwner())
|
||||
if (Creature* creature = unit->ToCreature())
|
||||
refMgr.setOnlineOfflineState(creature, creature->InSamePhase(newPhaseMask));
|
||||
|
||||
ref = ref->next();
|
||||
}
|
||||
|
||||
// modify threat lists for new phasemask
|
||||
if (!IsPlayer())
|
||||
{
|
||||
ThreatContainer::StorageType threatList = GetThreatMgr().GetThreatList();
|
||||
ThreatContainer::StorageType offlineThreatList = GetThreatMgr().GetOfflineThreatList();
|
||||
|
||||
// merge expects sorted lists
|
||||
threatList.sort();
|
||||
offlineThreatList.sort();
|
||||
threatList.merge(offlineThreatList);
|
||||
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
|
||||
if (Unit* unit = (*itr)->getTarget())
|
||||
unit->getHostileRefMgr().setOnlineOfflineState(ToCreature(), unit->InSamePhase(newPhaseMask));
|
||||
}
|
||||
}
|
||||
// Phase-related threat updates are done AFTER the phase change below
|
||||
}
|
||||
|
||||
WorldObject::SetPhaseMask(newPhaseMask, false);
|
||||
|
||||
// Now update threat online states with the new phase mask applied
|
||||
if (IsCreature() || (IsPlayer() && !ToPlayer()->IsGameMaster() && !ToPlayer()->GetSession()->PlayerLogout()))
|
||||
{
|
||||
// Update online state for units that have me on their threat list
|
||||
for (auto const& pair : GetThreatMgr().GetThreatenedByMeList())
|
||||
{
|
||||
if (ThreatReference* ref = pair.second)
|
||||
ref->UpdateOffline();
|
||||
}
|
||||
|
||||
// Update online state for units on my threat list
|
||||
if (!IsPlayer())
|
||||
{
|
||||
for (ThreatReference* ref : GetThreatMgr().GetModifiableThreatList())
|
||||
ref->UpdateOffline();
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsInWorld())
|
||||
{
|
||||
return;
|
||||
@@ -15655,11 +15410,6 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form, uint32 spellId)
|
||||
return modelid;
|
||||
}
|
||||
|
||||
Unit* Unit::GetRedirectThreatTarget() const
|
||||
{
|
||||
return _redirectThreatInfo.GetTargetGUID() ? ObjectAccessor::GetUnit(*this, _redirectThreatInfo.GetTargetGUID()) : nullptr;
|
||||
}
|
||||
|
||||
bool Unit::IsAttackSpeedOverridenShapeShift() const
|
||||
{
|
||||
// Mirroring clientside gameplay logic
|
||||
@@ -16221,64 +15971,6 @@ void Unit::UpdateHeight(float newZ)
|
||||
GetVehicleKit()->RelocatePassengers();
|
||||
}
|
||||
|
||||
void Unit::SendThreatListUpdate()
|
||||
{
|
||||
if (!GetThreatMgr().isThreatListEmpty())
|
||||
{
|
||||
uint32 count = GetThreatMgr().GetThreatList().size();
|
||||
|
||||
//LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_UPDATE Message");
|
||||
WorldPacket data(SMSG_THREAT_UPDATE, 8 + count * 8);
|
||||
data << GetPackGUID();
|
||||
data << uint32(count);
|
||||
ThreatContainer::StorageType const& tlist = GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr)
|
||||
{
|
||||
data << (*itr)->getUnitGuid().WriteAsPacked();
|
||||
data << uint32((*itr)->GetThreat() * 100);
|
||||
}
|
||||
SendMessageToSet(&data, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::SendChangeCurrentVictimOpcode(HostileReference* pHostileReference)
|
||||
{
|
||||
if (!GetThreatMgr().isThreatListEmpty())
|
||||
{
|
||||
uint32 count = GetThreatMgr().GetThreatList().size();
|
||||
|
||||
LOG_DEBUG("entities.unit", "WORLD: Send SMSG_HIGHEST_THREAT_UPDATE Message");
|
||||
WorldPacket data(SMSG_HIGHEST_THREAT_UPDATE, 8 + 8 + count * 8);
|
||||
data << GetPackGUID();
|
||||
data << pHostileReference->getUnitGuid().WriteAsPacked();
|
||||
data << uint32(count);
|
||||
ThreatContainer::StorageType const& tlist = GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr)
|
||||
{
|
||||
data << (*itr)->getUnitGuid().WriteAsPacked();
|
||||
data << uint32((*itr)->GetThreat() * 100);
|
||||
}
|
||||
SendMessageToSet(&data, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::SendClearThreatListOpcode()
|
||||
{
|
||||
LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_CLEAR Message");
|
||||
WorldPacket data(SMSG_THREAT_CLEAR, 8);
|
||||
data << GetPackGUID();
|
||||
SendMessageToSet(&data, false);
|
||||
}
|
||||
|
||||
void Unit::SendRemoveFromThreatListOpcode(HostileReference* pHostileReference)
|
||||
{
|
||||
LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_REMOVE Message");
|
||||
WorldPacket data(SMSG_THREAT_REMOVE, 8 + 8);
|
||||
data << GetPackGUID();
|
||||
data << pHostileReference->getUnitGuid().WriteAsPacked();
|
||||
SendMessageToSet(&data, false);
|
||||
}
|
||||
|
||||
void Unit::RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker)
|
||||
{
|
||||
// Rage formulae https://wowwiki-archive.fandom.com/wiki/Rage#Formulae
|
||||
@@ -16340,7 +16032,13 @@ void Unit::StopAttackFaction(uint32 faction_id)
|
||||
++itr;
|
||||
}
|
||||
|
||||
getHostileRefMgr().deleteReferencesForFaction(faction_id);
|
||||
// End combat and threat references with creatures in this faction
|
||||
std::vector<CombatReference*> refsToEnd;
|
||||
for (auto const& pair : m_combatManager.GetPvECombatRefs())
|
||||
if (pair.second->GetOther(this)->GetFactionTemplateEntry()->faction == faction_id)
|
||||
refsToEnd.push_back(pair.second);
|
||||
for (CombatReference* ref : refsToEnd)
|
||||
ref->EndCombat();
|
||||
|
||||
for (ControlSet::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
|
||||
(*itr)->StopAttackFaction(faction_id);
|
||||
@@ -17300,20 +16998,14 @@ DisplayRace Unit::GetDisplayRaceFromModelId(uint32 modelId) const
|
||||
// Check if unit in combat with specific unit
|
||||
bool Unit::IsInCombatWith(Unit const* who) const
|
||||
{
|
||||
// Check target exists
|
||||
if (!who)
|
||||
return false;
|
||||
// Search in threat list
|
||||
ObjectGuid guid = who->GetGUID();
|
||||
for (ThreatContainer::StorageType::const_iterator i = m_ThreatMgr.GetThreatList().begin(); i != m_ThreatMgr.GetThreatList().end(); ++i)
|
||||
{
|
||||
HostileReference* ref = (*i);
|
||||
// Return true if the unit matches
|
||||
if (ref && ref->getUnitGuid() == guid)
|
||||
return true;
|
||||
}
|
||||
// Nothing found, false.
|
||||
return false;
|
||||
return GetCombatManager().IsInCombatWith(who);
|
||||
}
|
||||
|
||||
bool Unit::IsThreatened() const
|
||||
{
|
||||
return !m_threatManager.IsThreatListEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include "EnumFlag.h"
|
||||
#include "EventProcessor.h"
|
||||
#include "HostileRefMgr.h"
|
||||
#include "CombatManager.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "Object.h"
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "SharedDefines.h"
|
||||
#include "SpellAuraDefines.h"
|
||||
#include "SpellDefines.h"
|
||||
#include "ThreatMgr.h"
|
||||
#include "ThreatManager.h"
|
||||
#include "UnitDefines.h"
|
||||
#include "UnitUtils.h"
|
||||
#include <functional>
|
||||
@@ -902,7 +902,7 @@ public:
|
||||
[[nodiscard]] bool isAttackingPlayer() const;
|
||||
[[nodiscard]] Unit* GetVictim() const { return m_attacking; }
|
||||
|
||||
void CombatStop(bool includingCast = false);
|
||||
void CombatStop(bool includingCast = false, bool mutualPvP = true);
|
||||
void CombatStopWithPets(bool includingCast = false);
|
||||
void StopAttackFaction(uint32 faction_id);
|
||||
void StopAttackingInvalidTarget();
|
||||
@@ -928,17 +928,15 @@ public:
|
||||
void SetImmuneToNPC(bool apply, bool keepCombat = false);
|
||||
bool IsImmuneToNPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); }
|
||||
|
||||
bool IsEngaged() const { return IsInCombat(); }
|
||||
bool IsEngagedBy(Unit const* who) const { return IsInCombatWith(who); }
|
||||
virtual bool IsEngaged() const { return IsInCombat(); }
|
||||
bool IsEngagedBy(Unit const* who) const { return CanHaveThreatList() ? IsThreatenedBy(who) : IsInCombatWith(who); }
|
||||
void EngageWithTarget(Unit* who);
|
||||
|
||||
[[nodiscard]] bool IsInCombat() const { return HasUnitFlag(UNIT_FLAG_IN_COMBAT); }
|
||||
bool IsInCombatWith(Unit const* who) const;
|
||||
|
||||
[[nodiscard]] bool IsPetInCombat() const { return HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT); }
|
||||
void CombatStart(Unit* target, bool initialAggro = true);
|
||||
void CombatStartOnCast(Unit* target, bool initialAggro = true, uint32 duration = 0);
|
||||
void SetInCombatState(bool PvP, Unit* enemy = nullptr, uint32 duration = 0);
|
||||
void SetInCombatWith(Unit* enemy, uint32 duration = 0);
|
||||
void SetInCombatWith(Unit* enemy, bool addSecondUnitSuppressed = false) { if (enemy) m_combatManager.SetInCombatWith(enemy, addSecondUnitSuppressed); }
|
||||
void ClearInCombat();
|
||||
void ClearInPetCombat();
|
||||
[[nodiscard]] uint32 GetCombatTimer() const { return m_CombatTimer; }
|
||||
@@ -947,21 +945,28 @@ public:
|
||||
// Threat related methods
|
||||
[[nodiscard]] bool CanHaveThreatList(bool skipAliveCheck = false) const;
|
||||
void AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr);
|
||||
float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL);
|
||||
void TauntApply(Unit* victim);
|
||||
void TauntFadeOut(Unit* taunter);
|
||||
ThreatMgr& GetThreatMgr() { return m_ThreatMgr; }
|
||||
ThreatMgr const& GetThreatMgr() const { return m_ThreatMgr; }
|
||||
void addHatedBy(HostileReference* pHostileReference) { m_HostileRefMgr.insertFirst(pHostileReference); };
|
||||
void removeHatedBy(HostileReference* /*pHostileReference*/) { /* nothing to do yet */ }
|
||||
HostileRefMgr& getHostileRefMgr() { return m_HostileRefMgr; }
|
||||
void AtTargetAttacked(Unit* target, bool canInitialAggro);
|
||||
|
||||
// Redirect Threat
|
||||
void SetRedirectThreat(ObjectGuid guid, uint32 pct) { _redirectThreatInfo.Set(guid, pct); }
|
||||
void ResetRedirectThreat() { SetRedirectThreat(ObjectGuid::Empty, 0); }
|
||||
void ModifyRedirectThreat(int32 amount) { _redirectThreatInfo.ModifyThreatPct(amount); }
|
||||
uint32 GetRedirectThreatPercent() { return _redirectThreatInfo.GetThreatPct(); }
|
||||
[[nodiscard]] Unit* GetRedirectThreatTarget() const;
|
||||
// ThreatManager/CombatManager accessors
|
||||
ThreatManager& GetThreatMgr() { return m_threatManager; }
|
||||
ThreatManager const& GetThreatMgr() const { return m_threatManager; }
|
||||
CombatManager& GetCombatManager() { return m_combatManager; }
|
||||
CombatManager const& GetCombatManager() const { return m_combatManager; }
|
||||
|
||||
// Combat state methods
|
||||
[[nodiscard]] bool IsThreatened() const;
|
||||
[[nodiscard]] bool IsThreatenedBy(Unit const* who) const { return who && m_threatManager.IsThreatenedBy(who, true); }
|
||||
void UpdatePetCombatState();
|
||||
|
||||
virtual void AtEnterCombat() {}
|
||||
virtual void AtExitCombat();
|
||||
|
||||
// Engagement callbacks - override in Creature for creature-specific behavior
|
||||
virtual void AtEngage(Unit* target);
|
||||
virtual void AtDisengage() {}
|
||||
|
||||
[[nodiscard]] bool IsCombatDisallowed() const { return _isCombatDisallowed; }
|
||||
void SetIsCombatDisallowed(bool value) { _isCombatDisallowed = value; }
|
||||
|
||||
void SetLastDamagedTargetGuid(ObjectGuid const& guid) { _lastDamagedTargetGuid = guid; }
|
||||
[[nodiscard]] ObjectGuid const& GetLastDamagedTargetGuid() const { return _lastDamagedTargetGuid; }
|
||||
@@ -1818,7 +1823,7 @@ public:
|
||||
[[nodiscard]] bool HasIgnoreTargetResistAura() const { return HasAuraType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); }
|
||||
[[nodiscard]] bool HasIncreaseMountedSpeedAura() const { return HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED); }
|
||||
[[nodiscard]] bool HasIncreaseMountedFlightSpeedAura() const { return HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED); }
|
||||
[[nodiscard]] bool HasThreatAura() const { return HasAuraType(SPELL_AURA_MOD_THREAT); }
|
||||
|
||||
[[nodiscard]] bool HasAttackerSpellCritChanceAura() const { return HasAuraType(SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE); }
|
||||
[[nodiscard]] bool HasUnattackableAura() const { return HasAuraType(SPELL_AURA_MOD_UNATTACKABLE); }
|
||||
[[nodiscard]] bool HasHealthRegenInCombatAura() const { return HasAuraType(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT); }
|
||||
@@ -2044,10 +2049,6 @@ public:
|
||||
void SendMovementFeatherFall(Player* sendTo);
|
||||
void SendMovementHover(Player* sendTo);
|
||||
|
||||
void SendChangeCurrentVictimOpcode(HostileReference* pHostileReference);
|
||||
void SendClearThreatListOpcode();
|
||||
void SendRemoveFromThreatListOpcode(HostileReference* pHostileReference);
|
||||
void SendThreatListUpdate();
|
||||
void SendClearTarget();
|
||||
|
||||
// Misc functions
|
||||
@@ -2077,7 +2078,6 @@ public:
|
||||
float m_modSpellHitChance;
|
||||
int32 m_baseSpellCritChance;
|
||||
|
||||
float m_threatModifier[MAX_SPELL_SCHOOL];
|
||||
float m_modAttackSpeedPct[3];
|
||||
|
||||
SpellImmuneList m_spellImmune[MAX_SPELL_IMMUNITY];
|
||||
@@ -2087,6 +2087,7 @@ public:
|
||||
typedef std::set<PetAura const*> PetAuraSet;
|
||||
PetAuraSet m_petAuras;
|
||||
|
||||
ObjectGuid LastCharmerGUID;
|
||||
bool IsAIEnabled;
|
||||
bool NeedChangeAI;
|
||||
|
||||
@@ -2184,7 +2185,8 @@ protected:
|
||||
uint32 m_reactiveTimer[MAX_REACTIVE];
|
||||
int32 m_regenTimer;
|
||||
|
||||
ThreatMgr m_ThreatMgr;
|
||||
ThreatManager m_threatManager;
|
||||
CombatManager m_combatManager;
|
||||
typedef std::map<ObjectGuid, float> CharmThreatMap;
|
||||
CharmThreatMap _charmThreatInfo;
|
||||
|
||||
@@ -2225,8 +2227,6 @@ private:
|
||||
//TimeTrackerSmall m_movesplineTimer;
|
||||
|
||||
Diminishing m_Diminishing;
|
||||
// Manage all Units that are threatened by us
|
||||
HostileRefMgr m_HostileRefMgr;
|
||||
|
||||
std::unordered_set<AbstractFollower*> m_followingMe;
|
||||
|
||||
@@ -2234,13 +2234,12 @@ private:
|
||||
int8 m_comboPoints;
|
||||
std::unordered_set<Unit*> m_ComboPointHolders;
|
||||
|
||||
RedirectThreatInfo _redirectThreatInfo;
|
||||
|
||||
bool m_cleanupDone; // lock made to not add stuff after cleanup before delete
|
||||
bool m_duringRemoveFromWorld; // lock made to not add stuff after begining removing from world
|
||||
|
||||
uint32 _oldFactionId; ///< faction before charm
|
||||
bool _isWalkingBeforeCharm; ///< Are we walking before we were charmed?
|
||||
bool _isCombatDisallowed;
|
||||
|
||||
uint32 _lastExtraAttackSpell;
|
||||
std::unordered_map<ObjectGuid /*guid*/, uint32 /*count*/> extraAttacksTargets;
|
||||
|
||||
@@ -445,6 +445,10 @@ bool Vehicle::AddPassenger(Unit* unit, int8 seatId)
|
||||
init.SetTransportEnter();
|
||||
init.Launch();
|
||||
|
||||
// Transfer threat from passenger to vehicle
|
||||
for (auto const& [guid, threatRef] : unit->GetThreatMgr().GetThreatenedByMeList())
|
||||
threatRef->GetOwner()->GetThreatMgr().AddThreat(_me, threatRef->GetThreat(), nullptr, true, true);
|
||||
|
||||
if (_me->IsCreature())
|
||||
{
|
||||
if (_me->ToCreature()->IsAIEnabled)
|
||||
|
||||
@@ -1158,12 +1158,9 @@ namespace Acore
|
||||
if (!u->IsWithinLOSInMap(i_enemy))
|
||||
return;
|
||||
|
||||
if (u->AI())
|
||||
{
|
||||
u->SetNoCallForHelp(true); // avoid recursive call for help causing stack overflow
|
||||
u->AI()->AttackStart(i_enemy);
|
||||
u->SetNoCallForHelp(false);
|
||||
}
|
||||
u->SetNoCallForHelp(true); // avoid recursive call for help causing stack overflow
|
||||
u->EngageWithTarget(i_enemy);
|
||||
u->SetNoCallForHelp(false);
|
||||
}
|
||||
private:
|
||||
Unit* const i_funit;
|
||||
|
||||
@@ -135,8 +135,8 @@ void WorldSession::HandleMoveWorldportAck()
|
||||
_player->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ONTRANSPORT);
|
||||
}
|
||||
|
||||
if (!_player->getHostileRefMgr().IsEmpty())
|
||||
_player->getHostileRefMgr().deleteReferences(true); // pussywizard: multithreading crashfix
|
||||
if (_player->GetThreatMgr().IsThreateningAnyone())
|
||||
_player->GetThreatMgr().RemoveMeFromThreatLists(); // pussywizard: multithreading crashfix
|
||||
|
||||
CellCoord pair(Acore::ComputeCellCoord(GetPlayer()->GetPositionX(), GetPlayer()->GetPositionY()));
|
||||
Cell cell(pair);
|
||||
|
||||
@@ -702,7 +702,7 @@ void Map::RemovePlayerFromMap(Player* player, bool remove)
|
||||
{
|
||||
UpdatePlayerZoneStats(player->GetZoneId(), MAP_INVALID_ZONE);
|
||||
|
||||
player->getHostileRefMgr().deleteReferences(true); // pussywizard: multithreading crashfix
|
||||
player->GetThreatMgr().RemoveMeFromThreatLists(); // pussywizard: multithreading crashfix
|
||||
|
||||
player->RemoveFromWorld();
|
||||
SendRemoveTransports(player);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include "HomeMovementGenerator.h"
|
||||
#include "CombatManager.h"
|
||||
#include "Creature.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "DisableMgr.h"
|
||||
@@ -23,11 +24,13 @@
|
||||
|
||||
void HomeMovementGenerator<Creature>::DoInitialize(Creature* owner)
|
||||
{
|
||||
owner->GetCombatManager().SetEvadeState(EVADE_STATE_HOME);
|
||||
_setTargetLocation(owner);
|
||||
}
|
||||
|
||||
void HomeMovementGenerator<Creature>::DoFinalize(Creature* owner)
|
||||
{
|
||||
owner->GetCombatManager().SetEvadeState(EVADE_STATE_NONE);
|
||||
owner->ClearUnitState(UNIT_STATE_EVADE);
|
||||
if (arrived)
|
||||
{
|
||||
|
||||
@@ -255,10 +255,17 @@ bool ChaseMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
|
||||
{
|
||||
i_recalculateTravel = false;
|
||||
i_path = nullptr;
|
||||
if (cOwner)
|
||||
cOwner->SetCannotReachTarget();
|
||||
owner->ClearUnitState(UNIT_STATE_CHASE_MOVE);
|
||||
owner->SetInFront(target);
|
||||
|
||||
if (cOwner)
|
||||
{
|
||||
if (cOwner->IsWithinMeleeRange(target))
|
||||
cOwner->SetCannotReachTarget();
|
||||
else
|
||||
cOwner->SetCannotReachTarget(target->GetGUID());
|
||||
}
|
||||
|
||||
MovementInform(owner);
|
||||
}
|
||||
|
||||
|
||||
@@ -414,7 +414,6 @@ void FlightPathMovementGenerator::DoFinalize(Player* player)
|
||||
|
||||
if (player->m_taxi.empty())
|
||||
{
|
||||
player->getHostileRefMgr().setOnlineOfflineState(true);
|
||||
// update z position to ground and orientation for landing point
|
||||
// this prevent cheating with landing point at lags
|
||||
// when client side flight end early in comparison server side
|
||||
@@ -443,7 +442,6 @@ void FlightPathMovementGenerator::DoReset(Player* player)
|
||||
if (player->pvpInfo.EndTimer)
|
||||
player->UpdatePvP(false, true); // PvP flag timer immediately ends when starting taxi
|
||||
|
||||
player->getHostileRefMgr().setOnlineOfflineState(false);
|
||||
player->AddUnitState(UNIT_STATE_IN_FLIGHT);
|
||||
player->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_TAXI_FLIGHT);
|
||||
|
||||
|
||||
@@ -628,7 +628,7 @@ void WorldSession::LogoutPlayer(bool save)
|
||||
//FIXME: logout must be delayed in case lost connection with client in time of combat
|
||||
if (_player->GetDeathTimer())
|
||||
{
|
||||
_player->getHostileRefMgr().deleteReferences(true);
|
||||
_player->GetThreatMgr().RemoveMeFromThreatLists();
|
||||
_player->BuildPlayerRepop();
|
||||
_player->RepopAtGraveyard();
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ enum AuraType
|
||||
SPELL_AURA_HASTE_RANGED = 218,
|
||||
SPELL_AURA_MOD_MANA_REGEN_FROM_STAT = 219,
|
||||
SPELL_AURA_MOD_RATING_FROM_STAT = 220,
|
||||
SPELL_AURA_IGNORED = 221,
|
||||
SPELL_AURA_MOD_DETAUNT = 221,
|
||||
SPELL_AURA_222 = 222,
|
||||
SPELL_AURA_RAID_PROC_FROM_CHARGE = 223,
|
||||
SPELL_AURA_224 = 224,
|
||||
|
||||
@@ -283,7 +283,7 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS] =
|
||||
&AuraEffect::HandleAuraModRangedHaste, //218 SPELL_AURA_HASTE_RANGED
|
||||
&AuraEffect::HandleModManaRegen, //219 SPELL_AURA_MOD_MANA_REGEN_FROM_STAT
|
||||
&AuraEffect::HandleModRatingFromStat, //220 SPELL_AURA_MOD_RATING_FROM_STAT
|
||||
&AuraEffect::HandleNoImmediateEffect, //221 SPELL_AURA_IGNORED
|
||||
&AuraEffect::HandleModDetaunt, //221 SPELL_AURA_MOD_DETAUNT
|
||||
&AuraEffect::HandleUnused, //222 unused (3.2.0) only for spell 44586 that not used in real spell cast
|
||||
&AuraEffect::HandleNoImmediateEffect, //223 SPELL_AURA_RAID_PROC_FROM_CHARGE
|
||||
&AuraEffect::HandleUnused, //224 unused (3.0.8a)
|
||||
@@ -2984,19 +2984,17 @@ void AuraEffect::HandleFeignDeath(AuraApplication const* aurApp, uint8 mode, boo
|
||||
}
|
||||
}
|
||||
|
||||
if (target->GetInstanceScript() && target->GetInstanceScript()->IsEncounterInProgress())
|
||||
for (auto& pair : target->GetThreatMgr().GetThreatenedByMeList())
|
||||
pair.second->ScaleThreat(0.0f);
|
||||
|
||||
if (target->GetMap()->IsDungeon()) // feign death does not remove combat in dungeons
|
||||
{
|
||||
// Xinef: replaced with CombatStop(false)
|
||||
target->AttackStop();
|
||||
target->RemoveAllAttackers();
|
||||
target->getHostileRefMgr().addThreatPercent(-100);
|
||||
target->ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
|
||||
if (Player* targetPlayer = target->ToPlayer())
|
||||
targetPlayer->SendAttackSwingCancelAttack();
|
||||
}
|
||||
else
|
||||
{
|
||||
target->CombatStop();
|
||||
target->getHostileRefMgr().deleteReferences();
|
||||
}
|
||||
target->CombatStop(false, false);
|
||||
|
||||
target->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_IMMUNE_OR_LOST_SELECTION);
|
||||
|
||||
@@ -3551,26 +3549,16 @@ void AuraEffect::HandleForceMoveForward(AuraApplication const* aurApp, uint8 mod
|
||||
/*** THREAT ***/
|
||||
/****************************/
|
||||
|
||||
void AuraEffect::HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const
|
||||
void AuraEffect::HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
|
||||
{
|
||||
if (!(mode & AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK))
|
||||
return;
|
||||
|
||||
Unit* target = aurApp->GetTarget();
|
||||
for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
|
||||
if (GetMiscValue() & (1 << i))
|
||||
{
|
||||
if (apply)
|
||||
AddPct(target->m_threatModifier[i], GetAmount());
|
||||
else
|
||||
{
|
||||
float amount = target->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, 1 << i);
|
||||
target->m_threatModifier[i] = amount;
|
||||
}
|
||||
}
|
||||
target->GetThreatMgr().UpdateMySpellSchoolModifiers();
|
||||
}
|
||||
|
||||
void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const
|
||||
void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
|
||||
{
|
||||
if (!(mode & AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK))
|
||||
return;
|
||||
@@ -3582,10 +3570,10 @@ void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 m
|
||||
|
||||
Unit* caster = GetCaster();
|
||||
if (caster && caster->IsAlive())
|
||||
target->getHostileRefMgr().addTempThreat((float)GetAmount(), apply);
|
||||
caster->GetThreatMgr().UpdateMyTempModifiers();
|
||||
}
|
||||
|
||||
void AuraEffect::HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool apply) const
|
||||
void AuraEffect::HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
|
||||
{
|
||||
if (!(mode & AURA_EFFECT_HANDLE_REAL))
|
||||
return;
|
||||
@@ -3595,17 +3583,21 @@ void AuraEffect::HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool
|
||||
if (!target->IsAlive() || !target->CanHaveThreatList())
|
||||
return;
|
||||
|
||||
Unit* caster = GetCaster();
|
||||
if (!caster || !caster->IsAlive())
|
||||
target->GetThreatMgr().TauntUpdate();
|
||||
}
|
||||
|
||||
void AuraEffect::HandleModDetaunt(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
|
||||
{
|
||||
if (!(mode & AURA_EFFECT_HANDLE_REAL))
|
||||
return;
|
||||
|
||||
if (apply)
|
||||
target->TauntApply(caster);
|
||||
else
|
||||
{
|
||||
// When taunt aura fades out, mob will switch to previous target if current has less than 1.1 * secondthreat
|
||||
target->TauntFadeOut(caster);
|
||||
}
|
||||
Unit* caster = GetCaster();
|
||||
Unit* target = aurApp->GetTarget();
|
||||
|
||||
if (!caster || !caster->IsAlive() || !target->IsAlive() || !caster->CanHaveThreatList())
|
||||
return;
|
||||
|
||||
caster->GetThreatMgr().TauntUpdate();
|
||||
}
|
||||
|
||||
/*****************************/
|
||||
@@ -3620,6 +3612,8 @@ void AuraEffect::HandleModConfuse(AuraApplication const* aurApp, uint8 mode, boo
|
||||
Unit* target = aurApp->GetTarget();
|
||||
|
||||
target->SetControlled(apply, UNIT_STATE_CONFUSED);
|
||||
if (apply)
|
||||
target->GetThreatMgr().EvaluateSuppressed();
|
||||
}
|
||||
|
||||
void AuraEffect::HandleModFear(AuraApplication const* aurApp, uint8 mode, bool apply) const
|
||||
@@ -3640,6 +3634,8 @@ void AuraEffect::HandleAuraModStun(AuraApplication const* aurApp, uint8 mode, bo
|
||||
Unit* target = aurApp->GetTarget();
|
||||
|
||||
target->SetControlled(apply, UNIT_STATE_STUNNED);
|
||||
if (apply)
|
||||
target->GetThreatMgr().EvaluateSuppressed();
|
||||
}
|
||||
|
||||
void AuraEffect::HandleAuraModRoot(AuraApplication const* aurApp, uint8 mode, bool apply) const
|
||||
@@ -4359,6 +4355,9 @@ void AuraEffect::HandleAuraModSchoolImmunity(AuraApplication const* aurApp, uint
|
||||
|
||||
target->ApplySpellImmune(GetId(), IMMUNITY_SCHOOL, GetMiscValue(), (apply));
|
||||
|
||||
if (apply)
|
||||
target->GetThreatMgr().EvaluateSuppressed();
|
||||
|
||||
if (GetSpellInfo()->Mechanic == MECHANIC_BANISH)
|
||||
{
|
||||
if (apply)
|
||||
@@ -4417,6 +4416,9 @@ void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 m
|
||||
Unit* target = aurApp->GetTarget();
|
||||
|
||||
target->ApplySpellImmune(GetId(), IMMUNITY_DAMAGE, GetMiscValue(), apply);
|
||||
|
||||
if (apply)
|
||||
target->GetThreatMgr().EvaluateSuppressed();
|
||||
}
|
||||
|
||||
void AuraEffect::HandleAuraModDispelImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const
|
||||
@@ -6895,7 +6897,7 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c
|
||||
float threat = float(caster->HealBySpell(healInfo)) * 0.5f;
|
||||
if (caster->IsClass(CLASS_PALADIN))
|
||||
threat *= 0.5f;
|
||||
caster->getHostileRefMgr().threatAssist(caster, threat, GetSpellInfo());
|
||||
caster->GetThreatMgr().ForwardThreatForAssistingMe(caster, threat, GetSpellInfo());
|
||||
}
|
||||
|
||||
void AuraEffect::HandlePeriodicHealthFunnelAuraTick(Unit* target, Unit* caster) const
|
||||
@@ -7030,8 +7032,7 @@ void AuraEffect::HandlePeriodicHealAurasTick(Unit* target, Unit* caster) const
|
||||
float threat = float(gain) * 0.5f;
|
||||
if (caster->IsClass(CLASS_PALADIN))
|
||||
threat *= 0.5f;
|
||||
|
||||
target->getHostileRefMgr().threatAssist(caster, threat, GetSpellInfo());
|
||||
target->GetThreatMgr().ForwardThreatForAssistingMe(caster, threat, GetSpellInfo());
|
||||
}
|
||||
|
||||
bool haveCastItem = GetBase()->GetCastItemGUID();
|
||||
@@ -7117,8 +7118,6 @@ void AuraEffect::HandlePeriodicManaLeechAuraTick(Unit* target, Unit* caster) con
|
||||
target->AddThreat(caster, float(gainedAmount) * 0.5f, GetSpellInfo()->GetSchoolMask(), GetSpellInfo());
|
||||
}
|
||||
|
||||
target->AddThreat(caster, float(gainedAmount) * 0.5f, GetSpellInfo()->GetSchoolMask(), GetSpellInfo());
|
||||
|
||||
// remove CC auras
|
||||
target->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TAKE_DAMAGE);
|
||||
|
||||
@@ -7169,7 +7168,7 @@ void AuraEffect::HandleObsModPowerAuraTick(Unit* target, Unit* caster) const
|
||||
int32 gain = target->ModifyPower(PowerType, amount);
|
||||
|
||||
if (caster)
|
||||
target->getHostileRefMgr().threatAssist(caster, float(gain) * 0.5f, GetSpellInfo());
|
||||
target->GetThreatMgr().ForwardThreatForAssistingMe(caster, float(gain) * 0.5f, GetSpellInfo(), true);
|
||||
}
|
||||
|
||||
void AuraEffect::HandlePeriodicEnergizeAuraTick(Unit* target, Unit* caster) const
|
||||
@@ -7203,7 +7202,7 @@ void AuraEffect::HandlePeriodicEnergizeAuraTick(Unit* target, Unit* caster) cons
|
||||
int32 gain = target->ModifyPower(PowerType, amount);
|
||||
|
||||
if (caster)
|
||||
target->getHostileRefMgr().threatAssist(caster, float(gain) * 0.5f, GetSpellInfo());
|
||||
target->GetThreatMgr().ForwardThreatForAssistingMe(caster, float(gain) * 0.5f, GetSpellInfo(), true);
|
||||
}
|
||||
|
||||
void AuraEffect::HandlePeriodicPowerBurnAuraTick(Unit* target, Unit* caster) const
|
||||
|
||||
@@ -206,6 +206,7 @@ public:
|
||||
void HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const;
|
||||
void HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const;
|
||||
void HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool apply) const;
|
||||
void HandleModDetaunt(AuraApplication const* aurApp, uint8 mode, bool apply) const;
|
||||
// control
|
||||
void HandleModConfuse(AuraApplication const* aurApp, uint8 mode, bool apply) const;
|
||||
void HandleModFear(AuraApplication const* aurApp, uint8 mode, bool apply) const;
|
||||
|
||||
@@ -237,6 +237,12 @@ public:
|
||||
|
||||
AuraScript* GetScriptByName(std::string const& scriptName) const;
|
||||
|
||||
template <class Script>
|
||||
Script* GetScript(std::string const& scriptName) const
|
||||
{
|
||||
return dynamic_cast<Script*>(GetScriptByName(scriptName));
|
||||
}
|
||||
|
||||
std::list<AuraScript*> m_loadedScripts;
|
||||
|
||||
virtual std::string GetDebugInfo() const;
|
||||
|
||||
@@ -2751,8 +2751,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
|
||||
float threat = float(gain) * 0.5f;
|
||||
if (caster->IsClass(CLASS_PALADIN))
|
||||
threat *= 0.5f;
|
||||
|
||||
unitTarget->getHostileRefMgr().threatAssist(caster, threat, m_spellInfo);
|
||||
unitTarget->GetThreatMgr().ForwardThreatForAssistingMe(caster, threat, m_spellInfo);
|
||||
m_healing = gain;
|
||||
|
||||
// Xinef: if heal actually healed something, add no overheal flag
|
||||
@@ -2877,8 +2876,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
|
||||
if (missInfo == SPELL_MISS_RESIST && m_spellInfo->HasAttribute(SPELL_ATTR0_CU_PICKPOCKET) && unitTarget->IsCreature() && m_caster)
|
||||
{
|
||||
m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TALK);
|
||||
if (unitTarget->ToCreature()->IsAIEnabled)
|
||||
unitTarget->ToCreature()->AI()->AttackStart(m_caster);
|
||||
unitTarget->ToCreature()->EngageWithTarget(m_caster);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2887,19 +2885,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
|
||||
if (missInfo != SPELL_MISS_EVADE && !m_caster->IsFriendlyTo(effectUnit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)))
|
||||
{
|
||||
if (!m_triggeredByAuraSpell.spellInfo || m_damage || (!(m_triggeredByAuraSpell.spellInfo->Effects[m_triggeredByAuraSpell.effectIndex].TriggerSpell == m_spellInfo->Id) && !(m_triggeredByAuraSpell.spellInfo->IsAuraEffectEqual(m_spellInfo))))
|
||||
m_caster->CombatStart(effectUnit, !(m_spellInfo->AttributesEx3 & SPELL_ATTR3_SUPPRESS_TARGET_PROCS));
|
||||
|
||||
// Patch 3.0.8: All player spells which cause a creature to become aggressive to you will now also immediately cause the creature to be tapped.
|
||||
if (effectUnit->IsInCombatWith(m_caster))
|
||||
{
|
||||
if (Creature* creature = effectUnit->ToCreature())
|
||||
{
|
||||
if (!creature->hasLootRecipient() && m_caster->IsPlayer())
|
||||
{
|
||||
creature->SetLootRecipient(m_caster);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_caster->AtTargetAttacked(effectUnit, !(m_spellInfo->HasAttribute(SPELL_ATTR1_NO_THREAT) || m_spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS)));
|
||||
|
||||
// Unsure if there are more spells that are not supposed to stop enemy from
|
||||
// regenerating HP from food, so for now it stays as an ID.
|
||||
@@ -3036,10 +3022,11 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA
|
||||
}
|
||||
|
||||
// xinef: triggered spells should not prolong combat
|
||||
if (unit->IsInCombat() && !m_spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS) && !m_triggeredByAuraSpell)
|
||||
if (m_originalCaster && unit->IsInCombat() && !m_spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS) && !m_triggeredByAuraSpell)
|
||||
{
|
||||
m_caster->SetInCombatState(unit->GetCombatTimer() > 0, unit);
|
||||
unit->getHostileRefMgr().threatAssist(m_caster, 0.0f);
|
||||
if (m_originalCaster->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
|
||||
m_originalCaster->GetCombatManager().InheritCombatStatesFrom(unit);
|
||||
unit->GetThreatMgr().ForwardThreatForAssistingMe(m_originalCaster, 0.0f, nullptr, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4048,12 +4035,6 @@ void Spell::_cast(bool skipCheck)
|
||||
if (target->IsCreature())
|
||||
m_caster->CastSpell(target, 32747, true);
|
||||
|
||||
// xinef: start combat at cast for delayed spells, only for explicit target
|
||||
if (Unit* target = m_targets.GetUnitTarget())
|
||||
if (m_caster->IsPlayer() || (m_caster->IsPet() && m_caster->IsControlledByPlayer()))
|
||||
if (GetDelayMoment() > 0 && !m_caster->IsFriendlyTo(target) && !m_spellInfo->HasAura(SPELL_AURA_BIND_SIGHT) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)))
|
||||
m_caster->CombatStartOnCast(target, !m_spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS), GetDelayMoment() + 500); // xinef: increase this time so we dont leave and enter combat in a moment
|
||||
|
||||
if (m_caster->IsPlayer())
|
||||
if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_COOLDOWN))
|
||||
m_caster->ToPlayer()->RemoveSpellCooldown(m_spellInfo->Id, true);
|
||||
@@ -5558,11 +5539,13 @@ void Spell::HandleThreatSpells()
|
||||
if (m_spellInfo->HasAttribute(SPELL_ATTR1_NO_THREAT) || m_spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS))
|
||||
return;
|
||||
|
||||
Unit* unitCaster = m_originalCaster ? m_originalCaster : m_caster;
|
||||
|
||||
float threat = 0.0f;
|
||||
if (SpellThreatEntry const* threatEntry = sSpellMgr->GetSpellThreatEntry(m_spellInfo->Id))
|
||||
{
|
||||
if (threatEntry->apPctMod != 0.0f)
|
||||
threat += threatEntry->apPctMod * m_caster->GetTotalAttackPowerValue(BASE_ATTACK);
|
||||
threat += threatEntry->apPctMod * unitCaster->GetTotalAttackPowerValue(BASE_ATTACK);
|
||||
|
||||
threat += threatEntry->flatMod;
|
||||
}
|
||||
@@ -5576,23 +5559,27 @@ void Spell::HandleThreatSpells()
|
||||
// since 2.0.1 threat from positive effects also is distributed among all targets, so the overall caused threat is at most the defined bonus
|
||||
threat /= m_UniqueTargetInfo.size();
|
||||
|
||||
for (std::list<TargetInfo>::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
|
||||
for (auto& ihit : m_UniqueTargetInfo)
|
||||
{
|
||||
float threatToAdd = threat;
|
||||
if (ihit->missCondition != SPELL_MISS_NONE)
|
||||
if (ihit.missCondition != SPELL_MISS_NONE)
|
||||
threatToAdd = 0.0f;
|
||||
|
||||
Unit* target = ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID);
|
||||
Unit* target = ObjectAccessor::GetUnit(*unitCaster, ihit.targetGUID);
|
||||
if (!target)
|
||||
continue;
|
||||
|
||||
bool IsFriendly = m_caster->IsFriendlyTo(target);
|
||||
// positive spells distribute threat among all units that are in combat with target, like healing
|
||||
if (m_spellInfo->_IsPositiveSpell() && IsFriendly)
|
||||
target->getHostileRefMgr().threatAssist(m_caster, threatToAdd, m_spellInfo);
|
||||
if (m_spellInfo->_IsPositiveSpell())
|
||||
target->GetThreatMgr().ForwardThreatForAssistingMe(unitCaster, threatToAdd, m_spellInfo);
|
||||
// for negative spells threat gets distributed among affected targets
|
||||
else if (!m_spellInfo->_IsPositiveSpell() && !IsFriendly && target->CanHaveThreatList())
|
||||
target->AddThreat(m_caster, threatToAdd, m_spellInfo->GetSchoolMask(), m_spellInfo);
|
||||
// ignoreModifiers=true because flat SpellThreatEntry threat should not have modifiers applied twice
|
||||
else
|
||||
{
|
||||
if (!target->CanHaveThreatList())
|
||||
continue;
|
||||
target->GetThreatMgr().AddThreat(unitCaster, threatToAdd, m_spellInfo, true);
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("spells.aura", "Spell {}, added an additional {} threat for {} {} target(s)", m_spellInfo->Id, threat, m_spellInfo->_IsPositiveSpell() ? "assisting" : "harming", uint32(m_UniqueTargetInfo.size()));
|
||||
}
|
||||
@@ -5772,8 +5759,7 @@ SpellCastResult Spell::CheckCast(bool strict)
|
||||
if (Unit* victim = member->GetVictim())
|
||||
if (victim->IsInCombat() && m_caster->GetDistance(victim) < m_caster->GetVisibilityRange())
|
||||
{
|
||||
m_caster->CombatStart(victim);
|
||||
victim->AddThreat(m_caster, 1.0f);
|
||||
m_caster->EngageWithTarget(victim);
|
||||
break;
|
||||
}
|
||||
return SPELL_FAILED_TARGET_CANNOT_BE_RESURRECTED;
|
||||
|
||||
@@ -2652,7 +2652,7 @@ void Spell::EffectDispel(SpellEffIndex effIndex)
|
||||
|
||||
// put in combat
|
||||
if (unitTarget->IsFriendlyTo(m_caster))
|
||||
unitTarget->getHostileRefMgr().threatAssist(m_caster, 0.0f, m_spellInfo);
|
||||
unitTarget->GetThreatMgr().ForwardThreatForAssistingMe(m_caster, 0.0f, m_spellInfo);
|
||||
|
||||
if (success_list.empty())
|
||||
return;
|
||||
@@ -3299,33 +3299,25 @@ void Spell::EffectTaunt(SpellEffIndex /*effIndex*/)
|
||||
if (!unitTarget)
|
||||
return;
|
||||
|
||||
// xinef: Hand of Reckoning, cast before checing canhavethreatlist. fixes damage against pets
|
||||
// xinef: Hand of Reckoning, cast before checking canhavethreatlist. fixes damage against pets
|
||||
if (m_spellInfo->Id == 62124 && unitTarget->GetVictim() != m_caster)
|
||||
{
|
||||
m_caster->CastSpell(unitTarget, 67485, true);
|
||||
unitTarget->CombatStart(m_caster);
|
||||
}
|
||||
|
||||
// this effect use before aura Taunt apply for prevent taunt already attacking target
|
||||
// for spell as marked "non effective at already attacking target"
|
||||
if (!unitTarget->CanHaveThreatList() || (unitTarget->GetVictim() == m_caster && !m_spellInfo->HasAura(SPELL_AURA_MOD_TAUNT)))
|
||||
if (!unitTarget->CanHaveThreatList())
|
||||
{
|
||||
SendCastResult(SPELL_FAILED_DONT_REPORT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!unitTarget->GetThreatMgr().GetOnlineContainer().empty())
|
||||
ThreatManager& mgr = unitTarget->GetThreatMgr();
|
||||
if (mgr.GetCurrentVictim() == m_caster)
|
||||
{
|
||||
// Also use this effect to set the taunter's threat to the taunted creature's highest value
|
||||
float myThreat = unitTarget->GetThreatMgr().GetThreat(m_caster);
|
||||
float topThreat = unitTarget->GetThreatMgr().GetOnlineContainer().getMostHated()->GetThreat();
|
||||
if (topThreat > myThreat)
|
||||
unitTarget->GetThreatMgr().DoAddThreat(m_caster, topThreat - myThreat);
|
||||
|
||||
//Set aggro victim to caster
|
||||
if (HostileReference* forcedVictim = unitTarget->GetThreatMgr().GetOnlineContainer().getReferenceByTarget(m_caster))
|
||||
unitTarget->GetThreatMgr().setCurrentVictim(forcedVictim);
|
||||
SendCastResult(SPELL_FAILED_DONT_REPORT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mgr.IsThreatListEmpty())
|
||||
mgr.MatchUnitThreatToHighestThreat(m_caster);
|
||||
}
|
||||
|
||||
void Spell::EffectWeaponDmg(SpellEffIndex effIndex)
|
||||
@@ -3677,7 +3669,8 @@ void Spell::EffectThreat(SpellEffIndex /*effIndex*/)
|
||||
if (!unitTarget->CanHaveThreatList() || m_caster->IsFriendlyTo(unitTarget))
|
||||
return;
|
||||
|
||||
unitTarget->AddThreat(m_caster, float(damage));
|
||||
// SPELL_EFFECT_THREAT adds flat threat that should not be modified by threat reduction
|
||||
unitTarget->GetThreatMgr().AddThreat(m_caster, float(damage), m_spellInfo, true);
|
||||
}
|
||||
|
||||
void Spell::EffectHealMaxHealth(SpellEffIndex /*effIndex*/)
|
||||
@@ -4002,24 +3995,18 @@ void Spell::EffectSanctuary(SpellEffIndex /*effIndex*/)
|
||||
if (!unitTarget)
|
||||
return;
|
||||
|
||||
if (unitTarget->GetInstanceScript() && unitTarget->GetInstanceScript()->IsEncounterInProgress())
|
||||
unitTarget->GetThreatMgr().EvaluateSuppressed();
|
||||
|
||||
if (unitTarget->IsPlayer() && !unitTarget->GetMap()->IsDungeon())
|
||||
{
|
||||
unitTarget->getHostileRefMgr().UpdateVisibility(true);
|
||||
// Xinef: replaced with CombatStop(false)
|
||||
unitTarget->AttackStop();
|
||||
unitTarget->RemoveAllAttackers();
|
||||
|
||||
// Night Elf: Shadowmeld only resets threat temporarily
|
||||
if (m_spellInfo->Id != 59646)
|
||||
unitTarget->getHostileRefMgr().addThreatPercent(-100);
|
||||
|
||||
if (unitTarget->IsPlayer())
|
||||
unitTarget->ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
|
||||
// stop all pve combat for players outside dungeons, suppress pvp combat
|
||||
unitTarget->CombatStop(false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
unitTarget->getHostileRefMgr().UpdateVisibility(m_spellInfo->Id == 59646); // Night Elf: Shadowmeld
|
||||
unitTarget->CombatStop(true);
|
||||
// in dungeons (or for nonplayers), reset this unit on all enemies' threat lists
|
||||
for (auto const& pair : unitTarget->GetThreatMgr().GetThreatenedByMeList())
|
||||
pair.second->ScaleThreat(0.0f);
|
||||
}
|
||||
|
||||
UnitList targets;
|
||||
@@ -5185,7 +5172,7 @@ void Spell::EffectDispelMechanic(SpellEffIndex effIndex)
|
||||
|
||||
// put in combat
|
||||
if (unitTarget->IsFriendlyTo(m_caster))
|
||||
unitTarget->getHostileRefMgr().threatAssist(m_caster, 0.0f, m_spellInfo);
|
||||
unitTarget->GetThreatMgr().ForwardThreatForAssistingMe(m_caster, 0.0f, m_spellInfo);
|
||||
}
|
||||
|
||||
void Spell::EffectResurrectPet(SpellEffIndex /*effIndex*/)
|
||||
@@ -5903,7 +5890,7 @@ void Spell::EffectRedirectThreat(SpellEffIndex /*effIndex*/)
|
||||
return;
|
||||
|
||||
if (unitTarget)
|
||||
m_caster->SetRedirectThreat(unitTarget->GetGUID(), uint32(damage));
|
||||
m_caster->GetThreatMgr().RegisterRedirectThreat(m_spellInfo->Id, unitTarget->GetGUID(), uint32(damage));
|
||||
}
|
||||
|
||||
void Spell::EffectGameObjectDamage(SpellEffIndex /*effIndex*/)
|
||||
|
||||
@@ -1286,6 +1286,11 @@ bool SpellInfo::IsAutoRepeatRangedSpell() const
|
||||
return AttributesEx2 & SPELL_ATTR2_AUTO_REPEAT;
|
||||
}
|
||||
|
||||
bool SpellInfo::HasInitialAggro() const
|
||||
{
|
||||
return !(HasAttribute(SPELL_ATTR1_NO_THREAT) || HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS));
|
||||
}
|
||||
|
||||
bool SpellInfo::IsAffected(uint32 familyName, flag96 const& familyFlags) const
|
||||
{
|
||||
if (!familyName)
|
||||
|
||||
@@ -466,6 +466,7 @@ public:
|
||||
bool IsBreakingStealth() const;
|
||||
bool IsRangedWeaponSpell() const;
|
||||
bool IsAutoRepeatRangedSpell() const;
|
||||
bool HasInitialAggro() const;
|
||||
|
||||
[[nodiscard]] bool IsAffected(uint32 familyName, flag96 const& familyFlags) const;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "Log.h"
|
||||
#include "M2Stores.h"
|
||||
#include "MapMgr.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "PoolMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
@@ -70,6 +71,8 @@ public:
|
||||
{
|
||||
{ "setbit", HandleDebugSet32BitCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "threat", HandleDebugThreatListCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "threatinfo", HandleDebugThreatInfoCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "combat", HandleDebugCombatListCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "hostile", HandleDebugHostileRefListCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "anim", HandleDebugAnimCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "arena", HandleDebugArenaCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
@@ -816,43 +819,182 @@ public:
|
||||
|
||||
static bool HandleDebugThreatListCommand(ChatHandler* handler)
|
||||
{
|
||||
Creature* target = handler->getSelectedCreature();
|
||||
if (!target || target->IsTotem() || target->IsPet())
|
||||
return false;
|
||||
Unit* target = handler->getSelectedUnit();
|
||||
if (!target)
|
||||
target = handler->GetPlayer();
|
||||
|
||||
ThreatManager& mgr = target->GetThreatMgr();
|
||||
if (!target->IsAlive())
|
||||
{
|
||||
handler->PSendSysMessage("{} ({}) is not alive.{}", target->GetName(), target->GetGUID().ToString(), target->IsEngaged() ? " (It is, however, engaged. Huh?)" : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
auto const& threatList = target->GetThreatMgr().GetThreatList();
|
||||
ThreatContainer::StorageType::const_iterator itr;
|
||||
uint32 count = 0;
|
||||
|
||||
handler->PSendSysMessage("Threat list of {} ({})", target->GetName(), target->GetGUID().ToString());
|
||||
|
||||
for (itr = threatList.begin(); itr != threatList.end(); ++itr)
|
||||
auto const& threatenedByMe = target->GetThreatMgr().GetThreatenedByMeList();
|
||||
if (threatenedByMe.empty())
|
||||
handler->PSendSysMessage("{} ({}) does not threaten any units.", target->GetName(), target->GetGUID().ToString());
|
||||
else
|
||||
{
|
||||
Unit* unit = (*itr)->getTarget();
|
||||
if (!unit)
|
||||
handler->PSendSysMessage("List of units threatened by {} ({})", target->GetName(), target->GetGUID().ToString());
|
||||
for (auto const& pair : threatenedByMe)
|
||||
{
|
||||
handler->PSendSysMessage(" {}. No Unit - threat {}", ++count, (*itr)->GetThreat());
|
||||
continue;
|
||||
Unit* unit = pair.second->GetOwner();
|
||||
handler->PSendSysMessage(" {}. {} ({}, SpawnID {}) - threat {}", ++count, unit->GetName(), unit->GetGUID().ToString(), unit->IsCreature() ? unit->ToCreature()->GetSpawnId() : 0, pair.second->GetThreat());
|
||||
}
|
||||
|
||||
handler->PSendSysMessage(" {}. {} ({}) - threat {}", ++count, unit->GetName(), unit->GetGUID().ToString(), (*itr)->GetThreat());
|
||||
handler->SendSysMessage("End of threatened-by-me list.");
|
||||
}
|
||||
|
||||
auto const& threatList2 = target->GetThreatMgr().GetOfflineThreatList();
|
||||
for (itr = threatList2.begin(); itr != threatList2.end(); ++itr)
|
||||
if (mgr.CanHaveThreatList())
|
||||
{
|
||||
Unit* unit = (*itr)->getTarget();
|
||||
if (!unit)
|
||||
if (!mgr.IsThreatListEmpty(true))
|
||||
{
|
||||
handler->PSendSysMessage(" {}. [offline] No Unit - threat {}", ++count, (*itr)->GetThreat());
|
||||
continue;
|
||||
}
|
||||
if (target->IsEngaged())
|
||||
handler->PSendSysMessage("Threat list of {} ({}, SpawnID {}):", target->GetName(), target->GetGUID().ToString(), target->IsCreature() ? target->ToCreature()->GetSpawnId() : 0);
|
||||
else
|
||||
handler->PSendSysMessage("{} ({}, SpawnID {}) is not engaged, but still has a threat list? Well, here it is:", target->GetName(), target->GetGUID().ToString(), target->IsCreature() ? target->ToCreature()->GetSpawnId() : 0);
|
||||
|
||||
handler->PSendSysMessage(" {}. [offline] {} ({}) - threat {}", ++count, unit->GetName(), unit->GetGUID().ToString(), (*itr)->GetThreat());
|
||||
count = 0;
|
||||
Unit* fixateVictim = mgr.GetFixateTarget();
|
||||
for (ThreatReference const* ref : mgr.GetSortedThreatList())
|
||||
{
|
||||
Unit* unit = ref->GetVictim();
|
||||
char const* onlineStr;
|
||||
switch (ref->GetOnlineState())
|
||||
{
|
||||
case ThreatReference::ONLINE_STATE_SUPPRESSED:
|
||||
onlineStr = " [SUPPRESSED]";
|
||||
break;
|
||||
case ThreatReference::ONLINE_STATE_OFFLINE:
|
||||
onlineStr = " [OFFLINE]";
|
||||
break;
|
||||
default:
|
||||
onlineStr = "";
|
||||
}
|
||||
char const* tauntStr;
|
||||
if (unit == fixateVictim)
|
||||
tauntStr = " [FIXATE]";
|
||||
else
|
||||
switch (ref->GetTauntState())
|
||||
{
|
||||
case ThreatReference::TAUNT_STATE_TAUNT:
|
||||
tauntStr = " [TAUNT]";
|
||||
break;
|
||||
case ThreatReference::TAUNT_STATE_DETAUNT:
|
||||
tauntStr = " [DETAUNT]";
|
||||
break;
|
||||
default:
|
||||
tauntStr = "";
|
||||
}
|
||||
handler->PSendSysMessage(" {}. {} ({}) - threat {}{}{}", ++count, unit->GetName(), unit->GetGUID().ToString(), ref->GetThreat(), tauntStr, onlineStr);
|
||||
}
|
||||
handler->SendSysMessage("End of threat list.");
|
||||
}
|
||||
else if (!target->IsEngaged())
|
||||
handler->PSendSysMessage("{} ({}, SpawnID {}) is not currently engaged.", target->GetName(), target->GetGUID().ToString(), target->IsCreature() ? target->ToCreature()->GetSpawnId() : 0);
|
||||
else
|
||||
handler->PSendSysMessage("{} ({}, SpawnID {}) seems to be engaged, but does not have a threat list??", target->GetName(), target->GetGUID().ToString(), target->IsCreature() ? target->ToCreature()->GetSpawnId() : 0);
|
||||
}
|
||||
else if (target->IsEngaged())
|
||||
handler->PSendSysMessage("{} ({}) is currently engaged. (This unit cannot have a threat list.)", target->GetName(), target->GetGUID().ToString());
|
||||
else
|
||||
handler->PSendSysMessage("{} ({}) is not currently engaged. (This unit cannot have a threat list.)", target->GetName(), target->GetGUID().ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleDebugThreatInfoCommand(ChatHandler* handler)
|
||||
{
|
||||
Unit* target = handler->getSelectedUnit();
|
||||
if (!target)
|
||||
{
|
||||
handler->SendSysMessage(LANG_SELECT_CHAR_OR_CREATURE);
|
||||
handler->SetSentErrorMessage(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
handler->SendSysMessage("End of threat list.");
|
||||
handler->PSendSysMessage("Threat info for {} ({}):", target->GetName(), target->GetGUID().ToString());
|
||||
|
||||
ThreatManager const& mgr = target->GetThreatMgr();
|
||||
|
||||
// _singleSchoolModifiers
|
||||
{
|
||||
auto& mods = mgr._singleSchoolModifiers;
|
||||
handler->SendSysMessage(" - Single-school threat modifiers:");
|
||||
handler->PSendSysMessage(" |-- Physical: {:.2f}%", mods[SPELL_SCHOOL_NORMAL] * 100.0f);
|
||||
handler->PSendSysMessage(" |-- Holy : {:.2f}%", mods[SPELL_SCHOOL_HOLY] * 100.0f);
|
||||
handler->PSendSysMessage(" |-- Fire : {:.2f}%", mods[SPELL_SCHOOL_FIRE] * 100.0f);
|
||||
handler->PSendSysMessage(" |-- Nature : {:.2f}%", mods[SPELL_SCHOOL_NATURE] * 100.0f);
|
||||
handler->PSendSysMessage(" |-- Frost : {:.2f}%", mods[SPELL_SCHOOL_FROST] * 100.0f);
|
||||
handler->PSendSysMessage(" |-- Shadow : {:.2f}%", mods[SPELL_SCHOOL_SHADOW] * 100.0f);
|
||||
handler->PSendSysMessage(" |-- Arcane : {:.2f}%", mods[SPELL_SCHOOL_ARCANE] * 100.0f);
|
||||
}
|
||||
|
||||
// _multiSchoolModifiers
|
||||
{
|
||||
auto& mods = mgr._multiSchoolModifiers;
|
||||
handler->PSendSysMessage(" - Multi-school threat modifiers ({} entries):", mods.size());
|
||||
for (auto const& pair : mods)
|
||||
handler->PSendSysMessage(" |-- Mask 0x{:x}: {:.2f}%", uint32(pair.first), pair.second * 100.0f);
|
||||
}
|
||||
|
||||
// _redirectInfo
|
||||
{
|
||||
auto const& redirectInfo = mgr._redirectInfo;
|
||||
if (redirectInfo.empty())
|
||||
handler->SendSysMessage(" - No redirects being applied");
|
||||
else
|
||||
{
|
||||
handler->PSendSysMessage(" - {:02} redirects being applied:", redirectInfo.size());
|
||||
for (auto const& pair : redirectInfo)
|
||||
{
|
||||
Unit* unit = ObjectAccessor::GetUnit(*target, pair.first);
|
||||
handler->PSendSysMessage(" |-- {:02}% to {}", pair.second, unit ? unit->GetName() : pair.first.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// _redirectRegistry
|
||||
{
|
||||
auto const& redirectRegistry = mgr._redirectRegistry;
|
||||
if (redirectRegistry.empty())
|
||||
handler->SendSysMessage(" - No redirects are registered");
|
||||
else
|
||||
{
|
||||
handler->PSendSysMessage(" - {:02} spells may have redirects registered", redirectRegistry.size());
|
||||
for (auto const& outerPair : redirectRegistry)
|
||||
{
|
||||
SpellInfo const* const spell = sSpellMgr->GetSpellInfo(outerPair.first);
|
||||
handler->PSendSysMessage(" |-- #{:06} {} ({} entries):", outerPair.first, spell ? spell->SpellName[0] : "<unknown>", outerPair.second.size());
|
||||
for (auto const& innerPair : outerPair.second)
|
||||
{
|
||||
Unit* unit = ObjectAccessor::GetUnit(*target, innerPair.first);
|
||||
handler->PSendSysMessage(" |-- {:02}% to {}", innerPair.second, unit ? unit->GetName() : innerPair.first.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleDebugCombatListCommand(ChatHandler* handler)
|
||||
{
|
||||
Unit* target = handler->getSelectedUnit();
|
||||
if (!target)
|
||||
target = handler->GetPlayer();
|
||||
|
||||
handler->PSendSysMessage("Combat refs: (Combat state: {} | Manager state: {})", target->IsInCombat(), target->GetCombatManager().HasCombat());
|
||||
for (auto const& ref : target->GetCombatManager().GetPvPCombatRefs())
|
||||
{
|
||||
Unit* unit = ref.second->GetOther(target);
|
||||
handler->PSendSysMessage("[PvP] {} (SpawnID {})", unit->GetName(), unit->IsCreature() ? unit->ToCreature()->GetSpawnId() : 0);
|
||||
}
|
||||
for (auto const& ref : target->GetCombatManager().GetPvECombatRefs())
|
||||
{
|
||||
Unit* unit = ref.second->GetOther(target);
|
||||
handler->PSendSysMessage("[PvE] {} (SpawnID {})", unit->GetName(), unit->IsCreature() ? unit->ToCreature()->GetSpawnId() : 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -862,27 +1004,32 @@ public:
|
||||
if (!target)
|
||||
target = handler->GetSession()->GetPlayer();
|
||||
|
||||
HostileReference* ref = target->getHostileRefMgr().getFirst();
|
||||
uint32 count = 0;
|
||||
|
||||
handler->PSendSysMessage("Hostile reference list of {} ({})", target->GetName(), target->GetGUID().ToString());
|
||||
handler->PSendSysMessage("Threatened by me list of {} ({})", target->GetName(), target->GetGUID().ToString());
|
||||
|
||||
while (ref)
|
||||
for (auto const& pair : target->GetThreatMgr().GetThreatenedByMeList())
|
||||
{
|
||||
if (Unit* unit = ref->GetSource()->GetOwner())
|
||||
ThreatReference const* ref = pair.second;
|
||||
Creature* owner = ref->GetOwner();
|
||||
if (owner)
|
||||
{
|
||||
handler->PSendSysMessage(" {}. {} {} ({}) - threat {}", ++count, (ref->IsOnline() ? "" : "[offline]"),
|
||||
unit->GetName(), unit->GetGUID().ToString(), ref->GetThreat());
|
||||
std::string stateStr = "";
|
||||
if (ref->IsOffline())
|
||||
stateStr = "[offline] ";
|
||||
else if (ref->IsSuppressed())
|
||||
stateStr = "[suppressed] ";
|
||||
|
||||
handler->PSendSysMessage(" {}. {}{} ({}) - threat {}", ++count, stateStr,
|
||||
owner->GetName(), owner->GetGUID().ToString(), ref->GetThreat());
|
||||
}
|
||||
else
|
||||
{
|
||||
handler->PSendSysMessage(" {}. No Owner - threat {}", ++count, ref->GetThreat());
|
||||
}
|
||||
|
||||
ref = ref->next();
|
||||
}
|
||||
|
||||
handler->SendSysMessage("End of hostile reference list.");
|
||||
handler->SendSysMessage("End of threatened by me list.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2904,7 +2904,7 @@ public:
|
||||
}
|
||||
|
||||
playerTarget->CombatStop();
|
||||
playerTarget->getHostileRefMgr().deleteReferences();
|
||||
playerTarget->GetThreatMgr().RemoveMeFromThreatLists();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -446,7 +446,7 @@ public:
|
||||
case SPELL_AURA_HASTE_RANGED: return "SPELL_AURA_HASTE_RANGED";
|
||||
case SPELL_AURA_MOD_MANA_REGEN_FROM_STAT: return "SPELL_AURA_MOD_MANA_REGEN_FROM_STAT";
|
||||
case SPELL_AURA_MOD_RATING_FROM_STAT: return "SPELL_AURA_MOD_RATING_FROM_STAT";
|
||||
case SPELL_AURA_IGNORED: return "SPELL_AURA_IGNORED";
|
||||
case SPELL_AURA_MOD_DETAUNT: return "SPELL_AURA_MOD_DETAUNT";
|
||||
case SPELL_AURA_222: return "SPELL_AURA_222";
|
||||
case SPELL_AURA_RAID_PROC_FROM_CHARGE: return "SPELL_AURA_RAID_PROC_FROM_CHARGE";
|
||||
case SPELL_AURA_224: return "SPELL_AURA_224";
|
||||
|
||||
+4
-3
@@ -672,11 +672,12 @@ struct boss_nefarian : public BossAI
|
||||
case EVENT_CLASSCALL:
|
||||
if (classesPresent.empty())
|
||||
{
|
||||
for (auto& ref : me->GetThreatMgr().GetThreatList())
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (ref->getTarget() && ref->getTarget()->IsPlayer())
|
||||
if (Unit* victim = ref->GetVictim())
|
||||
{
|
||||
classesPresent.insert(ref->getTarget()->getClass());
|
||||
if (victim->IsPlayer())
|
||||
classesPresent.insert(victim->getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -172,7 +172,9 @@ public:
|
||||
if (Unit* charmer = ObjectAccessor::GetUnit(*me, _charmerGUID))
|
||||
{
|
||||
charmer->RemoveAurasDueToSpell(SPELL_MINDCONTROL_VISUAL);
|
||||
me->TauntApply(charmer);
|
||||
me->EngageWithTarget(charmer);
|
||||
me->AddThreat(charmer, 100.0f);
|
||||
me->AI()->AttackStart(charmer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ public:
|
||||
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
{
|
||||
if (!me->GetThreatMgr().GetThreatList().empty())
|
||||
if (!me->GetThreatMgr().IsThreatListEmpty())
|
||||
{
|
||||
if (!_processingMagmaBurst)
|
||||
{
|
||||
|
||||
@@ -389,18 +389,13 @@ class spell_midnight_fixate : public AuraScript
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (Unit* caster = GetCaster())
|
||||
{
|
||||
caster->TauntApply(target);
|
||||
}
|
||||
caster->GetThreatMgr().FixateTarget(target);
|
||||
}
|
||||
|
||||
void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (Unit* caster = GetCaster())
|
||||
{
|
||||
caster->TauntFadeOut(target);
|
||||
}
|
||||
caster->GetThreatMgr().ClearFixate();
|
||||
}
|
||||
|
||||
void Register() override
|
||||
|
||||
@@ -239,16 +239,14 @@ struct boss_priestess_lackey_commonAI : public ScriptedAI
|
||||
|
||||
void RecalculateThreat()
|
||||
{
|
||||
ThreatContainer::StorageType const& tList = me->GetThreatMgr().GetThreatList();
|
||||
for (auto const& ref : tList)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
Unit* pUnit = ObjectAccessor::GetUnit(*me, ref->getUnitGuid());
|
||||
Unit* pUnit = ref->GetVictim();
|
||||
if (pUnit && pUnit->IsPlayer() && me->GetThreatMgr().GetThreat(pUnit))
|
||||
{
|
||||
float threatMod = GetThreatMod(me->GetDistance2d(pUnit), (float)pUnit->GetArmor(), pUnit->GetHealth(), pUnit->GetMaxHealth(), pUnit);
|
||||
me->GetThreatMgr().ModifyThreatByPercent(pUnit, -100);
|
||||
if (HostileReference* ref = me->GetThreatMgr().GetOnlineContainer().getReferenceByTarget(pUnit))
|
||||
ref->AddThreat(10000000.0f * threatMod);
|
||||
me->GetThreatMgr().ResetThreat(pUnit);
|
||||
me->GetThreatMgr().AddThreat(pUnit, 10000000.0f * threatMod, nullptr, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,14 +254,13 @@ class spell_scholomance_fixate_aura : public AuraScript
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (Unit* caster = GetCaster())
|
||||
caster->TauntApply(target);
|
||||
caster->GetThreatMgr().FixateTarget(target);
|
||||
}
|
||||
|
||||
void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (Unit* caster = GetCaster())
|
||||
caster->TauntFadeOut(target);
|
||||
caster->GetThreatMgr().ClearFixate();
|
||||
}
|
||||
|
||||
void Register() override
|
||||
@@ -339,10 +338,9 @@ public:
|
||||
|
||||
Unit* SelectUnitCasting()
|
||||
{
|
||||
ThreatContainer::StorageType threatlist = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
|
||||
if (Unit* unit = ref->GetVictim())
|
||||
{
|
||||
if (unit->HasUnitState(UNIT_STATE_CASTING))
|
||||
{
|
||||
|
||||
@@ -83,14 +83,14 @@ struct boss_hazzarah : public BossAI
|
||||
{
|
||||
if (me->GetThreatMgr().GetThreatListSize() > 1)
|
||||
{
|
||||
ThreatContainer::StorageType::const_iterator lastRef = me->GetThreatMgr().GetOnlineContainer().GetThreatList().end();
|
||||
--lastRef;
|
||||
if (Unit* lastTarget = (*lastRef)->getTarget())
|
||||
// Check if target is the lowest threat (last in sorted list)
|
||||
ThreatReference const* lowestRef = nullptr;
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetSortedThreatList())
|
||||
lowestRef = ref; // Last iteration will have the lowest threat target
|
||||
|
||||
if (lowestRef && lowestRef->GetVictim() != target)
|
||||
{
|
||||
if (lastTarget != target)
|
||||
{
|
||||
return !target->HasAura(SPELL_SLEEP);
|
||||
}
|
||||
return !target->HasAura(SPELL_SLEEP);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -470,10 +470,10 @@ public:
|
||||
case EVENT_CLEAVE:
|
||||
{
|
||||
std::list<Unit*> meleeRangeTargets;
|
||||
auto i = me->GetThreatMgr().GetThreatList().begin();
|
||||
for (; i != me->GetThreatMgr().GetThreatList().end(); ++i)
|
||||
auto i = me->GetThreatMgr().GetUnsortedThreatList().begin();
|
||||
for (; i != me->GetThreatMgr().GetUnsortedThreatList().end(); ++i)
|
||||
{
|
||||
Unit* target = (*i)->getTarget();
|
||||
Unit* target = (*i)->GetVictim();
|
||||
if (me->IsWithinMeleeRange(target))
|
||||
{
|
||||
meleeRangeTargets.push_back(target);
|
||||
|
||||
@@ -80,14 +80,14 @@ public:
|
||||
{
|
||||
if (me->GetThreatMgr().GetThreatListSize() > 1)
|
||||
{
|
||||
ThreatContainer::StorageType::const_iterator lastRef = me->GetThreatMgr().GetOnlineContainer().GetThreatList().end();
|
||||
--lastRef;
|
||||
if (Unit* lastTarget = (*lastRef)->getTarget())
|
||||
// Check if target is the lowest threat (last in sorted list)
|
||||
ThreatReference const* lowestRef = nullptr;
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetSortedThreatList())
|
||||
lowestRef = ref; // Last iteration will have the lowest threat target
|
||||
|
||||
if (lowestRef && lowestRef->GetVictim() != target)
|
||||
{
|
||||
if (lastTarget != target)
|
||||
{
|
||||
return !target->HasAura(SPELL_GOUGE);
|
||||
}
|
||||
return !target->HasAura(SPELL_GOUGE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,10 +161,9 @@ public:
|
||||
if (_thousandBladesTargets.empty())
|
||||
{
|
||||
std::vector<Unit*> targetList;
|
||||
ThreatContainer::StorageType const& threatlist = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (Unit* target = (*itr)->getTarget())
|
||||
if (Unit* target = ref->GetVictim())
|
||||
{
|
||||
if (target->IsAlive() && target->IsWithinDist2d(me, 100.f))
|
||||
{
|
||||
|
||||
@@ -270,7 +270,7 @@ struct boss_ouro : public BossAI
|
||||
|
||||
void EnterEvadeMode(EvadeReason /*why*/) override
|
||||
{
|
||||
if (me->GetThreatMgr().GetThreatList().empty())
|
||||
if (me->GetThreatMgr().IsThreatListEmpty())
|
||||
{
|
||||
DoCastSelf(SPELL_OURO_SUBMERGE_VISUAL);
|
||||
me->DespawnOrUnsummon(1s);
|
||||
|
||||
@@ -235,9 +235,10 @@ struct boss_hadronox : public BossAI
|
||||
|
||||
bool IsInCombatWithPlayer() const
|
||||
{
|
||||
return std::ranges::any_of(me->GetThreatMgr().GetThreatList(), [](auto const& ref) {
|
||||
return ref->getTarget()->IsControlledByPlayer();
|
||||
});
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
if (ref->GetVictim()->IsControlledByPlayer())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void DamageTaken(Unit* who, uint32& damage, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) override
|
||||
|
||||
@@ -1080,7 +1080,7 @@ class spell_halion_twilight_phasing_aura : public AuraScript
|
||||
GetTarget()->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
|
||||
GetTarget()->ToCreature()->SetReactState(REACT_DEFENSIVE);
|
||||
GetTarget()->GetMotionMaster()->Clear();
|
||||
GetTarget()->GetThreatMgr().clearReferences();
|
||||
GetTarget()->GetThreatMgr().ClearAllThreat();
|
||||
GetTarget()->RemoveAllAttackers();
|
||||
GetTarget()->AttackStop();
|
||||
}
|
||||
@@ -1090,7 +1090,7 @@ class spell_halion_twilight_phasing_aura : public AuraScript
|
||||
GetTarget()->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
|
||||
GetTarget()->ToCreature()->SetReactState(REACT_DEFENSIVE);
|
||||
GetTarget()->GetMotionMaster()->Clear();
|
||||
GetTarget()->GetThreatMgr().clearReferences();
|
||||
GetTarget()->GetThreatMgr().ClearAllThreat();
|
||||
GetTarget()->RemoveAllAttackers();
|
||||
GetTarget()->AttackStop();
|
||||
}
|
||||
|
||||
+2
-2
@@ -138,7 +138,7 @@ public:
|
||||
me->SetFaction(FACTION_FRIENDLY);
|
||||
events.Reset();
|
||||
Talk(SAY_EADRIC_DEFEATED);
|
||||
me->GetThreatMgr().clearReferences();
|
||||
me->GetThreatMgr().ClearAllThreat();
|
||||
me->SetRegeneratingHealth(false);
|
||||
_EnterEvadeMode();
|
||||
me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
@@ -281,7 +281,7 @@ public:
|
||||
me->SetFaction(FACTION_FRIENDLY);
|
||||
events.Reset();
|
||||
Talk(SAY_PALETRESS_DEFEATED);
|
||||
me->GetThreatMgr().clearReferences();
|
||||
me->GetThreatMgr().ClearAllThreat();
|
||||
me->SetRegeneratingHealth(false);
|
||||
_EnterEvadeMode();
|
||||
me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
|
||||
-1
@@ -143,7 +143,6 @@ public:
|
||||
void Reset() override
|
||||
{
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
me->getHostileRefMgr().setOnlineOfflineState(false);
|
||||
}
|
||||
|
||||
void OnCharmed(bool apply) override
|
||||
|
||||
+12
-19
@@ -103,17 +103,14 @@ struct boss_faction_championsAI : public ScriptedAI
|
||||
|
||||
void RecalculateThreat()
|
||||
{
|
||||
ThreatContainer::StorageType const& tList = me->GetThreatMgr().GetThreatList();
|
||||
for( ThreatContainer::StorageType::const_iterator itr = tList.begin(); itr != tList.end(); ++itr )
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
Unit* pUnit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid());
|
||||
Unit* pUnit = ref->GetVictim();
|
||||
if (pUnit && pUnit->IsPlayer() && me->GetThreatMgr().GetThreat(pUnit))
|
||||
{
|
||||
float threatMod = GetThreatMod(me->GetDistance2d(pUnit), (float)pUnit->GetArmor(), pUnit->GetHealth(), pUnit->GetMaxHealth(), pUnit);
|
||||
me->GetThreatMgr().ModifyThreatByPercent(pUnit, -100);
|
||||
//me->getThreatMgr().DoAddThreat(pUnit, 10000000.0f * threatMod);
|
||||
if (HostileReference* ref = me->GetThreatMgr().GetOnlineContainer().getReferenceByTarget(pUnit))
|
||||
ref->AddThreat(10000000.0f * threatMod);
|
||||
me->GetThreatMgr().ResetThreat(pUnit);
|
||||
me->GetThreatMgr().AddThreat(pUnit, 10000000.0f * threatMod, nullptr, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,27 +176,23 @@ struct boss_faction_championsAI : public ScriptedAI
|
||||
|
||||
uint32 EnemiesInRange(float distance)
|
||||
{
|
||||
ThreatContainer::StorageType const& tList = me->GetThreatMgr().GetThreatList();
|
||||
uint32 count = 0;
|
||||
Unit* target;
|
||||
for( ThreatContainer::StorageType::const_iterator iter = tList.begin(); iter != tList.end(); ++iter )
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
target = ObjectAccessor::GetUnit((*me), (*iter)->getUnitGuid());
|
||||
if (target && me->GetDistance2d(target) < distance )
|
||||
++count;
|
||||
if (Unit* target = ref->GetVictim())
|
||||
if (me->GetDistance2d(target) < distance)
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Unit* SelectEnemyCaster(bool casting, float range)
|
||||
{
|
||||
ThreatContainer::StorageType const& tList = me->GetThreatMgr().GetThreatList();
|
||||
Unit* target;
|
||||
for( ThreatContainer::StorageType::const_iterator iter = tList.begin(); iter != tList.end(); ++iter )
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
target = ObjectAccessor::GetUnit((*me), (*iter)->getUnitGuid());
|
||||
if (target && target->getPowerType() == POWER_MANA && (!casting || target->HasUnitState(UNIT_STATE_CASTING)) && me->GetExactDist(target) <= range )
|
||||
return target;
|
||||
if (Unit* target = ref->GetVictim())
|
||||
if (target->getPowerType() == POWER_MANA && (!casting || target->HasUnitState(UNIT_STATE_CASTING)) && me->GetExactDist(target) <= range)
|
||||
return target;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -980,7 +980,7 @@ public:
|
||||
for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
|
||||
if (Player* p = itr->GetSource())
|
||||
if (!p->IsGameMaster())
|
||||
p->SetInCombatState(true);
|
||||
p->SetInCombatWith(me);
|
||||
_events.ScheduleEvent(EVENT_KEEP_PLAYER_IN_COMBAT, 4s);
|
||||
}
|
||||
break;
|
||||
@@ -1319,7 +1319,7 @@ public:
|
||||
for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
|
||||
if (Player* p = itr->GetSource())
|
||||
if (!p->IsGameMaster())
|
||||
p->SetInCombatState(true);
|
||||
p->SetInCombatWith(me);
|
||||
_events.ScheduleEvent(EVENT_KEEP_PLAYER_IN_COMBAT, 4s);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -301,13 +301,13 @@ public:
|
||||
if (events.GetPhaseMask() & PHASE_ONE_MASK && damage >= me->GetPower(POWER_MANA))
|
||||
{
|
||||
// reset threat
|
||||
ThreatContainer::StorageType const& threatlist = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
Unit* unit = ObjectAccessor::GetUnit((*me), (*itr)->getUnitGuid());
|
||||
|
||||
if (unit && DoGetThreat(unit))
|
||||
DoModifyThreatByPercent(unit, -100);
|
||||
if (Unit* unit = ref->GetVictim())
|
||||
{
|
||||
if (DoGetThreat(unit))
|
||||
DoModifyThreatByPercent(unit, -100);
|
||||
}
|
||||
}
|
||||
|
||||
Talk(SAY_PHASE_2);
|
||||
|
||||
@@ -209,7 +209,7 @@ public:
|
||||
|
||||
bool operator()(Unit const* target) const
|
||||
{
|
||||
if (!me || !target || !target->IsPlayer() || target == me->GetThreatMgr().GetCurrentVictim())
|
||||
if (!me || !target || !target->IsPlayer() || target == me->GetThreatMgr().GetLastVictim())
|
||||
return false;
|
||||
|
||||
if (me->IsWithinCombatRange(target, 7.0f))
|
||||
|
||||
@@ -498,7 +498,7 @@ public:
|
||||
bool Execute(uint64 /*time*/, uint32 /*diff*/) override
|
||||
{
|
||||
_owner->SetReactState(REACT_AGGRESSIVE);
|
||||
if (!_owner->GetThreatMgr().isThreatListEmpty())
|
||||
if (!_owner->GetThreatMgr().IsThreatListEmpty())
|
||||
if (Unit* target = _owner->SelectVictim())
|
||||
_owner->AI()->AttackStart(target);
|
||||
if (!_owner->GetVictim())
|
||||
@@ -1180,7 +1180,7 @@ public:
|
||||
{
|
||||
if (summon->GetEntry() == NPC_VILE_SPIRIT)
|
||||
{
|
||||
summon->m_Events.KillAllEvents(true);
|
||||
summon->m_Events.KillAllEvents(false);
|
||||
summon->m_Events.AddEventAtOffset(new VileSpiritActivateEvent(summon), 55s);
|
||||
summon->GetMotionMaster()->Clear(true);
|
||||
summon->StopMoving();
|
||||
@@ -2273,7 +2273,7 @@ public:
|
||||
{
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
|
||||
if (!me->GetThreatMgr().isThreatListEmpty())
|
||||
if (!me->GetThreatMgr().IsThreatListEmpty())
|
||||
if (Unit* target = me->SelectVictim())
|
||||
AttackStart(target);
|
||||
if (!me->GetVictim())
|
||||
|
||||
@@ -593,12 +593,12 @@ public:
|
||||
{
|
||||
checkTimer = 3000;
|
||||
me->SetInCombatWithZone();
|
||||
ThreatContainer::StorageType const& threatList = me->GetThreatMgr().GetThreatList();
|
||||
if (!threatList.empty())
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
|
||||
if (Unit* target = (*itr)->getTarget())
|
||||
if (target->IsAlive() && target->IsPlayer() && me->GetExactDist(target) < 200.0f && !target->IsImmunedToDamageOrSchool(SPELL_SCHOOL_MASK_ALL))
|
||||
return;
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (Unit* target = ref->GetVictim())
|
||||
if (target->IsAlive() && target->IsPlayer() && me->GetExactDist(target) < 200.0f && !target->IsImmunedToDamageOrSchool(SPELL_SCHOOL_MASK_ALL))
|
||||
return;
|
||||
}
|
||||
EnterEvadeMode();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1762,15 +1762,26 @@ public:
|
||||
Position myPos = me->GetPosition();
|
||||
me->NearTeleportTo(c->GetPositionX(), c->GetPositionY(), c->GetPositionZ(), c->GetOrientation());
|
||||
c->NearTeleportTo(myPos.GetPositionX(), myPos.GetPositionY(), myPos.GetPositionZ(), myPos.GetOrientation());
|
||||
const ThreatContainer::StorageType me_tl = me->GetThreatMgr().GetThreatList();
|
||||
const ThreatContainer::StorageType target_tl = c->GetThreatMgr().GetThreatList();
|
||||
|
||||
// Store threat values before reset
|
||||
std::vector<std::pair<Unit*, float>> myThreats;
|
||||
std::vector<std::pair<Unit*, float>> targetThreats;
|
||||
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
if (Unit* victim = ref->GetVictim())
|
||||
myThreats.push_back({victim, ref->GetThreat()});
|
||||
|
||||
for (ThreatReference const* ref : c->GetThreatMgr().GetUnsortedThreatList())
|
||||
if (Unit* victim = ref->GetVictim())
|
||||
targetThreats.push_back({victim, ref->GetThreat()});
|
||||
|
||||
DoResetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator iter = target_tl.begin(); iter != target_tl.end(); ++iter)
|
||||
me->GetThreatMgr().AddThreat((*iter)->getTarget(), (*iter)->GetThreat());
|
||||
for (auto const& pair : targetThreats)
|
||||
me->GetThreatMgr().AddThreat(pair.first, pair.second);
|
||||
|
||||
c->GetThreatMgr().ResetAllThreat();
|
||||
for (ThreatContainer::StorageType::const_iterator iter = me_tl.begin(); iter != me_tl.end(); ++iter)
|
||||
c->GetThreatMgr().AddThreat((*iter)->getTarget(), (*iter)->GetThreat());
|
||||
for (auto const& pair : myThreats)
|
||||
c->GetThreatMgr().AddThreat(pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -406,16 +406,19 @@ public:
|
||||
events.Repeat(15s);
|
||||
break;
|
||||
case EVENT_TELEPORT:
|
||||
{
|
||||
me->AttackStop();
|
||||
if (IN_LIVE_SIDE(me))
|
||||
{
|
||||
me->CastSpell(me, SPELL_TELEPORT_DEAD, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
me->CastSpell(me, SPELL_TELEPORT_LIVE, false);
|
||||
}
|
||||
me->GetThreatMgr().resetAggro(NotOnSameSide(me));
|
||||
|
||||
// Clear threat from targets not on the same side as Gothik
|
||||
NotOnSameSide notOnSameSide(me);
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
if (notOnSameSide(ref->GetVictim()))
|
||||
me->GetThreatMgr().ClearThreat(ref->GetVictim());
|
||||
|
||||
if (Unit* pTarget = SelectTarget(SelectTargetMethod::MaxDistance, 0))
|
||||
{
|
||||
me->GetThreatMgr().AddThreat(pTarget, 100.0f);
|
||||
@@ -423,6 +426,7 @@ public:
|
||||
}
|
||||
events.Repeat(20s);
|
||||
break;
|
||||
}
|
||||
case EVENT_CHECK_HEALTH:
|
||||
if (me->HealthBelowPct(30))
|
||||
{
|
||||
|
||||
@@ -401,15 +401,17 @@ public:
|
||||
case EVENT_DETONATE_MANA:
|
||||
{
|
||||
std::vector<Unit*> unitList;
|
||||
ThreatContainer::StorageType const& threatList = me->GetThreatMgr().GetThreatList();
|
||||
for (auto itr : threatList)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (itr->getTarget()->IsPlayer()
|
||||
&& itr->getTarget()->getPowerType() == POWER_MANA
|
||||
&& itr->getTarget()->GetPower(POWER_MANA))
|
||||
{
|
||||
unitList.push_back(itr->getTarget());
|
||||
}
|
||||
if (Unit* target = ref->GetVictim())
|
||||
{
|
||||
if (target->IsPlayer()
|
||||
&& target->getPowerType() == POWER_MANA
|
||||
&& target->GetPower(POWER_MANA))
|
||||
{
|
||||
unitList.push_back(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!unitList.empty())
|
||||
{
|
||||
|
||||
@@ -117,11 +117,11 @@ public:
|
||||
std::list<Unit*> meleeRangeTargets;
|
||||
Unit* finalTarget = nullptr;
|
||||
uint8 counter = 0;
|
||||
auto i = me->GetThreatMgr().GetThreatList().begin();
|
||||
for (; i != me->GetThreatMgr().GetThreatList().end(); ++i, ++counter)
|
||||
auto threatList = me->GetThreatMgr().GetSortedThreatList();
|
||||
for (auto i = threatList.begin(); i != threatList.end(); ++i, ++counter)
|
||||
{
|
||||
// Gather all units with melee range
|
||||
Unit* target = (*i)->getTarget();
|
||||
Unit* target = (*i)->GetVictim();
|
||||
if (me->IsWithinMeleeRange(target))
|
||||
{
|
||||
meleeRangeTargets.push_back(target);
|
||||
|
||||
@@ -315,17 +315,17 @@ public:
|
||||
}
|
||||
|
||||
std::vector<Unit*> targets;
|
||||
auto i = me->GetThreatMgr().GetThreatList().begin();
|
||||
for (; i != me->GetThreatMgr().GetThreatList().end(); ++i)
|
||||
auto i = me->GetThreatMgr().GetUnsortedThreatList().begin();
|
||||
for (; i != me->GetThreatMgr().GetUnsortedThreatList().end(); ++i)
|
||||
{
|
||||
if ((*i)->getTarget()->IsPlayer())
|
||||
if ((*i)->GetVictim()->IsPlayer())
|
||||
{
|
||||
bool inList = false;
|
||||
if (!blockList.empty())
|
||||
{
|
||||
for (GuidList::const_iterator itr = blockList.begin(); itr != blockList.end(); ++itr)
|
||||
{
|
||||
if ((*i)->getTarget()->GetGUID() == *itr)
|
||||
if ((*i)->GetVictim()->GetGUID() == *itr)
|
||||
{
|
||||
inList = true;
|
||||
break;
|
||||
@@ -334,7 +334,7 @@ public:
|
||||
}
|
||||
if (!inList)
|
||||
{
|
||||
targets.push_back((*i)->getTarget());
|
||||
targets.push_back((*i)->GetVictim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,8 +128,8 @@ public:
|
||||
|
||||
bool IsAnyPlayerInMeleeRange() const
|
||||
{
|
||||
for (auto const& ref : me->GetThreatMgr().GetThreatList())
|
||||
if (Unit* target = ref->getTarget())
|
||||
for (auto const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
if (Unit* target = ref->GetVictim())
|
||||
if (target->IsPlayer() && me->IsWithinMeleeRange(target))
|
||||
return true;
|
||||
return false;
|
||||
|
||||
@@ -1419,7 +1419,7 @@ public:
|
||||
{
|
||||
me->CastSpell(me, SPELL_SAC_GHOUL_EXPLODE, true);
|
||||
me->KillSelf();
|
||||
me->m_Events.KillAllEvents(true);
|
||||
me->m_Events.KillAllEvents(false);
|
||||
Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
+7
-6
@@ -85,15 +85,16 @@ struct boss_blackheart_the_inciter : public BossAI
|
||||
DoCastAOE(SPELL_INCITE_CHAOS);
|
||||
DoCastSelf(SPELL_LAUGHTER, true);
|
||||
uint32 inciteTriggerID = NPC_INCITE_TRIGGER;
|
||||
std::list<HostileReference*> t_list = me->GetThreatMgr().GetThreatList();
|
||||
for (std::list<HostileReference*>::const_iterator itr = t_list.begin(); itr != t_list.end(); ++itr)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
Unit* target = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid());
|
||||
if (target && target->IsPlayer())
|
||||
if (Unit* target = ref->GetVictim())
|
||||
{
|
||||
if (Creature* inciteTrigger = me->SummonCreature(inciteTriggerID++, *target, TEMPSUMMON_TIMED_DESPAWN, 15 * IN_MILLISECONDS))
|
||||
if (target->IsPlayer())
|
||||
{
|
||||
inciteTrigger->CastSpell(target, SPELL_INCITE_CHAOS_B, true);
|
||||
if (Creature* inciteTrigger = me->SummonCreature(inciteTriggerID++, *target, TEMPSUMMON_TIMED_DESPAWN, 15 * IN_MILLISECONDS))
|
||||
{
|
||||
inciteTrigger->CastSpell(target, SPELL_INCITE_CHAOS_B, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ struct boss_murmur : public BossAI
|
||||
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
{
|
||||
if (me->GetThreatMgr().GetThreatList().empty())
|
||||
if (me->GetThreatMgr().IsThreatListEmpty())
|
||||
{
|
||||
BossAI::EnterEvadeMode(why);
|
||||
}
|
||||
|
||||
@@ -1104,7 +1104,7 @@ struct npc_maiev_illidan : public ScriptedAI
|
||||
void Reset() override
|
||||
{
|
||||
scheduler.CancelAll();
|
||||
me->m_Events.KillAllEvents(true);
|
||||
me->m_Events.KillAllEvents(false);
|
||||
}
|
||||
|
||||
void DoAction(int32 param) override
|
||||
|
||||
@@ -168,13 +168,14 @@ struct boss_supremus : public BossAI
|
||||
Unit* FindHatefulStrikeTarget()
|
||||
{
|
||||
Unit* target = nullptr;
|
||||
ThreatContainer::StorageType const& threatlist = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator i = threatlist.begin(); i != threatlist.end(); ++i)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
|
||||
if (unit && me->IsWithinMeleeRange(unit))
|
||||
if (!target || unit->GetHealth() > target->GetHealth())
|
||||
target = unit;
|
||||
if (Unit* unit = ref->GetVictim())
|
||||
{
|
||||
if (me->IsWithinMeleeRange(unit))
|
||||
if (!target || unit->GetHealth() > target->GetHealth())
|
||||
target = unit;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
|
||||
@@ -215,8 +215,9 @@ class spell_black_template_harpooners_mark_aura : public AuraScript
|
||||
GetUnitOwner()->GetCreaturesWithEntryInRange(creatureList, 80.0f, NPC_DRAGON_TURTLE);
|
||||
for (std::list<Creature*>::const_iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr)
|
||||
{
|
||||
(*itr)->TauntApply(GetUnitOwner());
|
||||
(*itr)->AddThreat(GetUnitOwner(), 10000000.0f);
|
||||
if ((*itr)->AI())
|
||||
(*itr)->AI()->AttackStart(GetUnitOwner());
|
||||
_turtleSet.insert((*itr)->GetGUID());
|
||||
}
|
||||
}
|
||||
@@ -226,7 +227,6 @@ class spell_black_template_harpooners_mark_aura : public AuraScript
|
||||
for (ObjectGuid const& guid : _turtleSet)
|
||||
if (Creature* turtle = ObjectAccessor::GetCreature(*GetUnitOwner(), guid))
|
||||
{
|
||||
turtle->TauntFadeOut(GetUnitOwner());
|
||||
turtle->AddThreat(GetUnitOwner(), -10000000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,10 +212,9 @@ struct boss_the_lurker_below : public BossAI
|
||||
}
|
||||
else
|
||||
{
|
||||
ThreatContainer::StorageType const& t_list = me->GetThreatMgr().GetThreatList();
|
||||
for (ThreatReference const* ref : t_list)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if (Unit* threatTarget = ObjectAccessor::GetUnit(*me, ref->getUnitGuid()))
|
||||
if (Unit* threatTarget = ref->GetVictim())
|
||||
{
|
||||
if (me->IsWithinMeleeRange(threatTarget))
|
||||
{
|
||||
|
||||
@@ -172,7 +172,7 @@ struct boss_alar : public BossAI
|
||||
{
|
||||
if (why == EVADE_REASON_BOUNDARY)
|
||||
BossAI::EnterEvadeMode(why);
|
||||
else if (me->GetThreatMgr().GetThreatList().empty())
|
||||
else if (me->GetThreatMgr().IsThreatListEmpty())
|
||||
BossAI::EnterEvadeMode(why);
|
||||
}
|
||||
|
||||
|
||||
@@ -1083,13 +1083,14 @@ class spell_kaelthas_nether_beam : public SpellScript
|
||||
{
|
||||
PreventHitEffect(effIndex);
|
||||
|
||||
ThreatContainer::StorageType const& ThreatList = GetCaster()-> GetThreatMgr().GetThreatList();
|
||||
std::list<Unit*> targetList;
|
||||
for (ThreatContainer::StorageType::const_iterator itr = ThreatList.begin(); itr != ThreatList.end(); ++itr)
|
||||
for (ThreatReference const* ref : GetCaster()->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
Unit* target = ObjectAccessor::GetUnit(*GetCaster(), (*itr)->getUnitGuid());
|
||||
if (target && target->IsPlayer())
|
||||
targetList.push_back(target);
|
||||
if (Unit* target = ref->GetVictim())
|
||||
{
|
||||
if (target->IsPlayer())
|
||||
targetList.push_back(target);
|
||||
}
|
||||
}
|
||||
|
||||
Acore::Containers::RandomResize(targetList, 5);
|
||||
|
||||
@@ -59,8 +59,9 @@ class spell_botanica_call_of_the_falcon_aura : public AuraScript
|
||||
GetUnitOwner()->GetCreaturesWithEntryInRange(creatureList, 80.0f, NPC_BLOODFALCON);
|
||||
for (std::list<Creature*>::const_iterator itr = creatureList.begin(); itr != creatureList.end(); ++itr)
|
||||
{
|
||||
(*itr)->TauntApply(GetUnitOwner());
|
||||
(*itr)->AddThreat(GetUnitOwner(), 10000000.0f);
|
||||
if ((*itr)->AI())
|
||||
(*itr)->AI()->AttackStart(GetUnitOwner());
|
||||
_falconSet.insert((*itr)->GetGUID());
|
||||
}
|
||||
}
|
||||
@@ -70,7 +71,6 @@ class spell_botanica_call_of_the_falcon_aura : public AuraScript
|
||||
for (ObjectGuid const& guid : _falconSet)
|
||||
if (Creature* falcon = ObjectAccessor::GetCreature(*GetUnitOwner(), guid))
|
||||
{
|
||||
falcon->TauntFadeOut(GetUnitOwner());
|
||||
falcon->AddThreat(GetUnitOwner(), -10000000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,6 @@ public:
|
||||
if (Creature* Target = GetClosestCreatureWithEntry(me, NPC_DEATHS_DOOR_FEL_CANNON_TARGET_BUNNY, 200.0f))
|
||||
{
|
||||
me->SetFacingToObject(Target);
|
||||
me->TauntFadeOut(Target);
|
||||
me->CombatStop(); // force
|
||||
}
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ class spell_pet_guard_dog : public AuraScript
|
||||
return;
|
||||
|
||||
float addThreat = CalculatePct(static_cast<float>(procSpellInfo->Effects[EFFECT_0].CalcValue(caster)), aurEff->GetAmount());
|
||||
target->GetThreatMgr().AddThreat(caster, addThreat, SPELL_SCHOOL_MASK_NORMAL, GetSpellInfo());
|
||||
target->GetThreatMgr().AddThreat(caster, addThreat, GetSpellInfo());
|
||||
}
|
||||
|
||||
void Register() override
|
||||
|
||||
@@ -106,12 +106,10 @@ struct npc_pet_mage_mirror_image : CasterAI
|
||||
|
||||
// Xinef: Inherit Master's Threat List (not yet implemented)
|
||||
//owner->CastSpell((Unit*)nullptr, SPELL_MAGE_MASTERS_THREAT_LIST, true);
|
||||
HostileReference* ref = owner->getHostileRefMgr().getFirst();
|
||||
while (ref)
|
||||
for (auto const& pair : owner->GetThreatMgr().GetThreatenedByMeList())
|
||||
{
|
||||
if (Unit* unit = ref->GetSource()->GetOwner())
|
||||
unit->AddThreat(me, ref->GetThreat() - ref->getTempThreatModifier());
|
||||
ref = ref->next();
|
||||
if (Unit* unit = pair.second->GetOwner())
|
||||
unit->GetThreatMgr().AddThreat(me, pair.second->GetThreat());
|
||||
}
|
||||
|
||||
_ebonGargoyleGUID.Clear();
|
||||
|
||||
@@ -673,6 +673,20 @@ class spell_dk_dancing_rune_weapon : public AuraScript
|
||||
{
|
||||
PrepareAuraScript(spell_dk_dancing_rune_weapon);
|
||||
|
||||
void HandleApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
||||
{
|
||||
Unit* caster = GetCaster();
|
||||
if (!caster)
|
||||
return;
|
||||
|
||||
// Redirect 100% of the DRW's threat to the DK player
|
||||
uint32 npcEntry = GetSpellInfo()->Effects[EFFECT_0].MiscValue;
|
||||
std::list<Creature*> runeWeapons;
|
||||
caster->GetAllMinionsByEntry(runeWeapons, npcEntry);
|
||||
for (Creature* temp : runeWeapons)
|
||||
temp->GetThreatMgr().RegisterRedirectThreat(GetId(), caster->GetGUID(), 100);
|
||||
}
|
||||
|
||||
bool CheckProc(ProcEventInfo& eventInfo)
|
||||
{
|
||||
if (!eventInfo.GetActor() || !eventInfo.GetActionTarget() || !eventInfo.GetActionTarget()->IsAlive() || !eventInfo.GetActor()->IsPlayer())
|
||||
@@ -749,6 +763,7 @@ class spell_dk_dancing_rune_weapon : public AuraScript
|
||||
|
||||
void Register() override
|
||||
{
|
||||
AfterEffectApply += AuraEffectApplyFn(spell_dk_dancing_rune_weapon::HandleApply, EFFECT_2, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
|
||||
DoCheckProc += AuraCheckProcFn(spell_dk_dancing_rune_weapon::CheckProc);
|
||||
OnEffectProc += AuraEffectProcFn(spell_dk_dancing_rune_weapon::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ enum HunterSpells
|
||||
SPELL_HUNTER_IMPROVED_MEND_PET = 24406,
|
||||
SPELL_HUNTER_INVIGORATION_TRIGGERED = 53398,
|
||||
SPELL_HUNTER_MASTERS_CALL_TRIGGERED = 62305,
|
||||
SPELL_HUNTER_MISDIRECTION = 34477,
|
||||
SPELL_HUNTER_MISDIRECTION_PROC = 35079,
|
||||
SPELL_HUNTER_PET_LAST_STAND_TRIGGERED = 53479,
|
||||
SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX = 55709,
|
||||
@@ -893,7 +894,7 @@ class spell_hun_misdirection : public AuraScript
|
||||
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
||||
{
|
||||
if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEFAULT || !GetTarget()->HasAura(SPELL_HUNTER_MISDIRECTION_PROC))
|
||||
GetTarget()->ResetRedirectThreat();
|
||||
GetTarget()->GetThreatMgr().UnregisterRedirectThreat(SPELL_HUNTER_MISDIRECTION);
|
||||
}
|
||||
|
||||
bool CheckProc(ProcEventInfo& eventInfo)
|
||||
@@ -902,7 +903,7 @@ class spell_hun_misdirection : public AuraScript
|
||||
if ((eventInfo.GetProcSpell() && (eventInfo.GetProcSpell()->GetSpellInfo()->SpellFamilyFlags[0] & 0x800000)) || (eventInfo.GetHealInfo() && (eventInfo.GetHealInfo()->GetSpellInfo()->SpellFamilyFlags[0] & 0x800000)))
|
||||
return false;
|
||||
|
||||
return GetTarget()->GetRedirectThreatTarget();
|
||||
return GetTarget()->GetThreatMgr().HasRedirects();
|
||||
}
|
||||
|
||||
void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/)
|
||||
@@ -926,7 +927,7 @@ class spell_hun_misdirection_proc : public AuraScript
|
||||
|
||||
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
||||
{
|
||||
GetTarget()->ResetRedirectThreat();
|
||||
GetTarget()->GetThreatMgr().UnregisterRedirectThreat(SPELL_HUNTER_MISDIRECTION);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
|
||||
@@ -915,9 +915,8 @@ class spell_pri_mind_control : public AuraScript
|
||||
{
|
||||
if (Unit* target = GetTarget())
|
||||
{
|
||||
uint32 duration = static_cast<uint32>(GetDuration());
|
||||
caster->SetInCombatWith(target, duration);
|
||||
target->SetInCombatWith(caster, duration);
|
||||
caster->SetInCombatWith(target);
|
||||
target->SetInCombatWith(caster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ enum RogueSpells
|
||||
SPELL_ROGUE_KILLING_SPREE_DMG_BUFF = 61851,
|
||||
SPELL_ROGUE_PREY_ON_THE_WEAK = 58670,
|
||||
SPELL_ROGUE_SHIV_TRIGGERED = 5940,
|
||||
SPELL_ROGUE_TRICKS_OF_THE_TRADE = 57934,
|
||||
SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST = 57933,
|
||||
SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC = 59628,
|
||||
// Proc system spells
|
||||
@@ -616,53 +617,68 @@ class spell_rog_shiv : public SpellScript
|
||||
}
|
||||
};
|
||||
|
||||
// 57934 - Tricks of the Trade
|
||||
class spell_rog_tricks_of_the_trade : public AuraScript
|
||||
// 57934 - Tricks of the Trade (AuraScript)
|
||||
class spell_rog_tricks_of_the_trade_aura : public AuraScript
|
||||
{
|
||||
PrepareAuraScript(spell_rog_tricks_of_the_trade);
|
||||
PrepareAuraScript(spell_rog_tricks_of_the_trade_aura);
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo({ SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC });
|
||||
}
|
||||
|
||||
bool Load() override
|
||||
{
|
||||
_redirectTarget = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
||||
{
|
||||
if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEFAULT)
|
||||
GetTarget()->ResetRedirectThreat();
|
||||
if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEFAULT || !GetTarget()->HasAura(SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC))
|
||||
GetTarget()->GetThreatMgr().UnregisterRedirectThreat(SPELL_ROGUE_TRICKS_OF_THE_TRADE);
|
||||
}
|
||||
|
||||
bool CheckProc(ProcEventInfo& /*eventInfo*/)
|
||||
{
|
||||
_redirectTarget = GetTarget()->GetRedirectThreatTarget();
|
||||
return _redirectTarget;
|
||||
}
|
||||
|
||||
void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/)
|
||||
void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/)
|
||||
{
|
||||
PreventDefaultAction();
|
||||
|
||||
Unit* target = GetTarget();
|
||||
target->CastSpell(_redirectTarget, SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST, true);
|
||||
target->CastSpell(target, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC, true);
|
||||
Remove(AURA_REMOVE_BY_DEFAULT); // maybe handle by proc charges
|
||||
Unit* rogue = GetTarget();
|
||||
Unit* target = ObjectAccessor::GetUnit(*rogue, _redirectTarget);
|
||||
if (target)
|
||||
{
|
||||
rogue->CastSpell(target, SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST, true, nullptr, aurEff);
|
||||
rogue->CastSpell(rogue, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC, true, nullptr, aurEff);
|
||||
}
|
||||
Remove(AURA_REMOVE_BY_DEFAULT);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
|
||||
DoCheckProc += AuraCheckProcFn(spell_rog_tricks_of_the_trade::CheckProc);
|
||||
OnEffectProc += AuraEffectProcFn(spell_rog_tricks_of_the_trade::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
|
||||
AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade_aura::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
|
||||
OnEffectProc += AuraEffectProcFn(spell_rog_tricks_of_the_trade_aura::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
|
||||
}
|
||||
|
||||
private:
|
||||
Unit* _redirectTarget;
|
||||
ObjectGuid _redirectTarget;
|
||||
public:
|
||||
void SetRedirectTarget(ObjectGuid const& guid) { _redirectTarget = guid; }
|
||||
};
|
||||
|
||||
// 57934 - Tricks of the Trade (SpellScript)
|
||||
class spell_rog_tricks_of_the_trade : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_rog_tricks_of_the_trade);
|
||||
|
||||
void DoAfterHit()
|
||||
{
|
||||
if (Aura* aura = GetHitAura())
|
||||
if (auto* script = aura->GetScript<spell_rog_tricks_of_the_trade_aura>("spell_rog_tricks_of_the_trade"))
|
||||
{
|
||||
if (Unit* explTarget = GetExplTargetUnit())
|
||||
script->SetRedirectTarget(explTarget->GetGUID());
|
||||
else
|
||||
script->SetRedirectTarget(ObjectGuid::Empty);
|
||||
}
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
AfterHit += SpellHitFn(spell_rog_tricks_of_the_trade::DoAfterHit);
|
||||
}
|
||||
};
|
||||
|
||||
// 59628 - Tricks of the Trade (Proc)
|
||||
@@ -672,7 +688,7 @@ class spell_rog_tricks_of_the_trade_proc : public AuraScript
|
||||
|
||||
void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
||||
{
|
||||
GetTarget()->ResetRedirectThreat();
|
||||
GetTarget()->GetThreatMgr().UnregisterRedirectThreat(SPELL_ROGUE_TRICKS_OF_THE_TRADE);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
@@ -1162,7 +1178,7 @@ void AddSC_rogue_spell_scripts()
|
||||
RegisterSpellScript(spell_rog_prey_on_the_weak);
|
||||
RegisterSpellScript(spell_rog_rupture);
|
||||
RegisterSpellScript(spell_rog_shiv);
|
||||
RegisterSpellScript(spell_rog_tricks_of_the_trade);
|
||||
RegisterSpellAndAuraScriptPair(spell_rog_tricks_of_the_trade, spell_rog_tricks_of_the_trade_aura);
|
||||
RegisterSpellScript(spell_rog_tricks_of_the_trade_proc);
|
||||
RegisterSpellScript(spell_rog_pickpocket);
|
||||
RegisterSpellScript(spell_rog_vanish_purge);
|
||||
|
||||
@@ -748,7 +748,7 @@ class spell_warr_vigilance : public AuraScript
|
||||
target->RemoveAurasDueToSpell(SPELL_GEN_DAMAGE_REDUCTION_AURA);
|
||||
}
|
||||
|
||||
target->ResetRedirectThreat();
|
||||
target->GetThreatMgr().UnregisterRedirectThreat(SPELL_WARRIOR_VIGILANCE_REDIRECT_THREAT, GetCasterGUID());
|
||||
}
|
||||
|
||||
bool CheckProc(ProcEventInfo& /*eventInfo*/)
|
||||
|
||||
@@ -320,13 +320,13 @@ public:
|
||||
{
|
||||
Talk(SAY_YSONDRE_SUMMON_DRUIDS);
|
||||
|
||||
auto const& attackers = me->GetThreatMgr().GetThreatList();
|
||||
uint8 attackersCount = 0;
|
||||
|
||||
for (const auto attacker : attackers)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if ((*attacker)->ToPlayer() && (*attacker)->IsAlive())
|
||||
++attackersCount;
|
||||
if (Unit* victim = ref->GetVictim())
|
||||
if (victim->ToPlayer() && victim->IsAlive())
|
||||
++attackersCount;
|
||||
}
|
||||
|
||||
uint8 amount = attackersCount < 30 ? attackersCount * 0.5f : 15;
|
||||
|
||||
@@ -53,11 +53,14 @@ void NPCStaveQuestAI::StorePlayerGUID()
|
||||
return;
|
||||
}
|
||||
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
if ((*itr)->getTarget()->IsPlayer())
|
||||
if (Unit* target = ref->GetVictim())
|
||||
{
|
||||
playerGUID = (*itr)->getUnitGuid();
|
||||
if (target->IsPlayer())
|
||||
{
|
||||
playerGUID = target->GetGUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,18 +110,16 @@ bool NPCStaveQuestAI::UnitIsUnfair(Unit* unit)
|
||||
|
||||
bool NPCStaveQuestAI::IsFairFight()
|
||||
{
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
|
||||
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
|
||||
{
|
||||
Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid());
|
||||
|
||||
if (!(*itr)->GetThreat())
|
||||
if (!ref->GetThreat())
|
||||
{
|
||||
// if target threat is 0 its fair, this prevents despawn in the case when
|
||||
// there is a bystander since UpdateVictim adds nearby enemies to the threatlist
|
||||
continue;
|
||||
}
|
||||
|
||||
if (UnitIsUnfair(unit))
|
||||
if (UnitIsUnfair(ref->GetVictim()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -129,7 +130,7 @@ bool NPCStaveQuestAI::IsFairFight()
|
||||
|
||||
bool NPCStaveQuestAI::ValidThreatlist()
|
||||
{
|
||||
if (threatList.size() == 1)
|
||||
if (me->GetThreatMgr().GetThreatListSize() == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -232,7 +233,7 @@ void NPCStaveQuestAI::ResetState(uint32 aura = 0)
|
||||
|
||||
if (InNormalForm())
|
||||
{
|
||||
me->m_Events.KillAllEvents(true);
|
||||
me->m_Events.KillAllEvents(false);
|
||||
me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP);
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,6 @@ struct NPCStaveQuestAI : public ScriptedAI
|
||||
ObjectGuid gossipPlayerGUID;
|
||||
ObjectGuid playerGUID;
|
||||
bool encounterStarted;
|
||||
ThreatContainer::StorageType const& threatList = me->GetThreatMgr().GetThreatList();
|
||||
|
||||
std::map<int, int> entryKeys = {
|
||||
{ ARTORIUS_NORMAL_ENTRY, 1 },
|
||||
|
||||
@@ -312,123 +312,85 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
enum eTrainingDummy
|
||||
struct npc_training_dummy : NullCreatureAI
|
||||
{
|
||||
SPELL_STUN_PERMANENT = 61204
|
||||
};
|
||||
npc_training_dummy(Creature* creature) : NullCreatureAI(creature) { }
|
||||
|
||||
class npc_training_dummy : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_training_dummy() : CreatureScript("npc_training_dummy") { }
|
||||
|
||||
struct npc_training_dummyAI : ScriptedAI
|
||||
void JustEnteredCombat(Unit* who) override
|
||||
{
|
||||
npc_training_dummyAI(Creature* creature) : ScriptedAI(creature)
|
||||
_combatTimer[who->GetGUID()] = 5s;
|
||||
}
|
||||
|
||||
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damageType, SpellSchoolMask) override
|
||||
{
|
||||
damage = 0;
|
||||
|
||||
if (!attacker || damageType == DOT)
|
||||
return;
|
||||
|
||||
_combatTimer[attacker->GetGUID()] = 5s;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
for (auto itr = _combatTimer.begin(); itr != _combatTimer.end();)
|
||||
{
|
||||
me->SetCombatMovement(false);
|
||||
me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true); //imune to knock aways like blast wave
|
||||
}
|
||||
|
||||
uint32 resetTimer;
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
me->CastSpell(me, SPELL_STUN_PERMANENT, true);
|
||||
resetTimer = 5000;
|
||||
}
|
||||
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
{
|
||||
if (!_EnterEvadeMode(why))
|
||||
return;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
|
||||
{
|
||||
resetTimer = 5000;
|
||||
damage = 0;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
if (resetTimer <= diff)
|
||||
itr->second -= Milliseconds(diff);
|
||||
if (itr->second <= 0s)
|
||||
{
|
||||
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
|
||||
resetTimer = 5000;
|
||||
// The attacker has not dealt any damage to the dummy for over 5 seconds. End combat.
|
||||
auto const& pveRefs = me->GetCombatManager().GetPvECombatRefs();
|
||||
auto it = pveRefs.find(itr->first);
|
||||
if (it != pveRefs.end())
|
||||
it->second->EndCombat();
|
||||
|
||||
itr = _combatTimer.erase(itr);
|
||||
}
|
||||
else
|
||||
resetTimer -= diff;
|
||||
++itr;
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override { }
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_training_dummyAI(creature);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<ObjectGuid, Milliseconds> _combatTimer;
|
||||
};
|
||||
|
||||
class npc_target_dummy : public CreatureScript
|
||||
struct npc_target_dummy : NullCreatureAI
|
||||
{
|
||||
public:
|
||||
npc_target_dummy() : CreatureScript("npc_target_dummy") { }
|
||||
|
||||
struct npc_target_dummyAI : ScriptedAI
|
||||
npc_target_dummy(Creature* creature) : NullCreatureAI(creature)
|
||||
{
|
||||
npc_target_dummyAI(Creature* creature) : ScriptedAI(creature)
|
||||
{
|
||||
me->SetCombatMovement(false);
|
||||
deathTimer = 15000;
|
||||
me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true); //imune to knock aways like blast wave
|
||||
}
|
||||
_deathTimer = 15s;
|
||||
}
|
||||
|
||||
uint32 deathTimer;
|
||||
void Reset() override
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_STUNNED);
|
||||
me->SetLootRecipient(me->GetOwner());
|
||||
me->SelectLevel();
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
|
||||
{
|
||||
damage = 0;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!me->HasUnitState(UNIT_STATE_STUNNED))
|
||||
me->SetControlled(true, UNIT_STATE_STUNNED);
|
||||
|
||||
_deathTimer -= Milliseconds(diff);
|
||||
if (_deathTimer <= 0s)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_STUNNED); //disable rotate
|
||||
me->SetLootRecipient(me->GetOwner());
|
||||
me->SelectLevel();
|
||||
me->LowerPlayerDamageReq(me->GetMaxHealth());
|
||||
me->KillSelf();
|
||||
_deathTimer = 600s;
|
||||
}
|
||||
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
{
|
||||
if (!_EnterEvadeMode(why))
|
||||
return;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!me->HasUnitState(UNIT_STATE_STUNNED))
|
||||
me->SetControlled(true, UNIT_STATE_STUNNED);//disable rotate
|
||||
|
||||
if (deathTimer <= diff)
|
||||
{
|
||||
me->SetLootRecipient(me->GetOwner());
|
||||
me->LowerPlayerDamageReq(me->GetMaxHealth());
|
||||
me->KillSelf();
|
||||
deathTimer = 600000;
|
||||
}
|
||||
else
|
||||
deathTimer -= diff;
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override { }
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_target_dummyAI(creature);
|
||||
}
|
||||
|
||||
private:
|
||||
Milliseconds _deathTimer;
|
||||
};
|
||||
|
||||
/*########
|
||||
@@ -2738,8 +2700,8 @@ void AddSC_npcs_special()
|
||||
{
|
||||
new npc_elder_clearwater();
|
||||
new npc_riggle_bassbait();
|
||||
new npc_target_dummy();
|
||||
new npc_training_dummy();
|
||||
RegisterCreatureAI(npc_target_dummy);
|
||||
RegisterCreatureAI(npc_training_dummy);
|
||||
new npc_venomhide_hatchling();
|
||||
new npc_air_force_bots();
|
||||
new npc_chicken_cluck();
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 "TestCreature.h"
|
||||
#include "ThreatManager.h"
|
||||
#include "CombatManager.h"
|
||||
|
||||
// Heap-allocated to avoid static init calling CreatureMovementData()
|
||||
// which requires sWorld to be set up (calls getIntConfig).
|
||||
CreatureTemplate* TestCreature::_fakeCreatureTemplate = nullptr;
|
||||
bool TestCreature::_fakeTemplateInitialized = false;
|
||||
|
||||
TestCreature::TestCreature() : Creature()
|
||||
{
|
||||
}
|
||||
|
||||
TestCreature::~TestCreature()
|
||||
{
|
||||
CleanupCombatState();
|
||||
}
|
||||
|
||||
void TestCreature::CleanupCombatState()
|
||||
{
|
||||
m_threatManager.ClearAllThreat();
|
||||
m_combatManager.EndAllCombat();
|
||||
// Must remove from world before destruction or ~Object will ABORT
|
||||
SetInWorld(false);
|
||||
}
|
||||
|
||||
void TestCreature::ForceInitValues(ObjectGuid::LowType guidLow, uint32 entry)
|
||||
{
|
||||
Object::_Create(guidLow, entry, HighGuid::Unit);
|
||||
|
||||
m_objectType |= TYPEMASK_UNIT;
|
||||
m_objectTypeId = TYPEID_UNIT;
|
||||
|
||||
m_originalEntry = entry;
|
||||
|
||||
// Initialize the fake creature template once with safe defaults.
|
||||
// Heap-allocated because CreatureMovementData() constructor needs sWorld.
|
||||
if (!_fakeTemplateInitialized)
|
||||
{
|
||||
_fakeCreatureTemplate = new CreatureTemplate();
|
||||
_fakeCreatureTemplate->Entry = 0;
|
||||
_fakeCreatureTemplate->faction = 14; // hostile monster default
|
||||
_fakeCreatureTemplate->unit_class = 1; // CLASS_WARRIOR
|
||||
_fakeCreatureTemplate->speed_walk = 1.0f;
|
||||
_fakeCreatureTemplate->speed_run = 1.14286f;
|
||||
_fakeCreatureTemplate->speed_swim = 1.0f;
|
||||
_fakeCreatureTemplate->speed_flight = 1.0f;
|
||||
_fakeCreatureTemplate->scale = 1.0f;
|
||||
_fakeCreatureTemplate->DamageModifier = 1.0f;
|
||||
_fakeCreatureTemplate->BaseAttackTime = 2000;
|
||||
_fakeCreatureTemplate->RangeAttackTime = 2000;
|
||||
_fakeCreatureTemplate->BaseVariance = 1.0f;
|
||||
_fakeCreatureTemplate->RangeVariance = 1.0f;
|
||||
_fakeCreatureTemplate->ModHealth = 1.0f;
|
||||
_fakeCreatureTemplate->ModMana = 1.0f;
|
||||
_fakeCreatureTemplate->ModArmor = 1.0f;
|
||||
_fakeCreatureTemplate->ModExperience = 1.0f;
|
||||
_fakeCreatureTemplate->HoverHeight = 1.0f;
|
||||
_fakeCreatureTemplate->detection_range = 20.0f;
|
||||
_fakeCreatureTemplate->flags_extra = 0;
|
||||
_fakeCreatureTemplate->unit_flags = 0;
|
||||
_fakeCreatureTemplate->unit_flags2 = 0;
|
||||
_fakeCreatureTemplate->dynamicflags = 0;
|
||||
_fakeCreatureTemplate->type = 0;
|
||||
_fakeCreatureTemplate->type_flags = 0;
|
||||
// Movement is default-constructed by new CreatureTemplate():
|
||||
// Ground=Run, Swim=true -> CanWalk() and CanSwim() return true
|
||||
_fakeTemplateInitialized = true;
|
||||
}
|
||||
|
||||
m_creatureInfo = _fakeCreatureTemplate;
|
||||
}
|
||||
|
||||
void TestCreature::SetTestMap(Map* map)
|
||||
{
|
||||
_testMap = map;
|
||||
// Also set the base class map pointer so GetMap() works
|
||||
// through the Unit* base pointer (polymorphic calls)
|
||||
WorldObject::SetMap(map);
|
||||
}
|
||||
|
||||
void TestCreature::SetAlive(bool alive)
|
||||
{
|
||||
m_deathState = alive ? DeathState::Alive : DeathState::Dead;
|
||||
}
|
||||
|
||||
void TestCreature::SetInWorld(bool inWorld)
|
||||
{
|
||||
if (inWorld && !Object::IsInWorld())
|
||||
Object::AddToWorld();
|
||||
else if (!inWorld && Object::IsInWorld())
|
||||
Object::RemoveFromWorld();
|
||||
}
|
||||
|
||||
void TestCreature::SetPhase(uint32 phase)
|
||||
{
|
||||
SetPhaseMask(phase, false);
|
||||
}
|
||||
|
||||
void TestCreature::SetFaction(uint32 faction)
|
||||
{
|
||||
// Set faction directly, bypassing Unit::SetFaction which calls
|
||||
// UpdateMoveInLineOfSightState() -> sObjectMgr->GetCreatureTemplate()
|
||||
SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE, faction);
|
||||
}
|
||||
|
||||
void TestCreature::SetupForCombatTest(Map* map, ObjectGuid::LowType guidLow, uint32 entry)
|
||||
{
|
||||
ForceInitValues(guidLow, entry);
|
||||
// SetTestMap calls WorldObject::SetMap which asserts !IsInWorld(),
|
||||
// so we must set map BEFORE SetInWorld
|
||||
SetTestMap(map);
|
||||
SetInWorld(true);
|
||||
SetAlive(true);
|
||||
SetPhase(1);
|
||||
SetHostileFaction();
|
||||
SetIsCombatDisallowed(false);
|
||||
ClearUnitState(UNIT_STATE_EVADE);
|
||||
ClearUnitState(UNIT_STATE_IN_FLIGHT);
|
||||
InitializeThreatManager();
|
||||
}
|
||||
|
||||
void TestCreature::InitializeThreatManager()
|
||||
{
|
||||
m_threatManager.Initialize();
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 TEST_CREATURE_H
|
||||
#define TEST_CREATURE_H
|
||||
|
||||
#include "Creature.h"
|
||||
#include "CreatureData.h"
|
||||
#include "ObjectGuid.h"
|
||||
|
||||
class TestMap;
|
||||
|
||||
/**
|
||||
* TestCreature - A test harness for Creature that bypasses database dependencies.
|
||||
*
|
||||
* Usage:
|
||||
* TestCreature* creature = new TestCreature();
|
||||
* creature->ForceInitValues(1, 12345); // guidLow, entry
|
||||
* creature->SetTestMap(testMap);
|
||||
* creature->SetAlive(true);
|
||||
* creature->SetupForCombatTest(); // Sets up all necessary state for combat/threat tests
|
||||
*/
|
||||
class TestCreature : public Creature
|
||||
{
|
||||
public:
|
||||
TestCreature();
|
||||
~TestCreature() override;
|
||||
|
||||
// Override methods that require database/world access
|
||||
void UpdateObjectVisibility(bool /*forced*/ = true, bool /*fromUpdate*/ = false) override { }
|
||||
void AddToWorld() override { }
|
||||
void RemoveFromWorld() override { }
|
||||
|
||||
// Force initialization without database
|
||||
void ForceInitValues(ObjectGuid::LowType guidLow, uint32 entry);
|
||||
|
||||
// Test control methods
|
||||
void SetTestMap(Map* map);
|
||||
|
||||
// Set alive state (affects m_deathState)
|
||||
void SetAlive(bool alive);
|
||||
|
||||
// Set in-world state (affects m_inWorld)
|
||||
void SetInWorld(bool inWorld);
|
||||
|
||||
// Set phase mask for phase checks
|
||||
void SetPhase(uint32 phase);
|
||||
|
||||
// Set faction for friendliness checks
|
||||
// Use hostile factions (14 = hostile monster) for combat tests
|
||||
void SetHostileFaction() { SetFaction(14); }
|
||||
void SetFriendlyFaction() { SetFaction(35); }
|
||||
void SetFaction(uint32 faction);
|
||||
|
||||
// Complete setup for combat/threat testing
|
||||
// Sets creature to be alive, in-world, hostile, and initializes managers
|
||||
void SetupForCombatTest(Map* map, ObjectGuid::LowType guidLow, uint32 entry);
|
||||
|
||||
// Initialize ThreatManager for testing
|
||||
void InitializeThreatManager();
|
||||
|
||||
// Access managers directly for testing
|
||||
ThreatManager& TestGetThreatMgr() { return m_threatManager; }
|
||||
CombatManager& TestGetCombatMgr() { return m_combatManager; }
|
||||
|
||||
// Clear all combat state for cleanup
|
||||
void CleanupCombatState();
|
||||
|
||||
private:
|
||||
Map* _testMap = nullptr;
|
||||
static CreatureTemplate* _fakeCreatureTemplate;
|
||||
static bool _fakeTemplateInitialized;
|
||||
};
|
||||
|
||||
#endif // TEST_CREATURE_H
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 "TestMap.h"
|
||||
#include "DBCStores.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptDefines/AllMapScript.h"
|
||||
#include "ScriptDefines/GlobalScript.h"
|
||||
#include "ScriptDefines/MiscScript.h"
|
||||
#include "ScriptDefines/UnitScript.h"
|
||||
#include "ScriptDefines/WorldObjectScript.h"
|
||||
|
||||
TestMap::TestMap()
|
||||
: Map(0, 0, REGULAR_DIFFICULTY, nullptr)
|
||||
{
|
||||
_fakeMapEntry = {};
|
||||
_fakeMapEntry.map_type = MAP_COMMON;
|
||||
_fakeMapEntry.MapID = 0;
|
||||
const_cast<MapEntry const*&>(i_mapEntry) = &_fakeMapEntry;
|
||||
}
|
||||
|
||||
TestMap::~TestMap()
|
||||
{
|
||||
}
|
||||
|
||||
/*static*/ void TestMap::EnsureDBC()
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (initialized)
|
||||
return;
|
||||
initialized = true;
|
||||
|
||||
// Insert a fake MapEntry so Map constructor doesn't crash
|
||||
if (!sMapStore.LookupEntry(0))
|
||||
{
|
||||
auto* entry = new MapEntry{};
|
||||
entry->MapID = 0;
|
||||
entry->map_type = MAP_COMMON;
|
||||
entry->entrance_map = -1;
|
||||
sMapStore.SetEntry(0, entry);
|
||||
}
|
||||
|
||||
// Initialize all script registries so CALL_ENABLED_HOOKS doesn't
|
||||
// crash on uninitialized vectors during Object/Unit/Map operations
|
||||
ScriptRegistry<AllMapScript>::InitEnabledHooksIfNeeded(ALLMAPHOOK_END);
|
||||
ScriptRegistry<GlobalScript>::InitEnabledHooksIfNeeded(GLOBALHOOK_END);
|
||||
ScriptRegistry<MiscScript>::InitEnabledHooksIfNeeded(MISCHOOK_END);
|
||||
ScriptRegistry<UnitScript>::InitEnabledHooksIfNeeded(UNITHOOK_END);
|
||||
ScriptRegistry<WorldObjectScript>::InitEnabledHooksIfNeeded(WORLDOBJECTHOOK_END);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 TEST_MAP_H
|
||||
#define TEST_MAP_H
|
||||
|
||||
#include "Map.h"
|
||||
#include "DBCStructure.h"
|
||||
|
||||
/**
|
||||
* TestMap - A minimal test harness for Map.
|
||||
*
|
||||
* Since Map has a complex constructor requiring MapEntry from DBC,
|
||||
* this creates a fake MapEntry to control IsRaid()/IsDungeon() behavior.
|
||||
*
|
||||
* Usage:
|
||||
* TestMap* map = new TestMap();
|
||||
* map->SetIsRaid(true); // Make this a raid map
|
||||
*/
|
||||
class TestMap : public Map
|
||||
{
|
||||
public:
|
||||
TestMap();
|
||||
~TestMap() override;
|
||||
|
||||
// Must be called before constructing TestMap to insert fake DBC entry
|
||||
// and initialize script registries
|
||||
static void EnsureDBC();
|
||||
|
||||
// Control map type for testing
|
||||
void SetMapType(uint32 type) { _fakeMapEntry.map_type = type; }
|
||||
void SetIsRaid(bool val) { _fakeMapEntry.map_type = val ? MAP_RAID : MAP_COMMON; }
|
||||
void SetIsDungeon(bool val) { _fakeMapEntry.map_type = val ? MAP_INSTANCE : MAP_COMMON; }
|
||||
|
||||
private:
|
||||
MapEntry _fakeMapEntry;
|
||||
};
|
||||
|
||||
#endif // TEST_MAP_H
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user