From e54c33a79296a4ca284d1f71cd65cf4055125160 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 22:06:22 -0700 Subject: [PATCH] feat(editor): add --info-project-water for cross-zone water rollup Project-wide companion to --info-zone-water. Walks every zone in , sums water chunks/layers and liquid type counts per zone, and emits a per-zone breakdown table plus project-wide totals broken down by liquid type (water/ocean/magma/slime). Useful for "do my coastal zones actually carry ocean data" sanity checks and for budget planning when many zones share liquid types (e.g. an archipelago where every zone has 256 water chunks per tile). Brings command count to 179. --- tools/editor/main.cpp | 131 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 078b24a1..e4c53317 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -688,6 +688,8 @@ static void printUsage(const char* argv0) { std::printf(" Combined spatial bounding box across every zone (per-zone table + project union)\n"); std::printf(" --info-zone-extents [--json]\n"); std::printf(" Compute the zone's bounding box (XY tile range, Z height min/max)\n"); + std::printf(" --info-project-water [--json]\n"); + std::printf(" Aggregate water-layer stats across every zone (per-zone breakdown + project totals)\n"); std::printf(" --info-zone-water [--json]\n"); std::printf(" Aggregate water-layer stats across all tiles (layer count, types, area)\n"); std::printf(" --info-zone-density [--json]\n"); @@ -882,7 +884,7 @@ int main(int argc, char* argv[]) { "--zone-summary", "--info-zone-tree", "--info-project-tree", "--info-zone-bytes", "--info-project-bytes", "--info-zone-extents", "--info-project-extents", - "--info-zone-water", + "--info-zone-water", "--info-project-water", "--info-zone-density", "--export-zone-summary-md", "--export-quest-graph", "--export-zone-csv", "--export-zone-html", "--export-project-html", @@ -6098,6 +6100,133 @@ int main(int argc, char* argv[]) { std::printf(" (no water in this zone)\n"); } return 0; + } else if (std::strcmp(argv[i], "--info-project-water") == 0 && i + 1 < argc) { + // Project-wide water rollup. Walks every zone in projectDir, + // sums water chunks/layers/types per zone, then totals + // across the project. Useful for "do my coastal zones + // actually carry ocean data" sanity checks and for budget + // planning when many zones share liquid types. + std::string projectDir = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + namespace fs = std::filesystem; + if (!fs::exists(projectDir) || !fs::is_directory(projectDir)) { + std::fprintf(stderr, + "info-project-water: %s is not a directory\n", + projectDir.c_str()); + return 1; + } + std::vector zones; + for (const auto& entry : fs::directory_iterator(projectDir)) { + if (!entry.is_directory()) continue; + if (!fs::exists(entry.path() / "zone.json")) continue; + zones.push_back(entry.path().string()); + } + std::sort(zones.begin(), zones.end()); + auto typeName = [](uint16_t t) { + switch (t) { + case 0: return "water"; + case 1: return "ocean"; + case 2: return "magma"; + case 3: return "slime"; + } + return "?"; + }; + struct ZRow { + std::string name; + int loadedTiles = 0, waterChunks = 0, totalLayers = 0; + std::map typeHist; + }; + std::vector rows; + int gLoadedTiles = 0, gWaterChunks = 0, gTotalLayers = 0; + std::map gTypeHist; + float gMinH = 1e30f, gMaxH = -1e30f; + for (const auto& zoneDir : zones) { + ZRow r; + r.name = fs::path(zoneDir).filename().string(); + wowee::editor::ZoneManifest zm; + if (!zm.load(zoneDir + "/zone.json")) { + rows.push_back(r); + continue; + } + for (const auto& [tx, ty] : zm.tiles) { + std::string tileBase = zoneDir + "/" + zm.mapName + "_" + + std::to_string(tx) + "_" + std::to_string(ty); + if (!wowee::pipeline::WoweeTerrainLoader::exists(tileBase)) continue; + wowee::pipeline::ADTTerrain terrain; + wowee::pipeline::WoweeTerrainLoader::load(tileBase, terrain); + r.loadedTiles++; + for (const auto& w : terrain.waterData) { + if (!w.hasWater()) continue; + r.waterChunks++; + r.totalLayers += static_cast(w.layers.size()); + for (const auto& layer : w.layers) { + r.typeHist[layer.liquidType]++; + gMinH = std::min(gMinH, layer.minHeight); + gMaxH = std::max(gMaxH, layer.maxHeight); + } + } + } + gLoadedTiles += r.loadedTiles; + gWaterChunks += r.waterChunks; + gTotalLayers += r.totalLayers; + for (const auto& [t, c] : r.typeHist) gTypeHist[t] += c; + rows.push_back(r); + } + if (gWaterChunks == 0) { gMinH = 0; gMaxH = 0; } + if (jsonOut) { + nlohmann::json j; + j["project"] = projectDir; + j["zoneCount"] = zones.size(); + j["loadedTiles"] = gLoadedTiles; + j["waterChunks"] = gWaterChunks; + j["totalLayers"] = gTotalLayers; + j["heightRange"] = {gMinH, gMaxH}; + nlohmann::json zarr = nlohmann::json::array(); + for (const auto& r : rows) { + nlohmann::json types = nlohmann::json::array(); + for (const auto& [t, c] : r.typeHist) { + types.push_back({{"type", t}, {"name", typeName(t)}, + {"layerCount", c}}); + } + zarr.push_back({{"name", r.name}, + {"loadedTiles", r.loadedTiles}, + {"waterChunks", r.waterChunks}, + {"totalLayers", r.totalLayers}, + {"types", types}}); + } + j["zones"] = zarr; + nlohmann::json gtypes = nlohmann::json::array(); + for (const auto& [t, c] : gTypeHist) { + gtypes.push_back({{"type", t}, {"name", typeName(t)}, + {"layerCount", c}}); + } + j["types"] = gtypes; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("Project water: %s\n", projectDir.c_str()); + std::printf(" zones : %zu\n", zones.size()); + std::printf(" loaded tiles : %d\n", gLoadedTiles); + std::printf(" water chunks : %d (out of %d possible)\n", + gWaterChunks, gLoadedTiles * 256); + std::printf(" total layers : %d\n", gTotalLayers); + if (gWaterChunks > 0) { + std::printf(" height range : %.2f to %.2f\n", gMinH, gMaxH); + std::printf("\n By liquid type (project-wide):\n"); + for (const auto& [t, c] : gTypeHist) { + std::printf(" %s (%u): %d layer(s)\n", + typeName(t), t, c); + } + } + std::printf("\n zone tiles water-chunks layers\n"); + for (const auto& r : rows) { + std::printf(" %-20s %5d %12d %6d\n", + r.name.substr(0, 20).c_str(), + r.loadedTiles, r.waterChunks, r.totalLayers); + } + return 0; } else if (std::strcmp(argv[i], "--info-zone-density") == 0 && i + 1 < argc) { // Per-tile content density. Catches sparse zones (5 mobs // across 16 tiles → boring) and over-stuffed ones (200 mobs