diff --git a/rwengine/include/engine/GameState.hpp b/rwengine/include/engine/GameState.hpp index c4bdd195..59884fc8 100644 --- a/rwengine/include/engine/GameState.hpp +++ b/rwengine/include/engine/GameState.hpp @@ -32,22 +32,22 @@ struct BasicState /// /!\ This is wchar_t[24] in the original format /!\ we convert on load for convenience char saveName[48]; SystemTime saveTime; - uint16_t unknown; + uint32_t unknown; uint16_t islandNumber; glm::vec3 cameraPosition; - uint16_t gameMinuteMS; - uint16_t lastTick; + uint32_t gameMinuteMS; + uint32_t lastTick; uint8_t gameHour; uint8_t _align0[3]; uint8_t gameMinute; uint8_t _align1[3]; uint16_t padMode; uint8_t _align2[2]; - uint16_t timeMS; + uint32_t timeMS; float timeScale; float timeStep; float timeStep_unclipped; // Unknown purpose - uint16_t frameCounter; + uint32_t frameCounter; float timeStep2; float framesPerUpdate; float timeScale2; @@ -59,11 +59,96 @@ struct BasicState uint8_t _align5[2]; float weatherInterpolation; uint8_t dateTime[24]; // Unused - uint16_t weatherType; + uint32_t weatherType; float cameraData; float cameraData2; }; +/** Block 15 player info */ +struct PlayerInfo +{ + uint16_t money; + uint8_t unknown1; + uint16_t unknown2; + uint8_t unknown3; + float unknown4; + uint16_t displayedMoney; + uint16_t hiddenPackagesCollected; + uint16_t hiddenPackageCount; + uint8_t neverTired; + uint8_t fastReload; + uint8_t thaneOfLibertyCity; + uint8_t singlePayerHealthcare; + uint8_t unknown5[70]; +}; + +/** Block 17 */ +struct GameStats +{ + uint16_t playerKills; + uint16_t otherKills; + uint16_t carsExploded; + uint16_t shotsHit; + uint16_t pedTypesKilled[23]; + uint16_t helicoptersDestroyed; + uint16_t playerProgress; + uint16_t explosiveKgsUsed; + uint16_t bulletsFired; + uint16_t bulletsHit; + uint16_t carsCrushed; + uint16_t headshots; + uint16_t timesBusted; + uint16_t timesHospital; + uint16_t daysPassed; + uint16_t mmRainfall; + uint16_t insaneJumpMaxDistance; + uint16_t insaneJumpMaxHeight; + uint16_t insaneJumpMaxFlips; + uint16_t insangeJumpMaxRotation; + /* + * 0 none completed + * 1 insane stunt + * 2 perfect insane stunt + * 3 double insane stunt + * 4 perfect double insane stunt + * 5 triple insane stunt + * 6 perfect " " " + * 7 quadruple + * 8 perfect quadruple + */ + uint16_t bestStunt; + uint16_t uniqueStuntsFound; + uint16_t uniqueStuntsTotal; + uint16_t missionAttempts; + uint16_t missionsPassed; + uint16_t passengersDroppedOff; + uint16_t taxiRevenue; + uint16_t portlandPassed; + uint16_t stauntonPassed; + uint16_t shoresidePassed; + uint16_t bestTurismoTime; + float distanceWalked; + float distanceDriven; + uint16_t patriotPlaygroundTime; + uint16_t aRideInTheParkTime; + uint16_t grippedTime; + uint16_t multistoryMayhemTime; + uint16_t peopleSaved; + uint16_t criminalsKilled; + uint16_t highestParamedicLevel; + uint16_t firesExtinguished; + uint16_t longestDodoFlight; + uint16_t bombDefusalTime; + uint16_t rampagesPassed; + uint16_t totalRampages; + uint16_t totalMissions; + uint16_t fastestTime[16]; // not used + uint16_t highestScore[16]; + uint16_t peopleKilledSinceCheckpoint; // ? + uint16_t peopleKilledSinceLastBustedOrWasted; + char lastMissionGXT[8]; +}; + struct TextDisplayData { // This is set by the final display text command. @@ -196,23 +281,32 @@ struct GarageInfo */ struct GameState { + /** + Basic Game State + */ + BasicState basic; + + /** + Player stats + */ + PlayerInfo playerInfo; + + /** + Game Stats + */ + GameStats gameStats; + /** * Second since game was started */ float gameTime; unsigned int currentProgress; unsigned int maxProgress; - unsigned int numMissions; - unsigned int numHiddenPackages; - unsigned int numHiddenPackagesDiscovered; - unsigned int numUniqueJumps; - unsigned int numRampages; + unsigned int maxWantedLevel; GameObjectID playerObject; - unsigned int currentWeather; - /** * @brief Stores a pointer to script global that stores the on-mission state. */ @@ -238,9 +332,6 @@ struct GameState float cutsceneStartTime; /** Flag for rendering cutscene letterbox */ bool isCinematic; - - short hour; - short minute; std::string lastMissionName; diff --git a/rwengine/src/engine/GameState.cpp b/rwengine/src/engine/GameState.cpp index c8e84421..36fa94d7 100644 --- a/rwengine/src/engine/GameState.cpp +++ b/rwengine/src/engine/GameState.cpp @@ -1,16 +1,13 @@ #include -GameState::GameState() : -gameTime(0.f), +GameState::GameState() + : basic{} + , playerInfo{} + , gameStats{} + , gameTime(0.f), currentProgress(0), maxProgress(1), -numMissions(0), -numHiddenPackages(0), -numHiddenPackagesDiscovered(0), -numUniqueJumps(0), -numRampages(0), maxWantedLevel(0), -currentWeather(0), scriptOnMissionFlag(nullptr), fadeOut(true), fadeStart(0.f), @@ -21,8 +18,6 @@ isIntroPlaying(false), currentCutscene(nullptr), cutsceneStartTime(-1.f), isCinematic(false), -hour(0), -minute(0), cameraNear(0.1f), cameraFixed(false), cameraTarget(0), diff --git a/rwengine/src/engine/GameWorld.cpp b/rwengine/src/engine/GameWorld.cpp index 8a314ddb..4edde5e2 100644 --- a/rwengine/src/engine/GameWorld.cpp +++ b/rwengine/src/engine/GameWorld.cpp @@ -622,12 +622,12 @@ void GameWorld::doWeaponScan(const WeaponScan &scan) int GameWorld::getHour() { - return state->hour; + return state->basic.gameHour; } int GameWorld::getMinute() { - return state->minute; + return state->basic.gameMinute; } glm::vec3 GameWorld::getGroundAtPosition(const glm::vec3 &pos) const diff --git a/rwengine/src/engine/SaveGame.cpp b/rwengine/src/engine/SaveGame.cpp index 8a887240..70b7f44e 100644 --- a/rwengine/src/engine/SaveGame.cpp +++ b/rwengine/src/engine/SaveGame.cpp @@ -11,6 +11,7 @@ #include #include #include +#include // Original save game file data structures typedef uint16_t BlockWord; @@ -102,10 +103,6 @@ struct Block1PlayerPed { uint8_t align[2]; }; -template void read(std::FILE* str, T& out) { - std::fread(&out, sizeof(out), 1, str); -} - struct StructStoredCar { BlockDword modelId; glm::vec3 position; @@ -210,31 +207,11 @@ void SaveGame::writeGame(GameState& state, const std::string& file) std::FILE* saveFile = std::fopen(file.c_str(), "w"); // BLOCK 0 - Variables - BasicState block0Data = {}; - //strcpy(block0Data.saveName, "OpenRW Save Game"); - block0Data.islandNumber = 1; - block0Data.cameraPosition = glm::vec3(0.f); - block0Data.gameMinuteMS = 1000; - block0Data.lastTick = 1; - block0Data.gameHour = 12; - block0Data.gameMinute = 13; - block0Data.padMode = 1; - block0Data.timeMS = 10000; - block0Data.timeScale = 1.0; - block0Data.timeStep = 1.0/60.f; - block0Data.timeStep_unclipped = block0Data.timeStep; - block0Data.frameCounter = 1000; - block0Data.timeStep2 = 1.0; - block0Data.framesPerUpdate = 1.0; - block0Data.timeScale2 = 1.0; - block0Data.lastWeather = 1; - block0Data.nextWeather = 1; - block0Data.weatherInterpolation = 1.0; - block0Data.weatherType = 1; - - BlockSize block0Size = sizeof(block0Data); // TODO calculate correctly. + BasicState stateCopy = state.basic; + + BlockSize block0Size = sizeof(BasicState); fwrite(&block0Size, sizeof(BlockSize), 1, saveFile); - fwrite(&block0Data, sizeof(block0Data), 1, saveFile); + fwrite(&state.basic, sizeof(BasicState), 1, saveFile); // BLOCK 0 - 0 Script const char header[4] = "SCR"; @@ -282,41 +259,77 @@ void SaveGame::writeGame(GameState& state, const std::string& file) } } -const size_type DWORDSZ = sizeof(BlockDword); -BlockDword readDword(std::FILE* file) -{ - BlockDword sz; - fread(&sz, sizeof(BlockDword), 1, file); - return sz; +template bool readBlock(std::FILE* str, T& out) { + return std::fread(&out, sizeof(out), 1, str) == 1; +} + +#define READ_BLOCK(var) \ + if (! readBlock(loadFile, var)) { \ + std::cerr << file << ": Failed to load block " #var << std::endl; \ + return false; \ + } +#define READ_SIZE(var) \ + if (! readBlock(loadFile, var)) { \ + std::cerr << file << ": Failed to load size " #var << std::endl; \ + return false; \ + } +#define CHECK_SIG(expected) \ +{\ + char signature[4]; \ + if(fread(signature, sizeof(char), 4, loadFile) != 4) { \ + std::cerr << "Failed to read signature" << std::endl; \ + return false; \ + } \ + if (strncmp(signature, expected, 3) != 0) { \ + std::cerr << "Signature " expected " incorrect" << std::endl; \ + return false; \ + } \ } bool SaveGame::loadGame(GameState& state, const std::string& file) { std::FILE* loadFile = std::fopen(file.c_str(), "r"); + if (loadFile == nullptr) { + std::cerr << "Failed to open save file" << std::endl; + return false; + } // BLOCK 0 BlockDword blockSize; - fread(&blockSize, sizeof(BlockDword), 1, loadFile); + READ_SIZE(blockSize) - BasicState block0Data; - fread(&block0Data, sizeof(BasicState), 1, loadFile); - - BlockDword scriptBlockSize; - fread(&scriptBlockSize, sizeof(BlockDword), 1, loadFile); + static_assert(sizeof(BasicState) == 0xBC, "BasicState is not the right size"); + READ_BLOCK(state.basic) - char signature[4]; - fread(signature, sizeof(char), 4, loadFile); - if( std::strncmp(signature, "SCR", 3) != 0 ) { - return false; + // Convert utf-16 to utf-8 + size_t bytes = 0; + for(;; bytes++ ) { + if(state.basic.saveName[bytes-1] == 0 && state.basic.saveName[bytes] == 0) break; } + size_t outSize = 24; + char outBuff[48]; + char* outCur = outBuff; + auto icv = iconv_open("UTF-8", "UTF-16"); + char* saveName = (char*)state.basic.saveName; - fread(&scriptBlockSize, sizeof(BlockDword), 1, loadFile); + iconv(icv, &saveName, &bytes, &outCur, &outSize); + strcpy(state.basic.saveName, outBuff); + + BlockDword scriptBlockSize; + + READ_SIZE(scriptBlockSize) + CHECK_SIG("SCR") + READ_SIZE(scriptBlockSize) BlockDword scriptVarCount; - fread(&scriptVarCount, sizeof(BlockDword), 1, loadFile); + READ_SIZE(scriptVarCount) assert(scriptVarCount == state.script->getFile()->getGlobalsSize()); - fread(state.script->getGlobals(), sizeof(SCMByte), scriptVarCount, loadFile); + if(fread(state.script->getGlobals(), sizeof(SCMByte), scriptVarCount, loadFile) != scriptVarCount) + { + std::cerr << "Failed to read script memory" << std::endl; + return false; + } BlockDword scriptDataBlockSize; fread(&scriptDataBlockSize, sizeof(BlockDword), 1, loadFile); @@ -328,43 +341,37 @@ bool SaveGame::loadGame(GameState& state, const std::string& file) fread(&scriptData, sizeof(Block0ScriptData), 1, loadFile); BlockDword numScripts; - fread(&numScripts, DWORDSZ, 1, loadFile); + READ_SIZE(numScripts) Block0RunningScript scripts[numScripts]; fread(scripts, sizeof(Block0RunningScript), numScripts, loadFile); // BLOCK 1 - BlockDword playerBlockSize = readDword(loadFile); - BlockDword playerInfoSize = readDword(loadFile); + BlockDword playerBlockSize; + READ_SIZE(playerBlockSize) + BlockDword playerInfoSize; + READ_SIZE(playerInfoSize) - BlockDword playerCount = readDword(loadFile); + BlockDword playerCount; + READ_SIZE(playerCount) Block1PlayerPed players[playerCount]; for(unsigned int p = 0; p < playerCount; ++p) { - read(loadFile, players[p].unknown0); - read(loadFile, players[p].unknown1); - read(loadFile, players[p].reference); - read(loadFile, players[p].info); - read(loadFile, players[p].maxWantedLevel); - read(loadFile, players[p].maxChaosLevel); - read(loadFile, players[p].modelName); - read(loadFile, players[p].align); + READ_BLOCK(players[p]) } // BLOCK 2 - BlockDword garageBlockSize = readDword(loadFile); - BlockDword garageDataSize = readDword(loadFile); + BlockDword garageBlockSize; + READ_SIZE(garageBlockSize) + BlockDword garageDataSize; + READ_SIZE(garageDataSize) Block2GarageData garageData; - fread(&garageData, sizeof(Block2GarageData), 1, loadFile); + READ_BLOCK(garageData) StructGarage garages[garageData.garageCount]; fread(garages, sizeof(StructGarage), garageData.garageCount, loadFile); - // Insert Game State. - state.hour = block0Data.gameHour; - state.minute = block0Data.gameMinute; - state.gameTime = block0Data.timeMS / 1000.f; - state.currentWeather = block0Data.nextWeather; - state.cameraPosition = block0Data.cameraPosition; + // We keep track of the game time as a float for now + state.gameTime = state.basic.timeMS / 1000.f; state.scriptOnMissionFlag = (unsigned int*)state.script->getGlobals() + (size_t)scriptData.onMissionOffset; @@ -381,7 +388,7 @@ bool SaveGame::loadGame(GameState& state, const std::string& file) thread.calls[i] = scripts[s].stack[i]; } /* TODO not hardcode +33 ms */ - thread.wakeCounter = scripts[s].wakeTimer - block0Data.lastTick + 33; + thread.wakeCounter = scripts[s].wakeTimer - state.basic.lastTick + 33; for(int i = 0; i < sizeof(Block0RunningScript::variables); ++i) { thread.locals[i] = scripts[s].variables[i]; } @@ -445,7 +452,6 @@ bool SaveGame::loadGame(GameState& state, const std::string& file) return true; } -#include #include bool SaveGame::getSaveInfo(const std::string& file, BasicState *basicState) { @@ -466,6 +472,7 @@ bool SaveGame::getSaveInfo(const std::string& file, BasicState *basicState) } std::fclose(loadFile); + size_t bytes = 0; for(;; bytes++ ) { if(basicState->saveName[bytes-1] == 0 && basicState->saveName[bytes] == 0) break; diff --git a/rwengine/src/render/GameRenderer.cpp b/rwengine/src/render/GameRenderer.cpp index 9d7462ba..677af086 100644 --- a/rwengine/src/render/GameRenderer.cpp +++ b/rwengine/src/render/GameRenderer.cpp @@ -247,10 +247,10 @@ void GameRenderer::renderWorld(GameWorld* world, const ViewCamera &camera, float glBindVertexArray( vao ); - float tod = world->state->hour + world->state->minute/60.f; + float tod = world->getHour() + world->getMinute()/60.f; // Requires a float 0-24 - auto weatherID = static_cast(world->state->currentWeather * 24); + auto weatherID = static_cast(world->state->basic.nextWeather * 24); auto weather = world->data->weatherLoader.getWeatherData(weatherID, tod); glm::vec3 skyTop = weather.skyTopColor; diff --git a/rwengine/src/script/modules/GameModule.cpp b/rwengine/src/script/modules/GameModule.cpp index 7359ce74..a6e44464 100644 --- a/rwengine/src/script/modules/GameModule.cpp +++ b/rwengine/src/script/modules/GameModule.cpp @@ -66,8 +66,8 @@ void game_get_time(const ScriptArguments& args) void game_set_time(const ScriptArguments& args) { - args.getWorld()->state->hour = args[0].integer; - args.getWorld()->state->minute = args[1].integer; + args.getWorld()->state->basic.gameHour = args[0].integer; + args.getWorld()->state->basic.gameMinute = args[1].integer; } bool game_is_button_pressed(const ScriptArguments& args) @@ -301,7 +301,7 @@ void game_enable_input(const ScriptArguments& args) void game_set_weather(const ScriptArguments& args) { - args.getWorld()->state->currentWeather = args[0].integer; + args.getWorld()->state->basic.nextWeather = args[0].integer; } void game_get_runtime(const ScriptArguments& args) @@ -593,7 +593,7 @@ void game_clear_cutscene(const ScriptArguments& args) void game_set_hidden_packages(const ScriptArguments& args) { - args.getWorld()->state->numHiddenPackages = args[0].integer; + args.getWorld()->state->playerInfo.hiddenPackageCount = args[0].integer; } void game_load_special_model(const ScriptArguments& args) @@ -638,7 +638,7 @@ void game_set_max_progress(const ScriptArguments& args) void game_set_unique_jumps(const ScriptArguments& args) { - args.getWorld()->state->numUniqueJumps = args[0].integer; + args.getWorld()->state->gameStats.uniqueStuntsTotal = args[0].integer; } void game_set_last_mission(const ScriptArguments& args) @@ -785,7 +785,7 @@ bool game_did_game_save(const ScriptArguments& args) void game_get_found_hidden_packages(const ScriptArguments& args) { - *args[0].globalInteger = args.getWorld()->state->numHiddenPackagesDiscovered; + *args[0].globalInteger = args.getWorld()->state->playerInfo.hiddenPackagesCollected; } void game_display_help(const ScriptArguments& args) @@ -830,7 +830,7 @@ void game_load_collision(const ScriptArguments& args) void game_set_rampages(const ScriptArguments& args) { - args.getWorld()->state->numRampages = args[0].integer; + args.getWorld()->state->gameStats.totalRampages = args[0].integer; } void game_set_near_clip(const ScriptArguments& args) @@ -840,7 +840,7 @@ void game_set_near_clip(const ScriptArguments& args) void game_set_missions(const ScriptArguments& args) { - args.getWorld()->state->numMissions = args[0].integer; + args.getWorld()->state->gameStats.totalMissions = args[0].integer; } void game_set_sound_fade(const ScriptArguments& args) diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index b004f26b..b697e28b 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -365,12 +365,12 @@ void RWGame::tick(float dt) clockAccumulator += dt; while( clockAccumulator >= 1.f ) { - world->state->minute ++; - while( state->minute >= 60 ) { - state->minute = 0; - state->hour ++; - while( state->hour >= 24 ) { - state->hour = 0; + world->state->basic.gameMinute ++; + while( state->basic.gameMinute >= 60 ) { + state->basic.gameMinute = 0; + state->basic.gameHour ++; + while( state->basic.gameHour >= 24 ) { + state->basic.gameHour = 0; } } clockAccumulator -= 1.f; @@ -685,10 +685,10 @@ void RWGame::globalKeyEvent(const sf::Event& event) { switch (event.key.code) { case sf::Keyboard::LBracket: - state->minute -= 30.f; + state->basic.gameMinute -= 30.f; break; case sf::Keyboard::RBracket: - state->minute += 30.f; + state->basic.gameMinute += 30.f; break; case sf::Keyboard::Num9: timescale *= 0.5f;