#include "GameState.h" #include "utils/Utils.h" #include #include #include #include namespace FRG { bool GameState::completeTutorialStep(TutorialStep step) { if (tutorialStep != step || step == TutorialStep::Completed) return false; tutorialStep = static_cast(static_cast(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(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(); currentLocationName = in.at("currentLocationId").get(); 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(); } //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(); } //logger() << "GameState::load step 5" << std::endl; // --- Tutorial --- tutorialStep = static_cast( in.at("tutorialStep").get()); 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(in["chatUnread"].size()); ++i) chatUnread[i] = in["chatUnread"][i].get(); } //logger() << "GameState::load step 7" << std::endl; if (in.contains("chatPreviewMsg") && in["chatPreviewMsg"].is_array()) { for (int i = 0; i < 3 && i < static_cast(in["chatPreviewMsg"].size()); ++i) chatPreviewMsg[i] = in["chatPreviewMsg"][i].get(); } //logger() << "GameState::load step 8" << std::endl; if (in.contains("chatHistory") && in["chatHistory"].is_array()) { for (int i = 0; i < 3 && i < static_cast(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(); 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