1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-15 15:02:34 +02:00

Clean up game state structure to better match save format

This commit is contained in:
Daniel Evans 2016-04-09 22:29:32 +01:00
parent f69f5c3cd7
commit 909f00b079
7 changed files with 209 additions and 116 deletions

View File

@ -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;

View File

@ -1,16 +1,13 @@
#include <engine/GameState.hpp>
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),

View File

@ -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

View File

@ -11,6 +11,7 @@
#include <ai/PlayerController.hpp>
#include <items/WeaponItem.hpp>
#include <cstring>
#include <iconv.h>
// Original save game file data structures
typedef uint16_t BlockWord;
@ -102,10 +103,6 @@ struct Block1PlayerPed {
uint8_t align[2];
};
template<class T> 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<class T> 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 <iconv.h>
#include <dirent.h>
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;

View File

@ -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<WeatherLoader::WeatherCondition>(world->state->currentWeather * 24);
auto weatherID = static_cast<WeatherLoader::WeatherCondition>(world->state->basic.nextWeather * 24);
auto weather = world->data->weatherLoader.getWeatherData(weatherID, tod);
glm::vec3 skyTop = weather.skyTopColor;

View File

@ -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)

View File

@ -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;