diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 5385dc4e..8ca2cf3f 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -73,6 +73,7 @@ const char* const kArgRequired[] = { "--export-wtax-json", "--import-wtax-json", "--gen-talents", "--gen-talents-warrior", "--gen-talents-mage", "--info-wtal", "--validate-wtal", + "--export-wtal-json", "--import-wtal-json", "--gen-maps", "--gen-maps-classic", "--gen-maps-bgarena", "--info-wms", "--validate-wms", "--gen-weather-temperate", "--gen-weather-arctic", diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 3fe0a010..1fbe472e 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -1061,6 +1061,10 @@ void printUsage(const char* argv0) { std::printf(" Print WTAL trees + per-talent grid position / max rank / prereq chain / rank-1 spellId\n"); std::printf(" --validate-wtal [--json]\n"); std::printf(" Static checks: tree+talent ids>0+unique, maxRank 1..5, prereq references resolve, no self-prereq\n"); + std::printf(" --export-wtal-json [out.json]\n"); + std::printf(" Export binary .wtal to a human-editable JSON sidecar (defaults to .wtal.json)\n"); + std::printf(" --import-wtal-json [out-base]\n"); + std::printf(" Import a .wtal.json sidecar back into binary .wtal (round-trip with --export-wtal-json)\n"); std::printf(" --gen-maps [name]\n"); std::printf(" Emit .wms starter: 1 map (Eastern Kingdoms) + 3 areas (Stormwind / Elwynn / Goldshire) with parent chain\n"); std::printf(" --gen-maps-classic [name]\n"); diff --git a/tools/editor/cli_talents_catalog.cpp b/tools/editor/cli_talents_catalog.cpp index b26bd352..e51dd2ea 100644 --- a/tools/editor/cli_talents_catalog.cpp +++ b/tools/editor/cli_talents_catalog.cpp @@ -149,6 +149,142 @@ int handleInfo(int& i, int argc, char** argv) { return 0; } +int handleExportJson(int& i, int argc, char** argv) { + // Mirrors the JSON pairs added for every other novel + // open format. Each tree emits scalar fields plus the + // talent array; rankSpellIds becomes a 5-element JSON + // array. + std::string base = argv[++i]; + std::string outPath; + if (parseOptArg(i, argc, argv)) outPath = argv[++i]; + base = stripWtalExt(base); + if (outPath.empty()) outPath = base + ".wtal.json"; + if (!wowee::pipeline::WoweeTalentLoader::exists(base)) { + std::fprintf(stderr, + "export-wtal-json: WTAL not found: %s.wtal\n", base.c_str()); + return 1; + } + auto c = wowee::pipeline::WoweeTalentLoader::load(base); + nlohmann::json j; + j["name"] = c.name; + nlohmann::json arr = nlohmann::json::array(); + for (const auto& t : c.trees) { + nlohmann::json jt; + jt["treeId"] = t.treeId; + jt["name"] = t.name; + jt["iconPath"] = t.iconPath; + jt["requiredClassMask"] = t.requiredClassMask; + nlohmann::json ta = nlohmann::json::array(); + for (const auto& a : t.talents) { + nlohmann::json ja; + ja["talentId"] = a.talentId; + ja["row"] = a.row; + ja["col"] = a.col; + ja["maxRank"] = a.maxRank; + ja["prereqTalentId"] = a.prereqTalentId; + ja["prereqRank"] = a.prereqRank; + nlohmann::json sa = nlohmann::json::array(); + for (int r = 0; r < wowee::pipeline::WoweeTalent::kMaxRanks; ++r) { + sa.push_back(a.rankSpellIds[r]); + } + ja["rankSpellIds"] = sa; + ta.push_back(ja); + } + jt["talents"] = ta; + arr.push_back(jt); + } + j["trees"] = arr; + std::ofstream out(outPath); + if (!out) { + std::fprintf(stderr, + "export-wtal-json: cannot write %s\n", outPath.c_str()); + return 1; + } + out << j.dump(2) << "\n"; + out.close(); + std::printf("Wrote %s\n", outPath.c_str()); + std::printf(" source : %s.wtal\n", base.c_str()); + std::printf(" trees : %zu\n", c.trees.size()); + return 0; +} + +int handleImportJson(int& i, int argc, char** argv) { + std::string jsonPath = argv[++i]; + std::string outBase; + if (parseOptArg(i, argc, argv)) outBase = argv[++i]; + if (outBase.empty()) { + outBase = jsonPath; + std::string suffix = ".wtal.json"; + if (outBase.size() > suffix.size() && + outBase.substr(outBase.size() - suffix.size()) == suffix) { + outBase = outBase.substr(0, outBase.size() - suffix.size()); + } else if (outBase.size() > 5 && + outBase.substr(outBase.size() - 5) == ".json") { + outBase = outBase.substr(0, outBase.size() - 5); + } + } + outBase = stripWtalExt(outBase); + std::ifstream in(jsonPath); + if (!in) { + std::fprintf(stderr, + "import-wtal-json: cannot read %s\n", jsonPath.c_str()); + return 1; + } + nlohmann::json j; + try { in >> j; } + catch (const std::exception& e) { + std::fprintf(stderr, + "import-wtal-json: bad JSON in %s: %s\n", + jsonPath.c_str(), e.what()); + return 1; + } + wowee::pipeline::WoweeTalent c; + c.name = j.value("name", std::string{}); + if (j.contains("trees") && j["trees"].is_array()) { + for (const auto& jt : j["trees"]) { + wowee::pipeline::WoweeTalent::Tree t; + t.treeId = jt.value("treeId", 0u); + t.name = jt.value("name", std::string{}); + t.iconPath = jt.value("iconPath", std::string{}); + t.requiredClassMask = jt.value("requiredClassMask", 0u); + if (jt.contains("talents") && jt["talents"].is_array()) { + for (const auto& ja : jt["talents"]) { + wowee::pipeline::WoweeTalent::Talent a; + a.talentId = ja.value("talentId", 0u); + a.row = static_cast(ja.value("row", 0)); + a.col = static_cast(ja.value("col", 0)); + a.maxRank = static_cast(ja.value("maxRank", 1)); + a.prereqTalentId = ja.value("prereqTalentId", 0u); + a.prereqRank = static_cast( + ja.value("prereqRank", 0)); + if (ja.contains("rankSpellIds") && + ja["rankSpellIds"].is_array()) { + const auto& sa = ja["rankSpellIds"]; + for (int r = 0; + r < wowee::pipeline::WoweeTalent::kMaxRanks && + r < static_cast(sa.size()); ++r) { + if (sa[r].is_number_integer()) { + a.rankSpellIds[r] = sa[r].get(); + } + } + } + t.talents.push_back(a); + } + } + c.trees.push_back(std::move(t)); + } + } + if (!wowee::pipeline::WoweeTalentLoader::save(c, outBase)) { + std::fprintf(stderr, + "import-wtal-json: failed to save %s.wtal\n", outBase.c_str()); + return 1; + } + std::printf("Wrote %s.wtal\n", outBase.c_str()); + std::printf(" source : %s\n", jsonPath.c_str()); + std::printf(" trees : %zu\n", c.trees.size()); + return 0; +} + int handleValidate(int& i, int argc, char** argv) { std::string base = argv[++i]; bool jsonOut = consumeJsonFlag(i, argc, argv); @@ -296,6 +432,12 @@ bool handleTalentsCatalog(int& i, int argc, char** argv, int& outRc) { if (std::strcmp(argv[i], "--validate-wtal") == 0 && i + 1 < argc) { outRc = handleValidate(i, argc, argv); return true; } + if (std::strcmp(argv[i], "--export-wtal-json") == 0 && i + 1 < argc) { + outRc = handleExportJson(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--import-wtal-json") == 0 && i + 1 < argc) { + outRc = handleImportJson(i, argc, argv); return true; + } return false; }