fix(Core/Spells): fix PPM proc chance calculation for healing spells (#24761)
Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
-- Soul Preserver (60510): add spell_proc entry to fix proc for non-Paladin healers
|
||||||
|
-- Auto-generated entry had SpellFamilyName=10 (Paladin) which blocked other classes
|
||||||
|
-- ProcFlags 0x4400: DONE_SPELL_MAGIC_DMG_CLASS_POS + DONE_SPELL_NONE_DMG_CLASS_POS (direct heals + HoTs)
|
||||||
|
-- SpellTypeMask 6: HEAL + NO_DMG_HEAL (HoT applications)
|
||||||
|
DELETE FROM `spell_proc` WHERE `SpellId` = 60510;
|
||||||
|
INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`)
|
||||||
|
VALUES (60510, 0, 0, 0, 0, 0, 0x4400, 6, 2, 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
-- Spark of Life (60519): add DONE_SPELL_NONE_DMG_CLASS_POS (0x400) and NO_DMG_HEAL to SpellTypeMask
|
||||||
|
-- Allows HoT casts to trigger the proc
|
||||||
|
UPDATE `spell_proc` SET `ProcFlags` = 0x14400, `SpellTypeMask` = 7 WHERE `SpellId` = 60519;
|
||||||
@@ -2280,13 +2280,14 @@ float Aura::CalcProcChance(SpellProcEntry const& procEntry, ProcEventInfo& event
|
|||||||
if (Unit* caster = GetCaster())
|
if (Unit* caster = GetCaster())
|
||||||
{
|
{
|
||||||
// If PPM exists calculate chance from PPM
|
// If PPM exists calculate chance from PPM
|
||||||
if (eventInfo.GetDamageInfo() && procEntry.ProcsPerMinute != 0)
|
if ((eventInfo.GetDamageInfo() || eventInfo.GetHealInfo()) && procEntry.ProcsPerMinute != 0)
|
||||||
{
|
{
|
||||||
SpellInfo const* procSpell = eventInfo.GetSpellInfo();
|
SpellInfo const* procSpell = eventInfo.GetSpellInfo();
|
||||||
uint32 attackSpeed = 0;
|
uint32 attackSpeed = 0;
|
||||||
if (!procSpell || procSpell->DmgClass == SPELL_DAMAGE_CLASS_MELEE || procSpell->IsRangedWeaponSpell())
|
if (!procSpell || procSpell->DmgClass == SPELL_DAMAGE_CLASS_MELEE || procSpell->IsRangedWeaponSpell())
|
||||||
{
|
{
|
||||||
attackSpeed = caster->GetAttackTime(eventInfo.GetDamageInfo()->GetAttackType());
|
if (eventInfo.GetDamageInfo())
|
||||||
|
attackSpeed = caster->GetAttackTime(eventInfo.GetDamageInfo()->GetAttackType());
|
||||||
}
|
}
|
||||||
else // spells use their cast time for PPM calculations
|
else // spells use their cast time for PPM calculations
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public:
|
|||||||
* @param chanceModifier Talent/aura modifier to chance
|
* @param chanceModifier Talent/aura modifier to chance
|
||||||
* @param ppmModifier Talent/aura modifier to PPM
|
* @param ppmModifier Talent/aura modifier to PPM
|
||||||
* @param hasDamageInfo Whether a DamageInfo is present (enables PPM)
|
* @param hasDamageInfo Whether a DamageInfo is present (enables PPM)
|
||||||
|
* @param hasHealInfo Whether a HealInfo is present (also enables PPM)
|
||||||
* @return Calculated proc chance
|
* @return Calculated proc chance
|
||||||
*/
|
*/
|
||||||
static float SimulateCalcProcChance(
|
static float SimulateCalcProcChance(
|
||||||
@@ -87,12 +88,13 @@ public:
|
|||||||
uint32 weaponSpeed = 2500,
|
uint32 weaponSpeed = 2500,
|
||||||
float chanceModifier = 0.0f,
|
float chanceModifier = 0.0f,
|
||||||
float ppmModifier = 0.0f,
|
float ppmModifier = 0.0f,
|
||||||
bool hasDamageInfo = true)
|
bool hasDamageInfo = true,
|
||||||
|
bool hasHealInfo = false)
|
||||||
{
|
{
|
||||||
float chance = procEntry.Chance;
|
float chance = procEntry.Chance;
|
||||||
|
|
||||||
// PPM calculation overrides base chance if PPM > 0 and we have DamageInfo
|
// PPM calculation overrides base chance if PPM > 0 and we have DamageInfo or HealInfo
|
||||||
if (hasDamageInfo && procEntry.ProcsPerMinute > 0.0f)
|
if ((hasDamageInfo || hasHealInfo) && procEntry.ProcsPerMinute > 0.0f)
|
||||||
{
|
{
|
||||||
chance = CalculatePPMChance(weaponSpeed, procEntry.ProcsPerMinute, ppmModifier);
|
chance = CalculatePPMChance(weaponSpeed, procEntry.ProcsPerMinute, ppmModifier);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,19 +91,50 @@ TEST_F(SpellProcChanceTest, PPM_OverridesBaseChance_WithDamageInfo)
|
|||||||
EXPECT_NEAR(result, 25.0f, 0.01f);
|
EXPECT_NEAR(result, 25.0f, 0.01f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpellProcChanceTest, PPM_NotApplied_WithoutDamageInfo)
|
TEST_F(SpellProcChanceTest, PPM_NotApplied_WithoutDamageOrHealInfo)
|
||||||
{
|
{
|
||||||
auto procEntry = SpellProcEntryBuilder()
|
auto procEntry = SpellProcEntryBuilder()
|
||||||
.WithChance(50.0f)
|
.WithChance(50.0f)
|
||||||
.WithProcsPerMinute(6.0f)
|
.WithProcsPerMinute(6.0f)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Without DamageInfo, base chance is used
|
// Without DamageInfo or HealInfo, base chance is used
|
||||||
float result = ProcChanceTestHelper::SimulateCalcProcChance(
|
float result = ProcChanceTestHelper::SimulateCalcProcChance(
|
||||||
procEntry, 80, 2500, 0.0f, 0.0f, false);
|
procEntry, 80, 2500, 0.0f, 0.0f, false, false);
|
||||||
EXPECT_NEAR(result, 50.0f, 0.01f);
|
EXPECT_NEAR(result, 50.0f, 0.01f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SpellProcChanceTest, PPM_Applied_WithHealInfo)
|
||||||
|
{
|
||||||
|
auto procEntry = SpellProcEntryBuilder()
|
||||||
|
.WithChance(0.0f)
|
||||||
|
.WithProcsPerMinute(3.5f)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// With HealInfo (no DamageInfo), PPM should still calculate
|
||||||
|
// 3000ms cast time * 3.5 PPM / 600 = 17.5%
|
||||||
|
float result = ProcChanceTestHelper::SimulateCalcProcChance(
|
||||||
|
procEntry, 80, 3000, 0.0f, 0.0f, false, true);
|
||||||
|
EXPECT_NEAR(result, 17.5f, 0.01f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SpellProcChanceTest, PPM_HealInfo_ZeroBaseChance_WouldBeZeroWithoutFix)
|
||||||
|
{
|
||||||
|
// Reproduces the Omen of Clarity healing bug:
|
||||||
|
// PPM=3.5, Chance=0, and only HealInfo present (no DamageInfo)
|
||||||
|
// Without the fix, chance would be 0% because PPM branch was skipped
|
||||||
|
auto procEntry = SpellProcEntryBuilder()
|
||||||
|
.WithChance(0.0f)
|
||||||
|
.WithProcsPerMinute(3.5f)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Instant cast uses 1500ms minimum
|
||||||
|
float result = ProcChanceTestHelper::SimulateCalcProcChance(
|
||||||
|
procEntry, 80, 1500, 0.0f, 0.0f, false, true);
|
||||||
|
EXPECT_NEAR(result, 8.75f, 0.01f);
|
||||||
|
EXPECT_GT(result, 0.0f) << "PPM with HealInfo must produce non-zero chance";
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(SpellProcChanceTest, PPM_WithWeaponSpeedVariation)
|
TEST_F(SpellProcChanceTest, PPM_WithWeaponSpeedVariation)
|
||||||
{
|
{
|
||||||
auto procEntry = SpellProcEntryBuilder()
|
auto procEntry = SpellProcEntryBuilder()
|
||||||
|
|||||||
Reference in New Issue
Block a user