diff --git a/contrib/fractured-dev-extras/CLIENT-PATCHES.md b/contrib/fractured-dev-extras/CLIENT-PATCHES.md index a6fc8b2..218218e 100644 --- a/contrib/fractured-dev-extras/CLIENT-PATCHES.md +++ b/contrib/fractured-dev-extras/CLIENT-PATCHES.md @@ -14,14 +14,16 @@ This file is the table of contents and install guide. | Artifact | Size | Purpose | |---|---|---| | `patch-enUS-4.MPQ` | ~5 MB | DBC + GlueXML bake. Adds `CLASS_PARAGON` (id 12), the character-create slot, glue strings, game-table DBCs, and a patched `Spell.dbc`: **(1)** `RuneCostID` zeroed on every rune-cost spell so non–Death Knight clients still send DK casts (rune costs are shown via `RuneFrame.lua`); **(2)** `Reagent[]` / `ReagentCount[]` zeroed on every spell whose `SpellFamilyName` is non-zero (all class abilities), while profession crafts (`SpellFamilyName == 0`) keep their materials. Both edits mirror server load-time corrections so client preflight and server validation stay aligned. Required for character creation as Paragon to even show up. | -| `patch-enUS-5.MPQ` | ~50 KB | FrameXML overrides. Replaces stock `PlayerFrame.lua` / `RuneFrame.lua` / `ComboFrame.lua` / `UnitFrame.lua` / `SpellBookFrame.lua` + `SpellBookFrame.xml` with Paragon-aware versions: rune simulator, combo-point simulator, server-authoritative resource sync over the `PARAA` addon channel, action-button usability + click guards, an expanded spellbook (higher `MAX_SPELLS`, 24 skill-line tabs instead of stock 8) so all-class spells render, Paragon stat tooltips on the character sheet, and a tooltip post-processor that appends ", Paragon" to the "Classes:" line on class-restricted gear / glyphs (the server bypasses `AllowableClass` for class 12, but the engine paints the line red and omits Paragon — the Lua hook recolors it green and adds the name so the player can tell it's wearable). The paper-doll **ammo slot** follows stock visibility rules (shown for hunters / ranged weapons; hidden when `UnitHasRelicSlot` applies). | -| `patch-enUS-6.MPQ` | ~160 KB | The `ParagonAdvancement` addon. Replaces the talent pane (`N` key) for Paragon characters with the Character Advancement panel: per-class spell tabs, talent grid, Overview/Search tabs, AE/TE currency, commit / reset / preview, login-time toast suppression. | +| `patch-enUS-5.MPQ` | ~57 KB | FrameXML overrides. Replaces stock `PlayerFrame.lua` / `RuneFrame.lua` / `ComboFrame.lua` / `UnitFrame.lua` / `SpellBookFrame.lua` + `SpellBookFrame.xml` with Paragon-aware versions: rune simulator, combo-point simulator, server-authoritative resource sync over the `PARAA` addon channel, action-button usability + click guards, an expanded spellbook (higher `MAX_SPELLS`, 24 skill-line tabs instead of stock 8) so all-class spells render, Paragon stat tooltips on the character sheet (including filtering duplicate “attack power from strength” lines so the paper doll matches server AP), a tooltip post-processor that appends ", Paragon" to the "Classes:" line on class-restricted gear / glyphs (the server bypasses `AllowableClass` for class 12, but the engine paints the line red and omits Paragon — the Lua hook recolors it green and adds the name so the player can tell it's wearable), and **PetFrame** re-anchored so the **pet unit frame sits below the rune row** for Paragon (stock layout had runes overlapping the pet portrait). The paper-doll **ammo slot** follows stock visibility rules (shown for hunters / ranged weapons; hidden when `UnitHasRelicSlot` applies). | +| `patch-enUS-6.MPQ` | ~123 KB | The `ParagonAdvancement` addon. Replaces the talent pane (`N` key) for Paragon characters with the Character Advancement panel: per-class spell tabs, talent grid, Overview/Search tabs, AE/TE currency, commit / reset / preview, login-time toast suppression, a **PETS** tab with live hunter pet talent trees (preview learn, no TE/AE cost), a dedicated **Reset Pet Talents** control (server `PARAA` `C RESET PET TALENTS` — instant, no gold, no confirmation; requires matching worldserver), and bottom-row **Reset all Abilities / Reset Build / Reset all Talents** disabled while on the PETS tab so those paths cannot dismiss the pet or unlearn Tame Beast. | | `Wow.exe` | ~7.5 MB | 3.3.5a (build 12340) client byte-patched to skip the MPQ signature check so custom `patch-enUS-N.MPQ` files load. Diff against stock is a few bytes; everything else is unchanged. | Server and client work as a pair: the addon talks to `mod-paragon` on the worldserver via `WHISPER` addon-channel messages with the `PARAA` prefix (currency push, spell/talent snapshot, commit, combo points, rune -cooldowns, learn-toast silence window). Mismatched versions usually +cooldowns, learn-toast silence window, and **`C RESET PET TALENTS`** +for hunter pet talent resets from the Character Advancement PETS tab). +Mismatched versions usually manifest as the panel rendering blank or AE/TE reading 0/0. --- diff --git a/modules/mod-paragon/src/Paragon_Essence.cpp b/modules/mod-paragon/src/Paragon_Essence.cpp index 7f5831c..68ce235 100644 --- a/modules/mod-paragon/src/Paragon_Essence.cpp +++ b/modules/mod-paragon/src/Paragon_Essence.cpp @@ -12,6 +12,7 @@ #include "Chat.h" #include "CommandScript.h" #include "Config.h" +#include "Pet.h" #include "Player.h" #include "RBAC.h" #include "ScriptMgr.h" @@ -1689,6 +1690,10 @@ public: // "Q SNAPSHOT" -- push R SPELLS and R TALENTS for Overview // "C COMMIT s:... t:..." -- apply pending learns from the panel // "C RESET ABILITIES" / "C RESET TALENTS" / "C RESET ALL" / "C RESET EVERYTHING" + // "C RESET PET TALENTS" -- free + instant pet talent reset (no popup, + // no gold). Routes to Player::ResetPetTalents + // which itself calls Pet::resetTalents and + // refreshes the talent points. void OnPlayerBeforeSendChatMessage(Player* player, uint32& /*type*/, uint32& lang, std::string& msg) override { if (!player || player->getClass() != CLASS_PARAGON) @@ -1765,6 +1770,29 @@ public: SendAddonMessage(player, "R ERR " + err); return; } + if (body == "C RESET PET TALENTS") + { + // Pet talent reset: deliberately bypasses the engine's + // gold-cost confirmation flow. Player::ResetPetTalents + // wraps Pet::resetTalents (which only refunds and unlearns; + // does NOT charge gold or dismiss the pet) and re-sends the + // talent UI to the client. Pre-conditions: + // - the player must own a HUNTER_PET (the only pet kind + // with a talent tree in 3.3.5) + // - the pet must have spent at least 1 talent point + // If either fails Player::ResetPetTalents returns silently; + // we ack with R OK so the client UI can refresh either way. + Pet* pet = player->GetPet(); + if (!pet || pet->getPetType() != HUNTER_PET) + { + SendAddonMessage(player, + "R ERR No active hunter pet to reset."); + return; + } + player->ResetPetTalents(); + SendAddonMessage(player, "R OK PET TALENTS RESET"); + return; + } } void OnPlayerLearnTalents(Player* player, uint32 talentId, uint32 /*talentRank*/, uint32 /*spellid*/) override diff --git a/modules/mod-paragon/src/Paragon_SC.cpp b/modules/mod-paragon/src/Paragon_SC.cpp index 1493c27..6e00f5e 100644 --- a/modules/mod-paragon/src/Paragon_SC.cpp +++ b/modules/mod-paragon/src/Paragon_SC.cpp @@ -7,9 +7,12 @@ #include "Chat.h" #include "Config.h" +#include "Creature.h" +#include "CreatureData.h" #include "GameTime.h" #include "Log.h" #include "ObjectGuid.h" +#include "Pet.h" #include "Player.h" #include "ScriptMgr.h" #include "SharedDefines.h" @@ -44,43 +47,142 @@ public: if (!player || player->getClass() != CLASS_PARAGON) return std::nullopt; - // Death Knight rune / runic power ability stack (narrow on purpose). - if (unitClass == CLASS_DEATH_KNIGHT && context == CLASS_CONTEXT_ABILITY) + // ============================================================ + // Ability stack -- claim ALL nine vanilla classes. + // ============================================================ + // CLASS_CONTEXT_ABILITY is read by every class-specific spell + // gate in core / scripts: DK rune mechanics (Spell.cpp, + // SpellEffects.cpp, spell_dk.cpp, SpellAuraEffects.cpp), + // Warrior Titan's Grip / Bladestorm (Player.cpp 3783, 15432, + // PlayerUpdates.cpp 1547), Paladin Rebuke (Player.cpp 15441), + // Shaman dual-wield bookkeeping (Player.cpp 5028), Hunter pet + // / Hunter's Mark gates (spell_item.cpp 3718), Druid Insect + // Swarm / Wild Growth (SpellAuraEffects.cpp 2153, 2232), + // Priest Spirit of Redemption out-of-bounds check (Unit.cpp + // 14238), Rogue pickpocketing (LootHandler.cpp 86/165/385, + // Vehicle.cpp 80). Paragon learns abilities from every class + // through Character Advancement, so claiming all of them lets + // every gated spell script execute its class-specific branch + // for our players. The only downside is double-pathed scripts + // (e.g. a spell with both warrior and rogue branches) will + // pick whichever the script tests first -- acceptable. + if (context == CLASS_CONTEXT_ABILITY) return true; - // Warrior ability stack: enables warrior-spec ability gates anywhere - // they're checked. None of the currently-traced sites in core/scripts - // gate on (CLASS_WARRIOR, CLASS_CONTEXT_ABILITY), so this is a safe - // forward-compatible claim. Rage generation itself is gated on - // HasActivePowerType(POWER_RAGE) and is wired below. - if (unitClass == CLASS_WARRIOR && context == CLASS_CONTEXT_ABILITY) - return true; - - // Reactive melee states: Overpower-on-dodge (warrior), Counterattack window (hunter). - // We intentionally do NOT claim CLASS_ROGUE here: that context skips the generic - // AURA_STATE_DEFENSE update on dodge (Riposte path) in Unit::ProcDamageAndSpellFor. + // ============================================================ + // Reactive melee states. + // ============================================================ + // Warrior dodge -> AURA_STATE_DEFENSE (Overpower window). + // Hunter parry -> AURA_STATE_HUNTER_PARRY (Counterattack). + // We intentionally do NOT claim CLASS_ROGUE here: + // Unit::ProcDamageAndSpellFor (Unit.cpp 12824) skips the + // generic AURA_STATE_DEFENSE update on dodge for rogues so + // Riposte can take over. Claiming rogue would silently kill + // Overpower for Paragon, and Riposte already works for us via + // the warrior-style state we already grant. if (context == CLASS_CONTEXT_ABILITY_REACTIVE) { if (unitClass == CLASS_WARRIOR || unitClass == CLASS_HUNTER) return true; } - // Unified relic / ranged slot for class 12. - // ---------------------------------------------------------------- - // CLASS_CONTEXT_EQUIP_RELIC is read in exactly two places in core - // (PlayerStorage.cpp): FindEquipSlot's INVTYPE_RELIC switch, which - // routes Librams/Idols/Totems/Misc/Sigils into EQUIPMENT_SLOT_RANGED - // for the matching class only, and CanEquipUniqueItem's per-subclass - // proficiency gate. By claiming this context for paladin/druid/ - // shaman/warlock/dk we let Paragon drop any of those relics into the - // ranged slot exactly the same way each native class does, with no - // core patch and no other side effects (the constant is not read - // anywhere else in the codebase). + // ============================================================ + // Pet ownership contexts. + // ============================================================ + // CLASS_CONTEXT_PET is read by Pet::AddToWorld, Pet::CreateBase + // AtCreatureInfo, Pet::InitStatsForLevel (twice -- the + // MAX_PET_TYPE bootstrap branch and the per-class attack-time + // scaling), Pet::IsPermanentPetFor, Player::SummonPet, + // Player::CanResummonPet, Spell::EffectTameCreature, + // SpellEffects.cpp (CreateTamedPet debug effects, Eyes of the + // Beast), spell_generic.cpp 1760 (charm-as-pet conversion), + // and PlayerGossip.cpp's hunter stable check. // - // Bows/guns/crossbows already equip via the regular - // INVTYPE_RANGED/RANGEDRIGHT routing -- weapon proficiencies for - // class 12 are seeded by the Paragon proficiency SQL migrations, so - // they pass the GetSkillValue check in CanEquipUniqueItem. + // The cleanest disambiguation is by the *active pet's* shape: + // HUNTER_PET -> hunter (beast tame) + // SUMMON_PET + DEMON type -> warlock (Imp/VW/Succ/...) + // SUMMON_PET + UNDEAD type -> DK ghoul / Army of Dead + // SUMMON_PET + ELEMENTAL type -> mage water / shaman fire + // For HUNTER specifically the no-pet case is also claimed so + // Tame Beast's EffectTameCreature gate passes during cast. + if (context == CLASS_CONTEXT_PET) + { + Pet const* activePet = const_cast(player)->GetPet(); + + // Hunter beast: claim during taming OR when a HUNTER_PET is + // already active. This is what makes Tame Beast / Call Pet + // / pet stable / Counterattack pet aura feedback work. + if (unitClass == CLASS_HUNTER) + { + if (!activePet || activePet->getPetType() == HUNTER_PET) + return true; + return std::nullopt; + } + + // All other classes only claim when an active SUMMON_PET is + // present. We then disambiguate by the creature's type + // because warlock / DK / mage / shaman all use SUMMON_PET. + if (!activePet || activePet->getPetType() != SUMMON_PET) + return std::nullopt; + + CreatureTemplate const* tmpl = activePet->GetCreatureTemplate(); + if (!tmpl) + return std::nullopt; + + switch (unitClass) + { + case CLASS_WARLOCK: + // Drives Master Demonologist / Demonic Knowledge / + // Demonic Pact propagation, last-pet-spell tracking + // (Pet.cpp 112), and IsPermanentPetFor (Pet.cpp + // 2288) so demon pets persist across logins. + if (tmpl->type == CREATURE_TYPE_DEMON) + return true; + break; + case CLASS_DEATH_KNIGHT: + // Risen Ghoul + Army of the Dead. Player.cpp 14354 + // and Pet.cpp 243 / 1046 / 2290 read this; without + // it the ghoul is invisible to the owner mid-load + // and ScriptedAI hooks on the ghoul mis-route. + if (tmpl->type == CREATURE_TYPE_UNDEAD) + return true; + break; + case CLASS_MAGE: + // Glyph-of-Eternal-Water permanent Water Elemental + // (entry 510, 37994). Used by Pet.cpp 1047/2292. + if (tmpl->type == CREATURE_TYPE_ELEMENTAL) + return true; + break; + case CLASS_SHAMAN: + // Fire Elemental / Earth Elemental. The base + // engine spawns these as creatures rather than + // proper Pet instances in most code paths, so the + // claim mostly matters for the Pet.cpp 1045 stat + // bootstrap when one is loaded as a SUMMON_PET. + if (tmpl->type == CREATURE_TYPE_ELEMENTAL) + return true; + break; + default: + break; + } + return std::nullopt; + } + + // Warlock pet-charm context (Enslave Demon -- Unit.cpp 14828, + // 14894, 15025). Without this claim, charming a demon as a + // Paragon doesn't get the warlock-flavor charm semantics + // (faction-set-on-charm, action-bar layout, charm-break logic). + if (unitClass == CLASS_WARLOCK && context == CLASS_CONTEXT_PET_CHARM) + return true; + + // ============================================================ + // Equipment contexts. + // ============================================================ + // CLASS_CONTEXT_EQUIP_RELIC: PlayerStorage.cpp 224-240 + + // 2475-2493. Routes Librams/Idols/Totems/Misc/Sigils into + // EQUIPMENT_SLOT_RANGED for the matching class. Claim every + // relic-bearing class so a Paragon can drop any of them into + // the ranged slot. if (context == CLASS_CONTEXT_EQUIP_RELIC) { switch (unitClass) @@ -96,6 +198,67 @@ public: } } + // CLASS_CONTEXT_EQUIP_ARMOR_CLASS: PlayerStorage.cpp 2326, + // 2330, 2503-2523. At level 40 each class auto-learns its + // top armor proficiency. Paragon should pick up plate (via + // paladin/DK), shields (paladin/warrior/shaman), mail + // (hunter/shaman), and leather (rogue) so the level-40 train + // event grants Paragon full proficiency and we don't have to + // hand-curate it through the Paragon proficiency SQL. + if (context == CLASS_CONTEXT_EQUIP_ARMOR_CLASS) + { + switch (unitClass) + { + case CLASS_PALADIN: + case CLASS_WARRIOR: + case CLASS_DEATH_KNIGHT: + case CLASS_HUNTER: + case CLASS_SHAMAN: + case CLASS_DRUID: + case CLASS_ROGUE: + return true; + default: + break; + } + } + + // CLASS_CONTEXT_EQUIP_SHIELDS: PlayerStorage.cpp 2467-2469. + // Lets a Paragon equip shields without a paladin/warrior/ + // shaman skill gate. + if (context == CLASS_CONTEXT_EQUIP_SHIELDS) + { + switch (unitClass) + { + case CLASS_PALADIN: + case CLASS_WARRIOR: + case CLASS_SHAMAN: + return true; + default: + break; + } + } + + // CLASS_CONTEXT_WEAPON_SWAP: PlayerStorage.cpp 1920, 2838 -- + // rogue uses cooldown spell 6123 instead of 6119 on weapon + // swap (Quick Draw / Combat Potency interactions). Claim + // rogue so Paragon picks up the same cooldown spell. + if (context == CLASS_CONTEXT_WEAPON_SWAP && unitClass == CLASS_ROGUE) + return true; + + // ============================================================ + // Contexts we DELIBERATELY DO NOT claim: + // ============================================================ + // CLASS_CONTEXT_STATS -- Paragon has its own STR/AGI->AP and + // INT/SPI->SP curves wired in StatSystem.cpp's CLASS_PARAGON + // branch (level*2 + STR + AGI - 20 etc.). Claiming any + // vanilla class here would override our curves with theirs. + // + // CLASS_CONTEXT_INIT, _TELEPORT, _QUEST, _TAXI, _SKILL, + // _GRAVEYARD, _CLASS_TRAINER, _TALENT_POINT_CALC -- all + // used by DK Ebon Hold / druid Moonglade starting-zone + // scripts. Paragon doesn't go through those zones and we + // don't want our players bound to Acherus or trapped in + // the DK starting quest gates. return std::nullopt; }