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:
blinkysc
2026-03-18 13:36:59 -05:00
committed by GitHub
co-authored by blinkysc Treeston killerwife Claude Opus 4.6
parent c80a0f1fad
commit 984baa92dd
101 changed files with 7045 additions and 2659 deletions
+1 -1
View File
@@ -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);
}
+5
View File
@@ -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 {}
+2 -2
View File
@@ -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;
}
+66 -11
View File
@@ -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)
{
+11 -8
View File
@@ -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);
};
+96 -24
View File
@@ -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;
}
+24 -4
View File
@@ -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)
+28 -4
View File
@@ -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);
+19 -25
View File
@@ -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);
+460
View File
@@ -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);
}
+167
View File
@@ -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
-250
View File
@@ -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;
}
}
-74
View File
@@ -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
+947
View File
@@ -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;
}
}
}
+329
View File
@@ -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
-733
View File
@@ -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);
}
-335
View File
@@ -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
-134
View File
@@ -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
+99 -64
View File
@@ -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
+7 -1
View File
@@ -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);
}
}
}
+1
View File
@@ -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;
}
+1 -9
View File
@@ -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);
+157 -465
View File
@@ -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();
}
/**
+33 -34
View File
@@ -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;
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
+23 -37
View File
@@ -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;
+22 -35
View File
@@ -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*/)
+5
View File
@@ -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)
+1
View File
@@ -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;
+180 -33
View File
@@ -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;
}
+1 -1
View File
@@ -2904,7 +2904,7 @@ public:
}
playerTarget->CombatStop();
playerTarget->getHostileRefMgr().deleteReferences();
playerTarget->GetThreatMgr().RemoveMeFromThreatLists();
return true;
}
+1 -1
View File
@@ -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";
@@ -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());
}
}
}
@@ -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();
}
@@ -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);
@@ -143,7 +143,6 @@ public:
void Reset() override
{
me->SetReactState(REACT_PASSIVE);
me->getHostileRefMgr().setOnlineOfflineState(false);
}
void OnCharmed(bool apply) override
@@ -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();
}
}
@@ -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
}
+1 -1
View File
@@ -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
+3 -5
View File
@@ -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();
+15
View File
@@ -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);
}
+4 -3
View File
@@ -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
+2 -3
View File
@@ -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);
}
}
}
+45 -29
View File
@@ -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);
+1 -1
View File
@@ -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 },
+62 -100
View File
@@ -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();
+141
View File
@@ -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();
}
+87
View File
@@ -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
+62
View File
@@ -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);
}
+51
View File
@@ -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