From 8fb3717995cb48870a3cd6e6320cffc1289c7970 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 12:42:17 -0700 Subject: [PATCH] feat(editor): add --list-quest-objectives and --list-quest-rewards --list-quests shows the quest roster but doesn't drill into objectives or item rewards (--info-quests just counts). These complete the detail picture and unblock --remove-quest-objective by exposing the 0-based objIdx the user needs: wowee_editor --list-quest-objectives custom_zones/Z/quests.json 0 Quest 0 ('Hunt'): 3 objective(s) idx type count target description 0 kill 5 Wolf Slay 5 Wolf 1 collect 3 Pelt Collect 3 Pelt 2 talk 1 Mayor Talk to Mayor wowee_editor --list-quest-rewards custom_zones/Z/quests.json 0 Quest 0 ('Hunt') rewards: xp : 999 coin : 5g 30s 99c items : 2 [0] Item:Sword [1] Item:Shield Both have --json mode for CI/scripting. Range-check questIdx and exit 1 on out-of-range with a precise message. Verified: scaffolded zone, added quest with 3 objectives + 2 items + full coin reward; both listings show the right data, JSON output is well-formed and enumerable. --- tools/editor/main.cpp | 123 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 2dc7e4af..a05e7b20 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -502,6 +502,10 @@ static void printUsage(const char* argv0) { std::printf(" List every object with index, type, path, position\n"); std::printf(" --list-quests

[--json]\n"); std::printf(" List every quest with index, title, giver, XP\n"); + std::printf(" --list-quest-objectives

[--json]\n"); + std::printf(" List every objective on a quest (for --remove-quest-objective)\n"); + std::printf(" --list-quest-rewards

[--json]\n"); + std::printf(" List XP/coin/item rewards on a quest\n"); std::printf(" --info-wcp [--json]\n"); std::printf(" Print WCP archive metadata (name, files) and exit\n"); std::printf(" --list-wcp Print every file inside a WCP archive (sorted by path) and exit\n"); @@ -530,6 +534,7 @@ int main(int argc, char* argv[]) { "--info-png", "--info-jsondbc", "--info-zone", "--info-wcp", "--list-wcp", "--list-creatures", "--list-objects", "--list-quests", + "--list-quest-objectives", "--list-quest-rewards", "--unpack-wcp", "--pack-wcp", "--validate", "--validate-wom", "--validate-wob", "--validate-woc", "--validate-whm", "--validate-all", "--zone-summary", @@ -1414,6 +1419,124 @@ int main(int argc, char* argv[]) { q.nextQuestId ? " [chained]" : ""); } return 0; + } else if (std::strcmp(argv[i], "--list-quest-objectives") == 0 && i + 2 < argc) { + // Per-quest objective listing — pairs with --remove-quest-objective + // (which takes objIdx). Tabulates type, target, count, description. + std::string path = argv[++i]; + std::string idxStr = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + int qIdx; + try { qIdx = std::stoi(idxStr); } + catch (...) { + std::fprintf(stderr, "list-quest-objectives: bad questIdx '%s'\n", idxStr.c_str()); + return 1; + } + wowee::editor::QuestEditor qe; + if (!qe.loadFromFile(path)) { + std::fprintf(stderr, "list-quest-objectives: failed to load %s\n", path.c_str()); + return 1; + } + if (qIdx < 0 || qIdx >= static_cast(qe.questCount())) { + std::fprintf(stderr, + "list-quest-objectives: questIdx %d out of range [0, %zu)\n", + qIdx, qe.questCount()); + return 1; + } + const auto& q = qe.getQuests()[qIdx]; + using OT = wowee::editor::QuestObjectiveType; + auto typeName = [](OT t) { + switch (t) { + case OT::KillCreature: return "kill"; + case OT::CollectItem: return "collect"; + case OT::TalkToNPC: return "talk"; + case OT::ExploreArea: return "explore"; + case OT::EscortNPC: return "escort"; + case OT::UseObject: return "use"; + } + return "?"; + }; + if (jsonOut) { + nlohmann::json j; + j["file"] = path; + j["questIdx"] = qIdx; + j["title"] = q.title; + j["count"] = q.objectives.size(); + nlohmann::json arr = nlohmann::json::array(); + for (size_t o = 0; o < q.objectives.size(); ++o) { + const auto& ob = q.objectives[o]; + arr.push_back({ + {"index", o}, + {"type", typeName(ob.type)}, + {"target", ob.targetName}, + {"count", ob.targetCount}, + {"description", ob.description}, + }); + } + j["objectives"] = arr; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("Quest %d ('%s'): %zu objective(s)\n", + qIdx, q.title.c_str(), q.objectives.size()); + std::printf(" idx type count target description\n"); + for (size_t o = 0; o < q.objectives.size(); ++o) { + const auto& ob = q.objectives[o]; + std::printf(" %3zu %-7s %5u %-18s %s\n", + o, typeName(ob.type), ob.targetCount, + ob.targetName.substr(0, 18).c_str(), + ob.description.c_str()); + } + return 0; + } else if (std::strcmp(argv[i], "--list-quest-rewards") == 0 && i + 2 < argc) { + // Per-quest reward listing. Shows XP/coin breakdown plus the + // full itemRewards list (which --info-quests only counts). + std::string path = argv[++i]; + std::string idxStr = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + int qIdx; + try { qIdx = std::stoi(idxStr); } + catch (...) { + std::fprintf(stderr, "list-quest-rewards: bad questIdx '%s'\n", idxStr.c_str()); + return 1; + } + wowee::editor::QuestEditor qe; + if (!qe.loadFromFile(path)) { + std::fprintf(stderr, "list-quest-rewards: failed to load %s\n", path.c_str()); + return 1; + } + if (qIdx < 0 || qIdx >= static_cast(qe.questCount())) { + std::fprintf(stderr, + "list-quest-rewards: questIdx %d out of range [0, %zu)\n", + qIdx, qe.questCount()); + return 1; + } + const auto& q = qe.getQuests()[qIdx]; + const auto& r = q.reward; + if (jsonOut) { + nlohmann::json j; + j["file"] = path; + j["questIdx"] = qIdx; + j["title"] = q.title; + j["xp"] = r.xp; + j["gold"] = r.gold; + j["silver"] = r.silver; + j["copper"] = r.copper; + j["items"] = r.itemRewards; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("Quest %d ('%s') rewards:\n", qIdx, q.title.c_str()); + std::printf(" xp : %u\n", r.xp); + std::printf(" coin : %ug %us %uc\n", r.gold, r.silver, r.copper); + std::printf(" items : %zu\n", r.itemRewards.size()); + for (size_t k = 0; k < r.itemRewards.size(); ++k) { + std::printf(" [%zu] %s\n", k, r.itemRewards[k].c_str()); + } + return 0; } else if (std::strcmp(argv[i], "--diff-wcp") == 0 && i + 2 < argc) { // Print which files differ between two WCP archives. Useful // when verifying that an authoring tweak only changed what