diff --git a/CMakeLists.txt b/CMakeLists.txt index 6463c915..84ae3268 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/include/pipeline/wowee_keybindings.hpp b/include/pipeline/wowee_keybindings.hpp new file mode 100644 index 00000000..71250bb6 --- /dev/null +++ b/include/pipeline/wowee_keybindings.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include + +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 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 diff --git a/src/pipeline/wowee_keybindings.cpp b/src/pipeline/wowee_keybindings.cpp new file mode 100644 index 00000000..8afb3cf8 --- /dev/null +++ b/src/pipeline/wowee_keybindings.cpp @@ -0,0 +1,243 @@ +#include "pipeline/wowee_keybindings.hpp" + +#include +#include +#include + +namespace wowee { +namespace pipeline { + +namespace { + +constexpr char kMagic[4] = {'W', 'K', 'B', 'D'}; +constexpr uint32_t kVersion = 1; + +template +void writePOD(std::ofstream& os, const T& v) { + os.write(reinterpret_cast(&v), sizeof(T)); +} + +template +bool readPOD(std::ifstream& is, T& v) { + is.read(reinterpret_cast(&v), sizeof(T)); + return is.gcount() == static_cast(sizeof(T)); +} + +void writeStr(std::ofstream& os, const std::string& s) { + uint32_t n = static_cast(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(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(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 diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 4a692005..3a79288b 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -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", diff --git a/tools/editor/cli_dispatch.cpp b/tools/editor/cli_dispatch.cpp index 7031abd8..c6dbdfff 100644 --- a/tools/editor/cli_dispatch.cpp +++ b/tools/editor/cli_dispatch.cpp @@ -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, diff --git a/tools/editor/cli_format_table.cpp b/tools/editor/cli_format_table.cpp index 842d5929..934cc772 100644 --- a/tools/editor/cli_format_table.cpp +++ b/tools/editor/cli_format_table.cpp @@ -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"}, diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 083d1065..cbbdeda9 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -1499,6 +1499,16 @@ void printUsage(const char* argv0) { std::printf(" Export binary .wsmc to a human-editable JSON sidecar (defaults to .wsmc.json)\n"); std::printf(" --import-wsmc-json [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 [name]\n"); + std::printf(" Emit .wkbd starter: 3 essential bindings (MOVE_FORWARD / TARGET_NEAREST_ENEMY / TOGGLE_CHARACTER)\n"); + std::printf(" --gen-kbd-movement [name]\n"); + std::printf(" Emit .wkbd 8 movement bindings (WASD / arrow keys / jump / autorun) with primary+alternate keys\n"); + std::printf(" --gen-kbd-ui [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 [--json]\n"); + std::printf(" Print WKBD entries (id / category / user-overridable / sort / default key + alt / actionName)\n"); + std::printf(" --validate-wkbd [--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 [zoneName]\n"); std::printf(" Emit .wow weather schedule: clear-dominant + occasional rain + fog (forest / grassland)\n"); std::printf(" --gen-weather-arctic [zoneName]\n"); diff --git a/tools/editor/cli_keybindings_catalog.cpp b/tools/editor/cli_keybindings_catalog.cpp new file mode 100644 index 00000000..b13c3f4f --- /dev/null +++ b/tools/editor/cli_keybindings_catalog.cpp @@ -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 + +#include +#include +#include +#include +#include +#include +#include + +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 errors; + std::vector warnings; + if (c.entries.empty()) { + warnings.push_back("catalog has zero entries"); + } + std::vector idsSeen; + std::set actionsSeen; + std::set 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 diff --git a/tools/editor/cli_keybindings_catalog.hpp b/tools/editor/cli_keybindings_catalog.hpp new file mode 100644 index 00000000..43a16ce3 --- /dev/null +++ b/tools/editor/cli_keybindings_catalog.hpp @@ -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 diff --git a/tools/editor/cli_list_formats.cpp b/tools/editor/cli_list_formats.cpp index 14c9ccab..2db4d76e 100644 --- a/tools/editor/cli_list_formats.cpp +++ b/tools/editor/cli_list_formats.cpp @@ -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