fix(Core/Spells): Fire CAST proc before HIT/FINISH for instant spells (#25647)

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: Shauren <shauren.trinity@gmail.com>
Co-authored-by: sudlud <sudlud@users.noreply.github.com>
This commit is contained in:
blinkysc
2026-07-14 20:30:33 -04:00
committed by GitHub
parent 82d44f885b
commit 2a544a5d6d
2 changed files with 41 additions and 40 deletions

View File

@@ -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;

View File

@@ -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<TargetInfo>::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<TargetInfo>::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<int32> const* spell_triggered = sSpellMgr->GetSpellLinked(m_spellInfo->Id))
{
for (int32 id : *spell_triggered)