Paragon: Runeforging support (panel-purchasable, no anvil required)
Lets the Paragon class buy Runeforging from the Character Advancement
panel and apply rune enchants from anywhere in the world without needing
to be near a runeforge GameObject. Three carve-outs work together:
* Spell.cpp: bypass the SpellFocusObject GO proximity check when the
caster is a Paragon and the spell belongs to SKILL_RUNEFORGING (776).
Stock DK behaviour is unchanged -- the bypass is gated on
getClass() == CLASS_PARAGON, not on the IsClass() context hook.
* Player.cpp: skip the Paragon class-skill cascade block for skill 776
so the rune-enchant SLA cascade actually fires. Without this the
player gets the Runeforging skill but no rune options at the anvil.
* Paragon_Essence.cpp:
- Treat SKILL_RUNEFORGING children as a meta-skill cluster: cascade
them like passives even though they're active casts, so they stick
as panel_spell_children and get cleaned up via the standard refund
path.
- Whitelist the 8 basic rune-enchants in PruneSkillLineCascadeChildren
so they don't get evicted as "active in children = legacy garbage".
- Force-attach them in PanelLearnSpellChain (the SLA rows ship with
AcquireMethod=0, so the engine cascade alone won't grant them).
- Add an OnPlayerLogin fixup so existing Paragons who bought
Runeforging before this change get the 8 runes retro-granted.
- Stop filtering SPELL_ATTR0_DO_NOT_DISPLAY in PushSpellSnapshot --
Runeforging itself is hidden in the DBC but is a real panel
purchase that must show in the Overview tab.
The two advanced runes (Stoneskin Gargoyle, Nerubian Carapace) are
intentionally excluded from the auto-grant -- retail gates them behind
heroic dungeon / raid item drops and the SLA AcquireMethod=0 honours
that gating.
No SQL migration needed; works against existing DBC + SLA data.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12048,7 +12048,20 @@ void Player::learnSkillRewardedSpells(uint32 skill_id, uint32 skill_value)
|
||||
// weapon, language, and racial skill cascades stay enabled so things
|
||||
// like recipe auto-learn, weapon proficiencies, and racial perks
|
||||
// still work.
|
||||
if (getClass() == CLASS_PARAGON)
|
||||
//
|
||||
// Carve-out: SKILL_RUNEFORGING (776) is a CLASS-category skill but
|
||||
// behaves like a profession in this context — the player buys ONE
|
||||
// panel ability (Runeforging, spell 53428) and the rune-enchant
|
||||
// spells (Rune of the Fallen Crusader, Razorice, Cinderglacier, ...)
|
||||
// are supposed to come along for the ride via the standard SLA
|
||||
// cascade, exactly the same way they do for a stock DK. Without
|
||||
// this carve-out, the early-return below blocks the cascade and a
|
||||
// Paragon who buys Runeforging gets the skill but no actual rune
|
||||
// options at the runeforge anvil. The cascade only fires once per
|
||||
// skill-grant for 776 (it's not on UpdateSkillsForLevel) so the
|
||||
// "leaking back into the spellbook" concern that motivates the
|
||||
// early-return doesn't apply to this skill.
|
||||
if (getClass() == CLASS_PARAGON && skill_id != SKILL_RUNEFORGING)
|
||||
{
|
||||
if (SkillLineEntry const* sl = sSkillLineStore.LookupEntry(skill_id))
|
||||
if (sl->categoryId == SKILL_CATEGORY_CLASS)
|
||||
|
||||
@@ -7860,6 +7860,36 @@ SpellCastResult Spell::CheckSpellFocus()
|
||||
// check spell focus object
|
||||
if (m_spellInfo->RequiresSpellFocus)
|
||||
{
|
||||
// Fractured / Paragon: skip the GO proximity check for Paragon
|
||||
// casters when the spell is a runeforge enchant (skill line 776).
|
||||
// Paragons get a Runeforge tab in the Character Advancement
|
||||
// panel that lets them apply rune-enchants from anywhere in the
|
||||
// world -- no need to fly back to Acherus or find the nearest
|
||||
// Eastern Plaguelands anvil. The skill-line gate keeps the
|
||||
// bypass tightly scoped: only the 10 SkillLineAbility-tagged
|
||||
// rune-enchant spells qualify, every other spell that uses
|
||||
// SpellFocusObject (Enchanting bench, Cooking fire, Lockpicking
|
||||
// anvil, etc.) keeps its requirement intact.
|
||||
//
|
||||
// DK / other class casters are unchanged -- this carve-out
|
||||
// intentionally checks getClass() == CLASS_PARAGON rather than
|
||||
// IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY) (which
|
||||
// would also return true for Paragons via mod-paragon's hook).
|
||||
// Stock DK behavior must stay vanilla; the QoL bypass is a
|
||||
// class-12 feature only.
|
||||
if (m_caster && m_caster->IsPlayer()
|
||||
&& m_caster->ToPlayer()->getClass() == CLASS_PARAGON)
|
||||
{
|
||||
auto bounds = sSpellMgr->GetSkillLineAbilityMapBounds(m_spellInfo->Id);
|
||||
for (auto it = bounds.first; it != bounds.second; ++it)
|
||||
{
|
||||
if (it->second->SkillLine == SKILL_RUNEFORGING)
|
||||
{
|
||||
return SPELL_CAST_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CellCoord p(Acore::ComputeCellCoord(m_caster->GetPositionX(), m_caster->GetPositionY()));
|
||||
Cell cell(p);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user