From dd36182cd3a36940e8430145efafe3ec35e30d64 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 7 May 2026 01:33:58 -0700 Subject: [PATCH] feat(editor): add --add-item, introducing zone items.json content type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a new per-zone content file alongside creatures.json / objects.json / quests.json. Schema: {"items": [{id, name, quality, displayId, itemLevel, stackable}, ...]}. Inline JSON manipulation via nlohmann::json — items are simple records and don't yet need NpcSpawner-style infrastructure. ID assignment: pass 0 (or omit) to auto-pick the smallest unused positive integer so numbering stays contiguous. Explicit IDs are honored. Duplicate IDs rejected with exit 1 so collisions are visible. Quality is 0..6 (poor/common/uncommon/rare/epic/legendary/artifact) — summary line maps the number to the human name so users see what they wrote. Verified: auto-id sequential (1, 2) → explicit id (99) honored → duplicate id rejected with exit 1; JSON schema stable, quality labels correct, file auto-created on first call. Brings command count to 196. --- tools/editor/main.cpp | 110 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 055a9f1c..8cb71f90 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -528,6 +528,8 @@ static void printUsage(const char* argv0) { std::printf(" Synthesize a placeholder texture (solid hex color or 'checker'/'grid'); default 256x256\n"); std::printf(" --gen-mesh [size]\n"); std::printf(" Synthesize a procedural WOM primitive with proper normals, UVs, and bounds\n"); + std::printf(" --add-item [id] [quality] [displayId] [itemLevel]\n"); + std::printf(" Append one item entry to /items.json (auto-creates the file)\n"); std::printf(" --convert-dbc-json [out.json]\n"); std::printf(" Convert one DBC file to wowee JSON sidecar format\n"); std::printf(" --convert-json-dbc [out.dbc]\n"); @@ -930,7 +932,7 @@ int main(int argc, char* argv[]) { "--check-zone-refs", "--check-zone-content", "--check-project-content", "--check-project-refs", "--export-zone-deps-md", "--export-zone-spawn-png", - "--add-creature", "--add-object", "--add-quest", + "--add-creature", "--add-object", "--add-quest", "--add-item", "--add-quest-objective", "--add-quest-reward-item", "--set-quest-reward", "--remove-quest-objective", "--clone-quest", "--clone-creature", "--clone-object", @@ -12476,6 +12478,112 @@ int main(int argc, char* argv[]) { std::printf("Added creature '%s' to %s (now %zu total)\n", name.c_str(), path.c_str(), spawner.spawnCount()); return 0; + } else if (std::strcmp(argv[i], "--add-item") == 0 && i + 2 < argc) { + // Append one item entry to /items.json. Inline + // JSON without a dedicated editor class — items.json is + // a simple {"items": [...]} array of records, and the + // schema is small enough that we don't need NpcSpawner- + // style infrastructure yet. + // + // Schema per item: + // id (uint32) — Item.dbc primary key (auto-increments + // from 1 if omitted) + // name (string) + // quality (uint8) — 0..6 (poor..artifact, default 1) + // displayId (uint32) — ItemDisplayInfo index (default 0) + // itemLevel (uint32) — default 1 + // stackable (uint32) — max stack size (default 1) + std::string zoneDir = argv[++i]; + std::string name = argv[++i]; + namespace fs = std::filesystem; + if (!fs::exists(zoneDir)) { + std::fprintf(stderr, + "add-item: zone '%s' does not exist\n", zoneDir.c_str()); + return 1; + } + uint32_t id = 0, displayId = 0, itemLevel = 1; + uint32_t quality = 1; + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { id = static_cast(std::stoul(argv[++i])); } + catch (...) {} + } + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { quality = static_cast(std::stoul(argv[++i])); } + catch (...) {} + if (quality > 6) quality = 1; + } + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { displayId = static_cast(std::stoul(argv[++i])); } + catch (...) {} + } + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { itemLevel = static_cast(std::stoul(argv[++i])); } + catch (...) {} + } + std::string path = zoneDir + "/items.json"; + nlohmann::json doc = nlohmann::json::object({{"items", + nlohmann::json::array()}}); + if (fs::exists(path)) { + std::ifstream in(path); + try { in >> doc; } catch (...) { + std::fprintf(stderr, + "add-item: %s exists but is not valid JSON\n", + path.c_str()); + return 1; + } + if (!doc.contains("items") || !doc["items"].is_array()) { + doc["items"] = nlohmann::json::array(); + } + } + // Auto-assign id if user passed 0 / nothing — pick the + // smallest unused positive integer so the items.json + // numbering stays contiguous. + if (id == 0) { + std::set used; + for (const auto& it : doc["items"]) { + if (it.contains("id") && it["id"].is_number_unsigned()) { + used.insert(it["id"].get()); + } + } + id = 1; + while (used.count(id)) ++id; + } + // Reject duplicate id so the user notices a collision. + for (const auto& it : doc["items"]) { + if (it.contains("id") && it["id"].is_number_unsigned() && + it["id"].get() == id) { + std::fprintf(stderr, + "add-item: id %u already in use in %s\n", + id, path.c_str()); + return 1; + } + } + nlohmann::json item = { + {"id", id}, + {"name", name}, + {"quality", quality}, + {"displayId", displayId}, + {"itemLevel", itemLevel}, + {"stackable", 1}, + }; + doc["items"].push_back(item); + std::ofstream out(path); + if (!out) { + std::fprintf(stderr, + "add-item: failed to write %s\n", path.c_str()); + return 1; + } + out << doc.dump(2); + out.close(); + static const char* qualityNames[] = { + "poor", "common", "uncommon", "rare", "epic", + "legendary", "artifact" + }; + std::printf("Added item '%s' (id=%u, quality=%s, ilvl=%u) to %s (now %zu total)\n", + name.c_str(), id, + qualityNames[quality], itemLevel, + path.c_str(), doc["items"].size()); + return 0; } else if (std::strcmp(argv[i], "--scaffold-zone") == 0 && i + 1 < argc) { // Generate a minimal valid empty zone — useful for kickstarting // a new authoring session without needing to launch the GUI.