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.
This commit is contained in:
Kelsi 2026-05-06 12:42:17 -07:00
parent d4b789b811
commit 8fb3717995

View file

@ -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 <p> [--json]\n");
std::printf(" List every quest with index, title, giver, XP\n");
std::printf(" --list-quest-objectives <p> <questIdx> [--json]\n");
std::printf(" List every objective on a quest (for --remove-quest-objective)\n");
std::printf(" --list-quest-rewards <p> <questIdx> [--json]\n");
std::printf(" List XP/coin/item rewards on a quest\n");
std::printf(" --info-wcp <wcp-path> [--json]\n");
std::printf(" Print WCP archive metadata (name, files) and exit\n");
std::printf(" --list-wcp <wcp-path> 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<int>(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<int>(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