Index: binaries/data/mods/public/gui/credits/texts/programming.json =================================================================== --- binaries/data/mods/public/gui/credits/texts/programming.json +++ binaries/data/mods/public/gui/credits/texts/programming.json @@ -259,6 +259,7 @@ { "nick": "silure" }, { "nick": "Simikolon", "name": "Yannick & Simon" }, { "nick": "smiley", "name": "M. L." }, + { "nick": "sotirangelo", "name": "Sotirios-Angelos Angelopoulos" }, { "nick": "Spahbod", "name": "Omid Davoodi" }, { "nick": "Stan", "name": "Stanislas Dolcini" }, { "nick": "Stefan" }, Index: binaries/data/mods/public/gui/loadgame/SavegameWriter.js =================================================================== --- binaries/data/mods/public/gui/loadgame/SavegameWriter.js +++ binaries/data/mods/public/gui/loadgame/SavegameWriter.js @@ -57,9 +57,6 @@ reallySaveGame(name, desc, nameIsPrefix) { - let simulationState = Engine.GuiInterfaceCall("GetSimulationState"); - this.savedGameData.timeElapsed = simulationState.timeElapsed; - this.savedGameData.states = simulationState.players.map(pState => pState.state); if (nameIsPrefix) Engine.SaveGamePrefix(name, desc, this.savedGameData); Index: binaries/data/mods/public/gui/session/session.js =================================================================== --- binaries/data/mods/public/gui/session/session.js +++ binaries/data/mods/public/gui/session/session.js @@ -581,8 +581,12 @@ function getSavedGameData() { + const simulationState = GetSimState(); + return { - "groups": g_Groups.groups + "groups": g_Groups.groups, + "timeElapsed": simulationState.timeElapsed, + "states": simulationState.players.map(pState => pState.state) }; } Index: source/ps/SavedGame.h =================================================================== --- source/ps/SavedGame.h +++ source/ps/SavedGame.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -87,6 +87,27 @@ * @return true if deletion was successful, or false on error */ bool DeleteSavedGame(const std::wstring& name); + + /** + * Create new saved game archive with "quicksave" name and given simulation data + * + * @param simulation + * @param guiMetadataClone if not NULL, store some UI-related data with the saved game + * @return INFO::OK if successfully saved, else an error Status + */ + Status QuickSave(CSimulation2& simulation, const Script::StructuredClone& guiMetadataClone); + + /** + * Load saved game with "quicksave" name + * + * Reads the matchID property from the game metadata of "quicksave" saved game archive. + * Proceeds to load the saved game, if said matchID is equal to the matchID of the given + * simulation object reference. + * + * @param simulation + * @return INFO::OK if successfully loaded, else another info or error Status + */ + Status QuickLoad(CSimulation2& simulation); } #endif // INCLUDED_SAVEDGAME Index: source/ps/SavedGame.cpp =================================================================== --- source/ps/SavedGame.cpp +++ source/ps/SavedGame.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "SavedGame.h" #include "graphics/GameView.h" +#include "gui/GUIManager.h" #include "i18n/L10n.h" #include "lib/allocators/shared_ptr.h" #include "lib/file/archive/archive_zip.h" @@ -35,6 +36,7 @@ #include "scriptinterface/JSON.h" #include "scriptinterface/StructuredClone.h" #include "simulation2/Simulation2.h" +#include "simulation2/system/TurnManager.h" // TODO: we ought to check version numbers when loading files @@ -303,3 +305,59 @@ // Successfully deleted file return true; } + +Status SavedGames::QuickSave(CSimulation2& simulation, const Script::StructuredClone& GUIMetadataClone) +{ + return Save(L"quicksave", L"quicksave", simulation, GUIMetadataClone); +} + +Status SavedGames::QuickLoad(CSimulation2& simulation) +{ + ScriptRequest rq(simulation.GetScriptInterface()); + + // Load quicksave archive + JS::RootedValue quickSaveMetadata(rq.cx); + std::string savedState; + Status status = SavedGames::Load(L"quicksave", simulation.GetScriptInterface(), &quickSaveMetadata, savedState); + if (status != INFO::OK) + { + if (status == ERR::FILE_NOT_FOUND) + return INFO::CANNOT_HANDLE; + else + return status; + } + + // Get matchID of quicksave + JS::RootedValue gameInitAttributes(rq.cx); + if (!Script::GetProperty(rq, quickSaveMetadata, "initAttributes", &gameInitAttributes)) + return ERR::FAIL; + std::wstring savedMatchID; + if (!Script::GetProperty(rq, gameInitAttributes, "matchID", savedMatchID)) + return ERR::FAIL; + + // Get matchID of current game + std::wstring matchID; + JS::RootedValue gameMetadata(rq.cx, simulation.GetInitAttributes()); + if (!Script::GetProperty(rq, gameMetadata, "matchID", matchID)) + return ERR::FAIL; + + // Compare IDs + if (matchID.compare(savedMatchID) != 0) + return INFO::CANNOT_HANDLE; + + std::stringstream stream(savedState); + if (!simulation.DeserializeState(stream)) + return ERR::FAIL; + + g_Game->GetTurnManager()->RewindTimeWarp(); + + // Provide a copy, so that GUI components don't have to clone to get mutable objects + JS::RootedValue quickSaveMetadataClone(rq.cx, Script::DeepCopy(rq, quickSaveMetadata)); + + JS::RootedValueArray<1> paramData(rq.cx); + paramData[0].set(quickSaveMetadataClone); + CStr EventNameSavegameLoaded = "SavegameLoaded"; + g_GUI->SendEventToAll(EventNameSavegameLoaded, paramData); + + return INFO::OK; +} Index: source/ps/scripting/JSInterface_SavedGame.cpp =================================================================== --- source/ps/scripting/JSInterface_SavedGame.cpp +++ source/ps/scripting/JSInterface_SavedGame.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "JSInterface_SavedGame.h" +#include "i18n/L10n.h" #include "network/NetClient.h" #include "network/NetServer.h" #include "ps/CLogger.h" @@ -55,24 +56,36 @@ LOGERROR("Failed to save game"); } -void QuickSave(JS::HandleValue GUIMetadata) +void QuickSave(const ScriptRequest& rq, JS::HandleValue GUIMetadata) { - if (g_NetServer || g_NetClient) + if (!g_Game) + LOGERROR("Can't store quicksave if game is not running!"); + else if (g_NetServer || g_NetClient) LOGERROR("Can't store quicksave during multiplayer!"); - else if (g_Game) - g_Game->GetTurnManager()->QuickSave(GUIMetadata); else - LOGERROR("Can't store quicksave if game is not running!"); + { + Script::StructuredClone GUIMetadataClone = Script::WriteStructuredClone(rq, GUIMetadata); + if (SavedGames::QuickSave(*g_Game->GetSimulation2(), GUIMetadataClone) != INFO::OK) + LOGERROR(g_L10n.Translate("Failed to quicksave game")); + } } void QuickLoad() { - if (g_NetServer || g_NetClient) + if (!g_Game) + LOGERROR("Can't load quicksave if game is not running!"); + else if (g_NetServer || g_NetClient) LOGERROR("Can't load quicksave during multiplayer!"); - else if (g_Game) - g_Game->GetTurnManager()->QuickLoad(); else - LOGERROR("Can't load quicksave if game is not running!"); + { + Status status = SavedGames::QuickLoad(*g_Game->GetSimulation2()); + if (status == INFO::OK) + LOGMESSAGERENDER(g_L10n.Translate("Game loaded")); + else if (status == INFO::CANNOT_HANDLE) + LOGERROR(g_L10n.Translate("Cannot quickload game - current match not quicksaved")); + else + LOGERROR(g_L10n.Translate("Failed to quickload game")); + } } JS::Value StartSavedGame(const ScriptInterface& scriptInterface, const std::wstring& name) Index: source/simulation2/system/TurnManager.h =================================================================== --- source/simulation2/system/TurnManager.h +++ source/simulation2/system/TurnManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -146,9 +146,6 @@ */ void RewindTimeWarp(); - void QuickSave(JS::HandleValue GUIMetadata); - void QuickLoad(); - u32 GetCurrentTurn() const { return m_CurrentTurn; } /** @@ -209,12 +206,9 @@ u32 m_FinalTurn; private: - static const CStr EventNameSavegameLoaded; size_t m_TimeWarpNumTurns; // 0 if disabled std::list m_TimeWarpStates; - std::string m_QuickSaveState; // TODO: should implement a proper disk-based quicksave system - JS::PersistentRootedValue m_QuickSaveMetadata; }; #endif // INCLUDED_TURNMANAGER Index: source/simulation2/system/TurnManager.cpp =================================================================== --- source/simulation2/system/TurnManager.cpp +++ source/simulation2/system/TurnManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -35,15 +35,11 @@ #define NETTURN_LOG(...) #endif -const CStr CTurnManager::EventNameSavegameLoaded = "SavegameLoaded"; - CTurnManager::CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, u32 commandDelay, int clientId, IReplayLogger& replay) : m_Simulation2(simulation), m_CurrentTurn(0), m_CommandDelay(commandDelay), m_ReadyTurn(commandDelay - 1), m_TurnLength(defaultTurnLength), m_PlayerId(-1), m_ClientId(clientId), m_DeltaSimTime(0), m_Replay(replay), m_FinalTurn(std::numeric_limits::max()), m_TimeWarpNumTurns(0) { - ScriptRequest rq(m_Simulation2.GetScriptInterface()); - m_QuickSaveMetadata.init(rq.cx); m_QueuedCommands.resize(1); } @@ -277,60 +273,3 @@ // here, but this is simpler for now.) ResetState(1, m_CommandDelay); } - -void CTurnManager::QuickSave(JS::HandleValue GUIMetadata) -{ - TIMER(L"QuickSave"); - - std::stringstream stream; - if (!m_Simulation2.SerializeState(stream)) - { - LOGERROR("Failed to quicksave game"); - return; - } - - m_QuickSaveState = stream.str(); - - ScriptRequest rq(m_Simulation2.GetScriptInterface()); - - m_QuickSaveMetadata.set(Script::DeepCopy(rq, GUIMetadata)); - // Freeze state to ensure that consectuvie loads don't modify the state - Script::FreezeObject(rq, m_QuickSaveMetadata, true); - - LOGMESSAGERENDER("Quicksaved game"); -} - -void CTurnManager::QuickLoad() -{ - TIMER(L"QuickLoad"); - - if (m_QuickSaveState.empty()) - { - LOGERROR("Cannot quickload game - no game was quicksaved"); - return; - } - - std::stringstream stream(m_QuickSaveState); - if (!m_Simulation2.DeserializeState(stream)) - { - LOGERROR("Failed to quickload game"); - return; - } - - // See RewindTimeWarp - ResetState(1, m_CommandDelay); - - if (!g_GUI) - return; - - ScriptRequest rq(m_Simulation2.GetScriptInterface()); - - // Provide a copy, so that GUI components don't have to clone to get mutable objects - JS::RootedValue quickSaveMetadataClone(rq.cx, Script::DeepCopy(rq, m_QuickSaveMetadata)); - - JS::RootedValueArray<1> paramData(rq.cx); - paramData[0].set(quickSaveMetadataClone); - g_GUI->SendEventToAll(EventNameSavegameLoaded, paramData); - - LOGMESSAGERENDER("Quickloaded game"); -}