Paragon: cross-class talents + Warrior stance bypass + Mirror Image spellbook draw
Server-side cross-class wildcard pass for several talents that were
previously locked to a single SpellFamilyName, plus a server+client
Warrior stance bypass and a Paragon-aware Mirror Image rebuild that
mimics the owner's spellbook instead of stock Frostbolt/Fire Blast.
Talent expansions (Paragon owners only; stock classes unchanged):
- Cold Snap (11958): resets cooldown of any Frost-school spell.
- Nature's Swiftness (17116, 16188) + Predator's Swiftness (69369):
instant-cast on any Nature-school spell.
- Vampiric Embrace (15286): leech-heals from any single-target
Shadow-school spell.
- Fingers of Frost (44543/44545) + Frostbite (11071/12496/12497):
proc from any Frost-school chill effect (DK Howling Blast / Icy
Touch / Chains of Ice, Hunter Frost Trap, Shaman Frost Shock,
cross-class chill auras via SPELL_AURA_MOD_DECREASE_SPEED).
- Maelstrom Weapon (53817): now also affects Mage Fireball (133),
Frostbolt (116), and Arcane Blast (30451) at every rank, both
for cast-time/cost spellmod and for stack consumption.
Warrior stance bypass:
- SpellInfo::CheckShapeshift returns SPELL_CAST_OK whenever a
Paragon caster hits any Stances!=0 spell (no SpellFamilyName
gate). Stock classes still see the regular form rules.
- Client side: patch-enUS-4.MPQ now zeroes Stances on every
SPELLFAMILY_WARRIOR Spell.dbc row (105 spells) so the engine's
pre-cast "Must be in Battle/Defensive/Berserker Stance" check
no longer eats CMSG_CAST_SPELL packets for Paragons. Server
bypass enforces the actual decision; stock Warriors still
error mid-cast if they actually click while out of stance.
- patch-enUS-5.MPQ Lua tooltip post-processor recolors and
appends "(Paragon: bypassed)" to "Requires *Stance*" lines on
Warrior abilities, plus Paragon notes on Maelstrom Weapon and
Mirror Image tooltips. Action-bar UseAction wrapper routes
stance-gated Warrior spell clicks through CastSpellByName so
the stance-zero DBC + server bypass actually run.
Mirror Image:
- npc_pet_mage_mirror_image rebuilds its spell list from the
Paragon owner's spellbook on InitializeAI AND JustEngagedWith
(the second pass + events.Reset clears any stale events the
CasterAI base scheduler may have queued from stock 59637 /
59638 entries before the rebuild ran).
- Curated filter keeps single-target damaging spells (instant,
cast-time, or channeled) with a base cooldown <=10s, with the
"damaging" definition expanded to include
SPELL_EFFECT_TRIGGER_MISSILE and
SPELL_AURA_PERIODIC_TRIGGER_SPELL so Arcane Missiles
qualifies. Rejects passives, AoE, melee/ranged weapon strikes,
item/reagent/stance/equip-gated, and lower spell ranks.
- UpdateAI picks a random spell from the curated list per cast
and reschedules the next pick by the actually-cast spell's
cast/channel duration + 750ms breather, so a 5s Arcane
Missiles channel waits its full duration before re-rolling
rather than visually looping across four images.
Helpers:
- Unit::IsParagonWildcardCaller / Unit::ParagonFamilyMatches
used by Spell.cpp, SpellInfo.cpp, Player.cpp,
SpellAuraEffects.cpp, and the spell scripts.
- SpellInfo::CheckShapeshift signature gains an optional caster
pointer; all call sites updated.
SQL migrations under modules/mod-paragon/data/sql/db-world/updates/:
- 2026_05_11_01.sql Vampiric Embrace spell_proc relax + script
gate (CheckProc enforces stock for non-Paragon).
- 2026_05_11_02.sql Maelstrom Weapon spell_proc relax (initial,
superseded by _04 below for stack-consumption fix).
- 2026_05_11_03.sql Fingers of Frost / Frostbite spell_proc relax
and spell_script_names binding.
- 2026_05_11_04.sql Maelstrom Weapon spell_proc fixup: restore
SpellPhaseMask=1 (CAST) and AttributesMask=8
(REQ_SPELLMOD); previous _02 set 8/0 which
silently dropped every proc event.
Diagnostics from this debugging session demoted from LOG_INFO to
LOG_DEBUG (silent at default info level) so production logs stay
quiet but the probes remain available for reproducing future
regressions: pet_mage.cpp MirrorImage probe/kept/rebuild/init/
engage/cast lines and SpellInfo.cpp CheckShapeshift bypass line.
CLIENT-PATCHES.md updated to document the new Warrior stance DBC
patcher (_patch_spell_dbc_stances.py), the spell-tooltip post-
processor and stance UseAction wrapper in patch-enUS-5.MPQ, and
the Mirror Image / Maelstrom Weapon Paragon notes.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
-- mod-paragon: Vampiric Embrace (15286) cross-family wildcard.
|
||||
--
|
||||
-- Stock 3.3.5 spell_proc row for Vampiric Embrace gates by SpellFamilyName=6
|
||||
-- (PRIEST) plus a Priest-Shadow-damage SpellFamilyMask. That blocks the proc
|
||||
-- engine from ever calling the AuraScript's CheckProc when a Paragon casts a
|
||||
-- non-Priest Shadow-school spell (e.g. Warlock Shadow Bolt, Death Knight
|
||||
-- Death Coil, etc.), because IsAffected's familyFlags gate fails before
|
||||
-- CheckProc runs even though the Phase A wildcard already loosens the
|
||||
-- familyName equality test (see SpellInfo::IsAffected, listenerOwner overload).
|
||||
--
|
||||
-- We relax this row so:
|
||||
-- * SchoolMask=32 (SHADOW) kept -- proc-engine still gates by school
|
||||
-- * SpellTypeMask=1 (DAMAGE) kept -- only damage events trigger CheckProc
|
||||
-- * SpellPhaseMask=2 (HIT) kept -- post-hit phase
|
||||
-- * AttributesMask=2 (TRIGGERED) kept -- triggered-spell payloads still proc
|
||||
-- * SpellFamilyName=0 wildcard -- IsAffected short-circuits to true
|
||||
-- * SpellFamilyMask{0,1,2}=0 wildcard -- no flag-bit gating at this layer
|
||||
--
|
||||
-- Real filtering moves into spell_pri_vampiric_embrace::CheckProc, which
|
||||
-- branches on IsParagonWildcardCaller(GetTarget()):
|
||||
--
|
||||
-- * For Paragon owners with `Paragon.WildcardFamilyMatching = 1`, accept any
|
||||
-- single-target Shadow-school spell (Mind Sear / AoE shadow spells like
|
||||
-- Seed of Corruption / Hellfire are filtered there via IsAffectingArea
|
||||
-- and the existing Mind Sear bit-mask).
|
||||
--
|
||||
-- * For stock Priest owners (and for the non-wildcard runtime path), the
|
||||
-- CheckProc re-enforces the EXACT original gate -- SpellFamilyName=6 plus
|
||||
-- the original 0x0280A010 / 0x00002402 / 0x00000008 SpellFamilyMask bits
|
||||
-- -- so behavior is byte-identical to before this change for any caster
|
||||
-- that is not a Paragon.
|
||||
--
|
||||
-- Net effect: Paragon characters with VE learned now leech-heal off any
|
||||
-- single-target Shadow spell they cast (Death Coil, Shadow Bolt, Searing
|
||||
-- Pain, Drain Soul, etc.); stock Shadow Priests are unchanged.
|
||||
|
||||
DELETE FROM `spell_proc` WHERE `SpellId` = 15286;
|
||||
|
||||
INSERT INTO `spell_proc`
|
||||
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
|
||||
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
|
||||
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
|
||||
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
|
||||
`Chance`, `Cooldown`, `Charges`)
|
||||
VALUES
|
||||
(15286, 32, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0);
|
||||
@@ -0,0 +1,51 @@
|
||||
-- mod-paragon: Maelstrom Weapon (53817) cross-family wildcard.
|
||||
--
|
||||
-- Stock 3.3.5 spell_proc row for Maelstrom Weapon gates by SpellFamilyName=11
|
||||
-- (SHAMAN) plus a Shaman SpellFamilyMask covering Lightning Bolt, Chain
|
||||
-- Lightning, Lesser Healing Wave, Healing Wave and Hex (Mask0=451,
|
||||
-- Mask1=32768). The proc engine therefore never delivers an event to the
|
||||
-- AuraScript when a Paragon casts a non-Shaman cast-time spell, even if the
|
||||
-- IsAffected wildcard relaxes SpellFamilyName equality (the SpellFamilyMask
|
||||
-- AND-with-target-FamilyFlags check still fails because Mage / Warlock /
|
||||
-- Druid spell-class bits do not overlap with Shaman bits).
|
||||
--
|
||||
-- We relax this row so:
|
||||
-- * SchoolMask=0 wildcard -- proc engine no longer gates by school
|
||||
-- * SpellTypeMask=1 (DAMAGE) kept -- only damage spells trigger CheckProc
|
||||
-- * SpellPhaseMask=8 (FINISH) kept -- post-cast phase, on cast finish
|
||||
-- * SpellFamilyName=0 wildcard -- IsAffected short-circuits to true
|
||||
-- * SpellFamilyMask{0,1,2}=0 wildcard -- no flag-bit gating at this layer
|
||||
--
|
||||
-- Real filtering moves into spell_sha_maelstrom_weapon::CheckProc:
|
||||
--
|
||||
-- * For stock Shaman owners (and for the non-wildcard runtime path), the
|
||||
-- CheckProc re-enforces the EXACT original gate -- SpellFamilyName=11
|
||||
-- plus the original Mask0=451 / Mask1=32768 bits -- so behavior is
|
||||
-- byte-identical to before this change for any caster that is not a
|
||||
-- Paragon.
|
||||
--
|
||||
-- * For Paragon owners with `Paragon.WildcardFamilyMatching = 1`, the
|
||||
-- stock allowlist still passes, AND we additionally accept the curated
|
||||
-- Mage cast-time nukes Fireball / Frostbolt / Arcane Blast (any rank,
|
||||
-- matched via GetFirstRankSpell).
|
||||
--
|
||||
-- The matching IsAffectedBySpellMod hook in SpellInfo.cpp ensures the cast
|
||||
-- time + power cost spellmods on aura 53817 also bridge across families for
|
||||
-- the same Mage spell allowlist, so Paragons get the full Maelstrom Weapon
|
||||
-- experience (instant cast at 5 stacks + reduced mana cost) on Fireball,
|
||||
-- Frostbolt and Arcane Blast.
|
||||
--
|
||||
-- Net effect: Paragon characters with Maelstrom Weapon learned now spend
|
||||
-- stacks on Mage cast-time nukes in addition to the stock Shaman list;
|
||||
-- stock Enhancement Shamans are unchanged.
|
||||
|
||||
DELETE FROM `spell_proc` WHERE `SpellId` = 53817;
|
||||
|
||||
INSERT INTO `spell_proc`
|
||||
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
|
||||
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
|
||||
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
|
||||
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
|
||||
`Chance`, `Cooldown`, `Charges`)
|
||||
VALUES
|
||||
(53817, 0, 0, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 0, 0, 0);
|
||||
@@ -0,0 +1,66 @@
|
||||
-- mod-paragon: Frostbite + Fingers of Frost cross-family wildcard.
|
||||
--
|
||||
-- Both talents are normally gated by SpellFamilyName=3 (MAGE) plus a Mage
|
||||
-- SpellFamilyMask covering specific Mage Frost spells (Frostbolt / Frost Nova
|
||||
-- / Cone of Cold / Blizzard / Frostfire Bolt / Deep Freeze for FoF; Frost
|
||||
-- slow-applying spells for Frostbite). That blocks the proc engine from
|
||||
-- delivering an event to the AuraScript when a Paragon casts a non-Mage
|
||||
-- Frost-school chill effect (DK Howling Blast / Icy Touch / Chains of Ice,
|
||||
-- Hunter Frost Trap, Shaman Frost Shock, etc.), because IsAffected's
|
||||
-- familyFlags AND-with-target-FamilyFlags check fails before CheckProc runs
|
||||
-- even after the Paragon family-name wildcard.
|
||||
--
|
||||
-- We relax these rows so:
|
||||
-- * SchoolMask=16 (FROST) gate by Frost school at the proc engine
|
||||
-- * SpellTypeMask=1 (DAMAGE) only damage events trigger CheckProc
|
||||
-- * SpellPhaseMask=2 (HIT) post-hit phase
|
||||
-- * AttributesMask=2 (TRIGGERED) triggered chill payloads still proc
|
||||
-- * SpellFamilyName=0 wildcard -- IsAffected short-circuits to true
|
||||
-- * SpellFamilyMask{0,1,2}=0 wildcard -- no flag-bit gating at this layer
|
||||
--
|
||||
-- Real filtering moves into the Mage AuraScripts:
|
||||
--
|
||||
-- spell_mage_fingers_of_frost_talent attached to 44543 / 44545
|
||||
-- stock Mage : SpellFamilyName=MAGE AND original Mask0 0x100120 / Mask1 0x1000
|
||||
-- Paragon : accept (FROST + DAMAGE gate already enforced)
|
||||
--
|
||||
-- spell_mage_frostbite attached to 11071 / 12496 / 12497
|
||||
-- stock Mage : SpellFamilyName=MAGE AND original Mage Frost-slow Mask
|
||||
-- (Frostbolt / Frost Nova / Cone of Cold / Blizzard / FFB)
|
||||
-- Paragon : accept iff proc spell applies SPELL_AURA_MOD_DECREASE_SPEED
|
||||
-- OR the Paragon already has a slow on the proc target
|
||||
-- (covers the Improved-Blizzard-style "chill via separate
|
||||
-- triggered aura" cross-class case)
|
||||
--
|
||||
-- Net effect: Paragon characters with these talents now have FoF / Frostbite
|
||||
-- proc off cross-class Frost-school chill effects; stock Mages are unchanged.
|
||||
|
||||
DELETE FROM `spell_proc` WHERE `SpellId` IN (44543, 44545, 11071, 12496, 12497);
|
||||
|
||||
INSERT INTO `spell_proc`
|
||||
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
|
||||
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
|
||||
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
|
||||
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
|
||||
`Chance`, `Cooldown`, `Charges`)
|
||||
VALUES
|
||||
-- Fingers of Frost talent ranks (Chance 7% / 15% preserved from stock row).
|
||||
(44543, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 7, 0, 0),
|
||||
(44545, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 15, 0, 0),
|
||||
-- Frostbite talent ranks (5% / 10% / 15% per rank, leave Chance=0 to use
|
||||
-- the DBC ProcChance which already encodes the per-rank percentage).
|
||||
(11071, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0),
|
||||
(12496, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0),
|
||||
(12497, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0);
|
||||
|
||||
-- Bind the new AuraScripts (defined in src/server/scripts/Spells/spell_mage.cpp).
|
||||
DELETE FROM `spell_script_names`
|
||||
WHERE `spell_id` IN (44543, 44545, 11071, 12496, 12497)
|
||||
AND `ScriptName` IN ('spell_mage_fingers_of_frost_talent', 'spell_mage_frostbite');
|
||||
|
||||
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
|
||||
(44543, 'spell_mage_fingers_of_frost_talent'),
|
||||
(44545, 'spell_mage_fingers_of_frost_talent'),
|
||||
(11071, 'spell_mage_frostbite'),
|
||||
(12496, 'spell_mage_frostbite'),
|
||||
(12497, 'spell_mage_frostbite');
|
||||
@@ -0,0 +1,51 @@
|
||||
-- mod-paragon: Maelstrom Weapon (53817) spell_proc fixup.
|
||||
--
|
||||
-- The previous migration (2026_05_11_02.sql) had two bugs in the rewritten
|
||||
-- spell_proc row that prevented stack consumption from firing at all -- not
|
||||
-- just for our new Mage targets, but also for the stock Shaman cast-time
|
||||
-- spells (Lightning Bolt, Chain Lightning, Lesser Healing Wave, etc.).
|
||||
--
|
||||
-- Bugs:
|
||||
--
|
||||
-- * SpellPhaseMask was set to 8. The valid values for SpellPhaseMask are
|
||||
-- PROC_SPELL_PHASE_CAST = 1
|
||||
-- PROC_SPELL_PHASE_HIT = 2
|
||||
-- PROC_SPELL_PHASE_FINISH = 4
|
||||
-- (see SpellMgr.h). Anything else, including 8, never matches a real
|
||||
-- proc event, so the proc engine silently dropped every event before it
|
||||
-- reached the AuraScript. The original stock row uses 1 (CAST), which
|
||||
-- is what fires when the cast packet's setup phase completes -- exactly
|
||||
-- when we want the spellmod-affected cast to consume the buff.
|
||||
--
|
||||
-- * AttributesMask was set to 0. The original stock row uses 8
|
||||
-- PROC_ATTR_REQ_SPELLMOD = 0x8
|
||||
-- which says "only proc on spells that were affected by one of this
|
||||
-- aura's spellmods". This is the bridge between IsAffectedBySpellMod
|
||||
-- (which records the aura into Spell::m_appliedMods when calculating
|
||||
-- cast time / cost) and the proc system (which then knows that the cast
|
||||
-- used the buff and should consume a charge). Without this attribute,
|
||||
-- the proc would either fire too aggressively or not at all depending
|
||||
-- on subsequent gating, but in practice the engine relies on it to
|
||||
-- correlate spellmod use with stack consumption.
|
||||
--
|
||||
-- Fixed row keeps the same family/mask wildcards from the previous
|
||||
-- migration (so the Paragon Mage allowlist in spell_sha_maelstrom_weapon's
|
||||
-- CheckProc still gets the chance to filter), restores SpellPhaseMask=1
|
||||
-- (CAST) and AttributesMask=8 (REQ_SPELLMOD) to match stock semantics, and
|
||||
-- resets SpellTypeMask to 0 (any spell type -- the REQ_SPELLMOD attribute
|
||||
-- already gates by "was the buff actually used", so an extra DAMAGE filter
|
||||
-- is redundant and would block e.g. Lesser Healing Wave on stock Shamans).
|
||||
--
|
||||
-- This restores stock Shaman behavior byte-identically and lets Paragon
|
||||
-- Mage casts also consume stacks via the IsAffectedBySpellMod allowlist.
|
||||
|
||||
DELETE FROM `spell_proc` WHERE `SpellId` = 53817;
|
||||
|
||||
INSERT INTO `spell_proc`
|
||||
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
|
||||
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
|
||||
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
|
||||
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
|
||||
`Chance`, `Cooldown`, `Charges`)
|
||||
VALUES
|
||||
(53817, 0, 0, 0, 0, 0, 0, 0, 1, 0, 8, 0, 0, 0, 0, 0);
|
||||
Reference in New Issue
Block a user