feat(editor): add WSPM (Spell Persistent Marker) — 104th open format

Novel replacement for the SpellAreaTrigger.dbc +
AreaTriggerCreateProperties pair vanilla used for AoE
ground decals. Each entry binds one spellId to a
ground-tracked decal: texture path, radius (in yards),
duration, damage tick interval, RGBA decal color, edge-
fade rendering mode (Hard / SoftEdge / Pulse), stack
flag, and destroy-on-cancel semantics for channeled
spells.

The catalog covers three distinct gameplay surfaces in
one shape: player-cast AoE (Blizzard, Flamestrike, etc.
that the visual effects pipeline spawns at cast time),
boss-arena hazard zones (Putricide poison pool,
Sindragosa frost tomb, Marrowgar Bone Storm radius
that raid encounters need to render so players know to
move), and persistent environmental effects
(Wintergrasp lightning strike, Silithus sandstorm cone
that the weather system spawns).

Three preset emitters one per surface: makeMageAoE
(Blizzard/Flamestrike/BlastWave/FrostNova), makeRaid-
Hazards (5 ICC encounter zones), makeEnvironment (3
weather/world hazards). Hazard variants set
destroyOnCancel=0 since they persist beyond any caster;
environment variants additionally set stackable=1 since
multiple lightning strikes can overlap.

Validator's most novel check is spellId uniqueness —
multiple WSPM entries binding the same spellId would
make the spell-cast lookup ambiguous (which decal does
the spell spawn?). Also catches empty texture paths
(decal would render solid color), radius<=0 (zero area),
tickIntervalMs<100ms (perf risk for stackable markers),
decalColor alpha=0 (invisible), and edge-fade enum
range.

Format count 103 -> 104. CLI flag count 1148 -> 1153.
This commit is contained in:
Kelsi 2026-05-10 01:29:56 -07:00
parent 7fa24af3b2
commit 62a10937e0
10 changed files with 765 additions and 0 deletions

View file

@ -2209,6 +2209,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .wtbd to a human-editable JSON sidecar (defaults to <base>.wtbd.json; emits both backgroundPattern/borderPattern ints AND name strings; all 3 colors as 0xAARRGGBB uint32)\n");
std::printf(" --import-wtbd-json <json-path> [out-base]\n");
std::printf(" Import a .wtbd.json sidecar back into binary .wtbd (backgroundPattern int OR \"solid\"/\"gradient\"/\"chevron\"/\"quartered\"/\"starburst\"; borderPattern int OR \"none\"/\"thin\"/\"thick\"/\"decorative\"; isApproved bool OR int)\n");
std::printf(" --gen-spm <wspm-base> [name]\n");
std::printf(" Emit .wspm 4 mage AoE ground markers (Blizzard / Flamestrike / BlastWave ring / Frost Nova ring)\n");
std::printf(" --gen-spm-raid <wspm-base> [name]\n");
std::printf(" Emit .wspm 5 boss-arena hazard zones (Putricide poison pool / Sindragosa frost tomb / Saurfang Mark zone / Putricide shadow puddle / Marrowgar Bone Storm)\n");
std::printf(" --gen-spm-env <wspm-base> [name]\n");
std::printf(" Emit .wspm 3 environmental ground effects (Wintergrasp lightning / Silithus sandstorm cone / open-world blizzard zone)\n");
std::printf(" --info-wspm <wspm-base> [--json]\n");
std::printf(" Print WSPM entries (id / spell / radius / duration / tick interval / fade mode / stack-flag / destroy-on-cancel / name)\n");
std::printf(" --validate-wspm <wspm-base> [--json]\n");
std::printf(" Static checks: id+name+spellId+texturePath required, radius>0, edgeFadeMode 0..2, no duplicate markerIds, no duplicate spellIds (spell-cast lookup ambiguity); warns on radius>100yd, tickIntervalMs<100ms (perf risk for stackable), decalColor alpha=0 (invisible)\n");
std::printf(" --catalog-pluck <wXXX-file> <id> [--json]\n");
std::printf(" Extract one entry by id from any registered catalog format. Auto-detects magic, dispatches to the per-format --info-* handler internally, then prints just the matching entry. Primary-key field is auto-detected (first *Id field, or first numeric)\n");
std::printf(" --catalog-find <directory> <id> [--magic <WXXX>] [--json]\n");