diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index d6cff958..846cf2f8 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -426,6 +426,8 @@ static void printUsage(const char* argv0) { std::printf(" --list-zones [--json] List discovered custom zones and exit\n"); std::printf(" --zone-stats [--json]\n"); std::printf(" Aggregate counts across every zone in \n"); + std::printf(" --info-tilemap [--json]\n"); + std::printf(" ASCII-render the 64x64 WoW ADT grid showing tile claims by zone\n"); std::printf(" --list-zone-deps [--json]\n"); std::printf(" List external M2/WMO model paths a zone references (objects + WOB doodads)\n"); std::printf(" --export-zone-deps-md [out.md]\n"); @@ -672,8 +674,8 @@ int main(int argc, char* argv[]) { "--export-zone-summary-md", "--export-quest-graph", "--export-zone-csv", "--export-zone-html", "--scaffold-zone", "--add-tile", "--remove-tile", "--list-tiles", - "--for-each-zone", "--zone-stats", "--list-zone-deps", - "--check-zone-refs", "--export-zone-deps-md", + "--for-each-zone", "--zone-stats", "--info-tilemap", + "--list-zone-deps", "--check-zone-refs", "--export-zone-deps-md", "--add-creature", "--add-object", "--add-quest", "--add-quest-objective", "--add-quest-reward-item", "--set-quest-reward", "--remove-quest-objective", "--clone-quest", "--clone-creature", @@ -9511,6 +9513,125 @@ int main(int argc, char* argv[]) { r.bytes / kKB); } return 0; + } else if (std::strcmp(argv[i], "--info-tilemap") == 0 && i + 1 < argc) { + // Visualize the WoW 64x64 ADT grid showing which tiles are + // claimed by which zones across a project. Useful for + // spotting tile-coord collisions before two zones try to + // ship overlapping content, and for getting a 'where am I + // working?' overview of a multi-zone project. + 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-tilemap: %s is not a directory\n", projectDir.c_str()); + return 1; + } + // Map (tx, ty) -> vector so collision overlaps + // are visible. Walk every zone in the project. + std::map, std::vector> claims; + std::vector zones; + for (const auto& entry : fs::directory_iterator(projectDir)) { + if (!entry.is_directory()) continue; + if (!fs::exists(entry.path() / "zone.json")) continue; + wowee::editor::ZoneManifest zm; + if (!zm.load((entry.path() / "zone.json").string())) continue; + std::string zname = zm.mapName.empty() + ? entry.path().filename().string() : zm.mapName; + zones.push_back(zname); + for (const auto& [tx, ty] : zm.tiles) { + if (tx >= 0 && tx < 64 && ty >= 0 && ty < 64) { + claims[{tx, ty}].push_back(zname); + } + } + } + // Per-zone label glyph: first letter of the zone name, + // uppercased so different zones get distinct chars in the + // grid. Multi-letter overlap collapses to '*'. + std::map zoneGlyph; + char nextGlyph = 'A'; + for (const auto& z : zones) { + if (zoneGlyph.count(z)) continue; + if (!z.empty() && std::isalpha(static_cast(z[0]))) { + zoneGlyph[z] = static_cast(std::toupper(static_cast(z[0]))); + } else { + zoneGlyph[z] = nextGlyph++; + if (nextGlyph > 'Z') nextGlyph = 'a'; + } + } + int collisions = 0; + for (const auto& [coord, owners] : claims) { + if (owners.size() > 1) collisions++; + } + if (jsonOut) { + nlohmann::json j; + j["projectDir"] = projectDir; + j["zoneCount"] = zones.size(); + j["claimedTiles"] = claims.size(); + j["collisions"] = collisions; + nlohmann::json claimsJson = nlohmann::json::array(); + for (const auto& [coord, owners] : claims) { + claimsJson.push_back({{"x", coord.first}, + {"y", coord.second}, + {"zones", owners}}); + } + j["claims"] = claimsJson; + std::printf("%s\n", j.dump(2).c_str()); + return collisions == 0 ? 0 : 1; + } + std::printf("Tilemap: %s\n", projectDir.c_str()); + std::printf(" zones : %zu\n", zones.size()); + std::printf(" tiles used : %zu\n", claims.size()); + std::printf(" collisions : %d (multiple zones claiming same tile)\n", + collisions); + std::printf(" legend :"); + for (const auto& [name, glyph] : zoneGlyph) { + std::printf(" %c=%s", glyph, name.c_str()); + } + std::printf("\n\n"); + // Render 64x64 grid. Print column header in groups of 10 + // for readability. + std::printf(" "); + for (int x = 0; x < 64; ++x) { + std::printf("%c", (x % 10 == 0) ? '0' + (x / 10) : ' '); + } + std::printf("\n"); + std::printf(" "); + for (int x = 0; x < 64; ++x) std::printf("%d", x % 10); + std::printf("\n"); + for (int y = 0; y < 64; ++y) { + // Skip rows that have no tiles claimed — keeps the + // output bounded for projects in one corner of the map. + bool rowHasContent = false; + for (int x = 0; x < 64 && !rowHasContent; ++x) { + if (claims.count({x, y})) rowHasContent = true; + } + if (!rowHasContent) continue; + std::printf(" y=%2d ", y); + for (int x = 0; x < 64; ++x) { + auto it = claims.find({x, y}); + if (it == claims.end()) { + std::printf("."); + } else if (it->second.size() > 1) { + std::printf("*"); // collision + } else { + std::printf("%c", zoneGlyph[it->second[0]]); + } + } + std::printf("\n"); + } + if (collisions > 0) { + std::printf("\n COLLISIONS:\n"); + for (const auto& [coord, owners] : claims) { + if (owners.size() < 2) continue; + std::printf(" (%d, %d) claimed by:", coord.first, coord.second); + for (const auto& o : owners) std::printf(" %s", o.c_str()); + std::printf("\n"); + } + } + return collisions == 0 ? 0 : 1; } else if (std::strcmp(argv[i], "--list-zone-deps") == 0 && i + 1 < argc) { // Enumerate every external model path a zone references — // both directly placed (objects.json) and indirectly via