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) :
|
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*
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user