diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index e035f9dbaa..a69041e816 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -301,7 +301,7 @@ bool DispelableAura::RollDispel() const Unit::Unit(bool isWorldObject) : WorldObject(isWorldObject), m_lastSanctuaryTime(0), LastCharmerGUID(), m_ControlledByPlayer(false), movespline(std::make_unique()), - m_procDeep(0), m_removedAurasCount(0), + m_procDeep(0), m_procChainLength(0), m_removedAurasCount(0), m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None), m_charmer(nullptr), m_charmed(nullptr), i_motionMaster(std::make_unique(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, 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; SpellInfo const* spellInfo = [&]() -> SpellInfo const* { diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index b573f0af1e..ec1762e034 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1560,6 +1560,7 @@ class TC_GAME_API Unit : public WorldObject // proc trigger system bool CanProc() const { return !m_procDeep; } void SetCantProc(bool apply); + int32 GetProcChainLength() const { return m_procChainLength; } uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const; uint32 GetModelForTotem(PlayerTotemType totemType); @@ -1671,7 +1672,9 @@ class TC_GAME_API Unit : public WorldObject 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 DynObjectList; DynObjectList m_dynObj; diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 11b8dd6b6d..07207e0904 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -656,6 +656,7 @@ m_caster((info->HasAttribute(SPELL_ATTR6_ORIGINATE_FROM_CONTROLLER) && caster->G m_cast_count = 0; m_glyphIndex = 0; m_triggeredByAuraSpell = nullptr; + m_procChainLength = caster->IsUnit() ? caster->ToUnit()->GetProcChainLength() : 0; unitCaster = nullptr; _spellAura = nullptr; _dynObjAura = nullptr; diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 0ae856b3da..e4ae08d122 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -454,6 +454,8 @@ class TC_GAME_API Spell bool IsTriggeredByAura(SpellInfo const* auraSpellInfo) const { return (auraSpellInfo == m_triggeredByAuraSpell); } + int32 GetProcChainLength() const { return m_procChainLength; } + bool IsDeletable() const { return !m_referencedFromCurrentSpell && !m_executedCurrently; } void SetReferencedFromCurrent(bool yes) { m_referencedFromCurrentSpell = yes; } 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 // and in same time need aura data and after aura deleting. SpellInfo const* m_triggeredByAuraSpell; + int32 m_procChainLength; uint8 m_auraScaleMask; std::unique_ptr m_preGeneratedPath; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 05d047018c..56029fdaec 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -1856,6 +1856,7 @@ void SpellMgr::LoadSpellProcs() procEntry.SpellPhaseMask = PROC_SPELL_PHASE_HIT; procEntry.HitMask = PROC_HIT_NONE; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent + bool triggersSpell = false; for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (!spellInfo->Effects[i].IsAura()) @@ -1881,6 +1882,10 @@ void SpellMgr::LoadSpellProcs() if (spellInfo->Effects[i].CalcValue() <= -100) procEntry.HitMask = PROC_HIT_MISS; break; + case SPELL_AURA_PROC_TRIGGER_SPELL: + case SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE: + triggersSpell = spellInfo->Effects[i].TriggerSpell != 0; + break; default: continue; } @@ -1899,6 +1904,22 @@ void SpellMgr::LoadSpellProcs() procEntry.Cooldown = Milliseconds::zero(); 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; ++count; }