diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e481045..27fdd523 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1349,6 +1349,7 @@ add_executable(wowee_editor tools/editor/cli_zone_mgmt.cpp tools/editor/cli_strip.cpp tools/editor/cli_repair.cpp + tools/editor/cli_makefile.cpp tools/editor/editor_app.cpp tools/editor/editor_camera.cpp tools/editor/editor_viewport.cpp diff --git a/tools/editor/cli_makefile.cpp b/tools/editor/cli_makefile.cpp new file mode 100644 index 00000000..6424c07a --- /dev/null +++ b/tools/editor/cli_makefile.cpp @@ -0,0 +1,211 @@ +#include "cli_makefile.hpp" + +#include "zone_manifest.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +namespace { + +int handleGenMakefile(int& i, int argc, char** argv) { + // Generate a Makefile that rebuilds every derived output for + // a zone. With this in place, designers can `make` to refresh + // glb/obj/stl/html/csv/md from sources after editing + // creatures.json or terrain — without remembering which + // wowee_editor flag does what. The Makefile uses dependency + // tracking so only stale outputs get rebuilt. + std::string zoneDir = argv[++i]; + std::string outPath; + if (i + 1 < argc && argv[i + 1][0] != '-') outPath = argv[++i]; + namespace fs = std::filesystem; + std::string manifestPath = zoneDir + "/zone.json"; + if (!fs::exists(manifestPath)) { + std::fprintf(stderr, + "gen-makefile: %s has no zone.json\n", zoneDir.c_str()); + return 1; + } + wowee::editor::ZoneManifest zm; + if (!zm.load(manifestPath)) { + std::fprintf(stderr, "gen-makefile: parse failed\n"); + return 1; + } + if (outPath.empty()) outPath = zoneDir + "/Makefile"; + std::ofstream out(outPath); + if (!out) { + std::fprintf(stderr, + "gen-makefile: cannot write %s\n", outPath.c_str()); + return 1; + } + // Use a single absolute editor path so the Makefile works + // from any cwd (running `make -C custom_zones/MyZone` etc.). + std::error_code ec; + std::string editorBin = fs::canonical("/proc/self/exe", ec).string(); + if (ec || editorBin.empty()) editorBin = "wowee_editor"; + // Per-tile WHM/WOT inputs feed the bake targets. Compose the + // list once so all targets share the same dep set. + std::string tileWhmDeps; + for (const auto& [tx, ty] : zm.tiles) { + tileWhmDeps += " " + zm.mapName + "_" + + std::to_string(tx) + "_" + std::to_string(ty) + + ".whm"; + } + std::string slug = zm.mapName; + out << "# Generated by wowee_editor --gen-makefile\n" + "# Zone: " << slug << "\n" + "# Run from this directory: `make` to rebuild all\n" + "# derived outputs from sources, `make clean` to wipe.\n\n"; + out << "EDITOR := " << editorBin << "\n"; + out << "ZONE := .\n\n"; + // Source dep aggregations for content-derived outputs. + out << "CONTENT_SRCS := zone.json $(wildcard creatures.json) " + "$(wildcard objects.json) $(wildcard quests.json)\n"; + out << "TERRAIN_SRCS := zone.json" << tileWhmDeps << "\n\n"; + out << ".PHONY: all clean glb obj stl html docs csv graph\n\n"; + out << "all: glb obj stl html docs csv graph\n\n"; + // Each target lists its dependencies so make can skip already- + // up-to-date outputs. + out << "glb: " << slug << ".glb\n" + << slug << ".glb: $(TERRAIN_SRCS)\n" + << "\t$(EDITOR) --bake-zone-glb $(ZONE)\n\n"; + out << "obj: " << slug << ".obj\n" + << slug << ".obj: $(TERRAIN_SRCS)\n" + << "\t$(EDITOR) --bake-zone-obj $(ZONE)\n\n"; + out << "stl: " << slug << ".stl\n" + << slug << ".stl: $(TERRAIN_SRCS)\n" + << "\t$(EDITOR) --bake-zone-stl $(ZONE)\n\n"; + out << "html: " << slug << ".html\n" + << slug << ".html: " << slug << ".glb\n" + << "\t$(EDITOR) --export-zone-html $(ZONE)\n\n"; + out << "docs: ZONE.md DEPS.md\n"; + out << "ZONE.md: $(CONTENT_SRCS)\n" + << "\t$(EDITOR) --export-zone-summary-md $(ZONE)\n"; + out << "DEPS.md: zone.json $(wildcard objects.json) $(wildcard *.wob)\n" + << "\t$(EDITOR) --export-zone-deps-md $(ZONE)\n\n"; + // CSV + graph targets use '-' prefix so missing-content + // (zone without creatures/quests) doesn't fail the whole + // 'make all'. The editor prints the error to stderr; make + // continues with the next target. + out << "csv:\n" + << "\t-$(EDITOR) --export-zone-csv $(ZONE)\n\n"; + out << "graph:\n" + << "\t-$(EDITOR) --export-quest-graph $(ZONE)\n\n"; + out << "clean:\n" + << "\t$(EDITOR) --strip-zone $(ZONE)\n\n"; + out << "validate:\n" + << "\t$(EDITOR) --validate-all $(ZONE)\n"; + out.close(); + std::printf("Wrote %s\n", outPath.c_str()); + std::printf(" zone : %s\n", slug.c_str()); + std::printf(" tiles : %zu (terrain dep)\n", zm.tiles.size()); + std::printf(" next : cd %s && make\n", zoneDir.c_str()); + return 0; +} + +int handleGenProjectMakefile(int& i, int argc, char** argv) { + // Top-level Makefile that delegates to per-zone Makefiles. + // Pairs with --gen-makefile (per-zone): one project-level + // command rebuilds every zone's derived outputs in parallel. + // + // wowee_editor --gen-project-makefile custom_zones + // make -C custom_zones -j$(nproc) # all zones in parallel + 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, + "gen-project-makefile: %s is not a directory\n", + projectDir.c_str()); + return 1; + } + if (outPath.empty()) outPath = projectDir + "/Makefile"; + // Find zones (dirs with zone.json). + 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().filename().string()); + } + std::sort(zones.begin(), zones.end()); + if (zones.empty()) { + std::fprintf(stderr, + "gen-project-makefile: no zones found in %s\n", + projectDir.c_str()); + return 1; + } + std::ofstream out(outPath); + if (!out) { + std::fprintf(stderr, + "gen-project-makefile: cannot write %s\n", outPath.c_str()); + return 1; + } + std::error_code ec; + std::string editorBin = fs::canonical("/proc/self/exe", ec).string(); + if (ec || editorBin.empty()) editorBin = "wowee_editor"; + out << "# Generated by wowee_editor --gen-project-makefile\n" + "# Project: " << projectDir << " (" << zones.size() << " zones)\n" + "# Run from this dir: `make` to rebuild all zone outputs.\n" + "# Pass -j to make for parallel zone builds across cores.\n\n"; + out << "EDITOR := " << editorBin << "\n\n"; + out << "ZONES := "; + for (const auto& z : zones) out << z << " "; + out << "\n\n"; + out << ".PHONY: all clean validate index html stats $(addsuffix -bake,$(ZONES)) " + "$(addsuffix -clean,$(ZONES)) $(addsuffix -validate,$(ZONES))\n\n"; + // Aggregate phony targets: 'make' rebuilds all zones; 'make + // ZONE-bake' targets just one. The per-zone Makefile must + // exist (regenerate via --gen-makefile if not). + out << "all: $(addsuffix -bake,$(ZONES)) index\n\n"; + for (const auto& z : zones) { + out << z << "-bake:\n"; + out << "\t@if [ ! -f " << z << "/Makefile ]; then \\\n" + << "\t $(EDITOR) --gen-makefile " << z << " >/dev/null; fi\n"; + out << "\t$(MAKE) -C " << z << " all\n\n"; + out << z << "-clean:\n"; + out << "\t-$(EDITOR) --strip-zone " << z << "\n\n"; + out << z << "-validate:\n"; + out << "\t$(EDITOR) --validate-all " << z << "\n\n"; + } + // Top-level utility targets. + out << "clean: $(addsuffix -clean,$(ZONES))\n\n"; + out << "validate: $(addsuffix -validate,$(ZONES))\n\n"; + out << "index:\n" + << "\t$(EDITOR) --export-project-html .\n\n"; + out << "stats:\n" + << "\t$(EDITOR) --zone-stats .\n\n"; + out << "tilemap:\n" + << "\t$(EDITOR) --info-tilemap .\n"; + out.close(); + std::printf("Wrote %s\n", outPath.c_str()); + std::printf(" %zu zone(s) wired up\n", zones.size()); + std::printf(" next: make -C %s -j$(nproc)\n", projectDir.c_str()); + return 0; +} + + +} // namespace + +bool handleMakefile(int& i, int argc, char** argv, int& outRc) { + if (std::strcmp(argv[i], "--gen-makefile") == 0 && i + 1 < argc) { + outRc = handleGenMakefile(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--gen-project-makefile") == 0 && i + 1 < argc) { + outRc = handleGenProjectMakefile(i, argc, argv); return true; + } + return false; +} + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/cli_makefile.hpp b/tools/editor/cli_makefile.hpp new file mode 100644 index 00000000..3d37a0ba --- /dev/null +++ b/tools/editor/cli_makefile.hpp @@ -0,0 +1,20 @@ +#pragma once + +namespace wowee { +namespace editor { +namespace cli { + +// Dispatch the Makefile-generation handlers — emit GNU make +// recipes that rebuild every derived output (.glb / .obj / +// .stl / .html / .csv / .md) from sources via wowee_editor +// flags. Designers can `make -j` to rebuild after editing +// without remembering which CLI flag does what. +// --gen-makefile per-zone Makefile inside the zone dir +// --gen-project-makefile project-wide Makefile delegating per-zone +// +// Returns true if matched; outRc holds the exit code. +bool handleMakefile(int& i, int argc, char** argv, int& outRc); + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 64da1872..e7e504ac 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -50,6 +50,7 @@ #include "cli_zone_mgmt.hpp" #include "cli_strip.hpp" #include "cli_repair.hpp" +#include "cli_makefile.hpp" #include "content_pack.hpp" #include "npc_spawner.hpp" #include "object_placer.hpp" @@ -538,6 +539,9 @@ int main(int argc, char* argv[]) { if (wowee::editor::cli::handleRepair(i, argc, argv, outRc)) { return outRc; } + if (wowee::editor::cli::handleMakefile(i, argc, argv, outRc)) { + return outRc; + } } if (std::strcmp(argv[i], "--data") == 0 && i + 1 < argc) { dataPath = argv[++i]; @@ -1666,177 +1670,6 @@ int main(int argc, char* argv[]) { std::printf(" next : --add-texture-to-mesh %s\n", destPath.c_str()); return 0; - } else if (std::strcmp(argv[i], "--gen-makefile") == 0 && i + 1 < argc) { - // Generate a Makefile that rebuilds every derived output for - // a zone. With this in place, designers can `make` to refresh - // glb/obj/stl/html/csv/md from sources after editing - // creatures.json or terrain — without remembering which - // wowee_editor flag does what. The Makefile uses dependency - // tracking so only stale outputs get rebuilt. - std::string zoneDir = argv[++i]; - std::string outPath; - if (i + 1 < argc && argv[i + 1][0] != '-') outPath = argv[++i]; - namespace fs = std::filesystem; - std::string manifestPath = zoneDir + "/zone.json"; - if (!fs::exists(manifestPath)) { - std::fprintf(stderr, - "gen-makefile: %s has no zone.json\n", zoneDir.c_str()); - return 1; - } - wowee::editor::ZoneManifest zm; - if (!zm.load(manifestPath)) { - std::fprintf(stderr, "gen-makefile: parse failed\n"); - return 1; - } - if (outPath.empty()) outPath = zoneDir + "/Makefile"; - std::ofstream out(outPath); - if (!out) { - std::fprintf(stderr, - "gen-makefile: cannot write %s\n", outPath.c_str()); - return 1; - } - // Use a single absolute editor path so the Makefile works - // from any cwd (running `make -C custom_zones/MyZone` etc.). - std::error_code ec; - std::string editorBin = fs::canonical("/proc/self/exe", ec).string(); - if (ec || editorBin.empty()) editorBin = "wowee_editor"; - // Per-tile WHM/WOT inputs feed the bake targets. Compose the - // list once so all targets share the same dep set. - std::string tileWhmDeps; - for (const auto& [tx, ty] : zm.tiles) { - tileWhmDeps += " " + zm.mapName + "_" + - std::to_string(tx) + "_" + std::to_string(ty) + - ".whm"; - } - std::string slug = zm.mapName; - out << "# Generated by wowee_editor --gen-makefile\n" - "# Zone: " << slug << "\n" - "# Run from this directory: `make` to rebuild all\n" - "# derived outputs from sources, `make clean` to wipe.\n\n"; - out << "EDITOR := " << editorBin << "\n"; - out << "ZONE := .\n\n"; - // Source dep aggregations for content-derived outputs. - out << "CONTENT_SRCS := zone.json $(wildcard creatures.json) " - "$(wildcard objects.json) $(wildcard quests.json)\n"; - out << "TERRAIN_SRCS := zone.json" << tileWhmDeps << "\n\n"; - out << ".PHONY: all clean glb obj stl html docs csv graph\n\n"; - out << "all: glb obj stl html docs csv graph\n\n"; - // Each target lists its dependencies so make can skip already- - // up-to-date outputs. - out << "glb: " << slug << ".glb\n" - << slug << ".glb: $(TERRAIN_SRCS)\n" - << "\t$(EDITOR) --bake-zone-glb $(ZONE)\n\n"; - out << "obj: " << slug << ".obj\n" - << slug << ".obj: $(TERRAIN_SRCS)\n" - << "\t$(EDITOR) --bake-zone-obj $(ZONE)\n\n"; - out << "stl: " << slug << ".stl\n" - << slug << ".stl: $(TERRAIN_SRCS)\n" - << "\t$(EDITOR) --bake-zone-stl $(ZONE)\n\n"; - out << "html: " << slug << ".html\n" - << slug << ".html: " << slug << ".glb\n" - << "\t$(EDITOR) --export-zone-html $(ZONE)\n\n"; - out << "docs: ZONE.md DEPS.md\n"; - out << "ZONE.md: $(CONTENT_SRCS)\n" - << "\t$(EDITOR) --export-zone-summary-md $(ZONE)\n"; - out << "DEPS.md: zone.json $(wildcard objects.json) $(wildcard *.wob)\n" - << "\t$(EDITOR) --export-zone-deps-md $(ZONE)\n\n"; - // CSV + graph targets use '-' prefix so missing-content - // (zone without creatures/quests) doesn't fail the whole - // 'make all'. The editor prints the error to stderr; make - // continues with the next target. - out << "csv:\n" - << "\t-$(EDITOR) --export-zone-csv $(ZONE)\n\n"; - out << "graph:\n" - << "\t-$(EDITOR) --export-quest-graph $(ZONE)\n\n"; - out << "clean:\n" - << "\t$(EDITOR) --strip-zone $(ZONE)\n\n"; - out << "validate:\n" - << "\t$(EDITOR) --validate-all $(ZONE)\n"; - out.close(); - std::printf("Wrote %s\n", outPath.c_str()); - std::printf(" zone : %s\n", slug.c_str()); - std::printf(" tiles : %zu (terrain dep)\n", zm.tiles.size()); - std::printf(" next : cd %s && make\n", zoneDir.c_str()); - return 0; - } else if (std::strcmp(argv[i], "--gen-project-makefile") == 0 && i + 1 < argc) { - // Top-level Makefile that delegates to per-zone Makefiles. - // Pairs with --gen-makefile (per-zone): one project-level - // command rebuilds every zone's derived outputs in parallel. - // - // wowee_editor --gen-project-makefile custom_zones - // make -C custom_zones -j$(nproc) # all zones in parallel - 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, - "gen-project-makefile: %s is not a directory\n", - projectDir.c_str()); - return 1; - } - if (outPath.empty()) outPath = projectDir + "/Makefile"; - // Find zones (dirs with zone.json). - 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().filename().string()); - } - std::sort(zones.begin(), zones.end()); - if (zones.empty()) { - std::fprintf(stderr, - "gen-project-makefile: no zones found in %s\n", - projectDir.c_str()); - return 1; - } - std::ofstream out(outPath); - if (!out) { - std::fprintf(stderr, - "gen-project-makefile: cannot write %s\n", outPath.c_str()); - return 1; - } - std::error_code ec; - std::string editorBin = fs::canonical("/proc/self/exe", ec).string(); - if (ec || editorBin.empty()) editorBin = "wowee_editor"; - out << "# Generated by wowee_editor --gen-project-makefile\n" - "# Project: " << projectDir << " (" << zones.size() << " zones)\n" - "# Run from this dir: `make` to rebuild all zone outputs.\n" - "# Pass -j to make for parallel zone builds across cores.\n\n"; - out << "EDITOR := " << editorBin << "\n\n"; - out << "ZONES := "; - for (const auto& z : zones) out << z << " "; - out << "\n\n"; - out << ".PHONY: all clean validate index html stats $(addsuffix -bake,$(ZONES)) " - "$(addsuffix -clean,$(ZONES)) $(addsuffix -validate,$(ZONES))\n\n"; - // Aggregate phony targets: 'make' rebuilds all zones; 'make - // ZONE-bake' targets just one. The per-zone Makefile must - // exist (regenerate via --gen-makefile if not). - out << "all: $(addsuffix -bake,$(ZONES)) index\n\n"; - for (const auto& z : zones) { - out << z << "-bake:\n"; - out << "\t@if [ ! -f " << z << "/Makefile ]; then \\\n" - << "\t $(EDITOR) --gen-makefile " << z << " >/dev/null; fi\n"; - out << "\t$(MAKE) -C " << z << " all\n\n"; - out << z << "-clean:\n"; - out << "\t-$(EDITOR) --strip-zone " << z << "\n\n"; - out << z << "-validate:\n"; - out << "\t$(EDITOR) --validate-all " << z << "\n\n"; - } - // Top-level utility targets. - out << "clean: $(addsuffix -clean,$(ZONES))\n\n"; - out << "validate: $(addsuffix -validate,$(ZONES))\n\n"; - out << "index:\n" - << "\t$(EDITOR) --export-project-html .\n\n"; - out << "stats:\n" - << "\t$(EDITOR) --zone-stats .\n\n"; - out << "tilemap:\n" - << "\t$(EDITOR) --info-tilemap .\n"; - out.close(); - std::printf("Wrote %s\n", outPath.c_str()); - std::printf(" %zu zone(s) wired up\n", zones.size()); - std::printf(" next: make -C %s -j$(nproc)\n", projectDir.c_str()); - return 0; } else if (std::strcmp(argv[i], "--list-zones") == 0) { // Optional --json after the flag for machine-readable output. bool jsonOut = (i + 1 < argc &&