mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-10 02:53:51 +00:00
refactor(editor): extract quest+objective handlers into cli_quest_objective.cpp
Moves three contiguous quest mutation handlers (--add-quest,
--add-quest-objective, --remove-quest-objective) out of
main.cpp into a new cli_quest_objective.{hpp,cpp} module.
All three operate on a zone's quests.json with the same
QuestEditor load-edit-save flow; the objective add/remove
pair includes auto-generated description text from
type+name+count for tooltip-friendly defaults.
main.cpp shrinks by 174 lines (6,603 to 6,429). Reward and
clone-quest handlers stay inline for follow-up extractions.
This commit is contained in:
parent
4189051c09
commit
db8d499e8e
4 changed files with 246 additions and 179 deletions
219
tools/editor/cli_quest_objective.cpp
Normal file
219
tools/editor/cli_quest_objective.cpp
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
#include "cli_quest_objective.hpp"
|
||||
|
||||
#include "quest_editor.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
namespace {
|
||||
|
||||
int handleAddQuest(int& i, int argc, char** argv) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
int handleAddQuestObjective(int& i, int argc, char** argv) {
|
||||
// Append a single objective to an existing quest. The quest
|
||||
// must already exist (use --add-quest first); index is 0-based
|
||||
// and matches --list-quests output.
|
||||
std::string zoneDir = argv[++i];
|
||||
std::string idxStr = argv[++i];
|
||||
std::string typeStr = argv[++i];
|
||||
std::string targetName = argv[++i];
|
||||
std::string path = zoneDir + "/quests.json";
|
||||
if (!std::filesystem::exists(path)) {
|
||||
std::fprintf(stderr, "add-quest-objective: %s not found — run --add-quest first\n",
|
||||
path.c_str());
|
||||
return 1;
|
||||
}
|
||||
int idx;
|
||||
try { idx = std::stoi(idxStr); }
|
||||
catch (...) {
|
||||
std::fprintf(stderr, "add-quest-objective: bad questIdx '%s'\n", idxStr.c_str());
|
||||
return 1;
|
||||
}
|
||||
using OT = wowee::editor::QuestObjectiveType;
|
||||
OT type;
|
||||
if (typeStr == "kill") type = OT::KillCreature;
|
||||
else if (typeStr == "collect") type = OT::CollectItem;
|
||||
else if (typeStr == "talk") type = OT::TalkToNPC;
|
||||
else if (typeStr == "explore") type = OT::ExploreArea;
|
||||
else if (typeStr == "escort") type = OT::EscortNPC;
|
||||
else if (typeStr == "use") type = OT::UseObject;
|
||||
else {
|
||||
std::fprintf(stderr,
|
||||
"add-quest-objective: type must be kill/collect/talk/explore/escort/use, got '%s'\n",
|
||||
typeStr.c_str());
|
||||
return 1;
|
||||
}
|
||||
uint32_t count = 1;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
||||
try {
|
||||
count = static_cast<uint32_t>(std::stoul(argv[++i]));
|
||||
if (count == 0) count = 1;
|
||||
} catch (...) {}
|
||||
}
|
||||
wowee::editor::QuestEditor qe;
|
||||
if (!qe.loadFromFile(path)) {
|
||||
std::fprintf(stderr, "add-quest-objective: failed to load %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (idx < 0 || idx >= static_cast<int>(qe.questCount())) {
|
||||
std::fprintf(stderr,
|
||||
"add-quest-objective: questIdx %d out of range [0, %zu)\n",
|
||||
idx, qe.questCount());
|
||||
return 1;
|
||||
}
|
||||
wowee::editor::QuestObjective obj;
|
||||
obj.type = type;
|
||||
obj.targetName = targetName;
|
||||
obj.targetCount = count;
|
||||
// Auto-generate a description from type+name+count so addons
|
||||
// and tooltips have something useful by default. The user can
|
||||
// edit quests.json directly if they want bespoke prose.
|
||||
const char* verb = "complete";
|
||||
switch (type) {
|
||||
case OT::KillCreature: verb = "Slay"; break;
|
||||
case OT::CollectItem: verb = "Collect"; break;
|
||||
case OT::TalkToNPC: verb = "Talk to"; break;
|
||||
case OT::ExploreArea: verb = "Explore"; break;
|
||||
case OT::EscortNPC: verb = "Escort"; break;
|
||||
case OT::UseObject: verb = "Use"; break;
|
||||
}
|
||||
obj.description = std::string(verb) + " " +
|
||||
(count > 1 ? std::to_string(count) + " " : "") +
|
||||
targetName;
|
||||
// Quest is stored by value in the editor's vector; mutate via
|
||||
// the non-const getter, which gives us a pointer we can write
|
||||
// through.
|
||||
wowee::editor::Quest* q = qe.getQuest(idx);
|
||||
if (!q) {
|
||||
std::fprintf(stderr, "add-quest-objective: getQuest(%d) returned null\n", idx);
|
||||
return 1;
|
||||
}
|
||||
q->objectives.push_back(obj);
|
||||
if (!qe.saveToFile(path)) {
|
||||
std::fprintf(stderr, "add-quest-objective: failed to write %s\n",
|
||||
path.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::printf("Added objective '%s' to quest %d ('%s'), now %zu objective(s)\n",
|
||||
obj.description.c_str(), idx, q->title.c_str(),
|
||||
q->objectives.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleRemoveQuestObjective(int& i, int argc, char** argv) {
|
||||
// Symmetric counterpart to --add-quest-objective. Removes the
|
||||
// objective at <objIdx> within quest <questIdx>. Pair with
|
||||
// --info-quests / --list-quests to find the right indices.
|
||||
std::string zoneDir = argv[++i];
|
||||
std::string qIdxStr = argv[++i];
|
||||
std::string oIdxStr = argv[++i];
|
||||
std::string path = zoneDir + "/quests.json";
|
||||
if (!std::filesystem::exists(path)) {
|
||||
std::fprintf(stderr, "remove-quest-objective: %s not found\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
int qIdx, oIdx;
|
||||
try {
|
||||
qIdx = std::stoi(qIdxStr);
|
||||
oIdx = std::stoi(oIdxStr);
|
||||
} catch (...) {
|
||||
std::fprintf(stderr, "remove-quest-objective: bad index\n");
|
||||
return 1;
|
||||
}
|
||||
wowee::editor::QuestEditor qe;
|
||||
if (!qe.loadFromFile(path)) {
|
||||
std::fprintf(stderr, "remove-quest-objective: failed to load %s\n",
|
||||
path.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (qIdx < 0 || qIdx >= static_cast<int>(qe.questCount())) {
|
||||
std::fprintf(stderr,
|
||||
"remove-quest-objective: questIdx %d out of range [0, %zu)\n",
|
||||
qIdx, qe.questCount());
|
||||
return 1;
|
||||
}
|
||||
wowee::editor::Quest* q = qe.getQuest(qIdx);
|
||||
if (!q) return 1;
|
||||
if (oIdx < 0 || oIdx >= static_cast<int>(q->objectives.size())) {
|
||||
std::fprintf(stderr,
|
||||
"remove-quest-objective: objIdx %d out of range [0, %zu)\n",
|
||||
oIdx, q->objectives.size());
|
||||
return 1;
|
||||
}
|
||||
std::string removedDesc = q->objectives[oIdx].description;
|
||||
q->objectives.erase(q->objectives.begin() + oIdx);
|
||||
if (!qe.saveToFile(path)) {
|
||||
std::fprintf(stderr, "remove-quest-objective: failed to write %s\n",
|
||||
path.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::printf("Removed objective '%s' (was index %d) from quest %d ('%s'), now %zu remaining\n",
|
||||
removedDesc.c_str(), oIdx, qIdx, q->title.c_str(),
|
||||
q->objectives.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool handleQuestObjective(int& i, int argc, char** argv, int& outRc) {
|
||||
if (std::strcmp(argv[i], "--add-quest") == 0 && i + 2 < argc) {
|
||||
outRc = handleAddQuest(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--add-quest-objective") == 0 && i + 4 < argc) {
|
||||
outRc = handleAddQuestObjective(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--remove-quest-objective") == 0 && i + 3 < argc) {
|
||||
outRc = handleRemoveQuestObjective(i, argc, argv); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
22
tools/editor/cli_quest_objective.hpp
Normal file
22
tools/editor/cli_quest_objective.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
// Dispatch the quest / quest-objective mutation handlers.
|
||||
// All three operate on a zone's quests.json:
|
||||
// --add-quest append a new quest (title + opt fields)
|
||||
// --add-quest-objective append an objective to an existing quest
|
||||
// --remove-quest-objective remove an objective by index
|
||||
//
|
||||
// Reward and clone-quest operations live in their own
|
||||
// modules (cli_quest_reward / cli_clone) so the per-quest
|
||||
// objective logic stays focused.
|
||||
//
|
||||
// Returns true if matched; outRc holds the exit code.
|
||||
bool handleQuestObjective(int& i, int argc, char** argv, int& outRc);
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
#include "cli_info_density.hpp"
|
||||
#include "cli_info_audio.hpp"
|
||||
#include "cli_world_info.hpp"
|
||||
#include "cli_quest_objective.hpp"
|
||||
#include "content_pack.hpp"
|
||||
#include "npc_spawner.hpp"
|
||||
#include "object_placer.hpp"
|
||||
|
|
@ -482,6 +483,9 @@ int main(int argc, char* argv[]) {
|
|||
if (wowee::editor::cli::handleWorldInfo(i, argc, argv, outRc)) {
|
||||
return outRc;
|
||||
}
|
||||
if (wowee::editor::cli::handleQuestObjective(i, argc, argv, outRc)) {
|
||||
return outRc;
|
||||
}
|
||||
}
|
||||
if (std::strcmp(argv[i], "--data") == 0 && i + 1 < argc) {
|
||||
dataPath = argv[++i];
|
||||
|
|
@ -1410,185 +1414,6 @@ 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-quest-objective") == 0 && i + 4 < argc) {
|
||||
// Append a single objective to an existing quest. The quest
|
||||
// must already exist (use --add-quest first); index is 0-based
|
||||
// and matches --list-quests output.
|
||||
std::string zoneDir = argv[++i];
|
||||
std::string idxStr = argv[++i];
|
||||
std::string typeStr = argv[++i];
|
||||
std::string targetName = argv[++i];
|
||||
std::string path = zoneDir + "/quests.json";
|
||||
if (!std::filesystem::exists(path)) {
|
||||
std::fprintf(stderr, "add-quest-objective: %s not found — run --add-quest first\n",
|
||||
path.c_str());
|
||||
return 1;
|
||||
}
|
||||
int idx;
|
||||
try { idx = std::stoi(idxStr); }
|
||||
catch (...) {
|
||||
std::fprintf(stderr, "add-quest-objective: bad questIdx '%s'\n", idxStr.c_str());
|
||||
return 1;
|
||||
}
|
||||
using OT = wowee::editor::QuestObjectiveType;
|
||||
OT type;
|
||||
if (typeStr == "kill") type = OT::KillCreature;
|
||||
else if (typeStr == "collect") type = OT::CollectItem;
|
||||
else if (typeStr == "talk") type = OT::TalkToNPC;
|
||||
else if (typeStr == "explore") type = OT::ExploreArea;
|
||||
else if (typeStr == "escort") type = OT::EscortNPC;
|
||||
else if (typeStr == "use") type = OT::UseObject;
|
||||
else {
|
||||
std::fprintf(stderr,
|
||||
"add-quest-objective: type must be kill/collect/talk/explore/escort/use, got '%s'\n",
|
||||
typeStr.c_str());
|
||||
return 1;
|
||||
}
|
||||
uint32_t count = 1;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
||||
try {
|
||||
count = static_cast<uint32_t>(std::stoul(argv[++i]));
|
||||
if (count == 0) count = 1;
|
||||
} catch (...) {}
|
||||
}
|
||||
wowee::editor::QuestEditor qe;
|
||||
if (!qe.loadFromFile(path)) {
|
||||
std::fprintf(stderr, "add-quest-objective: failed to load %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (idx < 0 || idx >= static_cast<int>(qe.questCount())) {
|
||||
std::fprintf(stderr,
|
||||
"add-quest-objective: questIdx %d out of range [0, %zu)\n",
|
||||
idx, qe.questCount());
|
||||
return 1;
|
||||
}
|
||||
wowee::editor::QuestObjective obj;
|
||||
obj.type = type;
|
||||
obj.targetName = targetName;
|
||||
obj.targetCount = count;
|
||||
// Auto-generate a description from type+name+count so addons
|
||||
// and tooltips have something useful by default. The user can
|
||||
// edit quests.json directly if they want bespoke prose.
|
||||
const char* verb = "complete";
|
||||
switch (type) {
|
||||
case OT::KillCreature: verb = "Slay"; break;
|
||||
case OT::CollectItem: verb = "Collect"; break;
|
||||
case OT::TalkToNPC: verb = "Talk to"; break;
|
||||
case OT::ExploreArea: verb = "Explore"; break;
|
||||
case OT::EscortNPC: verb = "Escort"; break;
|
||||
case OT::UseObject: verb = "Use"; break;
|
||||
}
|
||||
obj.description = std::string(verb) + " " +
|
||||
(count > 1 ? std::to_string(count) + " " : "") +
|
||||
targetName;
|
||||
// Quest is stored by value in the editor's vector; mutate via
|
||||
// the non-const getter, which gives us a pointer we can write
|
||||
// through.
|
||||
wowee::editor::Quest* q = qe.getQuest(idx);
|
||||
if (!q) {
|
||||
std::fprintf(stderr, "add-quest-objective: getQuest(%d) returned null\n", idx);
|
||||
return 1;
|
||||
}
|
||||
q->objectives.push_back(obj);
|
||||
if (!qe.saveToFile(path)) {
|
||||
std::fprintf(stderr, "add-quest-objective: failed to write %s\n",
|
||||
path.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::printf("Added objective '%s' to quest %d ('%s'), now %zu objective(s)\n",
|
||||
obj.description.c_str(), idx, q->title.c_str(),
|
||||
q->objectives.size());
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--remove-quest-objective") == 0 && i + 3 < argc) {
|
||||
// Symmetric counterpart to --add-quest-objective. Removes the
|
||||
// objective at <objIdx> within quest <questIdx>. Pair with
|
||||
// --info-quests / --list-quests to find the right indices.
|
||||
std::string zoneDir = argv[++i];
|
||||
std::string qIdxStr = argv[++i];
|
||||
std::string oIdxStr = argv[++i];
|
||||
std::string path = zoneDir + "/quests.json";
|
||||
if (!std::filesystem::exists(path)) {
|
||||
std::fprintf(stderr, "remove-quest-objective: %s not found\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
int qIdx, oIdx;
|
||||
try {
|
||||
qIdx = std::stoi(qIdxStr);
|
||||
oIdx = std::stoi(oIdxStr);
|
||||
} catch (...) {
|
||||
std::fprintf(stderr, "remove-quest-objective: bad index\n");
|
||||
return 1;
|
||||
}
|
||||
wowee::editor::QuestEditor qe;
|
||||
if (!qe.loadFromFile(path)) {
|
||||
std::fprintf(stderr, "remove-quest-objective: failed to load %s\n",
|
||||
path.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (qIdx < 0 || qIdx >= static_cast<int>(qe.questCount())) {
|
||||
std::fprintf(stderr,
|
||||
"remove-quest-objective: questIdx %d out of range [0, %zu)\n",
|
||||
qIdx, qe.questCount());
|
||||
return 1;
|
||||
}
|
||||
wowee::editor::Quest* q = qe.getQuest(qIdx);
|
||||
if (!q) return 1;
|
||||
if (oIdx < 0 || oIdx >= static_cast<int>(q->objectives.size())) {
|
||||
std::fprintf(stderr,
|
||||
"remove-quest-objective: objIdx %d out of range [0, %zu)\n",
|
||||
oIdx, q->objectives.size());
|
||||
return 1;
|
||||
}
|
||||
std::string removedDesc = q->objectives[oIdx].description;
|
||||
q->objectives.erase(q->objectives.begin() + oIdx);
|
||||
if (!qe.saveToFile(path)) {
|
||||
std::fprintf(stderr, "remove-quest-objective: failed to write %s\n",
|
||||
path.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::printf("Removed objective '%s' (was index %d) from quest %d ('%s'), now %zu remaining\n",
|
||||
removedDesc.c_str(), oIdx, qIdx, q->title.c_str(),
|
||||
q->objectives.size());
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--clone-quest") == 0 && i + 2 < argc) {
|
||||
// Duplicate a quest. Useful for templating: create a base
|
||||
// quest with objectives + rewards once, then clone N times
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue