Paragon: move module SQL to AC's data/sql/db-* layout for auto-update

AzerothCore's DBUpdater scans modules/<mod>/data/sql/db-{world,characters,auth}/
(see src/server/database/Updater/UpdateFetcher.cpp). The previous
modules/mod-paragon/sql/{world,characters}/base/ layout was non-standard
and skipped by the updater, so a fresh deploy never had Paragon tables
created automatically.

Move all five SQL files into the standard layout. Files are idempotent
(CREATE TABLE IF NOT EXISTS plus a DELETE+INSERT for the cost table)
and now apply on every dbimport / worldserver start, recorded by hash
in <db>.updates so re-runs are skipped.

Update BUILD-NATIVE.md to drop the manual mysql import section, and
document the SQL layout in the mod-paragon README.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Docker Build
2026-05-08 22:03:42 -04:00
parent 203356aca8
commit df7e943a74
7 changed files with 22 additions and 14 deletions
@@ -0,0 +1,9 @@
-- AE / TE currency storage (Paragon class progression).
-- Apply to the *character* database (same DB as `characters`, `character_spell`, etc.).
CREATE TABLE IF NOT EXISTS `character_paragon_currency` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`ability_essence` SMALLINT UNSIGNED NOT NULL DEFAULT '0',
`talent_essence` SMALLINT UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`guid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='mod-paragon: Ability Essence / Talent Essence';
@@ -0,0 +1,34 @@
-- Spells and talents learned only through Character Advancement (Lock In / .paragon learn).
-- Apply to the character database (same as `characters`, `character_spell`, etc.).
CREATE TABLE IF NOT EXISTS `character_paragon_panel_spells` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`spell_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`guid`, `spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: spells purchased via Character Advancement';
CREATE TABLE IF NOT EXISTS `character_paragon_panel_talents` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`talent_id` SMALLINT UNSIGNED NOT NULL,
`rank` TINYINT UNSIGNED NOT NULL,
PRIMARY KEY (`guid`, `talent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: talent ranks purchased via Character Advancement';
-- Passive "dependent" spells that AzerothCore's `addSpell` machinery
-- (via spell_learn_spell + SPELL_EFFECT_LEARN_SPELL) auto-grants when a
-- panel-purchased spell is learned. We keep them learned (some are
-- required for the parent ability to function -- e.g. Frost Fever for
-- Icy Touch, Blood Plague for Plague Strike) but record them here so
-- Reset Abilities / Reset Everything can unlearn them alongside the
-- parent. Only passive auto-learns are tracked here; active dependents
-- (Death Coil, Death Grip, ...) are revoked at learn time and never
-- reach this table.
CREATE TABLE IF NOT EXISTS `character_paragon_panel_spell_children` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`parent_spell_id` INT UNSIGNED NOT NULL COMMENT 'character_paragon_panel_spells.spell_id',
`child_spell_id` INT UNSIGNED NOT NULL COMMENT 'auto-learned passive spell id',
PRIMARY KEY (`guid`, `parent_spell_id`, `child_spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: passive auto-learn dependents to unlearn on reset';
@@ -0,0 +1,103 @@
-- mod-paragon: extend the gameTable SQL fallback tables to cover class 12.
--
-- AzerothCore loads each gtXxx DBC twice: first the .dbc file on disk, then
-- a `gtxxx_dbc` SQL fallback table that overrides / extends the in-memory
-- index (DBCDatabaseLoader::Load). For Player::OCTRegenMPPerSpirit,
-- Player::GetMissPercentageFromDefence, Player::GetRatingMultiplier and
-- friends, the SQL is the source of truth at runtime, not the DBC.
--
-- The vanilla acore_world ships these tables with rows for class 1..11 only.
-- For class 12 (Paragon), the lookup index is unmapped and every formula
-- silently returns 0 (mana regen, crit, dodge, rating multipliers).
--
-- We mirror Druid (class 11) values into class 12 -- druid is the closest
-- vanilla hybrid (mana + rage + energy across forms) and matches Paragon's
-- multi-resource intent. Same source class as the DBC patches and as
-- player_class_stats_paragon_basemana.sql, so the data stays consistent.
--
-- Lookup formulas (for cross-reference with src/server/game/Entities/Player):
-- gtChanceToMeleeCrit / gtChanceToSpellCrit / gt*RegenHP / gt*RegenMP /
-- gtRegenHPPerSpt / gtRegenMPPerSpt:
-- LookupEntry((class - 1) * GT_MAX_LEVEL + level - 1)
-- class 11 -> Ids 1000..1099 ; class 12 -> Ids 1100..1199
--
-- gtChanceToMeleeCritBase / gtChanceToSpellCritBase:
-- LookupEntry(class - 1)
-- class 11 -> Id 10 ; class 12 -> Id 11
--
-- gtOCTClassCombatRatingScalar:
-- LookupEntry((class - 1) * MAX_COMBAT_RATING + cr + 1)
-- class 11 -> Ids 321..352 ; class 12 -> Ids 353..384
--
-- Idempotent: each block deletes the class-12 Id range first, then re-clones
-- from class 11 via a materialised subquery (so we don't read the same
-- table we're inserting into in undefined order). Re-running the file is
-- safe and produces identical results.
--
-- Wrapped in a single transaction so a mid-script error rolls back the
-- whole thing.
--
-- Apply to acore_world.
START TRANSACTION;
-- gtChanceToMeleeCrit ---------------------------------------------------------
DELETE FROM `gtchancetomeleecrit_dbc` WHERE `ID` BETWEEN 1100 AND 1199;
INSERT INTO `gtchancetomeleecrit_dbc` (`ID`, `Data`)
SELECT t.ID + 100, t.Data
FROM (SELECT `ID`, `Data` FROM `gtchancetomeleecrit_dbc` WHERE `ID` BETWEEN 1000 AND 1099) AS t;
-- gtChanceToSpellCrit ---------------------------------------------------------
DELETE FROM `gtchancetospellcrit_dbc` WHERE `ID` BETWEEN 1100 AND 1199;
INSERT INTO `gtchancetospellcrit_dbc` (`ID`, `Data`)
SELECT t.ID + 100, t.Data
FROM (SELECT `ID`, `Data` FROM `gtchancetospellcrit_dbc` WHERE `ID` BETWEEN 1000 AND 1099) AS t;
-- gtOCTRegenHP ----------------------------------------------------------------
DELETE FROM `gtoctregenhp_dbc` WHERE `ID` BETWEEN 1100 AND 1199;
INSERT INTO `gtoctregenhp_dbc` (`ID`, `Data`)
SELECT t.ID + 100, t.Data
FROM (SELECT `ID`, `Data` FROM `gtoctregenhp_dbc` WHERE `ID` BETWEEN 1000 AND 1099) AS t;
-- gtRegenHPPerSpt -------------------------------------------------------------
DELETE FROM `gtregenhpperspt_dbc` WHERE `ID` BETWEEN 1100 AND 1199;
INSERT INTO `gtregenhpperspt_dbc` (`ID`, `Data`)
SELECT t.ID + 100, t.Data
FROM (SELECT `ID`, `Data` FROM `gtregenhpperspt_dbc` WHERE `ID` BETWEEN 1000 AND 1099) AS t;
-- gtRegenMPPerSpt -------------------------------------------------------------
DELETE FROM `gtregenmpperspt_dbc` WHERE `ID` BETWEEN 1100 AND 1199;
INSERT INTO `gtregenmpperspt_dbc` (`ID`, `Data`)
SELECT t.ID + 100, t.Data
FROM (SELECT `ID`, `Data` FROM `gtregenmpperspt_dbc` WHERE `ID` BETWEEN 1000 AND 1099) AS t;
-- gtChanceToMeleeCritBase -----------------------------------------------------
DELETE FROM `gtchancetomeleecritbase_dbc` WHERE `ID` = 11;
INSERT INTO `gtchancetomeleecritbase_dbc` (`ID`, `Data`)
SELECT t.ID + 1, t.Data
FROM (SELECT `ID`, `Data` FROM `gtchancetomeleecritbase_dbc` WHERE `ID` = 10) AS t;
-- gtChanceToSpellCritBase -----------------------------------------------------
DELETE FROM `gtchancetospellcritbase_dbc` WHERE `ID` = 11;
INSERT INTO `gtchancetospellcritbase_dbc` (`ID`, `Data`)
SELECT t.ID + 1, t.Data
FROM (SELECT `ID`, `Data` FROM `gtchancetospellcritbase_dbc` WHERE `ID` = 10) AS t;
-- gtOCTClassCombatRatingScalar (32 ratings per class, 1-based Ids) ------------
DELETE FROM `gtoctclasscombatratingscalar_dbc` WHERE `ID` BETWEEN 353 AND 384;
INSERT INTO `gtoctclasscombatratingscalar_dbc` (`ID`, `Data`)
SELECT t.ID + 32, t.Data
FROM (SELECT `ID`, `Data` FROM `gtoctclasscombatratingscalar_dbc` WHERE `ID` BETWEEN 321 AND 352) AS t;
COMMIT;
-- Sanity check (read-only). Expected class-12 counts:
-- 100 / 100 / 100 / 100 / 100 / 1 / 1 / 32
-- SELECT 'mc' cnt, COUNT(*) FROM gtchancetomeleecrit_dbc WHERE ID BETWEEN 1100 AND 1199
-- UNION ALL SELECT 'sc' cnt, COUNT(*) FROM gtchancetospellcrit_dbc WHERE ID BETWEEN 1100 AND 1199
-- UNION ALL SELECT 'orh' cnt, COUNT(*) FROM gtoctregenhp_dbc WHERE ID BETWEEN 1100 AND 1199
-- UNION ALL SELECT 'rh' cnt, COUNT(*) FROM gtregenhpperspt_dbc WHERE ID BETWEEN 1100 AND 1199
-- UNION ALL SELECT 'rm' cnt, COUNT(*) FROM gtregenmpperspt_dbc WHERE ID BETWEEN 1100 AND 1199
-- UNION ALL SELECT 'mcb' cnt, COUNT(*) FROM gtchancetomeleecritbase_dbc WHERE ID = 11
-- UNION ALL SELECT 'scb' cnt, COUNT(*) FROM gtchancetospellcritbase_dbc WHERE ID = 11
-- UNION ALL SELECT 'crs' cnt, COUNT(*) FROM gtoctclasscombatratingscalar_dbc WHERE ID BETWEEN 353 AND 384;
@@ -0,0 +1,446 @@
-- Per-spell AE costs for Paragon spell purchases (.paragon learn / panel Lock In).
-- Auto-generated by tools/_gen_paragon_spell_ae_cost_sql.py.
-- Apply to the *world* database (AzerothCore's SQL updater handles this on worldserver start).
-- The flat 1-AE cost is a Phase 3 placeholder; tune individual rows here as the
-- economy gets balanced (e.g., 5 AE for top-rank baseline like Cyclone).
CREATE TABLE IF NOT EXISTS `paragon_spell_ae_cost` (
`spell_id` INT UNSIGNED NOT NULL,
`ae_cost` SMALLINT UNSIGNED NOT NULL DEFAULT '1',
PRIMARY KEY (`spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: AE cost per spell';
-- Bulk-load: replace the entire table with the current bake. Manual edits
-- to specific rows will be lost when this script regenerates the file --
-- track per-spell tuning in a separate INSERT ... ON DUPLICATE KEY UPDATE
-- file (e.g. paragon_spell_ae_cost_overrides.sql) if needed.
DELETE FROM `paragon_spell_ae_cost`;
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(10, 1),
(17, 1),
(53, 1),
(72, 1),
(75, 1),
(78, 1),
(99, 1),
(100, 1),
(116, 1),
(118, 1),
(120, 1),
(122, 1),
(130, 1),
(131, 1),
(132, 1),
(133, 1),
(136, 1),
(139, 1),
(168, 1),
(172, 1),
(324, 1),
(331, 1),
(339, 1),
(348, 1),
(370, 1),
(403, 1),
(408, 1),
(421, 1),
(453, 1),
(465, 1),
(467, 1),
(469, 1),
(475, 1),
(498, 1),
(527, 1),
(528, 1),
(543, 1),
(546, 1),
(552, 1),
(556, 1),
(585, 1),
(586, 1),
(587, 1),
(588, 1),
(589, 1),
(596, 1),
(603, 1),
(604, 1),
(605, 1),
(633, 1),
(635, 1),
(642, 1),
(676, 1),
(686, 1),
(687, 1),
(689, 1),
(693, 1),
(694, 1),
(698, 1),
(702, 1),
(703, 1),
(706, 1),
(710, 1),
(740, 1),
(755, 1),
(759, 1),
(770, 1),
(772, 1),
(774, 1),
(779, 1),
(781, 1),
(845, 1),
(853, 1),
(871, 1),
(879, 1),
(883, 1),
(921, 1),
(976, 1),
(980, 1),
(982, 1),
(1002, 1),
(1008, 1),
(1022, 1),
(1038, 1),
(1044, 1),
(1064, 1),
(1079, 1),
(1082, 1),
(1098, 1),
(1120, 1),
(1126, 1),
(1130, 1),
(1152, 1),
(1160, 1),
(1161, 1),
(1243, 1),
(1449, 1),
(1454, 1),
(1459, 1),
(1462, 1),
(1463, 1),
(1464, 1),
(1490, 1),
(1494, 1),
(1495, 1),
(1499, 1),
(1510, 1),
(1513, 1),
(1515, 1),
(1535, 1),
(1543, 1),
(1680, 1),
(1706, 1),
(1714, 1),
(1715, 1),
(1719, 1),
(1725, 1),
(1752, 1),
(1766, 1),
(1776, 1),
(1784, 1),
(1822, 1),
(1833, 1),
(1842, 1),
(1850, 1),
(1856, 1),
(1943, 1),
(1949, 1),
(1953, 1),
(1966, 1),
(1978, 1),
(2006, 1),
(2008, 1),
(2050, 1),
(2054, 1),
(2060, 1),
(2061, 1),
(2062, 1),
(2094, 1),
(2096, 1),
(2098, 1),
(2120, 1),
(2136, 1),
(2139, 1),
(2362, 1),
(2457, 1),
(2458, 1),
(2484, 1),
(2565, 1),
(2637, 1),
(2641, 1),
(2643, 1),
(2645, 1),
(2687, 1),
(2782, 1),
(2812, 1),
(2825, 1),
(2893, 1),
(2908, 1),
(2912, 1),
(2944, 1),
(2948, 1),
(2973, 1),
(2974, 1),
(2983, 1),
(3034, 1),
(3043, 1),
(3044, 1),
(3045, 1),
(3411, 1),
(3561, 1),
(3562, 1),
(3563, 1),
(3565, 1),
(3566, 1),
(3567, 1),
(3738, 1),
(4987, 1),
(5116, 1),
(5118, 1),
(5138, 1),
(5143, 1),
(5171, 1),
(5176, 1),
(5185, 1),
(5209, 1),
(5211, 1),
(5215, 1),
(5217, 1),
(5221, 1),
(5225, 1),
(5229, 1),
(5246, 1),
(5277, 1),
(5308, 1),
(5384, 1),
(5484, 1),
(5500, 1),
(5502, 1),
(5504, 1);
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(5675, 1),
(5676, 1),
(5697, 1),
(5730, 1),
(5740, 1),
(5782, 1),
(5938, 1),
(6117, 1),
(6143, 1),
(6196, 1),
(6197, 1),
(6201, 1),
(6229, 1),
(6343, 1),
(6346, 1),
(6353, 1),
(6366, 1),
(6495, 1),
(6552, 1),
(6572, 1),
(6673, 1),
(6770, 1),
(6785, 1),
(6789, 1),
(6940, 1),
(7294, 1),
(7302, 1),
(7384, 1),
(8004, 1),
(8017, 1),
(8024, 1),
(8033, 1),
(8042, 1),
(8050, 1),
(8056, 1),
(8075, 1),
(8092, 1),
(8122, 1),
(8129, 1),
(8143, 1),
(8170, 1),
(8177, 1),
(8181, 1),
(8184, 1),
(8190, 1),
(8227, 1),
(8232, 1),
(8512, 1),
(8647, 1),
(8676, 1),
(8921, 1),
(8936, 1),
(8998, 1),
(9005, 1),
(9484, 1),
(10059, 1),
(10326, 1),
(10595, 1),
(11416, 1),
(11417, 1),
(11418, 1),
(11419, 1),
(11420, 1),
(13159, 1),
(13161, 1),
(13163, 1),
(13165, 1),
(13795, 1),
(13809, 1),
(13813, 1),
(14752, 1),
(14914, 1),
(15237, 1),
(16689, 1),
(16857, 1),
(16914, 1),
(18499, 1),
(19740, 1),
(19742, 1),
(19746, 1),
(19750, 1),
(19752, 1),
(19801, 1),
(19876, 1),
(19878, 1),
(19879, 1),
(19880, 1),
(19882, 1),
(19883, 1),
(19884, 1),
(19885, 1),
(19888, 1),
(19891, 1),
(20043, 1),
(20154, 1),
(20164, 1),
(20165, 1),
(20166, 1),
(20217, 1),
(20230, 1),
(20252, 1),
(20484, 1),
(20736, 1),
(21084, 1),
(21562, 1),
(21849, 1),
(22568, 1),
(22570, 1),
(22812, 1),
(22842, 1),
(23028, 1),
(23920, 1),
(23922, 1),
(24275, 1),
(25780, 1),
(25782, 1),
(25894, 1),
(25898, 1),
(25899, 1),
(26573, 1),
(26679, 1),
(27243, 1),
(27681, 1),
(27683, 1),
(28176, 1),
(29166, 1),
(29722, 1),
(29858, 1),
(29893, 1),
(30451, 1),
(30455, 1),
(30482, 1),
(31224, 1),
(31789, 1),
(31801, 1),
(31884, 1),
(32182, 1),
(32223, 1),
(32266, 1),
(32267, 1),
(32271, 1),
(32272, 1),
(32375, 1),
(32379, 1),
(32546, 1),
(32645, 1),
(33076, 1),
(33690, 1),
(33691, 1),
(33745, 1),
(33763, 1),
(33786, 1),
(34026, 1),
(34074, 1),
(34428, 1),
(34433, 1),
(34477, 1),
(34600, 1),
(35715, 1),
(35717, 1),
(36936, 1),
(42650, 1),
(42955, 1),
(43265, 1),
(43987, 1),
(44614, 1),
(45438, 1),
(45462, 1),
(45477, 1),
(45524, 1),
(45529, 1),
(45902, 1),
(46584, 1),
(47476, 1),
(47528, 1),
(47541, 1),
(47568, 1),
(47897, 1),
(48018, 1),
(48020, 1),
(48045, 1),
(48263, 1),
(48265, 1),
(48266, 1),
(48707, 1),
(48721, 1),
(48743, 1),
(48792, 1),
(49020, 1),
(49358, 1),
(49359, 1),
(49360, 1),
(49361, 1),
(49576, 1),
(49998, 1),
(50464, 1),
(50842, 1),
(51722, 1),
(51723, 1),
(52610, 1);
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(53140, 1),
(53142, 1),
(53407, 1),
(53408, 1),
(53600, 1),
(53601, 1),
(53736, 1),
(54428, 1),
(55342, 1),
(55694, 1),
(56641, 1),
(57755, 1),
(57934, 1),
(57994, 1),
(60192, 1),
(61846, 1),
(62078, 1),
(62124, 1),
(62757, 1),
(64382, 1),
(64843, 1);
@@ -0,0 +1,26 @@
-- mod-paragon: give class 12 (Paragon) a real mana pool.
--
-- The original Paragon install cloned warrior rows into player_class_stats
-- for class 12, which left basemana = 0 at every level. That makes
-- Player::GetCreateMana() return 0 on login, so Player::UpdateMaxPower(POWER_MANA)
-- writes UNIT_FIELD_MAXPOWER1 = 0 and the client renders an empty mana bar even
-- though our PlayerFrame is now showing the slot.
--
-- We mirror Druid (class 11) basemana values: druid is the closest vanilla
-- hybrid (mana + rage + energy across forms), which matches Paragon's
-- multi-resource design philosophy. basehp is left untouched (already cloned
-- from warrior, which is fine for a melee-leaning hybrid).
--
-- Apply to the *characters* database? No -- player_class_stats lives in the
-- world DB. Apply to acore_world.
--
-- Re-runs are safe: this is an idempotent UPDATE, not an INSERT.
UPDATE `player_class_stats` p12
JOIN `player_class_stats` p11 ON p11.class = 11 AND p11.level = p12.level
SET p12.basemana = p11.basemana
WHERE p12.class = 12;
-- Sanity check (informational, no side-effects):
-- SELECT class, level, basehp, basemana FROM player_class_stats
-- WHERE class IN (11, 12) AND level IN (1, 40, 80) ORDER BY class, level;