mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-11 03:23:51 +00:00
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:
parent
c41db0e0be
commit
1c6faa2891
10 changed files with 626 additions and 0 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
100
include/pipeline/wowee_keybindings.hpp
Normal file
100
include/pipeline/wowee_keybindings.hpp
Normal 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
|
||||
243
src/pipeline/wowee_keybindings.cpp
Normal file
243
src/pipeline/wowee_keybindings.cpp
Normal 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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
253
tools/editor/cli_keybindings_catalog.cpp
Normal file
253
tools/editor/cli_keybindings_catalog.cpp
Normal 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
|
||||
11
tools/editor/cli_keybindings_catalog.hpp
Normal file
11
tools/editor/cli_keybindings_catalog.hpp
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue