最终整理版

This commit is contained in:
2026-06-03 17:04:06 +08:00
commit 959055ce90
1240 changed files with 80570 additions and 0 deletions
+262
View File
@@ -0,0 +1,262 @@
#include "SaveGame.h"
#include <algorithm>
#include <fstream>
#include <sstream>
namespace {
std::vector<std::string> Split(const std::string& text, char separator)
{
std::vector<std::string> parts;
std::stringstream stream(text);
std::string part;
while (std::getline(stream, part, separator)) {
parts.push_back(part);
}
return parts;
}
std::string Join(const std::vector<std::string>& values, char separator)
{
std::ostringstream out;
for (std::size_t i = 0; i < values.size(); ++i) {
if (i > 0) {
out << separator;
}
out << values[i];
}
return out.str();
}
std::string ValueAfterEquals(const std::string& line)
{
const std::size_t pos = line.find('=');
return pos == std::string::npos ? std::string{} : line.substr(pos + 1);
}
bool StartsWith(const std::string& text, const std::string& prefix)
{
return text.rfind(prefix, 0) == 0;
}
void WritePet(std::ofstream& out, const std::string& key, const Pet& pet)
{
out << key << "=" << pet.name << "|" << pet.maxHp << "|" << pet.hp << "|" << pet.attack << "|" << pet.level << "|" << pet.exp
<< "|" << Join(pet.learnedSkillIds, ',')
<< "|" << pet.potential.hp << "|" << pet.potential.attack << "\n";
}
std::optional<Pet> ReadPet(const std::vector<std::string>& parts)
{
if (parts.size() != 4 && parts.size() != 6 && parts.size() != 7 && parts.size() != 9) {
return std::nullopt;
}
Pet pet = MakePet(parts[0], std::stoi(parts[1]), std::stoi(parts[3]));
pet.hp = std::stoi(parts[2]);
if (parts.size() >= 6) {
pet.level = std::stoi(parts[4]);
pet.exp = std::stoi(parts[5]);
}
if (parts.size() >= 7) {
pet.learnedSkillIds = Split(parts[6], ',');
if (pet.learnedSkillIds.size() == 1 && pet.learnedSkillIds.front().empty()) {
pet.learnedSkillIds.clear();
}
}
if (parts.size() == 9) {
pet.potential.hp = std::stoi(parts[7]);
pet.potential.attack = std::stoi(parts[8]);
}
NormalizePetAfterLoad(pet);
return pet;
}
QuestObjectiveProgress* FindObjective(QuestRuntime& runtime, const std::string& id)
{
const auto it = std::find_if(runtime.objectives.begin(), runtime.objectives.end(), [&](QuestObjectiveProgress& objective) {
return objective.id == id;
});
return it == runtime.objectives.end() ? nullptr : &*it;
}
std::string NewObjectiveIdForLegacyObjective(const std::string& legacyId)
{
static const std::map<std::string, std::string> migrated = {
{"arrive_tulimshar", "visit_tulimshar"},
{"capture_first", "catch_first_pet"},
{"reach_sandstorm", "visit_sandstorm"},
{"enter_mines", "visit_desert_mines"},
{"collect_three", "collect_first_pet_partner"},
{"collect_three_pets", "collect_first_pet_partner"},
{"snake_pit", "visit_snake_pit"},
};
const auto it = migrated.find(legacyId);
return it == migrated.end() ? legacyId : it->second;
}
void ApplyLoadedQuestObjective(QuestRuntime& runtime, const std::string& id, int current, bool completed)
{
QuestObjectiveProgress* objective = FindObjective(runtime, NewObjectiveIdForLegacyObjective(id));
if (!objective) {
return;
}
objective->current = std::max(objective->current, current);
objective->completed = objective->completed || completed || objective->current >= objective->required;
}
} // namespace
bool SaveGameToFile(const std::filesystem::path& path, const SaveState& state)
{
std::ofstream out(path);
if (!out) {
return false;
}
out << "MANA_SAVE_V1\n";
out << "map=" << state.mapName << "\n";
out << "player=" << state.playerX << "," << state.playerY << "\n";
out << "gold=" << state.gold << "\n";
out << "discovered=" << Join(state.discoveredMaps, '|') << "\n";
out << "collected=" << Join(state.collectedObjects, '|') << "\n";
out << "executed_dialogue_effects=" << Join(state.executedDialogueEffects, '|') << "\n";
out << "quest_states=" << state.questRuntime.states.RawStates().size() << "\n";
for (const auto& [questName, questState] : state.questRuntime.states.RawStates()) {
out << "quest_state=" << questName << "|" << questState << "\n";
}
const auto validMonsterRespawns = [&]() {
std::vector<MonsterRespawnSave> valid;
for (const MonsterRespawnSave& respawn : state.monsterRespawns) {
if (!respawn.objectKey.empty() && respawn.remainingSeconds > 0.0) {
valid.push_back(respawn);
}
}
return valid;
}();
out << "monster_respawns=" << validMonsterRespawns.size() << "\n";
for (const MonsterRespawnSave& respawn : validMonsterRespawns) {
out << "monster_respawn=" << respawn.objectKey << "|" << respawn.remainingSeconds << "\n";
}
out << "items=" << state.inventory.items.size() << "\n";
for (const InventoryItem& item : state.inventory.items) {
out << "item=" << item.name << "|" << item.count << "\n";
}
out << "pets=" << state.team.pets.size() << "\n";
for (const Pet& pet : state.team.pets) {
WritePet(out, "pet", pet);
}
out << "stored_pets=" << state.team.storage.size() << "\n";
for (const Pet& pet : state.team.storage) {
WritePet(out, "stored_pet", pet);
}
out << "pet_journal=" << state.petJournal.entries.size() << "\n";
for (const PetJournalEntry& entry : state.petJournal.entries) {
if (!entry.speciesName.empty()) {
out << "pet_journal_entry=" << entry.speciesName << "|" << entry.seenCount << "|" << entry.caughtCount << "\n";
}
}
out << "quest_objectives=" << state.questRuntime.objectives.size() << "\n";
for (const QuestObjectiveProgress& objective : state.questRuntime.objectives) {
out << "quest_objective="
<< objective.id << "|"
<< objective.current << "|"
<< (objective.completed ? "1" : "0") << "\n";
}
return static_cast<bool>(out);
}
std::optional<SaveState> LoadGameFromFile(const std::filesystem::path& path)
{
std::ifstream in(path);
if (!in) {
return std::nullopt;
}
std::string line;
if (!std::getline(in, line) || line != "MANA_SAVE_V1") {
return std::nullopt;
}
SaveState state;
state.questRuntime = MakeInitialTonoriQuestRuntime();
while (std::getline(in, line)) {
if (StartsWith(line, "map=")) {
state.mapName = ValueAfterEquals(line);
} else if (StartsWith(line, "player=")) {
const std::vector<std::string> parts = Split(ValueAfterEquals(line), ',');
if (parts.size() == 2) {
state.playerX = std::stof(parts[0]);
state.playerY = std::stof(parts[1]);
}
} else if (StartsWith(line, "gold=")) {
state.gold = std::max(0, std::stoi(ValueAfterEquals(line)));
} else if (StartsWith(line, "discovered=")) {
state.discoveredMaps = Split(ValueAfterEquals(line), '|');
if (state.discoveredMaps.size() == 1 && state.discoveredMaps.front().empty()) {
state.discoveredMaps.clear();
}
} else if (StartsWith(line, "collected=")) {
state.collectedObjects = Split(ValueAfterEquals(line), '|');
if (state.collectedObjects.size() == 1 && state.collectedObjects.front().empty()) {
state.collectedObjects.clear();
}
} else if (StartsWith(line, "executed_dialogue_effects=")) {
state.executedDialogueEffects = Split(ValueAfterEquals(line), '|');
if (state.executedDialogueEffects.size() == 1 && state.executedDialogueEffects.front().empty()) {
state.executedDialogueEffects.clear();
}
} else if (StartsWith(line, "quest_state=") || StartsWith(line, "script_quest=")) {
const std::vector<std::string> parts = Split(ValueAfterEquals(line), '|');
if (parts.size() == 2 && !parts[0].empty()) {
state.questRuntime.states.Set(parts[0], parts[1]);
}
} else if (StartsWith(line, "monster_respawn=")) {
const std::vector<std::string> parts = Split(ValueAfterEquals(line), '|');
if (parts.size() == 2 && !parts[0].empty()) {
const double remaining = std::stod(parts[1]);
if (remaining > 0.0) {
state.monsterRespawns.push_back({parts[0], remaining});
}
}
} else if (StartsWith(line, "item=")) {
const std::vector<std::string> parts = Split(ValueAfterEquals(line), '|');
if (parts.size() == 2) {
AddItem(state.inventory, parts[0], std::stoi(parts[1]));
}
} else if (StartsWith(line, "pet=")) {
const std::vector<std::string> parts = Split(ValueAfterEquals(line), '|');
const std::optional<Pet> pet = ReadPet(parts);
if (pet.has_value()) {
state.team.pets.push_back(*pet);
}
} else if (StartsWith(line, "stored_pet=")) {
const std::vector<std::string> parts = Split(ValueAfterEquals(line), '|');
const std::optional<Pet> pet = ReadPet(parts);
if (pet.has_value()) {
state.team.storage.push_back(*pet);
}
} else if (StartsWith(line, "pet_journal_entry=")) {
const std::vector<std::string> parts = Split(ValueAfterEquals(line), '|');
if (parts.size() == 3 && !parts[0].empty()) {
state.petJournal.entries.push_back({parts[0], std::stoi(parts[1]), std::stoi(parts[2])});
}
} else if (StartsWith(line, "quest_objective=")) {
const std::vector<std::string> parts = Split(ValueAfterEquals(line), '|');
if (parts.size() == 3 && !parts[0].empty()) {
ApplyLoadedQuestObjective(state.questRuntime, parts[0], std::stoi(parts[1]), parts[2] == "1");
}
} else if (StartsWith(line, "quest=")) {
const std::vector<std::string> parts = Split(ValueAfterEquals(line), '|');
if (parts.size() == 2 && !parts[0].empty()) {
ApplyLoadedQuestObjective(state.questRuntime, parts[0], parts[1] == "1" ? 1 : 0, parts[1] == "1");
}
}
}
if (state.mapName.empty()) {
return std::nullopt;
}
return state;
}