From d9761046f9c8b2560da3254367020bf6b2a39a98 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 14:25:41 -0700 Subject: [PATCH] feat(editor): add WOW JSON round-trip authoring workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the WOL JSON pair from the previous batch — same hand-edit authoring loop for the binary .wow weather format: • --export-wow-json [out.json] Dumps a .wow to a human-readable JSON sidecar (defaults to .wow.json). Each entry includes BOTH the raw typeId (0..6) and the human-friendly type name ("clear" / "rain" / "snow" / "storm" / "sandstorm" / "fog" / "blizzard"). • --import-wow-json [out-base] Reads a JSON sidecar and writes back binary .wow. Accepts either typeId int OR type-name string (typeId wins if both present). Schema mismatches fail with a clear message. Workflow: --gen-weather-* → --export-wow-json → hand-edit weights / durations / intensities → --import-wow-json → use in runtime. Round-trip verified: elwynn.wow → elwynn.wow.json → elwynn_rt.wow shows byte-identical entries via --info-wow. Both atmosphere formats now have full JSON authoring support: WOL: --export-wol-json / --import-wol-json WOW: --export-wow-json / --import-wow-json --- tools/editor/cli_arg_required.cpp | 1 + tools/editor/cli_help.cpp | 4 + tools/editor/cli_world_info.cpp | 128 ++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 0b01c0aa..ecfaf176 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -18,6 +18,7 @@ const char* const kArgRequired[] = { "--info-wol", "--info-wol-at", "--validate-wol", "--gen-light", "--gen-light-cave", "--gen-light-dungeon", "--gen-light-night", "--export-wol-json", "--import-wol-json", + "--export-wow-json", "--import-wow-json", "--info-wow", "--validate-wow", "--gen-weather-temperate", "--gen-weather-arctic", "--gen-weather-desert", "--gen-weather-stormy", diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index fe854a4c..a848c55e 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -803,6 +803,10 @@ void printUsage(const char* argv0) { std::printf(" Export binary .wol to a human-editable JSON sidecar (defaults to .wol.json)\n"); std::printf(" --import-wol-json [out-base]\n"); std::printf(" Import a .wol.json sidecar back into binary .wol (round-trip with --export-wol-json)\n"); + std::printf(" --export-wow-json [out.json]\n"); + std::printf(" Export binary .wow to a human-editable JSON sidecar (defaults to .wow.json)\n"); + std::printf(" --import-wow-json [out-base]\n"); + std::printf(" Import a .wow.json sidecar back into binary .wow (accepts type-name string OR typeId int)\n"); std::printf(" --info-wow [--json]\n"); std::printf(" Print WOW weather entries (zone + per-state type / intensity / weight / duration) and exit\n"); std::printf(" --validate-wow [--json]\n"); diff --git a/tools/editor/cli_world_info.cpp b/tools/editor/cli_world_info.cpp index 4e893f5c..a7977b3f 100644 --- a/tools/editor/cli_world_info.cpp +++ b/tools/editor/cli_world_info.cpp @@ -886,6 +886,128 @@ int handleGenWeatherStormy(int& i, int argc, char** argv) { "heavy rain + storm + occasional clear"); } +int handleExportWowJson(int& i, int argc, char** argv) { + // Export a binary .wow to a human-editable JSON sidecar. + // Pairs with --import-wow-json for the round-trip authoring + // workflow on weather schedules. + std::string base = argv[++i]; + std::string outPath; + if (i + 1 < argc && argv[i + 1][0] != '-') outPath = argv[++i]; + if (base.size() >= 4 && base.substr(base.size() - 4) == ".wow") + base = base.substr(0, base.size() - 4); + if (outPath.empty()) outPath = base + ".wow.json"; + if (!wowee::pipeline::WoweeWeatherLoader::exists(base)) { + std::fprintf(stderr, "WOW not found: %s.wow\n", base.c_str()); + return 1; + } + auto wow = wowee::pipeline::WoweeWeatherLoader::load(base); + if (!wow.isValid()) { + std::fprintf(stderr, "WOW parse failed: %s.wow\n", base.c_str()); + return 1; + } + nlohmann::json j; + j["name"] = wow.name; + nlohmann::json es = nlohmann::json::array(); + for (const auto& e : wow.entries) { + es.push_back({ + {"type", + wowee::pipeline::WoweeWeather::typeName(e.weatherTypeId)}, + {"typeId", e.weatherTypeId}, + {"minIntensity", e.minIntensity}, + {"maxIntensity", e.maxIntensity}, + {"weight", e.weight}, + {"minDurationSec", e.minDurationSec}, + {"maxDurationSec", e.maxDurationSec}, + }); + } + j["entries"] = es; + std::ofstream os(outPath); + if (!os) { + std::fprintf(stderr, + "export-wow-json: cannot write %s\n", outPath.c_str()); + return 1; + } + os << j.dump(2) << '\n'; + std::printf("Wrote %s (%zu entry/entries)\n", + outPath.c_str(), wow.entries.size()); + return 0; +} + +int handleImportWowJson(int& i, int argc, char** argv) { + // Import a JSON sidecar back into binary .wow. The "type" + // string field is human-friendly ("clear" / "rain" / etc.) + // but typeId still wins if both are present, so users can + // edit either. Schema mismatches fail with a clear message. + std::string jsonPath = argv[++i]; + std::string outBase; + if (i + 1 < argc && argv[i + 1][0] != '-') outBase = argv[++i]; + if (outBase.empty()) { + outBase = jsonPath; + if (outBase.size() >= 9 && + outBase.substr(outBase.size() - 9) == ".wow.json") { + outBase = outBase.substr(0, outBase.size() - 9); + } else if (outBase.size() >= 5 && + outBase.substr(outBase.size() - 5) == ".json") { + outBase = outBase.substr(0, outBase.size() - 5); + } + } + if (outBase.size() >= 4 && outBase.substr(outBase.size() - 4) == ".wow") { + outBase = outBase.substr(0, outBase.size() - 4); + } + std::ifstream is(jsonPath); + if (!is) { + std::fprintf(stderr, + "import-wow-json: cannot read %s\n", jsonPath.c_str()); + return 1; + } + nlohmann::json j; + try { is >> j; } catch (const std::exception& e) { + std::fprintf(stderr, + "import-wow-json: parse error: %s\n", e.what()); + return 1; + } + auto typeFromName = [](const std::string& s) -> uint32_t { + if (s == "clear") return wowee::pipeline::WoweeWeather::Clear; + if (s == "rain") return wowee::pipeline::WoweeWeather::Rain; + if (s == "snow") return wowee::pipeline::WoweeWeather::Snow; + if (s == "storm") return wowee::pipeline::WoweeWeather::Storm; + if (s == "sandstorm") return wowee::pipeline::WoweeWeather::Sandstorm; + if (s == "fog") return wowee::pipeline::WoweeWeather::Fog; + if (s == "blizzard") return wowee::pipeline::WoweeWeather::Blizzard; + return wowee::pipeline::WoweeWeather::Clear; + }; + wowee::pipeline::WoweeWeather wow; + try { + wow.name = j.value("name", std::string("Imported")); + for (const auto& je : j.at("entries")) { + wowee::pipeline::WoweeWeather::Entry e; + if (je.contains("typeId")) { + e.weatherTypeId = je.at("typeId").get(); + } else if (je.contains("type")) { + e.weatherTypeId = typeFromName(je.at("type").get()); + } + e.minIntensity = je.at("minIntensity").get(); + e.maxIntensity = je.at("maxIntensity").get(); + e.weight = je.at("weight").get(); + e.minDurationSec = je.at("minDurationSec").get(); + e.maxDurationSec = je.at("maxDurationSec").get(); + wow.entries.push_back(e); + } + } catch (const std::exception& e) { + std::fprintf(stderr, + "import-wow-json: schema error: %s\n", e.what()); + return 1; + } + if (!wowee::pipeline::WoweeWeatherLoader::save(wow, outBase)) { + std::fprintf(stderr, + "import-wow-json: failed to save %s.wow\n", outBase.c_str()); + return 1; + } + std::printf("Wrote %s.wow (%zu entry/entries, name=%s)\n", + outBase.c_str(), wow.entries.size(), wow.name.c_str()); + return 0; +} + int handleGenZoneAtmosphere(int& i, int argc, char** argv) { // Convenience composite: drop both a default day/night WOL // and a temperate WOW into /atmosphere.{wol,wow}. @@ -1021,6 +1143,12 @@ bool handleWorldInfo(int& i, int argc, char** argv, int& outRc) { if (std::strcmp(argv[i], "--gen-zone-atmosphere") == 0 && i + 1 < argc) { outRc = handleGenZoneAtmosphere(i, argc, argv); return true; } + if (std::strcmp(argv[i], "--export-wow-json") == 0 && i + 1 < argc) { + outRc = handleExportWowJson(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--import-wow-json") == 0 && i + 1 < argc) { + outRc = handleImportWowJson(i, argc, argv); return true; + } return false; }