Files
shadow-over-bishkek001/src/GameState.cpp
T

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