fix(Scripts/Spells): defer aura mutations out of proc-check handlers (#26644)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Andrew
2026-07-15 19:26:35 -03:00
committed by GitHub
parent a23dd1d385
commit fd04d94759
3 changed files with 30 additions and 6 deletions

View File

@@ -823,12 +823,14 @@ public:
#define AuraEffectSplitFn(F, I) EffectSplitFunction(&F, I)
// executed when aura checks if it can proc
// do not use this hook for triggering spellcasts/removing auras etc - runs mid proc-iteration and may be unsafe
// example: DoCheckProc += AuraCheckProcFn(class::function);
// where function is: bool function (ProcEventInfo& eventInfo);
HookList<CheckProcHandler> DoCheckProc;
#define AuraCheckProcFn(F) CheckProcHandlerFunction(&F)
// executed when aura effect checks if it can proc the aura
// do not use this hook for triggering spellcasts/removing auras etc - runs mid proc-iteration and may be unsafe
// example: DoCheckEffectProc += AuraCheckEffectProcFn(class::function, EffectIndexSpecifier, EffectAuraNameSpecifier);
// where function is: bool function (AuraEffect const* aurEff, ProcEventInfo& eventInfo);
HookList<CheckEffectProcHandler> DoCheckEffectProc;

View File

@@ -127,9 +127,19 @@ class spell_botanica_shift_form_aura : public AuraScript
{
_swapTime = GameTime::GetGameTime().count() + 6;
_lastSchool = spellInfo->GetSchoolMask();
GetUnitOwner()->RemoveAurasDueToSpell(_lastForm);
// Runs from Aura::GetProcEffectMask while the proc engine iterates the owner's
// applied-aura map. Swapping forms mutates that map (RemoveAura + CastSpell),
// which invalidates the live iterator now that aura containers are flat_multimaps.
// Defer the swap to the owner's event queue so it runs outside the proc walk.
Unit* owner = GetUnitOwner();
uint32 const oldForm = _lastForm;
_lastForm = form;
GetUnitOwner()->CastSpell(GetUnitOwner(), _lastForm, true);
owner->m_Events.AddEventAtOffset([owner, oldForm, form]()
{
owner->RemoveAurasDueToSpell(oldForm);
owner->CastSpell(owner, form, true);
}, 1ms);
}
}

View File

@@ -123,16 +123,21 @@ class spell_mage_burning_determination : public AuraScript
if (!(eventInfo.GetSpellInfo()->GetAllEffectsMechanicMask() & ((1ULL << MECHANIC_INTERRUPT) | (1ULL << MECHANIC_SILENCE))))
return false;
Unit* target = eventInfo.GetActionTarget();
// This hook runs while the proc engine iterates the target's aura map; removing an
// aura inline would invalidate the live iterator (aura containers are flat_multimaps).
// Defer removals to the target's event queue so they run outside the proc walk.
// Xinef: immuned effect should just eat charge
if (eventInfo.GetHitMask() & PROC_EX_IMMUNE)
{
eventInfo.GetActionTarget()->RemoveAurasDueToSpell(54748);
target->m_Events.AddEventAtOffset([target]() { target->RemoveAurasDueToSpell(54748); }, 1ms);
return false;
}
if (Aura* aura = eventInfo.GetActionTarget()->GetAura(54748))
if (Aura* aura = target->GetAura(54748))
{
if (aura->GetDuration() < aura->GetMaxDuration())
eventInfo.GetActionTarget()->RemoveAurasDueToSpell(54748);
target->m_Events.AddEventAtOffset([target]() { target->RemoveAurasDueToSpell(54748); }, 1ms);
return false;
}
@@ -1045,7 +1050,14 @@ class spell_mage_combustion : public AuraScript
// Do not take charges, add a stack of crit buff
if (!(eventInfo.GetHitMask() & PROC_HIT_CRITICAL))
{
eventInfo.GetActor()->CastSpell(static_cast<Unit*>(nullptr), SPELL_MAGE_COMBUSTION_PROC, true);
// Applying the stack mutates the actor's aura map while the proc engine iterates
// it (this hook runs from Aura::GetProcEffectMask); defer it so the insert can't
// invalidate the live iterator (aura containers are flat_multimaps).
Unit* actor = eventInfo.GetActor();
actor->m_Events.AddEventAtOffset([actor]()
{
actor->CastSpell(static_cast<Unit*>(nullptr), SPELL_MAGE_COMBUSTION_PROC, true);
}, 1ms);
return false;
}