mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-09 18:43:51 +00:00
refactor(editor): extract --repair-zone / --repair-project into cli_repair.cpp
Moves the two manifest-drift fix handlers (--repair-zone,
--repair-project) out of main.cpp into a new cli_repair.{hpp,cpp}
module. Both auto-fix the common manifest-vs-disk
inconsistencies that accumulate when zones are hand-edited or
partially copied — adding orphan WHM tiles to the manifest,
syncing the hasCreatures flag with the actual creatures.json
content, and warning (not removing) for tiles in the manifest
without backing files. Both honor --dry-run for safe previews.
main.cpp shrinks by 155 lines (3,946 to 3,791).
This commit is contained in:
parent
3d52d5fd20
commit
73b4a6362f
4 changed files with 226 additions and 159 deletions
|
|
@ -1348,6 +1348,7 @@ add_executable(wowee_editor
|
|||
tools/editor/cli_tiles.cpp
|
||||
tools/editor/cli_zone_mgmt.cpp
|
||||
tools/editor/cli_strip.cpp
|
||||
tools/editor/cli_repair.cpp
|
||||
tools/editor/editor_app.cpp
|
||||
tools/editor/editor_camera.cpp
|
||||
tools/editor/editor_viewport.cpp
|
||||
|
|
|
|||
202
tools/editor/cli_repair.cpp
Normal file
202
tools/editor/cli_repair.cpp
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
#include "cli_repair.hpp"
|
||||
|
||||
#include "zone_manifest.hpp"
|
||||
#include "npc_spawner.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
namespace {
|
||||
|
||||
int handleRepairZone(int& i, int argc, char** argv) {
|
||||
// Auto-fix the common manifest-vs-disk drift issues that
|
||||
// accumulate when a zone is hand-edited or partially copied:
|
||||
// - WHM/WOT files exist on disk but tile not in manifest
|
||||
// -> add to tiles
|
||||
// - manifest hasCreatures=false but creatures.json exists
|
||||
// and is non-empty -> set true
|
||||
// - manifest hasCreatures=true but no creatures.json or
|
||||
// empty -> clear false
|
||||
//
|
||||
// Tiles in manifest with NO disk files are NOT auto-removed
|
||||
// (they may indicate work-in-progress); they're warned about
|
||||
// so the user can decide.
|
||||
//
|
||||
// --dry-run flag previews changes without writing.
|
||||
std::string zoneDir = argv[++i];
|
||||
bool dryRun = false;
|
||||
if (i + 1 < argc && std::strcmp(argv[i + 1], "--dry-run") == 0) {
|
||||
dryRun = true; i++;
|
||||
}
|
||||
namespace fs = std::filesystem;
|
||||
std::string manifestPath = zoneDir + "/zone.json";
|
||||
if (!fs::exists(manifestPath)) {
|
||||
std::fprintf(stderr,
|
||||
"repair-zone: %s has no zone.json\n", zoneDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
wowee::editor::ZoneManifest zm;
|
||||
if (!zm.load(manifestPath)) {
|
||||
std::fprintf(stderr, "repair-zone: parse failed\n");
|
||||
return 1;
|
||||
}
|
||||
int fixes = 0, warnings = 0;
|
||||
// Pass 1: scan disk for WHM files matching mapName_X_Y.whm
|
||||
// pattern. Match against manifest tiles. Anything on disk
|
||||
// but missing from manifest gets queued for addition.
|
||||
std::set<std::pair<int,int>> manifestTiles(
|
||||
zm.tiles.begin(), zm.tiles.end());
|
||||
std::set<std::pair<int,int>> diskTiles;
|
||||
std::error_code ec;
|
||||
for (const auto& e : fs::directory_iterator(zoneDir, ec)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
std::string name = e.path().filename().string();
|
||||
if (e.path().extension() != ".whm") continue;
|
||||
// Expect "<mapName>_TX_TY.whm". Parse out the two
|
||||
// integers between the last two underscores.
|
||||
std::string stem = name.substr(0, name.size() - 4);
|
||||
std::string prefix = zm.mapName + "_";
|
||||
if (stem.size() <= prefix.size() ||
|
||||
stem.substr(0, prefix.size()) != prefix) {
|
||||
continue; // doesn't match map slug
|
||||
}
|
||||
std::string coords = stem.substr(prefix.size());
|
||||
auto under = coords.find('_');
|
||||
if (under == std::string::npos) continue;
|
||||
try {
|
||||
int tx = std::stoi(coords.substr(0, under));
|
||||
int ty = std::stoi(coords.substr(under + 1));
|
||||
diskTiles.insert({tx, ty});
|
||||
} catch (...) {}
|
||||
}
|
||||
// Tiles on disk but not in manifest -> add.
|
||||
std::vector<std::pair<int,int>> toAdd;
|
||||
for (const auto& d : diskTiles) {
|
||||
if (manifestTiles.count(d) == 0) toAdd.push_back(d);
|
||||
}
|
||||
for (const auto& [tx, ty] : toAdd) {
|
||||
std::printf(" %s tile (%d, %d) to manifest\n",
|
||||
dryRun ? "would add" : "added", tx, ty);
|
||||
if (!dryRun) zm.tiles.push_back({tx, ty});
|
||||
fixes++;
|
||||
}
|
||||
// Tiles in manifest but no .whm on disk -> warn (not auto-removed).
|
||||
for (const auto& m : manifestTiles) {
|
||||
if (diskTiles.count(m) == 0) {
|
||||
std::printf(" WARN: tile (%d, %d) in manifest but no %s_%d_%d.whm on disk\n",
|
||||
m.first, m.second, zm.mapName.c_str(),
|
||||
m.first, m.second);
|
||||
warnings++;
|
||||
}
|
||||
}
|
||||
// hasCreatures flag sync.
|
||||
bool creaturesPresent = false;
|
||||
wowee::editor::NpcSpawner sp;
|
||||
if (sp.loadFromFile(zoneDir + "/creatures.json") &&
|
||||
sp.spawnCount() > 0) {
|
||||
creaturesPresent = true;
|
||||
}
|
||||
if (zm.hasCreatures != creaturesPresent) {
|
||||
std::printf(" %s hasCreatures: %s -> %s\n",
|
||||
dryRun ? "would set" : "set",
|
||||
zm.hasCreatures ? "true" : "false",
|
||||
creaturesPresent ? "true" : "false");
|
||||
if (!dryRun) zm.hasCreatures = creaturesPresent;
|
||||
fixes++;
|
||||
}
|
||||
if (!dryRun && fixes > 0) {
|
||||
if (!zm.save(manifestPath)) {
|
||||
std::fprintf(stderr,
|
||||
"repair-zone: failed to write %s\n", manifestPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::printf("\nrepair-zone: %s%s\n",
|
||||
zoneDir.c_str(), dryRun ? " (dry-run)" : "");
|
||||
std::printf(" fixes : %d\n", fixes);
|
||||
std::printf(" warnings : %d (manual decision needed)\n", warnings);
|
||||
if (dryRun && fixes > 0) {
|
||||
std::printf(" re-run without --dry-run to apply\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleRepairProject(int& i, int argc, char** argv) {
|
||||
// Project-wide wrapper around --repair-zone. Spawns the
|
||||
// binary per-zone so each zone's full repair report
|
||||
// streams through, then aggregates a final tally. Honors
|
||||
// --dry-run for safe previews.
|
||||
std::string projectDir = argv[++i];
|
||||
bool dryRun = false;
|
||||
if (i + 1 < argc && std::strcmp(argv[i + 1], "--dry-run") == 0) {
|
||||
dryRun = true; i++;
|
||||
}
|
||||
namespace fs = std::filesystem;
|
||||
if (!fs::exists(projectDir) || !fs::is_directory(projectDir)) {
|
||||
std::fprintf(stderr,
|
||||
"repair-project: %s is not a directory\n",
|
||||
projectDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::vector<std::string> 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());
|
||||
std::string self = argv[0];
|
||||
int totalFailed = 0;
|
||||
std::printf("repair-project: %s%s\n",
|
||||
projectDir.c_str(), dryRun ? " (dry-run)" : "");
|
||||
std::printf(" zones : %zu\n", zones.size());
|
||||
for (const auto& zoneDir : zones) {
|
||||
std::printf("\n--- %s ---\n",
|
||||
fs::path(zoneDir).filename().string().c_str());
|
||||
// Flush so the section marker lands before the spawned
|
||||
// child's stdout — std::system inherits FDs but each
|
||||
// process has its own buffer.
|
||||
std::fflush(stdout);
|
||||
std::string cmd = "\"" + self + "\" --repair-zone \"" +
|
||||
zoneDir + "\"" + (dryRun ? " --dry-run" : "");
|
||||
int rc = std::system(cmd.c_str());
|
||||
if (rc != 0) totalFailed++;
|
||||
}
|
||||
std::printf("\n--- summary ---\n");
|
||||
std::printf(" zones processed : %zu\n", zones.size());
|
||||
std::printf(" failures : %d\n", totalFailed);
|
||||
if (dryRun) {
|
||||
std::printf(" re-run without --dry-run to apply changes\n");
|
||||
}
|
||||
return totalFailed == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
bool handleRepair(int& i, int argc, char** argv, int& outRc) {
|
||||
if (std::strcmp(argv[i], "--repair-zone") == 0 && i + 1 < argc) {
|
||||
outRc = handleRepairZone(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--repair-project") == 0 && i + 1 < argc) {
|
||||
outRc = handleRepairProject(i, argc, argv); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
19
tools/editor/cli_repair.hpp
Normal file
19
tools/editor/cli_repair.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
// Dispatch the repair-* manifest-drift fix handlers — auto-fix
|
||||
// the common manifest-vs-disk inconsistencies that accumulate
|
||||
// when zones are hand-edited or partially copied. Both honor
|
||||
// --dry-run for safe previews.
|
||||
// --repair-zone fix one zone (sync tiles, hasCreatures)
|
||||
// --repair-project per-zone wrapper with aggregate tally
|
||||
//
|
||||
// Returns true if matched; outRc holds the exit code.
|
||||
bool handleRepair(int& i, int argc, char** argv, int& outRc);
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
#include "cli_tiles.hpp"
|
||||
#include "cli_zone_mgmt.hpp"
|
||||
#include "cli_strip.hpp"
|
||||
#include "cli_repair.hpp"
|
||||
#include "content_pack.hpp"
|
||||
#include "npc_spawner.hpp"
|
||||
#include "object_placer.hpp"
|
||||
|
|
@ -534,6 +535,9 @@ int main(int argc, char* argv[]) {
|
|||
if (wowee::editor::cli::handleStrip(i, argc, argv, outRc)) {
|
||||
return outRc;
|
||||
}
|
||||
if (wowee::editor::cli::handleRepair(i, argc, argv, outRc)) {
|
||||
return outRc;
|
||||
}
|
||||
}
|
||||
if (std::strcmp(argv[i], "--data") == 0 && i + 1 < argc) {
|
||||
dataPath = argv[++i];
|
||||
|
|
@ -1662,165 +1666,6 @@ int main(int argc, char* argv[]) {
|
|||
std::printf(" next : --add-texture-to-mesh <wom-base> %s\n",
|
||||
destPath.c_str());
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--repair-zone") == 0 && i + 1 < argc) {
|
||||
// Auto-fix the common manifest-vs-disk drift issues that
|
||||
// accumulate when a zone is hand-edited or partially copied:
|
||||
// - WHM/WOT files exist on disk but tile not in manifest
|
||||
// -> add to tiles
|
||||
// - manifest hasCreatures=false but creatures.json exists
|
||||
// and is non-empty -> set true
|
||||
// - manifest hasCreatures=true but no creatures.json or
|
||||
// empty -> clear false
|
||||
//
|
||||
// Tiles in manifest with NO disk files are NOT auto-removed
|
||||
// (they may indicate work-in-progress); they're warned about
|
||||
// so the user can decide.
|
||||
//
|
||||
// --dry-run flag previews changes without writing.
|
||||
std::string zoneDir = argv[++i];
|
||||
bool dryRun = false;
|
||||
if (i + 1 < argc && std::strcmp(argv[i + 1], "--dry-run") == 0) {
|
||||
dryRun = true; i++;
|
||||
}
|
||||
namespace fs = std::filesystem;
|
||||
std::string manifestPath = zoneDir + "/zone.json";
|
||||
if (!fs::exists(manifestPath)) {
|
||||
std::fprintf(stderr,
|
||||
"repair-zone: %s has no zone.json\n", zoneDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
wowee::editor::ZoneManifest zm;
|
||||
if (!zm.load(manifestPath)) {
|
||||
std::fprintf(stderr, "repair-zone: parse failed\n");
|
||||
return 1;
|
||||
}
|
||||
int fixes = 0, warnings = 0;
|
||||
// Pass 1: scan disk for WHM files matching mapName_X_Y.whm
|
||||
// pattern. Match against manifest tiles. Anything on disk
|
||||
// but missing from manifest gets queued for addition.
|
||||
std::set<std::pair<int,int>> manifestTiles(
|
||||
zm.tiles.begin(), zm.tiles.end());
|
||||
std::set<std::pair<int,int>> diskTiles;
|
||||
std::error_code ec;
|
||||
for (const auto& e : fs::directory_iterator(zoneDir, ec)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
std::string name = e.path().filename().string();
|
||||
if (e.path().extension() != ".whm") continue;
|
||||
// Expect "<mapName>_TX_TY.whm". Parse out the two
|
||||
// integers between the last two underscores.
|
||||
std::string stem = name.substr(0, name.size() - 4);
|
||||
std::string prefix = zm.mapName + "_";
|
||||
if (stem.size() <= prefix.size() ||
|
||||
stem.substr(0, prefix.size()) != prefix) {
|
||||
continue; // doesn't match map slug
|
||||
}
|
||||
std::string coords = stem.substr(prefix.size());
|
||||
auto under = coords.find('_');
|
||||
if (under == std::string::npos) continue;
|
||||
try {
|
||||
int tx = std::stoi(coords.substr(0, under));
|
||||
int ty = std::stoi(coords.substr(under + 1));
|
||||
diskTiles.insert({tx, ty});
|
||||
} catch (...) {}
|
||||
}
|
||||
// Tiles on disk but not in manifest -> add.
|
||||
std::vector<std::pair<int,int>> toAdd;
|
||||
for (const auto& d : diskTiles) {
|
||||
if (manifestTiles.count(d) == 0) toAdd.push_back(d);
|
||||
}
|
||||
for (const auto& [tx, ty] : toAdd) {
|
||||
std::printf(" %s tile (%d, %d) to manifest\n",
|
||||
dryRun ? "would add" : "added", tx, ty);
|
||||
if (!dryRun) zm.tiles.push_back({tx, ty});
|
||||
fixes++;
|
||||
}
|
||||
// Tiles in manifest but no .whm on disk -> warn (not auto-removed).
|
||||
for (const auto& m : manifestTiles) {
|
||||
if (diskTiles.count(m) == 0) {
|
||||
std::printf(" WARN: tile (%d, %d) in manifest but no %s_%d_%d.whm on disk\n",
|
||||
m.first, m.second, zm.mapName.c_str(),
|
||||
m.first, m.second);
|
||||
warnings++;
|
||||
}
|
||||
}
|
||||
// hasCreatures flag sync.
|
||||
bool creaturesPresent = false;
|
||||
wowee::editor::NpcSpawner sp;
|
||||
if (sp.loadFromFile(zoneDir + "/creatures.json") &&
|
||||
sp.spawnCount() > 0) {
|
||||
creaturesPresent = true;
|
||||
}
|
||||
if (zm.hasCreatures != creaturesPresent) {
|
||||
std::printf(" %s hasCreatures: %s -> %s\n",
|
||||
dryRun ? "would set" : "set",
|
||||
zm.hasCreatures ? "true" : "false",
|
||||
creaturesPresent ? "true" : "false");
|
||||
if (!dryRun) zm.hasCreatures = creaturesPresent;
|
||||
fixes++;
|
||||
}
|
||||
if (!dryRun && fixes > 0) {
|
||||
if (!zm.save(manifestPath)) {
|
||||
std::fprintf(stderr,
|
||||
"repair-zone: failed to write %s\n", manifestPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::printf("\nrepair-zone: %s%s\n",
|
||||
zoneDir.c_str(), dryRun ? " (dry-run)" : "");
|
||||
std::printf(" fixes : %d\n", fixes);
|
||||
std::printf(" warnings : %d (manual decision needed)\n", warnings);
|
||||
if (dryRun && fixes > 0) {
|
||||
std::printf(" re-run without --dry-run to apply\n");
|
||||
}
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--repair-project") == 0 && i + 1 < argc) {
|
||||
// Project-wide wrapper around --repair-zone. Spawns the
|
||||
// binary per-zone so each zone's full repair report
|
||||
// streams through, then aggregates a final tally. Honors
|
||||
// --dry-run for safe previews.
|
||||
std::string projectDir = argv[++i];
|
||||
bool dryRun = false;
|
||||
if (i + 1 < argc && std::strcmp(argv[i + 1], "--dry-run") == 0) {
|
||||
dryRun = true; i++;
|
||||
}
|
||||
namespace fs = std::filesystem;
|
||||
if (!fs::exists(projectDir) || !fs::is_directory(projectDir)) {
|
||||
std::fprintf(stderr,
|
||||
"repair-project: %s is not a directory\n",
|
||||
projectDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::vector<std::string> 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());
|
||||
std::string self = argv[0];
|
||||
int totalFailed = 0;
|
||||
std::printf("repair-project: %s%s\n",
|
||||
projectDir.c_str(), dryRun ? " (dry-run)" : "");
|
||||
std::printf(" zones : %zu\n", zones.size());
|
||||
for (const auto& zoneDir : zones) {
|
||||
std::printf("\n--- %s ---\n",
|
||||
fs::path(zoneDir).filename().string().c_str());
|
||||
// Flush so the section marker lands before the spawned
|
||||
// child's stdout — std::system inherits FDs but each
|
||||
// process has its own buffer.
|
||||
std::fflush(stdout);
|
||||
std::string cmd = "\"" + self + "\" --repair-zone \"" +
|
||||
zoneDir + "\"" + (dryRun ? " --dry-run" : "");
|
||||
int rc = std::system(cmd.c_str());
|
||||
if (rc != 0) totalFailed++;
|
||||
}
|
||||
std::printf("\n--- summary ---\n");
|
||||
std::printf(" zones processed : %zu\n", zones.size());
|
||||
std::printf(" failures : %d\n", totalFailed);
|
||||
if (dryRun) {
|
||||
std::printf(" re-run without --dry-run to apply changes\n");
|
||||
}
|
||||
return totalFailed == 0 ? 0 : 1;
|
||||
} 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue