Kelsidavis-WoWee/include/pipeline/wowee_combat_maneuvers.hpp
Kelsi d62ac954da 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.
2026-05-10 00:41:45 -07:00

124 lines
4.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Combat Maneuver Group catalog (.wcmg) —
// novel replacement for the hardcoded class-mutex tables
// that the WoW client uses to grey out incompatible
// action-bar buttons (only one Warrior stance active at
// a time, only one Hunter aspect, only one DK presence,
// only one Druid form). Each entry is one mutually-
// exclusive spell group with its member spell IDs.
//
// Cross-references with previously-added formats:
// WCHC: classMask uses the WCHC class-bit convention
// (1=Warrior / 2=Paladin / 4=Hunter / 8=Rogue /
// 16=Priest / 32=DK / 64=Shaman / 128=Mage /
// 256=Warlock / 1024=Druid).
// WSPL: members[] entries are spell IDs that must
// resolve in the WSPL catalog. Validator can
// optionally cross-check with --catalog-pluck if
// WSPL is available.
// WACT: WACT button entries with spellId in any
// exclusive WCMG group will display the "active
// state" outline when that spell is the
// currently-applied one for the player.
//
// Binary layout (little-endian):
// magic[4] = "WCMG"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// groupId (uint32)
// nameLen + name
// descLen + description
// classMask (uint32)
// categoryKind (uint8) — Stance / Form / Aspect /
// Presence / Posture /
// Sigil
// exclusive (uint8) — 0/1 bool, only one
// active at a time
// pad0 (uint8) / pad1 (uint8)
// iconColorRGBA (uint32)
// memberCount (uint32)
// members (memberCount × uint32 spell IDs)
struct WoweeCombatManeuvers {
enum CategoryKind : uint8_t {
Stance = 0, // Warrior Battle/Def/Berserker
Form = 1, // Druid Bear/Cat/Tree/Moonkin
Aspect = 2, // Hunter Hawk/Cheetah/Pack/Viper
Presence = 3, // DK Frost/Unholy/Blood
Posture = 4, // generic posture (e.g. siege
// engine driver/gunner)
Sigil = 5, // DK Sigils (one ground-anchored
// at a time)
};
struct Entry {
uint32_t groupId = 0;
std::string name;
std::string description;
uint32_t classMask = 0;
uint8_t categoryKind = Stance;
uint8_t exclusive = 1; // default: mutex group
uint8_t pad0 = 0;
uint8_t pad1 = 0;
uint32_t iconColorRGBA = 0xFFFFFFFFu;
std::vector<uint32_t> members; // spell IDs
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
const Entry* findById(uint32_t groupId) const;
// Returns all maneuver groups available to a class
// (used by the action-bar UI to compute which spells
// share a mutex bucket and grey accordingly).
std::vector<const Entry*> findByClass(uint32_t classBit) const;
// Returns the group containing the given spell ID, if
// any. Used by the action-bar update path to know
// whether casting a spell should clear a "currently
// active" outline elsewhere on the bar.
const Entry* findGroupForSpell(uint32_t spellId) const;
};
class WoweeCombatManeuversLoader {
public:
static bool save(const WoweeCombatManeuvers& cat,
const std::string& basePath);
static WoweeCombatManeuvers load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-cmg* variants.
//
// makeWarrior — 1 entry: WarriorStances (3 spells:
// Battle / Defensive / Berserker)
// classMask=1, exclusive.
// makeDruid — 2 entries: DruidShapeshiftForms
// (5 spells: Bear / Cat / Travel /
// Tree of Life / Moonkin) and
// DruidFlightForms (2 spells:
// Flight Form / Swift Flight Form)
// classMask=1024, both exclusive.
// makeAllMutex — 4 entries spanning all classes
// that have mutex groups: Warrior
// stances / Hunter aspects / DK
// presences / Druid forms (one
// representative group per class).
static WoweeCombatManeuvers makeWarrior(const std::string& catalogName);
static WoweeCombatManeuvers makeDruid(const std::string& catalogName);
static WoweeCombatManeuvers makeAllMutex(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee