diff --git a/src/server/game/Spells/SpellScript.h b/src/server/game/Spells/SpellScript.h index 0ca94edb2..e13ce56cb 100644 --- a/src/server/game/Spells/SpellScript.h +++ b/src/server/game/Spells/SpellScript.h @@ -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 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 DoCheckEffectProc; diff --git a/src/server/scripts/Outland/TempestKeep/botanica/instance_the_botanica.cpp b/src/server/scripts/Outland/TempestKeep/botanica/instance_the_botanica.cpp index ad0f03273..12d8c8431 100644 --- a/src/server/scripts/Outland/TempestKeep/botanica/instance_the_botanica.cpp +++ b/src/server/scripts/Outland/TempestKeep/botanica/instance_the_botanica.cpp @@ -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); } } diff --git a/src/server/scripts/Spells/spell_mage.cpp b/src/server/scripts/Spells/spell_mage.cpp index b6a5cf812..ef3b49e36 100644 --- a/src/server/scripts/Spells/spell_mage.cpp +++ b/src/server/scripts/Spells/spell_mage.cpp @@ -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(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(nullptr), SPELL_MAGE_COMBUSTION_PROC, true); + }, 1ms); return false; }