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:
1 parent
a70d85e565
commit
0d1d468c88
5 files changed
+37
-2
No files matched your search
@@ -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<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_charmer(nullptr), m_charmed(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,
|
||||
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*
|
||||
{
|
||||
|
||||
@@ -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<DynamicObject*> DynObjectList;
|
||||
DynObjectList m_dynObj;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<PathGenerator> m_preGeneratedPath;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user