Server-side batch following v0.7.18, all gated to Paragon (or applied
server-wide where the design discussion called for it):
* Cross-class stance / form / presence / aspect exclusivity (server-wide).
New `IsFracturedExclusiveStanceSpell()` (`Unit.cpp`/`Unit.h`) returns
true for the union of warrior stances + druid forms (combat AND utility:
Travel, Aquatic, Flight, Swift Flight) + Ghost Wolf + base Stealth +
Shadowform + Metamorphosis + DK Presences + Hunter Aspects (combat AND
utility: Cheetah, Pack). `Aura::CanStackWith` (`SpellAuras.cpp`) refuses
to stack two spells from this set, which routes through
`_RemoveNoStackAurasDueToAura` to drop the older aura -- the same
mechanism Battle Elixirs / Curses use. Plugs the stock-AC gap where DK
Presences and Hunter Aspects (regular auras, just rendered in the
stance bar) coexisted with engine-shapeshifts.
* Stances / Presences / Aspects cancellable like Druid forms.
`SpellInfoCorrections.cpp` now zeroes `CategoryEntry` on warrior
stances, DK presences, and every rank of every hunter aspect (moves
them out of SpellCategory 47 "Combat States", which gates the client's
right-click / `/cancelaura` path), AND clears `AttributesEx6` bit
`0x1000` on warrior stances + DK presences (a second client-UI gate
surfaced via DBC diff -- aspects don't have it set). Mirrored client-
side by `_patch_spell_dbc_presences_cancelable.py`. Aspects / presences
do NOT swap action bars (those are owned by `SPELL_AURA_MOD_SHAPESHIFT`,
not by Category / AttrEx6) -- only warrior stances and druid forms keep
the bar swap, matching the design requirement that presences/aspects
not change the player's action bar.
* Hunter ammo soft-fail (server-wide).
Replaced both `SPELL_FAILED_NO_AMMO` returns in `Spell::CheckCast` with
`break;` so ranged + thrown abilities cast through with zero ammo;
`_ApplyAmmoBonuses` continues to gate the actual arrow/bullet DPS bonus
on a non-empty stack, so equipping ammo still pays off. New programmatic
`ApplySpellFix`-style block in `SpellInfoCorrections.cpp` iterates every
Hunter-family spell whose `EquippedItemClass == ITEM_CLASS_WEAPON` and
`EquippedItemSubClassMask` includes Bow/Gun/Crossbow and sets
`EquippedItemClass = -1` (skipping a small DENYLIST of Quiver / Ammo
Pouch passive haste auras + Aynasha's Bow + Legendary Bow Haste -- those
are item-equip-driven and must keep gating on the ranged weapon being
equipped). Server log: ">> Fractured: dropped EquippedItemClass on 196
hunter shot abilities". Mirrored client-side by the new
`_patch_spell_dbc_hunter_ammo.py` so the 3.3.5a client preflight stops
blocking the cast packet with "Ammo needs to be in the paper doll ammo
slot before it can be fired." `Spell::TakeAmmo` no longer clears
`PLAYER_AMMO_ID` to 0 when the bag empties (defense in depth so a half-
deployed pair degrades to soft-fail rather than hard-reject). Adds
`#include "ItemTemplate.h"` for `ITEM_SUBCLASS_WEAPON_*`.
* Feral Cat scaling (server-wide, cat-only).
`StatSystem.cpp` `UpdateAttackPowerAndDamage` FORM_CAT branch doubles
the AGI coefficient (1.0 -> 2.0). `SpellAuraEffects.cpp` Master
Shapeshifter FORM_CAT branch doubles the talent's bp before triggering
48420 (R1: 2% -> 4% crit, R2: 4% -> 8%). FORM_BEAR / FORM_DIREBEAR /
FORM_MOONKIN / FORM_TREE branches all left untouched so bear stays
"already fine" per the resident Feral expert. Client tooltip drift on
Cat Form (768) + Master Shapeshifter (48411 / 48412) + Pestilence
(50842) handled by `_patch_spell_dbc_feral_tooltips.py`.
* Pestilence spreads / refreshes Devouring Plague for Paragon casters.
`spell_dk.cpp` `spell_dk_pestilence::HandleScriptEffect` now also
spreads (and Glyph-of-Disease refreshes) Priest Devouring Plague when
`IsParagonWildcardCaller(caster)`. Uses `GetAuraOfRankedSpell(2944)` so
the spread copy carries the caster's actual rank. Stock DKs cannot
cast Devouring Plague at all, so this branch is a no-op for them.
* Dancing Rune Weapon: Paragon copies melee, not casts.
`spell_dk.cpp` `spell_dk_dancing_rune_weapon::CheckProc` for Paragon
callers requires `eventInfo.GetDamageInfo()` and
`spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE`, so the ghostly
weapon now copies cross-class melee strikes (Hamstring, Sinister Strike,
Heart Strike, Frost Strike, ...) and auto-attacks instead of re-casting
the DK's nukes. Stock DK gating below is untouched.
* Maelstrom Weapon: drop Arcane Blast from the allowlist.
`SpellInfo.cpp` and `spell_shaman.cpp` allowlists now match Fireball
and Frostbolt only -- Arcane Blast stacked with its own self-buff was
too potent.
* `BALANCE-TODO.md` added under `contrib/fractured-dev-extras/` to
capture the resident Feral expert's recommendation, the levers we
considered, and the cat-only Master-Shapeshifter / AGI-doubling
resolution we shipped, plus the next-lever knobs if field reports
still flag cat as weak.
DBC patcher pipeline (lives outside the repo in `fractured-tooling/`)
documented run order: runes -> reagents -> stances -> presences_cancelable
-> hunter_ammo -> feral_tooltips -> _make_paragon_dbc_patch.
No SQL migrations.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Weapon-narrow: replace blanket EquippedItemSubClassMask bypass with an
explicit allowlist (IsParagonWeaponSubclassWildcardSpell, currently
Maelstrom Weapon 51528 only). Applied at all three gates: Player::
HasItemFitToSpellRequirements, Player::CheckAttackFitToAuraRequirement,
and Aura::IsProcTriggeredOnEvent. Maelstrom Weapon still procs from any
weapon for Paragon, but Hack and Slash / Sword / Mace Specialization
remain correctly weapon-gated.
* Talent ability-rank cascade: TeachLevelGatedAbilityChainNoPanel +
CascadeRanksForTalentLearnSpellEffects walk the SPELL_EFFECT_LEARN_SPELL
chain a talent rank grants and learn each rank up to the player's level.
Wired into HandleCommit (on talent purchase) and OnPlayerLevelChanged
(on level-up). Fixes Mangle (and any future LEARN_SPELL talent) being
stuck at rank 1 because Player::learnSkillRewardedSpells is intentionally
disabled for Paragon's class skill lines.
* Riding-skill gate for flight forms (IsParagonSpellAllowedByRidingSkill):
Flight Form (33943) requires Expert Riding (34090); Swift Flight Form
(40120) requires Artisan Riding (34091). Applied in PanelLearnSpellChain
and TeachLevelGatedAbilityChainNoPanel so the cascade can't push past a
rank the player isn't trained for. Also backticks `rank` in the level-up
query (MySQL reserved word).
* Savage Defense (62600) on the panel: the SpellData bake's blanket
SPELL_ATTR0_PASSIVE filter dropped Savage Defense even though Druid
trainer 33 sells it at level 40. Bake (in fractured-tooling) now carves
out a small PASSIVE_TRAINER_ALLOWLIST and the regenerated
paragon_spell_ae_cost.sql + 2026_05_11_05.sql migration surface it on
the Druid Feral spell tab.
Co-authored-by: Cursor <cursoragent@cursor.com>
Cross-class wildcard now also relaxes the EquippedItemSubClassMask
gate on weapon-class proc talents so e.g. Maelstrom Weapon procs
from any weapon a Paragon has equipped, not just the talent's stock
melee subset (axe / mace / staff / fist / dagger / 2H sword/axe/mace).
Three independent gates run in the proc chain; all three needed the
bypass for the proc to actually fire:
- Player::HasItemFitToSpellRequirements -- talent-attach check at
item-equip / login time. Without this the passive talent aura
never even applies for a Paragon wielding a non-stock weapon.
- Player::CheckAttackFitToAuraRequirement -- per-swing match the
proc engine uses to decide whether attackType + weapon is
compatible with the aura's EquippedItemClass / SubClassMask.
- Aura::IsProcTriggeredOnEvent (SpellAuras.cpp) -- per-event proc
evaluator that calls Item::IsFitToSpellRequirements again,
independently of the previous two. Was the proc-killing gate
before this commit: talent attached, swing matched, but this
evaluator returned 0 charges for any weapon outside the stock
subclass mask, so no stack was ever applied.
All three bypasses are gated on:
- IsParagonWildcardCaller(this) (player class 12 + config flag
Paragon.WildcardFamilyMatching = 1).
- spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON. ARMOR-class
gates (shield) are deliberately left alone -- shield-required
talents (Shield Specialization, Shield Block, etc.) still need
an actual shield equipped.
Each bypass also requires *some* weapon to be in the relevant slot
(MAINHAND / OFFHAND / RANGED). Unarmed Paragons do not auto-activate
every weapon-gated talent in the game.
Net effect for Paragon characters with Maelstrom Weapon talented:
proc fires from any melee weapon -- 1H sword / polearm / spear /
fist / dagger / staff / 2H weapons / axes / maces. Stock Shamans
and every other non-Paragon class are unchanged.
Caveat (not addressed by this commit): the talent's stock ProcFlags
(0xC00014) only fire on melee swings + melee abilities. Ranged
auto-attacks (bow / gun / crossbow / wand) fire
PROC_FLAG_DONE_RANGED_AUTO_ATTACK (0x40), which the proc engine
never matches against this talent, so the new weapon-subclass
bypass is moot for those weapons. Adding ranged-auto-attack support
would require a Paragon-only ProcFlags expansion at the proc
engine layer; deferred until requested.
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
The skill-line cascade in Player::learnSkillRewardedSpells re-fires from
_LoadSkills (every login), UpdateSkillsForLevel (every level-up),
UpdateSkillPro (every weapon-skill tick on a training dummy), and
SetSkill (first time a class skill is granted). Each pass re-grants
every SkillLineAbility-tagged class ability on the matching skill line,
which leaks Blood Presence / Death Coil / Death Grip / etc. back into
the spellbook within seconds even after the player intentionally
refunded them via the Character Advancement panel.
Path B fix: a 5-line guard at the top of learnSkillRewardedSpells skips
the cascade for class-category skill lines on CLASS_PARAGON characters.
mod-paragon already calls Player::learnSpell directly for the abilities
the player actually purchased (and their attached passives), so the
panel becomes the sole authority over class abilities. Profession,
weapon, language, and racial cascades stay enabled so recipe auto-learn,
weapon proficiencies, and racial perks still work.
Side effect: passives that previously rode along on the cascade
(Forceful Deflection on Blood Strike, Runic Focus on Icy Touch) must be
force-attached the same way Blood Plague / Frost Fever already are.
Extend kAttached and kFixup in Paragon_Essence.cpp to do that; existing
characters self-heal on next login.
Backfill paragon_spell_ae_cost for 42 spells newly exposed by the panel
after the ClassMask=0 filter was removed from the client catalog
generator (Lava Burst, Hex, Evocation, Kill Shot, Path of Frost,
Horn of Winter, Rune Strike, Raise Ally, Dark Command, etc.). Migration
is INSERT IGNORE so any per-spell tuning on existing rows is preserved.
Co-authored-by: Cursor <cursoragent@cursor.com>
- PanelLearnSpellChain: record every non-chain passive as panel_spell_child;
only revoke non-passive (Blood Presence, Death Coil, Death Grip, etc.).
- RevokeUnwantedCascadeSpellsForPlayer: skip passive rewards on login sweep.
- RevokeBlockedSpellsForPlayer: migrate legacy passive revoke rows to
children; walk (parent, revoked) pairs from DB.
- PruneSkillLineCascadeChildrenFromDb: only strip actives wrongly stored as
children; never strip passives.
- SpellInfoCorrections: set SPELL_ATTR0_PASSIVE on Forceful Deflection (49410)
and Runic Focus (61455) so IsPassive() matches spellbook behavior.
- PanelUnlearnTalentPurchase: mirror resetTalents (_removeTalentAurasAndSpells,
_removeTalent, SendTalentsInfoData) so Beast Mastery loss triggers pet reset.
- OnPlayerLogin: run legacy passive attach before scoped cascade sweep.
- Add .paragon recalibrate GM command (RBAC modify): full panel reset + AE/TE
reconciliation for selected player or self.
Co-authored-by: Cursor <cursoragent@cursor.com>
- SpellInfoCorrections: zero Reagent/ReagentCount on spells with non-zero
SpellFamilyName so class abilities no longer require shards, candles,
etc., while profession crafts (SpellFamilyName 0) keep mats. Matches
the client Spell.dbc bake in patch-enUS-4.MPQ.
- Paragon_SC: OnPlayerIsClass returns true for CLASS_CONTEXT_EQUIP_RELIC
for paladin/druid/shaman/warlock/dk so Paragon can equip all relic types
in the ranged slot.
- CLIENT-PATCHES: document Spell.dbc reagent pass, rune script order, and
stock ammo slot behavior in patch-enUS-5.
Co-authored-by: Cursor <cursoragent@cursor.com>
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 <cursoragent@cursor.com>
Two paths still rejected glyph use on Paragon characters even after the
earlier AllowableClass server bypass:
1. Spell::CheckItems (server) treated cast-from-glyph as a normal
"equipped item required" cast and called HasItemFitToSpellRequirements,
which only handles weapon/armor and falls through default for
ITEM_CLASS_GLYPH -> SPELL_FAILED_EQUIPPED_ITEM_CLASS. Skip that check
when the cast item itself is the glyph.
2. The 3.3.5 client engine pre-checks ItemTemplate.AllowableClass against
the player's class locally and refuses the right-click before sending
CMSG_USE_ITEM, regardless of what the server would do. Bake the
Paragon class bit (1<<11 = 2048) into AllowableClass for every
class-restricted item via a mod-paragon SQL migration so the engine's
pre-check passes for class 12.
Cache caveat: clients that previously inspected an affected item have
the old AllowableClass cached in Cache/<locale>/itemcache.wdb; deleting
the Cache folder forces a re-query. The server also caches item_template
in memory at boot, so this migration only takes effect for clients after
a worldserver restart (or .reload item_template) once the SQL has been
applied -- DBUpdater handles the SQL automatically on the next start.
Co-authored-by: Cursor <cursoragent@cursor.com>
Paragon is a classless concept layered on top of WotLK class data: stock items and class glyphs gate equip / vendor visibility / loot rolls / AH 'usable' filter via ItemTemplate.AllowableClass, which never has the class-12 bit (0x800). Bypassing the gate at the five enforcement sites lets Paragon equip any class-restricted item -- including class glyphs, since EffectApplyGlyph itself has no class check beyond the item gate. Race / level / proficiency / skill / required-spell checks still apply, so Paragon can't skip baseline progression.
Co-authored-by: Cursor <cursoragent@cursor.com>
Player::LearnTalent enforces the column-arrow prereq (talentInfo->DependsOn)
even when called with command=true, so Character Advancement's commit path
was silently dropping any talent whose Talent.dbc row points to an unrelated
sibling -- e.g. Deep Wounds (depends on Improved Heroic Strike), Bloody
Vengeance (depends on Dark Conviction), Expose Weakness (depends on Lethal
Shots). Players spent points in the panel, hit Learn All, and the talent
silently never reached addTalent / OnPlayerLearnTalents -- the snapshot came
back without it and the client repainted the points as "unspent."
The Character Advancement panel gates progression via AE/TE essence cost,
not via the spec-tree column arrows, so the DependsOn rule doesn't apply to
class 12. Skip it for Paragon, mirroring the existing class-mask bypass a
few lines above.
Co-authored-by: Cursor <cursoragent@cursor.com>
The previous seed pinned auth/realmlist to production values
(`hsrwow.net` + RealmServerPort 47497), which silently bricked every
fresh local install: after auth login the realm hand-off pointed
clients at our public host, where their local credentials don't
exist, and they were dropped within a frame.
Seed now matches stock AzerothCore for solo dev:
- realmlist.address = 127.0.0.1 (was hsrwow.net)
- RealmServerPort = 3724 (was 47497)
Production owners apply both overrides post-dbimport via a one-shot
SQL UPDATE + an authserver.conf edit. Documented end-to-end in
contrib/fractured-dev-extras/BUILD-NATIVE.md (new "Production
deployment overrides" section) and the disconnect-after-login
symptom is called out in CLIENT-PATCHES.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
Use utf8mb4_unicode_ci in base SQL (MariaDB lacks utf8mb4_0900_ai_ci).
Add Updates.ExceptionShutdownDelay to dbimport.conf.dist to match
DBUpdater expectations.
- Parse real MariaDB version after MySQL 5.5.5 compatibility prefix for
DatabaseIncompatibleVersion checks.
- Relax client checks when building against MariaDB C connector
(MARIADB_VERSION_ID); keep Oracle MySQL 8.0+ rules otherwise.
- Use mysql_stmt_bind_param on MariaDB; mysql_stmt_bind_named_param only for
MySQL 8.3+ without MariaDB headers.
- SSL: MYSQL_OPT_SSL_ENFORCE on MariaDB connector, MYSQL_OPT_SSL_MODE elsewhere.
- Track custom_script_loader.cpp so static script link succeeds; CMake
find_package(MySQL) requires COMPONENTS lib on CMake 3.16.