diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b33fbf9..4c6d9d0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -620,6 +620,7 @@ set(WOWEE_SOURCES src/pipeline/wowee_titles.cpp src/pipeline/wowee_events.cpp src/pipeline/wowee_mounts.cpp + src/pipeline/wowee_battlegrounds.cpp src/pipeline/custom_zone_discovery.cpp src/pipeline/dbc_layout.cpp @@ -1386,6 +1387,7 @@ add_executable(wowee_editor tools/editor/cli_titles_catalog.cpp tools/editor/cli_events_catalog.cpp tools/editor/cli_mounts_catalog.cpp + tools/editor/cli_battlegrounds_catalog.cpp tools/editor/cli_quest_objective.cpp tools/editor/cli_quest_reward.cpp tools/editor/cli_clone.cpp @@ -1484,6 +1486,7 @@ add_executable(wowee_editor src/pipeline/wowee_titles.cpp src/pipeline/wowee_events.cpp src/pipeline/wowee_mounts.cpp + src/pipeline/wowee_battlegrounds.cpp src/pipeline/custom_zone_discovery.cpp src/pipeline/terrain_mesh.cpp diff --git a/include/pipeline/wowee_battlegrounds.hpp b/include/pipeline/wowee_battlegrounds.hpp new file mode 100644 index 00000000..95ce7f43 --- /dev/null +++ b/include/pipeline/wowee_battlegrounds.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include +#include +#include +#include + +namespace wowee { +namespace pipeline { + +// Wowee Open Battleground Definition catalog (.wbgd) — +// novel replacement for Blizzard's BattlemasterList.dbc + +// PvpDifficulty.dbc + the AzerothCore-style +// battleground_template SQL tables. The 33rd open format +// added to the editor. +// +// Defines per-BG gameplay rules: player count brackets, +// score-to-win, time limit, objective type (annihilation / +// capture flag / control nodes / king of hill / resource +// race / carry object), per-team start positions, respawn +// timer, and the WTKN currency token awarded on win. +// +// Cross-references with previously-added formats: +// WBGD.entry.mapId → WMS.map.mapId +// (where mapType=Battleground) +// WBGD.entry.markTokenId → WTKN.entry.tokenId +// (Mark of Honor for that BG) +// +// Binary layout (little-endian): +// magic[4] = "WBGD" +// version (uint32) = current 1 +// nameLen + name (catalog label) +// entryCount (uint32) +// entries (each): +// battlegroundId (uint32) +// mapId (uint32) +// nameLen + name +// descLen + description +// objectiveKind (uint8) +// minPlayersPerSide (uint8) / maxPlayersPerSide (uint8) / pad[1] +// minLevel (uint16) / maxLevel (uint16) +// scoreToWin (uint16) / timeLimitSeconds (uint16) +// bracketSize (uint8) / pad[3] +// allianceStart (3 × float) / allianceFacing (float) +// hordeStart (3 × float) / hordeFacing (float) +// respawnTimeSeconds (uint16) / pad[2] +// markTokenId (uint32) +struct WoweeBattleground { + enum ObjectiveKind : uint8_t { + Annihilation = 0, // wipe the opposing team + CaptureFlag = 1, // CTF — Warsong Gulch + ControlNodes = 2, // node capture — Arathi Basin / Eye of the Storm + KingOfHill = 3, // hold a single point + ResourceRace = 4, // Alterac Valley (general/wing-tower scoring) + CarryObject = 5, // carry a relic / orb — Eye of the Storm flag + }; + + struct Entry { + uint32_t battlegroundId = 0; + uint32_t mapId = 0; + std::string name; + std::string description; + uint8_t objectiveKind = Annihilation; + uint8_t minPlayersPerSide = 5; + uint8_t maxPlayersPerSide = 10; + uint16_t minLevel = 10; + uint16_t maxLevel = 80; + uint16_t scoreToWin = 3; + uint16_t timeLimitSeconds = 1800; // 30 min default + uint8_t bracketSize = 10; // 10-level brackets + glm::vec3 allianceStart{0}; + float allianceFacing = 0.0f; + glm::vec3 hordeStart{0}; + float hordeFacing = 0.0f; + uint16_t respawnTimeSeconds = 30; + uint32_t markTokenId = 0; // WTKN cross-ref + }; + + std::string name; + std::vector entries; + + bool isValid() const { return !entries.empty(); } + + const Entry* findById(uint32_t bgId) const; + + static const char* objectiveKindName(uint8_t k); +}; + +class WoweeBattlegroundLoader { +public: + static bool save(const WoweeBattleground& cat, + const std::string& basePath); + static WoweeBattleground load(const std::string& basePath); + static bool exists(const std::string& basePath); + + // Preset emitters used by --gen-bg* variants. + // + // makeStarter — 1 simple king-of-hill BG (10v10, 3-cap + // to win, 30 min time limit). + // makeClassic — Warsong Gulch (CTF) + Arathi Basin + // (control nodes) + Alterac Valley + // (resource race), each with WMS+WTKN + // cross-references and authentic player + // counts (10 / 15 / 40 per side). + // makeArena — 3 arena formats: 2v2 / 3v3 / 5v5 with + // small map IDs and annihilation + // objectives. + static WoweeBattleground makeStarter(const std::string& catalogName); + static WoweeBattleground makeClassic(const std::string& catalogName); + static WoweeBattleground makeArena(const std::string& catalogName); +}; + +} // namespace pipeline +} // namespace wowee diff --git a/src/pipeline/wowee_battlegrounds.cpp b/src/pipeline/wowee_battlegrounds.cpp new file mode 100644 index 00000000..98fc8346 --- /dev/null +++ b/src/pipeline/wowee_battlegrounds.cpp @@ -0,0 +1,267 @@ +#include "pipeline/wowee_battlegrounds.hpp" + +#include +#include +#include + +namespace wowee { +namespace pipeline { + +namespace { + +constexpr char kMagic[4] = {'W', 'B', 'G', '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) != ".wbgd") { + base += ".wbgd"; + } + return base; +} + +} // namespace + +const WoweeBattleground::Entry* +WoweeBattleground::findById(uint32_t bgId) const { + for (const auto& e : entries) if (e.battlegroundId == bgId) return &e; + return nullptr; +} + +const char* WoweeBattleground::objectiveKindName(uint8_t k) { + switch (k) { + case Annihilation: return "annihilation"; + case CaptureFlag: return "ctf"; + case ControlNodes: return "nodes"; + case KingOfHill: return "koh"; + case ResourceRace: return "resource-race"; + case CarryObject: return "carry-object"; + default: return "unknown"; + } +} + +bool WoweeBattlegroundLoader::save(const WoweeBattleground& 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.battlegroundId); + writePOD(os, e.mapId); + writeStr(os, e.name); + writeStr(os, e.description); + writePOD(os, e.objectiveKind); + writePOD(os, e.minPlayersPerSide); + writePOD(os, e.maxPlayersPerSide); + uint8_t pad1 = 0; + writePOD(os, pad1); + writePOD(os, e.minLevel); + writePOD(os, e.maxLevel); + writePOD(os, e.scoreToWin); + writePOD(os, e.timeLimitSeconds); + writePOD(os, e.bracketSize); + uint8_t pad3[3] = {0, 0, 0}; + os.write(reinterpret_cast(pad3), 3); + writePOD(os, e.allianceStart.x); + writePOD(os, e.allianceStart.y); + writePOD(os, e.allianceStart.z); + writePOD(os, e.allianceFacing); + writePOD(os, e.hordeStart.x); + writePOD(os, e.hordeStart.y); + writePOD(os, e.hordeStart.z); + writePOD(os, e.hordeFacing); + writePOD(os, e.respawnTimeSeconds); + os.write(reinterpret_cast(pad3), 2); + writePOD(os, e.markTokenId); + } + return os.good(); +} + +WoweeBattleground WoweeBattlegroundLoader::load(const std::string& basePath) { + WoweeBattleground 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.battlegroundId) || + !readPOD(is, e.mapId)) { + out.entries.clear(); return out; + } + if (!readStr(is, e.name) || !readStr(is, e.description)) { + out.entries.clear(); return out; + } + if (!readPOD(is, e.objectiveKind) || + !readPOD(is, e.minPlayersPerSide) || + !readPOD(is, e.maxPlayersPerSide)) { + out.entries.clear(); return out; + } + uint8_t pad1 = 0; + if (!readPOD(is, pad1)) { + out.entries.clear(); return out; + } + if (!readPOD(is, e.minLevel) || + !readPOD(is, e.maxLevel) || + !readPOD(is, e.scoreToWin) || + !readPOD(is, e.timeLimitSeconds) || + !readPOD(is, e.bracketSize)) { + out.entries.clear(); return out; + } + uint8_t pad3[3]; + is.read(reinterpret_cast(pad3), 3); + if (is.gcount() != 3) { out.entries.clear(); return out; } + if (!readPOD(is, e.allianceStart.x) || + !readPOD(is, e.allianceStart.y) || + !readPOD(is, e.allianceStart.z) || + !readPOD(is, e.allianceFacing) || + !readPOD(is, e.hordeStart.x) || + !readPOD(is, e.hordeStart.y) || + !readPOD(is, e.hordeStart.z) || + !readPOD(is, e.hordeFacing) || + !readPOD(is, e.respawnTimeSeconds)) { + out.entries.clear(); return out; + } + is.read(reinterpret_cast(pad3), 2); + if (is.gcount() != 2) { out.entries.clear(); return out; } + if (!readPOD(is, e.markTokenId)) { + out.entries.clear(); return out; + } + } + return out; +} + +bool WoweeBattlegroundLoader::exists(const std::string& basePath) { + std::ifstream is(normalizePath(basePath), std::ios::binary); + return is.good(); +} + +WoweeBattleground WoweeBattlegroundLoader::makeStarter(const std::string& catalogName) { + WoweeBattleground c; + c.name = catalogName; + { + WoweeBattleground::Entry e; + e.battlegroundId = 1; e.mapId = 0; + e.name = "Mountain Crown"; + e.description = "Hold the central peak. Three captures to win."; + e.objectiveKind = WoweeBattleground::KingOfHill; + e.minPlayersPerSide = 5; e.maxPlayersPerSide = 10; + e.scoreToWin = 3; e.timeLimitSeconds = 1800; + e.allianceStart = {-100.0f, 50.0f, 0.0f}; e.allianceFacing = 0.0f; + e.hordeStart = {100.0f, 50.0f, 0.0f}; e.hordeFacing = 3.14159265f; + c.entries.push_back(e); + } + return c; +} + +WoweeBattleground WoweeBattlegroundLoader::makeClassic(const std::string& catalogName) { + WoweeBattleground c; + c.name = catalogName; + { + WoweeBattleground::Entry e; + e.battlegroundId = 489; e.mapId = 489; + e.name = "Warsong Gulch"; + e.description = "Capture the enemy flag and return it 3 times."; + e.objectiveKind = WoweeBattleground::CaptureFlag; + e.minPlayersPerSide = 8; e.maxPlayersPerSide = 10; + e.minLevel = 10; e.maxLevel = 80; + e.scoreToWin = 3; e.timeLimitSeconds = 1800; + // markTokenId 102 matches WTKN.makePvp's + // "Mark of Honor: Warsong Gulch". + e.markTokenId = 102; + c.entries.push_back(e); + } + { + WoweeBattleground::Entry e; + e.battlegroundId = 529; e.mapId = 529; + e.name = "Arathi Basin"; + e.description = "Control 5 nodes to harvest 1600 resources."; + e.objectiveKind = WoweeBattleground::ControlNodes; + e.minPlayersPerSide = 12; e.maxPlayersPerSide = 15; + e.minLevel = 20; e.maxLevel = 80; + e.scoreToWin = 1600; e.timeLimitSeconds = 1500; + e.markTokenId = 103; + c.entries.push_back(e); + } + { + WoweeBattleground::Entry e; + e.battlegroundId = 30; e.mapId = 30; + e.name = "Alterac Valley"; + e.description = "Eliminate the opposing General. Reinforcements: 600."; + e.objectiveKind = WoweeBattleground::ResourceRace; + e.minPlayersPerSide = 30; e.maxPlayersPerSide = 40; + e.minLevel = 51; e.maxLevel = 80; + e.scoreToWin = 600; e.timeLimitSeconds = 0; // no time limit + e.markTokenId = 104; + c.entries.push_back(e); + } + return c; +} + +WoweeBattleground WoweeBattlegroundLoader::makeArena(const std::string& catalogName) { + WoweeBattleground c; + c.name = catalogName; + auto add = [&](uint32_t bgId, uint32_t mapId, const char* name, + uint8_t minPlayers, uint8_t maxPlayers) { + WoweeBattleground::Entry e; + e.battlegroundId = bgId; e.mapId = mapId; + e.name = name; + e.description = "Annihilation arena."; + e.objectiveKind = WoweeBattleground::Annihilation; + e.minPlayersPerSide = minPlayers; e.maxPlayersPerSide = maxPlayers; + e.minLevel = 80; e.maxLevel = 80; + e.scoreToWin = 1; e.timeLimitSeconds = 1500; // 25 min cap + e.bracketSize = 1; + e.respawnTimeSeconds = 0; // no respawn + c.entries.push_back(e); + }; + add(559, 559, "Nagrand Arena (2v2)", 2, 2); + add(562, 562, "Blade's Edge Arena (3v3)", 3, 3); + add(572, 572, "Ruins of Lordaeron (5v5)", 5, 5); + return c; +} + +} // namespace pipeline +} // namespace wowee diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index c82270ec..a940df73 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -94,6 +94,8 @@ const char* const kArgRequired[] = { "--export-wsea-json", "--import-wsea-json", "--gen-mounts", "--gen-mounts-racial", "--gen-mounts-flying", "--info-wmou", "--validate-wmou", + "--gen-bg", "--gen-bg-classic", "--gen-bg-arena", + "--info-wbgd", "--validate-wbgd", "--gen-weather-temperate", "--gen-weather-arctic", "--gen-weather-desert", "--gen-weather-stormy", "--gen-zone-atmosphere", diff --git a/tools/editor/cli_battlegrounds_catalog.cpp b/tools/editor/cli_battlegrounds_catalog.cpp new file mode 100644 index 00000000..c8a4fd20 --- /dev/null +++ b/tools/editor/cli_battlegrounds_catalog.cpp @@ -0,0 +1,250 @@ +#include "cli_battlegrounds_catalog.hpp" +#include "cli_arg_parse.hpp" +#include "cli_box_emitter.hpp" + +#include "pipeline/wowee_battlegrounds.hpp" +#include + +#include +#include +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +namespace { + +std::string stripWbgdExt(std::string base) { + stripExt(base, ".wbgd"); + return base; +} + +bool saveOrError(const wowee::pipeline::WoweeBattleground& c, + const std::string& base, const char* cmd) { + if (!wowee::pipeline::WoweeBattlegroundLoader::save(c, base)) { + std::fprintf(stderr, "%s: failed to save %s.wbgd\n", + cmd, base.c_str()); + return false; + } + return true; +} + +void printGenSummary(const wowee::pipeline::WoweeBattleground& c, + const std::string& base) { + std::printf("Wrote %s.wbgd\n", base.c_str()); + std::printf(" catalog : %s\n", c.name.c_str()); + std::printf(" bgs : %zu\n", c.entries.size()); +} + +int handleGenStarter(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "StarterBgs"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWbgdExt(base); + auto c = wowee::pipeline::WoweeBattlegroundLoader::makeStarter(name); + if (!saveOrError(c, base, "gen-bg")) return 1; + printGenSummary(c, base); + return 0; +} + +int handleGenClassic(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "ClassicBgs"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWbgdExt(base); + auto c = wowee::pipeline::WoweeBattlegroundLoader::makeClassic(name); + if (!saveOrError(c, base, "gen-bg-classic")) return 1; + printGenSummary(c, base); + return 0; +} + +int handleGenArena(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "ArenaSet"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWbgdExt(base); + auto c = wowee::pipeline::WoweeBattlegroundLoader::makeArena(name); + if (!saveOrError(c, base, "gen-bg-arena")) 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 = stripWbgdExt(base); + if (!wowee::pipeline::WoweeBattlegroundLoader::exists(base)) { + std::fprintf(stderr, "WBGD not found: %s.wbgd\n", base.c_str()); + return 1; + } + auto c = wowee::pipeline::WoweeBattlegroundLoader::load(base); + if (jsonOut) { + nlohmann::json j; + j["wbgd"] = base + ".wbgd"; + j["name"] = c.name; + j["count"] = c.entries.size(); + nlohmann::json arr = nlohmann::json::array(); + for (const auto& e : c.entries) { + arr.push_back({ + {"battlegroundId", e.battlegroundId}, + {"mapId", e.mapId}, + {"name", e.name}, + {"description", e.description}, + {"objectiveKind", e.objectiveKind}, + {"objectiveKindName", wowee::pipeline::WoweeBattleground::objectiveKindName(e.objectiveKind)}, + {"minPlayersPerSide", e.minPlayersPerSide}, + {"maxPlayersPerSide", e.maxPlayersPerSide}, + {"minLevel", e.minLevel}, + {"maxLevel", e.maxLevel}, + {"scoreToWin", e.scoreToWin}, + {"timeLimitSeconds", e.timeLimitSeconds}, + {"bracketSize", e.bracketSize}, + {"allianceStart", {e.allianceStart.x, e.allianceStart.y, e.allianceStart.z}}, + {"allianceFacing", e.allianceFacing}, + {"hordeStart", {e.hordeStart.x, e.hordeStart.y, e.hordeStart.z}}, + {"hordeFacing", e.hordeFacing}, + {"respawnTimeSeconds", e.respawnTimeSeconds}, + {"markTokenId", e.markTokenId}, + }); + } + j["entries"] = arr; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("WBGD: %s.wbgd\n", base.c_str()); + std::printf(" catalog : %s\n", c.name.c_str()); + std::printf(" bgs : %zu\n", c.entries.size()); + if (c.entries.empty()) return 0; + for (const auto& e : c.entries) { + std::printf("\n bgId=%u map=%u objective=%s vs%uv%u level=%u-%u\n", + e.battlegroundId, e.mapId, + wowee::pipeline::WoweeBattleground::objectiveKindName(e.objectiveKind), + e.minPlayersPerSide, e.maxPlayersPerSide, + e.minLevel, e.maxLevel); + std::printf(" name : %s\n", e.name.c_str()); + std::printf(" score : %u to win / %us time limit\n", + e.scoreToWin, e.timeLimitSeconds); + std::printf(" respawn : %us bracket: %u levels markToken: %u\n", + e.respawnTimeSeconds, e.bracketSize, e.markTokenId); + } + return 0; +} + +int handleValidate(int& i, int argc, char** argv) { + std::string base = argv[++i]; + bool jsonOut = consumeJsonFlag(i, argc, argv); + base = stripWbgdExt(base); + if (!wowee::pipeline::WoweeBattlegroundLoader::exists(base)) { + std::fprintf(stderr, + "validate-wbgd: WBGD not found: %s.wbgd\n", base.c_str()); + return 1; + } + auto c = wowee::pipeline::WoweeBattlegroundLoader::load(base); + std::vector errors; + std::vector warnings; + if (c.entries.empty()) { + warnings.push_back("catalog has zero entries"); + } + std::vector idsSeen; + for (size_t k = 0; k < c.entries.size(); ++k) { + const auto& e = c.entries[k]; + std::string ctx = "entry " + std::to_string(k) + + " (bgId=" + std::to_string(e.battlegroundId); + if (!e.name.empty()) ctx += " " + e.name; + ctx += ")"; + if (e.battlegroundId == 0) { + errors.push_back(ctx + ": battlegroundId is 0"); + } + if (e.name.empty()) errors.push_back(ctx + ": name is empty"); + if (e.objectiveKind > wowee::pipeline::WoweeBattleground::CarryObject) { + errors.push_back(ctx + ": objectiveKind " + + std::to_string(e.objectiveKind) + " not in 0..5"); + } + if (e.minPlayersPerSide == 0 || e.maxPlayersPerSide == 0) { + errors.push_back(ctx + ": player count is 0"); + } + if (e.minPlayersPerSide > e.maxPlayersPerSide) { + errors.push_back(ctx + + ": minPlayersPerSide > maxPlayersPerSide"); + } + if (e.minLevel > e.maxLevel) { + errors.push_back(ctx + ": minLevel > maxLevel"); + } + if (e.scoreToWin == 0) { + errors.push_back(ctx + + ": scoreToWin is 0 (no win condition)"); + } + // Annihilation BGs typically have respawnTimeSeconds=0 + // (no respawn until match ends). Other kinds need + // respawn > 0 or the losing side can't recover. + if (e.objectiveKind != wowee::pipeline::WoweeBattleground::Annihilation && + e.respawnTimeSeconds == 0) { + warnings.push_back(ctx + + ": non-annihilation BG with respawnTimeSeconds=0 " + "(losing side cannot recover)"); + } + for (uint32_t prev : idsSeen) { + if (prev == e.battlegroundId) { + errors.push_back(ctx + ": duplicate battlegroundId"); + break; + } + } + idsSeen.push_back(e.battlegroundId); + } + bool ok = errors.empty(); + if (jsonOut) { + nlohmann::json j; + j["wbgd"] = base + ".wbgd"; + 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-wbgd: %s.wbgd\n", base.c_str()); + if (ok && warnings.empty()) { + std::printf(" OK — %zu battlegrounds, all bgIds unique\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 handleBattlegroundsCatalog(int& i, int argc, char** argv, int& outRc) { + if (std::strcmp(argv[i], "--gen-bg") == 0 && i + 1 < argc) { + outRc = handleGenStarter(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--gen-bg-classic") == 0 && i + 1 < argc) { + outRc = handleGenClassic(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--gen-bg-arena") == 0 && i + 1 < argc) { + outRc = handleGenArena(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--info-wbgd") == 0 && i + 1 < argc) { + outRc = handleInfo(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--validate-wbgd") == 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_battlegrounds_catalog.hpp b/tools/editor/cli_battlegrounds_catalog.hpp new file mode 100644 index 00000000..5b81e87d --- /dev/null +++ b/tools/editor/cli_battlegrounds_catalog.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace wowee { +namespace editor { +namespace cli { + +bool handleBattlegroundsCatalog(int& i, int argc, char** argv, int& outRc); + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/cli_dispatch.cpp b/tools/editor/cli_dispatch.cpp index 9ebc6727..d96f3e29 100644 --- a/tools/editor/cli_dispatch.cpp +++ b/tools/editor/cli_dispatch.cpp @@ -60,6 +60,7 @@ #include "cli_titles_catalog.hpp" #include "cli_events_catalog.hpp" #include "cli_mounts_catalog.hpp" +#include "cli_battlegrounds_catalog.hpp" #include "cli_quest_objective.hpp" #include "cli_quest_reward.hpp" #include "cli_clone.hpp" @@ -161,6 +162,7 @@ constexpr DispatchFn kDispatchTable[] = { handleTitlesCatalog, handleEventsCatalog, handleMountsCatalog, + handleBattlegroundsCatalog, handleQuestObjective, handleQuestReward, handleClone, diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 2a72bdd7..1d3997b1 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -1161,6 +1161,16 @@ void printUsage(const char* argv0) { std::printf(" Print WMOU entries (id / kind / speed / required riding rank / faction / category / name)\n"); std::printf(" --validate-wmou [--json]\n"); std::printf(" Static checks: id>0+unique, name not empty, summonSpellId>0, flying needs rank>=150, racial needs raceMask\n"); + std::printf(" --gen-bg [name]\n"); + std::printf(" Emit .wbgd starter: 1 king-of-hill BG (10v10, 3-cap to win, 30 min limit)\n"); + std::printf(" --gen-bg-classic [name]\n"); + std::printf(" Emit .wbgd 3 classic BGs: Warsong Gulch (CTF) + Arathi Basin (nodes) + Alterac Valley (resource race)\n"); + std::printf(" --gen-bg-arena [name]\n"); + std::printf(" Emit .wbgd 3 arena formats: Nagrand 2v2 + Blade's Edge 3v3 + Lordaeron 5v5 (annihilation)\n"); + std::printf(" --info-wbgd [--json]\n"); + std::printf(" Print WBGD entries (id / map / objective / player counts / level range / score / token reward)\n"); + std::printf(" --validate-wbgd [--json]\n"); + std::printf(" Static checks: id>0+unique, name not empty, player counts>0+min<=max, level range valid, scoreToWin>0\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");