Fractured: Paragon core hooks, mod-paragon, mod-ale, Docker build cap

- Track mod-paragon and mod-ale (un-ignore modules in .gitignore).
- Ship docker-compose.override.yml with CMAKE_EXTRA_OPTIONS for LuaJIT (mod-ale).
- Dockerfile: CBUILD_PARALLEL default to limit OOM under Docker/WSL2.
- Core: CLASS_PARAGON sticky combo points (DetachComboTarget), selection rebind,
  Spell::CheckPower rune path for multi-resource Paragon.
- spell_dk_death_rune: IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY) for
  Blood of the North / Reaping / DRM on Paragon.
- Remove temporary Paragon CheckPower logging.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Docker Build
2026-05-08 00:03:09 -04:00
parent f9f2bc5e0c
commit 8e4c8f57e4
163 changed files with 54817 additions and 10 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,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,11 @@
-- Optional per-spell AE costs for Paragon spell purchases (.paragon learn).
-- Apply to the *world* database.
CREATE TABLE IF NOT EXISTS `paragon_spell_ae_cost` (
`spell_id` INT UNSIGNED NOT NULL,
`ae_cost` SMALLINT UNSIGNED NOT NULL DEFAULT '2',
PRIMARY KEY (`spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='mod-paragon: AE cost per spell';
-- Example (uncomment to use):
-- INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES (55050, 2);
@@ -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;