Two related additions to mod-paragon:
* HandleCommit gains a third payload section, " u:<id>,...", carrying
spell IDs the player wants to refund + unlearn in the same commit
that learns / talents through. The protocol stays backward-compat
(older clients omit the section). PanelUnlearnSpellPurchase mirrors
the per-spell branch of HandleParagonResetAbilities: tracked passive
children are removed first, then the chain head, then panel_spells /
panel_spell_children / panel_spell_revoked rows for that purchase
are dropped, then LookupSpellAECost(head) is refunded into the
cache. Unlearns are applied before learns inside the commit so the
refund covers the same-commit spends. Allow-list for the silence
window now includes chain ranks + panel_spell_children for the
intentional unlearns so "You have unlearned X" toasts stay visible
for the targeted spell while cascade dependents stay silenced.
* ReconcileEssenceForPlayer reads panel_spells + panel_talents and
sets the cache to ComputeStartingAE/TE(level) - sum-of-spends.
Self-heals drift in either direction: clamps the cache down when
the player has more essence than their level + spends allow
(cheese clamp), and tops up when they have less (admin-tweak /
crash recovery). Wired into OnPlayerLogin (after LoadCurrencyFromDb,
before PushCurrency so the first balance the client sees is the
reconciled one) and OnPlayerLevelChanged (replaces the old
GrantLevelUpEssence delta -- Reconcile sets the absolute correct
balance from level + spend, so it subsumes the per-level grant and
the cheese clamp in one call). Costs come from the same
paragon_spell_ae_cost / config keys HandleCommit uses so the math
stays in lockstep across any future cost rebalance.
Both features ship in patch-enUS-6.MPQ v0.9.16: right-click a learned
spell row to queue an unlearn (header shows +N AE refund preview) and
hit Learn All to apply. The icon picker also got two fixes -- the
leading INV_Misc_QuestionMark is no longer duplicated, and the
selection ring is now a tooltip-border Frame anchored to the cell
bounds (the prior UI-ActionButton-Border texture rendered nearly
invisible at non-native sizes).
Co-authored-by: Cursor <cursoragent@cursor.com>
mod-paragon
Server-side support module for the custom CLASS_PARAGON (id 12).
What it does today
Hooks Player::IsClass / Player::HasActivePowerType so Paragon
inherits Death Knight ability mechanics where it matters:
- Rune system:
InitRunes, rune cooldown tick, runic power regen,Spell::CheckRuneCost/Spell::TakeRunePower,SMSG_RESYNC_RUNEScast flags, combat exit grace reset. - DK ability scripts:
spell_dk_*,MUST_BE_DEATH_KNIGHTcast result, DK aura effects.
It is intentionally narrow: the hook only fires for
(realClass == PARAGON, queriedClass == DEATH_KNIGHT, context == CLASS_CONTEXT_ABILITY).
Other DK-flavored contexts (Ebon Hold teleport gating, heroic start
level, DK-only taxi mount, talent point math on Ebon Hold, etc.) keep
their normal behavior for Paragon.
HasActivePowerType additionally claims POWER_RUNIC_POWER and
POWER_RUNE for Paragon, which keeps the rune pool sized correctly
in Player::InitStatsForLevel even if Paragon's primary power type is
later switched away from runic power.
Building
Auto-detected by modules/CMakeLists.txt (GetModuleSourceList globs
every subdirectory). No additional CMake plumbing is needed; rebuild
the worldserver image and the loader symbol Addmod_paragonScripts
gets linked into the static modules target.
SQL layout
SQL files live under data/sql/db-world/base/ and
data/sql/db-characters/base/ — the standard AzerothCore module path
that the built-in DBUpdater scans (see
src/server/database/Updater/UpdateFetcher.cpp). Files placed there
are applied automatically by worldserver / dbimport on startup and
recorded by hash in the updates table of the target database, so
re-runs are idempotent. Any new SQL added under those directories will
be picked up on the next container/server start without manual import.