From 8abd40f2172423872d39f8419bdf349915860da6 Mon Sep 17 00:00:00 2001 From: Docker Build Date: Sat, 9 May 2026 20:39:23 -0400 Subject: [PATCH] Paragon: give class 12 intrinsic AP and SP scaling from stats Stock 3.3.5 hardcodes per-class stat -> AP/SP formulas in Player::UpdateAttackPowerAndDamage and Unit::SpellBase{Damage,Healing}BonusDone, so class 12 fell into the default branches and ended up with 0 AP and 0 SP regardless of STR / AGI / INT / SPI. The character sheet, combat log, and ability damage all reflected this, and Mental Quickness-style AP->SP plumbing silently no-oped on Paragon characters. Add Paragon-specific branches in core (no PlayerScript hooks - those caused SIGSEGVs when the new mid-list enum entry shifted later hook ordinals and broke vtable dispatch): - StatSystem.cpp: melee and ranged AP = level*2 + STR + AGI - 20, mirroring the formula the UI patch already advertises in tooltips. - Unit.cpp: intrinsic SP = level*2 + INT + SPI - 20 (clamped >=0), added symmetrically to SpellBaseDamageBonusDone and SpellBaseHealingBonusDone so the single advertised Spell Power value the character sheet renders matches what spells actually use in combat. Drop the now-unused UnitDefines.h include in Paragon_SC.cpp - it was only needed by the AP PlayerScript hook that was rolled back in favor of the core change. Co-authored-by: Cursor --- modules/mod-paragon/src/Paragon_SC.cpp | 1 - src/server/game/Entities/Unit/StatSystem.cpp | 11 ++++++++ src/server/game/Entities/Unit/Unit.cpp | 29 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/modules/mod-paragon/src/Paragon_SC.cpp b/modules/mod-paragon/src/Paragon_SC.cpp index b927d9c..476e36b 100644 --- a/modules/mod-paragon/src/Paragon_SC.cpp +++ b/modules/mod-paragon/src/Paragon_SC.cpp @@ -15,7 +15,6 @@ #include "SharedDefines.h" #include "SpellScript.h" #include "SpellScriptLoader.h" -#include "UnitDefines.h" #include "WorldPacket.h" #include "WorldSession.h" diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp index 24c025a..c66aa3e 100644 --- a/src/server/game/Entities/Unit/StatSystem.cpp +++ b/src/server/game/Entities/Unit/StatSystem.cpp @@ -385,6 +385,13 @@ void Player::UpdateAttackPowerAndDamage(bool ranged) break; } } + else if (getClass() == CLASS_PARAGON) + { + // Fractured class 12: same hybrid curve as requested for Paragon UI + // (level*2 + AGI + STR - 20). Implemented in core so we do not rely + // on PlayerScript hooks in this hot path. + val2 = level * 2.0f + GetStat(STAT_AGILITY) + GetStat(STAT_STRENGTH) - 20.0f; + } else { val2 = GetStat(STAT_AGILITY) - 10.0f; @@ -481,6 +488,10 @@ void Player::UpdateAttackPowerAndDamage(bool ranged) break; } } + else if (getClass() == CLASS_PARAGON) + { + val2 = level * 2.0f + GetStat(STAT_STRENGTH) + GetStat(STAT_AGILITY) - 20.0f; + } else if (IsClass(CLASS_MAGE, CLASS_CONTEXT_STATS) || IsClass(CLASS_PRIEST, CLASS_CONTEXT_STATS) || IsClass(CLASS_WARLOCK, CLASS_CONTEXT_STATS)) { val2 = GetStat(STAT_STRENGTH) - 10.0f; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 48a75ac..4e84d0a 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -9046,6 +9046,21 @@ int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) DoneAdvertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus(); DoneAdvertisedBenefit += ToPlayer()->GetBaseSpellDamageBonus(); + // Fractured class 12 (Paragon) intrinsic spell power: + // SP = level*2 + INT + SPI - 20 (clamped at 0) + // Read live from current stats so character-sheet refreshes (via + // UpdateSpellDamageAndHealingBonus) and live spell casts both see the + // up-to-date value with no script hooks or m_baseSpellPower mutation. + if (ToPlayer()->getClass() == CLASS_PARAGON) + { + int32 paragonSP = int32(GetLevel()) * 2 + + int32(GetStat(STAT_INTELLECT)) + + int32(GetStat(STAT_SPIRIT)) + - 20; + if (paragonSP > 0) + DoneAdvertisedBenefit += paragonSP; + } + // Damage bonus from stats AuraEffectList const& mDamageDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT); for (AuraEffectList::const_iterator i = mDamageDoneOfStatPercent.begin(); i != mDamageDoneOfStatPercent.end(); ++i) @@ -9803,6 +9818,20 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) AdvertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus(); AdvertisedBenefit += ToPlayer()->GetBaseSpellHealingBonus(); + // Fractured class 12 (Paragon) intrinsic spell power: same level*2 + + // INT + SPI - 20 floor as on the damage side (the character sheet + // shows a single Spell Power value, so both sides must add the same + // bonus). + if (ToPlayer()->getClass() == CLASS_PARAGON) + { + int32 paragonSP = int32(GetLevel()) * 2 + + int32(GetStat(STAT_INTELLECT)) + + int32(GetStat(STAT_SPIRIT)) + - 20; + if (paragonSP > 0) + AdvertisedBenefit += paragonSP; + } + // Healing bonus from stats AuraEffectList const& mHealingDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT); for (AuraEffectList::const_iterator i = mHealingDoneOfStatPercent.begin(); i != mHealingDoneOfStatPercent.end(); ++i)