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