Compare commits

...

60 Commits

Author SHA1 Message Date
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
Docker Build 34cc87a5f9 mod-paragon: fix panel cascade sweep revoking talent-granted spells
RevokeUnwantedCascadeSpellsForPlayer and RevokeBlockedSpellsForPlayer
built their allowlist only from character_paragon_panel_spells and
panel_spell_children. Many Character Advancement "abilities" (e.g.
Scourge Strike) are panel talents stored in character_paragon_panel_talents,
so learning Death Coil afterward activated DK skill lines and the sweep
removed those spells as false orphans.

Add BuildPanelOwnedSpellsAllowlist to union spell chains, talent rank spell
IDs up to the purchased rank, and passive children. Also keep the prior
fixes: clear stale panel_spell_revoked rows on purchase and skip+delete
revoke entries that now match the allowlist on login.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 17:37:08 -04:00
Docker Build f986fdcddd mod-paragon: let class 12 actually USE class-restricted glyphs / items
Two paths still rejected glyph use on Paragon characters even after the
earlier AllowableClass server bypass:

1. Spell::CheckItems (server) treated cast-from-glyph as a normal
   "equipped item required" cast and called HasItemFitToSpellRequirements,
   which only handles weapon/armor and falls through default for
   ITEM_CLASS_GLYPH -> SPELL_FAILED_EQUIPPED_ITEM_CLASS. Skip that check
   when the cast item itself is the glyph.

2. The 3.3.5 client engine pre-checks ItemTemplate.AllowableClass against
   the player's class locally and refuses the right-click before sending
   CMSG_USE_ITEM, regardless of what the server would do. Bake the
   Paragon class bit (1<<11 = 2048) into AllowableClass for every
   class-restricted item via a mod-paragon SQL migration so the engine's
   pre-check passes for class 12.

Cache caveat: clients that previously inspected an affected item have
the old AllowableClass cached in Cache/<locale>/itemcache.wdb; deleting
the Cache folder forces a re-query. The server also caches item_template
in memory at boot, so this migration only takes effect for clients after
a worldserver restart (or .reload item_template) once the SQL has been
applied -- DBUpdater handles the SQL automatically on the next start.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 16:52:37 -04:00
Docker Build a212717c37 docs: note tooltip 'Classes:' patcher in patch-enUS-5 description
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 16:08:19 -04:00
Docker Build 49cb354133 mod-paragon: ignore item AllowableClass for class 12 (gear + glyphs)
Paragon is a classless concept layered on top of WotLK class data: stock items and class glyphs gate equip / vendor visibility / loot rolls / AH 'usable' filter via ItemTemplate.AllowableClass, which never has the class-12 bit (0x800). Bypassing the gate at the five enforcement sites lets Paragon equip any class-restricted item -- including class glyphs, since EffectApplyGlyph itself has no class check beyond the item gate. Race / level / proficiency / skill / required-spell checks still apply, so Paragon can't skip baseline progression.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 16:07:01 -04:00
Docker Build 7298d89c9a mod-paragon: default HasActivePowers on for rage from white hits
Paragon OnPlayerHasActivePowerType only reported POWER_RAGE when
Paragon.MultiResource.HasActivePowers was true. Core melee rage uses
Unit::DealDamage -> HasActivePowerType(POWER_RAGE) before RewardRage;
missing module config (common on fresh clones / Docker without merged
mod_paragon.conf) fell through to GetOption(..., false) and white swings
never generated rage. Match mod_paragon.conf.dist and default the C++
fallback to true so Paragon behaves correctly out of the box. Set
Paragon.MultiResource.HasActivePowers = 0 only for intentional test builds.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 15:54:39 -04:00
Docker Build 3a2ae82593 mod-paragon: combined Arcane Torrent also refunds 15 rage
Extend spell_paragon_arcane_torrent to EnergizeBySpell POWER_RAGE 150 (15
displayed; rage uses the same 10x internal scaling as runic power, see the
`-20` rage decay step in Player::Regenerate). Paragon's combined Arcane
Torrent now refunds mana, rage, energy, and runic power -- whichever pool
the character is using at the moment. ModifyPower no-ops on pools with
MaxPower == 0, so it's safe even before the Paragon picks up rage abilities.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 15:51:37 -04:00
Docker Build 16717acdd3 mod-paragon: combined Arcane Torrent that refunds mana, energy, and runic power
Building on the previous fix that hid the rogue and DK Arcane Torrent variants
for Paragon Blood Elves: instead of just dropping the duplicates, turn the
remaining mana variant (28730) into a single combined racial that refunds
whichever resource pool the character is using at the moment.

Add SpellScript spell_paragon_arcane_torrent in modules/mod-paragon/src/
Paragon_SC.cpp. Hooks AfterCast on 28730: when the caster is class 12 the
script EnergizeBySpell's 15 energy and 150 internal runic power (= 15 displayed,
matching stock 25046 / 50613 amounts) on top of the spell's stock mana effect.
ModifyPower no-ops on pools the player has no max for, so it is safe even
before the Paragon picks up energy- or RP-using abilities. Non-Paragon Blood
Elves are untouched and keep learning their stock racial.

Update migration 2026_05_10_03.sql to also register the script binding via
spell_script_names (28730 -> 'spell_paragon_arcane_torrent'). Idempotent
DELETE + INSERT.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 15:44:42 -04:00
Docker Build d96123e661 mod-paragon: single Arcane Torrent for Paragon Blood Elves
Blood Elf racial skill line 756 grants three different Arcane Torrent spell
IDs (28730 mana, 25046 rogue energy, 50613 DK runic power). The blanket
SkillLineAbility overlay in 2026_05_10_02 OR'd class 12 into all three, so
Paragon Blood Elves auto-learned every variant and the spellbook listed three
identical "Arcane Torrent" entries.

Add db-world migration 2026_05_10_03.sql to clear the class-12 bit on the rogue
and DK rows only (SkillLineAbility IDs 13338 and 17510), leaving 28730 as the
sole Paragon-visible racial cast. OnPlayerLogin removes 25046/50613 if still
present so existing characters self-heal without a manual unlearn.

The fractured-tooling DBC overlay generator is updated in the same workspace
to skip those two rows when regenerating SkillLineAbility SQL.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 15:34:02 -04:00
Docker Build 8a0da95ed2 mod-paragon: bypass talent DependsOn check for Paragon class
Player::LearnTalent enforces the column-arrow prereq (talentInfo->DependsOn)
even when called with command=true, so Character Advancement's commit path
was silently dropping any talent whose Talent.dbc row points to an unrelated
sibling -- e.g. Deep Wounds (depends on Improved Heroic Strike), Bloody
Vengeance (depends on Dark Conviction), Expose Weakness (depends on Lethal
Shots). Players spent points in the panel, hit Learn All, and the talent
silently never reached addTalent / OnPlayerLearnTalents -- the snapshot came
back without it and the client repainted the points as "unspent."

The Character Advancement panel gates progression via AE/TE essence cost,
not via the spec-tree column arrows, so the DependsOn rule doesn't apply to
class 12. Skip it for Paragon, mirroring the existing class-mask bypass a
few lines above.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 15:16:30 -04:00
Docker Build 8363b1b6c8 Paragon: ship class-12 SkillLineAbility overlay so proficiency passives auto-learn
Companion to 2026_05_09_00.sql (DBC overlay for chrclasses + srci) and
2026_05_10_01.sql (proficiency skill rows in playercreateinfo_skills).
Those two grant the SKILL (Maces, Shield, Cloth, ...) to Paragon at
character creation; this one opens the SkillLineAbility rows that
CASCADE skill -> passive spell, so when a fresh Paragon is created
AC's `Player::LearnDefaultSkill` actually grants the proficiency
passives:

  Block (107), Parry (3127), Dual Wield (674), Defense, weapon Shoot,
  racial Mace/Sword Specialization, ...

Without this overlay, a class-12 Paragon spawns with the right skill
rows but a near-empty spellbook past the racials and class defaults
that come from playercreateinfo_action.

How it works
------------
AC's DBCStores.cpp::LoadDBC loads each store from the on-disk .dbc
file first, then merges <table>_dbc world-DB rows on top. Our patched
client SkillLineAbility.dbc (in patch-enUS-4.MPQ) OR's the class-12
bit (0x800) into ClassMask on 3,314 rows -- the same rows the server
needs for the cascade to fire on Paragon. Stock Docker installs use
the upstream `ac-wotlk-client-data` image which fills data/dbc/ from
a vanilla 3.3.5a extract, so without this SQL overlay the server
runs against an unmodified SkillLineAbility.dbc and the cascade
never fires.

Generation
----------
Auto-generated end-to-end by
`fractured-tooling/from-workspace-root/_gen_paragon_dbc_overlay_sql.py`,
extended in this commit to handle SkillLineAbility.dbc (14-int
WotLK layout, 56 bytes per record). The script diffs patched vs
stock by ID, keeps only rows whose stock ClassMask did NOT include
the class-12 bit but whose patched ClassMask does, and emits the
3,314 REPLACE INTO rows. Re-running with the same inputs is byte-
stable.

Verified locally
----------------
- Migration applies twice in a row at exactly 3,314 SQL overlay rows
  (idempotent: DELETE WHERE ID IN (...) before INSERT).
- ac-worldserver restart logs:
    >> Loaded 10219 SkillLineAbility MultiMap Data
  -- the same total as stock (10,219 rows), confirming our overlay
  REPLACES existing rows by ID rather than appending duplicates.
- Spot-checked spell IDs: 107 (Block, ClassMask 2115 = warrior +
  paladin + dk + Paragon), 3127 (Parry, 2063), 674 (Dual Wield,
  2157), 75 (Auto Shoot, 2052) all carry the 0x800 bit.

Existing characters
-------------------
The cascade fires inside Player::Create and Player::LearnDefaultSkill
at character spawn, so existing class-12 characters created before
this migration keep their broken state. Delete and re-roll, or hand-
grant the missing spells via .learn for individual existing chars.

CLIENT-PATCHES.md updated to add the third symptom ("proficiency
skills exist but passive spells don't auto-learn") and document
this migration as the fourth piece of the class-12 bootstrap.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 14:38:55 -04:00
Docker Build 2874119c6d Paragon: ship class-12 weapon/armor proficiencies as SQL migration
Companion to 2026_05_10_00.sql. The spawn-data migration teaches the
worldserver where Paragon characters spawn and what per-level base
stats they have; this one teaches it which weapon/armor skill lines
to grant at first character login.

Without these rows a fresh Paragon character lands in their newbie
zone with no weapon or armor proficiencies (auto-attack greys out
on anything beyond a fist) -- the universal classMask=0 rows in
playercreateinfo_skills only cover Defense, Unarmed, Cloth,
languages, Mounts, and Companion Pets.

Adds 20 rows in playercreateinfo_skills with classMask=2048 (class
12 only) for every weapon and armor proficiency:
  - Weapons: Swords, Axes, Bows, Guns, Maces, 2H Swords, Dual Wield,
             Staves, 2H Maces, 2H Axes, Daggers, Thrown, Crossbows,
             Wands, Polearms, Fist Weapons.
  - Armor:   Plate Mail, Mail, Leather, Shield. (Cloth already
             granted via the classMask=0 universal row.)

Idempotent: DELETE WHERE classMask=2048 then INSERT, so it replays
cleanly on a partially-seeded DB (e.g. one where a contributor hand-
patched these rows before the migration landed).

Verified locally: applies cleanly twice in a row, worldserver restart
now logs `>> Loaded 1391 Player Create Skills` (was 1371 pre-Paragon
= +20 class-12 rows) and a freshly-rolled Draenei Paragon spawns with
the full weapon/armor kit.

CLIENT-PATCHES.md troubleshooting block updated to call out the
"Paragon spawns naked / can't equip anything" failure mode and list
all three migrations in the current rebuild recipe.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 13:38:27 -04:00
Docker Build 56fa2fc7f7 docs(client): note spellbook expansion in patch-enUS-5.MPQ
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 13:24:50 -04:00
Docker Build 5deb9e3255 Paragon: ship class-12 starter spawn data so character creation works on fresh installs
Companion to 2026_05_09_00.sql (DBC overlay). The DBC overlay teaches
the world server that class 12 (Paragon) exists; this migration
teaches it WHERE class-12 characters spawn, what action bar they boot
with, and what per-level base stats Player::InitStatsForLevel uses.

Without these rows, contributors hit:
  - Player::Create -> "invalid race/class pair (R/12) - refusing"
    and the client shows "Error creating character".
  - WorldServer load -> "class-12 Level-L does not have stats data!"
    integrity warnings.

Tables touched (idempotent: DELETE WHERE class=12 then INSERT):
  - playercreateinfo         : 10 rows, every DK-eligible race spawning
                               in their racial newbie zone (Northshire,
                               Valley of Trials, Ammen Vale, ...).
                               NOT Acherus -- Paragon is from-level-1.
  - playercreateinfo_action  : 46 rows, default action bar layout
                               per race (attack 6603, eat 78, racial,
                               etc.).
  - player_class_stats       : 80 rows, per-level base HP/Mana/STR/AGI/
                               STA/INT/SPI. Curve mirrors Warrior to
                               level 60, Paladin-style HP inflation
                               past 60 to keep Paragon competitive
                               in Wrath content.

Tables intentionally untouched: playercreateinfo_item is empty for
class 12 (Paragon ships no per-class starting items, only racial
kit), and the mask-based playercreateinfo_skills/_cast_spell/
_spell_custom rows already cover class 12 via their classMask=0
"all classes" entries.

Verified locally: applies cleanly twice in a row (idempotent),
worldserver restart now logs `>> Loaded 72 Player Create Definitions`
(was 62 pre-Paragon = +10 races for class 12) and creates a Draenei
Paragon without rejection.

CLIENT-PATCHES.md troubleshooting block updated to merge the two
"Character Creation Failed" modes (DBC overlay missing + spawn data
missing) into a single fix recipe. Existing contributors with a
pre-built dbimport image need
`docker compose build ac-db-import ac-worldserver` before this
migration is visible to DBUpdater; fresh clones get it on first
`docker compose up`.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 13:06:39 -04:00
Docker Build ecd8eacb1f chore(conf): revert seed defaults to stock so fresh installs auto-connect
The previous seed pinned auth/realmlist to production values
(`hsrwow.net` + RealmServerPort 47497), which silently bricked every
fresh local install: after auth login the realm hand-off pointed
clients at our public host, where their local credentials don't
exist, and they were dropped within a frame.

Seed now matches stock AzerothCore for solo dev:
- realmlist.address  = 127.0.0.1   (was hsrwow.net)
- RealmServerPort    = 3724        (was 47497)

Production owners apply both overrides post-dbimport via a one-shot
SQL UPDATE + an authserver.conf edit. Documented end-to-end in
contrib/fractured-dev-extras/BUILD-NATIVE.md (new "Production
deployment overrides" section) and the disconnect-after-login
symptom is called out in CLIENT-PATCHES.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 12:44:45 -04:00
Docker Build 1811c0ec35 Fix MariaDB dbimport: utf8mb4 collation and dbimport.conf
Use utf8mb4_unicode_ci in base SQL (MariaDB lacks utf8mb4_0900_ai_ci).
Add Updates.ExceptionShutdownDelay to dbimport.conf.dist to match
DBUpdater expectations.
2026-05-09 11:41:57 -05:00
Docker Build fae3ff5028 Paragon: ship server-side DBC overlay as SQL so fresh installs can roll class 12
Stock Docker installs fill data/dbc/ from the vanilla 3.3.5a extract
in `ac-wotlk-client-data`, which has no class 12 in ChrClasses.dbc and
no class-12 bit on SkillRaceClassInfo.dbc. CharacterHandler.cpp's
sChrClassesStore.LookupEntry(12) returns null and the create fails
with CHAR_CREATE_FAILED ("Class (12) not found in DBC ...") before the
contributor ever sees the panel. Fixing it required hand-copying the
patched DBCs onto the named volume — undocumented, fragile, and not
portable to native installs.

DBCStores.cpp::LoadDBC merges every <table>_dbc world-DB row on top of
the on-disk DBC store (storage.LoadFromDB after storage.Load). We use
that merge layer to ship Paragon's class-12 deltas as SQL:

- chrclasses_dbc: 1 row defining class 12 (Paragon, power=Mana,
  family=Warrior, expansion=2). Resolves CHAR_CREATE_FAILED.
- skillraceclassinfo_dbc: 235 rows REPLACEing stock entries with the
  patched ClassMask (class-12 bit OR'd in) so baseline skills (defense,
  weapon skills, etc.) are available to Paragon characters.

The new `modules/mod-paragon/data/sql/db-world/updates/2026_05_09_00.sql`
is applied automatically by AC's DBUpdater on every fresh `ac-db-import`
run (Docker) or first worldserver boot (native). End-to-end verified
locally: truncate -> docker compose up ac-db-import -> rows reappear
with hash 33B1A05 recorded in updates table.

The migration is auto-generated by
fractured-tooling/from-workspace-root/_gen_paragon_dbc_overlay_sql.py
(outside this repo per the repo-tidy policy). Re-run it whenever the
DBC bake changes.

CLIENT-PATCHES.md is rewritten so contributors no longer need the
manual DBC sync section as their primary install path. Manual overlay
is preserved as a labelled fallback for tools that read data/dbc/
directly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 12:19:59 -04:00
Docker Build 20a24b7935 docs(client): document worldserver DBC sync for Paragon character create
Explains why Character Creation Failed occurs when the client has
patch-enUS-4 but Docker/native data/dbc is still vanilla: ChrClasses
row 12 only exists in the patched DBC set. Adds Docker volume copy
steps, native install path, and log verification.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 12:00:16 -04:00
Docker Build 526022e2bc Add VPS clone scripts for sparse checkout without Docker
Track vps-clone-without-docker.sh and vps-sparse-checkout-no-docker.sh
so a fresh GitHub clone can run the helper end-to-end. Mark both
executable in git for ./ usage after clone.
2026-05-09 10:41:40 -05:00
Docker Build 4a1f4d02f0 docs(client): add CLIENT-PATCHES.md describing the Fractured client bundle
Documents the four binary artifacts that pair with this server
(patch-enUS-4/5/6.MPQ + patched Wow.exe), where they live (the
Releases page; not in the tree per the repo-tidy policy), and how a
contributor installs them on top of a clean 3.3.5a client. Cross-
referenced from the contrib/fractured-dev-extras README.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 11:34:12 -04:00
Docker Build 4d2a80ddb8 Paragon: server-authoritative CP/rune sync + cascade-spell revoke hardening
mod-paragon Paragon_Essence.cpp:
- Broaden SkillLinesLinkedToSpell: collect every SkillLineAbility row for
  an anchor spell regardless of AcquireMethod, so anchor spells whose
  primary SLA uses AcquireMethod 0 (e.g. Blood Strike) correctly identify
  their skill lines and let the dependent classifier do its job.
- IsSpellSkillLineCascadeDependent / RevokeUnwantedCascadeSpellsForPlayer
  use the broadened helper. HandleCommit calls the post-purchase sweep
  immediately so the spellbook never carries lingering cascade dependents
  (Blood Presence / Forceful Deflection / Death Coil / Death Grip).
- New character_paragon_panel_spell_revoked table tracks which active
  dependents we've revoked per (guid, parent) so OnPlayerLogin can
  re-revoke them after AC's _LoadSkills -> learnSkillRewardedSpells
  silently re-grants them.
- OnPlayerLogin opens the client SILENCE window via SendSilenceOpenForCommit
  with an empty allow list and intentionally omits the matching
  SendSilenceClose: the chat frame buffers CHAT_MSG_SYSTEM during the
  loading screen and only flushes after PLAYER_ENTERING_WORLD, so a paired
  CLOSE would shut the filter before the buffered "you have unlearned X"
  toasts hit it. The addon's 8s fail-open closes the window after the flush.
- New `.paragon hat` chat command for diagnosing Honor Among Thieves
  triggers (talent rank, learned spell, applied aura, proc table entry).

mod-paragon Paragon_SC.cpp:
- OnPlayerUpdate pushes server-authoritative combo points to the client
  via PARAA "R CP <n>" whenever the count changes. The client-side
  ComboFrame Paragon simulator listens for this and updates the target
  frame, fixing HAT-generated CP not displaying (HAT's trigger casts
  with a null target, which the combat-log inference path can't see).
- OnPlayerUpdate also pushes "R RUNES <cd0..cd5>" (ms remaining per
  rune slot) on rune mask changes, so the client RuneFrame simulator
  stays in lock-step with Spell::TakeRunePower instead of drifting
  through combat-log latency.

mod-paragon SQL:
- New updates/2026_05_09_00.sql migration creates
  character_paragon_panel_spell_revoked for AC's auto-DBUpdater so a
  fresh checkout can stand up an existing characters DB without
  manual intervention. Matching CREATE TABLE IF NOT EXISTS in
  base/character_paragon_panel_learned.sql for fresh installs.

mod-paragon conf:
- New Paragon.Diag.PanelLearn flag traces every PanelLearnSpellChain
  commit (chain ids, before/after spell-map sizes, side-spell
  classification) for diagnosing "spell reappears on relog" bugs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 11:22:57 -04:00
Docker Build 81df32963f chore(repo): move fork docs and logs under contrib/fractured-dev-extras
Stock AzerothCore does not ship CLAUDE.md, BUILD-NATIVE.md, or local build
logs at repo root. Park them under contrib/fractured-dev-extras/ so the
repo root stays close to upstream and dev clutter is contained.

- mv CLAUDE.md, BUILD-NATIVE.md -> contrib/fractured-dev-extras/
- mv build-worldserver.log + _build_paragon_*.log there (untracked / ignored)
- add contrib/fractured-dev-extras/README.txt explaining the layout
- gitignore: contrib/fractured-dev-extras/*.log

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 10:39:28 -04:00
Dawnforger 2b98ddeadd chore(scripts): add world-db-from-base.sh for clean acore_world from base
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 00:59:49 -05:00
Dawnforger 4c999bee3f fix(db_world): add creature_summon_groups.Comment before 2023_08_13_00
Column was added in old/8.x SQL but not in the archive chain; idempotent
ALTER matches 2023_01_31_01 so INSERT using Comment succeeds on archive-only
installs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 00:49:08 -05:00
Dawnforger ba1cca9521 fix(db_world): clamp VerifiedBuild for MEDIUMINT (spell_target_position)
MariaDB rejects 34149345 for VerifiedBuild when the column is MEDIUMINT
(signed max 8388607), as in old 8.x schema migrations. Replace corrupt
value with 0 in archive and base dumps.

Add data/sql/tools/clamp_verifiedbuild_mediumint.py to re-scan archive,
updates, and base INSERT/UPDATE blocks that list VerifiedBuild.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 00:45:38 -05:00
Dawnforger b797877558 fix(db_world): ensure attributeMask exists before 2023_07_17_01 update
The column was introduced in old/9.x SQL but not in the archive chain;
fresh archive-only installs lacked the column. Add idempotent ALTER via
INFORMATION_SCHEMA before updating spell_enchant_proc_data.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 00:38:29 -05:00
Dawnforger 326644bbac fix(db_world): add dist/angle to creature_formations INSERTs (strict MariaDB)
Archive updates omitted NOT NULL dist/angle under strict SQL mode. Patch all
affected archive files, pending updates, and old 9.x copies. Use 0,0 for
stacked/default formations.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 00:36:16 -05:00
Dawnforger 36fdf9af82 fix(db_world): include dist and angle in creature_formations INSERT
MariaDB strict mode rejects INSERTs that omit NOT NULL columns without
defaults. Align second INSERT in 2023_05_09_01.sql with the first block.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 00:29:42 -05:00
Dawnforger 579574acb3 chore(conf): Fractured defaults — auth 47497, realmlist hsrwow.net:8085
- authserver.conf.dist: RealmServerPort 47497; document hsrwow.net client realmlist
- base realmlist: Fractured WoW / hsrwow.net; clarify world port vs auth port
- BUILD-NATIVE.md: Fractured network defaults section
2026-05-09 00:07:31 -05:00
Dawnforger 63ab74b4fb fix(db): MariaDB version parse for 10.x-MariaDB- without 5.5.5- prefix
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 21:50:14 -05:00
Docker Build df7e943a74 Paragon: move module SQL to AC's data/sql/db-* layout for auto-update
AzerothCore's DBUpdater scans modules/<mod>/data/sql/db-{world,characters,auth}/
(see src/server/database/Updater/UpdateFetcher.cpp). The previous
modules/mod-paragon/sql/{world,characters}/base/ layout was non-standard
and skipped by the updater, so a fresh deploy never had Paragon tables
created automatically.

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

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

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 22:03:42 -04:00
Docker Build 203356aca8 Paragon: AE/TE currency, Character Advancement commit/reset, panel SQL
- mod-paragon: Paragon_Essence addon channel (PARAA), commit queue, resets,
  spell chain learn with passive child tracking, silence-window hints for
  cascade learns, trainer exemptions for pet/portal trainers
- SQL: character_paragon_panel_* tables, paragon_spell_ae_cost world data
- Core: Player Paragon class talent learn hook; Trainer skip for Paragon
  where appropriate
- Ignore local build-worldserver.log

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 21:43:01 -04:00
Dawnforger e2bed00b5c fix(common): OpenSSLCrypto provider API only for OpenSSL 3+ 2026-05-08 20:36:25 -05:00
Dawnforger fdc5849a69 fix(common): ARC4 OpenSSL 1.1 vs 3 API (EVP_CIPHER_fetch) 2026-05-08 20:28:46 -05:00
Dawnforger 2f8c374569 fix(db): MariaDB 10.6+ server and connector compatibility
- Parse real MariaDB version after MySQL 5.5.5 compatibility prefix for
  DatabaseIncompatibleVersion checks.
- Relax client checks when building against MariaDB C connector
  (MARIADB_VERSION_ID); keep Oracle MySQL 8.0+ rules otherwise.
- Use mysql_stmt_bind_param on MariaDB; mysql_stmt_bind_named_param only for
  MySQL 8.3+ without MariaDB headers.
- SSL: MYSQL_OPT_SSL_ENFORCE on MariaDB connector, MYSQL_OPT_SSL_MODE elsewhere.
- Track custom_script_loader.cpp so static script link succeeds; CMake
  find_package(MySQL) requires COMPONENTS lib on CMake 3.16.
2026-05-08 20:11:43 -05:00
102 changed files with 19570 additions and 1302 deletions
+150
View File
@@ -0,0 +1,150 @@
# 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 == 'Dawnforger/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 == 'Dawnforger/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/
}
Get-ChildItem tools/fractured-launcher-electron/dist/*.blockmap -ErrorAction SilentlyContinue |
Copy-Item -Destination launcher-publish/
- uses: actions/upload-artifact@v4
with:
name: electron-dist
path: launcher-publish/
sync-distro:
needs: [meta, build-electron]
if: github.repository == 'Dawnforger/Fractured'
runs-on: ubuntu-latest
steps:
- 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
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
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,81 @@
# 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
tools/fractured-launcher-electron/dist/*.blockmap
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/*.yml
tools/fractured-launcher-electron/dist/*.blockmap
+224
View File
@@ -0,0 +1,224 @@
# Primary path for player-facing binaries: every *published* GitHub Release on this repo
# is mirrored to your self-hosted Gitea (same tag). No public GitHub distro repo.
#
# Triggers:
# - release: published / released → GitHub “Release” (not a raw git tag alone).
# - workflow_dispatch → Actions → this workflow → “Run workflow” (enter tag).
#
# Troubleshooting: “Re-run failed jobs” on an OLD run replays the *original* workflow
# YAML (e.g. still runs `npm run pack:win` without --publish never). After changing this
# file on default branch, start a *new* run via “Run workflow”, not Re-run on a pre-fix run.
#
# Important: pushing only a git tag does NOT run this — you must create/publish a
# Release on github.com (Releases → Draft/new release → Publish). The workflow
# definition must exist on the repo DEFAULT branch (GitHub runs it from there).
#
# Steps: Windows (NSIS+portable) + Linux (AppImage) in parallel, launcher from DEFAULT BRANCH
# overlay on tag checkout → merge with GitHub release assets → upload all 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 Dawnforger/Fractured.
name: Sync release to Gitea
on:
release:
types: [published, released]
workflow_dispatch:
inputs:
tag:
description: 'Release tag on this GitHub repo (must exist; e.g. v1.0.0)'
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 == 'Dawnforger/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=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
fi
build-electron:
needs: meta
if: github.repository == 'Dawnforger/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
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/
}
Get-ChildItem tools/fractured-launcher-electron/dist/*.blockmap -ErrorAction SilentlyContinue |
Copy-Item -Destination launcher-publish/
- uses: actions/upload-artifact@v4
with:
name: electron-dist-windows
path: launcher-publish/
build-electron-linux:
needs: meta
if: github.repository == 'Dawnforger/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
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
cp -f tools/fractured-launcher-electron/dist/*.yml launcher-linux-publish/ 2>/dev/null || true
cp -f tools/fractured-launcher-electron/dist/*.blockmap launcher-linux-publish/ 2>/dev/null || true
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 == 'Dawnforger/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 GitHub release assets + Electron build
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
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
cp -f "$f" combined/
fi
done
echo "Merged assets from ${{ github.repository }} release $TAG"
else
echo "GitHub release download note (continuing with launcher only):"
cat /tmp/dl.err || true
fi
shopt -s nullglob
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 }}"
+7
View File
@@ -23,6 +23,7 @@
/data/sql/custom/* /data/sql/custom/*
/src/server/scripts/Custom/* /src/server/scripts/Custom/*
!/src/server/scripts/Custom/README.md !/src/server/scripts/Custom/README.md
!/src/server/scripts/Custom/custom_script_loader.cpp
/*.override.yml /*.override.yml
/*.override.yaml /*.override.yaml
@@ -119,3 +120,9 @@ local.properties
!/modules/mod-ale/** !/modules/mod-ale/**
# 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
# Local build artifacts (do not commit)
build-worldserver.log
# Local logs parked under contrib/fractured-dev-extras (see README there)
contrib/fractured-dev-extras/*.log
+1 -1
View File
@@ -106,7 +106,7 @@ include(ConfigInstall)
CU_RUN_HOOK("AFTER_LOAD_CMAKE_MODULES") CU_RUN_HOOK("AFTER_LOAD_CMAKE_MODULES")
find_package(PCHSupport) find_package(PCHSupport)
find_package(MySQL REQUIRED) find_package(MySQL REQUIRED COMPONENTS lib)
if(UNIX AND WITH_PERFTOOLS) if(UNIX AND WITH_PERFTOOLS)
find_package(Gperftools) find_package(Gperftools)
@@ -15,6 +15,58 @@ prerequisites; everything here is just the deltas you need on top of it.
--- ---
## Fractured client + network defaults
Stock-friendly defaults for fresh local installs. A `git clone` ->
`docker compose up` (or native install) lets a single developer log in
from the same machine without any post-install config tweaks.
- **`authserver.conf` -> `RealmServerPort`** = **3724** (stock WoW). A
patched `Wow.exe` with `set realmlist 127.0.0.1` (no port) reaches
the auth handshake.
- **`realmlist` table -> `port`** is the **world** port (default
**8085**, matches `WorldServerPort` in `worldserver.conf.dist`).
Auth tells the client to handshake to this port for the world hand-off.
- **`realmlist` table -> `address`** defaults to **`127.0.0.1`** in the
base SQL. The auth server hands this address to clients after login,
so 127.0.0.1 means "talk to the world server on the same machine
auth is running on" -- correct for solo dev. **Override on production
deploys**, see *Production deployment overrides* below.
### Production deployment overrides
Production Fractured runs on a remote VPS at `hsrwow.net` with auth
bound to a non-stock port (47497 -- 3724 was unavailable on that host).
Apply the overrides **once per fresh dbimport** on the production box.
```sql
-- Run against acore_auth on the production database after first dbimport:
UPDATE realmlist
SET address = 'hsrwow.net',
port = 8085 -- world port; leave at 8085 unless changed
WHERE id = 1;
```
Edit the production `authserver.conf` (NOT `authserver.conf.dist`)
to bind the auth listener to the production port:
```ini
RealmServerPort = 47497
```
Restart the auth server. Production clients connect with:
```text
set realmlist hsrwow.net:47497
```
The Fractured-patched 3.3.5 client supports the `host:port` syntax;
stock 3.3.5 clients do not, so any contributor distributing the
client bundle for production must include the patched `Wow.exe` from
the GitHub release.
---
## What you get when you build this fork ## What you get when you build this fork
- Worldserver with `CLASS_PARAGON` and Paragon-aware DK rune / sticky - Worldserver with `CLASS_PARAGON` and Paragon-aware DK rune / sticky
@@ -172,22 +224,19 @@ The standard AzerothCore three-database layout (`acore_auth`,
Easiest path: run the worldserver once with empty databases and let Easiest path: run the worldserver once with empty databases and let
`AC_FORCE_CREATE_DB=1` populate them, **or** use `dbimport` from the `AC_FORCE_CREATE_DB=1` populate them, **or** use `dbimport` from the
install dir. Then layer the Fractured-specific SQL on top: install dir.
```bash The Fractured-specific SQL (mod-paragon) lives under
cd <repo>/modules/mod-paragon/sql `modules/mod-paragon/data/sql/db-{world,characters}/base/` — that's the
**AzerothCore-standard module layout**, which means AC's built-in
DBUpdater picks it up automatically on every `worldserver` /
`dbimport` start. New SQL files are applied; previously-applied ones
are skipped via the hash recorded in `acore_world.updates` /
`acore_characters.updates`. No manual `mysql < ...` steps required for
deploys.
# world DB Modify-and-redeploy works the same way: change the file, push, pull on
mysql -u acore -p acore_world < world/base/paragon_gametables.sql the VPS, and the next `dbimport` run sees the new hash and re-applies.
mysql -u acore -p acore_world < world/base/paragon_spell_ae_cost.sql
mysql -u acore -p acore_world < world/base/player_class_stats_paragon_basemana.sql
# characters DB
mysql -u acore -p acore_characters < characters/base/character_paragon_currency.sql
```
Re-run any module-specific SQL whenever you pull updates that touch
`modules/*/sql/`.
--- ---
@@ -0,0 +1,268 @@
# Fractured Client Patches
Binary client artifacts that pair with this server. They live on the
[Releases page](https://github.com/Dawnforger/Fractured/releases),
**not** in the tree, so the repo stays lean and binaries can be
re-downloaded without bloating `git clone`.
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
| Artifact | Size | Purpose |
|---|---|---|
| `patch-enUS-4.MPQ` | ~5 MB | DBC + GlueXML bake. Adds `CLASS_PARAGON` (id 12), the character-create slot, glue strings, game-table DBCs, and a patched `Spell.dbc`: **(1)** `RuneCostID` zeroed on every rune-cost spell so 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` | ~57 KB | FrameXML overrides. Replaces stock `PlayerFrame.lua` / `RuneFrame.lua` / `ComboFrame.lua` / `UnitFrame.lua` / `SpellBookFrame.lua` + `SpellBookFrame.xml` with Paragon-aware versions: rune simulator, combo-point simulator, server-authoritative resource sync over the `PARAA` addon channel, action-button usability + click guards, an expanded spellbook (higher `MAX_SPELLS`, 24 skill-line tabs instead of stock 8) so all-class spells render, Paragon stat tooltips on the character sheet (including filtering duplicate “attack power from strength” lines so the paper doll matches server AP), a tooltip post-processor that appends ", Paragon" to the "Classes:" line on class-restricted gear / glyphs (the server bypasses `AllowableClass` for class 12, but the engine paints the line red and omits Paragon — the Lua hook recolors it green and adds the name so the player can tell it's wearable), and **PetFrame** re-anchored so the **pet unit frame sits below the rune row** for Paragon (stock layout had runes overlapping the pet portrait). The paper-doll **ammo slot** follows stock visibility rules (shown for hunters / ranged weapons; hidden when `UnitHasRelicSlot` applies). |
| `patch-enUS-6.MPQ` | ~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. |
Server and client work as a pair: the addon talks to `mod-paragon` on the
worldserver via `WHISPER` addon-channel messages with the `PARAA` prefix
(currency push, spell/talent snapshot, commit, combo points, rune
cooldowns, learn-toast silence window, **`C RESET PET TALENTS`**
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.
---
## Install
You need a 3.3.5a (build 12340) WoW client. ChromieCraft's free 3.3.5a
download is the canonical clean source if you don't already have one.
1. Download every artifact from the latest release matching this server
build:
`https://github.com/Dawnforger/Fractured/releases/latest`
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.
3. Drop `patch-enUS-4.MPQ`, `patch-enUS-5.MPQ`, `patch-enUS-6.MPQ` into
`Data/enUS/`. (For other locales, rename the suffix accordingly —
contents are locale-independent, only the filename ordering matters
to the loader.)
4. Edit `Data/enUS/realmlist.wtf` to point at your server, e.g.
`set realmlist your.host.tld`.
5. Launch `Wow.exe`. The login screen should reach the realm list. Make
a Paragon character (the new class entry on the create screen) and
press `N` in-world to open the Character Advancement panel.
If the panel opens empty / AE+TE read 0/0 / `N` opens the stock talent
pane: your client is loading an older `patch-enUS-6.MPQ`, or the
worldserver image is older than commit `4d2a80d` (the
`character_paragon_panel_spell_revoked` migration). Pull both ends to
the same release tag and rebuild the worldserver image.
If the **client** shows the Paragon class on the create screen but the
server replies **Character Creation Failed** (sometimes shown as
"Error creating character") when you pick it -- **or** the character
is created but spawns with no weapon / armor proficiencies (auto-attack
greys out, can't equip anything beyond a fist), or with the proficiency
**skills** but no **passive spells** like Block, Parry, Dual Wield --
the worldserver is missing one of four pieces of class-12 data. All
ship as SQL migrations under
`modules/mod-paragon/data/sql/db-world/updates/` and are auto-applied
by AzerothCore's DBUpdater on every `ac-db-import` run, but the SQL
files are baked into the dbimport Docker image at build time -- so a
stale image won't pick up new migrations. Fix:
```bash
git pull origin main
docker compose build ac-db-import ac-worldserver
docker compose up -d ac-db-import
docker compose restart ac-worldserver
```
Existing class-12 characters created before these migrations will
keep their broken state -- the cascade only fires inside
`Player::Create` and `Player::LearnDefaultSkill` at character spawn.
Delete the old Paragon and re-roll after the rebuild.
The four migrations:
- `2026_05_09_00.sql` -- DBC overlay rows for `chrclasses_dbc` and
`skillraceclassinfo_dbc`. Without this the server can't even
resolve class 12 in `sChrClassesStore`. See **Server-side Paragon
DBC overlay** below.
- `2026_05_10_00.sql` -- `playercreateinfo`, `playercreateinfo_action`,
and `player_class_stats` rows for class 12. Without this
`Player::Create` rejects every (race, class=12) pair as an
"invalid race/class pair" and the worldserver prints
`class-N Level-L does not have stats data!` integrity warnings on
load.
- `2026_05_10_01.sql` -- 20 `playercreateinfo_skills` rows
(`classMask = 2048` = class 12) granting every weapon /
armor proficiency at level 1. Without this a Paragon spawns with
only the universal `classMask = 0` skills (Defense, Unarmed,
Cloth, languages, Mounts) -- no Swords, no Mail, no Shield, etc.
- `2026_05_10_02.sql` -- 3,314 `skilllineability_dbc` rows opening
the class-12 bit on every SkillLineAbility row our patched
`SkillLineAbility.dbc` modified. AC reads these rows in
`Player::LearnDefaultSkill` to drive the `skill -> passive spell`
cascade. Without it the proficiency *skills* from `_01.sql` exist
but the *passive spells* (Block, Parry, Dual Wield, Defense,
weapon Shoot, racial Mace/Sword Specialization, etc.) never auto-
learn, so the spellbook past the racials looks empty.
After the rebuild + restart, `ac-worldserver` should log
`>> Loaded 72 Player Create Definitions` (was 62 pre-Paragon),
`>> Loaded 1391 Player Create Skills` (was 1371),
`>> Loaded 10219 SkillLineAbility MultiMap Data` (unchanged total --
the SQL overlay replaces existing rows by ID, doesn't add new ones),
and character creation succeeds for any DK-eligible race with a full
weapon / armor kit and the matching passive spells.
If the client **logs in** successfully but **disconnects immediately**
when entering the realm: the auth server is handing your client the
wrong world-server address. On a fresh local install the seed defaults
to `127.0.0.1` (commit landing this paragraph). If your DB was
imported from an older Fractured checkout, the seed may still point at
`hsrwow.net`, which sends the client to our production world server
instead of yours. Fix:
```bash
# Docker:
docker exec ac-database mysql -uroot -ppassword \
-e "UPDATE acore_auth.realmlist SET address='127.0.0.1' WHERE id=1;"
docker compose restart ac-authserver
```
Substitute your public hostname/IP for `127.0.0.1` if remote players
will be connecting. See `BUILD-NATIVE.md` -> *Production deployment
overrides* for the full list of values to set on a production box.
---
## Server-side Paragon DBC overlay (automatic)
The Fractured **client** learns about Paragon from `patch-enUS-4.MPQ`
(DBC + GlueXML). The **worldserver** never reads your MPQs — it reads
plain `.dbc` files under its `DataDir` (`.../data/dbc/` by default).
Stock Docker installs populate `data/dbc/` from a vanilla 3.3.5a
extract (`ac-client-data-init` in `docker-compose.yml`). That tree has
no `ChrClasses` row for id **12** and no class-12 bit on
`SkillRaceClassInfo` rows, which would normally trigger:
`Class (12) not found in DBC while creating new char ... wrong DBC files or cheater?`
…and reject the create with `CHAR_CREATE_FAILED`.
To remove that gap, the repo ships
`modules/mod-paragon/data/sql/db-world/updates/2026_05_09_00.sql`,
which `INSERT`s the Paragon class-12 deltas into:
- `chrclasses_dbc` — 1 row defining class 12 ("Paragon", power=Mana,
family=Warrior, expansion=2).
- `skillraceclassinfo_dbc` — 235 rows replacing stock entries with the
patched ClassMask (class-12 bit OR'd in) so every baseline skill is
available to Paragon characters.
`AzerothCore`'s DBC loader (`DBCStores.cpp::LoadDBC` -> `LoadFromDB`)
merges these rows on top of whatever `data/dbc/` contains at every
worldserver boot. The DBUpdater in `ac-db-import` (Docker) or the
worldserver itself (native) applies the migration automatically — so
the **only** steps a fresh contributor needs are `git clone` and
`docker compose up -d`.
### Regenerating the migration
The SQL is auto-generated from the patched DBCs that already live
inside `patch-enUS-4.MPQ`. The bake script lives outside this repo
(per the repo-tidy policy) at:
`fractured-tooling/from-workspace-root/_gen_paragon_dbc_overlay_sql.py`
Re-run it whenever you change the Paragon DBC bake — for example,
adding a new race to the Paragon class mask. It diffs the patched
DBCs against a stock 3.3.5a DBC extract and emits a fresh
`2026_05_09_00.sql` (or successor migration with a new timestamp if
deltas change). Workflow:
```powershell
# Extract the patched DBCs once:
.\tools\mpq\mpqcli.exe extract `
"ChromieCraft_3.3.5a\Data\enUS\patch-enUS-4.MPQ" `
-o "$env:TEMP\paragon-dbc-extract"
# Regenerate the SQL migration:
python fractured-tooling\from-workspace-root\_gen_paragon_dbc_overlay_sql.py
```
If the regenerated SQL has new content, commit it as a **new** dated
migration filename (e.g. `2026_06_01_00.sql`) — never edit a file that
has already been applied to live databases, AC's DBUpdater will detect
the hash change and re-run the SQL, which can be fine but is best
reserved for emergencies.
### Manual DBC overlay (rare, fallback)
If you ever need the patched DBCs *on disk* — e.g. for a tool that
reads `data/dbc/` directly outside the worldserver, or to verify a
client-vs-server DBC mismatch — extract `patch-enUS-4.MPQ` and copy
its `DBFilesClient/*.dbc` into `data/dbc/`:
**Docker:**
```powershell
docker run --rm `
-v ac-client-data:/data `
-v ${PWD}\paragon-dbc-extract:/patch:ro `
alpine sh -c "cp -f /patch/*.dbc /data/dbc/"
docker compose restart ac-worldserver
```
**Native:** copy into `<CMAKE_INSTALL_PREFIX>/data/dbc/` and restart.
This is **not required** for normal operation — the SQL migration
covers everything `mod-paragon` needs at runtime. Use the manual
overlay only when you're consciously bypassing the SQL merge layer.
---
## Building the patches yourself
The MPQs are reproducible from the dev tree (not in this repo —
`Classless Dev/Paragon Patch UI/` and
`Classless Dev/Paragon Advancement/` on the maintainer's workstation)
via two PowerShell scripts in `tools/`:
```
tools\build_paragon_ui_patch.ps1 -Deploy # -> patch-enUS-5.MPQ
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
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). 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/_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
client. The diff is publicly documented in the WoW emulation community
under names like "MPQ signature patch" / "no-CD-signature patch".
---
## Versioning
Releases are tagged to match the state of `main` they were built from.
The release notes call out which server commit shipped alongside each
artifact set, so a contributor running `git checkout <tag>` on this
repo can pull the matching client bundle and have a guaranteed-aligned
end-to-end build.
+16
View File
@@ -0,0 +1,16 @@
Fractured / Paragon — non-runtime repo extras
==============================================
This folder holds material that is not required to configure, build, or run
AzerothCore. Upstream AzerothCore does not ship these paths.
Contents:
- BUILD-NATIVE.md — fork-specific native build notes (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 +
patched Wow.exe), where to download them (Releases page), and how
to install them. Binaries themselves are NOT in the tree.
- *.log — local build logs (moved from repo root when present).
Operational files (docker-compose.override.yml, env/dist, modules/mod-paragon,
etc.) stay at their normal locations.
+10 -10
View File
@@ -58,16 +58,16 @@ INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`,
(90991, 90993, 4, 270, 515); (90991, 90993, 4, 270, 515);
DELETE FROM `creature_formations` WHERE `memberGUID` IN (91247,91248,91249,90985,90986,90987,90988,90989,90990); DELETE FROM `creature_formations` WHERE `memberGUID` IN (91247,91248,91249,90985,90986,90987,90988,90989,90990);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(91247, 91247, 3), (91247, 91247, 0, 0, 3),
(91247, 91248, 3), (91247, 91248, 0, 0, 3),
(91247, 91249, 3), (91247, 91249, 0, 0, 3),
(90985, 90985, 3), (90985, 90985, 0, 0, 3),
(90985, 90986, 3), (90985, 90986, 0, 0, 3),
(90985, 90987, 3), (90985, 90987, 0, 0, 3),
(90988, 90988, 3), (90988, 90988, 0, 0, 3),
(90988, 90989, 3), (90988, 90989, 0, 0, 3),
(90988, 90990, 3); (90988, 90990, 0, 0, 3);
UPDATE `creature` SET `id1` = 15384 WHERE `guid` = 91250 AND `id1` = 19871; UPDATE `creature` SET `id1` = 15384 WHERE `guid` = 91250 AND `id1` = 19871;
+7 -7
View File
@@ -22,13 +22,13 @@ INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_
(17653, 0, 3, 0, 4, 0, 100, 512, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 9, 17653, 0, 100, 1, 0, 0, 0, 0, 'Shadowmoon Channeler - On Aggro - Set In Combat With Zone'); (17653, 0, 3, 0, 4, 0, 100, 512, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 9, 17653, 0, 100, 1, 0, 0, 0, 0, 'Shadowmoon Channeler - On Aggro - Set In Combat With Zone');
DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+0,@CGUID+1,@CGUID+2,@CGUID+3,@CGUID+4,@CGUID+5); DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+0,@CGUID+1,@CGUID+2,@CGUID+3,@CGUID+4,@CGUID+5);
INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `dist`, `angle`, `groupAI`) VALUES
(@CGUID+0, @CGUID+5, 24), (@CGUID+0, @CGUID+5, 0, 0, 24),
(@CGUID+1, @CGUID+5, 24), (@CGUID+1, @CGUID+5, 0, 0, 24),
(@CGUID+2, @CGUID+5, 24), (@CGUID+2, @CGUID+5, 0, 0, 24),
(@CGUID+3, @CGUID+5, 24), (@CGUID+3, @CGUID+5, 0, 0, 24),
(@CGUID+4, @CGUID+5, 24), (@CGUID+4, @CGUID+5, 0, 0, 24),
(@CGUID+5, @CGUID+5, 24); (@CGUID+5, @CGUID+5, 0, 0, 24);
DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` IN (-138519,-138519,-138520,-138521,-138522,-138523)); DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` IN (-138519,-138519,-138520,-138521,-138522,-138523));
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
+4 -4
View File
@@ -18,7 +18,7 @@ INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry
(13, 1, 30952, 0, 0, 31, 0, 3, 17687, 0, 0, 0, 0, '', 'Shoot Flame Arrow (30952) only hit Flame Arrow (17687)'); (13, 1, 30952, 0, 0, 31, 0, 3, 17687, 0, 0, 0, 0, '', 'Shoot Flame Arrow (30952) only hit Flame Arrow (17687)');
DELETE FROM `creature_formations` WHERE `memberGUID` IN (151094,151095,151096,151097); DELETE FROM `creature_formations` WHERE `memberGUID` IN (151094,151095,151096,151097);
INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `dist`, `angle`, `groupAI`) VALUES
(151095,151095,3), (151095, 151095, 0, 0, 3),
(151096,151095,3), (151096, 151095, 0, 0, 3),
(151097,151095,3); (151097, 151095, 0, 0, 3);
+6 -6
View File
@@ -1,9 +1,9 @@
-- DB update 2023_07_10_00 -> 2023_07_10_01 -- DB update 2023_07_10_00 -> 2023_07_10_01
-- --
DELETE FROM `creature_formations` WHERE `memberGUID` IN (90978, 90979, 90980, 90981, 90982); DELETE FROM `creature_formations` WHERE `memberGUID` IN (90978, 90979, 90980, 90981, 90982);
INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `dist`, `angle`, `groupAI`) VALUES
(90978, 90978, 3), (90978, 90978, 0, 0, 3),
(90979, 90978, 3), (90979, 90978, 0, 0, 3),
(90980, 90978, 3), (90980, 90978, 0, 0, 3),
(90981, 90978, 3), (90981, 90978, 0, 0, 3),
(90982, 90978, 3); (90982, 90978, 0, 0, 3);
@@ -1,3 +1,19 @@
-- DB update 2023_07_17_00 -> 2023_07_17_01 -- DB update 2023_07_17_00 -> 2023_07_17_01
-- --
SET @attrmask_exists := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'spell_enchant_proc_data'
AND COLUMN_NAME = 'attributeMask'
);
SET @sql := IF(
@attrmask_exists > 0,
'SELECT 1',
'ALTER TABLE `spell_enchant_proc_data` ADD COLUMN `attributeMask` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `procEx`'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `spell_enchant_proc_data` SET `attributeMask` = 0x2 WHERE `entry` = 2675; UPDATE `spell_enchant_proc_data` SET `attributeMask` = 0x2 WHERE `entry` = 2675;
+1 -1
View File
@@ -3,7 +3,7 @@ UPDATE `creature_template` SET `scriptname` = '' WHERE `entry` = 27326;
DELETE FROM `spell_target_position` WHERE `ID` = 48324 AND `EffectIndex` = 0; DELETE FROM `spell_target_position` WHERE `ID` = 48324 AND `EffectIndex` = 0;
INSERT INTO `spell_target_position` (`ID`, `EffectIndex`, `MapID`, `PositionX`, `PositionY`, `PositionZ`, `Orientation`, `VerifiedBuild`) VALUES INSERT INTO `spell_target_position` (`ID`, `EffectIndex`, `MapID`, `PositionX`, `PositionY`, `PositionZ`, `Orientation`, `VerifiedBuild`) VALUES
(48324, 0, 571, 3454.11, -2802.37, 202.14, 0, 34149345); (48324, 0, 571, 3454.11, -2802.37, 202.14, 0, 0);
DELETE FROM `spell_script_names` WHERE `spell_id` IN (48382, 47533); DELETE FROM `spell_script_names` WHERE `spell_id` IN (48382, 47533);
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
@@ -1,5 +1,21 @@
-- DB update 2023_08_12_02 -> 2023_08_13_00 -- DB update 2023_08_12_02 -> 2023_08_13_00
-- --
SET @csg_comment_exists := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'creature_summon_groups'
AND COLUMN_NAME = 'Comment'
);
SET @sql := IF(
@csg_comment_exists > 0,
'SELECT 1',
'ALTER TABLE `creature_summon_groups` ADD COLUMN `Comment` varchar(255) NOT NULL DEFAULT '''' AFTER `summonTime`'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
DELETE FROM `event_scripts` WHERE `id` = 14376; DELETE FROM `event_scripts` WHERE `id` = 14376;
UPDATE `gameobject_template` SET `AIName` = 'SmartGameObjectAI' WHERE `entry` = 185220; UPDATE `gameobject_template` SET `AIName` = 'SmartGameObjectAI' WHERE `entry` = 185220;
+126 -126
View File
@@ -1362,132 +1362,132 @@ UPDATE `creature_template_addon` SET `bytes2` = 1 WHERE (`entry` IN (21224, 2122
UPDATE `creature_template` SET `speed_walk` = 1.6, `speed_run` = 1.71428 WHERE (`entry` IN (21218, 21301)); UPDATE `creature_template` SET `speed_walk` = 1.6, `speed_run` = 1.71428 WHERE (`entry` IN (21218, 21301));
DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+1 ,@CGUID+2 ,@CGUID+3 ,@CGUID+4 ,@CGUID+5 ,@CGUID+6 ,@CGUID+7 ,@CGUID+8 ,@CGUID+9 ,@CGUID+10 ,@CGUID+11 ,@CGUID+12 ,@CGUID+13 ,@CGUID+14 ,@CGUID+15 ,@CGUID+22 ,@CGUID+23 ,@CGUID+24 ,@CGUID+25 ,@CGUID+26 ,@CGUID+27 ,@CGUID+28 ,@CGUID+29 ,@CGUID+30 ,@CGUID+31 ,@CGUID+32 ,@CGUID+33 ,@CGUID+34 ,@CGUID+35 ,@CGUID+36 ,@CGUID+37 ,@CGUID+38 ,@CGUID+39 ,@CGUID+40 ,@CGUID+41 ,@CGUID+42 ,@CGUID+43 ,@CGUID+44 ,@CGUID+45 ,@CGUID+46 ,@CGUID+47 ,@CGUID+48 ,@CGUID+49 ,@CGUID+50 ,@CGUID+51 ,@CGUID+52 ,@CGUID+53 ,@CGUID+54 ,@CGUID+55 ,@CGUID+56 ,@CGUID+57 ,@CGUID+58 ,@CGUID+59 ,@CGUID+60 ,@CGUID+61 ,@CGUID+62 ,@CGUID+63 ,@CGUID+64 ,@CGUID+65 ,@CGUID+66 ,@CGUID+67 ,@CGUID+68 ,@CGUID+69 ,@CGUID+70 ,@CGUID+71 ,@CGUID+72 ,@CGUID+73 ,@CGUID+74 ,@CGUID+75 ,@CGUID+76 ,@CGUID+77 ,@CGUID+78 ,@CGUID+79 ,@CGUID+80 ,@CGUID+81 ,@CGUID+82 ,@CGUID+83 ,@CGUID+84 ,@CGUID+85 ,@CGUID+86 ,@CGUID+87 ,@CGUID+88 ,@CGUID+89 ,@CGUID+90 ,@CGUID+91 ,@CGUID+92 ,@CGUID+93 ,@CGUID+94 ,@CGUID+95 ,@CGUID+96 ,@CGUID+97 ,@CGUID+98 ,@CGUID+99 ,@CGUID+102,@CGUID+103,@CGUID+104,@CGUID+105,@CGUID+106,@CGUID+107,@CGUID+108,@CGUID+109,@CGUID+115,@CGUID+116,@CGUID+117,@CGUID+118,@CGUID+119,@CGUID+120,@CGUID+121,@CGUID+122,@CGUID+123,@CGUID+124,@CGUID+125,@CGUID+126,@CGUID+127,@CGUID+128,@CGUID+129,@CGUID+130,@CGUID+131,@CGUID+132,@CGUID+133,@CGUID+134,@CGUID+135,@CGUID+136,@CGUID+137,@CGUID+138); DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+1 ,@CGUID+2 ,@CGUID+3 ,@CGUID+4 ,@CGUID+5 ,@CGUID+6 ,@CGUID+7 ,@CGUID+8 ,@CGUID+9 ,@CGUID+10 ,@CGUID+11 ,@CGUID+12 ,@CGUID+13 ,@CGUID+14 ,@CGUID+15 ,@CGUID+22 ,@CGUID+23 ,@CGUID+24 ,@CGUID+25 ,@CGUID+26 ,@CGUID+27 ,@CGUID+28 ,@CGUID+29 ,@CGUID+30 ,@CGUID+31 ,@CGUID+32 ,@CGUID+33 ,@CGUID+34 ,@CGUID+35 ,@CGUID+36 ,@CGUID+37 ,@CGUID+38 ,@CGUID+39 ,@CGUID+40 ,@CGUID+41 ,@CGUID+42 ,@CGUID+43 ,@CGUID+44 ,@CGUID+45 ,@CGUID+46 ,@CGUID+47 ,@CGUID+48 ,@CGUID+49 ,@CGUID+50 ,@CGUID+51 ,@CGUID+52 ,@CGUID+53 ,@CGUID+54 ,@CGUID+55 ,@CGUID+56 ,@CGUID+57 ,@CGUID+58 ,@CGUID+59 ,@CGUID+60 ,@CGUID+61 ,@CGUID+62 ,@CGUID+63 ,@CGUID+64 ,@CGUID+65 ,@CGUID+66 ,@CGUID+67 ,@CGUID+68 ,@CGUID+69 ,@CGUID+70 ,@CGUID+71 ,@CGUID+72 ,@CGUID+73 ,@CGUID+74 ,@CGUID+75 ,@CGUID+76 ,@CGUID+77 ,@CGUID+78 ,@CGUID+79 ,@CGUID+80 ,@CGUID+81 ,@CGUID+82 ,@CGUID+83 ,@CGUID+84 ,@CGUID+85 ,@CGUID+86 ,@CGUID+87 ,@CGUID+88 ,@CGUID+89 ,@CGUID+90 ,@CGUID+91 ,@CGUID+92 ,@CGUID+93 ,@CGUID+94 ,@CGUID+95 ,@CGUID+96 ,@CGUID+97 ,@CGUID+98 ,@CGUID+99 ,@CGUID+102,@CGUID+103,@CGUID+104,@CGUID+105,@CGUID+106,@CGUID+107,@CGUID+108,@CGUID+109,@CGUID+115,@CGUID+116,@CGUID+117,@CGUID+118,@CGUID+119,@CGUID+120,@CGUID+121,@CGUID+122,@CGUID+123,@CGUID+124,@CGUID+125,@CGUID+126,@CGUID+127,@CGUID+128,@CGUID+129,@CGUID+130,@CGUID+131,@CGUID+132,@CGUID+133,@CGUID+134,@CGUID+135,@CGUID+136,@CGUID+137,@CGUID+138);
INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `dist`, `angle`, `groupAI`) VALUES
(@CGUID+1 , @CGUID+1 , 3), (@CGUID+1, @CGUID+1, 0, 0, 3),
(@CGUID+2 , @CGUID+1 , 3), (@CGUID+2, @CGUID+1, 0, 0, 3),
(@CGUID+3 , @CGUID+1 , 3), (@CGUID+3, @CGUID+1, 0, 0, 3),
(@CGUID+4 , @CGUID+1 , 3), (@CGUID+4, @CGUID+1, 0, 0, 3),
(@CGUID+5 , @CGUID+1 , 3), (@CGUID+5, @CGUID+1, 0, 0, 3),
(@CGUID+6 , @CGUID+6 , 3), (@CGUID+6, @CGUID+6, 0, 0, 3),
(@CGUID+7 , @CGUID+6 , 3), (@CGUID+7, @CGUID+6, 0, 0, 3),
(@CGUID+8 , @CGUID+6 , 3), (@CGUID+8, @CGUID+6, 0, 0, 3),
(@CGUID+9 , @CGUID+6 , 3), (@CGUID+9, @CGUID+6, 0, 0, 3),
(@CGUID+10 , @CGUID+6 , 3), (@CGUID+10, @CGUID+6, 0, 0, 3),
(@CGUID+11 , @CGUID+11 , 3), (@CGUID+11, @CGUID+11, 0, 0, 3),
(@CGUID+12 , @CGUID+11 , 3), (@CGUID+12, @CGUID+11, 0, 0, 3),
(@CGUID+13 , @CGUID+11 , 3), (@CGUID+13, @CGUID+11, 0, 0, 3),
(@CGUID+14 , @CGUID+11 , 3), (@CGUID+14, @CGUID+11, 0, 0, 3),
(@CGUID+15 , @CGUID+11 , 3), (@CGUID+15, @CGUID+11, 0, 0, 3),
(@CGUID+22 , @CGUID+22 , 3), (@CGUID+22, @CGUID+22, 0, 0, 3),
(@CGUID+23 , @CGUID+22 , 3), (@CGUID+23, @CGUID+22, 0, 0, 3),
(@CGUID+24 , @CGUID+22 , 3), (@CGUID+24, @CGUID+22, 0, 0, 3),
(@CGUID+25 , @CGUID+22 , 3), (@CGUID+25, @CGUID+22, 0, 0, 3),
(@CGUID+26 , @CGUID+22 , 3), (@CGUID+26, @CGUID+22, 0, 0, 3),
(@CGUID+27 , @CGUID+22 , 3), (@CGUID+27, @CGUID+22, 0, 0, 3),
(@CGUID+28 , @CGUID+22 , 3), (@CGUID+28, @CGUID+22, 0, 0, 3),
(@CGUID+29 , @CGUID+22 , 3), (@CGUID+29, @CGUID+22, 0, 0, 3),
(@CGUID+30 , @CGUID+22 , 3), (@CGUID+30, @CGUID+22, 0, 0, 3),
(@CGUID+31 , @CGUID+31 , 3), (@CGUID+31, @CGUID+31, 0, 0, 3),
(@CGUID+32 , @CGUID+31 , 3), (@CGUID+32, @CGUID+31, 0, 0, 3),
(@CGUID+33 , @CGUID+31 , 3), (@CGUID+33, @CGUID+31, 0, 0, 3),
(@CGUID+34 , @CGUID+31 , 3), (@CGUID+34, @CGUID+31, 0, 0, 3),
(@CGUID+35 , @CGUID+31 , 3), (@CGUID+35, @CGUID+31, 0, 0, 3),
(@CGUID+36 , @CGUID+31 , 3), (@CGUID+36, @CGUID+31, 0, 0, 3),
(@CGUID+37 , @CGUID+31 , 3), (@CGUID+37, @CGUID+31, 0, 0, 3),
(@CGUID+38 , @CGUID+31 , 3), (@CGUID+38, @CGUID+31, 0, 0, 3),
(@CGUID+39 , @CGUID+31 , 3), (@CGUID+39, @CGUID+31, 0, 0, 3),
(@CGUID+40 , @CGUID+40 , 3), (@CGUID+40, @CGUID+40, 0, 0, 3),
(@CGUID+41 , @CGUID+40 , 3), (@CGUID+41, @CGUID+40, 0, 0, 3),
(@CGUID+42 , @CGUID+40 , 3), (@CGUID+42, @CGUID+40, 0, 0, 3),
(@CGUID+43 , @CGUID+40 , 3), (@CGUID+43, @CGUID+40, 0, 0, 3),
(@CGUID+44 , @CGUID+40 , 3), (@CGUID+44, @CGUID+40, 0, 0, 3),
(@CGUID+45 , @CGUID+40 , 3), (@CGUID+45, @CGUID+40, 0, 0, 3),
(@CGUID+46 , @CGUID+40 , 3), (@CGUID+46, @CGUID+40, 0, 0, 3),
(@CGUID+47 , @CGUID+40 , 3), (@CGUID+47, @CGUID+40, 0, 0, 3),
(@CGUID+48 , @CGUID+40 , 3), (@CGUID+48, @CGUID+40, 0, 0, 3),
(@CGUID+49 , @CGUID+49 , 3), (@CGUID+49, @CGUID+49, 0, 0, 3),
(@CGUID+50 , @CGUID+49 , 3), (@CGUID+50, @CGUID+49, 0, 0, 3),
(@CGUID+51 , @CGUID+49 , 3), (@CGUID+51, @CGUID+49, 0, 0, 3),
(@CGUID+52 , @CGUID+49 , 3), (@CGUID+52, @CGUID+49, 0, 0, 3),
(@CGUID+53 , @CGUID+49 , 3), (@CGUID+53, @CGUID+49, 0, 0, 3),
(@CGUID+54 , @CGUID+49 , 3), (@CGUID+54, @CGUID+49, 0, 0, 3),
(@CGUID+55 , @CGUID+49 , 3), (@CGUID+55, @CGUID+49, 0, 0, 3),
(@CGUID+56 , @CGUID+49 , 3), (@CGUID+56, @CGUID+49, 0, 0, 3),
(@CGUID+57 , @CGUID+49 , 3), (@CGUID+57, @CGUID+49, 0, 0, 3),
(@CGUID+58 , @CGUID+58 , 3), (@CGUID+58, @CGUID+58, 0, 0, 3),
(@CGUID+59 , @CGUID+58 , 3), (@CGUID+59, @CGUID+58, 0, 0, 3),
(@CGUID+60 , @CGUID+58 , 3), (@CGUID+60, @CGUID+58, 0, 0, 3),
(@CGUID+61 , @CGUID+58 , 3), (@CGUID+61, @CGUID+58, 0, 0, 3),
(@CGUID+62 , @CGUID+58 , 3), (@CGUID+62, @CGUID+58, 0, 0, 3),
(@CGUID+63 , @CGUID+58 , 3), (@CGUID+63, @CGUID+58, 0, 0, 3),
(@CGUID+64 , @CGUID+58 , 3), (@CGUID+64, @CGUID+58, 0, 0, 3),
(@CGUID+65 , @CGUID+58 , 3), (@CGUID+65, @CGUID+58, 0, 0, 3),
(@CGUID+66 , @CGUID+58 , 3), (@CGUID+66, @CGUID+58, 0, 0, 3),
(@CGUID+67 , @CGUID+67 , 3), (@CGUID+67, @CGUID+67, 0, 0, 3),
(@CGUID+68 , @CGUID+67 , 3), (@CGUID+68, @CGUID+67, 0, 0, 3),
(@CGUID+69 , @CGUID+67 , 3), (@CGUID+69, @CGUID+67, 0, 0, 3),
(@CGUID+70 , @CGUID+67 , 3), (@CGUID+70, @CGUID+67, 0, 0, 3),
(@CGUID+71 , @CGUID+67 , 3), (@CGUID+71, @CGUID+67, 0, 0, 3),
(@CGUID+72 , @CGUID+67 , 3), (@CGUID+72, @CGUID+67, 0, 0, 3),
(@CGUID+73 , @CGUID+67 , 3), (@CGUID+73, @CGUID+67, 0, 0, 3),
(@CGUID+74 , @CGUID+67 , 3), (@CGUID+74, @CGUID+67, 0, 0, 3),
(@CGUID+75 , @CGUID+67 , 3), (@CGUID+75, @CGUID+67, 0, 0, 3),
(@CGUID+76 , @CGUID+76 , 3), (@CGUID+76, @CGUID+76, 0, 0, 3),
(@CGUID+77 , @CGUID+76 , 3), (@CGUID+77, @CGUID+76, 0, 0, 3),
(@CGUID+78 , @CGUID+76 , 3), (@CGUID+78, @CGUID+76, 0, 0, 3),
(@CGUID+79 , @CGUID+76 , 3), (@CGUID+79, @CGUID+76, 0, 0, 3),
(@CGUID+80 , @CGUID+76 , 3), (@CGUID+80, @CGUID+76, 0, 0, 3),
(@CGUID+81 , @CGUID+76 , 3), (@CGUID+81, @CGUID+76, 0, 0, 3),
(@CGUID+82 , @CGUID+82 , 3), (@CGUID+82, @CGUID+82, 0, 0, 3),
(@CGUID+83 , @CGUID+82 , 3), (@CGUID+83, @CGUID+82, 0, 0, 3),
(@CGUID+84 , @CGUID+82 , 3), (@CGUID+84, @CGUID+82, 0, 0, 3),
(@CGUID+85 , @CGUID+82 , 3), (@CGUID+85, @CGUID+82, 0, 0, 3),
(@CGUID+86 , @CGUID+82 , 3), (@CGUID+86, @CGUID+82, 0, 0, 3),
(@CGUID+87 , @CGUID+82 , 3), (@CGUID+87, @CGUID+82, 0, 0, 3),
(@CGUID+88 , @CGUID+88 , 3), (@CGUID+88, @CGUID+88, 0, 0, 3),
(@CGUID+89 , @CGUID+88 , 3), (@CGUID+89, @CGUID+88, 0, 0, 3),
(@CGUID+90 , @CGUID+88 , 3), (@CGUID+90, @CGUID+88, 0, 0, 3),
(@CGUID+91 , @CGUID+88 , 3), (@CGUID+91, @CGUID+88, 0, 0, 3),
(@CGUID+92 , @CGUID+88 , 3), (@CGUID+92, @CGUID+88, 0, 0, 3),
(@CGUID+93 , @CGUID+88 , 3), (@CGUID+93, @CGUID+88, 0, 0, 3),
(@CGUID+94 , @CGUID+94 , 3), (@CGUID+94, @CGUID+94, 0, 0, 3),
(@CGUID+95 , @CGUID+94 , 3), (@CGUID+95, @CGUID+94, 0, 0, 3),
(@CGUID+96 , @CGUID+94 , 3), (@CGUID+96, @CGUID+94, 0, 0, 3),
(@CGUID+97 , @CGUID+94 , 3), (@CGUID+97, @CGUID+94, 0, 0, 3),
(@CGUID+98 , @CGUID+94 , 3), (@CGUID+98, @CGUID+94, 0, 0, 3),
(@CGUID+99 , @CGUID+94 , 3), (@CGUID+99, @CGUID+94, 0, 0, 3),
(@CGUID+102, @CGUID+102, 3), (@CGUID+102, @CGUID+102, 0, 0, 3),
(@CGUID+103, @CGUID+102, 3), (@CGUID+103, @CGUID+102, 0, 0, 3),
(@CGUID+104, @CGUID+102, 3), (@CGUID+104, @CGUID+102, 0, 0, 3),
(@CGUID+105, @CGUID+102, 3), (@CGUID+105, @CGUID+102, 0, 0, 3),
(@CGUID+106, @CGUID+106, 3), (@CGUID+106, @CGUID+106, 0, 0, 3),
(@CGUID+107, @CGUID+106, 3), (@CGUID+107, @CGUID+106, 0, 0, 3),
(@CGUID+108, @CGUID+106, 3), (@CGUID+108, @CGUID+106, 0, 0, 3),
(@CGUID+109, @CGUID+106, 3), (@CGUID+109, @CGUID+106, 0, 0, 3),
(@CGUID+115, @CGUID+115, 3), (@CGUID+115, @CGUID+115, 0, 0, 3),
(@CGUID+116, @CGUID+115, 3), (@CGUID+116, @CGUID+115, 0, 0, 3),
(@CGUID+117, @CGUID+115, 3), (@CGUID+117, @CGUID+115, 0, 0, 3),
(@CGUID+118, @CGUID+118, 3), (@CGUID+118, @CGUID+118, 0, 0, 3),
(@CGUID+119, @CGUID+118, 3), (@CGUID+119, @CGUID+118, 0, 0, 3),
(@CGUID+120, @CGUID+118, 3), (@CGUID+120, @CGUID+118, 0, 0, 3),
(@CGUID+121, @CGUID+118, 3), (@CGUID+121, @CGUID+118, 0, 0, 3),
(@CGUID+122, @CGUID+122, 3), (@CGUID+122, @CGUID+122, 0, 0, 3),
(@CGUID+123, @CGUID+122, 3), (@CGUID+123, @CGUID+122, 0, 0, 3),
(@CGUID+124, @CGUID+122, 3), (@CGUID+124, @CGUID+122, 0, 0, 3),
(@CGUID+125, @CGUID+122, 3), (@CGUID+125, @CGUID+122, 0, 0, 3),
(@CGUID+126, @CGUID+122, 3), (@CGUID+126, @CGUID+122, 0, 0, 3),
(@CGUID+127, @CGUID+127, 3), (@CGUID+127, @CGUID+127, 0, 0, 3),
(@CGUID+128, @CGUID+127, 3), (@CGUID+128, @CGUID+127, 0, 0, 3),
(@CGUID+129, @CGUID+127, 3), (@CGUID+129, @CGUID+127, 0, 0, 3),
(@CGUID+130, @CGUID+127, 3), (@CGUID+130, @CGUID+127, 0, 0, 3),
(@CGUID+131, @CGUID+127, 3), (@CGUID+131, @CGUID+127, 0, 0, 3),
(@CGUID+132, @CGUID+127, 3), (@CGUID+132, @CGUID+127, 0, 0, 3),
(@CGUID+133, @CGUID+133, 3), (@CGUID+133, @CGUID+133, 0, 0, 3),
(@CGUID+134, @CGUID+133, 3), (@CGUID+134, @CGUID+133, 0, 0, 3),
(@CGUID+135, @CGUID+133, 3), (@CGUID+135, @CGUID+133, 0, 0, 3),
(@CGUID+136, @CGUID+133, 3), (@CGUID+136, @CGUID+133, 0, 0, 3),
(@CGUID+137, @CGUID+133, 3), (@CGUID+137, @CGUID+133, 0, 0, 3),
(@CGUID+138, @CGUID+133, 3); (@CGUID+138, @CGUID+133, 0, 0, 3);
UPDATE `smart_scripts` SET `action_param2`=0 WHERE `entryorguid`=21230 AND `source_type`=0 AND `id`=1 AND `link`=0; UPDATE `smart_scripts` SET `action_param2`=0 WHERE `entryorguid`=21230 AND `source_type`=0 AND `id`=1 AND `link`=0;
UPDATE `smart_scripts` SET `action_param2`=0 WHERE `entryorguid`=21230 AND `source_type`=0 AND `id`=5 AND `link`=0; UPDATE `smart_scripts` SET `action_param2`=0 WHERE `entryorguid`=21230 AND `source_type`=0 AND `id`=5 AND `link`=0;
+5 -5
View File
@@ -183,11 +183,11 @@ INSERT INTO `linked_respawn`(`guid`, `linkedGuid`, `linkType`) VALUES
-- Leotheras formation -- Leotheras formation
DELETE FROM `creature_formations` WHERE `leaderGUID` = @LEOTHERAS; DELETE FROM `creature_formations` WHERE `leaderGUID` = @LEOTHERAS;
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(@LEOTHERAS, @LEOTHERAS, 24), (@LEOTHERAS, @LEOTHERAS, 0, 0, 24),
(@LEOTHERAS, @LEOTHERAS+1, 24), (@LEOTHERAS, @LEOTHERAS+1, 0, 0, 24),
(@LEOTHERAS, @LEOTHERAS+2, 24), (@LEOTHERAS, @LEOTHERAS+2, 0, 0, 24),
(@LEOTHERAS, @LEOTHERAS+3, 24); (@LEOTHERAS, @LEOTHERAS+3, 0, 0, 24);
SET @KARATHRESS := 153154; SET @KARATHRESS := 153154;
DELETE FROM `linked_respawn` WHERE `linkedGuid` = @KARATHRESS; DELETE FROM `linked_respawn` WHERE `linkedGuid` = @KARATHRESS;
+144 -144
View File
@@ -869,176 +869,176 @@ INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_
-- Static Formations -- Static Formations
DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+03,@CGUID+04,@CGUID+05,@CGUID+06,@CGUID+07,@CGUID+08,@CGUID+09,@CGUID+10,@CGUID+11,@CGUID+12,@CGUID+13,@CGUID+14,@CGUID+15,@CGUID+16,@CGUID+17,@CGUID+18,@CGUID+19,@CGUID+20,@CGUID+52,@CGUID+53,@CGUID+54,@CGUID+55,@CGUID+59,@CGUID+60,@CGUID+61,@CGUID+62,@CGUID+63,@CGUID+64,@CGUID+65,@CGUID+66,@CGUID+67,@CGUID+68,@CGUID+69,@CGUID+70,@CGUID+74,@CGUID+75,@CGUID+76,@CGUID+77,@CGUID+78,@CGUID+79,@CGUID+80,@CGUID+81,@CGUID+82,@CGUID+83,@CGUID+84,@CGUID+85,@CGUID+86,@CGUID+87,@CGUID+88,@CGUID+89,@CGUID+90,@CGUID+91,@CGUID+93,@CGUID+94,@CGUID+98,@CGUID+99,@CGUID+100,@CGUID+101,@CGUID+102,@CGUID+103,@CGUID+104,@CGUID+105,@CGUID+106,@CGUID+107,@CGUID+108,@CGUID+109,@CGUID+110,@CGUID+111,@CGUID+112,@CGUID+113,@CGUID+114,@CGUID+115,@CGUID+116,@CGUID+117,@CGUID+118,@CGUID+119,@CGUID+120,@CGUID+121,@CGUID+125,@CGUID+126,@CGUID+127,@CGUID+128,@CGUID+129,@CGUID+130,@CGUID+131,@CGUID+132,@CGUID+133,@CGUID+134,@CGUID+135,@CGUID+136,@CGUID+143,@CGUID+144,@CGUID+145,@CGUID+146,@CGUID+147,@CGUID+148,@CGUID+149,@CGUID+150,@CGUID+151,@CGUID+152,@CGUID+153,@CGUID+154,@CGUID+155,@CGUID+156,@CGUID+157,@CGUID+158,@CGUID+159,@CGUID+160,@CGUID+161,@CGUID+162,@CGUID+163,@CGUID+164,@CGUID+165,@CGUID+166,@CGUID+167,@CGUID+168,@CGUID+169,@CGUID+170,@CGUID+171,@CGUID+172,@CGUID+173,@CGUID+174,@CGUID+175,@CGUID+176,@CGUID+177,@CGUID+178,@CGUID+180,@CGUID+181,@CGUID+182,@CGUID+183,@CGUID+184,@CGUID+185,@CGUID+186,@CGUID+187,@CGUID+188,@CGUID+189,@CGUID+190,@CGUID+191,@CGUID+218,@CGUID+219,@CGUID+220,@CGUID+221,@CGUID+222) AND `groupAI` IN (3, 24); DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+03,@CGUID+04,@CGUID+05,@CGUID+06,@CGUID+07,@CGUID+08,@CGUID+09,@CGUID+10,@CGUID+11,@CGUID+12,@CGUID+13,@CGUID+14,@CGUID+15,@CGUID+16,@CGUID+17,@CGUID+18,@CGUID+19,@CGUID+20,@CGUID+52,@CGUID+53,@CGUID+54,@CGUID+55,@CGUID+59,@CGUID+60,@CGUID+61,@CGUID+62,@CGUID+63,@CGUID+64,@CGUID+65,@CGUID+66,@CGUID+67,@CGUID+68,@CGUID+69,@CGUID+70,@CGUID+74,@CGUID+75,@CGUID+76,@CGUID+77,@CGUID+78,@CGUID+79,@CGUID+80,@CGUID+81,@CGUID+82,@CGUID+83,@CGUID+84,@CGUID+85,@CGUID+86,@CGUID+87,@CGUID+88,@CGUID+89,@CGUID+90,@CGUID+91,@CGUID+93,@CGUID+94,@CGUID+98,@CGUID+99,@CGUID+100,@CGUID+101,@CGUID+102,@CGUID+103,@CGUID+104,@CGUID+105,@CGUID+106,@CGUID+107,@CGUID+108,@CGUID+109,@CGUID+110,@CGUID+111,@CGUID+112,@CGUID+113,@CGUID+114,@CGUID+115,@CGUID+116,@CGUID+117,@CGUID+118,@CGUID+119,@CGUID+120,@CGUID+121,@CGUID+125,@CGUID+126,@CGUID+127,@CGUID+128,@CGUID+129,@CGUID+130,@CGUID+131,@CGUID+132,@CGUID+133,@CGUID+134,@CGUID+135,@CGUID+136,@CGUID+143,@CGUID+144,@CGUID+145,@CGUID+146,@CGUID+147,@CGUID+148,@CGUID+149,@CGUID+150,@CGUID+151,@CGUID+152,@CGUID+153,@CGUID+154,@CGUID+155,@CGUID+156,@CGUID+157,@CGUID+158,@CGUID+159,@CGUID+160,@CGUID+161,@CGUID+162,@CGUID+163,@CGUID+164,@CGUID+165,@CGUID+166,@CGUID+167,@CGUID+168,@CGUID+169,@CGUID+170,@CGUID+171,@CGUID+172,@CGUID+173,@CGUID+174,@CGUID+175,@CGUID+176,@CGUID+177,@CGUID+178,@CGUID+180,@CGUID+181,@CGUID+182,@CGUID+183,@CGUID+184,@CGUID+185,@CGUID+186,@CGUID+187,@CGUID+188,@CGUID+189,@CGUID+190,@CGUID+191,@CGUID+218,@CGUID+219,@CGUID+220,@CGUID+221,@CGUID+222) AND `groupAI` IN (3, 24);
INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `dist`, `angle`, `groupAI`) VALUES
-- Entrance Group 1 -- Entrance Group 1
(@CGUID+3, @CGUID+3, 3), (@CGUID+3, @CGUID+3, 0, 0, 3),
(@CGUID+4, @CGUID+3, 3), (@CGUID+4, @CGUID+3, 0, 0, 3),
(@CGUID+5, @CGUID+3, 3), (@CGUID+5, @CGUID+3, 0, 0, 3),
(@CGUID+6, @CGUID+3, 3), (@CGUID+6, @CGUID+3, 0, 0, 3),
(@CGUID+7, @CGUID+3, 3), (@CGUID+7, @CGUID+3, 0, 0, 3),
(@CGUID+8, @CGUID+3, 3), (@CGUID+8, @CGUID+3, 0, 0, 3),
-- Entrance Group 2 -- Entrance Group 2
(@CGUID+9 , @CGUID+9, 3), (@CGUID+9, @CGUID+9, 0, 0, 3),
(@CGUID+10, @CGUID+9, 3), (@CGUID+10, @CGUID+9, 0, 0, 3),
(@CGUID+11, @CGUID+9, 3), (@CGUID+11, @CGUID+9, 0, 0, 3),
(@CGUID+12, @CGUID+9, 3), (@CGUID+12, @CGUID+9, 0, 0, 3),
(@CGUID+13, @CGUID+9, 3), (@CGUID+13, @CGUID+9, 0, 0, 3),
(@CGUID+14, @CGUID+9, 3), (@CGUID+14, @CGUID+9, 0, 0, 3),
-- Entrance Group 3 -- Entrance Group 3
(@CGUID+15, @CGUID+15, 3), (@CGUID+15, @CGUID+15, 0, 0, 3),
(@CGUID+16, @CGUID+15, 3), (@CGUID+16, @CGUID+15, 0, 0, 3),
(@CGUID+17, @CGUID+15, 3), (@CGUID+17, @CGUID+15, 0, 0, 3),
(@CGUID+18, @CGUID+15, 3), (@CGUID+18, @CGUID+15, 0, 0, 3),
(@CGUID+19, @CGUID+15, 3), (@CGUID+19, @CGUID+15, 0, 0, 3),
(@CGUID+20, @CGUID+15, 3), (@CGUID+20, @CGUID+15, 0, 0, 3),
-- Sentinel Group 1 -- Sentinel Group 1
(@CGUID+52, @CGUID+52, 3), (@CGUID+52, @CGUID+52, 0, 0, 3),
(@CGUID+53, @CGUID+52, 3), (@CGUID+53, @CGUID+52, 0, 0, 3),
-- Sentinel Group 2 -- Sentinel Group 2
(@CGUID+54, @CGUID+54, 3), (@CGUID+54, @CGUID+54, 0, 0, 3),
(@CGUID+55, @CGUID+54, 3), (@CGUID+55, @CGUID+54, 0, 0, 3),
-- Inquisitor Adds 1 -- Inquisitor Adds 1
(@CGUID+59, @CGUID+59, 3), (@CGUID+59, @CGUID+59, 0, 0, 3),
(@CGUID+60, @CGUID+59, 3), (@CGUID+60, @CGUID+59, 0, 0, 3),
(@CGUID+61, @CGUID+59, 3), (@CGUID+61, @CGUID+59, 0, 0, 3),
(@CGUID+62, @CGUID+59, 3), (@CGUID+62, @CGUID+59, 0, 0, 3),
(@CGUID+63, @CGUID+59, 3), (@CGUID+63, @CGUID+59, 0, 0, 3),
(@CGUID+64, @CGUID+59, 3), (@CGUID+64, @CGUID+59, 0, 0, 3),
-- Inquisitor Adds 2 -- Inquisitor Adds 2
(@CGUID+65, @CGUID+65, 3), (@CGUID+65, @CGUID+65, 0, 0, 3),
(@CGUID+66, @CGUID+65, 3), (@CGUID+66, @CGUID+65, 0, 0, 3),
(@CGUID+67, @CGUID+65, 3), (@CGUID+67, @CGUID+65, 0, 0, 3),
(@CGUID+68, @CGUID+65, 3), (@CGUID+68, @CGUID+65, 0, 0, 3),
(@CGUID+69, @CGUID+65, 3), (@CGUID+69, @CGUID+65, 0, 0, 3),
(@CGUID+70, @CGUID+65, 3), (@CGUID+70, @CGUID+65, 0, 0, 3),
-- Void Reaver Trash Group 1 -- Void Reaver Trash Group 1
(@CGUID+74, @CGUID+74, 3), (@CGUID+74, @CGUID+74, 0, 0, 3),
(@CGUID+75, @CGUID+74, 3), (@CGUID+75, @CGUID+74, 0, 0, 3),
(@CGUID+76, @CGUID+74, 3), (@CGUID+76, @CGUID+74, 0, 0, 3),
(@CGUID+77, @CGUID+74, 3), (@CGUID+77, @CGUID+74, 0, 0, 3),
(@CGUID+78, @CGUID+74, 3), (@CGUID+78, @CGUID+74, 0, 0, 3),
-- Void Reaver Trash Group 2 -- Void Reaver Trash Group 2
(@CGUID+79, @CGUID+79, 3), (@CGUID+79, @CGUID+79, 0, 0, 3),
(@CGUID+80, @CGUID+79, 3), (@CGUID+80, @CGUID+79, 0, 0, 3),
(@CGUID+81, @CGUID+79, 3), (@CGUID+81, @CGUID+79, 0, 0, 3),
(@CGUID+82, @CGUID+79, 3), (@CGUID+82, @CGUID+79, 0, 0, 3),
(@CGUID+83, @CGUID+79, 3), (@CGUID+83, @CGUID+79, 0, 0, 3),
-- Void Reaver Trash Group 3 -- Void Reaver Trash Group 3
(@CGUID+84, @CGUID+84, 3), (@CGUID+84, @CGUID+84, 0, 0, 3),
(@CGUID+85, @CGUID+84, 3), (@CGUID+85, @CGUID+84, 0, 0, 3),
(@CGUID+86, @CGUID+84, 3), (@CGUID+86, @CGUID+84, 0, 0, 3),
(@CGUID+87, @CGUID+84, 3), (@CGUID+87, @CGUID+84, 0, 0, 3),
-- Void Reaver Trash Group 4 -- Void Reaver Trash Group 4
(@CGUID+88, @CGUID+88, 3), (@CGUID+88, @CGUID+88, 0, 0, 3),
(@CGUID+89, @CGUID+88, 3), (@CGUID+89, @CGUID+88, 0, 0, 3),
(@CGUID+90, @CGUID+88, 3), (@CGUID+90, @CGUID+88, 0, 0, 3),
(@CGUID+91, @CGUID+88, 3), (@CGUID+91, @CGUID+88, 0, 0, 3),
-- Solarium Sentinel Group 1 -- Solarium Sentinel Group 1
(@CGUID+93, @CGUID+93, 3), (@CGUID+93, @CGUID+93, 0, 0, 3),
(@CGUID+94, @CGUID+93, 3), (@CGUID+94, @CGUID+93, 0, 0, 3),
-- Solarium Large Group 1 -- Solarium Large Group 1
(@CGUID+98 , @CGUID+98, 3), (@CGUID+98, @CGUID+98, 0, 0, 3),
(@CGUID+99 , @CGUID+98, 3), (@CGUID+99, @CGUID+98, 0, 0, 3),
(@CGUID+100, @CGUID+98, 3), (@CGUID+100, @CGUID+98, 0, 0, 3),
(@CGUID+101, @CGUID+98, 3), (@CGUID+101, @CGUID+98, 0, 0, 3),
(@CGUID+102, @CGUID+98, 3), (@CGUID+102, @CGUID+98, 0, 0, 3),
(@CGUID+103, @CGUID+98, 3), (@CGUID+103, @CGUID+98, 0, 0, 3),
(@CGUID+104, @CGUID+98, 3), (@CGUID+104, @CGUID+98, 0, 0, 3),
(@CGUID+105, @CGUID+98, 3), (@CGUID+105, @CGUID+98, 0, 0, 3),
(@CGUID+106, @CGUID+98, 3), (@CGUID+106, @CGUID+98, 0, 0, 3),
(@CGUID+107, @CGUID+98, 3), (@CGUID+107, @CGUID+98, 0, 0, 3),
(@CGUID+108, @CGUID+98, 3), (@CGUID+108, @CGUID+98, 0, 0, 3),
(@CGUID+109, @CGUID+98, 3), (@CGUID+109, @CGUID+98, 0, 0, 3),
-- Solarium Large Group 2 -- Solarium Large Group 2
(@CGUID+110, @CGUID+110, 3), (@CGUID+110, @CGUID+110, 0, 0, 3),
(@CGUID+111, @CGUID+110, 3), (@CGUID+111, @CGUID+110, 0, 0, 3),
(@CGUID+112, @CGUID+110, 3), (@CGUID+112, @CGUID+110, 0, 0, 3),
(@CGUID+113, @CGUID+110, 3), (@CGUID+113, @CGUID+110, 0, 0, 3),
(@CGUID+114, @CGUID+110, 3), (@CGUID+114, @CGUID+110, 0, 0, 3),
(@CGUID+115, @CGUID+110, 3), (@CGUID+115, @CGUID+110, 0, 0, 3),
(@CGUID+116, @CGUID+110, 3), (@CGUID+116, @CGUID+110, 0, 0, 3),
(@CGUID+117, @CGUID+110, 3), (@CGUID+117, @CGUID+110, 0, 0, 3),
(@CGUID+118, @CGUID+110, 3), (@CGUID+118, @CGUID+110, 0, 0, 3),
(@CGUID+119, @CGUID+110, 3), (@CGUID+119, @CGUID+110, 0, 0, 3),
(@CGUID+120, @CGUID+110, 3), (@CGUID+120, @CGUID+110, 0, 0, 3),
(@CGUID+121, @CGUID+110, 3), (@CGUID+121, @CGUID+110, 0, 0, 3),
-- Inquisitor Adds 3 -- Inquisitor Adds 3
(@CGUID+125, @CGUID+125, 3), (@CGUID+125, @CGUID+125, 0, 0, 3),
(@CGUID+126, @CGUID+125, 3), (@CGUID+126, @CGUID+125, 0, 0, 3),
(@CGUID+127, @CGUID+125, 3), (@CGUID+127, @CGUID+125, 0, 0, 3),
(@CGUID+128, @CGUID+125, 3), (@CGUID+128, @CGUID+125, 0, 0, 3),
(@CGUID+129, @CGUID+125, 3), (@CGUID+129, @CGUID+125, 0, 0, 3),
(@CGUID+130, @CGUID+125, 3), (@CGUID+130, @CGUID+125, 0, 0, 3),
-- Inquisitor Adds 4 -- Inquisitor Adds 4
(@CGUID+131, @CGUID+131, 3), (@CGUID+131, @CGUID+131, 0, 0, 3),
(@CGUID+132, @CGUID+131, 3), (@CGUID+132, @CGUID+131, 0, 0, 3),
(@CGUID+133, @CGUID+131, 3), (@CGUID+133, @CGUID+131, 0, 0, 3),
(@CGUID+134, @CGUID+131, 3), (@CGUID+134, @CGUID+131, 0, 0, 3),
(@CGUID+135, @CGUID+131, 3), (@CGUID+135, @CGUID+131, 0, 0, 3),
(@CGUID+136, @CGUID+131, 3), (@CGUID+136, @CGUID+131, 0, 0, 3),
-- Solarian Adds 1 -- Solarian Adds 1
(@CGUID+143, @CGUID+143, 3), (@CGUID+143, @CGUID+143, 0, 0, 3),
(@CGUID+144, @CGUID+143, 3), (@CGUID+144, @CGUID+143, 0, 0, 3),
(@CGUID+145, @CGUID+143, 3), (@CGUID+145, @CGUID+143, 0, 0, 3),
(@CGUID+146, @CGUID+143, 3), (@CGUID+146, @CGUID+143, 0, 0, 3),
(@CGUID+147, @CGUID+143, 3), (@CGUID+147, @CGUID+143, 0, 0, 3),
(@CGUID+148, @CGUID+143, 3), (@CGUID+148, @CGUID+143, 0, 0, 3),
-- Solarian Adds 2 -- Solarian Adds 2
(@CGUID+149, @CGUID+149, 3), (@CGUID+149, @CGUID+149, 0, 0, 3),
(@CGUID+150, @CGUID+149, 3), (@CGUID+150, @CGUID+149, 0, 0, 3),
(@CGUID+151, @CGUID+149, 3), (@CGUID+151, @CGUID+149, 0, 0, 3),
(@CGUID+152, @CGUID+149, 3), (@CGUID+152, @CGUID+149, 0, 0, 3),
(@CGUID+153, @CGUID+149, 3), (@CGUID+153, @CGUID+149, 0, 0, 3),
(@CGUID+154, @CGUID+149, 3), (@CGUID+154, @CGUID+149, 0, 0, 3),
-- Solarian Adds 3 -- Solarian Adds 3
(@CGUID+155, @CGUID+155, 3), (@CGUID+155, @CGUID+155, 0, 0, 3),
(@CGUID+156, @CGUID+155, 3), (@CGUID+156, @CGUID+155, 0, 0, 3),
(@CGUID+157, @CGUID+155, 3), (@CGUID+157, @CGUID+155, 0, 0, 3),
(@CGUID+158, @CGUID+155, 3), (@CGUID+158, @CGUID+155, 0, 0, 3),
(@CGUID+159, @CGUID+155, 3), (@CGUID+159, @CGUID+155, 0, 0, 3),
(@CGUID+160, @CGUID+155, 3), (@CGUID+160, @CGUID+155, 0, 0, 3),
-- Solarian Adds 4 -- Solarian Adds 4
(@CGUID+161, @CGUID+161, 3), (@CGUID+161, @CGUID+161, 0, 0, 3),
(@CGUID+162, @CGUID+161, 3), (@CGUID+162, @CGUID+161, 0, 0, 3),
(@CGUID+163, @CGUID+161, 3), (@CGUID+163, @CGUID+161, 0, 0, 3),
(@CGUID+164, @CGUID+161, 3), (@CGUID+164, @CGUID+161, 0, 0, 3),
(@CGUID+165, @CGUID+161, 3), (@CGUID+165, @CGUID+161, 0, 0, 3),
(@CGUID+166, @CGUID+161, 3), (@CGUID+166, @CGUID+161, 0, 0, 3),
-- Solarian Adds 5 -- Solarian Adds 5
(@CGUID+167, @CGUID+167, 3), (@CGUID+167, @CGUID+167, 0, 0, 3),
(@CGUID+168, @CGUID+167, 3), (@CGUID+168, @CGUID+167, 0, 0, 3),
(@CGUID+169, @CGUID+167, 3), (@CGUID+169, @CGUID+167, 0, 0, 3),
(@CGUID+170, @CGUID+167, 3), (@CGUID+170, @CGUID+167, 0, 0, 3),
(@CGUID+171, @CGUID+167, 3), (@CGUID+171, @CGUID+167, 0, 0, 3),
(@CGUID+172, @CGUID+167, 3), (@CGUID+172, @CGUID+167, 0, 0, 3),
-- Solarian Adds 6 -- Solarian Adds 6
(@CGUID+173, @CGUID+173, 3), (@CGUID+173, @CGUID+173, 0, 0, 3),
(@CGUID+174, @CGUID+173, 3), (@CGUID+174, @CGUID+173, 0, 0, 3),
(@CGUID+175, @CGUID+173, 3), (@CGUID+175, @CGUID+173, 0, 0, 3),
(@CGUID+176, @CGUID+173, 3), (@CGUID+176, @CGUID+173, 0, 0, 3),
(@CGUID+177, @CGUID+173, 3), (@CGUID+177, @CGUID+173, 0, 0, 3),
(@CGUID+178, @CGUID+173, 3), (@CGUID+178, @CGUID+173, 0, 0, 3),
-- Kael Trash 1 -- Kael Trash 1
(@CGUID+180, @CGUID+180, 3), (@CGUID+180, @CGUID+180, 0, 0, 3),
(@CGUID+181, @CGUID+180, 3), (@CGUID+181, @CGUID+180, 0, 0, 3),
(@CGUID+182, @CGUID+180, 3), (@CGUID+182, @CGUID+180, 0, 0, 3),
(@CGUID+183, @CGUID+180, 3), (@CGUID+183, @CGUID+180, 0, 0, 3),
-- Kael Trash 2 -- Kael Trash 2
(@CGUID+184, @CGUID+184, 3), (@CGUID+184, @CGUID+184, 0, 0, 3),
(@CGUID+185, @CGUID+184, 3), (@CGUID+185, @CGUID+184, 0, 0, 3),
(@CGUID+186, @CGUID+184, 3), (@CGUID+186, @CGUID+184, 0, 0, 3),
(@CGUID+187, @CGUID+184, 3), (@CGUID+187, @CGUID+184, 0, 0, 3),
-- Kael Trash 3 -- Kael Trash 3
(@CGUID+188, @CGUID+188, 3), (@CGUID+188, @CGUID+188, 0, 0, 3),
(@CGUID+189, @CGUID+188, 3), (@CGUID+189, @CGUID+188, 0, 0, 3),
(@CGUID+190, @CGUID+188, 3), (@CGUID+190, @CGUID+188, 0, 0, 3),
(@CGUID+191, @CGUID+188, 3), (@CGUID+191, @CGUID+188, 0, 0, 3),
-- Kael & Advisors -- Kael & Advisors
(@CGUID+218, @CGUID+218, 24), (@CGUID+218, @CGUID+218, 0, 0, 24),
(@CGUID+219, @CGUID+218, 24), (@CGUID+219, @CGUID+218, 0, 0, 24),
(@CGUID+220, @CGUID+218, 24), (@CGUID+220, @CGUID+218, 0, 0, 24),
(@CGUID+221, @CGUID+218, 24), (@CGUID+221, @CGUID+218, 0, 0, 24),
(@CGUID+222, @CGUID+218, 24); (@CGUID+222, @CGUID+218, 0, 0, 24);
-- Update SheatheState en masse -- Update SheatheState en masse
UPDATE `creature_template_addon` SET `bytes2` = 1 WHERE `entry` IN (18805,19514,19516,19622,20031,20032,20033,20034,20035,20036,20037,20038,20039,20040,20041,20042,20043,20044,20045,20046,20047,20048,20049,20050,20052,20060,20062,20063,20064,22515,22517); UPDATE `creature_template_addon` SET `bytes2` = 1 WHERE `entry` IN (18805,19514,19516,19622,20031,20032,20033,20034,20035,20036,20037,20038,20039,20040,20041,20042,20043,20044,20045,20046,20047,20048,20049,20050,20052,20060,20062,20063,20064,22515,22517);
+405 -405
View File
@@ -3770,411 +3770,411 @@ INSERT INTO `waypoint_data` (`id`,`point`,`position_x`,`position_y`,`position_z`
-- 0x203CA44680168B80007C2000008127DD .go xyz 409.20535 785.9281 14.643406 -- 0x203CA44680168B80007C2000008127DD .go xyz 409.20535 785.9281 14.643406
DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+1,@CGUID+2,@CGUID+3,@CGUID+4,@CGUID+5,@CGUID+6,@CGUID+7,@CGUID+8,@CGUID+9,@CGUID+10,@CGUID+11,@CGUID+12,@CGUID+23,@CGUID+24,@CGUID+25,@CGUID+26,@CGUID+27,@CGUID+28,@CGUID+29,@CGUID+30,@CGUID+31,@CGUID+32,@CGUID+33,@CGUID+34,@CGUID+35,@CGUID+36,@CGUID+37,@CGUID+38,@CGUID+39,@CGUID+40,@CGUID+41,@CGUID+42,@CGUID+43,@CGUID+44,@CGUID+45,@CGUID+46,@CGUID+47,@CGUID+48,@CGUID+49,@CGUID+50,@CGUID+51,@CGUID+52,@CGUID+53,@CGUID+54,@CGUID+62,@CGUID+63,@CGUID+64,@CGUID+65,@CGUID+66,@CGUID+67,@CGUID+68,@CGUID+69,@CGUID+70,@CGUID+71,@CGUID+74,@CGUID+75,@CGUID+76,@CGUID+77,@CGUID+78,@CGUID+79,@CGUID+82,@CGUID+83,@CGUID+84,@CGUID+85,@CGUID+86,@CGUID+87,@CGUID+88,@CGUID+89,@CGUID+90,@CGUID+91,@CGUID+92,@CGUID+93,@CGUID+94,@CGUID+95,@CGUID+96,@CGUID+97,@CGUID+98,@CGUID+99,@CGUID+146,@CGUID+147,@CGUID+148,@CGUID+149,@CGUID+150,@CGUID+151,@CGUID+152,@CGUID+153,@CGUID+154,@CGUID+155,@CGUID+156,@CGUID+157,@CGUID+158,@CGUID+159,@CGUID+160,@CGUID+161,@CGUID+162,@CGUID+163,@CGUID+164,@CGUID+165,@CGUID+166,@CGUID+167,@CGUID+168,@CGUID+169,@CGUID+170,@CGUID+171,@CGUID+172,@CGUID+173,@CGUID+174,@CGUID+175,@CGUID+188,@CGUID+189,@CGUID+190,@CGUID+191,@CGUID+192,@CGUID+193,@CGUID+194,@CGUID+195,@CGUID+196,@CGUID+197,@CGUID+198,@CGUID+199,@CGUID+200,@CGUID+201,@CGUID+202,@CGUID+203,@CGUID+204,@CGUID+205,@CGUID+206,@CGUID+207,@CGUID+208,@CGUID+209,@CGUID+210,@CGUID+211,@CGUID+212,@CGUID+213,@CGUID+222,@CGUID+223,@CGUID+224,@CGUID+225,@CGUID+226,@CGUID+227,@CGUID+228,@CGUID+229,@CGUID+253,@CGUID+254,@CGUID+255,@CGUID+256,@CGUID+266,@CGUID+267,@CGUID+268,@CGUID+269,@CGUID+270,@CGUID+271,@CGUID+272,@CGUID+273,@CGUID+274,@CGUID+275,@CGUID+276,@CGUID+277,@CGUID+278,@CGUID+279,@CGUID+280,@CGUID+281,@CGUID+282,@CGUID+283,@CGUID+284,@CGUID+287,@CGUID+289,@CGUID+290,@CGUID+291,@CGUID+292,@CGUID+293,@CGUID+294,@CGUID+295,@CGUID+296,@CGUID+297,@CGUID+298,@CGUID+299,@CGUID+300,@CGUID+301,@CGUID+302,@CGUID+303,@CGUID+304,@CGUID+305,@CGUID+318,@CGUID+319,@CGUID+320,@CGUID+321,@CGUID+322,@CGUID+323,@CGUID+324,@CGUID+325,@CGUID+326,@CGUID+327,@CGUID+328,@CGUID+329,@CGUID+330,@CGUID+331,@CGUID+365,@CGUID+366,@CGUID+367,@CGUID+368,@CGUID+369,@CGUID+370,@CGUID+371,@CGUID+372,@CGUID+373,@CGUID+374,@CGUID+375,@CGUID+376,@CGUID+377,@CGUID+378,@CGUID+379,@CGUID+380,@CGUID+383,@CGUID+384,@CGUID+385,@CGUID+386,@CGUID+387,@CGUID+388,@CGUID+389,@CGUID+390,@CGUID+391,@CGUID+392,@CGUID+393,@CGUID+396,@CGUID+397,@CGUID+398,@CGUID+399,@CGUID+400,@CGUID+401,@CGUID+402,@CGUID+403,@CGUID+404,@CGUID+405,@CGUID+406,@CGUID+418,@CGUID+419,@CGUID+420,@CGUID+421,@CGUID+422,@CGUID+423,@CGUID+540,@CGUID+541,@CGUID+542,@CGUID+543,@CGUID+544,@CGUID+545,@CGUID+546,@CGUID+547,@CGUID+548,@CGUID+549,@CGUID+550,@CGUID+551,@CGUID+552,@CGUID+553,@CGUID+554,@CGUID+555,@CGUID+556,@CGUID+557,@CGUID+558,@CGUID+559,@CGUID+560,@CGUID+561,@CGUID+562,@CGUID+563,@CGUID+564,@CGUID+565,@CGUID+566,@CGUID+567,@CGUID+568,@CGUID+569,@CGUID+570,@CGUID+571,@CGUID+572,@CGUID+573,@CGUID+574,@CGUID+575,@CGUID+576,@CGUID+577,@CGUID+578,@CGUID+579,@CGUID+580,@CGUID+581,@CGUID+582,@CGUID+583,@CGUID+584,@CGUID+585,@CGUID+586,@CGUID+587,@CGUID+588,@CGUID+589,@CGUID+592,@CGUID+593,@CGUID+594,@CGUID+595,@CGUID+596,@CGUID+597,@CGUID+598,@CGUID+599,@CGUID+600,@CGUID+601,@CGUID+602,@CGUID+603,@CGUID+605,@CGUID+606,@CGUID+607,@CGUID+608,@CGUID+609,@CGUID+610,@CGUID+611,@CGUID+612,@CGUID+613,@CGUID+614,@CGUID+615,@CGUID+616,@CGUID+617,@CGUID+618,@CGUID+619,@CGUID+620,@CGUID+621,@CGUID+622,@CGUID+623,@CGUID+624,@CGUID+625,@CGUID+626,@CGUID+627,@CGUID+628,@CGUID+629,@CGUID+630,@CGUID+631,@CGUID+632,@CGUID+633,@CGUID+634,@CGUID+635,@CGUID+636,@CGUID+637,@CGUID+638,@CGUID+639,@CGUID+640,@CGUID+641,@CGUID+642,@CGUID+643,@CGUID+644,@CGUID+645,@CGUID+646,@CGUID+647,@CGUID+648,@CGUID+649,@CGUID+650,@CGUID+651,@CGUID+652,@CGUID+653,@CGUID+654,@CGUID+655,@CGUID+656,@CGUID+657,@CGUID+658,@CGUID+659,@CGUID+660,@CGUID+661,@CGUID+662,@CGUID+663,@CGUID+664,@CGUID+665,@CGUID+666,@CGUID+667,@CGUID+668,@CGUID+669,@CGUID+670,@CGUID+671,@CGUID+672,@CGUID+673,@CGUID+674,@CGUID+675,@CGUID+676,@CGUID+677,@CGUID+691,@CGUID+692,@CGUID+693,@CGUID+694,@CGUID+695,@CGUID+696,@CGUID+697,@CGUID+698,@CGUID+699,@CGUID+700,@CGUID+701,@CGUID+702,@CGUID+703,@CGUID+704,@CGUID+705,@CGUID+706,@CGUID+707,@CGUID+708,@CGUID+709,@CGUID+710,@CGUID+711,@CGUID+712,@CGUID+713,@CGUID+714,@CGUID+716,@CGUID+717,@CGUID+718,@CGUID+719) AND `groupAI` IN (3, 27); DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+1,@CGUID+2,@CGUID+3,@CGUID+4,@CGUID+5,@CGUID+6,@CGUID+7,@CGUID+8,@CGUID+9,@CGUID+10,@CGUID+11,@CGUID+12,@CGUID+23,@CGUID+24,@CGUID+25,@CGUID+26,@CGUID+27,@CGUID+28,@CGUID+29,@CGUID+30,@CGUID+31,@CGUID+32,@CGUID+33,@CGUID+34,@CGUID+35,@CGUID+36,@CGUID+37,@CGUID+38,@CGUID+39,@CGUID+40,@CGUID+41,@CGUID+42,@CGUID+43,@CGUID+44,@CGUID+45,@CGUID+46,@CGUID+47,@CGUID+48,@CGUID+49,@CGUID+50,@CGUID+51,@CGUID+52,@CGUID+53,@CGUID+54,@CGUID+62,@CGUID+63,@CGUID+64,@CGUID+65,@CGUID+66,@CGUID+67,@CGUID+68,@CGUID+69,@CGUID+70,@CGUID+71,@CGUID+74,@CGUID+75,@CGUID+76,@CGUID+77,@CGUID+78,@CGUID+79,@CGUID+82,@CGUID+83,@CGUID+84,@CGUID+85,@CGUID+86,@CGUID+87,@CGUID+88,@CGUID+89,@CGUID+90,@CGUID+91,@CGUID+92,@CGUID+93,@CGUID+94,@CGUID+95,@CGUID+96,@CGUID+97,@CGUID+98,@CGUID+99,@CGUID+146,@CGUID+147,@CGUID+148,@CGUID+149,@CGUID+150,@CGUID+151,@CGUID+152,@CGUID+153,@CGUID+154,@CGUID+155,@CGUID+156,@CGUID+157,@CGUID+158,@CGUID+159,@CGUID+160,@CGUID+161,@CGUID+162,@CGUID+163,@CGUID+164,@CGUID+165,@CGUID+166,@CGUID+167,@CGUID+168,@CGUID+169,@CGUID+170,@CGUID+171,@CGUID+172,@CGUID+173,@CGUID+174,@CGUID+175,@CGUID+188,@CGUID+189,@CGUID+190,@CGUID+191,@CGUID+192,@CGUID+193,@CGUID+194,@CGUID+195,@CGUID+196,@CGUID+197,@CGUID+198,@CGUID+199,@CGUID+200,@CGUID+201,@CGUID+202,@CGUID+203,@CGUID+204,@CGUID+205,@CGUID+206,@CGUID+207,@CGUID+208,@CGUID+209,@CGUID+210,@CGUID+211,@CGUID+212,@CGUID+213,@CGUID+222,@CGUID+223,@CGUID+224,@CGUID+225,@CGUID+226,@CGUID+227,@CGUID+228,@CGUID+229,@CGUID+253,@CGUID+254,@CGUID+255,@CGUID+256,@CGUID+266,@CGUID+267,@CGUID+268,@CGUID+269,@CGUID+270,@CGUID+271,@CGUID+272,@CGUID+273,@CGUID+274,@CGUID+275,@CGUID+276,@CGUID+277,@CGUID+278,@CGUID+279,@CGUID+280,@CGUID+281,@CGUID+282,@CGUID+283,@CGUID+284,@CGUID+287,@CGUID+289,@CGUID+290,@CGUID+291,@CGUID+292,@CGUID+293,@CGUID+294,@CGUID+295,@CGUID+296,@CGUID+297,@CGUID+298,@CGUID+299,@CGUID+300,@CGUID+301,@CGUID+302,@CGUID+303,@CGUID+304,@CGUID+305,@CGUID+318,@CGUID+319,@CGUID+320,@CGUID+321,@CGUID+322,@CGUID+323,@CGUID+324,@CGUID+325,@CGUID+326,@CGUID+327,@CGUID+328,@CGUID+329,@CGUID+330,@CGUID+331,@CGUID+365,@CGUID+366,@CGUID+367,@CGUID+368,@CGUID+369,@CGUID+370,@CGUID+371,@CGUID+372,@CGUID+373,@CGUID+374,@CGUID+375,@CGUID+376,@CGUID+377,@CGUID+378,@CGUID+379,@CGUID+380,@CGUID+383,@CGUID+384,@CGUID+385,@CGUID+386,@CGUID+387,@CGUID+388,@CGUID+389,@CGUID+390,@CGUID+391,@CGUID+392,@CGUID+393,@CGUID+396,@CGUID+397,@CGUID+398,@CGUID+399,@CGUID+400,@CGUID+401,@CGUID+402,@CGUID+403,@CGUID+404,@CGUID+405,@CGUID+406,@CGUID+418,@CGUID+419,@CGUID+420,@CGUID+421,@CGUID+422,@CGUID+423,@CGUID+540,@CGUID+541,@CGUID+542,@CGUID+543,@CGUID+544,@CGUID+545,@CGUID+546,@CGUID+547,@CGUID+548,@CGUID+549,@CGUID+550,@CGUID+551,@CGUID+552,@CGUID+553,@CGUID+554,@CGUID+555,@CGUID+556,@CGUID+557,@CGUID+558,@CGUID+559,@CGUID+560,@CGUID+561,@CGUID+562,@CGUID+563,@CGUID+564,@CGUID+565,@CGUID+566,@CGUID+567,@CGUID+568,@CGUID+569,@CGUID+570,@CGUID+571,@CGUID+572,@CGUID+573,@CGUID+574,@CGUID+575,@CGUID+576,@CGUID+577,@CGUID+578,@CGUID+579,@CGUID+580,@CGUID+581,@CGUID+582,@CGUID+583,@CGUID+584,@CGUID+585,@CGUID+586,@CGUID+587,@CGUID+588,@CGUID+589,@CGUID+592,@CGUID+593,@CGUID+594,@CGUID+595,@CGUID+596,@CGUID+597,@CGUID+598,@CGUID+599,@CGUID+600,@CGUID+601,@CGUID+602,@CGUID+603,@CGUID+605,@CGUID+606,@CGUID+607,@CGUID+608,@CGUID+609,@CGUID+610,@CGUID+611,@CGUID+612,@CGUID+613,@CGUID+614,@CGUID+615,@CGUID+616,@CGUID+617,@CGUID+618,@CGUID+619,@CGUID+620,@CGUID+621,@CGUID+622,@CGUID+623,@CGUID+624,@CGUID+625,@CGUID+626,@CGUID+627,@CGUID+628,@CGUID+629,@CGUID+630,@CGUID+631,@CGUID+632,@CGUID+633,@CGUID+634,@CGUID+635,@CGUID+636,@CGUID+637,@CGUID+638,@CGUID+639,@CGUID+640,@CGUID+641,@CGUID+642,@CGUID+643,@CGUID+644,@CGUID+645,@CGUID+646,@CGUID+647,@CGUID+648,@CGUID+649,@CGUID+650,@CGUID+651,@CGUID+652,@CGUID+653,@CGUID+654,@CGUID+655,@CGUID+656,@CGUID+657,@CGUID+658,@CGUID+659,@CGUID+660,@CGUID+661,@CGUID+662,@CGUID+663,@CGUID+664,@CGUID+665,@CGUID+666,@CGUID+667,@CGUID+668,@CGUID+669,@CGUID+670,@CGUID+671,@CGUID+672,@CGUID+673,@CGUID+674,@CGUID+675,@CGUID+676,@CGUID+677,@CGUID+691,@CGUID+692,@CGUID+693,@CGUID+694,@CGUID+695,@CGUID+696,@CGUID+697,@CGUID+698,@CGUID+699,@CGUID+700,@CGUID+701,@CGUID+702,@CGUID+703,@CGUID+704,@CGUID+705,@CGUID+706,@CGUID+707,@CGUID+708,@CGUID+709,@CGUID+710,@CGUID+711,@CGUID+712,@CGUID+713,@CGUID+714,@CGUID+716,@CGUID+717,@CGUID+718,@CGUID+719) AND `groupAI` IN (3, 27);
INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `dist`, `angle`, `groupAI`) VALUES
(@CGUID+1 , @CGUID+1 , 3), (@CGUID+1, @CGUID+1, 0, 0, 3),
(@CGUID+2 , @CGUID+1 , 3), (@CGUID+2, @CGUID+1, 0, 0, 3),
(@CGUID+3 , @CGUID+1 , 3), (@CGUID+3, @CGUID+1, 0, 0, 3),
(@CGUID+4 , @CGUID+4 , 3), (@CGUID+4, @CGUID+4, 0, 0, 3),
(@CGUID+5 , @CGUID+4 , 3), (@CGUID+5, @CGUID+4, 0, 0, 3),
(@CGUID+6 , @CGUID+4 , 3), (@CGUID+6, @CGUID+4, 0, 0, 3),
(@CGUID+7 , @CGUID+7 , 3), (@CGUID+7, @CGUID+7, 0, 0, 3),
(@CGUID+8 , @CGUID+7 , 3), (@CGUID+8, @CGUID+7, 0, 0, 3),
(@CGUID+9 , @CGUID+7 , 3), (@CGUID+9, @CGUID+7, 0, 0, 3),
(@CGUID+10 , @CGUID+10 , 3), (@CGUID+10, @CGUID+10, 0, 0, 3),
(@CGUID+11 , @CGUID+10 , 3), (@CGUID+11, @CGUID+10, 0, 0, 3),
(@CGUID+12 , @CGUID+10 , 3), (@CGUID+12, @CGUID+10, 0, 0, 3),
(@CGUID+23 , @CGUID+23 , 3), (@CGUID+23, @CGUID+23, 0, 0, 3),
(@CGUID+24 , @CGUID+23 , 3), (@CGUID+24, @CGUID+23, 0, 0, 3),
(@CGUID+25 , @CGUID+23 , 3), (@CGUID+25, @CGUID+23, 0, 0, 3),
(@CGUID+26 , @CGUID+23 , 3), (@CGUID+26, @CGUID+23, 0, 0, 3),
(@CGUID+27 , @CGUID+23 , 3), (@CGUID+27, @CGUID+23, 0, 0, 3),
(@CGUID+28 , @CGUID+23 , 3), (@CGUID+28, @CGUID+23, 0, 0, 3),
(@CGUID+29 , @CGUID+23 , 3), (@CGUID+29, @CGUID+23, 0, 0, 3),
(@CGUID+30 , @CGUID+23 , 3), (@CGUID+30, @CGUID+23, 0, 0, 3),
(@CGUID+31 , @CGUID+23 , 3), (@CGUID+31, @CGUID+23, 0, 0, 3),
(@CGUID+32 , @CGUID+23 , 3), (@CGUID+32, @CGUID+23, 0, 0, 3),
(@CGUID+33 , @CGUID+33 , 3), (@CGUID+33, @CGUID+33, 0, 0, 3),
(@CGUID+34 , @CGUID+33 , 3), (@CGUID+34, @CGUID+33, 0, 0, 3),
(@CGUID+35 , @CGUID+33 , 3), (@CGUID+35, @CGUID+33, 0, 0, 3),
(@CGUID+36 , @CGUID+33 , 3), (@CGUID+36, @CGUID+33, 0, 0, 3),
(@CGUID+37 , @CGUID+33 , 3), (@CGUID+37, @CGUID+33, 0, 0, 3),
(@CGUID+38 , @CGUID+33 , 3), (@CGUID+38, @CGUID+33, 0, 0, 3),
(@CGUID+39 , @CGUID+33 , 3), (@CGUID+39, @CGUID+33, 0, 0, 3),
(@CGUID+40 , @CGUID+33 , 3), (@CGUID+40, @CGUID+33, 0, 0, 3),
(@CGUID+41 , @CGUID+33 , 3), (@CGUID+41, @CGUID+33, 0, 0, 3),
(@CGUID+42 , @CGUID+33 , 3), (@CGUID+42, @CGUID+33, 0, 0, 3),
(@CGUID+43 , @CGUID+43 , 3), (@CGUID+43, @CGUID+43, 0, 0, 3),
(@CGUID+44 , @CGUID+43 , 3), (@CGUID+44, @CGUID+43, 0, 0, 3),
(@CGUID+45 , @CGUID+43 , 3), (@CGUID+45, @CGUID+43, 0, 0, 3),
(@CGUID+46 , @CGUID+43 , 3), (@CGUID+46, @CGUID+43, 0, 0, 3),
(@CGUID+47 , @CGUID+43 , 3), (@CGUID+47, @CGUID+43, 0, 0, 3),
(@CGUID+48 , @CGUID+43 , 3), (@CGUID+48, @CGUID+43, 0, 0, 3),
(@CGUID+49 , @CGUID+49 , 3), (@CGUID+49, @CGUID+49, 0, 0, 3),
(@CGUID+50 , @CGUID+49 , 3), (@CGUID+50, @CGUID+49, 0, 0, 3),
(@CGUID+51 , @CGUID+49 , 3), (@CGUID+51, @CGUID+49, 0, 0, 3),
(@CGUID+52 , @CGUID+49 , 3), (@CGUID+52, @CGUID+49, 0, 0, 3),
(@CGUID+53 , @CGUID+49 , 3), (@CGUID+53, @CGUID+49, 0, 0, 3),
(@CGUID+54 , @CGUID+49 , 3), (@CGUID+54, @CGUID+49, 0, 0, 3),
(@CGUID+62 , @CGUID+62 , 27), (@CGUID+62, @CGUID+62, 0, 0, 27),
(@CGUID+63 , @CGUID+62 , 27), (@CGUID+63, @CGUID+62, 0, 0, 27),
(@CGUID+64 , @CGUID+62 , 27), (@CGUID+64, @CGUID+62, 0, 0, 27),
(@CGUID+65 , @CGUID+65 , 3), (@CGUID+65, @CGUID+65, 0, 0, 3),
(@CGUID+66 , @CGUID+65 , 3), (@CGUID+66, @CGUID+65, 0, 0, 3),
(@CGUID+67 , @CGUID+65 , 3), (@CGUID+67, @CGUID+65, 0, 0, 3),
(@CGUID+68 , @CGUID+65 , 3), (@CGUID+68, @CGUID+65, 0, 0, 3),
(@CGUID+69 , @CGUID+65 , 3), (@CGUID+69, @CGUID+65, 0, 0, 3),
(@CGUID+70 , @CGUID+65 , 3), (@CGUID+70, @CGUID+65, 0, 0, 3),
(@CGUID+71 , @CGUID+65 , 3), (@CGUID+71, @CGUID+65, 0, 0, 3),
(@CGUID+74 , @CGUID+74 , 3), (@CGUID+74, @CGUID+74, 0, 0, 3),
(@CGUID+75 , @CGUID+74 , 3), (@CGUID+75, @CGUID+74, 0, 0, 3),
(@CGUID+76 , @CGUID+74 , 3), (@CGUID+76, @CGUID+74, 0, 0, 3),
(@CGUID+77 , @CGUID+74 , 3), (@CGUID+77, @CGUID+74, 0, 0, 3),
(@CGUID+78 , @CGUID+74 , 3), (@CGUID+78, @CGUID+74, 0, 0, 3),
(@CGUID+79 , @CGUID+74 , 3), (@CGUID+79, @CGUID+74, 0, 0, 3),
(@CGUID+82 , @CGUID+82 , 3), (@CGUID+82, @CGUID+82, 0, 0, 3),
(@CGUID+83 , @CGUID+82 , 3), (@CGUID+83, @CGUID+82, 0, 0, 3),
(@CGUID+84 , @CGUID+82 , 3), (@CGUID+84, @CGUID+82, 0, 0, 3),
(@CGUID+85 , @CGUID+82 , 3), (@CGUID+85, @CGUID+82, 0, 0, 3),
(@CGUID+86 , @CGUID+82 , 3), (@CGUID+86, @CGUID+82, 0, 0, 3),
(@CGUID+87 , @CGUID+87 , 3), (@CGUID+87, @CGUID+87, 0, 0, 3),
(@CGUID+88 , @CGUID+87 , 3), (@CGUID+88, @CGUID+87, 0, 0, 3),
(@CGUID+89 , @CGUID+87 , 3), (@CGUID+89, @CGUID+87, 0, 0, 3),
(@CGUID+90 , @CGUID+87 , 3), (@CGUID+90, @CGUID+87, 0, 0, 3),
(@CGUID+91 , @CGUID+87 , 3), (@CGUID+91, @CGUID+87, 0, 0, 3),
(@CGUID+92 , @CGUID+87 , 3), (@CGUID+92, @CGUID+87, 0, 0, 3),
(@CGUID+93 , @CGUID+87 , 3), (@CGUID+93, @CGUID+87, 0, 0, 3),
(@CGUID+94 , @CGUID+94 , 3), (@CGUID+94, @CGUID+94, 0, 0, 3),
(@CGUID+95 , @CGUID+94 , 3), (@CGUID+95, @CGUID+94, 0, 0, 3),
(@CGUID+96 , @CGUID+94 , 3), (@CGUID+96, @CGUID+94, 0, 0, 3),
(@CGUID+97 , @CGUID+94 , 3), (@CGUID+97, @CGUID+94, 0, 0, 3),
(@CGUID+98 , @CGUID+94 , 3), (@CGUID+98, @CGUID+94, 0, 0, 3),
(@CGUID+99 , @CGUID+94 , 3), (@CGUID+99, @CGUID+94, 0, 0, 3),
(@CGUID+146, @CGUID+146, 3), (@CGUID+146, @CGUID+146, 0, 0, 3),
(@CGUID+147, @CGUID+146, 3), (@CGUID+147, @CGUID+146, 0, 0, 3),
(@CGUID+148, @CGUID+146, 3), (@CGUID+148, @CGUID+146, 0, 0, 3),
(@CGUID+149, @CGUID+146, 3), (@CGUID+149, @CGUID+146, 0, 0, 3),
(@CGUID+150, @CGUID+146, 3), (@CGUID+150, @CGUID+146, 0, 0, 3),
(@CGUID+151, @CGUID+146, 3), (@CGUID+151, @CGUID+146, 0, 0, 3),
(@CGUID+152, @CGUID+152, 3), (@CGUID+152, @CGUID+152, 0, 0, 3),
(@CGUID+153, @CGUID+152, 3), (@CGUID+153, @CGUID+152, 0, 0, 3),
(@CGUID+154, @CGUID+152, 3), (@CGUID+154, @CGUID+152, 0, 0, 3),
(@CGUID+155, @CGUID+152, 3), (@CGUID+155, @CGUID+152, 0, 0, 3),
(@CGUID+156, @CGUID+152, 3), (@CGUID+156, @CGUID+152, 0, 0, 3),
(@CGUID+157, @CGUID+152, 3), (@CGUID+157, @CGUID+152, 0, 0, 3),
(@CGUID+158, @CGUID+158, 3), (@CGUID+158, @CGUID+158, 0, 0, 3),
(@CGUID+159, @CGUID+158, 3), (@CGUID+159, @CGUID+158, 0, 0, 3),
(@CGUID+160, @CGUID+158, 3), (@CGUID+160, @CGUID+158, 0, 0, 3),
(@CGUID+161, @CGUID+158, 3), (@CGUID+161, @CGUID+158, 0, 0, 3),
(@CGUID+162, @CGUID+158, 3), (@CGUID+162, @CGUID+158, 0, 0, 3),
(@CGUID+163, @CGUID+158, 3), (@CGUID+163, @CGUID+158, 0, 0, 3),
(@CGUID+164, @CGUID+164, 3), (@CGUID+164, @CGUID+164, 0, 0, 3),
(@CGUID+165, @CGUID+164, 3), (@CGUID+165, @CGUID+164, 0, 0, 3),
(@CGUID+166, @CGUID+164, 3), (@CGUID+166, @CGUID+164, 0, 0, 3),
(@CGUID+167, @CGUID+164, 3), (@CGUID+167, @CGUID+164, 0, 0, 3),
(@CGUID+168, @CGUID+164, 3), (@CGUID+168, @CGUID+164, 0, 0, 3),
(@CGUID+169, @CGUID+164, 3), (@CGUID+169, @CGUID+164, 0, 0, 3),
(@CGUID+170, @CGUID+170, 3), (@CGUID+170, @CGUID+170, 0, 0, 3),
(@CGUID+171, @CGUID+170, 3), (@CGUID+171, @CGUID+170, 0, 0, 3),
(@CGUID+172, @CGUID+170, 3), (@CGUID+172, @CGUID+170, 0, 0, 3),
(@CGUID+173, @CGUID+170, 3), (@CGUID+173, @CGUID+170, 0, 0, 3),
(@CGUID+174, @CGUID+170, 3), (@CGUID+174, @CGUID+170, 0, 0, 3),
(@CGUID+175, @CGUID+170, 3), (@CGUID+175, @CGUID+170, 0, 0, 3),
(@CGUID+188, @CGUID+188, 3), (@CGUID+188, @CGUID+188, 0, 0, 3),
(@CGUID+189, @CGUID+188, 3), (@CGUID+189, @CGUID+188, 0, 0, 3),
(@CGUID+190, @CGUID+190, 3), (@CGUID+190, @CGUID+190, 0, 0, 3),
(@CGUID+191, @CGUID+190, 3), (@CGUID+191, @CGUID+190, 0, 0, 3),
(@CGUID+192, @CGUID+192, 3), (@CGUID+192, @CGUID+192, 0, 0, 3),
(@CGUID+193, @CGUID+192, 3), (@CGUID+193, @CGUID+192, 0, 0, 3),
(@CGUID+194, @CGUID+194, 3), (@CGUID+194, @CGUID+194, 0, 0, 3),
(@CGUID+195, @CGUID+194, 3), (@CGUID+195, @CGUID+194, 0, 0, 3),
(@CGUID+196, @CGUID+196, 3), (@CGUID+196, @CGUID+196, 0, 0, 3),
(@CGUID+197, @CGUID+196, 3), (@CGUID+197, @CGUID+196, 0, 0, 3),
(@CGUID+198, @CGUID+198, 3), (@CGUID+198, @CGUID+198, 0, 0, 3),
(@CGUID+199, @CGUID+198, 3), (@CGUID+199, @CGUID+198, 0, 0, 3),
(@CGUID+200, @CGUID+198, 3), (@CGUID+200, @CGUID+198, 0, 0, 3),
(@CGUID+201, @CGUID+198, 3), (@CGUID+201, @CGUID+198, 0, 0, 3),
(@CGUID+202, @CGUID+198, 3), (@CGUID+202, @CGUID+198, 0, 0, 3),
(@CGUID+203, @CGUID+198, 3), (@CGUID+203, @CGUID+198, 0, 0, 3),
(@CGUID+204, @CGUID+198, 3), (@CGUID+204, @CGUID+198, 0, 0, 3),
(@CGUID+205, @CGUID+198, 3), (@CGUID+205, @CGUID+198, 0, 0, 3),
(@CGUID+206, @CGUID+206, 3), (@CGUID+206, @CGUID+206, 0, 0, 3),
(@CGUID+207, @CGUID+206, 3), (@CGUID+207, @CGUID+206, 0, 0, 3),
(@CGUID+208, @CGUID+206, 3), (@CGUID+208, @CGUID+206, 0, 0, 3),
(@CGUID+209, @CGUID+206, 3), (@CGUID+209, @CGUID+206, 0, 0, 3),
(@CGUID+210, @CGUID+206, 3), (@CGUID+210, @CGUID+206, 0, 0, 3),
(@CGUID+211, @CGUID+206, 3), (@CGUID+211, @CGUID+206, 0, 0, 3),
(@CGUID+212, @CGUID+206, 3), (@CGUID+212, @CGUID+206, 0, 0, 3),
(@CGUID+213, @CGUID+206, 3), (@CGUID+213, @CGUID+206, 0, 0, 3),
(@CGUID+222, @CGUID+222, 3), (@CGUID+222, @CGUID+222, 0, 0, 3),
(@CGUID+223, @CGUID+222, 3), (@CGUID+223, @CGUID+222, 0, 0, 3),
(@CGUID+224, @CGUID+222, 3), (@CGUID+224, @CGUID+222, 0, 0, 3),
(@CGUID+225, @CGUID+222, 3), (@CGUID+225, @CGUID+222, 0, 0, 3),
(@CGUID+226, @CGUID+226, 3), (@CGUID+226, @CGUID+226, 0, 0, 3),
(@CGUID+227, @CGUID+226, 3), (@CGUID+227, @CGUID+226, 0, 0, 3),
(@CGUID+228, @CGUID+226, 3), (@CGUID+228, @CGUID+226, 0, 0, 3),
(@CGUID+229, @CGUID+226, 3), (@CGUID+229, @CGUID+226, 0, 0, 3),
(@CGUID+253, @CGUID+253, 3), (@CGUID+253, @CGUID+253, 0, 0, 3),
(@CGUID+254, @CGUID+253, 3), (@CGUID+254, @CGUID+253, 0, 0, 3),
(@CGUID+255, @CGUID+253, 3), (@CGUID+255, @CGUID+253, 0, 0, 3),
(@CGUID+256, @CGUID+253, 3), (@CGUID+256, @CGUID+253, 0, 0, 3),
(@CGUID+266, @CGUID+266, 3), (@CGUID+266, @CGUID+266, 0, 0, 3),
(@CGUID+267, @CGUID+266, 3), (@CGUID+267, @CGUID+266, 0, 0, 3),
(@CGUID+268, @CGUID+266, 3), (@CGUID+268, @CGUID+266, 0, 0, 3),
(@CGUID+269, @CGUID+266, 3), (@CGUID+269, @CGUID+266, 0, 0, 3),
(@CGUID+270, @CGUID+266, 3), (@CGUID+270, @CGUID+266, 0, 0, 3),
(@CGUID+271, @CGUID+266, 3), (@CGUID+271, @CGUID+266, 0, 0, 3),
(@CGUID+272, @CGUID+266, 3), (@CGUID+272, @CGUID+266, 0, 0, 3),
(@CGUID+273, @CGUID+273, 3), (@CGUID+273, @CGUID+273, 0, 0, 3),
(@CGUID+274, @CGUID+273, 3), (@CGUID+274, @CGUID+273, 0, 0, 3),
(@CGUID+275, @CGUID+273, 3), (@CGUID+275, @CGUID+273, 0, 0, 3),
(@CGUID+276, @CGUID+273, 3), (@CGUID+276, @CGUID+273, 0, 0, 3),
(@CGUID+277, @CGUID+273, 3), (@CGUID+277, @CGUID+273, 0, 0, 3),
(@CGUID+278, @CGUID+273, 3), (@CGUID+278, @CGUID+273, 0, 0, 3),
(@CGUID+279, @CGUID+273, 3), (@CGUID+279, @CGUID+273, 0, 0, 3),
(@CGUID+280, @CGUID+280, 3), (@CGUID+280, @CGUID+280, 0, 0, 3),
(@CGUID+281, @CGUID+280, 3), (@CGUID+281, @CGUID+280, 0, 0, 3),
(@CGUID+282, @CGUID+280, 3), (@CGUID+282, @CGUID+280, 0, 0, 3),
(@CGUID+283, @CGUID+280, 3), (@CGUID+283, @CGUID+280, 0, 0, 3),
(@CGUID+284, @CGUID+280, 3), (@CGUID+284, @CGUID+280, 0, 0, 3),
(@CGUID+287, @CGUID+287, 3), (@CGUID+287, @CGUID+287, 0, 0, 3),
(@CGUID+289, @CGUID+287, 3), (@CGUID+289, @CGUID+287, 0, 0, 3),
(@CGUID+290, @CGUID+287, 3), (@CGUID+290, @CGUID+287, 0, 0, 3),
(@CGUID+291, @CGUID+287, 3), (@CGUID+291, @CGUID+287, 0, 0, 3),
(@CGUID+292, @CGUID+287, 3), (@CGUID+292, @CGUID+287, 0, 0, 3),
(@CGUID+293, @CGUID+287, 3), (@CGUID+293, @CGUID+287, 0, 0, 3),
(@CGUID+294, @CGUID+287, 3), (@CGUID+294, @CGUID+287, 0, 0, 3),
(@CGUID+295, @CGUID+287, 3), (@CGUID+295, @CGUID+287, 0, 0, 3),
(@CGUID+296, @CGUID+287, 3), (@CGUID+296, @CGUID+287, 0, 0, 3),
(@CGUID+297, @CGUID+297, 3), (@CGUID+297, @CGUID+297, 0, 0, 3),
(@CGUID+298, @CGUID+297, 3), (@CGUID+298, @CGUID+297, 0, 0, 3),
(@CGUID+299, @CGUID+297, 3), (@CGUID+299, @CGUID+297, 0, 0, 3),
(@CGUID+300, @CGUID+297, 3), (@CGUID+300, @CGUID+297, 0, 0, 3),
(@CGUID+301, @CGUID+297, 3), (@CGUID+301, @CGUID+297, 0, 0, 3),
(@CGUID+302, @CGUID+302, 3), (@CGUID+302, @CGUID+302, 0, 0, 3),
(@CGUID+303, @CGUID+302, 3), (@CGUID+303, @CGUID+302, 0, 0, 3),
(@CGUID+304, @CGUID+302, 3), (@CGUID+304, @CGUID+302, 0, 0, 3),
(@CGUID+305, @CGUID+302, 3), (@CGUID+305, @CGUID+302, 0, 0, 3),
(@CGUID+318, @CGUID+318, 3), (@CGUID+318, @CGUID+318, 0, 0, 3),
(@CGUID+319, @CGUID+318, 3), (@CGUID+319, @CGUID+318, 0, 0, 3),
(@CGUID+320, @CGUID+318, 3), (@CGUID+320, @CGUID+318, 0, 0, 3),
(@CGUID+321, @CGUID+318, 3), (@CGUID+321, @CGUID+318, 0, 0, 3),
(@CGUID+322, @CGUID+318, 3), (@CGUID+322, @CGUID+318, 0, 0, 3),
(@CGUID+323, @CGUID+323, 3), (@CGUID+323, @CGUID+323, 0, 0, 3),
(@CGUID+324, @CGUID+323, 3), (@CGUID+324, @CGUID+323, 0, 0, 3),
(@CGUID+325, @CGUID+323, 3), (@CGUID+325, @CGUID+323, 0, 0, 3),
(@CGUID+326, @CGUID+323, 3), (@CGUID+326, @CGUID+323, 0, 0, 3),
(@CGUID+327, @CGUID+323, 3), (@CGUID+327, @CGUID+323, 0, 0, 3),
(@CGUID+328, @CGUID+328, 3), (@CGUID+328, @CGUID+328, 0, 0, 3),
(@CGUID+329, @CGUID+328, 3), (@CGUID+329, @CGUID+328, 0, 0, 3),
(@CGUID+330, @CGUID+330, 3), (@CGUID+330, @CGUID+330, 0, 0, 3),
(@CGUID+331, @CGUID+330, 3), (@CGUID+331, @CGUID+330, 0, 0, 3),
(@CGUID+365, @CGUID+365, 3), (@CGUID+365, @CGUID+365, 0, 0, 3),
(@CGUID+366, @CGUID+365, 3), (@CGUID+366, @CGUID+365, 0, 0, 3),
(@CGUID+367, @CGUID+365, 3), (@CGUID+367, @CGUID+365, 0, 0, 3),
(@CGUID+368, @CGUID+365, 3), (@CGUID+368, @CGUID+365, 0, 0, 3),
(@CGUID+369, @CGUID+365, 3), (@CGUID+369, @CGUID+365, 0, 0, 3),
(@CGUID+370, @CGUID+365, 3), (@CGUID+370, @CGUID+365, 0, 0, 3),
(@CGUID+371, @CGUID+365, 3), (@CGUID+371, @CGUID+365, 0, 0, 3),
(@CGUID+372, @CGUID+365, 3), (@CGUID+372, @CGUID+365, 0, 0, 3),
(@CGUID+373, @CGUID+373, 3), (@CGUID+373, @CGUID+373, 0, 0, 3),
(@CGUID+374, @CGUID+373, 3), (@CGUID+374, @CGUID+373, 0, 0, 3),
(@CGUID+375, @CGUID+373, 3), (@CGUID+375, @CGUID+373, 0, 0, 3),
(@CGUID+376, @CGUID+373, 3), (@CGUID+376, @CGUID+373, 0, 0, 3),
(@CGUID+377, @CGUID+373, 3), (@CGUID+377, @CGUID+373, 0, 0, 3),
(@CGUID+378, @CGUID+373, 3), (@CGUID+378, @CGUID+373, 0, 0, 3),
(@CGUID+379, @CGUID+373, 3), (@CGUID+379, @CGUID+373, 0, 0, 3),
(@CGUID+380, @CGUID+373, 3), (@CGUID+380, @CGUID+373, 0, 0, 3),
(@CGUID+383, @CGUID+383, 3), (@CGUID+383, @CGUID+383, 0, 0, 3),
(@CGUID+384, @CGUID+383, 3), (@CGUID+384, @CGUID+383, 0, 0, 3),
(@CGUID+385, @CGUID+383, 3), (@CGUID+385, @CGUID+383, 0, 0, 3),
(@CGUID+386, @CGUID+383, 3), (@CGUID+386, @CGUID+383, 0, 0, 3),
(@CGUID+387, @CGUID+383, 3), (@CGUID+387, @CGUID+383, 0, 0, 3),
(@CGUID+388, @CGUID+383, 3), (@CGUID+388, @CGUID+383, 0, 0, 3),
(@CGUID+389, @CGUID+383, 3), (@CGUID+389, @CGUID+383, 0, 0, 3),
(@CGUID+390, @CGUID+383, 3), (@CGUID+390, @CGUID+383, 0, 0, 3),
(@CGUID+391, @CGUID+383, 3), (@CGUID+391, @CGUID+383, 0, 0, 3),
(@CGUID+392, @CGUID+383, 3), (@CGUID+392, @CGUID+383, 0, 0, 3),
(@CGUID+393, @CGUID+383, 3), (@CGUID+393, @CGUID+383, 0, 0, 3),
(@CGUID+396, @CGUID+396, 3), (@CGUID+396, @CGUID+396, 0, 0, 3),
(@CGUID+397, @CGUID+396, 3), (@CGUID+397, @CGUID+396, 0, 0, 3),
(@CGUID+398, @CGUID+396, 3), (@CGUID+398, @CGUID+396, 0, 0, 3),
(@CGUID+399, @CGUID+396, 3), (@CGUID+399, @CGUID+396, 0, 0, 3),
(@CGUID+400, @CGUID+396, 3), (@CGUID+400, @CGUID+396, 0, 0, 3),
(@CGUID+401, @CGUID+396, 3), (@CGUID+401, @CGUID+396, 0, 0, 3),
(@CGUID+402, @CGUID+396, 3), (@CGUID+402, @CGUID+396, 0, 0, 3),
(@CGUID+403, @CGUID+396, 3), (@CGUID+403, @CGUID+396, 0, 0, 3),
(@CGUID+404, @CGUID+396, 3), (@CGUID+404, @CGUID+396, 0, 0, 3),
(@CGUID+405, @CGUID+396, 3), (@CGUID+405, @CGUID+396, 0, 0, 3),
(@CGUID+406, @CGUID+396, 3), (@CGUID+406, @CGUID+396, 0, 0, 3),
(@CGUID+418, @CGUID+418, 3), (@CGUID+418, @CGUID+418, 0, 0, 3),
(@CGUID+419, @CGUID+418, 3), (@CGUID+419, @CGUID+418, 0, 0, 3),
(@CGUID+420, @CGUID+418, 3), (@CGUID+420, @CGUID+418, 0, 0, 3),
(@CGUID+421, @CGUID+418, 3), (@CGUID+421, @CGUID+418, 0, 0, 3),
(@CGUID+422, @CGUID+418, 3), (@CGUID+422, @CGUID+418, 0, 0, 3),
(@CGUID+423, @CGUID+418, 3), (@CGUID+423, @CGUID+418, 0, 0, 3),
(@CGUID+540, @CGUID+540, 3), (@CGUID+540, @CGUID+540, 0, 0, 3),
(@CGUID+541, @CGUID+540, 3), (@CGUID+541, @CGUID+540, 0, 0, 3),
(@CGUID+542, @CGUID+540, 3), (@CGUID+542, @CGUID+540, 0, 0, 3),
(@CGUID+543, @CGUID+540, 3), (@CGUID+543, @CGUID+540, 0, 0, 3),
(@CGUID+544, @CGUID+540, 3), (@CGUID+544, @CGUID+540, 0, 0, 3),
(@CGUID+545, @CGUID+540, 3), (@CGUID+545, @CGUID+540, 0, 0, 3),
(@CGUID+546, @CGUID+540, 3), (@CGUID+546, @CGUID+540, 0, 0, 3),
(@CGUID+547, @CGUID+540, 3), (@CGUID+547, @CGUID+540, 0, 0, 3),
(@CGUID+548, @CGUID+540, 3), (@CGUID+548, @CGUID+540, 0, 0, 3),
(@CGUID+549, @CGUID+540, 3), (@CGUID+549, @CGUID+540, 0, 0, 3),
(@CGUID+550, @CGUID+550, 3), (@CGUID+550, @CGUID+550, 0, 0, 3),
(@CGUID+551, @CGUID+550, 3), (@CGUID+551, @CGUID+550, 0, 0, 3),
(@CGUID+552, @CGUID+550, 3), (@CGUID+552, @CGUID+550, 0, 0, 3),
(@CGUID+553, @CGUID+550, 3), (@CGUID+553, @CGUID+550, 0, 0, 3),
(@CGUID+554, @CGUID+550, 3), (@CGUID+554, @CGUID+550, 0, 0, 3),
(@CGUID+555, @CGUID+550, 3), (@CGUID+555, @CGUID+550, 0, 0, 3),
(@CGUID+556, @CGUID+550, 3), (@CGUID+556, @CGUID+550, 0, 0, 3),
(@CGUID+557, @CGUID+550, 3), (@CGUID+557, @CGUID+550, 0, 0, 3),
(@CGUID+558, @CGUID+550, 3), (@CGUID+558, @CGUID+550, 0, 0, 3),
(@CGUID+559, @CGUID+550, 3), (@CGUID+559, @CGUID+550, 0, 0, 3),
(@CGUID+560, @CGUID+560, 3), (@CGUID+560, @CGUID+560, 0, 0, 3),
(@CGUID+561, @CGUID+560, 3), (@CGUID+561, @CGUID+560, 0, 0, 3),
(@CGUID+562, @CGUID+560, 3), (@CGUID+562, @CGUID+560, 0, 0, 3),
(@CGUID+563, @CGUID+560, 3), (@CGUID+563, @CGUID+560, 0, 0, 3),
(@CGUID+564, @CGUID+560, 3), (@CGUID+564, @CGUID+560, 0, 0, 3),
(@CGUID+565, @CGUID+560, 3), (@CGUID+565, @CGUID+560, 0, 0, 3),
(@CGUID+566, @CGUID+560, 3), (@CGUID+566, @CGUID+560, 0, 0, 3),
(@CGUID+567, @CGUID+560, 3), (@CGUID+567, @CGUID+560, 0, 0, 3),
(@CGUID+568, @CGUID+560, 3), (@CGUID+568, @CGUID+560, 0, 0, 3),
(@CGUID+569, @CGUID+560, 3), (@CGUID+569, @CGUID+560, 0, 0, 3),
(@CGUID+570, @CGUID+570, 3), (@CGUID+570, @CGUID+570, 0, 0, 3),
(@CGUID+571, @CGUID+570, 3), (@CGUID+571, @CGUID+570, 0, 0, 3),
(@CGUID+572, @CGUID+570, 3), (@CGUID+572, @CGUID+570, 0, 0, 3),
(@CGUID+573, @CGUID+570, 3), (@CGUID+573, @CGUID+570, 0, 0, 3),
(@CGUID+574, @CGUID+570, 3), (@CGUID+574, @CGUID+570, 0, 0, 3),
(@CGUID+575, @CGUID+570, 3), (@CGUID+575, @CGUID+570, 0, 0, 3),
(@CGUID+576, @CGUID+570, 3), (@CGUID+576, @CGUID+570, 0, 0, 3),
(@CGUID+577, @CGUID+570, 3), (@CGUID+577, @CGUID+570, 0, 0, 3),
(@CGUID+578, @CGUID+570, 3), (@CGUID+578, @CGUID+570, 0, 0, 3),
(@CGUID+579, @CGUID+570, 3), (@CGUID+579, @CGUID+570, 0, 0, 3),
(@CGUID+580, @CGUID+580, 3), (@CGUID+580, @CGUID+580, 0, 0, 3),
(@CGUID+581, @CGUID+580, 3), (@CGUID+581, @CGUID+580, 0, 0, 3),
(@CGUID+582, @CGUID+580, 3), (@CGUID+582, @CGUID+580, 0, 0, 3),
(@CGUID+583, @CGUID+580, 3), (@CGUID+583, @CGUID+580, 0, 0, 3),
(@CGUID+584, @CGUID+580, 3), (@CGUID+584, @CGUID+580, 0, 0, 3),
(@CGUID+585, @CGUID+580, 3), (@CGUID+585, @CGUID+580, 0, 0, 3),
(@CGUID+586, @CGUID+580, 3), (@CGUID+586, @CGUID+580, 0, 0, 3),
(@CGUID+587, @CGUID+580, 3), (@CGUID+587, @CGUID+580, 0, 0, 3),
(@CGUID+588, @CGUID+580, 3), (@CGUID+588, @CGUID+580, 0, 0, 3),
(@CGUID+589, @CGUID+580, 3), (@CGUID+589, @CGUID+580, 0, 0, 3),
(@CGUID+592, @CGUID+592, 3), (@CGUID+592, @CGUID+592, 0, 0, 3),
(@CGUID+593, @CGUID+592, 3), (@CGUID+593, @CGUID+592, 0, 0, 3),
(@CGUID+594, @CGUID+592, 3), (@CGUID+594, @CGUID+592, 0, 0, 3),
(@CGUID+595, @CGUID+592, 3), (@CGUID+595, @CGUID+592, 0, 0, 3),
(@CGUID+596, @CGUID+592, 3), (@CGUID+596, @CGUID+592, 0, 0, 3),
(@CGUID+597, @CGUID+592, 3), (@CGUID+597, @CGUID+592, 0, 0, 3),
(@CGUID+598, @CGUID+592, 3), (@CGUID+598, @CGUID+592, 0, 0, 3),
(@CGUID+599, @CGUID+592, 3), (@CGUID+599, @CGUID+592, 0, 0, 3),
(@CGUID+600, @CGUID+592, 3), (@CGUID+600, @CGUID+592, 0, 0, 3),
(@CGUID+601, @CGUID+592, 3), (@CGUID+601, @CGUID+592, 0, 0, 3),
(@CGUID+602, @CGUID+602, 3), (@CGUID+602, @CGUID+602, 0, 0, 3),
(@CGUID+603, @CGUID+602, 3), (@CGUID+603, @CGUID+602, 0, 0, 3),
(@CGUID+605, @CGUID+605, 3), (@CGUID+605, @CGUID+605, 0, 0, 3),
(@CGUID+606, @CGUID+605, 3), (@CGUID+606, @CGUID+605, 0, 0, 3),
(@CGUID+607, @CGUID+605, 3), (@CGUID+607, @CGUID+605, 0, 0, 3),
(@CGUID+608, @CGUID+605, 3), (@CGUID+608, @CGUID+605, 0, 0, 3),
(@CGUID+609, @CGUID+605, 3), (@CGUID+609, @CGUID+605, 0, 0, 3),
(@CGUID+610, @CGUID+605, 3), (@CGUID+610, @CGUID+605, 0, 0, 3),
(@CGUID+611, @CGUID+605, 3), (@CGUID+611, @CGUID+605, 0, 0, 3),
(@CGUID+612, @CGUID+605, 3), (@CGUID+612, @CGUID+605, 0, 0, 3),
(@CGUID+613, @CGUID+605, 3), (@CGUID+613, @CGUID+605, 0, 0, 3),
(@CGUID+614, @CGUID+605, 3), (@CGUID+614, @CGUID+605, 0, 0, 3),
(@CGUID+615, @CGUID+615, 3), (@CGUID+615, @CGUID+615, 0, 0, 3),
(@CGUID+616, @CGUID+615, 3), (@CGUID+616, @CGUID+615, 0, 0, 3),
(@CGUID+617, @CGUID+615, 3), (@CGUID+617, @CGUID+615, 0, 0, 3),
(@CGUID+618, @CGUID+615, 3), (@CGUID+618, @CGUID+615, 0, 0, 3),
(@CGUID+619, @CGUID+615, 3), (@CGUID+619, @CGUID+615, 0, 0, 3),
(@CGUID+620, @CGUID+615, 3), (@CGUID+620, @CGUID+615, 0, 0, 3),
(@CGUID+621, @CGUID+615, 3), (@CGUID+621, @CGUID+615, 0, 0, 3),
(@CGUID+622, @CGUID+615, 3), (@CGUID+622, @CGUID+615, 0, 0, 3),
(@CGUID+623, @CGUID+615, 3), (@CGUID+623, @CGUID+615, 0, 0, 3),
(@CGUID+624, @CGUID+615, 3), (@CGUID+624, @CGUID+615, 0, 0, 3),
(@CGUID+625, @CGUID+625, 3), (@CGUID+625, @CGUID+625, 0, 0, 3),
(@CGUID+626, @CGUID+625, 3), (@CGUID+626, @CGUID+625, 0, 0, 3),
(@CGUID+627, @CGUID+625, 3), (@CGUID+627, @CGUID+625, 0, 0, 3),
(@CGUID+628, @CGUID+628, 3), (@CGUID+628, @CGUID+628, 0, 0, 3),
(@CGUID+629, @CGUID+628, 3), (@CGUID+629, @CGUID+628, 0, 0, 3),
(@CGUID+630, @CGUID+628, 3), (@CGUID+630, @CGUID+628, 0, 0, 3),
(@CGUID+631, @CGUID+631, 3), (@CGUID+631, @CGUID+631, 0, 0, 3),
(@CGUID+632, @CGUID+631, 3), (@CGUID+632, @CGUID+631, 0, 0, 3),
(@CGUID+633, @CGUID+631, 3), (@CGUID+633, @CGUID+631, 0, 0, 3),
(@CGUID+634, @CGUID+631, 3), (@CGUID+634, @CGUID+631, 0, 0, 3),
(@CGUID+635, @CGUID+635, 3), (@CGUID+635, @CGUID+635, 0, 0, 3),
(@CGUID+636, @CGUID+635, 3), (@CGUID+636, @CGUID+635, 0, 0, 3),
(@CGUID+637, @CGUID+635, 3), (@CGUID+637, @CGUID+635, 0, 0, 3),
(@CGUID+638, @CGUID+638, 3), (@CGUID+638, @CGUID+638, 0, 0, 3),
(@CGUID+639, @CGUID+638, 3), (@CGUID+639, @CGUID+638, 0, 0, 3),
(@CGUID+640, @CGUID+638, 3), (@CGUID+640, @CGUID+638, 0, 0, 3),
(@CGUID+641, @CGUID+638, 3), (@CGUID+641, @CGUID+638, 0, 0, 3),
(@CGUID+642, @CGUID+638, 3), (@CGUID+642, @CGUID+638, 0, 0, 3),
(@CGUID+643, @CGUID+638, 3), (@CGUID+643, @CGUID+638, 0, 0, 3),
(@CGUID+644, @CGUID+644, 3), (@CGUID+644, @CGUID+644, 0, 0, 3),
(@CGUID+645, @CGUID+644, 3), (@CGUID+645, @CGUID+644, 0, 0, 3),
(@CGUID+646, @CGUID+644, 3), (@CGUID+646, @CGUID+644, 0, 0, 3),
(@CGUID+647, @CGUID+644, 3), (@CGUID+647, @CGUID+644, 0, 0, 3),
(@CGUID+648, @CGUID+648, 3), (@CGUID+648, @CGUID+648, 0, 0, 3),
(@CGUID+649, @CGUID+648, 3), (@CGUID+649, @CGUID+648, 0, 0, 3),
(@CGUID+650, @CGUID+648, 3), (@CGUID+650, @CGUID+648, 0, 0, 3),
(@CGUID+651, @CGUID+648, 3), (@CGUID+651, @CGUID+648, 0, 0, 3),
(@CGUID+652, @CGUID+648, 3), (@CGUID+652, @CGUID+648, 0, 0, 3),
(@CGUID+653, @CGUID+648, 3), (@CGUID+653, @CGUID+648, 0, 0, 3),
(@CGUID+654, @CGUID+654, 3), (@CGUID+654, @CGUID+654, 0, 0, 3),
(@CGUID+655, @CGUID+654, 3), (@CGUID+655, @CGUID+654, 0, 0, 3),
(@CGUID+656, @CGUID+654, 3), (@CGUID+656, @CGUID+654, 0, 0, 3),
(@CGUID+657, @CGUID+654, 3), (@CGUID+657, @CGUID+654, 0, 0, 3),
(@CGUID+658, @CGUID+658, 3), (@CGUID+658, @CGUID+658, 0, 0, 3),
(@CGUID+659, @CGUID+658, 3), (@CGUID+659, @CGUID+658, 0, 0, 3),
(@CGUID+660, @CGUID+658, 3), (@CGUID+660, @CGUID+658, 0, 0, 3),
(@CGUID+661, @CGUID+658, 3), (@CGUID+661, @CGUID+658, 0, 0, 3),
(@CGUID+662, @CGUID+658, 3), (@CGUID+662, @CGUID+658, 0, 0, 3),
(@CGUID+663, @CGUID+658, 3), (@CGUID+663, @CGUID+658, 0, 0, 3),
(@CGUID+664, @CGUID+658, 3), (@CGUID+664, @CGUID+658, 0, 0, 3),
(@CGUID+665, @CGUID+658, 3), (@CGUID+665, @CGUID+658, 0, 0, 3),
(@CGUID+666, @CGUID+658, 3), (@CGUID+666, @CGUID+658, 0, 0, 3),
(@CGUID+667, @CGUID+658, 3), (@CGUID+667, @CGUID+658, 0, 0, 3),
(@CGUID+668, @CGUID+668, 3), (@CGUID+668, @CGUID+668, 0, 0, 3),
(@CGUID+669, @CGUID+668, 3), (@CGUID+669, @CGUID+668, 0, 0, 3),
(@CGUID+670, @CGUID+668, 3), (@CGUID+670, @CGUID+668, 0, 0, 3),
(@CGUID+671, @CGUID+668, 3), (@CGUID+671, @CGUID+668, 0, 0, 3),
(@CGUID+672, @CGUID+668, 3), (@CGUID+672, @CGUID+668, 0, 0, 3),
(@CGUID+673, @CGUID+673, 3), (@CGUID+673, @CGUID+673, 0, 0, 3),
(@CGUID+674, @CGUID+673, 3), (@CGUID+674, @CGUID+673, 0, 0, 3),
(@CGUID+675, @CGUID+673, 3), (@CGUID+675, @CGUID+673, 0, 0, 3),
(@CGUID+676, @CGUID+673, 3), (@CGUID+676, @CGUID+673, 0, 0, 3),
(@CGUID+677, @CGUID+673, 3), (@CGUID+677, @CGUID+673, 0, 0, 3),
(@CGUID+691, @CGUID+691, 3), (@CGUID+691, @CGUID+691, 0, 0, 3),
(@CGUID+692, @CGUID+691, 3), (@CGUID+692, @CGUID+691, 0, 0, 3),
(@CGUID+693, @CGUID+691, 3), (@CGUID+693, @CGUID+691, 0, 0, 3),
(@CGUID+694, @CGUID+691, 3), (@CGUID+694, @CGUID+691, 0, 0, 3),
(@CGUID+695, @CGUID+691, 3), (@CGUID+695, @CGUID+691, 0, 0, 3),
(@CGUID+696, @CGUID+691, 3), (@CGUID+696, @CGUID+691, 0, 0, 3),
(@CGUID+697, @CGUID+697, 3), (@CGUID+697, @CGUID+697, 0, 0, 3),
(@CGUID+698, @CGUID+697, 3), (@CGUID+698, @CGUID+697, 0, 0, 3),
(@CGUID+699, @CGUID+697, 3), (@CGUID+699, @CGUID+697, 0, 0, 3),
(@CGUID+700, @CGUID+697, 3), (@CGUID+700, @CGUID+697, 0, 0, 3),
(@CGUID+701, @CGUID+697, 3), (@CGUID+701, @CGUID+697, 0, 0, 3),
(@CGUID+702, @CGUID+697, 3), (@CGUID+702, @CGUID+697, 0, 0, 3),
(@CGUID+703, @CGUID+703, 3), (@CGUID+703, @CGUID+703, 0, 0, 3),
(@CGUID+704, @CGUID+703, 3), (@CGUID+704, @CGUID+703, 0, 0, 3),
(@CGUID+705, @CGUID+703, 3), (@CGUID+705, @CGUID+703, 0, 0, 3),
(@CGUID+706, @CGUID+703, 3), (@CGUID+706, @CGUID+703, 0, 0, 3),
(@CGUID+707, @CGUID+703, 3), (@CGUID+707, @CGUID+703, 0, 0, 3),
(@CGUID+708, @CGUID+703, 3), (@CGUID+708, @CGUID+703, 0, 0, 3),
(@CGUID+709, @CGUID+709, 3), (@CGUID+709, @CGUID+709, 0, 0, 3),
(@CGUID+710, @CGUID+709, 3), (@CGUID+710, @CGUID+709, 0, 0, 3),
(@CGUID+711, @CGUID+709, 3), (@CGUID+711, @CGUID+709, 0, 0, 3),
(@CGUID+712, @CGUID+709, 3), (@CGUID+712, @CGUID+709, 0, 0, 3),
(@CGUID+713, @CGUID+709, 3), (@CGUID+713, @CGUID+709, 0, 0, 3),
(@CGUID+714, @CGUID+709, 3), (@CGUID+714, @CGUID+709, 0, 0, 3),
(@CGUID+716, @CGUID+716, 3), (@CGUID+716, @CGUID+716, 0, 0, 3),
(@CGUID+717, @CGUID+716, 3), (@CGUID+717, @CGUID+716, 0, 0, 3),
(@CGUID+718, @CGUID+716, 3), (@CGUID+718, @CGUID+716, 0, 0, 3),
(@CGUID+719, @CGUID+716, 3); (@CGUID+719, @CGUID+716, 0, 0, 3);
DELETE FROM `creature_addon` WHERE `guid` IN (12726,12727,12728,12729,12730,12731,12732,12733,12734,12735,12736,12760,12776,12778,12779,12780,12781,12782,12784,12785,12786,12787,12788,12789,12790,12791,12792,12793,12794,12795,12796,12797,12798,12799,12800,12801,12802,12803,12804,12805,12806,12807,12808,12809,12810,12843,12866,12869,12870,12871,12872,12873,12874,12875,12876,12877,12878,12879,12880,12881,12882,12883,12884,12886,12888,12889,12892,12894,13230,13235,40446,40526,40527,42920,46817,52411,52418,52420,52423,52424,52427,52428,52429,52430,52431,52432,52433,52440,52441,52442,52443,52444,52445,52446,52447,52448,52449,52450,52451,52452,52453,52454,52455,52456,52457,52458,52459,52460,52461,52462,52463,52464,52465,52739,52740,52743,52768,52769,52772,52773,52846,52847,52848,52850,52854,52855,52857,53054,53055,53056,53057,53058,53059,53210,53229,53586,53710,53711,53816,84716); DELETE FROM `creature_addon` WHERE `guid` IN (12726,12727,12728,12729,12730,12731,12732,12733,12734,12735,12736,12760,12776,12778,12779,12780,12781,12782,12784,12785,12786,12787,12788,12789,12790,12791,12792,12793,12794,12795,12796,12797,12798,12799,12800,12801,12802,12803,12804,12805,12806,12807,12808,12809,12810,12843,12866,12869,12870,12871,12872,12873,12874,12875,12876,12877,12878,12879,12880,12881,12882,12883,12884,12886,12888,12889,12892,12894,13230,13235,40446,40526,40527,42920,46817,52411,52418,52420,52423,52424,52427,52428,52429,52430,52431,52432,52433,52440,52441,52442,52443,52444,52445,52446,52447,52448,52449,52450,52451,52452,52453,52454,52455,52456,52457,52458,52459,52460,52461,52462,52463,52464,52465,52739,52740,52743,52768,52769,52772,52773,52846,52847,52848,52850,52854,52855,52857,53054,53055,53056,53057,53058,53059,53210,53229,53586,53710,53711,53816,84716);
DELETE FROM `waypoint_data` WHERE `id` IN (128660,128690,128840,128860,128880,128890,404460,405260,429200,468170,524110,524180,524200,524230,524240,524270,527390,527400,527430,527680,527690,527720,527730,528460,528470,528480,528500,528540,528550,528570,530540,530550,530560,530570,530580,530590,538160); DELETE FROM `waypoint_data` WHERE `id` IN (128660,128690,128840,128860,128880,128890,404460,405260,429200,468170,524110,524180,524200,524230,524240,524270,527390,527400,527430,527680,527690,527720,527730,528460,528470,528480,528500,528540,528550,528570,530540,530550,530560,530570,530580,530590,538160);
+8 -8
View File
@@ -18,14 +18,14 @@ INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Lan
UPDATE `creature` SET `spawntimesecs` = 300 WHERE `id1` = 23191 AND `map` = 564; UPDATE `creature` SET `spawntimesecs` = 300 WHERE `id1` = 23191 AND `map` = 564;
DELETE FROM `creature_formations` WHERE `leaderGUID` = 148236; DELETE FROM `creature_formations` WHERE `leaderGUID` = 148236;
INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `dist`, `angle`, `groupAI`) VALUES
(148236, 148236, 24), (148236, 148236, 0, 0, 24),
(148237, 148236, 24), (148237, 148236, 0, 0, 24),
(148238, 148236, 24), (148238, 148236, 0, 0, 24),
(148239, 148236, 24), (148239, 148236, 0, 0, 24),
(148240, 148236, 24), (148240, 148236, 0, 0, 24),
(148241, 148236, 24), (148241, 148236, 0, 0, 24),
(148242, 148236, 24); (148242, 148236, 0, 0, 24);
-- Delete leftover gobs -- Delete leftover gobs
DELETE FROM `gameobject` WHERE `guid` IN (20523,20558,20559,20561,20563,20567) AND `map` = 564; DELETE FROM `gameobject` WHERE `guid` IN (20523,20558,20559,20561,20563,20567) AND `map` = 564;
+37 -37
View File
@@ -1,42 +1,42 @@
-- DB update 2024_07_09_06 -> 2024_07_09_07 -- DB update 2024_07_09_06 -> 2024_07_09_07
-- --
DELETE FROM `creature_formations` WHERE `memberGUID` IN (158167,158168,158169,158170,158171,158172,158173,158174,158175,158176,158177,158178,158155,158156,158157,158158,158159,158160,158161,158162,158163,158164,158165,158166,158143,158144,158145,158146,158147,158148,158149,158150,158151,158152,158153,158154); DELETE FROM `creature_formations` WHERE `memberGUID` IN (158167,158168,158169,158170,158171,158172,158173,158174,158175,158176,158177,158178,158155,158156,158157,158158,158159,158160,158161,158162,158163,158164,158165,158166,158143,158144,158145,158146,158147,158148,158149,158150,158151,158152,158153,158154);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(158167, 158167, 3), (158167, 158167, 0, 0, 3),
(158167, 158168, 3), (158167, 158168, 0, 0, 3),
(158167, 158169, 3), (158167, 158169, 0, 0, 3),
(158167, 158170, 3), (158167, 158170, 0, 0, 3),
(158167, 158171, 3), (158167, 158171, 0, 0, 3),
(158167, 158172, 3), (158167, 158172, 0, 0, 3),
(158167, 158173, 3), (158167, 158173, 0, 0, 3),
(158167, 158174, 3), (158167, 158174, 0, 0, 3),
(158167, 158175, 3), (158167, 158175, 0, 0, 3),
(158167, 158176, 3), (158167, 158176, 0, 0, 3),
(158167, 158177, 3), (158167, 158177, 0, 0, 3),
(158167, 158178, 3), (158167, 158178, 0, 0, 3),
(158155, 158155, 3), (158155, 158155, 0, 0, 3),
(158155, 158156, 3), (158155, 158156, 0, 0, 3),
(158155, 158157, 3), (158155, 158157, 0, 0, 3),
(158155, 158158, 3), (158155, 158158, 0, 0, 3),
(158155, 158159, 3), (158155, 158159, 0, 0, 3),
(158155, 158160, 3), (158155, 158160, 0, 0, 3),
(158155, 158161, 3), (158155, 158161, 0, 0, 3),
(158155, 158162, 3), (158155, 158162, 0, 0, 3),
(158155, 158163, 3), (158155, 158163, 0, 0, 3),
(158155, 158164, 3), (158155, 158164, 0, 0, 3),
(158155, 158165, 3), (158155, 158165, 0, 0, 3),
(158155, 158166, 3), (158155, 158166, 0, 0, 3),
(158143, 158143, 3), (158143, 158143, 0, 0, 3),
(158143, 158144, 3), (158143, 158144, 0, 0, 3),
(158143, 158145, 3), (158143, 158145, 0, 0, 3),
(158143, 158146, 3), (158143, 158146, 0, 0, 3),
(158143, 158147, 3), (158143, 158147, 0, 0, 3),
(158143, 158148, 3), (158143, 158148, 0, 0, 3),
(158143, 158149, 3), (158143, 158149, 0, 0, 3),
(158143, 158150, 3), (158143, 158150, 0, 0, 3),
(158143, 158151, 3), (158143, 158151, 0, 0, 3),
(158143, 158152, 3), (158143, 158152, 0, 0, 3),
(158143, 158153, 3), (158143, 158153, 0, 0, 3),
(158143, 158154, 3); (158143, 158154, 0, 0, 3);
+16 -1
View File
@@ -42,11 +42,26 @@ CREATE TABLE `realmlist` (
-- --
-- Dumping data for table `realmlist` -- Dumping data for table `realmlist`
-- --
-- Defaults are tuned for fresh local installs: `address` is what the auth
-- server hands clients after login as the WORLD server endpoint. Stock
-- 127.0.0.1 means "the same box auth is running on", so a fresh
-- `git clone` -> `docker compose up` works without any post-install
-- tweaks for a developer hosting on their own machine.
--
-- Production deployments must override `address` after first dbimport,
-- e.g.:
-- UPDATE realmlist SET address = 'your.public.host', port = 8085 WHERE id = 1;
-- See contrib/fractured-dev-extras/BUILD-NATIVE.md for the full deploy
-- checklist (auth/world ports, firewall, public hostnames).
--
-- `port` is the WORLD server port (must match WorldServerPort in
-- worldserver.conf). The auth-server LISTEN port is separately configured
-- via RealmServerPort in authserver.conf (stock default 3724).
LOCK TABLES `realmlist` WRITE; LOCK TABLES `realmlist` WRITE;
/*!40000 ALTER TABLE `realmlist` DISABLE KEYS */; /*!40000 ALTER TABLE `realmlist` DISABLE KEYS */;
INSERT INTO `realmlist` VALUES INSERT INTO `realmlist` VALUES
(1,'AzerothCore','127.0.0.1','127.0.0.1','255.255.255.0',8085,0,0,1,0,0,12340); (1,'Fractured WoW','127.0.0.1','127.0.0.1','255.255.255.0',8085,0,0,1,0,0,12340);
/*!40000 ALTER TABLE `realmlist` ENABLE KEYS */; /*!40000 ALTER TABLE `realmlist` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
+1 -1
View File
@@ -24,7 +24,7 @@ CREATE TABLE `world_state` (
`Id` int unsigned NOT NULL COMMENT 'Internal save ID', `Id` int unsigned NOT NULL COMMENT 'Internal save ID',
`Data` longtext, `Data` longtext,
PRIMARY KEY (`Id`) PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='WorldState save system'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='WorldState save system';
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -27,7 +27,7 @@ CREATE TABLE `player_shapeshift_model` (
`GenderID` tinyint unsigned NOT NULL, `GenderID` tinyint unsigned NOT NULL,
`ModelID` int unsigned NOT NULL, `ModelID` int unsigned NOT NULL,
PRIMARY KEY (`ShapeshiftID`,`RaceID`,`CustomizationID`,`GenderID`) PRIMARY KEY (`ShapeshiftID`,`RaceID`,`CustomizationID`,`GenderID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci PACK_KEYS=0; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci PACK_KEYS=0;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -25,7 +25,7 @@ CREATE TABLE `player_totem_model` (
`RaceID` tinyint unsigned NOT NULL, `RaceID` tinyint unsigned NOT NULL,
`ModelID` int unsigned NOT NULL, `ModelID` int unsigned NOT NULL,
PRIMARY KEY (`TotemID`,`RaceID`) PRIMARY KEY (`TotemID`,`RaceID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci PACK_KEYS=0; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci PACK_KEYS=0;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -546,7 +546,7 @@ INSERT INTO `spell_target_position` VALUES
(48274,0,575,302.36,-352.01,90.54,2.2,0), (48274,0,575,302.36,-352.01,90.54,2.2,0),
(48275,0,575,291.39,-352.01,90.54,0.91,0), (48275,0,575,291.39,-352.01,90.54,0.91,0),
(48276,0,575,296.651,-346.293,108.547,1.58,0), (48276,0,575,296.651,-346.293,108.547,1.58,0),
(48324,0,571,3454.11,-2802.37,202.14,0,34149345), (48324,0,571,3454.11,-2802.37,202.14,0,0),
(48622,0,571,4274.53,-3055.55,319.463,2.535,0), (48622,0,571,4274.53,-3055.55,319.463,2.535,0),
(48760,0,571,3876.16,6984.44,106.32,6.279,0), (48760,0,571,3876.16,6984.44,106.32,6.279,0),
(48960,0,571,4313.37,-2958.17,318.463,1.98,0), (48960,0,571,4313.37,-2958.17,318.463,1.98,0),
+94 -94
View File
@@ -665,97 +665,97 @@ INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`,
-- Static Linking -- Static Linking
DELETE FROM `creature_formations` WHERE `groupAI` IN (3, 27) AND `leaderGUID` IN (@CGUID+22,@CGUID+23,@CGUID+26,@CGUID+28,@CGUID+30,@CGUID+31,@CGUID+36,@CGUID+37,@CGUID+41,@CGUID+42,@CGUID+43,@CGUID+44,@CGUID+61,@CGUID+63,@CGUID+72,@CGUID+80,@CGUID+86,@CGUID+102,@CGUID+103,@CGUID+110,@CGUID+117,@CGUID+120,@CGUID+139,@CGUID+140) AND `memberGUID` IN (@CGUID+21,@CGUID+22,@CGUID+23,@CGUID+24,@CGUID+25,@CGUID+26,@CGUID+27,@CGUID+28,@CGUID+29,@CGUID+30,@CGUID+31,@CGUID+32,@CGUID+33,@CGUID+34,@CGUID+36,@CGUID+37,@CGUID+38,@CGUID+40,@CGUID+41,@CGUID+42,@CGUID+43,@CGUID+44,@CGUID+61,@CGUID+62,@CGUID+63,@CGUID+64,@CGUID+65,@CGUID+66,@CGUID+67,@CGUID+70,@CGUID+71,@CGUID+72,@CGUID+73,@CGUID+74,@CGUID+75,@CGUID+76,@CGUID+77,@CGUID+78,@CGUID+79,@CGUID+80,@CGUID+82,@CGUID+83,@CGUID+84,@CGUID+86,@CGUID+87,@CGUID+89,@CGUID+90,@CGUID+91,@CGUID+92,@CGUID+93,@CGUID+94,@CGUID+95,@CGUID+96,@CGUID+98,@CGUID+99,@CGUID+100,@CGUID+101,@CGUID+102,@CGUID+103,@CGUID+106,@CGUID+110,@CGUID+111,@CGUID+112,@CGUID+113,@CGUID+114,@CGUID+115,@CGUID+116,@CGUID+117,@CGUID+118,@CGUID+119,@CGUID+120,@CGUID+122,@CGUID+124,@CGUID+125,@CGUID+126,@CGUID+127,@CGUID+128,@CGUID+139,@CGUID+140,@CGUID+141,@CGUID+142,@CGUID+143,@CGUID+144,@CGUID+145,@CGUID+146,@CGUID+147,@CGUID+148,@CGUID+149,@CGUID+150,@CGUID+151,@CGUID+152,@CGUID+153,@CGUID+154); DELETE FROM `creature_formations` WHERE `groupAI` IN (3, 27) AND `leaderGUID` IN (@CGUID+22,@CGUID+23,@CGUID+26,@CGUID+28,@CGUID+30,@CGUID+31,@CGUID+36,@CGUID+37,@CGUID+41,@CGUID+42,@CGUID+43,@CGUID+44,@CGUID+61,@CGUID+63,@CGUID+72,@CGUID+80,@CGUID+86,@CGUID+102,@CGUID+103,@CGUID+110,@CGUID+117,@CGUID+120,@CGUID+139,@CGUID+140) AND `memberGUID` IN (@CGUID+21,@CGUID+22,@CGUID+23,@CGUID+24,@CGUID+25,@CGUID+26,@CGUID+27,@CGUID+28,@CGUID+29,@CGUID+30,@CGUID+31,@CGUID+32,@CGUID+33,@CGUID+34,@CGUID+36,@CGUID+37,@CGUID+38,@CGUID+40,@CGUID+41,@CGUID+42,@CGUID+43,@CGUID+44,@CGUID+61,@CGUID+62,@CGUID+63,@CGUID+64,@CGUID+65,@CGUID+66,@CGUID+67,@CGUID+70,@CGUID+71,@CGUID+72,@CGUID+73,@CGUID+74,@CGUID+75,@CGUID+76,@CGUID+77,@CGUID+78,@CGUID+79,@CGUID+80,@CGUID+82,@CGUID+83,@CGUID+84,@CGUID+86,@CGUID+87,@CGUID+89,@CGUID+90,@CGUID+91,@CGUID+92,@CGUID+93,@CGUID+94,@CGUID+95,@CGUID+96,@CGUID+98,@CGUID+99,@CGUID+100,@CGUID+101,@CGUID+102,@CGUID+103,@CGUID+106,@CGUID+110,@CGUID+111,@CGUID+112,@CGUID+113,@CGUID+114,@CGUID+115,@CGUID+116,@CGUID+117,@CGUID+118,@CGUID+119,@CGUID+120,@CGUID+122,@CGUID+124,@CGUID+125,@CGUID+126,@CGUID+127,@CGUID+128,@CGUID+139,@CGUID+140,@CGUID+141,@CGUID+142,@CGUID+143,@CGUID+144,@CGUID+145,@CGUID+146,@CGUID+147,@CGUID+148,@CGUID+149,@CGUID+150,@CGUID+151,@CGUID+152,@CGUID+153,@CGUID+154);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(@CGUID+103, @CGUID+103, 3), (@CGUID+103, @CGUID+103, 0, 0, 3),
(@CGUID+103, @CGUID+21, 3), (@CGUID+103, @CGUID+21, 0, 0, 3),
(@CGUID+103, @CGUID+25, 3), (@CGUID+103, @CGUID+25, 0, 0, 3),
(@CGUID+30, @CGUID+30, 3), (@CGUID+30, @CGUID+30, 0, 0, 3),
(@CGUID+30, @CGUID+95, 3), (@CGUID+30, @CGUID+95, 0, 0, 3),
(@CGUID+30, @CGUID+114, 3), (@CGUID+30, @CGUID+114, 0, 0, 3),
(@CGUID+30, @CGUID+83, 3), (@CGUID+30, @CGUID+83, 0, 0, 3),
(@CGUID+110, @CGUID+110, 3), (@CGUID+110, @CGUID+110, 0, 0, 3),
(@CGUID+110, @CGUID+77, 3), (@CGUID+110, @CGUID+77, 0, 0, 3),
(@CGUID+110, @CGUID+79, 3), (@CGUID+110, @CGUID+79, 0, 0, 3),
(@CGUID+22, @CGUID+22, 3), (@CGUID+22, @CGUID+22, 0, 0, 3),
(@CGUID+22, @CGUID+84, 3), (@CGUID+22, @CGUID+84, 0, 0, 3),
(@CGUID+22, @CGUID+99, 3), (@CGUID+22, @CGUID+99, 0, 0, 3),
(@CGUID+26, @CGUID+26, 3), (@CGUID+26, @CGUID+26, 0, 0, 3),
(@CGUID+26, @CGUID+96, 3), (@CGUID+26, @CGUID+96, 0, 0, 3),
(@CGUID+26, @CGUID+112, 3), (@CGUID+26, @CGUID+112, 0, 0, 3),
(@CGUID+26, @CGUID+27, 3), (@CGUID+26, @CGUID+27, 0, 0, 3),
(@CGUID+28, @CGUID+28, 3), (@CGUID+28, @CGUID+28, 0, 0, 3),
(@CGUID+28, @CGUID+29, 3), (@CGUID+28, @CGUID+29, 0, 0, 3),
(@CGUID+28, @CGUID+100, 3), (@CGUID+28, @CGUID+100, 0, 0, 3),
(@CGUID+28, @CGUID+111, 3), (@CGUID+28, @CGUID+111, 0, 0, 3),
(@CGUID+23, @CGUID+23, 3), (@CGUID+23, @CGUID+23, 0, 0, 3),
(@CGUID+23, @CGUID+78, 3), (@CGUID+23, @CGUID+78, 0, 0, 3),
(@CGUID+23, @CGUID+101, 3), (@CGUID+23, @CGUID+101, 0, 0, 3),
(@CGUID+23, @CGUID+24, 3), (@CGUID+23, @CGUID+24, 0, 0, 3),
(@CGUID+86, @CGUID+86, 3), (@CGUID+86, @CGUID+86, 0, 0, 3),
(@CGUID+86, @CGUID+87, 3), (@CGUID+86, @CGUID+87, 0, 0, 3),
(@CGUID+86, @CGUID+115, 3), (@CGUID+86, @CGUID+115, 0, 0, 3),
(@CGUID+86, @CGUID+106, 3), (@CGUID+86, @CGUID+106, 0, 0, 3),
(@CGUID+80, @CGUID+80, 3), (@CGUID+80, @CGUID+80, 0, 0, 3),
(@CGUID+80, @CGUID+98, 3), (@CGUID+80, @CGUID+98, 0, 0, 3),
(@CGUID+80, @CGUID+82, 3), (@CGUID+80, @CGUID+82, 0, 0, 3),
(@CGUID+80, @CGUID+113, 3), (@CGUID+80, @CGUID+113, 0, 0, 3),
(@CGUID+44, @CGUID+44, 27), (@CGUID+44, @CGUID+44, 0, 0, 27),
(@CGUID+44, @CGUID+124, 27), (@CGUID+44, @CGUID+124, 0, 0, 27),
(@CGUID+44, @CGUID+125, 27), (@CGUID+44, @CGUID+125, 0, 0, 27),
(@CGUID+61, @CGUID+61, 3), (@CGUID+61, @CGUID+61, 0, 0, 3),
(@CGUID+61, @CGUID+62, 3), (@CGUID+61, @CGUID+62, 0, 0, 3),
(@CGUID+102, @CGUID+102, 3), (@CGUID+102, @CGUID+102, 0, 0, 3),
(@CGUID+102, @CGUID+33, 3), (@CGUID+102, @CGUID+33, 0, 0, 3),
(@CGUID+102, @CGUID+34, 3), (@CGUID+102, @CGUID+34, 0, 0, 3),
(@CGUID+37, @CGUID+37, 3), (@CGUID+37, @CGUID+37, 0, 0, 3),
(@CGUID+37, @CGUID+91, 3), (@CGUID+37, @CGUID+91, 0, 0, 3),
(@CGUID+37, @CGUID+67, 3), (@CGUID+37, @CGUID+67, 0, 0, 3),
(@CGUID+37, @CGUID+66, 3), (@CGUID+37, @CGUID+66, 0, 0, 3),
(@CGUID+36, @CGUID+36, 3), (@CGUID+36, @CGUID+36, 0, 0, 3),
(@CGUID+36, @CGUID+90, 3), (@CGUID+36, @CGUID+90, 0, 0, 3),
(@CGUID+36, @CGUID+89, 3), (@CGUID+36, @CGUID+89, 0, 0, 3),
(@CGUID+36, @CGUID+64, 3), (@CGUID+36, @CGUID+64, 0, 0, 3),
(@CGUID+31, @CGUID+31, 3), (@CGUID+31, @CGUID+31, 0, 0, 3),
(@CGUID+31, @CGUID+32, 3), (@CGUID+31, @CGUID+32, 0, 0, 3),
(@CGUID+31, @CGUID+38, 3), (@CGUID+31, @CGUID+38, 0, 0, 3),
(@CGUID+31, @CGUID+70, 3), (@CGUID+31, @CGUID+70, 0, 0, 3),
(@CGUID+63, @CGUID+63, 3), (@CGUID+63, @CGUID+63, 0, 0, 3),
(@CGUID+63, @CGUID+65, 3), (@CGUID+63, @CGUID+65, 0, 0, 3),
(@CGUID+120, @CGUID+120, 3), (@CGUID+120, @CGUID+120, 0, 0, 3),
(@CGUID+120, @CGUID+122, 3), (@CGUID+120, @CGUID+122, 0, 0, 3),
(@CGUID+139, @CGUID+139, 3), (@CGUID+139, @CGUID+139, 0, 0, 3),
(@CGUID+139, @CGUID+146, 3), (@CGUID+139, @CGUID+146, 0, 0, 3),
(@CGUID+139, @CGUID+143, 3), (@CGUID+139, @CGUID+143, 0, 0, 3),
(@CGUID+139, @CGUID+145, 3), (@CGUID+139, @CGUID+145, 0, 0, 3),
(@CGUID+139, @CGUID+147, 3), (@CGUID+139, @CGUID+147, 0, 0, 3),
(@CGUID+139, @CGUID+149, 3), (@CGUID+139, @CGUID+149, 0, 0, 3),
(@CGUID+139, @CGUID+154, 3), (@CGUID+139, @CGUID+154, 0, 0, 3),
(@CGUID+139, @CGUID+144, 3), (@CGUID+139, @CGUID+144, 0, 0, 3),
(@CGUID+140, @CGUID+140, 3), (@CGUID+140, @CGUID+140, 0, 0, 3),
(@CGUID+140, @CGUID+152, 3), (@CGUID+140, @CGUID+152, 0, 0, 3),
(@CGUID+140, @CGUID+142, 3), (@CGUID+140, @CGUID+142, 0, 0, 3),
(@CGUID+140, @CGUID+150, 3), (@CGUID+140, @CGUID+150, 0, 0, 3),
(@CGUID+140, @CGUID+153, 3), (@CGUID+140, @CGUID+153, 0, 0, 3),
(@CGUID+140, @CGUID+151, 3), (@CGUID+140, @CGUID+151, 0, 0, 3),
(@CGUID+140, @CGUID+141, 3), (@CGUID+140, @CGUID+141, 0, 0, 3),
(@CGUID+140, @CGUID+148, 3), (@CGUID+140, @CGUID+148, 0, 0, 3),
(@CGUID+43, @CGUID+43, 3), (@CGUID+43, @CGUID+43, 0, 0, 3),
(@CGUID+43, @CGUID+127, 3), (@CGUID+43, @CGUID+127, 0, 0, 3),
(@CGUID+43, @CGUID+126, 3), (@CGUID+43, @CGUID+126, 0, 0, 3),
(@CGUID+43, @CGUID+128, 3), (@CGUID+43, @CGUID+128, 0, 0, 3),
(@CGUID+72, @CGUID+72, 3), (@CGUID+72, @CGUID+72, 0, 0, 3),
(@CGUID+72, @CGUID+116, 3), (@CGUID+72, @CGUID+116, 0, 0, 3),
(@CGUID+72, @CGUID+71, 3), (@CGUID+72, @CGUID+71, 0, 0, 3),
(@CGUID+72, @CGUID+40, 3), (@CGUID+72, @CGUID+40, 0, 0, 3),
(@CGUID+117, @CGUID+117, 3), (@CGUID+117, @CGUID+117, 0, 0, 3),
(@CGUID+117, @CGUID+74, 3), (@CGUID+117, @CGUID+74, 0, 0, 3),
(@CGUID+117, @CGUID+73, 3), (@CGUID+117, @CGUID+73, 0, 0, 3),
(@CGUID+117, @CGUID+92, 3), (@CGUID+117, @CGUID+92, 0, 0, 3),
(@CGUID+41, @CGUID+41, 3), (@CGUID+41, @CGUID+41, 0, 0, 3),
(@CGUID+41, @CGUID+75, 3), (@CGUID+41, @CGUID+75, 0, 0, 3),
(@CGUID+41, @CGUID+118, 3), (@CGUID+41, @CGUID+118, 0, 0, 3),
(@CGUID+41, @CGUID+93, 3), (@CGUID+41, @CGUID+93, 0, 0, 3),
(@CGUID+42, @CGUID+42, 3), (@CGUID+42, @CGUID+42, 0, 0, 3),
(@CGUID+42, @CGUID+76, 3), (@CGUID+42, @CGUID+76, 0, 0, 3),
(@CGUID+42, @CGUID+94, 3), (@CGUID+42, @CGUID+94, 0, 0, 3),
(@CGUID+42, @CGUID+119, 3); (@CGUID+42, @CGUID+119, 0, 0, 3);
+156 -156
View File
@@ -1239,162 +1239,162 @@ INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_
*/ */
DELETE FROM `creature_formations` WHERE `groupAI` = 3 AND `leaderGUID` IN (@CGUID+28,@CGUID+29,@CGUID+34,@CGUID+36,@CGUID+39,@CGUID+43,@CGUID+44,@CGUID+45,@CGUID+50,@CGUID+52,@CGUID+53,@CGUID+58,@CGUID+66,@CGUID+68,@CGUID+80,@CGUID+81,@CGUID+89,@CGUID+91,@CGUID+113,@CGUID+116,@CGUID+121,@CGUID+122,@CGUID+125,@CGUID+126,@CGUID+136,@CGUID+141,@CGUID+159,@CGUID+164,@CGUID+183,@CGUID+186,@CGUID+206,@CGUID+211,@CGUID+213,@CGUID+217,@CGUID+221,@CGUID+26) AND `memberGUID` IN (@CGUID+50,@CGUID+70,@CGUID+71,@CGUID+52,@CGUID+73,@CGUID+89,@CGUID+99,@CGUID+97,@CGUID+96,@CGUID+95,@CGUID+93,@CGUID+68,@CGUID+69,@CGUID+66,@CGUID+47,@CGUID+46,@CGUID+43,@CGUID+64,@CGUID+45,@CGUID+65,@CGUID+39,@CGUID+88,@CGUID+94,@CGUID+60,@CGUID+58,@CGUID+37,@CGUID+38,@CGUID+116,@CGUID+114,@CGUID+117,@CGUID+231,@CGUID+121,@CGUID+119,@CGUID+120,@CGUID+233,@CGUID+113,@CGUID+118,@CGUID+115,@CGUID+230,@CGUID+122,@CGUID+124,@CGUID+123,@CGUID+234,@CGUID+125,@CGUID+127,@CGUID+128,@CGUID+235,@CGUID+126,@CGUID+129,@CGUID+130,@CGUID+232,@CGUID+44,@CGUID+79,@CGUID+63,@CGUID+31,@CGUID+80,@CGUID+41,@CGUID+62,@CGUID+42,@CGUID+32,@CGUID+81,@CGUID+49,@CGUID+33,@CGUID+48,@CGUID+67,@CGUID+34,@CGUID+82,@CGUID+72,@CGUID+51,@CGUID+102,@CGUID+92,@CGUID+53,@CGUID+35,@CGUID+91,@CGUID+101,@CGUID+40,@CGUID+78,@CGUID+61,@CGUID+30,@CGUID+29,@CGUID+57,@CGUID+186,@CGUID+188,@CGUID+187,@CGUID+185,@CGUID+36,@CGUID+55,@CGUID+56,@CGUID+28,@CGUID+54,@CGUID+183,@CGUID+181,@CGUID+182,@CGUID+180,@CGUID+159,@CGUID+158,@CGUID+161,@CGUID+165,@CGUID+166,@CGUID+177,@CGUID+172,@CGUID+176,@CGUID+168,@CGUID+160,@CGUID+164,@CGUID+163,@CGUID+162,@CGUID+167,@CGUID+169,@CGUID+175,@CGUID+173,@CGUID+170,@CGUID+171,@CGUID+174,@CGUID+136,@CGUID+149,@CGUID+145,@CGUID+152,@CGUID+155,@CGUID+153,@CGUID+144,@CGUID+140,@CGUID+137,@CGUID+147,@CGUID+141,@CGUID+150,@CGUID+154,@CGUID+151,@CGUID+146,@CGUID+142,@CGUID+148,@CGUID+143,@CGUID+138,@CGUID+139,@CGUID+211,@CGUID+212,@CGUID+213,@CGUID+214,@CGUID+215,@CGUID+216,@CGUID+217,@CGUID+218,@CGUID+219,@CGUID+220,@CGUID+221,@CGUID+222,@CGUID+223,@CGUID+224,@CGUID+206,@CGUID+207,@CGUID+208,@CGUID+26,@CGUID+191,@CGUID+195,@CGUID+85); DELETE FROM `creature_formations` WHERE `groupAI` = 3 AND `leaderGUID` IN (@CGUID+28,@CGUID+29,@CGUID+34,@CGUID+36,@CGUID+39,@CGUID+43,@CGUID+44,@CGUID+45,@CGUID+50,@CGUID+52,@CGUID+53,@CGUID+58,@CGUID+66,@CGUID+68,@CGUID+80,@CGUID+81,@CGUID+89,@CGUID+91,@CGUID+113,@CGUID+116,@CGUID+121,@CGUID+122,@CGUID+125,@CGUID+126,@CGUID+136,@CGUID+141,@CGUID+159,@CGUID+164,@CGUID+183,@CGUID+186,@CGUID+206,@CGUID+211,@CGUID+213,@CGUID+217,@CGUID+221,@CGUID+26) AND `memberGUID` IN (@CGUID+50,@CGUID+70,@CGUID+71,@CGUID+52,@CGUID+73,@CGUID+89,@CGUID+99,@CGUID+97,@CGUID+96,@CGUID+95,@CGUID+93,@CGUID+68,@CGUID+69,@CGUID+66,@CGUID+47,@CGUID+46,@CGUID+43,@CGUID+64,@CGUID+45,@CGUID+65,@CGUID+39,@CGUID+88,@CGUID+94,@CGUID+60,@CGUID+58,@CGUID+37,@CGUID+38,@CGUID+116,@CGUID+114,@CGUID+117,@CGUID+231,@CGUID+121,@CGUID+119,@CGUID+120,@CGUID+233,@CGUID+113,@CGUID+118,@CGUID+115,@CGUID+230,@CGUID+122,@CGUID+124,@CGUID+123,@CGUID+234,@CGUID+125,@CGUID+127,@CGUID+128,@CGUID+235,@CGUID+126,@CGUID+129,@CGUID+130,@CGUID+232,@CGUID+44,@CGUID+79,@CGUID+63,@CGUID+31,@CGUID+80,@CGUID+41,@CGUID+62,@CGUID+42,@CGUID+32,@CGUID+81,@CGUID+49,@CGUID+33,@CGUID+48,@CGUID+67,@CGUID+34,@CGUID+82,@CGUID+72,@CGUID+51,@CGUID+102,@CGUID+92,@CGUID+53,@CGUID+35,@CGUID+91,@CGUID+101,@CGUID+40,@CGUID+78,@CGUID+61,@CGUID+30,@CGUID+29,@CGUID+57,@CGUID+186,@CGUID+188,@CGUID+187,@CGUID+185,@CGUID+36,@CGUID+55,@CGUID+56,@CGUID+28,@CGUID+54,@CGUID+183,@CGUID+181,@CGUID+182,@CGUID+180,@CGUID+159,@CGUID+158,@CGUID+161,@CGUID+165,@CGUID+166,@CGUID+177,@CGUID+172,@CGUID+176,@CGUID+168,@CGUID+160,@CGUID+164,@CGUID+163,@CGUID+162,@CGUID+167,@CGUID+169,@CGUID+175,@CGUID+173,@CGUID+170,@CGUID+171,@CGUID+174,@CGUID+136,@CGUID+149,@CGUID+145,@CGUID+152,@CGUID+155,@CGUID+153,@CGUID+144,@CGUID+140,@CGUID+137,@CGUID+147,@CGUID+141,@CGUID+150,@CGUID+154,@CGUID+151,@CGUID+146,@CGUID+142,@CGUID+148,@CGUID+143,@CGUID+138,@CGUID+139,@CGUID+211,@CGUID+212,@CGUID+213,@CGUID+214,@CGUID+215,@CGUID+216,@CGUID+217,@CGUID+218,@CGUID+219,@CGUID+220,@CGUID+221,@CGUID+222,@CGUID+223,@CGUID+224,@CGUID+206,@CGUID+207,@CGUID+208,@CGUID+26,@CGUID+191,@CGUID+195,@CGUID+85);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(@CGUID+50, @CGUID+50, 3), (@CGUID+50, @CGUID+50, 0, 0, 3),
(@CGUID+50, @CGUID+70, 3), (@CGUID+50, @CGUID+70, 0, 0, 3),
(@CGUID+50, @CGUID+71, 3), (@CGUID+50, @CGUID+71, 0, 0, 3),
(@CGUID+52, @CGUID+52, 3), (@CGUID+52, @CGUID+52, 0, 0, 3),
(@CGUID+52, @CGUID+73, 3), (@CGUID+52, @CGUID+73, 0, 0, 3),
(@CGUID+89, @CGUID+89, 3), (@CGUID+89, @CGUID+89, 0, 0, 3),
(@CGUID+89, @CGUID+99, 3), (@CGUID+89, @CGUID+99, 0, 0, 3),
(@CGUID+89, @CGUID+97, 3), (@CGUID+89, @CGUID+97, 0, 0, 3),
(@CGUID+89, @CGUID+96, 3), (@CGUID+89, @CGUID+96, 0, 0, 3),
(@CGUID+89, @CGUID+95, 3), (@CGUID+89, @CGUID+95, 0, 0, 3),
(@CGUID+89, @CGUID+93, 3), (@CGUID+89, @CGUID+93, 0, 0, 3),
(@CGUID+68, @CGUID+68, 3), (@CGUID+68, @CGUID+68, 0, 0, 3),
(@CGUID+68, @CGUID+69, 3), (@CGUID+68, @CGUID+69, 0, 0, 3),
(@CGUID+66, @CGUID+66, 3), (@CGUID+66, @CGUID+66, 0, 0, 3),
(@CGUID+66, @CGUID+47, 3), (@CGUID+66, @CGUID+47, 0, 0, 3),
(@CGUID+66, @CGUID+46, 3), (@CGUID+66, @CGUID+46, 0, 0, 3),
(@CGUID+43, @CGUID+43, 3), (@CGUID+43, @CGUID+43, 0, 0, 3),
(@CGUID+43, @CGUID+64, 3), (@CGUID+43, @CGUID+64, 0, 0, 3),
(@CGUID+45, @CGUID+45, 3), (@CGUID+45, @CGUID+45, 0, 0, 3),
(@CGUID+45, @CGUID+65, 3), (@CGUID+45, @CGUID+65, 0, 0, 3),
(@CGUID+39, @CGUID+39, 3), (@CGUID+39, @CGUID+39, 0, 0, 3),
(@CGUID+39, @CGUID+88, 3), (@CGUID+39, @CGUID+88, 0, 0, 3),
(@CGUID+39, @CGUID+94, 3), (@CGUID+39, @CGUID+94, 0, 0, 3),
(@CGUID+39, @CGUID+60, 3), (@CGUID+39, @CGUID+60, 0, 0, 3),
(@CGUID+58, @CGUID+58, 3), (@CGUID+58, @CGUID+58, 0, 0, 3),
(@CGUID+58, @CGUID+37, 3), (@CGUID+58, @CGUID+37, 0, 0, 3),
(@CGUID+58, @CGUID+38, 3), (@CGUID+58, @CGUID+38, 0, 0, 3),
(@CGUID+116, @CGUID+116, 3), (@CGUID+116, @CGUID+116, 0, 0, 3),
(@CGUID+116, @CGUID+114, 3), (@CGUID+116, @CGUID+114, 0, 0, 3),
(@CGUID+116, @CGUID+117, 3), (@CGUID+116, @CGUID+117, 0, 0, 3),
(@CGUID+116, @CGUID+231, 3), (@CGUID+116, @CGUID+231, 0, 0, 3),
(@CGUID+121, @CGUID+121, 3), (@CGUID+121, @CGUID+121, 0, 0, 3),
(@CGUID+121, @CGUID+119, 3), (@CGUID+121, @CGUID+119, 0, 0, 3),
(@CGUID+121, @CGUID+120, 3), (@CGUID+121, @CGUID+120, 0, 0, 3),
(@CGUID+121, @CGUID+233, 3), (@CGUID+121, @CGUID+233, 0, 0, 3),
(@CGUID+113, @CGUID+113, 3), (@CGUID+113, @CGUID+113, 0, 0, 3),
(@CGUID+113, @CGUID+118, 3), (@CGUID+113, @CGUID+118, 0, 0, 3),
(@CGUID+113, @CGUID+115, 3), (@CGUID+113, @CGUID+115, 0, 0, 3),
(@CGUID+113, @CGUID+230, 3), (@CGUID+113, @CGUID+230, 0, 0, 3),
(@CGUID+122, @CGUID+122, 3), (@CGUID+122, @CGUID+122, 0, 0, 3),
(@CGUID+122, @CGUID+124, 3), (@CGUID+122, @CGUID+124, 0, 0, 3),
(@CGUID+122, @CGUID+123, 3), (@CGUID+122, @CGUID+123, 0, 0, 3),
(@CGUID+122, @CGUID+234, 3), (@CGUID+122, @CGUID+234, 0, 0, 3),
(@CGUID+125, @CGUID+125, 3), (@CGUID+125, @CGUID+125, 0, 0, 3),
(@CGUID+125, @CGUID+127, 3), (@CGUID+125, @CGUID+127, 0, 0, 3),
(@CGUID+125, @CGUID+128, 3), (@CGUID+125, @CGUID+128, 0, 0, 3),
(@CGUID+125, @CGUID+235, 3), (@CGUID+125, @CGUID+235, 0, 0, 3),
(@CGUID+126, @CGUID+126, 3), (@CGUID+126, @CGUID+126, 0, 0, 3),
(@CGUID+126, @CGUID+129, 3), (@CGUID+126, @CGUID+129, 0, 0, 3),
(@CGUID+126, @CGUID+130, 3), (@CGUID+126, @CGUID+130, 0, 0, 3),
(@CGUID+126, @CGUID+232, 3), (@CGUID+126, @CGUID+232, 0, 0, 3),
(@CGUID+44, @CGUID+44, 3), (@CGUID+44, @CGUID+44, 0, 0, 3),
(@CGUID+44, @CGUID+79, 3), (@CGUID+44, @CGUID+79, 0, 0, 3),
(@CGUID+44, @CGUID+63, 3), (@CGUID+44, @CGUID+63, 0, 0, 3),
(@CGUID+44, @CGUID+31, 3), (@CGUID+44, @CGUID+31, 0, 0, 3),
(@CGUID+80, @CGUID+80, 3), (@CGUID+80, @CGUID+80, 0, 0, 3),
(@CGUID+80, @CGUID+41, 3), (@CGUID+80, @CGUID+41, 0, 0, 3),
(@CGUID+80, @CGUID+62, 3), (@CGUID+80, @CGUID+62, 0, 0, 3),
(@CGUID+80, @CGUID+42, 3), (@CGUID+80, @CGUID+42, 0, 0, 3),
(@CGUID+80, @CGUID+32, 3), (@CGUID+80, @CGUID+32, 0, 0, 3),
(@CGUID+81, @CGUID+81, 3), (@CGUID+81, @CGUID+81, 0, 0, 3),
(@CGUID+81, @CGUID+49, 3), (@CGUID+81, @CGUID+49, 0, 0, 3),
(@CGUID+81, @CGUID+33, 3), (@CGUID+81, @CGUID+33, 0, 0, 3),
(@CGUID+81, @CGUID+48, 3), (@CGUID+81, @CGUID+48, 0, 0, 3),
(@CGUID+81, @CGUID+67, 3), (@CGUID+81, @CGUID+67, 0, 0, 3),
(@CGUID+34, @CGUID+34, 3), (@CGUID+34, @CGUID+34, 0, 0, 3),
(@CGUID+34, @CGUID+82, 3), (@CGUID+34, @CGUID+82, 0, 0, 3),
(@CGUID+34, @CGUID+72, 3), (@CGUID+34, @CGUID+72, 0, 0, 3),
(@CGUID+34, @CGUID+51, 3), (@CGUID+34, @CGUID+51, 0, 0, 3),
(@CGUID+34, @CGUID+102, 3), (@CGUID+34, @CGUID+102, 0, 0, 3),
(@CGUID+34, @CGUID+92, 3), (@CGUID+34, @CGUID+92, 0, 0, 3),
(@CGUID+53, @CGUID+53, 3), (@CGUID+53, @CGUID+53, 0, 0, 3),
(@CGUID+53, @CGUID+35, 3), (@CGUID+53, @CGUID+35, 0, 0, 3),
(@CGUID+91, @CGUID+91, 3), (@CGUID+91, @CGUID+91, 0, 0, 3),
(@CGUID+91, @CGUID+101, 3), (@CGUID+91, @CGUID+101, 0, 0, 3),
(@CGUID+91, @CGUID+40, 3), (@CGUID+91, @CGUID+40, 0, 0, 3),
(@CGUID+91, @CGUID+78, 3), (@CGUID+91, @CGUID+78, 0, 0, 3),
(@CGUID+91, @CGUID+61, 3), (@CGUID+91, @CGUID+61, 0, 0, 3),
(@CGUID+91, @CGUID+30, 3), (@CGUID+91, @CGUID+30, 0, 0, 3),
(@CGUID+29, @CGUID+29, 3), (@CGUID+29, @CGUID+29, 0, 0, 3),
(@CGUID+29, @CGUID+57, 3), (@CGUID+29, @CGUID+57, 0, 0, 3),
(@CGUID+186, @CGUID+186, 3), (@CGUID+186, @CGUID+186, 0, 0, 3),
(@CGUID+186, @CGUID+188, 3), (@CGUID+186, @CGUID+188, 0, 0, 3),
(@CGUID+186, @CGUID+187, 3), (@CGUID+186, @CGUID+187, 0, 0, 3),
(@CGUID+186, @CGUID+185, 3), (@CGUID+186, @CGUID+185, 0, 0, 3),
(@CGUID+36, @CGUID+36, 3), (@CGUID+36, @CGUID+36, 0, 0, 3),
(@CGUID+36, @CGUID+55, 3), (@CGUID+36, @CGUID+55, 0, 0, 3),
(@CGUID+36, @CGUID+56, 3), (@CGUID+36, @CGUID+56, 0, 0, 3),
(@CGUID+28, @CGUID+28, 3), (@CGUID+28, @CGUID+28, 0, 0, 3),
(@CGUID+28, @CGUID+54, 3), (@CGUID+28, @CGUID+54, 0, 0, 3),
(@CGUID+183, @CGUID+183, 3), (@CGUID+183, @CGUID+183, 0, 0, 3),
(@CGUID+183, @CGUID+181, 3), (@CGUID+183, @CGUID+181, 0, 0, 3),
(@CGUID+183, @CGUID+182, 3), (@CGUID+183, @CGUID+182, 0, 0, 3),
(@CGUID+183, @CGUID+180, 3), (@CGUID+183, @CGUID+180, 0, 0, 3),
(@CGUID+159, @CGUID+159, 3), (@CGUID+159, @CGUID+159, 0, 0, 3),
(@CGUID+159, @CGUID+158, 3), (@CGUID+159, @CGUID+158, 0, 0, 3),
(@CGUID+159, @CGUID+161, 3), (@CGUID+159, @CGUID+161, 0, 0, 3),
(@CGUID+159, @CGUID+165, 3), (@CGUID+159, @CGUID+165, 0, 0, 3),
(@CGUID+159, @CGUID+166, 3), (@CGUID+159, @CGUID+166, 0, 0, 3),
(@CGUID+159, @CGUID+177, 3), (@CGUID+159, @CGUID+177, 0, 0, 3),
(@CGUID+159, @CGUID+172, 3), (@CGUID+159, @CGUID+172, 0, 0, 3),
(@CGUID+159, @CGUID+176, 3), (@CGUID+159, @CGUID+176, 0, 0, 3),
(@CGUID+159, @CGUID+168, 3), (@CGUID+159, @CGUID+168, 0, 0, 3),
(@CGUID+159, @CGUID+160, 3), (@CGUID+159, @CGUID+160, 0, 0, 3),
(@CGUID+164, @CGUID+164, 3), (@CGUID+164, @CGUID+164, 0, 0, 3),
(@CGUID+164, @CGUID+163, 3), (@CGUID+164, @CGUID+163, 0, 0, 3),
(@CGUID+164, @CGUID+162, 3), (@CGUID+164, @CGUID+162, 0, 0, 3),
(@CGUID+164, @CGUID+167, 3), (@CGUID+164, @CGUID+167, 0, 0, 3),
(@CGUID+164, @CGUID+169, 3), (@CGUID+164, @CGUID+169, 0, 0, 3),
(@CGUID+164, @CGUID+175, 3), (@CGUID+164, @CGUID+175, 0, 0, 3),
(@CGUID+164, @CGUID+173, 3), (@CGUID+164, @CGUID+173, 0, 0, 3),
(@CGUID+164, @CGUID+170, 3), (@CGUID+164, @CGUID+170, 0, 0, 3),
(@CGUID+164, @CGUID+171, 3), (@CGUID+164, @CGUID+171, 0, 0, 3),
(@CGUID+164, @CGUID+174, 3), (@CGUID+164, @CGUID+174, 0, 0, 3),
(@CGUID+136, @CGUID+136, 3), (@CGUID+136, @CGUID+136, 0, 0, 3),
(@CGUID+136, @CGUID+149, 3), (@CGUID+136, @CGUID+149, 0, 0, 3),
(@CGUID+136, @CGUID+145, 3), (@CGUID+136, @CGUID+145, 0, 0, 3),
(@CGUID+136, @CGUID+152, 3), (@CGUID+136, @CGUID+152, 0, 0, 3),
(@CGUID+136, @CGUID+155, 3), (@CGUID+136, @CGUID+155, 0, 0, 3),
(@CGUID+136, @CGUID+153, 3), (@CGUID+136, @CGUID+153, 0, 0, 3),
(@CGUID+136, @CGUID+144, 3), (@CGUID+136, @CGUID+144, 0, 0, 3),
(@CGUID+136, @CGUID+140, 3), (@CGUID+136, @CGUID+140, 0, 0, 3),
(@CGUID+136, @CGUID+137, 3), (@CGUID+136, @CGUID+137, 0, 0, 3),
(@CGUID+136, @CGUID+147, 3), (@CGUID+136, @CGUID+147, 0, 0, 3),
(@CGUID+141, @CGUID+141, 3), (@CGUID+141, @CGUID+141, 0, 0, 3),
(@CGUID+141, @CGUID+150, 3), (@CGUID+141, @CGUID+150, 0, 0, 3),
(@CGUID+141, @CGUID+154, 3), (@CGUID+141, @CGUID+154, 0, 0, 3),
(@CGUID+141, @CGUID+151, 3), (@CGUID+141, @CGUID+151, 0, 0, 3),
(@CGUID+141, @CGUID+146, 3), (@CGUID+141, @CGUID+146, 0, 0, 3),
(@CGUID+141, @CGUID+142, 3), (@CGUID+141, @CGUID+142, 0, 0, 3),
(@CGUID+141, @CGUID+148, 3), (@CGUID+141, @CGUID+148, 0, 0, 3),
(@CGUID+141, @CGUID+143, 3), (@CGUID+141, @CGUID+143, 0, 0, 3),
(@CGUID+141, @CGUID+138, 3), (@CGUID+141, @CGUID+138, 0, 0, 3),
(@CGUID+141, @CGUID+139, 3), (@CGUID+141, @CGUID+139, 0, 0, 3),
(@CGUID+211, @CGUID+211, 3), (@CGUID+211, @CGUID+211, 0, 0, 3),
(@CGUID+211, @CGUID+212, 3), (@CGUID+211, @CGUID+212, 0, 0, 3),
(@CGUID+213, @CGUID+213, 3), (@CGUID+213, @CGUID+213, 0, 0, 3),
(@CGUID+213, @CGUID+214, 3), (@CGUID+213, @CGUID+214, 0, 0, 3),
(@CGUID+213, @CGUID+215, 3), (@CGUID+213, @CGUID+215, 0, 0, 3),
(@CGUID+213, @CGUID+216, 3), (@CGUID+213, @CGUID+216, 0, 0, 3),
(@CGUID+217, @CGUID+217, 3), (@CGUID+217, @CGUID+217, 0, 0, 3),
(@CGUID+217, @CGUID+218, 3), (@CGUID+217, @CGUID+218, 0, 0, 3),
(@CGUID+217, @CGUID+219, 3), (@CGUID+217, @CGUID+219, 0, 0, 3),
(@CGUID+217, @CGUID+220, 3), (@CGUID+217, @CGUID+220, 0, 0, 3),
(@CGUID+221, @CGUID+221, 3), (@CGUID+221, @CGUID+221, 0, 0, 3),
(@CGUID+221, @CGUID+222, 3), (@CGUID+221, @CGUID+222, 0, 0, 3),
(@CGUID+221, @CGUID+223, 3), (@CGUID+221, @CGUID+223, 0, 0, 3),
(@CGUID+221, @CGUID+224, 3), (@CGUID+221, @CGUID+224, 0, 0, 3),
(@CGUID+206, @CGUID+206, 3), (@CGUID+206, @CGUID+206, 0, 0, 3),
(@CGUID+206, @CGUID+207, 3), (@CGUID+206, @CGUID+207, 0, 0, 3),
(@CGUID+206, @CGUID+208, 3), (@CGUID+206, @CGUID+208, 0, 0, 3),
(@CGUID+26, @CGUID+26, 3), (@CGUID+26, @CGUID+26, 0, 0, 3),
(@CGUID+26, @CGUID+191, 3), (@CGUID+26, @CGUID+191, 0, 0, 3),
(@CGUID+26, @CGUID+195, 3), (@CGUID+26, @CGUID+195, 0, 0, 3),
(@CGUID+26, @CGUID+85, 3); (@CGUID+26, @CGUID+85, 0, 0, 3);
-- Linked Respawn for the respawning creatures in Murmur's Room -- Linked Respawn for the respawning creatures in Murmur's Room
DELETE FROM `linked_respawn` WHERE `guid` IN (@CGUID+225, @CGUID+226, @CGUID+227, @CGUID+228, @CGUID+229) AND `linkType`=0; DELETE FROM `linked_respawn` WHERE `guid` IN (@CGUID+225, @CGUID+226, @CGUID+227, @CGUID+228, @CGUID+229) AND `linkType`=0;
+87 -87
View File
@@ -1395,90 +1395,90 @@ INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`,
(@CGUID+180, @CGUID+182, 3, 270, 515); (@CGUID+180, @CGUID+182, 3, 270, 515);
DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+006,@CGUID+007,@CGUID+010,@CGUID+011,@CGUID+012,@CGUID+013,@CGUID+014,@CGUID+015,@CGUID+016,@CGUID+017,@CGUID+018,@CGUID+008,@CGUID+009,@CGUID+019,@CGUID+023,@CGUID+021,@CGUID+022,@CGUID+020,@CGUID+024,@CGUID+027,@CGUID+025,@CGUID+028,@CGUID+026,@CGUID+029,@CGUID+032,@CGUID+030,@CGUID+033,@CGUID+031,@CGUID+034,@CGUID+035,@CGUID+036,@CGUID+037,@CGUID+038,@CGUID+039,@CGUID+040,@CGUID+041,@CGUID+094,@CGUID+095,@CGUID+096,@CGUID+097,@CGUID+121,@CGUID+122,@CGUID+123,@CGUID+124,@CGUID+131,@CGUID+134,@CGUID+132,@CGUID+135,@CGUID+133,@CGUID+136,@CGUID+137,@CGUID+138,@CGUID+139,@CGUID+140,@CGUID+141,@CGUID+142,@CGUID+175,@CGUID+176,@CGUID+177,@CGUID+178,@CGUID+183,@CGUID+184,@CGUID+192,@CGUID+193,@CGUID+046,@CGUID+047,@CGUID+048,@CGUID+148,@CGUID+149,@CGUID+150,@CGUID+151,@CGUID+152,@CGUID+143,@CGUID+144,@CGUID+145,@CGUID+147,@CGUID+146,@CGUID+153,@CGUID+155,@CGUID+157,@CGUID+154,@CGUID+156,@CGUID+158,@CGUID+159,@CGUID+160,@CGUID+161,@CGUID+162); DELETE FROM `creature_formations` WHERE `memberGUID` IN (@CGUID+006,@CGUID+007,@CGUID+010,@CGUID+011,@CGUID+012,@CGUID+013,@CGUID+014,@CGUID+015,@CGUID+016,@CGUID+017,@CGUID+018,@CGUID+008,@CGUID+009,@CGUID+019,@CGUID+023,@CGUID+021,@CGUID+022,@CGUID+020,@CGUID+024,@CGUID+027,@CGUID+025,@CGUID+028,@CGUID+026,@CGUID+029,@CGUID+032,@CGUID+030,@CGUID+033,@CGUID+031,@CGUID+034,@CGUID+035,@CGUID+036,@CGUID+037,@CGUID+038,@CGUID+039,@CGUID+040,@CGUID+041,@CGUID+094,@CGUID+095,@CGUID+096,@CGUID+097,@CGUID+121,@CGUID+122,@CGUID+123,@CGUID+124,@CGUID+131,@CGUID+134,@CGUID+132,@CGUID+135,@CGUID+133,@CGUID+136,@CGUID+137,@CGUID+138,@CGUID+139,@CGUID+140,@CGUID+141,@CGUID+142,@CGUID+175,@CGUID+176,@CGUID+177,@CGUID+178,@CGUID+183,@CGUID+184,@CGUID+192,@CGUID+193,@CGUID+046,@CGUID+047,@CGUID+048,@CGUID+148,@CGUID+149,@CGUID+150,@CGUID+151,@CGUID+152,@CGUID+143,@CGUID+144,@CGUID+145,@CGUID+147,@CGUID+146,@CGUID+153,@CGUID+155,@CGUID+157,@CGUID+154,@CGUID+156,@CGUID+158,@CGUID+159,@CGUID+160,@CGUID+161,@CGUID+162);
INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`memberGUID`, `leaderGUID`, `dist`, `angle`, `groupAI`) VALUES
(@CGUID+006,@CGUID+006,3), (@CGUID+006, @CGUID+006, 0, 0, 3),
(@CGUID+007,@CGUID+006,3), (@CGUID+007, @CGUID+006, 0, 0, 3),
(@CGUID+011,@CGUID+011,3), (@CGUID+011, @CGUID+011, 0, 0, 3),
(@CGUID+012,@CGUID+011,3), (@CGUID+012, @CGUID+011, 0, 0, 3),
(@CGUID+013,@CGUID+013,3), (@CGUID+013, @CGUID+013, 0, 0, 3),
(@CGUID+014,@CGUID+013,3), (@CGUID+014, @CGUID+013, 0, 0, 3),
(@CGUID+015,@CGUID+015,3), (@CGUID+015, @CGUID+015, 0, 0, 3),
(@CGUID+016,@CGUID+015,3), (@CGUID+016, @CGUID+015, 0, 0, 3),
(@CGUID+017,@CGUID+017,3), (@CGUID+017, @CGUID+017, 0, 0, 3),
(@CGUID+018,@CGUID+017,3), (@CGUID+018, @CGUID+017, 0, 0, 3),
(@CGUID+008,@CGUID+008,3), (@CGUID+008, @CGUID+008, 0, 0, 3),
(@CGUID+009,@CGUID+008,3), (@CGUID+009, @CGUID+008, 0, 0, 3),
(@CGUID+019,@CGUID+019,3), (@CGUID+019, @CGUID+019, 0, 0, 3),
(@CGUID+023,@CGUID+019,3), (@CGUID+023, @CGUID+019, 0, 0, 3),
(@CGUID+021,@CGUID+019,3), (@CGUID+021, @CGUID+019, 0, 0, 3),
(@CGUID+022,@CGUID+019,3), (@CGUID+022, @CGUID+019, 0, 0, 3),
(@CGUID+020,@CGUID+019,3), (@CGUID+020, @CGUID+019, 0, 0, 3),
(@CGUID+024,@CGUID+024,3), (@CGUID+024, @CGUID+024, 0, 0, 3),
(@CGUID+027,@CGUID+024,3), (@CGUID+027, @CGUID+024, 0, 0, 3),
(@CGUID+025,@CGUID+024,3), (@CGUID+025, @CGUID+024, 0, 0, 3),
(@CGUID+028,@CGUID+024,3), (@CGUID+028, @CGUID+024, 0, 0, 3),
(@CGUID+026,@CGUID+024,3), (@CGUID+026, @CGUID+024, 0, 0, 3),
(@CGUID+029,@CGUID+029,3), (@CGUID+029, @CGUID+029, 0, 0, 3),
(@CGUID+032,@CGUID+029,3), (@CGUID+032, @CGUID+029, 0, 0, 3),
(@CGUID+030,@CGUID+029,3), (@CGUID+030, @CGUID+029, 0, 0, 3),
(@CGUID+033,@CGUID+029,3), (@CGUID+033, @CGUID+029, 0, 0, 3),
(@CGUID+031,@CGUID+029,3), (@CGUID+031, @CGUID+029, 0, 0, 3),
(@CGUID+034,@CGUID+029,3), (@CGUID+034, @CGUID+029, 0, 0, 3),
(@CGUID+035,@CGUID+035,3), (@CGUID+035, @CGUID+035, 0, 0, 3),
(@CGUID+036,@CGUID+035,3), (@CGUID+036, @CGUID+035, 0, 0, 3),
(@CGUID+037,@CGUID+035,3), (@CGUID+037, @CGUID+035, 0, 0, 3),
(@CGUID+038,@CGUID+035,3), (@CGUID+038, @CGUID+035, 0, 0, 3),
(@CGUID+039,@CGUID+035,3), (@CGUID+039, @CGUID+035, 0, 0, 3),
(@CGUID+040,@CGUID+035,3), (@CGUID+040, @CGUID+035, 0, 0, 3),
(@CGUID+041,@CGUID+035,3), (@CGUID+041, @CGUID+035, 0, 0, 3),
(@CGUID+094,@CGUID+094,3), (@CGUID+094, @CGUID+094, 0, 0, 3),
(@CGUID+095,@CGUID+094,3), (@CGUID+095, @CGUID+094, 0, 0, 3),
(@CGUID+096,@CGUID+094,3), (@CGUID+096, @CGUID+094, 0, 0, 3),
(@CGUID+097,@CGUID+094,3), (@CGUID+097, @CGUID+094, 0, 0, 3),
(@CGUID+121,@CGUID+121,3), (@CGUID+121, @CGUID+121, 0, 0, 3),
(@CGUID+122,@CGUID+121,3), (@CGUID+122, @CGUID+121, 0, 0, 3),
(@CGUID+123,@CGUID+121,3), (@CGUID+123, @CGUID+121, 0, 0, 3),
(@CGUID+124,@CGUID+121,3), (@CGUID+124, @CGUID+121, 0, 0, 3),
(@CGUID+131,@CGUID+131,3), (@CGUID+131, @CGUID+131, 0, 0, 3),
(@CGUID+134,@CGUID+131,3), (@CGUID+134, @CGUID+131, 0, 0, 3),
(@CGUID+132,@CGUID+131,3), (@CGUID+132, @CGUID+131, 0, 0, 3),
(@CGUID+135,@CGUID+131,3), (@CGUID+135, @CGUID+131, 0, 0, 3),
(@CGUID+133,@CGUID+131,3), (@CGUID+133, @CGUID+131, 0, 0, 3),
(@CGUID+136,@CGUID+131,3), (@CGUID+136, @CGUID+131, 0, 0, 3),
(@CGUID+137,@CGUID+137,3), (@CGUID+137, @CGUID+137, 0, 0, 3),
(@CGUID+138,@CGUID+137,3), (@CGUID+138, @CGUID+137, 0, 0, 3),
(@CGUID+139,@CGUID+137,3), (@CGUID+139, @CGUID+137, 0, 0, 3),
(@CGUID+140,@CGUID+137,3), (@CGUID+140, @CGUID+137, 0, 0, 3),
(@CGUID+141,@CGUID+137,3), (@CGUID+141, @CGUID+137, 0, 0, 3),
(@CGUID+142,@CGUID+137,3), (@CGUID+142, @CGUID+137, 0, 0, 3),
(@CGUID+175,@CGUID+175,3), (@CGUID+175, @CGUID+175, 0, 0, 3),
(@CGUID+176,@CGUID+175,3), (@CGUID+176, @CGUID+175, 0, 0, 3),
(@CGUID+177,@CGUID+177,3), (@CGUID+177, @CGUID+177, 0, 0, 3),
(@CGUID+178,@CGUID+177,3), (@CGUID+178, @CGUID+177, 0, 0, 3),
(@CGUID+183,@CGUID+183,3), (@CGUID+183, @CGUID+183, 0, 0, 3),
(@CGUID+184,@CGUID+183,3), (@CGUID+184, @CGUID+183, 0, 0, 3),
(@CGUID+192,@CGUID+192,3), (@CGUID+192, @CGUID+192, 0, 0, 3),
(@CGUID+193,@CGUID+192,3), (@CGUID+193, @CGUID+192, 0, 0, 3),
(@CGUID+046,@CGUID+046,3), (@CGUID+046, @CGUID+046, 0, 0, 3),
(@CGUID+047,@CGUID+046,3), (@CGUID+047, @CGUID+046, 0, 0, 3),
(@CGUID+048,@CGUID+046,3), (@CGUID+048, @CGUID+046, 0, 0, 3),
(@CGUID+148,@CGUID+148,3), (@CGUID+148, @CGUID+148, 0, 0, 3),
(@CGUID+149,@CGUID+148,3), (@CGUID+149, @CGUID+148, 0, 0, 3),
(@CGUID+150,@CGUID+148,3), (@CGUID+150, @CGUID+148, 0, 0, 3),
(@CGUID+151,@CGUID+148,3), (@CGUID+151, @CGUID+148, 0, 0, 3),
(@CGUID+152,@CGUID+148,3), (@CGUID+152, @CGUID+148, 0, 0, 3),
(@CGUID+143,@CGUID+143,3), (@CGUID+143, @CGUID+143, 0, 0, 3),
(@CGUID+144,@CGUID+143,3), (@CGUID+144, @CGUID+143, 0, 0, 3),
(@CGUID+145,@CGUID+143,3), (@CGUID+145, @CGUID+143, 0, 0, 3),
(@CGUID+147,@CGUID+143,3), (@CGUID+147, @CGUID+143, 0, 0, 3),
(@CGUID+146,@CGUID+143,3), (@CGUID+146, @CGUID+143, 0, 0, 3),
(@CGUID+153,@CGUID+153,3), (@CGUID+153, @CGUID+153, 0, 0, 3),
(@CGUID+155,@CGUID+153,3), (@CGUID+155, @CGUID+153, 0, 0, 3),
(@CGUID+157,@CGUID+153,3), (@CGUID+157, @CGUID+153, 0, 0, 3),
(@CGUID+154,@CGUID+153,3), (@CGUID+154, @CGUID+153, 0, 0, 3),
(@CGUID+156,@CGUID+153,3), (@CGUID+156, @CGUID+153, 0, 0, 3),
(@CGUID+158,@CGUID+158,3), (@CGUID+158, @CGUID+158, 0, 0, 3),
(@CGUID+159,@CGUID+158,3), (@CGUID+159, @CGUID+158, 0, 0, 3),
(@CGUID+160,@CGUID+158,3), (@CGUID+160, @CGUID+158, 0, 0, 3),
(@CGUID+161,@CGUID+158,3), (@CGUID+161, @CGUID+158, 0, 0, 3),
(@CGUID+162,@CGUID+158,3); (@CGUID+162, @CGUID+158, 0, 0, 3);
@@ -0,0 +1,271 @@
#!/usr/bin/env python3
"""
Clamp VerifiedBuild literals to signed MEDIUMINT range for DBs that still use
MEDIUMINT on VerifiedBuild (see data/sql/old/db_world/8.x/2022_10_30_01.sql).
Processes INSERT (explicit column lists) and simple UPDATE ... VerifiedBuild = N.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
MAX_SIGNED_MEDIUMINT = 8388607
MIN_SIGNED_MEDIUMINT = -8388608
INSERT_RE = re.compile(
r"INSERT\s+INTO\s+`(?P<table>\w+)`\s*\((?P<cols>[^)]+)\)\s*VALUES\s*",
re.IGNORECASE,
)
# Base dumps: full row order ends with VerifiedBuild (6- or 8-column layouts).
INSERT_SPELL_TP_VALUES_RE = re.compile(
r"INSERT\s+INTO\s+`spell_target_position`\s*VALUES\s*",
re.IGNORECASE,
)
STMT_START = (
"INSERT ",
"DELETE ",
"UPDATE ",
"ALTER ",
"DROP ",
"CREATE ",
"SET ",
"REPLACE ",
)
def split_fields(inner: str) -> list[str]:
fields: list[str] = []
cur: list[str] = []
depth = 0
in_str = False
str_ch = ""
esc = False
for ch in inner:
if in_str:
cur.append(ch)
if esc:
esc = False
elif ch == "\\":
esc = True
elif ch == str_ch:
in_str = False
continue
if ch in "'\"":
in_str = True
str_ch = ch
cur.append(ch)
continue
if ch == "(":
depth += 1
elif ch == ")":
depth -= 1
if ch == "," and depth == 0:
fields.append("".join(cur).strip())
cur = []
else:
cur.append(ch)
fields.append("".join(cur).strip())
return fields
def join_fields(fields: list[str]) -> str:
return ", ".join(fields)
def clamp_tuple(tuple_str: str, vb_idx: int) -> str:
t = tuple_str.strip()
if not (t.startswith("(") and t.endswith(")")):
return tuple_str
fields = split_fields(t[1:-1])
if vb_idx >= len(fields):
return tuple_str
raw = fields[vb_idx].strip()
if not re.fullmatch(r"-?\d+", raw):
return tuple_str
n = int(raw)
if MIN_SIGNED_MEDIUMINT <= n <= MAX_SIGNED_MEDIUMINT:
return tuple_str
fields[vb_idx] = "0"
return "(" + join_fields(fields) + ")"
def process_value_segment(segment: str, vb_idx: int) -> tuple[str, int]:
out: list[str] = []
changes = 0
i, n = 0, len(segment)
while i < n:
if segment[i] == "(":
depth = 1
j = i + 1
while j < n and depth > 0:
if segment[j] == "(":
depth += 1
elif segment[j] == ")":
depth -= 1
j += 1
tup = segment[i:j]
new_tup = clamp_tuple(tup, vb_idx)
if new_tup != tup:
changes += 1
out.append(new_tup)
i = j
continue
out.append(segment[i])
i += 1
return "".join(out), changes
def clamp_update_verifiedbuild(line: str) -> tuple[str, int]:
changes = 0
def repl(m: re.Match[str]) -> str:
nonlocal changes
num = m.group(1)
n = int(num)
if MIN_SIGNED_MEDIUMINT <= n <= MAX_SIGNED_MEDIUMINT:
return m.group(0)
changes += 1
return f"`VerifiedBuild` = 0"
new_line = re.sub(
r"`VerifiedBuild`\s*=\s*([0-9]+)",
repl,
line,
flags=re.IGNORECASE,
)
return new_line, changes
def first_tuple_vb_index(segment: str) -> int | None:
i, n = 0, len(segment)
while i < n:
if segment[i] == "(":
depth = 1
j = i + 1
while j < n and depth > 0:
if segment[j] == "(":
depth += 1
elif segment[j] == ")":
depth -= 1
j += 1
tup = segment[i:j]
fields = split_fields(tup[1:-1])
if fields:
return len(fields) - 1
return None
i += 1
return None
def process_sql_text(text: str) -> tuple[str, int]:
lines = text.splitlines(keepends=True)
out: list[str] = []
mode: str | None = None
vb_idx = 0
total_changes = 0
for line in lines:
nl, ch = clamp_update_verifiedbuild(line)
total_changes += ch
line = nl
stripped = line.lstrip()
if mode == "insert_values":
if stripped and not stripped.startswith("(") and not stripped.startswith("--"):
if any(stripped.startswith(p) for p in STMT_START):
mode = None
if mode == "spell_tp_values":
if stripped and not stripped.startswith("(") and not stripped.startswith("--"):
if any(stripped.startswith(p) for p in STMT_START):
mode = None
if mode is None:
m = INSERT_RE.search(line)
if m and "`VerifiedBuild`" in m.group("cols"):
cols = [c.strip().strip("`") for c in m.group("cols").split(",")]
vb_idx = cols.index("VerifiedBuild")
mode = "insert_values"
prefix = line[: m.end()]
suffix = line[m.end() :]
if suffix.strip():
new_suf, ch2 = process_value_segment(suffix, vb_idx)
total_changes += ch2
line = prefix + new_suf
if ");" in line or line.rstrip().endswith(");"):
mode = None
out.append(line)
continue
m2 = INSERT_SPELL_TP_VALUES_RE.search(line)
if m2:
mode = "spell_tp_values"
vb_idx = -1
prefix = line[: m2.end()]
suffix = line[m2.end() :]
if suffix.strip():
vb_idx = first_tuple_vb_index(suffix)
if vb_idx is None:
vb_idx = 7
new_suf, ch2 = process_value_segment(suffix, vb_idx)
total_changes += ch2
line = prefix + new_suf
if ");" in line or line.rstrip().endswith(");"):
mode = None
out.append(line)
continue
out.append(line)
continue
if mode == "spell_tp_values":
if vb_idx < 0:
vb_idx = first_tuple_vb_index(line)
if vb_idx is None:
vb_idx = 7
new_line, ch3 = process_value_segment(line, vb_idx)
total_changes += ch3
out.append(new_line)
if ");" in line or line.rstrip().endswith(");"):
mode = None
continue
# insert_values continuation
new_line, ch3 = process_value_segment(line, vb_idx)
total_changes += ch3
out.append(new_line)
if ");" in line or line.rstrip().endswith(");"):
mode = None
return "".join(out), total_changes
def main() -> int:
sql_root = Path(__file__).resolve().parents[1]
roots = [
sql_root / "archive" / "db_world",
sql_root / "updates" / "db_world",
sql_root / "base" / "db_world",
]
file_count = 0
change_lines = 0
for root in roots:
if not root.exists():
continue
for path in sorted(root.glob("*.sql")):
text = path.read_text(encoding="utf-8", errors="strict")
new_text, ch = process_sql_text(text)
if ch:
path.write_text(new_text, encoding="utf-8")
change_lines += ch
file_count += 1
print(f"{path.relative_to(sql_root)}: {ch} change(s)")
print(f"Done. {file_count} file(s) written, {change_lines} change(s).")
return 0
if __name__ == "__main__":
sys.exit(main())
+10 -10
View File
@@ -31,15 +31,15 @@ INSERT INTO `creature` (`guid`, `id1`, `map`, `zoneId`, `areaId`, `spawnMask`, `
UPDATE `creature_template` SET `flags_extra` = `flags_extra`|33554432 WHERE `entry` IN (28732, 28733, 28734, 28731, 28730, 28729, 28684, 31612, 31616, 31615, 31617, 31608, 31605, 31606); UPDATE `creature_template` SET `flags_extra` = `flags_extra`|33554432 WHERE `entry` IN (28732, 28733, 28734, 28731, 28730, 28729, 28684, 31612, 31616, 31615, 31617, 31608, 31605, 31606);
DELETE FROM `creature_formations` WHERE `leaderGUID` IN (12758, 12759, 12760); DELETE FROM `creature_formations` WHERE `leaderGUID` IN (12758, 12759, 12760);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(12758, 12758, 11), (12758, 12758, 0, 0, 11),
(12758, 12762, 11), (12758, 12762, 0, 0, 11),
(12758, 12761, 11), (12758, 12761, 0, 0, 11),
(12759, 12759, 11), (12759, 12759, 0, 0, 11),
(12759, 12763, 11), (12759, 12763, 0, 0, 11),
(12759, 12764, 11), (12759, 12764, 0, 0, 11),
(12760, 12760, 11), (12760, 12760, 0, 0, 11),
(12760, 12765, 11), (12760, 12765, 0, 0, 11),
(12760, 12766, 11); (12760, 12766, 0, 0, 11);
+34 -34
View File
@@ -19,37 +19,37 @@ INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_
(16505, 0, 3, 0, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 39, 15, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Naxxramas Follower - On Aggro - Call For Help'); (16505, 0, 3, 0, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 39, 15, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Naxxramas Follower - On Aggro - Call For Help');
DELETE FROM `creature_formations` WHERE `memberGUID` IN (127800, 127987, 127988, 127989, 127990, 127991, 127992, 127993, 127994, 127995, 127996, 127997, 127998, 127999, 128000, 128001, 128019, 128020, 128021, 128022, 128023, 128024, 128025, 128026, 128027, 128028, 128029, 128030, 128031, 128032, 128033, 128034, 128035); DELETE FROM `creature_formations` WHERE `memberGUID` IN (127800, 127987, 127988, 127989, 127990, 127991, 127992, 127993, 127994, 127995, 127996, 127997, 127998, 127999, 128000, 128001, 128019, 128020, 128021, 128022, 128023, 128024, 128025, 128026, 128027, 128028, 128029, 128030, 128031, 128032, 128033, 128034, 128035);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(127800, 127800, 1), (127800, 127800, 0, 0, 1),
(127800, 127987, 1), (127800, 127987, 0, 0, 1),
(127800, 127988, 1), (127800, 127988, 0, 0, 1),
(127800, 127989, 1), (127800, 127989, 0, 0, 1),
(127800, 127990, 1), (127800, 127990, 0, 0, 1),
(127800, 127991, 1), (127800, 127991, 0, 0, 1),
(127800, 127992, 1), (127800, 127992, 0, 0, 1),
(127800, 127993, 1), (127800, 127993, 0, 0, 1),
(127800, 127994, 1), (127800, 127994, 0, 0, 1),
(127800, 127995, 1), (127800, 127995, 0, 0, 1),
(127800, 127996, 1), (127800, 127996, 0, 0, 1),
(127800, 127997, 1), (127800, 127997, 0, 0, 1),
(127800, 127998, 1), (127800, 127998, 0, 0, 1),
(127800, 127999, 1), (127800, 127999, 0, 0, 1),
(127800, 128000, 1), (127800, 128000, 0, 0, 1),
(127800, 128001, 1), (127800, 128001, 0, 0, 1),
(127800, 128019, 1), (127800, 128019, 0, 0, 1),
(127800, 128020, 1), (127800, 128020, 0, 0, 1),
(127800, 128021, 1), (127800, 128021, 0, 0, 1),
(127800, 128022, 1), (127800, 128022, 0, 0, 1),
(127800, 128023, 1), (127800, 128023, 0, 0, 1),
(127800, 128024, 1), (127800, 128024, 0, 0, 1),
(127800, 128025, 1), (127800, 128025, 0, 0, 1),
(127800, 128026, 1), (127800, 128026, 0, 0, 1),
(127800, 128027, 1), (127800, 128027, 0, 0, 1),
(127800, 128028, 1), (127800, 128028, 0, 0, 1),
(127800, 128029, 1), (127800, 128029, 0, 0, 1),
(127800, 128030, 1), (127800, 128030, 0, 0, 1),
(127800, 128031, 1), (127800, 128031, 0, 0, 1),
(127800, 128032, 1), (127800, 128032, 0, 0, 1),
(127800, 128033, 1), (127800, 128033, 0, 0, 1),
(127800, 128034, 1), (127800, 128034, 0, 0, 1),
(127800, 128035, 1); (127800, 128035, 0, 0, 1);
+10 -10
View File
@@ -1,16 +1,16 @@
-- DB update 2026_01_04_00 -> 2026_01_04_01 -- DB update 2026_01_04_00 -> 2026_01_04_01
-- --
DELETE FROM `creature_formations` WHERE `leaderGUID` IN (12758, 12759, 12760); DELETE FROM `creature_formations` WHERE `leaderGUID` IN (12758, 12759, 12760);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(12758, 12758, 7), (12758, 12758, 0, 0, 7),
(12758, 12762, 7), (12758, 12762, 0, 0, 7),
(12758, 12761, 7), (12758, 12761, 0, 0, 7),
(12759, 12759, 7), (12759, 12759, 0, 0, 7),
(12759, 12763, 7), (12759, 12763, 0, 0, 7),
(12759, 12764, 7), (12759, 12764, 0, 0, 7),
(12760, 12760, 7), (12760, 12760, 0, 0, 7),
(12760, 12765, 7), (12760, 12765, 0, 0, 7),
(12760, 12766, 7); (12760, 12766, 0, 0, 7);
DELETE FROM `linked_respawn` WHERE `linkedGuid` = 127214 AND `linkType` = 0; DELETE FROM `linked_respawn` WHERE `linkedGuid` = 127214 AND `linkType` = 0;
INSERT INTO `linked_respawn` (`guid`, `linkedGuid`, `linkType`) VALUES INSERT INTO `linked_respawn` (`guid`, `linkedGuid`, `linkType`) VALUES
+35 -35
View File
@@ -2,41 +2,41 @@
-- Hellfire Peninsula - Pools of Aggonar ooze aggro linking -- Hellfire Peninsula - Pools of Aggonar ooze aggro linking
DELETE FROM `creature_formations` WHERE `leaderGUID` BETWEEN 58465 and 58497; DELETE FROM `creature_formations` WHERE `leaderGUID` BETWEEN 58465 and 58497;
DELETE FROM `creature_formations` WHERE `leaderGUID` = 58786; DELETE FROM `creature_formations` WHERE `leaderGUID` = 58786;
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(58465, 58465, 3), (58465, 58498, 3), (58465, 58499, 3), (58465, 58465, 0, 0, 3), (58465, 58498, 0, 0, 3), (58465, 58499, 0, 0, 3),
(58466, 58466, 3), (58466, 58500, 3), (58466, 58504, 3), (58466, 58466, 0, 0, 3), (58466, 58500, 0, 0, 3), (58466, 58504, 0, 0, 3),
(58467, 58467, 3), (58467, 58501, 3), (58467, 58503, 3), (58467, 58467, 0, 0, 3), (58467, 58501, 0, 0, 3), (58467, 58503, 0, 0, 3),
(58468, 58468, 3), (58468, 58561, 3), (58468, 58562, 3), (58468, 58468, 0, 0, 3), (58468, 58561, 0, 0, 3), (58468, 58562, 0, 0, 3),
(58469, 58469, 3), (58469, 58507, 3), (58469, 58508, 3), (58469, 58469, 0, 0, 3), (58469, 58507, 0, 0, 3), (58469, 58508, 0, 0, 3),
(58470, 58470, 3), (58470, 58509, 3), (58470, 58510, 3), (58470, 58470, 0, 0, 3), (58470, 58509, 0, 0, 3), (58470, 58510, 0, 0, 3),
(58471, 58471, 3), (58471, 58502, 3), (58471, 58787, 3), (58471, 58471, 0, 0, 3), (58471, 58502, 0, 0, 3), (58471, 58787, 0, 0, 3),
(58786, 58786, 3), (58786, 58513, 3), (58786, 58514, 3), (58786, 58786, 0, 0, 3), (58786, 58513, 0, 0, 3), (58786, 58514, 0, 0, 3),
(58472, 58472, 3), (58472, 58511, 3), (58472, 58512, 3), (58472, 58472, 0, 0, 3), (58472, 58511, 0, 0, 3), (58472, 58512, 0, 0, 3),
(58473, 58473, 3), (58473, 58515, 3), (58473, 58516, 3), (58473, 58473, 0, 0, 3), (58473, 58515, 0, 0, 3), (58473, 58516, 0, 0, 3),
(58474, 58474, 3), (58474, 58517, 3), (58474, 58518, 3), (58474, 58474, 0, 0, 3), (58474, 58517, 0, 0, 3), (58474, 58518, 0, 0, 3),
(58475, 58475, 3), (58475, 58519, 3), (58475, 58520, 3), (58475, 58475, 0, 0, 3), (58475, 58519, 0, 0, 3), (58475, 58520, 0, 0, 3),
(58476, 58476, 3), (58476, 58521, 3), (58476, 58522, 3), (58476, 58476, 0, 0, 3), (58476, 58521, 0, 0, 3), (58476, 58522, 0, 0, 3),
(58477, 58477, 3), (58477, 58523, 3), (58477, 58524, 3), (58477, 58477, 0, 0, 3), (58477, 58523, 0, 0, 3), (58477, 58524, 0, 0, 3),
(58478, 58478, 3), (58478, 58525, 3), (58478, 58526, 3), (58478, 58478, 0, 0, 3), (58478, 58525, 0, 0, 3), (58478, 58526, 0, 0, 3),
(58479, 58479, 3), (58479, 58527, 3), (58479, 58528, 3), (58479, 58479, 0, 0, 3), (58479, 58527, 0, 0, 3), (58479, 58528, 0, 0, 3),
(58480, 58480, 3), (58480, 58529, 3), (58480, 58530, 3), (58480, 58480, 0, 0, 3), (58480, 58529, 0, 0, 3), (58480, 58530, 0, 0, 3),
(58481, 58481, 3), (58481, 58531, 3), (58481, 58532, 3), (58481, 58481, 0, 0, 3), (58481, 58531, 0, 0, 3), (58481, 58532, 0, 0, 3),
(58482, 58482, 3), (58482, 58535, 3), (58482, 58536, 3), (58482, 58482, 0, 0, 3), (58482, 58535, 0, 0, 3), (58482, 58536, 0, 0, 3),
(58483, 58483, 3), (58483, 58533, 3), (58483, 58534, 3), (58483, 58483, 0, 0, 3), (58483, 58533, 0, 0, 3), (58483, 58534, 0, 0, 3),
(58484, 58484, 3), (58484, 58537, 3), (58484, 58538, 3), (58484, 58484, 0, 0, 3), (58484, 58537, 0, 0, 3), (58484, 58538, 0, 0, 3),
(58485, 58485, 3), (58485, 58539, 3), (58485, 58540, 3), (58485, 58485, 0, 0, 3), (58485, 58539, 0, 0, 3), (58485, 58540, 0, 0, 3),
(58486, 58486, 3), (58486, 58541, 3), (58486, 58542, 3), (58486, 58486, 0, 0, 3), (58486, 58541, 0, 0, 3), (58486, 58542, 0, 0, 3),
(58487, 58487, 3), (58487, 58543, 3), (58487, 58544, 3), (58487, 58487, 0, 0, 3), (58487, 58543, 0, 0, 3), (58487, 58544, 0, 0, 3),
(58488, 58488, 3), (58488, 58545, 3), (58488, 58546, 3), (58488, 58488, 0, 0, 3), (58488, 58545, 0, 0, 3), (58488, 58546, 0, 0, 3),
(58489, 58489, 3), (58489, 58552, 3), (58489, 58553, 3), (58489, 58489, 0, 0, 3), (58489, 58552, 0, 0, 3), (58489, 58553, 0, 0, 3),
(58490, 58490, 3), (58490, 58554, 3), (58490, 58547, 3), (58490, 58490, 0, 0, 3), (58490, 58554, 0, 0, 3), (58490, 58547, 0, 0, 3),
(58491, 58491, 3), (58491, 58549, 3), (58491, 58548, 3), (58491, 58491, 0, 0, 3), (58491, 58549, 0, 0, 3), (58491, 58548, 0, 0, 3),
(58492, 58492, 3), (58492, 58550, 3), (58492, 58551, 3), (58492, 58492, 0, 0, 3), (58492, 58550, 0, 0, 3), (58492, 58551, 0, 0, 3),
(58493, 58493, 3), (58493, 58555, 3), (58493, 58556, 3), (58493, 58493, 0, 0, 3), (58493, 58555, 0, 0, 3), (58493, 58556, 0, 0, 3),
(58494, 58494, 3), (58494, 58557, 3), (58494, 58558, 3), (58494, 58494, 0, 0, 3), (58494, 58557, 0, 0, 3), (58494, 58558, 0, 0, 3),
(58495, 58495, 3), (58495, 58559, 3), (58495, 58560, 3), (58495, 58495, 0, 0, 3), (58495, 58559, 0, 0, 3), (58495, 58560, 0, 0, 3),
(58496, 58496, 3), (58496, 58505, 3), (58496, 58506, 3), (58496, 58496, 0, 0, 3), (58496, 58505, 0, 0, 3), (58496, 58506, 0, 0, 3),
(58497, 58497, 3), (58497, 58563, 3), (58497, 58564, 3); (58497, 58497, 0, 0, 3), (58497, 58563, 0, 0, 3), (58497, 58564, 0, 0, 3);
-- Reposition oozes to be closer to group members -- Reposition oozes to be closer to group members
UPDATE `creature` SET `position_x`=381.658, `position_y`=3482.38, `position_z`=61.609, `orientation`=5.43889 WHERE `guid` = 58496 AND `id1` = 16901; UPDATE `creature` SET `position_x`=381.658, `position_y`=3482.38, `position_z`=61.609, `orientation`=5.43889 WHERE `guid` = 58496 AND `id1` = 16901;
+21 -21
View File
@@ -392,27 +392,27 @@ INSERT INTO `creature_addon` (`guid`, `path_id`) VALUES
(@GUID+28, (@GUID+28)*10); (@GUID+28, (@GUID+28)*10);
DELETE FROM `creature_formations` WHERE `leaderGUID` IN (@GUID+32,@GUID+38,@GUID+41,@GUID+47,@GUID+16,@GUID+4,@GUID+9,@GUID+12,@GUID+2,@GUID+26); DELETE FROM `creature_formations` WHERE `leaderGUID` IN (@GUID+32,@GUID+38,@GUID+41,@GUID+47,@GUID+16,@GUID+4,@GUID+9,@GUID+12,@GUID+2,@GUID+26);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `groupAI`) VALUES INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`) VALUES
(@GUID+32, @GUID+32, 3), (@GUID+32, @GUID+32, 0, 0, 3),
(@GUID+32, @GUID+33, 3), (@GUID+32, @GUID+33, 0, 0, 3),
(@GUID+2, @GUID+2 , 3), (@GUID+2, @GUID+2, 0, 0, 3),
(@GUID+2, @GUID+3 , 3), (@GUID+2, @GUID+3, 0, 0, 3),
(@GUID+26, @GUID+26, 3), (@GUID+26, @GUID+26, 0, 0, 3),
(@GUID+26, @GUID+27, 3), (@GUID+26, @GUID+27, 0, 0, 3),
(@GUID+38, @GUID+38, 3), (@GUID+38, @GUID+38, 0, 0, 3),
(@GUID+38, @GUID+39, 3), (@GUID+38, @GUID+39, 0, 0, 3),
(@GUID+41, @GUID+41, 3), (@GUID+41, @GUID+41, 0, 0, 3),
(@GUID+41, @GUID+42, 3), (@GUID+41, @GUID+42, 0, 0, 3),
(@GUID+47, @GUID+47, 3), (@GUID+47, @GUID+47, 0, 0, 3),
(@GUID+47, @GUID+48, 3), (@GUID+47, @GUID+48, 0, 0, 3),
(@GUID+16, @GUID+16, 3), (@GUID+16, @GUID+16, 0, 0, 3),
(@GUID+16, @GUID+17, 3), (@GUID+16, @GUID+17, 0, 0, 3),
(@GUID+4, @GUID+4 , 3), (@GUID+4, @GUID+4, 0, 0, 3),
(@GUID+4, @GUID+5 , 3), (@GUID+4, @GUID+5, 0, 0, 3),
(@GUID+9, @GUID+9 , 3), (@GUID+9, @GUID+9, 0, 0, 3),
(@GUID+9, @GUID+10, 3), (@GUID+9, @GUID+10, 0, 0, 3),
(@GUID+12, @GUID+12, 3), (@GUID+12, @GUID+12, 0, 0, 3),
(@GUID+12, @GUID+13, 3); (@GUID+12, @GUID+13, 0, 0, 3);
DELETE FROM `creature_addon` WHERE `guid` IN (@GUID+32,@GUID+33,@GUID+2 ,@GUID+3 ,@GUID+26,@GUID+27,@GUID+38,@GUID+39,@GUID+41,@GUID+42,@GUID+47,@GUID+48,@GUID+16,@GUID+17,@GUID+4 ,@GUID+5 ,@GUID+9 ,@GUID+10,@GUID+12,@GUID+13); DELETE FROM `creature_addon` WHERE `guid` IN (@GUID+32,@GUID+33,@GUID+2 ,@GUID+3 ,@GUID+26,@GUID+27,@GUID+38,@GUID+39,@GUID+41,@GUID+42,@GUID+47,@GUID+48,@GUID+16,@GUID+17,@GUID+4 ,@GUID+5 ,@GUID+9 ,@GUID+10,@GUID+12,@GUID+13);
INSERT INTO `creature_addon` (`guid`, `bytes1`) VALUES INSERT INTO `creature_addon` (`guid`, `bytes1`) VALUES
+11
View File
@@ -30,3 +30,14 @@ Auto-detected by `modules/CMakeLists.txt` (`GetModuleSourceList` globs
every subdirectory). No additional CMake plumbing is needed; rebuild every subdirectory). No additional CMake plumbing is needed; rebuild
the worldserver image and the loader symbol `Addmod_paragonScripts` the worldserver image and the loader symbol `Addmod_paragonScripts`
gets linked into the static `modules` target. gets linked into the static `modules` target.
## SQL layout
SQL files live under `data/sql/db-world/base/` and
`data/sql/db-characters/base/` — the standard AzerothCore module path
that the built-in DBUpdater scans (see
`src/server/database/Updater/UpdateFetcher.cpp`). Files placed there
are applied automatically by `worldserver` / `dbimport` on startup and
recorded by hash in the `updates` table of the target database, so
re-runs are idempotent. Any new SQL added under those directories will
be picked up on the next container/server start without manual import.
+21 -1
View File
@@ -12,6 +12,11 @@ Paragon.StickyComboPoints = 1
# in addition to runes/runic power. Required for the patch-enUS-5.MPQ player # in addition to runes/runic power. Required for the patch-enUS-5.MPQ player
# frame to populate Mana/Rage/Energy bars - otherwise the server treats those # frame to populate Mana/Rage/Energy bars - otherwise the server treats those
# powers as inactive and never sends max values, leaving the bars empty. # powers as inactive and never sends max values, leaving the bars empty.
# Also required for core rage generation: Unit::DealDamage only calls
# RewardRage() when the attacker HasActivePowerType(POWER_RAGE); if this is off,
# Paragon white swings never grant rage (users without this line in any loaded
# config used to hit the C++ fallback default of false). Default is on; set 0
# only if you intentionally want a stripped-down Paragon test build.
Paragon.MultiResource.HasActivePowers = 1 Paragon.MultiResource.HasActivePowers = 1
# Ability / Talent Essence (AE/TE) — Ascension-inspired currency # Ability / Talent Essence (AE/TE) — Ascension-inspired currency
@@ -23,12 +28,27 @@ Paragon.Currency.GrantLevelMin = 10
Paragon.Currency.AE.PerLevel = 1 Paragon.Currency.AE.PerLevel = 1
Paragon.Currency.TE.PerLevel = 1 Paragon.Currency.TE.PerLevel = 1
# Flat TE cost per successful talent rank learn (hook runs once per LearnTalent). # Flat TE cost per successful talent rank learn (hook runs once per LearnTalent).
# Applies to passive / aura-only talents (addToSpellBook == 0).
Paragon.Currency.TE.TalentLearnCost = 1 Paragon.Currency.TE.TalentLearnCost = 1
# AE cost per rank for addToSpellBook talents (Starfall, Bladestorm, …).
# Those talents also charge TE.TalentLearnCost per rank — each rank costs
# both essences at the configured amounts.
Paragon.Currency.AE.TalentLearnCost = 1
# Default AE cost when spell is not listed in world.paragon_spell_ae_cost. # Default AE cost when spell is not listed in world.paragon_spell_ae_cost.
Paragon.Currency.AE.DefaultSpellCost = 2 # (Phase 3a: every learnable spell baked into the Character Advancement panel
# has an explicit row of value 1 in that table, so this default only kicks in
# for spells learned via .paragon learn that aren't in the panel's bake.)
Paragon.Currency.AE.DefaultSpellCost = 1
# Diagnostics ---------------------------------------------------------------- # Diagnostics ----------------------------------------------------------------
# When enabled, dumps every Paragon's rune cooldown state to the server log # When enabled, dumps every Paragon's rune cooldown state to the server log
# every 5 seconds (channel "module"). Use with `.paragon runes` for in-game # every 5 seconds (channel "module"). Use with `.paragon runes` for in-game
# verification. Leave at 0 in production — it's noisy. # verification. Leave at 0 in production — it's noisy.
Paragon.Diag.RuneTrace = 0 Paragon.Diag.RuneTrace = 0
# When enabled, traces every PanelLearnSpellChain commit: chain ids, before/
# after spell-map sizes for each rank learn, and the classification of every
# auto-granted side spell (chain rank / passive dep / active dep revoked).
# Use to diagnose "spell X reappears in spellbook on relog" style bugs.
# Leave at 0 in production.
Paragon.Diag.PanelLearn = 0
@@ -0,0 +1,51 @@
-- Spells and talents learned only through Character Advancement (Lock In / .paragon learn).
-- Apply to the character database (same as `characters`, `character_spell`, etc.).
CREATE TABLE IF NOT EXISTS `character_paragon_panel_spells` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`spell_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`guid`, `spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: spells purchased via Character Advancement';
CREATE TABLE IF NOT EXISTS `character_paragon_panel_talents` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`talent_id` SMALLINT UNSIGNED NOT NULL,
`rank` TINYINT UNSIGNED NOT NULL,
PRIMARY KEY (`guid`, `talent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: talent ranks purchased via Character Advancement';
-- Passive "dependent" spells that AzerothCore's `addSpell` machinery
-- (via spell_learn_spell + SPELL_EFFECT_LEARN_SPELL) auto-grants when a
-- panel-purchased spell is learned. We keep them learned (some are
-- required for the parent ability to function -- e.g. Frost Fever for
-- Icy Touch, Blood Plague for Plague Strike) but record them here so
-- Reset Abilities / Reset Everything can unlearn them alongside the
-- parent. Only passive auto-learns are tracked here; active dependents
-- (Death Coil, Death Grip, ...) are revoked at learn time and never
-- reach this table.
CREATE TABLE IF NOT EXISTS `character_paragon_panel_spell_children` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`parent_spell_id` INT UNSIGNED NOT NULL COMMENT 'character_paragon_panel_spells.spell_id',
`child_spell_id` INT UNSIGNED NOT NULL COMMENT 'auto-learned passive spell id',
PRIMARY KEY (`guid`, `parent_spell_id`, `child_spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: passive auto-learn dependents to unlearn on reset';
-- Active "dependent" spells that AzerothCore's `addSpell` /
-- `learnSkillRewardedSpells` machinery auto-grants alongside a
-- panel-purchased spell but that the player did NOT buy (e.g. Blood
-- Presence, Death Coil, Death Grip from purchasing Plague Strike).
-- We revoke them at panel-commit time, but AC's skill cascade re-runs
-- on every login (`Player::_LoadSkills` -> `learnSkillRewardedSpells`)
-- and silently re-grants them. We persist the revoke decisions here
-- and re-revoke at `OnPlayerLogin` so the spellbook stays in sync with
-- what the player actually purchased through Character Advancement.
CREATE TABLE IF NOT EXISTS `character_paragon_panel_spell_revoked` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`parent_spell_id` INT UNSIGNED NOT NULL COMMENT 'character_paragon_panel_spells.spell_id',
`revoked_spell_id` INT UNSIGNED NOT NULL COMMENT 'active spell id auto-granted by skill cascade and revoked',
PRIMARY KEY (`guid`, `parent_spell_id`, `revoked_spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: active auto-learn dependents to keep revoked across logins';
@@ -0,0 +1,20 @@
-- mod-paragon: tracking table for active spell dependents the panel
-- revoked at commit time. AzerothCore's `Player::_LoadSkills` ->
-- `learnSkillRewardedSpells` re-grants skill-rewarded actives (Blood
-- Presence, Death Coil, Death Grip, ...) on every login. Persisting
-- the revoke decisions here lets `OnPlayerLogin` re-revoke them after
-- the cascade has run, so the spellbook stays in sync with what was
-- actually purchased through Character Advancement.
--
-- This file lives under `updates/` so AC's DBUpdater applies it
-- incrementally on existing databases (the matching `CREATE TABLE
-- IF NOT EXISTS` block in base/character_paragon_panel_learned.sql
-- handles fresh installs).
CREATE TABLE IF NOT EXISTS `character_paragon_panel_spell_revoked` (
`guid` INT UNSIGNED NOT NULL COMMENT 'characters.guid',
`parent_spell_id` INT UNSIGNED NOT NULL COMMENT 'character_paragon_panel_spells.spell_id',
`revoked_spell_id` INT UNSIGNED NOT NULL COMMENT 'active spell id auto-granted by skill cascade and revoked',
PRIMARY KEY (`guid`, `parent_spell_id`, `revoked_spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: active auto-learn dependents to keep revoked across logins';
@@ -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';
@@ -0,0 +1,446 @@
-- Per-spell AE costs for Paragon spell purchases (.paragon learn / panel Lock In).
-- Auto-generated by tools/_gen_paragon_spell_ae_cost_sql.py.
-- Apply to the *world* database (AzerothCore's SQL updater handles this on worldserver start).
-- The flat 1-AE cost is a Phase 3 placeholder; tune individual rows here as the
-- economy gets balanced (e.g., 5 AE for top-rank baseline like Cyclone).
CREATE TABLE IF NOT EXISTS `paragon_spell_ae_cost` (
`spell_id` INT UNSIGNED NOT NULL,
`ae_cost` SMALLINT UNSIGNED NOT NULL DEFAULT '1',
PRIMARY KEY (`spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='mod-paragon: AE cost per spell';
-- Bulk-load: replace the entire table with the current bake. Manual edits
-- to specific rows will be lost when this script regenerates the file --
-- track per-spell tuning in a separate INSERT ... ON DUPLICATE KEY UPDATE
-- file (e.g. paragon_spell_ae_cost_overrides.sql) if needed.
DELETE FROM `paragon_spell_ae_cost`;
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(10, 1),
(17, 1),
(53, 1),
(72, 1),
(75, 1),
(78, 1),
(99, 1),
(100, 1),
(116, 1),
(118, 1),
(120, 1),
(122, 1),
(130, 1),
(131, 1),
(132, 1),
(133, 1),
(136, 1),
(139, 1),
(168, 1),
(172, 1),
(324, 1),
(331, 1),
(339, 1),
(348, 1),
(370, 1),
(403, 1),
(408, 1),
(421, 1),
(453, 1),
(465, 1),
(467, 1),
(469, 1),
(475, 1),
(498, 1),
(527, 1),
(528, 1),
(543, 1),
(546, 1),
(552, 1),
(556, 1),
(585, 1),
(586, 1),
(587, 1),
(588, 1),
(589, 1),
(596, 1),
(603, 1),
(604, 1),
(605, 1),
(633, 1),
(635, 1),
(642, 1),
(676, 1),
(686, 1),
(687, 1),
(689, 1),
(693, 1),
(694, 1),
(698, 1),
(702, 1),
(703, 1),
(706, 1),
(710, 1),
(740, 1),
(755, 1),
(759, 1),
(770, 1),
(772, 1),
(774, 1),
(779, 1),
(781, 1),
(845, 1),
(853, 1),
(871, 1),
(879, 1),
(883, 1),
(921, 1),
(976, 1),
(980, 1),
(982, 1),
(1002, 1),
(1008, 1),
(1022, 1),
(1038, 1),
(1044, 1),
(1064, 1),
(1079, 1),
(1082, 1),
(1098, 1),
(1120, 1),
(1126, 1),
(1130, 1),
(1152, 1),
(1160, 1),
(1161, 1),
(1243, 1),
(1449, 1),
(1454, 1),
(1459, 1),
(1462, 1),
(1463, 1),
(1464, 1),
(1490, 1),
(1494, 1),
(1495, 1),
(1499, 1),
(1510, 1),
(1513, 1),
(1515, 1),
(1535, 1),
(1543, 1),
(1680, 1),
(1706, 1),
(1714, 1),
(1715, 1),
(1719, 1),
(1725, 1),
(1752, 1),
(1766, 1),
(1776, 1),
(1784, 1),
(1822, 1),
(1833, 1),
(1842, 1),
(1850, 1),
(1856, 1),
(1943, 1),
(1949, 1),
(1953, 1),
(1966, 1),
(1978, 1),
(2006, 1),
(2008, 1),
(2050, 1),
(2054, 1),
(2060, 1),
(2061, 1),
(2062, 1),
(2094, 1),
(2096, 1),
(2098, 1),
(2120, 1),
(2136, 1),
(2139, 1),
(2362, 1),
(2457, 1),
(2458, 1),
(2484, 1),
(2565, 1),
(2637, 1),
(2641, 1),
(2643, 1),
(2645, 1),
(2687, 1),
(2782, 1),
(2812, 1),
(2825, 1),
(2893, 1),
(2908, 1),
(2912, 1),
(2944, 1),
(2948, 1),
(2973, 1),
(2974, 1),
(2983, 1),
(3034, 1),
(3043, 1),
(3044, 1),
(3045, 1),
(3411, 1),
(3561, 1),
(3562, 1),
(3563, 1),
(3565, 1),
(3566, 1),
(3567, 1),
(3738, 1),
(4987, 1),
(5116, 1),
(5118, 1),
(5138, 1),
(5143, 1),
(5171, 1),
(5176, 1),
(5185, 1),
(5209, 1),
(5211, 1),
(5215, 1),
(5217, 1),
(5221, 1),
(5225, 1),
(5229, 1),
(5246, 1),
(5277, 1),
(5308, 1),
(5384, 1),
(5484, 1),
(5500, 1),
(5502, 1),
(5504, 1);
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(5675, 1),
(5676, 1),
(5697, 1),
(5730, 1),
(5740, 1),
(5782, 1),
(5938, 1),
(6117, 1),
(6143, 1),
(6196, 1),
(6197, 1),
(6201, 1),
(6229, 1),
(6343, 1),
(6346, 1),
(6353, 1),
(6366, 1),
(6495, 1),
(6552, 1),
(6572, 1),
(6673, 1),
(6770, 1),
(6785, 1),
(6789, 1),
(6940, 1),
(7294, 1),
(7302, 1),
(7384, 1),
(8004, 1),
(8017, 1),
(8024, 1),
(8033, 1),
(8042, 1),
(8050, 1),
(8056, 1),
(8075, 1),
(8092, 1),
(8122, 1),
(8129, 1),
(8143, 1),
(8170, 1),
(8177, 1),
(8181, 1),
(8184, 1),
(8190, 1),
(8227, 1),
(8232, 1),
(8512, 1),
(8647, 1),
(8676, 1),
(8921, 1),
(8936, 1),
(8998, 1),
(9005, 1),
(9484, 1),
(10059, 1),
(10326, 1),
(10595, 1),
(11416, 1),
(11417, 1),
(11418, 1),
(11419, 1),
(11420, 1),
(13159, 1),
(13161, 1),
(13163, 1),
(13165, 1),
(13795, 1),
(13809, 1),
(13813, 1),
(14752, 1),
(14914, 1),
(15237, 1),
(16689, 1),
(16857, 1),
(16914, 1),
(18499, 1),
(19740, 1),
(19742, 1),
(19746, 1),
(19750, 1),
(19752, 1),
(19801, 1),
(19876, 1),
(19878, 1),
(19879, 1),
(19880, 1),
(19882, 1),
(19883, 1),
(19884, 1),
(19885, 1),
(19888, 1),
(19891, 1),
(20043, 1),
(20154, 1),
(20164, 1),
(20165, 1),
(20166, 1),
(20217, 1),
(20230, 1),
(20252, 1),
(20484, 1),
(20736, 1),
(21084, 1),
(21562, 1),
(21849, 1),
(22568, 1),
(22570, 1),
(22812, 1),
(22842, 1),
(23028, 1),
(23920, 1),
(23922, 1),
(24275, 1),
(25780, 1),
(25782, 1),
(25894, 1),
(25898, 1),
(25899, 1),
(26573, 1),
(26679, 1),
(27243, 1),
(27681, 1),
(27683, 1),
(28176, 1),
(29166, 1),
(29722, 1),
(29858, 1),
(29893, 1),
(30451, 1),
(30455, 1),
(30482, 1),
(31224, 1),
(31789, 1),
(31801, 1),
(31884, 1),
(32182, 1),
(32223, 1),
(32266, 1),
(32267, 1),
(32271, 1),
(32272, 1),
(32375, 1),
(32379, 1),
(32546, 1),
(32645, 1),
(33076, 1),
(33690, 1),
(33691, 1),
(33745, 1),
(33763, 1),
(33786, 1),
(34026, 1),
(34074, 1),
(34428, 1),
(34433, 1),
(34477, 1),
(34600, 1),
(35715, 1),
(35717, 1),
(36936, 1),
(42650, 1),
(42955, 1),
(43265, 1),
(43987, 1),
(44614, 1),
(45438, 1),
(45462, 1),
(45477, 1),
(45524, 1),
(45529, 1),
(45902, 1),
(46584, 1),
(47476, 1),
(47528, 1),
(47541, 1),
(47568, 1),
(47897, 1),
(48018, 1),
(48020, 1),
(48045, 1),
(48263, 1),
(48265, 1),
(48266, 1),
(48707, 1),
(48721, 1),
(48743, 1),
(48792, 1),
(49020, 1),
(49358, 1),
(49359, 1),
(49360, 1),
(49361, 1),
(49576, 1),
(49998, 1),
(50464, 1),
(50842, 1),
(51722, 1),
(51723, 1),
(52610, 1);
INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES
(53140, 1),
(53142, 1),
(53407, 1),
(53408, 1),
(53600, 1),
(53601, 1),
(53736, 1),
(54428, 1),
(55342, 1),
(55694, 1),
(56641, 1),
(57755, 1),
(57934, 1),
(57994, 1),
(60192, 1),
(61846, 1),
(62078, 1),
(62124, 1),
(62757, 1),
(64382, 1),
(64843, 1);
@@ -0,0 +1,270 @@
-- mod-paragon: server-side DBC overlay for class 12 (Paragon).
-- Auto-generated by fractured-tooling/from-workspace-root/
-- _gen_paragon_dbc_overlay_sql.py
--
-- AzerothCore's DBCStores.cpp::LoadDBC merges every <table>_dbc
-- world-DB row on top of the on-disk DBC store at startup
-- (storage.LoadFromDB). We use that to ship Paragon's class-12
-- DBC deltas in SQL form so a stock data/dbc/ tree (e.g. the
-- vanilla `ac-wotlk-client-data` Docker image) still resolves
-- class 12 in sChrClassesStore and class-12 entries in
-- sSkillRaceClassInfoStore.
--
-- Without this migration, fresh installs hit:
-- CHAR_CREATE_FAILED -- "Class (12) not found in DBC ..."
-- the moment a contributor tries to roll a Paragon character.
--
-- This file is regenerated end-to-end from patch-enUS-4.MPQ;
-- do not hand-edit. Update the patched DBC source and rerun
-- the bake script.
-- chrclasses_dbc: classes added or modified by patch-enUS-4.MPQ.
-- AzerothCore merges this on top of the on-disk ChrClasses.dbc
-- so a stock data/dbc tree still gets class 12 at runtime.
DELETE FROM `chrclasses_dbc` WHERE `ID` IN (12);
INSERT INTO `chrclasses_dbc` (`ID`,`Field01`,`DisplayPower`,`PetNameToken`,`Name_Lang_enUS`,`Name_Lang_Mask`,`Name_Female_Lang_Mask`,`Name_Male_Lang_Mask`,`Filename`,`SpellClassSet`,`Flags`,`CinematicSequenceID`,`Required_Expansion`) VALUES
(12, 0, 0, 0, 'Paragon', 0, 0, 0, 'PARAGON', 4, 50, 0, 2);
-- skillraceclassinfo_dbc: rows where patch-enUS-4 OR'd the
-- class-12 bit (0x800) into ClassMask, opening every
-- baseline skill to Paragon. Replaces the stock row by ID so
-- AzerothCore picks the patched mask on the SQL merge pass.
DELETE FROM `skillraceclassinfo_dbc` WHERE `ID` IN (
57,301,107,82,75,140,328,638,872,880,881,885,886,910,117,335,628,629,630,912,126,127,133,134,635,31,39,135,325,636,637,643,644,888,889,914,125,626,884,898,901,58,60,916,59,40,41,68,48,49,44,45,42,43,50,51,131,132,883,913,105,71,70,69,925,54,25,138,139,91,882,85,84,93,88,865,87,441,94,443,92,481,89,442,123,124,624,625,702,908,6,922,33,243,899,241,122,621,622,701,907,970,129,323,631,632,633,634,641,642,142,143,639,640,28,63,282,29,284,65,97,244,940,72,128,878,879,137,144,136,915,55,79,81,76,149,112,111,106,66,26,83,74,73,108,109,110,113,38,35,36,37,61,62,64,24,34,21,906,46,47,52,53,281,104,102,101,27,95,98,96,30,145,146,147,148,151,155,158,159,271,175,178,183,186,270,189,191,193,198,200,265,266,203,204,205,268,269,246,272,330,381,403,445,446,461,501,463,464,521,522,541,544,581,601,741,742,781,841,861,862,866,867,877,934,892,896,897,951,895,900,936,938,939,947
);
INSERT INTO `skillraceclassinfo_dbc` (`ID`,`SkillID`,`RaceMask`,`ClassMask`,`Flags`,`MinLevel`,`SkillTierID`,`SkillCostIndex`) VALUES
(57,6,-1,2176,1040,0,0,0),
(301,8,-1,2176,1040,0,0,0),
(107,26,-1,2049,1040,0,0,0),
(82,38,-1,2056,1040,0,0,0),
(75,39,-1,2056,1040,0,0,0),
(140,43,1115,2049,128,0,0,0),
(328,43,3071,2052,128,0,0,0),
(638,43,164,2049,128,0,0,0),
(872,43,32767,2056,128,0,0,0),
(880,43,1024,2052,128,0,0,0),
(881,43,32767,2432,128,0,0,0),
(885,43,1029,2050,128,0,0,0),
(886,43,512,2050,128,0,0,0),
(910,43,262143,2080,128,0,0,0),
(117,44,166,2052,128,0,0,0),
(335,44,2147483647,2122,128,0,0,0),
(628,44,1544,2052,128,0,0,0),
(629,44,167,2049,128,0,0,0),
(630,44,1112,2049,128,0,0,0),
(912,44,262143,2080,128,0,0,0),
(126,45,650,2052,128,0,0,0),
(127,45,32767,2061,128,0,0,0),
(133,46,36,2052,128,0,0,0),
(134,46,32767,2057,128,0,0,0),
(635,46,1674,2052,128,0,0,0),
(31,50,-1,2052,1040,0,0,0),
(39,51,-1,2052,1040,0,0,0),
(135,54,2147483647,2128,128,0,0,0),
(325,54,-1,2056,128,0,0,0),
(636,54,1133,2049,128,0,0,0),
(637,54,658,2049,128,0,0,0),
(643,54,8,3072,128,0,0,0),
(644,54,32,3072,128,0,0,0),
(888,54,261631,2050,128,0,0,0),
(889,54,512,2050,128,0,0,0),
(914,54,262143,2080,128,0,0,0),
(125,55,262143,2052,128,0,0,0),
(626,55,163839,2049,128,0,0,0),
(884,55,512,2050,128,0,0,0),
(898,55,262143,2080,128,0,0,0),
(901,55,261631,2050,128,0,0,0),
(58,56,-1,2064,1040,0,0,0),
(60,78,-1,2064,1040,0,0,0),
(916,95,524287,2080,640,0,0,0),
(59,96,2047,3072,1168,0,0,0),
(40,98,1101,3583,128,0,0,0),
(41,98,674,3551,160,0,21,0),
(68,101,4,3583,1170,0,0,0),
(48,109,690,3583,128,0,0,0),
(49,109,1101,3551,160,0,21,0),
(44,111,4,3583,128,0,0,0),
(45,111,2043,3551,160,0,21,0),
(42,113,8,3583,128,0,0,0),
(43,113,2039,3551,160,0,21,0),
(50,115,32,3583,128,0,0,0),
(51,115,2015,3551,160,0,21,0),
(131,118,32767,2056,146,1,0,0),
(132,118,32767,2053,146,20,0,0),
(883,118,32767,2112,402,0,0,0),
(913,118,262143,2080,146,0,0,0),
(105,120,2047,2304,1170,0,0,0),
(71,124,32,3583,1170,0,0,0),
(70,125,2,3583,146,0,0,0),
(69,126,8,3583,1170,0,0,0),
(925,129,-1,2080,128,0,63,0),
(54,130,2047,2176,1168,4,0,0),
(25,134,-1,3072,1040,10,0,0),
(138,136,32767,3536,128,0,0,0),
(139,136,32767,2053,128,0,0,0),
(91,137,1535,3551,160,0,21,0),
(882,137,512,3583,128,0,0,0),
(85,138,2047,3583,128,0,0,0),
(84,139,2047,3583,160,0,21,0),
(93,140,2047,3583,128,0,0,0),
(88,141,2047,3583,160,0,21,0),
(865,142,2047,3583,0,0,0,0),
(87,148,1,3551,1170,0,181,0),
(441,148,222,3583,1170,0,182,0),
(94,149,2,3551,1170,0,181,0),
(443,149,509,3583,1170,0,182,0),
(92,150,8,3551,1170,0,181,0),
(481,150,215,3583,1170,0,182,0),
(89,152,4,3551,1170,0,181,0),
(442,152,219,3583,1170,0,182,0),
(123,160,262143,2050,128,0,0,0),
(124,160,-1,3072,128,0,0,0),
(624,160,32,2049,128,0,0,0),
(625,160,262111,2049,128,0,0,0),
(702,160,-1,2112,128,0,0,0),
(908,160,262143,2080,128,0,0,0),
(6,162,2147483647,3551,128,0,0,0),
(922,162,262143,2080,128,0,0,0),
(33,163,-1,2052,1040,0,0,0),
(243,164,2047,3583,160,0,41,0),
(899,165,2047,3583,160,0,41,0),
(241,171,2047,3583,160,0,41,0),
(122,172,163839,2050,128,0,0,0),
(621,172,6,2049,128,0,0,0),
(622,172,1529,2049,128,0,0,0),
(701,172,163839,2112,128,0,0,0),
(907,172,524287,2080,128,0,0,0),
(970,172,163839,2052,128,0,0,0),
(129,173,32767,2312,128,0,0,0),
(323,173,32767,2256,128,0,0,0),
(631,173,520,2052,128,0,0,0),
(632,173,1190,2052,128,0,0,0),
(633,173,216,2049,128,0,0,0),
(634,173,1063,2049,128,0,0,0),
(641,173,32,3072,128,0,0,0),
(642,173,8,3072,128,0,0,0),
(142,176,-1,2056,128,0,0,0),
(143,176,-1,2052,128,0,0,0),
(639,176,128,2049,128,0,0,0),
(640,176,262015,2049,128,0,0,0),
(28,182,2047,3583,160,0,2,0),
(63,184,-1,2050,1040,0,0,0),
(282,185,2047,3583,128,0,61,0),
(29,186,2047,3583,160,0,2,0),
(284,197,2047,3583,160,0,62,0),
(65,198,2047,2050,1168,0,0,0),
(97,199,2047,2112,1168,0,0,0),
(244,202,2047,3583,160,0,41,0),
(940,205,524287,2176,2048,0,0,0),
(72,220,16,3583,1170,0,0,0),
(128,226,32767,2057,128,0,0,0),
(878,226,1024,2052,128,0,0,0),
(879,226,31743,2052,128,0,0,0),
(137,227,2047,3077,128,0,0,0),
(144,228,-1,2448,128,0,0,0),
(136,229,32767,3079,128,20,0,0),
(915,229,262143,2080,128,0,0,0),
(55,237,-1,2176,1040,0,0,0),
(79,238,2047,2056,1168,4,0,0),
(81,239,2047,2056,1168,0,0,0),
(76,241,2047,2056,128,40,0,0),
(149,242,2047,2056,1168,16,0,0),
(112,243,2047,2049,1170,0,0,0),
(111,244,2047,2049,1168,0,0,0),
(106,245,2047,2049,1168,0,0,0),
(66,246,2047,2050,1168,0,0,0),
(26,247,2047,3072,1168,20,0,0),
(83,252,2047,2057,128,0,0,0),
(74,253,-1,2056,1040,0,0,0),
(73,254,2047,2056,1168,10,0,0),
(108,255,2047,2049,1168,0,0,0),
(109,256,-1,2049,1040,0,0,0),
(110,257,-1,2049,1040,0,0,0),
(113,258,2047,2049,1168,10,0,0),
(38,260,2047,2052,128,0,0,0),
(35,262,2047,2052,128,0,0,0),
(36,263,2047,2052,128,0,0,0),
(37,264,2047,2052,128,0,0,0),
(61,267,-1,2050,1040,0,0,0),
(62,268,2047,2050,1170,0,0,0),
(64,269,2047,2050,1168,0,0,0),
(24,272,2047,3072,1168,10,0,0),
(34,273,2047,2052,128,0,0,0),
(21,293,2047,2051,128,40,0,0),
(906,293,262143,2080,128,0,0,0),
(46,313,64,3583,128,0,0,0),
(47,313,1983,3551,160,0,21,0),
(52,315,128,3583,128,0,0,0),
(53,315,1919,3551,160,0,21,0),
(281,333,2047,3583,160,0,62,0),
(104,353,2047,2304,1170,0,0,0),
(102,354,-1,2304,1040,0,0,0),
(101,355,-1,2304,1040,0,0,0),
(27,356,2047,3583,128,0,23,0),
(95,373,-1,2112,1040,0,0,0),
(98,374,262143,2112,1040,0,0,0),
(96,375,262143,2112,1040,0,0,0),
(30,393,2047,3583,160,0,161,0),
(145,413,2047,2116,128,40,0,0),
(146,413,2047,2083,128,0,0,0),
(147,414,2047,3183,128,0,0,0),
(148,415,2047,3583,128,0,0,0),
(151,416,2047,2049,192,0,0,0),
(155,416,2047,2050,192,0,0,1),
(158,416,2047,3136,192,0,0,1),
(159,416,2047,2060,192,0,0,1),
(271,416,2047,2448,192,0,0,2),
(175,418,2047,2049,384,0,0,0),
(178,418,2047,2050,384,0,0,0),
(183,418,2047,3332,384,0,0,1),
(186,418,2047,2192,384,0,0,1),
(270,418,2047,2120,384,0,0,1),
(189,419,2047,2060,640,0,0,2),
(191,419,2047,3072,640,0,0,1),
(193,419,2047,2192,640,0,0,0),
(198,419,2047,2050,640,0,0,1),
(200,419,2047,2049,640,0,0,2),
(265,419,2047,2304,640,0,0,0),
(266,419,2047,2112,640,0,0,1),
(203,420,2047,2061,1152,0,0,2),
(204,420,2047,3074,1152,0,0,1),
(205,420,2047,2320,1152,0,0,0),
(268,420,2047,2176,1152,0,0,0),
(269,420,2047,2112,1152,0,0,1),
(246,433,2047,2115,128,0,0,0),
(272,453,2047,2051,128,0,0,0),
(330,473,4095,3149,130,0,0,0),
(381,493,8,3583,164,0,0,0),
(403,515,2047,3551,128,0,0,0),
(445,533,128,3583,1170,0,181,0),
(446,533,95,3551,1170,0,182,0),
(461,553,64,3551,1170,0,181,0),
(501,553,4,3583,1170,0,182,0),
(463,554,16,3551,1170,0,181,0),
(464,554,207,3583,1170,0,182,0),
(521,573,-1,3072,1040,0,0,0),
(522,574,-1,3072,1040,0,0,0),
(541,593,-1,2304,1040,0,0,0),
(544,594,-1,2050,1040,0,0,0),
(581,613,-1,2064,1040,0,0,0),
(601,633,-1,2056,128,0,0,0),
(741,673,16,3583,128,0,0,0),
(742,673,2031,3551,160,0,21,0),
(781,713,255,3583,1170,0,181,0),
(841,733,128,3583,1170,0,0,0),
(861,753,64,3583,1170,0,0,0),
(862,754,1,3583,1170,0,0,0),
(866,755,2047,3583,160,0,41,0),
(867,756,512,3583,146,0,0,0),
(877,760,1024,3583,146,0,0,0),
(934,762,524287,2080,144,0,223,0),
(892,769,32767,3583,1040,0,0,0),
(896,770,-1,2080,1040,0,0,0),
(897,771,262143,2080,1040,0,0,0),
(951,771,2097151,3583,0,0,0,0),
(895,772,-1,2080,1040,0,0,0),
(900,773,262143,3583,160,0,41,0),
(936,776,262143,2080,128,0,0,0),
(938,777,524287,3583,2,0,0,0),
(939,778,524287,3583,2,0,0,0),
(947,778,2097151,3583,0,0,0,0);
@@ -0,0 +1,179 @@
-- mod-paragon: starter spawn data for class 12 (Paragon).
--
-- Companion to 2026_05_09_00.sql. The DBC overlay teaches the world
-- server that class 12 exists; this migration teaches it WHERE
-- characters of that class spawn, what action bar they boot with,
-- and what per-level base stats to integrity-check against.
--
-- Without these rows, character creation fails inside Player::Create:
--
-- PlayerInfo const* info = sObjectMgr->GetPlayerInfo(race, class);
-- if (!info) {
-- LOG_ERROR("entities.player",
-- "Player::Create: ... invalid race/class pair ({}/{})"
-- " - refusing to do so.", ..., race, class);
-- return false; // -> client sees "Error creating character"
-- }
--
-- and on world load the player_class_stats integrity check trips:
--
-- "Class N Level L does not have stats data!"
--
-- Tables touched:
-- - playercreateinfo : (race, class=12) -> map/zone/x/y/z
-- Race-specific starting zones (Paragon
-- spawns in each race's standard newbie
-- area, NOT Acherus, since it is a
-- from-level-1 class).
-- - playercreateinfo_action : (race, class=12, button) -> action,type
-- Default action bar layout per race.
-- - player_class_stats : (class=12, level 1..80) -> base stats
-- Per-level HP/Mana/STR/AGI/STA/INT/SPI
-- used by Player::InitStatsForLevel.
--
-- Tables intentionally NOT touched here:
-- - playercreateinfo_item : Paragon ships no per-class starting
-- items; gear comes from the racial
-- kit only.
-- - playercreateinfo_skills / _cast_spell / _spell_custom :
-- These are mask-based. Class-12 baseline
-- weapon/defense skills come through
-- classMask=0 ("all classes") rows that
-- already cover Paragon. The DBC overlay
-- in 2026_05_09_00.sql opens
-- SkillRaceClassInfo for class 12.
-- Idempotent: blow away any pre-existing class-12 rows first so this
-- migration can be replayed cleanly on a partially-seeded DB (e.g.
-- after a contributor manually patched their local DB before this
-- migration landed).
DELETE FROM `playercreateinfo` WHERE `class` = 12;
DELETE FROM `playercreateinfo_action` WHERE `class` = 12;
DELETE FROM `player_class_stats` WHERE `Class` = 12;
-- ---------------------------------------------------------------
-- playercreateinfo (10 rows: every DK-eligible race, racial start)
-- ---------------------------------------------------------------
INSERT INTO `playercreateinfo` (`race`, `class`, `map`, `zone`, `position_x`, `position_y`, `position_z`, `orientation`) VALUES
( 1, 12, 0, 12, -8949.95, -132.493, 83.5312, 0 ), -- Human -> Northshire, Elwynn Forest
( 2, 12, 1, 14, -618.518, -4251.67, 38.718, 0 ), -- Orc -> Valley of Trials, Durotar
( 3, 12, 0, 1, -6240.32, 331.033, 382.758, 6.17716 ), -- Dwarf -> Coldridge Valley, Dun Morogh
( 4, 12, 1, 141, 10311.3, 832.463, 1326.41, 5.69632 ), -- Night Elf -> Shadowglen, Teldrassil
( 5, 12, 0, 85, 1676.71, 1678.31, 121.67, 2.70526 ), -- Undead -> Deathknell, Tirisfal
( 6, 12, 1, 215, -2917.58, -257.98, 52.9968, 0 ), -- Tauren -> Camp Narache, Mulgore
( 7, 12, 0, 1, -6240.32, 331.033, 382.758, 0 ), -- Gnome -> Coldridge Valley (shared)
( 8, 12, 1, 14, -618.518, -4251.67, 38.718, 0 ), -- Troll -> Valley of Trials (shared)
(10, 12, 530, 3431, 10349.6, -6357.29, 33.4026, 5.31605 ), -- Blood Elf -> Sunstrider Isle, Eversong
(11, 12, 530, 3526, -3961.64,-13931.2, 100.615, 2.08364 ); -- Draenei -> Ammen Vale, Azuremyst Isle
-- ---------------------------------------------------------------
-- playercreateinfo_action (46 rows)
-- Buttons: 72=Attack(6603), 73=Eat(78), 74=racial, 75=race-extra,
-- 82=Skinning(59752, Tauren only), 84=Attack, 96=Attack
-- ---------------------------------------------------------------
INSERT INTO `playercreateinfo_action` (`race`, `class`, `button`, `action`, `type`) VALUES
( 1, 12, 72, 6603, 0), ( 1, 12, 73, 78, 0), ( 1, 12, 82, 59752, 0),
( 1, 12, 84, 6603, 0), ( 1, 12, 96, 6603, 0),
( 2, 12, 72, 6603, 0), ( 2, 12, 73, 78, 0), ( 2, 12, 74, 20572, 0),
( 2, 12, 84, 6603, 0), ( 2, 12, 96, 6603, 0),
( 3, 12, 72, 6603, 0), ( 3, 12, 73, 78, 0), ( 3, 12, 74, 20594, 0),
( 3, 12, 75, 2481, 0), ( 3, 12, 84, 6603, 0), ( 3, 12, 96, 6603, 0),
( 4, 12, 72, 6603, 0), ( 4, 12, 73, 78, 0), ( 4, 12, 74, 58984, 0),
( 4, 12, 84, 6603, 0), ( 4, 12, 96, 6603, 0),
( 5, 12, 72, 6603, 0), ( 5, 12, 73, 78, 0), ( 5, 12, 74, 20577, 0),
( 5, 12, 84, 6603, 0), ( 5, 12, 96, 6603, 0),
( 6, 12, 72, 6603, 0), ( 6, 12, 73, 78, 0), ( 6, 12, 74, 20549, 0),
( 6, 12, 84, 6603, 0), ( 6, 12, 96, 6603, 0),
( 7, 12, 72, 6603, 0), ( 7, 12, 73, 78, 0), ( 7, 12, 84, 6603, 0),
( 7, 12, 96, 6603, 0),
( 8, 12, 72, 6603, 0), ( 8, 12, 73, 78, 0), ( 8, 12, 74, 2764, 0),
( 8, 12, 75, 26297, 0), ( 8, 12, 84, 6603, 0), ( 8, 12, 96, 6603, 0),
(11, 12, 72, 6603, 0), (11, 12, 73, 78, 0), (11, 12, 74, 28880, 0),
(11, 12, 84, 6603, 0), (11, 12, 96, 6603, 0);
-- ---------------------------------------------------------------
-- player_class_stats (80 rows: levels 1..80 per-class base stats)
-- Curve mirrors Warrior baseline -> Paladin past 60 (vehicle-style HP
-- inflation past 60 to keep Paragon competitive in Wrath content).
-- ---------------------------------------------------------------
INSERT INTO `player_class_stats` (`Class`, `Level`, `BaseHP`, `BaseMana`, `Strength`, `Agility`, `Stamina`, `Intellect`, `Spirit`) VALUES
(12, 1, 20, 60, 23, 20, 22, 20, 20),
(12, 2, 29, 66, 24, 21, 23, 20, 20),
(12, 3, 38, 73, 25, 21, 24, 20, 21),
(12, 4, 47, 81, 26, 22, 25, 20, 21),
(12, 5, 56, 90, 28, 23, 26, 20, 21),
(12, 6, 65, 100, 29, 24, 27, 21, 21),
(12, 7, 74, 111, 30, 24, 28, 21, 22),
(12, 8, 83, 123, 31, 25, 29, 21, 22),
(12, 9, 92, 136, 32, 26, 30, 21, 22),
(12, 10, 97, 150, 33, 26, 31, 21, 23),
(12, 11, 103, 165, 35, 27, 33, 21, 23),
(12, 12, 109, 182, 36, 28, 34, 21, 23),
(12, 13, 118, 200, 37, 29, 35, 21, 24),
(12, 14, 128, 219, 39, 30, 36, 22, 24),
(12, 15, 139, 239, 40, 30, 37, 22, 24),
(12, 16, 151, 260, 41, 31, 38, 22, 25),
(12, 17, 154, 282, 42, 32, 40, 22, 25),
(12, 18, 168, 305, 44, 33, 41, 22, 25),
(12, 19, 183, 329, 45, 34, 42, 22, 26),
(12, 20, 199, 354, 47, 35, 43, 22, 26),
(12, 21, 206, 380, 48, 35, 45, 23, 26),
(12, 22, 224, 392, 49, 36, 46, 23, 27),
(12, 23, 243, 420, 51, 37, 47, 23, 27),
(12, 24, 253, 449, 52, 38, 49, 23, 28),
(12, 25, 274, 479, 54, 39, 50, 23, 28),
(12, 26, 296, 509, 55, 40, 51, 23, 28),
(12, 27, 309, 524, 57, 41, 53, 23, 29),
(12, 28, 333, 554, 58, 42, 54, 24, 29),
(12, 29, 348, 584, 60, 43, 56, 24, 30),
(12, 30, 374, 614, 62, 44, 57, 24, 30),
(12, 31, 401, 629, 63, 45, 58, 24, 30),
(12, 32, 419, 659, 65, 46, 60, 24, 31),
(12, 33, 448, 689, 66, 47, 61, 24, 31),
(12, 34, 468, 704, 68, 48, 63, 25, 32),
(12, 35, 499, 734, 70, 49, 64, 25, 32),
(12, 36, 521, 749, 72, 50, 66, 25, 33),
(12, 37, 545, 779, 73, 51, 68, 25, 33),
(12, 38, 581, 809, 75, 52, 69, 25, 33),
(12, 39, 609, 824, 77, 53, 71, 26, 34),
(12, 40, 649, 854, 79, 54, 72, 26, 34),
(12, 41, 681, 869, 80, 56, 74, 26, 35),
(12, 42, 715, 899, 82, 57, 76, 26, 35),
(12, 43, 761, 914, 84, 58, 77, 26, 36),
(12, 44, 799, 944, 86, 59, 79, 26, 36),
(12, 45, 839, 959, 88, 60, 81, 27, 37),
(12, 46, 881, 989, 90, 61, 83, 27, 37),
(12, 47, 935, 1004, 92, 63, 84, 27, 38),
(12, 48, 981, 1019, 94, 64, 86, 27, 38),
(12, 49, 1029, 1049, 96, 65, 88, 28, 39),
(12, 50, 1079, 1064, 98, 66, 90, 28, 39),
(12, 51, 1131, 1079, 100, 68, 92, 28, 40),
(12, 52, 1185, 1109, 102, 69, 94, 28, 40),
(12, 53, 1241, 1124, 104, 70, 96, 28, 41),
(12, 54, 1299, 1139, 106, 72, 98, 29, 42),
(12, 55, 1359, 1154, 109, 73, 100, 29, 42),
(12, 56, 1421, 1169, 111, 74, 102, 29, 43),
(12, 57, 1485, 1199, 113, 76, 104, 29, 43),
(12, 58, 1551, 1214, 115, 77, 106, 30, 44),
(12, 59, 1619, 1229, 118, 79, 108, 30, 44),
(12, 60, 1689, 1244, 120, 80, 110, 30, 45),
(12, 61, 1902, 1357, 122, 81, 112, 30, 46),
(12, 62, 2129, 1469, 125, 83, 114, 30, 46),
(12, 63, 2357, 1582, 127, 84, 117, 31, 47),
(12, 64, 2612, 1694, 130, 86, 119, 31, 47),
(12, 65, 2883, 1807, 132, 88, 121, 31, 48),
(12, 66, 3169, 1919, 135, 89, 123, 32, 49),
(12, 67, 3455, 2032, 137, 91, 126, 32, 49),
(12, 68, 3774, 2145, 140, 92, 128, 32, 50),
(12, 69, 4109, 2257, 142, 94, 130, 32, 51),
(12, 70, 4444, 2370, 145, 96, 133, 33, 51),
(12, 71, 4720, 2482, 148, 97, 135, 33, 52),
(12, 72, 5013, 2595, 150, 99, 138, 33, 53),
(12, 73, 5325, 2708, 153, 101, 140, 33, 54),
(12, 74, 5656, 2820, 156, 102, 143, 34, 54),
(12, 75, 6008, 2933, 159, 104, 145, 34, 55),
(12, 76, 6381, 3045, 162, 106, 148, 34, 56),
(12, 77, 6778, 3158, 165, 108, 151, 35, 57),
(12, 78, 7198, 3270, 168, 109, 153, 35, 57),
(12, 79, 7646, 3383, 171, 111, 156, 35, 58),
(12, 80, 8121, 3496, 174, 113, 159, 36, 59);
@@ -0,0 +1,50 @@
-- mod-paragon: starter weapon / armor skills for class 12 (Paragon).
--
-- Companion to 2026_05_10_00.sql. The spawn-data migration teaches
-- Player::Create *that* class 12 exists at a given race; this one
-- teaches it which weapon and armor skill lines to grant on first
-- character login.
--
-- Without these rows a fresh Paragon character lands in their newbie
-- zone with **no** weapon or armor proficiencies (auto-attack greys
-- out the moment they equip anything beyond a fist). The classMask=0
-- "all classes" rows in playercreateinfo_skills only cover Defense,
-- Unarmed, Cloth, the racial / language skills, Mounts and
-- Companion Pets -- which is exactly what bare-fisted, naked
-- characters look like.
--
-- Paragon plays every class, so it grants every weapon / armor
-- proficiency at level 1. The skillline rows themselves are still
-- gated by skillraceclassinfo_dbc (handled in 2026_05_09_00.sql),
-- so the client/server agree on what's allowed.
--
-- Idempotent: deletes any pre-existing classMask=2048 rows first
-- (class 12 owns this bitmask on Fractured) so the migration can
-- replay cleanly on a partially-seeded DB.
DELETE FROM `playercreateinfo_skills` WHERE `classMask` = 2048;
INSERT INTO `playercreateinfo_skills`
(`raceMask`, `classMask`, `skill`, `rank`, `comment`) VALUES
-- Weapon proficiencies
(0, 2048, 43, 0, 'Paragon - Swords'),
(0, 2048, 44, 0, 'Paragon - Axes'),
(0, 2048, 45, 0, 'Paragon - Bows'),
(0, 2048, 46, 0, 'Paragon - Guns'),
(0, 2048, 54, 0, 'Paragon - Maces'),
(0, 2048, 55, 0, 'Paragon - Two-Handed Swords'),
(0, 2048, 118, 0, 'Paragon - Dual Wield'),
(0, 2048, 136, 0, 'Paragon - Staves'),
(0, 2048, 160, 0, 'Paragon - Two-Handed Maces'),
(0, 2048, 172, 0, 'Paragon - Two-Handed Axes'),
(0, 2048, 173, 0, 'Paragon - Daggers'),
(0, 2048, 176, 0, 'Paragon - Thrown'),
(0, 2048, 226, 0, 'Paragon - Crossbows'),
(0, 2048, 228, 0, 'Paragon - Wands'),
(0, 2048, 229, 0, 'Paragon - Polearms'),
(0, 2048, 473, 0, 'Paragon - Fist Weapons'),
-- Armor proficiencies (Cloth is in a classMask=0 row already)
(0, 2048, 293, 0, 'Paragon - Plate Mail'),
(0, 2048, 413, 0, 'Paragon - Mail'),
(0, 2048, 414, 0, 'Paragon - Leather'),
(0, 2048, 433, 0, 'Paragon - Shield');
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
-- mod-paragon: Blood Elf "Arcane Torrent" uses three spell IDs in WotLK
-- (28730 mana/casters, 25046 rogue energy, 50613 death knight runic power),
-- all on racial skill line 756. Migration 2026_05_10_02 OR'd class 12 into
-- every SkillLineAbility delta from patch-enUS-4, so Paragon Blood Elves
-- auto-learned all three and the spellbook showed three identical entries.
--
-- Paragon should learn a single combined Arcane Torrent that refunds mana,
-- energy, AND runic power -- whichever pool the character is using at the
-- moment. We keep spell 28730 as the in-book entry for class 12 and attach
-- the SpellScript spell_paragon_arcane_torrent (modules/mod-paragon/src/
-- Paragon_SC.cpp) so casts by a Paragon also EnergizeBySpell energy + RP on
-- top of the stock mana effect. Other classes' Blood Elves are unaffected.
--
-- IDs 13338 / 17510 match stock WotLK SkillLineAbility rows for spells 25046
-- / 50613 on skill line 756.
UPDATE `skilllineability_dbc`
SET `ClassMask` = `ClassMask` & ~2048
WHERE `ID` IN (13338, 17510);
-- Bind spell_paragon_arcane_torrent (defined in Paragon_SC.cpp) to spell
-- 28730. AC's `spell_script_names` is the standard mapping: script name on
-- the right, spell id on the left. Idempotent via DELETE + INSERT.
DELETE FROM `spell_script_names`
WHERE `spell_id` = 28730 AND `ScriptName` = 'spell_paragon_arcane_torrent';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(28730, 'spell_paragon_arcane_torrent');
@@ -0,0 +1,30 @@
-- mod-paragon: extend ItemTemplate::AllowableClass to include class 12
-- (Paragon, bit 1<<11 = 2048) for every class-restricted item.
--
-- Server-side, Player::CanUseItem (PlayerStorage.cpp) already short-
-- circuits the AllowableClass check for class 12. That's enough for any
-- code path the server controls (vendor list filter, AH "usable" filter,
-- CanRollForItemInLFG, CanBuyItem). It is NOT enough on the 3.3.5 client:
-- the WoW.exe binary independently pre-checks AllowableClass against the
-- player's class on right-click of a bag item and refuses *locally* with
-- the red "You can't use that item." text in UIErrorsFrame, never sending
-- CMSG_USE_ITEM at all. Server logs stay silent; only client knows it
-- refused.
--
-- Fix: OR class 12's bit into AllowableClass on every class-restricted
-- row so the client engine's pre-check passes for Paragon. Other
-- classes' bits are unchanged, so e.g. a warrior-only item is still
-- warrior-only for everyone except Paragon. Items with AllowableClass
-- == -1 ("all classes") or 0 ("no restriction recorded") already pass
-- the client engine's check and are not touched.
--
-- After applying this migration the *client* still caches item info in
-- Cache/<locale>/itemcache.wdb. Players who already inspected the item
-- before the change must delete that file (or the whole Cache folder)
-- and reconnect to repopulate it from the worldserver, otherwise the
-- stale cached AllowableClass keeps the engine pre-check failing.
UPDATE `item_template`
SET `AllowableClass` = `AllowableClass` | 2048
WHERE `AllowableClass` > 0
AND (`AllowableClass` & 2048) = 0;
@@ -1,11 +0,0 @@
-- Optional per-spell AE costs for Paragon spell purchases (.paragon learn).
-- Apply to the *world* database.
CREATE TABLE IF NOT EXISTS `paragon_spell_ae_cost` (
`spell_id` INT UNSIGNED NOT NULL,
`ae_cost` SMALLINT UNSIGNED NOT NULL DEFAULT '2',
PRIMARY KEY (`spell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='mod-paragon: AE cost per spell';
-- Example (uncomment to use):
-- INSERT INTO `paragon_spell_ae_cost` (`spell_id`, `ae_cost`) VALUES (55050, 2);
File diff suppressed because it is too large Load Diff
+314 -20
View File
@@ -5,15 +5,24 @@
* so Paragon can reuse other classes' mechanics in narrowly scoped contexts. * so Paragon can reuse other classes' mechanics in narrowly scoped contexts.
*/ */
#include "Chat.h"
#include "Config.h"
#include "Creature.h"
#include "CreatureData.h"
#include "GameTime.h"
#include "Log.h"
#include "ObjectGuid.h"
#include "Pet.h"
#include "Player.h" #include "Player.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "UnitDefines.h" #include "SpellScript.h"
#include "Config.h" #include "SpellScriptLoader.h"
#include "Log.h" #include "WorldPacket.h"
#include "GameTime.h" #include "WorldSession.h"
#include "ObjectGuid.h"
#include <fmt/format.h>
#include <string>
#include <unordered_map> #include <unordered_map>
class Paragon_PlayerScript : public PlayerScript class Paragon_PlayerScript : public PlayerScript
@@ -30,7 +39,7 @@ public:
{ {
LOG_INFO("module", "[paragon] Paragon_PlayerScript registered " LOG_INFO("module", "[paragon] Paragon_PlayerScript registered "
"(MultiResource.HasActivePowers={})", "(MultiResource.HasActivePowers={})",
sConfigMgr->GetOption<bool>("Paragon.MultiResource.HasActivePowers", false)); sConfigMgr->GetOption<bool>("Paragon.MultiResource.HasActivePowers", true));
} }
[[nodiscard]] Optional<bool> OnPlayerIsClass(Player const* player, Classes unitClass, ClassContext context) override [[nodiscard]] Optional<bool> OnPlayerIsClass(Player const* player, Classes unitClass, ClassContext context) override
@@ -38,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;
} }
@@ -70,7 +270,7 @@ public:
if (power == POWER_RUNIC_POWER || power == POWER_RUNE) if (power == POWER_RUNIC_POWER || power == POWER_RUNE)
return true; return true;
if (sConfigMgr->GetOption<bool>("Paragon.MultiResource.HasActivePowers", false)) if (sConfigMgr->GetOption<bool>("Paragon.MultiResource.HasActivePowers", true))
{ {
switch (power) switch (power)
{ {
@@ -184,6 +384,45 @@ public:
{ {
player->ResyncRunes(MAX_RUNES); player->ResyncRunes(MAX_RUNES);
st.lastReadyMask = readyMask; st.lastReadyMask = readyMask;
// Authoritative rune CD pump (PARAA "R RUNES cd0 cd1 ... cd5",
// ms remaining per slot, 0 = ready). The 3.3.5 client engine
// class-gates SMSG_RESYNC_RUNES / SMSG_SPELL_GO RUNE_LIST to DK,
// so the Paragon RuneFrame sim drives the visual entirely off
// COMBAT_LOG_EVENT_UNFILTERED:SPELL_CAST_SUCCESS. The combat log
// arrives ~100200ms after the server already started the
// cooldown, so the client's local timer trails the server. When
// the user spams a rune spell, the server's slot refreshes
// first, accepts the next cast, but the client UI still shows
// CD remaining → "leak-through" past a greyed icon. Pushing the
// actual remaining ms on every mask transition keeps the
// visual locked to server state.
std::string body = "R RUNES";
for (uint8 i = 0; i < MAX_RUNES; ++i)
body += " " + std::to_string(player->GetRuneCooldown(i));
std::string const payload = std::string(kParagonAddonPrefix) + "\t" + body;
WorldPacket runePkt;
ChatHandler::BuildChatPacket(runePkt, CHAT_MSG_WHISPER, LANG_ADDON, player, player, payload);
player->SendDirectMessage(&runePkt);
}
// Combo point pump: the 3.3.5 client engine class-gates SMSG_UPDATE_COMBO_POINTS
// to rogue / druid, so the Paragon UI sim never sees CP changes from
// Honor Among Thieves / Mutilate / etc. via either the engine state or
// the client-side combat-log inference (HAT's 51699 trigger fires with a
// null target and doesn't always emit SPELL_CAST_SUCCESS in the log).
// Push the count over PARAA whenever it changes; the addon's combo
// simulator listens for "R CP <n>" and overwrites paragonCP, so the
// ComboFrame on the target frame paints reliably.
int8 const cp = player->GetComboPoints();
if (cp != st.lastCp)
{
std::string const payload = std::string(kParagonAddonPrefix) + "\t"
+ fmt::format("R CP {}", int32(cp));
WorldPacket data;
ChatHandler::BuildChatPacket(data, CHAT_MSG_WHISPER, LANG_ADDON, player, player, payload);
player->SendDirectMessage(&data);
st.lastCp = cp;
} }
if (!sConfigMgr->GetOption<bool>("Paragon.Diag.RuneTrace", false)) if (!sConfigMgr->GetOption<bool>("Paragon.Diag.RuneTrace", false))
@@ -214,14 +453,69 @@ private:
struct ParagonRuneSyncState struct ParagonRuneSyncState
{ {
uint8 lastReadyMask{0xFFu}; // sentinel: no prior snapshot uint8 lastReadyMask{0xFFu}; // sentinel: no prior snapshot
int8 lastCp{-1}; // sentinel: no prior snapshot
}; };
static constexpr char const* kParagonAddonPrefix = "PARAA";
static std::unordered_map<ObjectGuid, ParagonRuneSyncState> runeSyncByGuid; static std::unordered_map<ObjectGuid, ParagonRuneSyncState> runeSyncByGuid;
}; };
std::unordered_map<ObjectGuid, Paragon_PlayerScript::ParagonRuneSyncState> Paragon_PlayerScript::runeSyncByGuid; std::unordered_map<ObjectGuid, Paragon_PlayerScript::ParagonRuneSyncState> Paragon_PlayerScript::runeSyncByGuid;
// Arcane Torrent (28730) for Paragon: Blood Elf racial skill line 756 has
// three Arcane Torrent variants in stock WotLK (28730 mana, 25046 rogue
// energy, 50613 DK runic power). For Paragon Blood Elves we keep only 28730
// (see migration 2026_05_10_03.sql) and turn it into a "combined" version:
// the stock spell already silences nearby enemies and energizes mana via its
// own effects; this script adds energy, rage, and runic power energize on
// top when the caster is class 12, so a single button refunds whichever
// resource pool the player is actually using. Non-Paragon casters are
// untouched and keep learning their stock racial variant.
class spell_paragon_arcane_torrent : public SpellScript
{
PrepareSpellScript(spell_paragon_arcane_torrent);
void HandleAfterCast()
{
Unit* caster = GetCaster();
if (!caster || !caster->IsPlayer())
return;
Player* player = caster->ToPlayer();
if (player->getClass() != CLASS_PARAGON)
return;
// Stock energize amounts from spell_dbc:
// 25046 Arcane Torrent (Energy) -> 15 energy
// 50613 Arcane Torrent (Runic Power) -> 15 displayed RP (= 150
// internal; AC stores RP scaled 10x, see Player::SetMaxPower
// POWER_RUNIC_POWER, 1000).
// Rage uses the same 10x internal scaling as runic power (see
// Player.cpp:Regenerate where rage decay is `-20` for "2 rage by
// tick"), so 15 displayed rage = 150 internal.
// ModifyPower no-ops on pools the player has no max for, so this is
// safe even before the Paragon picks up energy/rage/RP abilities.
constexpr int32 kEnergyGain = 15;
constexpr int32 kRageGain = 150;
constexpr int32 kRunicPowerGain = 150;
SpellInfo const* spellInfo = GetSpellInfo();
uint32 const spellId = spellInfo ? spellInfo->Id : 28730u;
caster->EnergizeBySpell(player, spellId, kEnergyGain, POWER_ENERGY);
caster->EnergizeBySpell(player, spellId, kRageGain, POWER_RAGE);
caster->EnergizeBySpell(player, spellId, kRunicPowerGain, POWER_RUNIC_POWER);
}
void Register() override
{
AfterCast += SpellCastFn(spell_paragon_arcane_torrent::HandleAfterCast);
}
};
void AddSC_paragon() void AddSC_paragon()
{ {
new Paragon_PlayerScript(); new Paragon_PlayerScript();
RegisterSpellScript(spell_paragon_arcane_torrent);
} }
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Clone Dawnforger/Fractured 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
#
# Usage:
# bash scripts/vps-clone-without-docker.sh /path/to/Fractured git@github.com:Dawnforger/Fractured.git
set -euo pipefail
TARGET="${1:?usage: $0 /path/to/Fractured <git-remote-url>}"
REMOTE="${2:?usage: $0 /path/to/Fractured <git-remote-url>}"
if [[ -e "$TARGET" ]]; then
echo "error: $TARGET already exists; remove it or pick another path." >&2
exit 1
fi
mkdir -p "$(dirname "$TARGET")"
git clone "$REMOTE" "$TARGET"
cd "$TARGET"
if [[ ! -f scripts/vps-sparse-checkout-no-docker.sh ]]; then
echo "error: clone missing scripts/vps-sparse-checkout-no-docker.sh — pull latest main." >&2
exit 1
fi
bash scripts/vps-sparse-checkout-no-docker.sh
echo "Done. Next: docs/DEPLOY_LINUX_VPS.md"
+336
View File
@@ -0,0 +1,336 @@
#!/usr/bin/env bash
# Collect VPS evidence for Paragon / DBUpdater / binary staleness triage.
# Run ON the VPS (Linux). Safe: read-only; does not restart services.
#
# Usage (from clone):
# bash scripts/vps-paragon-diagnostics.sh
#
# Optional environment:
# FRACTURED_REPO — absolute path to Fractured git root (default: parent of scripts/)
# FRACTURED_WS_BIN — path to worldserver binary (default: auto-detect)
# FRACTURED_WORLDSERVER_CONF — path to worldserver.conf (default: guess from BIN + common layouts)
# FRACTURED_SYSTEMD_UNITS — space-separated units to try (default: "fractured-world worldserver ac-worldserver")
# FRACTURED_MYSQL — prefix to invoke mysql, e.g. 'mysql -uacore -pacore -h127.0.0.1'
# (default Fractured local DB user/password are often both "acore"; use ~/.my.cnf if you prefer not to pass -p on the command line)
# If unset, SQL blocks are printed for manual copy-paste only.
# FRACTURED_SPELL_IDS — space-separated spell IDs for spell_dbc spot-check (defaults to common DK rune spenders)
# FRACTURED_DIAG_OUTPUT — full log file path (default: <repo>/var/vps-paragon-diagnostics-last.txt)
#
# All output is mirrored to the log file (tee) while still printing to the terminal.
# Default path lives under var/ (gitignored in this repo). Open that file in Cursor,
# scp it down, or: git add -f var/vps-paragon-diagnostics-last.txt if you intend to commit it.
set -u
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="${FRACTURED_REPO:-$(cd "$SCRIPT_DIR/.." && pwd)}"
DIAG_OUT="${FRACTURED_DIAG_OUTPUT:-$REPO/var/vps-paragon-diagnostics-last.txt}"
mkdir -p "$(dirname "$DIAG_OUT")"
exec > >(tee "$DIAG_OUT") 2>&1
echo "Logging to: $DIAG_OUT"
hr() { printf '\n%s\n' "================================================================================"; }
sub() { printf '\n-- %s\n' "$1"; }
detect_worldserver_bin() {
local bin="" es path u units
if [[ -n "${FRACTURED_WS_BIN:-}" ]]; then
readlink -f "$FRACTURED_WS_BIN" 2>/dev/null && return
echo "$FRACTURED_WS_BIN"
return
fi
units="${FRACTURED_SYSTEMD_UNITS:-fractured-world worldserver ac-worldserver}"
for u in $units; do
if systemctl is-active --quiet "$u" 2>/dev/null || systemctl is-enabled --quiet "$u" 2>/dev/null; then
es=$(systemctl show "$u" -p ExecStart --value 2>/dev/null || true)
if [[ -n "$es" ]]; then
if [[ "$es" == \{*path=* ]]; then
path=$(printf '%s' "$es" | sed -n 's/.*path=\([^;]*\).*/\1/p')
else
path=$(printf '%s' "$es" | awk '{print $1}' | sed 's/^path=//')
fi
if [[ -n "$path" && -x "$path" ]]; then
readlink -f "$path" 2>/dev/null && return
fi
fi
fi
done
local pid
pid=$(pgrep -xo worldserver 2>/dev/null || true)
if [[ -n "$pid" ]]; then
readlink -f "/proc/$pid/exe" 2>/dev/null && return
fi
if command -v worldserver >/dev/null 2>&1; then
readlink -f "$(command -v worldserver)" 2>/dev/null && return
fi
echo ""
}
guess_worldserver_conf() {
local bin="$1"
local d cands=()
[[ -z "$bin" ]] && return
d=$(dirname "$bin")
cands+=("$d/../etc/worldserver.conf")
cands+=("$d/../../etc/worldserver.conf")
cands+=("$HOME/azeroth-server/etc/worldserver.conf")
cands+=("$HOME/env/dist/etc/worldserver.conf")
for f in "${cands[@]}"; do
f=$(readlink -f "$f" 2>/dev/null || true)
if [[ -n "$f" && -f "$f" ]]; then
echo "$f"
return
fi
done
echo ""
}
binary_strings_paths() {
local ws="$1"
[[ -z "$ws" || ! -f "$ws" ]] && return
strings "$ws" 2>/dev/null | grep -iE '/(home|root|opt|srv|var)[^[:space:]]*/(Fractured|fractured|azeroth|AzerothCore|acore)' | sort -u | head -40
}
hr
echo "Fractured Paragon / native VPS diagnostics"
echo "Date (UTC): $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "Repo (expected): $REPO"
sub "1A — worldserver binary"
WS=$(detect_worldserver_bin || true)
if [[ -z "$WS" ]]; then
echo "ERROR: Could not find worldserver. Set FRACTURED_WS_BIN=/full/path/to/worldserver and re-run."
else
echo "Binary: $WS"
if stat -c 'binary mtime: %y' "$WS" 2>/dev/null; then
:
else
stat -f 'binary mtime: %Sm' -t '%Y-%m-%d %H:%M:%S %z' "$WS" 2>/dev/null || stat "$WS"
fi
fi
sub "1B — repo HEAD + Paragon_Essence.cpp mtime"
if [[ -d "$REPO/.git" ]]; then
(cd "$REPO" && git log -1 --format='HEAD commit: %h %ci %s')
else
echo "WARN: not a git repo: $REPO (set FRACTURED_REPO)"
fi
PE="$REPO/modules/mod-paragon/src/Paragon_Essence.cpp"
if [[ -f "$PE" ]]; then
if stat -c 'Paragon_Essence.cpp mtime: %y' "$PE" 2>/dev/null; then
:
else
stat -f 'Paragon_Essence.cpp mtime: %Sm' -t '%Y-%m-%d %H:%M:%S %z' "$PE" 2>/dev/null || stat "$PE"
fi
else
echo "WARN: missing $PE"
fi
sub "1C — strings heuristics (0 can mean stripped binary — use 1A+1B)"
if [[ -n "$WS" && -f "$WS" ]]; then
c1=$(strings "$WS" 2>/dev/null | grep -c 'CLASS_PARAGON' || true)
c2=$(strings "$WS" 2>/dev/null | grep -c 'C BUILD SAVE_CURRENT' || true)
c3=$(strings "$WS" 2>/dev/null | grep -c 'character_paragon_build_share_archive' || true)
echo "CLASS_PARAGON count: $c1"
echo "C BUILD SAVE_CURRENT count: $c2"
echo "character_paragon_build_share_archive count: $c3"
else
echo "(skipped — no binary)"
fi
sub "1D — binary fingerprint (compare sha256 across dev vs VPS)"
if [[ -n "$WS" && -f "$WS" ]]; then
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$WS"
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$WS"
else
echo "(no sha256sum — install coreutils)"
fi
echo "Embedded revision / version strings (first matches):"
strings "$WS" 2>/dev/null | grep -iE 'azerothcore|revision|git|commit|build.*20[0-9]{2}' | head -25 || echo "(none matched)"
else
echo "(skipped — no binary)"
fi
CONF="${FRACTURED_WORLDSERVER_CONF:-}"
if [[ -z "$CONF" && -n "$WS" ]]; then
CONF=$(guess_worldserver_conf "$WS")
fi
sub "2B — worldserver.conf (updater / source / rates / paragon)"
if [[ -n "$CONF" && -f "$CONF" ]]; then
echo "Using conf: $CONF"
grep -E '^SourceDirectory|^Updates\.EnableDatabases|^Updates\.AutoSetup|^[[:space:]]*SourceDirectory|^[[:space:]]*Updates\.EnableDatabases|^[[:space:]]*Updates\.AutoSetup' "$CONF" 2>/dev/null || echo "(no matching lines or unreadable)"
echo "--- Rate.RunicPower (if set) ---"
grep -iE '^Rate\.RunicPower|^[[:space:]]*Rate\.RunicPower' "$CONF" 2>/dev/null || echo "(not set — server uses default)"
echo "--- Paragon.* module options (if any) ---"
grep -iE '^Paragon\.|^[[:space:]]*Paragon\.' "$CONF" 2>/dev/null || echo "(no Paragon.* keys in worldserver.conf — check etc/modules/mod_paragon.conf)"
else
echo "WARN: worldserver.conf not found. Set FRACTURED_WORLDSERVER_CONF=/path/to/worldserver.conf"
fi
if [[ -n "$WS" && -f "$WS" ]]; then
ETCGuess=$(readlink -f "$(dirname "$WS")/../etc" 2>/dev/null || true)
MPC="$ETCGuess/modules/mod_paragon.conf"
if [[ -f "$MPC" ]]; then
sub "2B2 — mod_paragon.conf Paragon.* toggles (non-comment)"
grep -E '^Paragon\.' "$MPC" 2>/dev/null | head -40 || echo "(no uncommented Paragon.* lines)"
fi
fi
sub "2A — path-like strings from binary (candidate source roots)"
if [[ -n "$WS" && -f "$WS" ]]; then
binary_strings_paths "$WS" || true
else
echo "(skipped)"
fi
sub "Resolved source root for 2D"
RESOLVED=""
if [[ -n "$CONF" && -f "$CONF" ]]; then
sd=$(awk -F= '/^[[:space:]]*SourceDirectory[[:space:]]*=/ {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2);
gsub(/^["'\'']|["'\'']$/, "", $2);
print $2; exit }' "$CONF" 2>/dev/null || true)
if [[ -n "${sd:-}" ]]; then
RESOLVED="$sd"
fi
fi
if [[ -z "$RESOLVED" ]]; then
RESOLVED="$REPO"
fi
echo "Using RESOLVED=$RESOLVED (from SourceDirectory if set in conf, else FRACTURED_REPO)"
sub "2D — Paragon SQL dirs under RESOLVED"
for subdir in \
"$RESOLVED/modules/mod-paragon/data/sql/db-world/updates/" \
"$RESOLVED/modules/mod-paragon/data/sql/db-characters/updates/"; do
if [[ -d "$subdir" ]]; then
echo "Listing: $subdir"
ls -la "$subdir" 2>/dev/null | tail -15
else
echo "MISSING: $subdir"
fi
done
sub "CMake build dir hints (common Fractured layouts)"
for cand in "$REPO/var/build/obj" "$REPO/build" "$REPO/../build"; do
if [[ -f "$cand/CMakeCache.txt" ]]; then
echo "Found CMakeCache: $cand/CMakeCache.txt"
grep -E '^CMAKE_HOME_DIRECTORY:|^MODULES:|^CMAKE_INSTALL_PREFIX:' "$cand/CMakeCache.txt" 2>/dev/null | head -5
fi
done
sub "DATABASE — updates rows (2026_05_10 / paragon)"
SQL_WORLD=$(cat <<'EOS'
SELECT name, hash, speed FROM updates
WHERE name LIKE '2026_05_10%' OR name LIKE '%paragon%'
ORDER BY name DESC LIMIT 30;
EOS
)
SQL_CHAR="$SQL_WORLD"
if [[ -n "${FRACTURED_MYSQL:-}" ]]; then
echo "--- acore_world ---"
$FRACTURED_MYSQL acore_world -e "$SQL_WORLD" || echo "(mysql failed for acore_world)"
echo "--- acore_characters ---"
$FRACTURED_MYSQL acore_characters -e "$SQL_CHAR" || echo "(mysql failed for acore_characters)"
sub "DATABASE — DBC parity for runes / Paragon (acore_world)"
# Common DK rune spenders (WotLK). Override: export FRACTURED_SPELL_IDS='45477 45462'
SPELL_IDS="${FRACTURED_SPELL_IDS:-45477 45462 49923 55050 56815}"
IDS_CSV=$(echo "$SPELL_IDS" | tr ' ' ',')
echo "--- spell_dbc table size (world DB overrides; 0 rows = all spells from disk DBC only) ---"
$FRACTURED_MYSQL acore_world -e "SELECT COUNT(*) AS spell_dbc_rows FROM spell_dbc;" 2>/dev/null || echo "(spell_dbc missing or no access)"
echo "--- acore_world.version (last core revision written by worldserver) ---"
$FRACTURED_MYSQL acore_world -e "SELECT * FROM version LIMIT 5;" 2>/dev/null || echo "(version table missing?)"
echo "--- chrclasses_dbc class 6 + 12 (DisplayPower: 0=mana, 5=POWER_RUNE in AC) ---"
$FRACTURED_MYSQL acore_world -e "
SELECT ID, DisplayPower, Name_Lang_enUS FROM chrclasses_dbc WHERE ID IN (6,12);
" 2>/dev/null || echo "(query failed — chrclasses_dbc missing?)"
echo "Note: If only ID=12 appears, class 6 (DK) is not overridden in DB — loaded from disk DBC (normal)."
echo "--- spell_dbc: are sample DK spells overridden in DB? ---"
spell_sample_n=$($FRACTURED_MYSQL acore_world -N -B -e \
"SELECT COUNT(*) FROM spell_dbc WHERE ID IN ($IDS_CSV);" 2>/dev/null || echo 0)
echo "Row count in spell_dbc for sample IDs ($SPELL_IDS): ${spell_sample_n:-0}"
if [[ "${spell_sample_n:-0}" == "0" ]]; then
echo "=> 0 means those spells use on-disk Spell.dbc only; the sample block below will be empty (not an error)."
fi
echo "--- spell_dbc sample (PowerType 5 = POWER_RUNE in AC) ---"
$FRACTURED_MYSQL acore_world -e "
SELECT ID, PowerType, ManaCost, RuneCostID FROM spell_dbc WHERE ID IN ($IDS_CSV);
" 2>/dev/null || echo "(query failed — spell_dbc missing or wrong schema)"
echo "--- spellrunecost join for sample IDs (empty if no spell_dbc rows above) ---"
$FRACTURED_MYSQL acore_world -e "
SELECT s.ID AS spell_id, s.PowerType, s.RuneCostID, r.Blood, r.Unholy, r.Frost, r.RunicPower
FROM spell_dbc s
LEFT JOIN spellrunecost_dbc r ON r.ID = s.RuneCostID
WHERE s.ID IN ($IDS_CSV);
" 2>/dev/null || echo "(join failed — check spellrunecost_dbc)"
echo "--- spell_dbc suspicious overrides: RuneCostID>0 but PowerType!=5 (can break rune checks) ---"
$FRACTURED_MYSQL acore_world -e "
SELECT ID, PowerType, ManaCost, RuneCostID FROM spell_dbc
WHERE RuneCostID > 0 AND PowerType <> 5
ORDER BY ID LIMIT 40;
" 2>/dev/null || echo "(query failed)"
echo "Compare counts/IDs to dev: unexpected rows here warrant a DB diff."
echo "--- spell_dbc POWER_RUNE (5) spells with RuneCostID (sample) ---"
$FRACTURED_MYSQL acore_world -e "
SELECT ID, PowerType, RuneCostID FROM spell_dbc
WHERE PowerType = 5 AND RuneCostID > 0
ORDER BY ID LIMIT 15;
" 2>/dev/null || echo "(query failed)"
else
echo "FRACTURED_MYSQL not set — run manually (example: export FRACTURED_MYSQL='mysql -uUSER -hHOST')"
echo "acore_world:"
echo "$SQL_WORLD"
echo "acore_characters:"
echo "$SQL_CHAR"
echo ""
echo "Optional DBC parity (acore_world) — run after connecting:"
echo " SELECT ID, DisplayPower, Name_Lang_enUS FROM chrclasses_dbc WHERE ID IN (6,12);"
echo " SELECT ID, PowerType, ManaCost, RuneCostID FROM spell_dbc WHERE ID IN (45477,45462,49923,55050,56815);"
echo " SELECT s.ID, s.RuneCostID, r.Blood, r.Unholy, r.Frost, r.RunicPower FROM spell_dbc s"
echo " LEFT JOIN spellrunecost_dbc r ON r.ID = s.RuneCostID WHERE s.ID IN (45477,45462,49923,55050,56815);"
fi
sub "mod_paragon.conf vs .dist (install etc)"
ETC=""
if [[ -n "$WS" ]]; then
ETC=$(readlink -f "$(dirname "$WS")/../etc" 2>/dev/null || true)
fi
if [[ -z "$ETC" || ! -d "$ETC" ]]; then
ETC=$(readlink -f "$HOME/azeroth-server/etc" 2>/dev/null || true)
fi
if [[ -n "$ETC" && -d "$ETC/modules" ]]; then
MP="$ETC/modules/mod_paragon.conf"
MPD="$ETC/modules/mod_paragon.conf.dist"
if [[ -f "$MP" && -f "$MPD" ]]; then
diff -u "$MP" "$MPD" 2>/dev/null | head -80 || true
else
echo "ETC=$ETC — mod_paragon.conf or .dist missing (MP=$MP MPD=$MPD)"
fi
else
echo "Could not find install etc/modules (set paths manually for diff)."
fi
hr
echo "DELIVERABLE for maintainer:"
echo "1) Paste 1A1D (binary mtime, git HEAD, strings, sha256 + revision strings)."
echo "2) Paste DATABASE blocks: updates + DBC parity (chrclasses 12, spell_dbc, spellrunecost join)."
echo "3) Paste 2A path strings + 2D listings (or MISSING lines)."
echo "4) From dev: same 1D sha256 of worldserver OR same SQL block — proves binary/data parity."
echo "5) ONE sentence: exact in-game symptom."
echo "Done."
echo ""
echo "Full transcript: $DIAG_OUT"
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Omit Docker-only paths from the working tree (native VPS / production clones).
# Repository root is the AzerothCore tree (flat layout).
#
# Run from repository root (directory that contains acore.sh and apps/).
#
# Usage:
# bash scripts/vps-sparse-checkout-no-docker.sh
#
# Restore full tree: git sparse-checkout disable
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
if [[ ! -d .git ]]; then
echo "error: run from a git clone (no .git in $ROOT)." >&2
exit 1
fi
git sparse-checkout init --no-cone
cat >.git/info/sparse-checkout <<'EOF'
/*
!/docker-compose.yml
!/docker-compose.override.yml
!/apps/docker/
!/env/docker-focal-build/
!/.devcontainer/
EOF
if git sparse-checkout reapply 2>/dev/null; then
:
else
git read-tree -mu HEAD
fi
echo "Sparse checkout applied (Docker-only paths omitted)."
echo "To restore full tree locally: git sparse-checkout disable"
+181
View File
@@ -0,0 +1,181 @@
#!/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. git pull on the current branch (optional; can skip)
# 2. ./acore.sh compiler build — or compiler all for a full clean rebuild
#
# Database migrations from data/sql/updates/ run when you next start worldserver/authserver
# (Updates.* / SourceDirectory in *.conf). This script does not start or stop daemons unless
# you pass --run-after or set FRACTURED_POST_UPDATE_CMD.
#
# Usage:
# bash scripts/vps-update-server.sh
# bash scripts/vps-update-server.sh --full
# bash scripts/vps-update-server.sh --no-pull
# bash scripts/vps-update-server.sh --dry-run
# FRACTURED_POST_UPDATE_CMD='sudo systemctl restart fractured-world' bash scripts/vps-update-server.sh --run-after
# bash scripts/vps-update-server.sh --run-after 'sudo systemctl restart fractured-world'
#
# Environment:
# FRACTURED_GIT_REMOTE — remote name (default: origin)
# FRACTURED_POST_UPDATE_CMD — shell command run after a successful compile (if --run-after is passed without an argument, this is used)
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
POST_UPDATE_CMD="${FRACTURED_POST_UPDATE_CMD:-}"
GIT_REMOTE="${FRACTURED_GIT_REMOTE:-origin}"
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).
--dry-run Print commands without running them.
--run-after [CMD] Run shell command after successful compile. If CMD is omitted,
uses FRACTURED_POST_UPDATE_CMD from the environment.
Environment:
FRACTURED_GIT_REMOTE Git remote (default: origin).
FRACTURED_POST_UPDATE_CMD Used with bare --run-after.
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
;;
--dry-run)
DRY_RUN=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"
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
}
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
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_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
echo "Done. Restart authserver/worldserver (or your service manager) when ready so new binaries and SQL updates apply."
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Rebuild acore_world from data/sql/base/db_world only (AzerothCore "shortcut"):
# the shipped base `updates` table already records archived migrations as applied,
# so worldserver only needs to run pending files under data/sql/updates/db_world
# (and your custom/pending paths if used).
#
# Usage:
# export MYSQL_PWD='...'
# ./scripts/world-db-from-base.sh -h 10.0.13.252 -u acore -D acore_world
#
# Requires: mysql client. Optional: pass --force to skip confirmation.
set -euo pipefail
usage() {
echo "Usage: $0 -h HOST -u USER -D DATABASE [--port PORT] [--force]" >&2
echo " Or set: MYSQL_HOST MYSQL_USER MYSQL_DATABASE [MYSQL_PWD in env]" >&2
exit 1
}
HOST="${MYSQL_HOST:-}"
USER="${MYSQL_USER:-}"
DBNAME="${MYSQL_DATABASE:-}"
PORT="${MYSQL_PORT:-3306}"
FORCE=0
while [[ $# -gt 0 ]]; do
case "$1" in
-h) HOST="$2"; shift 2 ;;
-u) USER="$2"; shift 2 ;;
-D) DBNAME="$2"; shift 2 ;;
--port) PORT="$2"; shift 2 ;;
--force) FORCE=1; shift ;;
*) usage ;;
esac
done
: "${HOST:?set -h or MYSQL_HOST}"
: "${USER:?set -u or MYSQL_USER}"
: "${DBNAME:?set -D or MYSQL_DATABASE}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BASE_DIR="$REPO_ROOT/data/sql/base/db_world"
if [[ ! -d "$BASE_DIR" ]]; then
echo "Base directory not found: $BASE_DIR" >&2
exit 1
fi
if [[ "$FORCE" -ne 1 ]]; then
read -r -p "DESTROY database '$DBNAME' on $HOST and rebuild from base? [yes/no]: " ans
[[ "$ans" == "yes" ]] || { echo "Aborted."; exit 1; }
fi
MYSQL=(mysql -h "$HOST" -P "$PORT" -u "$USER" --protocol=tcp)
"${MYSQL[@]}" -e "DROP DATABASE IF EXISTS \`$DBNAME\`; CREATE DATABASE \`$DBNAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mapfile -t SQL_FILES < <(find "$BASE_DIR" -maxdepth 1 -name '*.sql' -printf '%f\n' | sort)
for f in "${SQL_FILES[@]}"; do
echo ">> $f"
"${MYSQL[@]}" "$DBNAME" <"$BASE_DIR/$f"
done
echo ">> Done. Start worldserver once; it should report World DB up-to-date or only apply updates/db_world."
+8
View File
@@ -17,10 +17,16 @@
#include "ARC4.h" #include "ARC4.h"
#include "Errors.h" #include "Errors.h"
#include <openssl/opensslv.h>
Acore::Crypto::ARC4::ARC4() : _ctx(EVP_CIPHER_CTX_new()) Acore::Crypto::ARC4::ARC4() : _ctx(EVP_CIPHER_CTX_new())
{ {
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
_cipher = EVP_CIPHER_fetch(nullptr, "RC4", nullptr); _cipher = EVP_CIPHER_fetch(nullptr, "RC4", nullptr);
#else
_cipher = const_cast<EVP_CIPHER*>(EVP_rc4());
#endif
ASSERT(_cipher);
EVP_CIPHER_CTX_init(_ctx); EVP_CIPHER_CTX_init(_ctx);
int result = EVP_EncryptInit_ex(_ctx, _cipher, nullptr, nullptr, nullptr); int result = EVP_EncryptInit_ex(_ctx, _cipher, nullptr, nullptr, nullptr);
@@ -30,7 +36,9 @@ Acore::Crypto::ARC4::ARC4() : _ctx(EVP_CIPHER_CTX_new())
Acore::Crypto::ARC4::~ARC4() Acore::Crypto::ARC4::~ARC4()
{ {
EVP_CIPHER_CTX_free(_ctx); EVP_CIPHER_CTX_free(_ctx);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
EVP_CIPHER_free(_cipher); EVP_CIPHER_free(_cipher);
#endif
} }
void Acore::Crypto::ARC4::Init(uint8 const* seed, std::size_t len) void Acore::Crypto::ARC4::Init(uint8 const* seed, std::size_t len)
+10
View File
@@ -17,6 +17,9 @@
#include "OpenSSLCrypto.h" #include "OpenSSLCrypto.h"
#include <openssl/crypto.h> // NOTE: this import is NEEDED (even though some IDEs report it as unused) #include <openssl/crypto.h> // NOTE: this import is NEEDED (even though some IDEs report it as unused)
#include <openssl/opensslv.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#include <openssl/provider.h> #include <openssl/provider.h>
OSSL_PROVIDER* LegacyProvider; OSSL_PROVIDER* LegacyProvider;
@@ -53,3 +56,10 @@ void OpenSSLCrypto::threadsCleanup()
OSSL_PROVIDER_unload(DefaultProvider); OSSL_PROVIDER_unload(DefaultProvider);
OSSL_PROVIDER_set_default_search_path(nullptr, nullptr); OSSL_PROVIDER_set_default_search_path(nullptr, nullptr);
} }
#else
void OpenSSLCrypto::threadsSetup() {}
void OpenSSLCrypto::threadsCleanup() {}
#endif
@@ -52,7 +52,12 @@ MaxPingTime = 30
# #
# RealmServerPort # RealmServerPort
# Description: TCP port to reach the auth server. # Description: TCP port the auth server listens on (login handshake).
# 3724 is the stock WoW default; clients with `set realmlist <host>`
# (no port) connect here. Production deployments that cannot bind
# 3724 (NAT, conflicting service, etc.) can set this to e.g. 47497
# and have clients use `set realmlist <host>:47497` -- the
# Fractured-patched Wow.exe supports the host:port syntax.
# Default: 3724 # Default: 3724
RealmServerPort = 3724 RealmServerPort = 3724
@@ -31,6 +31,7 @@
#include "SQLOperation.h" #include "SQLOperation.h"
#include "Transaction.h" #include "Transaction.h"
#include "WorldDatabase.h" #include "WorldDatabase.h"
#include <array>
#include <limits> #include <limits>
#include <mysqld_error.h> #include <mysqld_error.h>
#include <sstream> #include <sstream>
@@ -59,10 +60,18 @@ DatabaseWorkerPool<T>::DatabaseWorkerPool() :
{ {
WPFatal(mysql_thread_safe(), "Used MySQL library isn't thread-safe."); WPFatal(mysql_thread_safe(), "Used MySQL library isn't thread-safe.");
bool isSupportClientDB = mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION; bool isSupportClientDB;
bool isSameClientDB = mysql_get_client_version() == MYSQL_VERSION_ID; bool isSameClientDB;
#ifdef MARIADB_VERSION_ID
isSupportClientDB = (MARIADB_VERSION_ID >= MIN_MARIADB_CLIENT_VERSION_ID);
// MariaDB packages often differ between build host and runtime; strict id match is too brittle.
isSameClientDB = true;
#else
isSupportClientDB = mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION;
isSameClientDB = mysql_get_client_version() == MYSQL_VERSION_ID;
#endif
WPFatal(isSupportClientDB, "AzerothCore does not support MySQL versions below 8.0\n\nFound version: {} / {}. Server compiled with: {}.\nSearch the wiki for ACE00043 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00043).", WPFatal(isSupportClientDB, "AzerothCore does not support this database client library.\n\nFound version: {} / {}. Server compiled with: {}.\nSearch the wiki for ACE00043 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00043).",
mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID); mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID);
WPFatal(isSameClientDB, "Used MySQL library version ({} id {}) does not match the version id used to compile AzerothCore (id {}).\nSearch the wiki for ACE00046 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00046).", WPFatal(isSameClientDB, "Used MySQL library version ({} id {}) does not match the version id used to compile AzerothCore (id {}).\nSearch the wiki for ACE00046 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00046).",
mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID); mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID);
@@ -386,32 +395,65 @@ void DatabaseWorkerPool<T>::KeepAlive()
*/ */
bool DatabaseIncompatibleVersion(std::string const mysqlVersion) bool DatabaseIncompatibleVersion(std::string const mysqlVersion)
{ {
// anon func to turn a version string into an array of uint8 auto parseTriplet = [](std::string const& input) -> std::array<unsigned, 3>
// "1.2.3" => [1, 2, 3]
auto parse = [](std::string const& input)
{ {
std::vector<uint8> result; std::array<unsigned, 3> v = { 0, 0, 0 };
std::istringstream parser(input); size_t idx = 0;
result.push_back(parser.get()); std::string num;
for (int i = 1; i < 3; i++) for (char ch : input)
{ {
// Skip period if (ch >= '0' && ch <= '9')
parser.get(); num.push_back(ch);
// Append int from parser to output else if (ch == '.')
result.push_back(parser.get()); {
if (!num.empty() && idx < 3)
{
v[idx++] = static_cast<unsigned>(std::stoul(num));
num.clear();
}
}
else
break;
} }
return result; if (!num.empty() && idx < 3)
v[idx] = static_cast<unsigned>(std::stoul(num));
return v;
}; };
// default to values for MySQL auto compareVersion = [](std::array<unsigned, 3> const& a, std::array<unsigned, 3> const& b) -> int
uint8 offset = 0; {
if (a[0] != b[0])
return (a[0] < b[0]) ? -1 : 1;
if (a[1] != b[1])
return (a[1] < b[1]) ? -1 : 1;
if (a[2] != b[2])
return (a[2] < b[2]) ? -1 : 1;
return 0;
};
std::string ver = mysqlVersion;
std::string minVersion = MIN_MYSQL_SERVER_VERSION; std::string minVersion = MIN_MYSQL_SERVER_VERSION;
auto parsedMySQLVersion = parse(mysqlVersion.substr(offset)); // MariaDB: version string may be:
auto parsedMinVersion = parse(minVersion); // - "5.5.5-10.6.11-MariaDB-1:10.6.11+maria~..." (MySQL wire compat prefix)
// - "10.6.11-MariaDB-1:10.6.11+maria~..." (no 5.5.5- prefix)
// parseTriplet() on the full second form stops at the first '-', yielding 10.6.11.
// Never take "firstDash..secondDash" — that becomes "MariaDB" and compares as 0.0.0.
if (ver.find("MariaDB") != std::string::npos)
{
minVersion = MIN_MARIADB_SERVER_VERSION;
if (ver.compare(0, 6, "5.5.5-") == 0)
{
size_t const afterPrefix = 6;
size_t const nextDash = ver.find('-', afterPrefix);
if (nextDash != std::string::npos)
ver = ver.substr(afterPrefix, nextDash - afterPrefix);
else
ver = ver.substr(afterPrefix);
}
}
return std::lexicographical_compare(parsedMySQLVersion.begin(), parsedMySQLVersion.end(), return compareVersion(parseTriplet(ver), parseTriplet(minVersion)) < 0;
parsedMinVersion.begin(), parsedMinVersion.end());
} }
template <class T> template <class T>
@@ -442,7 +484,7 @@ uint32 DatabaseWorkerPool<T>::OpenConnections(InternalIndex type, uint8 numConne
} }
else if (DatabaseIncompatibleVersion(connection->GetServerInfo())) else if (DatabaseIncompatibleVersion(connection->GetServerInfo()))
{ {
LOG_ERROR("sql.driver", "AzerothCore does not support MySQL versions below 8.0\n\nFound server version: {}. Server compiled with: {}.", LOG_ERROR("sql.driver", "Database server version is too old.\n\nFound server version: {}. Client library compile id: {}.",
connection->GetServerInfo(), MYSQL_VERSION_ID); connection->GetServerInfo(), MYSQL_VERSION_ID);
return 1; return 1;
} }
@@ -32,12 +32,24 @@
*/ */
#define MIN_MYSQL_CLIENT_VERSION 80000u #define MIN_MYSQL_CLIENT_VERSION 80000u
/**
* @def MIN_MARIADB_CLIENT_VERSION_ID
* Minimum MariaDB connector MARIADB_VERSION_ID (10.4.0 => 100400)
*/
#define MIN_MARIADB_CLIENT_VERSION_ID 100400u
/** /**
* @def MIN_MYSQL_SERVER_VERSION * @def MIN_MYSQL_SERVER_VERSION
* The minimum MySQL Server Version * The minimum MySQL Server Version
*/ */
#define MIN_MYSQL_SERVER_VERSION "8.0.0" #define MIN_MYSQL_SERVER_VERSION "8.0.0"
/**
* @def MIN_MARIADB_SERVER_VERSION
* Minimum MariaDB server (version triplet after the 5.5.5- compatibility prefix)
*/
#define MIN_MARIADB_SERVER_VERSION "10.4.0"
template <typename T> template <typename T>
class ProducerConsumerQueue; class ProducerConsumerQueue;
@@ -129,13 +129,17 @@ uint32 MySQLConnection::Open()
if (m_connectionInfo.ssl != "") if (m_connectionInfo.ssl != "")
{ {
#ifdef MARIADB_VERSION_ID
my_bool const ssl_enforce = (m_connectionInfo.ssl == "ssl") ? 1 : 0;
if (ssl_enforce)
mysql_options(mysqlInit, MYSQL_OPT_SSL_ENFORCE, &ssl_enforce);
#else
mysql_ssl_mode opt_use_ssl = SSL_MODE_DISABLED; mysql_ssl_mode opt_use_ssl = SSL_MODE_DISABLED;
if (m_connectionInfo.ssl == "ssl") if (m_connectionInfo.ssl == "ssl")
{
opt_use_ssl = SSL_MODE_REQUIRED; opt_use_ssl = SSL_MODE_REQUIRED;
}
mysql_options(mysqlInit, MYSQL_OPT_SSL_MODE, (char const*)&opt_use_ssl); mysql_options(mysqlInit, MYSQL_OPT_SSL_MODE, (char const*)&opt_use_ssl);
#endif
} }
m_Mysql = reinterpret_cast<MySQLHandle*>(mysql_real_connect(mysqlInit, m_connectionInfo.host.c_str(), m_connectionInfo.user.c_str(), m_Mysql = reinterpret_cast<MySQLHandle*>(mysql_real_connect(mysqlInit, m_connectionInfo.host.c_str(), m_connectionInfo.user.c_str(),
@@ -216,7 +220,7 @@ bool MySQLConnection::Execute(PreparedStatementBase* stmt)
uint32 _s = getMSTime(); uint32 _s = getMSTime();
#if MYSQL_VERSION_ID >= 80300 #if MYSQL_VERSION_ID >= 80300 && !defined(MARIADB_VERSION_ID)
if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr)) if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr))
#else #else
if (mysql_stmt_bind_param(msql_STMT, msql_BIND)) if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
@@ -268,7 +272,7 @@ bool MySQLConnection::_Query(PreparedStatementBase* stmt, MySQLPreparedStatement
uint32 _s = getMSTime(); uint32 _s = getMSTime();
#if MYSQL_VERSION_ID >= 80300 #if MYSQL_VERSION_ID >= 80300 && !defined(MARIADB_VERSION_ID)
if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr)) if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr))
#else #else
if (mysql_stmt_bind_param(msql_STMT, msql_BIND)) if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
@@ -669,7 +669,12 @@ bool AuctionHouseUsablePlayerInfo::PlayerCanUseItem(ItemTemplate const* proto) c
return false; return false;
} }
if ((proto->AllowableClass & classMask) == 0 || (proto->AllowableRace & raceMask) == 0) // mod-paragon: class 12 (Paragon) ignores AllowableClass for AH "Usable"
// filter. classMask here is the searching player's mask; PARAGON_BIT 0x800
// = (1 << (12 - 1)). Race restriction still applies.
bool const searcherIsParagon = (classMask & 0x800u) != 0;
if ((!searcherIsParagon && (proto->AllowableClass & classMask) == 0)
|| (proto->AllowableRace & raceMask) == 0)
return false; return false;
if (proto->RequiredSkill != 0) if (proto->RequiredSkill != 0)
@@ -208,6 +208,46 @@ namespace Trainer
bool Trainer::IsTrainerValidForPlayer(Player const* player) const bool Trainer::IsTrainerValidForPlayer(Player const* player) const
{ {
// Paragon (class 12) learns class abilities exclusively through the
// Character Advancement panel (mod-paragon). Generic class trainers
// refuse interaction. Pet trainers, mount/profession trainers, and
// specialized portal/teleport trainers (mage portal NPCs) stay valid:
// - Pet trainers: pet-skill purchases for hunter pets aren't covered
// by the panel and should remain trainer-driven.
// - Portal/teleport trainers: identified at runtime as a Class-type
// trainer whose spells are ALL TELEPORT_UNITS or TRANS_DOOR
// effects. The big general mage trainer fails this check (it
// teaches Fireball, Frostbolt, etc.) and is correctly blocked.
if (player && player->getClass() == CLASS_PARAGON
&& GetTrainerType() == Type::Class
&& !_spells.empty())
{
bool onlyPortalsAndTeleports = true;
for (Spell const& s : _spells)
{
SpellInfo const* info = sSpellMgr->GetSpellInfo(s.SpellId);
if (!info)
continue;
bool isPortalOrTeleport = false;
for (SpellEffectInfo const& eff : info->GetEffects())
{
if (eff.Effect == SPELL_EFFECT_TELEPORT_UNITS
|| eff.Effect == SPELL_EFFECT_TRANS_DOOR)
{
isPortalOrTeleport = true;
break;
}
}
if (!isPortalOrTeleport)
{
onlyPortalsAndTeleports = false;
break;
}
}
if (!onlyPortalsAndTeleports)
return false;
}
if (!GetTrainerRequirement()) if (!GetTrainerRequirement())
return true; return true;
+16 -3
View File
@@ -10687,7 +10687,12 @@ bool Player::BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uin
return false; return false;
} }
if (!(pProto->AllowableClass & getClassMask()) && pProto->Bonding == BIND_WHEN_PICKED_UP && !IsGameMaster()) // mod-paragon: class 12 ignores BoP buy-side AllowableClass gate, so
// class-restricted vendor items (e.g. class glyphs) can be purchased.
if (getClass() != CLASS_PARAGON
&& !(pProto->AllowableClass & getClassMask())
&& pProto->Bonding == BIND_WHEN_PICKED_UP
&& !IsGameMaster())
{ {
SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, item, 0); SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, item, 0);
return false; return false;
@@ -14052,7 +14057,10 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank, bool command /*= fa
return; return;
// xinef: prevent learn talent for different class (cheating) // xinef: prevent learn talent for different class (cheating)
if ((getClassMask() & talentTabInfo->ClassMask) == 0) // mod-paragon: Paragon (class 12) can spec into any class's talent tree
// via the Character Advancement panel; bypass the class-mask check.
if (getClass() != CLASS_PARAGON
&& (getClassMask() & talentTabInfo->ClassMask) == 0)
return; return;
// xinef: find current talent rank // xinef: find current talent rank
@@ -14081,7 +14089,12 @@ void Player::LearnTalent(uint32 talentId, uint32 talentRank, bool command /*= fa
} }
// xinef: check if talent deponds on another talent // xinef: check if talent deponds on another talent
if (talentInfo->DependsOn > 0) // mod-paragon: Character Advancement gates talents by AE/TE essence cost,
// not by the column-arrow prereq from Blizzard's spec UI. For class 12
// (Paragon) we skip the DependsOn check so e.g. Deep Wounds, Bloody
// Vengeance and Expose Weakness can be picked without first speccing into
// their unrelated prereq sibling.
if (talentInfo->DependsOn > 0 && getClass() != CLASS_PARAGON)
if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn)) if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn))
{ {
bool hasEnoughRank = false; bool hasEnoughRank = false;
@@ -2364,7 +2364,16 @@ InventoryResult Player::CanUseItem(ItemTemplate const* proto) const
return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
} }
if ((proto->AllowableClass & getClassMask()) == 0 || (proto->AllowableRace & getRaceMask()) == 0) // mod-paragon: class 12 (Paragon) ignores AllowableClass entirely, so any
// class-restricted item (including class glyphs) can be equipped/used.
// Race restriction still applies; proficiency/level/skill checks below
// still gate it sensibly via the standard skill cascade.
if (getClass() != CLASS_PARAGON
&& (proto->AllowableClass & getClassMask()) == 0)
{
return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
}
if ((proto->AllowableRace & getRaceMask()) == 0)
{ {
return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
} }
@@ -2430,7 +2439,11 @@ InventoryResult Player::CanRollForItemInLFG(ItemTemplate const* proto, WorldObje
SKILL_FISHING SKILL_FISHING
}; //Copy from function Item::GetSkill() }; //Copy from function Item::GetSkill()
if ((proto->AllowableClass & getClassMask()) == 0 || (proto->AllowableRace & getRaceMask()) == 0) // mod-paragon: class 12 ignores AllowableClass for LFG roll eligibility.
if (getClass() != CLASS_PARAGON
&& (proto->AllowableClass & getClassMask()) == 0)
return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
if ((proto->AllowableRace & getRaceMask()) == 0)
return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
if (proto->RequiredSpell != 0 && !HasSpell(proto->RequiredSpell)) if (proto->RequiredSpell != 0 && !HasSpell(proto->RequiredSpell))
@@ -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;
@@ -481,6 +488,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;
+29
View File
@@ -9046,6 +9046,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)
@@ -9803,6 +9818,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)
+6 -1
View File
@@ -908,7 +908,12 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid, uint32 vendorEntry)
{ {
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(item->item)) if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(item->item))
{ {
if (!(itemTemplate->AllowableClass & _player->getClassMask()) && itemTemplate->Bonding == BIND_WHEN_PICKED_UP && !_player->IsGameMaster()) // mod-paragon: class 12 sees every BoP class-restricted item
// in vendor lists (class glyphs, class tier sets, ...).
if (_player->getClass() != CLASS_PARAGON
&& !(itemTemplate->AllowableClass & _player->getClassMask())
&& itemTemplate->Bonding == BIND_WHEN_PICKED_UP
&& !_player->IsGameMaster())
{ {
continue; continue;
} }
+10 -2
View File
@@ -7296,8 +7296,16 @@ SpellCastResult Spell::CheckItems(uint32* param1, uint32* param2)
{ {
// Xinef: this is not true in my opinion, in eg bladestorm will not be canceled after disarm // Xinef: this is not true in my opinion, in eg bladestorm will not be canceled after disarm
//if (!HasTriggeredCastFlag(TRIGGERED_IGNORE_EQUIPPED_ITEM_REQUIREMENT)) //if (!HasTriggeredCastFlag(TRIGGERED_IGNORE_EQUIPPED_ITEM_REQUIREMENT))
if (m_caster->IsPlayer() && !m_caster->ToPlayer()->HasItemFitToSpellRequirements(m_spellInfo)) if (m_caster->IsPlayer())
return SPELL_FAILED_EQUIPPED_ITEM_CLASS; {
// Cast-from-glyph: many glyph on-use spells set EquippedItemClass to ITEM_CLASS_GLYPH.
// HasItemFitToSpellRequirements only implements weapon/armor, so it would always fail here
// even though the glyph item in the bag is the valid spell source.
bool const castFromGlyphScroll = m_CastItem && m_CastItem->GetTemplate() &&
m_CastItem->GetTemplate()->Class == ITEM_CLASS_GLYPH;
if (!castFromGlyphScroll && !m_caster->ToPlayer()->HasItemFitToSpellRequirements(m_spellInfo))
return SPELL_FAILED_EQUIPPED_ITEM_CLASS;
}
} }
// do not take reagents for these item casts // do not take reagents for these item casts
@@ -5368,6 +5368,56 @@ void SpellMgr::LoadSpellInfoCorrections()
LockEntry* key = const_cast<LockEntry*>(sLockStore.LookupEntry(36)); // 3366 Opening, allows to open without proper key LockEntry* key = const_cast<LockEntry*>(sLockStore.LookupEntry(36)); // 3366 Opening, allows to open without proper key
key->Type[2] = LOCK_KEY_NONE; key->Type[2] = LOCK_KEY_NONE;
// Fractured / Paragon: DK weapon-line "passives" Forceful Deflection and
// Runic Focus ship in 3.3.5a Spell.dbc without SPELL_ATTR0_PASSIVE set.
// SpellInfo::IsPassive() is therefore false, and mod-paragon's panel-learn
// diff treats them as castable actives and revokes them — while true
// actives (Blood Presence, Death Coil, Death Grip, ...) must stay
// stripped. Mark these two passive in-memory so the panel policy matches
// the spellbook UX for every class (stock DK benefits too).
ApplySpellFix({ 49410, 61455 }, [](SpellInfo* spellInfo)
{
spellInfo->Attributes |= SPELL_ATTR0_PASSIVE;
});
// Fractured: strip reagent requirements from every player-class spell at
// load time. Filtered by SpellFamilyName != 0 so that profession spells
// (cooking, alchemy, enchanting, blacksmithing, jewelcrafting, leatherworking,
// tailoring, engineering, inscription, mining, herbalism, skinning, fishing,
// first aid — all SpellFamilyName == SPELLFAMILY_GENERIC == 0) keep their
// mats and only the class abilities that asked for ankhs / candles / soul
// shards / verdant spheres / etc. cast freely. Done here in core spell
// data rather than as a runtime bypass in Spell::CheckItems / TakeReagents
// so the change is data-driven (the in-memory SpellInfo simply has no
// reagents to require). The client-side preflight is mirrored by the
// matching Spell.dbc patch shipped via patch-enUS-4.MPQ
// (fractured-tooling/_patch_spell_dbc_reagents.py).
{
uint32 fixedClassSpells = 0;
for (uint32 spellId = 1; spellId < sSpellMgr->GetSpellInfoStoreSize(); ++spellId)
{
SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId);
if (!info || info->SpellFamilyName == 0)
continue;
bool hadAny = false;
for (uint32 i = 0; i < MAX_SPELL_REAGENTS; ++i)
if (info->Reagent[i] != 0 || info->ReagentCount[i] != 0)
{ hadAny = true; break; }
if (!hadAny)
continue;
SpellInfo* mut = const_cast<SpellInfo*>(info);
for (uint32 i = 0; i < MAX_SPELL_REAGENTS; ++i)
{
mut->Reagent[i] = 0;
mut->ReagentCount[i] = 0;
}
++fixedClassSpells;
}
LOG_INFO("server.loading", ">> Fractured: cleared reagents on {} class spells", fixedClassSpells);
}
LOG_INFO("server.loading", ">> Loading spell dbc data corrections in {} ms", GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", ">> Loading spell dbc data corrections in {} ms", GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " "); LOG_INFO("server.loading", " ");
} }
@@ -0,0 +1,26 @@
/*
* 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/>.
*/
// This is where scripts' loading functions should be declared:
// void MyExampleScript()
// The name of this function should match:
// void Add${NameOfDirectory}Scripts()
void AddCustomScripts()
{
// MyExampleScript()
}
+8
View File
@@ -215,6 +215,14 @@ Updates.AllowRehash = 1
# -1 - (Enabled - unlimited) # -1 - (Enabled - unlimited)
Updates.CleanDeadRefMaxCount = 3 Updates.CleanDeadRefMaxCount = 3
#
# Updates.ExceptionShutdownDelay
# Description: Time (in milliseconds) to wait before shutting down after a fatal exception (e.g. failed SQL update).
# Default: 10000 - 10 seconds
# 0 - Disabled (immediate shutdown)
Updates.ExceptionShutdownDelay = 10000
################################################################################################### ###################################################################################################
################################################################################################### ###################################################################################################
@@ -0,0 +1,5 @@
node_modules/
dist/
launcher.json
.DS_Store
Thumbs.db
+169
View File
@@ -0,0 +1,169 @@
# Fractured Launcher (Electron)
**Windows** and **Linux (AppImage)** launcher with **no extra console window**, **native Browse folder** dialog, **Gitea or GitHub** release assets + GitHub repo file sync, **realmlist**, optional **auth**, **Play**, and **auto-update** (via `electron-updater`). This is the **only** supported client launcher in this repo.
## Requirements
- [Node.js](https://nodejs.org/) 20+ (includes npm)
## Run from source
```bash
cd tools/fractured-launcher-electron
npm install
npm start
```
On first run, `launcher.json` is created next to the app (dev: in this folder).
### Where patches download from
- **Recommended (self-hosted Gitea):** set **`gitea.base_url`**, **`gitea.owner`**, **`gitea.repo`** in `launcher.json` (see **`default-launcher.json`**). Players need **`GITEA_TOKEN`** (or the env name in **`gitea.token_env`**) if the Gitea repo is **private** — same trade-off as any private host (per-player token, SSO proxy, or a read-only deploy token you accept distributing).
- **Fallback:** if **`gitea.base_url`** is empty, **`from_release`** uses the **GitHub** Releases API against **`github.owner` / `github.repo`** (defaults to this **`Fractured`** repo for non-release paths), with optional **`GITHUB_TOKEN`** for private assets.
## Build Windows installers
```bash
npm install
npm run pack:win
```
Produces under **`dist/`**:
| Artifact | Purpose |
|----------|---------|
| `Fractured-Launcher-${version}-Setup.exe` (NSIS) | **Recommended for players** — supports seamless **auto-update** and restart. |
| `Fractured-Launcher-${version}-Windows-Portable.exe` | No installer; players replace the file manually. Auto-update is **less reliable** than NSIS. |
## Build Linux AppImage
```bash
cd tools/fractured-launcher-electron
npm install
npm run pack:linux
```
Produces **`dist/Fractured-Launcher-${version}-Linux-x86_64.AppImage`**. Same **`lib/baked-gitea-channel.js`** and **`default-launcher.json`** as Windows; run on **Linux** (or use **Fractured launcher CI** / **Sync release to Gitea**, which upload this file to Gitea with the Windows installers).
**Quick local test (avoids tag snapshot / CI):**
- **Linux:** from repo root, **`bash tools/fractured-launcher-electron/scripts/manual-pack-linux.sh`** → **`dist/*.AppImage`**.
- **Windows:** on a Windows machine, **`cd tools/fractured-launcher-electron`**, **`npm ci`**, **`npm run pack:win`** → **`dist/*.exe`**.
### Hardcoded Gitea channel (non-token)
**`lib/baked-gitea-channel.js`** exports **`base_url`**, **`owner`**, **`repo`**, **`release_tag`**. Set those strings once in the repo (same values you use for CI upload — not secret). At runtime **`config-store`** merges them into **`gitea.*`** so **`launcher.json`** does not need those fields; **`GITEA_TOKEN`** (or **`gitea.token_env`**) is still only for **private** Gitea. Leave a field **`''`** in the baked file to fall back to **`default-launcher.json`** / user **`launcher.json`** for that key.
**`npm run pack:win`** is plain **electron-builder** — no inject step, no extra JSON beside the app.
## Auto-update behaviour
- **Packaged** builds only (`npm run pack:win` output). In `npm start` dev mode, update checks are skipped (button still explains that).
- **No implicit GitHub feed:** the app does **not** guess `package.json``repository` anymore. Without configuration you get a clear “skipped” message instead of a **404** on a private repo.
- **Configured feeds** (first match wins): **`update_feed_url` / `LAUNCHER_UPDATE_URL`** (generic `latest.yml`); or **`gitea`** block filled in + **`GITEA_TOKEN`** when the instance is private (resolves `…/releases/download/{tag}/`); or **`GITHUB_TOKEN`** + **`github.owner` / `github.repo`** for **private** GitHub releases only.
- **~5 seconds** after launch, then **every 6 hours**, the app checks when a feed is configured.
- When a download finishes, a dialog offers **Restart now** (calls `quitAndInstall`) or **Later**.
- **Manual check:** button **Check launcher updates** in the UI.
### Where launcher updates are hosted
**`npm run publish:win`** runs **`electron-builder` with `--publish never`** — artifacts stay in **`dist/`**; CI uploads them to Gitea when you **publish a GitHub release**. For ad-hoc uploads, use **`scripts/upload-release-to-gitea.sh`**. For launcher auto-update, prefer:
- Set **`update_feed_url`** (or **`LAUNCHER_UPDATE_URL`**) to a **generic** HTTPS base URL where **`latest.yml`** and the installer files are hosted (often the same Gitea release attachment URLs pattern your reverse proxy exposes), **or**
- Keep publishing to a GitHub release only for **`latest.yml`** + installers if you accept that small metadata/binary channel there.
**Private GitHub** updater: set **`GH_TOKEN`** / **`GITHUB_TOKEN`** / **`github.token_env`** as documented in `lib/auto-update.js` behaviour.
**Generic feed:** optional Bearer token via the same token envs if your static host checks `Authorization`.
### Publishing a new launcher version
1. Bump **`version`** in `package.json` on `main` (or your release branch) and merge.
2. Create a **GitHub release** (tag + attach patches / `Wow.exe` if needed) and click **Publish****Sync release to Gitea** builds **Windows + Linux** launcher artifacts and mirrors everything to Gitea.
3. Local check: **`npm run pack:win`** (on Windows) or **`npm run pack:linux`** / **`scripts/manual-pack-linux.sh`**, then **`scripts/upload-release-to-gitea.sh`** with the same **`GITEA_*`** env vars as CI if you need a manual upload.
## Sync to Gitea (patches + launcher binaries)
CI workflow **Sync release to Gitea** (`.github/workflows/gitea-release-sync.yml`) runs on **every published GitHub release** on this repo:
1. Triggers on **release published** on **`Dawnforger/Fractured`** (or **workflow_dispatch** with a tag).
2. Builds **Windows** (NSIS + portable) and **Linux** (AppImage) in parallel, each using **`tools/fractured-launcher-electron` from the default branch** (overlaid onto the tag checkout), so older release tags never ship a launcher missing new **`lib/*.js`** files.
3. Downloads **all assets** attached to that **GitHub** release (MPQs, patched `Wow.exe`, etc.).
4. Merges with the built launcher artifacts and uploads everything to a **Gitea release** with the **same tag** (existing attachments on that Gitea release are replaced).
**GitHub Actions secrets** (repository → Settings → Secrets and variables → Actions):
| Secret | Example |
|--------|---------|
| **`GITEA_BASE_URL`** | `https://git.yourdomain.com` (no trailing slash) |
| **`GITEA_TOKEN`** | Gitea personal access token with permission to manage releases and attachments on the target repo |
| **`GITEA_OWNER`** | Organization or username on Gitea |
| **`GITEA_REPO`** | Repository name — must already have **at least one commit** (Gitea returns HTTP 422 “repo is empty” for zero-commit repos; push e.g. a README on **`main`** or set **`GITEA_TARGET_REF`** to your default branch) |
**Optional variable** (Settings → Variables): **`GITEA_TARGET_REF`** — default branch/commitish used **only when the workflow must create a new Gitea release** and Gitea needs `target_commitish` (defaults to **`main`** in the upload script if unset).
**Player `launcher.json`:** packaged builds should already include **`gitea.base_url` / `owner` / `repo`** from the bake step above. Players only need to set **`GITEA_TOKEN`** (or your **`token_env`**) if the Gitea repo is **private**. To point at another instance, edit **`gitea`** in **`launcher.json`**:
```json
"gitea": {
"base_url": "https://git.yourdomain.com",
"owner": "myorg",
"repo": "fractured-patches",
"release_tag": "latest",
"token_env": "GITEA_TOKEN"
}
```
**Manual upload:** `bash scripts/upload-release-to-gitea.sh /path/to/files v1.0.0` with the same env vars as CI.
### Sync did not run / Gitea unchanged — checklist
1. **Git tag ≠ GitHub Release** — Only **Releases** (published on the GitHub **Releases** page) trigger this workflow. If your teammate only **`git push --tags`**, create a **Release** from that tag and click **Publish** (or run **Actions → Sync release to Gitea → Run workflow** and enter the tag).
2. **Draft release** — Must click **Publish release**; drafts do not mirror.
3. **Workflow on default branch** — GitHub runs `release` workflows from the **default branch** (e.g. `main`). Ensure `.github/workflows/gitea-release-sync.yml` is merged there.
4. **Repo name guard** — Jobs use `if: github.repository == 'Dawnforger/Fractured'`. Forks or renames must change that line or runs are skipped.
5. **Secrets****`GITEA_BASE_URL`**, **`GITEA_TOKEN`**, **`GITEA_OWNER`**, **`GITEA_REPO`** must be set under **Settings → Secrets and variables → Actions**. A failed “Upload to Gitea” step usually prints which is missing.
6. **Actions tab** — Open the latest **Sync release to Gitea** run; a red **build-electron** (old tag without `package-lock.json`, etc.) or **Upload to Gitea** step shows the real error.
7. **HTTP 422 `repo is empty`** — The Gitea repo has **no commits** yet. Push any initial commit (e.g. **Add README** in the Gitea web UI, or `git push` to **`main`**). Optionally set **`GITEA_TARGET_REF`** to match your real default branch if it is not **`main`**. From this repo you can run **`scripts/bootstrap-gitea-repo.sh`** (see script header for `GITEA_*` env or pass the HTTPS/SSH clone URL as the first argument).
### Private Gitea token for players
Do **not** embed a shared admin PAT in a shipped `launcher.json`. Prefer read-only tokens scoped to one repo, short-lived tokens, or a small auth service that redirects to signed URLs.
**Release asset names** must match **`files[].source`** when **`from_release`**: true. Use **`release_tag`**: `"latest"` or a pinned tag matching both GitHub and Gitea.
## Patch versions (same filenames, different bytes)
The launcher does **not** read Git commits. For **turn-key** updates when asset names stay fixed (`patch-Z.MPQ`, `Wow-patched.exe`, …):
1. Ship **`patch-manifest.json`** next to those files on **every** release (Gitea/GitHub attachment). It lists a **`version`** label (any string you bump per release, e.g. `v0.9.0-client`) and a **`sha256`** per **`files[].source`** name.
2. With **`patch_manifest.enabled`**: true (default in **`default-launcher.json`**), **Download updates** first fetches the manifest from the same release channel. If the files already on disk match those checksums, the player sees **“already match build … (nothing to download)”** — no redundant downloads.
3. After a real download, the launcher **re-hashes** installed files and compares to the manifest; mismatch → clear error. It also writes **`.fractured/patch-state.json`** under the WoW folder so the UI can show **“Installed client files: …”**.
If **`patch-manifest.json`** is missing on a release, the launcher falls back to **always downloading** all configured files (same as before).
**Generate the manifest** when you cut a release (paths are your local patch binaries):
```bash
cd /path/to/staging
node tools/fractured-launcher-electron/scripts/generate-patch-manifest.js v0.9.0-client patch-Z.MPQ Wow-patched.exe > patch-manifest.json
```
Attach **`patch-manifest.json`** together with the MPQ/exe to the GitHub release (CI sync copies it to Gitea with everything else).
## CI
Workflow **Fractured launcher CI** (`.github/workflows/fractured-launcher-ci.yml`) runs on pushes/PRs under `tools/fractured-launcher-electron/`: **Windows** (`npm run pack:win`) and **Linux** (`npm run pack:linux`) jobs, each **`electron-builder … --publish never`**. **Actions → Fractured launcher CI → Run workflow** runs it manually.
**Sync release to Gitea** (`.github/workflows/gitea-release-sync.yml`) uses the same pack commands. If you see `GH_TOKEN` / `GitHubPublisher` errors in logs, the job is almost certainly an old **Re-run failed jobs** — open **Actions → Sync release to Gitea → Run workflow**, enter the tag, and start a **new** run instead.
## Config
Schema is defined by **`default-launcher.json`** (shipped in the app; first run copies to `launcher.json` beside the executable):
- **`game_dir`**: WoW 3.3.5a root (contains `Wow.exe`).
- **`update_feed_url`**: optional generic HTTPS base for launcher auto-update.
- **`launcher_updates_from_github`**: default **`false`**. Only when **`true`** will a **`GITHUB_TOKEN`** (or **`github.token_env`**) enable **electron-updater**s GitHub provider against **`github.owner` / `github.repo`**. Leave **`false`** when launcher binaries and **`latest.yml`** live on **Gitea** (use **`gitea`** + token instead) so a stray GitHub token does not produce “No published versions on GitHub”.
- **`gitea`**: **`base_url`**, **`owner`**, **`repo`**, **`release_tag`**, **`token_env`** — when **`base_url`** is set (and owner/repo set), **`from_release`** downloads and (with token if needed) the **generic** updater feed use **Gitea**. **Required** for players if your CI mirrors patches/launchers to Gitea only.
- **`github`**: used for **non-release** repo paths (`from_release`: false) and for **GitHub** **`from_release`** when **`gitea.base_url`** is empty.
- **`patch_manifest`**: **`enabled`**, **`source`** (default `patch-manifest.json`), **`from_release`** — checksum-based skip + verify (see above).
- **`files`**, **`realmlist`**, **`auth`**, **`launch`**.
@@ -0,0 +1,55 @@
{
"game_dir": "",
"update_feed_url": "",
"launcher_updates_from_github": false,
"gitea": {
"base_url": "",
"owner": "",
"repo": "",
"release_tag": "latest",
"token_env": "GITEA_TOKEN"
},
"github": {
"owner": "Dawnforger",
"repo": "Fractured",
"ref": "main",
"release_tag": "latest",
"token_env": "GITHUB_TOKEN"
},
"patch_manifest": {
"enabled": true,
"source": "patch-manifest.json",
"from_release": true
},
"files": [
{
"source": "patch-Z.MPQ",
"dest": "Data/patch-Z.MPQ",
"backup": true,
"from_release": true
},
{
"source": "Wow-patched.exe",
"dest": "Wow.exe",
"backup": true,
"from_release": true
}
],
"realmlist": {
"enabled": true,
"line": "set realmlist fracturedwow.ddns.net:47497",
"paths": ["Data/enUS/realmlist.wtf", "Data/enGB/realmlist.wtf"]
},
"auth": {
"enabled": false,
"url": "https://auth.your-realm.example/api/launcher/login",
"method": "POST",
"username_field": "username",
"password_field": "password"
},
"launch": {
"exe": "Wow.exe",
"args": [],
"linux_wrapper": ["wine"]
}
}
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" />
<title>Fractured Launcher</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<header>
<h1>Fractured Launcher</h1>
<p class="sub">Point at your 3.3.5a client, download patches, then play.</p>
</header>
<section class="card">
<label class="lbl">World of Warcraft folder (contains <span id="wowExeName">Wow.exe</span>)</label>
<div class="row">
<input type="text" id="gameDir" placeholder="Browse… or paste the folder that contains Wow.exe" />
<button type="button" id="btnBrowse">Browse…</button>
<button type="button" id="btnSaveFolder" class="primary">Save folder</button>
</div>
</section>
<section class="card hidden" id="authCard">
<label class="lbl">Account</label>
<div class="row stack">
<input type="text" id="username" autocomplete="username" placeholder="Username" />
<input type="password" id="password" autocomplete="current-password" placeholder="Password" />
<button type="button" id="btnAuth" class="primary">Sign in</button>
</div>
</section>
<section class="card row-actions">
<button type="button" id="btnCheckLauncher" class="ghost">Check launcher updates</button>
</section>
<section class="card">
<button type="button" id="btnSync" class="primary wide" disabled>Download updates</button>
<button type="button" id="btnPlay" class="success wide hidden" disabled>Play</button>
</section>
<pre id="log" class="log" aria-live="polite"></pre>
<script src="renderer.js"></script>
</body>
</html>
@@ -0,0 +1,148 @@
'use strict';
const { dialog } = require('electron');
const { autoUpdater } = require('electron-updater');
const { useGiteaReleases, getGiteaUpdaterFeedBase } = require('./gitea-release');
/**
* @param {import('electron').App} app
* @param {() => import('electron').BrowserWindow | null} getMainWindow
* @param {{
* updateFeedUrl?: string,
* githubOwner?: string,
* githubRepo?: string,
* githubToken?: string,
* giteaToken?: string,
* allowGithubLauncherUpdates?: boolean,
* config?: object,
* }} opts
*/
async function setupAutoUpdater(app, getMainWindow, opts = {}) {
if (!app.isPackaged) {
return {
checkNow: async () => ({ skipped: true, reason: 'development build' }),
};
}
const ghToken = String(opts.githubToken || '').trim();
const giteaTok = String(opts.giteaToken || '').trim();
const envGeneric = String(process.env.LAUNCHER_UPDATE_URL || '').trim();
const configGeneric = String(opts.updateFeedUrl || '').trim();
let genericUrl = envGeneric || configGeneric;
let genericAuthHeader = '';
if (!genericUrl && opts.config && useGiteaReleases(opts.config)) {
const gfb = await getGiteaUpdaterFeedBase(opts.config);
if (gfb && gfb.url) {
genericUrl = gfb.url;
const t = String(gfb.token || giteaTok || '').trim();
if (t) genericAuthHeader = `token ${t}`;
}
} else if (genericUrl) {
if (giteaTok) genericAuthHeader = `token ${giteaTok}`;
else if (ghToken) genericAuthHeader = `Bearer ${ghToken}`;
}
const owner = String(opts.githubOwner || '').trim();
const repo = String(opts.githubRepo || '').trim();
let feedConfigured = false;
if (genericUrl) {
const base = genericUrl.replace(/\/?$/, '/');
autoUpdater.setFeedURL({
provider: 'generic',
url: base,
});
if (genericAuthHeader) {
autoUpdater.requestHeaders = {
...autoUpdater.requestHeaders,
Authorization: genericAuthHeader,
};
}
feedConfigured = true;
} else if (opts.allowGithubLauncherUpdates && ghToken && owner && repo) {
autoUpdater.setFeedURL({
provider: 'github',
owner,
repo,
private: true,
token: ghToken,
});
feedConfigured = true;
}
if (!feedConfigured) {
const reason =
'No update channel configured. Set launcher.json → update_feed_url (HTTPS folder with latest.yml), ' +
'or fill gitea.base_url/owner/repo (+ GITEA_TOKEN for private), ' +
'or set launcher_updates_from_github to true with GITHUB_TOKEN for private GitHub release feeds.';
return {
checkNow: async () => ({ skipped: true, reason }),
};
}
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
const send = (msg) => {
const w = getMainWindow();
if (w && !w.isDestroyed()) {
w.webContents.send('launcher:progress', msg);
}
};
autoUpdater.on('checking-for-update', () => send('Checking for launcher updates…'));
autoUpdater.on('update-available', (info) => {
send(`Launcher update available: ${info.version}`);
});
autoUpdater.on('update-not-available', () => {});
autoUpdater.on('error', (err) => {
const m = (err && (err.message || String(err))) || '';
if (/404|releases\.atom|HttpError:\s*404/i.test(m)) {
send(
'Launcher update: 404 (no latest.yml or wrong URL). For Gitea use gitea.* + token, or set update_feed_url. ' +
'For private GitHub set GITHUB_TOKEN.'
);
return;
}
if (m && !/net::ERR|ENOTFOUND|ETIMEDOUT/i.test(m)) {
send(`Launcher update: ${m.slice(0, 400)}`);
}
});
autoUpdater.on('download-progress', (p) => {
const pct = Math.round(p.percent || 0);
send(`Launcher update download: ${pct}%`);
});
autoUpdater.on('update-downloaded', async (info) => {
const win = getMainWindow();
const r = await dialog.showMessageBox(win || undefined, {
type: 'info',
title: 'Launcher update',
message: `Version ${info.version} is ready to install.`,
detail: 'Restart the launcher now to finish. You can finish patching WoW after restart.',
buttons: ['Restart now', 'Later'],
defaultId: 0,
cancelId: 1,
noLink: true,
});
if (r.response === 0) {
autoUpdater.quitAndInstall(false, true);
}
});
const checkNow = async () => {
const r = await autoUpdater.checkForUpdates();
return { ok: true, updateInfo: r && r.updateInfo };
};
const tick = () => {
checkNow().catch(() => {});
};
setTimeout(tick, 5000);
setInterval(tick, 6 * 60 * 60 * 1000);
return { checkNow };
}
module.exports = { setupAutoUpdater };
@@ -0,0 +1,14 @@
'use strict';
/**
* Production Gitea mirror (non-secret). Edit here and ship no inject script,
* no fractured-release-channel.json, no CI env needed for these fields.
* Token stays in env: GITEA_TOKEN or launcher.json gitea.token_env.
*/
module.exports = {
// Scheme optional — gitea-release normalizes to https:// if missing.
base_url: 'https://brassnet.ddns.net:33983',
owner: 'Dawnsorrow',
repo: 'Fractured-Distro',
release_tag: 'latest',
};
@@ -0,0 +1,87 @@
'use strict';
const path = require('path');
const fs = require('fs').promises;
function mergeConfig(defaults, user) {
return {
...defaults,
...user,
update_feed_url:
user.update_feed_url != null && user.update_feed_url !== ''
? user.update_feed_url
: defaults.update_feed_url,
launcher_updates_from_github:
user.launcher_updates_from_github != null
? user.launcher_updates_from_github
: defaults.launcher_updates_from_github,
github: { ...defaults.github, ...(user.github || {}) },
gitea: { ...defaults.gitea, ...(user.gitea || {}) },
patch_manifest: { ...defaults.patch_manifest, ...(user.patch_manifest || {}) },
launch: { ...defaults.launch, ...(user.launch || {}) },
auth: user.auth != null ? { ...defaults.auth, ...user.auth } : defaults.auth,
realmlist: user.realmlist != null ? { ...defaults.realmlist, ...user.realmlist } : defaults.realmlist,
files: Array.isArray(user.files) && user.files.length ? user.files : defaults.files,
};
}
/** Hardcoded Gitea host/repo (see lib/baked-gitea-channel.js). Non-empty baked values win. */
function applyBakedGitea(cfg) {
let baked;
try {
baked = require('./baked-gitea-channel');
} catch {
return cfg;
}
if (!baked || typeof baked !== 'object') return cfg;
cfg.gitea = { ...(cfg.gitea || {}) };
for (const k of ['base_url', 'owner', 'repo', 'release_tag']) {
const v = baked[k];
if (v != null && String(v).trim() !== '') cfg.gitea[k] = String(v).trim();
}
return cfg;
}
function getConfigPath(app) {
if (process.env.FRACTURED_LAUNCHER_CONFIG) return process.env.FRACTURED_LAUNCHER_CONFIG;
if (app && app.isPackaged) {
return path.join(path.dirname(process.execPath), 'launcher.json');
}
return path.join(__dirname, '..', 'launcher.json');
}
async function loadConfig(app) {
const p = getConfigPath(app);
const defPath = path.join(__dirname, '..', 'default-launcher.json');
const defaults = JSON.parse(await fs.readFile(defPath, 'utf8'));
try {
const user = JSON.parse(await fs.readFile(p, 'utf8'));
return { configPath: p, config: applyBakedGitea(mergeConfig(defaults, user)) };
} catch (e) {
if (e.code === 'ENOENT') {
const initial = applyBakedGitea(mergeConfig(defaults, {}));
await fs.writeFile(p, JSON.stringify(initial, null, 2), 'utf8');
return { configPath: p, config: JSON.parse(JSON.stringify(initial)) };
}
throw e;
}
}
async function saveGameDir(configPath, gameDir) {
const defPath = path.join(__dirname, '..', 'default-launcher.json');
const defaults = JSON.parse(await fs.readFile(defPath, 'utf8'));
const user = JSON.parse(await fs.readFile(configPath, 'utf8'));
user.game_dir = gameDir;
const merged = applyBakedGitea(mergeConfig(defaults, user));
await fs.writeFile(configPath, JSON.stringify(merged, null, 2), 'utf8');
return merged;
}
function resolveGameDir(cfg, configPath) {
const gd = cfg.game_dir;
if (!gd) return '';
if (path.isAbsolute(gd)) return path.normalize(gd);
return path.normalize(path.join(path.dirname(configPath), gd));
}
module.exports = { getConfigPath, loadConfig, saveGameDir, resolveGameDir, mergeConfig, applyBakedGitea };
@@ -0,0 +1,107 @@
'use strict';
const { downloadBodyToFile } = require('./http-download');
function normalizeGiteaBaseUrl(raw) {
let b = String(raw || '').trim().replace(/\/+$/, '');
if (!b) return '';
if (!/^https?:\/\//i.test(b)) b = `https://${b}`;
return b;
}
function giteaApiBase(cfg) {
const base = normalizeGiteaBaseUrl(cfg.gitea.base_url);
return `${base}/api/v1`;
}
function giteaToken(cfg) {
const name = cfg.gitea && cfg.gitea.token_env;
if (name && process.env[name]) return String(process.env[name]).trim();
return String(process.env.GITEA_TOKEN || '').trim();
}
function giteaHeaders(token, json = false) {
const h = { 'User-Agent': 'Fractured-Launcher-Electron' };
if (json) h.Accept = 'application/json';
if (token) h.Authorization = `token ${token}`;
return h;
}
function useGiteaReleases(cfg) {
const g = cfg.gitea;
if (!g) return false;
return !!(String(g.base_url || '').trim() && String(g.owner || '').trim() && String(g.repo || '').trim());
}
async function downloadGiteaReleaseAsset(cfg, assetName, destPath) {
const api = giteaApiBase(cfg);
const { owner, repo } = cfg.gitea;
const tag = (cfg.gitea.release_tag || 'latest').trim() || 'latest';
const token = giteaToken(cfg);
let listUrl;
if (tag.toLowerCase() === 'latest') {
listUrl = `${api}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/latest`;
} else {
listUrl = `${api}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/tags/${encodeURIComponent(tag)}`;
}
const res = await fetch(listUrl, { headers: giteaHeaders(token, true) });
const text = await res.text();
if (!res.ok) {
let hint = '';
if (res.status === 404) hint = ' (wrong tag / no release / check base_url owner repo)';
if (res.status === 401 || res.status === 403) hint = ' (set GITEA_TOKEN or gitea.token_env)';
throw new Error(`Gitea release ${res.status}${hint}: ${text.slice(0, 600)}`);
}
const rel = JSON.parse(text);
const list = rel.attachments || rel.assets || [];
let downloadUrl = '';
for (const a of list) {
if (a.name !== assetName) continue;
downloadUrl = a.browser_download_url || a.download_url || '';
break;
}
if (!downloadUrl) {
const names = list.map((x) => x.name).filter(Boolean);
throw new Error(`Gitea release asset "${assetName}" not found; attachments: ${names.join(', ') || '(none)'}`);
}
const h = { Accept: 'application/octet-stream' };
if (token) h.Authorization = `token ${token}`;
const dl = await fetch(downloadUrl, { headers: h, redirect: 'follow' });
await downloadBodyToFile(dl, destPath);
}
/**
* Base URL for electron-updater generic provider (expects latest.yml under this path).
* Matches Giteas pattern: /owner/repo/releases/download/{tag}/latest.yml
*/
async function getGiteaUpdaterFeedBase(cfg) {
if (!useGiteaReleases(cfg)) return null;
const api = giteaApiBase(cfg);
const { owner, repo } = cfg.gitea;
const tag = (cfg.gitea.release_tag || 'latest').trim() || 'latest';
const token = giteaToken(cfg);
let listUrl;
if (tag.toLowerCase() === 'latest') {
listUrl = `${api}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/latest`;
} else {
listUrl = `${api}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/tags/${encodeURIComponent(tag)}`;
}
const res = await fetch(listUrl, { headers: giteaHeaders(token, true) });
if (!res.ok) return null;
const rel = await res.json();
const tagName = rel.tag_name;
if (!tagName || typeof tagName !== 'string') return null;
const root = normalizeGiteaBaseUrl(cfg.gitea.base_url);
const url = `${root}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/download/${encodeURIComponent(tagName)}/`;
return { url, token };
}
module.exports = {
downloadGiteaReleaseAsset,
giteaToken,
useGiteaReleases,
getGiteaUpdaterFeedBase,
};
@@ -0,0 +1,9 @@
'use strict';
function githubToken(cfg) {
const name = cfg.github && cfg.github.token_env;
if (name && process.env[name]) return process.env[name];
return process.env.GITHUB_TOKEN || '';
}
module.exports = { githubToken };
@@ -0,0 +1,121 @@
'use strict';
const path = require('path');
const fs = require('fs').promises;
const { githubToken } = require('./github-token');
const { downloadGiteaReleaseAsset, useGiteaReleases } = require('./gitea-release');
const { fetchToFile, downloadBodyToFile } = require('./http-download');
function encodeRepoPath(repoPath) {
let p = String(repoPath || '').replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
if (!p) return '';
return p.split('/').map((seg) => encodeURIComponent(seg)).join('/');
}
function ghHeaders(token, json = false) {
const h = {
'User-Agent': 'Fractured-Launcher-Electron',
'X-GitHub-Api-Version': '2022-11-28',
};
if (json) h.Accept = 'application/vnd.github+json';
if (token) h.Authorization = `Bearer ${token}`;
return h;
}
async function downloadGitHubRepoFile(cfg, repoPath, destPath) {
const token = githubToken(cfg);
const enc = encodeRepoPath(repoPath);
const ref = cfg.github.ref || 'main';
const { owner, repo } = cfg.github;
if (!token) {
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${enc}`;
await fetchToFile(url, {}, destPath);
return;
}
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${enc}?ref=${encodeURIComponent(ref)}`;
const res = await fetch(apiUrl, { headers: ghHeaders(token, true) });
const body = await res.text();
if (!res.ok) {
throw new Error(`GitHub contents API ${res.status}: ${body.slice(0, 800)}`);
}
const meta = JSON.parse(body);
if (meta.type && meta.type !== 'file') {
throw new Error(`not a file: ${repoPath}`);
}
if (meta.download_url) {
const h = { Accept: 'application/octet-stream' };
if (token) {
h.Authorization = `Bearer ${token}`;
h['X-GitHub-Api-Version'] = '2022-11-28';
}
await fetchToFile(meta.download_url, h, destPath);
return;
}
if (meta.content && meta.encoding === 'base64') {
const buf = Buffer.from(String(meta.content).replace(/\n/g, ''), 'base64');
if (!buf.length) throw new Error('empty base64 content');
await fs.mkdir(path.dirname(destPath), { recursive: true });
const tmp = destPath + '.downloading';
await fs.writeFile(tmp, buf);
await fs.rename(tmp, destPath);
return;
}
throw new Error(`unexpected GitHub response for ${repoPath}`);
}
async function downloadReleaseAsset(cfg, assetName, destPath) {
if (useGiteaReleases(cfg)) {
return downloadGiteaReleaseAsset(cfg, assetName, destPath);
}
const token = githubToken(cfg);
const tag = (cfg.github.release_tag || 'latest').trim() || 'latest';
const { owner, repo } = cfg.github;
let listUrl;
if (tag.toLowerCase() === 'latest') {
listUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
} else {
listUrl = `https://api.github.com/repos/${owner}/${repo}/releases/tags/${encodeURIComponent(tag)}`;
}
const res = await fetch(listUrl, { headers: ghHeaders(token, true) });
const text = await res.text();
if (!res.ok) {
let hint = '';
if (res.status === 404) {
hint =
' (wrong tag, private repo without token, or releases live on Gitea — set gitea.base_url, gitea.owner, gitea.repo in launcher.json)';
}
if (res.status === 401 || res.status === 403) hint = ' (set GITHUB_TOKEN or token_env PAT)';
throw new Error(`releases list ${res.status}${hint}: ${text.slice(0, 600)}`);
}
const rel = JSON.parse(text);
const assets = rel.assets || [];
let assetURL = '';
for (const a of assets) {
if (a.name !== assetName) continue;
if (token && a.url) {
assetURL = a.url;
break;
}
if (a.browser_download_url) {
assetURL = a.browser_download_url;
break;
}
assetURL = a.url;
break;
}
if (!assetURL) {
const names = assets.map((x) => x.name);
throw new Error(`release asset "${assetName}" not found; attachments: ${names.join(', ')}`);
}
const h = { Accept: 'application/octet-stream' };
if (token) {
h.Authorization = `Bearer ${token}`;
h['X-GitHub-Api-Version'] = '2022-11-28';
}
const dl = await fetch(assetURL, { headers: h, redirect: 'follow' });
await downloadBodyToFile(dl, destPath);
}
module.exports = { downloadGitHubRepoFile, downloadReleaseAsset, encodeRepoPath };
@@ -0,0 +1,40 @@
'use strict';
const fs = require('fs').promises;
const path = require('path');
const { createWriteStream } = require('fs');
const { pipeline } = require('stream/promises');
const { Readable } = require('stream');
async function downloadBodyToFile(res, destPath) {
if (!res.ok) {
const errText = await res.text().catch(() => '');
throw new Error(`HTTP ${res.status}: ${errText.slice(0, 500)}`);
}
if (!res.body) {
throw new Error('download has no body');
}
await fs.mkdir(path.dirname(destPath), { recursive: true });
const tmp = destPath + '.downloading';
let body = res.body;
if (body && typeof body.pipe !== 'function') {
body = Readable.fromWeb(body);
}
await pipeline(body, createWriteStream(tmp));
const st = await fs.stat(tmp);
if (st.size === 0) {
await fs.unlink(tmp).catch(() => {});
throw new Error('empty download');
}
await fs.rename(tmp, destPath);
}
async function fetchToFile(url, headers, destPath) {
const res = await fetch(url, {
headers,
redirect: 'follow',
});
await downloadBodyToFile(res, destPath);
}
module.exports = { fetchToFile, downloadBodyToFile };
@@ -0,0 +1,144 @@
'use strict';
const fs = require('fs').promises;
const path = require('path');
const os = require('os');
const { createHash } = require('node:crypto');
const { downloadReleaseAsset, downloadGitHubRepoFile } = require('./github');
async function sha256File(absPath) {
const buf = await fs.readFile(absPath);
return createHash('sha256').update(buf).digest('hex');
}
function stateDir(gameDir) {
return path.join(gameDir, '.fractured');
}
function statePath(gameDir) {
return path.join(stateDir(gameDir), 'patch-state.json');
}
async function readPatchState(gameDir) {
if (!gameDir) return null;
try {
const t = await fs.readFile(statePath(gameDir), 'utf8');
return JSON.parse(t);
} catch {
return null;
}
}
async function writePatchState(gameDir, manifestVersion, fileShas) {
const p = statePath(gameDir);
await fs.mkdir(path.dirname(p), { recursive: true });
const body = {
client_build: manifestVersion,
updated_at: new Date().toISOString(),
files: fileShas,
};
const tmp = p + '.tmp';
await fs.writeFile(tmp, JSON.stringify(body, null, 2), 'utf8');
await fs.rename(tmp, p);
}
function validateManifest(m) {
if (!m || m.version == null || String(m.version).trim() === '') return false;
if (!m.files || typeof m.files !== 'object') return false;
return true;
}
/**
* Download and parse patch-manifest.json (or custom name). Returns null on any failure.
*/
async function loadManifest(cfg) {
const pm = cfg.patch_manifest;
if (!pm || !pm.enabled || !String(pm.source || '').trim()) return null;
const tmp = path.join(os.tmpdir(), `fr-patch-manifest-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
try {
if (pm.from_release) {
await downloadReleaseAsset(cfg, String(pm.source).trim(), tmp);
} else {
await downloadGitHubRepoFile(cfg, String(pm.source).trim(), tmp);
}
const raw = await fs.readFile(tmp, 'utf8');
await fs.unlink(tmp).catch(() => {});
return JSON.parse(raw);
} catch {
await fs.unlink(tmp).catch(() => {});
return null;
}
}
/**
* True if every from_release file on disk matches manifest sha256.
*/
async function patchesMatchManifest(cfg, manifest, onStatus) {
if (!validateManifest(manifest)) return false;
const gameDir = cfg.game_dir;
for (const entry of cfg.files || []) {
if (!entry.from_release) continue;
const spec = manifest.files[entry.source];
if (!spec || !spec.sha256) return false;
const parts = String(entry.dest).replace(/\\/g, '/').split('/').filter(Boolean);
const destAbs = path.join(gameDir, ...parts);
let disk;
try {
disk = await sha256File(destAbs);
} catch {
return false;
}
if (disk.toLowerCase() !== String(spec.sha256).trim().toLowerCase()) return false;
}
if (onStatus) {
onStatus(`Client files already match build ${manifest.version} (nothing to download).`);
}
return true;
}
async function verifyInstalledAgainstManifest(cfg, manifest) {
if (!validateManifest(manifest)) return;
for (const entry of cfg.files || []) {
if (!entry.from_release) continue;
const spec = manifest.files[entry.source];
if (!spec || !spec.sha256) {
throw new Error(
`patch-manifest.json is missing a sha256 for "${entry.source}" — regenerate the manifest for this release.`
);
}
const parts = String(entry.dest).replace(/\\/g, '/').split('/').filter(Boolean);
const destAbs = path.join(cfg.game_dir, ...parts);
const disk = await sha256File(destAbs);
if (disk.toLowerCase() !== String(spec.sha256).trim().toLowerCase()) {
throw new Error(
`${entry.source}: checksum mismatch after install (expected ${spec.sha256.slice(0, 12)}…, got ${disk.slice(0, 12)}…). Try syncing again.`
);
}
}
}
async function recordPatchState(cfg, manifest) {
if (!validateManifest(manifest)) return;
const shas = {};
for (const entry of cfg.files || []) {
if (!entry.from_release) continue;
const parts = String(entry.dest).replace(/\\/g, '/').split('/').filter(Boolean);
const destAbs = path.join(cfg.game_dir, ...parts);
try {
shas[entry.source] = await sha256File(destAbs);
} catch {
/* skip */
}
}
await writePatchState(cfg.game_dir, String(manifest.version), shas);
}
module.exports = {
loadManifest,
validateManifest,
patchesMatchManifest,
verifyInstalledAgainstManifest,
recordPatchState,
readPatchState,
statePath,
};
@@ -0,0 +1,117 @@
'use strict';
const path = require('path');
const fs = require('fs').promises;
const { downloadGitHubRepoFile, downloadReleaseAsset } = require('./github');
function pad2(n) {
return String(n).padStart(2, '0');
}
function backupSuffix() {
const d = new Date();
return `${d.getFullYear()}${pad2(d.getMonth() + 1)}${pad2(d.getDate())}-${pad2(d.getHours())}${pad2(d.getMinutes())}${pad2(d.getSeconds())}`;
}
function wowExePath(cfg) {
const exe = (cfg.launch && cfg.launch.exe) || 'Wow.exe';
const parts = exe.replace(/\\/g, '/').split('/').filter(Boolean);
return path.join(cfg.game_dir, ...parts);
}
function wowInstallValid(cfg) {
if (!cfg.game_dir) return false;
return require('fs').existsSync(wowExePath(cfg));
}
async function installFile(cfg, entry) {
const parts = String(entry.dest).replace(/\\/g, '/').split('/').filter(Boolean);
const destAbs = path.join(cfg.game_dir, ...parts);
if (entry.backup) {
try {
const st = await fs.stat(destAbs);
if (st.isFile()) {
const bak = `${destAbs}.bak-${backupSuffix()}`;
await fs.rename(destAbs, bak);
}
} catch (e) {
if (e.code !== 'ENOENT') throw e;
}
} else {
try {
await fs.unlink(destAbs);
} catch (e) {
if (e.code !== 'ENOENT') throw e;
}
}
const tmp = destAbs + '.new';
if (entry.from_release) {
await downloadReleaseAsset(cfg, entry.source, tmp);
} else {
await downloadGitHubRepoFile(cfg, entry.source, tmp);
}
await fs.rename(tmp, destAbs);
}
async function applyRealmlist(cfg) {
if (!cfg.realmlist || !cfg.realmlist.enabled) return;
let line = String(cfg.realmlist.line || '').trim();
if (!line) throw new Error('realmlist.line empty');
if (!line.toLowerCase().startsWith('set realmlist ')) {
line = `set realmlist ${line}`;
}
const content = line + '\n';
let paths = cfg.realmlist.paths;
if (!paths || !paths.length) paths = ['Data/enUS/realmlist.wtf'];
for (const rel of paths) {
const r = String(rel).trim().replace(/\\/g, '/');
if (!r) continue;
const segs = r.split('/').filter(Boolean);
const abs = path.join(cfg.game_dir, ...segs);
await fs.mkdir(path.dirname(abs), { recursive: true });
await fs.writeFile(abs, content, 'utf8');
}
}
async function applyPatches(cfg, onStatus) {
for (const f of cfg.files || []) {
if (onStatus) onStatus(`Updating ${f.dest}`);
try {
await installFile(cfg, f);
} catch (e) {
throw new Error(`sync ${f.dest}: ${e.message || e}`);
}
}
if (cfg.realmlist && cfg.realmlist.enabled) {
if (onStatus) onStatus('Applying realmlist …');
await applyRealmlist(cfg);
}
if (onStatus) onStatus('All patches applied.');
}
async function doAuth(cfg, username, password) {
if (!cfg.auth || !cfg.auth.enabled) return;
const u = String(username || '').trim();
const p = String(password || '');
if (!u || !p) throw new Error('username and password required');
const body = {
[cfg.auth.username_field || 'username']: u,
[cfg.auth.password_field || 'password']: p,
};
const res = await fetch(cfg.auth.url, {
method: cfg.auth.method || 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(body),
});
const t = await res.text();
if (res.status < 200 || res.status >= 300) {
throw new Error(`login failed ${res.status}: ${t.slice(0, 400)}`);
}
}
module.exports = {
applyPatches,
applyRealmlist,
wowExePath,
wowInstallValid,
doAuth,
};
+156
View File
@@ -0,0 +1,156 @@
'use strict';
const { app, BrowserWindow, ipcMain, dialog, Menu } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
const { loadConfig, saveGameDir, resolveGameDir } = require('./lib/config-store');
const { applyPatches, wowExePath, wowInstallValid, doAuth } = require('./lib/patch');
const { readPatchState } = require('./lib/patch-manifest');
const { setupAutoUpdater } = require('./lib/auto-update');
let mainWindow;
let autoUpdateApi = {
checkNow: async () => ({ skipped: true, reason: 'not initialized' }),
};
function createWindow() {
mainWindow = new BrowserWindow({
width: 720,
height: 640,
show: false,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.cjs'),
contextIsolation: true,
nodeIntegration: false,
sandbox: false,
},
});
Menu.setApplicationMenu(null);
mainWindow.loadFile(path.join(__dirname, 'index.html'));
mainWindow.once('ready-to-show', () => mainWindow.show());
}
function sendProgress(msg) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('launcher:progress', msg);
}
}
async function readMergedConfig() {
const { configPath, config } = await loadConfig(app);
const gameDir = resolveGameDir(config, configPath);
const merged = { ...config, game_dir: gameDir };
return { configPath, config: merged };
}
app.whenReady().then(async () => {
createWindow();
const { config } = await loadConfig(app);
const ghEnv = config.github && config.github.token_env;
const githubToken =
(ghEnv && String(process.env[ghEnv] || '').trim()) ||
String(process.env.GH_TOKEN || process.env.GITHUB_TOKEN || '').trim();
const giteaEnv = config.gitea && config.gitea.token_env;
const giteaToken =
(giteaEnv && String(process.env[giteaEnv] || '').trim()) ||
String(process.env.GITEA_TOKEN || '').trim();
const updateFeedUrl = String(process.env.LAUNCHER_UPDATE_URL || config.update_feed_url || '').trim();
autoUpdateApi = await setupAutoUpdater(app, () => mainWindow, {
updateFeedUrl,
config,
githubOwner: config.github && config.github.owner,
githubRepo: config.github && config.github.repo,
githubToken,
giteaToken,
allowGithubLauncherUpdates: config.launcher_updates_from_github === true,
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
ipcMain.handle('launcher:load', async () => {
const { configPath, config } = await readMergedConfig();
let clientBuild = '';
if (wowInstallValid(config)) {
const st = await readPatchState(config.game_dir);
if (st && st.client_build) clientBuild = String(st.client_build);
}
return {
configPath,
gameDir: config.game_dir || '',
authEnabled: !!(config.auth && config.auth.enabled),
wowExe: (config.launch && config.launch.exe) || 'Wow.exe',
wowOk: wowInstallValid(config),
clientBuild,
};
});
ipcMain.handle('launcher:saveGameDir', async (_e, dir) => {
const trimmed = String(dir || '').trim();
if (!trimmed) throw new Error('folder path is empty');
const { configPath } = await loadConfig(app);
const norm = path.normalize(trimmed);
const probe = { ...(await readMergedConfig()).config, game_dir: norm };
if (!wowInstallValid(probe)) {
throw new Error(`That folder does not contain ${(probe.launch && probe.launch.exe) || 'Wow.exe'}`);
}
const c = await saveGameDir(configPath, norm);
const merged = { ...c, game_dir: resolveGameDir(c, configPath) };
return { ok: true, gameDir: merged.game_dir, wowOk: wowInstallValid(merged) };
});
ipcMain.handle('launcher:pickFolder', async (_e, startDir) => {
const win = BrowserWindow.getFocusedWindow() || mainWindow;
const r = await dialog.showOpenDialog(win, {
title: 'Select World of Warcraft 3.3.5a folder',
properties: ['openDirectory', 'createDirectory'],
defaultPath: startDir && String(startDir).trim() ? String(startDir).trim() : undefined,
});
if (r.canceled || !r.filePaths || !r.filePaths[0]) return { canceled: true, path: '' };
return { canceled: false, path: r.filePaths[0] };
});
ipcMain.handle('launcher:auth', async (_e, { user, pass }) => {
const { config } = await readMergedConfig();
await doAuth(config, user, pass);
return { ok: true };
});
ipcMain.handle('launcher:sync', async () => {
const { config } = await readMergedConfig();
if (!wowInstallValid(config)) {
throw new Error('Set a valid WoW folder (must contain Wow.exe) first.');
}
await applyPatches(config, sendProgress);
return { ok: true };
});
ipcMain.handle('launcher:checkUpdates', async () => {
try {
return await autoUpdateApi.checkNow();
} catch (e) {
const msg = e && (e.message || String(e));
return { ok: false, error: msg };
}
});
ipcMain.handle('launcher:play', async () => {
const { config } = await readMergedConfig();
const exe = wowExePath(config);
const args = (config.launch && config.launch.args) || [];
const child = spawn(exe, args, {
cwd: config.game_dir,
detached: true,
stdio: 'ignore',
windowsHide: true,
shell: false,
});
child.unref();
return { ok: true };
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,77 @@
{
"name": "fractured-launcher-electron",
"version": "1.0.2",
"description": "Fractured WoW launcher (Electron) — no console window, native folder picker, auto-update",
"main": "main.js",
"repository": {
"type": "git",
"url": "https://github.com/Dawnforger/Fractured.git"
},
"scripts": {
"start": "electron .",
"pack:win": "electron-builder --win nsis portable --x64 --publish never",
"pack:linux": "electron-builder --linux AppImage --x64 --publish never",
"publish:win": "electron-builder --win nsis portable --x64 --publish never"
},
"author": "",
"license": "GPL-3.0",
"devDependencies": {
"electron": "^33.2.1",
"electron-builder": "^25.1.8"
},
"dependencies": {
"electron-updater": "^6.3.9"
},
"build": {
"appId": "net.fractured.launcher",
"productName": "Fractured Launcher",
"directories": {
"output": "dist"
},
"publish": null,
"files": [
"main.js",
"preload.cjs",
"index.html",
"renderer.js",
"styles.css",
"default-launcher.json",
"lib/baked-gitea-channel.js",
"lib/gitea-release.js",
"lib/patch-manifest.js",
"lib/**/*"
],
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
},
{
"target": "portable",
"arch": ["x64"]
}
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"artifactName": "Fractured-Launcher-${version}-Setup.${ext}"
},
"portable": {
"artifactName": "Fractured-Launcher-${version}-Windows-Portable.${ext}"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
}
],
"category": "Game"
},
"appImage": {
"artifactName": "Fractured-Launcher-${version}-Linux-x86_64.${ext}"
}
}
}
@@ -0,0 +1,16 @@
'use strict';
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('launcher', {
load: () => ipcRenderer.invoke('launcher:load'),
saveGameDir: (dir) => ipcRenderer.invoke('launcher:saveGameDir', dir),
pickFolder: (startDir) => ipcRenderer.invoke('launcher:pickFolder', startDir),
auth: (user, pass) => ipcRenderer.invoke('launcher:auth', { user, pass }),
sync: () => ipcRenderer.invoke('launcher:sync'),
checkUpdates: () => ipcRenderer.invoke('launcher:checkUpdates'),
play: () => ipcRenderer.invoke('launcher:play'),
onProgress: (cb) => {
ipcRenderer.on('launcher:progress', (_e, msg) => cb(msg));
},
});
@@ -0,0 +1,130 @@
'use strict';
const logEl = document.getElementById('log');
const gameDirEl = document.getElementById('gameDir');
const btnBrowse = document.getElementById('btnBrowse');
const btnSave = document.getElementById('btnSaveFolder');
const btnSync = document.getElementById('btnSync');
const btnPlay = document.getElementById('btnPlay');
const btnCheckLauncher = document.getElementById('btnCheckLauncher');
const authCard = document.getElementById('authCard');
const btnAuth = document.getElementById('btnAuth');
const wowExeName = document.getElementById('wowExeName');
function log(msg) {
logEl.textContent += (logEl.textContent ? '\n' : '') + msg;
logEl.scrollTop = logEl.scrollHeight;
}
function setError(e) {
const m = e && (e.message || String(e));
log('Error: ' + m);
}
let authEnabled = false;
let signedIn = false;
async function refresh() {
try {
const s = await window.launcher.load();
authEnabled = s.authEnabled;
signedIn = !s.authEnabled;
wowExeName.textContent = s.wowExe || 'Wow.exe';
gameDirEl.value = s.gameDir || '';
authCard.classList.toggle('hidden', !authEnabled);
btnSync.disabled = !s.wowOk || (authEnabled && !signedIn);
btnPlay.classList.add('hidden');
btnPlay.disabled = true;
logEl.textContent = '';
if (!s.gameDir) log('Choose your WoW installation folder.');
else if (!s.wowOk) log('Folder does not look valid yet — pick the directory that contains ' + (s.wowExe || 'Wow.exe') + ', then Save folder.');
else if (authEnabled && !signedIn) log('Sign in, then download updates.');
else log('Ready — tap Download updates to sync from GitHub.');
} catch (e) {
setError(e);
}
}
window.launcher.onProgress((msg) => log(msg));
btnBrowse.addEventListener('click', async () => {
try {
const start = gameDirEl.value.trim();
const r = await window.launcher.pickFolder(start);
if (!r.canceled && r.path) {
gameDirEl.value = r.path;
log('Selected: ' + r.path);
}
} catch (e) {
setError(e);
}
});
btnSave.addEventListener('click', async () => {
try {
const dir = gameDirEl.value.trim();
if (!dir) {
log('Pick a folder with Browse… first.');
return;
}
const r = await window.launcher.saveGameDir(dir);
gameDirEl.value = r.gameDir;
btnSync.disabled = !r.wowOk || (authEnabled && !signedIn);
log('Saved installation folder.');
} catch (e) {
setError(e);
}
});
btnAuth.addEventListener('click', async () => {
try {
const u = document.getElementById('username').value;
const p = document.getElementById('password').value;
await window.launcher.auth(u, p);
signedIn = true;
log('Signed in.');
btnSync.disabled = !gameDirEl.value.trim() || (authEnabled && !signedIn);
const s = await window.launcher.load();
btnSync.disabled = !s.wowOk;
} catch (e) {
setError(e);
}
});
btnSync.addEventListener('click', async () => {
btnSync.disabled = true;
log('—');
try {
await window.launcher.sync();
btnPlay.classList.remove('hidden');
btnPlay.disabled = false;
log('Done. You can launch the game.');
} catch (e) {
setError(e);
} finally {
const s = await window.launcher.load().catch(() => null);
btnSync.disabled = !s || !s.wowOk || (authEnabled && !signedIn);
}
});
btnPlay.addEventListener('click', async () => {
try {
await window.launcher.play();
window.close();
} catch (e) {
setError(e);
}
});
btnCheckLauncher.addEventListener('click', async () => {
try {
log('Checking for launcher updates…');
const r = await window.launcher.checkUpdates();
if (r && r.skipped) log('Launcher auto-update: ' + (r.reason || 'skipped (use a packaged build).'));
else if (r && r.ok === false && r.error) setError(new Error(r.error));
} catch (e) {
setError(e);
}
});
refresh();
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Push a one-file README so the Gitea repo is non-empty (fixes HTTP 422 "repo is empty"
# when CI creates a release). Safe to re-run only if the repo still has no commits;
# if it already has history, skip or use the Gitea web UI instead.
#
# Usage:
# export GITEA_BASE_URL=https://git.example.com
# export GITEA_OWNER=myorg
# export GITEA_REPO=fractured-patches
# ./bootstrap-gitea-repo.sh
#
# Or pass an explicit clone URL (HTTPS or SSH):
# ./bootstrap-gitea-repo.sh https://git.example.com/myorg/fractured-patches.git
#
set -euo pipefail
BRANCH="${GITEA_TARGET_REF:-main}"
if [ "${1:-}" != "" ]; then
URL="$1"
else
: "${GITEA_BASE_URL:?Set GITEA_BASE_URL or pass clone URL as first argument}"
: "${GITEA_OWNER:?Set GITEA_OWNER or pass clone URL as first argument}"
: "${GITEA_REPO:?Set GITEA_REPO or pass clone URL as first argument}"
BASE="${GITEA_BASE_URL%/}"
URL="${BASE}/${GITEA_OWNER}/${GITEA_REPO}.git"
fi
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
cd "$TMP"
git init -q
git checkout -q -b "$BRANCH"
cat >README.md <<'EOF'
# Fractured release mirror
Release assets (launcher builds, patches, `patch-manifest.json`, etc.) are uploaded here by **GitHub Actions** (“Sync release to Gitea”) from the main Fractured repository.
This initial commit exists because **Gitea requires at least one commit** in the repository before releases can be created.
EOF
git add README.md
git commit -q -m "chore: initial commit (required for Gitea releases)"
git remote add origin "$URL"
git push -u origin "$BRANCH"
echo "Pushed initial README to $URL (branch $BRANCH)."
@@ -0,0 +1,32 @@
#!/usr/bin/env node
/**
* Build patch-manifest.json for a release (same names as files[].source in launcher.json).
*
* Usage (from a folder containing the patch binaries):
* node generate-patch-manifest.js v0.9.0-client patch-Z.MPQ Wow-patched.exe
*
* Prints JSON to stdout redirect to file:
* node generate-patch-manifest.js v0.9.0-client patch-Z.MPQ Wow-patched.exe > patch-manifest.json
*/
'use strict';
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const version = process.argv[2];
const names = process.argv.slice(3);
if (!version || names.length === 0) {
console.error('Usage: generate-patch-manifest.js <version-label> <file1> [file2 ...]');
console.error(' Example: generate-patch-manifest.js v0.9.0-client patch-Z.MPQ Wow-patched.exe');
process.exit(1);
}
const out = { version, files: {} };
for (const f of names) {
const base = path.basename(f);
const buf = fs.readFileSync(f);
const sha256 = crypto.createHash('sha256').update(buf).digest('hex');
out.files[base] = { sha256 };
}
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Local Linux AppImage build (uses current tree — no tag snapshot). Run from repo root or this dir.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
echo "==> npm ci"
npm ci
echo "==> npm run pack:linux (AppImage x64)"
npm run pack:linux
echo "==> dist/:"
ls -la dist/
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Upload local files to a GitHub release on the public distro repo (default: Dawnforger/Fractured-Distro).
#
# Usage (from repo root or this directory):
# export GH_TOKEN=ghp_... # PAT with repo/releases on the distro repo
# ./tools/fractured-launcher-electron/scripts/publish-to-distro.sh v1.0.0 patch-Z.MPQ Wow-patched.exe
#
# Optional:
# DISTRO_REPO=YourOrg/Fratured-Distro # if your GitHub slug differs
# SRC_TAG=v1.0.0 ./publish-to-distro.sh v1.0.0 # copy all assets from SOURCE_REPO release SRC_TAG
#
set -euo pipefail
DISTRO_REPO="${DISTRO_REPO:-Dawnforger/Fractured-Distro}"
SOURCE_REPO="${SOURCE_REPO:-Dawnforger/Fractured}"
if ! command -v gh >/dev/null 2>&1; then
echo "Install GitHub CLI: https://cli.github.com/"
exit 1
fi
if [ -z "${GH_TOKEN:-}" ]; then
echo "Set GH_TOKEN to a PAT with releases write access to ${DISTRO_REPO}."
exit 1
fi
if [ "$#" -lt 1 ]; then
echo "Usage: $0 <release-tag> [files...]"
echo " or: SRC_TAG=v1.0.0 $0 <release-tag> # copies all assets from ${SOURCE_REPO} release SRC_TAG"
exit 1
fi
TAG="$1"
shift
if [ "$#" -eq 0 ] && [ -z "${SRC_TAG:-}" ]; then
echo "After the tag, list files to upload, or set SRC_TAG=... to copy all assets from ${SOURCE_REPO}."
exit 1
fi
tmpdir=$(mktemp -d)
cleanup() { rm -rf "$tmpdir"; }
trap cleanup EXIT
if [ "$#" -eq 0 ] && [ -n "${SRC_TAG:-}" ]; then
echo "Downloading assets from ${SOURCE_REPO}@${SRC_TAG}"
gh release download "$SRC_TAG" -R "$SOURCE_REPO" -D "$tmpdir"
else
for f in "$@"; do
if [ ! -f "$f" ]; then
echo "Not a file: $f"
exit 1
fi
cp -a "$f" "$tmpdir/"
done
fi
shopt -s nullglob
files=("$tmpdir"/*)
if [ "${#files[@]}" -eq 0 ]; then
echo "No files to upload."
exit 1
fi
if gh release view "$TAG" -R "$DISTRO_REPO" &>/dev/null; then
gh release upload "$TAG" -R "$DISTRO_REPO" "${files[@]}" --clobber
echo "Uploaded to https://github.com/${DISTRO_REPO}/releases/tag/${TAG}"
else
gh release create "$TAG" -R "$DISTRO_REPO" \
--title "Fractured ${TAG}" \
--notes "Published from ${SOURCE_REPO} (local script)." \
"${files[@]}"
echo "Created https://github.com/${DISTRO_REPO}/releases/tag/${TAG}"
fi

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