feat(editor): add WCMG (Combat Maneuver Group) open catalog format

Novel replacement for the hardcoded class-mutex tables
the WoW client uses to grey out incompatible action-bar
buttons. Each entry is one mutually-exclusive spell
group: Warrior stances (Battle/Defensive/Berserker),
Druid shapeshift forms (Bear/Cat/Travel/Tree/Moonkin),
Hunter aspects (Hawk/Cheetah/Pack/Viper/Dragonhawk/Beast/
Wild), DK presences (Frost/Unholy/Blood). The action-bar
update path uses the catalog to know which spells share
a mutex bucket and clear "currently active" outlines
when a sibling is cast.

Six categoryKind enum values (Stance / Form / Aspect /
Presence / Posture / Sigil) and variable-length members[]
array of spell IDs (refs WSPL). Three presets:
makeWarrior (Warrior 3-stance), makeDruid (5 shapeshift
+ 2 flight, separate buckets so flying isn't broken by
Cat Form), makeAllMutex (cross-class catalog with one
representative group per mutex-having class).

Validator catches several authoring bugs: empty members[]
(group has nothing to switch between), spellId 0,
duplicate spellId within a group, and — most usefully —
the same spellId appearing in two different exclusive
groups (which would make the action-bar mutex
undecidable: which group's outline does the bar use?).
Warns on single-member groups (mutex with one element
has no exclusion to enforce).

Format count 98 -> 99 (one short of triple-digit
milestone). CLI flag count 1112 -> 1117.
This commit is contained in:
Kelsi 2026-05-10 00:41:45 -07:00
parent 16454c57c4
commit d62ac954da
10 changed files with 760 additions and 0 deletions

View file

@ -2139,6 +2139,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .wscb to a human-editable JSON sidecar (defaults to <base>.wscb.json; emits both channelKind/factionFilter ints AND name strings)\n");
std::printf(" --import-wscb-json <json-path> [out-base]\n");
std::printf(" Import a .wscb.json sidecar back into binary .wscb (channelKind int OR \"login\"/\"system\"/\"raidwarning\"/\"motd\"/\"helptip\"; factionFilter int OR \"alliance\"/\"horde\"/\"both\")\n");
std::printf(" --gen-cmg <wcmg-base> [name]\n");
std::printf(" Emit .wcmg 1 Warrior 3-stance mutex group (Battle / Defensive / Berserker)\n");
std::printf(" --gen-cmg-druid <wcmg-base> [name]\n");
std::printf(" Emit .wcmg 2 Druid mutex groups (5 ground shapeshift forms + 2 flight forms — separate buckets so flight isn't broken by Cat Form)\n");
std::printf(" --gen-cmg-all <wcmg-base> [name]\n");
std::printf(" Emit .wcmg 4 cross-class mutex groups (Warrior stances + Hunter aspects + DK presences + Druid forms — one representative group per mutex-having class)\n");
std::printf(" --info-wcmg <wcmg-base> [--json]\n");
std::printf(" Print WCMG entries (id / classMask / category / exclusive / member count / name) plus per-group spell ID list\n");
std::printf(" --validate-wcmg <wcmg-base> [--json]\n");
std::printf(" Static checks: id+name+classMask required, categoryKind 0..5, members[] non-empty, no duplicate spellIds within a group, no duplicate groupIds, no spellId in two exclusive groups (undecidable mutex); warns on single-member groups\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(" --gen-weather-temperate <wow-base> [zoneName]\n");