From 070919ef510b31c5c25a90895908c77efdb334c9 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 11:41:11 -0700 Subject: [PATCH] feat(editor): add --add-quest CLI for headless quest creation Third headless authoring command, finishing the trio: wowee_editor --add-quest [giverId] [turnInId] [xp] [level] Optional positional fields are read in order; omit from the right. Bare 'wowee_editor --add-quest zone Title' produces a valid quest with default values, matching the editor GUI's New Quest behavior. Verified: appended 3 quests (250+100+0 XP) to a scaffolded zone, --info-quests --json reports total=3, totalXp=450, withReward=3. --- tools/editor/main.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 17f9f7b2..2da69523 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -36,6 +36,8 @@ static void printUsage(const char* argv0) { std::printf(" Append one creature spawn to <zoneDir>/creatures.json and exit\n"); std::printf(" --add-object <zoneDir> <m2|wmo> <gamePath> <x> <y> <z> [scale]\n"); std::printf(" Append one object placement to <zoneDir>/objects.json and exit\n"); + std::printf(" --add-quest <zoneDir> <title> [giverId] [turnInId] [xp] [level]\n"); + std::printf(" Append one quest to <zoneDir>/quests.json and exit\n"); std::printf(" --build-woc <wot-base> Generate a WOC collision mesh from WHM/WOT and exit\n"); std::printf(" --regen-collision <zoneDir> Rebuild every WOC under a zone dir and exit\n"); std::printf(" --fix-zone <zoneDir> Re-parse + re-save zone JSONs to apply latest scrubs/caps and exit\n"); @@ -87,7 +89,7 @@ int main(int argc, char* argv[]) { "--info-extract", "--info-zone", "--info-wcp", "--list-wcp", "--unpack-wcp", "--pack-wcp", "--validate", "--zone-summary", - "--scaffold-zone", "--add-creature", "--add-object", + "--scaffold-zone", "--add-creature", "--add-object", "--add-quest", "--build-woc", "--regen-collision", "--fix-zone", "--export-png", "--convert-m2", "--convert-wmo", @@ -117,6 +119,11 @@ int main(int argc, char* argv[]) { "--add-object requires <zoneDir> <m2|wmo> <gamePath> <x> <y> <z>\n"); return 1; } + if (std::strcmp(argv[i], "--add-quest") == 0 && i + 2 >= argc) { + std::fprintf(stderr, + "--add-quest requires <zoneDir> <title>\n"); + return 1; + } } for (int i = 1; i < argc; i++) { @@ -1108,6 +1115,45 @@ int main(int argc, char* argv[]) { outPath.c_str(), col.triangles.size(), col.walkableCount(), col.steepCount()); return 0; + } else if (std::strcmp(argv[i], "--add-quest") == 0 && i + 2 < argc) { + // Append a single quest to a zone's quests.json. + // Args: <zoneDir> <title> [giverId] [turnInId] [xp] [level] + std::string zoneDir = argv[++i]; + std::string title = argv[++i]; + namespace fs = std::filesystem; + if (!fs::exists(zoneDir)) { + std::fprintf(stderr, "add-quest: zone '%s' does not exist\n", + zoneDir.c_str()); + return 1; + } + wowee::editor::Quest q; + q.title = title; + // Optional positional args after title. Each is read in order; + // an empty string or '-' stops consumption so users can omit + // later fields. + auto tryReadUint = [&](uint32_t& target) { + if (i + 1 >= argc || argv[i + 1][0] == '-') return false; + try { + target = static_cast<uint32_t>(std::stoul(argv[i + 1])); + ++i; + return true; + } catch (...) { return false; } + }; + tryReadUint(q.questGiverNpcId); + tryReadUint(q.turnInNpcId); + tryReadUint(q.reward.xp); + tryReadUint(q.requiredLevel); + wowee::editor::QuestEditor qe; + std::string path = zoneDir + "/quests.json"; + if (fs::exists(path)) qe.loadFromFile(path); + qe.addQuest(q); + if (!qe.saveToFile(path)) { + std::fprintf(stderr, "add-quest: failed to write %s\n", path.c_str()); + return 1; + } + std::printf("Added quest '%s' to %s (now %zu total)\n", + title.c_str(), 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: <zoneDir> <m2|wmo> <gamePath> <x> <y> <z> [scale]