warzone2100/src/game.cpp

6619 lines
203 KiB
C++

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2013 Warzone 2100 Project
Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "lib/framework/wzapp.h"
#include <QtCore/QMap>
/* Standard library headers */
#include <physfs.h>
#include <string.h>
/* Warzone src and library headers */
#include "lib/framework/endian_hack.h"
#include "lib/framework/wzconfig.h"
#include "lib/framework/file.h"
#include "lib/framework/physfs_ext.h"
#include "lib/framework/strres.h"
#include "lib/framework/opengl.h"
#include "lib/gamelib/gtime.h"
#include "lib/ivis_opengl/ivisdef.h"
#include "lib/ivis_opengl/pieblitfunc.h"
#include "lib/ivis_opengl/piestate.h"
#include "lib/ivis_opengl/piepalette.h"
#include "lib/netplay/netplay.h"
#include "lib/script/script.h"
#include "lib/sound/audio.h"
#include "lib/sound/audio_id.h"
#include "modding.h"
#include "main.h"
#include "game.h"
#include "qtscript.h"
#include "fpath.h"
#include "difficulty.h"
#include "map.h"
#include "droid.h"
#include "action.h"
#include "research.h"
#include "power.h"
#include "projectile.h"
#include "loadsave.h"
#include "text.h"
#include "message.h"
#include "hci.h"
#include "display.h"
#include "display3d.h"
#include "map.h"
#include "effects.h"
#include "init.h"
#include "mission.h"
#include "scores.h"
#include "anim_id.h"
#include "design.h"
#include "component.h"
#include "radar.h"
#include "cmddroid.h"
#include "warzoneconfig.h"
#include "multiplay.h"
#include "frontend.h"
#include "levels.h"
#include "mission.h"
#include "geometry.h"
#include "gateway.h"
#include "scripttabs.h"
#include "scriptvals.h"
#include "scriptextern.h"
#include "multistat.h"
#include "multiint.h"
#include "wrappers.h"
#include "scriptfuncs.h"
#include "challenge.h"
#include "combat.h"
#include "template.h"
#include "version.h"
#include "lib/ivis_opengl/screen.h"
#include "keymap.h"
#include <ctime>
#define MAX_SAVE_NAME_SIZE_V19 40
#define MAX_SAVE_NAME_SIZE 60
static const UDWORD NULL_ID = UDWORD_MAX;
#define SAVEKEY_ONMISSION 0x100
static UDWORD RemapPlayerNumber(UDWORD OldNumber);
static void plotFeature(char *backDropSprite);
bool writeGameInfo(const char *pFileName);
struct GAME_SAVEHEADER
{
char aFileType[4];
uint32_t version;
};
static bool serializeSaveGameHeader(PHYSFS_file* fileHandle, const GAME_SAVEHEADER* serializeHeader)
{
if (PHYSFS_write(fileHandle, serializeHeader->aFileType, 4, 1) != 1)
return false;
// Write version numbers below version 35 as little-endian, and those above as big-endian
if (serializeHeader->version < VERSION_35)
return PHYSFS_writeULE32(fileHandle, serializeHeader->version);
else
return PHYSFS_writeUBE32(fileHandle, serializeHeader->version);
}
static bool deserializeSaveGameHeader(PHYSFS_file* fileHandle, GAME_SAVEHEADER* serializeHeader)
{
// Read in the header from the file
if (PHYSFS_read(fileHandle, serializeHeader->aFileType, 4, 1) != 1
|| PHYSFS_read(fileHandle, &serializeHeader->version, sizeof(uint32_t), 1) != 1)
return false;
// All save game file versions below version 35 (i.e. _not_ version 35 itself)
// have their version numbers stored as little endian. Versions from 35 and
// onward use big-endian. This basically means that, because of endian
// swapping, numbers from 35 and onward will be ridiculously high if a
// little-endian byte-order is assumed.
// Convert from little endian to native byte-order and check if we get a
// ridiculously high number
endian_udword(&serializeHeader->version);
if (serializeHeader->version <= VERSION_34)
{
// Apparently we don't get a ridiculously high number if we assume
// little-endian, so lets assume our version number is 34 at max and return
debug(LOG_SAVE, "Version = %u (little-endian)", serializeHeader->version);
return true;
}
else
{
// Apparently we get a larger number than expected if using little-endian.
// So assume we have a version of 35 and onward
// Reverse the little-endian decoding
endian_udword(&serializeHeader->version);
}
// Considering that little-endian didn't work we now use big-endian instead
serializeHeader->version = PHYSFS_swapUBE32(serializeHeader->version);
debug(LOG_SAVE, "Version %u = (big-endian)", serializeHeader->version);
return true;
}
struct STRUCT_SAVEHEADER : public GAME_SAVEHEADER
{
UDWORD quantity;
};
struct FEATURE_SAVEHEADER : public GAME_SAVEHEADER
{
UDWORD quantity;
};
/* Structure definitions for loading and saving map data */
struct TILETYPE_SAVEHEADER : public GAME_SAVEHEADER
{
UDWORD quantity;
};
/* Sanity check definitions for the save struct file sizes */
#define DROIDINIT_HEADER_SIZE 12
#define STRUCT_HEADER_SIZE 12
#define FEATURE_HEADER_SIZE 12
#define TILETYPE_HEADER_SIZE 12
// general save definitions
#define MAX_LEVEL_SIZE 20
#define OBJECT_SAVE_V19 \
char name[MAX_SAVE_NAME_SIZE_V19]; \
UDWORD id; \
UDWORD x,y,z; \
UDWORD direction; \
UDWORD player; \
int32_t inFire; \
UDWORD periodicalDamageStart; \
UDWORD periodicalDamage
#define OBJECT_SAVE_V20 \
char name[MAX_SAVE_NAME_SIZE]; \
UDWORD id; \
UDWORD x,y,z; \
UDWORD direction; \
UDWORD player; \
int32_t inFire; \
UDWORD periodicalDamageStart; \
UDWORD periodicalDamage
struct SAVE_POWER
{
uint32_t currentPower;
uint32_t extractedPower; // used for hacks
};
static bool serializeSavePowerData(PHYSFS_file* fileHandle, const SAVE_POWER* serializePower)
{
return (PHYSFS_writeUBE32(fileHandle, serializePower->currentPower)
&& PHYSFS_writeUBE32(fileHandle, serializePower->extractedPower));
}
static bool deserializeSavePowerData(PHYSFS_file* fileHandle, SAVE_POWER* serializePower)
{
return (PHYSFS_readUBE32(fileHandle, &serializePower->currentPower)
&& PHYSFS_readUBE32(fileHandle, &serializePower->extractedPower));
}
static bool serializeVector3i(PHYSFS_file* fileHandle, const Vector3i* serializeVector)
{
return (PHYSFS_writeSBE32(fileHandle, serializeVector->x)
&& PHYSFS_writeSBE32(fileHandle, serializeVector->y)
&& PHYSFS_writeSBE32(fileHandle, serializeVector->z));
}
static bool deserializeVector3i(PHYSFS_file* fileHandle, Vector3i* serializeVector)
{
int32_t x, y, z;
if (!PHYSFS_readSBE32(fileHandle, &x)
|| !PHYSFS_readSBE32(fileHandle, &y)
|| !PHYSFS_readSBE32(fileHandle, &z))
return false;
serializeVector-> x = x;
serializeVector-> y = y;
serializeVector-> z = z;
return true;
}
static bool serializeVector2i(PHYSFS_file* fileHandle, const Vector2i* serializeVector)
{
return (PHYSFS_writeSBE32(fileHandle, serializeVector->x)
&& PHYSFS_writeSBE32(fileHandle, serializeVector->y));
}
static bool deserializeVector2i(PHYSFS_file* fileHandle, Vector2i* serializeVector)
{
int32_t x, y;
if (!PHYSFS_readSBE32(fileHandle, &x)
|| !PHYSFS_readSBE32(fileHandle, &y))
return false;
serializeVector-> x = x;
serializeVector-> y = y;
return true;
}
static bool serializeiViewData(PHYSFS_file* fileHandle, const iView* serializeView)
{
return (serializeVector3i(fileHandle, &serializeView->p)
&& serializeVector3i(fileHandle, &serializeView->r));
}
static bool deserializeiViewData(PHYSFS_file* fileHandle, iView* serializeView)
{
return (deserializeVector3i(fileHandle, &serializeView->p)
&& deserializeVector3i(fileHandle, &serializeView->r));
}
static bool serializeRunData(PHYSFS_file* fileHandle, const RUN_DATA* serializeRun)
{
return (serializeVector2i(fileHandle, &serializeRun->sPos)
&& PHYSFS_writeUBE8(fileHandle, serializeRun->forceLevel)
&& PHYSFS_writeUBE8(fileHandle, serializeRun->healthLevel)
&& PHYSFS_writeUBE8(fileHandle, serializeRun->leadership));
}
static bool deserializeRunData(PHYSFS_file* fileHandle, RUN_DATA* serializeRun)
{
return (deserializeVector2i(fileHandle, &serializeRun->sPos)
&& PHYSFS_readUBE8(fileHandle, &serializeRun->forceLevel)
&& PHYSFS_readUBE8(fileHandle, &serializeRun->healthLevel)
&& PHYSFS_readUBE8(fileHandle, &serializeRun->leadership));
}
static bool serializeLandingZoneData(PHYSFS_file* fileHandle, const LANDING_ZONE* serializeLandZone)
{
return (PHYSFS_writeUBE8(fileHandle, serializeLandZone->x1)
&& PHYSFS_writeUBE8(fileHandle, serializeLandZone->y1)
&& PHYSFS_writeUBE8(fileHandle, serializeLandZone->x2)
&& PHYSFS_writeUBE8(fileHandle, serializeLandZone->y2));
}
static bool deserializeLandingZoneData(PHYSFS_file* fileHandle, LANDING_ZONE* serializeLandZone)
{
return (PHYSFS_readUBE8(fileHandle, &serializeLandZone->x1)
&& PHYSFS_readUBE8(fileHandle, &serializeLandZone->y1)
&& PHYSFS_readUBE8(fileHandle, &serializeLandZone->x2)
&& PHYSFS_readUBE8(fileHandle, &serializeLandZone->y2));
}
static bool serializeMultiplayerGame(PHYSFS_file* fileHandle, const MULTIPLAYERGAME* serializeMulti)
{
const char *dummy8c = "DUMMYSTRING";
unsigned int i;
if (!PHYSFS_writeUBE8(fileHandle, serializeMulti->type)
|| PHYSFS_write(fileHandle, serializeMulti->map, 1, 128) != 128
|| PHYSFS_write(fileHandle, dummy8c, 1, 8) != 8
|| !PHYSFS_writeUBE8(fileHandle, serializeMulti->maxPlayers)
|| PHYSFS_write(fileHandle, serializeMulti->name, 1, 128) != 128
|| !PHYSFS_writeSBE32(fileHandle, 0)
|| !PHYSFS_writeUBE32(fileHandle, serializeMulti->power)
|| !PHYSFS_writeUBE8(fileHandle, serializeMulti->base)
|| !PHYSFS_writeUBE8(fileHandle, serializeMulti->alliance)
|| !PHYSFS_writeUBE8(fileHandle, serializeMulti->hash.Bytes)
|| !PHYSFS_write(fileHandle, serializeMulti->hash.bytes, serializeMulti->hash.Bytes, 1)
|| !PHYSFS_writeUBE16(fileHandle, 0) // dummy, was bytesPerSec
|| !PHYSFS_writeUBE8(fileHandle, 0) // dummy, was packetsPerSec
|| !PHYSFS_writeUBE8(fileHandle, challengeActive)) // reuse available field, was encryptKey
{
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE8(fileHandle, serializeMulti->skDiff[i]))
return false;
}
return true;
}
static bool deserializeMultiplayerGame(PHYSFS_file* fileHandle, MULTIPLAYERGAME* serializeMulti)
{
unsigned int i;
int32_t boolFog;
uint8_t dummy8;
uint16_t dummy16;
char dummy8c[8];
uint8_t hashSize;
serializeMulti->hash.setZero();
if (!PHYSFS_readUBE8(fileHandle, &serializeMulti->type)
|| PHYSFS_read(fileHandle, serializeMulti->map, 1, 128) != 128
|| PHYSFS_read(fileHandle, dummy8c, 1, 8) != 8
|| !PHYSFS_readUBE8(fileHandle, &serializeMulti->maxPlayers)
|| PHYSFS_read(fileHandle, serializeMulti->name, 1, 128) != 128
|| !PHYSFS_readSBE32(fileHandle, &boolFog)
|| !PHYSFS_readUBE32(fileHandle, &serializeMulti->power)
|| !PHYSFS_readUBE8(fileHandle, &serializeMulti->base)
|| !PHYSFS_readUBE8(fileHandle, &serializeMulti->alliance)
|| !PHYSFS_readUBE8(fileHandle, &hashSize)
|| (hashSize == serializeMulti->hash.Bytes && !PHYSFS_read(fileHandle, serializeMulti->hash.bytes, serializeMulti->hash.Bytes, 1))
|| !PHYSFS_readUBE16(fileHandle, &dummy16) // dummy, was bytesPerSec
|| !PHYSFS_readUBE8(fileHandle, &dummy8) // dummy, was packetsPerSec
|| !PHYSFS_readUBE8(fileHandle, &dummy8)) // reused for challenge, was encryptKey
{
return false;
}
challengeActive = dummy8; // hack
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE8(fileHandle, &serializeMulti->skDiff[i]))
return false;
}
return true;
}
static bool serializePlayer(PHYSFS_file* fileHandle, const PLAYER* serializePlayer, int player)
{
return (PHYSFS_writeUBE32(fileHandle, serializePlayer->position)
&& PHYSFS_write(fileHandle, serializePlayer->name, StringSize, 1) == 1
&& PHYSFS_write(fileHandle, getAIName(player), MAX_LEN_AI_NAME, 1) == 1
&& PHYSFS_writeSBE8(fileHandle, serializePlayer->difficulty)
&& PHYSFS_writeUBE8(fileHandle, (uint8_t)serializePlayer->allocated)
&& PHYSFS_writeUBE32(fileHandle, serializePlayer->colour)
&& PHYSFS_writeUBE32(fileHandle, serializePlayer->team));
}
static bool deserializePlayer(PHYSFS_file* fileHandle, PLAYER* serializePlayer, int player)
{
char aiName[MAX_LEN_AI_NAME];
uint32_t position, colour, team;
bool retval;
uint8_t allocated;
retval = (PHYSFS_readUBE32(fileHandle, &position)
&& PHYSFS_read(fileHandle, serializePlayer->name, StringSize, 1) == 1
&& PHYSFS_read(fileHandle, aiName, MAX_LEN_AI_NAME, 1) == 1
&& PHYSFS_readSBE8(fileHandle, &serializePlayer->difficulty)
&& PHYSFS_readUBE8(fileHandle, &allocated)
&& PHYSFS_readUBE32(fileHandle, &colour)
&& PHYSFS_readUBE32(fileHandle, &team));
serializePlayer->allocated = allocated;
if (player < game.maxPlayers)
{
serializePlayer->ai = matchAIbyName(aiName);
if (serializePlayer->ai == AI_NOT_FOUND)
{
debug(LOG_ERROR, "AI \"%s\" not found -- script loading will fail (player %d / %d)", aiName, player, game.maxPlayers);
}
}
serializePlayer->position = position;
serializePlayer->colour = colour;
serializePlayer->team = team;
return retval;
}
static bool serializeNetPlay(PHYSFS_file* fileHandle, const NETPLAY* serializeNetPlay)
{
unsigned int i;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!serializePlayer(fileHandle, &serializeNetPlay->players[i], i))
return false;
}
return (PHYSFS_writeUBE32(fileHandle, serializeNetPlay->bComms)
&& PHYSFS_writeUBE32(fileHandle, serializeNetPlay->playercount)
&& PHYSFS_writeUBE32(fileHandle, serializeNetPlay->hostPlayer)
&& PHYSFS_writeUBE32(fileHandle, selectedPlayer)
&& PHYSFS_writeUBE32(fileHandle, (uint32_t)game.scavengers)
&& PHYSFS_writeUBE32(fileHandle, 0)
&& PHYSFS_writeUBE32(fileHandle, 0));
}
static bool deserializeNetPlay(PHYSFS_file* fileHandle, NETPLAY* serializeNetPlay)
{
unsigned int i;
uint32_t dummy, scavs = game.scavengers;
bool retv;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!deserializePlayer(fileHandle, &serializeNetPlay->players[i], i))
return false;
}
serializeNetPlay->isHost = true; // only host can load
retv = (PHYSFS_readUBE32(fileHandle, &serializeNetPlay->bComms)
&& PHYSFS_readUBE32(fileHandle, &serializeNetPlay->playercount)
&& PHYSFS_readUBE32(fileHandle, &serializeNetPlay->hostPlayer)
&& PHYSFS_readUBE32(fileHandle, &selectedPlayer)
&& PHYSFS_readUBE32(fileHandle, &scavs)
&& PHYSFS_readUBE32(fileHandle, &dummy)
&& PHYSFS_readUBE32(fileHandle, &dummy));
game.scavengers = scavs;
return retv;
}
struct SAVE_GAME_V7
{
uint32_t gameTime;
uint32_t GameType; /* Type of game , one of the GTYPE_... enums. */
int32_t ScrollMinX; /* Scroll Limits */
int32_t ScrollMinY;
uint32_t ScrollMaxX;
uint32_t ScrollMaxY;
char levelName[MAX_LEVEL_SIZE]; //name of the level to load up when mid game
};
static bool serializeSaveGameV7Data(PHYSFS_file* fileHandle, const SAVE_GAME_V7* serializeGame)
{
return (PHYSFS_writeUBE32(fileHandle, serializeGame->gameTime)
&& PHYSFS_writeUBE32(fileHandle, serializeGame->GameType)
&& PHYSFS_writeSBE32(fileHandle, serializeGame->ScrollMinX)
&& PHYSFS_writeSBE32(fileHandle, serializeGame->ScrollMinY)
&& PHYSFS_writeUBE32(fileHandle, serializeGame->ScrollMaxX)
&& PHYSFS_writeUBE32(fileHandle, serializeGame->ScrollMaxY)
&& PHYSFS_write(fileHandle, serializeGame->levelName, MAX_LEVEL_SIZE, 1) == 1);
}
static bool deserializeSaveGameV7Data(PHYSFS_file* fileHandle, SAVE_GAME_V7* serializeGame)
{
return (PHYSFS_readUBE32(fileHandle, &serializeGame->gameTime)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->GameType)
&& PHYSFS_readSBE32(fileHandle, &serializeGame->ScrollMinX)
&& PHYSFS_readSBE32(fileHandle, &serializeGame->ScrollMinY)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->ScrollMaxX)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->ScrollMaxY)
&& PHYSFS_read(fileHandle, serializeGame->levelName, MAX_LEVEL_SIZE, 1) == 1);
}
struct SAVE_GAME_V10 : public SAVE_GAME_V7
{
SAVE_POWER power[MAX_PLAYERS];
};
static bool serializeSaveGameV10Data(PHYSFS_file* fileHandle, const SAVE_GAME_V10* serializeGame)
{
unsigned int i;
if (!serializeSaveGameV7Data(fileHandle, (const SAVE_GAME_V7*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!serializeSavePowerData(fileHandle, &serializeGame->power[i]))
return false;
}
return true;
}
static bool deserializeSaveGameV10Data(PHYSFS_file* fileHandle, SAVE_GAME_V10* serializeGame)
{
unsigned int i;
if (!deserializeSaveGameV7Data(fileHandle, (SAVE_GAME_V7*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!deserializeSavePowerData(fileHandle, &serializeGame->power[i]))
return false;
}
return true;
}
struct SAVE_GAME_V11 : public SAVE_GAME_V10
{
iView currentPlayerPos;
};
static bool serializeSaveGameV11Data(PHYSFS_file* fileHandle, const SAVE_GAME_V11* serializeGame)
{
return (serializeSaveGameV10Data(fileHandle, (const SAVE_GAME_V10*) serializeGame)
&& serializeiViewData(fileHandle, &serializeGame->currentPlayerPos));
}
static bool deserializeSaveGameV11Data(PHYSFS_file* fileHandle, SAVE_GAME_V11* serializeGame)
{
return (deserializeSaveGameV10Data(fileHandle, (SAVE_GAME_V10*) serializeGame)
&& deserializeiViewData(fileHandle, &serializeGame->currentPlayerPos));
}
struct SAVE_GAME_V12 : public SAVE_GAME_V11
{
uint32_t missionTime;
uint32_t saveKey;
};
static bool serializeSaveGameV12Data(PHYSFS_file* fileHandle, const SAVE_GAME_V12* serializeGame)
{
return (serializeSaveGameV11Data(fileHandle, (const SAVE_GAME_V11*) serializeGame)
&& PHYSFS_writeUBE32(fileHandle, serializeGame->missionTime)
&& PHYSFS_writeUBE32(fileHandle, serializeGame->saveKey));
}
static bool deserializeSaveGameV12Data(PHYSFS_file* fileHandle, SAVE_GAME_V12* serializeGame)
{
return (deserializeSaveGameV11Data(fileHandle, (SAVE_GAME_V11*) serializeGame)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->missionTime)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->saveKey));
}
struct SAVE_GAME_V14 : public SAVE_GAME_V12
{
int32_t missionOffTime;
int32_t missionETA;
uint16_t missionHomeLZ_X;
uint16_t missionHomeLZ_Y;
int32_t missionPlayerX;
int32_t missionPlayerY;
uint16_t iTranspEntryTileX[MAX_PLAYERS];
uint16_t iTranspEntryTileY[MAX_PLAYERS];
uint16_t iTranspExitTileX[MAX_PLAYERS];
uint16_t iTranspExitTileY[MAX_PLAYERS];
uint32_t aDefaultSensor[MAX_PLAYERS];
uint32_t aDefaultECM[MAX_PLAYERS];
uint32_t aDefaultRepair[MAX_PLAYERS];
};
static bool serializeSaveGameV14Data(PHYSFS_file* fileHandle, const SAVE_GAME_V14* serializeGame)
{
unsigned int i;
if (!serializeSaveGameV12Data(fileHandle, (const SAVE_GAME_V12*) serializeGame)
|| !PHYSFS_writeSBE32(fileHandle, serializeGame->missionOffTime)
|| !PHYSFS_writeSBE32(fileHandle, serializeGame->missionETA)
|| !PHYSFS_writeUBE16(fileHandle, serializeGame->missionHomeLZ_X)
|| !PHYSFS_writeUBE16(fileHandle, serializeGame->missionHomeLZ_Y)
|| !PHYSFS_writeSBE32(fileHandle, serializeGame->missionPlayerX)
|| !PHYSFS_writeSBE32(fileHandle, serializeGame->missionPlayerY))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspEntryTileX[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspEntryTileY[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspExitTileX[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspExitTileY[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE32(fileHandle, serializeGame->aDefaultSensor[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE32(fileHandle, serializeGame->aDefaultECM[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE32(fileHandle, serializeGame->aDefaultRepair[i]))
return false;
}
return true;
}
static bool deserializeSaveGameV14Data(PHYSFS_file* fileHandle, SAVE_GAME_V14* serializeGame)
{
unsigned int i;
if (!deserializeSaveGameV12Data(fileHandle, (SAVE_GAME_V12*) serializeGame)
|| !PHYSFS_readSBE32(fileHandle, &serializeGame->missionOffTime)
|| !PHYSFS_readSBE32(fileHandle, &serializeGame->missionETA)
|| !PHYSFS_readUBE16(fileHandle, &serializeGame->missionHomeLZ_X)
|| !PHYSFS_readUBE16(fileHandle, &serializeGame->missionHomeLZ_Y)
|| !PHYSFS_readSBE32(fileHandle, &serializeGame->missionPlayerX)
|| !PHYSFS_readSBE32(fileHandle, &serializeGame->missionPlayerY))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspEntryTileX[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspEntryTileY[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspExitTileX[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspExitTileY[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE32(fileHandle, &serializeGame->aDefaultSensor[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE32(fileHandle, &serializeGame->aDefaultECM[i]))
return false;
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE32(fileHandle, &serializeGame->aDefaultRepair[i]))
return false;
}
return true;
}
struct SAVE_GAME_V15 : public SAVE_GAME_V14
{
int32_t offWorldKeepLists; // was BOOL (which was a int)
uint8_t aDroidExperience[MAX_PLAYERS][MAX_RECYCLED_DROIDS];
uint32_t RubbleTile;
uint32_t WaterTile;
uint32_t fogColour;
uint32_t fogState;
};
static bool serializeSaveGameV15Data(PHYSFS_file* fileHandle, const SAVE_GAME_V15* serializeGame)
{
unsigned int i, j;
if (!serializeSaveGameV14Data(fileHandle, (const SAVE_GAME_V14*) serializeGame)
|| !PHYSFS_writeSBE32(fileHandle, serializeGame->offWorldKeepLists))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
{
if (!PHYSFS_writeUBE8(fileHandle, serializeGame->aDroidExperience[i][j]))
return false;
}
}
return (PHYSFS_writeUBE32(fileHandle, serializeGame->RubbleTile)
&& PHYSFS_writeUBE32(fileHandle, serializeGame->WaterTile)
&& PHYSFS_writeUBE32(fileHandle, 0)
&& PHYSFS_writeUBE32(fileHandle, 0));
}
static bool deserializeSaveGameV15Data(PHYSFS_file* fileHandle, SAVE_GAME_V15* serializeGame)
{
unsigned int i, j;
int32_t boolOffWorldKeepLists;
if (!deserializeSaveGameV14Data(fileHandle, (SAVE_GAME_V14*) serializeGame)
|| !PHYSFS_readSBE32(fileHandle, &boolOffWorldKeepLists))
return false;
serializeGame->offWorldKeepLists = boolOffWorldKeepLists;
for (i = 0; i < MAX_PLAYERS; ++i)
{
for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
{
if (!PHYSFS_readUBE8(fileHandle, &serializeGame->aDroidExperience[i][j]))
return false;
}
}
return (PHYSFS_readUBE32(fileHandle, &serializeGame->RubbleTile)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->WaterTile)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->fogColour)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->fogState));
}
struct SAVE_GAME_V16 : public SAVE_GAME_V15
{
LANDING_ZONE sLandingZone[MAX_NOGO_AREAS];
};
static bool serializeSaveGameV16Data(PHYSFS_file* fileHandle, const SAVE_GAME_V16* serializeGame)
{
unsigned int i;
if (!serializeSaveGameV15Data(fileHandle, (const SAVE_GAME_V15*) serializeGame))
return false;
for (i = 0; i < MAX_NOGO_AREAS; ++i)
{
if (!serializeLandingZoneData(fileHandle, &serializeGame->sLandingZone[i]))
return false;
}
return true;
}
static bool deserializeSaveGameV16Data(PHYSFS_file* fileHandle, SAVE_GAME_V16* serializeGame)
{
unsigned int i;
if (!deserializeSaveGameV15Data(fileHandle, (SAVE_GAME_V15*) serializeGame))
return false;
for (i = 0; i < MAX_NOGO_AREAS; ++i)
{
if (!deserializeLandingZoneData(fileHandle, &serializeGame->sLandingZone[i]))
return false;
}
return true;
}
struct SAVE_GAME_V17 : public SAVE_GAME_V16
{
uint32_t objId;
};
static bool serializeSaveGameV17Data(PHYSFS_file* fileHandle, const SAVE_GAME_V17* serializeGame)
{
return (serializeSaveGameV16Data(fileHandle, (const SAVE_GAME_V16*) serializeGame)
&& PHYSFS_writeUBE32(fileHandle, serializeGame->objId));
}
static bool deserializeSaveGameV17Data(PHYSFS_file* fileHandle, SAVE_GAME_V17* serializeGame)
{
return (deserializeSaveGameV16Data(fileHandle, (SAVE_GAME_V16*) serializeGame)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->objId));
}
struct SAVE_GAME_V18 : public SAVE_GAME_V17
{
char buildDate[MAX_STR_LENGTH];
uint32_t oldestVersion;
uint32_t validityKey;
};
static bool serializeSaveGameV18Data(PHYSFS_file* fileHandle, const SAVE_GAME_V18* serializeGame)
{
return (serializeSaveGameV17Data(fileHandle, (const SAVE_GAME_V17*) serializeGame)
&& PHYSFS_write(fileHandle, serializeGame->buildDate, MAX_STR_LENGTH, 1) == 1
&& PHYSFS_writeUBE32(fileHandle, serializeGame->oldestVersion)
&& PHYSFS_writeUBE32(fileHandle, serializeGame->validityKey));
}
static bool deserializeSaveGameV18Data(PHYSFS_file* fileHandle, SAVE_GAME_V18* serializeGame)
{
return (deserializeSaveGameV17Data(fileHandle, (SAVE_GAME_V17*) serializeGame)
&& PHYSFS_read(fileHandle, serializeGame->buildDate, MAX_STR_LENGTH, 1) == 1
&& PHYSFS_readUBE32(fileHandle, &serializeGame->oldestVersion)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->validityKey));
}
struct SAVE_GAME_V19 : public SAVE_GAME_V18
{
uint8_t alliances[MAX_PLAYERS][MAX_PLAYERS];
uint8_t playerColour[MAX_PLAYERS];
uint8_t radarZoom;
};
static bool serializeSaveGameV19Data(PHYSFS_file* fileHandle, const SAVE_GAME_V19* serializeGame)
{
unsigned int i, j;
if (!serializeSaveGameV18Data(fileHandle, (const SAVE_GAME_V18*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
for (j = 0; j < MAX_PLAYERS; ++j)
{
if (!PHYSFS_writeUBE8(fileHandle, serializeGame->alliances[i][j]))
return false;
}
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE8(fileHandle, serializeGame->playerColour[i]))
return false;
}
return PHYSFS_writeUBE8(fileHandle, serializeGame->radarZoom);
}
static bool deserializeSaveGameV19Data(PHYSFS_file* fileHandle, SAVE_GAME_V19* serializeGame)
{
unsigned int i, j;
if (!deserializeSaveGameV18Data(fileHandle, (SAVE_GAME_V18*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
for (j = 0; j < MAX_PLAYERS; ++j)
{
if (!PHYSFS_readUBE8(fileHandle, &serializeGame->alliances[i][j]))
{
return false;
}
}
}
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE8(fileHandle, &serializeGame->playerColour[i]))
return false;
}
return PHYSFS_readUBE8(fileHandle, &serializeGame->radarZoom);
}
struct SAVE_GAME_V20 : public SAVE_GAME_V19
{
uint8_t bDroidsToSafetyFlag;
Vector2i asVTOLReturnPos[MAX_PLAYERS];
};
static bool serializeSaveGameV20Data(PHYSFS_file* fileHandle, const SAVE_GAME_V20* serializeGame)
{
unsigned int i;
if (!serializeSaveGameV19Data(fileHandle, (const SAVE_GAME_V19*) serializeGame)
|| !PHYSFS_writeUBE8(fileHandle, serializeGame->bDroidsToSafetyFlag))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!serializeVector2i(fileHandle, &serializeGame->asVTOLReturnPos[i]))
return false;
}
return true;
}
static bool deserializeSaveGameV20Data(PHYSFS_file* fileHandle, SAVE_GAME_V20* serializeGame)
{
unsigned int i;
if (!deserializeSaveGameV19Data(fileHandle, (SAVE_GAME_V19*) serializeGame)
|| !PHYSFS_readUBE8(fileHandle, &serializeGame->bDroidsToSafetyFlag))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!deserializeVector2i(fileHandle, &serializeGame->asVTOLReturnPos[i]))
return false;
}
return true;
}
struct SAVE_GAME_V22 : public SAVE_GAME_V20
{
RUN_DATA asRunData[MAX_PLAYERS];
};
static bool serializeSaveGameV22Data(PHYSFS_file* fileHandle, const SAVE_GAME_V22* serializeGame)
{
unsigned int i;
if (!serializeSaveGameV20Data(fileHandle, (const SAVE_GAME_V20*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!serializeRunData(fileHandle, &serializeGame->asRunData[i]))
return false;
}
return true;
}
static bool deserializeSaveGameV22Data(PHYSFS_file* fileHandle, SAVE_GAME_V22* serializeGame)
{
unsigned int i;
if (!deserializeSaveGameV20Data(fileHandle, (SAVE_GAME_V20*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!deserializeRunData(fileHandle, &serializeGame->asRunData[i]))
return false;
}
return true;
}
struct SAVE_GAME_V24 : public SAVE_GAME_V22
{
uint32_t reinforceTime;
uint8_t bPlayCountDown;
uint8_t bPlayerHasWon;
uint8_t bPlayerHasLost;
uint8_t dummy3;
};
static bool serializeSaveGameV24Data(PHYSFS_file* fileHandle, const SAVE_GAME_V24* serializeGame)
{
return (serializeSaveGameV22Data(fileHandle, (const SAVE_GAME_V22*) serializeGame)
&& PHYSFS_writeUBE32(fileHandle, serializeGame->reinforceTime)
&& PHYSFS_writeUBE8(fileHandle, serializeGame->bPlayCountDown)
&& PHYSFS_writeUBE8(fileHandle, serializeGame->bPlayerHasWon)
&& PHYSFS_writeUBE8(fileHandle, serializeGame->bPlayerHasLost)
&& PHYSFS_writeUBE8(fileHandle, serializeGame->dummy3));
}
static bool deserializeSaveGameV24Data(PHYSFS_file* fileHandle, SAVE_GAME_V24* serializeGame)
{
return (deserializeSaveGameV22Data(fileHandle, (SAVE_GAME_V22*) serializeGame)
&& PHYSFS_readUBE32(fileHandle, &serializeGame->reinforceTime)
&& PHYSFS_readUBE8(fileHandle, &serializeGame->bPlayCountDown)
&& PHYSFS_readUBE8(fileHandle, &serializeGame->bPlayerHasWon)
&& PHYSFS_readUBE8(fileHandle, &serializeGame->bPlayerHasLost)
&& PHYSFS_readUBE8(fileHandle, &serializeGame->dummy3));
}
struct SAVE_GAME_V27 : public SAVE_GAME_V24
{
uint16_t awDroidExperience[MAX_PLAYERS][MAX_RECYCLED_DROIDS];
};
static bool serializeSaveGameV27Data(PHYSFS_file* fileHandle, const SAVE_GAME_V27* serializeGame)
{
unsigned int i, j;
if (!serializeSaveGameV24Data(fileHandle, (const SAVE_GAME_V24*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
{
if (!PHYSFS_writeUBE16(fileHandle, serializeGame->awDroidExperience[i][j]))
return false;
}
}
return true;
}
static bool deserializeSaveGameV27Data(PHYSFS_file* fileHandle, SAVE_GAME_V27* serializeGame)
{
unsigned int i, j;
if (!deserializeSaveGameV24Data(fileHandle, (SAVE_GAME_V24*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
{
if (!PHYSFS_readUBE16(fileHandle, &serializeGame->awDroidExperience[i][j]))
return false;
}
}
return true;
}
struct SAVE_GAME_V29 : public SAVE_GAME_V27
{
uint16_t missionScrollMinX;
uint16_t missionScrollMinY;
uint16_t missionScrollMaxX;
uint16_t missionScrollMaxY;
};
static bool serializeSaveGameV29Data(PHYSFS_file* fileHandle, const SAVE_GAME_V29* serializeGame)
{
return (serializeSaveGameV27Data(fileHandle, (const SAVE_GAME_V27*) serializeGame)
&& PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMinX)
&& PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMinY)
&& PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMaxX)
&& PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMaxY));
}
static bool deserializeSaveGameV29Data(PHYSFS_file* fileHandle, SAVE_GAME_V29* serializeGame)
{
return (deserializeSaveGameV27Data(fileHandle, (SAVE_GAME_V27*) serializeGame)
&& PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMinX)
&& PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMinY)
&& PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMaxX)
&& PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMaxY));
}
struct SAVE_GAME_V30 : public SAVE_GAME_V29
{
int32_t scrGameLevel;
uint8_t bExtraVictoryFlag;
uint8_t bExtraFailFlag;
uint8_t bTrackTransporter;
};
static bool serializeSaveGameV30Data(PHYSFS_file* fileHandle, const SAVE_GAME_V30* serializeGame)
{
return (serializeSaveGameV29Data(fileHandle, (const SAVE_GAME_V29*) serializeGame)
&& PHYSFS_writeSBE32(fileHandle, serializeGame->scrGameLevel)
&& PHYSFS_writeUBE8(fileHandle, serializeGame->bExtraVictoryFlag)
&& PHYSFS_writeUBE8(fileHandle, serializeGame->bExtraFailFlag)
&& PHYSFS_writeUBE8(fileHandle, serializeGame->bTrackTransporter));
}
static bool deserializeSaveGameV30Data(PHYSFS_file* fileHandle, SAVE_GAME_V30* serializeGame)
{
return (deserializeSaveGameV29Data(fileHandle, (SAVE_GAME_V29*) serializeGame)
&& PHYSFS_readSBE32(fileHandle, &serializeGame->scrGameLevel)
&& PHYSFS_readUBE8(fileHandle, &serializeGame->bExtraVictoryFlag)
&& PHYSFS_readUBE8(fileHandle, &serializeGame->bExtraFailFlag)
&& PHYSFS_readUBE8(fileHandle, &serializeGame->bTrackTransporter));
}
//extra code for the patch - saves out whether cheated with the mission timer
struct SAVE_GAME_V31 : public SAVE_GAME_V30
{
int32_t missionCheatTime;
};
static bool serializeSaveGameV31Data(PHYSFS_file* fileHandle, const SAVE_GAME_V31* serializeGame)
{
return (serializeSaveGameV30Data(fileHandle, (const SAVE_GAME_V30*) serializeGame)
&& PHYSFS_writeSBE32(fileHandle, serializeGame->missionCheatTime));
}
static bool deserializeSaveGameV31Data(PHYSFS_file* fileHandle, SAVE_GAME_V31* serializeGame)
{
return (deserializeSaveGameV30Data(fileHandle, (SAVE_GAME_V30*) serializeGame)
&& PHYSFS_readSBE32(fileHandle, &serializeGame->missionCheatTime));
}
// alexl. skirmish saves
struct SAVE_GAME_V33 : public SAVE_GAME_V31
{
MULTIPLAYERGAME sGame;
NETPLAY sNetPlay;
uint32_t savePlayer;
char sPName[32];
int32_t multiPlayer; // was BOOL (int) ** see warning about conversion
uint32_t sPlayerIndex[MAX_PLAYERS];
};
static bool serializeSaveGameV33Data(PHYSFS_file* fileHandle, const SAVE_GAME_V33* serializeGame)
{
unsigned int i;
if (!serializeSaveGameV31Data(fileHandle, (const SAVE_GAME_V31*) serializeGame)
|| !serializeMultiplayerGame(fileHandle, &serializeGame->sGame)
|| !serializeNetPlay(fileHandle, &serializeGame->sNetPlay)
|| !PHYSFS_writeUBE32(fileHandle, serializeGame->savePlayer)
|| PHYSFS_write(fileHandle, serializeGame->sPName, 1, 32) != 32
|| !PHYSFS_writeSBE32(fileHandle, serializeGame->multiPlayer))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_writeUBE32(fileHandle, serializeGame->sPlayerIndex[i]))
return false;
}
return true;
}
static bool deserializeSaveGameV33Data(PHYSFS_file* fileHandle, SAVE_GAME_V33* serializeGame)
{
unsigned int i;
int32_t boolMultiPlayer;
if (!deserializeSaveGameV31Data(fileHandle, (SAVE_GAME_V31*) serializeGame)
|| !deserializeMultiplayerGame(fileHandle, &serializeGame->sGame)
|| !deserializeNetPlay(fileHandle, &serializeGame->sNetPlay)
|| !PHYSFS_readUBE32(fileHandle, &serializeGame->savePlayer)
|| PHYSFS_read(fileHandle, serializeGame->sPName, 1, 32) != 32
|| !PHYSFS_readSBE32(fileHandle, &boolMultiPlayer))
return false;
serializeGame->multiPlayer = boolMultiPlayer;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (!PHYSFS_readUBE32(fileHandle, &serializeGame->sPlayerIndex[i]))
return false;
}
return true;
}
//Now holds AI names for multiplayer
struct SAVE_GAME_V34 : public SAVE_GAME_V33
{
char sPlayerName[MAX_PLAYERS][StringSize];
};
static bool serializeSaveGameV34Data(PHYSFS_file* fileHandle, const SAVE_GAME_V34* serializeGame)
{
unsigned int i;
if (!serializeSaveGameV33Data(fileHandle, (const SAVE_GAME_V33*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (PHYSFS_write(fileHandle, serializeGame->sPlayerName[i], StringSize, 1) != 1)
return false;
}
return true;
}
static bool deserializeSaveGameV34Data(PHYSFS_file* fileHandle, SAVE_GAME_V34* serializeGame)
{
unsigned int i;
if (!deserializeSaveGameV33Data(fileHandle, (SAVE_GAME_V33*) serializeGame))
return false;
for (i = 0; i < MAX_PLAYERS; ++i)
{
if (PHYSFS_read(fileHandle, serializeGame->sPlayerName[i], StringSize, 1) != 1)
return false;
}
return true;
}
// First version to utilize (de)serialization API and first to be big-endian (instead of little-endian)
struct SAVE_GAME_V35 : public SAVE_GAME_V34
{
};
static bool serializeSaveGameV35Data(PHYSFS_file* fileHandle, const SAVE_GAME_V35* serializeGame)
{
return serializeSaveGameV34Data(fileHandle, (const SAVE_GAME_V34*) serializeGame);
}
static bool deserializeSaveGameV35Data(PHYSFS_file* fileHandle, SAVE_GAME_V35* serializeGame)
{
return deserializeSaveGameV34Data(fileHandle, (SAVE_GAME_V34*) serializeGame);
}
// Store loaded mods in savegame
struct SAVE_GAME_V38 : public SAVE_GAME_V35
{
char modList[modlist_string_size];
};
static bool serializeSaveGameV38Data(PHYSFS_file* fileHandle, const SAVE_GAME_V38* serializeGame)
{
if (!serializeSaveGameV35Data(fileHandle, (const SAVE_GAME_V35*) serializeGame))
return false;
if (PHYSFS_write(fileHandle, serializeGame->modList, modlist_string_size, 1) != 1)
return false;
return true;
}
static bool deserializeSaveGameV38Data(PHYSFS_file* fileHandle, SAVE_GAME_V38* serializeGame)
{
if (!deserializeSaveGameV35Data(fileHandle, (SAVE_GAME_V35*) serializeGame))
return false;
if (PHYSFS_read(fileHandle, serializeGame->modList, modlist_string_size, 1) != 1)
return false;
return true;
}
// Current save game version
typedef SAVE_GAME_V38 SAVE_GAME;
static bool serializeSaveGameData(PHYSFS_file* fileHandle, const SAVE_GAME* serializeGame)
{
return serializeSaveGameV38Data(fileHandle, (const SAVE_GAME_V38*) serializeGame);
}
static bool deserializeSaveGameData(PHYSFS_file* fileHandle, SAVE_GAME* serializeGame)
{
return deserializeSaveGameV38Data(fileHandle, (SAVE_GAME_V38*) serializeGame);
}
struct DROIDINIT_SAVEHEADER : public GAME_SAVEHEADER
{
UDWORD quantity;
};
struct SAVE_DROIDINIT
{
OBJECT_SAVE_V19;
};
/*
* STRUCTURE Definitions
*/
#define STRUCTURE_SAVE_V2 \
OBJECT_SAVE_V19; \
UBYTE status; \
SDWORD currentBuildPts; \
UDWORD body; \
UDWORD armour; \
UDWORD resistance; \
UDWORD dummy1; \
UDWORD subjectInc; /*research inc or factory prod id*/\
UDWORD timeStarted; \
UDWORD output; \
UDWORD capacity; \
UDWORD quantity
struct SAVE_STRUCTURE_V2
{
STRUCTURE_SAVE_V2;
};
#define STRUCTURE_SAVE_V12 \
STRUCTURE_SAVE_V2; \
UDWORD factoryInc; \
UBYTE loopsPerformed; \
UDWORD powerAccrued; \
UDWORD dummy2; \
UDWORD droidTimeStarted; \
UDWORD timeToBuild; \
UDWORD timeStartHold
struct SAVE_STRUCTURE_V12
{
STRUCTURE_SAVE_V12;
};
#define STRUCTURE_SAVE_V14 \
STRUCTURE_SAVE_V12; \
UBYTE visible[MAX_PLAYERS]
struct SAVE_STRUCTURE_V14
{
STRUCTURE_SAVE_V14;
};
#define STRUCTURE_SAVE_V15 \
STRUCTURE_SAVE_V14; \
char researchName[MAX_SAVE_NAME_SIZE_V19]
struct SAVE_STRUCTURE_V15
{
STRUCTURE_SAVE_V15;
};
#define STRUCTURE_SAVE_V17 \
STRUCTURE_SAVE_V15;\
SWORD currentPowerAccrued
struct SAVE_STRUCTURE_V17
{
STRUCTURE_SAVE_V17;
};
#define STRUCTURE_SAVE_V20 \
OBJECT_SAVE_V20; \
UBYTE status; \
SDWORD currentBuildPts; \
UDWORD body; \
UDWORD armour; \
UDWORD resistance; \
UDWORD dummy1; \
UDWORD subjectInc; /*research inc or factory prod id*/\
UDWORD timeStarted; \
UDWORD output; \
UDWORD capacity; \
UDWORD quantity; \
UDWORD factoryInc; \
UBYTE loopsPerformed; \
UDWORD powerAccrued; \
UDWORD dummy2; \
UDWORD droidTimeStarted; \
UDWORD timeToBuild; \
UDWORD timeStartHold; \
UBYTE visible[MAX_PLAYERS]; \
char researchName[MAX_SAVE_NAME_SIZE]; \
SWORD currentPowerAccrued
struct SAVE_STRUCTURE_V20
{
STRUCTURE_SAVE_V20;
};
#define STRUCTURE_SAVE_V21 \
STRUCTURE_SAVE_V20; \
UDWORD commandId
struct SAVE_STRUCTURE_V21
{
STRUCTURE_SAVE_V21;
};
struct SAVE_STRUCTURE
{
STRUCTURE_SAVE_V21;
};
#define FEATURE_SAVE_V2 \
OBJECT_SAVE_V19
struct SAVE_FEATURE_V2
{
FEATURE_SAVE_V2;
};
#define FEATURE_SAVE_V14 \
FEATURE_SAVE_V2; \
UBYTE visible[MAX_PLAYERS]
struct SAVE_FEATURE_V14
{
FEATURE_SAVE_V14;
};
/***************************************************************************/
/*
* Local Variables
*/
/***************************************************************************/
extern uint32_t unsynchObjID; // unique ID creation thing..
extern uint32_t synchObjID; // unique ID creation thing..
static UDWORD saveGameVersion = 0;
static bool saveGameOnMission = false;
static SAVE_GAME saveGameData;
static UDWORD savedGameTime;
static UDWORD savedObjId;
static SDWORD startX, startY;
static UDWORD width, height;
static UDWORD gameType;
static bool IsScenario;
/***************************************************************************/
/*
* Local ProtoTypes
*/
/***************************************************************************/
static bool gameLoadV7(PHYSFS_file* fileHandle);
static bool gameLoadV(PHYSFS_file* fileHandle, unsigned int version);
static bool writeGameFile(const char* fileName, SDWORD saveType);
static bool writeMapFile(const char* fileName);
static bool loadSaveDroidInit(char *pFileData, UDWORD filesize);
static bool loadSaveDroid(const char *pFileName, DROID **ppsCurrentDroidLists);
static bool loadSaveDroidPointers(const QString &pFileName, DROID **ppsCurrentDroidLists);
static bool writeDroidFile(const char *pFileName, DROID **ppsCurrentDroidLists);
static bool loadSaveStructure(char *pFileData, UDWORD filesize);
static bool loadSaveStructure2(const char *pFileName, STRUCTURE **ppList);
static bool loadSaveStructurePointers(QString filename, STRUCTURE **ppList);
static bool writeStructFile(const char *pFileName);
static bool loadSaveTemplate(const char *pFileName);
static bool writeTemplateFile(const char *pFileName);
static bool loadSaveFeature(char *pFileData, UDWORD filesize);
static bool writeFeatureFile(const char *pFileName);
static bool loadSaveFeature2(const char *pFileName);
static bool writeTerrainTypeMapFile(char *pFileName);
static bool loadSaveCompList(const char *pFileName);
static bool writeCompListFile(const char *pFileName);
static bool loadSaveStructTypeList(const char *pFileName);
static bool writeStructTypeListFile(const char *pFileName);
static bool loadSaveResearch(const char *pFileName);
static bool writeResearchFile(char *pFileName);
static bool loadSaveMessage(const char *pFileName, SWORD levelType);
static bool writeMessageFile(const char *pFileName);
static bool loadSaveStructLimits(const char *pFileName);
static bool writeStructLimitsFile(const char *pFileName);
static bool readFiresupportDesignators(const char *pFileName);
static bool writeFiresupportDesignators(const char *pFileName);
static bool writeScriptState(const char *pFileName);
static bool gameLoad(const char* fileName);
/* set the global scroll values to use for the save game */
static void setMapScroll(void);
static bool gameLoad(const char* fileName);
static char *getSaveStructNameV19(SAVE_STRUCTURE_V17 *psSaveStructure)
{
return(psSaveStructure->name);
}
/*This just loads up the .gam file to determine which level data to set up - split up
so can be called in levLoadData when starting a game from a load save game*/
// -----------------------------------------------------------------------------------------
bool loadGameInit(const char* fileName)
{
if (!gameLoad(fileName))
{
debug(LOG_ERROR, "Corrupted / unsupported savegame file %s, Unable to load!", fileName);
// NOTE: why do we start the game clock on a *failed* load?
// Start the game clock
gameTimeStart();
return false;
}
return true;
}
// -----------------------------------------------------------------------------------------
// Load a file from a save game into the psx.
// This is divided up into 2 parts ...
//
// if it is a level loaded up from CD then UserSaveGame will by false
// UserSaveGame ... Extra stuff to load after scripts
bool loadMissionExtras(const char *pGameToLoad, SWORD levelType)
{
char aFileName[256];
UDWORD fileExten;
strcpy(aFileName, pGameToLoad);
fileExten = strlen(pGameToLoad) - 3;
aFileName[fileExten - 1] = '\0';
strcat(aFileName, "/");
if (saveGameVersion >= VERSION_11)
{
//if user save game then load up the messages AFTER any droids or structures are loaded
if (gameType == GTYPE_SAVE_START || gameType == GTYPE_SAVE_MIDMISSION)
{
//load in the message list file
aFileName[fileExten] = '\0';
strcat(aFileName, "messtate.ini");
if (!loadSaveMessage(aFileName, levelType))
{
debug(LOG_ERROR, "Failed to load mission extras from %s", aFileName);
return false;
}
}
}
return true;
}
static void sanityUpdate()
{
scrvUpdateBasePointers(); // update the script object pointers
for (int player = 0; player < game.maxPlayers; player++)
{
for (DROID *psDroid = apsDroidLists[player]; psDroid; psDroid = psDroid->psNext)
{
orderCheckList(psDroid);
actionSanity(psDroid);
}
}
}
static void getIniBaseObject(WzConfig &ini, QString const &key, BASE_OBJECT *&object)
{
object = NULL;
if (ini.contains(key + "/id"))
{
int tid = ini.value(key + "/id", -1).toInt();
int tplayer = ini.value(key + "/player", -1).toInt();
OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value(key + "/type", 0).toInt();
ASSERT_OR_RETURN(, tid >= 0 && tplayer >= 0, "Bad ID");
object = getBaseObjFromData(tid, tplayer, ttype);
ASSERT(object != NULL, "Failed to find target");
}
}
static void getIniStructureStats(WzConfig &ini, QString const &key, STRUCTURE_STATS *&stats)
{
stats = NULL;
if (ini.contains(key))
{
QString statName = ini.value(key).toString();
int tid = getStructStatFromName(statName.toUtf8().constData());
ASSERT_OR_RETURN(, tid >= 0, "Target stats not found %s", statName.toUtf8().constData());
stats = &asStructureStats[tid];
}
}
static void getIniDroidOrder(WzConfig &ini, QString const &key, DroidOrder &order)
{
order.type = (DroidOrderType)ini.value(key + "/type", DORDER_NONE).toInt();
order.pos = ini.vector2i(key + "/pos");
order.pos2 = ini.vector2i(key + "/pos2");
order.direction = ini.value(key + "/direction").toInt();
getIniBaseObject(ini, key + "/obj", order.psObj);
getIniStructureStats(ini, key + "/stats", order.psStats);
}
static void setIniBaseObject(WzConfig &ini, QString const &key, BASE_OBJECT const *object)
{
if (object != NULL && object->died <= 1)
{
ini.setValue(key + "/id", object->id);
ini.setValue(key + "/player", object->player);
ini.setValue(key + "/type", object->type);
#ifdef DEBUG
//ini.setValue(key + "/debugfunc", QString::fromUtf8(psCurr->targetFunc));
//ini.setValue(key + "/debugline", psCurr->targetLine);
#endif
}
}
static void setIniStructureStats(WzConfig &ini, QString const &key, STRUCTURE_STATS const *stats)
{
if (stats != NULL)
{
ini.setValue(key, stats->id);
}
}
static void setIniDroidOrder(WzConfig &ini, QString const &key, DroidOrder const &order)
{
ini.setValue(key + "/type", order.type);
ini.setVector2i(key + "/pos", order.pos);
ini.setVector2i(key + "/pos2", order.pos2);
ini.setValue(key + "/direction", order.direction);
setIniBaseObject(ini, key + "/obj", order.psObj);
setIniStructureStats(ini, key + "/stats", order.psStats);
}
// -----------------------------------------------------------------------------------------
// UserSaveGame ... this is true when you are loading a players save game
bool loadGame(const char *pGameToLoad, bool keepObjects, bool freeMem, bool UserSaveGame)
{
QMap<QString, DROID **> droidMap;
QMap<QString, STRUCTURE **> structMap;
char aFileName[256];
UDWORD fileExten, fileSize;
char *pFileData = NULL;
UDWORD player, inc, i, j;
DROID *psCurr;
UWORD missionScrollMinX = 0, missionScrollMinY = 0,
missionScrollMaxX = 0, missionScrollMaxY = 0;
/* Stop the game clock */
gameTimeStop();
if ((gameType == GTYPE_SAVE_START) ||
(gameType == GTYPE_SAVE_MIDMISSION))
{
gameTimeReset(savedGameTime);//added 14 may 98 JPS to solve kev's problem with no firing droids
//need to reset the event timer too - AB 14/01/99
eventTimeReset(savedGameTime/SCR_TICKRATE);
}
/* Clear all the objects off the map and free up the map memory */
proj_FreeAllProjectiles(); //always clear this
if (freeMem)
{
//clear out the audio
audio_StopAll();
freeAllDroids();
freeAllStructs();
freeAllFeatures();
//clear all the messages?
releaseAllProxDisp();
}
if (!keepObjects)
{
//initialise the lists
for (player = 0; player < MAX_PLAYERS; player++)
{
apsDroidLists[player] = NULL;
apsStructLists[player] = NULL;
apsFeatureLists[player] = NULL;
apsFlagPosLists[player] = NULL;
//clear all the messages?
apsProxDisp[player] = NULL;
apsSensorList[0] = NULL;
apsExtractorLists[player] = NULL;
}
apsOilList[0] = NULL;
initFactoryNumFlag();
}
if (UserSaveGame)//always !keepObjects
{
//initialise the lists
for (player = 0; player < MAX_PLAYERS; player++)
{
apsLimboDroids[player] = NULL;
mission.apsDroidLists[player] = NULL;
mission.apsStructLists[player] = NULL;
mission.apsFeatureLists[player] = NULL;
mission.apsFlagPosLists[player] = NULL;
mission.apsExtractorLists[player] = NULL;
}
mission.apsOilList[0] = NULL;
mission.apsSensorList[0] = NULL;
}
if (saveGameVersion >= VERSION_11)
{
//camera position
disp3d_setView(&(saveGameData.currentPlayerPos));
}
else
{
disp3d_resetView();
}
//Stuff added after level load to avoid being reset or initialised during load
if (UserSaveGame)//always !keepObjects
{
if (saveGameVersion >= VERSION_12)
{
mission.startTime = saveGameData.missionTime;
}
//set the scroll varaibles
startX = saveGameData.ScrollMinX;
startY = saveGameData.ScrollMinY;
width = saveGameData.ScrollMaxX - saveGameData.ScrollMinX;
height = saveGameData.ScrollMaxY - saveGameData.ScrollMinY;
gameType = saveGameData.GameType;
if (saveGameVersion >= VERSION_14)
{
//mission data
mission.time = saveGameData.missionOffTime;
mission.ETA = saveGameData.missionETA;
mission.homeLZ_X = saveGameData.missionHomeLZ_X;
mission.homeLZ_Y = saveGameData.missionHomeLZ_Y;
mission.playerX = saveGameData.missionPlayerX;
mission.playerY = saveGameData.missionPlayerY;
//mission data
for (player = 0; player < MAX_PLAYERS; player++)
{
aDefaultSensor[player] = saveGameData.aDefaultSensor[player];
aDefaultECM[player] = saveGameData.aDefaultECM[player];
aDefaultRepair[player] = saveGameData.aDefaultRepair[player];
//check for self repair having been set
if (aDefaultRepair[player] != 0
&& asRepairStats[aDefaultRepair[player]].location == LOC_DEFAULT)
{
enableSelfRepair((UBYTE)player);
}
mission.iTranspEntryTileX[player] = saveGameData.iTranspEntryTileX[player];
mission.iTranspEntryTileY[player] = saveGameData.iTranspEntryTileY[player];
mission.iTranspExitTileX[player] = saveGameData.iTranspExitTileX[player];
mission.iTranspExitTileY[player] = saveGameData.iTranspExitTileY[player];
}
}
if (saveGameVersion >= VERSION_15)//V21
{
offWorldKeepLists = saveGameData.offWorldKeepLists;
setRubbleTile(saveGameData.RubbleTile);
setUnderwaterTile(saveGameData.WaterTile);
}
if (saveGameVersion >= VERSION_19)//V21
{
for(i=0; i<MAX_PLAYERS; i++)
{
alliancebits[i] |= 0;
for(j=0; j<MAX_PLAYERS; j++)
{
alliances[i][j] = saveGameData.alliances[i][j];
if (i == j) alliances[i][j] = ALLIANCE_FORMED; // hack to fix old savegames
if (alliancesSharedVision(game.alliance) && alliances[i][j] == ALLIANCE_FORMED)
{
alliancebits[i] |= 1 << j;
}
}
}
for(i=0; i<MAX_PLAYERS; i++)
{
setPlayerColour(i,saveGameData.playerColour[i]);
}
SetRadarZoom(saveGameData.radarZoom);
}
if (saveGameVersion >= VERSION_20)//V21
{
setDroidsToSafetyFlag(saveGameData.bDroidsToSafetyFlag);
for (inc = 0; inc < MAX_PLAYERS; inc++)
{
memcpy(&asVTOLReturnPos[inc], &(saveGameData.asVTOLReturnPos[inc]), sizeof(Vector2i));
}
}
if (saveGameVersion >= VERSION_22)//V22
{
for (inc = 0; inc < MAX_PLAYERS; inc++)
{
memcpy(&asRunData[inc], &(saveGameData.asRunData[inc]), sizeof(RUN_DATA));
}
}
if (saveGameVersion >= VERSION_24)//V24
{
missionSetReinforcementTime(saveGameData.reinforceTime);
// horrible hack to catch savegames that were saving garbage into these fields
if (saveGameData.bPlayCountDown <= 1)
{
setPlayCountDown(saveGameData.bPlayCountDown);
}
if (saveGameData.bPlayerHasWon <= 1)
{
setPlayerHasWon(saveGameData.bPlayerHasWon);
}
if (saveGameData.bPlayerHasLost <= 1)
{
setPlayerHasLost(saveGameData.bPlayerHasLost);
}
}
if (saveGameVersion >= VERSION_27)//V27
{
for (player = 0; player < MAX_PLAYERS; player++)
{
for (inc = 0; inc < MAX_RECYCLED_DROIDS; inc++)
{
aDroidExperience[player][inc] = saveGameData.awDroidExperience[player][inc];
}
}
}
else
{
for (player = 0; player < MAX_PLAYERS; player++)
{
for (inc = 0; inc < MAX_RECYCLED_DROIDS; inc++)
{
aDroidExperience[player][inc] = saveGameData.aDroidExperience[player][inc];
}
}
}
if (saveGameVersion >= VERSION_30)
{
scrGameLevel = saveGameData.scrGameLevel;
bExtraVictoryFlag = saveGameData.bExtraVictoryFlag;
bExtraFailFlag = saveGameData.bExtraFailFlag;
bTrackTransporter = saveGameData.bTrackTransporter;
}
//extra code added for the first patch (v1.1) to save out if mission time is not being counted
if (saveGameVersion >= VERSION_31)
{
//mission data
mission.cheatTime = saveGameData.missionCheatTime;
}
// skirmish saves.
if (saveGameVersion >= VERSION_33)
{
PLAYERSTATS playerStats;
bool scav = game.scavengers;
game = saveGameData.sGame;
game.scavengers = scav; // ok, so this is butt ugly. but i'm just getting inspiration from the rest of the code around here. ok? - per
productionPlayer= selectedPlayer;
bMultiPlayer = saveGameData.multiPlayer;
bMultiMessages = bMultiPlayer;
cmdDroidMultiExpBoost(true);
NetPlay.bComms = (saveGameData.sNetPlay).bComms;
for (i = 0; i < MAX_PLAYERS; i++)
{
NetPlay.players[i].ai = saveGameData.sNetPlay.players[i].ai;
NetPlay.players[i].difficulty = saveGameData.sNetPlay.players[i].difficulty;
strcpy(NetPlay.players[i].name, saveGameData.sNetPlay.players[i].name);
if (saveGameData.sGame.skDiff[i] == UBYTE_MAX || (game.type == CAMPAIGN && i == 0))
{
NetPlay.players[i].allocated = true;
}
else
{
NetPlay.players[i].allocated = false;
}
}
if(bMultiPlayer)
{
loadMultiStats(saveGameData.sPName,&playerStats); // stats stuff
setMultiStats(selectedPlayer, playerStats, false);
setMultiStats(selectedPlayer, playerStats, true);
}
}
}
/* Get human and AI players names */
if (saveGameVersion >= VERSION_34)
{
for(i=0;i<MAX_PLAYERS;i++)
{
(void)setPlayerName(i, saveGameData.sPlayerName[i]);
}
}
//clear the player Power structs
if ((gameType != GTYPE_SAVE_START) && (gameType != GTYPE_SAVE_MIDMISSION) &&
(!keepObjects))
{
clearPlayerPower();
}
//before loading the data - turn power off so don't get any power low warnings
powerCalculated = false;
/* Load in the chosen file data */
strcpy(aFileName, pGameToLoad);
fileExten = strlen(aFileName) - 3; // hack - !
aFileName[fileExten - 1] = '\0';
strcat(aFileName, "/");
//the terrain type WILL only change with Campaign changes (well at the moment!)
if (gameType != GTYPE_SCENARIO_EXPAND || UserSaveGame)
{
//load in the terrain type map
aFileName[fileExten] = '\0';
strcat(aFileName, "ttypes.ttp");
/* Load in the chosen file data */
pFileData = fileLoadBuffer;
if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
//load the terrain type data
if (pFileData)
{
if (!loadTerrainTypeMap(pFileData, fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
}
//load up the Droid Templates BEFORE any structures are loaded
if (IsScenario==false)
{
{
DROID_TEMPLATE *pTemplate, *pNext;
for(pTemplate = apsDroidTemplates[0]; pTemplate != NULL;
pTemplate = pNext)
{
pNext = pTemplate->psNext;
delete pTemplate;
}
apsDroidTemplates[0] = NULL;
}
// In Multiplayer, clear templates out first.....
if (bMultiPlayer)
{
for(inc=0;inc<MAX_PLAYERS;inc++)
{
while(apsDroidTemplates[inc]) // clear the old template out.
{
DROID_TEMPLATE *psTempl;
psTempl = apsDroidTemplates[inc]->psNext;
delete apsDroidTemplates[inc];
apsDroidTemplates[inc] = psTempl;
}
}
}
localTemplates.clear();
//load in the templates
aFileName[fileExten] = '\0';
strcat(aFileName, "templates.ini");
//load the data into apsTemplates
if (!loadSaveTemplate(aFileName))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
if (saveGameOnMission && UserSaveGame)
{
//the scroll limits for the mission map have already been written
if (saveGameVersion >= VERSION_29)
{
missionScrollMinX = (UWORD)mission.scrollMinX;
missionScrollMinY = (UWORD)mission.scrollMinY;
missionScrollMaxX = (UWORD)mission.scrollMaxX;
missionScrollMaxY = (UWORD)mission.scrollMaxY;
}
//load the map and the droids then swap pointers
//load in the map file
aFileName[fileExten] = '\0';
strcat(aFileName, "mission.map");
if (!mapLoad(aFileName, false))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
return false;
}
//load in the visibility file
aFileName[fileExten] = '\0';
strcat(aFileName, "misvis.bjo");
// Load in the visibility data from the chosen file
if (!readVisibilityData(aFileName))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
// reload the objects that were in the mission list
//load in the features -do before the structures
aFileName[fileExten] = '\0';
strcat(aFileName, "mfeature.ini");
//load the data into apsFeatureLists
if (!loadSaveFeature2(aFileName))
{
aFileName[fileExten] = '\0';
strcat(aFileName, "mfeat.bjo");
/* Load in the chosen file data */
pFileData = fileLoadBuffer;
if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
if (!loadSaveFeature(pFileData, fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
initStructLimits();
aFileName[fileExten] = '\0';
strcat(aFileName, "mstruct.ini");
//load in the mission structures
if (!loadSaveStructure2(aFileName, apsStructLists))
{
aFileName[fileExten] = '\0';
strcat(aFileName, "mstruct.bjo");
/* Load in the chosen file data */
pFileData = fileLoadBuffer;
if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
//load the data into apsStructLists
if (!loadSaveStructure(pFileData, fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
else
{
structMap.insert(aFileName, mission.apsStructLists); // we swap pointers below
}
// load in the mission droids, if any
aFileName[fileExten] = '\0';
strcat(aFileName, "mdroid.ini");
if (loadSaveDroid(aFileName, apsDroidLists))
{
droidMap.insert(aFileName, mission.apsDroidLists); // need to swap here to read correct list later
}
/* after we've loaded in the units we need to redo the orientation because
* the direction may have been saved - we need to do it outside of the loop
* whilst the current map is valid for the units
*/
for (player = 0; player < MAX_PLAYERS; ++player)
{
for (psCurr = apsDroidLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
{
if (psCurr->droidType != DROID_PERSON
// && psCurr->droidType != DROID_CYBORG
&& !cyborgDroid(psCurr)
&& (psCurr->droidType != DROID_TRANSPORTER && psCurr->droidType != DROID_SUPERTRANSPORTER)
&& psCurr->pos.x != INVALID_XY)
{
updateDroidOrientation(psCurr);
}
}
}
swapMissionPointers();
//once the mission map has been loaded reset the mission scroll limits
if (saveGameVersion >= VERSION_29)
{
mission.scrollMinX = missionScrollMinX;
mission.scrollMinY = missionScrollMinY;
mission.scrollMaxX = missionScrollMaxX;
mission.scrollMaxY = missionScrollMaxY;
}
}
//if Campaign Expand then don't load in another map
if (gameType != GTYPE_SCENARIO_EXPAND)
{
psMapTiles = NULL;
//load in the map file
aFileName[fileExten] = '\0';
strcat(aFileName, "game.map");
if (!mapLoad(aFileName, false))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
return(false);
}
}
// FIXME THIS FILE IS A HUGE MESS, this code should probably appear at another position...
if (saveGameVersion > VERSION_12)
{
//if user save game then load up the FX
if ((gameType == GTYPE_SAVE_START) ||
(gameType == GTYPE_SAVE_MIDMISSION))
{
//load in the message list file
aFileName[fileExten] = '\0';
strcat(aFileName, "fxstate.ini");
// load the fx data from the file
if (!readFXData(aFileName))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
}
//save game stuff added after map load
if (saveGameVersion >= VERSION_16)
{
for (inc = 0; inc < MAX_NOGO_AREAS; inc++)
{
setNoGoArea(saveGameData.sLandingZone[inc].x1, saveGameData.sLandingZone[inc].y1,
saveGameData.sLandingZone[inc].x2, saveGameData.sLandingZone[inc].y2, (UBYTE)inc);
}
}
//adjust the scroll range for the new map or the expanded map
setMapScroll();
//if user save game then load up the research BEFORE any droids or structures are loaded
if (gameType == GTYPE_SAVE_START || gameType == GTYPE_SAVE_MIDMISSION)
{
//load in the research list file
aFileName[fileExten] = '\0';
strcat(aFileName, "resstate.ini");
if (!loadSaveResearch(aFileName))
{
debug(LOG_ERROR, "Failed to load research data from %s", aFileName);
goto error;
}
}
if (IsScenario)
{
//load in the droids
aFileName[fileExten] = '\0';
strcat(aFileName, "droid.ini");
//load the data into apsDroidLists
if (loadSaveDroid(aFileName, apsDroidLists))
{
debug(LOG_SAVE, "Loaded new style droids");
droidMap.insert(aFileName, apsDroidLists); // load pointers later
}
else
{
// load in the old style droid initialisation file
aFileName[fileExten] = '\0';
strcat(aFileName, "dinit.bjo");
/* Load in the chosen file data */
pFileData = fileLoadBuffer;
if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
if (!loadSaveDroidInit(pFileData,fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
debug(LOG_SAVE, "Loaded old style droids");
}
}
else
{
//load in the droids
aFileName[fileExten] = '\0';
strcat(aFileName, "droid.ini");
//load the data into apsDroidLists
if (!loadSaveDroid(aFileName, apsDroidLists))
{
debug(LOG_ERROR, "failed to load %s", aFileName);
goto error;
}
droidMap.insert(aFileName, apsDroidLists); // load pointers later
/* after we've loaded in the units we need to redo the orientation because
* the direction may have been saved - we need to do it outside of the loop
* whilst the current map is valid for the units
*/
for (player = 0; player < MAX_PLAYERS; ++player)
{
for (psCurr = apsDroidLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
{
if (psCurr->droidType != DROID_PERSON
&& !cyborgDroid(psCurr)
&& (psCurr->droidType != DROID_TRANSPORTER && psCurr->droidType != DROID_SUPERTRANSPORTER)
&& psCurr->pos.x != INVALID_XY)
{
updateDroidOrientation(psCurr);
}
}
}
if (!saveGameOnMission)
{
//load in the mission droids
aFileName[fileExten] = '\0';
strcat(aFileName, "mdroid.ini");
// load the data into mission.apsDroidLists, if any
if (loadSaveDroid(aFileName, mission.apsDroidLists))
{
droidMap.insert(aFileName, mission.apsDroidLists);
}
}
}
if (saveGameVersion >= VERSION_23)
{
// load in the limbo droids, if any
aFileName[fileExten] = '\0';
strcat(aFileName, "limbo.ini");
if (loadSaveDroid(aFileName, apsLimboDroids))
{
droidMap.insert(aFileName, apsLimboDroids);
}
}
//load in the features -do before the structures
aFileName[fileExten] = '\0';
strcat(aFileName, "feature.ini");
if (!loadSaveFeature2(aFileName))
{
aFileName[fileExten] = '\0';
strcat(aFileName, "feat.bjo");
/* Load in the chosen file data */
pFileData = fileLoadBuffer;
if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
//load the data into apsFeatureLists
if (!loadSaveFeature(pFileData, fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
//load in the structures
initStructLimits();
aFileName[fileExten] = '\0';
strcat(aFileName, "struct.ini");
if (!loadSaveStructure2(aFileName, apsStructLists))
{
aFileName[fileExten] = '\0';
strcat(aFileName, "struct.bjo");
/* Load in the chosen file data */
pFileData = fileLoadBuffer;
if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
//load the data into apsStructLists
if (!loadSaveStructure(pFileData, fileSize))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
else
{
structMap.insert(aFileName, apsStructLists);
}
//if user save game then load up the current level for structs and components
if (gameType == GTYPE_SAVE_START || gameType == GTYPE_SAVE_MIDMISSION)
{
//load in the component list file
aFileName[fileExten] = '\0';
strcat(aFileName, "complist.ini");
if (!loadSaveCompList(aFileName))
{
debug(LOG_ERROR, "failed to load %s", aFileName);
goto error;
}
//load in the structure type list file
aFileName[fileExten] = '\0';
strcat(aFileName, "strtype.ini");
if (!loadSaveStructTypeList(aFileName))
{
debug(LOG_ERROR, "failed to load %s", aFileName);
goto error;
}
}
if (saveGameVersion >= VERSION_11)
{
//if user save game then load up the Visibility
if ((gameType == GTYPE_SAVE_START) ||
(gameType == GTYPE_SAVE_MIDMISSION))
{
//load in the visibility file
aFileName[fileExten] = '\0';
strcat(aFileName, "visstate.bjo");
// Load in the visibility data from the chosen file
if (!readVisibilityData(aFileName))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
}
if (saveGameVersion >= VERSION_16)
{
//if user save game then load up the FX
if ((gameType == GTYPE_SAVE_START) ||
(gameType == GTYPE_SAVE_MIDMISSION))
{
aFileName[fileExten] = '\0';
strcat(aFileName, "score.ini");
// Load the fx data from the chosen file
if (!readScoreData(aFileName))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
}
if (saveGameVersion >= VERSION_21)
{
//rebuild the apsCommandDesignation AFTER all droids and structures are loaded
if ((gameType == GTYPE_SAVE_START) ||
(gameType == GTYPE_SAVE_MIDMISSION))
{
//load in the command list file
aFileName[fileExten] = '\0';
strcat(aFileName, "firesupport.ini");
if (!readFiresupportDesignators(aFileName))
{
debug(LOG_ERROR, "Failed with: %s", aFileName);
goto error;
}
}
}
if ((saveGameVersion >= VERSION_15) && UserSaveGame)
{
//load in the mission structures
aFileName[fileExten] = '\0';
strcat(aFileName, "limits.ini");
//load the data into apsStructLists
if (!loadSaveStructLimits(aFileName))
{
debug(LOG_ERROR, "failed to load %s", aFileName);
goto error;
}
//set up the structure Limits
setCurrentStructQuantity(false);
}
else
{
//set up the structure Limits
setCurrentStructQuantity(true);
}
//check that delivery points haven't been put down in invalid location
checkDeliveryPoints(saveGameVersion);
//turn power on for rest of game
powerCalculated = true;
if (!keepObjects)//only reset the pointers if they were set
{
// Reset the object pointers in the droid target lists
QStringList keys = droidMap.keys();
for (int i = 0; i < keys.size(); i++)
{
QString key = keys.at(i);
DROID **pList = droidMap.value(key);
loadSaveDroidPointers(key, pList);
}
keys = structMap.keys();
for (int i = 0; i < keys.size(); i++)
{
QString key = keys.at(i);
STRUCTURE **pList = structMap.value(key);
loadSaveStructurePointers(key, pList);
}
}
// Load labels
aFileName[fileExten] = '\0';
strcat(aFileName, "labels.ini");
loadLabels(aFileName);
//if user save game then reset the time - THIS SETS BOTH TIMERS - BEWARE IF YOU USE IT
if ((gameType == GTYPE_SAVE_START) ||
(gameType == GTYPE_SAVE_MIDMISSION))
{
ASSERT( gameTime == savedGameTime,"loadGame; game time modified during load" );
gameTimeReset(savedGameTime);//added 14 may 98 JPS to solve kev's problem with no firing droids
//need to reset the event timer too - AB 14/01/99
eventTimeReset(savedGameTime/SCR_TICKRATE);
//reset the objId for new objects
if (saveGameVersion >= VERSION_17)
{
unsynchObjID = (savedObjId + 1)/2; // Make new object ID start at savedObjId*8.
synchObjID = savedObjId*4; // Make new object ID start at savedObjId*8.
}
}
//check the research button isn't flashing unnecessarily
//cancel first
stopReticuleButtonFlash(IDRET_RESEARCH);
//then see if needs to be set
intNotifyResearchButton(0);
//set up the mission countdown flag
setMissionCountDown();
/* Start the game clock */
gameTimeStart();
//check if limbo_expand mission has changed to an expand mission for user save game (mid-mission)
if (gameType == GTYPE_SAVE_MIDMISSION && missionLimboExpand())
{
/* when all the units have moved from the mission.apsDroidList then the
* campaign has been reset to an EXPAND type - OK so there should have
* been another flag to indicate this state has changed but its late in
* the day excuses...excuses...excuses
*/
if (mission.apsDroidLists[selectedPlayer] == NULL)
{
//set the mission type
startMissionSave(LDS_EXPAND);
}
}
//set this if come into a save game mid mission
if (gameType == GTYPE_SAVE_MIDMISSION)
{
setScriptWinLoseVideo(PLAY_NONE);
}
//need to clear before setting up
clearMissionWidgets();
//put any widgets back on for the missions
resetMissionWidgets();
debug(LOG_NEVER, "Done loading");
return true;
error:
debug(LOG_ERROR, "Game load failed for %s, FS:%s, params=%s,%s,%s", pGameToLoad, PHYSFS_getRealDir(pGameToLoad),
keepObjects ? "true":"false", freeMem ? "true":"false", UserSaveGame ? "true":"false");
/* Clear all the objects off the map and free up the map memory */
freeAllDroids();
freeAllStructs();
freeAllFeatures();
droidTemplateShutDown();
psMapTiles = NULL;
/* Start the game clock */
gameTimeStart();
return false;
}
// -----------------------------------------------------------------------------------------
// Modified by AlexL , now takes a filename, with no popup....
bool saveGame(char *aFileName, GAME_TYPE saveType)
{
UDWORD fileExtension;
DROID *psDroid, *psNext;
char CurrentFileName[PATH_MAX] = {'\0'};
triggerEvent(TRIGGER_GAME_SAVING);
ASSERT_OR_RETURN(false, aFileName && strlen(aFileName) > 4, "Bad savegame filename");
sstrcpy(CurrentFileName, aFileName);
debug(LOG_WZ, "saveGame: %s", CurrentFileName);
fileExtension = strlen(CurrentFileName) - 3;
gameTimeStop();
sanityUpdate();
/* Write the data to the file */
if (!writeGameFile(CurrentFileName, saveType))
{
debug(LOG_ERROR, "saveGame: writeGameFile(\"%s\") failed", CurrentFileName);
goto error;
}
//remove the file extension
CurrentFileName[strlen(CurrentFileName) - 4] = '\0';
//create dir will fail if directory already exists but don't care!
(void) PHYSFS_mkdir(CurrentFileName);
//save the map file
strcat(CurrentFileName, "/game.map");
/* Write the data to the file */
if (!writeMapFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeMapFile(\"%s\") failed", CurrentFileName);
goto error;
}
// Save some game info
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "gameinfo.ini");
writeGameInfo(CurrentFileName);
// Save labels
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "labels.ini");
writeLabels(CurrentFileName);
//create the droids filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "droid.ini");
/*Write the current droid lists to the file*/
if (!writeDroidFile(CurrentFileName, apsDroidLists))
{
debug(LOG_ERROR, "writeDroidFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the structures filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "struct.ini");
/*Write the data to the file*/
if (!writeStructFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeStructFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the templates filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "templates.ini");
/*Write the data to the file*/
if (!writeTemplateFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeTemplateFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the features filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "feature.ini");
/*Write the data to the file*/
if (!writeFeatureFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeFeatureFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the terrain types filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "ttypes.ttp");
/*Write the data to the file*/
if (!writeTerrainTypeMapFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeTerrainTypeMapFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the strucutLimits filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "limits.ini");
/*Write the data to the file*/
if (!writeStructLimitsFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeStructLimitsFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the component lists filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "complist.ini");
/*Write the data to the file*/
if (!writeCompListFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeCompListFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the structure type lists filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "strtype.ini");
/*Write the data to the file*/
if (!writeStructTypeListFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeStructTypeListFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the research filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "resstate.ini");
/*Write the data to the file*/
if (!writeResearchFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeResearchFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the message filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "messtate.ini");
/*Write the data to the file*/
if (!writeMessageFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeMessageFile(\"%s\") failed", CurrentFileName);
goto error;
}
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "visstate.bjo");
/*Write the data to the file*/
if (!writeVisibilityData(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeVisibilityData(\"%s\") failed", CurrentFileName);
goto error;
}
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "fxstate.ini");
/*Write the data to the file*/
if (!writeFXData(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeFXData(\"%s\") failed", CurrentFileName);
goto error;
}
//added at V15 save
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "score.ini");
/*Write the data to the file*/
if (!writeScoreData(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeScoreData(\"%s\") failed", CurrentFileName);
goto error;
}
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "firesupport.ini");
/*Write the data to the file*/
if (!writeFiresupportDesignators(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeFiresupportDesignators(\"%s\") failed", CurrentFileName);
goto error;
}
// save the script state if necessary
if (saveType == GTYPE_SAVE_MIDMISSION)
{
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "scriptstate.es");
/*Write the data to the file*/
if (!writeScriptState(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeScriptState(\"%s\") failed", CurrentFileName);
goto error;
}
}
//create the droids filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "mdroid.ini");
/*Write the swapped droid lists to the file*/
if (!writeDroidFile(CurrentFileName, mission.apsDroidLists))
{
debug(LOG_ERROR, "writeDroidFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the limbo filename
//clear the list
if (saveGameVersion < VERSION_25)
{
for (psDroid = apsLimboDroids[selectedPlayer]; psDroid != NULL; psDroid = psNext)
{
psNext = psDroid->psNext;
//limbo list invalidate XY
psDroid->pos.x = INVALID_XY;
psDroid->pos.y = INVALID_XY;
//this is mainly for VTOLs
setSaveDroidBase(psDroid, NULL);
psDroid->cluster = 0;
orderDroid(psDroid, DORDER_STOP, ModeImmediate);
}
}
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "limbo.bjo");
/*Write the swapped droid lists to the file*/
if (!writeDroidFile(CurrentFileName, apsLimboDroids))
{
debug(LOG_ERROR, "saveGame: writeDroidFile(\"%s\") failed", CurrentFileName);
goto error;
}
if (saveGameOnMission )
{
//mission save swap the mission pointers and save the changes
swapMissionPointers();
//now save the map and droids
//save the map file
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "mission.map");
/* Write the data to the file */
if (!writeMapFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeMapFile(\"%s\") failed", CurrentFileName);
goto error;
}
//save the map file
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "misvis.bjo");
/* Write the data to the file */
if (!writeVisibilityData(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeVisibilityData(\"%s\") failed", CurrentFileName);
goto error;
}
//create the structures filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "mstruct.ini");
/*Write the data to the file*/
if (!writeStructFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeStructFile(\"%s\") failed", CurrentFileName);
goto error;
}
//create the features filename
CurrentFileName[fileExtension] = '\0';
strcat(CurrentFileName, "mfeature.ini");
/*Write the data to the file*/
if (!writeFeatureFile(CurrentFileName))
{
debug(LOG_ERROR, "saveGame: writeFeatureFile(\"%s\") failed", CurrentFileName);
goto error;
}
//mission save swap back so we can restart the game
swapMissionPointers();
}
// strip the last filename
CurrentFileName[fileExtension-1] = '\0';
/* Start the game clock */
triggerEvent(TRIGGER_GAME_SAVED);
gameTimeStart();
return true;
error:
/* Start the game clock */
gameTimeStart();
return false;
}
// -----------------------------------------------------------------------------------------
static bool writeMapFile(const char* fileName)
{
char* pFileData = NULL;
UDWORD fileSize;
/* Get the save data */
bool status = mapSave(&pFileData, &fileSize);
if (status)
{
/* Write the data to the file */
status = saveFile(fileName, pFileData, fileSize);
}
if (pFileData != NULL)
{
free(pFileData);
}
return status;
}
// -----------------------------------------------------------------------------------------
static bool gameLoad(const char* fileName)
{
GAME_SAVEHEADER fileHeader;
PHYSFS_file* fileHandle = openLoadFile(fileName, true);
if (!fileHandle)
{
// Failure to open the file is a failure to load the specified savegame
return true;
}
initLoadingScreen(true);
debug(LOG_WZ, "gameLoad");
// Read the header from the file
if (!deserializeSaveGameHeader(fileHandle, &fileHeader))
{
debug(LOG_ERROR, "gameLoad: error while reading header from file (%s): %s", fileName, PHYSFS_getLastError());
PHYSFS_close(fileHandle);
return false;
}
// Check the header to see if we've been given a file of the right type
if (fileHeader.aFileType[0] != 'g'
|| fileHeader.aFileType[1] != 'a'
|| fileHeader.aFileType[2] != 'm'
|| fileHeader.aFileType[3] != 'e')
{
debug(LOG_ERROR, "gameLoad: Weird file type found? Has header letters - '%c' '%c' '%c' '%c' (should be 'g' 'a' 'm' 'e')",
fileHeader.aFileType[0],
fileHeader.aFileType[1],
fileHeader.aFileType[2],
fileHeader.aFileType[3]);
PHYSFS_close(fileHandle);
return false;
}
debug(LOG_NEVER, "gl .gam file is version %u\n", fileHeader.version);
// Prior to getting here, the directory structure has been set to whereever the
// map or savegame is loaded from, so we will get the right ruleset file.
if (!PHYSFS_exists("ruleset.ini"))
{
debug(LOG_ERROR, "ruleset.ini not found! User generated data will not work.");
memset(rulesettag, 0, sizeof(rulesettag));
}
else
{
WzConfig ruleset("ruleset.ini", WzConfig::ReadOnly);
if (!ruleset.contains("ruleset/tag"))
{
debug(LOG_ERROR, "ruleset tag not found in ruleset.ini!"); // fall-through
}
QString tag = ruleset.value("ruleset/tag", "[]").toString();
sstrcpy(rulesettag, tag.toUtf8().constData());
if (strspn(rulesettag, "abcdefghijklmnopqrstuvwxyz") != strlen(rulesettag)) // for safety
{
debug(LOG_ERROR, "ruleset.ini userdata tag contains invalid characters!");
debug(LOG_ERROR, "User generated data will not work.");
memset(rulesettag, 0, sizeof(rulesettag));
}
else
{
char tmppath[PATH_MAX];
ssprintf(tmppath, "userdata/%s", rulesettag);
PHYSFS_mkdir(tmppath);
}
}
//set main version Id from game file
saveGameVersion = fileHeader.version;
debug(LOG_SAVE, "file version is %u, (%s)", fileHeader.version, fileName);
/* Check the file version */
if (fileHeader.version < VERSION_7)
{
debug(LOG_ERROR, "gameLoad: unsupported save format version %d", fileHeader.version);
PHYSFS_close(fileHandle);
return false;
}
else if (fileHeader.version < VERSION_9)
{
bool retVal = gameLoadV7(fileHandle);
PHYSFS_close(fileHandle);
return retVal;
}
else if (fileHeader.version <= CURRENT_VERSION_NUM)
{
bool retVal = gameLoadV(fileHandle, fileHeader.version);
PHYSFS_close(fileHandle);
return retVal;
}
else
{
debug(LOG_ERROR, "Unsupported main save format version %u", fileHeader.version);
PHYSFS_close(fileHandle);
return false;
}
}
// Fix endianness of a savegame
static void endian_SaveGameV(SAVE_GAME* psSaveGame, UDWORD version)
{
unsigned int i, j;
/* SAVE_GAME is GAME_SAVE_V33 */
/* GAME_SAVE_V33 includes GAME_SAVE_V31 */
if(version >= VERSION_33)
{
endian_udword(&psSaveGame->sGame.power);
endian_udword(&psSaveGame->sNetPlay.playercount);
endian_udword(&psSaveGame->savePlayer);
for(i = 0; i < MAX_PLAYERS; i++)
endian_udword(&psSaveGame->sPlayerIndex[i]);
}
/* GAME_SAVE_V31 includes GAME_SAVE_V30 */
if(version >= VERSION_31) {
endian_sdword(&psSaveGame->missionCheatTime);
}
/* GAME_SAVE_V30 includes GAME_SAVE_V29 */
if(version >= VERSION_30) {
endian_sdword(&psSaveGame->scrGameLevel);
}
/* GAME_SAVE_V29 includes GAME_SAVE_V27 */
if(version >= VERSION_29) {
endian_uword(&psSaveGame->missionScrollMinX);
endian_uword(&psSaveGame->missionScrollMinY);
endian_uword(&psSaveGame->missionScrollMaxX);
endian_uword(&psSaveGame->missionScrollMaxY);
}
/* GAME_SAVE_V27 includes GAME_SAVE_V24 */
if(version >= VERSION_27) {
for(i = 0; i < MAX_PLAYERS; i++)
for(j = 0; j < MAX_RECYCLED_DROIDS; j++)
endian_uword(&psSaveGame->awDroidExperience[i][j]);
}
/* GAME_SAVE_V24 includes GAME_SAVE_V22 */
if(version >= VERSION_24) {
endian_udword(&psSaveGame->reinforceTime);
}
/* GAME_SAVE_V22 includes GAME_SAVE_V20 */
if(version >= VERSION_22) {
for(i = 0; i < MAX_PLAYERS; i++) {
endian_sdword(&psSaveGame->asRunData[i].sPos.x);
endian_sdword(&psSaveGame->asRunData[i].sPos.y);
}
}
/* GAME_SAVE_V20 includes GAME_SAVE_V19 */
if(version >= VERSION_20) {
for(i = 0; i < MAX_PLAYERS; i++) {
endian_sdword(&psSaveGame->asVTOLReturnPos[i].x);
endian_sdword(&psSaveGame->asVTOLReturnPos[i].y);
}
}
/* GAME_SAVE_V19 includes GAME_SAVE_V18 */
if(version >= VERSION_19) {
}
/* GAME_SAVE_V18 includes GAME_SAVE_V17 */
if(version >= VERSION_18) {
endian_udword(&psSaveGame->oldestVersion);
endian_udword(&psSaveGame->validityKey);
}
/* GAME_SAVE_V17 includes GAME_SAVE_V16 */
if(version >= VERSION_17) {
endian_udword(&psSaveGame->objId);
}
/* GAME_SAVE_V16 includes GAME_SAVE_V15 */
if(version >= VERSION_16) {
}
/* GAME_SAVE_V15 includes GAME_SAVE_V14 */
if(version >= VERSION_15) {
endian_udword(&psSaveGame->RubbleTile);
endian_udword(&psSaveGame->WaterTile);
}
/* GAME_SAVE_V14 includes GAME_SAVE_V12 */
if(version >= VERSION_14) {
endian_sdword(&psSaveGame->missionOffTime);
endian_sdword(&psSaveGame->missionETA);
endian_uword(&psSaveGame->missionHomeLZ_X);
endian_uword(&psSaveGame->missionHomeLZ_Y);
endian_sdword(&psSaveGame->missionPlayerX);
endian_sdword(&psSaveGame->missionPlayerY);
for(i = 0; i < MAX_PLAYERS; i++) {
endian_uword(&psSaveGame->iTranspEntryTileX[i]);
endian_uword(&psSaveGame->iTranspEntryTileY[i]);
endian_uword(&psSaveGame->iTranspExitTileX[i]);
endian_uword(&psSaveGame->iTranspExitTileY[i]);
endian_udword(&psSaveGame->aDefaultSensor[i]);
endian_udword(&psSaveGame->aDefaultECM[i]);
endian_udword(&psSaveGame->aDefaultRepair[i]);
}
}
/* GAME_SAVE_V12 includes GAME_SAVE_V11 */
if(version >= VERSION_12) {
endian_udword(&psSaveGame->missionTime);
endian_udword(&psSaveGame->saveKey);
}
/* GAME_SAVE_V11 includes GAME_SAVE_V10 */
if(version >= VERSION_11) {
endian_sdword(&psSaveGame->currentPlayerPos.p.x);
endian_sdword(&psSaveGame->currentPlayerPos.p.y);
endian_sdword(&psSaveGame->currentPlayerPos.p.z);
endian_sdword(&psSaveGame->currentPlayerPos.r.x);
endian_sdword(&psSaveGame->currentPlayerPos.r.y);
endian_sdword(&psSaveGame->currentPlayerPos.r.z);
}
/* GAME_SAVE_V10 includes GAME_SAVE_V7 */
if(version >= VERSION_10) {
for(i = 0; i < MAX_PLAYERS; i++) {
endian_udword(&psSaveGame->power[i].currentPower);
endian_udword(&psSaveGame->power[i].extractedPower);
}
}
/* GAME_SAVE_V7 */
if(version >= VERSION_7) {
endian_udword(&psSaveGame->gameTime);
endian_udword(&psSaveGame->GameType);
endian_sdword(&psSaveGame->ScrollMinX);
endian_sdword(&psSaveGame->ScrollMinY);
endian_udword(&psSaveGame->ScrollMaxX);
endian_udword(&psSaveGame->ScrollMaxY);
}
}
// -----------------------------------------------------------------------------------------
// Get campaign number stuff is not needed in this form on the PSX (thank you very much)
static UDWORD getCampaignV(PHYSFS_file* fileHandle, unsigned int version)
{
SAVE_GAME_V14 saveGame;
debug(LOG_SAVE, "getCampaignV: version = %u", version);
if (version < VERSION_14)
{
return 0;
}
// We only need VERSION 12 data (saveGame.saveKey)
else if (version <= VERSION_34)
{
if (PHYSFS_read(fileHandle, &saveGame, sizeof(SAVE_GAME_V14), 1) != 1)
{
debug(LOG_ERROR, "getCampaignV: error while reading file: %s", PHYSFS_getLastError());
return 0;
}
// Convert from little-endian to native byte-order
endian_SaveGameV((SAVE_GAME*)&saveGame, VERSION_14);
}
else if (version <= CURRENT_VERSION_NUM)
{
if (!deserializeSaveGameV14Data(fileHandle, &saveGame))
{
debug(LOG_ERROR, "getCampaignV: error while reading file: %s", PHYSFS_getLastError());
return 0;
}
}
else
{
debug(LOG_ERROR, "Bad savegame version %u", version);
return 0;
}
return saveGame.saveKey & (SAVEKEY_ONMISSION - 1);
}
// -----------------------------------------------------------------------------------------
// Returns the campaign number --- apparently this is does alot less than it look like
/// it now does even less than it looks like on the psx ... cause its pc only
UDWORD getCampaign(const char* fileName)
{
GAME_SAVEHEADER fileHeader;
PHYSFS_file* fileHandle = openLoadFile(fileName, true);
if (!fileHandle)
{
// Failure to open the file is a failure to load the specified savegame
return false;
}
debug(LOG_WZ, "getCampaign: %s", fileName);
// Read the header from the file
if (!deserializeSaveGameHeader(fileHandle, &fileHeader))
{
debug(LOG_ERROR, "getCampaign: error while reading header from file (%s): %s", fileName, PHYSFS_getLastError());
PHYSFS_close(fileHandle);
return false;
}
// Check the header to see if we've been given a file of the right type
if (fileHeader.aFileType[0] != 'g'
|| fileHeader.aFileType[1] != 'a'
|| fileHeader.aFileType[2] != 'm'
|| fileHeader.aFileType[3] != 'e')
{
debug(LOG_ERROR, "getCampaign: Weird file type found? Has header letters - '%c' '%c' '%c' '%c' (should be 'g' 'a' 'm' 'e')",
fileHeader.aFileType[0],
fileHeader.aFileType[1],
fileHeader.aFileType[2],
fileHeader.aFileType[3]);
PHYSFS_close(fileHandle);
return false;
}
debug(LOG_NEVER, "gl .gam file is version %d\n", fileHeader.version);
//set main version Id from game file
saveGameVersion = fileHeader.version;
debug(LOG_SAVE, "fileversion is %u, (%s) ", fileHeader.version, fileName);
/* Check the file version */
if (fileHeader.version < VERSION_14)
{
PHYSFS_close(fileHandle);
return 0;
}
// what the arse bollocks is this
// the campaign number is fine prior to saving
// you save it out in a skirmish save and
// then don't bother putting it back in again
// when loading so it screws loads of stuff?!?
// dont check skirmish saves.
if (fileHeader.version <= CURRENT_VERSION_NUM)
{
UDWORD retVal = getCampaignV(fileHandle, fileHeader.version);
PHYSFS_close(fileHandle);
return retVal;
}
else
{
debug(LOG_ERROR, "getCampaign: undefined save format version %d", fileHeader.version);
PHYSFS_close(fileHandle);
return 0;
}
PHYSFS_close(fileHandle);
return 0;
}
// -----------------------------------------------------------------------------------------
/* code specific to version 7 of a save game */
bool gameLoadV7(PHYSFS_file* fileHandle)
{
SAVE_GAME_V7 saveGame;
if (PHYSFS_read(fileHandle, &saveGame, sizeof(saveGame), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV7: error while reading file: %s", PHYSFS_getLastError());
return false;
}
/* GAME_SAVE_V7 */
endian_udword(&saveGame.gameTime);
endian_udword(&saveGame.GameType);
endian_sdword(&saveGame.ScrollMinX);
endian_sdword(&saveGame.ScrollMinY);
endian_udword(&saveGame.ScrollMaxX);
endian_udword(&saveGame.ScrollMaxY);
savedGameTime = saveGame.gameTime;
//set the scroll varaibles
startX = saveGame.ScrollMinX;
startY = saveGame.ScrollMinY;
width = saveGame.ScrollMaxX - saveGame.ScrollMinX;
height = saveGame.ScrollMaxY - saveGame.ScrollMinY;
gameType = saveGame.GameType;
//set IsScenario to true if not a user saved game
if (gameType == GTYPE_SAVE_START)
{
LEVEL_DATASET* psNewLevel;
IsScenario = false;
//copy the level name across
sstrcpy(aLevelName, saveGame.levelName);
//load up the level dataset
if (!levLoadData(aLevelName, NULL, saveGameName, (GAME_TYPE)gameType))
{
return false;
}
// find the level dataset
psNewLevel = levFindDataSet(aLevelName);
if (psNewLevel == NULL)
{
debug( LOG_ERROR, "gameLoadV7: couldn't find level data" );
return false;
}
//check to see whether mission automatically starts
//shouldn't be able to be any other value at the moment!
if (psNewLevel->type == LDS_CAMSTART
|| psNewLevel->type == LDS_BETWEEN
|| psNewLevel->type == LDS_EXPAND
|| psNewLevel->type == LDS_EXPAND_LIMBO)
{
launchMission();
}
}
else
{
IsScenario = true;
}
return true;
}
// -----------------------------------------------------------------------------------------
/* non specific version of a save game */
bool gameLoadV(PHYSFS_file* fileHandle, unsigned int version)
{
unsigned int i, j;
static SAVE_POWER powerSaved[MAX_PLAYERS];
UDWORD player;
debug(LOG_WZ, "gameLoadV: version %u", version);
// Version 7 and earlier are loaded separately in gameLoadV7
//size is now variable so only check old save games
if (version <= VERSION_10)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V10), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version == VERSION_11)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V11), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_12)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V12), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_14)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V14), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_15)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V15), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_16)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V16), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_17)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V17), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_18)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V18), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_19)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V19), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_21)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V20), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_23)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V22), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_26)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V24), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_28)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V27), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_29)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V29), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_30)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V30), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_32)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V31), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_33)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V33), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version <= VERSION_34)
{
if (PHYSFS_read(fileHandle, &saveGameData, sizeof(SAVE_GAME_V34), 1) != 1)
{
debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else if (version < VERSION_39)
{
debug(LOG_ERROR, "Unsupported savegame version");
return false;
}
else if (version <= CURRENT_VERSION_NUM)
{
if (!deserializeSaveGameData(fileHandle, &saveGameData))
{
debug(LOG_ERROR, "gameLoadV: error while reading data from file for deserialization (with version number %u): %s", version, PHYSFS_getLastError());
return false;
}
}
else
{
debug(LOG_ERROR, "Unsupported version number (%u) for savegame", version);
return false;
}
debug(LOG_SAVE, "Savegame is of type: %u", saveGameData.sGame.type);
game.type = saveGameData.sGame.type;
/* Test mod list */
if (version >= VERSION_38)
{
setOverrideMods(saveGameData.modList);
}
// All savegames from version 34 or before are little endian so swap them. All
// from version 35, and onward, are already swapped to the native byte-order
// by the (de)serialization API
if (version <= VERSION_34)
{
endian_SaveGameV(&saveGameData, version);
}
savedGameTime = saveGameData.gameTime;
if (version >= VERSION_12)
{
mission.startTime = saveGameData.missionTime;
if (saveGameData.saveKey & SAVEKEY_ONMISSION)
{
saveGameOnMission = true;
}
else
{
saveGameOnMission = false;
}
}
else
{
saveGameOnMission = false;
}
//set the scroll varaibles
startX = saveGameData.ScrollMinX;
startY = saveGameData.ScrollMinY;
width = saveGameData.ScrollMaxX - saveGameData.ScrollMinX;
height = saveGameData.ScrollMaxY - saveGameData.ScrollMinY;
gameType = saveGameData.GameType;
if (version >= VERSION_11)
{
//camera position
disp3d_setView(&saveGameData.currentPlayerPos);
}
else
{
disp3d_resetView();
}
//load mission data from save game these values reloaded after load game
if (version >= VERSION_14)
{
//mission data
mission.time = saveGameData.missionOffTime;
mission.ETA = saveGameData.missionETA;
mission.homeLZ_X = saveGameData.missionHomeLZ_X;
mission.homeLZ_Y = saveGameData.missionHomeLZ_Y;
mission.playerX = saveGameData.missionPlayerX;
mission.playerY = saveGameData.missionPlayerY;
for (player = 0; player < MAX_PLAYERS; player++)
{
mission.iTranspEntryTileX[player] = saveGameData.iTranspEntryTileX[player];
mission.iTranspEntryTileY[player] = saveGameData.iTranspEntryTileY[player];
mission.iTranspExitTileX[player] = saveGameData.iTranspExitTileX[player];
mission.iTranspExitTileY[player] = saveGameData.iTranspExitTileY[player];
aDefaultSensor[player] = saveGameData.aDefaultSensor[player];
aDefaultECM[player] = saveGameData.aDefaultECM[player];
aDefaultRepair[player] = saveGameData.aDefaultRepair[player];
}
}
if (version >= VERSION_15)
{
offWorldKeepLists = saveGameData.offWorldKeepLists;
setRubbleTile(saveGameData.RubbleTile);
setUnderwaterTile(saveGameData.WaterTile);
}
if (version >= VERSION_17)
{
unsynchObjID = (saveGameData.objId + 1)/2; // Make new object ID start at savedObjId*8.
synchObjID = saveGameData.objId*4; // Make new object ID start at savedObjId*8.
savedObjId = saveGameData.objId;
}
if (version >= VERSION_19)//version 19
{
for(i=0; i<MAX_PLAYERS; i++)
{
for(j=0; j<MAX_PLAYERS; j++)
{
alliances[i][j] = saveGameData.alliances[i][j];
}
}
for(i=0; i<MAX_PLAYERS; i++)
{
setPlayerColour(i,saveGameData.playerColour[i]);
}
SetRadarZoom(saveGameData.radarZoom);
}
if (version >= VERSION_20)//version 20
{
setDroidsToSafetyFlag(saveGameData.bDroidsToSafetyFlag);
for (i = 0; i < MAX_PLAYERS; ++i)
{
memcpy(&asVTOLReturnPos[i], &saveGameData.asVTOLReturnPos[i], sizeof(Vector2i));
}
}
if (version >= VERSION_22)//version 22
{
for (i = 0; i < MAX_PLAYERS; ++i)
{
memcpy(&asRunData[i], &saveGameData.asRunData[i], sizeof(RUN_DATA));
}
}
if (saveGameVersion >= VERSION_24)//V24
{
missionSetReinforcementTime(saveGameData.reinforceTime);
// horrible hack to catch savegames that were saving garbage into these fields
if (saveGameData.bPlayCountDown <= 1)
{
setPlayCountDown(saveGameData.bPlayCountDown);
}
if (saveGameData.bPlayerHasWon <= 1)
{
setPlayerHasWon(saveGameData.bPlayerHasWon);
}
if (saveGameData.bPlayerHasLost <= 1)
{
setPlayerHasLost(saveGameData.bPlayerHasLost);
}
}
if (saveGameVersion >= VERSION_29)
{
mission.scrollMinX = saveGameData.missionScrollMinX;
mission.scrollMinY = saveGameData.missionScrollMinY;
mission.scrollMaxX = saveGameData.missionScrollMaxX;
mission.scrollMaxY = saveGameData.missionScrollMaxY;
}
if (saveGameVersion >= VERSION_30)
{
scrGameLevel = saveGameData.scrGameLevel;
bExtraVictoryFlag = saveGameData.bExtraVictoryFlag;
bExtraFailFlag = saveGameData.bExtraFailFlag;
bTrackTransporter = saveGameData.bTrackTransporter;
}
if (saveGameVersion >= VERSION_31)
{
mission.cheatTime = saveGameData.missionCheatTime;
}
for (player = 0; player < MAX_PLAYERS; player++)
{
for (i = 0; i < MAX_RECYCLED_DROIDS; ++i)
{
aDroidExperience[player][i] = 0;//clear experience before
}
}
//set IsScenario to true if not a user saved game
if ((gameType == GTYPE_SAVE_START) ||
(gameType == GTYPE_SAVE_MIDMISSION))
{
for (i = 0; i < MAX_PLAYERS; ++i)
{
powerSaved[i].currentPower = saveGameData.power[i].currentPower;
powerSaved[i].extractedPower = saveGameData.power[i].extractedPower;
}
for (player = 0; player < MAX_PLAYERS; player++)
{
for (i = 0; i < MAX_RECYCLED_DROIDS; ++i)
{
aDroidExperience[player][i] = 0;//clear experience before building saved units
}
NetPlay.players[player].ai = saveGameData.sNetPlay.players[player].ai;
NetPlay.players[player].difficulty = saveGameData.sNetPlay.players[player].difficulty;
strcpy(NetPlay.players[player].name, saveGameData.sNetPlay.players[player].name);
if ((saveGameData.sGame.skDiff[player] == UBYTE_MAX || game.type == CAMPAIGN) && player == 0)
{
NetPlay.players[player].allocated = true;
}
else
{
NetPlay.players[player].allocated = false;
}
}
IsScenario = false;
//copy the level name across
sstrcpy(aLevelName, saveGameData.levelName);
//load up the level dataset
// Not sure what aLevelName is, in relation to game.map. But need to use aLevelName here, to be able to start the right map for campaign, and need game.hash, to start the right non-campaign map, if there are multiple identically named maps.
if (!levLoadData(aLevelName, &saveGameData.sGame.hash, saveGameName, (GAME_TYPE)gameType))
{
return false;
}
if (saveGameVersion >= VERSION_33)
{
PLAYERSTATS playerStats;
bool scav = game.scavengers; // loaded earlier, keep it over struct copy below
bMultiPlayer = saveGameData.multiPlayer;
bMultiMessages = bMultiPlayer;
productionPlayer = selectedPlayer;
game = saveGameData.sGame; // why do this again????
game.scavengers = scav;
cmdDroidMultiExpBoost(true);
NetPlay.bComms = (saveGameData.sNetPlay).bComms;
if(bMultiPlayer)
{
loadMultiStats(saveGameData.sPName,&playerStats); // stats stuff
setMultiStats(selectedPlayer, playerStats, false);
setMultiStats(selectedPlayer, playerStats, true);
}
}
}
else
{
IsScenario = true;
}
/* Get human and AI players names */
if (saveGameVersion >= VERSION_34)
{
for(i=0;i<MAX_PLAYERS;i++)
(void)setPlayerName(i, saveGameData.sPlayerName[i]);
}
clearPlayerPower();
//don't adjust any power if a camStart (gameType is set to GTYPE_SCENARIO_START when a camChange saveGame is loaded)
if (gameType != GTYPE_SCENARIO_START)
{
//set the players power
for (i = 0; i < MAX_PLAYERS; ++i)
{
//only overwrite selectedPlayer's power on a startMission save game
if (gameType == GTYPE_SAVE_MIDMISSION || i == selectedPlayer)
{
setPower(i, powerSaved[i].currentPower);
}
}
}
radarPermitted = (bool)powerSaved[0].extractedPower; // nice hack, eh? don't want to break savegames now...
allowDesign = (bool)powerSaved[1].extractedPower; // nice hack, eh? don't want to break savegames now...
return true;
}
// -----------------------------------------------------------------------------------------
/*
Writes the game specifics to a file
*/
static bool writeGameFile(const char* fileName, SDWORD saveType)
{
GAME_SAVEHEADER fileHeader;
SAVE_GAME saveGame;
bool status;
unsigned int i, j;
PHYSFS_file* fileHandle = openSaveFile(fileName);
if (!fileHandle)
{
debug(LOG_ERROR, "openSaveFile(\"%s\") failed", fileName);
return false;
}
fileHeader.aFileType[0] = 'g';
fileHeader.aFileType[1] = 'a';
fileHeader.aFileType[2] = 'm';
fileHeader.aFileType[3] = 'e';
fileHeader.version = CURRENT_VERSION_NUM;
debug(LOG_SAVE, "fileversion is %u, (%s) ", fileHeader.version, fileName);
if (!serializeSaveGameHeader(fileHandle, &fileHeader))
{
debug(LOG_ERROR, "could not write header to %s; PHYSFS error: %s", fileName, PHYSFS_getLastError());
PHYSFS_close(fileHandle);
return false;
}
ASSERT( saveType == GTYPE_SAVE_START || saveType == GTYPE_SAVE_MIDMISSION, "invalid save type" );
// clear out structure
memset(&saveGame, 0x0, sizeof(SAVE_GAME));
// saveKeymissionIsOffworld
saveGame.saveKey = getCampaignNumber();
if (missionIsOffworld())
{
saveGame.saveKey |= SAVEKEY_ONMISSION;
saveGameOnMission = true;
}
else
{
saveGameOnMission = false;
}
/* Put the save game data into the buffer */
saveGame.gameTime = gameTime;
saveGame.missionTime = mission.startTime;
//put in the scroll data
saveGame.ScrollMinX = scrollMinX;
saveGame.ScrollMinY = scrollMinY;
saveGame.ScrollMaxX = scrollMaxX;
saveGame.ScrollMaxY = scrollMaxY;
saveGame.GameType = saveType;
//save the current level so we can load up the STARTING point of the mission
ASSERT_OR_RETURN(false, strlen(aLevelName) < MAX_LEVEL_SIZE, "Unable to save level name - too long (max %d) - %s",
(int)MAX_LEVEL_SIZE, aLevelName);
sstrcpy(saveGame.levelName, aLevelName);
//save out the players power
for (i = 0; i < MAX_PLAYERS; ++i)
{
saveGame.power[i].currentPower = getPower(i);
}
saveGame.power[0].extractedPower = radarPermitted; // hideous hack, don't want to break savegames now...
saveGame.power[1].extractedPower = allowDesign; // hideous hack, don't want to break savegames now...
//camera position
disp3d_getView(&(saveGame.currentPlayerPos));
//mission data
saveGame.missionOffTime = mission.time;
saveGame.missionETA = mission.ETA;
saveGame.missionCheatTime = mission.cheatTime;
saveGame.missionHomeLZ_X = mission.homeLZ_X;
saveGame.missionHomeLZ_Y = mission.homeLZ_Y;
saveGame.missionPlayerX = mission.playerX;
saveGame.missionPlayerY = mission.playerY;
saveGame.missionScrollMinX = (UWORD)mission.scrollMinX;
saveGame.missionScrollMinY = (UWORD)mission.scrollMinY;
saveGame.missionScrollMaxX = (UWORD)mission.scrollMaxX;
saveGame.missionScrollMaxY = (UWORD)mission.scrollMaxY;
saveGame.offWorldKeepLists = offWorldKeepLists;
saveGame.RubbleTile = getRubbleTileNum();
saveGame.WaterTile = getWaterTileNum();
for (i = 0; i < MAX_PLAYERS; ++i)
{
saveGame.iTranspEntryTileX[i] = mission.iTranspEntryTileX[i];
saveGame.iTranspEntryTileY[i] = mission.iTranspEntryTileY[i];
saveGame.iTranspExitTileX[i] = mission.iTranspExitTileX[i];
saveGame.iTranspExitTileY[i] = mission.iTranspExitTileY[i];
saveGame.aDefaultSensor[i] = aDefaultSensor[i];
saveGame.aDefaultECM[i] = aDefaultECM[i];
saveGame.aDefaultRepair[i] = aDefaultRepair[i];
for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
{
saveGame.awDroidExperience[i][j] = aDroidExperience[i][j];
}
}
for (i = 0; i < MAX_NOGO_AREAS; ++i)
{
LANDING_ZONE* psLandingZone = getLandingZone(i);
saveGame.sLandingZone[i].x1 = psLandingZone->x1; // in case struct changes
saveGame.sLandingZone[i].x2 = psLandingZone->x2;
saveGame.sLandingZone[i].y1 = psLandingZone->y1;
saveGame.sLandingZone[i].y2 = psLandingZone->y2;
}
//version 17
saveGame.objId = MAX(unsynchObjID*2, (synchObjID + 3)/4);
//version 18
memset(saveGame.buildDate, 0, sizeof(saveGame.buildDate));
saveGame.oldestVersion = 0;
saveGame.validityKey = 0;
//version 19
for(i=0; i<MAX_PLAYERS; i++)
{
for(j=0; j<MAX_PLAYERS; j++)
{
saveGame.alliances[i][j] = alliances[i][j];
}
}
for(i=0; i<MAX_PLAYERS; i++)
{
saveGame.playerColour[i] = getPlayerColour(i);
}
saveGame.radarZoom = (UBYTE)GetRadarZoom();
//version 20
saveGame.bDroidsToSafetyFlag = (UBYTE)getDroidsToSafetyFlag();
for (i = 0; i < MAX_PLAYERS; i++)
{
memcpy(&saveGame.asVTOLReturnPos[i], &asVTOLReturnPos[i], sizeof(Vector2i));
}
//version 22
for (i = 0; i < MAX_PLAYERS; i++)
{
memcpy(&saveGame.asRunData[i], &asRunData[i], sizeof(RUN_DATA));
}
//version 24
saveGame.reinforceTime = missionGetReinforcementTime();
saveGame.bPlayCountDown = (UBYTE)getPlayCountDown();
saveGame.bPlayerHasWon = (UBYTE)testPlayerHasWon();
saveGame.bPlayerHasLost = (UBYTE)testPlayerHasLost();
//version 30
saveGame.scrGameLevel = scrGameLevel;
saveGame.bExtraFailFlag = (UBYTE)bExtraFailFlag;
saveGame.bExtraVictoryFlag = (UBYTE)bExtraVictoryFlag;
saveGame.bTrackTransporter = (UBYTE)bTrackTransporter;
// version 33
saveGame.sGame = game;
saveGame.savePlayer = selectedPlayer;
saveGame.multiPlayer = bMultiPlayer;
saveGame.sNetPlay = NetPlay;
strcpy(saveGame.sPName, getPlayerName(selectedPlayer));
for (i = 0; i < MAX_PLAYERS; ++i)
{
saveGame.sPlayerIndex[i] = i;
}
//version 34
for (i = 0; i < MAX_PLAYERS; ++i)
{
strcpy(saveGame.sPlayerName[i], getPlayerName(i));
}
//version 38
sstrcpy(saveGame.modList, getModList());
// Attempt to see if we have a corrupted game structure in campaigns.
if (saveGame.sGame.type == CAMPAIGN)
{
// player 0 is always a human in campaign games
for (int i=1; i < MAX_PLAYERS; i++)
{
if (saveGame.sGame.skDiff[i] == UBYTE_MAX)
{
ASSERT(!"savegame corruption!","savegame corruption!");
debug(LOG_ERROR, "Savegame corruption detected, trying to salvage. Please Report this issue @ wz2100.net");
debug(LOG_ERROR, "skDiff[i] was %d, level %s / %s, ", (int)saveGame.sGame.skDiff[i], saveGame.levelName, saveGame.sGame.map);
saveGame.sGame.skDiff[i] = 0;
}
}
}
status = serializeSaveGameData(fileHandle, &saveGame);
// Close the file
PHYSFS_close(fileHandle);
// Return our success status with writing out the file!
return status;
}
// -----------------------------------------------------------------------------------------
// Process the droid initialisation file (dinit.bjo). Creates droids for
// the scenario being loaded. This is *NEVER* called for a user save game
//
bool loadSaveDroidInit(char *pFileData, UDWORD filesize)
{
DROIDINIT_SAVEHEADER *psHeader;
SAVE_DROIDINIT *pDroidInit;
DROID_TEMPLATE *psTemplate;
DROID *psDroid;
UDWORD i;
UDWORD NumberOfSkippedDroids = 0;
/* Check the file type */
psHeader = (DROIDINIT_SAVEHEADER *)pFileData;
if (psHeader->aFileType[0] != 'd' || psHeader->aFileType[1] != 'i' || psHeader->aFileType[2] != 'n' || psHeader->aFileType[3] != 't')
{
debug(LOG_ERROR, "Incorrect file type");
return false;
}
/* DROIDINIT_SAVEHEADER */
endian_udword(&psHeader->version);
endian_udword(&psHeader->quantity);
//increment to the start of the data
pFileData += DROIDINIT_HEADER_SIZE;
debug(LOG_SAVE, "fileversion is %u ", psHeader->version);
pDroidInit = (SAVE_DROIDINIT*)pFileData;
for (i = 0; i < psHeader->quantity; i++)
{
/* SAVE_DROIDINIT is OBJECT_SAVE_V19 */
/* OBJECT_SAVE_V19 */
endian_udword(&pDroidInit->id);
endian_udword(&pDroidInit->x);
endian_udword(&pDroidInit->y);
endian_udword(&pDroidInit->z);
endian_udword(&pDroidInit->direction);
endian_udword(&pDroidInit->player);
endian_udword(&pDroidInit->periodicalDamageStart);
endian_udword(&pDroidInit->periodicalDamage);
pDroidInit->player = RemapPlayerNumber(pDroidInit->player);
if (pDroidInit->player >= MAX_PLAYERS)
{
pDroidInit->player = MAX_PLAYERS-1; // now don't lose any droids ... force them to be the last player
NumberOfSkippedDroids++;
}
psTemplate = getTemplateFromTranslatedNameNoPlayer(pDroidInit->name);
if (psTemplate == NULL)
{
debug(LOG_ERROR, "Unable to find template for %s for player %d", pDroidInit->name, pDroidInit->player);
}
else
{
psDroid = reallyBuildDroid(psTemplate, Position((pDroidInit->x & ~TILE_MASK) + TILE_UNITS/2, (pDroidInit->y & ~TILE_MASK) + TILE_UNITS/2, 0), pDroidInit->player, false);
if (psDroid)
{
Vector2i startpos = getPlayerStartPosition(psDroid->player);
psDroid->id = pDroidInit->id > 0 ? pDroidInit->id : 0xFEDBCA98; // hack to remove droid id zero
psDroid->rot.direction = DEG(pDroidInit->direction);
addDroid(psDroid, apsDroidLists);
if (psDroid->droidType == DROID_CONSTRUCT && startpos.x == 0 && startpos.y == 0)
{
scriptSetStartPos(psDroid->player, psDroid->pos.x, psDroid->pos.y);
}
}
else
{
debug(LOG_ERROR, "This droid cannot be built - %s", pDroidInit->name);
return false;
}
}
pDroidInit++;
}
if(NumberOfSkippedDroids)
{
debug(LOG_ERROR, "Bad Player number in %d unit(s)... assigned to the last player!", NumberOfSkippedDroids);
return false;
}
return true;
}
// -----------------------------------------------------------------------------------------
// Remaps old player number based on position on map to new owner
static UDWORD RemapPlayerNumber(UDWORD OldNumber)
{
int i;
if (game.type == CAMPAIGN) // don't remap for SP games
{
return OldNumber;
}
for (i = 0; i < MAX_PLAYERS; i++)
{
if (OldNumber == NetPlay.players[i].position)
{
game.mapHasScavengers = game.mapHasScavengers || i == scavengerSlot();
return i;
}
}
ASSERT(false, "Found no player position for player %d", (int)OldNumber);
return 0;
}
static int getPlayer(WzConfig &ini)
{
if (ini.contains("player"))
{
QVariant result = ini.value("player");
if (result.toString().startsWith("scavenger"))
{
game.mapHasScavengers = true;
return scavengerSlot();
}
return result.toInt();
}
else if (ini.contains("startpos"))
{
int position = ini.value("startpos").toInt();
for (int i = 0; i < game.maxPlayers; i++)
{
if (NetPlay.players[i].position == position)
{
return i;
}
}
}
ASSERT(false, "No player info found!");
return 0;
}
static void setPlayer(WzConfig &ini, int player)
{
if (scavengerSlot() == player)
{
ini.setValue("player", "scavenger");
}
else
{
ini.setValue("player", player);
}
}
static bool skipForDifficulty(WzConfig &ini, int player)
{
if (ini.contains("difficulty")) // optionally skip this object
{
int difficulty = ini.value("difficulty").toInt();
if ((game.type == CAMPAIGN && difficulty > (int)getDifficultyLevel())
|| (game.type == SKIRMISH && difficulty > NetPlay.players[player].difficulty))
{
return true;
}
}
return false;
}
static bool loadSaveDroidPointers(const QString &pFileName, DROID **ppsCurrentDroidLists)
{
WzConfig ini(pFileName, WzConfig::ReadOnly);
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
DROID *psDroid;
int id = ini.value("id").toInt();
int player = getPlayer(ini);
if (id <= 0)
{
ini.endGroup();
continue; // special hack for campaign missions, cannot have targets
}
if (skipForDifficulty(ini, player))
{
ini.endGroup();
continue; // another hack for campaign missions, cannot have targets
}
for (psDroid = ppsCurrentDroidLists[player]; psDroid && psDroid->id != id; psDroid = psDroid->psNext)
{
if ((psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER) && psDroid->psGroup != NULL) // Check for droids in the transporter.
{
for (DROID *psTrDroid = psDroid->psGroup->psList; psTrDroid != NULL; psTrDroid = psTrDroid->psGrpNext)
{
if (psTrDroid->id == id)
{
psDroid = psTrDroid;
goto foundDroid;
}
}
}
}
foundDroid:
if (!psDroid)
{
for (psDroid = mission.apsDroidLists[player]; psDroid && psDroid->id != id; psDroid = psDroid->psNext) {}
// FIXME
if (psDroid) debug(LOG_ERROR, "Droid %s (%d) was in wrong file/list (was in %s)...", objInfo(psDroid), id, pFileName.toUtf8().constData());
}
ASSERT_OR_RETURN(false, psDroid, "Droid %d not found", id);
getIniDroidOrder(ini, "order", psDroid->order);
psDroid->listSize = clip(ini.value("orderList/size", 0).toInt(), 0, 10000);
psDroid->asOrderList.resize(psDroid->listSize); // Must resize before setting any orders, and must set in-place, since pointers are updated later.
for (int i = 0; i < psDroid->listSize; ++i)
{
getIniDroidOrder(ini, "orderList/" + QString::number(i), psDroid->asOrderList[i]);
}
psDroid->listPendingBegin = 0;
for (int j = 0; j < DROID_MAXWEAPS; j++)
{
objTrace(psDroid->id, "weapon %d, nStat %d", j, psDroid->asWeaps[j].nStat);
getIniBaseObject(ini, "actionTarget/" + QString::number(j), psDroid->psActionTarget[j]);
}
if (ini.contains("baseStruct/id"))
{
int tid = ini.value("baseStruct/id", -1).toInt();
int tplayer = ini.value("baseStruct/player", -1).toInt();
OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value("baseStruct/type", 0).toInt();
ASSERT(tid >= 0 && tplayer >= 0, "Bad ID");
BASE_OBJECT *psObj = getBaseObjFromData(tid, tplayer, ttype);
ASSERT(psObj, "Failed to find droid base structure");
ASSERT(psObj->type == OBJ_STRUCTURE, "Droid base structure not a structure");
setSaveDroidBase(psDroid, (STRUCTURE *)psObj);
}
if (ini.contains("commander"))
{
int tid = ini.value("commander", -1).toInt();
DROID *psCommander = (DROID *)getBaseObjFromData(tid, psDroid->player, OBJ_DROID);
ASSERT(psCommander, "Failed to find droid commander");
cmdDroidAddDroid(psCommander, psDroid);
}
ini.endGroup();
}
return true;
}
static int healthValue(WzConfig &ini, int defaultValue)
{
QString health = ini.value("health").toString();
if (health.isEmpty() || defaultValue == 0)
{
return defaultValue;
}
else if (health.contains('%'))
{
int perc = health.remove('%').toInt();
return MAX(defaultValue * perc / 100, 1); //hp not supposed to be 0
}
else
{
return MIN(health.toInt(), defaultValue);
}
}
static bool loadSaveDroid(const char *pFileName, DROID **ppsCurrentDroidLists)
{
if (!PHYSFS_exists(pFileName))
{
debug(LOG_SAVE, "No %s found -- use fallback method", pFileName);
return false; // try to use fallback method
}
WzConfig ini(pFileName, WzConfig::ReadOnly);
QStringList list = ini.childGroups();
// Sort list so transports are loaded first, since they must be loaded before the droids they contain.
std::vector<std::pair<int, QString> > sortedList;
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
DROID_TYPE droidType = (DROID_TYPE)ini.value("droidType").toInt();
int priority = 0;
switch (droidType)
{
case DROID_TRANSPORTER: ++priority;
case DROID_SUPERTRANSPORTER: ++priority;
case DROID_COMMAND: ++priority;
default: break;
}
sortedList.push_back(std::make_pair(-priority, list[i]));
ini.endGroup();
}
std::sort(sortedList.begin(), sortedList.end());
for (unsigned i = 0; i < sortedList.size(); ++i)
{
ini.beginGroup(sortedList[i].second);
DROID *psDroid;
int player = getPlayer(ini);
int id = ini.value("id").toInt();
Position pos = ini.vector3i("position");
Rotation rot = ini.vector3i("rotation");
Vector2i tmp;
bool onMission = ini.value("onMission", false).toBool();
DROID_TEMPLATE templ, *psTemplate = &templ;
if (skipForDifficulty(ini, player))
{
ini.endGroup();
continue;
}
if (ini.contains("template"))
{
// Use real template (for maps)
QString templName(ini.value("template").toString());
psTemplate = getTemplateFromTranslatedNameNoPlayer(templName.toUtf8().constData());
if (psTemplate == NULL)
{
debug(LOG_ERROR, "Unable to find template for %s for player %d -- unit skipped", templName.toUtf8().constData(), player);
ini.endGroup();
continue;
}
}
else
{
// Create fake template
templ.name = ini.value("name", "UNKNOWN").toString();
psTemplate->droidType = (DROID_TYPE)ini.value("droidType").toInt();
psTemplate->numWeaps = ini.value("weapons", 0).toInt();
ini.beginGroup("parts"); // the following is copy-pasted from loadSaveTemplate() -- fixme somehow
psTemplate->asParts[COMP_BODY] = getCompFromName(COMP_BODY, ini.value("body", "ZNULLBODY").toString());
psTemplate->asParts[COMP_BRAIN] = getCompFromName(COMP_BRAIN, ini.value("brain", "ZNULLBRAIN").toString());
psTemplate->asParts[COMP_PROPULSION] = getCompFromName(COMP_PROPULSION, ini.value("propulsion", "ZNULLPROP").toString());
psTemplate->asParts[COMP_REPAIRUNIT] = getCompFromName(COMP_REPAIRUNIT, ini.value("repair", "ZNULLREPAIR").toString());
psTemplate->asParts[COMP_ECM] = getCompFromName(COMP_ECM, ini.value("ecm", "ZNULLECM").toString());
psTemplate->asParts[COMP_SENSOR] = getCompFromName(COMP_SENSOR, ini.value("sensor", "ZNULLSENSOR").toString());
psTemplate->asParts[COMP_CONSTRUCT] = getCompFromName(COMP_CONSTRUCT, ini.value("construct", "ZNULLCONSTRUCT").toString());
psTemplate->asWeaps[0] = getCompFromName(COMP_WEAPON, ini.value("weapon/1", "ZNULLWEAPON").toString());
psTemplate->asWeaps[1] = getCompFromName(COMP_WEAPON, ini.value("weapon/2", "ZNULLWEAPON").toString());
psTemplate->asWeaps[2] = getCompFromName(COMP_WEAPON, ini.value("weapon/3", "ZNULLWEAPON").toString());
ini.endGroup();
}
// If droid is on a mission, calling with the saved position might cause an assertion. Or something like that.
pos.x = clip(pos.x, world_coord(1), world_coord(mapWidth - 1));
pos.y = clip(pos.y, world_coord(1), world_coord(mapHeight - 1));
/* Create the Droid */
turnOffMultiMsg(true);
psDroid = reallyBuildDroid(psTemplate, pos, player, onMission, rot);
ASSERT_OR_RETURN(NULL, psDroid != NULL, "Failed to build unit %d", id);
turnOffMultiMsg(false);
// Copy the values across
if (id > 0)
{
psDroid->id = id; // force correct ID, unless ID is set to eg -1, in which case we should keep new ID (useful for starting units in campaign)
}
ASSERT(id != 0, "Droid ID should never be zero here");
psDroid->body = healthValue(ini, psDroid->originalBody);
psDroid->periodicalDamage = ini.value("periodicalDamage", 0).toInt();
psDroid->periodicalDamageStart = ini.value("periodicalDamageStart", 0).toInt();
psDroid->experience = ini.value("experience", 0).toInt();
psDroid->timeLastHit = ini.value("timeLastHit", UDWORD_MAX).toInt();
psDroid->secondaryOrder = ini.value("secondaryOrder", psDroid->secondaryOrder).toInt();
psDroid->secondaryOrderPending = psDroid->secondaryOrder;
psDroid->action = (DROID_ACTION)ini.value("action", DACTION_NONE).toInt();
psDroid->actionPos = ini.vector2i("action/pos");
psDroid->actionStarted = ini.value("actionStarted", 0).toInt();
psDroid->actionPoints = ini.value("actionPoints", 0).toInt();
psDroid->resistance = ini.value("resistance", 0).toInt(); // zero resistance == no electronic damage
psDroid->lastFrustratedTime = ini.value("lastFrustratedTime", 0).toInt();
// copy the droid's weapon stats
for (int j = 0; j < psDroid->numWeaps; j++)
{
if (psDroid->asWeaps[j].nStat > 0)
{
psDroid->asWeaps[j].ammo = ini.value("ammo/" + QString::number(j)).toInt();
psDroid->asWeaps[j].lastFired = ini.value("lastFired/" + QString::number(j)).toInt();
psDroid->asWeaps[j].shotsFired = ini.value("shotsFired/" + QString::number(j)).toInt();
psDroid->asWeaps[j].rot = ini.vector3i("rotation/" + QString::number(j));
}
}
psDroid->group = ini.value("group", UBYTE_MAX).toInt();
psDroid->selected = ini.value("selected", false).toBool();
int aigroup = ini.value("aigroup", -1).toInt();
if (aigroup >= 0)
{
DROID_GROUP *psGroup = grpFind(aigroup);
psGroup->add(psDroid);
if (psGroup->type == GT_TRANSPORTER)
{
psDroid->selected = false; // Droid should be visible in the transporter interface.
}
}
else
{
if (psDroid->droidType == DROID_TRANSPORTER
|| psDroid->droidType == DROID_SUPERTRANSPORTER
|| psDroid->droidType == DROID_COMMAND)
{
DROID_GROUP *psGroup = grpCreate();
psGroup->add(psDroid);
}
else
{
psDroid->psGroup = NULL;
}
}
psDroid->died = ini.value("died", 0).toInt();
psDroid->lastEmission = ini.value("lastEmission", 0).toInt();
memset(psDroid->visible, 0, sizeof(psDroid->visible));
for (int j = 0; j < game.maxPlayers; j++)
{
psDroid->visible[j] = ini.value("visible/" + QString::number(j), 0).toInt();
}
psDroid->sMove.Status = (MOVE_STATUS)ini.value("moveStatus", 0).toInt();
psDroid->sMove.pathIndex = ini.value("pathIndex", 0).toInt();
psDroid->sMove.numPoints = ini.value("pathLength", 0).toInt();
psDroid->sMove.asPath = (Vector2i *)malloc(sizeof(*psDroid->sMove.asPath) * psDroid->sMove.numPoints);
for (int j = 0; j < psDroid->sMove.numPoints; j++)
{
psDroid->sMove.asPath[j] = ini.vector2i("pathNode/" + QString::number(j));
}
psDroid->sMove.destination = ini.vector2i("moveDestination");
psDroid->sMove.src = ini.vector2i("moveSource");
psDroid->sMove.target = ini.vector2i("moveTarget");
psDroid->sMove.speed = ini.value("moveSpeed").toInt();
psDroid->sMove.moveDir = ini.value("moveDirection").toInt();
psDroid->sMove.bumpDir = ini.value("bumpDir").toInt();
psDroid->sMove.iVertSpeed = ini.value("vertSpeed").toInt();
psDroid->sMove.bumpTime = ini.value("bumpTime").toInt();
psDroid->sMove.shuffleStart = ini.value("shuffleStart").toInt();
for (int j = 0; j < DROID_MAXWEAPS; ++j)
{
psDroid->asWeaps[j].usedAmmo = ini.value("attackRun/" + QString::number(j)).toInt();
}
psDroid->sMove.lastBump = ini.value("lastBump").toInt();
psDroid->sMove.pauseTime = ini.value("pauseTime").toInt();
tmp = ini.vector2i("bumpPosition");
psDroid->sMove.bumpX = tmp.x;
psDroid->sMove.bumpY = tmp.y;
psDroid->born = ini.value("born", 2).toInt();
// Recreate path-finding jobs
if (psDroid->sMove.Status == MOVEWAITROUTE)
{
psDroid->sMove.Status = MOVEINACTIVE;
fpathDroidRoute(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y, FMT_MOVE);
psDroid->sMove.Status = MOVEWAITROUTE;
// Droid might be on a mission, so finish pathfinding now, in case pointers swap and map size changes.
FPATH_RETVAL dr = fpathDroidRoute(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y, FMT_MOVE);
if (dr == FPR_OK)
{
psDroid->sMove.Status = MOVENAVIGATE;
psDroid->sMove.pathIndex = 0;
}
else // if (retVal == FPR_FAILED)
{
psDroid->sMove.Status = MOVEINACTIVE;
actionDroid(psDroid, DACTION_SULK);
}
ASSERT(dr != FPR_WAIT, " ");
}
// HACK!!
Vector2i startpos = getPlayerStartPosition(player);
if (psDroid->droidType == DROID_CONSTRUCT && startpos.x == 0 && startpos.y == 0)
{
scriptSetStartPos(psDroid->player, psDroid->pos.x, psDroid->pos.y); // set map start position, FIXME - save properly elsewhere!
}
if (psDroid->psGroup == NULL || psDroid->psGroup->type != GT_TRANSPORTER || psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER) // do not add to list if on a transport, then the group list is used instead
{
addDroid(psDroid, ppsCurrentDroidLists);
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
/*
Writes the linked list of droids for each player to a file
*/
static bool writeDroid(WzConfig &ini, DROID *psCurr, bool onMission, int &counter)
{
ini.beginGroup("droid_" + QString("%1").arg(counter++, 10, 10, QLatin1Char('0'))); // Zero padded so that alphabetical sort works.
ini.setValue("id", psCurr->id);
setPlayer(ini, psCurr->player);
ini.setValue("name", psCurr->aName);
ini.setVector3i("position", psCurr->pos);
ini.setVector3i("rotation", psCurr->rot);
ini.setValue("health", psCurr->body);
for (int i = 0; i < psCurr->numWeaps; i++)
{
if (psCurr->asWeaps[i].nStat > 0)
{
ini.setValue("ammo/" + QString::number(i), psCurr->asWeaps[i].ammo);
ini.setValue("lastFired/" + QString::number(i), psCurr->asWeaps[i].lastFired);
ini.setValue("shotsFired/" + QString::number(i), psCurr->asWeaps[i].shotsFired);
ini.setVector3i("rotation/" + QString::number(i), psCurr->asWeaps[i].rot);
}
}
for (int i = 0; i < DROID_MAXWEAPS; i++)
{
setIniBaseObject(ini, "actionTarget/" + QString::number(i), psCurr->psActionTarget[i]);
}
ini.setValue("born", psCurr->born);
if (psCurr->lastFrustratedTime > 0) ini.setValue("lastFrustratedTime", psCurr->lastFrustratedTime);
if (psCurr->experience > 0) ini.setValue("experience", psCurr->experience);
setIniDroidOrder(ini, "order", psCurr->order);
ini.setValue("orderList/size", psCurr->listSize);
for (int i = 0; i < psCurr->listSize; ++i)
{
setIniDroidOrder(ini, "orderList/" + QString::number(i), psCurr->asOrderList[i]);
}
if (psCurr->timeLastHit != UDWORD_MAX) ini.setValue("timeLastHit", psCurr->timeLastHit);
ini.setValue("secondaryOrder", psCurr->secondaryOrder);
ini.setValue("action", psCurr->action);
ini.setValue("actionString", getDroidActionName(psCurr->action)); // future-proofing
ini.setVector2i("action/pos", psCurr->actionPos);
ini.setValue("actionStarted", psCurr->actionStarted);
ini.setValue("actionPoints", psCurr->actionPoints);
if (psCurr->psBaseStruct != NULL)
{
ini.setValue("baseStruct/id", psCurr->psBaseStruct->id);
ini.setValue("baseStruct/player", psCurr->psBaseStruct->player); // always ours, but for completeness
ini.setValue("baseStruct/type", psCurr->psBaseStruct->type); // always a building, but for completeness
}
if (psCurr->psGroup)
{
ini.setValue("aigroup", psCurr->psGroup->id); // AI and commander/transport group
ini.setValue("aigroup/type", psCurr->psGroup->type);
}
ini.setValue("group", psCurr->group); // different kind of group. of course.
ini.setValue("selected", psCurr->selected); // third kind of group
if (psCurr->lastEmission)
{
ini.setValue("lastEmission", psCurr->lastEmission);
}
for (int i = 0; i < game.maxPlayers; i++)
{
ini.setValue("visible/" + QString::number(i), psCurr->visible[i]);
}
if (hasCommander(psCurr) && psCurr->psGroup->psCommander->died <= 1)
{
ini.setValue("commander", psCurr->psGroup->psCommander->id);
}
if (psCurr->died > 0) ini.setValue("died", psCurr->died);
if (psCurr->resistance > 0) ini.setValue("resistance", psCurr->resistance);
if (psCurr->periodicalDamageStart > 0) ini.setValue("periodicalDamageStart", psCurr->periodicalDamageStart);
if (psCurr->periodicalDamage > 0) ini.setValue("periodicalDamage", psCurr->periodicalDamage);
ini.setValue("droidType", psCurr->droidType);
ini.setValue("weapons", psCurr->numWeaps);
ini.setValue("parts/body", (asBodyStats + psCurr->asBits[COMP_BODY])->id);
ini.setValue("parts/propulsion", (asPropulsionStats + psCurr->asBits[COMP_PROPULSION])->id);
ini.setValue("parts/brain", (asBrainStats + psCurr->asBits[COMP_BRAIN])->id);
ini.setValue("parts/repair", (asRepairStats + psCurr->asBits[COMP_REPAIRUNIT])->id);
ini.setValue("parts/ecm", (asECMStats + psCurr->asBits[COMP_ECM])->id);
ini.setValue("parts/sensor", (asSensorStats + psCurr->asBits[COMP_SENSOR])->id);
ini.setValue("parts/construct", (asConstructStats + psCurr->asBits[COMP_CONSTRUCT])->id);
for (int j = 0; j < psCurr->numWeaps; j++)
{
ini.setValue("parts/weapon/" + QString::number(j + 1), (asWeaponStats + psCurr->asWeaps[j].nStat)->id);
}
ini.setValue("moveStatus", psCurr->sMove.Status);
ini.setValue("pathIndex", psCurr->sMove.pathIndex);
ini.setValue("pathLength", psCurr->sMove.numPoints);
for (int i = 0; i < psCurr->sMove.numPoints; i++)
{
ini.setVector2i("pathNode/" + QString::number(i), psCurr->sMove.asPath[i]);
}
ini.setVector2i("moveDestination", psCurr->sMove.destination);
ini.setVector2i("moveSource", psCurr->sMove.src);
ini.setVector2i("moveTarget", psCurr->sMove.target);
ini.setValue("moveSpeed", psCurr->sMove.speed);
ini.setValue("moveDirection", psCurr->sMove.moveDir);
ini.setValue("bumpDir", psCurr->sMove.bumpDir);
ini.setValue("vertSpeed", psCurr->sMove.iVertSpeed);
ini.setValue("bumpTime", psCurr->sMove.bumpTime);
ini.setValue("shuffleStart", psCurr->sMove.shuffleStart);
for (int i = 0; i < DROID_MAXWEAPS; ++i)
{
ini.setValue("attackRun/" + QString::number(i), psCurr->asWeaps[i].usedAmmo);
}
ini.setValue("lastBump", psCurr->sMove.lastBump);
ini.setValue("pauseTime", psCurr->sMove.pauseTime);
ini.setVector2i("bumpPosition", Vector2i(psCurr->sMove.bumpX, psCurr->sMove.bumpY));
ini.setValue("onMission", onMission);
ini.endGroup();
return true;
}
static bool writeDroidFile(const char *pFileName, DROID **ppsCurrentDroidLists)
{
WzConfig ini(pFileName);
int counter = 0;
bool onMission = (ppsCurrentDroidLists[0] == mission.apsDroidLists[0]);
for (int player = 0; player < MAX_PLAYERS; player++)
{
for (DROID *psCurr = ppsCurrentDroidLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
{
writeDroid(ini, psCurr, onMission, counter);
if (psCurr->droidType == DROID_TRANSPORTER || psCurr->droidType == DROID_SUPERTRANSPORTER) // if transporter save any droids in the grp
{
for (DROID *psTrans = psCurr->psGroup->psList; psTrans != NULL; psTrans = psTrans->psGrpNext)
{
if (psTrans != psCurr)
{
writeDroid(ini, psTrans, onMission, counter);
}
}
//always save transporter droids that are in the mission list with an invalid value
if (ppsCurrentDroidLists[player] == mission.apsDroidLists[player])
{
ini.setVector3i("position", Vector3i(-1, -1, -1)); // was INVALID_XY
}
}
}
}
return true;
}
// -----------------------------------------------------------------------------------------
bool loadSaveStructure(char *pFileData, UDWORD filesize)
{
STRUCT_SAVEHEADER *psHeader;
SAVE_STRUCTURE_V2 *psSaveStructure, sSaveStructure;
STRUCTURE *psStructure;
STRUCTURE_STATS *psStats = NULL;
UDWORD count, statInc;
int32_t found;
UDWORD NumberOfSkippedStructures=0;
UDWORD periodicalDamageTime;
/* Check the file type */
psHeader = (STRUCT_SAVEHEADER *)pFileData;
if (psHeader->aFileType[0] != 's' || psHeader->aFileType[1] != 't' ||
psHeader->aFileType[2] != 'r' || psHeader->aFileType[3] != 'u')
{
debug( LOG_ERROR, "loadSaveStructure: Incorrect file type" );
return false;
}
/* STRUCT_SAVEHEADER */
endian_udword(&psHeader->version);
endian_udword(&psHeader->quantity);
//increment to the start of the data
pFileData += STRUCT_HEADER_SIZE;
debug(LOG_SAVE, "file version is %u ", psHeader->version);
/* Check the file version */
if (psHeader->version < VERSION_7 || psHeader->version > VERSION_8)
{
debug( LOG_ERROR, "StructLoad: unsupported save format version %d", psHeader->version );
return false;
}
psSaveStructure = &sSaveStructure;
if ((sizeof(SAVE_STRUCTURE_V2) * psHeader->quantity + STRUCT_HEADER_SIZE) > filesize)
{
debug( LOG_ERROR, "structureLoad: unexpected end of file" );
return false;
}
/* Load in the structure data */
for (count = 0; count < psHeader->quantity; count ++, pFileData += sizeof(SAVE_STRUCTURE_V2))
{
memcpy(psSaveStructure, pFileData, sizeof(SAVE_STRUCTURE_V2));
/* STRUCTURE_SAVE_V2 includes OBJECT_SAVE_V19 */
endian_sdword(&psSaveStructure->currentBuildPts);
endian_udword(&psSaveStructure->body);
endian_udword(&psSaveStructure->armour);
endian_udword(&psSaveStructure->resistance);
endian_udword(&psSaveStructure->dummy1);
endian_udword(&psSaveStructure->subjectInc);
endian_udword(&psSaveStructure->timeStarted);
endian_udword(&psSaveStructure->output);
endian_udword(&psSaveStructure->capacity);
endian_udword(&psSaveStructure->quantity);
/* OBJECT_SAVE_V19 */
endian_udword(&psSaveStructure->id);
endian_udword(&psSaveStructure->x);
endian_udword(&psSaveStructure->y);
endian_udword(&psSaveStructure->z);
endian_udword(&psSaveStructure->direction);
endian_udword(&psSaveStructure->player);
endian_udword(&psSaveStructure->periodicalDamageStart);
endian_udword(&psSaveStructure->periodicalDamage);
psSaveStructure->player=RemapPlayerNumber(psSaveStructure->player);
if (psSaveStructure->player >= MAX_PLAYERS)
{
psSaveStructure->player=MAX_PLAYERS-1;
NumberOfSkippedStructures++;
}
//get the stats for this structure
found = false;
for (statInc = 0; statInc < numStructureStats; statInc++)
{
psStats = asStructureStats + statInc;
//loop until find the same name
if (psStats->id.compare(psSaveStructure->name) == 0)
{
found = true;
break;
}
}
//if haven't found the structure - ignore this record!
if (!found)
{
debug( LOG_ERROR, "This structure no longer exists - %s", getSaveStructNameV19((SAVE_STRUCTURE_V17*)psSaveStructure) );
//ignore this
continue;
}
//for modules - need to check the base structure exists
if (IsStatExpansionModule(psStats))
{
psStructure = getTileStructure(map_coord(psSaveStructure->x), map_coord(psSaveStructure->y));
if (psStructure == NULL)
{
debug( LOG_ERROR, "No owning structure for module - %s for player - %d", getSaveStructNameV19((SAVE_STRUCTURE_V17*)psSaveStructure), psSaveStructure->player );
//ignore this module
continue;
}
}
//check not trying to build too near the edge
if (map_coord(psSaveStructure->x) < TOO_NEAR_EDGE || map_coord(psSaveStructure->x) > mapWidth - TOO_NEAR_EDGE)
{
debug( LOG_ERROR, "Structure %s, x coord too near the edge of the map. id - %d", getSaveStructNameV19((SAVE_STRUCTURE_V17*)psSaveStructure), psSaveStructure->id );
//ignore this
continue;
}
if (map_coord(psSaveStructure->y) < TOO_NEAR_EDGE || map_coord(psSaveStructure->y) > mapHeight - TOO_NEAR_EDGE)
{
debug( LOG_ERROR, "Structure %s, y coord too near the edge of the map. id - %d", getSaveStructNameV19((SAVE_STRUCTURE_V17*)psSaveStructure), psSaveStructure->id );
//ignore this
continue;
}
psStructure = buildStructureDir(psStats, psSaveStructure->x, psSaveStructure->y, DEG(psSaveStructure->direction), psSaveStructure->player, true);
ASSERT(psStructure, "Unable to create structure");
if (!psStructure) continue;
// The original code here didn't work and so the scriptwriters worked round it by using the module ID - so making it work now will screw up
// the scripts -so in ALL CASES overwrite the ID!
psStructure->id = psSaveStructure->id > 0 ? psSaveStructure->id : 0xFEDBCA98; // hack to remove struct id zero
psStructure->periodicalDamage = psSaveStructure->periodicalDamage;
periodicalDamageTime = psSaveStructure->periodicalDamageStart;
psStructure->periodicalDamageStart = periodicalDamageTime;
psStructure->status = (STRUCT_STATES)psSaveStructure->status;
if (psStructure->status == SS_BUILT)
{
buildingComplete(psStructure);
}
if (psStructure->pStructureType->type == REF_HQ)
{
scriptSetStartPos(psSaveStructure->player, psStructure->pos.x, psStructure->pos.y);
}
else if (psStructure->pStructureType->type == REF_RESOURCE_EXTRACTOR)
{
scriptSetDerrickPos(psStructure->pos.x, psStructure->pos.y);
}
}
if (NumberOfSkippedStructures>0)
{
debug( LOG_ERROR, "structureLoad: invalid player number in %d structures ... assigned to the last player!\n\n", NumberOfSkippedStructures );
return false;
}
return true;
}
// -----------------------------------------------------------------------------------------
//return id of a research topic based on the name
static UDWORD getResearchIdFromName(const char *pName)
{
for (int inc = 0; inc < asResearch.size(); inc++)
{
if (asResearch[inc].id.compare(pName) == 0)
{
return inc;
}
}
debug(LOG_ERROR, "Unknown research - %s", pName);
return NULL_ID;
}
// -----------------------------------------------------------------------------------------
/* code for versions after version 20 of a save structure */
static bool loadSaveStructure2(const char *pFileName, STRUCTURE **ppList)
{
if (!PHYSFS_exists(pFileName))
{
debug(LOG_SAVE, "No %s found -- use fallback method", pFileName);
return false; // try to use fallback method
}
WzConfig ini(pFileName, WzConfig::ReadOnly);
freeAllFlagPositions(); //clear any flags put in during level loads
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
FACTORY *psFactory;
RESEARCH_FACILITY *psResearch;
REPAIR_FACILITY *psRepair;
REARM_PAD *psReArmPad;
STRUCTURE_STATS *psStats = NULL, *psModule;
int statInc, capacity, found, researchId;
STRUCTURE *psStructure;
ini.beginGroup(list[i]);
int player = getPlayer(ini);
int id = ini.value("id").toInt();
Position pos = ini.vector3i("position");
Rotation rot = ini.vector3i("rotation");
QString name = ini.value("name").toString();
//get the stats for this structure
found = false;
for (statInc = 0; statInc < numStructureStats; statInc++)
{
psStats = asStructureStats + statInc;
//loop until find the same name
if (name.compare(psStats->id) == 0)
{
found = true;
break;
}
}
//if haven't found the structure - ignore this record!
ASSERT(found, "This structure no longer exists - %s", name.toUtf8().constData());
if (!found)
{
ini.endGroup();
continue; // ignore this
}
/*create the Structure */
//for modules - need to check the base structure exists
if (IsStatExpansionModule(psStats))
{
STRUCTURE *psStructure = getTileStructure(map_coord(pos.x), map_coord(pos.y));
if (psStructure == NULL)
{
debug(LOG_ERROR, "No owning structure for module - %s for player - %d", name.toUtf8().constData(), player);
ini.endGroup();
continue; // ignore this module
}
}
//check not trying to build too near the edge
if (map_coord(pos.x) < TOO_NEAR_EDGE || map_coord(pos.x) > mapWidth - TOO_NEAR_EDGE
|| map_coord(pos.y) < TOO_NEAR_EDGE || map_coord(pos.y) > mapHeight - TOO_NEAR_EDGE)
{
debug(LOG_ERROR, "Structure %s, coord too near the edge of the map. id - %d", name.toUtf8().constData(), id);
ini.endGroup();
continue; // skip it
}
psStructure = buildStructureDir(psStats, pos.x, pos.y, rot.direction, player, true);
ASSERT(psStructure, "Unable to create structure");
if (!psStructure)
{
ini.endGroup();
continue;
}
if (id > 0)
{
psStructure->id = id; // force correct ID
}
psStructure->periodicalDamage = ini.value("periodicalDamage", 0).toInt();
psStructure->periodicalDamageStart = ini.value("periodicalDamageStart", 0).toInt();
memset(psStructure->visible, 0, sizeof(psStructure->visible));
for (int i = 0; i < game.maxPlayers; i++)
{
psStructure->visible[i] = ini.value("visible/" + QString::number(i), 0).toInt();
}
if (psStructure->pStructureType->type == REF_HQ)
{
scriptSetStartPos(player, psStructure->pos.x, psStructure->pos.y);
}
psStructure->resistance = ini.value("resistance", psStructure->resistance).toInt();
capacity = ini.value("modules", 0).toInt();
psStructure->capacity = 0; // increased when modules are built
switch (psStructure->pStructureType->type)
{
case REF_FACTORY:
case REF_VTOL_FACTORY:
case REF_CYBORG_FACTORY:
//if factory save the current build info
psFactory = ((FACTORY *)psStructure->pFunctionality);
psFactory->productionLoops = ini.value("Factory/productionLoops", psFactory->productionLoops).toInt();
psFactory->timeStarted = ini.value("Factory/timeStarted", psFactory->timeStarted).toInt();
psFactory->buildPointsRemaining = ini.value("Factory/buildPointsRemaining", psFactory->buildPointsRemaining).toInt();
psFactory->timeStartHold = ini.value("Factory/timeStartHold", psFactory->timeStartHold).toInt();
psFactory->loopsPerformed = ini.value("Factory/loopsPerformed", psFactory->loopsPerformed).toInt();
// statusPending and pendingCount belong to the GUI, not the game state.
psFactory->secondaryOrder = ini.value("Factory/secondaryOrder", psFactory->secondaryOrder).toInt();
//adjust the module structures IMD
if (capacity)
{
psModule = getModuleStat(psStructure);
//build the appropriate number of modules
for (int i = 0; i < capacity; i++)
{
buildStructure(psModule, psStructure->pos.x, psStructure->pos.y, psStructure->player, true);
}
}
if (ini.contains("Factory/template"))
{
int templId(ini.value("Factory/template").toInt());
psFactory->psSubject = getTemplateFromMultiPlayerID(templId);
}
if (ini.contains("Factory/assemblyPoint/pos"))
{
Position point = ini.vector3i("Factory/assemblyPoint/pos");
setAssemblyPoint(psFactory->psAssemblyPoint, point.x, point.y, player, true);
psFactory->psAssemblyPoint->selected = ini.value("Factory/assemblyPoint/selected", false).toBool();
}
if (ini.contains("Factory/assemblyPoint/number"))
{
psFactory->psAssemblyPoint->factoryInc = ini.value("Factory/assemblyPoint/number", 42).toInt();
}
for (int runNum = 0; runNum < ini.value("Factory/productionRuns", 0).toInt(); runNum++)
{
ProductionRunEntry currentProd;
currentProd.quantity = ini.value("Factory/Run/" + QString::number(runNum) + "/quantity").toInt();
currentProd.built = ini.value("Factory/Run/" + QString::number(runNum) + "/built").toInt();
if (ini.contains("Factory/Run/" + QString::number(runNum) + "/template"))
{
int tid = ini.value("Factory/Run/" + QString::number(runNum) + "/template").toInt();
DROID_TEMPLATE *psTempl = getTemplateFromMultiPlayerID(tid);
currentProd.psTemplate = psTempl;
ASSERT(psTempl, "No template found for template ID %d for %s (%d)", tid, objInfo(psStructure), id);
}
if (psFactory->psAssemblyPoint->factoryInc >= asProductionRun[psFactory->psAssemblyPoint->factoryType].size())
{
asProductionRun[psFactory->psAssemblyPoint->factoryType].resize(psFactory->psAssemblyPoint->factoryInc + 1);
}
asProductionRun[psFactory->psAssemblyPoint->factoryType][psFactory->psAssemblyPoint->factoryInc].push_back(currentProd);
}
break;
case REF_RESEARCH:
psResearch = ((RESEARCH_FACILITY *)psStructure->pFunctionality);
//adjust the module structures IMD
if (capacity)
{
psModule = getModuleStat(psStructure);
buildStructure(psModule, psStructure->pos.x, psStructure->pos.y, psStructure->player, true);
}
//clear subject
psResearch->psSubject = NULL;
psResearch->timeStartHold = 0;
//set the subject
if (ini.contains("Research/target"))
{
researchId = getResearchIdFromName(ini.value("Research/target").toString().toUtf8().constData());
if (researchId != NULL_ID)
{
psResearch->psSubject = &asResearch[researchId];
psResearch->timeStartHold = ini.value("Research/timeStartHold").toInt();
}
else
{
debug(LOG_ERROR, "Failed to look up research target %s", ini.value("Research/target").toString().toUtf8().constData());
}
}
break;
case REF_POWER_GEN:
// adjust the module structures IMD
if (capacity)
{
psModule = getModuleStat(psStructure);
buildStructure(psModule, psStructure->pos.x, psStructure->pos.y, psStructure->player, true);
}
break;
case REF_RESOURCE_EXTRACTOR:
break;
case REF_REPAIR_FACILITY:
psRepair = ((REPAIR_FACILITY *)psStructure->pFunctionality);
if (ini.contains("Repair/deliveryPoint/pos"))
{
Position point = ini.vector3i("Repair/deliveryPoint/pos");
setAssemblyPoint(psRepair->psDeliveryPoint, point.x, point.y, player, true);
psRepair->psDeliveryPoint->selected = ini.value("Repair/deliveryPoint/selected", false).toBool();
}
break;
case REF_REARM_PAD:
psReArmPad = ((REARM_PAD *)psStructure->pFunctionality);
psReArmPad->timeStarted = ini.value("Rearm/timeStarted", psReArmPad->timeStarted).toInt();
psReArmPad->timeLastUpdated = ini.value("Rearm/timeLastUpdated", psReArmPad->timeLastUpdated).toInt();
break;
case REF_WALL:
case REF_GATE:
psStructure->pFunctionality->wall.type = ini.value("Wall/type").toInt();
psStructure->sDisplay.imd = psStructure->pStructureType->pIMD[std::min<unsigned>(psStructure->pFunctionality->wall.type, psStructure->pStructureType->pIMD.size() - 1)];
break;
default:
break;
}
psStructure->body = healthValue(ini, structureBody(psStructure));
psStructure->currentBuildPts = ini.value("currentBuildPts", psStructure->pStructureType->buildPoints).toInt();
if (psStructure->status == SS_BUILT)
{
switch (psStructure->pStructureType->type)
{
case REF_POWER_GEN:
checkForResExtractors(psStructure);
if(selectedPlayer == psStructure->player)
{
audio_PlayObjStaticTrack(psStructure, ID_SOUND_POWER_HUM);
}
break;
case REF_RESOURCE_EXTRACTOR:
checkForPowerGen(psStructure);
/* GJ HACK! - add anim to deriks */
if (psStructure->psCurAnim == NULL)
{
psStructure->psCurAnim = animObj_Add(psStructure, ID_ANIM_DERIK, 0, 0);
}
break;
default:
//do nothing for factories etc
break;
}
}
// weapons
for (int j = 0; j < psStructure->pStructureType->numWeaps; j++)
{
if (psStructure->asWeaps[j].nStat > 0)
{
psStructure->asWeaps[j].ammo = ini.value("ammo/" + QString::number(j)).toInt();
psStructure->asWeaps[j].lastFired = ini.value("lastFired/" + QString::number(j)).toInt();
psStructure->asWeaps[j].shotsFired = ini.value("shotsFired/" + QString::number(j)).toInt();
psStructure->asWeaps[j].rot = ini.vector3i("rotation/" + QString::number(j));
}
}
psStructure->selected = ini.value("selected", false).toBool();
psStructure->died = ini.value("died", 0).toInt();
psStructure->lastEmission = ini.value("lastEmission", 0).toInt();
psStructure->timeLastHit = ini.value("timeLastHit", UDWORD_MAX).toInt();
psStructure->status = (STRUCT_STATES)ini.value("status", SS_BUILT).toInt();
if (psStructure->status == SS_BUILT)
{
buildingComplete(psStructure);
}
ini.endGroup();
}
resetFactoryNumFlag(); //reset flags into the masks
return true;
}
// -----------------------------------------------------------------------------------------
/*
Writes some version info
*/
bool writeGameInfo(const char *pFileName)
{
WzConfig ini(pFileName);
char ourtime[100] = {'\0'};
const time_t currentTime = time(NULL);
std::string time(ctime(&currentTime));
ini.beginGroup("GameProperties");
ini.setValue("current_time", time.data());
getAsciiTime(ourtime, graphicsTime);
ini.setValue("graphics_time", ourtime);
getAsciiTime(ourtime, gameTime);
ini.setValue("game_time", ourtime);
getAsciiTime( ourtime, gameTime - missionData.missionStarted);
ini.setValue("playing_time", ourtime);
ini.setValue("version", version_getVersionString());
ini.setValue("full_version", version_getFormattedVersionString());
ini.setValue("cheated", Cheated);
ini.setValue("debug", getDebugMappingStatus());
ini.setValue("level/map", getLevelName());
ini.setValue("mods", getModList() ? getModList() : "None");
ini.setValue("openGL_vendor", opengl.vendor);
ini.setValue("openGL_renderer", opengl.renderer);
ini.setValue("openGL_version", opengl.version);
ini.setValue("openGL_GLEW_version", opengl.GLEWversion);
ini.setValue("openGL_GLSL_version", opengl.GLSLversion);
// NOTE: deprecated for GL 3+. Needed this to check what extensions some chipsets support for the openGL hacks
std::string extensions = (const char *) glGetString(GL_EXTENSIONS);
ini.setValue("GL_EXTENSIONS", extensions.data());
ini.endGroup();
return true;
}
/*
Writes the linked list of structure for each player to a file
*/
bool writeStructFile(const char *pFileName)
{
WzConfig ini(pFileName);
int counter = 0;
for (int player = 0; player < MAX_PLAYERS; player++)
{
for (STRUCTURE *psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
{
ini.beginGroup("structure_" + QString("%1").arg(counter++, 10, 10, QLatin1Char('0'))); // Zero padded so that alphabetical sort works.
ini.setValue("id", psCurr->id);
setPlayer(ini, psCurr->player);
ini.setValue("name", psCurr->pStructureType->id);
ini.setVector3i("position", psCurr->pos);
ini.setVector3i("rotation", psCurr->rot);
ini.setValue("health", psCurr->body);
ini.setValue("born", psCurr->born);
if (psCurr->timeLastHit != UDWORD_MAX) ini.setValue("timeLastHit", psCurr->timeLastHit);
if (psCurr->selected) ini.setValue("selected", psCurr->selected);
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (psCurr->visible[i]) ini.setValue("visible/" + QString::number(i), psCurr->visible[i]);
}
if (psCurr->died > 0) ini.setValue("died", psCurr->died);
if (psCurr->resistance > 0) ini.setValue("resistance", psCurr->resistance);
if (psCurr->periodicalDamageStart > 0) ini.setValue("periodicalDamageStart", psCurr->periodicalDamageStart);
if (psCurr->periodicalDamage > 0) ini.setValue("periodicalDamage", psCurr->periodicalDamage);
if (psCurr->status != SS_BUILT) ini.setValue("status", psCurr->status);
ini.setValue("weapons", psCurr->numWeaps);
for (int j = 0; j < psCurr->numWeaps; j++)
{
ini.setValue("parts/weapon/" + QString::number(j + 1), (asWeaponStats + psCurr->asWeaps[j].nStat)->id);
if (psCurr->asWeaps[j].nStat > 0)
{
ini.setValue("ammo/" + QString::number(j), psCurr->asWeaps[j].ammo);
ini.setValue("lastFired/" + QString::number(j), psCurr->asWeaps[j].lastFired);
ini.setValue("shotsFired/" + QString::number(j), psCurr->asWeaps[j].shotsFired);
ini.setVector3i("rotation/" + QString::number(j), psCurr->asWeaps[j].rot);
}
}
for (int i = 0; i < psCurr->numWeaps; i++)
{
if (psCurr->psTarget[i] && !psCurr->psTarget[i]->died)
{
ini.setValue("target/" + QString::number(i) + "/id", psCurr->psTarget[i]->id);
ini.setValue("target/" + QString::number(i) + "/player", psCurr->psTarget[i]->player);
ini.setValue("target/" + QString::number(i) + "/type", psCurr->psTarget[i]->type);
#ifdef DEBUG
ini.setValue("target/" + QString::number(i) + "/debugfunc", QString::fromUtf8(psCurr->targetFunc[i]));
ini.setValue("target/" + QString::number(i) + "/debugline", psCurr->targetLine[i]);
#endif
}
}
ini.setValue("currentBuildPts", psCurr->currentBuildPts);
if (psCurr->pFunctionality)
{
if (psCurr->pStructureType->type == REF_FACTORY || psCurr->pStructureType->type == REF_CYBORG_FACTORY
|| psCurr->pStructureType->type == REF_VTOL_FACTORY)
{
FACTORY *psFactory = (FACTORY *)psCurr->pFunctionality;
ini.setValue("modules", psCurr->capacity);
ini.setValue("Factory/productionLoops", psFactory->productionLoops);
ini.setValue("Factory/timeStarted", psFactory->timeStarted);
ini.setValue("Factory/buildPointsRemaining", psFactory->buildPointsRemaining);
ini.setValue("Factory/timeStartHold", psFactory->timeStartHold);
ini.setValue("Factory/loopsPerformed", psFactory->loopsPerformed);
// statusPending and pendingCount belong to the GUI, not the game state.
ini.setValue("Factory/secondaryOrder", psFactory->secondaryOrder);
if (psFactory->psSubject != NULL) ini.setValue("Factory/template", psFactory->psSubject->multiPlayerID);
FLAG_POSITION *psFlag = ((FACTORY *)psCurr->pFunctionality)->psAssemblyPoint;
if (psFlag != NULL)
{
ini.setVector3i("Factory/assemblyPoint/pos", psFlag->coords);
if (psFlag->selected) ini.setValue("Factory/assemblyPoint/selected", psFlag->selected);
ini.setValue("Factory/assemblyPoint/number", psFlag->factoryInc);
}
if (psFactory->psCommander)
{
ini.setValue("Factory/commander/id", psFactory->psCommander->id);
ini.setValue("Factory/commander/player", psFactory->psCommander->player);
}
ini.setValue("Factory/secondaryOrder", psFactory->secondaryOrder);
ProductionRun emptyRun;
bool haveRun = psFactory->psAssemblyPoint->factoryInc < asProductionRun[psFactory->psAssemblyPoint->factoryType].size();
ProductionRun const &productionRun = haveRun? asProductionRun[psFactory->psAssemblyPoint->factoryType][psFactory->psAssemblyPoint->factoryInc] : emptyRun;
ini.setValue("Factory/productionRuns", (int)productionRun.size());
for (int runNum = 0; runNum < productionRun.size(); runNum++)
{
ProductionRunEntry psCurrentProd = productionRun.at(runNum);
ini.setValue("Factory/Run/" + QString::number(runNum) + "/quantity", psCurrentProd.quantity);
ini.setValue("Factory/Run/" + QString::number(runNum) + "/built", psCurrentProd.built);
if (psCurrentProd.psTemplate) ini.setValue("Factory/Run/" + QString::number(runNum) + "/template",
psCurrentProd.psTemplate->multiPlayerID);
}
}
else if (psCurr->pStructureType->type == REF_RESEARCH)
{
ini.setValue("modules", psCurr->capacity);
ini.setValue("Research/timeStartHold", ((RESEARCH_FACILITY *)psCurr->pFunctionality)->timeStartHold);
if (((RESEARCH_FACILITY *)psCurr->pFunctionality)->psSubject)
{
ini.setValue("Research/target", ((RESEARCH_FACILITY *)psCurr->pFunctionality)->psSubject->id);
}
}
else if (psCurr->pStructureType->type == REF_POWER_GEN)
{
ini.setValue("modules", psCurr->capacity);
}
else if (psCurr->pStructureType->type == REF_REPAIR_FACILITY)
{
REPAIR_FACILITY *psRepair = ((REPAIR_FACILITY *)psCurr->pFunctionality);
if (psRepair->psObj)
{
ini.setValue("Repair/target/id", psRepair->psObj->id);
ini.setValue("Repair/target/player", psRepair->psObj->player);
ini.setValue("Repair/target/type", psRepair->psObj->type);
}
FLAG_POSITION *psFlag = psRepair->psDeliveryPoint;
if (psFlag)
{
ini.setVector3i("Repair/deliveryPoint/pos", psFlag->coords);
if (psFlag->selected) ini.setValue("Repair/deliveryPoint/selected", psFlag->selected);
}
}
else if (psCurr->pStructureType->type == REF_REARM_PAD)
{
REARM_PAD *psReArmPad = ((REARM_PAD *)psCurr->pFunctionality);
ini.setValue("Rearm/timeStarted", psReArmPad->timeStarted);
ini.setValue("Rearm/timeLastUpdated", psReArmPad->timeLastUpdated);
if (psReArmPad->psObj)
{
ini.setValue("Rearm/target/id", psReArmPad->psObj->id);
ini.setValue("Rearm/target/player", psReArmPad->psObj->player);
ini.setValue("Rearm/target/type", psReArmPad->psObj->type);
}
}
else if (psCurr->pStructureType->type == REF_WALL || psCurr->pStructureType->type == REF_GATE)
{
ini.setValue("Wall/type", psCurr->pFunctionality->wall.type);
}
}
ini.endGroup();
}
}
return true;
}
// -----------------------------------------------------------------------------------------
bool loadSaveStructurePointers(QString filename, STRUCTURE **ppList)
{
WzConfig ini(filename, WzConfig::ReadOnly);
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
STRUCTURE *psStruct;
int player = getPlayer(ini);
int id = ini.value("id").toInt();
for (psStruct = ppList[player]; psStruct && psStruct->id != id; psStruct = psStruct->psNext) { }
if (!psStruct)
{
ini.endGroup();
continue; // it is not unusual for a structure to 'disappear' like this; it can happen eg because of module upgrades
}
for (int j = 0; j < STRUCT_MAXWEAPS; j++)
{
objTrace(psStruct->id, "weapon %d, nStat %d", j, psStruct->asWeaps[j].nStat);
if (ini.contains("target/" + QString::number(j) + "/id"))
{
int tid = ini.value("target/" + QString::number(j) + "/id", -1).toInt();
int tplayer = ini.value("target/" + QString::number(j) + "/player", -1).toInt();
OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value("target/" + QString::number(j) + "/type", 0).toInt();
ASSERT(tid >= 0 && tplayer >= 0, "Bad ID");
setStructureTarget(psStruct, getBaseObjFromData(tid, tplayer, ttype), j, ORIGIN_UNKNOWN);
ASSERT(psStruct->psTarget[j], "Failed to find target");
}
if (ini.contains("Factory/commander/id"))
{
ASSERT(psStruct->pStructureType->type == REF_FACTORY || psStruct->pStructureType->type == REF_CYBORG_FACTORY
|| psStruct->pStructureType->type == REF_VTOL_FACTORY, "Bad type");
FACTORY *psFactory = (FACTORY *)psStruct->pFunctionality;
OBJECT_TYPE ttype = OBJ_DROID;
int tid = ini.value("Factory/commander/id", -1).toInt();
int tplayer = ini.value("Factory/commander/player", -1).toInt();
ASSERT(tid >= 0 && tplayer >= 0, "Bad commander ID %d for player %d for building %d", tid, tplayer, id);
DROID *psCommander = (DROID *)getBaseObjFromData(tid, tplayer, ttype);
ASSERT(psCommander, "Commander %d not found for building %d", tid, id);
if (ppList == mission.apsStructLists)
{
psFactory->psCommander = psCommander;
}
else
{
assignFactoryCommandDroid(psStruct, psCommander);
}
}
if (ini.contains("Repair/target/id"))
{
ASSERT(psStruct->pStructureType->type == REF_REPAIR_FACILITY, "Bad type");
REPAIR_FACILITY *psRepair = ((REPAIR_FACILITY *)psStruct->pFunctionality);
OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value("Repair/target/type", OBJ_DROID).toInt();
int tid = ini.value("Repair/target/id", -1).toInt();
int tplayer = ini.value("Repair/target/player", -1).toInt();
ASSERT(tid >= 0 && tplayer >= 0, "Bad repair ID %d for player %d for building %d", tid, tplayer, id);
psRepair->psObj = getBaseObjFromData(tid, tplayer, ttype);
ASSERT(psRepair->psObj, "Repair target %d not found for building %d", tid, id);
}
if (ini.contains("Rearm/target/id"))
{
ASSERT(psStruct->pStructureType->type == REF_REARM_PAD, "Bad type");
REARM_PAD *psReArmPad = ((REARM_PAD *)psStruct->pFunctionality);
OBJECT_TYPE ttype = OBJ_DROID; // always, for now
int tid = ini.value("Rearm/target/id", -1).toInt();
int tplayer = ini.value("Rearm/target/player", -1).toInt();
ASSERT(tid >= 0 && tplayer >= 0, "Bad rearm ID %d for player %d for building %d", tid, tplayer, id);
psReArmPad->psObj = getBaseObjFromData(tid, tplayer, ttype);
ASSERT(psReArmPad->psObj, "Rearm target %d not found for building %d", tid, id);
}
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
bool loadSaveFeature(char *pFileData, UDWORD filesize)
{
FEATURE_SAVEHEADER *psHeader;
SAVE_FEATURE_V14 *psSaveFeature;
FEATURE *pFeature;
UDWORD count, i, statInc;
FEATURE_STATS *psStats = NULL;
bool found;
UDWORD sizeOfSaveFeature;
/* Check the file type */
psHeader = (FEATURE_SAVEHEADER *)pFileData;
if (psHeader->aFileType[0] != 'f' || psHeader->aFileType[1] != 'e' ||
psHeader->aFileType[2] != 'a' || psHeader->aFileType[3] != 't')
{
debug( LOG_ERROR, "loadSaveFeature: Incorrect file type" );
return false;
}
/* FEATURE_SAVEHEADER */
endian_udword(&psHeader->version);
endian_udword(&psHeader->quantity);
debug(LOG_SAVE, "Feature file version is %u ", psHeader->version);
//increment to the start of the data
pFileData += FEATURE_HEADER_SIZE;
/* Check the file version */
if (psHeader->version < VERSION_7 || psHeader->version > VERSION_19)
{
debug(LOG_ERROR, "Unsupported save format version %u", psHeader->version);
return false;
}
if (psHeader->version < VERSION_14)
{
sizeOfSaveFeature = sizeof(SAVE_FEATURE_V2);
}
else
{
sizeOfSaveFeature = sizeof(SAVE_FEATURE_V14);
}
if ((sizeOfSaveFeature * psHeader->quantity + FEATURE_HEADER_SIZE) > filesize)
{
debug( LOG_ERROR, "featureLoad: unexpected end of file" );
return false;
}
/* Load in the feature data */
for (count = 0; count < psHeader->quantity; count ++, pFileData += sizeOfSaveFeature)
{
psSaveFeature = (SAVE_FEATURE_V14*) pFileData;
/* FEATURE_SAVE_V14 is FEATURE_SAVE_V2 */
/* FEATURE_SAVE_V2 is OBJECT_SAVE_V19 */
/* OBJECT_SAVE_V19 */
endian_udword(&psSaveFeature->id);
endian_udword(&psSaveFeature->x);
endian_udword(&psSaveFeature->y);
endian_udword(&psSaveFeature->z);
endian_udword(&psSaveFeature->direction);
endian_udword(&psSaveFeature->player);
endian_udword(&psSaveFeature->periodicalDamageStart);
endian_udword(&psSaveFeature->periodicalDamage);
//get the stats for this feature
found = false;
for (statInc = 0; statInc < numFeatureStats; statInc++)
{
psStats = asFeatureStats + statInc;
//loop until find the same name
if (psStats->id.compare(psSaveFeature->name) == 0)
{
found = true;
break;
}
}
//if haven't found the feature - ignore this record!
if (!found)
{
debug( LOG_ERROR, "This feature no longer exists - %s", psSaveFeature->name );
//ignore this
continue;
}
//create the Feature
pFeature = buildFeature(psStats, psSaveFeature->x, psSaveFeature->y,true);
if (!pFeature)
{
debug(LOG_ERROR, "Unable to create feature %s", psSaveFeature->name);
continue;
}
if (pFeature->psStats->subType == FEAT_OIL_RESOURCE)
{
scriptSetDerrickPos(pFeature->pos.x, pFeature->pos.y);
}
//restore values
pFeature->id = psSaveFeature->id;
pFeature->rot.direction = DEG(psSaveFeature->direction);
pFeature->periodicalDamage = psSaveFeature->periodicalDamage;
if (psHeader->version >= VERSION_14)
{
for (i=0; i < MAX_PLAYERS; i++)
{
pFeature->visible[i] = psSaveFeature->visible[i];
}
}
}
return true;
}
bool loadSaveFeature2(const char *pFileName)
{
if (!PHYSFS_exists(pFileName))
{
debug(LOG_SAVE, "No %s found -- use fallback method", pFileName);
return false;
}
WzConfig ini(pFileName, WzConfig::ReadOnly);
QStringList list = ini.childGroups();
debug(LOG_SAVE, "Loading new style features (%d found)", list.size());
for (int i = 0; i < list.size(); ++i)
{
FEATURE *pFeature;
ini.beginGroup(list[i]);
QString name = ini.value("name").toString();
Position pos = ini.vector3i("position");
int statInc;
bool found = false;
FEATURE_STATS *psStats = NULL;
//get the stats for this feature
for (statInc = 0; statInc < numFeatureStats; statInc++)
{
psStats = asFeatureStats + statInc;
//loop until find the same name
if (psStats->id.compare(name) == 0)
{
found = true;
break;
}
}
//if haven't found the feature - ignore this record!
if (!found)
{
debug(LOG_ERROR, "This feature no longer exists - %s", name.toUtf8().constData());
//ignore this
continue;
}
//create the Feature
pFeature = buildFeature(psStats, pos.x, pos.y, true);
if (!pFeature)
{
debug(LOG_ERROR, "Unable to create feature %s", name.toUtf8().constData());
continue;
}
if (pFeature->psStats->subType == FEAT_OIL_RESOURCE)
{
scriptSetDerrickPos(pFeature->pos.x, pFeature->pos.y);
}
//restore values
pFeature->id = ini.value("id").toInt();
pFeature->rot = ini.vector3i("rotation");
pFeature->periodicalDamage = ini.value("periodicalDamage", 0).toInt();
pFeature->periodicalDamageStart = ini.value("periodicalDamageStart", 0).toInt();
pFeature->born = ini.value("born", 2).toInt();
pFeature->timeLastHit = ini.value("timeLastHit", UDWORD_MAX).toInt();
pFeature->selected = ini.value("selected", false).toBool();
pFeature->body = healthValue(ini, pFeature->psStats->body);
for (int i = 0; i < MAX_PLAYERS; i++)
{
pFeature->visible[i] = ini.value("visible/" + QString::number(i), 0).toInt();
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
/*
Writes the linked list of features to a file
*/
bool writeFeatureFile(const char *pFileName)
{
WzConfig ini(pFileName);
int counter = 0;
for(FEATURE *psCurr = apsFeatureLists[0]; psCurr != NULL; psCurr = psCurr->psNext)
{
ini.beginGroup("feature_" + QString("%1").arg(counter++, 10, 10, QLatin1Char('0'))); // Zero padded so that alphabetical sort works.
ini.setValue("id", psCurr->id);
ini.setValue("name", psCurr->psStats->id);
ini.setVector3i("position", psCurr->pos);
ini.setVector3i("rotation", psCurr->rot);
ini.setValue("periodicalDamage", psCurr->periodicalDamage);
ini.setValue("periodicalDamageStart", psCurr->periodicalDamageStart);
ini.setValue("health", psCurr->body);
ini.setValue("born", psCurr->born);
if (psCurr->selected) ini.setValue("selected", psCurr->selected);
if (psCurr->timeLastHit != UDWORD_MAX) ini.setValue("timeLastHit", psCurr->timeLastHit);
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (psCurr->visible[i]) ini.setValue("visible/" + QString::number(i), psCurr->visible[i]);
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
bool loadSaveTemplate(const char *pFileName)
{
WzConfig ini(pFileName, WzConfig::ReadOnly);
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
int player = getPlayer(ini);
DROID_TEMPLATE *psTemplate = new DROID_TEMPLATE;
psTemplate->name = ini.value("name").toString();
psTemplate->ref = ini.value("ref").toInt();
psTemplate->droidType = (DROID_TYPE)ini.value("droidType").toInt();
psTemplate->multiPlayerID = ini.value("multiPlayerID").toInt();
psTemplate->asParts[COMP_BODY] = getCompFromName(COMP_BODY, ini.value("body", "ZNULLBODY").toString());
psTemplate->asParts[COMP_BRAIN] = getCompFromName(COMP_BRAIN, ini.value("brain", "ZNULLBRAIN").toString());
psTemplate->asParts[COMP_PROPULSION] = getCompFromName(COMP_PROPULSION, ini.value("propulsion", "ZNULLPROP").toString());
psTemplate->asParts[COMP_REPAIRUNIT] = getCompFromName(COMP_REPAIRUNIT, ini.value("repair", "ZNULLREPAIR").toString());
psTemplate->asParts[COMP_ECM] = getCompFromName(COMP_ECM, ini.value("ecm", "ZNULLECM").toString());
psTemplate->asParts[COMP_SENSOR] = getCompFromName(COMP_SENSOR, ini.value("sensor", "ZNULLSENSOR").toString());
psTemplate->asParts[COMP_CONSTRUCT] = getCompFromName(COMP_CONSTRUCT, ini.value("construct", "ZNULLCONSTRUCT").toString());
psTemplate->asWeaps[0] = getCompFromName(COMP_WEAPON, ini.value("weapon/1", "ZNULLWEAPON").toString());
psTemplate->asWeaps[1] = getCompFromName(COMP_WEAPON, ini.value("weapon/2", "ZNULLWEAPON").toString());
psTemplate->asWeaps[2] = getCompFromName(COMP_WEAPON, ini.value("weapon/3", "ZNULLWEAPON").toString());
psTemplate->numWeaps = ini.value("weapons").toInt();
psTemplate->enabled = ini.value("enabled").toBool();
psTemplate->prefab = false; // not AI template
//store it in the apropriate player' list
//if a template with the same multiplayerID exists overwrite it
//else add this template to the top of the list
DROID_TEMPLATE *psDestTemplate = apsDroidTemplates[player];
while (psDestTemplate != NULL)
{
if (psTemplate->multiPlayerID == psDestTemplate->multiPlayerID)
{
//whooh get rid of this one
break;
}
psDestTemplate = psDestTemplate->psNext;
}
if (psDestTemplate != NULL)
{
psTemplate->psNext = psDestTemplate->psNext;//preserve the list
*psDestTemplate = *psTemplate;
}
else
{
//add it to the top of the list
psTemplate->psNext = apsDroidTemplates[player];
apsDroidTemplates[player] = psTemplate;
}
ini.endGroup();
}
for (DROID_TEMPLATE *t = apsDroidTemplates[selectedPlayer]; t != NULL; t = t->psNext)
{
localTemplates.push_front(*t);
}
return true;
}
bool writeTemplateFile(const char *pFileName)
{
WzConfig ini(pFileName);
for (int player = 0; player < MAX_PLAYERS; player++)
{
if (apsDroidLists[player] || apsStructLists[player]) // only write out templates of players that are still 'alive'
{
for (DROID_TEMPLATE *psCurr = apsDroidTemplates[player]; psCurr != NULL; psCurr = psCurr->psNext)
{
ini.beginGroup("template_" + QString::number(psCurr->multiPlayerID) + "_player" + QString::number(player));
ini.setValue("name", psCurr->name);
ini.setValue("ref", psCurr->ref);
ini.setValue("droidType", psCurr->droidType);
ini.setValue("multiPlayerID", psCurr->multiPlayerID);
setPlayer(ini, player);
ini.setValue("body", (asBodyStats + psCurr->asParts[COMP_BODY])->id);
ini.setValue("propulsion", (asPropulsionStats + psCurr->asParts[COMP_PROPULSION])->id);
ini.setValue("brain", (asBrainStats + psCurr->asParts[COMP_BRAIN])->id);
ini.setValue("repair", (asRepairStats + psCurr->asParts[COMP_REPAIRUNIT])->id);
ini.setValue("ecm", (asECMStats + psCurr->asParts[COMP_ECM])->id);
ini.setValue("sensor", (asSensorStats + psCurr->asParts[COMP_SENSOR])->id);
ini.setValue("construct", (asConstructStats + psCurr->asParts[COMP_CONSTRUCT])->id);
ini.setValue("weapons", psCurr->numWeaps);
ini.setValue("enabled", psCurr->enabled);
for (int j = 0; j < psCurr->numWeaps; j++)
{
ini.setValue("weapon/" + QString::number(j + 1), (asWeaponStats + psCurr->asWeaps[j])->id);
}
ini.endGroup();
}
}
}
return true;
}
// -----------------------------------------------------------------------------------------
// load up a terrain tile type map file
bool loadTerrainTypeMap(const char *pFileData, UDWORD filesize)
{
TILETYPE_SAVEHEADER *psHeader;
UDWORD i;
UWORD *pType;
if (filesize < TILETYPE_HEADER_SIZE)
{
debug( LOG_ERROR, "loadTerrainTypeMap: file too small" );
return false;
}
// Check the header
psHeader = (TILETYPE_SAVEHEADER *)pFileData;
if (psHeader->aFileType[0] != 't' || psHeader->aFileType[1] != 't' ||
psHeader->aFileType[2] != 'y' || psHeader->aFileType[3] != 'p')
{
debug( LOG_ERROR, "loadTerrainTypeMap: Incorrect file type" );
return false;
}
/* TILETYPE_SAVEHEADER */
endian_udword(&psHeader->version);
endian_udword(&psHeader->quantity);
// reset the terrain table
memset(terrainTypes, 0, sizeof(terrainTypes));
// Load the terrain type mapping
pType = (UWORD *)(pFileData + TILETYPE_HEADER_SIZE);
endian_uword(pType);
if (psHeader->quantity >= MAX_TILE_TEXTURES)
{
// Workaround for fugly map editor bug, since we can't fix the map editor
psHeader->quantity = MAX_TILE_TEXTURES - 1;
}
for(i = 0; i < psHeader->quantity; i++)
{
if (*pType > TER_MAX)
{
debug( LOG_ERROR, "loadTerrainTypeMap: terrain type out of range" );
return false;
}
terrainTypes[i] = (UBYTE)*pType;
pType++;
endian_uword(pType);
}
return true;
}
// -----------------------------------------------------------------------------------------
// Write out the terrain type map
static bool writeTerrainTypeMapFile(char *pFileName)
{
TILETYPE_SAVEHEADER *psHeader;
char *pFileData;
UDWORD fileSize, i;
UWORD *pType;
// Calculate the file size
fileSize = TILETYPE_HEADER_SIZE + sizeof(UWORD) * MAX_TILE_TEXTURES;
pFileData = (char*)malloc(fileSize);
if (!pFileData)
{
debug( LOG_FATAL, "writeTerrainTypeMapFile: Out of memory" );
abort();
return false;
}
// Put the file header on the file
psHeader = (TILETYPE_SAVEHEADER *)pFileData;
psHeader->aFileType[0] = 't';
psHeader->aFileType[1] = 't';
psHeader->aFileType[2] = 'y';
psHeader->aFileType[3] = 'p';
psHeader->version = CURRENT_VERSION_NUM;
psHeader->quantity = MAX_TILE_TEXTURES;
pType = (UWORD *)(pFileData + TILETYPE_HEADER_SIZE);
for(i=0; i<MAX_TILE_TEXTURES; i++)
{
*pType = terrainTypes[i];
endian_uword(pType);
pType += 1;
}
/* TILETYPE_SAVEHEADER */
endian_udword(&psHeader->version);
endian_udword(&psHeader->quantity);
if (!saveFile(pFileName, pFileData, fileSize))
{
return false;
}
free(pFileData);
return true;
}
// -----------------------------------------------------------------------------------------
bool loadSaveCompList(const char *pFileName)
{
WzConfig ini(pFileName, WzConfig::ReadOnly);
for (int player = 0; player < MAX_PLAYERS; player++)
{
ini.beginGroup("player_" + QString::number(player));
QStringList list = ini.childKeys();
for (int i = 0; i < list.size(); ++i)
{
QString name = list[i];
int state = ini.value(name, UNAVAILABLE).toInt();
COMPONENT_STATS *psComp = getCompStatsFromName(name);
ASSERT(psComp, "Bad component %s", name.toUtf8().constData());
ASSERT(psComp->compType >= 0 && psComp->compType != COMP_NUMCOMPONENTS, "Bad type %d", psComp->compType);
ASSERT_OR_RETURN(false, state == UNAVAILABLE || state == AVAILABLE || state == FOUND || state == REDUNDANT,
"Bad state %d for %s", state, name.toUtf8().constData());
apCompLists[player][psComp->compType][psComp->index] = state;
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
// Write out the current state of the Comp lists per player
static bool writeCompListFile(const char *pFileName)
{
WzConfig ini(pFileName);
// Save each type of struct type
for (int player = 0; player < MAX_PLAYERS; player++)
{
ini.beginGroup("player_" + QString::number(player));
for (int i = 0; i < numBodyStats; i++)
{
COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asBodyStats + i);
const int state = apCompLists[player][COMP_BODY][i];
if (state != UNAVAILABLE) ini.setValue(psStats->id, state);
}
for (int i = 0; i < numWeaponStats; i++)
{
COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asWeaponStats + i);
const int state = apCompLists[player][COMP_WEAPON][i];
if (state != UNAVAILABLE) ini.setValue(psStats->id, state);
}
for (int i = 0; i < numConstructStats; i++)
{
COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asConstructStats + i);
const int state = apCompLists[player][COMP_CONSTRUCT][i];
if (state != UNAVAILABLE) ini.setValue(psStats->id, state);
}
for (int i = 0; i < numECMStats; i++)
{
COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asECMStats + i);
const int state = apCompLists[player][COMP_ECM][i];
if (state != UNAVAILABLE) ini.setValue(psStats->id, state);
}
for (int i = 0; i < numPropulsionStats; i++)
{
COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asPropulsionStats + i);
const int state = apCompLists[player][COMP_PROPULSION][i];
if (state != UNAVAILABLE) ini.setValue(psStats->id, state);
}
for (int i = 0; i < numSensorStats; i++)
{
COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asSensorStats + i);
const int state = apCompLists[player][COMP_SENSOR][i];
if (state != UNAVAILABLE) ini.setValue(psStats->id, state);
}
for (int i = 0; i < numRepairStats; i++)
{
COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asRepairStats + i);
const int state = apCompLists[player][COMP_REPAIRUNIT][i];
if (state != UNAVAILABLE) ini.setValue(psStats->id, state);
}
for (int i = 0; i < numBrainStats; i++)
{
COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asBrainStats + i);
const int state = apCompLists[player][COMP_BRAIN][i];
if (state != UNAVAILABLE) ini.setValue(psStats->id, state);
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
// load up structure type list file
static bool loadSaveStructTypeList(const char *pFileName)
{
WzConfig ini(pFileName, WzConfig::ReadOnly);
for (int player = 0; player < MAX_PLAYERS; player++)
{
ini.beginGroup("player_" + QString::number(player));
QStringList list = ini.childKeys();
for (int i = 0; i < list.size(); ++i)
{
QString name = list[i];
int state = ini.value(name, UNAVAILABLE).toInt();
int statInc;
ASSERT_OR_RETURN(false, state == UNAVAILABLE || state == AVAILABLE || state == FOUND || state == REDUNDANT,
"Bad state %d for %s", state, name.toUtf8().constData());
for (statInc = 0; statInc < numStructureStats; statInc++) // loop until find the same name
{
STRUCTURE_STATS *psStats = asStructureStats + statInc;
if (name.compare(psStats->id) == 0)
{
apStructTypeLists[player][statInc] = state;
break;
}
}
ASSERT_OR_RETURN(false, statInc != numStructureStats, "Did not find structure %s", name.toUtf8().constData());
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
// Write out the current state of the Struct Type List per player
static bool writeStructTypeListFile(const char *pFileName)
{
WzConfig ini(pFileName);
// Save each type of struct type
for (int player = 0; player < MAX_PLAYERS; player++)
{
ini.beginGroup("player_" + QString::number(player));
STRUCTURE_STATS *psStats = asStructureStats;
for (int i = 0; i < numStructureStats; i++, psStats++)
{
if (apStructTypeLists[player][i] != UNAVAILABLE) ini.setValue(psStats->id, apStructTypeLists[player][i]);
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
// load up saved research file
bool loadSaveResearch(const char *pFileName)
{
WzConfig ini(pFileName, WzConfig::ReadOnly);
const int players = game.maxPlayers;
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
bool found = false;
char name[MAX_SAVE_NAME_SIZE];
sstrcpy(name, ini.value("name").toString().toUtf8().constData());
int statInc;
for (statInc = 0; statInc < asResearch.size(); statInc++)
{
RESEARCH *psStats = &asResearch[statInc];
//loop until find the same name
if (psStats->id.compare(name) == 0)
{
found = true;
break;
}
}
if (!found)
{
//ignore this record
continue;
}
QStringList researchedList = ini.value("researched").toStringList();
QStringList possiblesList = ini.value("possible").toStringList();
QStringList pointsList = ini.value("currentPoints").toStringList();
ASSERT(researchedList.size() == players, "Bad researched list for %s", name);
ASSERT(possiblesList.size() == players, "Bad possible list for %s", name);
ASSERT(pointsList.size() == players, "Bad points list for %s", name);
for (int plr = 0; plr < players; plr++)
{
PLAYER_RESEARCH *psPlRes;
int researched = researchedList.at(plr).toInt();
int possible = possiblesList.at(plr).toInt();
int points = pointsList.at(plr).toInt();
psPlRes = &asPlayerResList[plr][statInc];
// Copy the research status
psPlRes->ResearchStatus = (researched & RESBITS);
if (possible != 0)
{
MakeResearchPossible(psPlRes);
}
psPlRes->currentPoints = points;
//for any research that has been completed - perform so that upgrade values are set up
if (researched == RESEARCHED)
{
researchResult(statInc, plr, false, NULL, false);
}
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
// Write out the current state of the Research per player
static bool writeResearchFile(char *pFileName)
{
WzConfig ini(pFileName);
for (int i = 0; i < asResearch.size(); ++i)
{
RESEARCH *psStats = &asResearch[i];
bool valid = false;
QStringList possibles, researched, points;
for (int player = 0; player < game.maxPlayers; player++)
{
possibles.push_back(QString::number(IsResearchPossible(&asPlayerResList[player][i])));
researched.push_back(QString::number(asPlayerResList[player][i].ResearchStatus & RESBITS));
points.push_back(QString::number(asPlayerResList[player][i].currentPoints));
if (IsResearchPossible(&asPlayerResList[player][i]) || (asPlayerResList[player][i].ResearchStatus & RESBITS) || asPlayerResList[player][i].currentPoints)
{
valid = true; // write this entry
}
}
if (valid)
{
ini.beginGroup("research_" + QString::number(i));
ini.setValue("name", psStats->id);
ini.setValue("possible", possibles);
ini.setValue("researched", researched);
ini.setValue("currentPoints", points);
ini.endGroup();
}
}
return true;
}
// -----------------------------------------------------------------------------------------
// load up saved message file
bool loadSaveMessage(const char *pFileName, SWORD levelType)
{
// Only clear the messages if its a mid save game
if (gameType == GTYPE_SAVE_MIDMISSION)
{
freeMessages();
}
else if (gameType == GTYPE_SAVE_START)
{
// If we are loading in a CamStart or a CamChange then we are not interested in any saved messages
if (levelType == LDS_CAMSTART || levelType == LDS_CAMCHANGE)
{
return true;
}
}
WzConfig ini(pFileName, WzConfig::ReadOnly);
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
MESSAGE_TYPE type = (MESSAGE_TYPE)ini.value("type").toInt();
bool bObj = ini.contains("obj/id");
int player = ini.value("player").toInt();
int id = ini.value("id").toInt();
int dataType = ini.value("dataType").toInt();
if (type == MSG_PROXIMITY)
{
//only load proximity if a mid-mission save game
if (gameType == GTYPE_SAVE_MIDMISSION)
{
if (bObj)
{
// Proximity object so create get the obj from saved idy
int objId = ini.value("obj/id").toInt();
int objPlayer = ini.value("obj/player").toInt();
OBJECT_TYPE objType = (OBJECT_TYPE)ini.value("obj/type").toInt();
MESSAGE *psMessage = addMessage(type, true, player);
if (psMessage)
{
psMessage->pViewData = (MSG_VIEWDATA *)getBaseObjFromData(objId, objPlayer, objType);
ASSERT(psMessage->pViewData, "Viewdata object id %d not found for message %d", objId, id);
}
else
{
debug(LOG_ERROR, "Proximity object could not be created (type=%d, player=%d, message=%d)",
type, player, id);
}
}
else
{
VIEWDATA *psViewData = NULL;
// Proximity position so get viewdata pointer from the name
MESSAGE *psMessage = addMessage(type, false, player);
if (psMessage)
{
if (dataType == MSG_DATA_BEACON)
{
Vector2i pos = ini.vector2i("position");
int sender = ini.value("sender").toInt();
psViewData = CreateBeaconViewData(sender, pos.x, pos.y);
ASSERT(psViewData, "Could not create view data for message %d", id);
if (psViewData == NULL)
{
// Skip this message
removeMessage(psMessage, player);
continue;
}
}
else if (ini.contains("name"))
{
psViewData = (VIEWDATA *)getViewData(ini.value("name").toString().toUtf8().constData());
ASSERT(psViewData, "Failed to find view data for proximity position - skipping message %d", id);
if (psViewData == NULL)
{
// Skip this message
removeMessage(psMessage, player);
continue;
}
}
else
{
debug(LOG_ERROR, "Proximity position with empty name skipped (message %d)", id);
removeMessage(psMessage, player);
continue;
}
psMessage->pViewData = (MSG_VIEWDATA *)psViewData;
// Check the z value is at least the height of the terrain
const int height = map_Height(((VIEW_PROXIMITY *)psViewData->pData)->x, ((VIEW_PROXIMITY *)psViewData->pData)->y);
if (((VIEW_PROXIMITY *)psViewData->pData)->z < height)
{
((VIEW_PROXIMITY *)psViewData->pData)->z = height;
}
}
else
{
debug(LOG_ERROR, "Proximity position could not be created (type=%d, player=%d, message=%d)",
type, player, id);
}
}
}
}
else
{
// Only load Campaign/Mission messages if a mid-mission save game; always load research messages
if (type == MSG_RESEARCH || gameType == GTYPE_SAVE_MIDMISSION)
{
MESSAGE *psMessage = addMessage(type, false, player);
ASSERT(psMessage, "Could not create message %d", id);
if (psMessage)
{
psMessage->pViewData = (MSG_VIEWDATA *)getViewData(ini.value("name").toString().toUtf8().constData());
ASSERT(psMessage->pViewData, "Failed to find view data for message %d", id);
}
}
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
// Write out the current messages per player
static bool writeMessageFile(const char *pFileName)
{
WzConfig ini(pFileName);
int numMessages = 0;
// save each type of research
for (int player = 0; player < game.maxPlayers; player++)
{
for (MESSAGE *psMessage = apsMessages[player]; psMessage != NULL;psMessage = psMessage->psNext)
{
ini.beginGroup("message_" + QString::number(numMessages++));
ini.setValue("id", numMessages - 1); // for future use
ini.setValue("player", player);
ini.setValue("type", psMessage->type);
ini.setValue("dataType", psMessage->dataType);
if (psMessage->type == MSG_PROXIMITY)
{
//get the matching proximity message
PROXIMITY_DISPLAY *psProx = NULL;
for (psProx = apsProxDisp[player]; psProx != NULL; psProx = psProx->psNext)
{
//compare the pointers
if (psProx->psMessage == psMessage)
{
break;
}
}
ASSERT(psProx != NULL,"Save message; proximity display not found for message");
if (psProx->type == POS_PROXDATA)
{
//message has viewdata so store the name
VIEWDATA *pViewData = (VIEWDATA*)psMessage->pViewData;
ini.setValue("name", pViewData->pName);
// save beacon data
if (psMessage->dataType == MSG_DATA_BEACON)
{
VIEW_PROXIMITY *viewData = (VIEW_PROXIMITY *)((VIEWDATA *)psMessage->pViewData)->pData;
ini.setVector2i("position", Vector2i(viewData->x, viewData->y));
ini.setValue("sender", viewData->sender);
}
}
else
{
// message has object so store Object Id
BASE_OBJECT *psObj = (BASE_OBJECT*)psMessage->pViewData;
if (psObj)
{
ini.setValue("obj/id", psObj->id);
ini.setValue("obj/player", psObj->player);
ini.setValue("obj/type", psObj->type);
}
else
{
ASSERT(false, "Message type has no object data to save ?");
}
}
}
else
{
VIEWDATA *pViewData = (VIEWDATA*)psMessage->pViewData;
ini.setValue("name", pViewData != NULL? pViewData->pName : "NULL");
}
ini.setValue("read", psMessage->read); // flag to indicate whether message has been read; not that this is/was _not_ read by loading code!?
ASSERT(player == psMessage->player, "Bad player number (%d == %d)", player, psMessage->player);
ini.endGroup();
}
}
return true;
}
// -----------------------------------------------------------------------------------------
bool loadSaveStructLimits(const char *pFileName)
{
WzConfig ini(pFileName, WzConfig::ReadOnly);
for (int player = 0; player < game.maxPlayers; player++)
{
ini.beginGroup("player_" + QString::number(player));
QStringList list = ini.childKeys();
for (int i = 0; i < list.size(); ++i)
{
QString name = list[i];
int limit = ini.value(name, 0).toInt();
int statInc;
for (statInc = 0; statInc < numStructureStats; statInc++)
{
STRUCTURE_STATS *psStats = asStructureStats + statInc;
if (name.compare(psStats->id) == 0)
{
asStructLimits[player][statInc].limit = limit != 255? limit : LOTS_OF;
break;
}
}
ASSERT_OR_RETURN(false, statInc != numStructureStats, "Did not find structure %s", name.toUtf8().constData());
}
ini.endGroup();
}
return true;
}
// -----------------------------------------------------------------------------------------
/*
Writes the list of structure limits to a file
*/
bool writeStructLimitsFile(const char *pFileName)
{
WzConfig ini(pFileName);
// Save each type of struct type
for (int player = 0; player < game.maxPlayers; player++)
{
ini.beginGroup("player_" + QString::number(player));
STRUCTURE_STATS *psStats = asStructureStats;
for (int i = 0; i < numStructureStats; i++, psStats++)
{
const int limit = MIN(asStructLimits[player][i].limit, 255);
if (limit != 255) ini.setValue(psStats->id, limit);
}
ini.endGroup();
}
return true;
}
/*!
* Load the current fire-support designated commanders (the one who has fire-support enabled)
*/
bool readFiresupportDesignators(const char *pFileName)
{
WzConfig ini(pFileName, WzConfig::ReadOnly);
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
uint32_t id = ini.value("Player_" + QString::number(i) + "/id", NULL_ID).toInt();
if (id != NULL_ID)
{
cmdDroidSetDesignator((DROID*)getBaseObjFromId(id));
}
}
return true;
}
/*!
* Save the current fire-support designated commanders (the one who has fire-support enabled)
*/
bool writeFiresupportDesignators(const char *pFileName)
{
int player;
WzConfig ini(pFileName);
for (player = 0; player < MAX_PLAYERS; player++)
{
DROID *psDroid = cmdDroidGetDesignator(player);
if (psDroid != NULL)
{
ini.setValue("Player_" + QString::number(player) + "/id", psDroid->id);
}
}
return true;
}
// -----------------------------------------------------------------------------------------
// write the event state to a file on disk
static bool writeScriptState(const char *pFileName)
{
char jsFilename[PATH_MAX], *ext;
if (!eventSaveState(pFileName))
{
return false;
}
// The below belongs to the new javascript stuff
strcpy(jsFilename, pFileName);
ext = strrchr(jsFilename, '/');
*ext = '\0';
strcat(jsFilename, "/scriptstate.ini");
saveScriptStates(jsFilename);
return true;
}
// -----------------------------------------------------------------------------------------
// load the script state given a .gam name
bool loadScriptState(char *pFileName)
{
char jsFilename[PATH_MAX];
pFileName[strlen(pFileName) - 4] = '\0';
// The below belongs to the new javascript stuff
strcpy(jsFilename, pFileName);
strcat(jsFilename, "/scriptstate.ini");
loadScriptStates(jsFilename);
// change the file extension
strcat(pFileName, "/scriptstate.es");
if (!eventLoadState(pFileName))
{
return false;
}
return true;
}
// -----------------------------------------------------------------------------------------
/* set the global scroll values to use for the save game */
static void setMapScroll(void)
{
//if loading in a pre version5 then scroll values will not have been set up so set to max poss
if (width == 0 && height == 0)
{
scrollMinX = 0;
scrollMaxX = mapWidth;
scrollMinY = 0;
scrollMaxY = mapHeight;
return;
}
scrollMinX = startX;
scrollMinY = startY;
scrollMaxX = startX + width;
scrollMaxY = startY + height;
//check not going beyond width/height of map
if (scrollMaxX > (SDWORD)mapWidth)
{
scrollMaxX = mapWidth;
debug( LOG_NEVER, "scrollMaxX was too big It has been set to map width" );
}
if (scrollMaxY > (SDWORD)mapHeight)
{
scrollMaxY = mapHeight;
debug( LOG_NEVER, "scrollMaxY was too big It has been set to map height" );
}
}
// -----------------------------------------------------------------------------------------
/*returns the current type of save game being loaded*/
UDWORD getSaveGameType(void)
{
return gameType;
}
static void plotBackdropPixel(char *backDropSprite, int xx, int yy, PIELIGHT const &colour)
{
xx = clip(xx, 0, BACKDROP_HACK_WIDTH - 1);
yy = clip(yy, 0, BACKDROP_HACK_HEIGHT - 1);
char *pixel = backDropSprite + (yy*BACKDROP_HACK_WIDTH + xx)*3;
pixel[0] = colour.byte.r;
pixel[1] = colour.byte.g;
pixel[2] = colour.byte.b;
}
/**
* \param[out] backDropSprite The premade map texture.
* \param scale Scale of the map texture.
* \param[out] playeridpos Will contain the position on the map where the player's HQ are located.
*
* Reads the current map and colours the map preview for any structures
* present. Additionally we load the player's HQ location into playeridpos so
* we know the player's starting location.
*/
bool plotStructurePreview16(char *backDropSprite, Vector2i playeridpos[])
{
union { SAVE_STRUCTURE_V2 v2; SAVE_STRUCTURE_V20 v20; } sSave; // close eyes now.
SAVE_STRUCTURE_V2 *psSaveStructure2 = &sSave.v2;
SAVE_STRUCTURE_V20 *psSaveStructure20 = &sSave.v20;
// ok you can open them again..
STRUCT_SAVEHEADER *psHeader;
char aFileName[256];
UDWORD xx, yy, count, fileSize, sizeOfSaveStructure = sizeof(SAVE_STRUCTURE);
UDWORD playerid = 0;
char *pFileData = NULL;
LEVEL_DATASET *psLevel;
PIELIGHT color = WZCOL_BLACK ;
bool HQ = false;
psLevel = levFindDataSet(game.map, &game.hash);
strcpy(aFileName, psLevel->apDataFiles[0]);
aFileName[strlen(aFileName) - 4] = '\0';
strcat(aFileName, "/struct.bjo");
if (!PHYSFS_exists(aFileName)) // use new version of structure data
{
strcpy(aFileName, psLevel->apDataFiles[0]);
aFileName[strlen(aFileName) - 4] = '\0';
strcat(aFileName, "/struct.ini");
WzConfig ini(aFileName, WzConfig::ReadOnly);
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
QString name = ini.value("name").toString();
Position pos = ini.vector3i("position");
playerid = ini.value("startpos", scavengerSlot()).toInt(); // No conversion should be going on, this is the map makers position when player X should be.
if (name.startsWith("A0CommandCentre"))
{
HQ = true;
xx = playeridpos[playerid].x = map_coord(pos.x);
yy = playeridpos[playerid].y = map_coord(pos.y);
}
else
{
HQ = false;
xx = map_coord(pos.x);
yy = map_coord(pos.y);
}
playerid = getPlayerColour(RemapPlayerNumber(playerid));
// kludge to fix black, so you can see it on some maps.
if (playerid == 3) // in this case 3 = palette entry for black.
{
color = WZCOL_GREY;
}
else
{
color.rgba = clanColours[playerid].rgba;
}
if (HQ)
{ // This shows where the HQ is on the map in a special color.
// We could do the same for anything else (oil/whatever) also.
// Possible future enhancement?
color = WZCOL_MAP_PREVIEW_HQ;
}
// and now we blit the color to the texture
plotBackdropPixel(backDropSprite, xx, yy, color);
ini.endGroup();
}
// And now we need to show features.
plotFeature(backDropSprite);
return true;
}
pFileData = fileLoadBuffer;
if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
{
debug(LOG_NEVER, "Failed to load file to buffer.");
}
/* Check the file type */
psHeader = (STRUCT_SAVEHEADER *)pFileData;
if (psHeader->aFileType[0] != 's' || psHeader->aFileType[1] != 't' ||
psHeader->aFileType[2] != 'r' || psHeader->aFileType[3] != 'u')
{
debug(LOG_ERROR, "Invalid file type.");
return false;
}
/* STRUCT_SAVEHEADER */
endian_udword(&psHeader->version);
endian_udword(&psHeader->quantity);
//increment to the start of the data
pFileData += STRUCT_HEADER_SIZE;
if (psHeader->version < VERSION_12)
{
sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V2);
}
else if (psHeader->version < VERSION_14)
{
sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V12);
}
else if (psHeader->version <= VERSION_14)
{
sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V14);
}
else if (psHeader->version <= VERSION_16)
{
sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V15);
}
else if (psHeader->version <= VERSION_19)
{
sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V17);
}
else if (psHeader->version <= VERSION_20)
{
sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V20);
}
/* Load in the structure data */
for (count = 0; count < psHeader->quantity; count++, pFileData += sizeOfSaveStructure)
{
// we are specifically looking for the HQ, and it seems this is the only way to
// find it via parsing map.
// We store the coordinates of the structure, into a array for as many players as are on the map.
if (psHeader->version <= VERSION_19)
{
// All versions up to 19 are compatible with V2.
memcpy(psSaveStructure2, pFileData, sizeof(SAVE_STRUCTURE_V2));
endian_udword(&psSaveStructure2->x);
endian_udword(&psSaveStructure2->y);
endian_udword(&psSaveStructure2->player);
playerid = psSaveStructure2->player;
if (strncmp(psSaveStructure2->name, "A0CommandCentre", 15) == 0)
{
HQ = true;
xx = playeridpos[playerid].x = map_coord(psSaveStructure2->x);
yy = playeridpos[playerid].y = map_coord(psSaveStructure2->y);
}
else
{
HQ = false;
xx = map_coord(psSaveStructure2->x);
yy = map_coord(psSaveStructure2->y);
}
}
else
{
// All newer versions are compatible with V20.
memcpy(psSaveStructure20, pFileData, sizeof(SAVE_STRUCTURE_V20));
endian_udword(&psSaveStructure20->x);
endian_udword(&psSaveStructure20->y);
endian_udword(&psSaveStructure20->player);
playerid = psSaveStructure20->player;
if (strncmp(psSaveStructure20->name, "A0CommandCentre", 15) == 0)
{
HQ = true;
xx = playeridpos[playerid].x = map_coord(psSaveStructure20->x);
yy = playeridpos[playerid].y = map_coord(psSaveStructure20->y);
}
else
{
HQ = false;
xx = map_coord(psSaveStructure20->x);
yy = map_coord(psSaveStructure20->y);
}
}
playerid = getPlayerColour(RemapPlayerNumber(playerid));
// kludge to fix black, so you can see it on some maps.
if (playerid == 3) // in this case 3 = palette entry for black.
{
color = WZCOL_GREY;
}
else
{
color.rgba = clanColours[playerid].rgba;
}
if (HQ)
{ // This shows where the HQ is on the map in a special color.
// We could do the same for anything else (oil/whatever) also.
// Possible future enhancement?
color = WZCOL_MAP_PREVIEW_HQ;
}
// and now we blit the color to the texture
plotBackdropPixel(backDropSprite, xx, yy, color);
}
// And now we need to show features.
plotFeature(backDropSprite);
return true;
}
// Show location of (at this time) oil on the map preview
static void plotFeature(char *backDropSprite)
{
FEATURE_SAVEHEADER *psHeader;
SAVE_FEATURE_V2 *psSaveFeature;
LEVEL_DATASET *psLevel;
UDWORD xx, yy, count, fileSize;
UDWORD sizeOfSaveFeature = 0;
char *pFileData = NULL;
char aFileName[256];
const PIELIGHT colourOil = WZCOL_MAP_PREVIEW_OIL;
const PIELIGHT colourBarrel = WZCOL_MAP_PREVIEW_BARREL;
psLevel = levFindDataSet(game.map, &game.hash);
strcpy(aFileName, psLevel->apDataFiles[0]);
aFileName[strlen(aFileName) - 4] = '\0';
strcat(aFileName, "/feat.bjo");
if (!PHYSFS_exists(aFileName)) // use new version of feature data
{
strcpy(aFileName, psLevel->apDataFiles[0]);
aFileName[strlen(aFileName) - 4] = '\0';
strcat(aFileName, "/feature.ini");
WzConfig ini(aFileName, WzConfig::ReadOnly);
if (ini.status() != QSettings::NoError)
{
debug(LOG_ERROR, "Could not open %s", aFileName);
return;
}
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
QString name = ini.value("name").toString();
Position pos = ini.vector3i("position");
// we only care about oil
PIELIGHT const *colour = NULL;
if (name.startsWith("OilResource"))
{
colour = &colourOil;
}
else if (name.startsWith("OilDrum"))
{
colour = &colourBarrel;
}
if (colour != NULL)
{
// and now we blit the color to the texture
xx = map_coord(pos.x);
yy = map_coord(pos.y);
plotBackdropPixel(backDropSprite, xx, yy, *colour);
}
ini.endGroup();
}
return;
}
// Load in the chosen file data/
pFileData = fileLoadBuffer;
if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
{
debug( LOG_ERROR, "Unable to load file %s?", aFileName);
return;
}
// Check the file type
psHeader = (FEATURE_SAVEHEADER *)pFileData;
if (psHeader->aFileType[0] != 'f' || psHeader->aFileType[1] != 'e' || psHeader->aFileType[2] != 'a' || psHeader->aFileType[3] != 't')
{
debug( LOG_ERROR, "Incorrect file type, looking at %s", aFileName);
return;
}
endian_udword(&psHeader->version);
endian_udword(&psHeader->quantity);
//increment to the start of the data
pFileData += FEATURE_HEADER_SIZE;
sizeOfSaveFeature = sizeof(SAVE_FEATURE_V2);
if ((sizeOfSaveFeature * psHeader->quantity + FEATURE_HEADER_SIZE) > fileSize)
{
debug( LOG_ERROR, "Unexpected end of file ?" );
return;
}
// Load in the feature data
for (count = 0; count < psHeader->quantity; count++, pFileData += sizeOfSaveFeature)
{
// All versions up to 19 are compatible with V2.
psSaveFeature = (SAVE_FEATURE_V2*) pFileData;
// we only care about oil
PIELIGHT const *colour = NULL;
if (strncmp(psSaveFeature->name, "OilResource", 11) == 0)
{
colour = &colourOil;
}
else if (strncmp(psSaveFeature->name, "OilDrum", 7) == 0)
{
colour = &colourBarrel;
}
else
{
continue;
}
endian_udword(&psSaveFeature->x);
endian_udword(&psSaveFeature->y);
xx = map_coord(psSaveFeature->x);
yy = map_coord(psSaveFeature->y);
// and now we blit the color to the texture
plotBackdropPixel(backDropSprite, xx, yy, *colour);
}
}