2017-10-31 04:05:12 +01:00
|
|
|
#include "engine/SaveGame.hpp"
|
|
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
#include <cstdlib>
|
2016-09-09 21:13:19 +01:00
|
|
|
#include <cstring>
|
2017-10-31 04:05:12 +01:00
|
|
|
#include <cstdio>
|
|
|
|
|
|
|
|
#include <iostream>
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2017-10-29 22:50:59 +00:00
|
|
|
#include <rw/filesystem.hpp>
|
2016-08-03 18:25:45 -07:00
|
|
|
|
2017-10-31 04:05:12 +01:00
|
|
|
#include <glm/glm.hpp>
|
|
|
|
#include <glm/gtc/quaternion.hpp>
|
|
|
|
|
|
|
|
#include <rw/defines.hpp>
|
|
|
|
|
|
|
|
#include "data/ZoneData.hpp"
|
|
|
|
#include "engine/GameData.hpp"
|
|
|
|
#include "engine/GameState.hpp"
|
|
|
|
#include "engine/GameWorld.hpp"
|
|
|
|
#include "objects/CharacterObject.hpp"
|
|
|
|
#include "objects/GameObject.hpp"
|
|
|
|
#include "objects/InstanceObject.hpp"
|
|
|
|
#include "objects/VehicleObject.hpp"
|
|
|
|
#include "script/SCMFile.hpp"
|
|
|
|
#include "script/ScriptMachine.hpp"
|
|
|
|
#include "script/ScriptTypes.hpp"
|
|
|
|
|
2018-01-27 21:24:15 +01:00
|
|
|
namespace {
|
|
|
|
constexpr int kNrOfWeapons = 13;
|
|
|
|
constexpr int kNrOfStoredCars = 18;
|
|
|
|
constexpr int kNrOfPickups = 336;
|
|
|
|
constexpr int kNrOfBlips = 32;
|
|
|
|
constexpr int kNrOfNavZones = 50;
|
|
|
|
constexpr int kNrOfDayNightInfo = 100;
|
|
|
|
constexpr int kNrOfMapZones = 25;
|
|
|
|
constexpr int kNrOfAudioZones = 36;
|
|
|
|
constexpr int kNrOfGangs = 9;
|
|
|
|
constexpr int kNrOfPedTypes = 23;
|
|
|
|
}
|
|
|
|
|
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 {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword missionFlag;
|
|
|
|
BlockDword baseBrief;
|
2015-06-14 02:44:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block0BuildingSwap {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword type;
|
|
|
|
BlockDword handle;
|
|
|
|
BlockDword newModel;
|
|
|
|
BlockDword oldModel;
|
2015-06-14 02:44:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block0InvisibilitySettings {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword type;
|
|
|
|
BlockDword handle;
|
2015-06-14 02:44:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block0RunningScript {
|
2016-09-09 21:13:19 +01:00
|
|
|
uint32_t nextPointer;
|
|
|
|
uint32_t prevPointer;
|
|
|
|
char name[8];
|
|
|
|
BlockDword programCounter;
|
|
|
|
BlockDword stack[4];
|
|
|
|
BlockDword unknown0;
|
|
|
|
BlockDword unknown1;
|
|
|
|
BlockWord stackCounter;
|
|
|
|
BlockWord unknown2;
|
|
|
|
SCMByte variables[16 * 4];
|
|
|
|
BlockDword timerA;
|
|
|
|
BlockDword timerB;
|
|
|
|
uint8_t ifFlag;
|
|
|
|
uint8_t unknown3;
|
|
|
|
uint8_t unknown4;
|
|
|
|
uint8_t _align0;
|
|
|
|
BlockDword wakeTimer;
|
|
|
|
BlockWord ifNumber; // ?
|
|
|
|
uint8_t unknown[6];
|
2015-06-14 02:44:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block0ScriptData {
|
2016-09-09 21:13:19 +01:00
|
|
|
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];
|
2015-06-14 02:44:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct StructWeaponSlot {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword weaponId;
|
|
|
|
BlockDword unknown0;
|
|
|
|
BlockDword inClip;
|
|
|
|
BlockDword totalBullets;
|
|
|
|
BlockDword unknown1;
|
|
|
|
BlockDword unknown2;
|
2015-06-14 02:44:51 +01:00
|
|
|
};
|
|
|
|
struct StructPed {
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t unknown0_[52];
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t unknown1[640];
|
|
|
|
float health;
|
|
|
|
float armour;
|
|
|
|
uint8_t unknown2[148];
|
2018-01-27 21:24:15 +01:00
|
|
|
std::array<StructWeaponSlot, kNrOfWeapons> weapons;
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t unknown3[348];
|
2015-06-14 02:44:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block1PlayerPed {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword unknown0;
|
|
|
|
BlockWord unknown1;
|
|
|
|
BlockDword reference;
|
|
|
|
StructPed info;
|
|
|
|
BlockDword maxWantedLevel;
|
|
|
|
BlockDword maxChaosLevel;
|
|
|
|
uint8_t modelName[24];
|
|
|
|
uint8_t align[2];
|
2015-06-14 02:44:51 +01:00
|
|
|
};
|
|
|
|
|
2015-06-14 18:08:55 +01:00
|
|
|
struct StructStoredCar {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword modelId;
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
|
|
|
glm::vec3 rotation{};
|
2016-09-09 21:13:19 +01:00
|
|
|
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
|
|
|
|
};
|
2015-06-14 18:08:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct StructGarage {
|
2016-09-09 21:13:19 +01:00
|
|
|
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;
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec2 unknownCoord1{};
|
|
|
|
glm::vec2 unknownCoord2{};
|
2016-09-09 21:13:19 +01:00
|
|
|
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];
|
2015-06-14 18:08:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block2GarageData {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword garageCount;
|
|
|
|
BlockDword freeBombs;
|
|
|
|
BlockDword freeResprays;
|
|
|
|
BlockDword unknown0;
|
|
|
|
BlockDword unknown1;
|
|
|
|
BlockDword unknown2;
|
|
|
|
BlockDword bfImportExportPortland;
|
|
|
|
BlockDword bfImportExportShoreside;
|
|
|
|
BlockDword bfImportExportUnused;
|
|
|
|
BlockDword GA_21lastTime;
|
2018-01-27 21:24:15 +01:00
|
|
|
std::array<StructStoredCar, kNrOfStoredCars> cars;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block3VehicleState {
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t unknown1[52];
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t unknown2[1384];
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block3Vehicle {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword unknown1;
|
|
|
|
BlockWord modelId;
|
|
|
|
BlockDword unknown2;
|
|
|
|
Block3VehicleState state;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block3BoatState {
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t unknown1[52];
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t unknown2[1092];
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block3Boat {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword unknown1;
|
|
|
|
BlockWord modelId;
|
|
|
|
BlockDword unknown2;
|
|
|
|
Block3BoatState state;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block4Object {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockWord modelId;
|
|
|
|
BlockDword reference;
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-09-09 21:13:19 +01:00
|
|
|
int8_t rotation[9]; /// @todo Confirm that this is: right, forward, down
|
|
|
|
uint8_t unknown1[3];
|
|
|
|
float unknown2;
|
|
|
|
float unknown3[3];
|
|
|
|
uint8_t unknown4[12];
|
|
|
|
uint8_t unknown5[8];
|
|
|
|
float unknown6;
|
|
|
|
uint8_t unknown7[2];
|
|
|
|
BlockDword unknown8;
|
|
|
|
BlockDword unknown9;
|
|
|
|
BlockDword unknown10;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block6Crane {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword reference;
|
|
|
|
BlockDword hookReference;
|
|
|
|
BlockDword audioReference;
|
|
|
|
float x1Pickup;
|
|
|
|
float y1Pickup;
|
|
|
|
float x2Pickup;
|
|
|
|
float y2Pickup;
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 dropoff{};
|
2016-09-09 21:13:19 +01:00
|
|
|
float dropoffHeadingRads;
|
|
|
|
float pickupArmRads;
|
|
|
|
float dropoffArmRads;
|
|
|
|
float armPickupDistance;
|
|
|
|
float armDropoffDistance;
|
|
|
|
float armPickupHeight;
|
|
|
|
float armDropoffHeight;
|
|
|
|
float armCurrentRads;
|
|
|
|
float armCurrentDistance;
|
|
|
|
float armCurrentHeight;
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 hookInitialPosition{};
|
|
|
|
glm::vec3 hookCurrentPosition{};
|
2016-09-09 21:13:19 +01:00
|
|
|
float unknown1[2];
|
|
|
|
BlockDword vehiclePtr;
|
|
|
|
BlockDword gameTime;
|
|
|
|
uint8_t activity;
|
|
|
|
uint8_t status;
|
|
|
|
uint8_t vehiclesCollected;
|
|
|
|
uint8_t isCrusher;
|
|
|
|
uint8_t isMilitary;
|
|
|
|
uint8_t unknown2;
|
|
|
|
uint8_t isNotdoc_crane_cab;
|
|
|
|
uint8_t padding;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block6Data {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword numCranes;
|
|
|
|
BlockDword militaryCollected;
|
|
|
|
Block6Crane cranes[8];
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block7Pickup {
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t type;
|
|
|
|
uint8_t unknown1;
|
|
|
|
BlockWord ammo;
|
|
|
|
BlockDword objectRef;
|
|
|
|
BlockDword regenTime;
|
|
|
|
BlockWord modelId;
|
|
|
|
BlockWord flags;
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block7Data {
|
2018-01-27 21:24:15 +01:00
|
|
|
std::array<Block7Pickup, kNrOfPickups> pickups;
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockWord pickupIndex;
|
|
|
|
uint8_t align[2];
|
|
|
|
BlockWord collectedPickups[20];
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block8Data {
|
2018-06-18 07:24:34 +03:00
|
|
|
BlockDword numPayphones;
|
|
|
|
BlockDword numActivePayphones;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
2018-06-18 07:24:34 +03:00
|
|
|
struct Block8Payphone {
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword messagePtr[6];
|
|
|
|
BlockDword messageEndTime;
|
|
|
|
BlockDword staticIndex;
|
|
|
|
BlockDword state;
|
|
|
|
uint8_t playerInRange;
|
|
|
|
uint8_t align[3];
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block9Restart {
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-09-09 21:13:19 +01:00
|
|
|
float angle;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block9Data {
|
2016-09-09 21:13:19 +01:00
|
|
|
Block9Restart hospitalRestarts[8];
|
|
|
|
Block9Restart policeRestarts[8];
|
|
|
|
BlockWord numHospitals;
|
|
|
|
BlockWord numPolice;
|
|
|
|
uint8_t overrideFlag;
|
|
|
|
uint8_t align[3];
|
|
|
|
Block9Restart overrideRestart;
|
|
|
|
uint8_t deathFadeInFlag;
|
|
|
|
uint8_t arrestFadeInFlag;
|
|
|
|
uint8_t hospitalLevelOverride;
|
|
|
|
uint8_t policeLevelOverride;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block10Blip {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword color;
|
|
|
|
BlockDword type;
|
|
|
|
BlockDword entityHandle;
|
|
|
|
float unknown1;
|
|
|
|
float unknown2;
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockWord unknown3;
|
|
|
|
uint8_t brightness;
|
|
|
|
uint8_t unknown4;
|
|
|
|
float unknown5;
|
|
|
|
BlockWord scale;
|
|
|
|
BlockWord display;
|
|
|
|
BlockWord sprite;
|
|
|
|
BlockWord align;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block10Data {
|
2018-01-27 21:24:15 +01:00
|
|
|
std::array<Block10Blip, kNrOfBlips> blips;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block11Zone {
|
2016-09-09 21:13:19 +01:00
|
|
|
char name[8];
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 coordA{};
|
|
|
|
glm::vec3 coordB{};
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword type;
|
|
|
|
BlockDword level;
|
|
|
|
BlockWord dayZoneInfo;
|
|
|
|
BlockWord nightZoneInfo;
|
|
|
|
BlockDword childZone;
|
|
|
|
BlockDword parentZone;
|
|
|
|
BlockDword siblingZone;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
struct Block11ZoneInfo {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockWord density;
|
2017-01-18 23:15:09 +00:00
|
|
|
BlockWord unknown1[16];
|
|
|
|
BlockWord peddensity;
|
|
|
|
BlockWord copdensity;
|
|
|
|
BlockWord gangpeddensity[9];
|
|
|
|
BlockWord pedgroup;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
struct Block11AudioZone {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockWord zoneId;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block11Data {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword currentZone;
|
|
|
|
BlockDword currentLevel;
|
|
|
|
BlockWord findIndex;
|
|
|
|
BlockWord align;
|
2018-01-27 21:24:15 +01:00
|
|
|
std::array<Block11Zone, kNrOfNavZones> navZones;
|
|
|
|
std::array<Block11ZoneInfo, kNrOfDayNightInfo> dayNightInfo;
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockWord numNavZones;
|
|
|
|
BlockWord numZoneInfos;
|
2018-01-27 21:24:15 +01:00
|
|
|
std::array<Block11Zone, kNrOfMapZones> mapZones;
|
|
|
|
std::array<Block11AudioZone, kNrOfAudioZones> audioZones;
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockWord numMapZones;
|
|
|
|
BlockWord numAudioZones;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block12Gang {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword carModelId;
|
|
|
|
uint8_t pedModelOverrideFlag;
|
|
|
|
uint8_t align[3];
|
|
|
|
BlockDword weaponPrimary;
|
|
|
|
BlockDword weaponSecondary;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block12Data {
|
2018-01-27 21:24:15 +01:00
|
|
|
std::array<Block12Gang, kNrOfGangs> gangs;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block13CarGenerator {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword modelId;
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-09-09 21:13:19 +01:00
|
|
|
float angle;
|
|
|
|
BlockWord colourFG;
|
|
|
|
BlockWord colourBG;
|
|
|
|
uint8_t force;
|
|
|
|
uint8_t alarmChance;
|
|
|
|
uint8_t lockedChance;
|
|
|
|
uint8_t align;
|
|
|
|
BlockWord minDelay;
|
|
|
|
BlockWord maxDelay;
|
|
|
|
BlockDword timestamp;
|
|
|
|
BlockDword unknown1;
|
|
|
|
BlockDword unknown2;
|
|
|
|
float unknown3;
|
|
|
|
float unknown4;
|
|
|
|
float unknown5;
|
|
|
|
float unknown6;
|
|
|
|
float unknown7;
|
|
|
|
float unknown8;
|
|
|
|
BlockDword unknown9;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block13Data {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword blockSize;
|
|
|
|
BlockDword generatorCount;
|
|
|
|
BlockDword activeGenerators;
|
|
|
|
uint8_t counter;
|
|
|
|
uint8_t generateNearPlayerCounter;
|
|
|
|
uint8_t align[2];
|
|
|
|
BlockDword generatorSize;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block14Particle {
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t unknown[0x88];
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block15AudioObject {
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword index;
|
|
|
|
BlockWord soundIndex;
|
|
|
|
uint8_t align[2];
|
2018-02-06 20:47:31 +01:00
|
|
|
glm::vec3 position{};
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword unknown1;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block18Data {
|
2016-09-09 21:13:19 +01:00
|
|
|
uint8_t unknown1[200];
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Block19PedType {
|
2017-01-15 23:17:54 +00:00
|
|
|
BlockDword bitstring_;
|
2016-09-09 21:13:19 +01:00
|
|
|
float unknown2;
|
|
|
|
float unknown3;
|
|
|
|
float unknown4;
|
2017-01-18 23:55:55 +00:00
|
|
|
float fleedistance;
|
|
|
|
float headingchangerate;
|
2017-01-15 23:17:54 +00:00
|
|
|
BlockDword threatflags_;
|
|
|
|
BlockDword avoidflags_;
|
2016-04-10 04:38:31 +01:00
|
|
|
};
|
|
|
|
struct Block19Data {
|
2018-01-27 21:24:15 +01:00
|
|
|
std::array<Block19PedType, kNrOfPedTypes> types;
|
2015-06-14 18:08:55 +01:00
|
|
|
};
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
void SaveGame::writeGame(GameState& state, const std::string& file) {
|
|
|
|
RW_UNUSED(state);
|
|
|
|
RW_UNUSED(file);
|
|
|
|
RW_UNIMPLEMENTED("Saving the game is not implemented yet.");
|
2015-06-14 02:44:51 +01:00
|
|
|
}
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
template <class T>
|
|
|
|
bool readBlock(std::FILE* str, T& out) {
|
|
|
|
return std::fread(&out, sizeof(out), 1, str) == 1;
|
2016-04-09 22:29:32 +01:00
|
|
|
}
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
#define READ_VALUE(var) \
|
|
|
|
if (!readBlock(loadFile, var)) { \
|
2017-10-31 04:05:12 +01:00
|
|
|
RW_ERROR(file << ": Failed to load block " #var); \
|
2016-09-09 21:13:19 +01:00
|
|
|
return false; \
|
|
|
|
}
|
|
|
|
#define READ_SIZE(var) \
|
|
|
|
if (!readBlock(loadFile, var)) { \
|
2017-10-31 04:05:12 +01:00
|
|
|
RW_ERROR(file << ": Failed to load size " #var); \
|
2016-09-09 21:13:19 +01:00
|
|
|
return false; \
|
|
|
|
}
|
|
|
|
#define CHECK_SIG(expected) \
|
|
|
|
{ \
|
|
|
|
char signature[4]; \
|
|
|
|
if (fread(signature, sizeof(char), 4, loadFile) != 4) { \
|
2017-10-31 04:05:12 +01:00
|
|
|
RW_ERROR("Failed to read signature"); \
|
2016-09-09 21:13:19 +01:00
|
|
|
return false; \
|
|
|
|
} \
|
|
|
|
if (strncmp(signature, expected, 3) != 0) { \
|
2017-10-31 04:05:12 +01:00
|
|
|
RW_ERROR("Signature " expected " incorrect"); \
|
2016-09-09 21:13:19 +01:00
|
|
|
return false; \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
#define BLOCK_HEADER(sizevar) \
|
|
|
|
fseek(loadFile, nextBlock, SEEK_SET); \
|
|
|
|
READ_SIZE(sizevar) \
|
|
|
|
nextBlock += sizeof(sizevar) + sizevar;
|
|
|
|
|
|
|
|
bool SaveGame::loadGame(GameState& state, const std::string& file) {
|
|
|
|
std::FILE* loadFile = std::fopen(file.c_str(), "rb");
|
|
|
|
if (loadFile == nullptr) {
|
2017-10-31 04:05:12 +01:00
|
|
|
RW_ERROR("Failed to open save file");
|
2016-09-09 21:13:19 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
BlockSize nextBlock = 0;
|
|
|
|
|
|
|
|
// BLOCK 0
|
|
|
|
BlockDword blockSize;
|
|
|
|
BLOCK_HEADER(blockSize);
|
|
|
|
|
|
|
|
static_assert(sizeof(BasicState) == 0xBC,
|
|
|
|
"BasicState is not the right size");
|
|
|
|
READ_VALUE(state.basic)
|
|
|
|
|
|
|
|
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-09-09 21:13:19 +01:00
|
|
|
READ_SIZE(scriptVarCount)
|
2017-09-12 18:38:06 +02:00
|
|
|
RW_ASSERT(scriptVarCount == state.script->getFile()->getGlobalsSize());
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
if (fread(state.script->getGlobals(), sizeof(SCMByte), scriptVarCount,
|
|
|
|
loadFile) != scriptVarCount) {
|
2017-10-31 04:05:12 +01:00
|
|
|
RW_ERROR("Failed to read script memory");
|
2016-09-09 21:13:19 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
BlockDword scriptDataBlockSize;
|
|
|
|
READ_SIZE(scriptDataBlockSize);
|
|
|
|
if (scriptDataBlockSize != 0x03C8) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Block0ScriptData scriptData;
|
|
|
|
READ_VALUE(scriptData);
|
|
|
|
|
|
|
|
BlockDword numScripts;
|
|
|
|
READ_SIZE(numScripts)
|
|
|
|
std::vector<Block0RunningScript> scripts(numScripts);
|
|
|
|
for (size_t i = 0; i < numScripts; ++i) {
|
|
|
|
READ_VALUE(scripts[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// BLOCK 1
|
|
|
|
BlockDword playerBlockSize;
|
|
|
|
BLOCK_HEADER(playerBlockSize);
|
|
|
|
BlockDword playerInfoSize;
|
|
|
|
READ_SIZE(playerInfoSize)
|
|
|
|
BlockDword playerCount;
|
|
|
|
READ_SIZE(playerCount)
|
|
|
|
|
|
|
|
std::vector<Block1PlayerPed> players(playerCount);
|
|
|
|
for (unsigned int p = 0; p < playerCount; ++p) {
|
|
|
|
Block1PlayerPed& ped = players[p];
|
|
|
|
READ_VALUE(ped.unknown0)
|
|
|
|
READ_VALUE(ped.unknown1)
|
|
|
|
READ_VALUE(ped.reference)
|
|
|
|
READ_VALUE(ped.info)
|
|
|
|
READ_VALUE(ped.maxWantedLevel)
|
|
|
|
READ_VALUE(ped.maxChaosLevel)
|
|
|
|
READ_VALUE(ped.modelName)
|
|
|
|
READ_VALUE(ped.align)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "Player Health: " << ped.info.health << " ("
|
|
|
|
<< ped.info.armour << ")" << std::endl;
|
|
|
|
std::cout << "Player model: " << ped.modelName << std::endl;
|
2018-01-27 21:24:15 +01:00
|
|
|
for (const auto &wep : players[p].info.weapons) {
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "ID " << wep.weaponId << " " << wep.inClip << " "
|
|
|
|
<< wep.totalBullets << std::endl;
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// BLOCK 2
|
|
|
|
BlockDword garageBlockSize;
|
|
|
|
BLOCK_HEADER(garageBlockSize);
|
|
|
|
BlockDword garageDataSize;
|
|
|
|
READ_SIZE(garageDataSize)
|
|
|
|
|
|
|
|
Block2GarageData garageData;
|
|
|
|
READ_VALUE(garageData.garageCount)
|
|
|
|
READ_VALUE(garageData.freeBombs)
|
|
|
|
READ_VALUE(garageData.freeResprays)
|
|
|
|
READ_VALUE(garageData.unknown0)
|
|
|
|
READ_VALUE(garageData.unknown1)
|
|
|
|
READ_VALUE(garageData.unknown2)
|
|
|
|
READ_VALUE(garageData.bfImportExportPortland)
|
|
|
|
READ_VALUE(garageData.bfImportExportShoreside)
|
|
|
|
READ_VALUE(garageData.bfImportExportUnused)
|
|
|
|
READ_VALUE(garageData.GA_21lastTime)
|
|
|
|
READ_VALUE(garageData.cars)
|
|
|
|
|
|
|
|
std::vector<StructGarage> garages(garageData.garageCount);
|
|
|
|
for (size_t i = 0; i < garageData.garageCount; ++i) {
|
|
|
|
READ_VALUE(garages[i]);
|
|
|
|
}
|
2015-06-14 02:44:51 +01:00
|
|
|
|
2016-04-10 04:38:31 +01:00
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "Garages: " << garageData.garageCount << std::endl;
|
|
|
|
std::cout << "Bombs Free: " << garageData.freeBombs << std::endl;
|
|
|
|
std::cout << "Sprays Free: " << garageData.freeResprays << std::endl;
|
|
|
|
std::cout << "Portland IE: " << garageData.bfImportExportPortland
|
|
|
|
<< std::endl;
|
|
|
|
std::cout << "Shoreside IE: " << garageData.bfImportExportShoreside
|
|
|
|
<< std::endl;
|
|
|
|
std::cout << "GA21 last shown: " << garageData.GA_21lastTime << std::endl;
|
2018-01-27 21:24:15 +01:00
|
|
|
for (const auto &car : garageData.cars) {
|
2016-09-09 21:13:19 +01:00
|
|
|
if (car.modelId == 0) continue;
|
|
|
|
std::cout << " " << car.modelId << " " << uint16_t(car.colorFG) << "/"
|
|
|
|
<< uint16_t(car.colorBG) << " " << car.immunities
|
|
|
|
<< std::endl;
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 3
|
|
|
|
BlockSize vehicleBlockSize;
|
|
|
|
BLOCK_HEADER(vehicleBlockSize);
|
|
|
|
BlockSize vehicleDataSize;
|
|
|
|
READ_SIZE(vehicleDataSize)
|
|
|
|
|
|
|
|
BlockDword vehicleCount;
|
|
|
|
BlockDword boatCount;
|
|
|
|
READ_VALUE(vehicleCount)
|
|
|
|
READ_VALUE(boatCount)
|
|
|
|
|
|
|
|
std::vector<Block3Vehicle> vehicles(vehicleCount);
|
|
|
|
for (size_t v = 0; v < vehicleCount; ++v) {
|
|
|
|
Block3Vehicle& veh = vehicles[v];
|
|
|
|
READ_VALUE(veh.unknown1)
|
|
|
|
READ_VALUE(veh.modelId)
|
|
|
|
READ_VALUE(veh.unknown2)
|
|
|
|
READ_VALUE(veh.state)
|
2016-04-10 04:38:31 +01:00
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << " v " << veh.modelId << " " << veh.state.position.x << " "
|
|
|
|
<< veh.state.position.y << " " << veh.state.position.z
|
|
|
|
<< std::endl;
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
|
|
|
std::vector<Block3Boat> boats(boatCount);
|
|
|
|
for (size_t v = 0; v < boatCount; ++v) {
|
|
|
|
Block3Boat& veh = boats[v];
|
|
|
|
READ_VALUE(veh.unknown1)
|
|
|
|
READ_VALUE(veh.modelId)
|
|
|
|
READ_VALUE(veh.unknown2)
|
|
|
|
READ_VALUE(veh.state)
|
2016-04-10 04:38:31 +01:00
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << " b " << veh.modelId << " " << veh.state.position.x << " "
|
|
|
|
<< veh.state.position.y << " " << veh.state.position.z
|
|
|
|
<< std::endl;
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Block 4
|
|
|
|
BlockSize objectsBlockSize;
|
|
|
|
BLOCK_HEADER(objectsBlockSize)
|
|
|
|
BlockDword objectDataSize;
|
|
|
|
READ_VALUE(objectDataSize)
|
|
|
|
|
|
|
|
BlockDword objectCount;
|
|
|
|
READ_VALUE(objectCount);
|
|
|
|
|
|
|
|
std::vector<Block4Object> objects(objectCount);
|
|
|
|
for (size_t o = 0; o < objectCount; ++o) {
|
|
|
|
Block4Object& obj = objects[o];
|
|
|
|
READ_VALUE(obj.modelId)
|
|
|
|
READ_VALUE(obj.reference)
|
|
|
|
READ_VALUE(obj.position)
|
|
|
|
READ_VALUE(obj.rotation)
|
|
|
|
READ_VALUE(obj.unknown1)
|
|
|
|
READ_VALUE(obj.unknown2)
|
|
|
|
READ_VALUE(obj.unknown3)
|
|
|
|
READ_VALUE(obj.unknown4)
|
|
|
|
READ_VALUE(obj.unknown5)
|
|
|
|
READ_VALUE(obj.unknown6)
|
|
|
|
READ_VALUE(obj.unknown7)
|
|
|
|
READ_VALUE(obj.unknown8)
|
|
|
|
READ_VALUE(obj.unknown9)
|
|
|
|
READ_VALUE(obj.unknown10)
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t o = 0; o < objectCount; ++o) {
|
|
|
|
auto& obj = objects[o];
|
|
|
|
GameObject* inst =
|
|
|
|
state.world->createInstance(obj.modelId, obj.position);
|
|
|
|
glm::vec3 right = glm::normalize(
|
|
|
|
glm::vec3(obj.rotation[0], obj.rotation[1], obj.rotation[2]));
|
|
|
|
glm::vec3 forward = glm::normalize(
|
|
|
|
glm::vec3(obj.rotation[3], obj.rotation[4], obj.rotation[5]));
|
|
|
|
glm::vec3 down = glm::normalize(
|
|
|
|
glm::vec3(obj.rotation[6], obj.rotation[7], obj.rotation[8]));
|
|
|
|
glm::mat3 m = glm::mat3(right, forward, -down);
|
|
|
|
inst->setRotation(glm::normalize(static_cast<glm::quat>(m)));
|
|
|
|
}
|
2016-05-27 12:47:42 +02:00
|
|
|
|
2016-04-10 04:38:31 +01:00
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "Objects " << objectCount << std::endl;
|
|
|
|
for (size_t o = 0; o < objectCount; ++o) {
|
|
|
|
auto& obj = objects[o];
|
|
|
|
glm::vec3 right = glm::normalize(
|
|
|
|
glm::vec3(obj.rotation[0], obj.rotation[1], obj.rotation[2]));
|
|
|
|
glm::vec3 forward = glm::normalize(
|
|
|
|
glm::vec3(obj.rotation[3], obj.rotation[4], obj.rotation[5]));
|
|
|
|
glm::vec3 down = glm::normalize(
|
|
|
|
glm::vec3(obj.rotation[6], obj.rotation[7], obj.rotation[8]));
|
|
|
|
std::cout << "modelId " << obj.modelId << " ";
|
|
|
|
std::cout << "position " << obj.position.x << " " << obj.position.y
|
|
|
|
<< " " << obj.position.z << " ";
|
|
|
|
std::cout << "right " << right.x << " " << right.y << " " << right.z
|
|
|
|
<< " ";
|
|
|
|
std::cout << "forward " << forward.x << " " << forward.y << " "
|
|
|
|
<< forward.z << " ";
|
|
|
|
std::cout << "down " << down.x << " " << down.y << " " << down.z
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
std::cout << std::endl;
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 5
|
|
|
|
BlockSize pathBlockSize;
|
|
|
|
BLOCK_HEADER(pathBlockSize)
|
|
|
|
BlockDword pathDataSize;
|
|
|
|
READ_VALUE(pathDataSize)
|
|
|
|
|
|
|
|
BlockDword numPaths;
|
|
|
|
READ_VALUE(numPaths)
|
|
|
|
for (size_t b = 0; b < numPaths; ++b) {
|
|
|
|
uint8_t bits;
|
|
|
|
READ_VALUE(bits)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Block 6
|
|
|
|
BlockSize craneBlockSize;
|
|
|
|
BLOCK_HEADER(craneBlockSize)
|
|
|
|
BlockDword craneDataSize;
|
|
|
|
READ_VALUE(craneDataSize)
|
|
|
|
|
|
|
|
Block6Data craneData;
|
|
|
|
READ_VALUE(craneData.numCranes)
|
|
|
|
READ_VALUE(craneData.militaryCollected)
|
|
|
|
for (size_t c = 0; c < craneData.numCranes; ++c) {
|
|
|
|
Block6Crane& crane = craneData.cranes[c];
|
|
|
|
READ_VALUE(crane)
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "Cranes: " << craneData.numCranes << std::endl;
|
|
|
|
for (size_t c = 0; c < craneData.numCranes; ++c) {
|
|
|
|
Block6Crane& crane = craneData.cranes[c];
|
|
|
|
std::cout << "pickup " << crane.x1Pickup << " " << crane.y1Pickup << " "
|
|
|
|
<< crane.x2Pickup << " " << crane.y2Pickup << std::endl;
|
|
|
|
std::cout << "vehicles collected " << uint16_t(crane.vehiclesCollected)
|
|
|
|
<< std::endl;
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 7
|
|
|
|
BlockSize pickupBlockSize;
|
|
|
|
BLOCK_HEADER(pickupBlockSize)
|
|
|
|
BlockDword pickupDataSize;
|
|
|
|
READ_VALUE(pickupDataSize)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
Block7Data pickupData;
|
|
|
|
READ_VALUE(pickupData);
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2018-01-27 21:24:15 +01:00
|
|
|
for (const auto &pickup : pickupData.pickups) {
|
|
|
|
if (pickup.type == 0) continue;
|
|
|
|
std::cout << " " << uint16_t(pickup.type) << " " << pickup.position.x << " "
|
|
|
|
<< pickup.position.y << " " << pickup.position.z << std::endl;
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 8
|
2018-06-18 07:24:34 +03:00
|
|
|
BlockSize payphoneBlockSize;
|
|
|
|
BLOCK_HEADER(payphoneBlockSize)
|
|
|
|
BlockDword payphoneDataSize;
|
|
|
|
READ_VALUE(payphoneDataSize)
|
|
|
|
|
|
|
|
Block8Data payphoneData;
|
|
|
|
READ_VALUE(payphoneData);
|
|
|
|
std::vector<Block8Payphone> payphones(payphoneData.numPayphones);
|
|
|
|
for (auto& payphone : payphones) {
|
|
|
|
READ_VALUE(payphone)
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2018-06-18 07:24:34 +03:00
|
|
|
std::cout << "Payphones: " << payphoneData.numPayphones << std::endl;
|
|
|
|
for (const auto& payphone : payphones) {
|
|
|
|
std::cout << " " << uint16_t(payphone.state) << " " << payphone.position.x
|
|
|
|
<< " " << payphone.position.y << " " << payphone.position.z
|
2016-09-09 21:13:19 +01:00
|
|
|
<< std::endl;
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 9
|
|
|
|
BlockSize restartBlockSize;
|
|
|
|
BLOCK_HEADER(restartBlockSize)
|
|
|
|
BlockDword restartDataSize;
|
|
|
|
READ_VALUE(restartDataSize)
|
|
|
|
CHECK_SIG("RST")
|
|
|
|
READ_VALUE(restartDataSize)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
Block9Data restartData;
|
|
|
|
READ_VALUE(restartData);
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "Hospitals: " << restartData.numHospitals
|
|
|
|
<< " police: " << restartData.numPolice << std::endl;
|
|
|
|
for (int s = 0; s < restartData.numHospitals; ++s) {
|
|
|
|
Block9Restart& p = restartData.hospitalRestarts[s];
|
|
|
|
std::cout << " H " << p.position.x << " " << p.position.y << " "
|
|
|
|
<< p.position.z << std::endl;
|
|
|
|
}
|
|
|
|
for (int s = 0; s < restartData.numPolice; ++s) {
|
|
|
|
Block9Restart& p = restartData.policeRestarts[s];
|
|
|
|
std::cout << " P " << p.position.x << " " << p.position.y << " "
|
|
|
|
<< p.position.z << std::endl;
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 10
|
|
|
|
BlockSize radarBlockSize;
|
|
|
|
BLOCK_HEADER(radarBlockSize)
|
|
|
|
BlockDword radarDataSize;
|
|
|
|
READ_VALUE(radarDataSize)
|
|
|
|
CHECK_SIG("RDR")
|
|
|
|
READ_VALUE(radarDataSize)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
Block10Data radarData;
|
|
|
|
READ_VALUE(radarData);
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2018-01-27 21:24:15 +01:00
|
|
|
for (const auto &blip : radarData.blips) {
|
|
|
|
if (blip.type == 0) continue;
|
|
|
|
std::cout << " " << blip.position.x << " " << blip.position.y << " "
|
|
|
|
<< blip.position.z << std::endl;
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 11
|
|
|
|
BlockSize zoneBlockSize;
|
|
|
|
BLOCK_HEADER(zoneBlockSize)
|
|
|
|
BlockDword zoneDataSize;
|
|
|
|
READ_VALUE(zoneDataSize)
|
|
|
|
CHECK_SIG("ZNS")
|
|
|
|
READ_VALUE(zoneDataSize)
|
|
|
|
|
|
|
|
Block11Data zoneData;
|
|
|
|
READ_VALUE(zoneData.currentZone);
|
|
|
|
READ_VALUE(zoneData.currentLevel);
|
|
|
|
READ_VALUE(zoneData.findIndex);
|
|
|
|
READ_VALUE(zoneData.align);
|
2018-01-27 21:24:15 +01:00
|
|
|
for (auto &zone : zoneData.navZones) {
|
2016-09-09 21:13:19 +01:00
|
|
|
READ_VALUE(zone.name);
|
|
|
|
READ_VALUE(zone.coordA);
|
|
|
|
READ_VALUE(zone.coordB);
|
|
|
|
READ_VALUE(zone.type);
|
|
|
|
READ_VALUE(zone.level);
|
|
|
|
READ_VALUE(zone.dayZoneInfo);
|
|
|
|
READ_VALUE(zone.nightZoneInfo);
|
|
|
|
READ_VALUE(zone.childZone);
|
|
|
|
READ_VALUE(zone.parentZone);
|
|
|
|
READ_VALUE(zone.siblingZone);
|
|
|
|
}
|
2018-01-27 21:24:15 +01:00
|
|
|
for (auto &info : zoneData.dayNightInfo) {
|
2016-09-09 21:13:19 +01:00
|
|
|
READ_VALUE(info.density)
|
|
|
|
READ_VALUE(info.unknown1)
|
2017-01-18 23:15:09 +00:00
|
|
|
READ_VALUE(info.peddensity)
|
|
|
|
READ_VALUE(info.copdensity)
|
|
|
|
READ_VALUE(info.gangpeddensity)
|
|
|
|
READ_VALUE(info.pedgroup);
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
|
|
|
READ_VALUE(zoneData.numNavZones);
|
|
|
|
READ_VALUE(zoneData.numZoneInfos);
|
2018-01-27 21:24:15 +01:00
|
|
|
for (auto &zone : zoneData.mapZones) {
|
2016-09-09 21:13:19 +01:00
|
|
|
READ_VALUE(zone.name);
|
|
|
|
READ_VALUE(zone.coordA);
|
|
|
|
READ_VALUE(zone.coordB);
|
|
|
|
READ_VALUE(zone.type);
|
|
|
|
READ_VALUE(zone.level);
|
|
|
|
READ_VALUE(zone.dayZoneInfo);
|
|
|
|
READ_VALUE(zone.nightZoneInfo);
|
|
|
|
READ_VALUE(zone.childZone);
|
|
|
|
READ_VALUE(zone.parentZone);
|
|
|
|
READ_VALUE(zone.siblingZone);
|
|
|
|
}
|
2018-01-27 21:24:15 +01:00
|
|
|
for (auto &audioZone : zoneData.audioZones) {
|
|
|
|
READ_VALUE(audioZone);
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
|
|
|
READ_VALUE(zoneData.numMapZones);
|
|
|
|
READ_VALUE(zoneData.numAudioZones);
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "zones: " << zoneData.numNavZones << " "
|
|
|
|
<< zoneData.numZoneInfos << " " << zoneData.numMapZones << " "
|
|
|
|
<< zoneData.numAudioZones << std::endl;
|
|
|
|
for (int z = 0; z < zoneData.numNavZones; ++z) {
|
|
|
|
Block11Zone& zone = zoneData.navZones[z];
|
|
|
|
std::cout << " " << zone.name << std::endl;
|
2017-01-18 23:15:09 +00:00
|
|
|
auto& dayinfo = zoneData.dayNightInfo[zone.dayZoneInfo];
|
|
|
|
std::cout << " DAY " << dayinfo.density << " " << dayinfo.peddensity
|
|
|
|
<< " " << dayinfo.copdensity << " "
|
|
|
|
<< " [";
|
|
|
|
for (BlockDword gang : dayinfo.gangpeddensity) {
|
|
|
|
std::cout << " " << gang;
|
|
|
|
}
|
|
|
|
std::cout << "] " << dayinfo.pedgroup << std::endl;
|
|
|
|
for (BlockDword dw : dayinfo.unknown1) {
|
|
|
|
std::cout << " " << dw;
|
|
|
|
}
|
|
|
|
std::cout << std::endl;
|
|
|
|
|
|
|
|
auto& nightinfo = zoneData.dayNightInfo[zone.nightZoneInfo];
|
|
|
|
std::cout << " NIGHT " << nightinfo.density << " "
|
|
|
|
<< nightinfo.peddensity << " " << nightinfo.copdensity << " "
|
|
|
|
<< " [";
|
|
|
|
for (BlockDword gang : nightinfo.gangpeddensity) {
|
|
|
|
std::cout << " " << gang;
|
|
|
|
}
|
|
|
|
std::cout << "] " << nightinfo.pedgroup << std::endl;
|
|
|
|
for (BlockDword dw : nightinfo.unknown1) {
|
|
|
|
std::cout << " " << dw;
|
|
|
|
}
|
|
|
|
std::cout << std::endl;
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
|
|
|
for (int z = 0; z < zoneData.numMapZones; ++z) {
|
|
|
|
Block11Zone& zone = zoneData.mapZones[z];
|
|
|
|
std::cout << " " << zone.name << std::endl;
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2017-01-18 23:15:09 +00:00
|
|
|
// Clear existing zone data
|
|
|
|
auto& gamezones = state.world->data->gamezones;
|
|
|
|
gamezones.clear();
|
|
|
|
for (int z = 0; z < zoneData.numNavZones; ++z) {
|
|
|
|
Block11Zone& zone = zoneData.navZones[z];
|
|
|
|
Block11ZoneInfo& day = zoneData.dayNightInfo[zone.dayZoneInfo];
|
|
|
|
Block11ZoneInfo& night = zoneData.dayNightInfo[zone.nightZoneInfo];
|
|
|
|
// @toodo restore gang density
|
2018-01-10 02:02:06 +01:00
|
|
|
gamezones.emplace_back(zone.name, zone.type, zone.coordA, zone.coordB,
|
|
|
|
zone.level, day.pedgroup, night.pedgroup);
|
2017-01-18 23:15:09 +00:00
|
|
|
}
|
|
|
|
// Re-build zone hierarchy
|
|
|
|
for (ZoneData& zone : gamezones) {
|
|
|
|
if (&zone == &gamezones[0]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
gamezones[0].insertZone(zone);
|
|
|
|
}
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 12
|
|
|
|
BlockSize gangBlockSize;
|
|
|
|
BLOCK_HEADER(gangBlockSize)
|
|
|
|
BlockDword gangDataSize;
|
|
|
|
READ_VALUE(gangDataSize)
|
|
|
|
CHECK_SIG("GNG")
|
|
|
|
READ_VALUE(gangDataSize)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
Block12Data gangData;
|
|
|
|
READ_VALUE(gangData);
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2018-01-27 21:24:15 +01:00
|
|
|
for (const auto &gang : gangData.gangs) {
|
|
|
|
std::cout << " " << gang.carModelId << " " << gang.weaponPrimary << " "
|
|
|
|
<< gang.weaponSecondary << std::endl;
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 13
|
|
|
|
BlockSize carGeneratorBlockSize;
|
|
|
|
BLOCK_HEADER(carGeneratorBlockSize)
|
|
|
|
BlockDword carGeneratorDataSize;
|
|
|
|
READ_VALUE(carGeneratorDataSize)
|
|
|
|
CHECK_SIG("CGN")
|
|
|
|
READ_VALUE(carGeneratorDataSize)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
Block13Data carGeneratorData;
|
|
|
|
READ_VALUE(carGeneratorData);
|
2016-04-10 04:38:31 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
std::vector<Block13CarGenerator> carGenerators(
|
|
|
|
carGeneratorData.generatorCount);
|
|
|
|
for (size_t g = 0; g < carGeneratorData.generatorCount; ++g) {
|
|
|
|
READ_VALUE(carGenerators[g])
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "Car generators: " << carGeneratorData.generatorCount
|
|
|
|
<< std::endl;
|
|
|
|
for (size_t g = 0; g < carGeneratorData.generatorCount; ++g) {
|
|
|
|
Block13CarGenerator& gen = carGenerators[g];
|
|
|
|
std::cout << " " << gen.modelId << " " << gen.position.x << " "
|
|
|
|
<< gen.position.y << " " << gen.position.z << std::endl;
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 14
|
|
|
|
BlockSize particleBlockSize;
|
|
|
|
BLOCK_HEADER(particleBlockSize)
|
|
|
|
BlockDword particleDataSize;
|
|
|
|
READ_VALUE(particleDataSize)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword particleCount;
|
|
|
|
READ_VALUE(particleCount);
|
|
|
|
std::vector<Block14Particle> particles(particleCount);
|
|
|
|
for (size_t p = 0; p < particleCount; ++p) {
|
|
|
|
READ_VALUE(particles[p])
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "particles: " << particleCount << std::endl;
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 15
|
|
|
|
BlockSize audioBlockSize;
|
|
|
|
BLOCK_HEADER(audioBlockSize)
|
|
|
|
BlockDword audioDataSize;
|
|
|
|
READ_VALUE(audioDataSize)
|
|
|
|
CHECK_SIG("AUD")
|
|
|
|
READ_VALUE(audioDataSize)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
BlockDword audioCount;
|
|
|
|
READ_VALUE(audioCount)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
std::vector<Block15AudioObject> audioObjects(audioCount);
|
|
|
|
for (size_t a = 0; a < audioCount; ++a) {
|
|
|
|
READ_VALUE(audioObjects[a])
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "Audio Objects: " << audioCount << std::endl;
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 16
|
|
|
|
BlockSize playerInfoBlockSize;
|
|
|
|
BLOCK_HEADER(playerInfoBlockSize)
|
|
|
|
BlockDword playerInfoDataSize;
|
|
|
|
READ_VALUE(playerInfoDataSize)
|
|
|
|
READ_VALUE(state.playerInfo.money)
|
|
|
|
READ_VALUE(state.playerInfo.unknown1)
|
|
|
|
READ_VALUE(state.playerInfo.unknown2)
|
|
|
|
READ_VALUE(state.playerInfo.unknown3)
|
|
|
|
READ_VALUE(state.playerInfo.unknown4)
|
|
|
|
READ_VALUE(state.playerInfo.displayedMoney)
|
|
|
|
READ_VALUE(state.playerInfo.hiddenPackagesCollected)
|
|
|
|
READ_VALUE(state.playerInfo.hiddenPackageCount)
|
|
|
|
READ_VALUE(state.playerInfo.neverTired)
|
|
|
|
READ_VALUE(state.playerInfo.fastReload)
|
|
|
|
READ_VALUE(state.playerInfo.thaneOfLibertyCity)
|
|
|
|
READ_VALUE(state.playerInfo.singlePayerHealthcare)
|
|
|
|
READ_VALUE(state.playerInfo.unknown5)
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "Player money: " << state.playerInfo.money << " ("
|
|
|
|
<< state.playerInfo.displayedMoney << ")" << std::endl;
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 17
|
|
|
|
BlockSize statsBlockSize;
|
|
|
|
BLOCK_HEADER(statsBlockSize)
|
|
|
|
BlockDword statsDataSize;
|
|
|
|
READ_VALUE(statsDataSize)
|
|
|
|
|
|
|
|
READ_VALUE(state.gameStats.playerKills);
|
|
|
|
READ_VALUE(state.gameStats.otherKills);
|
|
|
|
READ_VALUE(state.gameStats.carsExploded);
|
|
|
|
READ_VALUE(state.gameStats.shotsHit);
|
|
|
|
READ_VALUE(state.gameStats.pedTypesKilled);
|
|
|
|
READ_VALUE(state.gameStats.helicoptersDestroyed);
|
|
|
|
READ_VALUE(state.gameStats.playerProgress);
|
|
|
|
READ_VALUE(state.gameStats.explosiveKgsUsed);
|
|
|
|
READ_VALUE(state.gameStats.bulletsFired);
|
|
|
|
READ_VALUE(state.gameStats.bulletsHit);
|
|
|
|
READ_VALUE(state.gameStats.carsCrushed);
|
|
|
|
READ_VALUE(state.gameStats.headshots);
|
|
|
|
READ_VALUE(state.gameStats.timesBusted);
|
|
|
|
READ_VALUE(state.gameStats.timesHospital);
|
|
|
|
READ_VALUE(state.gameStats.daysPassed);
|
|
|
|
READ_VALUE(state.gameStats.mmRainfall);
|
|
|
|
READ_VALUE(state.gameStats.insaneJumpMaxDistance);
|
|
|
|
READ_VALUE(state.gameStats.insaneJumpMaxHeight);
|
|
|
|
READ_VALUE(state.gameStats.insaneJumpMaxFlips);
|
2018-05-23 16:56:07 +03:00
|
|
|
READ_VALUE(state.gameStats.insaneJumpMaxRotation);
|
2016-09-09 21:13:19 +01:00
|
|
|
READ_VALUE(state.gameStats.bestStunt);
|
|
|
|
READ_VALUE(state.gameStats.uniqueStuntsFound);
|
|
|
|
READ_VALUE(state.gameStats.uniqueStuntsTotal);
|
|
|
|
READ_VALUE(state.gameStats.missionAttempts);
|
|
|
|
READ_VALUE(state.gameStats.missionsPassed);
|
|
|
|
READ_VALUE(state.gameStats.passengersDroppedOff);
|
|
|
|
READ_VALUE(state.gameStats.taxiRevenue);
|
|
|
|
READ_VALUE(state.gameStats.portlandPassed);
|
|
|
|
READ_VALUE(state.gameStats.stauntonPassed);
|
|
|
|
READ_VALUE(state.gameStats.shoresidePassed);
|
|
|
|
READ_VALUE(state.gameStats.bestTurismoTime);
|
|
|
|
READ_VALUE(state.gameStats.distanceWalked);
|
|
|
|
READ_VALUE(state.gameStats.distanceDriven);
|
|
|
|
READ_VALUE(state.gameStats.patriotPlaygroundTime);
|
|
|
|
READ_VALUE(state.gameStats.aRideInTheParkTime);
|
|
|
|
READ_VALUE(state.gameStats.grippedTime);
|
|
|
|
READ_VALUE(state.gameStats.multistoryMayhemTime);
|
|
|
|
READ_VALUE(state.gameStats.peopleSaved);
|
|
|
|
READ_VALUE(state.gameStats.criminalsKilled);
|
|
|
|
READ_VALUE(state.gameStats.highestParamedicLevel);
|
|
|
|
READ_VALUE(state.gameStats.firesExtinguished);
|
|
|
|
READ_VALUE(state.gameStats.longestDodoFlight);
|
|
|
|
READ_VALUE(state.gameStats.bombDefusalTime);
|
|
|
|
READ_VALUE(state.gameStats.rampagesPassed);
|
|
|
|
READ_VALUE(state.gameStats.totalRampages);
|
|
|
|
READ_VALUE(state.gameStats.totalMissions);
|
|
|
|
READ_VALUE(state.gameStats.fastestTime);
|
|
|
|
READ_VALUE(state.gameStats.highestScore);
|
|
|
|
READ_VALUE(state.gameStats.peopleKilledSinceCheckpoint);
|
|
|
|
READ_VALUE(state.gameStats.peopleKilledSinceLastBustedOrWasted);
|
|
|
|
READ_VALUE(state.gameStats.lastMissionGXT);
|
2016-04-10 04:38:31 +01:00
|
|
|
|
|
|
|
#if RW_DEBUG
|
2016-09-09 21:13:19 +01:00
|
|
|
std::cout << "Player kills: " << state.gameStats.playerKills << std::endl;
|
|
|
|
std::cout << "longest flight " << state.gameStats.longestDodoFlight
|
|
|
|
<< std::endl;
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 18
|
|
|
|
BlockSize streamingBlockSize;
|
|
|
|
BLOCK_HEADER(streamingBlockSize);
|
|
|
|
BlockDword streamingDataSize;
|
|
|
|
READ_VALUE(streamingDataSize);
|
|
|
|
|
|
|
|
Block18Data streamingData;
|
|
|
|
READ_VALUE(streamingData);
|
|
|
|
|
|
|
|
#if RW_DEBUG && 0 // No idea what the data in the section means yet
|
|
|
|
static const size_t streamSize = sqrt(200 * 8);
|
|
|
|
for (int x = 0; x < streamSize; ++x) {
|
|
|
|
for (int y = 0; y < streamSize; ++y) {
|
|
|
|
size_t coord = (y * streamSize) + x;
|
|
|
|
if (streamingData.unknown1[(coord / 8)] & (0x1 << (coord % 8))) {
|
|
|
|
std::cout << "\u2588";
|
|
|
|
} else {
|
|
|
|
std::cout << " ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::cout << std::endl;
|
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Block 19
|
|
|
|
BlockSize pedTypeBlockSize;
|
|
|
|
BLOCK_HEADER(pedTypeBlockSize);
|
|
|
|
BlockDword pedTypeDataSize;
|
|
|
|
READ_VALUE(pedTypeDataSize);
|
|
|
|
CHECK_SIG("PTP");
|
|
|
|
READ_VALUE(pedTypeDataSize);
|
|
|
|
|
|
|
|
Block19Data pedTypeData;
|
|
|
|
READ_VALUE(pedTypeData);
|
|
|
|
|
2017-01-15 23:17:54 +00:00
|
|
|
#if RW_DEBUG
|
2018-01-27 21:24:15 +01:00
|
|
|
for (const auto &type : pedTypeData.types) {
|
|
|
|
printf("%08x: %f %f %f %f %f threat %08x avoid %08x\n", type.bitstring_,
|
2018-02-07 17:04:49 +01:00
|
|
|
static_cast<double>(type.unknown2),
|
|
|
|
static_cast<double>(type.unknown3),
|
|
|
|
static_cast<double>(type.unknown4),
|
|
|
|
static_cast<double>(type.fleedistance),
|
|
|
|
static_cast<double>(type.headingchangerate),
|
2018-01-27 21:24:15 +01:00
|
|
|
type.threatflags_,
|
|
|
|
type.avoidflags_);
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
2016-04-10 04:38:31 +01:00
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// Data Cleanup
|
|
|
|
|
|
|
|
// We keep track of the game time as a float for now
|
|
|
|
state.gameTime = state.basic.timeMS / 1000.f;
|
|
|
|
|
|
|
|
state.scriptOnMissionFlag = (int32_t*)(state.script->getGlobals() +
|
|
|
|
(size_t)scriptData.onMissionOffset);
|
|
|
|
|
|
|
|
auto& threads = state.script->getThreads();
|
|
|
|
for (size_t s = 0; s < numScripts; ++s) {
|
|
|
|
state.script->startThread(scripts[s].programCounter);
|
|
|
|
SCMThread& thread = threads.back();
|
|
|
|
// thread.baseAddress // ??
|
|
|
|
strncpy(thread.name, scripts[s].name, sizeof(SCMThread::name) - 1);
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
/* TODO not hardcode +33 ms */
|
|
|
|
thread.wakeCounter = scripts[s].wakeTimer - state.basic.lastTick + 33;
|
|
|
|
for (size_t i = 0; i < sizeof(Block0RunningScript::variables); ++i) {
|
|
|
|
thread.locals[i] = scripts[s].variables[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (playerCount > 0) {
|
|
|
|
auto& ply = players[0];
|
|
|
|
std::cout << ply.reference << std::endl;
|
|
|
|
auto player = state.world->createPlayer(players[0].info.position);
|
|
|
|
CharacterState& cs = player->getCurrentState();
|
|
|
|
cs.health = players[0].info.health;
|
|
|
|
cs.armour = players[0].info.armour;
|
|
|
|
state.playerObject = player->getGameObjectID();
|
|
|
|
state.maxWantedLevel = players[0].maxWantedLevel;
|
2018-01-27 21:24:15 +01:00
|
|
|
for (int w = 0; w < kNrOfWeapons; ++w) {
|
2016-09-09 21:13:19 +01:00
|
|
|
auto& wep = ply.info.weapons[w];
|
|
|
|
cs.weapons[w].weaponId = wep.weaponId;
|
|
|
|
cs.weapons[w].bulletsClip = wep.inClip;
|
|
|
|
cs.weapons[w].bulletsTotal = wep.totalBullets;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-18 07:24:34 +03:00
|
|
|
// @todo restore properly
|
|
|
|
for (const auto& payphone : payphones) {
|
|
|
|
state.world->createPayphone(glm::vec2(payphone.position));
|
|
|
|
}
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
// TODO restore garage data
|
|
|
|
// http://gtaforums.com/topic/758692-gta-iii-save-file-documentation/
|
|
|
|
for (size_t g = 0; g < garageData.garageCount; ++g) {
|
|
|
|
auto& garage = garages[g];
|
2018-05-31 23:38:47 +03:00
|
|
|
state.world->createGarage(glm::vec3(garage.x1, garage.y1, garage.z1),
|
|
|
|
glm::vec3(garage.x2, garage.y2, garage.z2),
|
|
|
|
static_cast<Garage::Type>(garage.type));
|
2016-09-09 21:13:19 +01:00
|
|
|
}
|
2018-01-27 21:24:15 +01:00
|
|
|
for (auto &c : garageData.cars) {
|
|
|
|
if (c.modelId == 0) continue;
|
|
|
|
auto& car = c;
|
2016-09-09 21:13:19 +01:00
|
|
|
glm::quat rotation(
|
|
|
|
glm::mat3(glm::cross(car.rotation, glm::vec3(0.f, 0.f, 1.f)),
|
|
|
|
car.rotation, glm::vec3(0.f, 0.f, 1.f)));
|
|
|
|
|
|
|
|
VehicleObject* vehicle =
|
|
|
|
state.world->createVehicle(car.modelId, car.position, rotation);
|
|
|
|
vehicle->setPrimaryColour(car.colorFG);
|
|
|
|
vehicle->setSecondaryColour(car.colorBG);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (unsigned g = 0; g < carGenerators.size(); ++g) {
|
|
|
|
auto& gen = carGenerators[g];
|
|
|
|
state.vehicleGenerators.emplace_back(
|
|
|
|
g, gen.position, gen.angle, gen.modelId, gen.colourFG, gen.colourBG,
|
|
|
|
gen.force, gen.alarmChance, gen.lockedChance, gen.minDelay,
|
|
|
|
gen.maxDelay, gen.timestamp,
|
|
|
|
101 /// @todo determine where the remainingSpawns should be
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load import / export lists
|
|
|
|
state.importExportPortland = garageData.bfImportExportPortland;
|
|
|
|
state.importExportShoreside = garageData.bfImportExportShoreside;
|
|
|
|
state.importExportUnused = garageData.bfImportExportUnused;
|
|
|
|
|
|
|
|
std::fclose(loadFile);
|
|
|
|
|
|
|
|
return true;
|
2015-06-14 02:44:51 +01:00
|
|
|
}
|
2016-08-03 18:25:45 -07:00
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
bool SaveGame::getSaveInfo(const std::string& file, BasicState* basicState) {
|
|
|
|
std::FILE* loadFile = std::fopen(file.c_str(), "rb");
|
|
|
|
|
|
|
|
SaveGameInfo info;
|
|
|
|
info.savePath = file;
|
|
|
|
|
|
|
|
// BLOCK 0
|
|
|
|
BlockDword blockSize;
|
|
|
|
if (fread(&blockSize, sizeof(BlockDword), 1, loadFile) == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read block 0 into state
|
|
|
|
if (fread(basicState, sizeof(BasicState), 1, loadFile) == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::fclose(loadFile);
|
|
|
|
|
|
|
|
return true;
|
2015-06-14 18:08:55 +01:00
|
|
|
}
|
|
|
|
|
2018-01-24 12:29:49 +02:00
|
|
|
#ifdef RW_WINDOWS
|
|
|
|
char* readUserPath() {
|
|
|
|
LONG retval;
|
|
|
|
const char* lpSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
|
|
|
|
const char* lpValueName = "Personal";
|
|
|
|
DWORD lpType = REG_SZ;
|
|
|
|
HKEY phkResult;
|
|
|
|
static char lpData[1000];
|
|
|
|
DWORD lpcbData = sizeof(lpData);
|
|
|
|
|
|
|
|
retval = RegOpenKeyEx(HKEY_CURRENT_USER, lpSubKey, 0, KEY_READ, &phkResult);
|
|
|
|
if (ERROR_SUCCESS != retval) { return nullptr; }
|
|
|
|
|
|
|
|
retval = RegQueryValueEx(phkResult, lpValueName, NULL, &lpType, (LPBYTE) lpData, &lpcbData);
|
|
|
|
if (ERROR_SUCCESS != retval) { return nullptr; }
|
|
|
|
|
|
|
|
retval = RegCloseKey(phkResult);
|
|
|
|
if (ERROR_SUCCESS != retval) { return nullptr; }
|
|
|
|
|
|
|
|
return lpData;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-09-09 21:13:19 +01:00
|
|
|
std::vector<SaveGameInfo> SaveGame::getAllSaveGameInfo() {
|
2018-01-24 12:29:49 +02:00
|
|
|
#ifdef RW_WINDOWS
|
|
|
|
auto homedir = readUserPath(); // already includes MyDocuments/Documents
|
|
|
|
#else
|
2016-09-09 21:13:19 +01:00
|
|
|
auto homedir = getenv("HOME");
|
2018-01-24 12:29:49 +02:00
|
|
|
#endif
|
2016-09-09 21:13:19 +01:00
|
|
|
if (homedir == nullptr) {
|
|
|
|
std::cerr << "Unable to determine home directory" << std::endl;
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
const char gameDir[] = "GTA3 User Files";
|
|
|
|
|
2017-10-29 22:50:59 +00:00
|
|
|
rwfs::path gamePath(homedir);
|
2016-09-09 21:13:19 +01:00
|
|
|
gamePath /= gameDir;
|
|
|
|
|
2017-10-29 22:50:59 +00:00
|
|
|
if (!rwfs::exists(gamePath) || !rwfs::is_directory(gamePath)) return {};
|
2016-09-09 21:13:19 +01:00
|
|
|
|
|
|
|
std::vector<SaveGameInfo> infos;
|
2017-10-29 22:50:59 +00:00
|
|
|
for (const rwfs::path& save_path : rwfs::directory_iterator(gamePath)) {
|
2016-09-09 21:13:19 +01:00
|
|
|
if (save_path.extension() == ".b") {
|
|
|
|
infos.emplace_back(
|
|
|
|
SaveGameInfo{save_path.string(), false, BasicState()});
|
|
|
|
infos.back().valid =
|
|
|
|
getSaveInfo(infos.back().savePath, &infos.back().basicState);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return infos;
|
2015-06-14 18:08:55 +01:00
|
|
|
}
|