From cde7258411a9ca5328ab2404ccf1b9bb809458d9 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 23 Feb 2026 22:38:34 +0100 Subject: [PATCH 1/3] Scripts/Spells: Implement Execute damage and Improved Execute (arms) (#31658) --- .../world/master/2026_02_23_03_world.sql | 9 ++ src/server/scripts/Spells/spell_warrior.cpp | 117 +++++++++++++++++- 2 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 sql/updates/world/master/2026_02_23_03_world.sql diff --git a/sql/updates/world/master/2026_02_23_03_world.sql b/sql/updates/world/master/2026_02_23_03_world.sql new file mode 100644 index 0000000000000..cc72d8dc25162 --- /dev/null +++ b/sql/updates/world/master/2026_02_23_03_world.sql @@ -0,0 +1,9 @@ +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_warr_execute', 'spell_warr_execute_refund_rage'); +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_warr_execute_damage') AND `spell_id` IN (317483); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(163201, 'spell_warr_execute'), +(281000, 'spell_warr_execute'), +(330334, 'spell_warr_execute'), +(317349, 'spell_warr_execute'), +(317483, 'spell_warr_execute_damage'), +(260798, 'spell_warr_execute_refund_rage'); diff --git a/src/server/scripts/Spells/spell_warrior.cpp b/src/server/scripts/Spells/spell_warrior.cpp index 8e762327b96d5..f182286e3441a 100644 --- a/src/server/scripts/Spells/spell_warrior.cpp +++ b/src/server/scripts/Spells/spell_warrior.cpp @@ -53,8 +53,8 @@ enum WarriorSpells SPELL_WARRIOR_COLOSSUS_SMASH_AURA = 208086, SPELL_WARRIOR_CRITICAL_THINKING_ENERGIZE = 392776, SPELL_WARRIOR_DEFT_EXPERIENCE = 383295, - SPELL_WARRIOR_EXECUTE = 20647, SPELL_WARRIOR_ENRAGE = 184362, + SPELL_WARRIOR_EXECUTE = 20647, SPELL_WARRIOR_FRENZIED_ENRAGE = 383848, SPELL_WARRIOR_FRENZY_TALENT = 335077, SPELL_WARRIOR_FRENZY_BUFF = 335082, @@ -69,6 +69,7 @@ enum WarriorSpells SPELL_WARRIOR_HEROIC_LEAP_JUMP = 178368, SPELL_WARRIOR_HEROIC_LEAP_DAMAGE = 52174, SPELL_WARRIOR_IGNORE_PAIN = 190456, + SPELL_WARRIOR_IMPROVED_EXECUTE_ARMS = 316405, SPELL_WARRIOR_IMPROVED_RAGING_BLOW = 383854, SPELL_WARRIOR_IMPROVED_WHIRLWIND = 12950, SPELL_WARRIOR_INTERVENE_CHARGE = 316531, @@ -699,13 +700,91 @@ class spell_warr_enrage_proc : public AuraScript } }; +// 163201 - Execute (Arms, Protection) +// 281000 - Massacre +// 330334 - Condemn (Venthyr) (Arms) +// 317349 - Condemn (Venthyr) (Protection) +class spell_warr_execute : public SpellScript +{ + bool Validate(SpellInfo const* spellInfo) override + { + return ValidateSpellEffect({ + { SPELL_WARRIOR_SUDDEN_DEATH, EFFECT_0 }, + { spellInfo->Id, EFFECT_0 } + }) && ValidateSpellInfo({ + SPELL_WARRIOR_SUDDEN_DEATH_BUFF, + spellInfo->GetEffect(EFFECT_0).TriggerSpell + }); + } + + Optional GetSuddenDeathRageCost() const + { + if (Aura* suddenDeathAura = GetCaster()->GetAura(SPELL_WARRIOR_SUDDEN_DEATH_BUFF)) + if (GetSpell()->m_appliedMods.contains(suddenDeathAura)) + if (AuraEffect const* suddenDeathTalentAura = GetCaster()->GetAuraEffect(SPELL_WARRIOR_SUDDEN_DEATH, EFFECT_0)) + return suddenDeathTalentAura->GetAmount() * 10; + + return {}; + } + + std::pair GetRageCost() const + { + for (SpellPowerEntry const* powerCost : GetSpellInfo()->PowerCosts) + if (powerCost->PowerType == POWER_RAGE) + return { powerCost->ManaCost, powerCost->OptionalCost }; + + return { 0, 0 }; + } + + void HandleExecuteCast(SpellEffIndex spellEffIndex) + { + PreventHitDefaultEffect(spellEffIndex); + + // Can't use SpellInfo::CalcPowerCost here since Sudden Death modifies the costs so it's free. + auto [baseCost, optionalCost] = GetRageCost(); + int32 rageSpent = GetSpell()->GetPowerTypeCostAmount(POWER_RAGE).value_or(0); + + GetCaster()->CastSpell(GetHitUnit(), GetEffectInfo(spellEffIndex).TriggerSpell, CastSpellExtraArgsInit{ + .TriggerFlags = TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR, + .TriggeringSpell = GetSpell(), + .CustomArg = TriggerArgs{ + .BaseRageCost = baseCost, + .OptionalRageCost = optionalCost, + .DamageRageSpent = GetSuddenDeathRageCost().value_or(rageSpent), + .RefundRage = CalculatePct(rageSpent, GetEffectInfo(EFFECT_1).CalcValue()) + } + }); + } + + void Register() override + { + OnEffectLaunchTarget += SpellEffectFn(spell_warr_execute::HandleExecuteCast, EFFECT_0, SPELL_EFFECT_TRIGGER_SPELL); + } + +public: + struct TriggerArgs + { + int32 BaseRageCost; + int32 OptionalRageCost; + int32 DamageRageSpent; + int32 RefundRage; + }; +}; + // 260798 - Execute (Arms, Protection) +// 317483 - Condemn (Venthyr) (Arms, Protection) class spell_warr_execute_damage : public SpellScript { - static void CalculateExecuteDamage(SpellScript const&, SpellEffectInfo const& /*spellEffectInfo*/, Unit const* /*victim*/, int32 const& /*damageOrHealing*/, int32 const& /*flatMod*/, float& pctMod) + void CalculateExecuteDamage(SpellEffectInfo const& /*spellEffectInfo*/, Unit const* /*victim*/, int32 const& /*damageOrHealing*/, int32 const& /*flatMod*/, float& pctMod) const { - // tooltip has 2 multiplier hardcoded in it $damage=${2.0*$260798s1} - pctMod *= 2.0f; + spell_warr_execute::TriggerArgs const* triggerArgs = std::any_cast(&GetSpell()->m_customArg); + if (!triggerArgs) + return; + + if (triggerArgs->OptionalRageCost <= 0) + return; + + pctMod *= (static_cast(triggerArgs->DamageRageSpent - triggerArgs->BaseRageCost) / static_cast(triggerArgs->OptionalRageCost) + 1.0f); } void Register() override @@ -714,6 +793,34 @@ class spell_warr_execute_damage : public SpellScript } }; +// 260798 - Execute -> 316405 (Improved Execute (Arms)) +class spell_warr_execute_refund_rage : public SpellScript +{ + bool Load() override + { + return GetCaster()->HasAura(SPELL_WARRIOR_IMPROVED_EXECUTE_ARMS); + } + + void DetermineKillStatus(DamageInfo const& damageInfo, uint32& /*resistAmount*/, int32& /*absorbAmount*/) const + { + bool killed = damageInfo.GetDamage() >= damageInfo.GetVictim()->GetHealth(); + if (killed) + return; + + spell_warr_execute::TriggerArgs const* triggerArgs = std::any_cast(&GetSpell()->m_customArg); + if (!triggerArgs) + return; + + GetCaster()->ModifyPower(POWER_RAGE, triggerArgs->RefundRage, false); + } + + void Register() override + { + // abuse OnCalculateResistAbsorb to determine if this spell will kill target or not (its still not perfect - happens before absorbs are applied) + OnCalculateResistAbsorb += SpellOnResistAbsorbCalculateFn(spell_warr_execute_refund_rage::DetermineKillStatus); + } +}; + // 383848 - Frenzied Enrage (attached to 184362 - Enrage) class spell_warr_frenzied_enrage : public SpellScript { @@ -1850,7 +1957,9 @@ void AddSC_warrior_spell_scripts() RegisterSpellScript(spell_warr_deft_experience); RegisterSpellScript(spell_warr_devastator); RegisterSpellScript(spell_warr_enrage_proc); + RegisterSpellScript(spell_warr_execute); RegisterSpellScript(spell_warr_execute_damage); + RegisterSpellScript(spell_warr_execute_refund_rage); RegisterSpellScript(spell_warr_frenzied_enrage); RegisterSpellScript(spell_warr_frothing_berserker); RegisterSpellScript(spell_warr_fueled_by_violence); From 4a3cb9c76b45a9e28f6cb4743480843960268e69 Mon Sep 17 00:00:00 2001 From: Shauren Date: Mon, 23 Feb 2026 23:21:55 +0100 Subject: [PATCH 2/3] Core/Spells: Update CAST_FLAG_NO_GCD flag implementation --- src/server/game/Spells/Spell.cpp | 11 ++++------- src/server/game/Spells/Spell.h | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 342d3fa0103f9..f9bbc29be5144 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -4793,8 +4793,8 @@ void Spell::SendSpellStart() && std::ranges::any_of(m_powerCost, [](SpellPowerCost const& cost) { return cost.Power != POWER_HEALTH; })) castFlags |= CAST_FLAG_POWER_LEFT_SELF; - if (HasPowerTypeCost(POWER_RUNES)) - castFlags |= CAST_FLAG_NO_GCD; // not needed, but Blizzard sends it + if (m_fromClient) + castFlags |= CAST_FLAG_FROM_CLIENT; if (m_spellInfo->HasAttribute(SPELL_ATTR8_HEAL_PREDICTION) && m_casttime && m_caster->IsUnit()) castFlags |= CAST_FLAG_HEAL_PREDICTION; @@ -4896,16 +4896,13 @@ void Spell::SendSpellGo() && (m_caster->ToPlayer()->GetClass() == CLASS_DEATH_KNIGHT) && HasPowerTypeCost(POWER_RUNES) && !(_triggeredCastFlags & TRIGGERED_IGNORE_POWER_COST)) - { - castFlags |= CAST_FLAG_NO_GCD; // not needed, but it's being sent according to sniffs castFlags |= CAST_FLAG_RUNE_LIST; // rune cooldowns list - } if (m_targets.HasTraj()) castFlags |= CAST_FLAG_ADJUST_MISSILE; - if (!m_spellInfo->StartRecoveryTime) - castFlags |= CAST_FLAG_NO_GCD; + if (m_fromClient) + castFlags |= CAST_FLAG_FROM_CLIENT; WorldPackets::Spells::SpellGo packet; WorldPackets::Spells::SpellCastData& castData = packet.Cast; diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index d902252d17fb9..a600f48b0bbaa 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -109,7 +109,7 @@ enum SpellCastFlags : uint32 CAST_FLAG_UNKNOWN_16 = 0x00008000, CAST_FLAG_UNKNOWN_17 = 0x00010000, CAST_FLAG_ADJUST_MISSILE = 0x00020000, - CAST_FLAG_NO_GCD = 0x00040000, // no GCD for spell casts from charm/summon (vehicle spells is an example) + CAST_FLAG_FROM_CLIENT = 0x00040000, // no GCD for spell casts from charm/summon (vehicle spells is an example) CAST_FLAG_VISUAL_CHAIN = 0x00080000, CAST_FLAG_UNKNOWN_21 = 0x00100000, CAST_FLAG_RUNE_LIST = 0x00200000, From d649427edc057011562b61c814d912f756745ff6 Mon Sep 17 00:00:00 2001 From: Shauren Date: Tue, 24 Feb 2026 00:23:21 +0100 Subject: [PATCH 3/3] Core/AreaTriggers: Fixed crashes caused by areatrigger OnUnitEnter/OnUnitExit scripts teleporting players to other maps --- src/server/game/Entities/AreaTrigger/AreaTrigger.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/server/game/Entities/AreaTrigger/AreaTrigger.cpp b/src/server/game/Entities/AreaTrigger/AreaTrigger.cpp index 082bdc840caec..ad447d792fbcf 100644 --- a/src/server/game/Entities/AreaTrigger/AreaTrigger.cpp +++ b/src/server/game/Entities/AreaTrigger/AreaTrigger.cpp @@ -895,8 +895,8 @@ void AreaTrigger::HandleUnitEnter(Unit* unit) _ai->OnUnitEnter(unit); - // OnUnitEnter script can despawn this areatrigger - if (!IsInWorld()) + // OnUnitEnter script can despawn this areatrigger or teleport player to a different map + if (!IsInWorld() || !IsInMap(unit)) return; // Register areatrigger in Unit after actions/scripts to allow them to determine @@ -925,10 +925,11 @@ void AreaTrigger::HandleUnitExitInternal(Unit* unit, AreaTriggerExitReason exitM UndoActions(unit); + // OnUnitExit script can teleport player to another map, causing it to attempt to exit the areatrigger again (from Unit::ExitAllAreaTriggers) + unit->ExitAreaTrigger(this); + if (canTriggerOnExit) _ai->OnUnitExit(unit, exitMode); - - unit->ExitAreaTrigger(this); } void AreaTrigger::HandleUnitExit(Unit* unit)