Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions sql/updates/world/master/2026_02_23_03_world.sql
Original file line number Diff line number Diff line change
@@ -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');
9 changes: 5 additions & 4 deletions src/server/game/Entities/AreaTrigger/AreaTrigger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 4 additions & 7 deletions src/server/game/Spells/Spell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/server/game/Spells/Spell.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
117 changes: 113 additions & 4 deletions src/server/scripts/Spells/spell_warrior.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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<int32> 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<int32, int32> 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<spell_warr_execute::TriggerArgs>(&GetSpell()->m_customArg);
if (!triggerArgs)
return;

if (triggerArgs->OptionalRageCost <= 0)
return;

pctMod *= (static_cast<float>(triggerArgs->DamageRageSpent - triggerArgs->BaseRageCost) / static_cast<float>(triggerArgs->OptionalRageCost) + 1.0f);
}

void Register() override
Expand All @@ -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<spell_warr_execute::TriggerArgs>(&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
{
Expand Down Expand Up @@ -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);
Expand Down
Loading