最终整理版

This commit is contained in:
2026-06-03 17:04:06 +08:00
commit 959055ce90
1240 changed files with 80570 additions and 0 deletions
+394
View File
@@ -0,0 +1,394 @@
#include "QuestSystem.h"
#include <algorithm>
#include <fstream>
#include <sstream>
#include <utility>
namespace {
constexpr const char* kUnknownQuestState = "ProgressCommons.UnknownProgress";
constexpr const char* kCompletedToken = ".REWARDS_WITHDREW";
constexpr const char* kTutorialCompleted = "ProgressCommons.TUTORIAL.EKINU_DONE";
constexpr const char* kCompletedProgress = "ProgressCommons.CompletedProgress";
bool HasSuffix(const std::string& value, const std::string& suffix)
{
return value.size() >= suffix.size()
&& value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0;
}
std::string TrimQuestValue(std::string value)
{
while (!value.empty() && (value.front() == ' ' || value.front() == '\t')) {
value.erase(value.begin());
}
while (!value.empty() && (value.back() == ' ' || value.back() == '\t' || value.back() == '\r')) {
value.pop_back();
}
if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
value = value.substr(1, value.size() - 2);
}
return value;
}
std::string QuestSymbolForId(int id)
{
static const std::map<int, std::string> symbols = {
{0, "ProgressCommons.Quest.SPLATYNA_OFFERING"},
{1, "ProgressCommons.Quest.GRAIN_IN_THE_SAND"},
{2, "ProgressCommons.Quest.SNAKE_PIT_THIEF"},
{3, "ProgressCommons.Quest.SNAKE_PIT_BITING_THIRST"},
{4, "ProgressCommons.Quest.SANDSTORM_MINE_ABANDONED_TREASURE"},
{5, "ProgressCommons.Quest.DESERT_DEEP_XAKELBAEL"},
{6, "ProgressCommons.Quest.TULIMSHAR_OLD_FRIENDSHIP"},
{7, "ProgressCommons.Quest.TUTORIAL"},
{8, "ProgressCommons.Quest.ELANORE_POTION"},
{9, "ProgressCommons.Quest.NINA_HUNGRY"},
{10, "ProgressCommons.Quest.MINE_EXPLORATION"},
{11, "ProgressCommons.Quest.SANDSTORM_NATHAN_WATER"},
};
const auto it = symbols.find(id);
return it == symbols.end() ? std::string{} : it->second;
}
void ApplyQuestField(QuestDefinition& definition, const std::string& key, const std::string& value)
{
if (key == "id") {
definition.id = std::stoi(value);
definition.symbol = QuestSymbolForId(definition.id);
} else if (key == "name") {
definition.name = value;
} else if (key == "description") {
definition.description = value;
} else if (key == "giver") {
definition.giver = value;
} else if (key == "giverLocation") {
definition.giverLocation = value;
} else if (key == "target") {
definition.target = value;
} else if (key == "targetLocation") {
definition.targetLocation = value;
} else if (key == "reward") {
definition.reward = value;
}
}
bool ObjectiveMatchesEvent(const QuestObjectiveProgress& objective, const QuestEvent& event)
{
if (objective.completed) {
return false;
}
if (objective.kind == QuestObjectiveKind::CatchAnyPet && event.kind == QuestObjectiveKind::CatchAnyPet) {
return true;
}
if (objective.kind != event.kind) {
return false;
}
return objective.target.empty() || objective.target == event.target;
}
} // namespace
std::string UnknownQuestState()
{
return kUnknownQuestState;
}
bool IsUnknownQuestState(const std::string& questState)
{
return questState.empty()
|| questState == kUnknownQuestState
|| HasSuffix(questState, ".INACTIVE");
}
bool IsCompletedQuestState(const std::string& questState)
{
return questState == kCompletedProgress
|| questState == kTutorialCompleted
|| HasSuffix(questState, kCompletedToken);
}
std::string QuestStateStore::Get(const std::string& questName) const
{
const auto it = states_.find(questName);
return it == states_.end() ? UnknownQuestState() : it->second;
}
QuestTransition QuestStateStore::Set(const std::string& questName, const std::string& questState)
{
QuestTransition transition;
transition.questName = questName;
transition.previousState = Get(questName);
transition.newState = questState.empty() ? UnknownQuestState() : questState;
transition.changed = transition.previousState != transition.newState;
transition.started = transition.changed
&& IsUnknownQuestState(transition.previousState)
&& !IsUnknownQuestState(transition.newState);
transition.completed = transition.changed && IsCompletedQuestState(transition.newState);
states_[questName] = transition.newState;
return transition;
}
bool QuestStateStore::IsStarted(const std::string& questName) const
{
const std::string state = Get(questName);
return !IsUnknownQuestState(state) && !IsCompletedQuestState(state);
}
bool QuestStateStore::IsCompleted(const std::string& questName) const
{
return IsCompletedQuestState(Get(questName));
}
const std::map<std::string, std::string>& QuestStateStore::RawStates() const
{
return states_;
}
void QuestStateStore::ImportRawStates(const std::map<std::string, std::string>& rawStates)
{
states_ = rawStates;
}
std::vector<QuestDefinition> LoadQuestDefinitionsFromDirectory(const std::filesystem::path& questDirectory)
{
std::vector<QuestDefinition> definitions;
if (!std::filesystem::exists(questDirectory)) {
return definitions;
}
for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(questDirectory)) {
if (!entry.is_regular_file() || entry.path().extension() != ".tres") {
continue;
}
QuestDefinition definition;
std::ifstream in(entry.path());
std::string line;
while (std::getline(in, line)) {
const std::size_t equals = line.find('=');
if (equals == std::string::npos) {
continue;
}
const std::string key = TrimQuestValue(line.substr(0, equals));
const std::string value = TrimQuestValue(line.substr(equals + 1));
ApplyQuestField(definition, key, value);
}
if (!definition.symbol.empty() && !definition.name.empty()) {
definitions.push_back(definition);
}
}
std::sort(definitions.begin(), definitions.end(), [](const QuestDefinition& a, const QuestDefinition& b) {
return a.id < b.id;
});
return definitions;
}
const QuestDefinition* FindQuestDefinition(
const std::vector<QuestDefinition>& definitions,
const std::string& questSymbol)
{
const auto it = std::find_if(definitions.begin(), definitions.end(), [&](const QuestDefinition& definition) {
return definition.symbol == questSymbol;
});
return it == definitions.end() ? nullptr : &*it;
}
std::string QuestTitleForSymbol(
const std::vector<QuestDefinition>& definitions,
const std::string& questSymbol)
{
const QuestDefinition* definition = FindQuestDefinition(definitions, questSymbol);
return definition ? definition->name : questSymbol;
}
QuestEvent QuestEvent::VisitedMap(std::string mapName)
{
return {QuestObjectiveKind::VisitMap, std::move(mapName), 1};
}
QuestEvent QuestEvent::CapturedPet(std::string speciesName)
{
return {QuestObjectiveKind::CatchAnyPet, std::move(speciesName), 1};
}
QuestEvent QuestEvent::SawPet(std::string speciesName)
{
return {QuestObjectiveKind::SeeSpecies, std::move(speciesName), 1};
}
QuestEvent QuestEvent::DefeatedPet(std::string speciesName)
{
return {QuestObjectiveKind::DefeatSpecies, std::move(speciesName), 1};
}
QuestEvent QuestEvent::OwnedItem(std::string itemName, int count)
{
return {QuestObjectiveKind::OwnItem, std::move(itemName), count};
}
QuestEvent QuestEvent::TalkedToNpc(std::string npcName)
{
return {QuestObjectiveKind::TalkToNpc, std::move(npcName), 1};
}
QuestEvent QuestEvent::HatchedEgg(std::string eggName)
{
return {QuestObjectiveKind::HatchEgg, std::move(eggName), 1};
}
QuestEvent QuestEvent::ChoseStarterPet(std::string speciesName)
{
return {QuestObjectiveKind::ChooseStarterPet, std::move(speciesName), 1};
}
void ApplyQuestEvent(QuestRuntime& runtime, const QuestEvent& event)
{
for (QuestObjectiveProgress& objective : runtime.objectives) {
if (!ObjectiveMatchesEvent(objective, event)) {
continue;
}
if (objective.kind == QuestObjectiveKind::OwnItem) {
objective.current = std::max(objective.current, event.amount);
} else {
objective.current += event.amount;
}
objective.completed = objective.current >= objective.required;
}
}
std::vector<QuestJournalEntry> BuildQuestJournal(
const std::vector<QuestDefinition>& definitions,
const QuestRuntime& runtime)
{
std::vector<QuestJournalEntry> journal;
for (const QuestDefinition& definition : definitions) {
const bool started = runtime.states.IsStarted(definition.symbol);
const bool completed = runtime.states.IsCompleted(definition.symbol);
if (!started && !completed) {
continue;
}
QuestJournalEntry entry;
entry.questSymbol = definition.symbol;
entry.title = definition.name;
entry.description = definition.description;
entry.giver = definition.giver;
entry.target = definition.target;
entry.reward = definition.reward;
entry.completed = completed;
for (const QuestObjectiveProgress& objective : runtime.objectives) {
if (!objective.questSymbol.empty() && objective.questSymbol != definition.symbol) {
continue;
}
QuestJournalObjective journalObjective;
journalObjective.title = objective.title;
journalObjective.current = objective.current;
journalObjective.required = objective.required;
journalObjective.completed = objective.completed;
entry.objectives.push_back(journalObjective);
}
journal.push_back(entry);
}
return journal;
}
std::size_t CompletedObjectiveCount(const QuestRuntime& runtime)
{
return static_cast<std::size_t>(std::count_if(
runtime.objectives.begin(),
runtime.objectives.end(),
[](const QuestObjectiveProgress& objective) {
return objective.completed;
}));
}
std::size_t QuestObjectiveWindowStart(const QuestRuntime& runtime, std::size_t maxVisible)
{
if (maxVisible == 0 || runtime.objectives.size() <= maxVisible) {
return 0;
}
const auto firstIncomplete = std::find_if(
runtime.objectives.begin(),
runtime.objectives.end(),
[](const QuestObjectiveProgress& objective) {
return !objective.completed;
});
if (firstIncomplete == runtime.objectives.end()) {
return runtime.objectives.size() - maxVisible;
}
const std::size_t index = static_cast<std::size_t>(std::distance(runtime.objectives.begin(), firstIncomplete));
const std::size_t lead = std::min<std::size_t>(2, maxVisible / 2);
return std::min(index > lead ? index - lead : 0, runtime.objectives.size() - maxVisible);
}
std::optional<std::string> NextQuestTargetMap(const QuestRuntime& runtime)
{
for (const QuestObjectiveProgress& objective : runtime.objectives) {
if (!objective.completed && objective.kind == QuestObjectiveKind::VisitMap && !objective.target.empty()) {
return objective.target;
}
}
return std::nullopt;
}
std::vector<std::string> QuestTargetMaps(const QuestRuntime& runtime)
{
std::vector<std::string> maps;
for (const QuestObjectiveProgress& objective : runtime.objectives) {
if (objective.kind == QuestObjectiveKind::VisitMap && !objective.target.empty()) {
maps.push_back(objective.target);
}
}
return maps;
}
QuestRuntime MakeInitialTonoriQuestRuntime()
{
QuestRuntime runtime;
runtime.states.Set(
"ProgressCommons.Quest.TUTORIAL",
"ProgressCommons.TUTORIAL.INTRO_ITEMS_GIVEN");
runtime.objectives = {
{"visit_tulimshar", "抵达图利姆沙", QuestObjectiveKind::VisitMap, "Tulimshar", 1, 0, false, "ProgressCommons.Quest.TUTORIAL"},
{"talk_kael", "找凯尔确认身体状况", QuestObjectiveKind::TalkToNpc, "Kael", 1, 0, false, "ProgressCommons.Quest.TUTORIAL"},
{"choose_starter_pet", "选择初始宠物", QuestObjectiveKind::ChooseStarterPet, {}, 1, 0, false, "ProgressCommons.Quest.TUTORIAL"},
{"clear_peyotes", "清理 5 只仙人掌怪", QuestObjectiveKind::DefeatSpecies, "Peyote", 5, 0, false, "ProgressCommons.Quest.TUTORIAL"},
{"clear_maggots", "清理 5 只沙虫", QuestObjectiveKind::DefeatSpecies, "Maggot", 5, 0, false, "ProgressCommons.Quest.TUTORIAL"},
{"report_ekinu", "向艾基努汇报农田情况", QuestObjectiveKind::TalkToNpc, "Ekinu", 1, 0, false, "ProgressCommons.Quest.TUTORIAL"},
{"catch_first_pet", "捕捉第一只野外宠物", QuestObjectiveKind::CatchAnyPet, {}, 1, 0, false, "ProgressCommons.Quest.TUTORIAL"},
{"elanore_maggot_slime", "收集 6 份沙虫黏液", QuestObjectiveKind::OwnItem, "Maggot Slime", 6, 0, false, "ProgressCommons.Quest.ELANORE_POTION"},
{"elanore_water", "准备 1 瓶水", QuestObjectiveKind::OwnItem, "Water Bottle", 1, 0, false, "ProgressCommons.Quest.ELANORE_POTION"},
{"elanore_cactus_drink", "准备 1 杯仙人掌饮料", QuestObjectiveKind::OwnItem, "Cactus Drink", 1, 0, false, "ProgressCommons.Quest.ELANORE_POTION"},
{"nina_croissant", "给妮娜带一份可颂", QuestObjectiveKind::OwnItem, "Croissant", 1, 0, false, "ProgressCommons.Quest.NINA_HUNGRY"},
{"riskim_flour", "在港口找到蓝封蜡面粉桶", QuestObjectiveKind::OwnItem, "Flour Barrel", 1, 0, false, "ProgressCommons.Quest.GRAIN_IN_THE_SAND"},
{"talk_dausen", "向城外的道森询问矿洞路线", QuestObjectiveKind::TalkToNpc, "Dausen", 1, 0, false, "ProgressCommons.Quest.MINE_EXPLORATION"},
{"visit_sandstorm", "前往沙漠风暴谷地", QuestObjectiveKind::VisitMap, "Sandstorm", 1, 0, false, "ProgressCommons.Quest.MINE_EXPLORATION"},
{"talk_nathan", "与矿洞入口的内森会合", QuestObjectiveKind::TalkToNpc, "Nathan", 1, 0, false, "ProgressCommons.Quest.MINE_EXPLORATION"},
{"visit_desert_mines", "进入沙漠矿洞", QuestObjectiveKind::VisitMap, "Desert Mines", 1, 0, false, "ProgressCommons.Quest.MINE_EXPLORATION"},
{"defeat_scorpion", "击败矿洞里的沙蝎", QuestObjectiveKind::DefeatSpecies, "Scorpion", 1, 0, false, "ProgressCommons.Quest.MINE_EXPLORATION"},
{"visit_deep_level", "抵达沙漠深层矿道", QuestObjectiveKind::VisitMap, "Desert Deep Level", 1, 0, false, "ProgressCommons.Quest.DESERT_DEEP_XAKELBAEL"},
{"face_xakelbael", "直面夏凯尔贝尔", QuestObjectiveKind::TalkToNpc, "Xakelbael", 1, 0, false, "ProgressCommons.Quest.DESERT_DEEP_XAKELBAEL"},
{"defeat_xakelbael", "击败夏凯尔贝尔", QuestObjectiveKind::DefeatSpecies, "Xakelbael", 1, 0, false, "ProgressCommons.Quest.DESERT_DEEP_XAKELBAEL"},
{"find_mine_key", "找到废弃矿层钥匙", QuestObjectiveKind::OwnItem, "Chest Mine Key", 1, 0, false, "ProgressCommons.Quest.SANDSTORM_MINE_ABANDONED_TREASURE"},
{"open_mine_chest", "打开矿洞遗落宝箱", QuestObjectiveKind::OwnItem, "Short Sword", 1, 0, false, "ProgressCommons.Quest.SANDSTORM_MINE_ABANDONED_TREASURE"},
{"nathan_water", "带给内森一瓶水", QuestObjectiveKind::OwnItem, "Water Bottle", 1, 0, false, "ProgressCommons.Quest.SANDSTORM_NATHAN_WATER"},
{"visit_snake_pit", "调查蛇坑", QuestObjectiveKind::VisitMap, "Snake Pit", 1, 0, false, "ProgressCommons.Quest.SNAKE_PIT_THIEF"},
{"find_snake_clues", "找到 5 条蛇坑线索", QuestObjectiveKind::OwnItem, "Thief's Key", 1, 0, false, "ProgressCommons.Quest.SNAKE_PIT_THIEF"},
{"open_thief_chest", "打开盗贼宝箱", QuestObjectiveKind::OwnItem, "Scimitar", 1, 0, false, "ProgressCommons.Quest.SNAKE_PIT_THIEF"},
{"mauro_accept", "答应帮助毛罗取水", QuestObjectiveKind::TalkToNpc, "Mauro", 1, 0, false, "ProgressCommons.Quest.SNAKE_PIT_BITING_THIRST"},
{"mauro_water", "从蛇坑清水池装满水", QuestObjectiveKind::OwnItem, "Water Bottle", 1, 0, false, "ProgressCommons.Quest.SNAKE_PIT_BITING_THIRST"},
{"old_friend_letters", "从城堡暗处取回旧信件", QuestObjectiveKind::OwnItem, "Sealed Letters", 1, 0, false, "ProgressCommons.Quest.TULIMSHAR_OLD_FRIENDSHIP"},
{"collect_first_pet_partner", "收服 1 只宠物伙伴", QuestObjectiveKind::CatchAnyPet, {}, 1, 0, false, "ProgressCommons.Quest.TUTORIAL"},
};
return runtime;
}
+124
View File
@@ -0,0 +1,124 @@
#pragma once
#include <filesystem>
#include <map>
#include <optional>
#include <string>
#include <vector>
struct QuestTransition {
bool changed = false;
bool started = false;
bool completed = false;
std::string questName;
std::string previousState;
std::string newState;
};
class QuestStateStore {
public:
std::string Get(const std::string& questName) const;
QuestTransition Set(const std::string& questName, const std::string& questState);
bool IsStarted(const std::string& questName) const;
bool IsCompleted(const std::string& questName) const;
const std::map<std::string, std::string>& RawStates() const;
void ImportRawStates(const std::map<std::string, std::string>& rawStates);
private:
std::map<std::string, std::string> states_;
};
struct QuestDefinition {
int id = -1;
std::string symbol;
std::string name;
std::string description;
std::string giver;
std::string giverLocation;
std::string target;
std::string targetLocation;
std::string reward;
};
enum class QuestObjectiveKind {
VisitMap,
CatchAnyPet,
CatchSpecies,
SeeSpecies,
DefeatSpecies,
OwnItem,
TalkToNpc,
HatchEgg,
ChooseStarterPet,
};
struct QuestObjectiveProgress {
std::string id;
std::string title;
QuestObjectiveKind kind = QuestObjectiveKind::VisitMap;
std::string target;
int required = 1;
int current = 0;
bool completed = false;
std::string questSymbol;
};
struct QuestRuntime {
QuestStateStore states;
std::vector<QuestObjectiveProgress> objectives;
};
struct QuestEvent {
QuestObjectiveKind kind = QuestObjectiveKind::VisitMap;
std::string target;
int amount = 1;
static QuestEvent VisitedMap(std::string mapName);
static QuestEvent CapturedPet(std::string speciesName);
static QuestEvent SawPet(std::string speciesName);
static QuestEvent DefeatedPet(std::string speciesName);
static QuestEvent OwnedItem(std::string itemName, int count);
static QuestEvent TalkedToNpc(std::string npcName);
static QuestEvent HatchedEgg(std::string eggName);
static QuestEvent ChoseStarterPet(std::string speciesName);
};
struct QuestJournalObjective {
std::string title;
int current = 0;
int required = 1;
bool completed = false;
};
struct QuestJournalEntry {
std::string questSymbol;
std::string title;
std::string description;
std::string giver;
std::string target;
std::string reward;
bool completed = false;
std::vector<QuestJournalObjective> objectives;
};
bool IsUnknownQuestState(const std::string& questState);
bool IsCompletedQuestState(const std::string& questState);
std::string UnknownQuestState();
std::vector<QuestDefinition> LoadQuestDefinitionsFromDirectory(const std::filesystem::path& questDirectory);
const QuestDefinition* FindQuestDefinition(
const std::vector<QuestDefinition>& definitions,
const std::string& questSymbol);
std::string QuestTitleForSymbol(
const std::vector<QuestDefinition>& definitions,
const std::string& questSymbol);
void ApplyQuestEvent(QuestRuntime& runtime, const QuestEvent& event);
std::vector<QuestJournalEntry> BuildQuestJournal(
const std::vector<QuestDefinition>& definitions,
const QuestRuntime& runtime);
std::size_t CompletedObjectiveCount(const QuestRuntime& runtime);
std::size_t QuestObjectiveWindowStart(const QuestRuntime& runtime, std::size_t maxVisible);
std::optional<std::string> NextQuestTargetMap(const QuestRuntime& runtime);
std::vector<std::string> QuestTargetMaps(const QuestRuntime& runtime);
QuestRuntime MakeInitialTonoriQuestRuntime();