feat(pipeline): add WKBD (Wowee Keybinding) catalog

55th open format — replaces KeyBinding.dbc plus the
AzerothCore-style default-keybind SQL data. Defines the key
bindings shipped with the game: movement (W/A/S/D),
targeting (Tab), action bars (1-9, 0, -, =), UI panels
(C/I/B/P/N/L), chat (Enter), camera (Insert/Delete).

Each binding has an internal action name (SCREAMING_SNAKE
convention — "MOVE_FORWARD"), a primary key, an optional
alternate key, a category for the keybindings UI grouping,
and a flag indicating whether the user can override it.
Hardcoded engine bindings (alt-F4, ESC) set
isUserOverridable=0 so the rebind dialog can't accidentally
break them.

9 categories (Movement / Combat / Targeting / Camera /
UIPanels / Chat / Macro / Bar / Other) for the rebind
dialog grouping.

CLI: --gen-kbd (3 essential WASD/Tab/C bindings), --gen-kbd-
movement (8 movement: WASD + arrow alternates + jump +
autorun), --gen-kbd-ui (10 UI panel bindings covering all
the standard interface windows), --info-wkbd, --validate-wkbd
with --json variants. Validator catches id+actionName+
defaultKey required, category 0..8, alternateKey ==
defaultKey (no point in alt), action-name lowercase warning
(should be SCREAMING_SNAKE), duplicate primary keys (would
silently shadow earlier binding), and duplicate action names.

Format graph: 54 → 55 binary formats. CLI flag count: 791
→ 796.
This commit is contained in:
Kelsi 2026-05-09 20:13:37 -07:00
parent c41db0e0be
commit 1c6faa2891
10 changed files with 626 additions and 0 deletions

View file

