2015-05-03 05:22:03 +01:00
|
|
|
#include <engine/SaveGame.hpp>
|
|
|
|
#include <engine/GameState.hpp>
|
|
|
|
#include <engine/GameWorld.hpp>
|
|
|
|
#include <objects/GameObject.hpp>
|
2015-06-14 21:52:47 +01:00
|
|
|
#include <engine/GameData.hpp>
|
2015-05-03 05:22:03 +01:00
|
|
|
#include <objects/VehicleObject.hpp>
|
|
|
|
#include <objects/CharacterObject.hpp>
|
2015-06-14 18:08:55 +01:00
|
|
|
#include <objects/InstanceObject.hpp>
|
2015-05-03 05:22:03 +01:00
|
|
|
#include <script/ScriptMachine.hpp>
|
|
|
|
#include <script/SCMFile.hpp>
|
2015-06-14 02:44:51 +01:00
|
|
|
#include <ai/PlayerController.hpp>
|
2015-06-14 21:52:47 +01:00
|
|
|
#include <items/WeaponItem.hpp>
|
2015-06-24 01:48:26 +01:00
|
|
|
#include <cstring>
|
2016-04-09 22:29:32 +01:00
|
|
|
#include <iconv.h>
|
2015-06-14 02:44:51 +01:00
|
|
|
|
|
|
|
// Original save game file data structures
|
|
|
|
typedef uint16_t BlockWord;
|
|
|
|
typedef uint32_t BlockDword;
|
|
|
|
typedef BlockDword BlockSize;
|
|
|
|
|
|
|
|
struct Block0ContactInfo {
|
|
|
|
BlockDword missionFlag;
|
|
|
|
BlockDword baseBrief;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Block0BuildingSwap {
|
|
|
|
BlockDword type;
|
|
|
|
BlockDword handle;
|
|
|
|
BlockDword newModel;
|
|
|
|
BlockDword oldModel;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Block0InvisibilitySettings {
|
|
|
|
BlockDword type;
|
|
|
|
BlockDword handle;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Block0RunningScript {
|
|
|
|
uint32_t nextPointer;
|
|
|
|
uint32_t prevPointer;
|
|
|
|
char name[8];
|
|
|
|
BlockDword programCounter;
|
|
|
|
BlockDword stack[4];
|
|
|
|
BlockDword unknown0;
|
|
|
|
BlockDword unknown1;
|
|
|
|
BlockWord stackCounter;
|
|
|
|
BlockWord unknown2;
|
2015-06-24 01:48:26 +01:00
|
|
|
SCMByte variables[16*4];
|
2015-06-14 02:44:51 +01:00
|
|
|
BlockDword timerA;
|
|
|
|
BlockDword timerB;
|
|
|
|
uint8_t ifFlag;
|
|
|
|
uint8_t unknown3;
|
|
|
|
uint8_t unknown4;
|
|
|
|
uint8_t _align0;
|
|
|
|
BlockDword wakeTimer;
|
|
|
|
BlockWord ifNumber; // ?
|
|
|
|
uint8_t unknown[6];
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Block0ScriptData {
|
|
|
|
BlockDword onMissionOffset;
|
|
|
|
Block0ContactInfo contactInfo[16];
|
|
|
|
uint8_t unknown[0x100];
|
|
|
|
BlockDword lastMissionPassedTime;
|
|
|
|
Block0BuildingSwap buildingSwap[25];
|
|
|
|
Block0InvisibilitySettings invisibilitySettings[20];
|
|
|
|
uint8_t scriptRunning;
|
|
|
|
uint8_t _align0[3];
|
|
|
|
BlockDword mainSize;
|
|
|
|
BlockDword largestMissionSize;
|
|
|
|
BlockWord missionCount;
|
|
|
|
uint8_t _align1[2];
|
|
|
|
};
|
|
|
|
|
|
|
|
struct StructWeaponSlot {
|
|
|
|
BlockDword weaponId;
|
|
|
|
BlockDword unknown0;
|
|
|
|
BlockDword inClip;
|
|
|
|
BlockDword totalBullets;
|
|
|
|
BlockDword unknown1;
|
|
|
|
BlockDword unknown2;
|
|
|
|
};
|
|
|
|
struct StructPed {
|
|
|
|
uint8_t unknown0_[52];
|
|
|
|
glm::vec3 position;
|
|
|
|
uint8_t unknown1[640];
|
|
|
|
float health;
|
|
|
|
float armour;
|
|
|
|
uint8_t unknown2[148];
|
|
|
|
StructWeaponSlot weapons[13];
|
|
|
|
uint8_t unknown3[348];
|
|
|
|
};
|
|
|
|
|
|
|
|
// NOTE commented members are read manually, due to alignment.
|
|
|
|
struct Block1PlayerPed {
|
2015-06-14 18:08:55 +01:00
|
|
|
BlockDword unknown0;
|
|
|
|
BlockWord unknown1;
|
|
|
|
BlockDword reference;
|
|
|
|
StructPed info;
|
2015-06-14 02:44:51 +01:00
|
|
|
BlockDword maxWantedLevel;
|
|
|
|
BlockDword maxChaosLevel;
|
|
|
|
uint8_t modelName[24];
|
|
|
|
uint8_t align[2];
|
|
|
|
};
|
|
|
|
|
2015-06-14 18:08:55 +01:00
|
|
|
struct StructStoredCar {
|
|
|
|
BlockDword modelId;
|
|
|
|
glm::vec3 position;
|
|
|
|
glm::vec3 rotation;
|
|
|
|
BlockDword immunities;
|
|
|
|
uint8_t colorFG;
|
|
|
|
uint8_t colorBG;
|
|
|
|
uint8_t radio;
|
|
|
|
uint8_t variantA;
|
|
|
|
uint8_t variantB;
|
|
|
|
uint8_t bombType;
|
|
|
|
uint8_t align0[2];
|
|
|
|
|
|
|
|
// TODO Migrate to more available location (GameConstants?)
|
|
|
|
enum /*VehicleImmunities*/ {
|
|
|
|
Bulletproof = 1 << 0,
|
|
|
|
Fireproof = 1 << 1,
|
|
|
|
Explosionproof = 1 << 2,
|
|
|
|
CollisionProof = 1 << 3,
|
|
|
|
UnknownProof = 1 << 4
|
|
|
|
};
|
|
|
|
enum /*VehicleBombType*/ {
|
|
|
|
NoBomb = 0,
|
|
|
|
TimerBomb = 1,
|
|
|
|
IgnitionBomb = 2,
|
|
|
|
RemoteBomb = 3,
|
|
|
|
TimerBombArmed = 4,
|
|
|
|
IgnitionBombArmed = 5
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
struct StructGarage {
|
|
|
|
uint8_t type;
|
|
|
|
uint8_t unknown0;
|
|
|
|
uint8_t unknown1;
|
|
|
|
uint8_t unknown2;
|
|
|
|
uint8_t unknown3;
|
|
|
|
uint8_t unknown4;
|
|
|
|
uint8_t unknown5;
|
|
|
|
uint8_t align0[2];
|
|
|
|
BlockDword unknown6;
|
|
|
|
BlockDword unknown7;
|
|
|
|
uint8_t unknown8;
|
|
|
|
uint8_t unknown9;
|
|
|
|
uint8_t unknown10;
|
|
|
|
uint8_t unknown11;
|
|
|
|
uint8_t unknown12;
|
|
|
|
uint8_t unknown13;
|
|
|
|
uint8_t unknown14;
|
|
|
|
uint8_t align1;
|
|
|
|
float x1;
|
|
|
|
float x2;
|
|
|
|
float y1;
|
|
|
|
float y2;
|
|
|
|
float z1;
|
|
|
|
float z2;
|
|
|
|
float doorOpenStart;
|
|
|
|
float doorOpenAngle;
|
|
|
|
glm::vec2 unknownCoord1;
|
|
|
|
glm::vec2 unknownCoord2;
|
|
|
|
float doorAZ;
|
|
|
|
float doorBZ;
|
|
|
|
BlockDword unknown15;
|
|
|
|
uint8_t unknown16;
|
|
|
|
uint8_t align2[3];
|
|
|
|
BlockDword unknown17;
|
|
|
|
BlockDword unknown18;
|
|
|
|
BlockDword unknown19;
|
|
|
|
float unknown20;
|
|
|
|
float unknown21;
|
|
|
|
float unknown22;
|
|
|
|
float unknown23;
|
|
|
|
float unknown24;
|
|
|
|
float unknown25;
|
|
|
|
BlockDword unknown26;
|
|
|
|
uint8_t unknown27;
|
|
|
|
uint8_t unknown28;
|
|
|
|
uint8_t unknown29;
|
|
|
|
uint8_t unknown30;
|
|
|
|
uint8_t unknown31;
|
|
|
|
uint8_t unknown32;
|
|
|
|
uint8_t align3[2];
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Block2GarageData {
|
|
|
|
BlockDword garageCount;
|
|
|
|
BlockDword freeBombs;
|
|
|
|
BlockDword freeResprays;
|
|
|
|
BlockDword unknown0;
|
|
|
|
BlockDword unknown1;
|
|
|
|
BlockDword unknown2;
|
|
|
|
BlockDword bfImportExportPortland;
|
|
|
|
BlockDword bfImportExportShoreside;
|
|
|
|
BlockDword bfImportExportUnused;
|
|
|
|
BlockDword GA_21lastTime;
|
|
|
|
StructStoredCar cars[18];
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2015-06-14 02:44:51 +01:00
|
|
|
void SaveGame::writeGame(GameState& state, const std::string& file)
|
|
|
|
{
|
|
|
|
std::FILE* saveFile = std::fopen(file.c_str(), "w");
|
|
|
|
|
|
|
|
// BLOCK 0 - Variables
|
2016-04-09 22:29:32 +01:00
|
|
|
BasicState stateCopy = state.basic;
|
|
|
|
|
|
|
|
BlockSize block0Size = sizeof(BasicState);
|
2015-06-14 02:44:51 +01:00
|
|
|
fwrite(&block0Size, sizeof(BlockSize), 1, saveFile);
|
2016-04-09 22:29:32 +01:00
|
|
|
fwrite(&state.basic, sizeof(BasicState), 1, saveFile);
|
2015-06-14 02:44:51 +01:00
|
|
|
|
|
|
|
// BLOCK 0 - 0 Script
|
|
|
|
const char header[4] = "SCR";
|
|
|
|
BlockSize block0ScriptSize = sizeof(Block0ScriptData);
|
|
|
|
BlockSize block0ScriptHeaderSize = block0ScriptSize + sizeof(char) * 4 + sizeof(BlockDword);
|
|
|
|
fwrite(header, sizeof(char), 4, saveFile);
|
|
|
|
fwrite(&block0ScriptHeaderSize, sizeof(BlockSize), 1, saveFile);
|
|
|
|
BlockDword scriptVariablesCount = state.script->getGlobalData().size();
|
|
|
|
fwrite(&scriptVariablesCount, sizeof(BlockDword), 1, saveFile);
|
|
|
|
fwrite(state.script->getGlobals(), sizeof(SCMByte), scriptVariablesCount, saveFile);
|
|
|
|
|
|
|
|
BlockDword scriptDataSize = 0x03C8;
|
|
|
|
fwrite(&scriptDataSize, sizeof(BlockDword), 1, saveFile);
|
|
|
|
|
|
|
|
Block0ScriptData block0ScriptData = {};
|
|
|
|
block0ScriptData.onMissionOffset = (BlockDword)((SCMByte*)state.scriptOnMissionFlag - state.script->getGlobals());
|
|
|
|
block0ScriptData.lastMissionPassedTime = 0;
|
|
|
|
block0ScriptData.scriptRunning = 1;
|
|
|
|
block0ScriptData.mainSize = state.script->getFile()->getMainSize();
|
|
|
|
block0ScriptData.largestMissionSize = state.script->getFile()->getLargestMissionSize();
|
|
|
|
block0ScriptData.missionCount = state.script->getFile()->getMissionOffsets().size();
|
|
|
|
fwrite(&block0ScriptData, sizeof(block0ScriptData), 1, saveFile);
|
|
|
|
|
|
|
|
BlockDword scriptCount = state.script->getThreads().size();
|
|
|
|
fwrite(&scriptCount, sizeof(BlockDword), 1, saveFile);
|
|
|
|
|
|
|
|
for(SCMThread& thread : state.script->getThreads())
|
|
|
|
{
|
|
|
|
Block0RunningScript script = {};
|
|
|
|
strcpy(script.name, thread.name);
|
|
|
|
script.programCounter = thread.programCounter;
|
|
|
|
for(int i = 0; i < SCM_STACK_DEPTH; i++) {
|
|
|
|
script.stack[i] = thread.calls[i];
|
|
|
|
}
|
|
|
|
script.stackCounter = thread.stackDepth;
|
2015-06-24 01:48:26 +01:00
|
|
|
for(int i = 0; i < sizeof(Block0RunningScript::variables); i++) {
|
|
|
|
script.variables[i] = thread.locals[i];
|
2015-06-14 02:44:51 +01:00
|
|
|
}
|
|
|
|
script.timerA = *(BlockDword*)(thread.locals.data() + 16 * sizeof ( SCMByte ) * 4);
|
|
|
|
script.timerB = *(BlockDword*)(thread.locals.data() + 16 * sizeof ( SCMByte ) * 4);
|
|
|
|
script.ifFlag = thread.conditionResult;
|
|
|
|
script.wakeTimer = thread.wakeCounter;
|
|
|
|
script.ifNumber = thread.conditionCount;
|
|
|
|
fwrite(&script, sizeof(block0ScriptData), 1, saveFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-09 22:29:32 +01:00
|
|
|
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; \
|
|
|
|
} \
|
2015-06-14 02:44:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SaveGame::loadGame(GameState& state, const std::string& file)
|
|
|
|
{
|
|
|
|
std::FILE* loadFile = std::fopen(file.c_str(), "r");
|
2016-04-09 22:29:32 +01:00
|
|
|
if (loadFile == nullptr) {
|
|
|
|
std::cerr << "Failed to open save file" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
2015-06-14 02:44:51 +01:00
|
|
|
|
|
|
|
// BLOCK 0
|
|
|
|
BlockDword blockSize;
|
2016-04-09 22:29:32 +01:00
|
|
|
READ_SIZE(blockSize)
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2016-04-09 22:29:32 +01:00
|
|
|
static_assert(sizeof(BasicState) == 0xBC, "BasicState is not the right size");
|
|
|
|
READ_BLOCK(state.basic)
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2016-04-09 22:29:32 +01:00
|
|
|
// 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;
|
2015-06-14 02:44:51 +01:00
|
|
|
}
|
2016-04-09 22:29:32 +01:00
|
|
|
size_t outSize = 24;
|
|
|
|
char outBuff[48];
|
|
|
|
char* outCur = outBuff;
|
|
|
|
auto icv = iconv_open("UTF-8", "UTF-16");
|
|
|
|
char* saveName = (char*)state.basic.saveName;
|
|
|
|
|
|
|
|
iconv(icv, &saveName, &bytes, &outCur, &outSize);
|
|
|
|
strcpy(state.basic.saveName, outBuff);
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2016-04-09 22:29:32 +01:00
|
|
|
BlockDword scriptBlockSize;
|
|
|
|
|
|
|
|
READ_SIZE(scriptBlockSize)
|
|
|
|
CHECK_SIG("SCR")
|
|
|
|
READ_SIZE(scriptBlockSize)
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2015-07-01 23:16:06 +01:00
|
|
|
BlockDword scriptVarCount;
|
2016-04-09 22:29:32 +01:00
|
|
|
READ_SIZE(scriptVarCount)
|
2015-07-01 23:16:06 +01:00
|
|
|
assert(scriptVarCount == state.script->getFile()->getGlobalsSize());
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2016-04-09 22:29:32 +01:00
|
|
|
if(fread(state.script->getGlobals(), sizeof(SCMByte), scriptVarCount, loadFile) != scriptVarCount)
|
|
|
|
{
|
|
|
|
std::cerr << "Failed to read script memory" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
2015-06-14 02:44:51 +01:00
|
|
|
|
|
|
|
BlockDword scriptDataBlockSize;
|
|
|
|
fread(&scriptDataBlockSize, sizeof(BlockDword), 1, loadFile);
|
|
|
|
if( scriptDataBlockSize != 0x03C8 ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Block0ScriptData scriptData;
|
|
|
|
fread(&scriptData, sizeof(Block0ScriptData), 1, loadFile);
|
|
|
|
|
|
|
|
BlockDword numScripts;
|
2016-04-09 22:29:32 +01:00
|
|
|
READ_SIZE(numScripts)
|
2015-06-14 02:44:51 +01:00
|
|
|
Block0RunningScript scripts[numScripts];
|
|
|
|
fread(scripts, sizeof(Block0RunningScript), numScripts, loadFile);
|
|
|
|
|
|
|
|
// BLOCK 1
|
2016-04-09 22:29:32 +01:00
|
|
|
BlockDword playerBlockSize;
|
|
|
|
READ_SIZE(playerBlockSize)
|
|
|
|
BlockDword playerInfoSize;
|
|
|
|
READ_SIZE(playerInfoSize)
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2016-04-09 22:29:32 +01:00
|
|
|
BlockDword playerCount;
|
|
|
|
READ_SIZE(playerCount)
|
2015-06-14 02:44:51 +01:00
|
|
|
Block1PlayerPed players[playerCount];
|
2015-07-01 23:16:06 +01:00
|
|
|
for(unsigned int p = 0; p < playerCount; ++p) {
|
2016-04-09 22:29:32 +01:00
|
|
|
READ_BLOCK(players[p])
|
2015-06-14 18:08:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// BLOCK 2
|
2016-04-09 22:29:32 +01:00
|
|
|
BlockDword garageBlockSize;
|
|
|
|
READ_SIZE(garageBlockSize)
|
|
|
|
BlockDword garageDataSize;
|
|
|
|
READ_SIZE(garageDataSize)
|
2015-06-14 18:08:55 +01:00
|
|
|
|
|
|
|
Block2GarageData garageData;
|
2016-04-09 22:29:32 +01:00
|
|
|
READ_BLOCK(garageData)
|
2015-06-14 18:08:55 +01:00
|
|
|
|
|
|
|
StructGarage garages[garageData.garageCount];
|
|
|
|
fread(garages, sizeof(StructGarage), garageData.garageCount, loadFile);
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2016-04-09 22:29:32 +01:00
|
|
|
// We keep track of the game time as a float for now
|
|
|
|
state.gameTime = state.basic.timeMS / 1000.f;
|
2015-06-14 02:44:51 +01:00
|
|
|
|
|
|
|
state.scriptOnMissionFlag = (unsigned int*)state.script->getGlobals() + (size_t)scriptData.onMissionOffset;
|
|
|
|
|
|
|
|
auto& threads = state.script->getThreads();
|
|
|
|
for(int s = 0; s < numScripts; ++s) {
|
|
|
|
state.script->startThread(scripts[s].programCounter);
|
|
|
|
SCMThread& thread = threads.back();
|
|
|
|
// thread.baseAddress // ??
|
2015-07-06 00:54:13 +01:00
|
|
|
strncpy(thread.name, scripts[s].name, sizeof(SCMThread::name)-1);
|
2015-06-14 02:44:51 +01:00
|
|
|
thread.conditionResult = scripts[s].ifFlag;
|
|
|
|
thread.conditionCount = scripts[s].ifNumber;
|
|
|
|
thread.stackDepth = scripts[s].stackCounter;
|
|
|
|
for(int i = 0; i < SCM_STACK_DEPTH; ++i) {
|
|
|
|
thread.calls[i] = scripts[s].stack[i];
|
|
|
|
}
|
2015-06-14 21:52:47 +01:00
|
|
|
/* TODO not hardcode +33 ms */
|
2016-04-09 22:29:32 +01:00
|
|
|
thread.wakeCounter = scripts[s].wakeTimer - state.basic.lastTick + 33;
|
2015-06-24 01:48:26 +01:00
|
|
|
for(int i = 0; i < sizeof(Block0RunningScript::variables); ++i) {
|
|
|
|
thread.locals[i] = scripts[s].variables[i];
|
|
|
|
}
|
2015-06-14 02:44:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if( playerCount > 0 ) {
|
2015-06-14 21:52:47 +01:00
|
|
|
auto& ply = players[0];
|
2015-06-24 01:48:26 +01:00
|
|
|
std::cout << ply.reference << std::endl;
|
2015-06-14 02:44:51 +01:00
|
|
|
auto player = state.world->createPlayer(players[0].info.position);
|
|
|
|
player->mHealth = players[0].info.health;
|
|
|
|
state.playerObject = player->getGameObjectID();
|
|
|
|
state.maxWantedLevel = players[0].maxWantedLevel;
|
2015-06-14 21:52:47 +01:00
|
|
|
for(int w = 0; w < 13; ++w) {
|
|
|
|
auto& wep = ply.info.weapons[w];
|
|
|
|
if(wep.weaponId != 0) {
|
|
|
|
auto& weaponInfo = state.world->data->weaponData.at(wep.weaponId);
|
|
|
|
auto item = new WeaponItem(player, weaponInfo);
|
|
|
|
player->addToInventory(item);
|
|
|
|
}
|
|
|
|
}
|
2015-06-14 02:44:51 +01:00
|
|
|
}
|
2015-06-14 18:08:55 +01:00
|
|
|
|
|
|
|
// TODO restore garage data
|
|
|
|
// http://gtaforums.com/topic/758692-gta-iii-save-file-documentation/
|
|
|
|
for(int g = 0; g < garageData.garageCount; ++g) {
|
|
|
|
auto& garage = garages[g];
|
|
|
|
state.garages.push_back({
|
|
|
|
glm::vec3(garage.x1, garage.y1, garage.z1),
|
|
|
|
glm::vec3(garage.x2, garage.y2, garage.z2),
|
|
|
|
garage.type
|
|
|
|
});
|
|
|
|
auto& gameGarage = state.garages.back();
|
|
|
|
auto center = (gameGarage.min + gameGarage.max)/2.f;
|
|
|
|
// Find the nearest dynamic instance?
|
|
|
|
float distance = std::numeric_limits<float>::max();
|
|
|
|
GameObject* nearinst = nullptr;
|
2015-06-24 01:48:26 +01:00
|
|
|
for(std::pair<GameObjectID, GameObject*> object : state.world->instancePool.objects) {
|
|
|
|
auto instance = static_cast<InstanceObject*>(object.second);
|
|
|
|
if( instance->dynamics ) {
|
|
|
|
float idist = glm::distance(center, instance->getPosition());
|
|
|
|
if( idist < distance ) {
|
|
|
|
distance = idist;
|
|
|
|
nearinst = instance;
|
2015-06-14 18:08:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Nearinst is probably the garage door.
|
|
|
|
}
|
|
|
|
for(int c = 0; c < 18; ++c) {
|
|
|
|
if(garageData.cars[c].modelId == 0) continue;
|
|
|
|
auto& car = garageData.cars[c];
|
|
|
|
glm::quat rotation(-glm::vec3(car.rotation.z, car.rotation.y, car.rotation.x));
|
|
|
|
|
|
|
|
VehicleObject* vehicle = state.world->createVehicle(car.modelId, car.position, rotation);
|
|
|
|
vehicle->setPrimaryColour(car.colorFG);
|
|
|
|
vehicle->setSecondaryColour(car.colorBG);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::fclose(loadFile);
|
2015-06-14 02:44:51 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-14 18:08:55 +01:00
|
|
|
#include <dirent.h>
|
2016-04-09 02:21:22 +01:00
|
|
|
bool SaveGame::getSaveInfo(const std::string& file, BasicState *basicState)
|
2015-06-14 18:08:55 +01:00
|
|
|
{
|
|
|
|
std::FILE* loadFile = std::fopen(file.c_str(), "r");
|
2016-04-09 02:21:22 +01:00
|
|
|
|
|
|
|
SaveGameInfo info;
|
|
|
|
info.savePath = file;
|
2015-06-14 18:08:55 +01:00
|
|
|
|
|
|
|
// BLOCK 0
|
|
|
|
BlockDword blockSize;
|
2016-04-09 02:21:22 +01:00
|
|
|
if( fread(&blockSize, sizeof(BlockDword), 1, loadFile) == 0 ) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-06-14 18:08:55 +01:00
|
|
|
|
2016-04-09 02:21:22 +01:00
|
|
|
// Read block 0 into state
|
|
|
|
if( fread(basicState, sizeof(BasicState), 1, loadFile) == 0 ) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-06-14 18:08:55 +01:00
|
|
|
|
|
|
|
std::fclose(loadFile);
|
2016-04-09 22:29:32 +01:00
|
|
|
|
2015-06-14 18:08:55 +01:00
|
|
|
size_t bytes = 0;
|
|
|
|
for(;; bytes++ ) {
|
2016-04-09 02:21:22 +01:00
|
|
|
if(basicState->saveName[bytes-1] == 0 && basicState->saveName[bytes] == 0) break;
|
2015-06-14 18:08:55 +01:00
|
|
|
}
|
|
|
|
size_t outSize = 24;
|
2016-04-09 02:21:22 +01:00
|
|
|
char outBuff[48];
|
2015-06-14 18:08:55 +01:00
|
|
|
char* outCur = outBuff;
|
|
|
|
auto icv = iconv_open("UTF-8", "UTF-16");
|
2016-04-09 02:21:22 +01:00
|
|
|
char* saveName = (char*)basicState->saveName;
|
|
|
|
|
|
|
|
// Convert to UTF-8 and copy back to the return struct
|
2015-06-14 18:08:55 +01:00
|
|
|
iconv(icv, &saveName, &bytes, &outCur, &outSize);
|
2016-04-09 02:21:22 +01:00
|
|
|
strcpy(basicState->saveName, outBuff);
|
|
|
|
|
|
|
|
return true;
|
2015-06-14 18:08:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector< SaveGameInfo > SaveGame::getAllSaveGameInfo()
|
|
|
|
{
|
|
|
|
// TODO consider windows
|
|
|
|
auto homedir = getenv("HOME");
|
|
|
|
if( homedir == nullptr ) {
|
|
|
|
std::cerr << "Unable to determine home directory" << std::endl;
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
const char gameDir[] = "GTA3 User Files";
|
|
|
|
std::string gamePath(homedir);
|
|
|
|
gamePath.append("/");
|
|
|
|
gamePath.append(gameDir);
|
|
|
|
|
|
|
|
DIR* dp = opendir(gamePath.c_str());
|
|
|
|
dirent* ep;
|
|
|
|
std::string realName;
|
|
|
|
if ( dp == NULL ) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
std::vector<SaveGameInfo> infos;
|
|
|
|
while( (ep = readdir(dp)) )
|
|
|
|
{
|
|
|
|
if ( ep->d_type == DT_REG ) {
|
|
|
|
realName = ep->d_name;
|
|
|
|
if(realName.find(".b") != realName.npos) {
|
2016-04-09 02:21:22 +01:00
|
|
|
std::string path = gamePath+"/"+realName;
|
|
|
|
infos.emplace_back(SaveGameInfo{path, false, {}});
|
|
|
|
infos.back().valid = getSaveInfo(infos.back().savePath, &infos.back().basicState);
|
2015-06-14 18:08:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(dp);
|
|
|
|
return infos;
|
|
|
|
}
|
|
|
|
|