refactor(editor): extract quest-reward handlers into cli_quest_reward.cpp

Moves the two quest-reward mutation handlers
(--add-quest-reward-item, --set-quest-reward) out of main.cpp
into a new cli_quest_reward.{hpp,cpp} module. Both operate on
a quest's reward struct in zone.json: the first greedy-consumes
multiple item paths in one invocation, the second uses
order-independent flag/value pairs (--xp / --gold / --silver
/ --copper) with strict 'only changed fields are written'
semantics so partial updates don't clobber unrelated fields.

main.cpp shrinks by 114 lines (6,429 to 6,315).
This commit is contained in:
Kelsi 2026-05-09 08:01:28 -07:00
parent af3c4f0bd6
commit 408d7a611a
4 changed files with 175 additions and 118 deletions

View file

@ -1337,6 +1337,7 @@ add_executable(wowee_editor
tools/editor/cli_info_audio.cpp
tools/editor/cli_world_info.cpp
tools/editor/cli_quest_objective.cpp
tools/editor/cli_quest_reward.cpp
tools/editor/editor_app.cpp
tools/editor/editor_camera.cpp
tools/editor/editor_viewport.cpp

View file

@ -0,0 +1,153 @@
#include "cli_quest_reward.hpp"
#include "quest_editor.hpp"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <string>
namespace wowee {
namespace editor {
namespace cli {
namespace {
int handleAddQuestRewardItem(int& i, int argc, char** argv) {
// Append one or more item rewards to a quest. Multiple paths
// can be passed in a single invocation:
// --add-quest-reward-item zone 0 'Item:Sword' 'Item:Shield'
std::string zoneDir = argv[++i];
std::string idxStr = argv[++i];
std::string path = zoneDir + "/quests.json";
if (!std::filesystem::exists(path)) {
std::fprintf(stderr, "add-quest-reward-item: %s not found\n", path.c_str());
return 1;
}
int idx;
try { idx = std::stoi(idxStr); }
catch (...) {
std::fprintf(stderr, "add-quest-reward-item: bad questIdx '%s'\n", idxStr.c_str());
return 1;
}
wowee::editor::QuestEditor qe;
if (!qe.loadFromFile(path)) {
std::fprintf(stderr, "add-quest-reward-item: failed to load %s\n", path.c_str());
return 1;
}
if (idx < 0 || idx >= static_cast<int>(qe.questCount())) {
std::fprintf(stderr,
"add-quest-reward-item: questIdx %d out of range [0, %zu)\n",
idx, qe.questCount());
return 1;
}
wowee::editor::Quest* q = qe.getQuest(idx);
if (!q) return 1;
int added = 0;
// Greedy-consume any remaining args that don't start with '-'
// so the caller can batch-add a whole loot table in one shot.
while (i + 1 < argc && argv[i + 1][0] != '-') {
q->reward.itemRewards.push_back(argv[++i]);
added++;
}
if (added == 0) {
std::fprintf(stderr, "add-quest-reward-item: need at least one itemPath\n");
return 1;
}
if (!qe.saveToFile(path)) {
std::fprintf(stderr, "add-quest-reward-item: failed to write %s\n", path.c_str());
return 1;
}
std::printf("Added %d item reward(s) to quest %d ('%s'), now %zu total\n",
added, idx, q->title.c_str(), q->reward.itemRewards.size());
return 0;
}
int handleSetQuestReward(int& i, int argc, char** argv) {
// Update XP / coin reward fields on an existing quest. Each
// field is optional — only the ones explicitly passed are
// changed. This avoids the round-trip-and-clobber footgun of
// a "replace whole reward" command.
std::string zoneDir = argv[++i];
std::string idxStr = argv[++i];
std::string path = zoneDir + "/quests.json";
if (!std::filesystem::exists(path)) {
std::fprintf(stderr, "set-quest-reward: %s not found\n", path.c_str());
return 1;
}
int idx;
try { idx = std::stoi(idxStr); }
catch (...) {
std::fprintf(stderr, "set-quest-reward: bad questIdx '%s'\n", idxStr.c_str());
return 1;
}
wowee::editor::QuestEditor qe;
if (!qe.loadFromFile(path)) {
std::fprintf(stderr, "set-quest-reward: failed to load %s\n", path.c_str());
return 1;
}
if (idx < 0 || idx >= static_cast<int>(qe.questCount())) {
std::fprintf(stderr,
"set-quest-reward: questIdx %d out of range [0, %zu)\n",
idx, qe.questCount());
return 1;
}
wowee::editor::Quest* q = qe.getQuest(idx);
if (!q) return 1;
int changed = 0;
auto consumeUint = [&](const char* flag, uint32_t& target) {
if (i + 2 < argc && std::strcmp(argv[i + 1], flag) == 0) {
try {
target = static_cast<uint32_t>(std::stoul(argv[i + 2]));
i += 2;
changed++;
return true;
} catch (...) {
std::fprintf(stderr, "set-quest-reward: bad %s value '%s'\n",
flag, argv[i + 2]);
}
}
return false;
};
// Loop until no more recognised flags consume their value —
// order-independent, so callers can pass --gold then --xp.
bool any = true;
while (any) {
any = false;
if (consumeUint("--xp", q->reward.xp)) any = true;
if (consumeUint("--gold", q->reward.gold)) any = true;
if (consumeUint("--silver", q->reward.silver)) any = true;
if (consumeUint("--copper", q->reward.copper)) any = true;
}
if (changed == 0) {
std::fprintf(stderr,
"set-quest-reward: no fields changed — pass --xp / --gold / --silver / --copper\n");
return 1;
}
if (!qe.saveToFile(path)) {
std::fprintf(stderr, "set-quest-reward: failed to write %s\n", path.c_str());
return 1;
}
std::printf("Updated %d field(s) on quest %d ('%s'): xp=%u gold=%u silver=%u copper=%u\n",
changed, idx, q->title.c_str(),
q->reward.xp, q->reward.gold,
q->reward.silver, q->reward.copper);
return 0;
}
} // namespace
bool handleQuestReward(int& i, int argc, char** argv, int& outRc) {
if (std::strcmp(argv[i], "--add-quest-reward-item") == 0 && i + 3 < argc) {
outRc = handleAddQuestRewardItem(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--set-quest-reward") == 0 && i + 2 < argc) {
outRc = handleSetQuestReward(i, argc, argv); return true;
}
return false;
}
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -0,0 +1,17 @@
#pragma once
namespace wowee {
namespace editor {
namespace cli {
// Dispatch the quest-reward mutation handlers — both operate
// on a quest's reward struct in zone.json.
// --add-quest-reward-item append item rewards (greedy multi-arg)
// --set-quest-reward update XP / gold / silver / copper
//
// Returns true if matched; outRc holds the exit code.
bool handleQuestReward(int& i, int argc, char** argv, int& outRc);
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -38,6 +38,7 @@
#include "cli_info_audio.hpp"
#include "cli_world_info.hpp"
#include "cli_quest_objective.hpp"
#include "cli_quest_reward.hpp"
#include "content_pack.hpp"
#include "npc_spawner.hpp"
#include "object_placer.hpp"
@ -486,6 +487,9 @@ int main(int argc, char* argv[]) {
if (wowee::editor::cli::handleQuestObjective(i, argc, argv, outRc)) {
return outRc;
}
if (wowee::editor::cli::handleQuestReward(i, argc, argv, outRc)) {
return outRc;
}
}
if (std::strcmp(argv[i], "--data") == 0 && i + 1 < argc) {
dataPath = argv[++i];
@ -1602,124 +1606,6 @@ int main(int argc, char* argv[]) {
clone.position.x, clone.position.y, clone.position.z,
objs.size());
return 0;
} else if (std::strcmp(argv[i], "--add-quest-reward-item") == 0 && i + 3 < argc) {
// Append one or more item rewards to a quest. Multiple paths
// can be passed in a single invocation:
// --add-quest-reward-item zone 0 'Item:Sword' 'Item:Shield'
std::string zoneDir = argv[++i];
std::string idxStr = argv[++i];
std::string path = zoneDir + "/quests.json";
if (!std::filesystem::exists(path)) {
std::fprintf(stderr, "add-quest-reward-item: %s not found\n", path.c_str());
return 1;
}
int idx;
try { idx = std::stoi(idxStr); }
catch (...) {
std::fprintf(stderr, "add-quest-reward-item: bad questIdx '%s'\n", idxStr.c_str());
return 1;
}
wowee::editor::QuestEditor qe;
if (!qe.loadFromFile(path)) {
std::fprintf(stderr, "add-quest-reward-item: failed to load %s\n", path.c_str());
return 1;
}
if (idx < 0 || idx >= static_cast<int>(qe.questCount())) {
std::fprintf(stderr,
"add-quest-reward-item: questIdx %d out of range [0, %zu)\n",
idx, qe.questCount());
return 1;
}
wowee::editor::Quest* q = qe.getQuest(idx);
if (!q) return 1;
int added = 0;
// Greedy-consume any remaining args that don't start with '-'
// so the caller can batch-add a whole loot table in one shot.
while (i + 1 < argc && argv[i + 1][0] != '-') {
q->reward.itemRewards.push_back(argv[++i]);
added++;
}
if (added == 0) {
std::fprintf(stderr, "add-quest-reward-item: need at least one itemPath\n");
return 1;
}
if (!qe.saveToFile(path)) {
std::fprintf(stderr, "add-quest-reward-item: failed to write %s\n", path.c_str());
return 1;
}
std::printf("Added %d item reward(s) to quest %d ('%s'), now %zu total\n",
added, idx, q->title.c_str(), q->reward.itemRewards.size());
return 0;
} else if (std::strcmp(argv[i], "--set-quest-reward") == 0 && i + 2 < argc) {
// Update XP / coin reward fields on an existing quest. Each
// field is optional — only the ones explicitly passed are
// changed. This avoids the round-trip-and-clobber footgun of
// a "replace whole reward" command.
std::string zoneDir = argv[++i];
std::string idxStr = argv[++i];
std::string path = zoneDir + "/quests.json";
if (!std::filesystem::exists(path)) {
std::fprintf(stderr, "set-quest-reward: %s not found\n", path.c_str());
return 1;
}
int idx;
try { idx = std::stoi(idxStr); }
catch (...) {
std::fprintf(stderr, "set-quest-reward: bad questIdx '%s'\n", idxStr.c_str());
return 1;
}
wowee::editor::QuestEditor qe;
if (!qe.loadFromFile(path)) {
std::fprintf(stderr, "set-quest-reward: failed to load %s\n", path.c_str());
return 1;
}
if (idx < 0 || idx >= static_cast<int>(qe.questCount())) {
std::fprintf(stderr,
"set-quest-reward: questIdx %d out of range [0, %zu)\n",
idx, qe.questCount());
return 1;
}
wowee::editor::Quest* q = qe.getQuest(idx);
if (!q) return 1;
int changed = 0;
auto consumeUint = [&](const char* flag, uint32_t& target) {
if (i + 2 < argc && std::strcmp(argv[i + 1], flag) == 0) {
try {
target = static_cast<uint32_t>(std::stoul(argv[i + 2]));
i += 2;
changed++;
return true;
} catch (...) {
std::fprintf(stderr, "set-quest-reward: bad %s value '%s'\n",
flag, argv[i + 2]);
}
}
return false;
};
// Loop until no more recognised flags consume their value —
// order-independent, so callers can pass --gold then --xp.
bool any = true;
while (any) {
any = false;
if (consumeUint("--xp", q->reward.xp)) any = true;
if (consumeUint("--gold", q->reward.gold)) any = true;
if (consumeUint("--silver", q->reward.silver)) any = true;
if (consumeUint("--copper", q->reward.copper)) any = true;
}
if (changed == 0) {
std::fprintf(stderr,
"set-quest-reward: no fields changed — pass --xp / --gold / --silver / --copper\n");
return 1;
}
if (!qe.saveToFile(path)) {
std::fprintf(stderr, "set-quest-reward: failed to write %s\n", path.c_str());
return 1;
}
std::printf("Updated %d field(s) on quest %d ('%s'): xp=%u gold=%u silver=%u copper=%u\n",
changed, idx, q->title.c_str(),
q->reward.xp, q->reward.gold,
q->reward.silver, q->reward.copper);
return 0;
} else if (std::strcmp(argv[i], "--remove-creature") == 0 && i + 2 < argc) {
// Remove a creature spawn by 0-based index. Pair with
// --info-creatures (or your editor) to find the right index