From 87219cb4ebb37a5f1d1c5e749c94c3d454340950 Mon Sep 17 00:00:00 2001 From: Docker Build Date: Tue, 12 May 2026 19:20:13 -0400 Subject: [PATCH] Paragon: multidot Devouring Plague, stance/presence clones, advancement SLA - Allow multiple Devouring Plague DoTs on different targets (core + DK script). - Warrior stance and DK presence clone spells for Character Advancement; spellbook SkillLineAbility rows and aura/shapeshift attribute fixes. - World SQL updates 2026_05_12_02 through 07 (mod-paragon db-world). Client patch-enUS-4/5/6 and Wow.exe ship on the matching GitHub Release (not in repo). Co-authored-by: Cursor --- ...2_02_paragon_multidot_devouring_plague.sql | 21 +++++ ...3_paragon_multidot_dp_skilllineability.sql | 15 ++++ ..._04_paragon_adv_stance_presence_clones.sql | 21 +++++ ...2_05_paragon_dk_presence_clone_scripts.sql | 9 +++ ..._paragon_adv_sla_spellbook_and_attrfix.sql | 8 ++ src/server/game/Entities/Unit/Unit.cpp | 10 +++ src/server/game/Entities/Unit/UnitDefines.h | 8 +- .../game/Spells/Auras/SpellAuraEffects.cpp | 61 ++++++++++---- src/server/game/Spells/SpellInfo.cpp | 4 + .../game/Spells/SpellInfoCorrections.cpp | 21 +++++ src/server/scripts/Spells/spell_dk.cpp | 79 +++++++++++++++---- 11 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 modules/mod-paragon/data/sql/db-world/updates/2026_05_12_02_paragon_multidot_devouring_plague.sql create mode 100644 modules/mod-paragon/data/sql/db-world/updates/2026_05_12_03_paragon_multidot_dp_skilllineability.sql create mode 100644 modules/mod-paragon/data/sql/db-world/updates/2026_05_12_04_paragon_adv_stance_presence_clones.sql create mode 100644 modules/mod-paragon/data/sql/db-world/updates/2026_05_12_05_paragon_dk_presence_clone_scripts.sql create mode 100644 modules/mod-paragon/data/sql/db-world/updates/2026_05_12_07_paragon_adv_sla_spellbook_and_attrfix.sql diff --git a/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_02_paragon_multidot_devouring_plague.sql b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_02_paragon_multidot_devouring_plague.sql new file mode 100644 index 0000000..ee192ba --- /dev/null +++ b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_02_paragon_multidot_devouring_plague.sql @@ -0,0 +1,21 @@ +-- Fractured / Paragon: multidot Devouring Plague clone (spell IDs 951000-951008). +-- Spell rows live in the patched client Spell.dbc (see fractured-tooling +-- from-workspace-root/_patch_spell_dbc_paragon_multidot_devouring_plague.py). +-- Deploy the same Spell.dbc into the worldserver `data/dbc/` folder OR import +-- equivalent `spell_dbc` rows from a full exporter; stock SQL cannot express +-- the SpellEntryfmt NA padding columns safely in one INSERT here. + +DELETE FROM `spell_ranks` WHERE `first_spell_id` = 951000; +INSERT INTO `spell_ranks` (`first_spell_id`,`spell_id`,`rank`) VALUES +(951000,951000,1), +(951000,951001,2), +(951000,951002,3), +(951000,951003,4), +(951000,951004,5), +(951000,951005,6), +(951000,951006,7), +(951000,951007,8), +(951000,951008,9); + +DELETE FROM `paragon_spell_ae_cost` WHERE `spell_id` IN (2944,951000); +INSERT INTO `paragon_spell_ae_cost` (`spell_id`,`ae_cost`) VALUES (951000, 1); diff --git a/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_03_paragon_multidot_dp_skilllineability.sql b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_03_paragon_multidot_dp_skilllineability.sql new file mode 100644 index 0000000..25b4afa --- /dev/null +++ b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_03_paragon_multidot_dp_skilllineability.sql @@ -0,0 +1,15 @@ +-- Fractured / Paragon: spellbook tab for multidot Devouring Plague (951000 chain). +-- Shadow priest skill line (78); ClassMask 2064 matches mod-paragon SLA overlay. +-- Client: patched SkillLineAbility.dbc in patch-enUS-4 from the same script. + +DELETE FROM `skilllineability_dbc` WHERE `ID` IN (1951000, 1951001, 1951002, 1951003, 1951004, 1951005, 1951006, 1951007, 1951008); +INSERT INTO `skilllineability_dbc` (`ID`,`SkillLine`,`Spell`,`RaceMask`,`ClassMask`,`ExcludeRace`,`ExcludeClass`,`MinSkillLineRank`,`SupercededBySpell`,`AcquireMethod`,`TrivialSkillLineRankHigh`,`TrivialSkillLineRankLow`,`CharacterPoints_1`,`CharacterPoints_2`) VALUES + (1951000,78,951000,0,2064,0,0,1,0,0,0,0,0,0), + (1951001,78,951001,0,2064,0,0,1,0,0,0,0,0,0), + (1951002,78,951002,0,2064,0,0,1,0,0,0,0,0,0), + (1951003,78,951003,0,2064,0,0,1,0,0,0,0,0,0), + (1951004,78,951004,0,2064,0,0,1,0,0,0,0,0,0), + (1951005,78,951005,0,2064,0,0,1,0,0,0,0,0,0), + (1951006,78,951006,0,2064,0,0,1,0,0,0,0,0,0), + (1951007,78,951007,0,2064,0,0,1,0,0,0,0,0,0), + (1951008,78,951008,0,2064,0,0,1,0,0,0,0,0,0); diff --git a/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_04_paragon_adv_stance_presence_clones.sql b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_04_paragon_adv_stance_presence_clones.sql new file mode 100644 index 0000000..86199b1 --- /dev/null +++ b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_04_paragon_adv_stance_presence_clones.sql @@ -0,0 +1,21 @@ +-- Fractured / Paragon: Character Advancement stance/presence clones (951010-951015). +-- Client: patched Spell.dbc + SpellShapeshiftForm.dbc + SkillLineAbility.dbc in patch-enUS-4.MPQ. +-- Server: copy Spell.dbc + SpellShapeshiftForm.dbc into `data/dbc/` (SpellShapeshiftForm is not in stock MPQ); SkillLineAbility is DB-driven on server. + +DELETE FROM `paragon_spell_ae_cost` WHERE `spell_id` IN (951010,951011,951012,951013,951014,951015); +INSERT INTO `paragon_spell_ae_cost` (`spell_id`,`ae_cost`) VALUES + (951010, 1), + (951011, 1), + (951012, 1), + (951013, 1), + (951014, 1), + (951015, 1); + +DELETE FROM `skilllineability_dbc` WHERE `ID` IN (1951020,1951021,1951022,1951023,1951024,1951025); +INSERT INTO `skilllineability_dbc` (`ID`,`SkillLine`,`Spell`,`RaceMask`,`ClassMask`,`ExcludeRace`,`ExcludeClass`,`MinSkillLineRank`,`SupercededBySpell`,`AcquireMethod`,`TrivialSkillLineRankHigh`,`TrivialSkillLineRankLow`,`CharacterPoints_1`,`CharacterPoints_2`) VALUES + (1951020,26,951010,0,2049,0,0,1,0,2,0,0,0,0), + (1951021,257,951011,0,2049,0,0,1,0,0,0,0,0,0), + (1951022,256,951012,0,2049,0,0,1,0,0,0,0,0,0), + (1951023,770,951013,0,2080,0,0,1,0,2,0,0,0,0), + (1951024,771,951014,0,2080,0,0,1,0,0,0,0,0,0), + (1951025,772,951015,0,2080,0,0,1,0,0,0,0,0,0); diff --git a/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_05_paragon_dk_presence_clone_scripts.sql b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_05_paragon_dk_presence_clone_scripts.sql new file mode 100644 index 0000000..8b8caaf --- /dev/null +++ b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_05_paragon_dk_presence_clone_scripts.sql @@ -0,0 +1,9 @@ +-- Fractured / Paragon: run spell_dk_presence on Character Advancement DK presence clones (951013-951015). +-- Spell.dbc sets SpellFamilyName=0 on these rows (see fractured-tooling/_patch_spell_dbc_paragon_stance_presence_clones.py) +-- so the stock client does not map them onto DK stance buttons; core still needs the aura script for Improved Presence. + +DELETE FROM `spell_script_names` WHERE `spell_id` IN (951013, 951014, 951015); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(951013, 'spell_dk_presence'), +(951014, 'spell_dk_presence'), +(951015, 'spell_dk_presence'); diff --git a/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_07_paragon_adv_sla_spellbook_and_attrfix.sql b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_07_paragon_adv_sla_spellbook_and_attrfix.sql new file mode 100644 index 0000000..b594a8b --- /dev/null +++ b/modules/mod-paragon/data/sql/db-world/updates/2026_05_12_07_paragon_adv_sla_spellbook_and_attrfix.sql @@ -0,0 +1,8 @@ +-- Fractured / Paragon: Character Advancement stance/presence clones — spellbook + client bits. +-- 1) SkillLineAbility: DK presence clones belong on 770/771/772 (Blood/Frost/Unholy tabs), not 760 (General). +-- (760 was an experiment; stance bar visibility is driven by Spell.dbc AttributesEx2 USE_SHAPESHIFT_BAR.) +-- 2) Idempotent if rows already match. + +UPDATE `skilllineability_dbc` SET `SkillLine` = 770 WHERE `ID` = 1951023 AND `Spell` = 951013; +UPDATE `skilllineability_dbc` SET `SkillLine` = 771 WHERE `ID` = 1951024 AND `Spell` = 951014; +UPDATE `skilllineability_dbc` SET `SkillLine` = 772 WHERE `ID` = 1951025 AND `Spell` = 951015; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 49d2cc5..8b13873 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -151,6 +151,11 @@ bool IsFracturedExclusiveStanceSpell(uint32 spellId) case 71: // Defensive Stance case 2458: // Berserker Stance + // -- Paragon Advancement warrior stance clones (951010-951012). + case 951010: + case 951011: + case 951012: + // -- Druid combat forms (engine-shapeshifts). case 5487: // Bear Form case 9634: // Dire Bear Form @@ -189,6 +194,11 @@ bool IsFracturedExclusiveStanceSpell(uint32 spellId) case 48263: // Frost Presence case 48265: // Unholy Presence + // -- Paragon Advancement DK presence clones (951013-951015). + case 951013: + case 951014: + case 951015: + // -- Hunter Aspects (combat). Like presences, these are regular // auras stock AC, not engine-shapeshifts; rank-1 ids cover all // ranks via GetFirstRankSpell. Cheetah / Pack are the utility diff --git a/src/server/game/Entities/Unit/UnitDefines.h b/src/server/game/Entities/Unit/UnitDefines.h index 3b81afc..200c090 100644 --- a/src/server/game/Entities/Unit/UnitDefines.h +++ b/src/server/game/Entities/Unit/UnitDefines.h @@ -99,7 +99,13 @@ enum ShapeshiftForm FORM_FLIGHT = 0x1D, FORM_STEALTH = 0x1E, FORM_MOONKIN = 0x1F, - FORM_SPIRITOFREDEMPTION = 0x20 + FORM_SPIRITOFREDEMPTION = 0x20, + + // Fractured / Paragon: Character Advancement warrior stance clones (Spell.dbc + // MOD_SHAPESHIFT -> SpellShapeshiftForm 33-35, BonusActionBar=0, no client bar swap). + FORM_PARAGON_BATTLE_STANCE = 33, + FORM_PARAGON_DEFENSIVE_STANCE = 34, + FORM_PARAGON_BERSERKER_STANCE = 35, }; enum ShapeshiftFlags diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index ba55952..0dc9c8d 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -1373,12 +1373,15 @@ void AuraEffect::HandleShapeshiftBoosts(Unit* target, bool apply) const HotWSpellId = 24899; break; case FORM_BATTLESTANCE: + case FORM_PARAGON_BATTLE_STANCE: spellId = 21156; break; case FORM_DEFENSIVESTANCE: + case FORM_PARAGON_DEFENSIVE_STANCE: spellId = 7376; break; case FORM_BERSERKERSTANCE: + case FORM_PARAGON_BERSERKER_STANCE: spellId = 7381; break; case FORM_MOONKIN: @@ -1995,7 +1998,7 @@ void AuraEffect::HandlePhase(AuraApplication const* aurApp, uint8 mode, bool app /*** UNIT MODEL ***/ /**********************/ -void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mode, bool apply) const +static void Fractured_ApplyShapeshiftFormFromAuraEffect(AuraEffect const* aurEff, AuraApplication const* aurApp, uint8 mode, bool apply, ShapeshiftForm form) { if (!(mode & AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK)) return; @@ -2004,8 +2007,6 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo uint32 modelid = 0; Powers PowerType = POWER_MANA; - ShapeshiftForm form = ShapeshiftForm(GetMiscValue()); - switch (form) { case FORM_CAT: // 0x01 @@ -2019,6 +2020,9 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo case FORM_BATTLESTANCE: // 0x11 case FORM_DEFENSIVESTANCE: // 0x12 case FORM_BERSERKERSTANCE: // 0x13 + case FORM_PARAGON_BATTLE_STANCE: + case FORM_PARAGON_DEFENSIVE_STANCE: + case FORM_PARAGON_BERSERKER_STANCE: PowerType = POWER_RAGE; break; @@ -2049,10 +2053,10 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo case FORM_SPIRITOFREDEMPTION: // 0x20 break; default: - LOG_ERROR("spells.aura.effect", "Auras: Unknown Shapeshift Type: {}", GetMiscValue()); + LOG_ERROR("spells.aura.effect", "Auras: Unknown Shapeshift Type: {}", aurEff->GetMiscValue()); } - modelid = target->GetModelForForm(form, GetId()); + modelid = target->GetModelForForm(form, aurEff->GetId()); if (apply) { @@ -2087,8 +2091,8 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo // remove other shapeshift before applying a new one // xinef: rogue shouldnt be wrapped by this check (shadow dance vs stealth) - if (GetSpellInfo()->SpellFamilyName != SPELLFAMILY_ROGUE) - target->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT, ObjectGuid::Empty, GetBase()); + if (aurEff->GetSpellInfo()->SpellFamilyName != SPELLFAMILY_ROGUE) + target->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT, ObjectGuid::Empty, aurEff->GetBase()); // stop handling the effect if it was removed by linked event if (aurApp->GetRemoveMode()) @@ -2112,13 +2116,13 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo if (AuraEffect const* dummy = target->GetDummyAuraEffect(SPELLFAMILY_DRUID, 238, 0)) FurorChance = std::max(dummy->GetAmount(), 0); - switch (GetMiscValue()) + switch (aurEff->GetMiscValue()) { case FORM_CAT: { int32 basePoints = int32(std::min(oldPower, FurorChance)); target->SetPower(POWER_ENERGY, 0); - target->CastCustomSpell(target, 17099, &basePoints, nullptr, nullptr, true, nullptr, this); + target->CastCustomSpell(target, 17099, &basePoints, nullptr, nullptr, true, nullptr, aurEff); break; } case FORM_BEAR: @@ -2192,13 +2196,16 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo case FORM_BATTLESTANCE: case FORM_DEFENSIVESTANCE: case FORM_BERSERKERSTANCE: + case FORM_PARAGON_BATTLE_STANCE: + case FORM_PARAGON_DEFENSIVE_STANCE: + case FORM_PARAGON_BERSERKER_STANCE: { uint32 Rage_val = 0; // Defensive Tactics - if (form == FORM_DEFENSIVESTANCE) + if (form == FORM_DEFENSIVESTANCE || form == FORM_PARAGON_DEFENSIVE_STANCE) { - if (AuraEffect const* aurEff = target->IsScriptOverriden(m_spellInfo, 831)) - Rage_val += aurEff->GetAmount() * 10; + if (AuraEffect const* scriptEff = target->IsScriptOverriden(aurEff->GetSpellInfo(), 831)) + Rage_val += scriptEff->GetAmount() * 10; } // Stance mastery + Tactical mastery (both passive, and last have aura only in defense stance, but need apply at any stance switch) if (target->IsPlayer()) @@ -2238,7 +2245,7 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo // adding/removing linked auras // add/remove the shapeshift aura's boosts - HandleShapeshiftBoosts(target, apply); + aurEff->HandleShapeshiftBoosts(target, apply); if (target->IsPlayer()) target->ToPlayer()->InitDataForForm(); @@ -2246,8 +2253,8 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo if (target->IsClass(CLASS_DRUID, CLASS_CONTEXT_ABILITY)) { // Dash - if (AuraEffect* aurEff = target->GetAuraEffect(SPELL_AURA_MOD_INCREASE_SPEED, SPELLFAMILY_DRUID, 0, 0, 0x8)) - aurEff->RecalculateAmount(); + if (AuraEffect* dashSpeedEff = target->GetAuraEffect(SPELL_AURA_MOD_INCREASE_SPEED, SPELLFAMILY_DRUID, 0, 0, 0x8)) + dashSpeedEff->RecalculateAmount(); // Disarm handling // If druid shifts while being disarmed we need to deal with that since forms aren't affected by disarm @@ -2281,6 +2288,11 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo if (target->IsPlayer()) { SpellShapeshiftFormEntry const* shapeInfo = sSpellShapeshiftFormStore.LookupEntry(form); + if (!shapeInfo) + { + LOG_ERROR("spells.aura.effect", "Fractured_ApplyShapeshiftFormFromAuraEffect: missing SpellShapeshiftForm {}", uint32(form)); + return; + } // Learn spells for shapeshift form - no need to send action bars or add spells to spellbook for (uint8 i = 0; i < MAX_SHAPESHIFT_SPELLS; ++i) { @@ -2294,6 +2306,11 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo } } +void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mode, bool apply) const +{ + Fractured_ApplyShapeshiftFormFromAuraEffect(this, aurApp, mode, apply, ShapeshiftForm(GetMiscValue())); +} + void AuraEffect::HandleAuraTransform(AuraApplication const* aurApp, uint8 mode, bool apply) const { if (!(mode & AURA_EFFECT_HANDLE_SEND_FOR_CLIENT_MASK)) @@ -5174,6 +5191,20 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool Unit* caster = GetCaster(); + // Fractured: Paragon warrior stance clones (951010-951012) use SPELL_AURA_DUMMY on Spell.dbc **effect2** + // (misc = Paragon SpellShapeshiftForm 33-35). Effect1 is a separate DUMMY for the buff strip; passive stats + // (e.g. armor pen / threat) live on Effect3. **AttributesEx** clears SPELL_ATTR1_NO_AURA_ICON (DBC + SpellInfoCorrections) + // so the client shows an aura icon — stock Warrior stances keep that bit set on purpose. + if (GetAuraType() == SPELL_AURA_DUMMY && m_effIndex == 1) + { + uint32 const sid = GetSpellInfo()->Id; + if (sid == 951010 || sid == 951011 || sid == 951012) + { + Fractured_ApplyShapeshiftFormFromAuraEffect(this, aurApp, mode, apply, ShapeshiftForm(GetMiscValue())); + return; + } + } + if (mode & AURA_EFFECT_HANDLE_REAL) { // pet auras diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 89286cc..0a60f02 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -2147,6 +2147,10 @@ SpellSpecificType SpellInfo::LoadSpellSpecific() const { case SPELLFAMILY_GENERIC: { + // Fractured / Paragon: DK presence advancement clones (SpellFamilyName=0 in Spell.dbc for client UX). + if (Id == 951013 || Id == 951014 || Id == 951015) + return SPELL_SPECIFIC_PRESENCE; + // Food / Drinks (mostly) if (AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) { diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index 2e1587b..22b3cd8 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -5427,11 +5427,13 @@ void SpellMgr::LoadSpellInfoCorrections() 2457, // Battle Stance 71, // Defensive Stance 2458, // Berserker Stance + 951010, 951011, 951012, // Paragon advancement warrior stance clones // Death Knight Presences. 48266, // Blood Presence 48263, // Frost Presence 48265, // Unholy Presence + 951013, 951014, 951015, // Paragon advancement DK presence clones (SpellFamily GENERIC in Spell.dbc) // Hunter Aspects -- every rank, since AC stores the per-rank // SpellInfo as separate objects and `Category` lives on each. @@ -5485,14 +5487,33 @@ void SpellMgr::LoadSpellInfoCorrections() 2457, // Battle Stance 71, // Defensive Stance 2458, // Berserker Stance + 951010, 951011, 951012, // Paragon advancement warrior stance clones 48266, // Blood Presence 48263, // Frost Presence 48265, // Unholy Presence + 951013, 951014, 951015, // Paragon advancement DK presence clones (SpellFamily GENERIC in Spell.dbc) }, [](SpellInfo* spellInfo) { spellInfo->AttributesEx6 &= ~SPELL_ATTR6_ALLOW_WHILE_RIDING_VEHICLE; }); + // Fractured / Paragon: advancement warrior stance clones — strip SPELL_ATTR1_NO_AURA_ICON + // (copied from stock 2457/71/2458). Stock Warrior stances intentionally hide from the default aura bar; + // these clones are meant to show a cancellable buff icon instead. Client Spell.dbc is patched in tandem via + // fractured-tooling/_patch_spell_dbc_paragon_stance_presence_clones.py. + ApplySpellFix({ 951010, 951011, 951012 }, [](SpellInfo* spellInfo) + { + spellInfo->AttributesEx &= ~SPELL_ATTR1_NO_AURA_ICON; + }); + + // Fractured / Paragon: advancement DK presence clones — strip SPELL_ATTR2_USE_SHAPESHIFT_BAR (0x10) copied + // from 48266/48263/48265. That client-only bit is what parks a spell on the secondary stance bar above the + // action bar; SkillLine / SpellFamily alone do not remove it. Spellbook tabs still come from SkillLines 770/771/772. + ApplySpellFix({ 951013, 951014, 951015 }, [](SpellInfo* spellInfo) + { + spellInfo->AttributesEx2 &= ~SPELL_ATTR2_USE_SHAPESHIFT_BAR; + }); + // Fractured: strip reagent requirements from every player-class spell at // load time. Filtered by SpellFamilyName != 0 so that profession spells // (cooking, alchemy, enchanting, blacksmithing, jewelcrafting, leatherworking, diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp index 27966fb..cec69c1 100644 --- a/src/server/scripts/Spells/spell_dk.cpp +++ b/src/server/scripts/Spells/spell_dk.cpp @@ -66,6 +66,9 @@ enum DeathKnightSpells SPELL_DK_ITEM_T8_MELEE_4P_BONUS = 64736, SPELL_DK_MASTER_OF_GHOULS = 52143, SPELL_DK_BLOOD_PLAGUE = 55078, + // Fractured / Paragon: stock Priest Devouring Plague vs Character Advancement multidot clone + SPELL_PRIEST_DEVOURING_PLAGUE_R1 = 2944, + SPELL_PARAGON_MULTIDOT_DEVOURING_PLAGUE_R1 = 951000, SPELL_DK_RAISE_DEAD_USE_REAGENT = 48289, SPELL_DK_RUNIC_POWER_ENERGIZE = 49088, SPELL_DK_SCENT_OF_BLOOD = 50422, @@ -107,6 +110,10 @@ enum DeathKnightSpells SPELL_DK_RUNE_STRIKE_OFF_HAND_R1 = 66217, SPELL_DK_BLOOD_STRIKE_OFF_HAND_R1 = 66215, SPELL_DK_KILLING_MACHINE = 51124, + // Fractured / Paragon: Character Advancement DK presence clones (SpellFamily GENERIC in Spell.dbc). + SPELL_PARAGON_ADV_BLOOD_PRESENCE = 951013, + SPELL_PARAGON_ADV_FROST_PRESENCE = 951014, + SPELL_PARAGON_ADV_UNHOLY_PRESENCE = 951015, }; enum DeathKnightSpellIcons @@ -126,6 +133,21 @@ enum Misc NPC_RISEN_ALLY = 30230 }; +inline bool Fractured_UnitHasBloodPresenceAura(Unit const* unit) +{ + return unit->HasAura(SPELL_DK_BLOOD_PRESENCE) || unit->HasAura(SPELL_PARAGON_ADV_BLOOD_PRESENCE); +} + +inline bool Fractured_UnitHasFrostPresenceAura(Unit const* unit) +{ + return unit->HasAura(SPELL_DK_FROST_PRESENCE) || unit->HasAura(SPELL_PARAGON_ADV_FROST_PRESENCE); +} + +inline bool Fractured_UnitHasUnholyPresenceAura(Unit const* unit) +{ + return unit->HasAura(SPELL_DK_UNHOLY_PRESENCE) || unit->HasAura(SPELL_PARAGON_ADV_UNHOLY_PRESENCE); +} + // 50526 - Wandering Plague class spell_dk_wandering_plague : public SpellScript { @@ -1797,8 +1819,11 @@ class spell_dk_improved_blood_presence : public AuraScript return ValidateSpellInfo( { SPELL_DK_BLOOD_PRESENCE, + SPELL_PARAGON_ADV_BLOOD_PRESENCE, SPELL_DK_FROST_PRESENCE, + SPELL_PARAGON_ADV_FROST_PRESENCE, SPELL_DK_UNHOLY_PRESENCE, + SPELL_PARAGON_ADV_UNHOLY_PRESENCE, SPELL_DK_IMPROVED_BLOOD_PRESENCE_TRIGGERED }); } @@ -1806,14 +1831,14 @@ class spell_dk_improved_blood_presence : public AuraScript void HandleEffectApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) { Unit* target = GetTarget(); - if (target->HasAnyAuras(SPELL_DK_FROST_PRESENCE, SPELL_DK_UNHOLY_PRESENCE) && !target->HasAura(SPELL_DK_IMPROVED_BLOOD_PRESENCE_TRIGGERED)) + if (target->HasAnyAuras(SPELL_DK_FROST_PRESENCE, SPELL_DK_UNHOLY_PRESENCE, SPELL_PARAGON_ADV_FROST_PRESENCE, SPELL_PARAGON_ADV_UNHOLY_PRESENCE) && !target->HasAura(SPELL_DK_IMPROVED_BLOOD_PRESENCE_TRIGGERED)) target->CastCustomSpell(SPELL_DK_IMPROVED_BLOOD_PRESENCE_TRIGGERED, SPELLVALUE_BASE_POINT1, aurEff->GetAmount(), target, true, nullptr, aurEff); } void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { Unit* target = GetTarget(); - if (!target->HasAura(SPELL_DK_BLOOD_PRESENCE)) + if (!Fractured_UnitHasBloodPresenceAura(target)) target->RemoveAura(SPELL_DK_IMPROVED_BLOOD_PRESENCE_TRIGGERED); } @@ -1834,8 +1859,11 @@ class spell_dk_improved_frost_presence : public AuraScript return ValidateSpellInfo( { SPELL_DK_BLOOD_PRESENCE, + SPELL_PARAGON_ADV_BLOOD_PRESENCE, SPELL_DK_FROST_PRESENCE, + SPELL_PARAGON_ADV_FROST_PRESENCE, SPELL_DK_UNHOLY_PRESENCE, + SPELL_PARAGON_ADV_UNHOLY_PRESENCE, SPELL_DK_FROST_PRESENCE_TRIGGERED }); } @@ -1843,14 +1871,14 @@ class spell_dk_improved_frost_presence : public AuraScript void HandleEffectApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) { Unit* target = GetTarget(); - if (target->HasAnyAuras(SPELL_DK_BLOOD_PRESENCE, SPELL_DK_UNHOLY_PRESENCE) && !target->HasAura(SPELL_DK_FROST_PRESENCE_TRIGGERED)) + if (target->HasAnyAuras(SPELL_DK_BLOOD_PRESENCE, SPELL_DK_UNHOLY_PRESENCE, SPELL_PARAGON_ADV_BLOOD_PRESENCE, SPELL_PARAGON_ADV_UNHOLY_PRESENCE) && !target->HasAura(SPELL_DK_FROST_PRESENCE_TRIGGERED)) target->CastCustomSpell(SPELL_DK_FROST_PRESENCE_TRIGGERED, SPELLVALUE_BASE_POINT0, aurEff->GetAmount(), target, true, nullptr, aurEff); } void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { Unit* target = GetTarget(); - if (!target->HasAura(SPELL_DK_FROST_PRESENCE)) + if (!Fractured_UnitHasFrostPresenceAura(target)) target->RemoveAura(SPELL_DK_FROST_PRESENCE_TRIGGERED); } @@ -1871,8 +1899,11 @@ class spell_dk_improved_unholy_presence : public AuraScript return ValidateSpellInfo( { SPELL_DK_BLOOD_PRESENCE, + SPELL_PARAGON_ADV_BLOOD_PRESENCE, SPELL_DK_FROST_PRESENCE, + SPELL_PARAGON_ADV_FROST_PRESENCE, SPELL_DK_UNHOLY_PRESENCE, + SPELL_PARAGON_ADV_UNHOLY_PRESENCE, SPELL_DK_IMPROVED_UNHOLY_PRESENCE_TRIGGERED, SPELL_DK_UNHOLY_PRESENCE_TRIGGERED }); @@ -1881,14 +1912,14 @@ class spell_dk_improved_unholy_presence : public AuraScript void HandleEffectApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) { Unit* target = GetTarget(); - if (target->HasAura(SPELL_DK_UNHOLY_PRESENCE) && !target->HasAura(SPELL_DK_IMPROVED_UNHOLY_PRESENCE_TRIGGERED)) + if (Fractured_UnitHasUnholyPresenceAura(target) && !target->HasAura(SPELL_DK_IMPROVED_UNHOLY_PRESENCE_TRIGGERED)) { // Not listed as any effect, only base points set in dbc int32 basePoints = GetSpellInfo()->Effects[EFFECT_1].CalcValue(); target->CastCustomSpell(target, SPELL_DK_IMPROVED_UNHOLY_PRESENCE_TRIGGERED, &basePoints, &basePoints, &basePoints, true, nullptr, aurEff); } - if (target->HasAnyAuras(SPELL_DK_BLOOD_PRESENCE, SPELL_DK_FROST_PRESENCE) && !target->HasAura(SPELL_DK_UNHOLY_PRESENCE_TRIGGERED)) + if (target->HasAnyAuras(SPELL_DK_BLOOD_PRESENCE, SPELL_DK_FROST_PRESENCE, SPELL_PARAGON_ADV_BLOOD_PRESENCE, SPELL_PARAGON_ADV_FROST_PRESENCE) && !target->HasAura(SPELL_DK_UNHOLY_PRESENCE_TRIGGERED)) target->CastCustomSpell(SPELL_DK_UNHOLY_PRESENCE_TRIGGERED, SPELLVALUE_BASE_POINT0, aurEff->GetAmount(), target, true, nullptr, aurEff); } @@ -1898,7 +1929,7 @@ class spell_dk_improved_unholy_presence : public AuraScript target->RemoveAura(SPELL_DK_IMPROVED_UNHOLY_PRESENCE_TRIGGERED); - if (!target->HasAura(SPELL_DK_UNHOLY_PRESENCE)) + if (!Fractured_UnitHasUnholyPresenceAura(target)) target->RemoveAura(SPELL_DK_UNHOLY_PRESENCE_TRIGGERED); } @@ -1939,12 +1970,12 @@ class spell_dk_pestilence : public SpellScript // and Unit::GetDiseasesByCaster already counts it for Paragon callers // (see Unit.cpp), so it is conceptually a disease; stock Pestilence // just hard-codes Blood Plague + Frost Fever and so silently drops it. - // GetAuraOfRankedSpell with the rank-1 id (2944) covers every rank of + // GetAuraOfRankedSpell with the rank-1 id (2944 / 951000) covers every rank of // Devouring Plague the player has on the target -- we re-cast that // exact same rank so the spread copy carries the caster's actual // damage tier rather than always rank 1. Stock DKs cannot cast - // Devouring Plague at all, so the GetAuraOfRankedSpell will return - // null for them and this branch is a no-op there. + // Devouring Plague at all, so both lookups return null for them and + // this branch is a no-op there. bool const paragonSpread = IsParagonWildcardCaller(caster); // Spread on others @@ -1958,10 +1989,16 @@ class spell_dk_pestilence : public SpellScript if (target->GetAura(SPELL_DK_FROST_FEVER, caster->GetGUID())) caster->CastSpell(hitUnit, SPELL_DK_FROST_FEVER, true); - // Fractured / Paragon: Devouring Plague spread. + // Fractured / Paragon: Devouring Plague spread (stock 2944 chain or + // Character Advancement multidot clone 951000 chain). if (paragonSpread) - if (Aura const* dp = target->GetAuraOfRankedSpell(2944 /* Devouring Plague r1 */, caster->GetGUID())) + { + Aura const* dp = target->GetAuraOfRankedSpell(SPELL_PRIEST_DEVOURING_PLAGUE_R1, caster->GetGUID()); + if (!dp) + dp = target->GetAuraOfRankedSpell(SPELL_PARAGON_MULTIDOT_DEVOURING_PLAGUE_R1, caster->GetGUID()); + if (dp) caster->CastSpell(hitUnit, dp->GetId(), true); + } } // Refresh on target else if (caster->GetAura(SPELL_DK_GLYPH_OF_DISEASE)) @@ -1985,8 +2022,13 @@ class spell_dk_pestilence : public SpellScript // Fractured / Paragon: Devouring Plague Glyph-of-Disease refresh. if (paragonSpread) - if (Aura* dp = target->GetAuraOfRankedSpell(2944 /* Devouring Plague r1 */, caster->GetGUID())) + { + Aura* dp = target->GetAuraOfRankedSpell(SPELL_PRIEST_DEVOURING_PLAGUE_R1, caster->GetGUID()); + if (!dp) + dp = target->GetAuraOfRankedSpell(SPELL_PARAGON_MULTIDOT_DEVOURING_PLAGUE_R1, caster->GetGUID()); + if (dp) dp->RefreshDuration(); + } } } @@ -2010,6 +2052,9 @@ class spell_dk_presence : public AuraScript SPELL_DK_BLOOD_PRESENCE, SPELL_DK_FROST_PRESENCE, SPELL_DK_UNHOLY_PRESENCE, + SPELL_PARAGON_ADV_BLOOD_PRESENCE, + SPELL_PARAGON_ADV_FROST_PRESENCE, + SPELL_PARAGON_ADV_UNHOLY_PRESENCE, SPELL_DK_IMPROVED_BLOOD_PRESENCE_R1, SPELL_DK_IMPROVED_FROST_PRESENCE_R1, SPELL_DK_IMPROVED_UNHOLY_PRESENCE_R1, @@ -2024,7 +2069,7 @@ class spell_dk_presence : public AuraScript { Unit* target = GetTarget(); - if (GetId() == SPELL_DK_BLOOD_PRESENCE) + if (GetId() == SPELL_DK_BLOOD_PRESENCE || GetId() == SPELL_PARAGON_ADV_BLOOD_PRESENCE) target->CastSpell(target, SPELL_DK_IMPROVED_BLOOD_PRESENCE_TRIGGERED, true); else if (AuraEffect const* impAurEff = target->GetAuraEffectOfRankedSpell(SPELL_DK_IMPROVED_BLOOD_PRESENCE_R1, EFFECT_0)) if (!target->HasAura(SPELL_DK_IMPROVED_BLOOD_PRESENCE_TRIGGERED)) @@ -2035,7 +2080,7 @@ class spell_dk_presence : public AuraScript { Unit* target = GetTarget(); - if (GetId() == SPELL_DK_FROST_PRESENCE) + if (GetId() == SPELL_DK_FROST_PRESENCE || GetId() == SPELL_PARAGON_ADV_FROST_PRESENCE) target->CastSpell(target, SPELL_DK_FROST_PRESENCE_TRIGGERED, true); else if (AuraEffect const* impAurEff = target->GetAuraEffectOfRankedSpell(SPELL_DK_IMPROVED_FROST_PRESENCE_R1, EFFECT_0)) if (!target->HasAura(SPELL_DK_FROST_PRESENCE_TRIGGERED)) @@ -2046,12 +2091,12 @@ class spell_dk_presence : public AuraScript { Unit* target = GetTarget(); - if (GetId() == SPELL_DK_UNHOLY_PRESENCE) + if (GetId() == SPELL_DK_UNHOLY_PRESENCE || GetId() == SPELL_PARAGON_ADV_UNHOLY_PRESENCE) target->CastSpell(target, SPELL_DK_UNHOLY_PRESENCE_TRIGGERED, true); if (AuraEffect const* impAurEff = target->GetAuraEffectOfRankedSpell(SPELL_DK_IMPROVED_UNHOLY_PRESENCE_R1, EFFECT_0)) { - if (GetId() == SPELL_DK_UNHOLY_PRESENCE) + if (GetId() == SPELL_DK_UNHOLY_PRESENCE || GetId() == SPELL_PARAGON_ADV_UNHOLY_PRESENCE) { // Not listed as any effect, only base points set int32 bp = impAurEff->GetSpellInfo()->Effects[EFFECT_1].CalcValue();