diff --git a/CMakeLists.txt b/CMakeLists.txt index 316f649a..cfc710f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1339,6 +1339,7 @@ add_executable(wowee_editor tools/editor/cli_quest_objective.cpp tools/editor/cli_quest_reward.cpp tools/editor/cli_clone.cpp + tools/editor/cli_remove.cpp tools/editor/editor_app.cpp tools/editor/editor_camera.cpp tools/editor/editor_viewport.cpp diff --git a/tools/editor/cli_remove.cpp b/tools/editor/cli_remove.cpp new file mode 100644 index 00000000..f617139a --- /dev/null +++ b/tools/editor/cli_remove.cpp @@ -0,0 +1,199 @@ +#include "cli_remove.hpp" + +#include "npc_spawner.hpp" +#include "object_placer.hpp" +#include "quest_editor.hpp" +#include + +#include +#include +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +namespace { + +int handleRemoveCreature(int& i, int /*argc*/, char** argv) { + // Remove a creature spawn by 0-based index. Pair with + // --info-creatures (or your editor) to find the right index + // first; nothing identifies entries reliably across reloads. + std::string zoneDir = argv[++i]; + std::string idxStr = argv[++i]; + std::string path = zoneDir + "/creatures.json"; + if (!std::filesystem::exists(path)) { + std::fprintf(stderr, "remove-creature: %s not found\n", path.c_str()); + return 1; + } + int idx; + try { idx = std::stoi(idxStr); } + catch (...) { + std::fprintf(stderr, "remove-creature: bad index '%s'\n", idxStr.c_str()); + return 1; + } + wowee::editor::NpcSpawner sp; + sp.loadFromFile(path); + if (idx < 0 || idx >= static_cast(sp.spawnCount())) { + std::fprintf(stderr, "remove-creature: index %d out of range [0, %zu)\n", + idx, sp.spawnCount()); + return 1; + } + std::string removedName = sp.getSpawns()[idx].name; + sp.removeCreature(idx); + if (!sp.saveToFile(path)) { + std::fprintf(stderr, "remove-creature: failed to write %s\n", path.c_str()); + return 1; + } + std::printf("Removed creature '%s' (was index %d) from %s (now %zu total)\n", + removedName.c_str(), idx, path.c_str(), sp.spawnCount()); + return 0; +} + +int handleRemoveObject(int& i, int /*argc*/, char** argv) { + std::string zoneDir = argv[++i]; + std::string idxStr = argv[++i]; + std::string path = zoneDir + "/objects.json"; + if (!std::filesystem::exists(path)) { + std::fprintf(stderr, "remove-object: %s not found\n", path.c_str()); + return 1; + } + int idx; + try { idx = std::stoi(idxStr); } + catch (...) { + std::fprintf(stderr, "remove-object: bad index '%s'\n", idxStr.c_str()); + return 1; + } + wowee::editor::ObjectPlacer placer; + placer.loadFromFile(path); + auto& objs = placer.getObjects(); + if (idx < 0 || idx >= static_cast(objs.size())) { + std::fprintf(stderr, "remove-object: index %d out of range [0, %zu)\n", + idx, objs.size()); + return 1; + } + std::string removedPath = objs[idx].path; + objs.erase(objs.begin() + idx); + if (!placer.saveToFile(path)) { + std::fprintf(stderr, "remove-object: failed to write %s\n", path.c_str()); + return 1; + } + std::printf("Removed object '%s' (was index %d) from %s (now %zu total)\n", + removedPath.c_str(), idx, path.c_str(), objs.size()); + return 0; +} + +int handleRemoveQuest(int& i, int /*argc*/, char** argv) { + std::string zoneDir = argv[++i]; + std::string idxStr = argv[++i]; + std::string path = zoneDir + "/quests.json"; + if (!std::filesystem::exists(path)) { + std::fprintf(stderr, "remove-quest: %s not found\n", path.c_str()); + return 1; + } + int idx; + try { idx = std::stoi(idxStr); } + catch (...) { + std::fprintf(stderr, "remove-quest: bad index '%s'\n", idxStr.c_str()); + return 1; + } + wowee::editor::QuestEditor qe; + qe.loadFromFile(path); + if (idx < 0 || idx >= static_cast(qe.questCount())) { + std::fprintf(stderr, "remove-quest: index %d out of range [0, %zu)\n", + idx, qe.questCount()); + return 1; + } + std::string removedTitle = qe.getQuests()[idx].title; + qe.removeQuest(idx); + if (!qe.saveToFile(path)) { + std::fprintf(stderr, "remove-quest: failed to write %s\n", path.c_str()); + return 1; + } + std::printf("Removed quest '%s' (was index %d) from %s (now %zu total)\n", + removedTitle.c_str(), idx, path.c_str(), qe.questCount()); + return 0; +} + +int handleRemoveItem(int& i, int /*argc*/, char** argv) { + // Remove the item at given 0-based index from / + // items.json. Mirrors --remove-creature/--remove-object/ + // --remove-quest semantics — bounds-checked, file rewrites + // on success, exit 1 on out-of-range. + std::string zoneDir = argv[++i]; + int idx = -1; + try { idx = std::stoi(argv[++i]); } + catch (...) { + std::fprintf(stderr, + "remove-item: index must be an integer\n"); + return 1; + } + namespace fs = std::filesystem; + std::string path = zoneDir + "/items.json"; + if (!fs::exists(path)) { + std::fprintf(stderr, + "remove-item: %s has no items.json\n", zoneDir.c_str()); + return 1; + } + nlohmann::json doc; + try { + std::ifstream in(path); + in >> doc; + } catch (...) { + std::fprintf(stderr, + "remove-item: %s is not valid JSON\n", path.c_str()); + return 1; + } + if (!doc.contains("items") || !doc["items"].is_array()) { + std::fprintf(stderr, + "remove-item: %s has no 'items' array\n", path.c_str()); + return 1; + } + auto& items = doc["items"]; + if (idx < 0 || static_cast(idx) >= items.size()) { + std::fprintf(stderr, + "remove-item: index %d out of range (have %zu)\n", + idx, items.size()); + return 1; + } + std::string removedName = items[idx].value("name", std::string("(unnamed)")); + uint32_t removedId = items[idx].value("id", 0u); + items.erase(items.begin() + idx); + std::ofstream out(path); + if (!out) { + std::fprintf(stderr, + "remove-item: failed to write %s\n", path.c_str()); + return 1; + } + out << doc.dump(2); + out.close(); + std::printf("Removed item '%s' (id=%u) from %s (now %zu total)\n", + removedName.c_str(), removedId, + path.c_str(), items.size()); + return 0; +} + +} // namespace + +bool handleRemove(int& i, int argc, char** argv, int& outRc) { + if (std::strcmp(argv[i], "--remove-creature") == 0 && i + 2 < argc) { + outRc = handleRemoveCreature(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--remove-object") == 0 && i + 2 < argc) { + outRc = handleRemoveObject(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--remove-quest") == 0 && i + 2 < argc) { + outRc = handleRemoveQuest(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--remove-item") == 0 && i + 2 < argc) { + outRc = handleRemoveItem(i, argc, argv); return true; + } + return false; +} + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/cli_remove.hpp b/tools/editor/cli_remove.hpp new file mode 100644 index 00000000..8e83fdf8 --- /dev/null +++ b/tools/editor/cli_remove.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace wowee { +namespace editor { +namespace cli { + +// Dispatch the remove-* by-index handlers — strip a single +// entry out of a zone's creatures/objects/quests/items list +// by 0-based index. All four use bounds-checked load-erase-save +// and report what was removed for audit trails. +// --remove-creature +// --remove-object +// --remove-quest +// --remove-item +// +// Returns true if matched; outRc holds the exit code. +bool handleRemove(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 312d7d91..041fa05f 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -40,6 +40,7 @@ #include "cli_quest_objective.hpp" #include "cli_quest_reward.hpp" #include "cli_clone.hpp" +#include "cli_remove.hpp" #include "content_pack.hpp" #include "npc_spawner.hpp" #include "object_placer.hpp" @@ -495,6 +496,9 @@ int main(int argc, char* argv[]) { if (wowee::editor::cli::handleClone(i, argc, argv, outRc)) { return outRc; } + if (wowee::editor::cli::handleRemove(i, argc, argv, outRc)) { + return outRc; + } } if (std::strcmp(argv[i], "--data") == 0 && i + 1 < argc) { dataPath = argv[++i]; @@ -1423,100 +1427,6 @@ int main(int argc, char* argv[]) { outPath.c_str(), col.triangles.size(), col.walkableCount(), col.steepCount()); return 0; - } else if (std::strcmp(argv[i], "--remove-creature") == 0 && i + 2 < argc) { - // Remove a creature spawn by 0-based index. Pair with - // --info-creatures (or your editor) to find the right index - // first; nothing identifies entries reliably across reloads. - std::string zoneDir = argv[++i]; - std::string idxStr = argv[++i]; - std::string path = zoneDir + "/creatures.json"; - if (!std::filesystem::exists(path)) { - std::fprintf(stderr, "remove-creature: %s not found\n", path.c_str()); - return 1; - } - int idx; - try { idx = std::stoi(idxStr); } - catch (...) { - std::fprintf(stderr, "remove-creature: bad index '%s'\n", idxStr.c_str()); - return 1; - } - wowee::editor::NpcSpawner sp; - sp.loadFromFile(path); - if (idx < 0 || idx >= static_cast(sp.spawnCount())) { - std::fprintf(stderr, "remove-creature: index %d out of range [0, %zu)\n", - idx, sp.spawnCount()); - return 1; - } - std::string removedName = sp.getSpawns()[idx].name; - sp.removeCreature(idx); - if (!sp.saveToFile(path)) { - std::fprintf(stderr, "remove-creature: failed to write %s\n", path.c_str()); - return 1; - } - std::printf("Removed creature '%s' (was index %d) from %s (now %zu total)\n", - removedName.c_str(), idx, path.c_str(), sp.spawnCount()); - return 0; - } else if (std::strcmp(argv[i], "--remove-object") == 0 && i + 2 < argc) { - std::string zoneDir = argv[++i]; - std::string idxStr = argv[++i]; - std::string path = zoneDir + "/objects.json"; - if (!std::filesystem::exists(path)) { - std::fprintf(stderr, "remove-object: %s not found\n", path.c_str()); - return 1; - } - int idx; - try { idx = std::stoi(idxStr); } - catch (...) { - std::fprintf(stderr, "remove-object: bad index '%s'\n", idxStr.c_str()); - return 1; - } - wowee::editor::ObjectPlacer placer; - placer.loadFromFile(path); - auto& objs = placer.getObjects(); - if (idx < 0 || idx >= static_cast(objs.size())) { - std::fprintf(stderr, "remove-object: index %d out of range [0, %zu)\n", - idx, objs.size()); - return 1; - } - std::string removedPath = objs[idx].path; - objs.erase(objs.begin() + idx); - if (!placer.saveToFile(path)) { - std::fprintf(stderr, "remove-object: failed to write %s\n", path.c_str()); - return 1; - } - std::printf("Removed object '%s' (was index %d) from %s (now %zu total)\n", - removedPath.c_str(), idx, path.c_str(), objs.size()); - return 0; - } else if (std::strcmp(argv[i], "--remove-quest") == 0 && i + 2 < argc) { - std::string zoneDir = argv[++i]; - std::string idxStr = argv[++i]; - std::string path = zoneDir + "/quests.json"; - if (!std::filesystem::exists(path)) { - std::fprintf(stderr, "remove-quest: %s not found\n", path.c_str()); - return 1; - } - int idx; - try { idx = std::stoi(idxStr); } - catch (...) { - std::fprintf(stderr, "remove-quest: bad index '%s'\n", idxStr.c_str()); - return 1; - } - wowee::editor::QuestEditor qe; - qe.loadFromFile(path); - if (idx < 0 || idx >= static_cast(qe.questCount())) { - std::fprintf(stderr, "remove-quest: index %d out of range [0, %zu)\n", - idx, qe.questCount()); - return 1; - } - std::string removedTitle = qe.getQuests()[idx].title; - qe.removeQuest(idx); - if (!qe.saveToFile(path)) { - std::fprintf(stderr, "remove-quest: failed to write %s\n", path.c_str()); - return 1; - } - std::printf("Removed quest '%s' (was index %d) from %s (now %zu total)\n", - removedTitle.c_str(), idx, path.c_str(), qe.questCount()); - return 0; } else if (std::strcmp(argv[i], "--add-object") == 0 && i + 5 < argc) { // Append a single object placement to a zone's objects.json. // Args: [scale] @@ -2578,62 +2488,6 @@ int main(int argc, char* argv[]) { std::printf(" zones with items : %zu\n", zones.size()); std::printf(" rows : %d\n", totalRows); return 0; - } else if (std::strcmp(argv[i], "--remove-item") == 0 && i + 2 < argc) { - // Remove the item at given 0-based index from / - // items.json. Mirrors --remove-creature/--remove-object/ - // --remove-quest semantics — bounds-checked, file rewrites - // on success, exit 1 on out-of-range. - std::string zoneDir = argv[++i]; - int idx = -1; - try { idx = std::stoi(argv[++i]); } - catch (...) { - std::fprintf(stderr, - "remove-item: index must be an integer\n"); - return 1; - } - namespace fs = std::filesystem; - std::string path = zoneDir + "/items.json"; - if (!fs::exists(path)) { - std::fprintf(stderr, - "remove-item: %s has no items.json\n", zoneDir.c_str()); - return 1; - } - nlohmann::json doc; - try { - std::ifstream in(path); - in >> doc; - } catch (...) { - std::fprintf(stderr, - "remove-item: %s is not valid JSON\n", path.c_str()); - return 1; - } - if (!doc.contains("items") || !doc["items"].is_array()) { - std::fprintf(stderr, - "remove-item: %s has no 'items' array\n", path.c_str()); - return 1; - } - auto& items = doc["items"]; - if (idx < 0 || static_cast(idx) >= items.size()) { - std::fprintf(stderr, - "remove-item: index %d out of range (have %zu)\n", - idx, items.size()); - return 1; - } - std::string removedName = items[idx].value("name", std::string("(unnamed)")); - uint32_t removedId = items[idx].value("id", 0u); - items.erase(items.begin() + idx); - std::ofstream out(path); - if (!out) { - std::fprintf(stderr, - "remove-item: failed to write %s\n", path.c_str()); - return 1; - } - out << doc.dump(2); - out.close(); - std::printf("Removed item '%s' (id=%u) from %s (now %zu total)\n", - removedName.c_str(), removedId, - path.c_str(), items.size()); - return 0; } else if (std::strcmp(argv[i], "--copy-zone-items") == 0 && i + 2 < argc) { // Copy items from one zone to another. Default mode // replaces the destination items.json wholesale; --merge