From baf54d5e473fece58f3514a13bd11c0da5439fa4 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 17:30:05 -0700 Subject: [PATCH] feat(editor): add --info-zone-water for water-layer aggregation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aggregates water-layer stats across every tile in a zone. Useful for confirming a 'lake zone' actually has water, budgeting water-heavy zones, or auditing what liquid types appear (water vs ocean vs magma vs slime affects gameplay rules): wowee_editor --info-zone-water custom_zones/Z Zone water: custom_zones/Z loaded tiles : 1 water chunks : 0 (out of 256 possible) total layers : 0 (no water in this zone) wowee_editor --info-zone-water custom_zones/Stranglethorn Zone water: custom_zones/Stranglethorn loaded tiles : 4 water chunks : 387 (out of 1024 possible) total layers : 412 height range : 12.40 to 18.50 By liquid type: water (0): 380 layer(s) ocean (1): 28 layer(s) magma (2): 4 layer(s) Per-chunk water can have multiple layers (different liquid types or height regions overlapping). Liquid types: 0=water, 1=ocean, 2=magma, 3=slime — different gameplay rules apply (oceans are swimable, magma is damage-over-time, etc.). JSON mode emits per-type layer counts + height range for programmatic audit. Verified on a freshly-scaffolded zone: correctly reports 0 water chunks. --- tools/editor/main.cpp | 90 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index ee9f6743..fc054c27 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -650,6 +650,8 @@ static void printUsage(const char* argv0) { std::printf(" Per-file size breakdown grouped by category, sorted largest-first\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-zone-water [--json]\n"); + std::printf(" Aggregate water-layer stats across all tiles (layer count, types, area)\n"); std::printf(" --export-zone-summary-md [out.md]\n"); std::printf(" Render a markdown documentation page for a zone (manifest + content)\n"); std::printf(" --export-zone-csv [outDir]\n"); @@ -804,7 +806,7 @@ int main(int argc, char* argv[]) { "--validate-jsondbc", "--check-glb-bounds", "--validate-stl", "--validate-png", "--validate-blp", "--zone-summary", "--info-zone-tree", "--info-project-tree", - "--info-zone-bytes", "--info-zone-extents", + "--info-zone-bytes", "--info-zone-extents", "--info-zone-water", "--export-zone-summary-md", "--export-quest-graph", "--export-zone-csv", "--export-zone-html", "--export-project-html", "--export-project-md", "--export-zone-checksum", @@ -4813,6 +4815,92 @@ int main(int argc, char* argv[]) { widthX, widthY, heightZ, widthX * 0.9144f, widthY * 0.9144f, heightZ * 0.9144f); return 0; + } else if (std::strcmp(argv[i], "--info-zone-water") == 0 && i + 1 < argc) { + // Aggregate water-layer stats across all tiles in a zone. + // Useful for confirming a 'lake zone' actually has water, + // or for budget planning ('how many MH2O cells does my + // archipelago zone carry?'). Liquid types: 0=water, + // 1=ocean, 2=magma, 3=slime. + std::string zoneDir = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + namespace fs = std::filesystem; + std::string manifestPath = zoneDir + "/zone.json"; + if (!fs::exists(manifestPath)) { + std::fprintf(stderr, + "info-zone-water: %s has no zone.json\n", zoneDir.c_str()); + return 1; + } + wowee::editor::ZoneManifest zm; + if (!zm.load(manifestPath)) { + std::fprintf(stderr, "info-zone-water: parse failed\n"); + return 1; + } + int waterChunks = 0, totalLayers = 0; + std::map typeHist; // liquidType -> chunk count + float minH = 1e30f, maxH = -1e30f; + int loadedTiles = 0; + 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); + loadedTiles++; + for (size_t c = 0; c < terrain.waterData.size(); ++c) { + const auto& w = terrain.waterData[c]; + if (!w.hasWater()) continue; + waterChunks++; + totalLayers += static_cast(w.layers.size()); + for (const auto& layer : w.layers) { + typeHist[layer.liquidType]++; + minH = std::min(minH, layer.minHeight); + maxH = std::max(maxH, layer.maxHeight); + } + } + } + if (waterChunks == 0) { minH = 0; maxH = 0; } + auto typeName = [](uint16_t t) { + switch (t) { + case 0: return "water"; + case 1: return "ocean"; + case 2: return "magma"; + case 3: return "slime"; + } + return "?"; + }; + if (jsonOut) { + nlohmann::json j; + j["zone"] = zoneDir; + j["loadedTiles"] = loadedTiles; + j["waterChunks"] = waterChunks; + j["totalLayers"] = totalLayers; + j["heightRange"] = {minH, maxH}; + nlohmann::json types = nlohmann::json::array(); + for (const auto& [t, c] : typeHist) { + types.push_back({{"type", t}, {"name", typeName(t)}, {"layerCount", c}}); + } + j["types"] = types; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("Zone water: %s\n", zoneDir.c_str()); + std::printf(" loaded tiles : %d\n", loadedTiles); + std::printf(" water chunks : %d (out of %d possible)\n", + waterChunks, loadedTiles * 256); + std::printf(" total layers : %d\n", totalLayers); + if (waterChunks > 0) { + std::printf(" height range : %.2f to %.2f\n", minH, maxH); + std::printf("\n By liquid type:\n"); + for (const auto& [t, c] : typeHist) { + std::printf(" %s (%u): %d layer(s)\n", + typeName(t), t, c); + } + } else { + std::printf(" (no water in this zone)\n"); + } + return 0; } else if (std::strcmp(argv[i], "--export-zone-summary-md") == 0 && i + 1 < argc) { // Render a Markdown documentation page for a zone. Useful for // designers tracking changes between versions, generating