diff --git a/data/sql/updates/pending_db_world/rev_1777425238415321551.sql b/data/sql/updates/pending_db_world/rev_1777425238415321551.sql new file mode 100644 index 000000000..2bea3fef6 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1777425238415321551.sql @@ -0,0 +1,3 @@ +-- Missile Barrage: consume on FINISH instead of CAST; its mods (DURATION, +-- ACTIVATION_TIME, COST) are applied inside handle_immediate, after CAST fires. +UPDATE `spell_proc` SET `SpellPhaseMask` = 4 WHERE `SpellId` = 44401; diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index a5f8bcc1d..4609a074d 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -3951,6 +3951,44 @@ void Spell::_cast(bool skipCheck) } } + // CAST -> HIT -> FINISH ordering: fire CAST before handle_immediate so an aura + // applied during HIT (e.g. Arcane Potency from Clearcasting) isn't consumed by + // the same cast. Triggered spells skip this so periodic ticks (Blizzard etc.) + // don't burn cast-charge buffs. + if (m_originalCaster && !IsTriggered()) + { + uint32 procAttacker = m_procAttacker; + if (!procAttacker) + { + bool IsPositive = m_spellInfo->IsPositive(); + if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC) + { + procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; + } + else + { + procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG; + } + } + + uint32 hitMask = PROC_HIT_NORMAL; + + for (std::list::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->missCondition != SPELL_MISS_NONE) + continue; + + if (!ihit->crit) + continue; + + hitMask |= PROC_HIT_CRITICAL; + break; + } + + Unit::ProcSkillsAndAuras(m_originalCaster, nullptr, procAttacker, PROC_FLAG_NONE, hitMask, 1, BASE_ATTACK, m_spellInfo, m_triggeredByAuraSpell.spellInfo, + m_triggeredByAuraSpell.effectIndex, this, nullptr, nullptr, PROC_SPELL_PHASE_CAST); + } + // Okay, everything is prepared. Now we need to distinguish between immediate and evented delayed spells if ((m_spellInfo->Speed > 0.0f && !m_spellInfo->IsChanneled())/* xinef: we dont need this || m_spellInfo->Id == 14157*/) { @@ -4008,46 +4046,6 @@ void Spell::_cast(bool skipCheck) if (modOwner) modOwner->SetSpellModTakingSpell(this, false); - // Handle procs on cast - only for non-triggered spells - // Triggered spells (from auras, items, etc.) should not fire CAST phase procs - // as they are not player-initiated casts. This prevents issues like Arcane Potency - // charges being consumed by periodic damage effects (e.g., Blizzard ticks). - // Must be called AFTER handle_immediate() so spell mods (like Missile Barrage's - // duration reduction) are applied before the aura is consumed by the proc. - if (m_originalCaster && !IsTriggered()) - { - uint32 procAttacker = m_procAttacker; - if (!procAttacker) - { - bool IsPositive = m_spellInfo->IsPositive(); - if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC) - { - procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; - } - else - { - procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG; - } - } - - uint32 hitMask = PROC_HIT_NORMAL; - - for (std::list::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) - { - if (ihit->missCondition != SPELL_MISS_NONE) - continue; - - if (!ihit->crit) - continue; - - hitMask |= PROC_HIT_CRITICAL; - break; - } - - Unit::ProcSkillsAndAuras(m_originalCaster, nullptr, procAttacker, PROC_FLAG_NONE, hitMask, 1, BASE_ATTACK, m_spellInfo, m_triggeredByAuraSpell.spellInfo, - m_triggeredByAuraSpell.effectIndex, this, nullptr, nullptr, PROC_SPELL_PHASE_CAST); - } - if (std::vector const* spell_triggered = sSpellMgr->GetSpellLinked(m_spellInfo->Id)) { for (int32 id : *spell_triggered)