Core/Spells: Fully prevent infinite proc loops and add logging to detect most spells that could possibly trigger this behavior at startup

This commit is contained in:
Shauren authored and Ovahlord committed 2025-08-03 14:28:23 +02:00
1 parent a70d85e565
commit 0d1d468c88
5 files changed
+37 -2

No files matched your search

+8 -1
View File
@@ -301,7 +301,7 @@ bool DispelableAura::RollDispel() const
Unit::Unit(bool isWorldObject) : Unit::Unit(bool isWorldObject) :
WorldObject(isWorldObject), m_lastSanctuaryTime(0), WorldObject(isWorldObject), m_lastSanctuaryTime(0),
LastCharmerGUID(), m_ControlledByPlayer(false), movespline(std::make_unique<Movement::MoveSpline>()), LastCharmerGUID(), m_ControlledByPlayer(false), movespline(std::make_unique<Movement::MoveSpline>()),
m_procDeep(0), m_removedAurasCount(0), m_procDeep(0), m_procChainLength(0), m_removedAurasCount(0),
m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None), m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None),
m_charmer(nullptr), m_charmed(nullptr), m_charmer(nullptr), m_charmed(nullptr),
i_motionMaster(std::make_unique<MotionMaster>(this)), m_vehicle(nullptr), i_motionMaster(std::make_unique<MotionMaster>(this)), m_vehicle(nullptr),
@@ -5108,6 +5108,13 @@ void Unit::SendSpellNonMeleeDamageLog(Unit* target, uint32 spellID, uint32 damag
/*static*/ void Unit::ProcSkillsAndAuras(Unit* actor, Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, /*static*/ void Unit::ProcSkillsAndAuras(Unit* actor, Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget,
ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo)
{ {
static constexpr int32 ProcChainHardLimit = 10;
if (spell && spell->GetProcChainLength() >= ProcChainHardLimit)
{
TC_LOG_ERROR("spells.aura.effect", "Unit::ProcSkillsAndAuras: Possible infinite proc loop detected, current triggering spell %u", spell->GetSpellInfo()->Id);
return;
}
WeaponAttackType attType = damageInfo ? damageInfo->GetAttackType() : BASE_ATTACK; WeaponAttackType attType = damageInfo ? damageInfo->GetAttackType() : BASE_ATTACK;
SpellInfo const* spellInfo = [&]() -> SpellInfo const* SpellInfo const* spellInfo = [&]() -> SpellInfo const*
{ {
+4 -1
View File
@@ -1560,6 +1560,7 @@ class TC_GAME_API Unit : public WorldObject
// proc trigger system // proc trigger system
bool CanProc() const { return !m_procDeep; } bool CanProc() const { return !m_procDeep; }
void SetCantProc(bool apply); void SetCantProc(bool apply);
int32 GetProcChainLength() const { return m_procChainLength; }
uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const; uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const;
uint32 GetModelForTotem(PlayerTotemType totemType); uint32 GetModelForTotem(PlayerTotemType totemType);
@@ -1671,7 +1672,9 @@ class TC_GAME_API Unit : public WorldObject
DeathState m_deathState; DeathState m_deathState;
int32 m_procDeep; int32 m_procDeep; // tracked for proc system correctness (what spells should proc what)
int32 m_procChainLength; // tracked to protect against infinite proc loops (hard limit, will disallow procs even if they should happen)
typedef std::list<DynamicObject*> DynObjectList; typedef std::list<DynamicObject*> DynObjectList;
DynObjectList m_dynObj; DynObjectList m_dynObj;
+1
View File
@@ -656,6 +656,7 @@ m_caster((info->HasAttribute(SPELL_ATTR6_ORIGINATE_FROM_CONTROLLER) && caster->G
m_cast_count = 0; m_cast_count = 0;
m_glyphIndex = 0; m_glyphIndex = 0;
m_triggeredByAuraSpell = nullptr; m_triggeredByAuraSpell = nullptr;
m_procChainLength = caster->IsUnit() ? caster->ToUnit()->GetProcChainLength() : 0;
unitCaster = nullptr; unitCaster = nullptr;
_spellAura = nullptr; _spellAura = nullptr;
_dynObjAura = nullptr; _dynObjAura = nullptr;
+3
View File
@@ -454,6 +454,8 @@ class TC_GAME_API Spell
bool IsTriggeredByAura(SpellInfo const* auraSpellInfo) const { return (auraSpellInfo == m_triggeredByAuraSpell); } bool IsTriggeredByAura(SpellInfo const* auraSpellInfo) const { return (auraSpellInfo == m_triggeredByAuraSpell); }
int32 GetProcChainLength() const { return m_procChainLength; }
bool IsDeletable() const { return !m_referencedFromCurrentSpell && !m_executedCurrently; } bool IsDeletable() const { return !m_referencedFromCurrentSpell && !m_executedCurrently; }
void SetReferencedFromCurrent(bool yes) { m_referencedFromCurrentSpell = yes; } void SetReferencedFromCurrent(bool yes) { m_referencedFromCurrentSpell = yes; }
bool IsInterruptable() const { return !m_executedCurrently; } bool IsInterruptable() const { return !m_executedCurrently; }
@@ -725,6 +727,7 @@ class TC_GAME_API Spell
// we can't store original aura link to prevent access to deleted auras // we can't store original aura link to prevent access to deleted auras
// and in same time need aura data and after aura deleting. // and in same time need aura data and after aura deleting.
SpellInfo const* m_triggeredByAuraSpell; SpellInfo const* m_triggeredByAuraSpell;
int32 m_procChainLength;
uint8 m_auraScaleMask; uint8 m_auraScaleMask;
std::unique_ptr<PathGenerator> m_preGeneratedPath; std::unique_ptr<PathGenerator> m_preGeneratedPath;
+21
View File
@@ -1856,6 +1856,7 @@ void SpellMgr::LoadSpellProcs()
procEntry.SpellPhaseMask = PROC_SPELL_PHASE_HIT; procEntry.SpellPhaseMask = PROC_SPELL_PHASE_HIT;
procEntry.HitMask = PROC_HIT_NONE; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent procEntry.HitMask = PROC_HIT_NONE; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent
bool triggersSpell = false;
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{ {
if (!spellInfo->Effects[i].IsAura()) if (!spellInfo->Effects[i].IsAura())
@@ -1881,6 +1882,10 @@ void SpellMgr::LoadSpellProcs()
if (spellInfo->Effects[i].CalcValue() <= -100) if (spellInfo->Effects[i].CalcValue() <= -100)
procEntry.HitMask = PROC_HIT_MISS; procEntry.HitMask = PROC_HIT_MISS;
break; break;
case SPELL_AURA_PROC_TRIGGER_SPELL:
case SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE:
triggersSpell = spellInfo->Effects[i].TriggerSpell != 0;
break;
default: default:
continue; continue;
} }
@@ -1899,6 +1904,22 @@ void SpellMgr::LoadSpellProcs()
procEntry.Cooldown = Milliseconds::zero(); procEntry.Cooldown = Milliseconds::zero();
procEntry.Charges = spellInfo->ProcCharges; procEntry.Charges = spellInfo->ProcCharges;
if (spellInfo->HasAttribute(SPELL_ATTR3_CAN_PROC_FROM_PROCS) && !procEntry.SpellFamilyMask
&& procEntry.Chance >= 100
&& procEntry.ProcsPerMinute <= 0.0f
&& procEntry.Cooldown <= 0ms
&& procEntry.Charges <= 0
&& procEntry.ProcFlags & (PROC_FLAG_DEAL_MELEE_ABILITY | PROC_FLAG_DEAL_RANGED_ATTACK | PROC_FLAG_DEAL_RANGED_ABILITY | PROC_FLAG_DEAL_HELPFUL_ABILITY
| PROC_FLAG_DEAL_HARMFUL_ABILITY | PROC_FLAG_DEAL_HELPFUL_SPELL | PROC_FLAG_DEAL_HARMFUL_SPELL | PROC_FLAG_MAIN_HAND_WEAPON_SWING
| PROC_FLAG_OFF_HAND_WEAPON_SWING)
&& triggersSpell)
{
TC_LOG_ERROR("sql.sql", "Spell Id %u has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction on what spells can cause it to proc and no cooldown. "
"This spell can cause infinite proc loops. Proc data for this spell was not generated, data in `spell_proc` table is required for it to function!",
spellInfo->Id);
continue;
}
mSpellProcMap[spellInfo->Id] = procEntry; mSpellProcMap[spellInfo->Id] = procEntry;
++count; ++count;
} }