From 91065b6af4bec04e663e3547b43a5062c6c9431c Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Sun, 3 May 2015 05:22:03 +0100 Subject: [PATCH] Initial Save + Load implementation * Fix VM Global Addressing * Modify VM structures to simplify storage * Add explicit GameWorld::createPlayer() method * Move gameTime to GameState for storage * Add SaveGame class for reading + writing * New Dependancy: cereal --- rwengine/include/engine/GameState.hpp | 9 +- rwengine/include/engine/GameWorld.hpp | 18 +- rwengine/include/engine/SaveGame.hpp | 33 ++ rwengine/include/objects/GameObject.hpp | 2 +- rwengine/include/script/ScriptMachine.hpp | 12 +- rwengine/src/engine/GameData.cpp | 2 +- rwengine/src/engine/GameState.cpp | 2 +- rwengine/src/engine/GameWorld.cpp | 68 +++- rwengine/src/engine/SaveGame.cpp | 315 +++++++++++++++++++ rwengine/src/items/WeaponItem.cpp | 6 +- rwengine/src/objects/ProjectileObject.cpp | 2 +- rwengine/src/render/GameRenderer.cpp | 18 +- rwengine/src/render/MapRenderer.cpp | 14 +- rwengine/src/render/WaterRenderer.cpp | 2 +- rwengine/src/script/ScriptMachine.cpp | 20 +- rwengine/src/script/modules/GameModule.cpp | 47 +-- rwengine/src/script/modules/ObjectModule.cpp | 16 +- rwengine/src/script/modules/VMModule.cpp | 15 +- rwengine/tests/test_SaveGame.cpp | 49 +++ rwgame/RWGame.cpp | 84 ++++- rwgame/RWGame.hpp | 14 +- rwgame/debugstate.cpp | 41 +-- rwgame/ingamestate.cpp | 33 +- rwgame/ingamestate.hpp | 6 +- rwgame/loadingstate.cpp | 20 +- rwgame/menustate.cpp | 6 +- 26 files changed, 694 insertions(+), 160 deletions(-) create mode 100644 rwengine/include/engine/SaveGame.hpp create mode 100644 rwengine/src/engine/SaveGame.cpp create mode 100644 rwengine/tests/test_SaveGame.cpp diff --git a/rwengine/include/engine/GameState.hpp b/rwengine/include/engine/GameState.hpp index aa42fcfa..f0d136f6 100644 --- a/rwengine/include/engine/GameState.hpp +++ b/rwengine/include/engine/GameState.hpp @@ -74,7 +74,7 @@ struct VehicleGenerator struct BlipData { int id; - GameObject* target; + GameObjectID target; // If target is null then use coord glm::vec3 coord; @@ -92,7 +92,7 @@ struct BlipData DisplayMode display; BlipData() - : id(-1), target(nullptr), display(Show) + : id(-1), target(0), display(Show) { } }; @@ -102,6 +102,10 @@ struct BlipData */ struct GameState { + /** + * Second since game was started + */ + float gameTime; unsigned int currentProgress; unsigned int maxProgress; unsigned int numMissions; @@ -110,7 +114,6 @@ struct GameState unsigned int numUniqueJumps; unsigned int numRampages; unsigned int maxWantedLevel; - PlayerController* player; GameObjectID playerObject; unsigned int currentWeather; diff --git a/rwengine/include/engine/GameWorld.hpp b/rwengine/include/engine/GameWorld.hpp index 334bb9fb..58166acb 100644 --- a/rwengine/include/engine/GameWorld.hpp +++ b/rwengine/include/engine/GameWorld.hpp @@ -89,12 +89,17 @@ public: /** * Creates a vehicle */ - VehicleObject *createVehicle(const uint16_t id, const glm::vec3& pos, const glm::quat& rot = glm::quat()); + VehicleObject *createVehicle(const uint16_t id, const glm::vec3& pos, const glm::quat& rot = glm::quat(), GameObjectID gid = 0); /** * Creates a pedestrian. */ - CharacterObject* createPedestrian(const uint16_t id, const glm::vec3& pos, const glm::quat& rot = glm::quat()); + CharacterObject* createPedestrian(const uint16_t id, const glm::vec3& pos, const glm::quat& rot = glm::quat(), GameObjectID gid = 0); + + /** + * Creates a player + */ + CharacterObject* createPlayer(const glm::vec3& pos, const glm::quat& rot = glm::quat(), GameObjectID gid = 0); /** * Inserts the given game object into the world. @@ -147,12 +152,9 @@ public: int getMinute(); glm::vec3 getGroundAtPosition(const glm::vec3& pos) const; - - /** - * Game Clock - */ - float gameTime; - + + float getGameTime() const; + /** * Game data */ diff --git a/rwengine/include/engine/SaveGame.hpp b/rwengine/include/engine/SaveGame.hpp new file mode 100644 index 00000000..d9ad5a1f --- /dev/null +++ b/rwengine/include/engine/SaveGame.hpp @@ -0,0 +1,33 @@ +#pragma once +#ifndef _SAVEGAME_HPP_ +#define _SAVEGAME_HPP_ + +#include + +#include + +struct GameState; +class GameWorld; +class ScriptMachine; + +/** + * Reads and Writes GameStates to disk, restoring the required state information + */ +class SaveGame +{ +public: + + static void writeState(GameState& state, const std::string& file); + + static bool loadState(GameState& state, const std::string& file); + + static void writeScript(ScriptMachine& sm, const std::string& file); + + static bool loadScript(ScriptMachine&, const std::string& file); + + static void writeObjects(GameWorld& world, const std::string& file); + + static bool loadObjects(GameWorld& world, const std::string& file); +}; + +#endif \ No newline at end of file diff --git a/rwengine/include/objects/GameObject.hpp b/rwengine/include/objects/GameObject.hpp index e19f115d..034d86dc 100644 --- a/rwengine/include/objects/GameObject.hpp +++ b/rwengine/include/objects/GameObject.hpp @@ -59,7 +59,7 @@ public: bool visible; GameObject(GameWorld* engine, const glm::vec3& pos, const glm::quat& rot, ModelRef model) - : _lastPosition(pos), _lastRotation(rot), objectID(-1), position(pos), rotation(rot), + : _lastPosition(pos), _lastRotation(rot), objectID(0), position(pos), rotation(rot), model(model), engine(engine), animator(nullptr), skeleton(nullptr), mHealth(0.f), inWater(false), _lastHeight(std::numeric_limits::max()), visible(true), lifetime(GameObject::UnknownLifetime) diff --git a/rwengine/include/script/ScriptMachine.hpp b/rwengine/include/script/ScriptMachine.hpp index a46667cf..35cf0f52 100644 --- a/rwengine/include/script/ScriptMachine.hpp +++ b/rwengine/include/script/ScriptMachine.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,7 @@ * Changing this will break saves. */ #define SCM_VARIABLE_SIZE 4 +#define SCM_STACK_DEPTH 32 class GameState; @@ -111,7 +113,7 @@ struct SCMThread { typedef unsigned int pc_t; - std::string name; + char name[17]; pc_t baseAddress; pc_t programCounter; @@ -122,13 +124,14 @@ struct SCMThread /** Number of MS until the thread should be waked (-1 = yeilded) */ int wakeCounter; - SCMByte locals[SCM_THREAD_LOCAL_SIZE * (SCM_VARIABLE_SIZE)]; + std::array locals; bool isMission; bool finished; + unsigned int stackDepth; /// Stores the return-addresses for calls. - std::stack calls; + std::array calls; }; /** @@ -176,6 +179,7 @@ public: std::vector& getThreads() { return _activeThreads; } SCMByte* getGlobals(); + std::vector& getGlobalData() { return globalData; } GameState* getState() const { return state; } @@ -215,7 +219,7 @@ private: void executeThread(SCMThread& t, int msPassed); - SCMByte* _globals; + std::vector globalData; BreakpointHandler bpHandler; std::set breakpoints; diff --git a/rwengine/src/engine/GameData.cpp b/rwengine/src/engine/GameData.cpp index 897a0289..43671b45 100644 --- a/rwengine/src/engine/GameData.cpp +++ b/rwengine/src/engine/GameData.cpp @@ -593,7 +593,7 @@ int GameData::getWaterIndexAt(const glm::vec3 &ws) const float GameData::getWaveHeightAt(const glm::vec3 &ws) const { - return (1+sin(engine->gameTime + (ws.x + ws.y) * WATER_SCALE)) * WATER_HEIGHT; + return (1+sin(engine->getGameTime() + (ws.x + ws.y) * WATER_SCALE)) * WATER_HEIGHT; } bool GameData::isValidGameDirectory(const std::string& path) diff --git a/rwengine/src/engine/GameState.cpp b/rwengine/src/engine/GameState.cpp index 2a9ee528..93d04d93 100644 --- a/rwengine/src/engine/GameState.cpp +++ b/rwengine/src/engine/GameState.cpp @@ -1,6 +1,7 @@ #include GameState::GameState() : +gameTime(0.f), currentProgress(0), maxProgress(1), numMissions(0), @@ -9,7 +10,6 @@ numHiddenPackagesDiscovered(0), numUniqueJumps(0), numRampages(0), maxWantedLevel(0), -player(nullptr), currentWeather(0), scriptOnMissionFlag(nullptr), fadeOut(true), diff --git a/rwengine/src/engine/GameWorld.cpp b/rwengine/src/engine/GameWorld.cpp index 9796da4f..c5f63fdc 100644 --- a/rwengine/src/engine/GameWorld.cpp +++ b/rwengine/src/engine/GameWorld.cpp @@ -76,7 +76,7 @@ public: }; GameWorld::GameWorld(Logger* log, WorkContext* work, GameData* dat) - : logger(log), gameTime(0.f), data(dat), randomEngine(rand()), + : logger(log), data(dat), randomEngine(rand()), _work( work ), cutsceneAudio(nullptr), missionAudio(nullptr), paused(false) { @@ -277,7 +277,11 @@ CutsceneObject *GameWorld::createCutsceneObject(const uint16_t id, const glm::ve } if( id == 0 ) { - modelname = state->player->getCharacter()->model->name; + auto playerobj = findObject(state->playerObject); + if( playerobj ) + { + modelname = playerobj->model->name; + } } // Ensure the relevant data is loaded. @@ -308,7 +312,7 @@ CutsceneObject *GameWorld::createCutsceneObject(const uint16_t id, const glm::ve return instance; } -VehicleObject *GameWorld::createVehicle(const uint16_t id, const glm::vec3& pos, const glm::quat& rot) +VehicleObject *GameWorld::createVehicle(const uint16_t id, const glm::vec3& pos, const glm::quat& rot, GameObjectID gid) { auto vti = data->findObjectType(id); if( vti ) { @@ -365,6 +369,7 @@ VehicleObject *GameWorld::createVehicle(const uint16_t id, const glm::vec3& pos, } auto vehicle = new VehicleObject{ this, pos, rot, m, vti, info->second, prim, sec }; + vehicle->setGameObjectID(gid); insertObject( vehicle ); @@ -373,7 +378,7 @@ VehicleObject *GameWorld::createVehicle(const uint16_t id, const glm::vec3& pos, return nullptr; } -CharacterObject* GameWorld::createPedestrian(const uint16_t id, const glm::vec3 &pos, const glm::quat& rot) +CharacterObject* GameWorld::createPedestrian(const uint16_t id, const glm::vec3& pos, const glm::quat& rot, GameObjectID gid) { auto pt = data->findObjectType(id); if( pt ) { @@ -405,6 +410,7 @@ CharacterObject* GameWorld::createPedestrian(const uint16_t id, const glm::vec3 if(m && m->resource) { auto ped = new CharacterObject( this, pos, rot, m, pt ); + ped->setGameObjectID(gid); insertObject(ped); characters.insert(ped); new DefaultAIController(ped); @@ -414,17 +420,48 @@ CharacterObject* GameWorld::createPedestrian(const uint16_t id, const glm::vec3 return nullptr; } +CharacterObject* GameWorld::createPlayer(const glm::vec3& pos, const glm::quat& rot, GameObjectID gid) +{ + // Player object ID is hardcoded to 0. + auto pt = data->findObjectType(0); + if( pt ) { + // Model name is also hardcoded. + std::string modelname = "player"; + std::string texturename = "player"; + + // Ensure the relevant data is loaded. + data->loadDFF(modelname + ".dff"); + data->loadTXD(texturename + ".txd"); + + ModelRef m = data->models[modelname]; + + if(m && m->resource) { + auto ped = new CharacterObject( this, pos, rot, m, nullptr ); + ped->setGameObjectID(gid); + ped->setLifetime(GameObject::PlayerLifetime); + insertObject(ped); + characters.insert(ped); + new PlayerController(ped); + return ped; + } + } + return nullptr; +} + void GameWorld::insertObject(GameObject* object) { - // Find the lowest free GameObjectID. - GameObjectID availID = 1; - for( auto& p : objects ) + if( object->getGameObjectID() == 0 ) { - if( p.first == availID ) availID++; - } + // Find the lowest free GameObjectID. + GameObjectID availID = 1; + for( auto& p : objects ) + { + if( p.first == availID ) availID++; + } - object->setGameObjectID( availID ); - objects[availID] = object; + object->setGameObjectID( availID ); + } + objects[object->getGameObjectID()] = object; } GameObject* GameWorld::findObject(GameObjectID id) const @@ -582,6 +619,11 @@ glm::vec3 GameWorld::getGroundAtPosition(const glm::vec3 &pos) const return pos; } +float GameWorld::getGameTime() const +{ + return state->gameTime; +} + void handleVehicleResponse(GameObject* object, btManifoldPoint& mp, bool isA) { bool isVehicle = object->type() == GameObject::Vehicle; @@ -720,7 +762,7 @@ void GameWorld::loadCutscene(const std::string &name) void GameWorld::startCutscene() { - state->cutsceneStartTime = gameTime; + state->cutsceneStartTime = getGameTime(); state->skipCutscene = false; if( cutsceneAudio ) { cutsceneAudio->play(); @@ -752,7 +794,7 @@ void GameWorld::clearCutscene() bool GameWorld::isCutsceneDone() { if( state->currentCutscene ) { - float time = gameTime - state->cutsceneStartTime; + float time = getGameTime() - state->cutsceneStartTime; if( state->skipCutscene ) { return true; } diff --git a/rwengine/src/engine/SaveGame.cpp b/rwengine/src/engine/SaveGame.cpp new file mode 100644 index 00000000..99d65b58 --- /dev/null +++ b/rwengine/src/engine/SaveGame.cpp @@ -0,0 +1,315 @@ +#include +#include +#include +#include +#include +#include +#include