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
@@ -31,6 +31,7 @@
#include "ProcChanceTestHelper.h"
#include "ProcEventInfoHelper.h"
#include "SpellInfoTestHelper.h"
#include "AuraStub.h"
#include "UnitStub.h"
#include "gtest/gtest.h"
@@ -135,6 +136,151 @@ TEST_F(SpellProcAttributeTest, ReqSpellmod_AttributeNotSet)
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
// =============================================================================