fix(Core/Spells): Fix Rapid Recuperation, Rapid Killing, and auto-generate PROC_ATTR_REQ_SPELLMOD (#24830)

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
This commit is contained in:
blinkysc
2026-02-23 12:37:37 -06:00
committed by GitHub
parent 57119764a7
commit 4277ac0b26
5 changed files with 183 additions and 41 deletions
@@ -0,0 +1,9 @@
-- Fix spell_script_names for spell_hun_rapid_recuperation
-- Script was moved from talent (53228/53232) to periodic mana aura (56654/58882)
DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_hun_rapid_recuperation';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(56654, 'spell_hun_rapid_recuperation'),
(58882, 'spell_hun_rapid_recuperation');
-- Remove explicit Inner Focus spell_proc entry (now auto-generated with PROC_ATTR_REQ_SPELLMOD)
DELETE FROM `spell_proc` WHERE `SpellId` = 14751;
@@ -6563,12 +6563,6 @@ void AuraEffect::HandlePeriodicTriggerSpellAuraTick(Unit* target, Unit* caster)
caster->CastSpell(target, triggerSpellId, false); caster->CastSpell(target, triggerSpellId, false);
return; return;
} }
// Hunter - Rapid Recuperation
case 56654:
case 58882:
int32 amount = int32(target->GetMaxPower(POWER_MANA) * GetAmount() / 100.0f);
target->CastCustomSpell(target, triggerSpellId, &amount, nullptr, nullptr, true, nullptr, this);
return;
} }
} }
+14
View File
@@ -2073,6 +2073,20 @@ void SpellMgr::LoadSpellProcs()
if (addTriggerFlag) if (addTriggerFlag)
procEntry.AttributesMask |= PROC_ATTR_TRIGGERED_CAN_PROC; procEntry.AttributesMask |= PROC_ATTR_TRIGGERED_CAN_PROC;
// Modifier auras with charges should require spellmod validation
if (spellInfo->ProcCharges)
{
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (spellInfo->Effects[i].IsAura(SPELL_AURA_ADD_FLAT_MODIFIER) ||
spellInfo->Effects[i].IsAura(SPELL_AURA_ADD_PCT_MODIFIER))
{
procEntry.AttributesMask |= PROC_ATTR_REQ_SPELLMOD;
break;
}
}
}
// Calculate DisableEffectsMask for effects that shouldn't trigger procs // Calculate DisableEffectsMask for effects that shouldn't trigger procs
uint32 nonProcMask = 0; uint32 nonProcMask = 0;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+14 -35
View File
@@ -73,8 +73,6 @@ enum HunterSpells
// Proc system spells // Proc system spells
SPELL_HUNTER_THRILL_OF_THE_HUNT_MANA = 34720, SPELL_HUNTER_THRILL_OF_THE_HUNT_MANA = 34720,
SPELL_HUNTER_REPLENISHMENT = 57669, SPELL_HUNTER_REPLENISHMENT = 57669,
SPELL_HUNTER_RAPID_RECUPERATION_R1 = 56654,
SPELL_HUNTER_RAPID_RECUPERATION_R2 = 58882,
SPELL_HUNTER_GLYPH_OF_MEND_PET_HAPPINESS = 57894, SPELL_HUNTER_GLYPH_OF_MEND_PET_HAPPINESS = 57894,
SPELL_HUNTER_KILL_COMMAND_HUNTER = 34027, SPELL_HUNTER_KILL_COMMAND_HUNTER = 34027,
SPELL_HUNTER_RAPID_RECUPERATION_MANA_R1 = 56654, SPELL_HUNTER_RAPID_RECUPERATION_MANA_R1 = 56654,
@@ -1403,48 +1401,29 @@ class spell_hun_hunting_party : public AuraScript
} }
}; };
// -53228 - Rapid Recuperation // 56654, 58882 - Rapid Recuperation
class spell_hun_rapid_recuperation : public AuraScript class spell_hun_rapid_recuperation : public AuraScript
{ {
PrepareAuraScript(spell_hun_rapid_recuperation); PrepareAuraScript(spell_hun_rapid_recuperation);
bool Validate(SpellInfo const* /*spellInfo*/) override bool Validate(SpellInfo const* spellInfo) override
{ {
return ValidateSpellInfo({ SPELL_HUNTER_RAPID_RECUPERATION_R1, SPELL_HUNTER_RAPID_RECUPERATION_R2 }); return ValidateSpellInfo({ spellInfo->Effects[EFFECT_0].TriggerSpell });
} }
bool CheckProc(ProcEventInfo& eventInfo) void HandlePeriodic(AuraEffect const* aurEff)
{
SpellInfo const* procSpell = eventInfo.GetSpellInfo();
if (!procSpell)
return false;
// This effect only from Rapid Killing (mana regen)
return (procSpell->SpellFamilyFlags[1] & 0x01000000) != 0;
}
void HandleProc(ProcEventInfo& /*eventInfo*/)
{ {
PreventDefaultAction(); PreventDefaultAction();
uint32 triggeredSpell = 0;
switch (GetSpellInfo()->Id) Unit* target = GetTarget();
{ uint32 triggerSpellId = GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell;
case 53228: // Rank 1 int32 amount = CalculatePct(static_cast<int32>(target->GetMaxPower(POWER_MANA)), aurEff->GetAmount());
triggeredSpell = SPELL_HUNTER_RAPID_RECUPERATION_R1; target->CastCustomSpell(target, triggerSpellId, &amount, nullptr, nullptr, true, nullptr, aurEff);
break;
case 53232: // Rank 2
triggeredSpell = SPELL_HUNTER_RAPID_RECUPERATION_R2;
break;
default:
return;
}
GetTarget()->CastSpell(GetTarget(), triggeredSpell, true);
} }
void Register() override void Register() override
{ {
DoCheckProc += AuraCheckProcFn(spell_hun_rapid_recuperation::CheckProc); OnEffectPeriodic += AuraEffectPeriodicFn(spell_hun_rapid_recuperation::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
OnProc += AuraProcFn(spell_hun_rapid_recuperation::HandleProc);
} }
}; };
@@ -1522,7 +1501,7 @@ class spell_hun_kill_command_pet : public AuraScript
} }
}; };
// -53228 - Rapid Recuperation (trigger) // -53228 - Rapid Recuperation (talent aura)
class spell_hun_rapid_recuperation_trigger : public AuraScript class spell_hun_rapid_recuperation_trigger : public AuraScript
{ {
PrepareAuraScript(spell_hun_rapid_recuperation_trigger); PrepareAuraScript(spell_hun_rapid_recuperation_trigger);
@@ -1559,8 +1538,8 @@ class spell_hun_rapid_recuperation_trigger : public AuraScript
return; return;
uint8 rank = GetSpellInfo()->GetRank(); uint8 rank = GetSpellInfo()->GetRank();
if (rank > 0 && rank <= 2) uint32 spellId = triggerSpells[rank - 1];
GetTarget()->CastSpell(GetTarget(), triggerSpells[rank - 1], true, nullptr, aurEff); eventInfo.GetActor()->CastSpell(eventInfo.GetActor(), spellId, true, nullptr, aurEff);
} }
void Register() override void Register() override
@@ -1676,9 +1655,9 @@ void AddSC_hunter_spell_scripts()
RegisterSpellScript(spell_hun_explosive_shot); RegisterSpellScript(spell_hun_explosive_shot);
RegisterSpellScript(spell_hun_thrill_of_the_hunt); RegisterSpellScript(spell_hun_thrill_of_the_hunt);
RegisterSpellScript(spell_hun_hunting_party); RegisterSpellScript(spell_hun_hunting_party);
RegisterSpellScript(spell_hun_rapid_recuperation);
RegisterSpellScript(spell_hun_glyph_of_mend_pet); RegisterSpellScript(spell_hun_glyph_of_mend_pet);
// Proc system scripts // Proc system scripts
RegisterSpellScript(spell_hun_rapid_recuperation);
RegisterSpellScript(spell_hun_kill_command_pet); RegisterSpellScript(spell_hun_kill_command_pet);
RegisterSpellScript(spell_hun_piercing_shots); RegisterSpellScript(spell_hun_piercing_shots);
RegisterSpellScript(spell_hun_rapid_recuperation_trigger); RegisterSpellScript(spell_hun_rapid_recuperation_trigger);
@@ -31,6 +31,7 @@
#include "ProcChanceTestHelper.h" #include "ProcChanceTestHelper.h"
#include "ProcEventInfoHelper.h" #include "ProcEventInfoHelper.h"
#include "SpellInfoTestHelper.h"
#include "AuraStub.h" #include "AuraStub.h"
#include "UnitStub.h" #include "UnitStub.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
@@ -135,6 +136,151 @@ TEST_F(SpellProcAttributeTest, ReqSpellmod_AttributeNotSet)
EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD); EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD);
} }
// =============================================================================
// PROC_ATTR_REQ_SPELLMOD Auto-Generation Tests
// Validates that LoadSpellProcs auto-generates REQ_SPELLMOD for modifier
// auras with charges, preventing charge consumption by unrelated spells.
// =============================================================================
namespace
{
// Replicates the auto-generation logic from SpellMgr::LoadSpellProcs
void ApplyAutoGeneratedSpellmodFlag(SpellInfo const* spellInfo, SpellProcEntry& procEntry)
{
if (spellInfo->ProcCharges)
{
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (spellInfo->Effects[i].IsAura(SPELL_AURA_ADD_FLAT_MODIFIER) ||
spellInfo->Effects[i].IsAura(SPELL_AURA_ADD_PCT_MODIFIER))
{
procEntry.AttributesMask |= PROC_ATTR_REQ_SPELLMOD;
break;
}
}
}
}
}
TEST_F(SpellProcAttributeTest, ReqSpellmod_AutoGen_PctModifierWithCharges)
{
// Rapid Killing (35099): ADD_PCT_MODIFIER + 1 charge
auto spellInfo = SpellInfoBuilder()
.WithId(35099)
.WithEffect(0, SPELL_EFFECT_APPLY_AURA, SPELL_AURA_ADD_PCT_MODIFIER)
.WithProcFlags(PROC_FLAG_DONE_RANGED_AUTO_ATTACK | PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS)
.WithProcCharges(1)
.BuildUnique();
SpellProcEntry procEntry = SpellProcEntryBuilder()
.WithProcFlags(PROC_FLAG_DONE_RANGED_AUTO_ATTACK | PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS)
.WithCharges(1)
.WithChance(100.0f)
.Build();
ApplyAutoGeneratedSpellmodFlag(spellInfo.get(), procEntry);
EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD);
}
TEST_F(SpellProcAttributeTest, ReqSpellmod_AutoGen_FlatModifierWithCharges)
{
// Clearcasting-like: ADD_FLAT_MODIFIER + charges
auto spellInfo = SpellInfoBuilder()
.WithId(12345)
.WithEffect(0, SPELL_EFFECT_APPLY_AURA, SPELL_AURA_ADD_FLAT_MODIFIER)
.WithProcCharges(2)
.BuildUnique();
SpellProcEntry procEntry = SpellProcEntryBuilder()
.WithCharges(2)
.WithChance(100.0f)
.Build();
ApplyAutoGeneratedSpellmodFlag(spellInfo.get(), procEntry);
EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD);
}
TEST_F(SpellProcAttributeTest, ReqSpellmod_AutoGen_NoCharges_NotSet)
{
// Modifier aura without charges should NOT get the flag
auto spellInfo = SpellInfoBuilder()
.WithId(12345)
.WithEffect(0, SPELL_EFFECT_APPLY_AURA, SPELL_AURA_ADD_PCT_MODIFIER)
.WithProcCharges(0)
.BuildUnique();
SpellProcEntry procEntry = SpellProcEntryBuilder()
.WithCharges(0)
.WithChance(100.0f)
.Build();
ApplyAutoGeneratedSpellmodFlag(spellInfo.get(), procEntry);
EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD);
}
TEST_F(SpellProcAttributeTest, ReqSpellmod_AutoGen_NonModifierWithCharges_NotSet)
{
// Non-modifier aura with charges should NOT get the flag
auto spellInfo = SpellInfoBuilder()
.WithId(12345)
.WithEffect(0, SPELL_EFFECT_APPLY_AURA, SPELL_AURA_PROC_TRIGGER_SPELL)
.WithProcCharges(1)
.BuildUnique();
SpellProcEntry procEntry = SpellProcEntryBuilder()
.WithCharges(1)
.WithChance(100.0f)
.Build();
ApplyAutoGeneratedSpellmodFlag(spellInfo.get(), procEntry);
EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD);
}
TEST_F(SpellProcAttributeTest, ReqSpellmod_AutoGen_ModifierOnSecondEffect)
{
// Modifier on effect index 1, not 0
auto spellInfo = SpellInfoBuilder()
.WithId(12345)
.WithEffect(0, SPELL_EFFECT_APPLY_AURA, SPELL_AURA_PROC_TRIGGER_SPELL)
.WithEffect(1, SPELL_EFFECT_APPLY_AURA, SPELL_AURA_ADD_FLAT_MODIFIER)
.WithProcCharges(1)
.BuildUnique();
SpellProcEntry procEntry = SpellProcEntryBuilder()
.WithCharges(1)
.WithChance(100.0f)
.Build();
ApplyAutoGeneratedSpellmodFlag(spellInfo.get(), procEntry);
EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD);
}
TEST_F(SpellProcAttributeTest, ReqSpellmod_AutoGen_PreservesExistingAttributes)
{
// Should OR with existing attributes, not replace
auto spellInfo = SpellInfoBuilder()
.WithId(12345)
.WithEffect(0, SPELL_EFFECT_APPLY_AURA, SPELL_AURA_ADD_PCT_MODIFIER)
.WithProcCharges(1)
.BuildUnique();
SpellProcEntry procEntry = SpellProcEntryBuilder()
.WithCharges(1)
.WithChance(100.0f)
.WithAttributesMask(PROC_ATTR_TRIGGERED_CAN_PROC)
.Build();
ApplyAutoGeneratedSpellmodFlag(spellInfo.get(), procEntry);
EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD);
EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC);
}
// ============================================================================= // =============================================================================
// PROC_ATTR_USE_STACKS_FOR_CHARGES (0x10) Tests // PROC_ATTR_USE_STACKS_FOR_CHARGES (0x10) Tests
// ============================================================================= // =============================================================================