diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 846cf2f8..77db14b0 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -549,6 +549,8 @@ static void printUsage(const char* argv0) { std::printf(" Emit creatures.csv / objects.csv / quests.csv for spreadsheet workflows\n"); std::printf(" --export-zone-html [out.html]\n"); std::printf(" Emit a single-file HTML viewer next to the zone .glb (model-viewer based)\n"); + std::printf(" --export-project-html [out.html]\n"); + std::printf(" Generate an index.html linking to every zone's HTML viewer in \n"); std::printf(" --export-quest-graph [out.dot]\n"); std::printf(" Render quest-chain DAG as Graphviz DOT (pipe to `dot -Tpng -o quests.png`)\n"); std::printf(" --info [--json]\n"); @@ -672,7 +674,7 @@ int main(int argc, char* argv[]) { "--validate-png", "--zone-summary", "--info-zone-tree", "--info-zone-bytes", "--export-zone-summary-md", "--export-quest-graph", - "--export-zone-csv", "--export-zone-html", + "--export-zone-csv", "--export-zone-html", "--export-project-html", "--scaffold-zone", "--add-tile", "--remove-tile", "--list-tiles", "--for-each-zone", "--zone-stats", "--info-tilemap", "--list-zone-deps", "--check-zone-refs", "--export-zone-deps-md", @@ -4434,6 +4436,121 @@ int main(int argc, char* argv[]) { std::printf(" references %s (must sit next to .html)\n", glbHref.c_str()); std::printf(" open in any modern browser — no install required\n"); return 0; + } else if (std::strcmp(argv[i], "--export-project-html") == 0 && i + 1 < argc) { + // Project-level index page linking every zone's HTML viewer. + // Pairs with --export-zone-html (single zone) and + // --bake-zone-glb (terrain bake). Designed for github-pages + // style 'all my zones' showcase. + std::string projectDir = argv[++i]; + std::string outPath; + if (i + 1 < argc && argv[i + 1][0] != '-') outPath = argv[++i]; + namespace fs = std::filesystem; + if (!fs::exists(projectDir) || !fs::is_directory(projectDir)) { + std::fprintf(stderr, + "export-project-html: %s is not a directory\n", + projectDir.c_str()); + return 1; + } + if (outPath.empty()) outPath = projectDir + "/index.html"; + // Walk for zones (dirs with zone.json). For each, record: + // - display name + // - relative path to its .html viewer (or null if not generated) + // - tile count, content counts + struct ZoneEntry { + std::string name, dirRel, htmlRel, glbRel; + bool htmlExists = false, glbExists = false; + int tiles = 0, creatures = 0, objects = 0, quests = 0; + }; + std::vector entries; + 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; + ZoneEntry ze; + ze.name = zm.displayName.empty() ? zm.mapName : zm.displayName; + ze.dirRel = entry.path().filename().string(); + ze.htmlRel = ze.dirRel + "/" + zm.mapName + ".html"; + ze.glbRel = ze.dirRel + "/" + zm.mapName + ".glb"; + ze.htmlExists = fs::exists(entry.path() / (zm.mapName + ".html")); + ze.glbExists = fs::exists(entry.path() / (zm.mapName + ".glb")); + ze.tiles = static_cast(zm.tiles.size()); + wowee::editor::NpcSpawner sp; + if (sp.loadFromFile((entry.path() / "creatures.json").string())) { + ze.creatures = static_cast(sp.spawnCount()); + } + wowee::editor::ObjectPlacer op; + if (op.loadFromFile((entry.path() / "objects.json").string())) { + ze.objects = static_cast(op.getObjects().size()); + } + wowee::editor::QuestEditor qe; + if (qe.loadFromFile((entry.path() / "quests.json").string())) { + ze.quests = static_cast(qe.questCount()); + } + entries.push_back(ze); + } + std::sort(entries.begin(), entries.end(), + [](const ZoneEntry& a, const ZoneEntry& b) { + return a.name < b.name; + }); + std::ofstream out(outPath); + if (!out) { + std::fprintf(stderr, + "export-project-html: cannot write %s\n", outPath.c_str()); + return 1; + } + out << "\n" + "\n" + "\n" + " \n" + " Wowee Project — Zone Index\n" + " \n" + "\n" + "\n" + "

Wowee Project — Zone Index

\n" + "
" << entries.size() << " zone(s) found in " + << projectDir << "
\n" + "
\n"; + for (const auto& z : entries) { + out << "
\n" + "

" << z.name << "

\n" + "
" + << z.tiles << " tile" << (z.tiles == 1 ? "" : "s") << " · " + << z.creatures << " creature" << (z.creatures == 1 ? "" : "s") << " · " + << z.objects << " object" << (z.objects == 1 ? "" : "s") << " · " + << z.quests << " quest" << (z.quests == 1 ? "" : "s") << "
\n"; + if (z.htmlExists) { + out << " Open viewer →\n"; + } else if (z.glbExists) { + out << "
No HTML viewer (run --export-zone-html)
\n"; + } else { + out << "
No .glb (run --bake-zone-glb)
\n"; + } + out << "
\n"; + } + out << "
\n" + "
Generated by wowee_editor --export-project-html
\n" + "\n" + "\n"; + out.close(); + int withViewer = 0; + for (const auto& z : entries) if (z.htmlExists) withViewer++; + std::printf("Wrote %s\n", outPath.c_str()); + std::printf(" %zu zone(s) listed, %d with viewable HTML\n", + entries.size(), withViewer); + return 0; } else if (std::strcmp(argv[i], "--export-quest-graph") == 0 && i + 1 < argc) { // Render quest chains as a Graphviz DOT graph. Visualizing // quest dependencies in plain text rapidly becomes unreadable