251 lines
8.9 KiB
C++
251 lines
8.9 KiB
C++
#include "GameState.h"
|
|
#include "utils/Utils.h"
|
|
#include <ctime>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
|
|
namespace FRG {
|
|
|
|
bool GameState::completeTutorialStep(TutorialStep step)
|
|
{
|
|
if (tutorialStep != step || step == TutorialStep::Completed) return false;
|
|
tutorialStep = static_cast<TutorialStep>(static_cast<int>(step) + 1);
|
|
return true;
|
|
}
|
|
|
|
CharacterState* GameState::findCharacter(
|
|
const std::string& locationId, const std::string& characterId)
|
|
{
|
|
auto player = players.find(characterId);
|
|
if (player != players.end() && player->second) return player->second.get();
|
|
auto location = locations.find(locationId);
|
|
return location == locations.end()
|
|
? nullptr : location->second.findCharacter(characterId, players);
|
|
}
|
|
|
|
const CharacterState* GameState::findCharacter(
|
|
const std::string& locationId, const std::string& characterId) const
|
|
{
|
|
auto player = players.find(characterId);
|
|
if (player != players.end() && player->second) return player->second.get();
|
|
auto location = locations.find(locationId);
|
|
return location == locations.end()
|
|
? nullptr : location->second.findCharacter(characterId, players);
|
|
}
|
|
|
|
void GameState::save(nlohmann::json& out) const
|
|
{
|
|
out["schemaVersion"] = kSchemaVersion;
|
|
// Timestamp
|
|
std::time_t now = std::time(nullptr);
|
|
std::tm* tm = std::localtime(&now);
|
|
std::ostringstream ts;
|
|
ts << std::put_time(tm, "%Y-%m-%d %H:%M");
|
|
out["savedAt"] = ts.str();
|
|
|
|
// --- World ---
|
|
out["campaignId"] = campaignId;
|
|
out["currentLocationId"] = currentLocationName;
|
|
out["isDarklands"] = isDarklands;
|
|
out["isNight"] = isNight;
|
|
out["isDawn"] = isDawn;
|
|
|
|
// --- Player resources ---
|
|
//out["playerHp"] = playerHp;
|
|
out["playerMaxHp"] = playerMaxHp;
|
|
out["money"] = money;
|
|
|
|
// --- Script globals ---
|
|
nlohmann::json gi = nlohmann::json::object();
|
|
for (const auto& [k, v] : globalInts) gi[k] = v;
|
|
out["globalInts"] = std::move(gi);
|
|
|
|
nlohmann::json gf = nlohmann::json::object();
|
|
for (const auto& [k, v] : globalFloats) gf[k] = v;
|
|
out["globalFloats"] = std::move(gf);
|
|
|
|
// --- Tutorial ---
|
|
out["tutorialStep"] = static_cast<int>(tutorialStep);
|
|
out["tutorialPhoneChatScreenOpened"] = tutorialPhoneChatScreenOpened;
|
|
out["tutorialJournalScreenOpened"] = tutorialJournalScreenOpened;
|
|
out["tutorialNeedOpenTaxiScreen"] = tutorialNeedOpenTaxiScreen;
|
|
out["tutorialTaxiHintDone"] = tutorialTaxiHintDone;
|
|
|
|
// --- Phone / chat ---
|
|
out["chatUnread"] = { chatUnread[0], chatUnread[1], chatUnread[2] };
|
|
out["chatPreviewMsg"] = { chatPreviewMsg[0], chatPreviewMsg[1], chatPreviewMsg[2] };
|
|
|
|
nlohmann::json histories = nlohmann::json::array();
|
|
for (int i = 0; i < 3; ++i) {
|
|
nlohmann::json msgs = nlohmann::json::array();
|
|
for (const auto& msg : chatHistory[i]) {
|
|
msgs.push_back({ {"text", msg.text}, {"incoming", msg.incoming} });
|
|
}
|
|
histories.push_back(std::move(msgs));
|
|
}
|
|
out["chatHistory"] = std::move(histories);
|
|
|
|
// --- Inventory ---
|
|
nlohmann::json invJson;
|
|
inventory.save(invJson);
|
|
out["inventory"] = std::move(invJson);
|
|
|
|
// --- Quest journal ---
|
|
nlohmann::json questJson;
|
|
questJournal.save(questJson);
|
|
out["questJournal"] = std::move(questJson);
|
|
|
|
// --- Players ---
|
|
out["players"] = nlohmann::json::object();
|
|
for (const auto& [id, player] : players) {
|
|
if (!player) continue;
|
|
nlohmann::json playerJson;
|
|
player->save(playerJson);
|
|
out["players"][id] = std::move(playerJson);
|
|
}
|
|
|
|
// --- Locations ---
|
|
nlohmann::json locsJson = nlohmann::json::object();
|
|
for (const auto& [name, location] : locations) {
|
|
nlohmann::json locJson;
|
|
location.save(locJson);
|
|
locsJson[name] = std::move(locJson);
|
|
}
|
|
out["locations"] = std::move(locsJson);
|
|
|
|
out["taxiIsCalled"] = taxiIsCalled;
|
|
}
|
|
|
|
void GameState::load(const nlohmann::json& in)
|
|
{
|
|
const int schemaVersion = in.value("schemaVersion", -1);
|
|
if (schemaVersion != kSchemaVersion)
|
|
throw std::runtime_error(
|
|
"Unsupported game-state schema version: " +
|
|
std::to_string(schemaVersion));
|
|
//logger() << "GameState::load step 1" << std::endl;
|
|
|
|
// --- World ---
|
|
campaignId = in.at("campaignId").get<std::string>();
|
|
currentLocationName = in.at("currentLocationId").get<std::string>();
|
|
isDarklands = in.value("isDarklands", isDarklands);
|
|
isNight = in.value("isNight", isNight);
|
|
isDawn = in.value("isDawn", isDawn);
|
|
|
|
//() << "GameState::load step 2" << std::endl;
|
|
|
|
|
|
// --- Player resources ---
|
|
//playerHp = in.value("playerHp", playerHp);
|
|
playerMaxHp = in.value("playerMaxHp", playerMaxHp);
|
|
money = in.value("money", money);
|
|
|
|
//logger() << "GameState::load step 3" << std::endl;
|
|
|
|
// --- Script globals ---
|
|
globalInts.clear();
|
|
if (in.contains("globalInts") && in["globalInts"].is_object()) {
|
|
for (const auto& [k, v] : in["globalInts"].items())
|
|
globalInts[k] = v.get<int>();
|
|
}
|
|
//logger() << "GameState::load step 4" << std::endl;
|
|
|
|
globalFloats.clear();
|
|
if (in.contains("globalFloats") && in["globalFloats"].is_object()) {
|
|
for (const auto& [k, v] : in["globalFloats"].items())
|
|
globalFloats[k] = v.get<float>();
|
|
}
|
|
|
|
//logger() << "GameState::load step 5" << std::endl;
|
|
|
|
// --- Tutorial ---
|
|
tutorialStep = static_cast<TutorialStep>(
|
|
in.at("tutorialStep").get<int>());
|
|
tutorialPhoneChatScreenOpened = in.value("tutorialPhoneChatScreenOpened", tutorialPhoneChatScreenOpened);
|
|
tutorialJournalScreenOpened = in.value("tutorialJournalScreenOpened", tutorialJournalScreenOpened);
|
|
tutorialNeedOpenTaxiScreen = in.value("tutorialNeedOpenTaxiScreen", tutorialNeedOpenTaxiScreen);
|
|
tutorialTaxiHintDone = in.value("tutorialTaxiHintDone", false);
|
|
|
|
//logger() << "GameState::load step 6" << std::endl;
|
|
|
|
// --- Phone / chat ---
|
|
if (in.contains("chatUnread") && in["chatUnread"].is_array()) {
|
|
for (int i = 0; i < 3 && i < static_cast<int>(in["chatUnread"].size()); ++i)
|
|
chatUnread[i] = in["chatUnread"][i].get<bool>();
|
|
}
|
|
//logger() << "GameState::load step 7" << std::endl;
|
|
|
|
if (in.contains("chatPreviewMsg") && in["chatPreviewMsg"].is_array()) {
|
|
for (int i = 0; i < 3 && i < static_cast<int>(in["chatPreviewMsg"].size()); ++i)
|
|
chatPreviewMsg[i] = in["chatPreviewMsg"][i].get<std::string>();
|
|
}
|
|
//logger() << "GameState::load step 8" << std::endl;
|
|
|
|
if (in.contains("chatHistory") && in["chatHistory"].is_array()) {
|
|
for (int i = 0; i < 3 && i < static_cast<int>(in["chatHistory"].size()); ++i) {
|
|
chatHistory[i].clear();
|
|
if (in["chatHistory"][i].is_array()) {
|
|
for (const auto& msg : in["chatHistory"][i]) {
|
|
StoredChatMessage m;
|
|
m.text = msg.value("text", "");
|
|
m.incoming = msg.value("incoming", false);
|
|
chatHistory[i].push_back(std::move(m));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
//logger() << "GameState::load step 8" << std::endl;
|
|
|
|
// --- Inventory ---
|
|
if (in.contains("inventory")) inventory.load(in["inventory"]);
|
|
|
|
//logger() << "GameState::load step 9" << std::endl;
|
|
|
|
// --- Quest journal ---
|
|
if (in.contains("questJournal")) questJournal.load(in["questJournal"]);
|
|
|
|
logger() << "GameState::load step 10" << std::endl;
|
|
|
|
// --- Players ---
|
|
const auto& playerValues = in.at("players");
|
|
if (!playerValues.is_object())
|
|
throw std::runtime_error("Game state players must be an object");
|
|
for (const auto& [id, value] : playerValues.items()) {
|
|
auto& player = players[id];
|
|
if (!player) player = std::make_shared<CharacterState>();
|
|
player->load(value);
|
|
player->configureAsPlayer();
|
|
}
|
|
for (auto it = players.begin(); it != players.end();) {
|
|
if (!playerValues.contains(it->first)) it = players.erase(it);
|
|
else ++it;
|
|
}
|
|
|
|
// --- Locations ---
|
|
const auto& locationValues = in.at("locations");
|
|
if (!locationValues.is_object())
|
|
throw std::runtime_error("Game state locations must be an object");
|
|
if (!currentLocationName.empty() &&
|
|
!locationValues.contains(currentLocationName))
|
|
throw std::runtime_error(
|
|
"Game state does not contain current location '" +
|
|
currentLocationName + "'");
|
|
for (const auto& [id, value] : locationValues.items())
|
|
locations[id].load(value);
|
|
for (auto it = locations.begin(); it != locations.end();) {
|
|
if (!locationValues.contains(it->first)) it = locations.erase(it);
|
|
else ++it;
|
|
}
|
|
//logger() << "GameState::load step 11" << std::endl;
|
|
|
|
|
|
// --- Taxi ---
|
|
taxiIsCalled = in.value("taxiIsCalled", taxiIsCalled);
|
|
//logger() << "GameState::load step 12" << std::endl;
|
|
|
|
}
|
|
|
|
} // namespace FRG
|