shadow-over-bishkek001/src/quest/QuestJournal.cpp
2026-07-07 11:24:21 +03:00

264 lines
8.6 KiB
C++

#include "quest/QuestJournal.h"
#include "external/nlohmann/json.hpp"
#include "utils/Utils.h"
#include "Localization.h"
#include <algorithm>
#include <iostream>
namespace ZL::Quest {
using json = nlohmann::json;
const char* toString(QuestStatus status) {
switch (status) {
case QuestStatus::Hidden: return "Hidden";
case QuestStatus::Available: return "Available";
case QuestStatus::Completed: return "Completed";
case QuestStatus::Failed: return "Failed";
default: return "Unknown";
}
}
static QuestStatus parseQuestStatus(const std::string& value) {
if (value == "Available") return QuestStatus::Available;
if (value == "Completed") return QuestStatus::Completed;
if (value == "Failed") return QuestStatus::Failed;
return QuestStatus::Hidden;
}
bool QuestJournal::loadFromFile(const std::string& path, const std::string& zipFile) {
const std::string content = ZL::readLocalizedConfigFile(path, zipFile);
if (content.empty()) {
std::cerr << "[quest] Failed to read " << path << std::endl;
return false;
}
json root;
try {
root = json::parse(content);
}
catch (const std::exception& e) {
std::cerr << "[quest] JSON parse error in " << path << ": " << e.what() << std::endl;
return false;
}
if (!root.contains("quests") || !root["quests"].is_array()) {
std::cerr << "[quest] Missing quests array in " << path << std::endl;
return false;
}
// Only clear existing definitions/state once the new file is confirmed
// valid, so a failed reload (e.g. a broken localized file) doesn't wipe
// the currently loaded quest journal.
quests.clear();
questOrder.clear();
for (const auto& item : root["quests"]) {
QuestDefinition def;
def.id = item.value("id", "");
def.title = item.value("title", def.id);
def.description = item.value("description", "");
def.initialStatus = parseQuestStatus(item.value("status", "Hidden"));
def.autoComplete = item.value("autoComplete", false);
if (item.contains("objectives") && item["objectives"].is_array()) {
for (const auto& obj : item["objectives"]) {
QuestObjective objective;
objective.id = obj.value("id", "");
objective.text = obj.value("text", "");
objective.completed = obj.value("completed", false);
objective.visible = obj.value("visible", true);
if (!objective.id.empty()) {
def.objectives.push_back(objective);
}
}
}
if (def.id.empty()) {
continue;
}
QuestState state;
state.definition = def;
state.status = def.initialStatus;
state.activeObjectiveIndex = 0;
state.orderIndex = static_cast<int>(questOrder.size());
quests[def.id] = std::move(state);
questOrder.push_back(def.id);
}
return true;
}
bool QuestJournal::reloadDefinitionsPreservingState(const std::string& path, const std::string& zipFile) {
json snapshot;
save(snapshot);
if (!loadFromFile(path, zipFile)) return false;
load(snapshot);
return true;
}
bool QuestJournal::setStatus(const std::string& questId, QuestStatus status) {
QuestState* quest = findQuest(questId);
if (!quest) return false;
quest->status = status;
return true;
}
bool QuestJournal::unlockQuest(const std::string& questId) {
QuestState* quest = findQuest(questId);
if (!quest) return false;
if (quest->status == QuestStatus::Hidden) {
quest->status = QuestStatus::Available;
if (onQuestUnlocked) onQuestUnlocked(questId);
}
return true;
}
bool QuestJournal::completeQuest(const std::string& questId) {
bool changed = setStatus(questId, QuestStatus::Completed);
if (changed && onQuestCompleted) onQuestCompleted(questId);
return changed;
}
bool QuestJournal::failQuest(const std::string& questId) {
bool changed = setStatus(questId, QuestStatus::Failed);
if (changed && onQuestFailed) onQuestFailed(questId);
return changed;
}
bool QuestJournal::setObjectiveCompleted(const std::string& questId, const std::string& objectiveId, bool completed) {
QuestState* quest = findQuest(questId);
if (!quest) return false;
for (auto& objective : quest->definition.objectives) {
if (objective.id == objectiveId) {
objective.completed = completed;
if (completed) {
if (onObjectiveCompleted) onObjectiveCompleted(questId, objectiveId);
}
if (completed && quest->definition.autoComplete && quest->status == QuestStatus::Available) {
const bool allDone = std::all_of(
quest->definition.objectives.begin(),
quest->definition.objectives.end(),
[](const QuestObjective& o) { return o.completed; });
if (allDone) {
quest->status = QuestStatus::Completed;
if (onQuestCompleted) onQuestCompleted(questId);
}
}
return true;
}
}
return false;
}
bool QuestJournal::setObjectiveVisible(const std::string& questId, const std::string& objectiveId, bool visible) {
QuestState* quest = findQuest(questId);
if (!quest) return false;
for (auto& objective : quest->definition.objectives) {
if (objective.id == objectiveId) {
objective.visible = visible;
return true;
}
}
return false;
}
bool QuestJournal::setActiveObjective(const std::string& questId, int objectiveIndex) {
QuestState* quest = findQuest(questId);
if (!quest) return false;
if (objectiveIndex < 0 || objectiveIndex >= static_cast<int>(quest->definition.objectives.size())) {
return false;
}
quest->activeObjectiveIndex = objectiveIndex;
return true;
}
QuestState* QuestJournal::findQuest(const std::string& questId) {
auto it = quests.find(questId);
return (it != quests.end()) ? &it->second : nullptr;
}
const QuestState* QuestJournal::findQuest(const std::string& questId) const {
auto it = quests.find(questId);
return (it != quests.end()) ? &it->second : nullptr;
}
std::vector<QuestState*> QuestJournal::getVisibleQuests() {
std::vector<QuestState*> result;
for (const std::string& id : questOrder) {
QuestState* quest = findQuest(id);
if (!quest) continue;
if (quest->status != QuestStatus::Hidden) {
result.push_back(quest);
}
}
return result;
}
std::vector<const QuestState*> QuestJournal::getVisibleQuests() const {
std::vector<const QuestState*> result;
for (const std::string& id : questOrder) {
const QuestState* quest = findQuest(id);
if (!quest) continue;
if (quest->status != QuestStatus::Hidden) {
result.push_back(quest);
}
}
return result;
}
void QuestJournal::save(nlohmann::json& out) const {
json arr = json::array();
for (const auto& id : questOrder) {
const auto& qs = quests.at(id);
json qj;
qj["id"] = id;
qj["status"] = static_cast<int>(qs.status);
qj["activeObjectiveIndex"] = qs.activeObjectiveIndex;
json objArr = json::array();
for (const auto& obj : qs.definition.objectives) {
objArr.push_back({ {"id", obj.id}, {"completed", obj.completed}, {"visible", obj.visible} });
}
qj["objectives"] = std::move(objArr);
arr.push_back(std::move(qj));
}
out["quests"] = std::move(arr);
}
void QuestJournal::load(const nlohmann::json& in) {
if (!in.contains("quests") || !in["quests"].is_array()) return;
for (const auto& qj : in["quests"]) {
const std::string id = qj.value("id", "");
auto it = quests.find(id);
if (it == quests.end()) continue;
auto& qs = it->second;
qs.status = static_cast<QuestStatus>(qj.value("status", 0));
qs.activeObjectiveIndex = qj.value("activeObjectiveIndex", 0);
if (qj.contains("objectives") && qj["objectives"].is_array()) {
for (const auto& oj : qj["objectives"]) {
const std::string objId = oj.value("id", "");
for (auto& obj : qs.definition.objectives) {
if (obj.id == objId) {
obj.completed = oj.value("completed", obj.completed);
obj.visible = oj.value("visible", obj.visible);
break;
}
}
}
}
}
}
} // namespace ZL::Quest