Compare commits

...

58 Commits

Author SHA1 Message Date
Docker Build 38710e8254 Frozen Power: cross-class proc allowlist and SQL wildcards
Relax spell_proc / spell_proc_event family gates for talent -63373; gate eligible spells in spell_sha_frozen_power via chain-head IDs (Mage, Druid, Hunter, Warlock, DK, extra Shaman spells including totems and weapon imbues).

World SQL: data/sql/updates/db_world/2026_05_13_01.sql and mod-paragon 2026_05_13_00_paragon_frozen_power_proc.sql. Base dumps and SpellProcTestData aligned.

Docs: CLIENT-PATCHES.md (Maelstrom tooltip note, Frozen Power hook).

Client sources (rebuild patch-enUS-5 / patch-enUS-6): Classless Dev/Paragon Patch UI/Interface/FrameXML/PlayerFrame.lua and Classless Dev/Paragon Advancement/.../ParagonAdvancement_TalentData.lua — not in this repo; bake MPQ before attaching Wow.exe + patches to the Gitea release.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 16:55:25 -04:00
Docker Build 297c3813d3 Add mod-transmog and mod-aoe-loot with Paragon-friendly transmog defaults
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 15:22:45 -04:00
Docker Build bb98661212 feat(vps): override kill/start scripts for --restart via env
FRACTURED_KILL_SCRIPT and FRACTURED_START_SCRIPT default to repo scripts/.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 14:33:11 -04:00
Dawnsorrow 6f1fdcb48d Delete scripts/vps-paragon-diagnostics.sh 2026-05-13 17:36:23 +00:00
Docker Build 3568a580aa fix(vps): migrate Dawnforger GitHub Fractured remote to Gitea
Production origin used github.com:Dawnforger/Fractured; extend auto-migration
to match that legacy mirror (and tighten regex to repo name suffix).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 13:27:47 -04:00
Docker Build 310bff4e06 ci: Gitea-first launcher release; VPS origin migration
Fractured launcher CI / electron-launcher-windows (push) Waiting to run
Fractured launcher CI / electron-launcher-linux (push) Waiting to run
- Gitea release workflow downloads existing Gitea attachments, merges CI-built
  launchers, uploads merged set (no GitHub release mirror).
- Add download-release-from-gitea.sh; clarify release-sync filters.
- VPS update: repoint github.com/HighSocietyRaiding/Fractured remote to Gitea
  before git pull unless skipped via env.
- Docs and bootstrap comment; distro workflow uses shared skip helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 13:22:23 -04:00
Docker Build c3f679ab74 Gitea canonical URLs and CI repo guard; stack gems and trade goods to 200
Fractured launcher CI / electron-launcher-windows (push) Has been cancelled
Fractured launcher CI / electron-launcher-linux (push) Has been cancelled
- Point docs, clone examples, launcher package/repo defaults, and GitHub
  Actions repository guards at HighSocietyRaiding/Fractured (Gitea).
- Add db_world update: stackable 200 for item classes 3 (gems), 7 (trade
  goods), and 5 (reagents) where stack size was below 200.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 12:04:30 -04:00
Docker Build 6a1f8eec89 Paragon tester hunter BiS, mount cast QoL, learn all mounts RBAC, trade cap 11
- mod-paragon: .paragon tester bis hunter (Sanctified Ahn'Kahar Blood Hunter + Windrunner's Heartseeker), bis gems kits, AGI bow vs ranged/gun/crossbow, ranged for spi/hybrid weapons.
- .learn all mounts: RBAC 916 + db_auth migration 2026_05_12_00.sql.
- Cast-time mount spells: allow start/complete while moving; block in combat; interrupt mount cast on combat enter; relax movement prevention for NPCs/units.
- MaxPrimaryTradeSkill default 11 (all WotLK primary professions) in WorldConfig + worldserver.conf.dist.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 23:02:30 -04:00
Docker Build 0bb6b0ef84 feat(launcher): script to replace launcher-only on legacy Gitea release
- gitea-replace-launcher-only.sh: swap Fractured-Launcher* + yml without wiping MPQs
- Only remove latest.yml / latest-linux.yml if a replacement exists in dist/
- README: bridge rollout steps and checklist item

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 21:44:13 -05:00
Docker Build 295cb6df52 chore(launcher): point baked Gitea to git.hisora.dev, bump 1.0.13
Players on the old DDNS host can fetch this build from there once, then
patches and launcher updates use https://git.hisora.dev (Dawnsorrow/Fractured-Distro).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 21:39:33 -05:00
Docker Build fbd6ea47f2 Switch server scripts to tmux for panel console access
Rewrite start-azeroth-servers.sh to launch auth/worldserver in named
tmux sessions instead of nohup/disown. Add kill-azeroth-servers.sh to
tear down sessions and stray processes. Update vps-update-server.sh
with a --restart flag that stops servers before compile and restarts
them in tmux after.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 21:39:33 -05:00
Docker Build a64279ed7e fix(player): restore missing additional save timer and reduce autosave interval
The m_additionalSaveTimer was never processed in the update loop, so quick
partial saves after important events (rare+ item pickups, quest completions)
never fired. This caused players to lose progress on disconnect/crash since
only the 15-minute full autosave protected them.

- Add m_additionalSaveTimer tick logic to Player::Update
- Reduce default PlayerSaveInterval from 900000 (15 min) to 300000 (5 min)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 21:39:33 -05:00
Docker Build 87219cb4eb 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 <cursoragent@cursor.com>
2026-05-12 19:20:13 -04:00
Docker Build da17074a63 Paragon: Runeforging support (panel-purchasable, no anvil required)
Lets the Paragon class buy Runeforging from the Character Advancement
panel and apply rune enchants from anywhere in the world without needing
to be near a runeforge GameObject. Three carve-outs work together:

* Spell.cpp: bypass the SpellFocusObject GO proximity check when the
  caster is a Paragon and the spell belongs to SKILL_RUNEFORGING (776).
  Stock DK behaviour is unchanged -- the bypass is gated on
  getClass() == CLASS_PARAGON, not on the IsClass() context hook.

* Player.cpp: skip the Paragon class-skill cascade block for skill 776
  so the rune-enchant SLA cascade actually fires. Without this the
  player gets the Runeforging skill but no rune options at the anvil.

* Paragon_Essence.cpp:
  - Treat SKILL_RUNEFORGING children as a meta-skill cluster: cascade
    them like passives even though they're active casts, so they stick
    as panel_spell_children and get cleaned up via the standard refund
    path.
  - Whitelist the 8 basic rune-enchants in PruneSkillLineCascadeChildren
    so they don't get evicted as "active in children = legacy garbage".
  - Force-attach them in PanelLearnSpellChain (the SLA rows ship with
    AcquireMethod=0, so the engine cascade alone won't grant them).
  - Add an OnPlayerLogin fixup so existing Paragons who bought
    Runeforging before this change get the 8 runes retro-granted.
  - Stop filtering SPELL_ATTR0_DO_NOT_DISPLAY in PushSpellSnapshot --
    Runeforging itself is hidden in the DBC but is a real panel
    purchase that must show in the Overview tab.

The two advanced runes (Stoneskin Gargoyle, Nerubian Carapace) are
intentionally excluded from the auto-grant -- retail gates them behind
heroic dungeon / raid item drops and the SLA AcquireMethod=0 honours
that gating.

No SQL migration needed; works against existing DBC + SLA data.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 04:49:38 -04:00
Docker Build b8826370c6 Paragon: cross-class stance exclusivity + cancel, hunter ammo soft-fail, Feral Cat scaling, Pestilence DP spread
Server-side batch following v0.7.18, all gated to Paragon (or applied
server-wide where the design discussion called for it):

* Cross-class stance / form / presence / aspect exclusivity (server-wide).
  New `IsFracturedExclusiveStanceSpell()` (`Unit.cpp`/`Unit.h`) returns
  true for the union of warrior stances + druid forms (combat AND utility:
  Travel, Aquatic, Flight, Swift Flight) + Ghost Wolf + base Stealth +
  Shadowform + Metamorphosis + DK Presences + Hunter Aspects (combat AND
  utility: Cheetah, Pack). `Aura::CanStackWith` (`SpellAuras.cpp`) refuses
  to stack two spells from this set, which routes through
  `_RemoveNoStackAurasDueToAura` to drop the older aura -- the same
  mechanism Battle Elixirs / Curses use. Plugs the stock-AC gap where DK
  Presences and Hunter Aspects (regular auras, just rendered in the
  stance bar) coexisted with engine-shapeshifts.

* Stances / Presences / Aspects cancellable like Druid forms.
  `SpellInfoCorrections.cpp` now zeroes `CategoryEntry` on warrior
  stances, DK presences, and every rank of every hunter aspect (moves
  them out of SpellCategory 47 "Combat States", which gates the client's
  right-click / `/cancelaura` path), AND clears `AttributesEx6` bit
  `0x1000` on warrior stances + DK presences (a second client-UI gate
  surfaced via DBC diff -- aspects don't have it set). Mirrored client-
  side by `_patch_spell_dbc_presences_cancelable.py`. Aspects / presences
  do NOT swap action bars (those are owned by `SPELL_AURA_MOD_SHAPESHIFT`,
  not by Category / AttrEx6) -- only warrior stances and druid forms keep
  the bar swap, matching the design requirement that presences/aspects
  not change the player's action bar.

* Hunter ammo soft-fail (server-wide).
  Replaced both `SPELL_FAILED_NO_AMMO` returns in `Spell::CheckCast` with
  `break;` so ranged + thrown abilities cast through with zero ammo;
  `_ApplyAmmoBonuses` continues to gate the actual arrow/bullet DPS bonus
  on a non-empty stack, so equipping ammo still pays off. New programmatic
  `ApplySpellFix`-style block in `SpellInfoCorrections.cpp` iterates every
  Hunter-family spell whose `EquippedItemClass == ITEM_CLASS_WEAPON` and
  `EquippedItemSubClassMask` includes Bow/Gun/Crossbow and sets
  `EquippedItemClass = -1` (skipping a small DENYLIST of Quiver / Ammo
  Pouch passive haste auras + Aynasha's Bow + Legendary Bow Haste -- those
  are item-equip-driven and must keep gating on the ranged weapon being
  equipped). Server log: ">> Fractured: dropped EquippedItemClass on 196
  hunter shot abilities". Mirrored client-side by the new
  `_patch_spell_dbc_hunter_ammo.py` so the 3.3.5a client preflight stops
  blocking the cast packet with "Ammo needs to be in the paper doll ammo
  slot before it can be fired." `Spell::TakeAmmo` no longer clears
  `PLAYER_AMMO_ID` to 0 when the bag empties (defense in depth so a half-
  deployed pair degrades to soft-fail rather than hard-reject). Adds
  `#include "ItemTemplate.h"` for `ITEM_SUBCLASS_WEAPON_*`.

* Feral Cat scaling (server-wide, cat-only).
  `StatSystem.cpp` `UpdateAttackPowerAndDamage` FORM_CAT branch doubles
  the AGI coefficient (1.0 -> 2.0). `SpellAuraEffects.cpp` Master
  Shapeshifter FORM_CAT branch doubles the talent's bp before triggering
  48420 (R1: 2% -> 4% crit, R2: 4% -> 8%). FORM_BEAR / FORM_DIREBEAR /
  FORM_MOONKIN / FORM_TREE branches all left untouched so bear stays
  "already fine" per the resident Feral expert. Client tooltip drift on
  Cat Form (768) + Master Shapeshifter (48411 / 48412) + Pestilence
  (50842) handled by `_patch_spell_dbc_feral_tooltips.py`.

* Pestilence spreads / refreshes Devouring Plague for Paragon casters.
  `spell_dk.cpp` `spell_dk_pestilence::HandleScriptEffect` now also
  spreads (and Glyph-of-Disease refreshes) Priest Devouring Plague when
  `IsParagonWildcardCaller(caster)`. Uses `GetAuraOfRankedSpell(2944)` so
  the spread copy carries the caster's actual rank. Stock DKs cannot
  cast Devouring Plague at all, so this branch is a no-op for them.

* Dancing Rune Weapon: Paragon copies melee, not casts.
  `spell_dk.cpp` `spell_dk_dancing_rune_weapon::CheckProc` for Paragon
  callers requires `eventInfo.GetDamageInfo()` and
  `spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE`, so the ghostly
  weapon now copies cross-class melee strikes (Hamstring, Sinister Strike,
  Heart Strike, Frost Strike, ...) and auto-attacks instead of re-casting
  the DK's nukes. Stock DK gating below is untouched.

* Maelstrom Weapon: drop Arcane Blast from the allowlist.
  `SpellInfo.cpp` and `spell_shaman.cpp` allowlists now match Fireball
  and Frostbolt only -- Arcane Blast stacked with its own self-buff was
  too potent.

* `BALANCE-TODO.md` added under `contrib/fractured-dev-extras/` to
  capture the resident Feral expert's recommendation, the levers we
  considered, and the cat-only Master-Shapeshifter / AGI-doubling
  resolution we shipped, plus the next-lever knobs if field reports
  still flag cat as weak.

DBC patcher pipeline (lives outside the repo in `fractured-tooling/`)
documented run order: runes -> reagents -> stances -> presences_cancelable
-> hunter_ammo -> feral_tooltips -> _make_paragon_dbc_patch.

No SQL migrations.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 23:17:37 -04:00
Docker Build d1d68cb44a fix(scripts): update server paths for new VPS location
Start script now defaults to /home/fractured-panel/azeroth-server and
passes -c flags so binaries find configs regardless of compiled-in path.
Update script gains --prefix flag to override CMAKE_INSTALL_PREFIX
(persisted to conf/config.sh) during rebuild.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 19:44:03 -05:00
Docker Build 999f7e94bd Paragon: narrow weapon bypass; cascade talent ranks; Savage Defense
* Weapon-narrow: replace blanket EquippedItemSubClassMask bypass with an
  explicit allowlist (IsParagonWeaponSubclassWildcardSpell, currently
  Maelstrom Weapon 51528 only). Applied at all three gates: Player::
  HasItemFitToSpellRequirements, Player::CheckAttackFitToAuraRequirement,
  and Aura::IsProcTriggeredOnEvent. Maelstrom Weapon still procs from any
  weapon for Paragon, but Hack and Slash / Sword / Mace Specialization
  remain correctly weapon-gated.
* Talent ability-rank cascade: TeachLevelGatedAbilityChainNoPanel +
  CascadeRanksForTalentLearnSpellEffects walk the SPELL_EFFECT_LEARN_SPELL
  chain a talent rank grants and learn each rank up to the player's level.
  Wired into HandleCommit (on talent purchase) and OnPlayerLevelChanged
  (on level-up). Fixes Mangle (and any future LEARN_SPELL talent) being
  stuck at rank 1 because Player::learnSkillRewardedSpells is intentionally
  disabled for Paragon's class skill lines.
* Riding-skill gate for flight forms (IsParagonSpellAllowedByRidingSkill):
  Flight Form (33943) requires Expert Riding (34090); Swift Flight Form
  (40120) requires Artisan Riding (34091). Applied in PanelLearnSpellChain
  and TeachLevelGatedAbilityChainNoPanel so the cascade can't push past a
  rank the player isn't trained for. Also backticks `rank` in the level-up
  query (MySQL reserved word).
* Savage Defense (62600) on the panel: the SpellData bake's blanket
  SPELL_ATTR0_PASSIVE filter dropped Savage Defense even though Druid
  trainer 33 sells it at level 40. Bake (in fractured-tooling) now carves
  out a small PASSIVE_TRAINER_ALLOWLIST and the regenerated
  paragon_spell_ae_cost.sql + 2026_05_11_05.sql migration surface it on
  the Druid Feral spell tab.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 19:09:25 -04:00
Docker Build 7c57abd69f Paragon: weapon-class subclass bypass for proc talents (Maelstrom Weapon any weapon)
Cross-class wildcard now also relaxes the EquippedItemSubClassMask
gate on weapon-class proc talents so e.g. Maelstrom Weapon procs
from any weapon a Paragon has equipped, not just the talent's stock
melee subset (axe / mace / staff / fist / dagger / 2H sword/axe/mace).

Three independent gates run in the proc chain; all three needed the
bypass for the proc to actually fire:

- Player::HasItemFitToSpellRequirements -- talent-attach check at
  item-equip / login time. Without this the passive talent aura
  never even applies for a Paragon wielding a non-stock weapon.
- Player::CheckAttackFitToAuraRequirement -- per-swing match the
  proc engine uses to decide whether attackType + weapon is
  compatible with the aura's EquippedItemClass / SubClassMask.
- Aura::IsProcTriggeredOnEvent (SpellAuras.cpp) -- per-event proc
  evaluator that calls Item::IsFitToSpellRequirements again,
  independently of the previous two. Was the proc-killing gate
  before this commit: talent attached, swing matched, but this
  evaluator returned 0 charges for any weapon outside the stock
  subclass mask, so no stack was ever applied.

All three bypasses are gated on:
- IsParagonWildcardCaller(this) (player class 12 + config flag
  Paragon.WildcardFamilyMatching = 1).
- spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON. ARMOR-class
  gates (shield) are deliberately left alone -- shield-required
  talents (Shield Specialization, Shield Block, etc.) still need
  an actual shield equipped.

Each bypass also requires *some* weapon to be in the relevant slot
(MAINHAND / OFFHAND / RANGED). Unarmed Paragons do not auto-activate
every weapon-gated talent in the game.

Net effect for Paragon characters with Maelstrom Weapon talented:
proc fires from any melee weapon -- 1H sword / polearm / spear /
fist / dagger / staff / 2H weapons / axes / maces. Stock Shamans
and every other non-Paragon class are unchanged.

Caveat (not addressed by this commit): the talent's stock ProcFlags
(0xC00014) only fire on melee swings + melee abilities. Ranged
auto-attacks (bow / gun / crossbow / wand) fire
PROC_FLAG_DONE_RANGED_AUTO_ATTACK (0x40), which the proc engine
never matches against this talent, so the new weapon-subclass
bypass is moot for those weapons. Adding ranged-auto-attack support
would require a Paragon-only ProcFlags expansion at the proc
engine layer; deferred until requested.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 18:19:42 -04:00
Docker Build e649402163 Paragon: cross-class talents + Warrior stance bypass + Mirror Image spellbook draw
Server-side cross-class wildcard pass for several talents that were
previously locked to a single SpellFamilyName, plus a server+client
Warrior stance bypass and a Paragon-aware Mirror Image rebuild that
mimics the owner's spellbook instead of stock Frostbolt/Fire Blast.

Talent expansions (Paragon owners only; stock classes unchanged):
- Cold Snap (11958): resets cooldown of any Frost-school spell.
- Nature's Swiftness (17116, 16188) + Predator's Swiftness (69369):
  instant-cast on any Nature-school spell.
- Vampiric Embrace (15286): leech-heals from any single-target
  Shadow-school spell.
- Fingers of Frost (44543/44545) + Frostbite (11071/12496/12497):
  proc from any Frost-school chill effect (DK Howling Blast / Icy
  Touch / Chains of Ice, Hunter Frost Trap, Shaman Frost Shock,
  cross-class chill auras via SPELL_AURA_MOD_DECREASE_SPEED).
- Maelstrom Weapon (53817): now also affects Mage Fireball (133),
  Frostbolt (116), and Arcane Blast (30451) at every rank, both
  for cast-time/cost spellmod and for stack consumption.

Warrior stance bypass:
- SpellInfo::CheckShapeshift returns SPELL_CAST_OK whenever a
  Paragon caster hits any Stances!=0 spell (no SpellFamilyName
  gate). Stock classes still see the regular form rules.
- Client side: patch-enUS-4.MPQ now zeroes Stances on every
  SPELLFAMILY_WARRIOR Spell.dbc row (105 spells) so the engine's
  pre-cast "Must be in Battle/Defensive/Berserker Stance" check
  no longer eats CMSG_CAST_SPELL packets for Paragons. Server
  bypass enforces the actual decision; stock Warriors still
  error mid-cast if they actually click while out of stance.
- patch-enUS-5.MPQ Lua tooltip post-processor recolors and
  appends "(Paragon: bypassed)" to "Requires *Stance*" lines on
  Warrior abilities, plus Paragon notes on Maelstrom Weapon and
  Mirror Image tooltips. Action-bar UseAction wrapper routes
  stance-gated Warrior spell clicks through CastSpellByName so
  the stance-zero DBC + server bypass actually run.

Mirror Image:
- npc_pet_mage_mirror_image rebuilds its spell list from the
  Paragon owner's spellbook on InitializeAI AND JustEngagedWith
  (the second pass + events.Reset clears any stale events the
  CasterAI base scheduler may have queued from stock 59637 /
  59638 entries before the rebuild ran).
- Curated filter keeps single-target damaging spells (instant,
  cast-time, or channeled) with a base cooldown <=10s, with the
  "damaging" definition expanded to include
  SPELL_EFFECT_TRIGGER_MISSILE and
  SPELL_AURA_PERIODIC_TRIGGER_SPELL so Arcane Missiles
  qualifies. Rejects passives, AoE, melee/ranged weapon strikes,
  item/reagent/stance/equip-gated, and lower spell ranks.
- UpdateAI picks a random spell from the curated list per cast
  and reschedules the next pick by the actually-cast spell's
  cast/channel duration + 750ms breather, so a 5s Arcane
  Missiles channel waits its full duration before re-rolling
  rather than visually looping across four images.

Helpers:
- Unit::IsParagonWildcardCaller / Unit::ParagonFamilyMatches
  used by Spell.cpp, SpellInfo.cpp, Player.cpp,
  SpellAuraEffects.cpp, and the spell scripts.
- SpellInfo::CheckShapeshift signature gains an optional caster
  pointer; all call sites updated.

SQL migrations under modules/mod-paragon/data/sql/db-world/updates/:
- 2026_05_11_01.sql  Vampiric Embrace spell_proc relax + script
                     gate (CheckProc enforces stock for non-Paragon).
- 2026_05_11_02.sql  Maelstrom Weapon spell_proc relax (initial,
                     superseded by _04 below for stack-consumption fix).
- 2026_05_11_03.sql  Fingers of Frost / Frostbite spell_proc relax
                     and spell_script_names binding.
- 2026_05_11_04.sql  Maelstrom Weapon spell_proc fixup: restore
                     SpellPhaseMask=1 (CAST) and AttributesMask=8
                     (REQ_SPELLMOD); previous _02 set 8/0 which
                     silently dropped every proc event.

Diagnostics from this debugging session demoted from LOG_INFO to
LOG_DEBUG (silent at default info level) so production logs stay
quiet but the probes remain available for reproducing future
regressions: pet_mage.cpp MirrorImage probe/kept/rebuild/init/
engage/cast lines and SpellInfo.cpp CheckShapeshift bypass line.

CLIENT-PATCHES.md updated to document the new Warrior stance DBC
patcher (_patch_spell_dbc_stances.py), the spell-tooltip post-
processor and stance UseAction wrapper in patch-enUS-5.MPQ, and
the Mirror Image / Maelstrom Weapon Paragon notes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 14:54:05 -04:00
Docker Build a1c9172beb Paragon cross-class family wildcard + Predatory Strikes proc
- CONFIG_PARAGON_WILDCARD_FAMILY + Paragon.WildcardFamilyMatching (reloadable)
- SpellInfo::IsAffected / IsAffectedBySpellMod(listenerOwner) for Paragon proc/mod wildcard
- SpellMgr::CanSpellTriggerProcOnEvent(procOwner) + Aura::IsProcTriggeredOnEvent wiring
- Player::IsAffectedBySpellmod passes listener for SpellMod wildcard
- ParagonFamilyMatches helper + Nourish / Shred-Maul bleed gate usage in Unit.cpp
- Spell::prepare: Paragon consumes 69369 for Nature spells <10s base cast (non-channeled)
- spell_paragon_predatory_strikes + SQL 2026_05_11_00.sql (spell_proc + spell_script_names)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 01:25:18 -04:00
Docker Build b408c8a95d Add script to start auth and world servers detached from SSH
Runs authserver then worldserver from /root/azeroth-server/bin by default,
kills existing instances, and uses nohup/disown so processes survive logout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 00:15:36 -05:00
Docker Build f88a303327 feat(launcher): Linux play wrapper, patch UX, Gitea sync cleanup
- Play on Linux: use launch.linux_wrapper (wine) or linux_steam_uri; chmod .exe after install
- Windows: retry EBUSY on MPQ replace; download to .new before rename
- After successful sync: remove .bak-* backups; realmlist only Data/enUS (ignore enGB)
- Gitea/distro merge: skip Fractured-Launcher* from GitHub assets (CI default-branch build wins)
- Omit blockmap and builder-debug from staged artifacts and Gitea uploads; upload script validates before clearing attachments
- README and launcher version 1.0.12

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 23:20:22 -05:00
Docker Build 8ad6a2aca3 Paragon: cascade guard for class skill lines + panel catalog backfill
The skill-line cascade in Player::learnSkillRewardedSpells re-fires from
_LoadSkills (every login), UpdateSkillsForLevel (every level-up),
UpdateSkillPro (every weapon-skill tick on a training dummy), and
SetSkill (first time a class skill is granted). Each pass re-grants
every SkillLineAbility-tagged class ability on the matching skill line,
which leaks Blood Presence / Death Coil / Death Grip / etc. back into
the spellbook within seconds even after the player intentionally
refunded them via the Character Advancement panel.

Path B fix: a 5-line guard at the top of learnSkillRewardedSpells skips
the cascade for class-category skill lines on CLASS_PARAGON characters.
mod-paragon already calls Player::learnSpell directly for the abilities
the player actually purchased (and their attached passives), so the
panel becomes the sole authority over class abilities. Profession,
weapon, language, and racial cascades stay enabled so recipe auto-learn,
weapon proficiencies, and racial perks still work.

Side effect: passives that previously rode along on the cascade
(Forceful Deflection on Blood Strike, Runic Focus on Icy Touch) must be
force-attached the same way Blood Plague / Frost Fever already are.
Extend kAttached and kFixup in Paragon_Essence.cpp to do that; existing
characters self-heal on next login.

Backfill paragon_spell_ae_cost for 42 spells newly exposed by the panel
after the ClassMask=0 filter was removed from the client catalog
generator (Lava Burst, Hex, Evocation, Kill Shot, Path of Frost,
Horn of Winter, Rune Strike, Raise Ally, Dark Command, etc.). Migration
is INSERT IGNORE so any per-spell tuning on existing rows is preserved.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 23:53:13 -04:00
Docker Build 36ac3dbd1d fix(launcher): force .MPQ extension uppercase on disk for WoW compatibility
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 22:09:01 -05:00
Docker Build 24d1ae71d9 fix(launcher): install release MPQs under Data/enUS (not Data root)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 22:06:48 -05:00
Docker Build 9cef99f0ff feat(launcher): sync release assets from manifest or attachment list (no fixed exe name)
- default files []: resolve sync list from patch-manifest keys, else discover
  release attachments (exclude launcher artifacts).
- Explicit files[] still overrides; strip deprecated Wow-patched.exe on merge.
- listReleaseAttachmentNames + fetchGiteaReleaseRecord helpers.
- Version 1.0.7; README config docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 22:04:48 -05:00
Docker Build f409ffad12 fix(launcher): Gitea http URL; Wine Z: path + Wow.exe case check
- baked-gitea-channel: http:// for brassnet mirror.
- win-game-dir: map Unix /home/... to Z:\ under win32 (Wine folder picker).
- resolveGameDir + saveGameDir + patch paths use it; Wow.exe resolved case-insensitively.
- Version 1.0.6; README checklist for Wine.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 21:54:04 -05:00
Docker Build c1f7eaa153 fix(launcher): clearer fetch errors for Gitea TLS/DNS (fetch failed)
- fetchOrThrow wraps global fetch with TLS/DNS/refused hints + URL (sanitized).
- Use in gitea-release, github paths; fetchToFile already benefits.
- README checklist for sync Wow.exe fetch failed; version 1.0.5.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 21:46:48 -05:00
Docker Build b455db0db8 fix(launcher): drop patch-Z.MPQ from default files and migrate old configs
- default-launcher.json files: only Wow-patched.exe from release.
- config-store: strip deprecated patch-Z.MPQ from merged files; rewrite
  launcher.json on load if user still had that entry.
- Docs/scripts examples updated; version 1.0.4.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 21:36:50 -05:00
Docker Build 1fb284cb5c docs(launcher): clarify userData path wording for Linux config
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 21:33:07 -05:00
Docker Build ebd8d81924 fix(launcher): Linux/macOS packaged config in userData (AppImage EROFS)
AppImage mounts read-only at /tmp/.mount_*; writing launcher.json beside
execPath failed. Use app.getPath('userData') for linux/darwin when packaged.
Bump version to 1.0.3.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 21:32:43 -05:00
Docker Build 362084b829 ci(gitea-sync): validate workflow_dispatch tag; reject release title as ref
- Trim input; fail fast if tag contains whitespace (common mistake: pasting
  release title instead of git tag).
- Multiline GITHUB_OUTPUT for tag value safety.
- README checklist + input description clarify tag vs title.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 21:21:45 -05:00
Docker Build 656cf2d07d Paragon panel: keep cascade passives, strip free actives; DK passive DBC fix
- PanelLearnSpellChain: record every non-chain passive as panel_spell_child;
  only revoke non-passive (Blood Presence, Death Coil, Death Grip, etc.).
- RevokeUnwantedCascadeSpellsForPlayer: skip passive rewards on login sweep.
- RevokeBlockedSpellsForPlayer: migrate legacy passive revoke rows to
  children; walk (parent, revoked) pairs from DB.
- PruneSkillLineCascadeChildrenFromDb: only strip actives wrongly stored as
  children; never strip passives.
- SpellInfoCorrections: set SPELL_ATTR0_PASSIVE on Forceful Deflection (49410)
  and Runic Focus (61455) so IsPassive() matches spellbook behavior.
- PanelUnlearnTalentPurchase: mirror resetTalents (_removeTalentAurasAndSpells,
  _removeTalent, SendTalentsInfoData) so Beast Mastery loss triggers pet reset.
- OnPlayerLogin: run legacy passive attach before scoped cascade sweep.
- Add .paragon recalibrate GM command (RBAC modify): full panel reset + AE/TE
  reconciliation for selected player or self.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 22:20:13 -04:00
Docker Build bfe51f6ad4 docs(launcher): note manual Windows pack for local test
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 20:50:16 -05:00
Docker Build 2a3107a78d feat(launcher): Linux AppImage 1.0.2, Gitea sync + CI, manual pack script
- Add pack:linux (AppImage x64), linux/appImage artifact names in package.json.
- Gitea sync: parallel build-electron-linux, merge Windows+Linux into Gitea upload;
  rename Windows artifact to electron-dist-windows.
- Fractured launcher CI: electron-launcher-windows + electron-launcher-linux jobs.
- scripts/manual-pack-linux.sh for local test builds from current tree.
- Normalize Gitea base_url (prepend https if missing); baked channel uses full URL.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 20:50:06 -05:00
Docker Build 48826e21d6 refactor(launcher): hardcode Gitea channel in lib/baked-gitea-channel.js
- Merge baked base_url/owner/repo/release_tag at load time (no inject script,
  no fractured-release-channel.json, no CI env for pack).
- Fix mergeConfig deep-merge for gitea, patch_manifest, launcher_updates_from_github.
- Remove inject-release-channel.js and fractured-release-channel.json.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 20:15:38 -05:00
Docker Build 15c476c12d ci(gitea-sync): overlay launcher from default branch before pack
Release tags can point at commits older than launcher lib additions; building
only from the tag omitted gitea-release.js etc. Fetch default branch and
checkout tools/fractured-launcher-electron from it before npm ci/pack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 19:15:27 -05:00
Docker Build 6c4d7244c3 fix(launcher): add missing gitea-release and patch-manifest to repo
These modules were required by main.js / auto-update.js / github.js but never
committed, so packaged builds lacked them and crashed at startup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 19:08:55 -05:00
Docker Build 9fb80102c8 Paragon: spell unlearn queue + AE/TE reconciliation
Two related additions to mod-paragon:

  * HandleCommit gains a third payload section, " u:<id>,...", carrying
    spell IDs the player wants to refund + unlearn in the same commit
    that learns / talents through. The protocol stays backward-compat
    (older clients omit the section). PanelUnlearnSpellPurchase mirrors
    the per-spell branch of HandleParagonResetAbilities: tracked passive
    children are removed first, then the chain head, then panel_spells /
    panel_spell_children / panel_spell_revoked rows for that purchase
    are dropped, then LookupSpellAECost(head) is refunded into the
    cache. Unlearns are applied before learns inside the commit so the
    refund covers the same-commit spends. Allow-list for the silence
    window now includes chain ranks + panel_spell_children for the
    intentional unlearns so "You have unlearned X" toasts stay visible
    for the targeted spell while cascade dependents stay silenced.

  * ReconcileEssenceForPlayer reads panel_spells + panel_talents and
    sets the cache to ComputeStartingAE/TE(level) - sum-of-spends.
    Self-heals drift in either direction: clamps the cache down when
    the player has more essence than their level + spends allow
    (cheese clamp), and tops up when they have less (admin-tweak /
    crash recovery). Wired into OnPlayerLogin (after LoadCurrencyFromDb,
    before PushCurrency so the first balance the client sees is the
    reconciled one) and OnPlayerLevelChanged (replaces the old
    GrantLevelUpEssence delta -- Reconcile sets the absolute correct
    balance from level + spend, so it subsumes the per-level grant and
    the cheese clamp in one call). Costs come from the same
    paragon_spell_ae_cost / config keys HandleCommit uses so the math
    stays in lockstep across any future cost rebalance.

Both features ship in patch-enUS-6.MPQ v0.9.16: right-click a learned
spell row to queue an unlearn (header shows +N AE refund preview) and
hit Learn All to apply. The icon picker also got two fixes -- the
leading INV_Misc_QuestionMark is no longer duplicated, and the
selection ring is now a tooltip-border Frame anchored to the cell
bounds (the prior UI-ActionButton-Border texture rendered nearly
invisible at non-native sizes).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 19:57:47 -04:00
Docker Build 7028258084 feat(launcher): bake Gitea base_url/owner/repo into pack from env or channel file
- inject-release-channel.js merges GITEA_* (or fractured-release-channel.json) into
  default-launcher.json before electron-builder.
- CI passes existing GITEA_BASE_URL/OWNER/REPO secrets into the Windows pack job.
- npm run pack:win/publish:win run the injector; workflows use npm run pack:win.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 17:33:28 -05:00
Docker Build 5966eb0ffc scripts: document default mysql acore/acore for FRACTURED_MYSQL example
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 16:24:37 -05:00
Docker Build 90c8db0b04 scripts: tee vps-paragon-diagnostics output to var/vps-paragon-diagnostics-last.txt
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 15:57:54 -05:00
Docker Build 9240bf1243 scripts: clarify empty spell_dbc samples; add version + rune override probes
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 15:53:57 -05:00
Docker Build 88f8dcb0e7 scripts: extend vps-paragon-diagnostics for rune/RP DBC and binary parity
- Binary sha256 + revision-like strings for dev vs VPS compare
- worldserver.conf Rate.RunicPower and mod_paragon.conf Paragon.* keys
- MySQL: chrclasses_dbc 6/12, spell_dbc sample, spellrunecost join
- FRACTURED_SPELL_IDS override for custom spell spot-checks

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 15:46:35 -05:00
Docker Build 9cb3c79dbe fix(launcher): opt-in GitHub auto-update; clarify Gitea for from_release
- Gate electron-updater GitHub provider on launcher_updates_from_github (default false)
  so GITHUB_TOKEN no longer targets the source repo without latest.yml.
- Improve GitHub releases 404 hint when assets are on Gitea.
- Document in README and default-launcher.json.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 15:38:07 -05:00
Docker Build 75e3b59442 chore(gitea): add bootstrap-gitea-repo.sh for initial README commit
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 15:29:07 -05:00
Docker Build 030c2307c2 scripts: add vps-paragon-diagnostics.sh for native VPS triage
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 15:19:59 -05:00
Docker Build 27d54f15a2 fix(gitea): document and explain HTTP 422 repo is empty on release create
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 14:37:02 -05:00
Docker Build 5e18c2b766 docs(ci): explain Re-run vs Run workflow for Gitea sync (GH_TOKEN error)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 14:29:21 -05:00
Docker Build 1c85341b1f ci: disable electron-builder GitHub publish; add Gitea sync workflow
- Use --publish never in pack/CI so tagged builds do not require GH_TOKEN.
- Set build.publish to null and align publish:win with local-only packaging.
- Add Gitea release sync workflow and upload script; fetch script from default
  branch so reruns work for tags that predate the script.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 14:24:42 -05:00
Docker Build ef02839ea0 Paragon: Save-Current build, archive retired share codes, reset clears active
Server side of the v0.7.10 Builds drop. Squashes a few footguns from
the original Builds catalog and adds a one-click "save what I have
right now" path the Overview pane can hook directly into.

- HandleBuildSaveCurrent: new C BUILD SAVE_CURRENT verb. Inserts a
  fresh build row, snapshots the live panel into its recipe, sets it
  active. No AE/TE motion, no relearning -- just a named slot for
  whatever the player already has.
- Reset abilities / Reset talents now SetActiveBuildId(0) and re-push
  the catalog. Without this, the next swap silently overwrote the
  active build's saved recipe with the (now empty/partial) post-reset
  state -- effectively erasing the build.
- Delete of the *active* build is now a hard reset (HandleParagonResetAll):
  unlearn everything the panel bought, refund all AE/TE. Deleting a
  non-active slot still just removes the saved recipe row + parked pet.
- Load of the currently-active build is now a "revert to last snapshot"
  instead of a no-op refresh: keeps the saved recipe authoritative,
  parks the pet, resets, re-applies. Useful for discarding pending
  edits.
- After a successful Learn All while a build is active: archive the
  build's previous share_code + recipe into
  character_paragon_build_share_archive* (so codes already posted to
  Discord keep importing the frozen loadout), snapshot the new panel
  into the live build, assign a fresh share_code, push catalog.
- HandleBuildImport now falls back to the archive tables when a code
  isn't in the live catalog -- old shared codes resurrect the recipe
  they pointed at when they were retired.
- Imports never copy pet_number (the parked pet belongs to the source
  player); if the imported recipe contains Tame Beast we hint that the
  importer needs to tame their own pet.
- BuildPanelOwnedSpellsAllowlist now walks SPELL_EFFECT_LEARN_SPELL
  effects on talent rank spells (Mangle, Feral Charge, Mutilate, ...)
  so the login cascade sweep stops revoking talent-granted active
  abilities.

Schema: new mod-paragon migration 2026_05_10_05.sql adds
character_paragon_build_share_archive (+ _spells / _talents).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 15:12:12 -04:00
Docker Build 377927b878 chore(launcher): Electron-only distro, CI sync with Windows pack 2026-05-10 12:34:43 -05:00
Docker Build a251e56c59 Paragon: Builds QoL -- share codes, unload, remaining AE/TE on hover
- Replace the "favorite" toggle with import-by-share-code: every build
  gets a 6-char realm-unique alphanumeric code on creation; pasting one
  into the BuildsPane share box copies the recipe (name + icon + spells
  + talents) into the importer's catalog as a new build, with a fresh
  share code so the imported copy can be re-shared independently.
- Add C BUILD UNLOAD verb so the client can clear a stale active-build
  pointer without forcing a swap. Wired to a new "Unload (clear active)"
  right-click context menu entry on the active build.
- Per-build tooltip now shows "Remaining if loaded: X AE / Y TE",
  computed server-side as total_earned - recipe_cost. Negative renders
  red so the player sees insufficient-currency cases before clicking
  Load. Suppressed for the active build (HandleBuildLoad short-circuits
  on target == active so the line would be misleading).
- Schema migration 2026_05_10_04.sql: drop is_favorite from
  character_paragon_builds and add share_code CHAR(6) UNIQUE NULL with
  lazy backfill on every PushBuildCatalog (so pre-migration rows pick
  up codes the first time the player opens the panel).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 04:15:11 -04:00
Docker Build 7de018f7eb 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>
2026-05-10 02:35:55 -04:00
Docker Build abb25f56d1 Paragon: expand IsClass hooks and addon pet talent reset
Broaden OnPlayerIsClass for CLASS_CONTEXT_ABILITY, pet/charm/equip contexts; add PARAA C RESET PET TALENTS handler. Update CLIENT-PATCHES.md for patch-enUS-5/6 and PARAA.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 01:36:54 -04:00
Docker Build 7a92231614 Add scripts/vps-update-server.sh for native VPS git pull and compile
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 21:42:38 -05:00
Docker Build f2952c905a Fractured: strip class-spell reagents at load; Paragon relic ranged slot
- SpellInfoCorrections: zero Reagent/ReagentCount on spells with non-zero
  SpellFamilyName so class abilities no longer require shards, candles,
  etc., while profession crafts (SpellFamilyName 0) keep mats. Matches
  the client Spell.dbc bake in patch-enUS-4.MPQ.
- Paragon_SC: OnPlayerIsClass returns true for CLASS_CONTEXT_EQUIP_RELIC
  for paladin/druid/shaman/warlock/dk so Paragon can equip all relic types
  in the ranged slot.
- CLIENT-PATCHES: document Spell.dbc reagent pass, rune script order, and
  stock ammo slot behavior in patch-enUS-5.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 22:38:32 -04:00
Docker Build 8abd40f217 Paragon: give class 12 intrinsic AP and SP scaling from stats
Stock 3.3.5 hardcodes per-class stat -> AP/SP formulas in
Player::UpdateAttackPowerAndDamage and Unit::SpellBase{Damage,Healing}BonusDone,
so class 12 fell into the default branches and ended up with 0 AP and 0 SP
regardless of STR / AGI / INT / SPI. The character sheet, combat log, and
ability damage all reflected this, and Mental Quickness-style AP->SP plumbing
silently no-oped on Paragon characters.

Add Paragon-specific branches in core (no PlayerScript hooks - those caused
SIGSEGVs when the new mid-list enum entry shifted later hook ordinals and
broke vtable dispatch):

- StatSystem.cpp: melee and ranged AP = level*2 + STR + AGI - 20, mirroring
  the formula the UI patch already advertises in tooltips.
- Unit.cpp:       intrinsic SP    = level*2 + INT + SPI - 20 (clamped >=0),
  added symmetrically to SpellBaseDamageBonusDone and
  SpellBaseHealingBonusDone so the single advertised Spell Power value the
  character sheet renders matches what spells actually use in combat.

Drop the now-unused UnitDefines.h include in Paragon_SC.cpp - it was only
needed by the AP PlayerScript hook that was rolled back in favor of the
core change.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 20:39:23 -04:00
141 changed files with 22962 additions and 232 deletions
+161
View File
@@ -0,0 +1,161 @@
# When a release is published on this repo (or manual dispatch):
# 1. Builds the Electron launcher from that tag (npm run pack:win).
# 2. Downloads any assets attached to the same release on this repo (patches, Wow exe, …).
# 3. Merges them (launcher files win on name collision) and creates/updates the matching
# release on Fractured-Distro.
#
# Setup (GitHub → Settings → Secrets and variables → Actions):
# DISTRO_SYNC_TOKEN — PAT with releases write on Fractured-Distro (see repo README).
#
# Change DISTRO_REPO or the job `if:` if your GitHub slugs differ.
name: Sync release to Fractured-Distro
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Release tag on this repo (must exist; e.g. v1.0.0)'
required: true
type: string
permissions:
contents: read
env:
DISTRO_REPO: Dawnforger/Fractured-Distro
jobs:
meta:
runs-on: ubuntu-latest
if: github.repository == 'HighSocietyRaiding/Fractured'
outputs:
tag: ${{ steps.t.outputs.tag }}
steps:
- name: Resolve tag
id: t
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
fi
build-electron:
needs: meta
if: github.repository == 'HighSocietyRaiding/Fractured'
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.meta.outputs.tag }}
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: tools/fractured-launcher-electron/package-lock.json
- name: Install and pack (NSIS + portable)
working-directory: tools/fractured-launcher-electron
run: |
npm ci
npm run pack:win
- name: Stage launcher files for upload
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path launcher-publish | Out-Null
Copy-Item tools/fractured-launcher-electron/dist/*.exe launcher-publish/
if (Test-Path tools/fractured-launcher-electron/dist/latest.yml) {
Copy-Item tools/fractured-launcher-electron/dist/latest.yml launcher-publish/
}
- uses: actions/upload-artifact@v4
with:
name: electron-dist
path: launcher-publish/
sync-distro:
needs: [meta, build-electron]
if: github.repository == 'HighSocietyRaiding/Fractured'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
sparse-checkout: |
tools/fractured-launcher-electron/scripts
sparse-checkout-cone-mode: true
- uses: actions/download-artifact@v4
with:
name: electron-dist
path: /tmp/electron
- name: Merge main release assets + Electron build
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
. tools/fractured-launcher-electron/scripts/release-sync-filters.sh
TAG="${{ needs.meta.outputs.tag }}"
mkdir -p combined
mkdir -p /tmp/from-main
if gh release download "$TAG" -R "${{ github.repository }}" -D /tmp/from-main 2>/tmp/dl.err; then
shopt -s nullglob
for f in /tmp/from-main/*; do
if [ -f "$f" ]; then
bn=$(basename "$f")
if should_skip_existing_launcher_artifact "$bn"; then
echo "Skipping GitHub release asset (CI launcher or excluded): $bn"
continue
fi
cp -f "$f" combined/
fi
done
echo "Merged assets from ${{ github.repository }} release $TAG"
else
echo "Main release download note (continuing with launcher only):"
cat /tmp/dl.err || true
fi
shopt -s nullglob
for f in /tmp/electron/*; do
if [ -f "$f" ]; then
cp -f "$f" combined/
fi
done
echo "Combined directory:"
ls -la combined/
- name: Upload to Fractured-Distro
env:
GH_TOKEN: ${{ secrets.DISTRO_SYNC_TOKEN }}
run: |
set -euo pipefail
if [ -z "${GH_TOKEN:-}" ]; then
echo "Missing secret DISTRO_SYNC_TOKEN (PAT with access to $DISTRO_REPO)."
exit 1
fi
TAG="${{ needs.meta.outputs.tag }}"
shopt -s nullglob
files=(combined/*)
if [ "${#files[@]}" -eq 0 ]; then
echo "Nothing to upload (Electron pack produced no files?)."
exit 1
fi
SRC_URL="https://github.com/${{ github.repository }}/releases/tag/${TAG}"
if gh release view "$TAG" -R "$DISTRO_REPO" &>/dev/null; then
gh release upload "$TAG" -R "$DISTRO_REPO" "${files[@]}" --clobber
echo "Uploaded (clobber) to $DISTRO_REPO release $TAG"
else
gh release create "$TAG" -R "$DISTRO_REPO" \
--title "Fractured $TAG" \
--notes "Synced from [$TAG]($SRC_URL) on ${{ github.repository }}. Includes CI-built Electron launcher + release assets." \
"${files[@]}"
echo "Created $DISTRO_REPO release $TAG with ${#files[@]} asset(s)."
fi
@@ -0,0 +1,80 @@
# Verifies Electron launcher Windows pack and uploads installers for testing.
name: Fractured launcher CI
on:
workflow_dispatch:
push:
branches: [master, main]
paths:
- 'tools/fractured-launcher-electron/**'
- '.github/workflows/fractured-launcher-ci.yml'
pull_request:
paths:
- 'tools/fractured-launcher-electron/**'
- '.github/workflows/fractured-launcher-ci.yml'
permissions:
contents: read
concurrency:
group: fractured-launcher-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
electron-launcher-windows:
runs-on: windows-latest
timeout-minutes: 45
defaults:
run:
working-directory: tools/fractured-launcher-electron
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: tools/fractured-launcher-electron/package-lock.json
- name: Install and pack (NSIS + portable)
run: |
npm ci
npm run pack:win
- uses: actions/upload-artifact@v4
with:
name: fractured-launcher-electron-windows-${{ github.run_id }}
if-no-files-found: warn
path: |
tools/fractured-launcher-electron/dist/*.exe
tools/fractured-launcher-electron/dist/latest.yml
electron-launcher-linux:
runs-on: ubuntu-latest
timeout-minutes: 45
defaults:
run:
working-directory: tools/fractured-launcher-electron
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: tools/fractured-launcher-electron/package-lock.json
- name: Install and pack (AppImage)
run: |
npm ci
npm run pack:linux
- uses: actions/upload-artifact@v4
with:
name: fractured-launcher-electron-linux-${{ github.run_id }}
if-no-files-found: warn
path: |
tools/fractured-launcher-electron/dist/*.AppImage
tools/fractured-launcher-electron/dist/latest.yml
tools/fractured-launcher-electron/dist/latest-linux.yml
+234
View File
@@ -0,0 +1,234 @@
# Player-facing binaries live on self-hosted Gitea releases (same tag as this run).
# This workflow builds the Electron launcher (Windows + Linux) and uploads it together with
# whatever attachments already exist on that Gitea release (patches, Wow exe, …). It does **not**
# copy from GitHub releases anymore — create/publish the release on Gitea first, then run CI.
#
# Triggers:
# - release: published / released → GitHub “Release” (optional hook; tag must match Gitea).
# - workflow_dispatch → Actions → this workflow → “Run workflow” (enter tag = Gitea release tag).
#
# Troubleshooting: “Re-run failed jobs” on an OLD run replays the *original* workflow
# YAML. After changing this file on default branch, start a *new* run via “Run workflow”.
#
# Important: the Gitea release for the chosen tag must already exist with non-launcher assets
# if you expect them on the final upload (CI rewrites all attachments on that release).
#
# Steps: Windows (NSIS+portable) + Linux (AppImage) in parallel, launcher from DEFAULT BRANCH
# overlay on tag checkout → download existing Gitea attachments → merge CI launcher → upload to Gitea.
#
# Secrets: GITEA_BASE_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO
# Optional variable: GITEA_TARGET_REF (see tools/fractured-launcher-electron/README.md)
#
# Job guard: edit `if:` if github.repository is not HighSocietyRaiding/Fractured.
name: Gitea release — attach launcher builds
on:
release:
types: [published, released]
workflow_dispatch:
inputs:
tag:
description: 'Gitea release tag (e.g. v0.7.11-paragon-foo). Must match an existing Gitea release on GITEA_OWNER/GITEA_REPO.'
required: true
type: string
permissions:
contents: read
concurrency:
group: gitea-release-sync-${{ github.repository }}-${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.event.release.tag_name }}
cancel-in-progress: false
jobs:
meta:
runs-on: ubuntu-latest
if: github.repository == 'HighSocietyRaiding/Fractured'
outputs:
tag: ${{ steps.t.outputs.tag }}
steps:
- name: Resolve tag
id: t
shell: bash
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
RAW="${{ github.event.inputs.tag }}"
else
RAW="${{ github.event.release.tag_name }}"
fi
TAG="$(printf '%s' "$RAW" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
if [ -z "$TAG" ]; then
echo '::error::Tag input is empty. Paste the git tag (e.g. v0.7.11-…).'
exit 1
fi
if printf '%s' "$TAG" | grep -q '[[:space:]]'; then
echo '::error::Tag contains whitespace — use the exact Gitea tag (e.g. v0.7.11-…), not the release title.'
exit 1
fi
fi
{
echo "tag<<__TAG_EOF__"
echo "$TAG"
echo "__TAG_EOF__"
} >> "$GITHUB_OUTPUT"
build-electron:
needs: meta
if: github.repository == 'HighSocietyRaiding/Fractured'
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.meta.outputs.tag }}
# Release tags often point at server/game commits that predate launcher lib fixes.
# Always pack the launcher from default branch so app.asar includes the full tree.
- name: Overlay launcher from default branch
shell: bash
run: |
set -euo pipefail
DB="${{ github.event.repository.default_branch }}"
git fetch --no-tags --depth=1 origin "+refs/heads/${DB}:refs/remotes/origin/${DB}"
git checkout "origin/${DB}" -- tools/fractured-launcher-electron
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: tools/fractured-launcher-electron/package-lock.json
- name: Install and pack (NSIS + portable)
working-directory: tools/fractured-launcher-electron
run: |
npm ci
node -p "'Launcher package.json version: ' + require('./package.json').version"
npm run pack:win
- name: Stage launcher files for upload
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path launcher-publish | Out-Null
Copy-Item tools/fractured-launcher-electron/dist/*.exe launcher-publish/
if (Test-Path tools/fractured-launcher-electron/dist/latest.yml) {
Copy-Item tools/fractured-launcher-electron/dist/latest.yml launcher-publish/
}
- uses: actions/upload-artifact@v4
with:
name: electron-dist-windows
path: launcher-publish/
build-electron-linux:
needs: meta
if: github.repository == 'HighSocietyRaiding/Fractured'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.meta.outputs.tag }}
- name: Overlay launcher from default branch
shell: bash
run: |
set -euo pipefail
DB="${{ github.event.repository.default_branch }}"
git fetch --no-tags --depth=1 origin "+refs/heads/${DB}:refs/remotes/origin/${DB}"
git checkout "origin/${DB}" -- tools/fractured-launcher-electron
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: tools/fractured-launcher-electron/package-lock.json
- name: Install and pack (AppImage)
working-directory: tools/fractured-launcher-electron
run: |
npm ci
node -p "'Launcher package.json version: ' + require('./package.json').version"
npm run pack:linux
- name: Stage Linux launcher for upload
shell: bash
run: |
set -euo pipefail
mkdir -p launcher-linux-publish
shopt -s nullglob
cp -f tools/fractured-launcher-electron/dist/*.AppImage launcher-linux-publish/ 2>/dev/null || true
for f in tools/fractured-launcher-electron/dist/latest.yml tools/fractured-launcher-electron/dist/latest-linux.yml; do
if [ -f "$f" ]; then cp -f "$f" launcher-linux-publish/; fi
done
ls -la launcher-linux-publish/
if ! compgen -G "launcher-linux-publish/*.AppImage" > /dev/null; then
echo "No AppImage under dist/ — electron-builder linux target failed" >&2
exit 1
fi
- uses: actions/upload-artifact@v4
with:
name: electron-dist-linux
path: launcher-linux-publish/
sync-gitea:
needs: [meta, build-electron, build-electron-linux]
if: github.repository == 'HighSocietyRaiding/Fractured'
runs-on: ubuntu-latest
env:
GITEA_BASE_URL: ${{ secrets.GITEA_BASE_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_OWNER: ${{ secrets.GITEA_OWNER }}
GITEA_REPO: ${{ secrets.GITEA_REPO }}
GITEA_TARGET_REF: ${{ vars.GITEA_TARGET_REF }}
steps:
- uses: actions/checkout@v4
with:
# Script may not exist on older release tags; always use default branch.
ref: ${{ github.event.repository.default_branch }}
sparse-checkout: |
tools/fractured-launcher-electron/scripts
sparse-checkout-cone-mode: true
- uses: actions/download-artifact@v4
with:
name: electron-dist-windows
path: /tmp/electron-win
- uses: actions/download-artifact@v4
with:
name: electron-dist-linux
path: /tmp/electron-linux
- name: Merge Gitea release assets + Electron build
run: |
set -euo pipefail
TAG="${{ needs.meta.outputs.tag }}"
mkdir -p combined
mkdir -p /tmp/from-gitea
bash tools/fractured-launcher-electron/scripts/download-release-from-gitea.sh /tmp/from-gitea "$TAG"
shopt -s nullglob
for f in /tmp/from-gitea/*; do
if [ -f "$f" ]; then
cp -f "$f" combined/
fi
done
for f in /tmp/electron-win/* /tmp/electron-linux/*; do
if [ -f "$f" ]; then
cp -f "$f" combined/
fi
done
ls -la combined/
- name: Upload to Gitea
run: |
set -euo pipefail
for v in GITEA_BASE_URL GITEA_TOKEN GITEA_OWNER GITEA_REPO; do
if [ -z "${!v:-}" ]; then
echo "Missing secret $v — add it under repo Settings → Secrets and variables → Actions." >&2
exit 1
fi
done
bash tools/fractured-launcher-electron/scripts/upload-release-to-gitea.sh combined "${{ needs.meta.outputs.tag }}"
+4
View File
@@ -118,6 +118,10 @@ local.properties
!/modules/mod-paragon/** !/modules/mod-paragon/**
!/modules/mod-ale/ !/modules/mod-ale/
!/modules/mod-ale/** !/modules/mod-ale/**
!/modules/mod-transmog/
!/modules/mod-transmog/**
!/modules/mod-aoe-loot/
!/modules/mod-aoe-loot/**
# Team Docker: ALE needs Lua at configure time (see mod-ale docs) # Team Docker: ALE needs Lua at configure time (see mod-ale docs)
!docker-compose.override.yml !docker-compose.override.yml
@@ -0,0 +1,94 @@
# Fractured / Paragon — Balance Backlog
Open balance / scaling questions surfaced by play-testers that have not yet
been actioned. Each entry should record the *symptom*, the *suspected cause*
based on a quick code dive, the *option set* we discussed, and any *links*
to relevant code. Knock items off as they ship.
---
## Feral Cat scaling feels weak (2026-05-11)
**Reporter feedback:**
> "Weapons don't automatically feature feral AP on this server and nothing
> is currently rescaled resulting in a super low feral scale. We can either
> rescale their abilities or add the AP back to all weapons."
>
> Resident Feral expert: "this is not a bear issue unfortunately, I have
> 11k AP" / "Stam > AP and Armor > AP means this is completely a cat issue."
**What's actually happening on the server:**
Feral AP *is* being granted on weapons — `ItemTemplate::getFeralBonus`
(`src/server/game/Entities/Item/ItemTemplate.h`) synthesises it from the
weapon's DPS for any weapon with `INVTYPE_WEAPON / 2HWEAPON / WEAPONMAINHAND
/ WEAPONOFFHAND`:
```cpp
int32 bonus = int32((extraDPS + getDPS()) * 14.0f) - 767;
```
That's then routed through `Player::ApplyFeralAPBonus` (Player.cpp ~6896)
into `m_baseFeralAP`, which `Player::UpdateAttackPowerAndDamage` adds into
the cat / bear formulas in `src/server/game/Entities/Unit/StatSystem.cpp`
(~line 477):
```cpp
case FORM_CAT:
val2 = (level * mLevelMult) + STR*2 + AGI - 20 + weapon_bonus + m_baseFeralAP;
break;
case FORM_BEAR:
case FORM_DIREBEAR:
val2 = (level * mLevelMult) + STR*2 - 20 + weapon_bonus + m_baseFeralAP;
break;
```
So bear and cat get the same `m_baseFeralAP` — the only delta is `+ AGI` for
cat. The "bears feel fine, cats feel weak" complaint is real; it's because
bear damage is rage / proc / mitigation driven (Lacerate stack, Savage
Defense, Pulverize-style talents) while cat damage is much more
AP-coefficient driven (Shred, Mangle, Rake, Rip, FB).
**Options discussed (pick when we revisit):**
| ID | Lever | Pros | Cons |
|----|-------|------|------|
| A | Bump the cat AP formula (e.g. `+ AGI*1.5`) | Cleanest, very tunable, hits both auto-attacks and abilities | Blunt instrument, also affects PvP |
| B | Boost cat-form ability coefficients (Shred / Rake / Rip / Mangle / FB) via spellmod auras | Most retail-faithful, surgical | More moving parts, harder to communicate |
| C | Increase `getFeralBonus` payout for druid weapons (e.g. `* 18.0f` or drop the `-767` floor) | Single-line change | Buffs bears too — bears are already fine, would over-buff |
| D | New Cat-only passive "Predator's Edge" = `+X% physical damage in Cat Form` | Easy to balance, easy to communicate, easy to undo | Adds another aura to track |
**Recommendation when we pick this back up:** start with **D + small A**
D is the readable "+15-20% cat damage" knob, A is a backup if AP-scaling
abilities (Mangle / FB) still feel weak relative to bleeds. Both are
trivially tunable via a single config knob during play-testing.
Do **not** pick C — it over-buffs bears, which the Feral expert explicitly
said are already fine.
**Resolution (2026-05-11, second pass):** Per the resident Feral expert
("instead of adding a new passive, you could probably just increase Cat
Form's Master Shapeshifter value along with its tooltip, alongside buffing
the agi scaling") we shipped a hybrid of **A** and a *cat-only* knob that
sits next to **D** but reuses an existing aura instead of inventing a new
one:
* `StatSystem.cpp` `UpdateAttackPowerAndDamage` FORM_CAT branch now
reads `+ GetStat(STAT_AGILITY) * 2.0f` (stock 1.0). FORM_BEAR /
FORM_DIREBEAR / FORM_MOONKIN are untouched, so bear's "already fine"
state is preserved.
* `SpellAuraEffects.cpp` Master Shapeshifter FORM_CAT branch multiplies
the talent's value by 2 before triggering 48420 (cat-form aura).
Talent ranks: 2% -> 4% (R1), 4% -> 8% (R2) crit chance in Cat Form.
Bear / Moonkin / Tree branches still pass `bp` through unchanged.
* Client tooltip drift handled by
`fractured-tooling/from-workspace-root/_patch_spell_dbc_feral_tooltips.py`,
which appends a `[Fractured]` paragraph to the Description column of
Cat Form (768) and Master Shapeshifter ranks (48411 / 48412).
If field reports after this lands still say cat is weak, the next levers
are (in order): bump `2.0f` to `2.5f` in StatSystem.cpp, then bump the
Master Shapeshifter cat multiplier from `* 2` to `* 3` in
SpellAuraEffects.cpp, then -- only if those are exhausted -- revisit
**B** (per-ability spellmod coefficients).
+1 -1
View File
@@ -127,7 +127,7 @@ brew install cmake boost openssl@3 mysql readline ncurses p7zip
## 2. Clone the repo ## 2. Clone the repo
```bash ```bash
git clone https://github.com/Dawnforger/Fractured.git git clone https://git.hisora.dev/HighSocietyRaiding/Fractured.git
cd Fractured cd Fractured
``` ```
+27 -8
View File
@@ -1,28 +1,41 @@
# Fractured Client Patches # Fractured Client Patches
Binary client artifacts that pair with this server. They live on the Binary client artifacts that pair with this server. They live on the
[Releases page](https://github.com/Dawnforger/Fractured/releases), [Releases page](https://git.hisora.dev/HighSocietyRaiding/Fractured/releases),
**not** in the tree, so the repo stays lean and binaries can be **not** in the tree, so the repo stays lean and binaries can be
re-downloaded without bloating `git clone`. re-downloaded without bloating `git clone`.
This file is the table of contents and install guide. This file is the table of contents and install guide.
**Launcher (Windows):** The maintained client launcher lives in
[`tools/fractured-launcher-electron/`](../../tools/fractured-launcher-electron/)
(see its README for build and config). **Public downloads** for the launcher
and mirrored patch assets are pushed to
[Fractured-Distro releases](https://github.com/Dawnforger/Fractured-Distro/releases)
when a release is published here (workflow **Sync release to Fractured-Distro**).
--- ---
## What ships in a release ## What ships in a release
| Artifact | Size | Purpose | | Artifact | Size | Purpose |
|---|---|---| |---|---|---|
| `patch-enUS-4.MPQ` | ~5 MB | DBC + GlueXML bake. Adds `CLASS_PARAGON` (id 12), the character-create slot, glue strings, talent-tab DBC entries, and the Paragon resource bar definitions. Required for character creation as Paragon to even show up. | | `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 nonDeath 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, 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). | | `patch-enUS-5.MPQ` | ~64 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), a **spell-tooltip post-processor** that (1) recolors and appends "(Paragon: bypassed)" to "Requires *Stance*" lines on Warrior abilities (server-side `SpellInfo::CheckShapeshift` skips stance enforcement for Paragons on `SPELLFAMILY_WARRIOR` spells, but the client still renders the requirement from the stock `Stances` DBC field which we deliberately leave unzeroed so stock Warriors keep enforcement), (2) for Paragon players appends a line to **Maelstrom Weapon** (53817) noting that Fireball and Frostbolt also benefit from the buff, (3) for Paragon players appends a line to **Mirror Image** (55342) noting that the images cast random damage spells with a cast time from the caster's spellbook instead of Frostbolt, and (4) for **every** player appends a **Fractured** line to **Frozen Power** listing the spells that can proc the 15+ yard root (server `spell_sha_frozen_power` allowlist). Warrior stance lines, Maelstrom, and Mirror Image still require Paragon so other stock-class spell tooltips stay unchanged where those hooks apply, and **PetFrame** re-anchored so the **pet unit frame sits below the rune row** for Paragon (stock layout had runes overlapping the pet portrait). A **Warrior stance click bypass** wraps `UseAction` so that for Paragon characters, clicking an action slot bound to a stance-gated Warrior ability (Whirlwind, Charge, Pummel, Shield Slam, Hamstring, Overpower, Shield Bash, Shield Block, Disarm, Revenge, Spell Reflection, Recklessness, Bladestorm, Shockwave, Concussion Blow, Last Stand, Sweeping Strikes, Mocking Blow, Heroic Fury, Slam, Devastate, Intercept) routes through `CastSpellByName(name)` instead of the engine's stance-gated `UseAction` path; the engine's stance pre-check inside `UseAction` would otherwise drop the cast packet client-side and our server-side `CheckShapeshift` bypass would never get to run. Stock classes never enter the bypass branch. 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-6.MPQ` | ~134 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), 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, and a **Builds** page (full-pane overlay opened from the bottom-row Builds button) for saving named, icon-tagged loadouts: New Build (+) icon picker reuses `MACRO_ICON_FILENAMES`, right-click for edit/delete, shift-left-click to favorite (favorites bubble to the top), left-click pops a Load Build confirm. Build swaps reset + refund AE/TE, re-spend on the saved recipe, and **park hunter pets** to `PET_SAVE_NOT_IN_SLOT` so their name/talents/exp are preserved across swaps. |
| `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. | | `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 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 worldserver via `WHISPER` addon-channel messages with the `PARAA` prefix
(currency push, spell/talent snapshot, commit, combo points, rune (currency push, spell/talent snapshot, commit, combo points, rune
cooldowns, learn-toast silence window). Mismatched versions usually cooldowns, learn-toast silence window, **`C RESET PET TALENTS`**
manifest as the panel rendering blank or AE/TE reading 0/0. for hunter pet talent resets from the Character Advancement PETS tab,
and the **build catalog** verbs `Q BUILDS` / `C BUILD NEW` / `C BUILD
EDIT` / `C BUILD DELETE` / `C BUILD FAVORITE` / `C BUILD LOAD` for the
saved-loadout system on the Builds page). Build swaps require the
matching worldserver image because the swap path is server-driven
(snapshot → reset → re-spend → pet park/unpark). Mismatched versions
usually manifest as the panel rendering blank or AE/TE reading 0/0.
--- ---
@@ -33,7 +46,7 @@ download is the canonical clean source if you don't already have one.
1. Download every artifact from the latest release matching this server 1. Download every artifact from the latest release matching this server
build: build:
`https://github.com/Dawnforger/Fractured/releases/latest` `https://git.hisora.dev/HighSocietyRaiding/Fractured/releases` (open the latest release that matches this server build)
2. Replace `Wow.exe` at the root of your client install with the patched 2. Replace `Wow.exe` at the root of your client install with the patched
one from the release. Keep a backup of the original if you want. one from the release. Keep a backup of the original if you want.
3. Drop `patch-enUS-4.MPQ`, `patch-enUS-5.MPQ`, `patch-enUS-6.MPQ` into 3. Drop `patch-enUS-4.MPQ`, `patch-enUS-5.MPQ`, `patch-enUS-6.MPQ` into
@@ -233,7 +246,13 @@ tools\build_paragon_advancement_patch.ps1 -Deploy # -> patch-enUS-6.MPQ
`patch-enUS-4.MPQ` is the DBC + GlueXML bake; the bake scripts live with `patch-enUS-4.MPQ` is the DBC + GlueXML bake; the bake scripts live with
the rest of the dev tooling and are not part of this repo by design the rest of the dev tooling and are not part of this repo by design
(see the repo-tidy policy in `README.txt` next to this file). (see the repo-tidy policy in `README.txt` next to this file). Typical
order on a maintainer machine:
1. `fractured-tooling/from-workspace-root/_patch_spell_dbc_runes.py` — stage `Spell.dbc` with `RuneCostID` cleared.
2. `fractured-tooling/from-workspace-root/_patch_spell_dbc_reagents.py` — same staged `Spell.dbc`, clear class-spell reagents for client preflight.
3. `fractured-tooling/from-workspace-root/_patch_spell_dbc_stances.py` — same staged `Spell.dbc`, zero the `Stances` field on every `SpellFamilyName == 4` (Warrior) row so the client engine's "Must be in Battle/Defensive/Berserker Stance" pre-cast check stops eating `CMSG_CAST_SPELL` packets for Paragon casters who never picked the stance form. The server's `SpellInfo::CheckShapeshift` Paragon bypass takes over from there. Stock Warriors still see the server-side stance error mid-cast if they actually click while out of stance.
4. `fractured-tooling/from-workspace-root/_make_paragon_dbc_patch.py` — rebuild `ChrClasses` / `CharBaseInfo` / game tables, then pack `patch-enUS-4.MPQ`.
The patched `Wow.exe` is a one-time hex-edit of the stock 3.3.5a The patched `Wow.exe` is a one-time hex-edit of the stock 3.3.5a
client. The diff is publicly documented in the WoW emulation community client. The diff is publicly documented in the WoW emulation community
+3
View File
@@ -6,6 +6,9 @@ AzerothCore. Upstream AzerothCore does not ship these paths.
Contents: Contents:
- BUILD-NATIVE.md — fork-specific native build notes (moved from repo root). - BUILD-NATIVE.md — fork-specific native build notes (moved from repo root).
- BALANCE-TODO.md — open balance / scaling questions raised by play-testers
that have not yet been actioned (e.g. Feral Cat scaling). Knock items off
as they ship.
- CLAUDE.md — optional AI assistant context (moved from repo root). - CLAUDE.md — optional AI assistant context (moved from repo root).
- CLIENT-PATCHES.md — what ships in a Fractured client release (MPQs + - CLIENT-PATCHES.md — what ships in a Fractured client release (MPQs +
patched Wow.exe), where to download them (Releases page), and how patched Wow.exe), where to download them (Releases page), and how
+1 -1
View File
@@ -52,7 +52,7 @@ INSERT INTO `spell_proc` VALUES
(-65661,0,15,4194321,537001988,0,16,0,2,1143,0,0,0,100,0,0), (-65661,0,15,4194321,537001988,0,16,0,2,1143,0,0,0,100,0,0),
(-64127,0,6,1,1,0,0,6,2,0,0,0,0,0,0,0), (-64127,0,6,1,1,0,0,6,2,0,0,0,0,0,0,0),
(-63730,0,6,2048,4,0,0,0,2,0,0,0,0,0,100,0), (-63730,0,6,2048,4,0,0,0,2,0,0,0,0,0,100,0),
(-63373,0,11,2147483648,0,0,65536,1,2,0,0,0,0,0,0,0), (-63373,0,0,0,0,0,65536,1,2,0,0,0,0,0,0,0),
(-63156,0,5,1,192,0,0,1,2,0,0,2,0,0,0,0), (-63156,0,5,1,192,0,0,1,2,0,0,2,0,0,0,0),
(-62764,0,9,0,268435456,0,65536,4,2,0,0,0,0,100,0,0), (-62764,0,9,0,268435456,0,65536,4,2,0,0,0,0,100,0,0),
(-61846,0,0,0,0,0,64,0,2,0,0,0,0,0,0,0), (-61846,0,0,0,0,0,64,0,2,0,0,0,0,0,0,0),
+1 -1
View File
@@ -48,7 +48,7 @@ INSERT INTO `spell_proc_event` VALUES
(-65661,0,15,4194321,537001988,0,16,0,0,0,100,0), (-65661,0,15,4194321,537001988,0,16,0,0,0,100,0),
(-64127,0,6,1,1,0,0,0,0,0,0,0), (-64127,0,6,1,1,0,0,0,0,0,0,0),
(-63730,0,6,2048,4,0,0,0,1,0,0,0), (-63730,0,6,2048,4,0,0,0,1,0,0,0),
(-63373,0,11,2147483648,0,0,65536,0,0,0,0,0), (-63373,0,0,0,0,0,65536,0,0,0,0,0),
(-63156,126,5,1,192,0,65536,0,0,0,0,0), (-63156,126,5,1,192,0,65536,0,0,0,0,0),
(-62764,0,9,0,268435456,0,65536,0,0,0,100,0), (-62764,0,9,0,268435456,0,65536,0,0,0,100,0),
(-61846,0,0,0,0,0,64,0,0,0,0,0), (-61846,0,0,0,0,0,64,0,0,0,0,0),
@@ -0,0 +1,10 @@
-- DB update 2026_05_03_00 -> 2026_05_12_00
-- RBAC permission for .learn all mounts (Admin 196, Gamemaster 197).
DELETE FROM `rbac_permissions` WHERE `id` = 916;
INSERT INTO `rbac_permissions` (`id`, `name`) VALUES
(916, 'Command: learn all mounts');
DELETE FROM `rbac_linked_permissions` WHERE `linkedId` = 916;
INSERT INTO `rbac_linked_permissions` (`id`, `linkedId`) VALUES
(196, 916),
(197, 916);
@@ -0,0 +1,22 @@
-- DB update 2026_05_06_00 -> 2026_05_13_00
-- Fractured: raise stack size to 200 for socketable gems (item class 3), all Trade Goods
-- (class 7: cloth, leather, ore/bar, herbs, parts, elementals, dusts, etc.), and Reagents
-- (class 5). Rows with ItemLimitCategory or maxcount still obey those limits when looting/carrying.
UPDATE `item_template` SET `stackable` = 200
WHERE `class` = 3
AND `stackable` > 0
AND `stackable` < 200
AND `stackable` <> 2147483647;
UPDATE `item_template` SET `stackable` = 200
WHERE `class` = 7
AND `stackable` > 0
AND `stackable` < 200
AND `stackable` <> 2147483647;
UPDATE `item_template` SET `stackable` = 200
WHERE `class` = 5
AND `stackable` > 0
AND `stackable` < 200
AND `stackable` <> 2147483647;
@@ -0,0 +1,10 @@
-- DB update 2026_05_13_00 -> 2026_05_13_01
-- Fractured: Frozen Power (-63373) proc family gate cleared; eligible spells are enforced in spell_sha_frozen_power (cross-class nuke/shock list).
UPDATE `spell_proc`
SET `SpellFamilyName` = 0, `SpellFamilyMask0` = 0, `SpellFamilyMask1` = 0, `SpellFamilyMask2` = 0
WHERE `SpellId` = -63373;
UPDATE `spell_proc_event`
SET `SpellFamilyName` = 0, `SpellFamilyMask0` = 0, `SpellFamilyMask1` = 0, `SpellFamilyMask2` = 0
WHERE `entry` = -63373;
+8
View File
@@ -0,0 +1,8 @@
[*]
charset = utf-8
indent_style = space
indent_size = 4
tab_width = 4
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 80
+104
View File
@@ -0,0 +1,104 @@
## AUTO-DETECT
## Handle line endings automatically for files detected as
## text and leave all files detected as binary untouched.
## This will handle all files NOT defined below.
* text eol=lf
# Text
*.conf text
*.conf.dist text
*.cmake text
## Scripts
*.sh text
*.fish text
*.lua text
## SQL
*.sql text
## C++
*.c text
*.cc text
*.cxx text
*.cpp text
*.c++ text
*.hpp text
*.h text
*.h++ text
*.hh text
## For documentation
# Documents
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
## DOCUMENTATION
*.markdown text
*.md text
*.mdwn text
*.mdown text
*.mkd text
*.mkdn text
*.mdtxt text
*.mdtext text
*.txt text
AUTHORS text
CHANGELOG text
CHANGES text
CONTRIBUTING text
COPYING text
copyright text
COPYRIGHT text
INSTALL text
license text
LICENSE text
NEWS text
readme text
README text
TODO text
## GRAPHICS
*.ai binary
*.bmp binary
*.eps binary
*.gif binary
*.ico binary
*.jng binary
*.jp2 binary
*.jpg binary
*.jpeg binary
*.jpx binary
*.jxr binary
*.pdf binary
*.png binary
*.psb binary
*.psd binary
*.svg text
*.svgz binary
*.tif binary
*.tiff binary
*.wbmp binary
*.webp binary
## ARCHIVES
*.7z binary
*.gz binary
*.jar binary
*.rar binary
*.tar binary
*.zip binary
## EXECUTABLES
*.exe binary
*.pyc binary
@@ -0,0 +1,72 @@
name: Bug report
description: Create a bug report to help us improve.
title: "Bug: "
body:
- type: textarea
id: current
attributes:
label: Current Behaviour
description: |
Description of the problem or issue here.
Include entries of affected creatures / items / quests / spells etc.
If this is a crash, post the crashlog (upload to https://gist.github.com/) and include the link here.
Never upload files! Use GIST for text and YouTube for videos!
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behaviour
description: |
Tell us what should happen instead.
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce the problem
description: |
What does someone else need to do to encounter the same bug?
placeholder: |
1. Step 1
2. Step 2
3. Step 3
validations:
required: true
- type: textarea
id: extra
attributes:
label: Extra Notes
description: |
Do you have any extra notes that can help solve the issue that does not fit any other field?
placeholder: |
None
validations:
required: false
- type: textarea
id: commit
attributes:
label: AC rev. hash/commit
description: |
Copy the result of the `.server debug` command (if you need to run it from the client get a prat addon)
validations:
required: true
- type: input
id: os
attributes:
label: Operating system
description: |
The Operating System the Server is running on.
i.e. Windows 11 x64, Debian 10 x64, macOS 12, Ubuntu 20.04
validations:
required: true
- type: textarea
id: custom
attributes:
label: Custom changes or Modules
description: |
List which custom changes or modules you have applied, i.e. Eluna module, etc.
placeholder: |
None
validations:
required: false
@@ -0,0 +1,33 @@
name: Feature request
description: Suggest an idea for this project
title: "Feature: "
body:
- type: markdown
attributes:
value: |
Thank you for taking your time to fill out a feature request. Remember to fill out all fields including the title above.
An issue that is not properly filled out will be closed.
- type: textarea
id: description
attributes:
label: Describe your feature request or suggestion in detail
description: |
A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: solution
attributes:
label: Describe a possible solution to your feature or suggestion in detail
description: |
A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional context
description: |
Add any other context or screenshots about the feature request here.
validations:
required: false
+294
View File
@@ -0,0 +1,294 @@
# ![logo](https://raw.githubusercontent.com/azerothcore/azerothcore.github.io/master/images/logo-github.png) AzerothCore
# mod-aoe-loot
[English](README.md) | [Español](README_ES.md)
[![Build Status](https://github.com/azerothcore/mod-aoe-loot/workflows/core-build/badge.svg?branch=master&event=push)](https://github.com/azerothcore/mod-aoe-loot/actions)
## Description
This module enables Area of Effect (AOE) looting functionality for AzerothCore, allowing players to loot multiple nearby corpses by interacting with just one of them. All items and gold from corpses within the configured range are automatically collected into a single loot window.
## Features
- **AOE Looting**: Automatically collect loot from multiple nearby corpses with a single interaction
- **Player Toggle Commands**: Individual players can enable/disable AOE loot using `.aoeloot on/off` commands
- **Multi-language Support**: Full internationalization support with English and Spanish translations (easily extensible to other languages)
- **Configurable Range**: Server administrators can set the maximum distance for AOE loot collection
- **Group Support**: Optional group looting with configurable settings
- **Performance Optimized**: Limits number of corpses processed to maintain server stability
- **Smart Item Management**:
- Automatic gold accumulation with overflow protection
- Quest items sent directly to inventory
- Maximum 15 items per loot window to prevent UI issues
- **Corpse Management**: Automatically cleans up looted corpses to reduce clutter
## Recent Updates
### v2.0 - Player Control & Internationalization
- ✅ Added `.aoeloot on/off` player commands for individual control
- ✅ Implemented multi-language support via `acore_string` system
- ✅ Fixed enum ID alignment issues
- ✅ Complete English and Spanish translations
- ✅ Improved code documentation and structure
### v1.x - Core Functionality
- Initial AOE loot implementation
- Configuration system
- Range and group settings
## Requirements
- AzerothCore v3.0.0+ (latest master branch recommended)
- MySQL 8.0+
- Compiler with C++17 support
## Installation
### 1. Clone the Module
Navigate to your AzerothCore modules directory:
```bash
cd <ACoreDir>/modules
git clone https://github.com/azerothcore/mod-aoe-loot.git
```
### 2. Compile
Re-compile AzerothCore:
```bash
cd <ACoreDir>/build
cmake ../ -DCMAKE_INSTALL_PREFIX=/path/to/server -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++
make -j $(nproc)
make install
```
### 3. Configure
Edit your `worldserver.conf` file (or create `AOELoot.conf` in configs folder):
```conf
###################################################################################################
# AOE LOOT MODULE CONFIGURATION
###################################################################################################
#
# AOELoot.Enable
# Description: Enable or disable the AOE Loot module globally
# Default: 1 (enabled)
# 0 (disabled)
AOELoot.Enable = 1
#
# AOELoot.Range
# Description: Maximum distance (in yards) to collect loot from nearby corpses
# Default: 55.0
# Range: 5.0 - 100.0
AOELoot.Range = 55.0
#
# AOELoot.Group
# Description: Allow AOE looting when player is in a group
# Default: 1 (allowed)
# 0 (not allowed)
AOELoot.Group = 1
#
# AOELoot.Message
# Description: Show informational message on player login
# Default: 1 (show message)
# 0 (no message)
AOELoot.Message = 1
###################################################################################################
```
### 4. Corpse Decay Configuration (IMPORTANT)
For optimal experience, modify the corpse decay settings in `worldserver.conf`:
```conf
#
# Rate.Corpse.Decay.Looted
# Description: Multiplier for Corpse.Decay.* to configure how long creature corpses stay
# after they have been looted.
# Default: 0.5
# Recommended: 0.01 (for AOE Loot module)
Rate.Corpse.Decay.Looted = 0.01
```
**Why this is important:** The default decay rate (0.5) can cause corpses to linger after being looted via AOE, creating visual clutter. Setting this to 0.01 ensures corpses disappear quickly after looting.
### 5. Restart Server
Restart your worldserver to load the module:
```bash
./worldserver
```
## Usage
### For Players
#### Commands
- `.aoeloot on` - Enable AOE looting for your character
- `.aoeloot off` - Disable AOE looting for your character
#### How to Use
1. Kill multiple enemies in close proximity
2. Right-click on any corpse to loot
3. All items from nearby corpses will appear in a single loot window
4. Quest items are automatically added to your inventory
**Note:** Player preferences reset on logout. AOE loot is enabled by default if the module is active.
### For Administrators
The module can be controlled through configuration file settings (see Configuration section above).
## Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `AOELoot.Enable` | Boolean | 1 | Enable/disable module globally |
| `AOELoot.Range` | Float | 55.0 | Maximum loot collection radius (5.0 - 100.0) |
| `AOELoot.Group` | Boolean | 1 | Allow AOE loot in groups |
| `AOELoot.Message` | Boolean | 1 | Show login message |
## Multi-language Support
The module includes full multi-language support through AzerothCore's `acore_string` system.
### Currently Supported Languages
- 🇬🇧 English (en_US)
- 🇪🇸 Spanish (es_ES / es_MX)
### Adding More Languages
To add additional language support, update the SQL file:
```sql
UPDATE `acore_string` SET
`locale_frFR` = 'Votre traduction ici',
`locale_deDE` = 'Ihre Übersetzung hier',
`locale_ruRU` = 'Ваш перевод здесь'
WHERE `entry` BETWEEN 50000 AND 50007;
```
Supported locale columns:
- `locale_koKR` (Korean)
- `locale_frFR` (French)
- `locale_deDE` (German)
- `locale_zhCN` (Chinese Simplified)
- `locale_zhTW` (Chinese Traditional)
- `locale_ruRU` (Russian)
## Technical Details
### Database Entries
The module uses `acore_string` entries 50000-50007:
| Entry | Constant | Purpose |
|-------|----------|---------|
| 50000 | AOE_ACORE_STRING_MESSAGE | Login message |
| 50001 | AOE_ITEM_IN_THE_MAIL | Mail notification (reserved) |
| 50002-50003 | - | Reserved for future use |
| 50004 | AOE_LOOT_ALREADY_ENABLED | "Already enabled" message |
| 50005 | AOE_LOOT_ENABLED | "Enabled" confirmation |
| 50006 | AOE_LOOT_ALREADY_DISABLED | "Already disabled" message |
| 50007 | AOE_LOOT_DISABLED | "Disabled" confirmation |
### Performance Considerations
- Maximum 10 corpses processed per loot operation (hardcoded for stability)
- Maximum 15 items per loot window
- Gold overflow protection (prevents exceeding uint32 max value)
- Efficient corpse filtering and cleanup
## Troubleshooting
### Issue: AOE loot not working
**Solutions:**
- Verify module is enabled: `AOELoot.Enable = 1`
- Check if you disabled it personally: use `.aoeloot on`
- Ensure you're within range (default 55 yards)
- If in group, check `AOELoot.Group` setting
### Issue: Corpses not disappearing
**Solution:**
- Set `Rate.Corpse.Decay.Looted = 0.01` in worldserver.conf
### Issue: Messages in wrong language
**Solution:**
- Verify SQL was imported correctly
- Check client locale settings
- Confirm `acore_string` table has translations for your locale
### Issue: "Already enabled/disabled" messages appearing incorrectly
**Solution:**
- This is expected behavior - preferences reset on logout
- On first login, AOE loot is enabled by default
## Known Limitations
- Player preferences do not persist across logout/login sessions
- Maximum 10 corpses processed at once (performance limit)
- Quest items sent to inventory may fill bags quickly
- Range limited to 100 yards maximum
## Future Enhancements
Potential features for future versions:
- [ ] Database persistence for player preferences
- [ ] Configurable max corpses limit
- [ ] Quest item mail option (instead of direct inventory)
- [ ] Visual range indicator
- [ ] Per-character settings UI
- [ ] Statistics tracking (total items/gold looted)
## Credits
- **acidmanifesto** - [Original author and concept](https://github.com/azerothcore/mod-aoe-loot/pull/2)
- **AzerothCore Community** - Hooks, updates, and improvements
- **Contributors** - Player commands, multi-language support, and bug fixes
## Links
- **AzerothCore:** [Repository](https://github.com/azerothcore) | [Website](https://azerothcore.org/) | [Discord](https://discord.gg/PaqQRkd)
- **Module Repository:** [GitHub](https://github.com/azerothcore/mod-aoe-loot)
- **Issues & Suggestions:** [Issue Tracker](https://github.com/azerothcore/mod-aoe-loot/issues)
## License
This module is released under the [GNU AGPL v3 License](https://github.com/azerothcore/mod-aoe-loot/blob/master/LICENSE).
---
### Support
If you encounter any issues or have suggestions:
1. Check the [Troubleshooting](#troubleshooting) section
2. Search [existing issues](https://github.com/azerothcore/mod-aoe-loot/issues)
3. Join the [AzerothCore Discord](https://discord.gg/PaqQRkd)
4. Create a [new issue](https://github.com/azerothcore/mod-aoe-loot/issues/new) with detailed information
**Please include:**
- AzerothCore commit hash
- Operating system and version
- Complete error messages (if any)
- Configuration settings
- Steps to reproduce the issue
+293
View File
@@ -0,0 +1,293 @@
# ![logo](https://raw.githubusercontent.com/azerothcore/azerothcore.github.io/master/images/logo-github.png) AzerothCore
# mod-aoe-loot
[English](README.md) | [Español](README_ES.md)
[![Build Status](https://github.com/azerothcore/mod-aoe-loot/workflows/core-build/badge.svg?branch=master&event=push)](https://github.com/azerothcore/mod-aoe-loot/actions)
## Descripción
Este módulo habilita la funcionalidad de saqueo en área (AOE) para AzerothCore, permitiendo a los jugadores saquear múltiples cadáveres cercanos interactuando con solo uno de ellos. Todos los objetos y oro de los cadáveres dentro del rango configurado se recopilan automáticamente en una sola ventana de botín.
## Características
- **Saqueo AOE**: Recolecta automáticamente el botín de múltiples cadáveres cercanos con una sola interacción
- **Comandos de Activación Individual**: Los jugadores pueden activar/desactivar el saqueo AOE usando los comandos `.aoeloot on/off`
- **Soporte Multi-idioma**: Internacionalización completa con traducciones en inglés y español (fácilmente extensible a otros idiomas)
- **Rango Configurable**: Los administradores del servidor pueden establecer la distancia máxima para la recolección de botín AOE
- **Soporte de Grupo**: Saqueo en grupo opcional con configuración personalizable
- **Optimizado para Rendimiento**: Limita el número de cadáveres procesados para mantener la estabilidad del servidor
- **Gestión Inteligente de Objetos**:
- Acumulación automática de oro con protección contra desbordamiento
- Objetos de misión enviados directamente al inventario
- Máximo de 15 objetos por ventana de botín para evitar problemas de interfaz
- **Gestión de Cadáveres**: Limpia automáticamente los cadáveres saqueados para reducir el desorden visual
## Actualizaciones Recientes
### v2.0 - Control del Jugador e Internacionalización
- ✅ Agregados comandos `.aoeloot on/off` para control individual del jugador
- ✅ Implementado soporte multi-idioma mediante el sistema `acore_string`
- ✅ Corregidos problemas de alineación de IDs en enums
- ✅ Traducciones completas en inglés y español
- ✅ Mejorada la documentación y estructura del código
### v1.x - Funcionalidad Principal
- Implementación inicial del saqueo AOE
- Sistema de configuración
- Ajustes de rango y grupo
## Requisitos
- AzerothCore v3.0.0+ (se recomienda la última rama master)
- MySQL 8.0+
- Compilador con soporte para C++17
## Instalación
### 1. Clonar el Módulo
Navega al directorio de módulos de AzerothCore:
```bash
cd <DirectorioACore>/modules
git clone https://github.com/azerothcore/mod-aoe-loot.git
```
### 2. Compilar
Recompila AzerothCore:
```bash
cd <DirectorioACore>/build
cmake ../ -DCMAKE_INSTALL_PREFIX=/ruta/al/servidor -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++
make -j $(nproc)
make install
```
### 3. Configurar
Edita tu archivo `worldserver.conf` (o crea `AOELoot.conf` en la carpeta de configuraciones):
```conf
###################################################################################################
# CONFIGURACIÓN DEL MÓDULO AOE LOOT
###################################################################################################
#
# AOELoot.Enable
# Descripción: Habilita o deshabilita el módulo AOE Loot globalmente
# Por defecto: 1 (habilitado)
# 0 (deshabilitado)
AOELoot.Enable = 1
#
# AOELoot.Range
# Descripción: Distancia máxima (en yardas) para recolectar botín de cadáveres cercanos
# Por defecto: 55.0
# Rango: 5.0 - 100.0
AOELoot.Range = 55.0
#
# AOELoot.Group
# Descripción: Permitir saqueo AOE cuando el jugador está en un grupo
# Por defecto: 1 (permitido)
# 0 (no permitido)
AOELoot.Group = 1
#
# AOELoot.Message
# Descripción: Mostrar mensaje informativo al iniciar sesión
# Por defecto: 1 (mostrar mensaje)
# 0 (sin mensaje)
AOELoot.Message = 1
###################################################################################################
```
### 4. Configuración de Degradación de Cadáveres (IMPORTANTE)
Para una experiencia óptima, modifica la configuración de degradación de cadáveres en `worldserver.conf`:
```conf
#
# Rate.Corpse.Decay.Looted
# Descripción: Multiplicador para Corpse.Decay.* que configura cuánto tiempo permanecen
# los cadáveres de las criaturas después de ser saqueados.
# Por defecto: 0.5
# Recomendado: 0.01 (para el módulo AOE Loot)
Rate.Corpse.Decay.Looted = 0.01
```
**Por qué es importante:** La tasa de degradación predeterminada (0.5) puede hacer que los cadáveres permanezcan después de ser saqueados mediante AOE, creando desorden visual. Establecer esto en 0.01 asegura que los cadáveres desaparezcan rápidamente después del saqueo.
### 5. Reiniciar el Servidor
Reinicia tu worldserver para cargar el módulo:
```bash
./worldserver
```
## Uso
### Para Jugadores
#### Comandos
- `.aoeloot on` - Activar el saqueo AOE para tu personaje
- `.aoeloot off` - Desactivar el saqueo AOE para tu personaje
#### Cómo Usar
1. Mata múltiples enemigos en proximidad cercana
2. Haz clic derecho en cualquier cadáver para saquear
3. Todos los objetos de los cadáveres cercanos aparecerán en una sola ventana de botín
4. Los objetos de misión se agregan automáticamente a tu inventario
**Nota:** Las preferencias del jugador se restablecen al cerrar sesión. El saqueo AOE está habilitado por defecto si el módulo está activo.
### Para Administradores
El módulo puede controlarse a través de la configuración del archivo (ver sección de Configuración arriba).
## Opciones de Configuración
| Opción | Tipo | Por Defecto | Descripción |
|--------|------|-------------|-------------|
| `AOELoot.Enable` | Booleano | 1 | Habilitar/deshabilitar módulo globalmente |
| `AOELoot.Range` | Decimal | 55.0 | Radio máximo de recolección de botín (5.0 - 100.0) |
| `AOELoot.Group` | Booleano | 1 | Permitir saqueo AOE en grupos |
| `AOELoot.Message` | Booleano | 1 | Mostrar mensaje de inicio de sesión |
## Soporte Multi-idioma
El módulo incluye soporte completo multi-idioma a través del sistema `acore_string` de AzerothCore.
### Idiomas Actualmente Soportados
- 🇬🇧 Inglés (en_US)
- 🇪🇸 Español (es_ES / es_MX)
### Agregar Más Idiomas
Para agregar soporte de idiomas adicionales, actualiza el archivo SQL:
```sql
UPDATE `acore_string` SET
`locale_frFR` = 'Votre traduction ici',
`locale_deDE` = 'Ihre Übersetzung hier',
`locale_ruRU` = 'Ваш перевод здесь'
WHERE `entry` BETWEEN 50000 AND 50007;
```
Columnas de localización soportadas:
- `locale_koKR` (Coreano)
- `locale_frFR` (Francés)
- `locale_deDE` (Alemán)
- `locale_zhCN` (Chino Simplificado)
- `locale_zhTW` (Chino Tradicional)
- `locale_ruRU` (Ruso)
## Detalles Técnicos
### Entradas de Base de Datos
El módulo utiliza las entradas `acore_string` 50000-50007:
| Entrada | Constante | Propósito |
|---------|-----------|-----------|
| 50000 | AOE_ACORE_STRING_MESSAGE | Mensaje de inicio de sesión |
| 50001 | AOE_ITEM_IN_THE_MAIL | Notificación de correo (reservado) |
| 50002-50003 | - | Reservado para uso futuro |
| 50004 | AOE_LOOT_ALREADY_ENABLED | Mensaje "Ya activado" |
| 50005 | AOE_LOOT_ENABLED | Confirmación "Activado" |
| 50006 | AOE_LOOT_ALREADY_DISABLED | Mensaje "Ya desactivado" |
| 50007 | AOE_LOOT_DISABLED | Confirmación "Desactivado" |
### Consideraciones de Rendimiento
- Máximo 10 cadáveres procesados por operación de saqueo (fijo para estabilidad)
- Máximo 15 objetos por ventana de botín
- Protección contra desbordamiento de oro (previene exceder el valor máximo uint32)
- Filtrado y limpieza eficiente de cadáveres
## Solución de Problemas
### Problema: El saqueo AOE no funciona
**Soluciones:**
- Verifica que el módulo esté habilitado: `AOELoot.Enable = 1`
- Comprueba si lo desactivaste personalmente: usa `.aoeloot on`
- Asegúrate de estar dentro del rango (55 yardas por defecto)
- Si estás en grupo, verifica la configuración `AOELoot.Group`
### Problema: Los cadáveres no desaparecen
**Solución:**
- Establece `Rate.Corpse.Decay.Looted = 0.01` en worldserver.conf
### Problema: Mensajes en idioma incorrecto
**Solución:**
- Verifica que el SQL se importó correctamente
- Comprueba la configuración de localización del cliente
- Confirma que la tabla `acore_string` tiene traducciones para tu localización
### Problema: Mensajes "Ya activado/desactivado" aparecen incorrectamente
**Solución:**
- Este es el comportamiento esperado - las preferencias se restablecen al cerrar sesión
- En el primer inicio de sesión, el saqueo AOE está habilitado por defecto
## Limitaciones Conocidas
- Las preferencias del jugador no persisten entre sesiones de inicio/cierre de sesión
- Máximo 10 cadáveres procesados a la vez (límite de rendimiento)
- Los objetos de misión enviados al inventario pueden llenar las bolsas rápidamente
- Rango limitado a un máximo de 100 yardas
## Mejoras Futuras
Características potenciales para versiones futuras:
- [ ] Persistencia en base de datos para preferencias del jugador
- [ ] Límite configurable de cadáveres máximos
- [ ] Opción de envío de objetos de misión por correo (en lugar de inventario directo)
- [ ] Indicador visual de rango
- [ ] Interfaz de configuración por personaje
- [ ] Seguimiento de estadísticas (objetos/oro total saqueado)
## Créditos
- **acidmanifesto** - [Autor original y concepto](https://github.com/azerothcore/mod-aoe-loot/pull/2)
- **Comunidad AzerothCore** - Hooks, actualizaciones y mejoras
- **Colaboradores** - Comandos de jugador, soporte multi-idioma y correcciones de errores
## Enlaces
- **AzerothCore:** [Repositorio](https://github.com/azerothcore) | [Sitio Web](https://azerothcore.org/) | [Discord](https://discord.gg/PaqQRkd)
- **Repositorio del Módulo:** [GitHub](https://github.com/azerothcore/mod-aoe-loot)
- **Problemas y Sugerencias:** [Rastreador de Problemas](https://github.com/azerothcore/mod-aoe-loot/issues)
## Licencia
Este módulo se publica bajo la [Licencia GNU AGPL v3](https://github.com/azerothcore/mod-aoe-loot/blob/master/LICENSE).
---
### Soporte
Si encuentras algún problema o tienes sugerencias:
1. Consulta la sección [Solución de Problemas](#solución-de-problemas)
2. Busca en [problemas existentes](https://github.com/azerothcore/mod-aoe-loot/issues)
3. Únete al [Discord de AzerothCore](https://discord.gg/PaqQRkd)
4. Crea un [nuevo problema](https://github.com/azerothcore/mod-aoe-loot/issues/new) con información detallada
**Por favor incluye:**
- Hash del commit de AzerothCore
- Sistema operativo y versión
- Mensajes de error completos (si los hay)
- Configuraciones utilizadas
- Pasos para reproducir el problema
+12
View File
@@ -0,0 +1,12 @@
name: core-build
on:
push:
branches:
- 'master'
pull_request:
jobs:
build:
uses: azerothcore/reusable-workflows/.github/workflows/core_build_modules.yml@main
with:
module_repo: ${{ github.event.repository.name }}
+47
View File
@@ -0,0 +1,47 @@
!.gitignore
#
#Generic
#
.directory
.mailmap
* .orig
* .rej
* .*~
.hg /
*.kdev *
.DS_Store
CMakeLists.txt.user
* .bak
* .patch
* .diff
* .REMOTE.*
* .BACKUP.*
* .BASE.*
* .LOCAL.*
#
# IDE & other softwares
#
/ .settings/
/.externalToolBuilders/*
# exclude in all levels
nbproject/
.sync.ffs_db
*.kate-swp
#
# Eclipse
#
*.pydevproject
.metadata
.gradle
tmp/
*.tmp
*.swp
*~.nib
local.properties
.settings/
.loadpath
.project
.cproject
+661
View File
@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.
@@ -0,0 +1,54 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# User has manually chosen to ignore the git-tests, so throw them a warning.
# This is done EACH compile so they can be alerted about the consequences.
#
########################################
# AoeLoot module configuration
# Fractured: stock defaults; no class-specific logic in this module.
########################################
#
# AOELoot.Enable
# Description: Enables Module
# Default: 1 - (Enabled)
# 0 - (Disabled)
#
AOELoot.Enable = 1
#
# AOELoot.Message
# Description: Enables area loot if the player is in a group
# Default: 1 - (Enabled)
# 0 - (Disabled)
#
AOELoot.Message = 1
#
# AOELoot.Range
# Description: Maximum reach range search loot.
# Default: 55.0
# Range: 5.0 - 100.0
#
AOELoot.Range = 55.0
#
# AOELoot.Group
# Description: Enables area loot if the player is in a group
# Default: 1 - (Enabled)
# 0 - (Disabled)
#
AOELoot.Group = 1
View File
@@ -0,0 +1,33 @@
SET @MODULE_STRING := 'mod-aoe-loot';
-- module string
DELETE FROM `module_string` WHERE `module` = @MODULE_STRING;
INSERT INTO `module_string` (`module`, `id`, `string`) VALUES
(@MODULE_STRING, 1, 'This server is running the |cff4CFF00Loot aoe|r module.'),
(@MODULE_STRING, 2, '|cff4CFF00[Loot aoe]|r Your items has been mailed to you.'),
(@MODULE_STRING, 3, 'AOE Loot module is active. Use .aoeloot on/off to toggle it.'),
(@MODULE_STRING, 4, 'AOE Loot: Quest item sent to your mailbox.'),
(@MODULE_STRING, 5, 'AOE Loot is already enabled for your character.'),
(@MODULE_STRING, 6, 'AOE Loot enabled for your character. Type .aoeloot off to disable it.'),
(@MODULE_STRING, 7, 'AOE Loot is already disabled for your character.'),
(@MODULE_STRING, 8, 'AOE Loot disabled for your character. Type .aoeloot on to enable it.');
-- localizations
DELETE FROM `module_string_locale` WHERE `module` = @MODULE_STRING;
INSERT INTO `module_string_locale` (`module`, `id`, `locale`, `string`) VALUES
(@MODULE_STRING, 1, 'esES', 'Este servidor está ejecutando el módulo |cff4CFF00Loot aoe|r.'),
(@MODULE_STRING, 2, 'esES', '|cff4CFF00[Loot aoe]|r Sus artículos le han sido enviados por correo.'),
(@MODULE_STRING, 3, 'esES', 'El módulo de Botín AOE está activo. Usa .aoeloot on/off para activarlo o desactivarlo.'),
(@MODULE_STRING, 4, 'esES', 'Botín AOE: Objeto de misión enviado a tu buzón.'),
(@MODULE_STRING, 5, 'esES', 'El Botín AOE ya está activado para tu personaje.'),
(@MODULE_STRING, 6, 'esES', 'Botín AOE activado para tu personaje. Escribe .aoeloot off para desactivarlo.'),
(@MODULE_STRING, 7, 'esES', 'El Botín AOE ya está desactivado para tu personaje.'),
(@MODULE_STRING, 8, 'esES', 'Botín AOE desactivado para tu personaje. Escribe .aoeloot on para activarlo.'),
(@MODULE_STRING, 1, 'esMX', 'Este servidor está ejecutando el módulo |cff4CFF00Loot aoe|r.'),
(@MODULE_STRING, 2, 'esMX', '|cff4CFF00[Loot aoe]|r Sus artículos le han sido enviados por correo.'),
(@MODULE_STRING, 3, 'esMX', 'El módulo de Botín AOE está activo. Usa .aoeloot on/off para activarlo o desactivarlo.'),
(@MODULE_STRING, 4, 'esMX', 'Botín AOE: Objeto de misión enviado a tu buzón.'),
(@MODULE_STRING, 5, 'esMX', 'El Botín AOE ya está activado para tu personaje.'),
(@MODULE_STRING, 6, 'esMX', 'Botín AOE activado para tu personaje. Escribe .aoeloot off para desactivarlo.'),
(@MODULE_STRING, 7, 'esMX', 'El Botín AOE ya está desactivado para tu personaje.'),
(@MODULE_STRING, 8, 'esMX', 'Botín AOE desactivado para tu personaje. Escribe .aoeloot on para activarlo.');
View File
@@ -0,0 +1,25 @@
<!-- First of all, THANK YOU for your contribution. -->
## Changes Proposed:
-
-
## Issues Addressed:
<!-- If your fix has a relating issue, link it below -->
- Closes
## SOURCE:
<!-- If you can, include a source that can strengthen your claim -->
## Tests Performed:
<!-- Does it build without errors? Did you test in-game? What did you test? On which OS did you test? Describe any other tests performed -->
-
-
## How to Test the Changes:
<!-- Describe in a detailed step-by-step order how to test the changes -->
1.
2.
3.
+321
View File
@@ -0,0 +1,321 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "aoe_loot.h"
#include "ObjectMgr.h"
#include <algorithm>
#include <limits>
std::map<uint64, bool> AoeLootCommandScript::playerAoeLootEnabled;
void AOELootPlayer::OnPlayerLogin(Player* player)
{
if (!player)
return;
if (sConfigMgr->GetOption<bool>("AOELoot.Enable", true) && sConfigMgr->GetOption<bool>("AOELoot.Message", true))
if (WorldSession* session = player->GetSession())
ChatHandler(session).PSendModuleSysMessage(MODULE_STRING, AOE_LOGIN_MESSAGE);
}
bool AOELootServer::CanPacketReceive(WorldSession* session, WorldPacket const& packet)
{
// Only handle loot packets
if (packet.GetOpcode() != CMSG_LOOT)
return true;
// Basic validation checks
if (!session)
return true;
Player* player = session->GetPlayer();
if (!player)
return true;
// Check if module is enabled
if (!sConfigMgr->GetOption<bool>("AOELoot.Enable", true))
return true;
// Check if player has AOE loot disabled via command
uint64 playerGuid = player->GetGUID().GetRawValue();
if (AoeLootCommandScript::hasPlayerAoeLootEnabled(playerGuid) &&
!AoeLootCommandScript::getPlayerAoeLootEnabled(playerGuid))
return true;
// Check group settings
if (player->GetGroup() && !sConfigMgr->GetOption<bool>("AOELoot.Group", true))
return true;
// Get configured loot range
float range = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0f);
// Limit range to reasonable values
if (range < 5.0f)
range = 5.0f;
if (range > 100.0f)
range = 100.0f;
// Read target GUID from packet
WorldPacket packetCopy(packet);
ObjectGuid targetGuid;
packetCopy >> targetGuid;
if (!targetGuid)
return true;
// Get target creature
Creature* mainCreature = player->GetMap()->GetCreature(targetGuid);
if (!mainCreature)
return true;
// Check if main creature has loot
if (!mainCreature->HasDynamicFlag(UNIT_DYNFLAG_LOOTABLE))
return true;
// Get nearby corpses
std::list<Creature*> nearbyCorpses;
player->GetDeadCreatureListInGrid(nearbyCorpses, range);
// Remove invalid corpses and main target
nearbyCorpses.remove_if([&](Creature* c)
{
return !c ||
c->GetGUID() == targetGuid ||
!c->HasDynamicFlag(UNIT_DYNFLAG_LOOTABLE) ||
!player->isAllowedToLoot(c);
});
// If no other corpses, process normally
if (nearbyCorpses.empty())
{
player->SendLoot(targetGuid, LOOT_CORPSE);
return false;
}
// Get main loot
Loot* mainLoot = &mainCreature->loot;
// Limit number of corpses to process
size_t const maxCorpses = 10; // set to 10 to improve stability
size_t processedCorpses = 0;
// Track total gold to merge
uint32 totalGold = mainLoot->gold;
// Collect all items to merge (don't modify main loot directly)
std::vector<LootItem> itemsToAdd;
std::vector<LootItem> questItemsToAdd;
for (Creature* creature : nearbyCorpses)
{
if (processedCorpses >= maxCorpses)
break;
if (!creature)
continue;
Loot* loot = &creature->loot;
// Skip already looted corpses
if (loot->isLooted())
continue;
// Collect gold
if (loot->gold > 0)
{
// Prevent overflow
if (totalGold < (std::numeric_limits<uint32>::max() - loot->gold))
totalGold += loot->gold;
}
// Collect regular items
for (size_t i = 0; i < loot->items.size(); ++i)
{
// Check if there's still space
if ((mainLoot->items.size() + itemsToAdd.size() + mainLoot->quest_items.size() + questItemsToAdd.size()) >= MAX_LOOT_ITEMS)
break;
itemsToAdd.push_back(loot->items[i]);
}
// Collect quest items (only for active quests, limited to needed count)
for (size_t i = 0; i < loot->quest_items.size(); ++i)
{
// Check if there's still space
if ((mainLoot->items.size() + itemsToAdd.size() + mainLoot->quest_items.size() + questItemsToAdd.size()) >= MAX_LOOT_ITEMS)
break;
LootItem const& questItem = loot->quest_items[i];
// Skip items the player doesn't need for any active quest
if (!player->HasQuestForItem(questItem.itemid))
continue;
// Calculate how many the player still needs across all active quests
uint32 maxNeeded = 0;
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 questId = player->GetQuestSlotQuestId(slot);
if (!questId)
continue;
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
continue;
for (uint8 j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
{
if (quest->RequiredItemId[j] == questItem.itemid && quest->RequiredItemCount[j] > maxNeeded)
maxNeeded = quest->RequiredItemCount[j];
}
}
if (maxNeeded == 0)
continue;
// Count how many the player already has, plus pending adds
// and quest items already in the main loot window
uint32 ownedCount = player->GetItemCount(questItem.itemid, true);
for (auto const& pending : questItemsToAdd)
{
if (pending.itemid == questItem.itemid)
ownedCount += pending.count;
}
for (auto const& mainQuestItem : mainLoot->quest_items)
{
if (mainQuestItem.itemid == questItem.itemid)
ownedCount += mainQuestItem.count;
}
if (ownedCount >= maxNeeded)
continue;
uint32 stillNeeded = maxNeeded - ownedCount;
LootItem cappedItem = questItem;
cappedItem.count = std::min(static_cast<uint32>(questItem.count), stillNeeded);
questItemsToAdd.push_back(cappedItem);
}
// Clear source loot (but don't modify vector directly)
loot->clear();
creature->AllLootRemovedFromCorpse();
creature->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
processedCorpses++;
}
// Now safely add collected items to main loot
// Update gold
mainLoot->gold = totalGold;
// Add regular items
for (const auto& item : itemsToAdd)
{
if (mainLoot->items.size() < MAX_LOOT_ITEMS)
mainLoot->items.push_back(item);
}
// Add quest items directly to player inventory
for (const auto& item : questItemsToAdd)
{
if (!player->HasQuestForItem(item.itemid))
continue;
player->AddItem(item.itemid, item.count);
}
// Send merged loot window
player->SendLoot(targetGuid, LOOT_CORPSE);
return false;
}
ChatCommandTable AoeLootCommandScript::GetCommands() const
{
static ChatCommandTable aoeLootSubCommandTable =
{
{ "on", HandleAoeLootOnCommand, SEC_PLAYER, Console::No },
{ "off", HandleAoeLootOffCommand, SEC_PLAYER, Console::No }
};
static ChatCommandTable aoeLootCommandTable =
{
{ "aoeloot", aoeLootSubCommandTable }
};
return aoeLootCommandTable;
}
bool AoeLootCommandScript::hasPlayerAoeLootEnabled(uint64 guid)
{
return playerAoeLootEnabled.count(guid) > 0;
}
bool AoeLootCommandScript::getPlayerAoeLootEnabled(uint64 guid)
{
auto it = playerAoeLootEnabled.find(guid);
if (it != playerAoeLootEnabled.end())
return it->second;
return false;
}
void AoeLootCommandScript::setPlayerAoeLootEnabled(uint64 guid, bool mode)
{
playerAoeLootEnabled[guid] = mode;
}
bool AoeLootCommandScript::HandleAoeLootOnCommand(ChatHandler* handler, Optional<std::string> /*args*/)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
uint64 playerGuid = player->GetGUID().GetRawValue();
if (AoeLootCommandScript::hasPlayerAoeLootEnabled(playerGuid) &&
AoeLootCommandScript::getPlayerAoeLootEnabled(playerGuid))
{
handler->PSendModuleSysMessage(MODULE_STRING, AOE_LOOT_ALREADY_ENABLED);
return true;
}
AoeLootCommandScript::setPlayerAoeLootEnabled(playerGuid, true);
handler->PSendModuleSysMessage(MODULE_STRING, AOE_LOOT_ENABLED);
return true;
}
bool AoeLootCommandScript::HandleAoeLootOffCommand(ChatHandler* handler, Optional<std::string> /*args*/)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
uint64 playerGuid = player->GetGUID().GetRawValue();
if (AoeLootCommandScript::hasPlayerAoeLootEnabled(playerGuid) &&
!AoeLootCommandScript::getPlayerAoeLootEnabled(playerGuid))
{
handler->PSendModuleSysMessage(MODULE_STRING, AOE_LOOT_ALREADY_DISABLED);
return true;
}
AoeLootCommandScript::setPlayerAoeLootEnabled(playerGuid, false);
handler->PSendModuleSysMessage(MODULE_STRING, AOE_LOOT_DISABLED);
return true;
}
+138
View File
@@ -0,0 +1,138 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MODULE_AOELOOT_H
#define MODULE_AOELOOT_H
#include "ScriptMgr.h"
#include "Config.h"
#include "Chat.h"
#include "Player.h"
#include "ScriptedGossip.h"
#include "Group.h"
#include "LootMgr.h"
#include "Creature.h"
#include "Log.h"
#include <vector>
#include <list>
#include <algorithm>
#include <string>
#define MODULE_STRING "mod-aoe-loot"
// Maximum loot items count
constexpr size_t MAX_LOOT_ITEMS = 16;
using namespace Acore::ChatCommands;
enum AoeLootString
{
AOE_LOGIN_MESSAGE = 1, // Login message
AOE_ITEM_IN_THE_MAIL = 2, // Mail notification
AOE_MODULE_ACTIVE = 3, // unused
AOE_QUEST_ITEM_SENT = 4, // unused
AOE_LOOT_ALREADY_ENABLED = 5, // Already enabled message
AOE_LOOT_ENABLED = 6, // Enabled confirmation
AOE_LOOT_ALREADY_DISABLED = 7, // Already disabled message
AOE_LOOT_DISABLED = 8 // Disabled confirmation
};
class AOELootPlayer : public PlayerScript
{
public:
AOELootPlayer() : PlayerScript("AOELootPlayer", { PLAYERHOOK_ON_LOGIN }) {}
void OnPlayerLogin(Player* player) override;
};
class AOELootServer : public ServerScript
{
public:
AOELootServer() : ServerScript("AOELootServer", { SERVERHOOK_CAN_PACKET_RECEIVE }) {}
bool CanPacketReceive(WorldSession* session, WorldPacket const& packet) override;
private:
// Helper function - Check if loot is valid
bool IsLootValid(Loot* loot) const;
// Check if loot can be merged
bool CanMergeLoot(Player* player, Creature* creature) const;
// Safely get item count
size_t GetSafeItemCount(Loot* loot) const;
// Safely merge loot items
bool SafeMergeLootItems(Loot* mainLoot, Loot* sourceLoot, size_t& remainingSlots);
};
// Configuration options structure (optional, for better config management)
struct AOELootConfig
{
bool enabled = true;
bool messageOnLogin = true;
bool allowInGroup = true;
float range = 55.0f;
uint32 maxCorpses = 20;
static AOELootConfig* instance()
{
static AOELootConfig instance;
return &instance;
}
void Load()
{
enabled = sConfigMgr->GetOption<bool>("AOELoot.Enable", true);
messageOnLogin = sConfigMgr->GetOption<bool>("AOELoot.Message", true);
allowInGroup = sConfigMgr->GetOption<bool>("AOELoot.Group", true);
range = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0f);
maxCorpses = sConfigMgr->GetOption<uint32>("AOELoot.MaxCorpses", 20);
// Validate configuration values
if (range < 5.0f) range = 5.0f;
if (range > 100.0f) range = 100.0f;
if (maxCorpses < 1) maxCorpses = 1;
if (maxCorpses > 50) maxCorpses = 50;
}
};
class AoeLootCommandScript : public CommandScript
{
public:
AoeLootCommandScript() : CommandScript("AoeLootCommandScript") {}
ChatCommandTable GetCommands() const override;
static bool HandleAoeLootOnCommand(ChatHandler* handler, Optional<std::string> args);
static bool HandleAoeLootOffCommand(ChatHandler* handler, Optional<std::string> args);
// Getters and setters for player AOE loot settings
static bool getPlayerAoeLootEnabled(uint64 guid);
static void setPlayerAoeLootEnabled(uint64 guid, bool mode);
static bool hasPlayerAoeLootEnabled(uint64 guid);
private:
static std::map<uint64, bool> playerAoeLootEnabled;
};
void AddSC_AoeLoot()
{
new AOELootPlayer();
new AOELootServer();
new AoeLootCommandScript();
}
#endif //MODULE_AOELOOT_H
@@ -0,0 +1,25 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// From SC
void AddSC_AoeLoot();
// Add all
void Addmod_aoe_lootScripts()
{
AddSC_AoeLoot();
}
@@ -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)';
@@ -0,0 +1,30 @@
-- mod-paragon Character Advancement: Builds catalog schema cleanup.
-- ----------------------------------------------------------------------------
-- Two changes:
-- 1. Drop `is_favorite` -- the favorite flag and shift-click-to-favorite
-- flow are removed. Builds are now ordered solely by build_id ASC.
-- 2. Add `share_code` CHAR(6) -- a random alphanumeric token generated
-- server-side at build creation that uniquely identifies a saved
-- build across the realm. Players exchange codes out-of-band and
-- use the BuildsPane "Load Build!" share box to import a copy of
-- the build (name + icon + spell + talent recipe) into their own
-- catalog. The copy gets a fresh share_code so re-sharing is
-- always traceable to the latest owner; the original isn't touched.
--
-- The column is NULL-tolerant so any rows that pre-date this migration
-- (created under 2026_05_10_03's schema) coexist cleanly. The server
-- backfills NULLs lazily in PushBuildCatalog -- the next time a player
-- opens the BuildsPane on a Paragon character, any of their builds that
-- still have a NULL share_code will get one generated and persisted.
--
-- Charset: 31 unambiguous chars (A-Z minus I/O minus 0/1) gives 31^6 ~=
-- 887M codes; collision retry on insert keeps probability of a duplicate
-- vanishing for any realistic catalog size.
-- ----------------------------------------------------------------------------
ALTER TABLE `character_paragon_builds`
DROP COLUMN `is_favorite`,
ADD COLUMN `share_code` CHAR(6) NULL DEFAULT NULL
COMMENT 'random alphanumeric token for import-by-code; lazily generated'
AFTER `icon`,
ADD UNIQUE INDEX `uk_share_code` (`share_code`);
@@ -0,0 +1,34 @@
-- mod-paragon: preserve superseded share codes as importable snapshots.
-- ----------------------------------------------------------------------------
-- When an active build is updated (Learn All), the live row gets a new
-- share_code and a fresh recipe. Older codes the player posted to Discord
-- must keep working: each retired code is frozen here with its spell/talent
-- recipe so `C BUILD IMPORT <code>` still materializes that exact loadout.
-- ----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `character_paragon_build_share_archive` (
`share_code` CHAR(6) NOT NULL COMMENT 'retired code (same charset as live builds)',
`name` VARCHAR(32) NOT NULL,
`icon` VARCHAR(64) NOT NULL DEFAULT 'INV_Misc_QuestionMark',
`archived_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`share_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: frozen build metadata for retired share codes';
CREATE TABLE IF NOT EXISTS `character_paragon_build_share_archive_spells` (
`share_code` CHAR(6) NOT NULL,
`spell_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`share_code`, `spell_id`),
KEY `idx_share` (`share_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: spell recipe rows for an archived share code';
CREATE TABLE IF NOT EXISTS `character_paragon_build_share_archive_talents` (
`share_code` CHAR(6) NOT NULL,
`spec` TINYINT UNSIGNED NOT NULL,
`talent_id` SMALLINT UNSIGNED NOT NULL,
`rank` TINYINT UNSIGNED NOT NULL,
PRIMARY KEY (`share_code`, `spec`, `talent_id`),
KEY `idx_share` (`share_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: talent recipe rows for an archived share code';
@@ -21,6 +21,7 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(10, 1), (10, 1),
(17, 1), (17, 1),
(53, 1), (53, 1),
(66, 1),
(72, 1), (72, 1),
(75, 1), (75, 1),
(78, 1), (78, 1),
@@ -30,6 +31,7 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(118, 1), (118, 1),
(120, 1), (120, 1),
(122, 1), (122, 1),
(126, 1),
(130, 1), (130, 1),
(131, 1), (131, 1),
(132, 1), (132, 1),
@@ -52,6 +54,7 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(469, 1), (469, 1),
(475, 1), (475, 1),
(498, 1), (498, 1),
(526, 1),
(527, 1), (527, 1),
(528, 1), (528, 1),
(543, 1), (543, 1),
@@ -73,22 +76,28 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(676, 1), (676, 1),
(686, 1), (686, 1),
(687, 1), (687, 1),
(688, 1),
(689, 1), (689, 1),
(691, 1),
(693, 1), (693, 1),
(694, 1), (694, 1),
(697, 1),
(698, 1), (698, 1),
(702, 1), (702, 1),
(703, 1), (703, 1),
(706, 1), (706, 1),
(710, 1), (710, 1),
(712, 1),
(740, 1), (740, 1),
(755, 1), (755, 1),
(759, 1), (759, 1),
(768, 1),
(770, 1), (770, 1),
(772, 1), (772, 1),
(774, 1), (774, 1),
(779, 1), (779, 1),
(781, 1), (781, 1),
(783, 1),
(845, 1), (845, 1),
(853, 1), (853, 1),
(871, 1), (871, 1),
@@ -104,6 +113,7 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(1038, 1), (1038, 1),
(1044, 1), (1044, 1),
(1064, 1), (1064, 1),
(1066, 1),
(1079, 1), (1079, 1),
(1082, 1), (1082, 1),
(1098, 1), (1098, 1),
@@ -176,6 +186,7 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(2812, 1), (2812, 1),
(2825, 1), (2825, 1),
(2893, 1), (2893, 1),
(2894, 1),
(2908, 1), (2908, 1),
(2912, 1), (2912, 1),
(2944, 1), (2944, 1),
@@ -194,6 +205,7 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(3565, 1), (3565, 1),
(3566, 1), (3566, 1),
(3567, 1), (3567, 1),
(3714, 1),
(3738, 1), (3738, 1),
(4987, 1), (4987, 1),
(5116, 1), (5116, 1),
@@ -205,7 +217,9 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(5185, 1), (5185, 1),
(5209, 1), (5209, 1),
(5211, 1), (5211, 1),
(5215, 1), (5215, 1);
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(5217, 1), (5217, 1),
(5221, 1), (5221, 1),
(5225, 1), (5225, 1),
@@ -215,11 +229,10 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(5308, 1), (5308, 1),
(5384, 1), (5384, 1),
(5484, 1), (5484, 1),
(5487, 1),
(5500, 1), (5500, 1),
(5502, 1), (5502, 1),
(5504, 1); (5504, 1),
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(5675, 1), (5675, 1),
(5676, 1), (5676, 1),
(5697, 1), (5697, 1),
@@ -244,6 +257,8 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(6770, 1), (6770, 1),
(6785, 1), (6785, 1),
(6789, 1), (6789, 1),
(6795, 1),
(6807, 1),
(6940, 1), (6940, 1),
(7294, 1), (7294, 1),
(7302, 1), (7302, 1),
@@ -283,6 +298,7 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(11418, 1), (11418, 1),
(11419, 1), (11419, 1),
(11420, 1), (11420, 1),
(12051, 1),
(13159, 1), (13159, 1),
(13161, 1), (13161, 1),
(13163, 1), (13163, 1),
@@ -297,6 +313,7 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(16857, 1), (16857, 1),
(16914, 1), (16914, 1),
(18499, 1), (18499, 1),
(19263, 1),
(19740, 1), (19740, 1),
(19742, 1), (19742, 1),
(19746, 1), (19746, 1),
@@ -323,7 +340,6 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(20252, 1), (20252, 1),
(20484, 1), (20484, 1),
(20736, 1), (20736, 1),
(21084, 1),
(21562, 1), (21562, 1),
(21849, 1), (21849, 1),
(22568, 1), (22568, 1),
@@ -331,6 +347,8 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(22812, 1), (22812, 1),
(22842, 1), (22842, 1),
(23028, 1), (23028, 1),
(23161, 1),
(23214, 1),
(23920, 1), (23920, 1),
(23922, 1), (23922, 1),
(24275, 1), (24275, 1),
@@ -349,6 +367,7 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(29722, 1), (29722, 1),
(29858, 1), (29858, 1),
(29893, 1), (29893, 1),
(30449, 1),
(30451, 1), (30451, 1),
(30455, 1), (30455, 1),
(30482, 1), (30482, 1),
@@ -372,12 +391,14 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(33745, 1), (33745, 1),
(33763, 1), (33763, 1),
(33786, 1), (33786, 1),
(33943, 1),
(34026, 1), (34026, 1),
(34074, 1), (34074, 1),
(34428, 1), (34428, 1),
(34433, 1), (34433, 1),
(34477, 1), (34477, 1),
(34600, 1), (34600, 1),
(34767, 1),
(35715, 1), (35715, 1),
(35717, 1), (35717, 1),
(36936, 1), (36936, 1),
@@ -398,7 +419,9 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(47541, 1), (47541, 1),
(47568, 1), (47568, 1),
(47897, 1), (47897, 1),
(48018, 1), (48018, 1);
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(48020, 1), (48020, 1),
(48045, 1), (48045, 1),
(48263, 1), (48263, 1),
@@ -416,14 +439,19 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(49576, 1), (49576, 1),
(49998, 1), (49998, 1),
(50464, 1), (50464, 1),
(50769, 1),
(50842, 1), (50842, 1),
(51505, 1),
(51514, 1),
(51722, 1), (51722, 1),
(51723, 1), (51723, 1),
(52610, 1); (51730, 1),
(52127, 1),
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES (52610, 1),
(53140, 1), (53140, 1),
(53142, 1), (53142, 1),
(53271, 1),
(53351, 1),
(53407, 1), (53407, 1),
(53408, 1), (53408, 1),
(53600, 1), (53600, 1),
@@ -432,15 +460,24 @@ INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(54428, 1), (54428, 1),
(55342, 1), (55342, 1),
(55694, 1), (55694, 1),
(56222, 1),
(56641, 1), (56641, 1),
(56815, 1),
(57330, 1),
(57755, 1), (57755, 1),
(57934, 1), (57934, 1),
(57994, 1), (57994, 1),
(60192, 1), (60192, 1),
(61846, 1), (61846, 1),
(61999, 1),
(62078, 1), (62078, 1),
(62124, 1), (62124, 1),
(62600, 1),
(62757, 1), (62757, 1),
(64382, 1), (64382, 1),
(64843, 1); (64843, 1),
(64901, 1),
(66842, 1),
(66843, 1),
(66844, 1);
@@ -0,0 +1,62 @@
-- mod-paragon: backfill paragon_spell_ae_cost rows for spells newly exposed
-- by the Character Advancement panel after removing the over-aggressive
-- ClassMask=0 filter from tools/_gen_paragon_advancement_spells_lua.py.
--
-- The base file (data/sql/db-world/base/paragon_spell_ae_cost.sql) was
-- regenerated alongside this migration so fresh deployments already have
-- these rows. Existing servers do not re-run base files on content change,
-- so this update inserts the new (spell_id, ae_cost) pairs idempotently.
-- INSERT IGNORE keeps any per-row tuning a server operator may have already
-- applied to spell_ids that happen to overlap.
--
-- New ids include: 51505 Lava Burst (Shaman), 12051 Evocation / 1066 Aqueous
-- Form / Hex / Mage Ward / Spellsteal (Mage), 53351 Kill Shot / 19263
-- Deterrence / 53271 Master's Call (Hunter), 3714 Path of Frost / 57330
-- Horn of Winter / 56815 Rune Strike / 61999 Raise Ally / 56222 Dark Command
-- (DK), and 39 other trainer-taught class abilities whose stock
-- SkillLineAbility.dbc rows have ClassMask=0 (the skill line itself pins the
-- class for these rows; ClassMask is redundant on class-spec lines).
INSERT IGNORE INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(66, 1), -- Invisibility (Mage)
(126, 1), -- Eye of Kilrogg (Warlock)
(526, 1), -- Cure Toxins (Shaman)
(688, 1), -- Summon Imp (Warlock)
(691, 1), -- Summon Felhunter (Warlock)
(697, 1), -- Summon Voidwalker (Warlock)
(712, 1), -- Summon Succubus (Warlock)
(768, 1), -- Cat Form (Druid)
(783, 1), -- Travel Form (Druid)
(1066, 1), -- Aqueous Form (Mage)
(2894, 1), -- Fire Resistance Totem (Shaman)
(3714, 1), -- Path of Frost (DK)
(5215, 1), -- Prowl (Druid)
(5487, 1), -- Bear Form (Druid)
(5504, 1), -- Conjure Refreshment (Mage)
(6795, 1), -- Growl (Druid)
(6807, 1), -- Maul (Druid)
(12051, 1), -- Evocation (Mage)
(19263, 1), -- Deterrence (Hunter)
(23161, 1), -- Summon Dreadsteed (Warlock)
(23214, 1), -- Summon Charger (Paladin)
(30449, 1), -- Spellsteal (Mage)
(33943, 1), -- Flight Form (Druid)
(34767, 1), -- Summon Felguard (Warlock)
(48018, 1), -- Demonic Circle: Summon (Warlock)
(50769, 1), -- Revive (Druid)
(51505, 1), -- Lava Burst (Shaman)
(51514, 1), -- Hex (Shaman)
(51730, 1), -- Earthliving Weapon (Shaman)
(52127, 1), -- Water Shield (Shaman)
(52610, 1), -- Savage Roar (Druid)
(53271, 1), -- Master's Call (Hunter)
(53351, 1), -- Kill Shot (Hunter)
(56222, 1), -- Dark Command (DK)
(56815, 1), -- Rune Strike (DK)
(57330, 1), -- Horn of Winter (DK)
(61999, 1), -- Raise Ally (DK)
(64843, 1), -- Divine Hymn (Priest)
(64901, 1), -- Hymn of Hope (Priest)
(66842, 1), -- Call of the Elements (Shaman totem set)
(66843, 1), -- Call of the Ancestors (Shaman totem set)
(66844, 1); -- Call of the Spirits (Shaman totem set)
@@ -0,0 +1,60 @@
-- mod-paragon: Predatory Strikes (16972 / 16974 / 16975) Cataclysm-style
-- finisher proc for CLASS_PARAGON characters.
--
-- The 3.3.5 Predatory Strikes is a passive AP / ranged-attack-power talent
-- with no proc payload. The Cataclysm redesign added "Predator's Swiftness"
-- (69369), which makes the next Nature spell <10s base cast time instant.
-- That buff already exists in the WotLK Spell.dbc (because Blizzard reused
-- the spell id), but no server-side trigger ever calls CastSpell(69369) on
-- a 3.3.5 server. We need both halves: the proc handler AND a spell_proc
-- row so the proc evaluator actually invokes our AuraScript.
--
-- AuraScript binding: spell_paragon_predatory_strikes is registered in
-- modules/mod-paragon/src/Paragon_SC.cpp. It checks
-- (a) caster is CLASS_PARAGON,
-- (b) source spell consumes combo points (NeedsComboPoints),
-- (c) source spell deals damage (DmgClass MELEE/RANGED + at least one
-- damage effect or periodic-damage aura -- filters Slice and Dice,
-- Savage Roar, Maim, Kidney Shot, Expose Armor, Recuperate),
-- then rolls a per-rank chance of (CP * 3 / 5 / 7)% to cast 69369 on
-- the caster.
--
-- spell_proc row params:
-- ProcFlags = 0x40000 = PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS
-- SpellTypeMask = 0x3 (DAMAGE | HEAL bitmask in proc engine; we
-- filter precisely in CheckProc anyway, the
-- mask just gates "spell-type events" through)
-- SpellPhaseMask = 0x2 = PROC_SPELL_PHASE_CAST -- fires DURING cast
-- so player->GetComboPoints() inside HandleProc
-- still returns the pre-_handle_finish_phase
-- value.
-- Chance = 100 (the per-CP chance is rolled inside the script)
--
-- Note: this row's SpellFamilyName / SpellFamilyMask are 0 so the proc
-- engine's IsAffected check is a wildcard at the entry-level. The
-- AuraScript's CheckProc owns all real filtering. Combined with Phase A
-- (Paragon SpellFamilyName wildcard) this is harmless on stock classes
-- because non-Paragon characters cannot learn Predatory Strikes via the
-- Character Advancement panel.
DELETE FROM `spell_script_names`
WHERE `spell_id` IN (16972, 16974, 16975)
AND `ScriptName` = 'spell_paragon_predatory_strikes';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(16972, 'spell_paragon_predatory_strikes'),
(16974, 'spell_paragon_predatory_strikes'),
(16975, 'spell_paragon_predatory_strikes');
DELETE FROM `spell_proc` WHERE `SpellId` IN (16972, 16974, 16975);
INSERT INTO `spell_proc`
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
`Chance`, `Cooldown`, `Charges`)
VALUES
(16972, 0, 0, 0, 0, 0, 0x40000, 0x3, 0x2, 0, 0, 0, 0, 100.0, 0, 0),
(16974, 0, 0, 0, 0, 0, 0x40000, 0x3, 0x2, 0, 0, 0, 0, 100.0, 0, 0),
(16975, 0, 0, 0, 0, 0, 0x40000, 0x3, 0x2, 0, 0, 0, 0, 100.0, 0, 0);
@@ -0,0 +1,46 @@
-- mod-paragon: Vampiric Embrace (15286) cross-family wildcard.
--
-- Stock 3.3.5 spell_proc row for Vampiric Embrace gates by SpellFamilyName=6
-- (PRIEST) plus a Priest-Shadow-damage SpellFamilyMask. That blocks the proc
-- engine from ever calling the AuraScript's CheckProc when a Paragon casts a
-- non-Priest Shadow-school spell (e.g. Warlock Shadow Bolt, Death Knight
-- Death Coil, etc.), because IsAffected's familyFlags gate fails before
-- CheckProc runs even though the Phase A wildcard already loosens the
-- familyName equality test (see SpellInfo::IsAffected, listenerOwner overload).
--
-- We relax this row so:
-- * SchoolMask=32 (SHADOW) kept -- proc-engine still gates by school
-- * SpellTypeMask=1 (DAMAGE) kept -- only damage events trigger CheckProc
-- * SpellPhaseMask=2 (HIT) kept -- post-hit phase
-- * AttributesMask=2 (TRIGGERED) kept -- triggered-spell payloads still proc
-- * SpellFamilyName=0 wildcard -- IsAffected short-circuits to true
-- * SpellFamilyMask{0,1,2}=0 wildcard -- no flag-bit gating at this layer
--
-- Real filtering moves into spell_pri_vampiric_embrace::CheckProc, which
-- branches on IsParagonWildcardCaller(GetTarget()):
--
-- * For Paragon owners with `Paragon.WildcardFamilyMatching = 1`, accept any
-- single-target Shadow-school spell (Mind Sear / AoE shadow spells like
-- Seed of Corruption / Hellfire are filtered there via IsAffectingArea
-- and the existing Mind Sear bit-mask).
--
-- * For stock Priest owners (and for the non-wildcard runtime path), the
-- CheckProc re-enforces the EXACT original gate -- SpellFamilyName=6 plus
-- the original 0x0280A010 / 0x00002402 / 0x00000008 SpellFamilyMask bits
-- -- so behavior is byte-identical to before this change for any caster
-- that is not a Paragon.
--
-- Net effect: Paragon characters with VE learned now leech-heal off any
-- single-target Shadow spell they cast (Death Coil, Shadow Bolt, Searing
-- Pain, Drain Soul, etc.); stock Shadow Priests are unchanged.
DELETE FROM `spell_proc` WHERE `SpellId` = 15286;
INSERT INTO `spell_proc`
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
`Chance`, `Cooldown`, `Charges`)
VALUES
(15286, 32, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0);
@@ -0,0 +1,51 @@
-- mod-paragon: Maelstrom Weapon (53817) cross-family wildcard.
--
-- Stock 3.3.5 spell_proc row for Maelstrom Weapon gates by SpellFamilyName=11
-- (SHAMAN) plus a Shaman SpellFamilyMask covering Lightning Bolt, Chain
-- Lightning, Lesser Healing Wave, Healing Wave and Hex (Mask0=451,
-- Mask1=32768). The proc engine therefore never delivers an event to the
-- AuraScript when a Paragon casts a non-Shaman cast-time spell, even if the
-- IsAffected wildcard relaxes SpellFamilyName equality (the SpellFamilyMask
-- AND-with-target-FamilyFlags check still fails because Mage / Warlock /
-- Druid spell-class bits do not overlap with Shaman bits).
--
-- We relax this row so:
-- * SchoolMask=0 wildcard -- proc engine no longer gates by school
-- * SpellTypeMask=1 (DAMAGE) kept -- only damage spells trigger CheckProc
-- * SpellPhaseMask=8 (FINISH) kept -- post-cast phase, on cast finish
-- * SpellFamilyName=0 wildcard -- IsAffected short-circuits to true
-- * SpellFamilyMask{0,1,2}=0 wildcard -- no flag-bit gating at this layer
--
-- Real filtering moves into spell_sha_maelstrom_weapon::CheckProc:
--
-- * For stock Shaman owners (and for the non-wildcard runtime path), the
-- CheckProc re-enforces the EXACT original gate -- SpellFamilyName=11
-- plus the original Mask0=451 / Mask1=32768 bits -- so behavior is
-- byte-identical to before this change for any caster that is not a
-- Paragon.
--
-- * For Paragon owners with `Paragon.WildcardFamilyMatching = 1`, the
-- stock allowlist still passes, AND we additionally accept the curated
-- Mage cast-time nukes Fireball / Frostbolt / Arcane Blast (any rank,
-- matched via GetFirstRankSpell).
--
-- The matching IsAffectedBySpellMod hook in SpellInfo.cpp ensures the cast
-- time + power cost spellmods on aura 53817 also bridge across families for
-- the same Mage spell allowlist, so Paragons get the full Maelstrom Weapon
-- experience (instant cast at 5 stacks + reduced mana cost) on Fireball,
-- Frostbolt and Arcane Blast.
--
-- Net effect: Paragon characters with Maelstrom Weapon learned now spend
-- stacks on Mage cast-time nukes in addition to the stock Shaman list;
-- stock Enhancement Shamans are unchanged.
DELETE FROM `spell_proc` WHERE `SpellId` = 53817;
INSERT INTO `spell_proc`
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
`Chance`, `Cooldown`, `Charges`)
VALUES
(53817, 0, 0, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 0, 0, 0);
@@ -0,0 +1,66 @@
-- mod-paragon: Frostbite + Fingers of Frost cross-family wildcard.
--
-- Both talents are normally gated by SpellFamilyName=3 (MAGE) plus a Mage
-- SpellFamilyMask covering specific Mage Frost spells (Frostbolt / Frost Nova
-- / Cone of Cold / Blizzard / Frostfire Bolt / Deep Freeze for FoF; Frost
-- slow-applying spells for Frostbite). That blocks the proc engine from
-- delivering an event to the AuraScript when a Paragon casts a non-Mage
-- Frost-school chill effect (DK Howling Blast / Icy Touch / Chains of Ice,
-- Hunter Frost Trap, Shaman Frost Shock, etc.), because IsAffected's
-- familyFlags AND-with-target-FamilyFlags check fails before CheckProc runs
-- even after the Paragon family-name wildcard.
--
-- We relax these rows so:
-- * SchoolMask=16 (FROST) gate by Frost school at the proc engine
-- * SpellTypeMask=1 (DAMAGE) only damage events trigger CheckProc
-- * SpellPhaseMask=2 (HIT) post-hit phase
-- * AttributesMask=2 (TRIGGERED) triggered chill payloads still proc
-- * SpellFamilyName=0 wildcard -- IsAffected short-circuits to true
-- * SpellFamilyMask{0,1,2}=0 wildcard -- no flag-bit gating at this layer
--
-- Real filtering moves into the Mage AuraScripts:
--
-- spell_mage_fingers_of_frost_talent attached to 44543 / 44545
-- stock Mage : SpellFamilyName=MAGE AND original Mask0 0x100120 / Mask1 0x1000
-- Paragon : accept (FROST + DAMAGE gate already enforced)
--
-- spell_mage_frostbite attached to 11071 / 12496 / 12497
-- stock Mage : SpellFamilyName=MAGE AND original Mage Frost-slow Mask
-- (Frostbolt / Frost Nova / Cone of Cold / Blizzard / FFB)
-- Paragon : accept iff proc spell applies SPELL_AURA_MOD_DECREASE_SPEED
-- OR the Paragon already has a slow on the proc target
-- (covers the Improved-Blizzard-style "chill via separate
-- triggered aura" cross-class case)
--
-- Net effect: Paragon characters with these talents now have FoF / Frostbite
-- proc off cross-class Frost-school chill effects; stock Mages are unchanged.
DELETE FROM `spell_proc` WHERE `SpellId` IN (44543, 44545, 11071, 12496, 12497);
INSERT INTO `spell_proc`
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
`Chance`, `Cooldown`, `Charges`)
VALUES
-- Fingers of Frost talent ranks (Chance 7% / 15% preserved from stock row).
(44543, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 7, 0, 0),
(44545, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 15, 0, 0),
-- Frostbite talent ranks (5% / 10% / 15% per rank, leave Chance=0 to use
-- the DBC ProcChance which already encodes the per-rank percentage).
(11071, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0),
(12496, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0),
(12497, 16, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0, 0, 0, 0);
-- Bind the new AuraScripts (defined in src/server/scripts/Spells/spell_mage.cpp).
DELETE FROM `spell_script_names`
WHERE `spell_id` IN (44543, 44545, 11071, 12496, 12497)
AND `ScriptName` IN ('spell_mage_fingers_of_frost_talent', 'spell_mage_frostbite');
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(44543, 'spell_mage_fingers_of_frost_talent'),
(44545, 'spell_mage_fingers_of_frost_talent'),
(11071, 'spell_mage_frostbite'),
(12496, 'spell_mage_frostbite'),
(12497, 'spell_mage_frostbite');
@@ -0,0 +1,51 @@
-- mod-paragon: Maelstrom Weapon (53817) spell_proc fixup.
--
-- The previous migration (2026_05_11_02.sql) had two bugs in the rewritten
-- spell_proc row that prevented stack consumption from firing at all -- not
-- just for our new Mage targets, but also for the stock Shaman cast-time
-- spells (Lightning Bolt, Chain Lightning, Lesser Healing Wave, etc.).
--
-- Bugs:
--
-- * SpellPhaseMask was set to 8. The valid values for SpellPhaseMask are
-- PROC_SPELL_PHASE_CAST = 1
-- PROC_SPELL_PHASE_HIT = 2
-- PROC_SPELL_PHASE_FINISH = 4
-- (see SpellMgr.h). Anything else, including 8, never matches a real
-- proc event, so the proc engine silently dropped every event before it
-- reached the AuraScript. The original stock row uses 1 (CAST), which
-- is what fires when the cast packet's setup phase completes -- exactly
-- when we want the spellmod-affected cast to consume the buff.
--
-- * AttributesMask was set to 0. The original stock row uses 8
-- PROC_ATTR_REQ_SPELLMOD = 0x8
-- which says "only proc on spells that were affected by one of this
-- aura's spellmods". This is the bridge between IsAffectedBySpellMod
-- (which records the aura into Spell::m_appliedMods when calculating
-- cast time / cost) and the proc system (which then knows that the cast
-- used the buff and should consume a charge). Without this attribute,
-- the proc would either fire too aggressively or not at all depending
-- on subsequent gating, but in practice the engine relies on it to
-- correlate spellmod use with stack consumption.
--
-- Fixed row keeps the same family/mask wildcards from the previous
-- migration (so the Paragon Mage allowlist in spell_sha_maelstrom_weapon's
-- CheckProc still gets the chance to filter), restores SpellPhaseMask=1
-- (CAST) and AttributesMask=8 (REQ_SPELLMOD) to match stock semantics, and
-- resets SpellTypeMask to 0 (any spell type -- the REQ_SPELLMOD attribute
-- already gates by "was the buff actually used", so an extra DAMAGE filter
-- is redundant and would block e.g. Lesser Healing Wave on stock Shamans).
--
-- This restores stock Shaman behavior byte-identically and lets Paragon
-- Mage casts also consume stacks via the IsAffectedBySpellMod allowlist.
DELETE FROM `spell_proc` WHERE `SpellId` = 53817;
INSERT INTO `spell_proc`
(`SpellId`, `SchoolMask`, `SpellFamilyName`,
`SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`,
`ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`,
`AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`,
`Chance`, `Cooldown`, `Charges`)
VALUES
(53817, 0, 0, 0, 0, 0, 0, 0, 1, 0, 8, 0, 0, 0, 0, 0);
@@ -0,0 +1,19 @@
-- mod-paragon: surface Savage Defense (62600) on the Druid Feral spell tab
-- of the Character Advancement panel.
--
-- The bake (tools/_gen_paragon_advancement_spells_lua.py) used to drop every
-- SPELL_ATTR0_PASSIVE spell up front, even when the trainer explicitly sells
-- it. That filter was correct for class-internal triggers (Feline Grace, etc.)
-- but kicked out Savage Defense -- a passive that DRUID trainer 33 sells at
-- level 40 (trainer_spell.sql line 2457). Bake now carves out a small
-- PASSIVE_TRAINER_ALLOWLIST so legitimate trainer-taught passives survive.
--
-- The base file (data/sql/db-world/base/paragon_spell_ae_cost.sql) was
-- regenerated alongside this migration so fresh deployments already have
-- this row. Existing servers do not re-run base files on content change,
-- so this update inserts the new (spell_id, ae_cost) pair idempotently.
-- INSERT IGNORE keeps any per-row tuning a server operator may have already
-- applied.
INSERT IGNORE INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(62600, 1); -- Savage Defense (Druid, trainer 33, level 40)
@@ -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);
@@ -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);
@@ -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);
@@ -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');
@@ -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;
@@ -0,0 +1,26 @@
-- mod-paragon: Frozen Power (-63373) cross-family wildcard (Fractured).
--
-- Stock 3.3.5 `spell_proc` for Frozen Power gates by SpellFamilyName=11
-- (SHAMAN) plus SpellFamilyMask0=0x80000000 (Frost Shock only). The proc
-- engine therefore never calls spell_sha_frozen_power::CheckProc when the
-- damaging spell is not a Shaman Frost Shock family match.
--
-- We relax the row so:
-- * SpellFamilyName=0 wildcard -- IsAffected short-circuits to true
-- * SpellFamilyMask{0,1,2}=0 wildcard -- no flag-bit gating at this layer
-- * ProcFlags / SpellTypeMask / SpellPhaseMask / HitMask unchanged from stock
--
-- Real filtering moves into spell_sha_frozen_power::CheckProc (first-rank
-- spell id allowlist: Mage nukes, DK Howling Blast, Shaman LB/CL/shocks/
-- Lava Burst, etc.) plus the existing 15 yd minimum and dummy effect chance.
--
-- Net effect: Fractured Frozen Power roots can proc from the curated spell
-- list; stock proc timing (done negative magic damage, hit phase) is unchanged.
UPDATE `spell_proc`
SET `SpellFamilyName` = 0, `SpellFamilyMask0` = 0, `SpellFamilyMask1` = 0, `SpellFamilyMask2` = 0
WHERE `SpellId` = -63373;
UPDATE `spell_proc_event`
SET `SpellFamilyName` = 0, `SpellFamilyMask0` = 0, `SpellFamilyMask1` = 0, `SpellFamilyMask2` = 0
WHERE `entry` = -63373;
File diff suppressed because it is too large Load Diff
+335 -14
View File
@@ -7,15 +7,17 @@
#include "Chat.h" #include "Chat.h"
#include "Config.h" #include "Config.h"
#include "Creature.h"
#include "CreatureData.h"
#include "GameTime.h" #include "GameTime.h"
#include "Log.h" #include "Log.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "Pet.h"
#include "Player.h" #include "Player.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "SpellScript.h" #include "SpellScript.h"
#include "SpellScriptLoader.h" #include "SpellScriptLoader.h"
#include "UnitDefines.h"
#include "WorldPacket.h" #include "WorldPacket.h"
#include "WorldSession.h" #include "WorldSession.h"
@@ -45,27 +47,218 @@ public:
if (!player || player->getClass() != CLASS_PARAGON) if (!player || player->getClass() != CLASS_PARAGON)
return std::nullopt; 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; return true;
// Warrior ability stack: enables warrior-spec ability gates anywhere // ============================================================
// they're checked. None of the currently-traced sites in core/scripts // Reactive melee states.
// gate on (CLASS_WARRIOR, CLASS_CONTEXT_ABILITY), so this is a safe // ============================================================
// forward-compatible claim. Rage generation itself is gated on // Warrior dodge -> AURA_STATE_DEFENSE (Overpower window).
// HasActivePowerType(POWER_RAGE) and is wired below. // Hunter parry -> AURA_STATE_HUNTER_PARRY (Counterattack).
if (unitClass == CLASS_WARRIOR && context == CLASS_CONTEXT_ABILITY) // We intentionally do NOT claim CLASS_ROGUE here:
return true; // Unit::ProcDamageAndSpellFor (Unit.cpp 12824) skips the
// generic AURA_STATE_DEFENSE update on dodge for rogues so
// Reactive melee states: Overpower-on-dodge (warrior), Counterattack window (hunter). // Riposte can take over. Claiming rogue would silently kill
// We intentionally do NOT claim CLASS_ROGUE here: that context skips the generic // Overpower for Paragon, and Riposte already works for us via
// AURA_STATE_DEFENSE update on dodge (Riposte path) in Unit::ProcDamageAndSpellFor. // the warrior-style state we already grant.
if (context == CLASS_CONTEXT_ABILITY_REACTIVE) if (context == CLASS_CONTEXT_ABILITY_REACTIVE)
{ {
if (unitClass == CLASS_WARRIOR || unitClass == CLASS_HUNTER) if (unitClass == CLASS_WARRIOR || unitClass == CLASS_HUNTER)
return true; return true;
} }
// ============================================================
// 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.
//
// 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*>(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)
{
case CLASS_PALADIN:
case CLASS_DRUID:
case CLASS_SHAMAN:
case CLASS_WARLOCK:
case CLASS_DEATH_KNIGHT:
return true;
default:
break;
}
}
// 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; return std::nullopt;
} }
@@ -321,8 +514,136 @@ class spell_paragon_arcane_torrent : public SpellScript
} }
}; };
// Predatory Strikes (16972 / 16974 / 16975) for Paragon: re-implements the
// Cataclysm-era proc behavior of the talent so a Paragon's damaging
// finishers (Eviscerate / Envenom / Ferocious Bite / Rip / Rupture) can
// roll Predator's Swiftness (69369) -- the same buff that real druids
// get from the Cata redesign of this talent. Combined with the
// Spell::prepare interception in core (Spell.cpp), 69369 makes the
// Paragon's NEXT Nature-school spell with a base cast time below 10s
// instant cast: Chain Lightning, Lightning Bolt, Healing Touch, Wrath,
// Nourish, etc. -- not just the Druid-family Nature subset that the
// stock SPELLMOD_CASTING_TIME mask on 69369 covers.
//
// Filter logic:
// - Source spell must consume combo points (NeedsComboPoints() — gates
// out non-finisher combo-point builders).
// - "Damaging finisher": SPELL_ATTR1_FINISHING_MOVE_DAMAGE (Eviscerate,
// Envenom, Ferocious Bite, ...) OR a SPELL_ATTR1_FINISHING_MOVE_DURATION
// finisher that applies periodic damage (Rip, Rupture). Duration
// finishers that only heal (Recuperate) or only buff / CC / armor shred
// (Slice and Dice, Savage Roar, Kidney Shot, Maim, Expose Armor) are
// rejected.
//
// Chance per combo point matches the Cataclysm tuning that the user's
// client tooltip text reflects: rank 1 = 3% per CP, rank 2 = 5% per CP,
// rank 3 = 7% per CP. At 5 CP that is 15% / 25% / 35%, capped at 100%.
//
// Combo-point read happens during PROC_SPELL_PHASE_CAST, which fires in
// Spell::cast → Spell::ProcReflectProcs / Unit::ProcDamageAndSpellFor
// BEFORE Spell::_handle_finish_phase clears the player's combo points
// (see Spell.cpp:_handle_finish_phase clearing combo points). So
// player->GetComboPoints() inside HandleProc returns the pre-clear value.
class spell_paragon_predatory_strikes : public AuraScript
{
PrepareAuraScript(spell_paragon_predatory_strikes);
static constexpr uint32 SPELL_PARAGON_PREDATORS_SWIFTNESS = 69369;
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_PARAGON_PREDATORS_SWIFTNESS });
}
bool CheckProc(ProcEventInfo& eventInfo)
{
SpellInfo const* spellInfo = eventInfo.GetSpellInfo();
if (!spellInfo || !spellInfo->NeedsComboPoints())
return false;
if (spellInfo->HasAttribute(SPELL_ATTR1_FINISHING_MOVE_DAMAGE))
return true;
if (spellInfo->HasAttribute(SPELL_ATTR1_FINISHING_MOVE_DURATION))
{
bool periodicHeal = false;
bool periodicDamage = false;
for (SpellEffectInfo const& eff : spellInfo->Effects)
{
if (eff.Effect != SPELL_EFFECT_APPLY_AURA && eff.Effect != SPELL_EFFECT_APPLY_AREA_AURA_PARTY
&& eff.Effect != SPELL_EFFECT_PERSISTENT_AREA_AURA)
continue;
switch (eff.ApplyAuraName)
{
case SPELL_AURA_PERIODIC_HEAL:
case SPELL_AURA_PERIODIC_HEALTH_FUNNEL:
case SPELL_AURA_OBS_MOD_HEALTH:
periodicHeal = true;
break;
case SPELL_AURA_PERIODIC_DAMAGE:
case SPELL_AURA_PERIODIC_DAMAGE_PERCENT:
case SPELL_AURA_PERIODIC_LEECH:
periodicDamage = true;
break;
default:
break;
}
}
if (periodicHeal)
return false;
return periodicDamage;
}
return false;
}
void HandleProc(ProcEventInfo& eventInfo)
{
PreventDefaultAction();
Unit* actor = eventInfo.GetActor();
Player* player = actor ? actor->ToPlayer() : nullptr;
if (!player || player->getClass() != CLASS_PARAGON)
return;
uint8 const cp = player->GetComboPoints();
if (cp == 0)
return;
SpellInfo const* talent = GetSpellInfo();
if (!talent)
return;
uint32 pctPerCP = 0;
switch (talent->Id)
{
case 16972: pctPerCP = 3; break;
case 16974: pctPerCP = 5; break;
case 16975: pctPerCP = 7; break;
default:
return;
}
uint32 const chance = std::min<uint32>(100u, pctPerCP * uint32(cp));
if (!roll_chance_i(int32(chance)))
return;
player->CastSpell(player, SPELL_PARAGON_PREDATORS_SWIFTNESS, true);
}
void Register() override
{
DoCheckProc += AuraCheckProcFn(spell_paragon_predatory_strikes::CheckProc);
OnProc += AuraProcFn(spell_paragon_predatory_strikes::HandleProc);
}
};
void AddSC_paragon() void AddSC_paragon()
{ {
new Paragon_PlayerScript(); new Paragon_PlayerScript();
RegisterSpellScript(spell_paragon_arcane_torrent); RegisterSpellScript(spell_paragon_arcane_torrent);
RegisterSpellScript(spell_paragon_predatory_strikes);
} }
+8
View File
@@ -0,0 +1,8 @@
[*]
charset = utf-8
indent_style = space
indent_size = 4
tab_width = 4
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 80
@@ -0,0 +1,49 @@
### TITLE
## Type(Scope/Subscope): Commit ultra short explanation
## |---- Write below the examples with a maximum of 50 characters ----|
## Example 1: fix(DB/SAI): Missing spell to NPC Hogger
## Example 2: fix(CORE/Raid): Phase 2 of Ragnaros
## Example 3: feat(CORE/Commands): New GM command to do something
### DESCRIPTION
## Explain why this change is being made, what does it fix etc...
## |---- Write below the examples with a maximum of 72 characters per lines ----|
## Example: Hogger (id: 492) was not charging player when being engaged.
## Provide links to any issue, commit, pull request or other resource
## Example 1: Closes issue #23
## Example 2: Ported from other project's commit (link)
## Example 3: References taken from wowpedia / wowhead / wowwiki / https://wowgaming.altervista.org/aowow/
## =======================================================
## EXTRA INFOS
## =======================================================
## "Type" can be:
## feat (new feature)
## fix (bug fix)
## refactor (refactoring production code)
## style (formatting, missing semi colons, etc; no code change)
## docs (changes to documentation)
## test (adding or refactoring tests; no production code change)
## chore (updating bash scripts, git files etc; no production code change)
## --------------------
## Remember to
## Capitalize the subject line
## Use the imperative mood in the subject line
## Do not end the subject line with a period
## Separate subject from body with a blank line
## Use the body to explain what and why rather than how
## Can use multiple lines with "-" for bullet points in body
## --------------------
## More info here https://www.conventionalcommits.org/en/v1.0.0-beta.2/
## =======================================================
## "Scope" can be:
## CORE (core related, c++)
## DB (database related, sql)
## =======================================================
## "Subscope" is optional and depends on the nature of the commit.
## =======================================================
+105
View File
@@ -0,0 +1,105 @@
## AUTO-DETECT
## Handle line endings automatically for files detected as
## text and leave all files detected as binary untouched.
## This will handle all files NOT defined below.
* text=auto eol=lf
# Text
*.conf text
*.conf.dist text
*.cmake text
## Scripts
*.sh text
*.fish text
*.lua text
## SQL
*.sql text
## C++
*.c text
*.cc text
*.cxx text
*.cpp text
*.c++ text
*.hpp text
*.h text
*.h++ text
*.hh text
## For documentation
# Documents
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
## DOCUMENTATION
*.markdown text
*.md text
*.mdwn text
*.mdown text
*.mkd text
*.mkdn text
*.mdtxt text
*.mdtext text
*.txt text
AUTHORS text
CHANGELOG text
CHANGES text
CONTRIBUTING text
COPYING text
copyright text
*COPYRIGHT* text
INSTALL text
license text
LICENSE text
NEWS text
readme text
*README* text
TODO text
## GRAPHICS
*.ai binary
*.bmp binary
*.eps binary
*.gif binary
*.ico binary
*.jng binary
*.jp2 binary
*.jpg binary
*.jpeg binary
*.jpx binary
*.jxr binary
*.pdf binary
*.png binary
*.psb binary
*.psd binary
*.svg text
*.svgz binary
*.tif binary
*.tiff binary
*.wbmp binary
*.webp binary
## ARCHIVES
*.7z binary
*.gz binary
*.jar binary
*.rar binary
*.tar binary
*.zip binary
## EXECUTABLES
*.exe binary
*.pyc binary
+12
View File
@@ -0,0 +1,12 @@
name: core-build
on:
push:
branches:
- 'master'
pull_request:
jobs:
build:
uses: azerothcore/reusable-workflows/.github/workflows/core_build_modules.yml@main
with:
module_repo: ${{ github.event.repository.name }}
+48
View File
@@ -0,0 +1,48 @@
!.gitignore
#
#Generic
#
.directory
.mailmap
*.orig
*.rej
*.*~
.hg/
*.kdev*
.DS_Store
CMakeLists.txt.user
*.bak
*.patch
*.diff
*.REMOTE.*
*.BACKUP.*
*.BASE.*
*.LOCAL.*
#
# IDE & other softwares
#
/.settings/
/.externalToolBuilders/*
# exclude in all levels
nbproject/
.sync.ffs_db
*.kate-swp
#
# Eclipse
#
*.pydevproject
.metadata
.gradle
tmp/
*.tmp
*.swp
*~.nib
local.properties
.settings/
.loadpath
.project
.cproject
+661
View File
@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.
+73
View File
@@ -0,0 +1,73 @@
# Transmog Module
> [!WARNING]
> If you used the old-subscription system for TransmogPlus option before this [commit](https://github.com/azerothcore/mod-transmog/commit/8237df6f88d40d1d83a6f11b86a7187f99f57c99), please update your mod-transmog module to the latest revision and also download the new module [mod-acore-subscriptions](https://github.com/azerothcore/mod-acore-subscriptions).
- Latest Transmog build status with azerothcore: [![Build Status](https://github.com/azerothcore/mod-transmog/actions/workflows/core_build.yml/badge.svg)](https://github.com/azerothcore/mod-transmog/actions)
This is a module for [AzerothCore](http://www.azerothcore.org) that adds **Transmog**rification feature, it's based on [Rochet2 Transmog Script](http://rochet2.github.io/Transmogrification.html)
## Important notes
You have to use at least this AzerothCore commit:
<https://github.com/azerothcore/azerothcore-wotlk/commit/b6cb9247ba96a862ee274c0765004e6d2e66e9e4>
If using this module with an AzerothCore commit older than
<https://github.com/azerothcore/azerothcore-wotlk/commit/b34bc28e5b02514fca3519beac420c58faa89cad>
please delete the IDs 50000 and 50001 from npc_text before upgrading AzerothCore:
```sql
DELETE FROM `npc_text` WHERE `ID` IN (50000,50001);
```
Otherwise there will be conflicts for these IDs. The module will now use IDs 601083 and 601084 as default.
## Requirements
Transmogrification module currently requires:
AzerothCore v1.0.2+
## How to install
### 1) Simply place the module under the `modules` folder of your AzerothCore source folder.
You can do clone it via git under the azerothcore/modules directory:
```sh
cd path/to/azerothcore/modules
git clone https://github.com/azerothcore/mod-transmog.git
```
or you can manually [download the module](https://github.com/azerothcore/mod-transmog/archive/master.zip), unzip the Transmog folder and place it under the `azerothcore/modules` directory.
### 2) Import the SQL to the right Database (auth, world or characters)
Import the SQL manually to the right Database (auth, world or characters) or with the `db_assembler.sh` (if `include.sh` provided).
### 3) Re-run cmake and launch a clean build of AzerothCore
### 4) Place transmog npc
With a gm account goto the location you want to add the npc and use this command:
```
.npc add 190010
```
**That's it.**
### (Optional) Edit module configuration
If you need to change the module configuration, go to your server configuration folder (e.g. **etc**), copy `transmog.conf.dist` to `transmog.conf` and edit it as you prefer.
## License
This module is released under the [GNU AGPL license](https://github.com/azerothcore/mod-transmog/blob/master/LICENSE).
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
#
# CUSTOM
#
DB_CHARACTERS_CUSTOM_PATHS+=(
$TRANSM_PATH_ROOT"/data/sql/db-characters/"
)
DB_WORLD_CUSTOM_PATHS+=(
$TRANSM_PATH_ROOT"/data/sql/db-world/"
)
#
# UPDATES
#
DB_CHARACTERS_UPDATE_PATHS+=(
$TRANSM_PATH_ROOT"/data/sql/updates/char/"
)
DB_WORLD_UPDATE_PATHS+=(
$TRANSM_PATH_ROOT"/data/sql/updates/world/"
)
@@ -0,0 +1,345 @@
[worldserver]
# Fractured / Paragon (class 12): stock item AllowableClass never includes the Paragon
# class bit, so transmog eligibility must not gate on class (see mod-transmog
# Transmogrification.cpp). Hybrid armor proficiency is also non-standard; relax
# skill checks so appearances are not rejected when weapon/armor skills differ
# from a single stock class.
###################################################################################################
# Transmogrification config
###################################################################################################
#
# SETTINGS
#
# Transmogrification.Enable
# Description: Enables/Disables transmog.
# Players won't be able to see any transmogrified item while disabled, however, database data remains intact.
# Default: 1
#
# Transmogrification.ShowSetDisclaimer
# Description: If enabled, players receive a chat notice when transmogrifying an item that belongs to a set,
# explaining that set bonuses remain active even though the tooltip won't reflect them.
# Players can suppress the notice per-character with: .transmog disclaimer off
# Default: 0
#
# Transmogrification.UseCollectionSystem
# Description: Enables/Disables Legion-style appearance collection system.
# If enabled, players can use the appearance of any item equipped or rewarded from a completed quest.
# If disabled, players must have an item in their bags to use as a transmogrification appearance source.
# Default: 1
#
# Transmogrification.UseVendorInterface
# Description: Enables/Disables the use of a fake vendor interface for item selection.
# There are (optional) custom items available for Hide Item and Remove Transmog if data/sql/db-world/tasm_world_VendorItems.sql is imported.
# If enabled, players can select items from a vendor menu, complete with ctrl-click previews.
# If disabled, players will use the gossip menu to select items and will not have access to previews.
# Default: 0
#
# Transmogrification.AllowHiddenTransmog
# Description: Enables/Disables the hiding of equipment through transmog
# If enabled, players can select an "invisible" appearance for items at the transmog vendor
# Default: 1
#
# Transmogrification.HiddenTransmogIsFree
# Description: Enables/Disables free hiding of items through the transmog system.
# If enabled, players can hide pieces of equipment for free.
# If disabled, players will be charged the standard transmog price (affected by all modifiers) to hide an item.
# Default: 1
#
# Transmogrification.TrackUnusableItems
# Description: If enabled, appearances are collected even for items that are not suitable for transmogrification.
# This allows these appearances to be used later if the configuration is changed.
# Default: 1
#
# Transmogrification.RetroActiveAppearances
# Description: Enables/Disables checking all completed quests for uncollected appearances.
# Occurs only once per player.
# Default: 1
#
# Transmogrification.ResetRetroActiveAppearancesFlag
# Description: Resets the flag indicating whether the retroactive appearance check has been run.
# Occurs for each character on log in.
# Default: 0
#
# Transmogrification.EnableTransmogInfo
# Description: Enables / Disables the info button for transmogrification
# Default: 1
#
# Transmogrification.TransmogNpcText
# Description: The npc_text entry of the info menu for transmogrification
# Default: 601083
#
# Transmogrification.Allowed
# Description: A list of item entries that are allowed for transmogrification (skips quality and CanUseItem check)
# Example: "25 35674 5623"
# Default: ""
#
# Transmogrification.NotAllowed
# Description: A list of item entries that are NOT allowed for transmogrification
# Example: "25 35674 5623"
# Default: ""
#
# Transmogrification.EnablePortable
# Description: Enables / Disables the portable transmogrification NPC.
# Default: 1
#
# Transmogrification.EnableSortByQualityAndName
# Description: Enables / Disables the sorting of the items by quality and then by names
# Default: 1
#
Transmogrification.Enable = 1
Transmogrification.ShowSetDisclaimer = 0
Transmogrification.UseCollectionSystem = 1
Transmogrification.UseVendorInterface = 0
Transmogrification.AllowHiddenTransmog = 1
Transmogrification.HiddenTransmogIsFree = 1
Transmogrification.TrackUnusableItems = 1
Transmogrification.RetroActiveAppearances = 1
Transmogrification.ResetRetroActiveAppearancesFlag = 0
Transmogrification.EnableTransmogInfo = 1
Transmogrification.TransmogNpcText = 601083
Transmogrification.Allowed = ""
Transmogrification.NotAllowed = ""
Transmogrification.EnablePortable = 1
Transmogrification.EnableSortByQualityAndName = 1
#
# COPPER COST
#
# Transmogrification.ScaledCostModifier
# Description: A multiplier for the default gold cost (change to 0 for no default cost)
# Default: 1.0
#
# Transmogrification.CopperCost
# Description: Cost added on top of other costs (can be negative)
# Default: 0
#
# For custom gold cost set ScaledCostModifier to 0.0 and CopperCost to what ever cost you want
Transmogrification.ScaledCostModifier = 1.0
Transmogrification.CopperCost = 0
#
# TOKEN COST
#
# Transmogrification.RequireToken
# Description: Adds/disables token cost
# Default: 0
#
# Transmogrification.TokenEntry
# Description: Entry of the token item
# Default: 49426
#
# Transmogrification.TokenAmount
# Description: Amount of tokens required
# Default: 1
Transmogrification.RequireToken = 0
Transmogrification.TokenEntry = 49426
Transmogrification.TokenAmount = 1
#
# REQUIREMENTS
#
# Transmogrification.AllowPoor
# Description: Allow poor quality items to be used as source and target items
# Default: 0
#
# Transmogrification.AllowCommon
# Description: Allow common quality items to be used as source and target items
# Default: 0
#
# Transmogrification.AllowUncommon
# Description: Allow uncommon quality items to be used as source and target items
# Default: 1
#
# Transmogrification.AllowRare
# Description: Allow rare quality items to be used as source and target items
# Default: 1
#
# Transmogrification.AllowEpic
# Description: Allow epic quality items to be used as source and target items
# Default: 1
#
# Transmogrification.AllowLegendary
# Description: Allow legendary quality items to be used as source and target items
# Default: 0
#
# Transmogrification.AllowArtifact
# Description: Allow artifact quality items to be used as source and target items
# Default: 0
#
# Transmogrification.AllowHeirloom
# Description: Allow heirloom quality items to be used as source and target items
# Default: 1
#
# Transmogrification.AllowTradeable
# Description: Allow items that are tradeable (i.e. During 2 hour grace period during an instance)
# Default: 0
#
# Transmogrification.AllowMixedArmorTypes
# Description: Allow cloth items to be transmogrified with plate for example
# Default: 0
#
# Transmogrification.AllowLowerTiers
# Description: Allows using any armor tier the player can equip (i.e. Warrior plate->cloth | Mage cloth)
# Default: 0
#
# Transmogrification.AllowMixedOffhandArmorTypes
# Description: Allow shields, offhands (i.e. lamps), and bucklers to be used interchangeably
# Default: 0
#
# Transmogrification.AllowMixedWeaponTypes
# Description: Allow axe to be transmogrified with dagger for example
# Possible options:
# Default: 0 - STRICT - Fully restricted like original Blizzard 4.3 transmog - Weapons must be the same weapon type (exceptions: Guns, Crossbows, or Bows)
# 1 - MODERN - Like later retail WoW, allow swords to be transmogged to axes, etc.
# 2 - FULL - No restrictions, allow any weapon to be transmogrified to any other weapon
#
# Transmogrification.AllowMixedWeaponHandedness
# Description: Normally main-hand and off-hand weapons can only be transmogged to this same type. Enable to remove this restriction.
# Not necessary when AllowMixedWeaponTypes is set to FULL.
# Default: 0
#
# Transmogrification.AllowFishingPoles
# Description: Allow fishing poles to be transmogrified
# Default: 0
#
# Transmogrification.IgnoreReqRace
# Description: Ignore required race for source items
# Default: 0
#
# Transmogrification.IgnoreReqClass
# Description: Ignore required class for source items
# Default: 0
#
# Transmogrification.IgnoreReqSkill
# Description: Ignore required skill for source items
# Default: 0
#
# Transmogrification.IgnoreReqSpell
# Description: Ignore required spell for source items
# Default: 0
#
# Transmogrification.IgnoreReqLevel
# Description: Ignore required level for source items
# Default: 0
#
# Transmogrification.IgnoreReqEvent
# Description: Ignore required event for source items
# Default: 0
#
# Transmogrification.IgnoreReqStats
# Description: Ignore stat count > 0 requirement for source items
# Default: 0
Transmogrification.AllowPoor = 0
Transmogrification.AllowCommon = 0
Transmogrification.AllowUncommon = 1
Transmogrification.AllowRare = 1
Transmogrification.AllowEpic = 1
Transmogrification.AllowLegendary = 0
Transmogrification.AllowArtifact = 0
Transmogrification.AllowHeirloom = 1
Transmogrification.AllowTradeable = 0
Transmogrification.AllowMixedArmorTypes = 0
Transmogrification.AllowLowerTiers = 0
Transmogrification.AllowMixedOffhandArmorTypes = 0
Transmogrification.AllowMixedWeaponTypes = 0
Transmogrification.AllowMixedWeaponHandedness = 0
Transmogrification.AllowFishingPoles = 0
Transmogrification.IgnoreReqRace = 0
Transmogrification.IgnoreReqClass = 1
Transmogrification.IgnoreReqSkill = 1
Transmogrification.IgnoreReqSpell = 0
Transmogrification.IgnoreReqLevel = 0
Transmogrification.IgnoreReqEvent = 0
Transmogrification.IgnoreReqStats = 0
#
# SET FEATURE
#
# Transmogrification.EnableSets
# Description: Enables / Disables the set feature. If you want permanent disable, check Transmogrification.h
# Default: 1
#
# Transmogrification.MaxSets
# Description: Maximum amount of sets a player can save (hardcap at 25)
# Default: 10
#
# Transmogrification.EnableSetInfo
# Description: Enables / Disables the info button for set fature
# Default: 1
#
# Transmogrification.SetNpcText
# Description: The npc_text entry of the info menu for the set feature
# Default: 601084
#
# Transmogrification.SetCostModifier
# Description: A multiplier for the default gold cost (all costs summed together) (change to 0 for no default cost)
# Default: 3.0
#
# Transmogrification.SetCopperCost
# Description: Cost added on top of other costs (can be negative)
# Default: 0
Transmogrification.EnableSets = 1
Transmogrification.MaxSets = 10
Transmogrification.EnableSetInfo = 1
Transmogrification.SetNpcText = 601084
Transmogrification.SetCostModifier = 3.0
Transmogrification.SetCopperCost = 0
#
# TRANSMOG PLUS
#
# Transmogrification.EnablePlus
# Description: Enables/Disables TransmogPlus.
# Default: 0
#
# Transmogrification.MembershipLevels
# Description: Membership levels ID from acore_cms_subscriptions.
# Example: Transmogrification.MembershipLevels = "1,2,3"
# Default: ""
#
# Transmogrification.MembershipLevelsLegendary
# Description: Membership levels ID from acore_cms_subscriptions that define the eligibility for legendary items
# Example: Transmogrification.MembershipLevelsLegendary = "1,2,3"
# Default: ""
#
# Transmogrification.MembershipLevelsPet
# Description: Membership levels ID from acore_cms_subscriptions that define the eligibility for transmog pet
# Example: Transmogrification.MembershipLevelsPet = "1,2,3"
# Default: ""
#
# Transmogrification.MembershipLevelsSkipLevelReq
# Description: Membership levels ID from acore_cms_subscriptions that define the eligibility for skipping level checks when transmogfrifying
# Example: Transmogrification.MembershipLevelsSkipLevelReq = "1,2,3"
# Default: ""
#
# Transmogrification.PetSpellId
# Description: The ID used by the transmog pet in the spell_dbc table
# Example: Transmogrification.PetSpellId = 2000100
# Default: 2000100
#
Transmogrification.EnablePlus = 0
Transmogrification.MembershipLevels = ""
Transmogrification.MembershipLevelsLegendary = ""
Transmogrification.MembershipLevelsPet = ""
Transmogrification.MembershipLevelsSkipLevelReq = ""
Transmogrification.PetSpellId = 200100
#
###################################################################################################
@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS `acore_cms_subscriptions` (
`account_name` VARCHAR(255) NOT NULL,
`membership_level` INT NOT NULL
);
@@ -0,0 +1,26 @@
-- Dumping structure for table tc_c.custom_transmogrification
CREATE TABLE IF NOT EXISTS `custom_transmogrification` (
`GUID` int(10) unsigned NOT NULL COMMENT 'Item guidLow',
`FakeEntry` int(10) unsigned NOT NULL COMMENT 'Item entry',
`Owner` int(10) unsigned NOT NULL COMMENT 'Player guidLow',
PRIMARY KEY (`GUID`),
KEY `Owner` (`Owner`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='6_2';
-- Data exporting was unselected.
-- Dumping structure for table tc_c.custom_transmogrification_sets
CREATE TABLE IF NOT EXISTS `custom_transmogrification_sets` (
`Owner` int(10) unsigned NOT NULL COMMENT 'Player guidlow',
`PresetID` tinyint(3) unsigned NOT NULL COMMENT 'Preset identifier',
`SetName` text COMMENT 'SetName',
`SetData` text COMMENT 'Slot1 Entry1 Slot2 Entry2',
PRIMARY KEY (`Owner`,`PresetID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='6_1';
CREATE TABLE IF NOT EXISTS `custom_unlocked_appearances` (
`account_id` int(10) unsigned NOT NULL,
`item_template_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`account_id`, `item_template_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
@@ -0,0 +1,49 @@
SET
@Entry = 190010,
@Name = "Warpweaver";
DELETE FROM `creature_template` WHERE `entry` = @Entry;
INSERT INTO `creature_template` (`entry`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `faction`, `npcflag`, `rank`, `dmgschool`, `BaseAttackTime`, `RangeAttackTime`, `unit_class`, `unit_flags`, `type`, `type_flags`, `lootid`, `pickpocketloot`, `skinloot`, `AIName`, `MovementType`, `HoverHeight`, `RacialLeader`, `movementId`, `RegenHealth`, `CreatureImmunitiesId`, `flags_extra`, `ScriptName`) VALUES
(@Entry, @Name, "Transmogrifier", NULL, 0, 80, 80, 2, 35, 1, 0, 0, 2000, 0, 1, 0, 7, 138936390, 0, 0, 0, '', 0, 1, 0, 0, 1, 0, 0, 'npc_transmogrifier');
DELETE FROM `creature_template_model` WHERE `CreatureID` = @Entry;
INSERT INTO `creature_template_model` (`CreatureID`, `Idx`, `CreatureDisplayID`, `DisplayScale`, `Probability`, `VerifiedBuild`) VALUES
(@Entry, 0, 19646, 1, 1, 0);
DELETE FROM `creature_template_locale` WHERE `entry` IN (@Entry);
INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`) VALUES
(@Entry, 'koKR', @Name, "변형기"),
(@Entry, 'frFR', @Name, "Transmogrificateur"),
(@Entry, 'deDE', @Name, "Transmogrifier"),
(@Entry, 'zhCN', @Name, "变形者"),
(@Entry, 'zhTW', @Name, "幻化大師"),
(@Entry, 'esES', @Name, "Transfigurador"),
(@Entry, 'esMX', @Name, "Transfigurador"),
(@Entry, 'ruRU', @Name, "Трансмогрификатор");
SET
@Entry = 190011,
@Name = "Ethereal Warpweaver";
DELETE FROM `creature_template` WHERE `entry` = @Entry;
INSERT INTO `creature_template` (`entry`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `faction`, `npcflag`, `rank`, `dmgschool`, `BaseAttackTime`, `RangeAttackTime`, `unit_class`, `unit_flags`, `type`, `type_flags`, `lootid`, `pickpocketloot`, `skinloot`, `AIName`, `MovementType`, `HoverHeight`, `RacialLeader`, `movementId`, `RegenHealth`, `CreatureImmunitiesId`, `flags_extra`, `ScriptName`) VALUES
(@Entry, @Name, "Transmogrifier", NULL, 0, 80, 80, 2, 35, 1, 0, 0, 2000, 0, 1, 0, 7, 138936390, 0, 0, 0, '', 0, 1, 0, 0, 1, 0, 0, 'npc_transmogrifier');
DELETE FROM `creature_template_model` WHERE `CreatureID` = @Entry;
INSERT INTO `creature_template_model` (`CreatureID`, `Idx`, `CreatureDisplayID`, `DisplayScale`, `Probability`, `VerifiedBuild`) VALUES
(@Entry, 0, 19646, 1, 1, 0);
DELETE FROM `creature_template_locale` WHERE `entry` IN (@Entry);
INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`) VALUES
(@Entry, 'koKR', @Name, "변형기"),
(@Entry, 'frFR', @Name, "Transmogrificateur"),
(@Entry, 'deDE', @Name, "Transmogrifier"),
(@Entry, 'zhCN', @Name, "变形者"),
(@Entry, 'zhTW', @Name, "幻化大師"),
(@Entry, 'esES', @Name, "Transfigurador"),
(@Entry, 'esMX', @Name, "Transfigurador"),
(@Entry, 'ruRU', @Name, "Трансмогрификатор");
DELETE FROM `spell_dbc` WHERE `ID` = 200100;
INSERT INTO `spell_dbc` (`ID`, `Category`, `DispelType`, `Mechanic`, `Attributes`, `AttributesEx`, `AttributesEx2`, `AttributesEx3`, `AttributesEx4`, `AttributesEx5`, `AttributesEx6`, `AttributesEx7`, `ShapeshiftMask`, `unk_320_2`, `ShapeshiftExclude`, `unk_320_3`, `Targets`, `TargetCreatureType`, `RequiresSpellFocus`, `FacingCasterFlags`, `CasterAuraState`, `TargetAuraState`, `ExcludeCasterAuraState`, `ExcludeTargetAuraState`, `CasterAuraSpell`, `TargetAuraSpell`, `ExcludeCasterAuraSpell`, `ExcludeTargetAuraSpell`, `CastingTimeIndex`, `RecoveryTime`, `CategoryRecoveryTime`, `InterruptFlags`, `AuraInterruptFlags`, `ChannelInterruptFlags`, `ProcTypeMask`, `ProcChance`, `ProcCharges`, `MaxLevel`, `BaseLevel`, `SpellLevel`, `DurationIndex`, `PowerType`, `ManaCost`, `ManaCostPerLevel`, `ManaPerSecond`, `ManaPerSecondPerLevel`, `RangeIndex`, `Speed`, `ModalNextSpell`, `CumulativeAura`, `Totem_1`, `Totem_2`, `Reagent_1`, `Reagent_2`, `Reagent_3`, `Reagent_4`, `Reagent_5`, `Reagent_6`, `Reagent_7`, `Reagent_8`, `ReagentCount_1`, `ReagentCount_2`, `ReagentCount_3`, `ReagentCount_4`, `ReagentCount_5`, `ReagentCount_6`, `ReagentCount_7`, `ReagentCount_8`, `EquippedItemClass`, `EquippedItemSubclass`, `EquippedItemInvTypes`, `Effect_1`, `Effect_2`, `Effect_3`, `EffectDieSides_1`, `EffectDieSides_2`, `EffectDieSides_3`, `EffectRealPointsPerLevel_1`, `EffectRealPointsPerLevel_2`, `EffectRealPointsPerLevel_3`, `EffectBasePoints_1`, `EffectBasePoints_2`, `EffectBasePoints_3`, `EffectMechanic_1`, `EffectMechanic_2`, `EffectMechanic_3`, `ImplicitTargetA_1`, `ImplicitTargetA_2`, `ImplicitTargetA_3`, `ImplicitTargetB_1`, `ImplicitTargetB_2`, `ImplicitTargetB_3`, `EffectRadiusIndex_1`, `EffectRadiusIndex_2`, `EffectRadiusIndex_3`, `EffectAura_1`, `EffectAura_2`, `EffectAura_3`, `EffectAuraPeriod_1`, `EffectAuraPeriod_2`, `EffectAuraPeriod_3`, `EffectMultipleValue_1`, `EffectMultipleValue_2`, `EffectMultipleValue_3`, `EffectChainTargets_1`, `EffectChainTargets_2`, `EffectChainTargets_3`, `EffectItemType_1`, `EffectItemType_2`, `EffectItemType_3`, `EffectMiscValue_1`, `EffectMiscValue_2`, `EffectMiscValue_3`, `EffectMiscValueB_1`, `EffectMiscValueB_2`, `EffectMiscValueB_3`, `EffectTriggerSpell_1`, `EffectTriggerSpell_2`, `EffectTriggerSpell_3`, `EffectPointsPerCombo_1`, `EffectPointsPerCombo_2`, `EffectPointsPerCombo_3`, `EffectSpellClassMaskA_1`, `EffectSpellClassMaskA_2`, `EffectSpellClassMaskA_3`, `EffectSpellClassMaskB_1`, `EffectSpellClassMaskB_2`, `EffectSpellClassMaskB_3`, `EffectSpellClassMaskC_1`, `EffectSpellClassMaskC_2`, `EffectSpellClassMaskC_3`, `SpellVisualID_1`, `SpellVisualID_2`, `SpellIconID`, `ActiveIconID`, `SpellPriority`, `Name_Lang_enUS`, `Name_Lang_enGB`, `Name_Lang_koKR`, `Name_Lang_frFR`, `Name_Lang_deDE`, `Name_Lang_enCN`, `Name_Lang_zhCN`, `Name_Lang_enTW`, `Name_Lang_zhTW`, `Name_Lang_esES`, `Name_Lang_esMX`, `Name_Lang_ruRU`, `Name_Lang_ptPT`, `Name_Lang_ptBR`, `Name_Lang_itIT`, `Name_Lang_Unk`, `Name_Lang_Mask`, `NameSubtext_Lang_enUS`, `NameSubtext_Lang_enGB`, `NameSubtext_Lang_koKR`, `NameSubtext_Lang_frFR`, `NameSubtext_Lang_deDE`, `NameSubtext_Lang_enCN`, `NameSubtext_Lang_zhCN`, `NameSubtext_Lang_enTW`, `NameSubtext_Lang_zhTW`, `NameSubtext_Lang_esES`, `NameSubtext_Lang_esMX`, `NameSubtext_Lang_ruRU`, `NameSubtext_Lang_ptPT`, `NameSubtext_Lang_ptBR`, `NameSubtext_Lang_itIT`, `NameSubtext_Lang_Unk`, `NameSubtext_Lang_Mask`, `Description_Lang_enUS`, `Description_Lang_enGB`, `Description_Lang_koKR`, `Description_Lang_frFR`, `Description_Lang_deDE`, `Description_Lang_enCN`, `Description_Lang_zhCN`, `Description_Lang_enTW`, `Description_Lang_zhTW`, `Description_Lang_esES`, `Description_Lang_esMX`, `Description_Lang_ruRU`, `Description_Lang_ptPT`, `Description_Lang_ptBR`, `Description_Lang_itIT`, `Description_Lang_Unk`, `Description_Lang_Mask`, `AuraDescription_Lang_enUS`, `AuraDescription_Lang_enGB`, `AuraDescription_Lang_koKR`, `AuraDescription_Lang_frFR`, `AuraDescription_Lang_deDE`, `AuraDescription_Lang_enCN`, `AuraDescription_Lang_zhCN`, `AuraDescription_Lang_enTW`, `AuraDescription_Lang_zhTW`, `AuraDescription_Lang_esES`, `AuraDescription_Lang_esMX`, `AuraDescription_Lang_ruRU`, `AuraDescription_Lang_ptPT`, `AuraDescription_Lang_ptBR`, `AuraDescription_Lang_itIT`, `AuraDescription_Lang_Unk`, `AuraDescription_Lang_Mask`, `ManaCostPct`, `StartRecoveryCategory`, `StartRecoveryTime`, `MaxTargetLevel`, `SpellClassSet`, `SpellClassMask_1`, `SpellClassMask_2`, `SpellClassMask_3`, `MaxTargets`, `DefenseType`, `PreventionType`, `StanceBarOrder`, `EffectChainAmplitude_1`, `EffectChainAmplitude_2`, `EffectChainAmplitude_3`, `MinFactionID`, `MinReputation`, `RequiredAuraVision`, `RequiredTotemCategoryID_1`, `RequiredTotemCategoryID_2`, `RequiredAreasID`, `SchoolMask`, `RuneCostID`, `SpellMissileID`, `PowerDisplayID`, `EffectBonusMultiplier_1`, `EffectBonusMultiplier_2`, `EffectBonusMultiplier_3`, `SpellDescriptionVariableID`, `SpellDifficultyID`) VALUE
(200100,0,0,0,262416,0,0,536870912,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,31,0,0,0,101,0,0,0,0,21,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,0,0,28,0,0,1,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,0,0,0,0,0,0,0,190011,0,0,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,458,0,2808,0,0,'Ethereal Warpweaver','','','','','','','','','','','','','','','',16712190,'','','','','','','','','','','','','','','','',16712190,'Right Click to summon and dismiss your Ethereal Warpweaver.','','','','','','','','','','','','','','','',16712190,'','','','','','','','','','','','','','','','',16712190,0,133,1500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
@@ -0,0 +1,30 @@
SET
@HideEntry = 57575,
@RemoveEntry = 57576,
@HideName = "Hide Equipped",
@RemoveName = "Clear Transmog";
DELETE FROM `item_template` WHERE `entry` = @HideEntry OR `entry` = @RemoveEntry;
INSERT INTO `item_template` (`entry`, `class`, `subclass`, `name`, `displayid`, `InventoryType`, `description`) VALUES
(@HideEntry, 15, 0, @HideName, 55112, 0, "Hide the item in this slot."),
(@RemoveEntry, 15, 0, @RemoveName, 8931, 0, "Remove active transmog for this item.");
DELETE FROM `item_template_locale` WHERE `ID` = @HideEntry OR `ID` = @RemoveEntry;
INSERT INTO `item_template_locale` (`ID`, `locale`, `Name`, `Description`) VALUES
(@HideEntry, "koKR", "장착된 아이템 숨기기", "이 슬롯의 아이템을 숨깁니다."),
(@RemoveEntry,"koKR", "변형 지우기", "이 아이템의 활성화된 변형을 제거합니다."),
(@HideEntry, "frFR", "Masquer l'équipement", "Masquer l'objet dans cet emplacement."),
(@RemoveEntry,"frFR", "Effacer transmog", "Supprimer la transmog active."),
(@HideEntry, "deDE", "Ausgerüstet verbergen", "Item in diesem Slot verbergen."),
(@RemoveEntry,"deDE", "Transmog zurücksetzen", "Aktive Transmogrifikation entfernen."),
(@HideEntry, "zhCN", "隐藏已装备", "隐藏此物品。"),
(@RemoveEntry,"zhCN", "清除幻化", "移除激活的幻化。"),
(@HideEntry, "zhTW", "隱藏已裝備", "隱藏此物品。"),
(@RemoveEntry,"zhTW", "清除幻化", "移除啟用的幻化。"),
(@HideEntry, "esES", "Ocultar equipado", "Ocultar el objeto en esta ranura."),
(@RemoveEntry,"esES", "Borrar transmog", "Eliminar la transmog activa."),
(@HideEntry, "esMX", "Ocultar equipado", "Ocultar el objeto en este espacio."),
(@RemoveEntry,"esMX", "Borrar transmog", "Eliminar la transmog activa."),
(@HideEntry, "ruRU", "Скрыть экипированное", "Скрыть предмет в слоте."),
(@RemoveEntry,"ruRU", "Очистить трансмог", "Удалить активный трансмог.");
@@ -0,0 +1,476 @@
SET @TEXT_ID := 601083;
DELETE FROM `npc_text` WHERE `ID` IN (@TEXT_ID,@TEXT_ID+1);
INSERT INTO `npc_text` (`ID`, `text0_0`) VALUES
(@TEXT_ID, 'Transmogrification allows you to change how your items look like without changing the stats of the items.\r\nItems used in transmogrification are no longer refundable, tradeable and are bound to you.\r\nUpdating a menu updates the view and prices.\r\n\r\nNot everything can be transmogrified with each other.\r\nRestrictions include but are not limited to:\r\nOnly armor and weapons can be transmogrified\r\nGuns, bows and crossbows can be transmogrified with each other\r\nFishing poles can not be transmogrified\r\nYou must be able to equip both items used in the process.\r\n\r\nTransmogrifications stay on your items as long as you own them.\r\nIf you try to put the item in guild bank or mail it to someone else, the transmogrification is stripped.\r\n\r\nYou can also remove transmogrifications for free at the transmogrifier.'),
(@TEXT_ID+1, 'You can save your own transmogrification sets.\r\n\r\nTo save, first you must transmogrify your equipped items.\r\nThen when you go to the set management menu and go to save set menu,\r\nall items you have transmogrified are displayed so you see what you are saving.\r\nIf you think the set is fine, you can click to save the set and name it as you wish.\r\n\r\nTo use a set you can click the saved set in the set management menu and then select use set.\r\nIf the set has a transmogrification for an item that is already transmogrified, the old transmogrification is lost.\r\nNote that the same transmogrification restrictions apply when trying to use a set as in normal transmogrification.\r\n\r\nTo delete a set you can go to the set\'s menu and select delete set.');
DELETE FROM `npc_text_locale` WHERE `ID` IN (@TEXT_ID,@TEXT_ID+1);
INSERT INTO `npc_text_locale` (`ID`, `Locale`, `Text0_0`) VALUES
(@TEXT_ID, 'koKR', '형상변환을 사용하면 아이템의 능력치를 변경하지 않고도 아이템의 모양을 변경할 수 있습니다.\r\n형변환에 사용된 아이템은 더 이상 환불 및 거래가 불가능하며 귀속됩니다.\r\n메뉴를 업데이트하면 보기 및 가격이 업데이트됩니다.\r\n\r\n모든 것을 서로 형상변환할 수 있는 것은 아닙니다.\r\n제한사항에는 다음이 포함되지만 이에 국한되지는 않습니다:\r\n갑옷과 무기만 형상변환할 수 있습니다.\r\n총, 활, 석궁은 서로 형상변환할 수 있습니다 \r\n낚싯대는 형상변환할 수 없습니다.\r\n이 과정에서 사용되는 두 아이템을 장착할 수 있어야 합니다.\r\n\r\n변환은 아이템을 소유하고 있는 동안 유지됩니다.\r\n시도하면 아이템을 길드 은행에 넣거나 다른 사람에게 우편으로 보내면 형상변환이 제거됩니다.\r\n\r\n변환기에서 무료로 형상변환을 제거할 수도 있습니다.'),
(@TEXT_ID+1, 'koKR', '나만의 형상변환 세트를 저장할 수 있습니다.\r\n\r\n저장하려면 먼저 장착한 아이템을 형상변환해야 합니다.\r\n그 다음 세트 관리 메뉴에서 세트 저장 메뉴로 이동하면\r\n모든 아이템이 변환한 항목이 표시되어 저장 중인 항목을 볼 수 있습니다.\r\n세트가 괜찮다고 생각되면 클릭하여 세트를 저장하고 원하는 이름을 지정할 수 있습니다.\r\n\r\n세트를 사용하려면 세트 관리 메뉴에서 저장된 세트를 클릭한 다음 세트 사용을 선택할 수 있습니다.\r\n세트에 이미 형상변환된 아이템에 대한 형상변환이 있는 경우 이전 형상변환은 손실됩니다.\r\n다음과 같은 경우 동일한 형상변환 제한이 적용됩니다. 일반 형상변환에서와 같이 세트를 사용하려고 합니다.\r\n\r\n세트를 삭제하려면 세트 메뉴로 이동하여 세트 삭제를 선택하면 됩니다.'),
(@TEXT_ID, 'frFR', 'La transmogrification vous permet de modifier l''apparence de vos objets sans modifier les statistiques des objets.\\r\\nLes objets utilisés dans la transmogrification ne sont plus remboursables, échangeables et vous sont liés.\\r\\nLa mise à jour d''un menu met à jour la vue et les prix.\\ r\\n\\r\\nTout ne peut pas être transmogrifié les uns avec les autres.\\r\\nLes restrictions incluent, mais ne sont pas limitées à :\\r\\nSeules les armures et les armes peuvent être transmogrifiées\\r\\nLes fusils, les arcs et les arbalètes peuvent être transmogrifiés les uns avec les autres \\r\\nLes cannes à pêche ne peuvent pas être transmogrifiées\\r\\nVous devez être capable d''équiper les deux objets utilisés dans le processus.\\r\\n\\r\\nLes transmogrifications restent sur vos objets tant que vous les possédez.\\r\\nSi vous essayez pour mettre l''objet dans la banque de guilde ou l''envoyer à quelqu''un d''autre, la transmogrification est supprimée.\\r\\n\\r\\nVous pouvez également supprimer les transmogrifications gratuitement sur le transmogrificateur.'),
(@TEXT_ID+1, 'frFR', 'Vous pouvez enregistrer vos propres ensembles de transmogrification.\r\n\r\nPour enregistrer, vous devez d''abord transmogrifier vos objets équipés.\r\nEnsuite, lorsque vous accédez au menu de gestion des ensembles et accédez au menu de sauvegarde,\r\ntous les objets vous avez transmogrifié sont affichés afin que vous puissiez voir ce que vous enregistrez.\r\nSi vous pensez que l''ensemble est correct, vous pouvez cliquer pour enregistrer l''ensemble et le nommer comme vous le souhaitez.\r\n\r\nPour utiliser un ensemble, vous pouvez cliquer sur l''ensemble enregistré dans le menu de gestion de l''ensemble, puis sélectionnez utiliser l''ensemble.\r\nSi l''ensemble a une transmogrification pour un élément déjà transmogrifié, l''ancienne transmogrification est perdue.\r\nNotez que les mêmes restrictions de transmogrification s''appliquent lorsque essayer d''utiliser un ensemble comme dans une transmogrification normale.\r\n\r\nPour supprimer un ensemble, vous pouvez aller dans le menu de l''ensemble et sélectionner supprimer l''ensemble.'),
(@TEXT_ID, 'deDE', 'Mit der Transmogrifizierung können Sie das Aussehen Ihrer Gegenstände ändern, ohne die Werte der Gegenstände zu ändern.\r\nBei der Transmogrifizierung verwendete Gegenstände sind nicht mehr erstattungsfähig, handelbar und an Sie gebunden.\r\nDurch das Aktualisieren eines Menüs werden die Ansicht und die Preise aktualisiert.\ r\n\r\nNicht alles kann miteinander transmogrifiziert werden.\r\nZu den Einschränkungen gehören unter anderem:\r\nNur Rüstungen und Waffen können miteinander transmogrifiziert werden.\r\nWaffen, Bögen und Armbrüste können miteinander transmogrifiziert werden \r\nAngelruten können nicht transmogrifiziert werden.\r\nSie müssen in der Lage sein, beide dabei verwendeten Gegenstände auszurüsten.\r\n\r\nTransmogrifikationen bleiben auf Ihren Gegenständen, solange Sie sie besitzen.\r\nWenn Sie es versuchen Um den Gegenstand in die Gildenbank zu legen oder an jemand anderen zu verschicken, wird die Transmogrifikation entfernt.\r\n\r\nSie können Transmogrifikationen auch kostenlos beim Transmogrifizierer entfernen.'),
(@TEXT_ID+1, 'deDE', 'Sie können Ihre eigenen Transmogrifikationssets speichern.\r\n\r\nZum Speichern müssen Sie zuerst Ihre ausgerüsteten Gegenstände transmogrifizieren.\r\nWenn Sie dann zum Set-Verwaltungsmenü gehen und zum Set-Speichermenü gehen,\r\nalle Gegenstände Sie werden angezeigt, sodass Sie sehen, was Sie speichern.\r\nWenn Sie der Meinung sind, dass das Set in Ordnung ist, können Sie auf klicken, um das Set zu speichern und es nach Ihren Wünschen zu benennen.\r\n\r\nSo verwenden Sie ein Set Sie können im Setverwaltungsmenü auf das gespeicherte Set klicken und dann „Set verwenden“ auswählen.\r\nWenn das Set eine Transmogrifizierung für einen Gegenstand hat, der bereits transmogrifiziert ist, geht die alte Transmogrifizierung verloren.\r\nBeachten Sie, dass die gleichen Transmogrifizierungsbeschränkungen gelten, wenn Ich versuche, ein Set wie bei der normalen Transmogrifizierung zu verwenden.\r\n\r\nUm ein Set zu löschen, gehen Sie zum Menü des Sets und wählen Sie „Set löschen“.'),
(@TEXT_ID, 'zhCN', '变形允许您更改物品的外观,而无需更改物品的统计信息。\r\n变形中使用的物品不再可退款、可交易并且与您绑定。\r\n更新菜单会更新视图和价格。\ r\n\r\n并不是所有东西都可以互相变形。\r\n限制包括但不限于:\r\n只有盔甲和武器可以变形\r\n枪、弓、弩可以互相变形 \r\n鱼竿无法变形\r\n您必须能够装备在此过程中使用的两个物品。\r\n\r\n只要您拥有它们,变形就会保留在您的物品上。\r\n如果您尝试 将物品放入公会银行或邮寄给其他人时,变形会被剥离。\r\n\r\n您也可以在变形器处免费移除变形。'),
(@TEXT_ID+1, 'zhCN', '您可以保存自己的变形套装。\r\n\r\n要保存,首先必须变形您装备的物品。\r\n然后当您进入套装管理菜单并进入保存套装菜单时,\r\n所有物品 将显示您已经变形的内容,以便您看到正在保存的内容。\r\n如果您认为该集合没问题,可以单击以保存该集合并根据需要命名它。\r\n\r\n要使用您的集合 可以在集合管理菜单中单击已保存的集合,然后选择使用集合。\r\n如果该集合对已变形的项目有变形,则旧的变形将丢失。\r\n请注意,以下情况适用相同的变形限制: 尝试像正常变形一样使用集合。\r\n\r\n要删除集合,您可以转到集合的菜单并选择删除集合。'),
(@TEXT_ID, 'zhTW', '變身術讓你能夠改變物品的外觀,而不改變物品的屬性。\r\n在變身術中使用的物品將不再可退款、可交易,並且將綁定於你。\r\n更新菜單將更新視圖和價格。\r\n\r\n並非所有物品都可以互相變身。\r\n限制條件包括但不限於:\r\n只有盔甲和武器可以變身。\r\n槍械、弓和弩可以互相變身。\r\n釣魚竿無法進行變身。\r\n你必須能夠裝備在此過程中使用的兩件物品。\r\n\r\n變身效果會持續存在於你的物品上,只要你擁有它們。\r\n如果你試圖將物品放入公會銀行或郵寄給他人,將會去除變身效果。\r\n\r\n你也可以在變身術轉化師處免費移除變身效果。'),
(@TEXT_ID+1, 'zhTW', '你可以保存自己的變身套裝。\r\n\r\n要保存,首先必須對你裝備的物品進行變身。\r\n然後,當你進入套裝管理菜單並進入保存套裝菜單時,\r\n所有你已經變身的物品都會顯示出來,這樣你就可以看到你正在保存的內容。\r\n如果你認為這套裝很好,你可以點擊保存套裝並按你的意願命名。\r\n\r\n要使用一個套裝,你可以在套裝管理菜單中點擊已保存的套裝,然後選擇使用套裝。\r\n如果該套裝中的物品已經有變身效果,舊的變身效果將會遺失。\r\n請注意,當嘗試使用套裝時,與普通變身相同的限制將適用。\r\n\r\n要刪除一個套裝,你可以進入套裝的菜單,然後選擇刪除套裝。'),
(@TEXT_ID, 'esES', 'La transfiguración permite cambiar la apariencia de tus objetos sin cambiar sus estadísticas.\r\nLos objetos utilizados en la transfiguración ya no son reembolsables, intercambiables y están vinculados a usted.\r\nActualizar un menú actualiza la vista y los precios.\ r\n\r\nNo todo se puede transfigurar entre sí.\r\nLas restricciones incluyen, pero no se limitan a:\r\nSolo se pueden transfigurar armaduras y armas\r\nArmas, arcos y ballestas se pueden transfigurar entre sí \r\nLas cañas de pescar no se pueden transfigurar.\r\nDebes poder equipar ambos elementos utilizados en el proceso.\r\n\r\nLas transfiguraciones permanecen en tus elementos mientras los tengas.\r\nSi lo intentas para poner el artículo en el banco del gremio o enviarlo por correo a otra persona, la transfiguración se elimina.\r\n\r\nTambién puedes eliminar las transfiguraciones de forma gratuita en el transfigurador.'),
(@TEXT_ID+1, 'esES', 'Puede guardar sus propios conjuntos de transfiguración.\r\n\r\nPara guardar, primero debe transfigurar sus elementos equipados.\r\nLuego, cuando vaya al menú de administración de conjuntos y vaya al menú Guardar conjunto,\r\ntodos los elementos que ha transfigurado se muestran para que vea lo que está guardando.\r\nSi cree que el conjunto está bien, puede hacer clic para guardar el conjunto y nombrarlo como desee.\r\n\r\nPara usar un conjunto puede hacer clic en el conjunto guardado en el menú de administración de conjuntos y luego seleccionar usar conjunto.\r\nSi el conjunto tiene una transfiguración para un elemento que ya está transfigurado, la transfiguración anterior se pierde.\r\nTenga en cuenta que se aplican las mismas restricciones de transfiguración cuando tratando de usar un conjunto como en la transfiguración normal.\r\n\r\nPara eliminar un conjunto, puede ir al menú del conjunto y seleccionar eliminar conjunto.'),
(@TEXT_ID, 'esMX', 'La transfiguración permite cambiar la apariencia de tus objetos sin cambiar sus estadísticas.\r\nLos objetos utilizados en la transfiguración ya no son reembolsables, intercambiables y están vinculados a usted.\r\nActualizar un menú actualiza la vista y los precios.\ r\n\r\nNo todo se puede transfigurar entre sí.\r\nLas restricciones incluyen, pero no se limitan a:\r\nSolo se pueden transfigurar armaduras y armas\r\nArmas, arcos y ballestas se pueden transfigurar entre sí \r\nLas cañas de pescar no se pueden transfigurar.\r\nDebes poder equipar ambos elementos utilizados en el proceso.\r\n\r\nLas transfiguraciones permanecen en tus elementos mientras los tengas.\r\nSi lo intentas para poner el artículo en el banco del gremio o enviarlo por correo a otra persona, la transfiguración se elimina.\r\n\r\nTambién puedes eliminar las transfiguraciones de forma gratuita en el transfigurador.'),
(@TEXT_ID+1, 'esMX', 'Puede guardar sus propios conjuntos de transfiguración.\r\n\r\nPara guardar, primero debe transfigurar sus elementos equipados.\r\nLuego, cuando vaya al menú de administración de conjuntos y vaya al menú Guardar conjunto,\r\ntodos los elementos que ha transfigurado se muestran para que vea lo que está guardando.\r\nSi cree que el conjunto está bien, puede hacer clic para guardar el conjunto y nombrarlo como desee.\r\n\r\nPara usar un conjunto puede hacer clic en el conjunto guardado en el menú de administración de conjuntos y luego seleccionar usar conjunto.\r\nSi el conjunto tiene una transfiguración para un elemento que ya está transfigurado, la transfiguración anterior se pierde.\r\nTenga en cuenta que se aplican las mismas restricciones de transfiguración cuando tratando de usar un conjunto como en la transfiguración normal.\r\n\r\nPara eliminar un conjunto, puede ir al menú del conjunto y seleccionar eliminar conjunto.'),
(@TEXT_ID, 'ruRU', 'Трансмогрификация позволяет вам изменить внешний вид ваших предметов без изменения характеристик предметов.\r\nПредметы, использованные в трансмогрификации, больше не подлежат возврату, обмену и привязаны к вам.\r\nОбновление меню обновляет вид и цены.\ r\n\r\nНе все можно трансмогрифицировать друг с другом.\r\nОграничения включают, но не ограничиваются:\r\nМожно трансмогрифицировать только доспехи и оружие\r\nОружие, луки и арбалеты можно трансмогрифицировать друг с другом \r\nУдочки не могут быть трансмогрифицированы\r\nВы должны быть в состоянии экипировать оба предмета, используемые в процессе.\r\n\r\nПреобразования остаются на ваших предметах, пока они у вас есть.\r\nЕсли вы попытаетесь чтобы положить предмет в банк гильдии или отправить кому-то другому, трансмогрификация снимается.\r\n\r\nВы также можете бесплатно удалить трансмогрификацию в трансмогрификаторе.'),
(@TEXT_ID+1, 'ruRU', 'Вы можете сохранять свои собственные наборы для трансмогрификации.\r\n\r\nЧтобы сохранить, сначала вы должны трансмогрифицировать свои экипированные предметы.\r\nЗатем, когда вы перейдете в меню управления наборами и перейдете в меню сохранения набора,\r\nвсе предметы которые вы преобразовали, отображаются так, что вы видите, что вы сохраняете.\r\nЕсли вы считаете, что набор в порядке, вы можете нажать, чтобы сохранить набор и назвать его по своему желанию.\r\n\r\nЧтобы использовать набор, можно щелкнуть сохраненный набор в меню управления набором, а затем выбрать использовать набор.\r\nЕсли в наборе есть трансмогрификация предмета, который уже трансмогрифицирован, старая трансмогрификация теряется.\r\nОбратите внимание, что те же ограничения на трансмогрификацию применяются, когда пытается использовать набор, как при обычной трансмогрификации.\r\n\r\nЧтобы удалить набор, вы можете перейти в меню набора и выбрать удалить набор.');
DELETE FROM `module_string` WHERE `module` = 'mod-transmog';
DELETE FROM `module_string_locale` WHERE `module` = 'mod-transmog';
INSERT INTO `module_string` (`module`, `id`, `string`) VALUES
-- Transmog result strings
('mod-transmog', 1, 'Item successfully transmogrified.'),
('mod-transmog', 2, 'Equipment slot is empty.'),
('mod-transmog', 3, 'Invalid source item selected.'),
('mod-transmog', 4, 'Source item does not exist.'),
('mod-transmog', 5, 'Destination item does not exist.'),
('mod-transmog', 6, 'Selected items are invalid.'),
('mod-transmog', 7, 'You don''t have enough money.'),
('mod-transmog', 8, 'You don''t have enough tokens.'),
('mod-transmog', 9, 'All your transmogrifications were removed.'),
('mod-transmog', 10, 'No transmogrification found.'),
('mod-transmog', 11, 'Invalid name inserted.'),
-- Command strings
('mod-transmog', 12, 'Showing transmogrified items, relog to update the current area.'),
('mod-transmog', 13, 'Hiding transmogrified items, relog to update the current area.'),
('mod-transmog', 14, 'The selected item is not suitable for transmogrification.'),
('mod-transmog', 15, 'The selected item cannot be used for transmogrification of the target player.'),
('mod-transmog', 16, 'Performing transmog appearance sync...'),
('mod-transmog', 17, 'Appearance sync complete.'),
('mod-transmog', 18, 'The transmog NPC will now display available appearances as a vendor interface, allowing preview. \nDISCLAIMER: If you have too many appearances, some will not be displayed due to a client limitation. In that case, disable this option.'),
('mod-transmog', 19, 'The transmog NPC will now display available appearances as gossip list.'),
-- Gossip/UI strings
('mod-transmog', 20, 'How does transmogrification work?'),
('mod-transmog', 21, 'Manage sets'),
('mod-transmog', 22, 'Remove all transmogrifications'),
('mod-transmog', 23, 'Remove transmogrifications from all equipped items?'),
('mod-transmog', 24, 'Update menu'),
('mod-transmog', 25, 'How do sets work?'),
('mod-transmog', 26, 'Save set'),
('mod-transmog', 27, 'Back...'),
('mod-transmog', 28, 'Use this set'),
('mod-transmog', 29, 'Using this set for transmogrify will bind transmogrified items to you and make them non-refundable and non-tradeable.\nDo you wish to continue?\n\n'),
('mod-transmog', 30, 'Delete set'),
('mod-transmog', 31, 'Are you sure you want to delete '),
('mod-transmog', 32, 'Insert set name'),
('mod-transmog', 33, 'Search...'),
('mod-transmog', 34, 'Searching for: '),
('mod-transmog', 35, 'Search for what item?'),
('mod-transmog', 36, 'You are hiding the item in this slot.\nDo you wish to continue?\n\n'),
('mod-transmog', 37, 'Hide Slot'),
('mod-transmog', 38, 'Remove transmogrification from the slot?'),
('mod-transmog', 39, 'Using this item for transmogrify will bind it to you and make it non-refundable and non-tradeable.\nDo you wish to continue?\n\n'),
('mod-transmog', 40, 'Previous Page'),
('mod-transmog', 41, 'Next Page'),
('mod-transmog', 42, 'has been added to your appearance collection.'),
-- Disclaimer strings
('mod-transmog', 43, '|cFF4DB3FFSet bonuses won''t appear in the item tooltip while transmogrified, but they are still fully active.\nTo stop seeing this notice, type |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 44, 'Set bonus disclaimer enabled.'),
('mod-transmog', 45, 'Set bonus disclaimer disabled.');
INSERT INTO `module_string_locale` (`module`, `id`, `locale`, `string`) VALUES
-- ID 1: OK
('mod-transmog', 1, 'koKR', '성공적으로 형상변환된 아이템'),
('mod-transmog', 1, 'frFR', 'Objet transmogrifié avec succès'),
('mod-transmog', 1, 'deDE', 'Gegenstand erfolgreich transmogrifiziert'),
('mod-transmog', 1, 'zhCN', '物品变形成功'),
('mod-transmog', 1, 'zhTW', '物品變身成功。'),
('mod-transmog', 1, 'esES', 'Objeto transfigurado con éxito'),
('mod-transmog', 1, 'esMX', 'Objeto transfigurado con éxito'),
('mod-transmog', 1, 'ruRU', 'Предмет успешно трансмогрифицирован'),
-- ID 2: INVALID_SLOT
('mod-transmog', 2, 'koKR', '장비 슬롯이 비어 있습니다.'),
('mod-transmog', 2, 'frFR', 'L''emplacement d''équipement est vide.'),
('mod-transmog', 2, 'deDE', 'Der Ausrüstungsplatz ist leer.'),
('mod-transmog', 2, 'zhCN', '装备槽是空的。'),
('mod-transmog', 2, 'zhTW', '裝備欄位是空的。'),
('mod-transmog', 2, 'esES', 'La ranura de equipo está vacía.'),
('mod-transmog', 2, 'esMX', 'La ranura de equipo está vacía.'),
('mod-transmog', 2, 'ruRU', 'Слот снаряжения пуст.'),
-- ID 3: INVALID_SRC_ENTRY
('mod-transmog', 3, 'koKR', '선택한 소스 요소가 잘못되었습니다.'),
('mod-transmog', 3, 'frFR', 'Élément source sélectionné non valide.'),
('mod-transmog', 3, 'deDE', 'Ungültiges ausgewähltes Quellelement.'),
('mod-transmog', 3, 'zhCN', '所选源元素无效。'),
('mod-transmog', 3, 'zhTW', '選擇的來源物品無效。'),
('mod-transmog', 3, 'esES', 'Elemento fuente seleccionado no válido.'),
('mod-transmog', 3, 'esMX', 'Elemento fuente seleccionado no válido.'),
('mod-transmog', 3, 'ruRU', 'Недопустимый выбранный исходный элемент.'),
-- ID 4: MISSING_SRC_ITEM
('mod-transmog', 4, 'koKR', '소스 요소가 존재하지 않습니다.'),
('mod-transmog', 4, 'frFR', 'L''élément source n''existe pas.'),
('mod-transmog', 4, 'deDE', 'Das Quellelement existiert nicht.'),
('mod-transmog', 4, 'zhCN', '源元素不存在。'),
('mod-transmog', 4, 'zhTW', '來源物品不存在。'),
('mod-transmog', 4, 'esES', 'El elemento de origen no existe.'),
('mod-transmog', 4, 'esMX', 'El elemento de origen no existe.'),
('mod-transmog', 4, 'ruRU', 'Исходный элемент не существует.'),
-- ID 5: MISSING_DEST_ITEM
('mod-transmog', 5, 'koKR', '대상 요소가 존재하지 않습니다.'),
('mod-transmog', 5, 'frFR', 'L''élément cible n''existe pas.'),
('mod-transmog', 5, 'deDE', 'Das Zielelement existiert nicht.'),
('mod-transmog', 5, 'zhCN', '目标元素不存在。'),
('mod-transmog', 5, 'zhTW', '目標物品不存在。'),
('mod-transmog', 5, 'esES', 'El elemento de destino no existe.'),
('mod-transmog', 5, 'esMX', 'El elemento de destino no existe.'),
('mod-transmog', 5, 'ruRU', 'Целевой элемент не существует.'),
-- ID 6: INVALID_ITEMS
('mod-transmog', 6, 'koKR', '선택한 항목이 잘못되었습니다.'),
('mod-transmog', 6, 'frFR', 'Les éléments sélectionnés ne sont pas valides'),
('mod-transmog', 6, 'deDE', 'Die ausgewählten Elemente sind ungültig'),
('mod-transmog', 6, 'zhCN', '所选项目无效'),
('mod-transmog', 6, 'zhTW', '所選物品無效。'),
('mod-transmog', 6, 'esES', 'Los elementos seleccionados no son válidos'),
('mod-transmog', 6, 'esMX', 'Los elementos seleccionados no son válidos'),
('mod-transmog', 6, 'ruRU', 'Выбранные элементы недействительны'),
-- ID 7: NOT_ENOUGH_MONEY
('mod-transmog', 7, 'koKR', '당신은 돈이 충분하지 않습니다.'),
('mod-transmog', 7, 'frFR', 'Vous n''avez pas assez d''argent.'),
('mod-transmog', 7, 'deDE', 'Du hast nicht genug Geld.'),
('mod-transmog', 7, 'zhCN', '你没有足够的钱。'),
('mod-transmog', 7, 'zhTW', '你沒有足夠的金錢。'),
('mod-transmog', 7, 'esES', 'No tienes suficiente dinero.'),
('mod-transmog', 7, 'esMX', 'No tienes suficiente dinero.'),
('mod-transmog', 7, 'ruRU', 'У вас недостаточно денег.'),
-- ID 8: NOT_ENOUGH_TOKENS
('mod-transmog', 8, 'koKR', '칩이 충분하지 않습니다.'),
('mod-transmog', 8, 'frFR', 'Vous n''avez pas assez de jetons.'),
('mod-transmog', 8, 'deDE', 'Du hast nicht genug Chips.'),
('mod-transmog', 8, 'zhCN', '你的筹码不够。'),
('mod-transmog', 8, 'zhTW', '你沒有足夠的代幣。'),
('mod-transmog', 8, 'esES', 'No tienes suficientes fichas.'),
('mod-transmog', 8, 'esMX', 'No tienes suficientes fichas.'),
('mod-transmog', 8, 'ruRU', 'У вас недостаточно фишек.'),
-- ID 9: UNTRANSMOG_OK
('mod-transmog', 9, 'koKR', '모든 형상변환을 제거했습니다.'),
('mod-transmog', 9, 'frFR', 'Suppression de toutes vos transmogrifications'),
('mod-transmog', 9, 'deDE', 'Alle deine Transmogrifikationen wurden entfernt'),
('mod-transmog', 9, 'zhCN', '删除了你所有的变形'),
('mod-transmog', 9, 'zhTW', '你的所有變身效果都被移除了。'),
('mod-transmog', 9, 'esES', 'Se eliminaron todas tus transfiguraciones'),
('mod-transmog', 9, 'esMX', 'Se eliminaron todas tus transfiguraciones'),
('mod-transmog', 9, 'ruRU', 'Удалены все ваши трансмогрификации'),
-- ID 10: UNTRANSMOG_NO_TRANSMOGS
('mod-transmog', 10, 'koKR', '변형이 없습니다.'),
('mod-transmog', 10, 'frFR', 'Aucune transfiguration trouvée.'),
('mod-transmog', 10, 'deDE', 'Keine Verwandlung gefunden.'),
('mod-transmog', 10, 'zhCN', '没有发现变形。'),
('mod-transmog', 10, 'zhTW', '找不到變身效果。'),
('mod-transmog', 10, 'esES', 'No se encontró transfiguración.'),
('mod-transmog', 10, 'esMX', 'No se encontró transfiguración.'),
('mod-transmog', 10, 'ruRU', 'Трансфигурация не найдена.'),
-- ID 11: PRESET_ERR_INVALID_NAME
('mod-transmog', 11, 'koKR', '삽입된 이름이 잘못되었습니다.'),
('mod-transmog', 11, 'frFR', 'Nom inséré non valide.'),
('mod-transmog', 11, 'deDE', 'Ungültiger eingegebener Name.'),
('mod-transmog', 11, 'zhCN', '插入的名称无效。'),
('mod-transmog', 11, 'zhTW', '插入的名稱無效。'),
('mod-transmog', 11, 'esES', 'Nombre insertado no válido.'),
('mod-transmog', 11, 'esMX', 'Nombre insertado no válido.'),
('mod-transmog', 11, 'ruRU', 'Недопустимое вставленное имя.'),
-- ID 12: CMD_SHOW
('mod-transmog', 12, 'koKR', '형상변환된 아이템이 표시되며, 현재 영역을 새로고침하려면 다시 로그인하세요.'),
('mod-transmog', 12, 'frFR', 'En affichant les objets transmogrifiés, reconnectez-vous pour actualiser la zone actuelle.'),
('mod-transmog', 12, 'deDE', 'Transmogrifizierte Gegenstände werden angezeigt. Melden Sie sich erneut an, um den aktuellen Bereich zu aktualisieren.'),
('mod-transmog', 12, 'zhCN', '显示已变形物品,重新登录即可刷新当前区域。'),
('mod-transmog', 12, 'zhTW', '顯示已變身的物品,重新登錄以更新當前區域。'),
('mod-transmog', 12, 'esES', 'Mostrando elementos transfigurados, vuelva a iniciar sesión para actualizar el área actual.'),
('mod-transmog', 12, 'esMX', 'Mostrando elementos transfigurados, vuelva a iniciar sesión para actualizar el área actual.'),
('mod-transmog', 12, 'ruRU', 'Отображение трансмогрифицированных предметов. Войдите в систему еще раз, чтобы обновить текущую область.'),
-- ID 13: CMD_HIDE
('mod-transmog', 13, 'koKR', '형상변환된 아이템을 숨기고 다시 로그인하여 현재 영역을 새로 고칩니다.'),
('mod-transmog', 13, 'frFR', 'Masquez les objets transmogrifiés, reconnectez-vous pour actualiser la zone actuelle.'),
('mod-transmog', 13, 'deDE', 'Transmogrifizierte Gegenstände ausblenden, erneut anmelden, um den aktuellen Bereich zu aktualisieren.'),
('mod-transmog', 13, 'zhCN', '隐藏变形物品,重新登录以刷新当前区域。'),
('mod-transmog', 13, 'zhTW', '隱藏已變身的物品,重新登錄以更新當前區域。'),
('mod-transmog', 13, 'esES', 'Ocultar elementos transfigurados, volver a iniciar sesión para actualizar el área actual.'),
('mod-transmog', 13, 'esMX', 'Ocultar elementos transfigurados, volver a iniciar sesión para actualizar el área actual.'),
('mod-transmog', 13, 'ruRU', 'Скройте трансмогрифицированные предметы, войдите в систему, чтобы обновить текущую область.'),
-- ID 14: CMD_ADD_UNSUITABLE
('mod-transmog', 14, 'koKR', '선택한 항목은 변형에 적합하지 않습니다.'),
('mod-transmog', 14, 'frFR', 'L''objet sélectionné ne convient pas à la métamorphose.'),
('mod-transmog', 14, 'deDE', 'Der ausgewählte Gegenstand ist nicht für die Verwandlung geeignet.'),
('mod-transmog', 14, 'zhCN', '所选项目不适合变形。'),
('mod-transmog', 14, 'zhTW', '所選物品不適合進行變身。'),
('mod-transmog', 14, 'esES', 'El elemento seleccionado no es adecuado para la transfiguración.'),
('mod-transmog', 14, 'esMX', 'El elemento seleccionado no es adecuado para la transfiguración.'),
('mod-transmog', 14, 'ruRU', 'Выбранный предмет не подходит для преображения.'),
-- ID 15: CMD_ADD_FORBIDDEN
('mod-transmog', 15, 'koKR', '선택한 아이템은 대상 플레이어의 형상변환에 사용할 수 없습니다.'),
('mod-transmog', 15, 'frFR', 'L''objet sélectionné ne peut pas être utilisé pour la transmogrification du joueur ciblé'),
('mod-transmog', 15, 'deDE', 'Der ausgewählte Gegenstand kann nicht für die Transmogrifizierung des Zielspielers verwendet werden'),
('mod-transmog', 15, 'zhCN', '所选物品不能用于目标玩家的变身'),
('mod-transmog', 15, 'zhTW', '所選物品不能用於目標玩家的變身效果。'),
('mod-transmog', 15, 'esES', 'El elemento seleccionado no se puede usar para la transfiguración del jugador objetivo'),
('mod-transmog', 15, 'esMX', 'El elemento seleccionado no se puede usar para la transfiguración del jugador objetivo'),
('mod-transmog', 15, 'ruRU', 'Выбранный предмет нельзя использовать для трансмогрификации целевого игрока.'),
-- ID 16: CMD_BEGIN_SYNC
('mod-transmog', 16, 'koKR', '형상변환 모양 동기화 수행 중...'),
('mod-transmog', 16, 'frFR', 'Exécution de la synchronisation de l''apparence de transmogrification...'),
('mod-transmog', 16, 'deDE', 'Synchronisierung des Transmog-Erscheinungsbilds wird durchgeführt ...'),
('mod-transmog', 16, 'zhCN', '正在执行 Transmog 外观同步...'),
('mod-transmog', 16, 'zhTW', '正在執行外觀同步變身操作...'),
('mod-transmog', 16, 'esES', 'Realizando sincronización de apariencia de transfiguración...'),
('mod-transmog', 16, 'esMX', 'Realizando sincronización de apariencia de transfiguración...'),
('mod-transmog', 16, 'ruRU', 'Выполнение синхронизации внешнего вида Transmog...'),
-- ID 17: CMD_COMPLETE_SYNC
('mod-transmog', 17, 'koKR', '전체 모양 동기화'),
('mod-transmog', 17, 'frFR', 'Synchronisation complète de l''apparence'),
('mod-transmog', 17, 'deDE', 'Vollständige Synchronisierung des Erscheinungsbilds'),
('mod-transmog', 17, 'zhCN', '完全外观同步'),
('mod-transmog', 17, 'zhTW', '外觀同步變身操作完成。'),
('mod-transmog', 17, 'esES', 'Sincronización de apariencia completa'),
('mod-transmog', 17, 'esMX', 'Sincronización de apariencia completa'),
('mod-transmog', 17, 'ruRU', 'Полная синхронизация внешнего вида'),
-- ID 20: HOWWORKS
('mod-transmog', 20, 'koKR', '형상변환은 어떻게 작동합니까?'),
('mod-transmog', 20, 'frFR', 'Comment fonctionne la transmogrification ?'),
('mod-transmog', 20, 'deDE', 'Wie funktioniert Transmogrifizierung?'),
('mod-transmog', 20, 'zhCN', '变形术是如何运作的?'),
('mod-transmog', 20, 'zhTW', '幻化是如何運作的?'),
('mod-transmog', 20, 'esES', '¿Cómo funciona la transfiguración?'),
('mod-transmog', 20, 'esMX', '¿Cómo funciona la transfiguración?'),
('mod-transmog', 20, 'ruRU', 'Как работает трансмогрификация?'),
-- ID 21: MANAGESETS
('mod-transmog', 21, 'koKR', '세트 관리'),
('mod-transmog', 21, 'frFR', 'Gérer les ensembles'),
('mod-transmog', 21, 'deDE', 'Sets verwalten'),
('mod-transmog', 21, 'zhCN', '管理套装'),
('mod-transmog', 21, 'zhTW', '管理套裝'),
('mod-transmog', 21, 'esES', 'Administrar conjuntos'),
('mod-transmog', 21, 'esMX', 'Administrar conjuntos'),
('mod-transmog', 21, 'ruRU', 'Управление комплектами'),
-- ID 22: REMOVETRANSMOG
('mod-transmog', 22, 'koKR', '모든 변형 제거'),
('mod-transmog', 22, 'frFR', 'Supprimer toutes les transmogrifications'),
('mod-transmog', 22, 'deDE', 'Alle Transmogrifikationen entfernen'),
('mod-transmog', 22, 'zhCN', '移除所有幻化'),
('mod-transmog', 22, 'zhTW', '移除所有幻化'),
('mod-transmog', 22, 'esES', 'Eliminar todas las transfiguraciones'),
('mod-transmog', 22, 'esMX', 'Eliminar todas las transfiguraciones'),
('mod-transmog', 22, 'ruRU', 'Удалить все трансмогрификации'),
-- ID 23: REMOVETRANSMOG_ASK
('mod-transmog', 23, 'koKR', '장착한 모든 아이템의 변형을 제거합니까?'),
('mod-transmog', 23, 'frFR', 'Supprimer les transmogrifications de tous les objets équipés ?'),
('mod-transmog', 23, 'deDE', 'Transmogrifikationen von allen ausgerüsteten Gegenständen entfernen?'),
('mod-transmog', 23, 'zhCN', '是否要从所有已装备的物品中移除幻化?'),
('mod-transmog', 23, 'zhTW', '從所有已裝備物品中移除幻化?'),
('mod-transmog', 23, 'esES', '¿Eliminar las transfiguraciones de todos los objetos equipados?'),
('mod-transmog', 23, 'esMX', '¿Eliminar las transfiguraciones de todos los objetos equipados?'),
('mod-transmog', 23, 'ruRU', 'Удалить трансмогрификации со всех экипированных предметов?'),
-- ID 24: UPDATEMENU
('mod-transmog', 24, 'koKR', '메뉴 업데이트'),
('mod-transmog', 24, 'frFR', 'Mettre à jour le menu'),
('mod-transmog', 24, 'deDE', 'Menü aktualisieren'),
('mod-transmog', 24, 'zhCN', '更新菜单'),
('mod-transmog', 24, 'zhTW', '更新選單'),
('mod-transmog', 24, 'esES', 'Actualizar menú'),
('mod-transmog', 24, 'esMX', 'Actualizar menú'),
('mod-transmog', 24, 'ruRU', 'Обновить меню'),
-- ID 25: HOWSETSWORK
('mod-transmog', 25, 'koKR', '세트는 어떻게 작동합니까?'),
('mod-transmog', 25, 'frFR', 'Comment fonctionnent les ensembles ?'),
('mod-transmog', 25, 'deDE', 'Wie funktionieren Sets?'),
('mod-transmog', 25, 'zhCN', '套装是如何运作的?'),
('mod-transmog', 25, 'zhTW', '套裝如何運作?'),
('mod-transmog', 25, 'esES', '¿Cómo funcionan los conjuntos?'),
('mod-transmog', 25, 'esMX', '¿Cómo funcionan los conjuntos?'),
('mod-transmog', 25, 'ruRU', 'Как работают комплекты?'),
-- ID 26: SAVESET
('mod-transmog', 26, 'koKR', '세트 저장'),
('mod-transmog', 26, 'frFR', 'Sauvegarder l''ensemble'),
('mod-transmog', 26, 'deDE', 'Set speichern'),
('mod-transmog', 26, 'zhCN', '保存套装'),
('mod-transmog', 26, 'zhTW', '儲存套裝'),
('mod-transmog', 26, 'esES', 'Guardar conjunto'),
('mod-transmog', 26, 'esMX', 'Guardar conjunto'),
('mod-transmog', 26, 'ruRU', 'Сохранить комплект'),
-- ID 27: BACK
('mod-transmog', 27, 'koKR', '뒤로...'),
('mod-transmog', 27, 'frFR', 'Retour...'),
('mod-transmog', 27, 'deDE', 'Zurück...'),
('mod-transmog', 27, 'zhCN', '返回...'),
('mod-transmog', 27, 'zhTW', '返回...'),
('mod-transmog', 27, 'esES', 'Atrás...'),
('mod-transmog', 27, 'esMX', 'Atrás...'),
('mod-transmog', 27, 'ruRU', 'Назад...'),
-- ID 28: USESET
('mod-transmog', 28, 'koKR', '이 세트를 사용'),
('mod-transmog', 28, 'frFR', 'Utiliser cet ensemble'),
('mod-transmog', 28, 'deDE', 'Dieses Set verwenden'),
('mod-transmog', 28, 'zhCN', '使用此套装'),
('mod-transmog', 28, 'zhTW', '使用此套裝'),
('mod-transmog', 28, 'esES', 'Usar este conjunto'),
('mod-transmog', 28, 'esMX', 'Usar este conjunto'),
('mod-transmog', 28, 'ruRU', 'Использовать этот комплект'),
-- ID 29: CONFIRM_USESET
('mod-transmog', 29, 'koKR', '이 세트를 변형에 사용하면 변형된 아이템이 계정에 제한되어 환불 및 거래가 불가능합니다.\n계속하시겠습니까?\n\n'),
('mod-transmog', 29, 'frFR', 'En utilisant cet ensemble pour la transmogrification, les objets transmogrifiés seront liés à votre personnage et deviendront non remboursables et non échangeables.\nVoulez-vous continuer ?\n\n'),
('mod-transmog', 29, 'deDE', 'Wenn du dieses Set für die Transmogrifikation verwendest, werden die transmogrifizierten Gegenstände an dich gebunden und können nicht erstattet oder gehandelt werden.\nMöchtest du fortfahren?\n\n'),
('mod-transmog', 29, 'zhCN', '将此套装用于幻化将使幻化后的物品与您绑定,并使其不可退还和不可交易。\n您是否要继续?\n\n'),
('mod-transmog', 29, 'zhTW', '使用此套裝進行幻化將使幻化後的物品與您綁定,並使其無法退款和無法交易。\n您是否希望繼續?\n\n'),
('mod-transmog', 29, 'esES', 'Usar este conjunto para transfigurar vinculará los objetos transfigurados a ti y los volverá no reembolsables y no intercambiables.\n¿Deseas continuar?\n\n'),
('mod-transmog', 29, 'esMX', 'Usar este conjunto para transfigurar vinculará los objetos transfigurados a ti y los volverá no reembolsables y no intercambiables.\n¿Deseas continuar?\n\n'),
('mod-transmog', 29, 'ruRU', 'Использование этого комплекта для трансмогрификации привяжет трансмогрифицированные предметы к вам и сделает их неподлежащими возврату и обмену.\nЖелаете продолжить?\n\n'),
-- ID 30: DELETESET
('mod-transmog', 30, 'koKR', '세트 삭제'),
('mod-transmog', 30, 'frFR', 'Supprimer l''ensemble'),
('mod-transmog', 30, 'deDE', 'Set löschen'),
('mod-transmog', 30, 'zhCN', '删除套装'),
('mod-transmog', 30, 'zhTW', '刪除套裝'),
('mod-transmog', 30, 'esES', 'Eliminar conjunto'),
('mod-transmog', 30, 'esMX', 'Eliminar conjunto'),
('mod-transmog', 30, 'ruRU', 'Удалить комплект'),
-- ID 31: CONFIRM_DELETESET
('mod-transmog', 31, 'koKR', '을(를) 삭제하시겠습니까 '),
('mod-transmog', 31, 'frFR', 'Êtes-vous sûr de vouloir supprimer '),
('mod-transmog', 31, 'deDE', 'Möchten Sie wirklich löschen '),
('mod-transmog', 31, 'zhCN', '您确定要删除吗 '),
('mod-transmog', 31, 'zhTW', '您確定要刪除 '),
('mod-transmog', 31, 'esES', '¿Estás seguro de que quieres eliminar '),
('mod-transmog', 31, 'esMX', '¿Estás seguro de que quieres eliminar '),
('mod-transmog', 31, 'ruRU', 'Вы уверены, что хотите удалить '),
-- ID 32: INSERTSETNAME
('mod-transmog', 32, 'koKR', '세트 이름 입력'),
('mod-transmog', 32, 'frFR', 'Insérer le nom de l''ensemble'),
('mod-transmog', 32, 'deDE', 'Set-Namen einfügen'),
('mod-transmog', 32, 'zhCN', '插入套装名称'),
('mod-transmog', 32, 'zhTW', '輸入套裝名稱'),
('mod-transmog', 32, 'esES', 'Insertar nombre del conjunto'),
('mod-transmog', 32, 'esMX', 'Insertar nombre del conjunto'),
('mod-transmog', 32, 'ruRU', 'Введите имя комплекта'),
-- ID 33: SEARCH
('mod-transmog', 33, 'koKR', '검색...'),
('mod-transmog', 33, 'frFR', 'Rechercher...'),
('mod-transmog', 33, 'deDE', 'Suche...'),
('mod-transmog', 33, 'zhCN', '搜索...'),
('mod-transmog', 33, 'zhTW', '搜索...'),
('mod-transmog', 33, 'esES', 'Buscar...'),
('mod-transmog', 33, 'esMX', 'Buscar...'),
('mod-transmog', 33, 'ruRU', 'Поиск...'),
-- ID 34: SEARCHING_FOR
('mod-transmog', 34, 'koKR', '검색 중: '),
('mod-transmog', 34, 'frFR', 'Recherche en cours: '),
('mod-transmog', 34, 'deDE', 'Suche nach: '),
('mod-transmog', 34, 'zhCN', '正在搜索: '),
('mod-transmog', 34, 'zhTW', '正在搜尋:'),
('mod-transmog', 34, 'esES', 'Buscando:'),
('mod-transmog', 34, 'esMX', 'Buscando: '),
('mod-transmog', 34, 'ruRU', 'Поиск: '),
-- ID 35: SEARCH_FOR_ITEM
('mod-transmog', 35, 'koKR', '어떤 아이템을 찾으시겠습니까?'),
('mod-transmog', 35, 'frFR', 'Rechercher quel objet ?'),
('mod-transmog', 35, 'deDE', 'Nach welchem Gegenstand suchen?'),
('mod-transmog', 35, 'zhCN', '搜索哪个物品?'),
('mod-transmog', 35, 'zhTW', '搜索哪個物品?'),
('mod-transmog', 35, 'esES', '¿Buscar un objeto?'),
('mod-transmog', 35, 'esMX', '¿Buscar un objeto?'),
('mod-transmog', 35, 'ruRU', 'Поиск предмета:'),
-- ID 36: CONFIRM_HIDE_ITEM
('mod-transmog', 36, 'koKR', '이 슬롯에 아이템을 감추고 있습니다.\n계속하시겠습니까?\n\n'),
('mod-transmog', 36, 'frFR', 'Vous masquez l''objet dans cet emplacement.\nVoulez-vous continuer ?\n\n'),
('mod-transmog', 36, 'deDE', 'Du versteckst das Item in diesem Slot.\nMöchtest du fortfahren?\n\n'),
('mod-transmog', 36, 'zhCN', '您正在隐藏此槽中的物品。\n您是否要继续?\n\n'),
('mod-transmog', 36, 'zhTW', '您正在隱藏此槽中的物品。\n您是否希望繼續?\n\n'),
('mod-transmog', 36, 'esES', 'Estás ocultando el objeto en esta ranura.\n¿Deseas continuar?\n\n'),
('mod-transmog', 36, 'esMX', 'Estás ocultando el objeto en esta ranura.\n¿Deseas continuar?\n\n'),
('mod-transmog', 36, 'ruRU', 'Вы скрываете предмет в этом слоте.\nЖелаете продолжить?\n\n'),
-- ID 37: HIDESLOT
('mod-transmog', 37, 'koKR', '슬롯 숨기기'),
('mod-transmog', 37, 'frFR', 'Cacher l''emplacement'),
('mod-transmog', 37, 'deDE', 'Slot verbergen'),
('mod-transmog', 37, 'zhCN', '隐藏槽位'),
('mod-transmog', 37, 'zhTW', '隱藏槽位'),
('mod-transmog', 37, 'esES', 'Ocultar ranura'),
('mod-transmog', 37, 'esMX', 'Ocultar ranura'),
('mod-transmog', 37, 'ruRU', 'Скрыть слот'),
-- ID 38: REMOVETRANSMOG_SLOT
('mod-transmog', 38, 'koKR', '해당 슬롯의 형상변환을 제거합니까?'),
('mod-transmog', 38, 'frFR', 'Supprimer la transmogrification de l''emplacement ?'),
('mod-transmog', 38, 'deDE', 'Transmogrifikation aus dem Slot entfernen?'),
('mod-transmog', 38, 'zhCN', '是否要从该槽位中移除幻化?'),
('mod-transmog', 38, 'zhTW', '從該槽位移除幻化?'),
('mod-transmog', 38, 'esES', '¿Eliminar la transfiguración del espacio?'),
('mod-transmog', 38, 'esMX', '¿Eliminar la transfiguración del espacio?'),
('mod-transmog', 38, 'ruRU', 'Удалить трансмогрификацию из ячейки?'),
-- ID 39: CONFIRM_USEITEM
('mod-transmog', 39, 'koKR', '이 아이템을 변형에 사용하면 계정에 제한되어 환불 및 거래가 불가능하게 됩니다.\n계속하시겠습니까?\n\n'),
('mod-transmog', 39, 'frFR', 'En utilisant cet objet pour la transmogrification, il sera lié à votre personnage et deviendra non remboursable et non échangeable.\nVoulez-vous continuer ?\n\n'),
('mod-transmog', 39, 'deDE', 'Wenn du diesen Gegenstand für die Transmogrifikation verwendest, wird er an dich gebunden und kann nicht erstattet oder gehandelt werden.\nMöchtest du fortfahren?\n\n'),
('mod-transmog', 39, 'zhCN', '将此物品用于幻化将使其与您绑定,并使其不可退还和不可交易。\n您是否要继续?\n\n'),
('mod-transmog', 39, 'zhTW', '使用此物品進行幻化將使其與您綁定,並使其無法退款和無法交易。\n您是否希望繼續?\n\n'),
('mod-transmog', 39, 'esES', 'Usar este objeto para transfigurar lo vinculará a ti y lo volverá no reembolsable y no intercambiable.\n¿Deseas continuar?\n\n'),
('mod-transmog', 39, 'esMX', 'Usar este objeto para transfigurar lo vinculará a ti y lo volverá no reembolsable y no intercambiable.\n¿Deseas continuar?\n\n'),
('mod-transmog', 39, 'ruRU', 'Использование этого предмета для трансмогрификации привяжет его к вам и сделает его неподлежащим возврату и обмену.\nЖелаете продолжить?\n\n'),
-- ID 40: PREVIOUS_PAGE
('mod-transmog', 40, 'koKR', '이전 페이지'),
('mod-transmog', 40, 'frFR', 'Page précédente'),
('mod-transmog', 40, 'deDE', 'Vorherige Seite'),
('mod-transmog', 40, 'zhCN', '上一页'),
('mod-transmog', 40, 'zhTW', '上一頁'),
('mod-transmog', 40, 'esES', 'Página anterior'),
('mod-transmog', 40, 'esMX', 'Página anterior'),
('mod-transmog', 40, 'ruRU', 'Предыдущая страница'),
-- ID 41: NEXT_PAGE
('mod-transmog', 41, 'koKR', '다음 페이지'),
('mod-transmog', 41, 'frFR', 'Page suivante'),
('mod-transmog', 41, 'deDE', 'Nächste Seite'),
('mod-transmog', 41, 'zhCN', '下一页'),
('mod-transmog', 41, 'zhTW', '下一頁'),
('mod-transmog', 41, 'esES', 'Página siguiente'),
('mod-transmog', 41, 'esMX', 'Página siguiente'),
('mod-transmog', 41, 'ruRU', 'Следующая страница'),
-- ID 42: ADDED_APPEARANCE
('mod-transmog', 42, 'koKR', '이(가) 외형 컬렉션에 추가되었습니다.'),
('mod-transmog', 42, 'frFR', 'a été ajouté(e) à votre collection d''apparences.'),
('mod-transmog', 42, 'deDE', 'wurde deiner Transmog-Sammlung hinzugefügt.'),
('mod-transmog', 42, 'zhCN', '已添加到外观收藏中。'),
('mod-transmog', 42, 'zhTW', '已加入您的外觀收藏。'),
('mod-transmog', 42, 'esES', 'se ha añadido a tu colección de apariencias.'),
('mod-transmog', 42, 'esMX', 'se ha agregado a tu colección de apariencias.'),
('mod-transmog', 42, 'ruRU', 'был добавлен в вашу коллекцию обликов.'),
('mod-transmog', 43, 'koKR', '|cFF4DB3FF변형 중에는 아이템 툴팁에 세트 보너스가 표시되지 않지만, 여전히 완전히 활성화되어 있습니다.\n이 알림을 중지하려면 |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF 을 입력하세요.|r'),
('mod-transmog', 43, 'frFR', '|cFF4DB3FFLes bonus de set n''apparaîtront pas dans l''info-bulle de l''objet lors de la transmogrification, mais ils restent pleinement actifs.\nPour arrêter d''afficher cette notice, tapez |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 43, 'deDE', '|cFF4DB3FFSetboni werden in der Gegenstandsbeschreibung während der Transmogrifizierung nicht angezeigt, sind aber weiterhin voll aktiv.\nUm diesen Hinweis zu deaktivieren, gib |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF ein.|r'),
('mod-transmog', 43, 'zhCN', '|cFF4DB3FF幻化后物品提示中不会显示套装加成,但套装加成仍然完全有效。\n若要停止显示此提示,请输入 |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF。|r'),
('mod-transmog', 43, 'zhTW', '|cFF4DB3FF幻化後物品提示中不會顯示套裝加成,但套裝加成仍然完全有效。\n若要停止顯示此提示,請輸入 |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF。|r'),
('mod-transmog', 43, 'esES', '|cFF4DB3FFLas bonificaciones de conjunto no aparecerán en la descripción del objeto mientras esté transfigurado, pero siguen activas.\nPara dejar de ver este aviso, escribe |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 43, 'esMX', '|cFF4DB3FFLas bonificaciones de conjunto no aparecerán en la descripción del objeto mientras esté transfigurado, pero siguen activas.\nPara dejar de ver este aviso, escribe |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 43, 'ruRU', '|cFF4DB3FFБонусы набора не будут отображаться в подсказке предмета при трансмогрификации, но они по-прежнему полностью активны.\nЧтобы скрыть это уведомление, введите |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 44, 'koKR', '세트 보너스 알림이 활성화되었습니다.'),
('mod-transmog', 44, 'frFR', 'Avertissement de bonus de set activé.'),
('mod-transmog', 44, 'deDE', 'Setbonus-Hinweis aktiviert.'),
('mod-transmog', 44, 'zhCN', '套装加成提示已启用。'),
('mod-transmog', 44, 'zhTW', '套裝加成提示已啟用。'),
('mod-transmog', 44, 'esES', 'Aviso de bonificación de conjunto activado.'),
('mod-transmog', 44, 'esMX', 'Aviso de bonificación de conjunto activado.'),
('mod-transmog', 44, 'ruRU', 'Уведомление о бонусах набора включено.'),
('mod-transmog', 45, 'koKR', '세트 보너스 알림이 비활성화되었습니다.'),
('mod-transmog', 45, 'frFR', 'Avertissement de bonus de set désactivé.'),
('mod-transmog', 45, 'deDE', 'Setbonus-Hinweis deaktiviert.'),
('mod-transmog', 45, 'zhCN', '套装加成提示已禁用。'),
('mod-transmog', 45, 'zhTW', '套裝加成提示已禁用。'),
('mod-transmog', 45, 'esES', 'Aviso de bonificación de conjunto desactivado.'),
('mod-transmog', 45, 'esMX', 'Aviso de bonificación de conjunto desactivado.'),
('mod-transmog', 45, 'ruRU', 'Уведомление о бонусах набора отключено.');
DELETE FROM `command` WHERE `name` IN ('transmog', 'transmog add', 'transmog sync', 'transmog add set', 'transmog portable', 'transmog interface', 'transmog disclaimer');
INSERT INTO `command` (`name`, `security`, `help`) VALUES
('transmog', 0, 'Syntax: .transmog <on/off>\nAllows seeing transmogrified items and the transmogrifier NPC.'),
('transmog add', 1, 'Syntax: .transmog add $player $item\nAdds an item to a player\'s appearance collection.'),
('transmog sync', 0, 'Syntax: .transmog sync\nSyncs transmog addon appearances with the server.'),
('transmog add set', 1, 'Syntax: .transmog add set $player $itemSet\nAdds items of an item set to a player\'s appearance collection.'),
('transmog portable', 1, 'Syntax: .transmog portable \nSummons the Ethereal Warpweaver, a portable version of the transmogrification NPC.'),
('transmog interface', 1, 'Syntax: .transmog interface <on/off>\nEnables transmogrifier "vendor" interface, allowing appearance previews.\nDISCLAIMER: If you have too many appearances, some will not be displayed due to a client limitation. In that case, disable this option.'),
('transmog disclaimer', 0, 'Syntax: .transmog disclaimer <on/off>\nToggles the set bonus disclaimer notice shown when transmogrifying set items.');
@@ -0,0 +1,445 @@
-- Migrate transmog strings from acore_string (global IDs 11100-11121) to module_string.
-- Also migrates hardcoded gossip strings from C++ to module_string.
DELETE FROM `acore_string` WHERE `entry` BETWEEN 11100 AND 11121;
DELETE FROM `module_string` WHERE `module` = 'mod-transmog';
DELETE FROM `module_string_locale` WHERE `module` = 'mod-transmog';
INSERT INTO `module_string` (`module`, `id`, `string`) VALUES
-- Transmog result strings
('mod-transmog', 1, 'Item successfully transmogrified.'),
('mod-transmog', 2, 'Equipment slot is empty.'),
('mod-transmog', 3, 'Invalid source item selected.'),
('mod-transmog', 4, 'Source item does not exist.'),
('mod-transmog', 5, 'Destination item does not exist.'),
('mod-transmog', 6, 'Selected items are invalid.'),
('mod-transmog', 7, 'You don''t have enough money.'),
('mod-transmog', 8, 'You don''t have enough tokens.'),
('mod-transmog', 9, 'All your transmogrifications were removed.'),
('mod-transmog', 10, 'No transmogrification found.'),
('mod-transmog', 11, 'Invalid name inserted.'),
-- Command strings
('mod-transmog', 12, 'Showing transmogrified items, relog to update the current area.'),
('mod-transmog', 13, 'Hiding transmogrified items, relog to update the current area.'),
('mod-transmog', 14, 'The selected item is not suitable for transmogrification.'),
('mod-transmog', 15, 'The selected item cannot be used for transmogrification of the target player.'),
('mod-transmog', 16, 'Performing transmog appearance sync...'),
('mod-transmog', 17, 'Appearance sync complete.'),
('mod-transmog', 18, 'The transmog NPC will now display available appearances as a vendor interface, allowing preview. \nDISCLAIMER: If you have too many appearances, some will not be displayed due to a client limitation. In that case, disable this option.'),
('mod-transmog', 19, 'The transmog NPC will now display available appearances as gossip list.'),
-- Gossip/UI strings
('mod-transmog', 20, 'How does transmogrification work?'),
('mod-transmog', 21, 'Manage sets'),
('mod-transmog', 22, 'Remove all transmogrifications'),
('mod-transmog', 23, 'Remove transmogrifications from all equipped items?'),
('mod-transmog', 24, 'Update menu'),
('mod-transmog', 25, 'How do sets work?'),
('mod-transmog', 26, 'Save set'),
('mod-transmog', 27, 'Back...'),
('mod-transmog', 28, 'Use this set'),
('mod-transmog', 29, 'Using this set for transmogrify will bind transmogrified items to you and make them non-refundable and non-tradeable.\nDo you wish to continue?\n\n'),
('mod-transmog', 30, 'Delete set'),
('mod-transmog', 31, 'Are you sure you want to delete '),
('mod-transmog', 32, 'Insert set name'),
('mod-transmog', 33, 'Search...'),
('mod-transmog', 34, 'Searching for: '),
('mod-transmog', 35, 'Search for what item?'),
('mod-transmog', 36, 'You are hiding the item in this slot.\nDo you wish to continue?\n\n'),
('mod-transmog', 37, 'Hide Slot'),
('mod-transmog', 38, 'Remove transmogrification from the slot?'),
('mod-transmog', 39, 'Using this item for transmogrify will bind it to you and make it non-refundable and non-tradeable.\nDo you wish to continue?\n\n'),
('mod-transmog', 40, 'Previous Page'),
('mod-transmog', 41, 'Next Page'),
('mod-transmog', 42, 'has been added to your appearance collection.'),
-- Disclaimer strings
('mod-transmog', 43, '|cFF4DB3FFSet bonuses won''t appear in the item tooltip while transmogrified, but they are still fully active.\nTo stop seeing this notice, type |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 44, 'Set bonus disclaimer enabled.'),
('mod-transmog', 45, 'Set bonus disclaimer disabled.');
INSERT INTO `module_string_locale` (`module`, `id`, `locale`, `string`) VALUES
-- ID 1: OK
('mod-transmog', 1, 'koKR', '성공적으로 형상변환된 아이템'),
('mod-transmog', 1, 'frFR', 'Objet transmogrifié avec succès'),
('mod-transmog', 1, 'deDE', 'Gegenstand erfolgreich transmogrifiziert'),
('mod-transmog', 1, 'zhCN', '物品变形成功'),
('mod-transmog', 1, 'zhTW', '物品變身成功。'),
('mod-transmog', 1, 'esES', 'Objeto transfigurado con éxito'),
('mod-transmog', 1, 'esMX', 'Objeto transfigurado con éxito'),
('mod-transmog', 1, 'ruRU', 'Предмет успешно трансмогрифицирован'),
-- ID 2: INVALID_SLOT
('mod-transmog', 2, 'koKR', '장비 슬롯이 비어 있습니다.'),
('mod-transmog', 2, 'frFR', 'L''emplacement d''équipement est vide.'),
('mod-transmog', 2, 'deDE', 'Der Ausrüstungsplatz ist leer.'),
('mod-transmog', 2, 'zhCN', '装备槽是空的。'),
('mod-transmog', 2, 'zhTW', '裝備欄位是空的。'),
('mod-transmog', 2, 'esES', 'La ranura de equipo está vacía.'),
('mod-transmog', 2, 'esMX', 'La ranura de equipo está vacía.'),
('mod-transmog', 2, 'ruRU', 'Слот снаряжения пуст.'),
-- ID 3: INVALID_SRC_ENTRY
('mod-transmog', 3, 'koKR', '선택한 소스 요소가 잘못되었습니다.'),
('mod-transmog', 3, 'frFR', 'Élément source sélectionné non valide.'),
('mod-transmog', 3, 'deDE', 'Ungültiges ausgewähltes Quellelement.'),
('mod-transmog', 3, 'zhCN', '所选源元素无效。'),
('mod-transmog', 3, 'zhTW', '選擇的來源物品無效。'),
('mod-transmog', 3, 'esES', 'Elemento fuente seleccionado no válido.'),
('mod-transmog', 3, 'esMX', 'Elemento fuente seleccionado no válido.'),
('mod-transmog', 3, 'ruRU', 'Недопустимый выбранный исходный элемент.'),
-- ID 4: MISSING_SRC_ITEM
('mod-transmog', 4, 'koKR', '소스 요소가 존재하지 않습니다.'),
('mod-transmog', 4, 'frFR', 'L''élément source n''existe pas.'),
('mod-transmog', 4, 'deDE', 'Das Quellelement existiert nicht.'),
('mod-transmog', 4, 'zhCN', '源元素不存在。'),
('mod-transmog', 4, 'zhTW', '來源物品不存在。'),
('mod-transmog', 4, 'esES', 'El elemento de origen no existe.'),
('mod-transmog', 4, 'esMX', 'El elemento de origen no existe.'),
('mod-transmog', 4, 'ruRU', 'Исходный элемент не существует.'),
-- ID 5: MISSING_DEST_ITEM
('mod-transmog', 5, 'koKR', '대상 요소가 존재하지 않습니다.'),
('mod-transmog', 5, 'frFR', 'L''élément cible n''existe pas.'),
('mod-transmog', 5, 'deDE', 'Das Zielelement existiert nicht.'),
('mod-transmog', 5, 'zhCN', '目标元素不存在。'),
('mod-transmog', 5, 'zhTW', '目標物品不存在。'),
('mod-transmog', 5, 'esES', 'El elemento de destino no existe.'),
('mod-transmog', 5, 'esMX', 'El elemento de destino no existe.'),
('mod-transmog', 5, 'ruRU', 'Целевой элемент не существует.'),
-- ID 6: INVALID_ITEMS
('mod-transmog', 6, 'koKR', '선택한 항목이 잘못되었습니다.'),
('mod-transmog', 6, 'frFR', 'Les éléments sélectionnés ne sont pas valides'),
('mod-transmog', 6, 'deDE', 'Die ausgewählten Elemente sind ungültig'),
('mod-transmog', 6, 'zhCN', '所选项目无效'),
('mod-transmog', 6, 'zhTW', '所選物品無效。'),
('mod-transmog', 6, 'esES', 'Los elementos seleccionados no son válidos'),
('mod-transmog', 6, 'esMX', 'Los elementos seleccionados no son válidos'),
('mod-transmog', 6, 'ruRU', 'Выбранные элементы недействительны'),
-- ID 7: NOT_ENOUGH_MONEY
('mod-transmog', 7, 'koKR', '당신은 돈이 충분하지 않습니다.'),
('mod-transmog', 7, 'frFR', 'Vous n''avez pas assez d''argent.'),
('mod-transmog', 7, 'deDE', 'Du hast nicht genug Geld.'),
('mod-transmog', 7, 'zhCN', '你没有足够的钱。'),
('mod-transmog', 7, 'zhTW', '你沒有足夠的金錢。'),
('mod-transmog', 7, 'esES', 'No tienes suficiente dinero.'),
('mod-transmog', 7, 'esMX', 'No tienes suficiente dinero.'),
('mod-transmog', 7, 'ruRU', 'У вас недостаточно денег.'),
-- ID 8: NOT_ENOUGH_TOKENS
('mod-transmog', 8, 'koKR', '칩이 충분하지 않습니다.'),
('mod-transmog', 8, 'frFR', 'Vous n''avez pas assez de jetons.'),
('mod-transmog', 8, 'deDE', 'Du hast nicht genug Chips.'),
('mod-transmog', 8, 'zhCN', '你的筹码不够。'),
('mod-transmog', 8, 'zhTW', '你沒有足夠的代幣。'),
('mod-transmog', 8, 'esES', 'No tienes suficientes fichas.'),
('mod-transmog', 8, 'esMX', 'No tienes suficientes fichas.'),
('mod-transmog', 8, 'ruRU', 'У вас недостаточно фишек.'),
-- ID 9: UNTRANSMOG_OK
('mod-transmog', 9, 'koKR', '모든 형상변환을 제거했습니다.'),
('mod-transmog', 9, 'frFR', 'Suppression de toutes vos transmogrifications'),
('mod-transmog', 9, 'deDE', 'Alle deine Transmogrifikationen wurden entfernt'),
('mod-transmog', 9, 'zhCN', '删除了你所有的变形'),
('mod-transmog', 9, 'zhTW', '你的所有變身效果都被移除了。'),
('mod-transmog', 9, 'esES', 'Se eliminaron todas tus transfiguraciones'),
('mod-transmog', 9, 'esMX', 'Se eliminaron todas tus transfiguraciones'),
('mod-transmog', 9, 'ruRU', 'Удалены все ваши трансмогрификации'),
-- ID 10: UNTRANSMOG_NO_TRANSMOGS
('mod-transmog', 10, 'koKR', '변형이 없습니다.'),
('mod-transmog', 10, 'frFR', 'Aucune transfiguration trouvée.'),
('mod-transmog', 10, 'deDE', 'Keine Verwandlung gefunden.'),
('mod-transmog', 10, 'zhCN', '没有发现变形。'),
('mod-transmog', 10, 'zhTW', '找不到變身效果。'),
('mod-transmog', 10, 'esES', 'No se encontró transfiguración.'),
('mod-transmog', 10, 'esMX', 'No se encontró transfiguración.'),
('mod-transmog', 10, 'ruRU', 'Трансфигурация не найдена.'),
-- ID 11: PRESET_ERR_INVALID_NAME
('mod-transmog', 11, 'koKR', '삽입된 이름이 잘못되었습니다.'),
('mod-transmog', 11, 'frFR', 'Nom inséré non valide.'),
('mod-transmog', 11, 'deDE', 'Ungültiger eingegebener Name.'),
('mod-transmog', 11, 'zhCN', '插入的名称无效。'),
('mod-transmog', 11, 'zhTW', '插入的名稱無效。'),
('mod-transmog', 11, 'esES', 'Nombre insertado no válido.'),
('mod-transmog', 11, 'esMX', 'Nombre insertado no válido.'),
('mod-transmog', 11, 'ruRU', 'Недопустимое вставленное имя.'),
-- ID 12: CMD_SHOW
('mod-transmog', 12, 'koKR', '형상변환된 아이템이 표시되며, 현재 영역을 새로고침하려면 다시 로그인하세요.'),
('mod-transmog', 12, 'frFR', 'En affichant les objets transmogrifiés, reconnectez-vous pour actualiser la zone actuelle.'),
('mod-transmog', 12, 'deDE', 'Transmogrifizierte Gegenstände werden angezeigt. Melden Sie sich erneut an, um den aktuellen Bereich zu aktualisieren.'),
('mod-transmog', 12, 'zhCN', '显示已变形物品,重新登录即可刷新当前区域。'),
('mod-transmog', 12, 'zhTW', '顯示已變身的物品,重新登錄以更新當前區域。'),
('mod-transmog', 12, 'esES', 'Mostrando elementos transfigurados, vuelva a iniciar sesión para actualizar el área actual.'),
('mod-transmog', 12, 'esMX', 'Mostrando elementos transfigurados, vuelva a iniciar sesión para actualizar el área actual.'),
('mod-transmog', 12, 'ruRU', 'Отображение трансмогрифицированных предметов. Войдите в систему еще раз, чтобы обновить текущую область.'),
-- ID 13: CMD_HIDE
('mod-transmog', 13, 'koKR', '형상변환된 아이템을 숨기고 다시 로그인하여 현재 영역을 새로 고칩니다.'),
('mod-transmog', 13, 'frFR', 'Masquez les objets transmogrifiés, reconnectez-vous pour actualiser la zone actuelle.'),
('mod-transmog', 13, 'deDE', 'Transmogrifizierte Gegenstände ausblenden, erneut anmelden, um den aktuellen Bereich zu aktualisieren.'),
('mod-transmog', 13, 'zhCN', '隐藏变形物品,重新登录以刷新当前区域。'),
('mod-transmog', 13, 'zhTW', '隱藏已變身的物品,重新登錄以更新當前區域。'),
('mod-transmog', 13, 'esES', 'Ocultar elementos transfigurados, volver a iniciar sesión para actualizar el área actual.'),
('mod-transmog', 13, 'esMX', 'Ocultar elementos transfigurados, volver a iniciar sesión para actualizar el área actual.'),
('mod-transmog', 13, 'ruRU', 'Скройте трансмогрифицированные предметы, войдите в систему, чтобы обновить текущую область.'),
-- ID 14: CMD_ADD_UNSUITABLE
('mod-transmog', 14, 'koKR', '선택한 항목은 변형에 적합하지 않습니다.'),
('mod-transmog', 14, 'frFR', 'L''objet sélectionné ne convient pas à la métamorphose.'),
('mod-transmog', 14, 'deDE', 'Der ausgewählte Gegenstand ist nicht für die Verwandlung geeignet.'),
('mod-transmog', 14, 'zhCN', '所选项目不适合变形。'),
('mod-transmog', 14, 'zhTW', '所選物品不適合進行變身。'),
('mod-transmog', 14, 'esES', 'El elemento seleccionado no es adecuado para la transfiguración.'),
('mod-transmog', 14, 'esMX', 'El elemento seleccionado no es adecuado para la transfiguración.'),
('mod-transmog', 14, 'ruRU', 'Выбранный предмет не подходит для преображения.'),
-- ID 15: CMD_ADD_FORBIDDEN
('mod-transmog', 15, 'koKR', '선택한 아이템은 대상 플레이어의 형상변환에 사용할 수 없습니다.'),
('mod-transmog', 15, 'frFR', 'L''objet sélectionné ne peut pas être utilisé pour la transmogrification du joueur ciblé'),
('mod-transmog', 15, 'deDE', 'Der ausgewählte Gegenstand kann nicht für die Transmogrifizierung des Zielspielers verwendet werden'),
('mod-transmog', 15, 'zhCN', '所选物品不能用于目标玩家的变身'),
('mod-transmog', 15, 'zhTW', '所選物品不能用於目標玩家的變身效果。'),
('mod-transmog', 15, 'esES', 'El elemento seleccionado no se puede usar para la transfiguración del jugador objetivo'),
('mod-transmog', 15, 'esMX', 'El elemento seleccionado no se puede usar para la transfiguración del jugador objetivo'),
('mod-transmog', 15, 'ruRU', 'Выбранный предмет нельзя использовать для трансмогрификации целевого игрока.'),
-- ID 16: CMD_BEGIN_SYNC
('mod-transmog', 16, 'koKR', '형상변환 모양 동기화 수행 중...'),
('mod-transmog', 16, 'frFR', 'Exécution de la synchronisation de l''apparence de transmogrification...'),
('mod-transmog', 16, 'deDE', 'Synchronisierung des Transmog-Erscheinungsbilds wird durchgeführt ...'),
('mod-transmog', 16, 'zhCN', '正在执行 Transmog 外观同步...'),
('mod-transmog', 16, 'zhTW', '正在執行外觀同步變身操作...'),
('mod-transmog', 16, 'esES', 'Realizando sincronización de apariencia de transfiguración...'),
('mod-transmog', 16, 'esMX', 'Realizando sincronización de apariencia de transfiguración...'),
('mod-transmog', 16, 'ruRU', 'Выполнение синхронизации внешнего вида Transmog...'),
-- ID 17: CMD_COMPLETE_SYNC
('mod-transmog', 17, 'koKR', '전체 모양 동기화'),
('mod-transmog', 17, 'frFR', 'Synchronisation complète de l''apparence'),
('mod-transmog', 17, 'deDE', 'Vollständige Synchronisierung des Erscheinungsbilds'),
('mod-transmog', 17, 'zhCN', '完全外观同步'),
('mod-transmog', 17, 'zhTW', '外觀同步變身操作完成。'),
('mod-transmog', 17, 'esES', 'Sincronización de apariencia completa'),
('mod-transmog', 17, 'esMX', 'Sincronización de apariencia completa'),
('mod-transmog', 17, 'ruRU', 'Полная синхронизация внешнего вида'),
-- ID 20: HOWWORKS
('mod-transmog', 20, 'koKR', '형상변환은 어떻게 작동합니까?'),
('mod-transmog', 20, 'frFR', 'Comment fonctionne la transmogrification ?'),
('mod-transmog', 20, 'deDE', 'Wie funktioniert Transmogrifizierung?'),
('mod-transmog', 20, 'zhCN', '变形术是如何运作的?'),
('mod-transmog', 20, 'zhTW', '幻化是如何運作的?'),
('mod-transmog', 20, 'esES', '¿Cómo funciona la transfiguración?'),
('mod-transmog', 20, 'esMX', '¿Cómo funciona la transfiguración?'),
('mod-transmog', 20, 'ruRU', 'Как работает трансмогрификация?'),
-- ID 21: MANAGESETS
('mod-transmog', 21, 'koKR', '세트 관리'),
('mod-transmog', 21, 'frFR', 'Gérer les ensembles'),
('mod-transmog', 21, 'deDE', 'Sets verwalten'),
('mod-transmog', 21, 'zhCN', '管理套装'),
('mod-transmog', 21, 'zhTW', '管理套裝'),
('mod-transmog', 21, 'esES', 'Administrar conjuntos'),
('mod-transmog', 21, 'esMX', 'Administrar conjuntos'),
('mod-transmog', 21, 'ruRU', 'Управление комплектами'),
-- ID 22: REMOVETRANSMOG
('mod-transmog', 22, 'koKR', '모든 변형 제거'),
('mod-transmog', 22, 'frFR', 'Supprimer toutes les transmogrifications'),
('mod-transmog', 22, 'deDE', 'Alle Transmogrifikationen entfernen'),
('mod-transmog', 22, 'zhCN', '移除所有幻化'),
('mod-transmog', 22, 'zhTW', '移除所有幻化'),
('mod-transmog', 22, 'esES', 'Eliminar todas las transfiguraciones'),
('mod-transmog', 22, 'esMX', 'Eliminar todas las transfiguraciones'),
('mod-transmog', 22, 'ruRU', 'Удалить все трансмогрификации'),
-- ID 23: REMOVETRANSMOG_ASK
('mod-transmog', 23, 'koKR', '장착한 모든 아이템의 변형을 제거합니까?'),
('mod-transmog', 23, 'frFR', 'Supprimer les transmogrifications de tous les objets équipés ?'),
('mod-transmog', 23, 'deDE', 'Transmogrifikationen von allen ausgerüsteten Gegenständen entfernen?'),
('mod-transmog', 23, 'zhCN', '是否要从所有已装备的物品中移除幻化?'),
('mod-transmog', 23, 'zhTW', '從所有已裝備物品中移除幻化?'),
('mod-transmog', 23, 'esES', '¿Eliminar las transfiguraciones de todos los objetos equipados?'),
('mod-transmog', 23, 'esMX', '¿Eliminar las transfiguraciones de todos los objetos equipados?'),
('mod-transmog', 23, 'ruRU', 'Удалить трансмогрификации со всех экипированных предметов?'),
-- ID 24: UPDATEMENU
('mod-transmog', 24, 'koKR', '메뉴 업데이트'),
('mod-transmog', 24, 'frFR', 'Mettre à jour le menu'),
('mod-transmog', 24, 'deDE', 'Menü aktualisieren'),
('mod-transmog', 24, 'zhCN', '更新菜单'),
('mod-transmog', 24, 'zhTW', '更新選單'),
('mod-transmog', 24, 'esES', 'Actualizar menú'),
('mod-transmog', 24, 'esMX', 'Actualizar menú'),
('mod-transmog', 24, 'ruRU', 'Обновить меню'),
-- ID 25: HOWSETSWORK
('mod-transmog', 25, 'koKR', '세트는 어떻게 작동합니까?'),
('mod-transmog', 25, 'frFR', 'Comment fonctionnent les ensembles ?'),
('mod-transmog', 25, 'deDE', 'Wie funktionieren Sets?'),
('mod-transmog', 25, 'zhCN', '套装是如何运作的?'),
('mod-transmog', 25, 'zhTW', '套裝如何運作?'),
('mod-transmog', 25, 'esES', '¿Cómo funcionan los conjuntos?'),
('mod-transmog', 25, 'esMX', '¿Cómo funcionan los conjuntos?'),
('mod-transmog', 25, 'ruRU', 'Как работают комплекты?'),
-- ID 26: SAVESET
('mod-transmog', 26, 'koKR', '세트 저장'),
('mod-transmog', 26, 'frFR', 'Sauvegarder l''ensemble'),
('mod-transmog', 26, 'deDE', 'Set speichern'),
('mod-transmog', 26, 'zhCN', '保存套装'),
('mod-transmog', 26, 'zhTW', '儲存套裝'),
('mod-transmog', 26, 'esES', 'Guardar conjunto'),
('mod-transmog', 26, 'esMX', 'Guardar conjunto'),
('mod-transmog', 26, 'ruRU', 'Сохранить комплект'),
-- ID 27: BACK
('mod-transmog', 27, 'koKR', '뒤로...'),
('mod-transmog', 27, 'frFR', 'Retour...'),
('mod-transmog', 27, 'deDE', 'Zurück...'),
('mod-transmog', 27, 'zhCN', '返回...'),
('mod-transmog', 27, 'zhTW', '返回...'),
('mod-transmog', 27, 'esES', 'Atrás...'),
('mod-transmog', 27, 'esMX', 'Atrás...'),
('mod-transmog', 27, 'ruRU', 'Назад...'),
-- ID 28: USESET
('mod-transmog', 28, 'koKR', '이 세트를 사용'),
('mod-transmog', 28, 'frFR', 'Utiliser cet ensemble'),
('mod-transmog', 28, 'deDE', 'Dieses Set verwenden'),
('mod-transmog', 28, 'zhCN', '使用此套装'),
('mod-transmog', 28, 'zhTW', '使用此套裝'),
('mod-transmog', 28, 'esES', 'Usar este conjunto'),
('mod-transmog', 28, 'esMX', 'Usar este conjunto'),
('mod-transmog', 28, 'ruRU', 'Использовать этот комплект'),
-- ID 29: CONFIRM_USESET
('mod-transmog', 29, 'koKR', '이 세트를 변형에 사용하면 변형된 아이템이 계정에 제한되어 환불 및 거래가 불가능합니다.\n계속하시겠습니까?\n\n'),
('mod-transmog', 29, 'frFR', 'En utilisant cet ensemble pour la transmogrification, les objets transmogrifiés seront liés à votre personnage et deviendront non remboursables et non échangeables.\nVoulez-vous continuer ?\n\n'),
('mod-transmog', 29, 'deDE', 'Wenn du dieses Set für die Transmogrifikation verwendest, werden die transmogrifizierten Gegenstände an dich gebunden und können nicht erstattet oder gehandelt werden.\nMöchtest du fortfahren?\n\n'),
('mod-transmog', 29, 'zhCN', '将此套装用于幻化将使幻化后的物品与您绑定,并使其不可退还和不可交易。\n您是否要继续?\n\n'),
('mod-transmog', 29, 'zhTW', '使用此套裝進行幻化將使幻化後的物品與您綁定,並使其無法退款和無法交易。\n您是否希望繼續?\n\n'),
('mod-transmog', 29, 'esES', 'Usar este conjunto para transfigurar vinculará los objetos transfigurados a ti y los volverá no reembolsables y no intercambiables.\n¿Deseas continuar?\n\n'),
('mod-transmog', 29, 'esMX', 'Usar este conjunto para transfigurar vinculará los objetos transfigurados a ti y los volverá no reembolsables y no intercambiables.\n¿Deseas continuar?\n\n'),
('mod-transmog', 29, 'ruRU', 'Использование этого комплекта для трансмогрификации привяжет трансмогрифицированные предметы к вам и сделает их неподлежащими возврату и обмену.\nЖелаете продолжить?\n\n'),
-- ID 30: DELETESET
('mod-transmog', 30, 'koKR', '세트 삭제'),
('mod-transmog', 30, 'frFR', 'Supprimer l''ensemble'),
('mod-transmog', 30, 'deDE', 'Set löschen'),
('mod-transmog', 30, 'zhCN', '删除套装'),
('mod-transmog', 30, 'zhTW', '刪除套裝'),
('mod-transmog', 30, 'esES', 'Eliminar conjunto'),
('mod-transmog', 30, 'esMX', 'Eliminar conjunto'),
('mod-transmog', 30, 'ruRU', 'Удалить комплект'),
-- ID 31: CONFIRM_DELETESET
('mod-transmog', 31, 'koKR', '을(를) 삭제하시겠습니까 '),
('mod-transmog', 31, 'frFR', 'Êtes-vous sûr de vouloir supprimer '),
('mod-transmog', 31, 'deDE', 'Möchten Sie wirklich löschen '),
('mod-transmog', 31, 'zhCN', '您确定要删除吗 '),
('mod-transmog', 31, 'zhTW', '您確定要刪除 '),
('mod-transmog', 31, 'esES', '¿Estás seguro de que quieres eliminar '),
('mod-transmog', 31, 'esMX', '¿Estás seguro de que quieres eliminar '),
('mod-transmog', 31, 'ruRU', 'Вы уверены, что хотите удалить '),
-- ID 32: INSERTSETNAME
('mod-transmog', 32, 'koKR', '세트 이름 입력'),
('mod-transmog', 32, 'frFR', 'Insérer le nom de l''ensemble'),
('mod-transmog', 32, 'deDE', 'Set-Namen einfügen'),
('mod-transmog', 32, 'zhCN', '插入套装名称'),
('mod-transmog', 32, 'zhTW', '輸入套裝名稱'),
('mod-transmog', 32, 'esES', 'Insertar nombre del conjunto'),
('mod-transmog', 32, 'esMX', 'Insertar nombre del conjunto'),
('mod-transmog', 32, 'ruRU', 'Введите имя комплекта'),
-- ID 33: SEARCH
('mod-transmog', 33, 'koKR', '검색...'),
('mod-transmog', 33, 'frFR', 'Rechercher...'),
('mod-transmog', 33, 'deDE', 'Suche...'),
('mod-transmog', 33, 'zhCN', '搜索...'),
('mod-transmog', 33, 'zhTW', '搜索...'),
('mod-transmog', 33, 'esES', 'Buscar...'),
('mod-transmog', 33, 'esMX', 'Buscar...'),
('mod-transmog', 33, 'ruRU', 'Поиск...'),
-- ID 34: SEARCHING_FOR
('mod-transmog', 34, 'koKR', '검색 중: '),
('mod-transmog', 34, 'frFR', 'Recherche en cours: '),
('mod-transmog', 34, 'deDE', 'Suche nach: '),
('mod-transmog', 34, 'zhCN', '正在搜索: '),
('mod-transmog', 34, 'zhTW', '正在搜尋:'),
('mod-transmog', 34, 'esES', 'Buscando:'),
('mod-transmog', 34, 'esMX', 'Buscando: '),
('mod-transmog', 34, 'ruRU', 'Поиск: '),
-- ID 35: SEARCH_FOR_ITEM
('mod-transmog', 35, 'koKR', '어떤 아이템을 찾으시겠습니까?'),
('mod-transmog', 35, 'frFR', 'Rechercher quel objet ?'),
('mod-transmog', 35, 'deDE', 'Nach welchem Gegenstand suchen?'),
('mod-transmog', 35, 'zhCN', '搜索哪个物品?'),
('mod-transmog', 35, 'zhTW', '搜索哪個物品?'),
('mod-transmog', 35, 'esES', '¿Buscar un objeto?'),
('mod-transmog', 35, 'esMX', '¿Buscar un objeto?'),
('mod-transmog', 35, 'ruRU', 'Поиск предмета:'),
-- ID 36: CONFIRM_HIDE_ITEM
('mod-transmog', 36, 'koKR', '이 슬롯에 아이템을 감추고 있습니다.\n계속하시겠습니까?\n\n'),
('mod-transmog', 36, 'frFR', 'Vous masquez l''objet dans cet emplacement.\nVoulez-vous continuer ?\n\n'),
('mod-transmog', 36, 'deDE', 'Du versteckst das Item in diesem Slot.\nMöchtest du fortfahren?\n\n'),
('mod-transmog', 36, 'zhCN', '您正在隐藏此槽中的物品。\n您是否要继续?\n\n'),
('mod-transmog', 36, 'zhTW', '您正在隱藏此槽中的物品。\n您是否希望繼續?\n\n'),
('mod-transmog', 36, 'esES', 'Estás ocultando el objeto en esta ranura.\n¿Deseas continuar?\n\n'),
('mod-transmog', 36, 'esMX', 'Estás ocultando el objeto en esta ranura.\n¿Deseas continuar?\n\n'),
('mod-transmog', 36, 'ruRU', 'Вы скрываете предмет в этом слоте.\nЖелаете продолжить?\n\n'),
-- ID 37: HIDESLOT
('mod-transmog', 37, 'koKR', '슬롯 숨기기'),
('mod-transmog', 37, 'frFR', 'Cacher l''emplacement'),
('mod-transmog', 37, 'deDE', 'Slot verbergen'),
('mod-transmog', 37, 'zhCN', '隐藏槽位'),
('mod-transmog', 37, 'zhTW', '隱藏槽位'),
('mod-transmog', 37, 'esES', 'Ocultar ranura'),
('mod-transmog', 37, 'esMX', 'Ocultar ranura'),
('mod-transmog', 37, 'ruRU', 'Скрыть слот'),
-- ID 38: REMOVETRANSMOG_SLOT
('mod-transmog', 38, 'koKR', '해당 슬롯의 형상변환을 제거합니까?'),
('mod-transmog', 38, 'frFR', 'Supprimer la transmogrification de l''emplacement ?'),
('mod-transmog', 38, 'deDE', 'Transmogrifikation aus dem Slot entfernen?'),
('mod-transmog', 38, 'zhCN', '是否要从该槽位中移除幻化?'),
('mod-transmog', 38, 'zhTW', '從該槽位移除幻化?'),
('mod-transmog', 38, 'esES', '¿Eliminar la transfiguración del espacio?'),
('mod-transmog', 38, 'esMX', '¿Eliminar la transfiguración del espacio?'),
('mod-transmog', 38, 'ruRU', 'Удалить трансмогрификацию из ячейки?'),
-- ID 39: CONFIRM_USEITEM
('mod-transmog', 39, 'koKR', '이 아이템을 변형에 사용하면 계정에 제한되어 환불 및 거래가 불가능하게 됩니다.\n계속하시겠습니까?\n\n'),
('mod-transmog', 39, 'frFR', 'En utilisant cet objet pour la transmogrification, il sera lié à votre personnage et deviendra non remboursable et non échangeable.\nVoulez-vous continuer ?\n\n'),
('mod-transmog', 39, 'deDE', 'Wenn du diesen Gegenstand für die Transmogrifikation verwendest, wird er an dich gebunden und kann nicht erstattet oder gehandelt werden.\nMöchtest du fortfahren?\n\n'),
('mod-transmog', 39, 'zhCN', '将此物品用于幻化将使其与您绑定,并使其不可退还和不可交易。\n您是否要继续?\n\n'),
('mod-transmog', 39, 'zhTW', '使用此物品進行幻化將使其與您綁定,並使其無法退款和無法交易。\n您是否希望繼續?\n\n'),
('mod-transmog', 39, 'esES', 'Usar este objeto para transfigurar lo vinculará a ti y lo volverá no reembolsable y no intercambiable.\n¿Deseas continuar?\n\n'),
('mod-transmog', 39, 'esMX', 'Usar este objeto para transfigurar lo vinculará a ti y lo volverá no reembolsable y no intercambiable.\n¿Deseas continuar?\n\n'),
('mod-transmog', 39, 'ruRU', 'Использование этого предмета для трансмогрификации привяжет его к вам и сделает его неподлежащим возврату и обмену.\nЖелаете продолжить?\n\n'),
-- ID 40: PREVIOUS_PAGE
('mod-transmog', 40, 'koKR', '이전 페이지'),
('mod-transmog', 40, 'frFR', 'Page précédente'),
('mod-transmog', 40, 'deDE', 'Vorherige Seite'),
('mod-transmog', 40, 'zhCN', '上一页'),
('mod-transmog', 40, 'zhTW', '上一頁'),
('mod-transmog', 40, 'esES', 'Página anterior'),
('mod-transmog', 40, 'esMX', 'Página anterior'),
('mod-transmog', 40, 'ruRU', 'Предыдущая страница'),
-- ID 41: NEXT_PAGE
('mod-transmog', 41, 'koKR', '다음 페이지'),
('mod-transmog', 41, 'frFR', 'Page suivante'),
('mod-transmog', 41, 'deDE', 'Nächste Seite'),
('mod-transmog', 41, 'zhCN', '下一页'),
('mod-transmog', 41, 'zhTW', '下一頁'),
('mod-transmog', 41, 'esES', 'Página siguiente'),
('mod-transmog', 41, 'esMX', 'Página siguiente'),
('mod-transmog', 41, 'ruRU', 'Следующая страница'),
-- ID 42: ADDED_APPEARANCE
('mod-transmog', 42, 'koKR', '이(가) 외형 컬렉션에 추가되었습니다.'),
('mod-transmog', 42, 'frFR', 'a été ajouté(e) à votre collection d''apparences.'),
('mod-transmog', 42, 'deDE', 'wurde deiner Transmog-Sammlung hinzugefügt.'),
('mod-transmog', 42, 'zhCN', '已添加到外观收藏中。'),
('mod-transmog', 42, 'zhTW', '已加入您的外觀收藏。'),
('mod-transmog', 42, 'esES', 'se ha añadido a tu colección de apariencias.'),
('mod-transmog', 42, 'esMX', 'se ha agregado a tu colección de apariencias.'),
('mod-transmog', 42, 'ruRU', 'был добавлен в вашу коллекцию обликов.'),
('mod-transmog', 43, 'koKR', '|cFF4DB3FF변형 중에는 아이템 툴팁에 세트 보너스가 표시되지 않지만, 여전히 완전히 활성화되어 있습니다.\n이 알림을 중지하려면 |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF 을 입력하세요.|r'),
('mod-transmog', 43, 'frFR', '|cFF4DB3FFLes bonus de set n''apparaîtront pas dans l''info-bulle de l''objet lors de la transmogrification, mais ils restent pleinement actifs.\nPour arrêter d''afficher cette notice, tapez |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 43, 'deDE', '|cFF4DB3FFSetboni werden in der Gegenstandsbeschreibung während der Transmogrifizierung nicht angezeigt, sind aber weiterhin voll aktiv.\nUm diesen Hinweis zu deaktivieren, gib |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF ein.|r'),
('mod-transmog', 43, 'zhCN', '|cFF4DB3FF幻化后物品提示中不会显示套装加成,但套装加成仍然完全有效。\n若要停止显示此提示,请输入 |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF。|r'),
('mod-transmog', 43, 'zhTW', '|cFF4DB3FF幻化後物品提示中不會顯示套裝加成,但套裝加成仍然完全有效。\n若要停止顯示此提示,請輸入 |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF。|r'),
('mod-transmog', 43, 'esES', '|cFF4DB3FFLas bonificaciones de conjunto no aparecerán en la descripción del objeto mientras esté transfigurado, pero siguen activas.\nPara dejar de ver este aviso, escribe |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 43, 'esMX', '|cFF4DB3FFLas bonificaciones de conjunto no aparecerán en la descripción del objeto mientras esté transfigurado, pero siguen activas.\nPara dejar de ver este aviso, escribe |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 43, 'ruRU', '|cFF4DB3FFБонусы набора не будут отображаться в подсказке предмета при трансмогрификации, но они по-прежнему полностью активны.\nЧтобы скрыть это уведомление, введите |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
('mod-transmog', 44, 'koKR', '세트 보너스 알림이 활성화되었습니다.'),
('mod-transmog', 44, 'frFR', 'Avertissement de bonus de set activé.'),
('mod-transmog', 44, 'deDE', 'Setbonus-Hinweis aktiviert.'),
('mod-transmog', 44, 'zhCN', '套装加成提示已启用。'),
('mod-transmog', 44, 'zhTW', '套裝加成提示已啟用。'),
('mod-transmog', 44, 'esES', 'Aviso de bonificación de conjunto activado.'),
('mod-transmog', 44, 'esMX', 'Aviso de bonificación de conjunto activado.'),
('mod-transmog', 44, 'ruRU', 'Уведомление о бонусах набора включено.'),
('mod-transmog', 45, 'koKR', '세트 보너스 알림이 비활성화되었습니다.'),
('mod-transmog', 45, 'frFR', 'Avertissement de bonus de set désactivé.'),
('mod-transmog', 45, 'deDE', 'Setbonus-Hinweis deaktiviert.'),
('mod-transmog', 45, 'zhCN', '套装加成提示已禁用。'),
('mod-transmog', 45, 'zhTW', '套裝加成提示已禁用。'),
('mod-transmog', 45, 'esES', 'Aviso de bonificación de conjunto desactivado.'),
('mod-transmog', 45, 'esMX', 'Aviso de bonificación de conjunto desactivado.'),
('mod-transmog', 45, 'ruRU', 'Уведомление о бонусах набора отключено.');
@@ -0,0 +1 @@
@@ -0,0 +1 @@
@@ -0,0 +1,35 @@
-- Set bonus disclaimer strings and command (entries 11119-11121)
DELETE FROM `acore_string` WHERE `entry` BETWEEN 11119 AND 11121;
INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`)
VALUES
(11119, '|cFF4DB3FFSet bonuses won''t appear in the item tooltip while transmogrified, but they are still fully active.\nTo stop seeing this notice, type |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r',
'|cFF4DB3FF변형 중에는 아이템 툴팁에 세트 보너스가 표시되지 않지만, 여전히 완전히 활성화되어 있습니다.\n이 알림을 중지하려면 |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF 을 입력하세요.|r',
'|cFF4DB3FFLes bonus de set n''apparaîtront pas dans l''info-bulle de l''objet lors de la transmogrification, mais ils restent pleinement actifs.\nPour arrêter d''afficher cette notice, tapez |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r',
'|cFF4DB3FFSetboni werden in der Gegenstandsbeschreibung während der Transmogrifizierung nicht angezeigt, sind aber weiterhin voll aktiv.\nUm diesen Hinweis zu deaktivieren, gib |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF ein.|r',
'|cFF4DB3FF幻化后物品提示中不会显示套装加成,但套装加成仍然完全有效。\n若要停止显示此提示,请输入 |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF。|r',
'|cFF4DB3FF幻化後物品提示中不會顯示套裝加成,但套裝加成仍然完全有效。\n若要停止顯示此提示,請輸入 |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF。|r',
'|cFF4DB3FFLas bonificaciones de conjunto no aparecerán en la descripción del objeto mientras esté transfigurado, pero siguen activas.\nPara dejar de ver este aviso, escribe |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r',
'|cFF4DB3FFLas bonificaciones de conjunto no aparecerán en la descripción del objeto mientras esté transfigurado, pero siguen activas.\nPara dejar de ver este aviso, escribe |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r',
'|cFF4DB3FFБонусы набора не будут отображаться в подсказке предмета при трансмогрификации, но они по-прежнему полностью активны.\nЧтобы скрыть это уведомление, введите |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'),
(11120, 'Set bonus disclaimer enabled.',
'세트 보너스 알림이 활성화되었습니다.',
'Avertissement de bonus de set activé.',
'Setbonus-Hinweis aktiviert.',
'套装加成提示已启用。',
'套裝加成提示已啟用。',
'Aviso de bonificación de conjunto activado.',
'Aviso de bonificación de conjunto activado.',
'Уведомление о бонусах набора включено.'),
(11121, 'Set bonus disclaimer disabled.',
'세트 보너스 알림이 비활성화되었습니다.',
'Avertissement de bonus de set désactivé.',
'Setbonus-Hinweis deaktiviert.',
'套装加成提示已禁用。',
'套裝加成提示已禁用。',
'Aviso de bonificación de conjunto desactivado.',
'Aviso de bonificación de conjunto desactivado.',
'Уведомление о бонусах набора отключено.');
DELETE FROM `command` WHERE `name` = 'transmog disclaimer';
INSERT INTO `command` (`name`, `security`, `help`) VALUES
('transmog disclaimer', 0, 'Syntax: .transmog disclaimer <on/off>\nToggles the set bonus disclaimer notice shown when transmogrifying set items.');
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
TRANSM_PATH_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/" && pwd )"
source $TRANSM_PATH_ROOT"/conf/conf.sh.dist"
if [ -f $TRANSM_PATH_ROOT"/conf/conf.sh" ]; then
source $TRANSM_PATH_ROOT"/conf/conf.sh"
fi
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
## Set a local git commit template
git config --local commit.template ".git_commit_template.txt" ;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,318 @@
#ifndef DEF_TRANSMOGRIFICATION_H
#define DEF_TRANSMOGRIFICATION_H
#include "Player.h"
#include "Config.h"
#include "ScriptMgr.h"
#include "ScriptedGossip.h"
#include "GameEventMgr.h"
#include "Item.h"
#include "ScriptMgr.h"
#include "Chat.h"
#include "ItemTemplate.h"
#include "QuestDef.h"
#include "ItemTemplate.h"
#include <unordered_map>
#include <unordered_set>
#include <vector>
#define PRESETS // comment this line to disable preset feature totally
#define HIDDEN_ITEM_ID 1 // used for hidden transmog - do not use a valid equipment ID
#define MAX_OPTIONS 25 // do not alter
#define MAX_SEARCH_STRING_LENGTH 50
class Item;
class Player;
class WorldSession;
struct ItemTemplate;
enum TransmogSettings
{
SETTING_HIDE_TRANSMOG = 0,
SETTING_RETROACTIVE_CHECK = 1,
SETTING_VENDOR_INTERFACE = 2,
SETTING_HIDE_SET_DISCLAIMER = 3,
// Subscriptions
SETTING_TRANSMOG_MEMBERSHIP_LEVEL = 0
};
enum MixedWeaponSettings
{
MIXED_WEAPONS_STRICT = 0,
MIXED_WEAPONS_MODERN = 1,
MIXED_WEAPONS_LOOSE = 2
};
enum TransmogStrings : uint32
{
// Transmog result strings
LANG_TRANSMOG_OK = 1,
LANG_TRANSMOG_INVALID_SLOT = 2,
LANG_TRANSMOG_INVALID_SRC_ENTRY = 3,
LANG_TRANSMOG_MISSING_SRC_ITEM = 4,
LANG_TRANSMOG_MISSING_DEST_ITEM = 5,
LANG_TRANSMOG_INVALID_ITEMS = 6,
LANG_TRANSMOG_NOT_ENOUGH_MONEY = 7,
LANG_TRANSMOG_NOT_ENOUGH_TOKENS = 8,
LANG_TRANSMOG_UNTRANSMOG_OK = 9,
LANG_TRANSMOG_UNTRANSMOG_NO_TRANSMOGS = 10,
LANG_TRANSMOG_PRESET_ERR_INVALID_NAME = 11,
// Command strings
LANG_TRANSMOG_CMD_SHOW = 12,
LANG_TRANSMOG_CMD_HIDE = 13,
LANG_TRANSMOG_CMD_ADD_UNSUITABLE = 14,
LANG_TRANSMOG_CMD_ADD_FORBIDDEN = 15,
LANG_TRANSMOG_CMD_BEGIN_SYNC = 16,
LANG_TRANSMOG_CMD_COMPLETE_SYNC = 17,
LANG_TRANSMOG_CMD_VENDOR_INTERFACE_ENABLE = 18,
LANG_TRANSMOG_CMD_VENDOR_INTERFACE_DISABLE = 19,
// Gossip/UI strings
LANG_TRANSMOG_HOWWORKS = 20,
LANG_TRANSMOG_MANAGESETS = 21,
LANG_TRANSMOG_REMOVETRANSMOG = 22,
LANG_TRANSMOG_REMOVETRANSMOG_ASK = 23,
LANG_TRANSMOG_UPDATEMENU = 24,
LANG_TRANSMOG_HOWSETSWORK = 25,
LANG_TRANSMOG_SAVESET = 26,
LANG_TRANSMOG_BACK = 27,
LANG_TRANSMOG_USESET = 28,
LANG_TRANSMOG_CONFIRM_USESET = 29,
LANG_TRANSMOG_DELETESET = 30,
LANG_TRANSMOG_CONFIRM_DELETESET = 31,
LANG_TRANSMOG_INSERTSETNAME = 32,
LANG_TRANSMOG_SEARCH = 33,
LANG_TRANSMOG_SEARCHING_FOR = 34,
LANG_TRANSMOG_SEARCH_FOR_ITEM = 35,
LANG_TRANSMOG_CONFIRM_HIDE_ITEM = 36,
LANG_TRANSMOG_HIDESLOT = 37,
LANG_TRANSMOG_REMOVETRANSMOG_SLOT = 38,
LANG_TRANSMOG_CONFIRM_USEITEM = 39,
LANG_TRANSMOG_PREVIOUS_PAGE = 40,
LANG_TRANSMOG_NEXT_PAGE = 41,
LANG_TRANSMOG_ADDED_APPEARANCE = 42,
// Disclaimer strings
LANG_TRANSMOG_SET_DISCLAIMER = 43,
LANG_TRANSMOG_CMD_DISCLAIMER_ON = 44,
LANG_TRANSMOG_CMD_DISCLAIMER_OFF = 45,
};
enum ArmorClassSpellIDs
{
SPELL_PLATE = 750,
SPELL_MAIL = 8737,
SPELL_LEATHER = 9077,
SPELL_CLOTH = 9078
};
const uint32 AllArmorSpellIds[4] =
{
SPELL_PLATE,
SPELL_MAIL,
SPELL_LEATHER,
SPELL_CLOTH
};
const uint32 AllArmorTiers[4] =
{
ITEM_SUBCLASS_ARMOR_PLATE,
ITEM_SUBCLASS_ARMOR_MAIL,
ITEM_SUBCLASS_ARMOR_LEATHER,
ITEM_SUBCLASS_ARMOR_CLOTH
};
enum PlusFeatures
{
PLUS_FEATURE_GREY_ITEMS,
PLUS_FEATURE_LEGENDARY_ITEMS,
PLUS_FEATURE_PET,
PLUS_FEATURE_SKIP_LEVEL_REQ
};
const uint32 TMOG_VENDOR_CREATURE_ID = 190010;
class Transmogrification
{
public:
static Transmogrification* instance();
typedef std::unordered_map<ObjectGuid, ObjectGuid> transmogData;
typedef std::unordered_map<ObjectGuid, uint32> transmog2Data;
typedef std::unordered_map<ObjectGuid, transmog2Data> transmogMap;
typedef std::unordered_map<uint32, std::unordered_set<uint32>> collectionCacheMap;
typedef std::unordered_map<uint32, std::string> searchStringMap;
typedef std::unordered_map<uint32, std::vector<uint32>> transmogPlusData;
typedef std::unordered_map<ObjectGuid, uint8> selectedSlotMap;
transmogPlusData plusDataMap;
transmogMap entryMap; // entryMap[pGUID][iGUID] = entry
transmogData dataMap; // dataMap[iGUID] = pGUID
collectionCacheMap collectionCache;
selectedSlotMap selectionCache;
#ifdef PRESETS
bool EnableSetInfo;
uint32 SetNpcText;
typedef std::map<uint8, uint32> slotMap;
typedef std::map<uint8, slotMap> presetData;
typedef std::unordered_map<ObjectGuid, presetData> presetDataMap;
presetDataMap presetById; // presetById[pGUID][presetID][slot] = entry
typedef std::map<uint8, std::string> presetIdMap;
typedef std::unordered_map<ObjectGuid, presetIdMap> presetNameMap;
presetNameMap presetByName; // presetByName[pGUID][presetID] = presetName
searchStringMap searchStringByPlayer;
void PresetTransmog(Player* player, Item* itemTransmogrified, uint32 fakeEntry, uint8 slot);
bool EnableSets;
uint8 MaxSets;
float SetCostModifier;
int32 SetCopperCost;
bool GetEnableSets() const;
uint8 GetMaxSets() const;
float GetSetCostModifier() const;
int32 GetSetCopperCost() const;
void LoadPlayerSets(ObjectGuid pGUID);
void UnloadPlayerSets(ObjectGuid pGUID);
void LoadCollections();
#endif
bool EnableTransmogInfo;
uint32 TransmogNpcText;
// Use IsAllowed() and IsNotAllowed()
// these are thread unsafe, but assumed to be static data so it should be safe
std::set<uint32> Allowed;
std::set<uint32> NotAllowed;
float ScaledCostModifier;
int32 CopperCost;
bool RequireToken;
uint32 TokenEntry;
uint32 TokenAmount;
bool AllowPoor;
bool AllowCommon;
bool AllowUncommon;
bool AllowRare;
bool AllowEpic;
bool AllowLegendary;
bool AllowArtifact;
bool AllowHeirloom;
bool AllowTradeable;
bool AllowMixedArmorTypes;
bool AllowLowerTiers;
bool AllowMixedOffhandArmorTypes;
bool AllowMixedWeaponHandedness;
bool AllowFishingPoles;
uint8 AllowMixedWeaponTypes;
bool IgnoreReqRace;
bool IgnoreReqClass;
bool IgnoreReqSkill;
bool IgnoreReqSpell;
bool IgnoreReqLevel;
bool IgnoreReqEvent;
bool IgnoreReqStats;
bool UseCollectionSystem;
bool UseVendorInterface;
bool AllowHiddenTransmog;
bool HiddenTransmogIsFree;
bool TrackUnusableItems;
bool RetroActiveAppearances;
bool ResetRetroActiveAppearances;
bool ShowSetDisclaimer;
bool IsTransmogEnabled;
bool IsPortableNPCEnabled;
bool IsAllowed(uint32 entry) const;
bool IsNotAllowed(uint32 entry) const;
bool IsAllowedQuality(uint32 quality, ObjectGuid const & playerGuid) const;
bool IsRangedWeapon(uint32 Class, uint32 SubClass) const;
bool CanNeverTransmog(ItemTemplate const* itemTemplate);
void LoadConfig(bool reload); // thread unsafe
std::string GetItemIcon(uint32 entry, uint32 width, uint32 height, int x, int y) const;
std::string GetSlotIcon(uint8 slot, uint32 width, uint32 height, int x, int y) const;
const char * GetSlotName(uint8 slot, WorldSession* session) const;
std::string GetItemLink(Item* item, WorldSession* session) const;
std::string GetItemLink(uint32 entry, WorldSession* session) const;
uint32 GetFakeEntry(ObjectGuid itemGUID) const;
void UpdateItem(Player* player, Item* item) const;
void DeleteFakeEntry(Player* player, uint8 slot, Item* itemTransmogrified, CharacterDatabaseTransaction* trans = nullptr);
void SetFakeEntry(Player* player, uint32 newEntry, uint8 slot, Item* itemTransmogrified);
bool AddCollectedAppearance(uint32 accountId, uint32 itemId);
TransmogStrings Transmogrify(Player* player, ObjectGuid itemGUID, uint8 slot, /*uint32 newEntry, */bool no_cost = false);
TransmogStrings Transmogrify(Player* player, uint32 itemEntry, uint8 slot, /*uint32 newEntry, */bool no_cost = false);
TransmogStrings Transmogrify(Player* player, Item* itemTransmogrifier, uint8 slot, /*uint32 newEntry, */bool no_cost = false, bool hidden_transmog = false);
bool CanTransmogrifyItemWithItem(Player* player, ItemTemplate const* destination, ItemTemplate const* source) const;
bool SuitableForTransmogrification(Player* player, ItemTemplate const* proto) const;
bool SuitableForTransmogrification(ObjectGuid guid, ItemTemplate const* proto) const;
bool IsItemTransmogrifiable(ItemTemplate const* proto, ObjectGuid const &playerGuid) const;
uint32 GetSpecialPrice(ItemTemplate const* proto) const;
void DeleteFakeFromDB(ObjectGuid::LowType itemLowGuid, CharacterDatabaseTransaction* trans = nullptr);
float GetScaledCostModifier() const;
int32 GetCopperCost() const;
bool GetRequireToken() const;
uint32 GetTokenEntry() const;
uint32 GetTokenAmount() const;
bool GetAllowMixedArmorTypes() const;
bool GetAllowLowerTiers() const;
bool GetAllowMixedOffhandArmorTypes() const;
uint8 GetAllowMixedWeaponTypes() const;
// Config
bool GetEnableTransmogInfo() const;
uint32 GetTransmogNpcText() const;
bool GetEnableSetInfo() const;
uint32 GetSetNpcText() const;
bool GetAllowTradeable() const;
bool GetUseCollectionSystem() const;
bool GetUseVendorInterface() const;
bool GetAllowHiddenTransmog() const;
bool GetHiddenTransmogIsFree() const;
bool GetTrackUnusableItems() const;
bool EnableRetroActiveAppearances() const;
bool EnableResetRetroActiveAppearances() const;
[[nodiscard]] bool IsEnabled() const;
bool IsValidOffhandArmor(uint32 subclass, uint32 invType) const;
bool IsTieredArmorSubclass(uint32 subclass) const;
uint32 GetHighestAvailableForPlayer(Player* player) const;
uint32 GetHighestAvailableForPlayer(int playerGuid) const;
bool TierAvailable(Player* player, int playerGuid, uint32 tierSpell) const;
bool IsInvTypeMismatchAllowed (const ItemTemplate *source, const ItemTemplate *target) const;
bool IsSubclassMismatchAllowed (Player *player, const ItemTemplate *source, const ItemTemplate *target) const;
// Transmog Plus
bool IsTransmogPlusEnabled;
[[nodiscard]] bool IsPlusFeatureEligible(ObjectGuid const& playerGuid, uint32 feature) const;
[[nodiscard]] uint32 GetPlayerMembershipLevel(Player* player) const { return player->GetPlayerSetting("acore_cms_subscriptions", SETTING_TRANSMOG_MEMBERSHIP_LEVEL).value; };
[[nodiscard]] bool IgnoreLevelRequirement(ObjectGuid const& playerGuid) const { return IgnoreReqLevel || IsPlusFeatureEligible(playerGuid, PLUS_FEATURE_SKIP_LEVEL_REQ); }
uint32 PetSpellId;
uint32 PetEntry;
[[nodiscard]] bool IsTransmogVendor(uint32 entry) const { return entry == TMOG_VENDOR_CREATURE_ID || entry == PetEntry; };
};
#define sTransmogrification Transmogrification::instance()
#endif
+350
View File
@@ -0,0 +1,350 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Chat.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "Transmogrification.h"
#include "Tokenize.h"
#include "DatabaseEnv.h"
#include "SpellMgr.h"
using namespace Acore::ChatCommands;
class transmog_commandscript : public CommandScript
{
public:
transmog_commandscript() : CommandScript("transmog_commandscript") { }
ChatCommandTable GetCommands() const override
{
static ChatCommandTable addCollectionTable =
{
{ "set", HandleAddTransmogItemSet, SEC_MODERATOR, Console::Yes },
{ "", HandleAddTransmogItem, SEC_MODERATOR, Console::Yes },
};
static ChatCommandTable transmogTable =
{
{ "add", addCollectionTable },
{ "", HandleDisableTransMogVisual, SEC_PLAYER, Console::No },
{ "sync", HandleSyncTransMogCommand, SEC_PLAYER, Console::No },
{ "portable", HandleTransmogPortableCommand, SEC_PLAYER, Console::No },
{ "interface", HandleInterfaceOption, SEC_PLAYER, Console::No },
{ "disclaimer", HandleDisclaimerOption, SEC_PLAYER, Console::No },
{ "reload", HandleReloadTransmogConfig, SEC_ADMINISTRATOR, Console::Yes}
};
static ChatCommandTable commandTable =
{
{ "transmog", transmogTable },
};
return commandTable;
}
static bool HandleSyncTransMogCommand(ChatHandler* handler)
{
Player* player = handler->GetPlayer();
uint32 accountId = player->GetSession()->GetAccountId();
handler->PSendModuleSysMessage("mod-transmog", LANG_TRANSMOG_CMD_BEGIN_SYNC);
for (uint32 itemId : sTransmogrification->collectionCache[accountId])
handler->PSendSysMessage("TRANSMOG_SYNC:{}", itemId);
handler->PSendModuleSysMessage("mod-transmog", LANG_TRANSMOG_CMD_COMPLETE_SYNC);
return true;
}
static bool HandleDisableTransMogVisual(ChatHandler* handler, bool hide)
{
Player* player = handler->GetPlayer();
if (hide)
{
player->UpdatePlayerSetting("mod-transmog", SETTING_HIDE_TRANSMOG, 0);
handler->PSendModuleSysMessage("mod-transmog", LANG_TRANSMOG_CMD_SHOW);
}
else
{
player->UpdatePlayerSetting("mod-transmog", SETTING_HIDE_TRANSMOG, 1);
handler->PSendModuleSysMessage("mod-transmog", LANG_TRANSMOG_CMD_HIDE);
}
player->UpdateObjectVisibility();
return true;
}
static bool HandleAddTransmogItem(ChatHandler* handler, Optional<PlayerIdentifier> player, ItemTemplate const* itemTemplate)
{
if (!sTransmogrification->GetUseCollectionSystem())
return true;
if (!sObjectMgr->GetItemTemplate(itemTemplate->ItemId))
{
handler->PSendSysMessage(LANG_COMMAND_ITEMIDINVALID, itemTemplate->ItemId);
handler->SetSentErrorMessage(true);
return false;
}
if (!player)
player = PlayerIdentifier::FromTargetOrSelf(handler);
if (!player)
return false;
Player* target = player->GetConnectedPlayer();
bool isNotConsole = handler->GetSession();
bool suitableForTransmog;
if (target)
suitableForTransmog = sTransmogrification->SuitableForTransmogrification(target, itemTemplate);
else
suitableForTransmog = sTransmogrification->SuitableForTransmogrification(player->GetGUID(), itemTemplate);
if (!sTransmogrification->GetTrackUnusableItems() && !suitableForTransmog)
{
handler->PSendModuleSysMessage("mod-transmog", LANG_TRANSMOG_CMD_ADD_UNSUITABLE);
handler->SetSentErrorMessage(true);
return true;
}
if (itemTemplate->Class != ITEM_CLASS_ARMOR && itemTemplate->Class != ITEM_CLASS_WEAPON)
{
handler->PSendModuleSysMessage("mod-transmog", LANG_TRANSMOG_CMD_ADD_FORBIDDEN);
handler->SetSentErrorMessage(true);
return true;
}
auto guid = player->GetGUID();
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid);
uint32 itemId = itemTemplate->ItemId;
std::stringstream tempStream;
tempStream << std::hex << ItemQualityColors[itemTemplate->Quality];
std::string itemQuality = tempStream.str();
std::string itemName = itemTemplate->Name1;
if (target) {
// get locale item name
int loc_idex = target->GetSession()->GetSessionDbLocaleIndex();
if (ItemLocale const* il = sObjectMgr->GetItemLocale(itemId))
ObjectMgr::GetLocaleString(il->Name, loc_idex, itemName);
}
std::string playerName = player->GetName();
std::string nameLink = handler->playerLink(playerName);
if (sTransmogrification->AddCollectedAppearance(accountId, itemId))
{
// Notify target of new item in appearance collection
if (target && !(target->GetPlayerSetting("mod-transmog", SETTING_HIDE_TRANSMOG).value) && !sTransmogrification->CanNeverTransmog(itemTemplate))
ChatHandler(target->GetSession()).PSendSysMessage(R"(|c{}|Hitem:{}:0:0:0:0:0:0:0:0|h[{}]|h|r has been added to your appearance collection.)", itemQuality.c_str(), itemId, itemName.c_str());
// Feedback of successful command execution to GM
if (isNotConsole && target != handler->GetPlayer())
handler->PSendSysMessage(R"(|c{}|Hitem:{}:0:0:0:0:0:0:0:0|h[{}]|h|r has been added to the appearance collection of Player {}.)", itemQuality.c_str(), itemId, itemName.c_str(), nameLink);
CharacterDatabase.Execute("INSERT INTO custom_unlocked_appearances (account_id, item_template_id) VALUES ({}, {})", accountId, itemId);
}
else
{
// Feedback of failed command execution to GM
if (isNotConsole)
{
handler->PSendSysMessage(R"(Player {} already has item |c{}|Hitem:{}:0:0:0:0:0:0:0:0|h[{}]|h|r in the appearance collection.)", nameLink, itemQuality.c_str(), itemId, itemName.c_str());
handler->SetSentErrorMessage(true);
}
}
return true;
}
static bool HandleAddTransmogItemSet(ChatHandler* handler, Optional<PlayerIdentifier> player, Variant<Hyperlink<itemset>, uint32> itemSetId)
{
if (!sTransmogrification->GetUseCollectionSystem())
return true;
if (!*itemSetId)
{
handler->PSendSysMessage(LANG_NO_ITEMS_FROM_ITEMSET_FOUND, uint32(itemSetId));
handler->SetSentErrorMessage(true);
return false;
}
if (!player)
player = PlayerIdentifier::FromTargetOrSelf(handler);
if (!player)
return false;
Player* target = player->GetConnectedPlayer();
ItemSetEntry const* set = sItemSetStore.LookupEntry(uint32(itemSetId));
bool isNotConsole = handler->GetSession();
if (!set)
{
handler->PSendSysMessage(LANG_NO_ITEMS_FROM_ITEMSET_FOUND, uint32(itemSetId));
handler->SetSentErrorMessage(true);
return false;
}
auto guid = player->GetGUID();
CharacterCacheEntry const* playerData = sCharacterCache->GetCharacterCacheByGuid(guid);
if (!playerData)
return false;
bool added = false;
uint32 error = 0; // holds a TransmogStrings id, 0 = no error
uint32 itemId;
uint32 accountId = playerData->AccountId;
for (uint32 i = 0; i < MAX_ITEM_SET_ITEMS; ++i)
{
itemId = set->itemId[i];
if (itemId)
{
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId);
if (itemTemplate)
{
if (!sTransmogrification->GetTrackUnusableItems() && (
(target && !sTransmogrification->SuitableForTransmogrification(target, itemTemplate)) ||
!sTransmogrification->SuitableForTransmogrification(guid, itemTemplate)
))
{
error = LANG_TRANSMOG_CMD_ADD_UNSUITABLE;
continue;
}
if (itemTemplate->Class != ITEM_CLASS_ARMOR && itemTemplate->Class != ITEM_CLASS_WEAPON)
{
error = LANG_TRANSMOG_CMD_ADD_FORBIDDEN;
continue;
}
if (sTransmogrification->AddCollectedAppearance(accountId, itemId))
{
CharacterDatabase.Execute("INSERT INTO custom_unlocked_appearances (account_id, item_template_id) VALUES ({}, {})", accountId, itemId);
added = true;
}
}
}
}
if (!added && error > 0)
{
handler->PSendModuleSysMessage("mod-transmog", error);
handler->SetSentErrorMessage(true);
return true;
}
int locale = handler->GetSessionDbcLocale();
std::string setName = set->name[locale];
std::string nameLink = handler->playerLink(player->GetName());
// Feedback of command execution to GM
if (isNotConsole)
{
// Failed command execution
if (!added)
{
handler->PSendSysMessage("Player {} already has ItemSet |cffffffff|Hitemset:{}|h[{} {}]|h|r in the appearance collection.", nameLink, uint32(itemSetId), setName.c_str(), localeNames[locale]);
handler->SetSentErrorMessage(true);
return true;
}
// Successful command execution
if (target != handler->GetPlayer())
handler->PSendSysMessage("ItemSet |cffffffff|Hitemset:{}|h[{} {}]|h|r has been added to the appearance collection of Player {}.", uint32(itemSetId), setName.c_str(), localeNames[locale], nameLink);
}
// Notify target of new item in appearance collection
if (target && !(target->GetPlayerSetting("mod-transmog", SETTING_HIDE_TRANSMOG).value))
ChatHandler(target->GetSession()).PSendSysMessage("ItemSet |cffffffff|Hitemset:%d|h[{} {}]|h|r has been added to your appearance collection.", uint32(itemSetId), setName.c_str(), localeNames[locale]);
return true;
}
static bool HandleTransmogPortableCommand(ChatHandler* handler)
{
if (!sTransmogrification->IsPortableNPCEnabled)
{
handler->SendErrorMessage("The portable transmogrification NPC is disabled.");
return true;
}
if (!sTransmogrification->IsTransmogPlusEnabled)
{
handler->SendErrorMessage("The portable transmogrification NPC is a plus feature. Plus features are currently disabled.");
return true;
}
Player* player = PlayerIdentifier::FromSelf(handler)->GetConnectedPlayer();
if (!sTransmogrification->IsPlusFeatureEligible(player->GetGUID(), PLUS_FEATURE_PET))
{
handler->SendErrorMessage("You are not eligible for the portable transmogrification NPC. Please check your subscription level.");
return true;
}
if (!sSpellMgr->GetSpellInfo(sTransmogrification->PetSpellId))
{
handler->SendErrorMessage("The portable transmogrification NPC spell is not available.");
return true;
}
player->CastSpell((Unit*)nullptr, sTransmogrification->PetSpellId, true);
return true;
};
static bool HandleInterfaceOption(ChatHandler* handler, bool enable)
{
handler->GetPlayer()->UpdatePlayerSetting("mod-transmog", SETTING_VENDOR_INTERFACE, enable);
handler->PSendModuleSysMessage("mod-transmog", enable ? LANG_TRANSMOG_CMD_VENDOR_INTERFACE_ENABLE : LANG_TRANSMOG_CMD_VENDOR_INTERFACE_DISABLE);
return true;
}
static bool HandleDisclaimerOption(ChatHandler* handler, bool enable)
{
Player* player = handler->GetPlayer();
if (enable)
{
player->UpdatePlayerSetting("mod-transmog", SETTING_HIDE_SET_DISCLAIMER, 0);
handler->PSendModuleSysMessage("mod-transmog", LANG_TRANSMOG_CMD_DISCLAIMER_ON);
}
else
{
player->UpdatePlayerSetting("mod-transmog", SETTING_HIDE_SET_DISCLAIMER, 1);
handler->PSendModuleSysMessage("mod-transmog", LANG_TRANSMOG_CMD_DISCLAIMER_OFF);
}
return true;
}
static bool HandleReloadTransmogConfig(ChatHandler* handler)
{
sTransmogrification->LoadConfig(true);
handler->SendSysMessage("Transmog configs reloaded.");
sTransmogrification->LoadCollections();
handler->SendSysMessage("Transmog collections reloaded.");
return true;
}
};
void AddSC_transmog_commandscript()
{
new transmog_commandscript();
}
@@ -0,0 +1,15 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
*/
// From SC
void AddSC_Transmog();
void AddSC_transmog_commandscript();
// Add all
void Addmod_transmogScripts()
{
AddSC_Transmog();
AddSC_transmog_commandscript();
}
File diff suppressed because it is too large Load Diff
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Kill AzerothCore authserver + worldserver tmux sessions and any stray processes.
#
# Usage:
# bash scripts/kill-azeroth-servers.sh
set -euo pipefail
AUTH_SESSION="authserver"
WORLD_SESSION="worldserver"
echo "Stopping servers..."
# Kill tmux sessions
if tmux has-session -t "$WORLD_SESSION" 2>/dev/null; then
tmux kill-session -t "$WORLD_SESSION"
echo " killed tmux session: $WORLD_SESSION"
else
echo " no tmux session: $WORLD_SESSION"
fi
if tmux has-session -t "$AUTH_SESSION" 2>/dev/null; then
tmux kill-session -t "$AUTH_SESSION"
echo " killed tmux session: $AUTH_SESSION"
else
echo " no tmux session: $AUTH_SESSION"
fi
# Clean up any stray processes not managed by tmux
if pkill -x worldserver 2>/dev/null; then
echo " killed stray worldserver process"
fi
if pkill -x authserver 2>/dev/null; then
echo " killed stray authserver process"
fi
echo "Done."
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Start AzerothCore authserver + worldserver in named tmux sessions.
# Kills any already-running sessions first (acts as a restart).
#
# Usage:
# bash scripts/start-azeroth-servers.sh
# AZEROTH_BIN=/path/to/bin bash scripts/start-azeroth-servers.sh
#
# Environment:
# AZEROTH_BIN — directory with authserver and worldserver (default: /home/fractured-panel/azeroth-server/bin)
# AZEROTH_LOG_DIR — log directory (default: <parent of bin>/logs)
#
# tmux sessions:
# authserver — authserver console
# worldserver — worldserver console
set -euo pipefail
BIN_DIR="${AZEROTH_BIN:-/home/fractured-panel/azeroth-server/bin}"
BASE_DIR="$(cd "$(dirname "$BIN_DIR")" && pwd)"
LOG_DIR="${AZEROTH_LOG_DIR:-${BASE_DIR}/logs}"
CONF_DIR="${BASE_DIR}/etc"
AUTH_BIN="${BIN_DIR}/authserver"
WORLD_BIN="${BIN_DIR}/worldserver"
AUTH_SESSION="authserver"
WORLD_SESSION="worldserver"
if ! command -v tmux &>/dev/null; then
echo "error: tmux is not installed" >&2
exit 1
fi
if [[ ! -x "$AUTH_BIN" ]]; then
echo "error: not found or not executable: $AUTH_BIN" >&2
exit 1
fi
if [[ ! -x "$WORLD_BIN" ]]; then
echo "error: not found or not executable: $WORLD_BIN" >&2
exit 1
fi
mkdir -p "$LOG_DIR"
# Tear down existing sessions (ignore errors if they don't exist)
tmux kill-session -t "$AUTH_SESSION" 2>/dev/null || true
tmux kill-session -t "$WORLD_SESSION" 2>/dev/null || true
# Also kill any stray processes not managed by tmux
pkill -x authserver 2>/dev/null || true
pkill -x worldserver 2>/dev/null || true
sleep 1
# Launch authserver in a tmux session
tmux new-session -d -s "$AUTH_SESSION" -c "$BIN_DIR" \
"$AUTH_BIN -c ${CONF_DIR}/authserver.conf 2>&1 | tee -a ${LOG_DIR}/authserver.log"
sleep 2
# Launch worldserver in a tmux session
tmux new-session -d -s "$WORLD_SESSION" -c "$BIN_DIR" \
"$WORLD_BIN -c ${CONF_DIR}/worldserver.conf 2>&1 | tee -a ${LOG_DIR}/worldserver.log"
echo "Started servers in tmux sessions."
echo " tmux attach -t $AUTH_SESSION — authserver console"
echo " tmux attach -t $WORLD_SESSION — worldserver console"
echo "Bin: $BIN_DIR"
echo "Config: $CONF_DIR"
echo "Logs: $LOG_DIR/authserver.log"
echo " $LOG_DIR/worldserver.log"
+2 -2
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Clone Dawnforger/Fractured and omit Docker-only paths. Use when this script is # Clone HighSocietyRaiding/Fractured (Gitea) and omit Docker-only paths. Use when this script is
# already on disk (e.g. scp). Otherwise: git clone … && cd Fractured && bash scripts/vps-sparse-checkout-no-docker.sh # already on disk (e.g. scp). Otherwise: git clone … && cd Fractured && bash scripts/vps-sparse-checkout-no-docker.sh
# #
# Usage: # Usage:
# bash scripts/vps-clone-without-docker.sh /path/to/Fractured git@github.com:Dawnforger/Fractured.git # bash scripts/vps-clone-without-docker.sh /path/to/Fractured https://git.hisora.dev/HighSocietyRaiding/Fractured.git
set -euo pipefail set -euo pipefail
+260
View File
@@ -0,0 +1,260 @@
#!/usr/bin/env bash
# Fractured / AzerothCore — native VPS rolling update (git + compile).
#
# Run from anywhere; resolves the repository root from this script's location.
# Typical production layout: sources in ~/src/Fractured, install prefix in ~/azeroth-server
# (see docs/DEPLOY_LINUX_VPS.md).
#
# What this does:
# 1. Optionally kill running servers (tmux sessions)
# 2. git pull on the current branch (optional; can skip)
# 3. ./acore.sh compiler build — or compiler all for a full clean rebuild
# 4. Optionally restart servers in tmux sessions
#
# Database migrations from data/sql/updates/ run when you next start worldserver/authserver
# (Updates.* / SourceDirectory in *.conf).
#
# Usage:
# bash scripts/vps-update-server.sh # pull + compile only
# bash scripts/vps-update-server.sh --restart # pull + compile + restart servers in tmux
# bash scripts/vps-update-server.sh --full --restart # clean rebuild + restart
# bash scripts/vps-update-server.sh --no-pull --restart # compile current tree + restart
# bash scripts/vps-update-server.sh --dry-run
# bash scripts/vps-update-server.sh --run-after 'custom command here'
#
# Canonical Fractured source (Gitea): https://git.hisora.dev/HighSocietyRaiding/Fractured.git
# If origin still points at a known legacy GitHub mirror (HighSocietyRaiding/Fractured or
# Dawnforger/Fractured), the script rewrites that remote to Gitea before pulling (override with
# FRACTURED_GITEA_ORIGIN_URL; disable with FRACTURED_SKIP_ORIGIN_MIGRATION=1).
#
# Environment:
# FRACTURED_GIT_REMOTE — remote name (default: origin)
# FRACTURED_GITEA_ORIGIN_URL — URL used when auto-migrating off GitHub (default: Gitea HTTPS above)
# FRACTURED_SKIP_ORIGIN_MIGRATION — set to 1 to never rewrite remote URLs
# FRACTURED_POST_UPDATE_CMD — shell command run after compile (used by bare --run-after)
# FRACTURED_KILL_SCRIPT — with --restart: path to kill script (default: scripts/kill-azeroth-servers.sh)
# FRACTURED_START_SCRIPT — with --restart: path to start script (default: scripts/start-azeroth-servers.sh)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
NO_PULL=0
FULL_BUILD=0
COMPILE_ONLY=0
DRY_RUN=0
DO_RUN_AFTER=0
DO_RESTART=0
INSTALL_PREFIX=""
POST_UPDATE_CMD="${FRACTURED_POST_UPDATE_CMD:-}"
GIT_REMOTE="${FRACTURED_GIT_REMOTE:-origin}"
FRACTURED_GITEA_ORIGIN_URL="${FRACTURED_GITEA_ORIGIN_URL:-https://git.hisora.dev/HighSocietyRaiding/Fractured.git}"
usage() {
cat <<'EOF'
Fractured VPS update — git pull + compiler (see header in script for full notes).
Usage:
bash scripts/vps-update-server.sh [options]
Options:
--no-pull Skip git pull (only compile current tree).
--full ./acore.sh compiler all (clean + configure + compile).
--compile-only ./acore.sh compiler compile (incremental).
--prefix PATH Override CMAKE_INSTALL_PREFIX (updates conf/config.sh BINPATH).
--dry-run Print commands without running them.
--restart Kill servers before compile, restart after (see FRACTURED_KILL_SCRIPT / FRACTURED_START_SCRIPT).
--run-after [CMD] Run a custom shell command after successful compile.
If CMD is omitted, uses FRACTURED_POST_UPDATE_CMD.
Environment:
FRACTURED_GIT_REMOTE Git remote name (default: origin).
FRACTURED_GITEA_ORIGIN_URL Target URL when auto-migrating from GitHub Fractured remote.
FRACTURED_SKIP_ORIGIN_MIGRATION Set to 1 to skip GitHub → Gitea remote rewrite.
FRACTURED_POST_UPDATE_CMD Used with bare --run-after.
FRACTURED_KILL_SCRIPT Override kill script used with --restart (default: repo scripts/).
FRACTURED_START_SCRIPT Override start script used with --restart (default: repo scripts/).
EOF
}
run() {
if [[ "$DRY_RUN" -eq 1 ]]; then
printf '[dry-run] '
printf '%q ' "$@"
printf '\n'
else
"$@"
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h | --help)
usage
exit 0
;;
--no-pull)
NO_PULL=1
shift
;;
--full)
FULL_BUILD=1
shift
;;
--compile-only)
COMPILE_ONLY=1
shift
;;
--prefix)
shift
if [[ $# -eq 0 || "$1" == -* ]]; then
echo "error: --prefix requires a path argument" >&2
exit 2
fi
INSTALL_PREFIX="$1"
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
--restart)
DO_RESTART=1
shift
;;
--run-after)
DO_RUN_AFTER=1
shift
if [[ $# -gt 0 && "$1" != -* ]]; then
POST_UPDATE_CMD="$1"
shift
fi
;;
*)
echo "error: unknown option: $1" >&2
echo "Try: bash scripts/vps-update-server.sh --help" >&2
exit 2
;;
esac
done
if [[ "$FULL_BUILD" -eq 1 && "$COMPILE_ONLY" -eq 1 ]]; then
echo "error: use only one of --full or --compile-only" >&2
exit 2
fi
if [[ ! -d "$ROOT/.git" ]]; then
echo "error: not a git clone: $ROOT" >&2
exit 1
fi
if [[ ! -f "$ROOT/acore.sh" ]]; then
echo "error: acore.sh not found under $ROOT" >&2
exit 1
fi
if [[ ! -f "$ROOT/conf/config.sh" ]]; then
echo "error: missing $ROOT/conf/config.sh — copy conf/dist/config.sh and edit (see DEPLOY_LINUX_VPS.md)." >&2
exit 1
fi
cd "$ROOT"
KILL_SCRIPT="${FRACTURED_KILL_SCRIPT:-$SCRIPT_DIR/kill-azeroth-servers.sh}"
START_SCRIPT="${FRACTURED_START_SCRIPT:-$SCRIPT_DIR/start-azeroth-servers.sh}"
if [[ "$DO_RESTART" -eq 1 ]]; then
if [[ ! -f "$KILL_SCRIPT" || ! -f "$START_SCRIPT" ]]; then
echo "error: --restart needs kill and start scripts (defaults under scripts/). Set FRACTURED_KILL_SCRIPT / FRACTURED_START_SCRIPT to override." >&2
exit 1
fi
echo "==> stopping servers before compile"
run bash "$KILL_SCRIPT"
fi
if [[ -n "$INSTALL_PREFIX" ]]; then
echo "==> updating conf/config.sh BINPATH to: $INSTALL_PREFIX"
if grep -q '^BINPATH=' conf/config.sh; then
run sed -i "s|^BINPATH=.*|BINPATH=\"$INSTALL_PREFIX\"|" conf/config.sh
else
echo "BINPATH=\"$INSTALL_PREFIX\"" >> conf/config.sh
fi
export BINPATH="$INSTALL_PREFIX"
fi
if [[ "$DO_RUN_AFTER" -eq 1 && -z "${POST_UPDATE_CMD// }" ]]; then
echo "error: --run-after needs a command or FRACTURED_POST_UPDATE_CMD set in the environment." >&2
exit 2
fi
current_branch() {
git symbolic-ref -q --short HEAD || git rev-parse --short HEAD
}
# Old VPS clones may still have origin → github.com:…/Fractured (org mirror); repoint to Gitea.
ensure_fractured_origin_on_gitea() {
if [[ "${FRACTURED_SKIP_ORIGIN_MIGRATION:-0}" == "1" ]]; then
return 0
fi
local url
url="$(git remote get-url "$GIT_REMOTE" 2>/dev/null || true)"
[[ -z "$url" ]] && return 0
# HTTPS: …/Org/Fractured or …/Fractured.git SSH: github.com:Org/Fractured(.git)
if [[ "$url" =~ github\.com[:/](HighSocietyRaiding|Dawnforger)/Fractured(\.git)?$ ]]; then
echo "==> $GIT_REMOTE still points at GitHub Fractured (${BASH_REMATCH[1]}); switching to Gitea: $FRACTURED_GITEA_ORIGIN_URL"
run git remote set-url "$GIT_REMOTE" "$FRACTURED_GITEA_ORIGIN_URL"
fi
}
if [[ "$NO_PULL" -eq 0 ]]; then
ref="$(current_branch)"
if [[ "$ref" == "HEAD" ]]; then
echo "error: detached HEAD; checkout a branch or use --no-pull." >&2
exit 1
fi
ensure_fractured_origin_on_gitea
echo "==> git pull $GIT_REMOTE $ref"
run git pull "$GIT_REMOTE" "$ref"
else
echo "==> skipping git pull (--no-pull)"
fi
echo "==> ensuring acore.sh and JSONPath are executable"
if [[ "$DRY_RUN" -eq 1 ]]; then
run chmod +x acore.sh deps/jsonpath/JSONPath.sh
else
chmod +x acore.sh deps/jsonpath/JSONPath.sh 2>/dev/null || true
fi
if [[ "$FULL_BUILD" -eq 1 ]]; then
echo "==> ./acore.sh compiler all (clean, configure, compile)"
run ./acore.sh compiler all
elif [[ "$COMPILE_ONLY" -eq 1 ]]; then
echo "==> ./acore.sh compiler compile (incremental; build dir must exist)"
run ./acore.sh compiler compile
else
echo "==> ./acore.sh compiler build (configure + compile)"
run ./acore.sh compiler build
fi
if [[ "$DO_RESTART" -eq 1 ]]; then
echo "==> restarting servers in tmux sessions"
run bash "$START_SCRIPT"
fi
if [[ "$DO_RUN_AFTER" -eq 1 ]]; then
echo "==> post-update: $POST_UPDATE_CMD"
if [[ "$DRY_RUN" -eq 1 ]]; then
printf '[dry-run] eval %q\n' "$POST_UPDATE_CMD"
else
# shellcheck disable=SC2086
eval "$POST_UPDATE_CMD"
fi
fi
if [[ "$DO_RESTART" -eq 0 && "$DO_RUN_AFTER" -eq 0 ]]; then
echo "Done. (Re)start: bash \"$SCRIPT_DIR/start-azeroth-servers.sh\" — or set FRACTURED_START_SCRIPT for a custom path."
else
echo "Done."
fi
@@ -1753,9 +1753,9 @@ InstantLogout = 1
# #
# PlayerSaveInterval # PlayerSaveInterval
# Description: Time (in milliseconds) for player save interval. # Description: Time (in milliseconds) for player save interval.
# Default: 900000 - (15 min) # Default: 300000 - (5 min)
PlayerSaveInterval = 900000 PlayerSaveInterval = 300000
# #
# PlayerSave.Stats.MinLevel # PlayerSave.Stats.MinLevel
@@ -2260,9 +2260,9 @@ Achievement.RealmFirstKillWindow = 60
# MaxPrimaryTradeSkill # MaxPrimaryTradeSkill
# Description: Maximum number of primary professions a character can learn. # Description: Maximum number of primary professions a character can learn.
# Range: 0-11 # Range: 0-11
# Default: 2 # Default: 11 - (All WotLK primary professions; set 2 for retail-like two-slot cap.)
MaxPrimaryTradeSkill = 2 MaxPrimaryTradeSkill = 11
# #
# SkillChance.Prospecting # SkillChance.Prospecting
@@ -4767,6 +4767,36 @@ Respawn.DynamicEscortNPC = 0
Respawn.ForceCompatibilityMode = 0 Respawn.ForceCompatibilityMode = 0
#
# Paragon.WildcardFamilyMatching
# Description: Fractured / Paragon class (CLASS_PARAGON, id 12) only.
# When enabled, the SpellFamilyName equality check is
# wildcarded for Paragon characters in proc evaluation
# (SpellMgr::CanSpellTriggerProcOnEvent), talent
# SpellMod application (Player::ApplySpellMod /
# SpellInfo::IsAffectedBySpellMod), and the
# ParagonFamilyMatches() helper used by ad-hoc
# `switch (SpellFamilyName)` listener gates in
# Unit/SpellEffects/SpellAuraEffects code.
# This makes cross-class talent procs and modifiers
# (e.g. Predator's Swiftness 69369 making Shaman
# Chain Lightning instant cast off a Rogue Eviscerate
# finisher) apply to Paragon characters even when the
# listener was authored for one specific class family.
# SpellFamilyFlags / class-mask flag-bit checks still
# run, so listener gates that explicitly opt into a
# subset of spells via flag bits are still respected.
# Stock classes (Warrior / Paladin / etc.) are NEVER
# wildcarded; this only affects players whose class
# id is CLASS_PARAGON. Set to 0 to disable the
# wildcard at runtime (no rebuild required) if a
# regression appears.
# Default: 1 - (Enabled, Paragon characters get cross-class procs/mods)
# 0 - (Disabled, Paragon characters are gated by stock family equality)
#
Paragon.WildcardFamilyMatching = 1
# #
################################################################################################### ###################################################################################################
+1
View File
@@ -678,6 +678,7 @@ enum RBACPermissions
RBAC_PERM_COMMAND_BF_QUEUE = 913, RBAC_PERM_COMMAND_BF_QUEUE = 913,
RBAC_PERM_COMMAND_PET_LIST = 914, RBAC_PERM_COMMAND_PET_LIST = 914,
RBAC_PERM_COMMAND_PET_DELETE = 915, RBAC_PERM_COMMAND_PET_DELETE = 915,
RBAC_PERM_COMMAND_LEARN_ALL_MOUNTS = 916,
// custom permissions 1000+ // custom permissions 1000+
RBAC_PERM_MAX RBAC_PERM_MAX
}; };
@@ -3640,6 +3640,13 @@ bool Creature::IsMovementPreventedByCasting() const
return false; return false;
} }
// Fractured: cast-time mount summon (player-style mount spells on NPCs are rare but supported).
if (Spell* genSpell = m_currentSpells[CURRENT_GENERIC_SPELL])
{
if (genSpell->getState() == SPELL_STATE_PREPARING && genSpell->m_spellInfo->IsCastTimeRidingMountSpell())
return false;
}
if (HasSpellFocus()) if (HasSpellFocus())
{ {
return true; return true;
+79 -3
View File
@@ -7143,6 +7143,18 @@ bool Player::CheckAttackFitToAuraRequirement(WeaponAttackType attackType, AuraEf
if (spellInfo->EquippedItemClass == -1) if (spellInfo->EquippedItemClass == -1)
return true; return true;
// Fractured / Paragon: cross-class wildcard relaxes the weapon-subclass
// gate ONLY for the curated allowlist of cross-class proc talents
// (currently just Maelstrom Weapon 51528-51532). Weapon-specialization
// talents like Sword Specialization, Mace Specialization, Hack and
// Slash, Two-Handed Weapon Specialization etc. deliberately stay
// weapon-gated for Paragon -- the player picks a weapon and the
// matching specialization passive activates, same as any class.
if (spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON
&& IsParagonWildcardCaller(this)
&& IsParagonWeaponSubclassWildcardSpell(spellInfo->Id))
return GetWeaponForAttack(attackType, true) != nullptr;
Item* item = GetWeaponForAttack(attackType, true); Item* item = GetWeaponForAttack(attackType, true);
if (!item || !item->IsFitToSpellRequirements(spellInfo)) if (!item || !item->IsFitToSpellRequirements(spellInfo))
return false; return false;
@@ -7208,7 +7220,7 @@ void Player::ApplyEquipSpell(SpellInfo const* spellInfo, Item* item, bool apply,
return; return;
// Cannot be used in this stance/form // Cannot be used in this stance/form
if (spellInfo->CheckShapeshift(GetShapeshiftForm()) != SPELL_CAST_OK) if (spellInfo->CheckShapeshift(GetShapeshiftForm(), this) != SPELL_CAST_OK)
return; return;
if (form_change) // check aura active state from other form if (form_change) // check aura active state from other form
@@ -7228,7 +7240,7 @@ void Player::ApplyEquipSpell(SpellInfo const* spellInfo, Item* item, bool apply,
if (form_change) // check aura compatibility if (form_change) // check aura compatibility
{ {
// Cannot be used in this stance/form // Cannot be used in this stance/form
if (spellInfo->CheckShapeshift(GetShapeshiftForm()) == SPELL_CAST_OK) if (spellInfo->CheckShapeshift(GetShapeshiftForm(), this) == SPELL_CAST_OK)
return; // and remove only not compatible at form change return; // and remove only not compatible at form change
} }
@@ -9773,7 +9785,11 @@ bool Player::IsAffectedBySpellmod(SpellInfo const* spellInfo, SpellModifier* mod
if (mod->op == SPELLMOD_DURATION && spellInfo->GetDuration() == -1) if (mod->op == SPELLMOD_DURATION && spellInfo->GetDuration() == -1)
return false; return false;
return spellInfo->IsAffectedBySpellMod(mod); // Fractured / Paragon: pass the player owning the modifier aura so the
// SpellFamilyName equality check can be wildcarded for CLASS_PARAGON.
// Stock classes hit the same code path with `this` as a non-Paragon
// unit, which makes IsAffected behave identically to the 2-arg form.
return spellInfo->IsAffectedBySpellMod(mod, this);
} }
template <class T> template <class T>
@@ -12017,6 +12033,41 @@ void Player::learnSkillRewardedSpells(uint32 skill_id, uint32 skill_value)
uint32 raceMask = getRaceMask(); uint32 raceMask = getRaceMask();
uint32 classMask = getClassMask(); uint32 classMask = getClassMask();
// Fractured / Paragon: the Character Advancement panel is the sole
// authority over which class abilities a Paragon owns. The skill-line
// cascade re-fires from _LoadSkills (every login), UpdateSkillsForLevel
// (every level-up), UpdateSkillPro (every weapon-skill tick on a
// training dummy), and SetSkill (first time a class skill is granted).
// Each of those re-grants every SLA-tagged class ability on the
// matching skill line — leaking Blood Presence / Death Coil / Death
// Grip / etc. back into the spellbook within seconds even after the
// player intentionally refunded them via the panel. Skip the cascade
// for class-category skill lines on Paragon characters; mod-paragon
// calls Player::learnSpell directly for the abilities the player
// actually purchased, including their attached passives. Profession,
// weapon, language, and racial skill cascades stay enabled so things
// like recipe auto-learn, weapon proficiencies, and racial perks
// still work.
//
// Carve-out: SKILL_RUNEFORGING (776) is a CLASS-category skill but
// behaves like a profession in this context — the player buys ONE
// panel ability (Runeforging, spell 53428) and the rune-enchant
// spells (Rune of the Fallen Crusader, Razorice, Cinderglacier, ...)
// are supposed to come along for the ride via the standard SLA
// cascade, exactly the same way they do for a stock DK. Without
// this carve-out, the early-return below blocks the cascade and a
// Paragon who buys Runeforging gets the skill but no actual rune
// options at the runeforge anvil. The cascade only fires once per
// skill-grant for 776 (it's not on UpdateSkillsForLevel) so the
// "leaking back into the spellbook" concern that motivates the
// early-return doesn't apply to this skill.
if (getClass() == CLASS_PARAGON && skill_id != SKILL_RUNEFORGING)
{
if (SkillLineEntry const* sl = sSkillLineStore.LookupEntry(skill_id))
if (sl->categoryId == SKILL_CATEGORY_CLASS)
return;
}
// Get all abilities for this skill and sort by MinSkillLineRank (lowest to highest) // Get all abilities for this skill and sort by MinSkillLineRank (lowest to highest)
auto abilities = GetSkillLineAbilitiesBySkillLine(skill_id); auto abilities = GetSkillLineAbilitiesBySkillLine(skill_id);
std::vector<SkillLineAbilityEntry const*> sortedAbilities(abilities.begin(), abilities.end()); std::vector<SkillLineAbilityEntry const*> sortedAbilities(abilities.begin(), abilities.end());
@@ -12555,6 +12606,31 @@ bool Player::HasItemFitToSpellRequirements(SpellInfo const* spellInfo, Item cons
if (spellInfo->EquippedItemClass < 0) if (spellInfo->EquippedItemClass < 0)
return true; return true;
// Fractured / Paragon: cross-class wildcard relaxes the weapon-subclass
// gate ONLY for the curated allowlist of cross-class proc talents
// (currently just Maelstrom Weapon 51528-51532) so the passive talent
// aura attaches when the player wields a non-stock weapon. Weapon-
// specialization talents (Sword/Mace Specialization, Hack and Slash,
// Two-Handed Weapon Specialization, etc.) deliberately stay weapon-
// gated -- they're meant to bind to a specific weapon type. Still
// requires *some* weapon equipped (unarmed Paragons don't auto-activate
// weapon talents). ITEM_CLASS_ARMOR (shield) is left alone -- shield-
// gated talents still need an actual shield.
if (spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON
&& IsParagonWildcardCaller(this)
&& IsParagonWeaponSubclassWildcardSpell(spellInfo->Id))
{
for (uint8 i = EQUIPMENT_SLOT_MAINHAND; i < EQUIPMENT_SLOT_TABARD; ++i)
if (Item const* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i))
if (item != ignoreItem)
if (ItemTemplate const* proto = item->GetTemplate())
if (proto->Class == ITEM_CLASS_WEAPON)
return true;
// No weapon equipped at all -- fall through to stock logic, which
// returns false for passive talent auras (correct: an unarmed
// Paragon shouldn't have weapon talents active).
}
// scan other equipped items for same requirements (mostly 2 daggers/etc) // scan other equipped items for same requirements (mostly 2 daggers/etc)
// for optimize check 2 used cases only // for optimize check 2 used cases only
switch (spellInfo->EquippedItemClass) switch (spellInfo->EquippedItemClass)
+1
View File
@@ -1828,6 +1828,7 @@ public:
uint32 GetLastPotionId() { return m_lastPotionId; } uint32 GetLastPotionId() { return m_lastPotionId; }
void SetLastPotionId(uint32 item_id) { m_lastPotionId = item_id; } void SetLastPotionId(uint32 item_id) { m_lastPotionId = item_id; }
void UpdatePotionCooldown(Spell* spell = nullptr); void UpdatePotionCooldown(Spell* spell = nullptr);
void AtEnterCombat() override;
void AtExitCombat() override; void AtExitCombat() override;
void setResurrectRequestData(ObjectGuid guid, uint32 mapId, float X, float Y, float Z, uint32 health, uint32 mana) void setResurrectRequestData(ObjectGuid guid, uint32 mapId, float X, float Y, float Z, uint32 health, uint32 mana)
@@ -32,6 +32,7 @@
#include "Player.h" #include "Player.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
#include "SkillDiscovery.h" #include "SkillDiscovery.h"
#include "Spell.h"
#include "SpellAuraEffects.h" #include "SpellAuraEffects.h"
#include "SpellMgr.h" #include "SpellMgr.h"
#include "UpdateFieldFlags.h" #include "UpdateFieldFlags.h"
@@ -332,6 +333,28 @@ void Player::Update(uint32 p_time)
} }
} }
if (m_additionalSaveTimer)
{
if (p_time >= m_additionalSaveTimer)
{
m_additionalSaveTimer = 0;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
if (m_additionalSaveMask & ADDITIONAL_SAVING_INVENTORY_AND_GOLD)
SaveInventoryAndGoldToDB(trans);
if (m_additionalSaveMask & ADDITIONAL_SAVING_QUEST_STATUS)
_SaveQuestStatus(trans);
CharacterDatabase.CommitTransaction(trans);
m_additionalSaveMask = 0;
}
else
{
m_additionalSaveTimer -= p_time;
}
}
// Handle Water/drowning // Handle Water/drowning
HandleDrowning(p_time); HandleDrowning(p_time);
@@ -1539,6 +1562,27 @@ void Player::UpdatePvP(bool state, bool _override)
sScriptMgr->OnPlayerPVPFlagChange(this, state); sScriptMgr->OnPlayerPVPFlagChange(this, state);
} }
void Player::AtEnterCombat()
{
Unit::AtEnterCombat();
// Fractured: cancel cast-time mount summon if combat starts mid-cast.
for (uint32 spellType = CURRENT_FIRST_NON_MELEE_SPELL; spellType < CURRENT_MAX_SPELL; ++spellType)
{
if (Spell* spell = GetCurrentSpell(CurrentSpellTypes(spellType)))
{
if (SpellInfo const* info = spell->GetSpellInfo())
{
if (info->IsCastTimeRidingMountSpell())
{
InterruptSpell(CurrentSpellTypes(spellType), false, false);
break;
}
}
}
}
}
void Player::AtExitCombat() void Player::AtExitCombat()
{ {
Unit::AtExitCombat(); Unit::AtExitCombat();
+30 -1
View File
@@ -385,6 +385,13 @@ void Player::UpdateAttackPowerAndDamage(bool ranged)
break; break;
} }
} }
else if (getClass() == CLASS_PARAGON)
{
// Fractured class 12: same hybrid curve as requested for Paragon UI
// (level*2 + AGI + STR - 20). Implemented in core so we do not rely
// on PlayerScript hooks in this hot path.
val2 = level * 2.0f + GetStat(STAT_AGILITY) + GetStat(STAT_STRENGTH) - 20.0f;
}
else else
{ {
val2 = GetStat(STAT_AGILITY) - 10.0f; val2 = GetStat(STAT_AGILITY) - 10.0f;
@@ -467,7 +474,25 @@ void Player::UpdateAttackPowerAndDamage(bool ranged)
switch (GetShapeshiftForm()) switch (GetShapeshiftForm())
{ {
case FORM_CAT: case FORM_CAT:
val2 = (GetLevel() * mLevelMult) + GetStat(STAT_STRENGTH) * 2.0f + GetStat(STAT_AGILITY) - 20.0f + weapon_bonus + m_baseFeralAP; // Fractured: Cat Form gets 2 AP per Agility instead of stock 1.
// Field reports said "weapons dont automatically feature feral
// AP on this server and nothing is currently rescaled, super
// low feral scale" -- specifically a CAT issue, not a bear
// issue (the resident bear had 11k AP, the resident cat was
// miles behind because Stam > AP and Armor > AP for bears
// hides the missing weapon-AP for them but cat's whole
// mainline is melee crits scaling off AP). The cleanest knob
// that does NOT touch bear is the AGI multiplier in this
// switch -- bears get STR*2 with no AGI term, so doubling
// the AGI coefficient lifts cat's primary scaling stat
// without re-buffing bear. Also pairs with the cat-form
// Master Shapeshifter buff in SpellAuraEffects.cpp's
// FORM_CAT branch (bp doubled there). Together that lands
// the resident Feral expert's recommendation
// ("instead of adding a new passive, you could probably
// just increase Cat Form's Master Shapeshifter value along
// with its tooltip, alongside buffing the agi scaling").
val2 = (GetLevel() * mLevelMult) + GetStat(STAT_STRENGTH) * 2.0f + GetStat(STAT_AGILITY) * 2.0f - 20.0f + weapon_bonus + m_baseFeralAP;
break; break;
case FORM_BEAR: case FORM_BEAR:
case FORM_DIREBEAR: case FORM_DIREBEAR:
@@ -481,6 +506,10 @@ void Player::UpdateAttackPowerAndDamage(bool ranged)
break; break;
} }
} }
else if (getClass() == CLASS_PARAGON)
{
val2 = level * 2.0f + GetStat(STAT_STRENGTH) + GetStat(STAT_AGILITY) - 20.0f;
}
else if (IsClass(CLASS_MAGE, CLASS_CONTEXT_STATS) || IsClass(CLASS_PRIEST, CLASS_CONTEXT_STATS) || IsClass(CLASS_WARLOCK, CLASS_CONTEXT_STATS)) else if (IsClass(CLASS_MAGE, CLASS_CONTEXT_STATS) || IsClass(CLASS_PRIEST, CLASS_CONTEXT_STATS) || IsClass(CLASS_WARLOCK, CLASS_CONTEXT_STATS))
{ {
val2 = GetStat(STAT_STRENGTH) - 10.0f; val2 = GetStat(STAT_STRENGTH) - 10.0f;
+209 -8
View File
@@ -72,11 +72,153 @@
#include "Util.h" #include "Util.h"
#include "Vehicle.h" #include "Vehicle.h"
#include "World.h" #include "World.h"
#include "WorldConfig.h"
#include "WorldPacket.h" #include "WorldPacket.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <limits> #include <limits>
// Fractured / Paragon: single source of truth for the runtime "is this
// caller eligible for the cross-class wildcard?" question. Centralizing
// here keeps every dependent behavior (family-name skip in
// SpellInfo::IsAffected, PERIODIC_LEECH disease counting in
// GetDiseasesByCaster, instant-cast intercept in Spell::prepare for
// Predator's / Nature's Swiftness, Vampiric Embrace CheckProc cross-family
// path, etc.) flipping in lockstep when the config flag is toggled.
bool IsParagonWildcardCaller(Unit const* listener)
{
return listener && listener->getClass() == CLASS_PARAGON
&& sWorld->getBoolConfig(CONFIG_PARAGON_WILDCARD_FAMILY);
}
// Fractured / Paragon: cross-class wildcard helper used by ad-hoc
// `switch (SpellFamilyName)` listener gates in Unit / SpellEffects /
// SpellAuraEffects. Returns true when the listener is a CLASS_PARAGON
// player and the wildcard config flag is enabled, otherwise falls back
// to strict family-name equality.
bool ParagonFamilyMatches(Unit const* listener, uint32 expectedFamily, uint32 actualFamily)
{
if (IsParagonWildcardCaller(listener))
return true;
return expectedFamily == actualFamily;
}
bool IsParagonWeaponSubclassWildcardSpell(uint32 spellId)
{
if (!spellId)
return false;
// Resolve to the first-rank id so callers can pass any rank.
uint32 firstRankId = spellId;
if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId))
if (SpellInfo const* first = info->GetFirstRankSpell())
firstRankId = first->Id;
switch (firstRankId)
{
// Maelstrom Weapon (talent ranks 51528 / 51529 / 51530 / 51531 / 51532).
// Cross-class proc talent that should fire off any equipped weapon
// for a Paragon caster (1H sword, polearm, staff, fist, dagger, etc.).
case 51528:
return true;
default:
return false;
}
}
bool IsFracturedExclusiveStanceSpell(uint32 spellId)
{
if (!spellId)
return false;
// Resolve to the first-rank id so callers can pass any rank. This means
// every rank of Aspect of the Hawk / Wild / Pack / Dragonhawk is covered
// by listing only the rank-1 id below; same for druid forms that have
// multiple ranks via talent (none in WotLK actually, but kept consistent).
uint32 firstRankId = spellId;
if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId))
if (SpellInfo const* first = info->GetFirstRankSpell())
firstRankId = first->Id;
switch (firstRankId)
{
// -- Warrior stances (engine-shapeshifts; engine already mutually
// excludes them with each other and with druid forms via
// AuraEffect::HandleAuraModShapeshift's RemoveAurasByType, but we
// list them here so they participate in the union with presences /
// aspects).
case 2457: // Battle Stance
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
case 768: // Cat Form
case 24858: // Moonkin Form
case 33891: // Tree of Life Form
// -- Druid utility forms (engine-shapeshifts; included per design
// decision 2026-05-11 -- player must drop Travel/Aquatic/Flight to
// apply Hawk / Frost Presence / Berserker Stance, and vice versa).
case 783: // Travel Form
case 1066: // Aquatic Form
case 33943: // Flight Form
case 40120: // Swift Flight Form
// -- Shaman utility form (engine-shapeshift FORM_GHOSTWOLF).
case 2645: // Ghost Wolf
// -- Rogue base stealth (engine-shapeshift FORM_STEALTH). Shadow
// Dance (51713) is intentionally NOT listed -- it is a 6s
// stealth-burst on a 60s CD, gating it would defeat its purpose.
case 1784: // Stealth
// -- Priest combat form (engine-shapeshift FORM_SHADOW).
case 15473: // Shadowform
// -- Warlock combat form (engine-shapeshift FORM_METAMORPHOSIS).
case 47241: // Metamorphosis
// -- Death Knight Presences. NOT engine-shapeshifts in stock AC --
// they are regular auras that the client just renders in the
// stance bar -- which is exactly why stock DK can stack them on
// top of Bear Form / Defensive Stance / Aspect of the Hawk on a
// Paragon character. Listing them here is what plugs the gap.
case 48266: // Blood Presence
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
// aspects -- included per design decision so a hunter must pick
// between Hawk and Cheetah (no more "always Hawk while running",
// matches Ascension's nerf rationale for Monkey).
case 13165: // Aspect of the Hawk (rank 1; ranks 14318/14319/14320/14321/14322/25296/27044 covered via first-rank)
case 5118: // Aspect of the Cheetah
case 13159: // Aspect of the Pack (rank 1; rank 27047 covered via first-rank)
case 20043: // Aspect of the Wild (rank 1; ranks 20190/27045 covered via first-rank)
case 13161: // Aspect of the Beast
case 13163: // Aspect of the Monkey
case 34074: // Aspect of the Viper
case 61846: // Aspect of the Dragonhawk (rank 1; rank 61847 covered via first-rank)
return true;
default:
return false;
}
}
float baseMoveSpeed[MAX_MOVE_TYPE] = float baseMoveSpeed[MAX_MOVE_TYPE] =
{ {
2.5f, // MOVE_WALK 2.5f, // MOVE_WALK
@@ -4361,6 +4503,13 @@ bool Unit::IsMovementPreventedByCasting() const
return false; return false;
} }
// Fractured: cast-time mount summon may be completed while moving.
if (Spell* genSpell = m_currentSpells[CURRENT_GENERIC_SPELL])
{
if (genSpell->getState() == SPELL_STATE_PREPARING && genSpell->m_spellInfo->IsCastTimeRidingMountSpell())
return false;
}
// channeled spells during channel stage (after the initial cast timer) allow movement with a specific spell attribute // channeled spells during channel stage (after the initial cast timer) allow movement with a specific spell attribute
if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL])
{ {
@@ -6122,17 +6271,40 @@ AuraEffect* Unit::IsScriptOverriden(SpellInfo const* spell, int32 script) const
uint32 Unit::GetDiseasesByCaster(ObjectGuid casterGUID, uint8 mode) uint32 Unit::GetDiseasesByCaster(ObjectGuid casterGUID, uint8 mode)
{ {
static const AuraType diseaseAuraTypes[] = ObjectGuid drwGUID;
// Fractured / Paragon: when the caller (the unit whose strike is
// counting diseases -- e.g. Death Strike heal, Blood Strike / Heart
// Strike / Obliterate per-disease damage, Glyph of Scourge Strike
// refresh) is a CLASS_PARAGON player AND Paragon.WildcardFamilyMatching
// is on, also walk SPELL_AURA_PERIODIC_LEECH. That picks up Priest
// Devouring Plague, which uses ApplyAuraName 53 (PERIODIC_LEECH) instead
// of 3 (PERIODIC_DAMAGE) and is therefore invisible to the stock loop
// even though its Dispel field is DISPEL_DISEASE. A full Spell.dbc scan
// confirms Devouring Plague is the ONLY entry that satisfies both
// `Dispel == DISPEL_DISEASE` and a leech periodic effect, so this does
// not accidentally drag any other spell into the disease pool. Stock
// (non-Paragon) callers fall through to the original 2-entry iteration
// and observe identical behavior.
bool paragonWildcardLeech = false;
if (Player* playerCaster = ObjectAccessor::GetPlayer(*this, casterGUID))
{
drwGUID = playerCaster->getRuneWeaponGUID();
paragonWildcardLeech = IsParagonWildcardCaller(playerCaster);
}
AuraType diseaseAuraTypes[4] =
{ {
SPELL_AURA_PERIODIC_DAMAGE, // Frost Fever and Blood Plague SPELL_AURA_PERIODIC_DAMAGE, // Frost Fever and Blood Plague
SPELL_AURA_LINKED, // Crypt Fever and Ebon Plague SPELL_AURA_LINKED, // Crypt Fever and Ebon Plague
SPELL_AURA_NONE,
SPELL_AURA_NONE SPELL_AURA_NONE
}; };
if (paragonWildcardLeech)
ObjectGuid drwGUID; {
diseaseAuraTypes[2] = SPELL_AURA_PERIODIC_LEECH; // Priest Devouring Plague (Paragon-only)
if (Player* playerCaster = ObjectAccessor::GetPlayer(*this, casterGUID)) diseaseAuraTypes[3] = SPELL_AURA_NONE;
drwGUID = playerCaster->getRuneWeaponGUID(); }
uint32 diseases = 0; uint32 diseases = 0;
for (uint8 index = 0; diseaseAuraTypes[index] != SPELL_AURA_NONE; ++index) for (uint8 index = 0; diseaseAuraTypes[index] != SPELL_AURA_NONE; ++index)
@@ -9046,6 +9218,21 @@ int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask)
DoneAdvertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus(); DoneAdvertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus();
DoneAdvertisedBenefit += ToPlayer()->GetBaseSpellDamageBonus(); DoneAdvertisedBenefit += ToPlayer()->GetBaseSpellDamageBonus();
// Fractured class 12 (Paragon) intrinsic spell power:
// SP = level*2 + INT + SPI - 20 (clamped at 0)
// Read live from current stats so character-sheet refreshes (via
// UpdateSpellDamageAndHealingBonus) and live spell casts both see the
// up-to-date value with no script hooks or m_baseSpellPower mutation.
if (ToPlayer()->getClass() == CLASS_PARAGON)
{
int32 paragonSP = int32(GetLevel()) * 2
+ int32(GetStat(STAT_INTELLECT))
+ int32(GetStat(STAT_SPIRIT))
- 20;
if (paragonSP > 0)
DoneAdvertisedBenefit += paragonSP;
}
// Damage bonus from stats // Damage bonus from stats
AuraEffectList const& mDamageDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT); AuraEffectList const& mDamageDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT);
for (AuraEffectList::const_iterator i = mDamageDoneOfStatPercent.begin(); i != mDamageDoneOfStatPercent.end(); ++i) for (AuraEffectList::const_iterator i = mDamageDoneOfStatPercent.begin(); i != mDamageDoneOfStatPercent.end(); ++i)
@@ -9687,7 +9874,7 @@ uint32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, u
// Nourish cast - 20% bonus if target has Rejuvenation, Regrowth, Lifebloom, or Wild Growth from caster // Nourish cast - 20% bonus if target has Rejuvenation, Regrowth, Lifebloom, or Wild Growth from caster
// Glyph of Nourish is handled by spell_dru_nourish script // Glyph of Nourish is handled by spell_dru_nourish script
if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[1] & 0x2000000 && caster) if (ParagonFamilyMatches(caster, SPELLFAMILY_DRUID, spellProto->SpellFamilyName) && spellProto->SpellFamilyFlags[1] & 0x2000000 && caster)
{ {
AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL); AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL);
for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i)
@@ -9803,6 +9990,20 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask)
AdvertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus(); AdvertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus();
AdvertisedBenefit += ToPlayer()->GetBaseSpellHealingBonus(); AdvertisedBenefit += ToPlayer()->GetBaseSpellHealingBonus();
// Fractured class 12 (Paragon) intrinsic spell power: same level*2 +
// INT + SPI - 20 floor as on the damage side (the character sheet
// shows a single Spell Power value, so both sides must add the same
// bonus).
if (ToPlayer()->getClass() == CLASS_PARAGON)
{
int32 paragonSP = int32(GetLevel()) * 2
+ int32(GetStat(STAT_INTELLECT))
+ int32(GetStat(STAT_SPIRIT))
- 20;
if (paragonSP > 0)
AdvertisedBenefit += paragonSP;
}
// Healing bonus from stats // Healing bonus from stats
AuraEffectList const& mHealingDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT); AuraEffectList const& mHealingDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT);
for (AuraEffectList::const_iterator i = mHealingDoneOfStatPercent.begin(); i != mHealingDoneOfStatPercent.end(); ++i) for (AuraEffectList::const_iterator i = mHealingDoneOfStatPercent.begin(); i != mHealingDoneOfStatPercent.end(); ++i)
@@ -10392,7 +10593,7 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT
uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask(); uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask();
// Shred, Maul - "Effects which increase Bleed damage also increase Shred damage" // Shred, Maul - "Effects which increase Bleed damage also increase Shred damage"
if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[0] & 0x00008800) if (ParagonFamilyMatches(attacker, SPELLFAMILY_DRUID, spellProto->SpellFamilyName) && spellProto->SpellFamilyFlags[0] & 0x00008800)
mechanicMask |= (1ULL << MECHANIC_BLEED); mechanicMask |= (1ULL << MECHANIC_BLEED);
if (mechanicMask) if (mechanicMask)
+60
View File
@@ -2268,6 +2268,66 @@ private:
ValuesUpdateCache _valuesUpdateCache; ValuesUpdateCache _valuesUpdateCache;
}; };
// Fractured / Paragon: returns true iff `listener` is a CLASS_PARAGON player
// AND `Paragon.WildcardFamilyMatching` is enabled. Single source of truth for
// the gate that controls every cross-class wildcard path (family-name skip in
// SpellInfo::IsAffected, leech-aura disease counting in
// Unit::GetDiseasesByCaster, the cross-school instant-cast intercept in
// Spell::prepare for Predator's / Nature's Swiftness, the Vampiric Embrace
// CheckProc cross-family path, etc.). Centralizing the check means runtime
// kill-switching the wildcard config flips every behavior together.
[[nodiscard]] bool IsParagonWildcardCaller(Unit const* listener);
// Fractured / Paragon: helper for ad-hoc `switch (SpellFamilyName)` listener
// gates scattered across Unit / SpellEffects / SpellAuraEffects. When the
// listener (i.e. the unit holding the gating talent / aura) is a Paragon
// AND `Paragon.WildcardFamilyMatching` is enabled, accept any source family
// so cross-class procs / bonuses can fire. Stock classes use stock equality.
// Defined inline here so call sites do not need an extra include for World.h
// beyond what they already include via Unit.h's transitive headers.
[[nodiscard]] bool ParagonFamilyMatches(Unit const* listener, uint32 expectedFamily, uint32 actualFamily);
// Fractured / Paragon: returns true iff `spellId` is on the curated allowlist
// of cross-class proc talents whose `EquippedItemSubClassMask` should be
// bypassed for Paragon casters (e.g. Maelstrom Weapon ranks 51528-51532).
// Used by `Player::HasItemFitToSpellRequirements`,
// `Player::CheckAttackFitToAuraRequirement`, and
// `Aura::IsProcTriggeredOnEvent` to let these specific talents fire from
// any equipped weapon while keeping weapon-specialization talents
// (Sword Specialization, Mace Specialization, Hack and Slash, Two-Handed
// Weapon Specialization, etc.) gated by their stock weapon-class mask.
// The allowlist is matched against `SpellInfo::GetFirstRankSpell()`'s id
// so all ranks of a talent are covered by listing the rank-1 id.
[[nodiscard]] bool IsParagonWeaponSubclassWildcardSpell(uint32 spellId);
// Fractured: returns true iff `spellId` is one of the cross-class
// "stance-like" auras that we treat as mutually exclusive on this server,
// regardless of the caster's class. Stock AC engine-shapeshifts (warrior
// stances, druid forms, Shadowform, Metamorphosis) already auto-replace
// each other via `RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT)` in
// `AuraEffect::HandleAuraModShapeshift`, but DK Presences and Hunter
// Aspects are regular auras (the client just renders them in the stance
// bar), so they coexist with shapeshifts in stock AC. The Fractured rule
// makes the entire union mutually exclusive: warrior stances + druid
// forms (combat AND utility -- Travel/Aquatic/Flight/Swift Flight) +
// Ghost Wolf + Stealth + Shadowform + Metamorphosis + DK Presences +
// Hunter Aspects (combat AND utility -- Cheetah/Pack). Casting any of
// them removes any other from the same set, so e.g. a Paragon DK can no
// longer stack Frost Presence on top of Bear Form, and a hunter must
// pick between Hawk and Cheetah even out of combat.
//
// The set is matched against `SpellInfo::GetFirstRankSpell()`'s id so
// every rank of every aspect / form is covered by listing the rank-1 id.
// Server-wide -- this is *not* gated on CLASS_PARAGON because the only
// stock-class-only effect of the rule (a DK losing Travel Form when
// they cast Frost Presence) is impossible: stock DKs cannot shapeshift.
// Used by `Aura::CanStackWith` to refuse stacking, which then drives
// `Unit::_RemoveNoStackAurasDueToAura` to drop the older aura -- the
// same mechanism Battle Elixirs / Curses already use (spell_group rule
// SPELL_GROUP_STACK_RULE_EXCLUSIVE), implemented in C++ here so we do
// not have to enumerate every rank of every aspect / form in SQL.
[[nodiscard]] bool IsFracturedExclusiveStanceSpell(uint32 spellId);
namespace Acore namespace Acore
{ {
// Binary predicate for sorting Units based on percent value of a power // Binary predicate for sorting Units based on percent value of a power
+7 -1
View File
@@ -99,7 +99,13 @@ enum ShapeshiftForm
FORM_FLIGHT = 0x1D, FORM_FLIGHT = 0x1D,
FORM_STEALTH = 0x1E, FORM_STEALTH = 0x1E,
FORM_MOONKIN = 0x1F, 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 enum ShapeshiftFlags
@@ -1373,12 +1373,15 @@ void AuraEffect::HandleShapeshiftBoosts(Unit* target, bool apply) const
HotWSpellId = 24899; HotWSpellId = 24899;
break; break;
case FORM_BATTLESTANCE: case FORM_BATTLESTANCE:
case FORM_PARAGON_BATTLE_STANCE:
spellId = 21156; spellId = 21156;
break; break;
case FORM_DEFENSIVESTANCE: case FORM_DEFENSIVESTANCE:
case FORM_PARAGON_DEFENSIVE_STANCE:
spellId = 7376; spellId = 7376;
break; break;
case FORM_BERSERKERSTANCE: case FORM_BERSERKERSTANCE:
case FORM_PARAGON_BERSERKER_STANCE:
spellId = 7381; spellId = 7381;
break; break;
case FORM_MOONKIN: case FORM_MOONKIN:
@@ -1545,7 +1548,21 @@ void AuraEffect::HandleShapeshiftBoosts(Unit* target, bool apply) const
// Master Shapeshifter - Cat // Master Shapeshifter - Cat
if (AuraEffect const* aurEff = target->GetDummyAuraEffect(SPELLFAMILY_GENERIC, 2851, 0)) if (AuraEffect const* aurEff = target->GetDummyAuraEffect(SPELLFAMILY_GENERIC, 2851, 0))
{ {
int32 bp = aurEff->GetAmount(); // Fractured: cat-only Master Shapeshifter bonus is
// doubled (rank 1: 2% -> 4%, rank 2: 4% -> 8%) to
// make Feral Cat builds feel less "super low feral
// scale" without touching bear / moonkin / tree (the
// FORM_BEAR / FORM_MOONKIN / FORM_TREE branches
// below pass `bp` straight through, unchanged). The
// talent's own SpellInfo Effects[].BasePoints is
// intentionally NOT bumped -- aurEff->GetAmount()
// returns the per-rank talent value (2 / 4) shared
// across all four forms, so we apply the cat
// multiplier here at the cast site, leaving every
// other form on the stock value. Pairs with the
// Cat-Form AGI doubling in StatSystem.cpp's
// UpdateAttackPowerAndDamage FORM_CAT branch.
int32 bp = aurEff->GetAmount() * 2;
target->CastCustomSpell(target, 48420, &bp, nullptr, nullptr, true); target->CastCustomSpell(target, 48420, &bp, nullptr, nullptr, true);
} }
break; break;
@@ -1630,7 +1647,7 @@ void AuraEffect::HandleShapeshiftBoosts(Unit* target, bool apply) const
// Xinef: Remove autoattack spells // Xinef: Remove autoattack spells
if (Spell* spell = target->GetCurrentSpell(CURRENT_MELEE_SPELL)) if (Spell* spell = target->GetCurrentSpell(CURRENT_MELEE_SPELL))
if (spell->GetSpellInfo()->CheckShapeshift(newAura ? newAura->GetMiscValue() : 0) != SPELL_CAST_OK) if (spell->GetSpellInfo()->CheckShapeshift(newAura ? newAura->GetMiscValue() : 0, target) != SPELL_CAST_OK)
spell->cancel(true); spell->cancel(true);
} }
} }
@@ -1981,7 +1998,7 @@ void AuraEffect::HandlePhase(AuraApplication const* aurApp, uint8 mode, bool app
/*** UNIT MODEL ***/ /*** 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)) if (!(mode & AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK))
return; return;
@@ -1990,8 +2007,6 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
uint32 modelid = 0; uint32 modelid = 0;
Powers PowerType = POWER_MANA; Powers PowerType = POWER_MANA;
ShapeshiftForm form = ShapeshiftForm(GetMiscValue());
switch (form) switch (form)
{ {
case FORM_CAT: // 0x01 case FORM_CAT: // 0x01
@@ -2005,6 +2020,9 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
case FORM_BATTLESTANCE: // 0x11 case FORM_BATTLESTANCE: // 0x11
case FORM_DEFENSIVESTANCE: // 0x12 case FORM_DEFENSIVESTANCE: // 0x12
case FORM_BERSERKERSTANCE: // 0x13 case FORM_BERSERKERSTANCE: // 0x13
case FORM_PARAGON_BATTLE_STANCE:
case FORM_PARAGON_DEFENSIVE_STANCE:
case FORM_PARAGON_BERSERKER_STANCE:
PowerType = POWER_RAGE; PowerType = POWER_RAGE;
break; break;
@@ -2035,10 +2053,10 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
case FORM_SPIRITOFREDEMPTION: // 0x20 case FORM_SPIRITOFREDEMPTION: // 0x20
break; break;
default: 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) if (apply)
{ {
@@ -2073,8 +2091,8 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
// remove other shapeshift before applying a new one // remove other shapeshift before applying a new one
// xinef: rogue shouldnt be wrapped by this check (shadow dance vs stealth) // xinef: rogue shouldnt be wrapped by this check (shadow dance vs stealth)
if (GetSpellInfo()->SpellFamilyName != SPELLFAMILY_ROGUE) if (aurEff->GetSpellInfo()->SpellFamilyName != SPELLFAMILY_ROGUE)
target->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT, ObjectGuid::Empty, GetBase()); target->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT, ObjectGuid::Empty, aurEff->GetBase());
// stop handling the effect if it was removed by linked event // stop handling the effect if it was removed by linked event
if (aurApp->GetRemoveMode()) if (aurApp->GetRemoveMode())
@@ -2098,13 +2116,13 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
if (AuraEffect const* dummy = target->GetDummyAuraEffect(SPELLFAMILY_DRUID, 238, 0)) if (AuraEffect const* dummy = target->GetDummyAuraEffect(SPELLFAMILY_DRUID, 238, 0))
FurorChance = std::max(dummy->GetAmount(), 0); FurorChance = std::max(dummy->GetAmount(), 0);
switch (GetMiscValue()) switch (aurEff->GetMiscValue())
{ {
case FORM_CAT: case FORM_CAT:
{ {
int32 basePoints = int32(std::min(oldPower, FurorChance)); int32 basePoints = int32(std::min(oldPower, FurorChance));
target->SetPower(POWER_ENERGY, 0); 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; break;
} }
case FORM_BEAR: case FORM_BEAR:
@@ -2178,13 +2196,16 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
case FORM_BATTLESTANCE: case FORM_BATTLESTANCE:
case FORM_DEFENSIVESTANCE: case FORM_DEFENSIVESTANCE:
case FORM_BERSERKERSTANCE: case FORM_BERSERKERSTANCE:
case FORM_PARAGON_BATTLE_STANCE:
case FORM_PARAGON_DEFENSIVE_STANCE:
case FORM_PARAGON_BERSERKER_STANCE:
{ {
uint32 Rage_val = 0; uint32 Rage_val = 0;
// Defensive Tactics // Defensive Tactics
if (form == FORM_DEFENSIVESTANCE) if (form == FORM_DEFENSIVESTANCE || form == FORM_PARAGON_DEFENSIVE_STANCE)
{ {
if (AuraEffect const* aurEff = target->IsScriptOverriden(m_spellInfo, 831)) if (AuraEffect const* scriptEff = target->IsScriptOverriden(aurEff->GetSpellInfo(), 831))
Rage_val += aurEff->GetAmount() * 10; 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) // Stance mastery + Tactical mastery (both passive, and last have aura only in defense stance, but need apply at any stance switch)
if (target->IsPlayer()) if (target->IsPlayer())
@@ -2224,7 +2245,7 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
// adding/removing linked auras // adding/removing linked auras
// add/remove the shapeshift aura's boosts // add/remove the shapeshift aura's boosts
HandleShapeshiftBoosts(target, apply); aurEff->HandleShapeshiftBoosts(target, apply);
if (target->IsPlayer()) if (target->IsPlayer())
target->ToPlayer()->InitDataForForm(); target->ToPlayer()->InitDataForForm();
@@ -2232,8 +2253,8 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
if (target->IsClass(CLASS_DRUID, CLASS_CONTEXT_ABILITY)) if (target->IsClass(CLASS_DRUID, CLASS_CONTEXT_ABILITY))
{ {
// Dash // Dash
if (AuraEffect* aurEff = target->GetAuraEffect(SPELL_AURA_MOD_INCREASE_SPEED, SPELLFAMILY_DRUID, 0, 0, 0x8)) if (AuraEffect* dashSpeedEff = target->GetAuraEffect(SPELL_AURA_MOD_INCREASE_SPEED, SPELLFAMILY_DRUID, 0, 0, 0x8))
aurEff->RecalculateAmount(); dashSpeedEff->RecalculateAmount();
// Disarm handling // Disarm handling
// If druid shifts while being disarmed we need to deal with that since forms aren't affected by disarm // If druid shifts while being disarmed we need to deal with that since forms aren't affected by disarm
@@ -2267,6 +2288,11 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
if (target->IsPlayer()) if (target->IsPlayer())
{ {
SpellShapeshiftFormEntry const* shapeInfo = sSpellShapeshiftFormStore.LookupEntry(form); 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 // 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) for (uint8 i = 0; i < MAX_SHAPESHIFT_SPELLS; ++i)
{ {
@@ -2280,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 void AuraEffect::HandleAuraTransform(AuraApplication const* aurApp, uint8 mode, bool apply) const
{ {
if (!(mode & AURA_EFFECT_HANDLE_SEND_FOR_CLIENT_MASK)) if (!(mode & AURA_EFFECT_HANDLE_SEND_FOR_CLIENT_MASK))
@@ -5160,6 +5191,20 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool
Unit* caster = GetCaster(); 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) if (mode & AURA_EFFECT_HANDLE_REAL)
{ {
// pet auras // pet auras
+44 -2
View File
@@ -1973,6 +1973,31 @@ bool Aura::CanStackWith(Aura const* existingAura) const
|| (sameCaster && m_spellInfo->IsAuraExclusiveBySpecificPerCasterWith(existingSpellInfo))) || (sameCaster && m_spellInfo->IsAuraExclusiveBySpecificPerCasterWith(existingSpellInfo)))
return false; return false;
// Fractured: cross-class stance / form / presence / aspect exclusivity.
// Stock AC's engine-shapeshift removal in HandleAuraModShapeshift only
// covers warrior stances, druid forms, Shadowform, Metamorphosis, etc.
// -- DK Presences and Hunter Aspects are regular auras (the client just
// happens to render them in the stance bar) and therefore stack with
// engine-shapeshifts in stock AC. The Fractured rule (server-wide --
// see IsFracturedExclusiveStanceSpell in Unit.h for the curated set
// and the design rationale) treats the union of stances + forms (combat
// AND utility) + presences + aspects as mutually exclusive. Refusing
// to stack here triggers the same _RemoveNoStackAurasDueToAura cleanup
// path that Battle Elixirs / Curses already use, so the older aura
// drops and the newly-cast one applies cleanly. Different ranks of the
// same talent (e.g. Hawk rank 4 -> Hawk rank 7) are NOT treated as
// exclusive with each other -- IsFracturedExclusiveStanceSpell resolves
// to first-rank ids, so we compare those.
if (IsFracturedExclusiveStanceSpell(m_spellInfo->Id) && IsFracturedExclusiveStanceSpell(existingSpellInfo->Id))
{
SpellInfo const* newFirst = m_spellInfo->GetFirstRankSpell();
SpellInfo const* oldFirst = existingSpellInfo->GetFirstRankSpell();
uint32 newFirstId = newFirst ? newFirst->Id : m_spellInfo->Id;
uint32 oldFirstId = oldFirst ? oldFirst->Id : existingSpellInfo->Id;
if (newFirstId != oldFirstId)
return false;
}
// check spell group stack rules // check spell group stack rules
switch (sSpellMgr->CheckSpellGroupStackRules(m_spellInfo, existingSpellInfo)) switch (sSpellMgr->CheckSpellGroupStackRules(m_spellInfo, existingSpellInfo))
{ {
@@ -2175,7 +2200,9 @@ uint8 Aura::GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo,
return 0; return 0;
// do checks against db data // do checks against db data
if (!sSpellMgr->CanSpellTriggerProcOnEvent(*procEntry, eventInfo)) // Fractured / Paragon: the unit that owns this aura is the listener;
// pass it through so cross-family procs can match for Paragon players.
if (!sSpellMgr->CanSpellTriggerProcOnEvent(*procEntry, eventInfo, aurApp->GetTarget()))
return 0; return 0;
// check if spell was affected by this aura's spellmod (used by Arcane Potency and similar effects) // check if spell was affected by this aura's spellmod (used by Arcane Potency and similar effects)
@@ -2249,8 +2276,23 @@ uint8 Aura::GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo,
item = target->ToPlayer()->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); item = target->ToPlayer()->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
} }
if (!item || item->IsBroken() || !item->IsFitToSpellRequirements(GetSpellInfo())) if (!item || item->IsBroken())
return 0; return 0;
if (!item->IsFitToSpellRequirements(GetSpellInfo()))
{
// Fractured / Paragon: cross-class wildcard relaxes the
// weapon-subclass gate ONLY for the curated allowlist of
// cross-class proc talents (currently just Maelstrom Weapon
// 51528-51532). Weapon-specialization talents (Sword/Mace
// Specialization, Hack and Slash, Two-Handed Weapon
// Specialization, etc.) deliberately stay weapon-gated for
// Paragon. Restricted to ITEM_CLASS_WEAPON so shield-gated
// talents still need an actual shield.
if (!(GetSpellInfo()->EquippedItemClass == ITEM_CLASS_WEAPON
&& IsParagonWildcardCaller(target)
&& IsParagonWeaponSubclassWildcardSpell(GetSpellInfo()->Id)))
return 0;
}
} }
} }
+155 -56
View File
@@ -51,6 +51,7 @@
#include "Vehicle.h" #include "Vehicle.h"
#include "World.h" #include "World.h"
#include "WorldPacket.h" #include "WorldPacket.h"
#include <array>
#include <cmath> #include <cmath>
/// @todo: this import is not necessary for compilation and marked as unused by the IDE /// @todo: this import is not necessary for compilation and marked as unused by the IDE
@@ -3540,16 +3541,66 @@ SpellCastResult Spell::prepare(SpellCastTargets const* targets, AuraEffect const
if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_CASTTIME)) if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_CASTTIME))
m_casttime = 0; m_casttime = 0;
// Fractured / Paragon: cross-class "next Nature spell becomes instant"
// intercept for the three buffs that share that semantic in 3.3.5:
//
// 69369 - Predator's Swiftness (Cataclysm proc payload triggered by
// our spell_paragon_predatory_strikes; see Paragon_SC.cpp)
// 17116 - Druid Nature's Swiftness
// 16188 - Shaman Nature's Swiftness
//
// All three apply SPELL_AURA_ADD_PCT_MODIFIER on SPELLMOD_CASTING_TIME
// gated by a Druid- or Shaman-only SpellClassMask, so a Paragon with the
// buff cannot instant-cast a Nature spell from a different family
// (e.g. a Druid NS Paragon casting Shaman Chain Lightning, or a Shaman
// NS Paragon casting Druid Healing Touch). Tooltip text on all three
// promises "next Nature spell with a base cast time below 10 sec becomes
// instant"; honor that here for CLASS_PARAGON callers when the wildcard
// config is on. The stock SpellMod path is untouched -- real Druids /
// Shamans / proc consumers continue to hit the existing class-mask code
// path unchanged.
if (Player* paragonCaster = m_caster->ToPlayer())
{
if (m_casttime > 0
&& IsParagonWildcardCaller(paragonCaster)
&& (m_spellInfo->SchoolMask & SPELL_SCHOOL_MASK_NATURE)
&& m_spellInfo->CastTimeEntry
&& !m_spellInfo->IsChanneled()
&& !HasTriggeredCastFlag(TRIGGERED_FULL_MASK)
&& m_spellInfo->CalcCastTime() < 10 * IN_MILLISECONDS)
{
static constexpr std::array<uint32, 3> kParagonNatureInstantBuffs =
{
69369u, // Predator's Swiftness (Paragon proc payload)
17116u, // Druid Nature's Swiftness
16188u // Shaman Nature's Swiftness
};
for (uint32 buffId : kParagonNatureInstantBuffs)
{
if (paragonCaster->HasAura(buffId))
{
m_casttime = 0;
paragonCaster->RemoveAurasDueToSpell(buffId);
break; // consume only one buff per cast
}
}
}
}
// don't allow channeled spells / spells with cast time to be casted while moving // don't allow channeled spells / spells with cast time to be casted while moving
// (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in) // (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in)
// Fractured: cast-time mount summons (SPELL_AURA_MOUNTED + non-zero base cast) may be started while moving.
if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->IsPlayer() && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT && !IsTriggered()) if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->IsPlayer() && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT && !IsTriggered())
{ {
// 1. Has casttime, 2. Or doesn't have flag to allow action during channel if (!m_spellInfo->IsCastTimeRidingMountSpell())
if (m_casttime || !m_spellInfo->IsActionAllowedChannel())
{ {
SendCastResult(SPELL_FAILED_MOVING); // 1. Has casttime, 2. Or doesn't have flag to allow action during channel
finish(false); if (m_casttime || !m_spellInfo->IsActionAllowedChannel())
return SPELL_FAILED_MOVING; {
SendCastResult(SPELL_FAILED_MOVING);
finish(false);
return SPELL_FAILED_MOVING;
}
} }
} }
@@ -4389,9 +4440,11 @@ void Spell::update(uint32 difftime)
// check if the player caster has moved before the spell finished // check if the player caster has moved before the spell finished
// xinef: added preparing state (real cast, skip channels as they have other flags for this) // xinef: added preparing state (real cast, skip channels as they have other flags for this)
// Fractured: cast-time mount summons are allowed to complete while moving.
if ((m_caster->IsPlayer() && m_timer != 0) && if ((m_caster->IsPlayer() && m_timer != 0) &&
m_caster->isMoving() && (m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) && m_spellState == SPELL_STATE_PREPARING && m_caster->isMoving() && (m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) && m_spellState == SPELL_STATE_PREPARING &&
(m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK || !m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR))) (m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK || !m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR)) &&
!m_spellInfo->IsCastTimeRidingMountSpell())
{ {
// don't cancel for melee, autorepeat, triggered and instant spells // don't cancel for melee, autorepeat, triggered and instant spells
if (!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !IsTriggered()) if (!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !IsTriggered())
@@ -5722,7 +5775,7 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* /*param1*/, uint32* /*para
if (checkForm) if (checkForm)
{ {
// Cannot be used in this stance/form // Cannot be used in this stance/form
SpellCastResult shapeError = m_spellInfo->CheckShapeshift(m_caster->GetShapeshiftForm()); SpellCastResult shapeError = m_spellInfo->CheckShapeshift(m_caster->GetShapeshiftForm(), m_caster);
if (shapeError != SPELL_CAST_OK) if (shapeError != SPELL_CAST_OK)
return shapeError; return shapeError;
@@ -5768,6 +5821,10 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* /*param1*/, uint32* /*para
if (reqCombat && m_caster->IsInCombat() && !m_spellInfo->CanBeUsedInCombat()) if (reqCombat && m_caster->IsInCombat() && !m_spellInfo->CanBeUsedInCombat())
return SPELL_FAILED_AFFECTING_COMBAT; return SPELL_FAILED_AFFECTING_COMBAT;
// Fractured: cast-time mount summons cannot be used while in combat (even if DBC omits NOT_IN_COMBAT_ONLY_PEACEFUL).
if (reqCombat && m_caster->IsInCombat() && m_spellInfo->IsCastTimeRidingMountSpell())
return SPELL_FAILED_AFFECTING_COMBAT;
} }
// Xinef: exploit protection // Xinef: exploit protection
@@ -5795,10 +5852,14 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* /*param1*/, uint32* /*para
// (not wand currently autorepeat cast delayed to moving stop anyway in spell update code) // (not wand currently autorepeat cast delayed to moving stop anyway in spell update code)
if (m_caster->IsPlayer() && m_caster->ToPlayer()->isMoving() && !IsTriggered()) if (m_caster->IsPlayer() && m_caster->ToPlayer()->isMoving() && !IsTriggered())
{ {
// skip stuck spell to allow use it in falling case and apply spell limitations at movement // Fractured: cast-time mount summons may be started while moving.
if ((!m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR) || m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK) && if (!m_spellInfo->IsCastTimeRidingMountSpell())
(IsAutoRepeat() || (m_spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) != 0)) {
return SPELL_FAILED_MOVING; // skip stuck spell to allow use it in falling case and apply spell limitations at movement
if ((!m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR) || m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK) &&
(IsAutoRepeat() || (m_spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) != 0))
return SPELL_FAILED_MOVING;
}
} }
Vehicle* vehicle = m_caster->GetVehicle(); Vehicle* vehicle = m_caster->GetVehicle();
@@ -7685,56 +7746,64 @@ SpellCastResult Spell::CheckItems(uint32* param1, uint32* param2)
switch (pItem->GetTemplate()->SubClass) switch (pItem->GetTemplate()->SubClass)
{ {
case ITEM_SUBCLASS_WEAPON_THROWN: case ITEM_SUBCLASS_WEAPON_THROWN:
{ {
uint32 ammo = pItem->GetEntry(); // Fractured: thrown abilities behave like DK runes -- they
if (!m_caster->ToPlayer()->HasItemCount(ammo)) // remain usable even when the player has run out of the
return SPELL_FAILED_NO_AMMO; // throwing item. Stock AC returned SPELL_FAILED_NO_AMMO
}; // here; we just drop the gate. Spell::TakeAmmo's stack
// decrement is wrapped in a HasItemCount check via
// DestroyItemCount and will silently no-op at zero. The
// ranged-DPS bonus naturally vanishes when the stack runs
// out, so the player still throws but loses the per-shot
// damage contribution from the throwing item.
break; break;
};
case ITEM_SUBCLASS_WEAPON_GUN: case ITEM_SUBCLASS_WEAPON_GUN:
case ITEM_SUBCLASS_WEAPON_BOW: case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW: case ITEM_SUBCLASS_WEAPON_CROSSBOW:
{ {
uint32 ammo = m_caster->ToPlayer()->GetUInt32Value(PLAYER_AMMO_ID); // Fractured: ranged abilities behave like DK runes -- they
if (!ammo) // remain usable when the player has no ammo loaded or the
{ // quiver / pouch is empty. The DPS-bonus path (StatSystem.cpp:
// Requires No Ammo // `weaponMin/MaxDamage += GetAmmoDPS() * attackSpeedMod`)
if (m_caster->HasAura(46699)) // reads `m_ammoDPS`, which is 0 when no ammo is loaded and
break; // skip other checks // recomputed via Player::_ApplyAmmoBonuses on equip / stack
// exhaustion, so a hunter with an empty bag still casts
return SPELL_FAILED_NO_AMMO; // Steady Shot / Aimed Shot etc. -- they just lose the arrow
} // / bullet DPS contribution.
//
ItemTemplate const* ammoProto = sObjectMgr->GetItemTemplate(ammo); // We deliberately do NOT clear PLAYER_AMMO_ID when the bag
if (!ammoProto) // empties. Defense in depth alongside the data-side fix:
return SPELL_FAILED_NO_AMMO; //
// * The primary client-side fix lives in Spell.dbc --
if (ammoProto->Class != ITEM_CLASS_PROJECTILE) // SpellInfoCorrections.cpp's "drop EquippedItemClass on
return SPELL_FAILED_NO_AMMO; // hunter shot abilities" block (mirrored client-side by
// fractured-tooling/_patch_spell_dbc_hunter_ammo.py)
// check ammo ws. weapon compatibility // sets EquippedItemClass = -1 on every player-castable
switch (pItem->GetTemplate()->SubClass) // hunter shot, which removes the 3.3.5a client's
{ // "ranged weapon AND ammo slot non-empty" preflight
case ITEM_SUBCLASS_WEAPON_BOW: // gate entirely. After that, ammo is purely a
case ITEM_SUBCLASS_WEAPON_CROSSBOW: // server-side DPS bonus, never a hard requirement.
if (ammoProto->SubClass != ITEM_SUBCLASS_ARROW) //
return SPELL_FAILED_NO_AMMO; // * Keeping the (now-stale) ammo id in PLAYER_AMMO_ID
break; // field is harmless: TakeAmmo's DestroyItemCount
case ITEM_SUBCLASS_WEAPON_GUN: // silently no-ops when HasItemCount is 0, and
if (ammoProto->SubClass != ITEM_SUBCLASS_BULLET) // _ApplyAmmoBonuses already recomputes m_ammoDPS to 0
return SPELL_FAILED_NO_AMMO; // when the proto can no longer be found / the stack is
break; // empty. So the StatSystem.cpp ammo-DPS path gracefully
default: // degrades to "no bonus" the moment the bag goes empty.
return SPELL_FAILED_NO_AMMO; //
} // * Player un-equipping the ammo via the paper-doll
// right-click still routes through RemoveAmmo() and
if (!m_caster->ToPlayer()->HasItemCount(ammo)) // zeroes the field -- that is the player's explicit
{ // action and we leave it alone.
m_caster->ToPlayer()->SetUInt32Value(PLAYER_AMMO_ID, 0); //
return SPELL_FAILED_NO_AMMO; // Net result: hunter has bow + ammo -> full DPS; bow only ->
} // shots still fire, no ammo DPS; no bow -> client engine's
}; // own ranged-weapon gate still blocks (Auto Shot timer
// simply never spins up without a ranged weapon equipped).
break; break;
};
case ITEM_SUBCLASS_WEAPON_WAND: case ITEM_SUBCLASS_WEAPON_WAND:
break; break;
default: default:
@@ -7805,6 +7874,36 @@ SpellCastResult Spell::CheckSpellFocus()
// check spell focus object // check spell focus object
if (m_spellInfo->RequiresSpellFocus) if (m_spellInfo->RequiresSpellFocus)
{ {
// Fractured / Paragon: skip the GO proximity check for Paragon
// casters when the spell is a runeforge enchant (skill line 776).
// Paragons get a Runeforge tab in the Character Advancement
// panel that lets them apply rune-enchants from anywhere in the
// world -- no need to fly back to Acherus or find the nearest
// Eastern Plaguelands anvil. The skill-line gate keeps the
// bypass tightly scoped: only the 10 SkillLineAbility-tagged
// rune-enchant spells qualify, every other spell that uses
// SpellFocusObject (Enchanting bench, Cooking fire, Lockpicking
// anvil, etc.) keeps its requirement intact.
//
// DK / other class casters are unchanged -- this carve-out
// intentionally checks getClass() == CLASS_PARAGON rather than
// IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY) (which
// would also return true for Paragons via mod-paragon's hook).
// Stock DK behavior must stay vanilla; the QoL bypass is a
// class-12 feature only.
if (m_caster && m_caster->IsPlayer()
&& m_caster->ToPlayer()->getClass() == CLASS_PARAGON)
{
auto bounds = sSpellMgr->GetSkillLineAbilityMapBounds(m_spellInfo->Id);
for (auto it = bounds.first; it != bounds.second; ++it)
{
if (it->second->SkillLine == SKILL_RUNEFORGING)
{
return SPELL_CAST_OK;
}
}
}
CellCoord p(Acore::ComputeCellCoord(m_caster->GetPositionX(), m_caster->GetPositionY())); CellCoord p(Acore::ComputeCellCoord(m_caster->GetPositionX(), m_caster->GetPositionY()));
Cell cell(p); Cell cell(p);
+106 -3
View File
@@ -27,6 +27,8 @@
#include "SpellAuraDefines.h" #include "SpellAuraDefines.h"
#include "SpellAuraEffects.h" #include "SpellAuraEffects.h"
#include "SpellMgr.h" #include "SpellMgr.h"
#include "World.h"
#include "WorldConfig.h"
uint32 GetTargetFlagMask(SpellTargetObjectTypes objType) uint32 GetTargetFlagMask(SpellTargetObjectTypes objType)
{ {
@@ -907,6 +909,15 @@ bool SpellInfo::HasAura(AuraType aura) const
return false; return false;
} }
bool SpellInfo::IsCastTimeRidingMountSpell() const
{
if (IsChanneled())
return false;
if (!HasAura(SPELL_AURA_MOUNTED))
return false;
return CalcCastTime(nullptr, nullptr) > 0;
}
bool SpellInfo::HasAnyAura() const bool SpellInfo::HasAnyAura() const
{ {
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
@@ -1323,11 +1334,26 @@ bool SpellInfo::HasInitialAggro() const
} }
bool SpellInfo::IsAffected(uint32 familyName, flag96 const& familyFlags) const bool SpellInfo::IsAffected(uint32 familyName, flag96 const& familyFlags) const
{
return IsAffected(familyName, familyFlags, nullptr);
}
bool SpellInfo::IsAffected(uint32 familyName, flag96 const& familyFlags,
Unit const* listenerOwner) const
{ {
if (!familyName) if (!familyName)
return true; return true;
if (familyName != SpellFamilyName) // Fractured / Paragon: when the unit that owns the listening proc /
// spellmod aura is a Paragon, accept any source family. The class
// mask flag-bit check below still runs, so listeners that explicitly
// opt into a subset of spells via SpellFamilyFlags / class mask are
// still respected; only the family-name equality gate is wildcarded.
bool const wildcardFamily = listenerOwner
&& listenerOwner->getClass() == CLASS_PARAGON
&& sWorld->getBoolConfig(CONFIG_PARAGON_WILDCARD_FAMILY);
if (!wildcardFamily && familyName != SpellFamilyName)
return false; return false;
if (familyFlags && !(familyFlags & SpellFamilyFlags)) if (familyFlags && !(familyFlags & SpellFamilyFlags))
@@ -1342,6 +1368,11 @@ bool SpellInfo::IsAffectedBySpellMods() const
} }
bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const
{
return IsAffectedBySpellMod(mod, nullptr);
}
bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod, Unit const* listenerOwner) const
{ {
// xinef: dont check duration mod // xinef: dont check duration mod
if (mod->op != SPELLMOD_DURATION) if (mod->op != SPELLMOD_DURATION)
@@ -1356,7 +1387,43 @@ bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const
if (!sScriptMgr->OnIsAffectedBySpellModCheck(affectSpell, this, mod)) if (!sScriptMgr->OnIsAffectedBySpellModCheck(affectSpell, this, mod))
return true; return true;
return IsAffected(affectSpell->SpellFamilyName, mod->mask); if (IsAffected(affectSpell->SpellFamilyName, mod->mask, listenerOwner))
return true;
// Fractured / Paragon: explicit cross-family allowlist for specific
// listener auras whose SpellClassMask cannot otherwise bridge classes.
// The standard IsAffected wildcard relaxes SpellFamilyName equality but
// still requires SpellClassMask & SpellFamilyFlags to overlap; for these
// Paragon-only cross-class enablers the source spells live in different
// families with non-overlapping class bits, so we whitelist by mod owner
// spell ID + target spell first-rank ID. Stock classes never enter here
// because IsParagonWildcardCaller short-circuits on non-Paragon owners.
if (IsParagonWildcardCaller(listenerOwner))
{
switch (mod->spellId)
{
case 53817: // Shaman: Maelstrom Weapon
{
// Allow any rank of Mage Fireball / Frostbolt to benefit from
// the cast-time + cost reduction spellmod. Arcane Blast was on
// the allowlist briefly but proved too potent stacked with its
// own self-buff -- removed.
if (SpellFamilyName == SPELLFAMILY_MAGE)
{
SpellInfo const* first = GetFirstRankSpell();
uint32 firstId = first ? first->Id : Id;
if (firstId == 133 /*Fireball*/
|| firstId == 116 /*Frostbolt*/)
return true;
}
break;
}
default:
break;
}
}
return false;
} }
bool SpellInfo::CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const bool SpellInfo::CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const
@@ -1441,7 +1508,7 @@ bool SpellInfo::IsAuraExclusiveBySpecificPerCasterWith(SpellInfo const* spellInf
} }
} }
SpellCastResult SpellInfo::CheckShapeshift(uint32 form) const SpellCastResult SpellInfo::CheckShapeshift(uint32 form, Unit const* caster /*= nullptr*/) const
{ {
// talents that learn spells can have stance requirements that need ignore // talents that learn spells can have stance requirements that need ignore
// (this requirement only for client-side stance show in talent description) // (this requirement only for client-side stance show in talent description)
@@ -1449,6 +1516,38 @@ SpellCastResult SpellInfo::CheckShapeshift(uint32 form) const
(Effects[0].Effect == SPELL_EFFECT_LEARN_SPELL || Effects[1].Effect == SPELL_EFFECT_LEARN_SPELL || Effects[2].Effect == SPELL_EFFECT_LEARN_SPELL)) (Effects[0].Effect == SPELL_EFFECT_LEARN_SPELL || Effects[1].Effect == SPELL_EFFECT_LEARN_SPELL || Effects[2].Effect == SPELL_EFFECT_LEARN_SPELL))
return SPELL_CAST_OK; return SPELL_CAST_OK;
// Fractured / Paragon: Paragons learn Warrior abilities through Advancement
// without picking up Battle/Defensive/Berserker Stance, so stance-gated
// Warrior spells (e.g. Whirlwind, Sunder Armor, Shield Slam) would otherwise
// be uncastable. Bypass the stance check for Paragon casters on any spell
// that has a non-zero Stances bitmask, regardless of SpellFamilyName.
//
// We previously gated this on SpellFamilyName == SPELLFAMILY_WARRIOR, but a
// number of SPELLFAMILY_GENERIC spells (notably the iconic Warrior toolbox
// -- Berserker Rage 18499, Sunder Armor 7405 / 11596 / 11597 / 25225 /
// 47467, Charge 100 / 6178 / 11578, Pummel 6552 / 6554, Shield Bash 72 /
// 1671 / 1672 / 29704, Retaliation 20230, Recklessness 1719, Shield Wall
// 871, etc.) carry the Stances bitmask but live under SPELLFAMILY_GENERIC
// (family 0). The previous narrower gate let those re-trigger the stance
// failure for Paragons. Widening to "any non-zero Stances + Paragon" is
// safe because:
//
// * The bypass returns SPELL_CAST_OK only when IsParagonWildcardCaller
// is true -- stock classes never enter this branch.
// * Druid form-gated spells (Cat Form / Bear Form / Moonkin / Tree)
// still fire the Druid GCD/form rules elsewhere; CheckShapeshift is
// about *requiring* a form to cast, which is exactly what we want
// to bypass for Paragons (they never picked the form).
// * Item enchant scrolls and other shapeshift-marked utility spells
// remain unaffected because they aren't in a Paragon's spellbook.
if (Stances != 0 && IsParagonWildcardCaller(caster))
{
LOG_DEBUG("server.scripts",
"[paragon-diag] CheckShapeshift bypass: spell={} family={} stances=0x{:x} form={}",
Id, SpellFamilyName, Stances, form);
return SPELL_CAST_OK;
}
uint32 stanceMask = (form ? 1 << (form - 1) : 0); uint32 stanceMask = (form ? 1 << (form - 1) : 0);
if (stanceMask & StancesNot) // can explicitly not be casted in this stance if (stanceMask & StancesNot) // can explicitly not be casted in this stance
@@ -2057,6 +2156,10 @@ SpellSpecificType SpellInfo::LoadSpellSpecific() const
{ {
case SPELLFAMILY_GENERIC: 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) // Food / Drinks (mostly)
if (AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) if (AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED)
{ {
+16 -1
View File
@@ -434,6 +434,9 @@ public:
bool HasEffect(SpellEffects effect) const; bool HasEffect(SpellEffects effect) const;
bool HasEffectMechanic(Mechanics mechanic) const; bool HasEffectMechanic(Mechanics mechanic) const;
bool HasAura(AuraType aura) const; bool HasAura(AuraType aura) const;
/// Summon mount aura (SPELL_AURA_MOUNTED) with a non-zero base cast time from SpellCastTimes.dbc.
/// Used by Fractured mount rules: castable while moving, never in combat, interrupted on combat enter.
bool IsCastTimeRidingMountSpell() const;
bool HasAnyAura() const; bool HasAnyAura() const;
bool HasAreaAuraEffect() const; bool HasAreaAuraEffect() const;
bool HasOnlyDamageEffects() const; bool HasOnlyDamageEffects() const;
@@ -494,9 +497,21 @@ public:
bool HasInitialAggro() const; bool HasInitialAggro() const;
[[nodiscard]] bool IsAffected(uint32 familyName, flag96 const& familyFlags) const; [[nodiscard]] bool IsAffected(uint32 familyName, flag96 const& familyFlags) const;
// Fractured / Paragon overload. When `listenerOwner` is a CLASS_PARAGON
// unit and Paragon.WildcardFamilyMatching is enabled, the
// SpellFamilyName equality check is skipped (flag-bit check still runs)
// so cross-class procs / spellmods can react to the spell. Passing
// nullptr (or any non-Paragon unit) reproduces the stock 2-arg
// behavior; the 2-arg form forwards to this overload with nullptr.
[[nodiscard]] bool IsAffected(uint32 familyName, flag96 const& familyFlags,
Unit const* listenerOwner) const;
bool IsAffectedBySpellMods() const; bool IsAffectedBySpellMods() const;
bool IsAffectedBySpellMod(SpellModifier const* mod) const; bool IsAffectedBySpellMod(SpellModifier const* mod) const;
// Fractured / Paragon overload: pass the player who owns the modifier
// aura so wildcard-family matching can apply when that player is a
// Paragon. Stock callers may forward to this with nullptr.
bool IsAffectedBySpellMod(SpellModifier const* mod, Unit const* listenerOwner) const;
bool CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const; bool CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const;
bool CanDispelAura(SpellInfo const* auraSpellInfo) const; bool CanDispelAura(SpellInfo const* auraSpellInfo) const;
@@ -509,7 +524,7 @@ public:
bool IsAuraExclusiveBySpecificWith(SpellInfo const* spellInfo) const; bool IsAuraExclusiveBySpecificWith(SpellInfo const* spellInfo) const;
bool IsAuraExclusiveBySpecificPerCasterWith(SpellInfo const* spellInfo) const; bool IsAuraExclusiveBySpecificPerCasterWith(SpellInfo const* spellInfo) const;
SpellCastResult CheckShapeshift(uint32 form) const; SpellCastResult CheckShapeshift(uint32 form, Unit const* caster = nullptr) const;
SpellCastResult CheckLocation(uint32 map_id, uint32 zone_id, uint32 area_id, Player* player = nullptr, bool strict = true) const; SpellCastResult CheckLocation(uint32 map_id, uint32 zone_id, uint32 area_id, Player* player = nullptr, bool strict = true) const;
SpellCastResult CheckTarget(Unit const* caster, WorldObject const* target, bool implicit = true) const; SpellCastResult CheckTarget(Unit const* caster, WorldObject const* target, bool implicit = true) const;
SpellCastResult CheckExplicitTarget(Unit const* caster, WorldObject const* target, Item const* itemTarget = nullptr) const; SpellCastResult CheckExplicitTarget(Unit const* caster, WorldObject const* target, Item const* itemTarget = nullptr) const;

Some files were not shown because too many files have changed in this diff Show More