@ -643,6 +643,7 @@ set(WOWEE_SOURCES
src/pipeline/wowee_game_tips.cpp
src/pipeline/wowee_companions.cpp
src/pipeline/wowee_spell_mechanics.cpp
src/pipeline/wowee_keybindings.cpp
src/pipeline/custom_zone_discovery.cpp
src/pipeline/dbc_layout.cpp
@ -1438,6 +1439,7 @@ add_executable(wowee_editor
tools/editor/cli_game_tips_catalog.cpp
tools/editor/cli_companions_catalog.cpp
tools/editor/cli_spell_mechanics_catalog.cpp
tools/editor/cli_keybindings_catalog.cpp
tools/editor/cli_quest_objective.cpp
tools/editor/cli_quest_reward.cpp
tools/editor/cli_clone.cpp
@ -1559,6 +1561,7 @@ add_executable(wowee_editor
src/pipeline/wowee_game_tips.cpp
src/pipeline/wowee_companions.cpp
src/pipeline/wowee_spell_mechanics.cpp
src/pipeline/wowee_keybindings.cpp
src/pipeline/custom_zone_discovery.cpp
src/pipeline/terrain_mesh.cpp

View file

@ -0,0 +1,100 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Keybinding catalog (.wkbd) — novel replacement
// for Blizzard's KeyBinding.dbc plus the AzerothCore-style
// default-keybind SQL data. Defines the key bindings shipped
// with the game: movement (W/A/S/D), targeting (Tab),
// action bars (1-9, 0, -, =), UI panels (C/I/B/P/N/L), chat
// (Enter), camera control (Insert/Delete), macro slots, etc.
//
// Each binding has an internal action name ("MOVE_FORWARD"),
// a primary key, an optional alternate key, a category for
// the keybindings UI grouping, and a flag indicating whether
// the user can override it. Hardcoded engine bindings (alt-
// F4, ESC) set isUserOverridable=0 so the rebind dialog
// can't accidentally break them.
//
// This catalog has no cross-references to other formats —
// it's a self-contained binding map between strings and
// keys, consumed directly by the input layer.
//
// Binary layout (little-endian):
// magic[4] = "WKBD"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// bindingId (uint32)
// actionLen + actionName
// descLen + description
// primaryLen + defaultKey
// altLen + alternateKey
// category (uint8) / isUserOverridable (uint8) /
// sortOrder (uint8) / pad[1]
struct WoweeKeyBinding {
enum Category : uint8_t {
Movement = 0, // WASD, jump, walk
Combat = 1, // attack, spell cast, action bars
Targeting = 2, // tab, focus, assist
Camera = 3, // mouse look, zoom, pitch
UIPanels = 4, // character/inv/bags/spellbook
Chat = 5, // enter, slash, reply
Macro = 6, // macro slots
Bar = 7, // bar shift / page
Other = 8, // misc / scripted
};
struct Entry {
uint32_t bindingId = 0;
std::string actionName; // "MOVE_FORWARD"
std::string description;
std::string defaultKey; // "W"
std::string alternateKey; // "" if none
uint8_t category = Movement;
uint8_t isUserOverridable = 1;
uint8_t sortOrder = 0; // display order within category
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
const Entry* findById(uint32_t bindingId) const;
const Entry* findByActionName(const std::string& actionName) const;
static const char* categoryName(uint8_t c);
};
class WoweeKeyBindingLoader {
public:
static bool save(const WoweeKeyBinding& cat,
const std::string& basePath);
static WoweeKeyBinding load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-kbd* variants.
//
// makeStarter — 3 essential bindings (MOVE_FORWARD,
// TARGET_NEAREST_ENEMY, TOGGLE_CHARACTER).
// makeMovement — 8 movement bindings (4-directional,
// JUMP, TOGGLE_AUTORUN, TOGGLE_WALK,
// SIT_OR_STAND).
// makeUIPanels — 10 UI toggle bindings
// (Character/Inventory/Bags/Spellbook/
// Talents/QuestLog/Friends/Guild/
// MainMenu/Calendar).
static WoweeKeyBinding makeStarter(const std::string& catalogName);
static WoweeKeyBinding makeMovement(const std::string& catalogName);
static WoweeKeyBinding makeUIPanels(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,243 @@
#include "pipeline/wowee_keybindings.hpp"
#include <cstdio>
#include <cstring>
#include <fstream>
namespace wowee {
namespace pipeline {
namespace {
constexpr char kMagic[4] = {'W', 'K', 'B', 'D'};
constexpr uint32_t kVersion = 1;
template <typename T>
void writePOD(std::ofstream& os, const T& v) {
os.write(reinterpret_cast<const char*>(&v), sizeof(T));
}
template <typename T>
bool readPOD(std::ifstream& is, T& v) {
is.read(reinterpret_cast<char*>(&v), sizeof(T));
return is.gcount() == static_cast<std::streamsize>(sizeof(T));
}
void writeStr(std::ofstream& os, const std::string& s) {
uint32_t n = static_cast<uint32_t>(s.size());
writePOD(os, n);
if (n > 0) os.write(s.data(), n);
}
bool readStr(std::ifstream& is, std::string& s) {
uint32_t n = 0;
if (!readPOD(is, n)) return false;
if (n > (1u << 20)) return false;
s.resize(n);
if (n > 0) {
is.read(s.data(), n);
if (is.gcount() != static_cast<std::streamsize>(n)) {
s.clear();
return false;
}
}
return true;
}
std::string normalizePath(std::string base) {
if (base.size() < 5 || base.substr(base.size() - 5) != ".wkbd") {
base += ".wkbd";
}
return base;
}
} // namespace
const WoweeKeyBinding::Entry*
WoweeKeyBinding::findById(uint32_t bindingId) const {
for (const auto& e : entries)
if (e.bindingId == bindingId) return &e;
return nullptr;
}
const WoweeKeyBinding::Entry*
WoweeKeyBinding::findByActionName(const std::string& actionName) const {
for (const auto& e : entries)
if (e.actionName == actionName) return &e;
return nullptr;
}
const char* WoweeKeyBinding::categoryName(uint8_t c) {
switch (c) {
case Movement: return "movement";
case Combat: return "combat";
case Targeting: return "targeting";
case Camera: return "camera";
case UIPanels: return "ui-panels";
case Chat: return "chat";
case Macro: return "macro";
case Bar: return "bar";
case Other: return "other";
default: return "unknown";
}
}
bool WoweeKeyBindingLoader::save(const WoweeKeyBinding& cat,
const std::string& basePath) {
std::ofstream os(normalizePath(basePath), std::ios::binary);
if (!os) return false;
os.write(kMagic, 4);
writePOD(os, kVersion);
writeStr(os, cat.name);
uint32_t entryCount = static_cast<uint32_t>(cat.entries.size());
writePOD(os, entryCount);
for (const auto& e : cat.entries) {
writePOD(os, e.bindingId);
writeStr(os, e.actionName);
writeStr(os, e.description);
writeStr(os, e.defaultKey);
writeStr(os, e.alternateKey);
writePOD(os, e.category);
writePOD(os, e.isUserOverridable);
writePOD(os, e.sortOrder);
uint8_t pad = 0;
writePOD(os, pad);
}
return os.good();
}
WoweeKeyBinding WoweeKeyBindingLoader::load(const std::string& basePath) {
WoweeKeyBinding out;
std::ifstream is(normalizePath(basePath), std::ios::binary);
if (!is) return out;
char magic[4];
is.read(magic, 4);
if (std::memcmp(magic, kMagic, 4) != 0) return out;
uint32_t version = 0;
if (!readPOD(is, version) || version != kVersion) return out;
if (!readStr(is, out.name)) return out;
uint32_t entryCount = 0;
if (!readPOD(is, entryCount)) return out;
if (entryCount > (1u << 20)) return out;
out.entries.resize(entryCount);
for (auto& e : out.entries) {
if (!readPOD(is, e.bindingId)) {
out.entries.clear(); return out;
}
if (!readStr(is, e.actionName) ||
!readStr(is, e.description) ||
!readStr(is, e.defaultKey) ||
!readStr(is, e.alternateKey)) {
out.entries.clear(); return out;
}
if (!readPOD(is, e.category) ||
!readPOD(is, e.isUserOverridable) ||
!readPOD(is, e.sortOrder)) {
out.entries.clear(); return out;
}
uint8_t pad = 0;
if (!readPOD(is, pad)) { out.entries.clear(); return out; }
}
return out;
}
bool WoweeKeyBindingLoader::exists(const std::string& basePath) {
std::ifstream is(normalizePath(basePath), std::ios::binary);
return is.good();
}
WoweeKeyBinding WoweeKeyBindingLoader::makeStarter(
const std::string& catalogName) {
WoweeKeyBinding c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* action, const char* key,
const char* desc, uint8_t cat, uint8_t sort) {
WoweeKeyBinding::Entry e;
e.bindingId = id; e.actionName = action;
e.defaultKey = key; e.description = desc;
e.category = cat; e.sortOrder = sort;
c.entries.push_back(e);
};
add(1, "MOVE_FORWARD", "W",
"Move character forward.",
WoweeKeyBinding::Movement, 0);
add(2, "TARGET_NEAREST_ENEMY", "TAB",
"Cycle to the next enemy in front of you.",
WoweeKeyBinding::Targeting, 0);
add(3, "TOGGLE_CHARACTER", "C",
"Open / close the character pane.",
WoweeKeyBinding::UIPanels, 0);
return c;
}
WoweeKeyBinding WoweeKeyBindingLoader::makeMovement(
const std::string& catalogName) {
WoweeKeyBinding c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* action, const char* key,
const char* alt, const char* desc, uint8_t sort) {
WoweeKeyBinding::Entry e;
e.bindingId = id; e.actionName = action;
e.defaultKey = key; e.alternateKey = alt;
e.description = desc;
e.category = WoweeKeyBinding::Movement;
e.sortOrder = sort;
c.entries.push_back(e);
};
add(10, "MOVE_FORWARD", "W", "UP",
"Move forward.", 0);
add(11, "MOVE_BACKWARD", "S", "DOWN",
"Move backward.", 1);
add(12, "STRAFE_LEFT", "A", "",
"Strafe left (sidestep).", 2);
add(13, "STRAFE_RIGHT", "D", "",
"Strafe right (sidestep).", 3);
add(14, "TURN_LEFT", "LEFT", "Q",
"Turn the character left.", 4);
add(15, "TURN_RIGHT", "RIGHT", "E",
"Turn the character right.", 5);
add(16, "JUMP", "SPACE", "",
"Jump up.", 6);
add(17, "TOGGLE_AUTORUN", "NUMLOCK", "",
"Toggle continuous forward movement.", 7);
return c;
}
WoweeKeyBinding WoweeKeyBindingLoader::makeUIPanels(
const std::string& catalogName) {
WoweeKeyBinding c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* action, const char* key,
const char* desc, uint8_t sort) {
WoweeKeyBinding::Entry e;
e.bindingId = id; e.actionName = action;
e.defaultKey = key; e.description = desc;
e.category = WoweeKeyBinding::UIPanels;
e.sortOrder = sort;
c.entries.push_back(e);
};
add(100, "TOGGLE_CHARACTER", "C",
"Open / close character pane.", 0);
add(101, "TOGGLE_INVENTORY", "I",
"Open / close inventory.", 1);
add(102, "TOGGLE_BAGS", "B",
"Open / close bags (alternative).", 2);
add(103, "TOGGLE_SPELLBOOK", "P",
"Open / close spellbook.", 3);
add(104, "TOGGLE_TALENTS", "N",
"Open / close talent tree.", 4);
add(105, "TOGGLE_QUEST_LOG", "L",
"Open / close quest log.", 5);
add(106, "TOGGLE_FRIENDS", "O",
"Open / close social pane (friends + guild).", 6);
add(107, "TOGGLE_GUILD", "J",
"Open / close guild pane.", 7);
add(108, "TOGGLE_MAIN_MENU", "ESCAPE",
"Open main menu (system + logout).", 8);
add(109, "TOGGLE_CALENDAR", "K",
"Open / close in-game calendar.", 9);
return c;
}
} // namespace pipeline
} // namespace wowee

View file

@ -166,6 +166,8 @@ const char* const kArgRequired[] = {
"--gen-smc", "--gen-smc-hard", "--gen-smc-roots",
"--info-wsmc", "--validate-wsmc",
"--export-wsmc-json", "--import-wsmc-json",
"--gen-kbd", "--gen-kbd-movement", "--gen-kbd-ui",
"--info-wkbd", "--validate-wkbd",
"--gen-weather-temperate", "--gen-weather-arctic",
"--gen-weather-desert", "--gen-weather-stormy",
"--gen-zone-atmosphere",

View file

@ -88,6 +88,7 @@
#include "cli_game_tips_catalog.hpp"
#include "cli_companions_catalog.hpp"
#include "cli_spell_mechanics_catalog.hpp"
#include "cli_keybindings_catalog.hpp"
#include "cli_quest_objective.hpp"
#include "cli_quest_reward.hpp"
#include "cli_clone.hpp"
@ -217,6 +218,7 @@ constexpr DispatchFn kDispatchTable[] = {
handleGameTipsCatalog,
handleCompanionsCatalog,
handleSpellMechanicsCatalog,
handleKeybindingsCatalog,
handleQuestObjective,
handleQuestReward,
handleClone,

View file

@ -57,6 +57,7 @@ constexpr FormatMagicEntry kFormats[] = {
{{'W','G','T','P'}, ".wgtp", "ui", "--info-wgtp", "Game tips / tutorial catalog"},
{{'W','C','M','P'}, ".wcmp", "pets", "--info-wcmp", "Companion / vanity pet catalog"},
{{'W','S','M','C'}, ".wsmc", "spells", "--info-wsmc", "Spell mechanic catalog"},
{{'W','K','B','D'}, ".wkbd", "input", "--info-wkbd", "Keybinding catalog"},
{{'W','F','A','C'}, ".wfac", "factions", nullptr, "Faction catalog"},
{{'W','L','C','K'}, ".wlck", "locks", nullptr, "Lock catalog"},
{{'W','S','K','L'}, ".wskl", "skills", nullptr, "Skill catalog"},

View file

@ -1499,6 +1499,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .wsmc to a human-editable JSON sidecar (defaults to <base>.wsmc.json)\n");
std::printf(" --import-wsmc-json <json-path> [out-base]\n");
std::printf(" Import a .wsmc.json sidecar back into binary .wsmc (accepts drCategory/dispelType int OR name string)\n");
std::printf(" --gen-kbd <wkbd-base> [name]\n");
std::printf(" Emit .wkbd starter: 3 essential bindings (MOVE_FORWARD / TARGET_NEAREST_ENEMY / TOGGLE_CHARACTER)\n");
std::printf(" --gen-kbd-movement <wkbd-base> [name]\n");
std::printf(" Emit .wkbd 8 movement bindings (WASD / arrow keys / jump / autorun) with primary+alternate keys\n");
std::printf(" --gen-kbd-ui <wkbd-base> [name]\n");
std::printf(" Emit .wkbd 10 UI panel bindings (Character C / Inventory I / Bags B / Spellbook P / Talents N / etc)\n");
std::printf(" --info-wkbd <wkbd-base> [--json]\n");
std::printf(" Print WKBD entries (id / category / user-overridable / sort / default key + alt / actionName)\n");
std::printf(" --validate-wkbd <wkbd-base> [--json]\n");
std::printf(" Static checks: id+actionName+defaultKey required, category 0..8, alt!=default, unique action names + primary keys\n");
std::printf(" --gen-weather-temperate <wow-base> [zoneName]\n");
std::printf(" Emit .wow weather schedule: clear-dominant + occasional rain + fog (forest / grassland)\n");
std::printf(" --gen-weather-arctic <wow-base> [zoneName]\n");

View file

@ -0,0 +1,253 @@
#include "cli_keybindings_catalog.hpp"
#include "cli_arg_parse.hpp"
#include "cli_box_emitter.hpp"
#include "pipeline/wowee_keybindings.hpp"
#include <nlohmann/json.hpp>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <set>
#include <string>
#include <vector>
namespace wowee {
namespace editor {
namespace cli {
namespace {
std::string stripWkbdExt(std::string base) {
stripExt(base, ".wkbd");
return base;
}
bool saveOrError(const wowee::pipeline::WoweeKeyBinding& c,
const std::string& base, const char* cmd) {
if (!wowee::pipeline::WoweeKeyBindingLoader::save(c, base)) {
std::fprintf(stderr, "%s: failed to save %s.wkbd\n",
cmd, base.c_str());
return false;
}
return true;
}
void printGenSummary(const wowee::pipeline::WoweeKeyBinding& c,
const std::string& base) {
std::printf("Wrote %s.wkbd\n", base.c_str());
std::printf(" catalog : %s\n", c.name.c_str());
std::printf(" bindings : %zu\n", c.entries.size());
}
int handleGenStarter(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "StarterKeybindings";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWkbdExt(base);
auto c = wowee::pipeline::WoweeKeyBindingLoader::makeStarter(name);
if (!saveOrError(c, base, "gen-kbd")) return 1;
printGenSummary(c, base);
return 0;
}
int handleGenMovement(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "MovementKeybindings";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWkbdExt(base);
auto c = wowee::pipeline::WoweeKeyBindingLoader::makeMovement(name);
if (!saveOrError(c, base, "gen-kbd-movement")) return 1;
printGenSummary(c, base);
return 0;
}
int handleGenUI(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "UIPanelKeybindings";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWkbdExt(base);
auto c = wowee::pipeline::WoweeKeyBindingLoader::makeUIPanels(name);
if (!saveOrError(c, base, "gen-kbd-ui")) return 1;
printGenSummary(c, base);
return 0;
}
int handleInfo(int& i, int argc, char** argv) {
std::string base = argv[++i];
bool jsonOut = consumeJsonFlag(i, argc, argv);
base = stripWkbdExt(base);
if (!wowee::pipeline::WoweeKeyBindingLoader::exists(base)) {
std::fprintf(stderr, "WKBD not found: %s.wkbd\n", base.c_str());
return 1;
}
auto c = wowee::pipeline::WoweeKeyBindingLoader::load(base);
if (jsonOut) {
nlohmann::json j;
j["wkbd"] = base + ".wkbd";
j["name"] = c.name;
j["count"] = c.entries.size();
nlohmann::json arr = nlohmann::json::array();
for (const auto& e : c.entries) {
arr.push_back({
{"bindingId", e.bindingId},
{"actionName", e.actionName},
{"description", e.description},
{"defaultKey", e.defaultKey},
{"alternateKey", e.alternateKey},
{"category", e.category},
{"categoryName", wowee::pipeline::WoweeKeyBinding::categoryName(e.category)},
{"isUserOverridable", e.isUserOverridable},
{"sortOrder", e.sortOrder},
});
}
j["entries"] = arr;
std::printf("%s\n", j.dump(2).c_str());
return 0;
}
std::printf("WKBD: %s.wkbd\n", base.c_str());
std::printf(" catalog : %s\n", c.name.c_str());
std::printf(" bindings : %zu\n", c.entries.size());
if (c.entries.empty()) return 0;
std::printf(" id category user sort default alt actionName\n");
for (const auto& e : c.entries) {
std::printf(" %4u %-9s %u %3u %-12s %-10s %s\n",
e.bindingId,
wowee::pipeline::WoweeKeyBinding::categoryName(e.category),
e.isUserOverridable, e.sortOrder,
e.defaultKey.c_str(),
e.alternateKey.empty() ? "-" : e.alternateKey.c_str(),
e.actionName.c_str());
}
return 0;
}
int handleValidate(int& i, int argc, char** argv) {
std::string base = argv[++i];
bool jsonOut = consumeJsonFlag(i, argc, argv);
base = stripWkbdExt(base);
if (!wowee::pipeline::WoweeKeyBindingLoader::exists(base)) {
std::fprintf(stderr,
"validate-wkbd: WKBD not found: %s.wkbd\n", base.c_str());
return 1;
}
auto c = wowee::pipeline::WoweeKeyBindingLoader::load(base);
std::vector<std::string> errors;
std::vector<std::string> warnings;
if (c.entries.empty()) {
warnings.push_back("catalog has zero entries");
}
std::vector<uint32_t> idsSeen;
std::set<std::string> actionsSeen;
std::set<std::string> primaryKeysSeen;
for (size_t k = 0; k < c.entries.size(); ++k) {
const auto& e = c.entries[k];
std::string ctx = "entry " + std::to_string(k) +
" (id=" + std::to_string(e.bindingId);
if (!e.actionName.empty()) ctx += " " + e.actionName;
ctx += ")";
if (e.bindingId == 0)
errors.push_back(ctx + ": bindingId is 0");
if (e.actionName.empty())
errors.push_back(ctx + ": actionName is empty");
if (e.defaultKey.empty())
errors.push_back(ctx + ": defaultKey is empty");
if (e.category > wowee::pipeline::WoweeKeyBinding::Other) {
errors.push_back(ctx + ": category " +
std::to_string(e.category) + " not in 0..8");
}
if (e.alternateKey == e.defaultKey && !e.defaultKey.empty()) {
errors.push_back(ctx +
": alternateKey == defaultKey (no point in alt)");
}
// Action name should be SCREAMING_SNAKE — anything
// with lowercase is suspect.
for (char ch : e.actionName) {
if (ch >= 'a' && ch <= 'z') {
warnings.push_back(ctx +
": actionName contains lowercase chars "
"(convention is SCREAMING_SNAKE)");
break;
}
}
// Duplicate primary keys would conflict at runtime —
// last one binding loaded wins, leaving the first
// silently shadowed.
if (!e.defaultKey.empty()) {
if (primaryKeysSeen.count(e.defaultKey)) {
errors.push_back(ctx + ": defaultKey '" +
e.defaultKey +
"' already bound by an earlier entry "
"(would shadow that binding)");
}
primaryKeysSeen.insert(e.defaultKey);
}
if (!e.actionName.empty()) {
if (actionsSeen.count(e.actionName)) {
errors.push_back(ctx +
": duplicate actionName '" + e.actionName + "'");
}
actionsSeen.insert(e.actionName);
}
for (uint32_t prev : idsSeen) {
if (prev == e.bindingId) {
errors.push_back(ctx + ": duplicate bindingId");
break;
}
}
idsSeen.push_back(e.bindingId);
}
bool ok = errors.empty();
if (jsonOut) {
nlohmann::json j;
j["wkbd"] = base + ".wkbd";
j["ok"] = ok;
j["errors"] = errors;
j["warnings"] = warnings;
std::printf("%s\n", j.dump(2).c_str());
return ok ? 0 : 1;
}
std::printf("validate-wkbd: %s.wkbd\n", base.c_str());
if (ok && warnings.empty()) {
std::printf(" OK — %zu bindings, all bindingIds unique, "
"no key conflicts\n", c.entries.size());
return 0;
}
if (!warnings.empty()) {
std::printf(" warnings (%zu):\n", warnings.size());
for (const auto& w : warnings)
std::printf(" - %s\n", w.c_str());
}
if (!errors.empty()) {
std::printf(" ERRORS (%zu):\n", errors.size());
for (const auto& e : errors)
std::printf(" - %s\n", e.c_str());
}
return ok ? 0 : 1;
}
} // namespace
bool handleKeybindingsCatalog(int& i, int argc, char** argv, int& outRc) {
if (std::strcmp(argv[i], "--gen-kbd") == 0 && i + 1 < argc) {
outRc = handleGenStarter(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-kbd-movement") == 0 && i + 1 < argc) {
outRc = handleGenMovement(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-kbd-ui") == 0 && i + 1 < argc) {
outRc = handleGenUI(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--info-wkbd") == 0 && i + 1 < argc) {
outRc = handleInfo(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--validate-wkbd") == 0 && i + 1 < argc) {
outRc = handleValidate(i, argc, argv); return true;
}
return false;
}
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -0,0 +1,11 @@
#pragma once
namespace wowee {
namespace editor {
namespace cli {
bool handleKeybindingsCatalog(int& i, int argc, char** argv, int& outRc);
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -79,6 +79,7 @@ constexpr FormatRow kFormats[] = {
{"WGTP", ".wgtp", "ui", "GameTips.dbc + tutorial hints", "Game tips / tutorial / loading-screen catalog"},
{"WCMP", ".wcmp", "pets", "CreatureFamily + companion SQL", "Companion / vanity pet catalog"},
{"WSMC", ".wsmc", "spells", "SpellMechanic.dbc + DR tables", "Spell mechanic / CC category catalog"},
{"WKBD", ".wkbd", "input", "KeyBinding.dbc + default binds", "Default keybinding catalog"},
// Additional pipeline catalogs without the alternating
// gen/info/validate CLI surface (loaded by the engine