Paragon: add Builds catalog (saved loadouts with pet park/unpark)

Server-side Character Advancement now stores named, icon-tagged build
recipes (panel-purchased spells + per-spec talent ranks) and atomically
swaps between them by snapshotting the active build, refunding AE/TE
through HandleParagonReset{Talents,Abilities}, and re-spending on the
target recipe. Hunter pets attached to a build are parked to
PET_SAVE_NOT_IN_SLOT (mirroring HandleStableSwapPet) so name, talents,
and exp survive swaps; non-hunter pets (warlock demon, DK ghoul, mage
water elemental) are NOT parked because the engine resummons them from
a fresh template each cast.

New PARAA verbs: Q BUILDS / C BUILD NEW / C BUILD EDIT / C BUILD
DELETE / C BUILD FAVORITE / C BUILD LOAD. The catalog is pushed on
login and after every mutation as a single addon message.

Schema (mod-paragon migration 2026_05_10_03.sql):
- character_paragon_builds (build_id PK, guid, name, icon, is_favorite,
  pet_number, created_at)
- character_paragon_build_spells (build_id, spell_id)
- character_paragon_build_talents (build_id, spec, talent_id, rank)
- character_paragon_active_build (guid PK, build_id)

The talent recipe table is spec-keyed so a build remembers tank/dps
dual-spec layouts independently. Swaps are blocked while in combat.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Docker Build
2026-05-10 02:35:55 -04:00
parent abb25f56d1
commit 7de018f7eb
3 changed files with 857 additions and 5 deletions
@@ -0,0 +1,62 @@
-- mod-paragon Character Advancement: Build catalog (saved loadouts).
-- ----------------------------------------------------------------------------
-- A "build" is a named, icon-tagged loadout of panel-purchased spells and
-- talent ranks. Each Paragon character can save many builds and swap
-- between them via the Builds page in the Character Advancement panel.
--
-- Swap workflow (see HandleBuildLoad in Paragon_Builds.cpp):
-- 1. If a build is currently active, snapshot the player's current
-- panel-purchased spells + per-spec talent ranks into that build's
-- recipe rows (overwriting the stored recipe).
-- 2. If the active build's hunter pet is currently summoned, unsummon
-- it to PET_SAVE_NOT_IN_SLOT and store its `pet_number` on the
-- active build row so it can be restored on swap-back.
-- 3. Reset all panel-bought abilities and talents (refunding AE/TE).
-- 4. Re-buy each spell + talent in the target build's recipe (charging
-- AE/TE; aborts if insufficient AE/TE -- player keeps refunded
-- currency in that case and active becomes NULL).
-- 5. Move the target build's parked pet (if any) back to current.
-- 6. Update active_build pointer.
--
-- Pet ownership: a parked pet sits in `character_pet` with slot=100
-- (PET_SAVE_NOT_IN_SLOT), exactly like the engine's stable-master
-- offload, but tied to the build via `pet_number` instead of any
-- in-game stable slot. Build deletion drops the parked pet rows
-- entirely (PET_SAVE_AS_DELETED equivalent) -- player is warned.
-- ----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `character_paragon_builds` (
`build_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`name` VARCHAR(32) NOT NULL,
`icon` VARCHAR(64) NOT NULL DEFAULT 'INV_Misc_QuestionMark',
`is_favorite` TINYINT UNSIGNED NOT NULL DEFAULT 0,
`pet_number` INT UNSIGNED NULL COMMENT 'character_pet.id of parked hunter pet, NULL when no pet bound to this build',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`build_id`),
KEY `idx_guid` (`guid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: saved Character Advancement build catalog';
CREATE TABLE IF NOT EXISTS `character_paragon_build_spells` (
`build_id` INT UNSIGNED NOT NULL,
`spell_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`build_id`, `spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: per-build recipe -- panel-purchased spells';
CREATE TABLE IF NOT EXISTS `character_paragon_build_talents` (
`build_id` INT UNSIGNED NOT NULL,
`spec` TINYINT UNSIGNED NOT NULL COMMENT '0 = primary spec, 1 = secondary (dual spec)',
`talent_id` SMALLINT UNSIGNED NOT NULL,
`rank` TINYINT UNSIGNED NOT NULL,
PRIMARY KEY (`build_id`, `spec`, `talent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: per-build recipe -- panel-purchased talent ranks per spec';
CREATE TABLE IF NOT EXISTS `character_paragon_active_build` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`build_id` INT UNSIGNED NOT NULL COMMENT 'character_paragon_builds.build_id (per-character active pointer)',
PRIMARY KEY (`guid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: pointer to whichever build is currently loaded (one row per Paragon character)';