mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-09 18:43:51 +00:00
Moves the three items.json mutation handlers (--set-item,
--copy-zone-items, --clone-item) out of main.cpp into a new
cli_items_mutate.{hpp,cpp} module. All three operate on the
same {"items": [...]} schema; --set-item supports both
id-lookup ("123") and index-lookup ("#0") with strict
"only changed fields are written" semantics, --copy-zone-items
has replace and --merge modes, --clone-item auto-assigns the
smallest unused id.
main.cpp shrinks by 316 lines (5,087 to 4,771) and finally
drops below 5K. items.json now has its full mutation surface
in one focused module instead of scattered through the
megafile.
364 lines
12 KiB
C++
364 lines
12 KiB
C++
#include "cli_items_mutate.hpp"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace wowee {
|
|
namespace editor {
|
|
namespace cli {
|
|
|
|
namespace {
|
|
|
|
int handleSetItem(int& i, int argc, char** argv) {
|
|
// Edit fields on an existing item in place. Lookup is by
|
|
// id by default; '#N' for index lookup. Only specified
|
|
// flags are changed; everything else is preserved
|
|
// verbatim — including any extra fields added by hand.
|
|
//
|
|
// Supported flags: --name, --quality, --displayId,
|
|
// --itemLevel, --stackable. Each takes one positional
|
|
// argument that follows the flag.
|
|
std::string zoneDir = argv[++i];
|
|
std::string lookup = argv[++i];
|
|
namespace fs = std::filesystem;
|
|
std::string path = zoneDir + "/items.json";
|
|
if (!fs::exists(path)) {
|
|
std::fprintf(stderr,
|
|
"set-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,
|
|
"set-item: %s is not valid JSON\n", path.c_str());
|
|
return 1;
|
|
}
|
|
if (!doc.contains("items") || !doc["items"].is_array()) {
|
|
std::fprintf(stderr,
|
|
"set-item: %s has no 'items' array\n", path.c_str());
|
|
return 1;
|
|
}
|
|
auto& items = doc["items"];
|
|
int foundIdx = -1;
|
|
if (!lookup.empty() && lookup[0] == '#') {
|
|
try {
|
|
int idx = std::stoi(lookup.substr(1));
|
|
if (idx >= 0 && static_cast<size_t>(idx) < items.size())
|
|
foundIdx = idx;
|
|
} catch (...) {}
|
|
} else {
|
|
uint32_t targetId = 0;
|
|
try { targetId = static_cast<uint32_t>(std::stoul(lookup)); }
|
|
catch (...) {
|
|
std::fprintf(stderr,
|
|
"set-item: lookup '%s' is not a number\n",
|
|
lookup.c_str());
|
|
return 1;
|
|
}
|
|
for (size_t k = 0; k < items.size(); ++k) {
|
|
if (items[k].contains("id") &&
|
|
items[k]["id"].is_number_unsigned() &&
|
|
items[k]["id"].get<uint32_t>() == targetId) {
|
|
foundIdx = static_cast<int>(k);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (foundIdx < 0) {
|
|
std::fprintf(stderr,
|
|
"set-item: no match for '%s' in %s\n",
|
|
lookup.c_str(), path.c_str());
|
|
return 1;
|
|
}
|
|
auto& it = items[foundIdx];
|
|
std::vector<std::string> changes;
|
|
// Walk the remaining args looking for known --field value
|
|
// pairs. Anything unrecognized is reported and aborts so
|
|
// typos don't silently no-op.
|
|
while (i + 2 < argc) {
|
|
std::string flag = argv[i + 1];
|
|
std::string val = argv[i + 2];
|
|
if (flag.size() < 2 || flag[0] != '-' || flag[1] != '-') break;
|
|
if (flag == "--name") {
|
|
it["name"] = val;
|
|
changes.push_back("name=" + val);
|
|
} else if (flag == "--quality") {
|
|
try {
|
|
uint32_t q = static_cast<uint32_t>(std::stoul(val));
|
|
if (q > 6) {
|
|
std::fprintf(stderr,
|
|
"set-item: quality %u out of range (0..6)\n", q);
|
|
return 1;
|
|
}
|
|
it["quality"] = q;
|
|
changes.push_back("quality=" + val);
|
|
} catch (...) {
|
|
std::fprintf(stderr,
|
|
"set-item: --quality needs a number\n");
|
|
return 1;
|
|
}
|
|
} else if (flag == "--displayId") {
|
|
try {
|
|
it["displayId"] = static_cast<uint32_t>(std::stoul(val));
|
|
changes.push_back("displayId=" + val);
|
|
} catch (...) {
|
|
std::fprintf(stderr,
|
|
"set-item: --displayId needs a number\n");
|
|
return 1;
|
|
}
|
|
} else if (flag == "--itemLevel") {
|
|
try {
|
|
it["itemLevel"] = static_cast<uint32_t>(std::stoul(val));
|
|
changes.push_back("itemLevel=" + val);
|
|
} catch (...) {
|
|
std::fprintf(stderr,
|
|
"set-item: --itemLevel needs a number\n");
|
|
return 1;
|
|
}
|
|
} else if (flag == "--stackable") {
|
|
try {
|
|
uint32_t s = static_cast<uint32_t>(std::stoul(val));
|
|
if (s == 0 || s > 1000) {
|
|
std::fprintf(stderr,
|
|
"set-item: stackable %u out of range (1..1000)\n", s);
|
|
return 1;
|
|
}
|
|
it["stackable"] = s;
|
|
changes.push_back("stackable=" + val);
|
|
} catch (...) {
|
|
std::fprintf(stderr,
|
|
"set-item: --stackable needs a number\n");
|
|
return 1;
|
|
}
|
|
} else {
|
|
std::fprintf(stderr,
|
|
"set-item: unknown flag '%s' (typo?)\n", flag.c_str());
|
|
return 1;
|
|
}
|
|
i += 2;
|
|
}
|
|
if (changes.empty()) {
|
|
std::fprintf(stderr,
|
|
"set-item: no field flags supplied — nothing to change\n");
|
|
return 1;
|
|
}
|
|
std::ofstream out(path);
|
|
if (!out) {
|
|
std::fprintf(stderr,
|
|
"set-item: failed to write %s\n", path.c_str());
|
|
return 1;
|
|
}
|
|
out << doc.dump(2);
|
|
out.close();
|
|
std::printf("Updated item %d in %s:\n", foundIdx, path.c_str());
|
|
for (const auto& c : changes) {
|
|
std::printf(" %s\n", c.c_str());
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int handleCopyZoneItems(int& i, int argc, char** argv) {
|
|
// Copy items from one zone to another. Default mode
|
|
// replaces the destination items.json wholesale; --merge
|
|
// appends each source item to the existing destination
|
|
// list, re-id'ing on collision so the destination's
|
|
// existing IDs are preserved and the source's new
|
|
// entries get fresh ones.
|
|
std::string fromZone = argv[++i];
|
|
std::string toZone = argv[++i];
|
|
bool mergeMode = false;
|
|
if (i + 1 < argc && std::strcmp(argv[i + 1], "--merge") == 0) {
|
|
mergeMode = true; i++;
|
|
}
|
|
namespace fs = std::filesystem;
|
|
std::string srcPath = fromZone + "/items.json";
|
|
if (!fs::exists(srcPath)) {
|
|
std::fprintf(stderr,
|
|
"copy-zone-items: %s has no items.json\n", fromZone.c_str());
|
|
return 1;
|
|
}
|
|
if (!fs::exists(toZone) || !fs::is_directory(toZone)) {
|
|
std::fprintf(stderr,
|
|
"copy-zone-items: dest %s is not a directory\n",
|
|
toZone.c_str());
|
|
return 1;
|
|
}
|
|
nlohmann::json src;
|
|
try {
|
|
std::ifstream in(srcPath);
|
|
in >> src;
|
|
} catch (...) {
|
|
std::fprintf(stderr,
|
|
"copy-zone-items: %s is not valid JSON\n", srcPath.c_str());
|
|
return 1;
|
|
}
|
|
if (!src.contains("items") || !src["items"].is_array()) {
|
|
std::fprintf(stderr,
|
|
"copy-zone-items: %s has no 'items' array\n",
|
|
srcPath.c_str());
|
|
return 1;
|
|
}
|
|
std::string dstPath = toZone + "/items.json";
|
|
nlohmann::json dst = nlohmann::json::object({{"items",
|
|
nlohmann::json::array()}});
|
|
int copied = 0, reIded = 0;
|
|
if (mergeMode && fs::exists(dstPath)) {
|
|
try {
|
|
std::ifstream in(dstPath);
|
|
in >> dst;
|
|
} catch (...) {}
|
|
if (!dst.contains("items") || !dst["items"].is_array()) {
|
|
dst["items"] = nlohmann::json::array();
|
|
}
|
|
std::set<uint32_t> usedIds;
|
|
for (const auto& it : dst["items"]) {
|
|
if (it.contains("id") && it["id"].is_number_unsigned()) {
|
|
usedIds.insert(it["id"].get<uint32_t>());
|
|
}
|
|
}
|
|
for (const auto& it : src["items"]) {
|
|
nlohmann::json newItem = it;
|
|
uint32_t srcId = it.value("id", 0u);
|
|
if (srcId == 0 || usedIds.count(srcId)) {
|
|
// Pick the next free id.
|
|
uint32_t fresh = 1;
|
|
while (usedIds.count(fresh)) ++fresh;
|
|
newItem["id"] = fresh;
|
|
usedIds.insert(fresh);
|
|
if (srcId != 0) reIded++;
|
|
} else {
|
|
usedIds.insert(srcId);
|
|
}
|
|
dst["items"].push_back(newItem);
|
|
copied++;
|
|
}
|
|
} else {
|
|
// Replace mode: destination becomes a verbatim copy of
|
|
// the source items array.
|
|
dst["items"] = src["items"];
|
|
copied = static_cast<int>(src["items"].size());
|
|
}
|
|
std::ofstream out(dstPath);
|
|
if (!out) {
|
|
std::fprintf(stderr,
|
|
"copy-zone-items: failed to write %s\n", dstPath.c_str());
|
|
return 1;
|
|
}
|
|
out << dst.dump(2);
|
|
out.close();
|
|
std::printf("Copied %d item(s) from %s to %s\n",
|
|
copied, fromZone.c_str(), toZone.c_str());
|
|
std::printf(" mode : %s\n",
|
|
mergeMode ? "merge (append + re-id)" : "replace");
|
|
std::printf(" dst total : %zu\n", dst["items"].size());
|
|
if (reIded > 0) {
|
|
std::printf(" re-ided : %d (id collisions)\n", reIded);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int handleCloneItem(int& i, int argc, char** argv) {
|
|
// Duplicate the item at given 0-based index. Auto-assigns
|
|
// the smallest unused positive id; optional <newName>
|
|
// overrides the cloned name (without it the new entry
|
|
// gets " (copy)" appended).
|
|
std::string zoneDir = argv[++i];
|
|
int idx = -1;
|
|
try { idx = std::stoi(argv[++i]); }
|
|
catch (...) {
|
|
std::fprintf(stderr,
|
|
"clone-item: index must be an integer\n");
|
|
return 1;
|
|
}
|
|
std::string newName;
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') newName = argv[++i];
|
|
namespace fs = std::filesystem;
|
|
std::string path = zoneDir + "/items.json";
|
|
if (!fs::exists(path)) {
|
|
std::fprintf(stderr,
|
|
"clone-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,
|
|
"clone-item: %s is not valid JSON\n", path.c_str());
|
|
return 1;
|
|
}
|
|
if (!doc.contains("items") || !doc["items"].is_array()) {
|
|
std::fprintf(stderr,
|
|
"clone-item: %s has no 'items' array\n", path.c_str());
|
|
return 1;
|
|
}
|
|
auto& items = doc["items"];
|
|
if (idx < 0 || static_cast<size_t>(idx) >= items.size()) {
|
|
std::fprintf(stderr,
|
|
"clone-item: index %d out of range (have %zu)\n",
|
|
idx, items.size());
|
|
return 1;
|
|
}
|
|
// Pick the next free id.
|
|
std::set<uint32_t> used;
|
|
for (const auto& it : items) {
|
|
if (it.contains("id") && it["id"].is_number_unsigned()) {
|
|
used.insert(it["id"].get<uint32_t>());
|
|
}
|
|
}
|
|
uint32_t newId = 1;
|
|
while (used.count(newId)) ++newId;
|
|
nlohmann::json clone = items[idx];
|
|
clone["id"] = newId;
|
|
if (!newName.empty()) {
|
|
clone["name"] = newName;
|
|
} else {
|
|
std::string oldName = clone.value("name", std::string("(unnamed)"));
|
|
clone["name"] = oldName + " (copy)";
|
|
}
|
|
items.push_back(clone);
|
|
std::ofstream out(path);
|
|
if (!out) {
|
|
std::fprintf(stderr,
|
|
"clone-item: failed to write %s\n", path.c_str());
|
|
return 1;
|
|
}
|
|
out << doc.dump(2);
|
|
out.close();
|
|
std::printf("Cloned item idx %d to '%s' (id=%u) in %s (now %zu total)\n",
|
|
idx, clone["name"].get<std::string>().c_str(),
|
|
newId, path.c_str(), items.size());
|
|
return 0;
|
|
}
|
|
|
|
|
|
} // namespace
|
|
|
|
bool handleItemsMutate(int& i, int argc, char** argv, int& outRc) {
|
|
if (std::strcmp(argv[i], "--set-item") == 0 && i + 2 < argc) {
|
|
outRc = handleSetItem(i, argc, argv); return true;
|
|
}
|
|
if (std::strcmp(argv[i], "--copy-zone-items") == 0 && i + 2 < argc) {
|
|
outRc = handleCopyZoneItems(i, argc, argv); return true;
|
|
}
|
|
if (std::strcmp(argv[i], "--clone-item") == 0 && i + 2 < argc) {
|
|
outRc = handleCloneItem(i, argc, argv); return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace cli
|
|
} // namespace editor
|
|
} // namespace wowee
|