1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 1999-2004 Eidos Interactive
4 Copyright (C) 2005-2020 Warzone 2100 Project
5
6 Warzone 2100 is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Warzone 2100 is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Warzone 2100; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 #include "lib/framework/wzapp.h"
21
22 /* Standard library headers */
23 #include <physfs.h>
24 #include <string.h>
25
26 /* Warzone src and library headers */
27 #include "lib/framework/endian_hack.h"
28 #include "lib/framework/math_ext.h"
29 #include "lib/framework/wzconfig.h"
30 #include "lib/framework/file.h"
31 #include "lib/framework/physfs_ext.h"
32 #include "lib/framework/strres.h"
33 #include "lib/framework/frameresource.h"
34
35 #include "lib/gamelib/gtime.h"
36 #include "lib/ivis_opengl/ivisdef.h"
37 #include "lib/ivis_opengl/pieblitfunc.h"
38 #include "lib/ivis_opengl/piestate.h"
39 #include "lib/ivis_opengl/piepalette.h"
40 #include "lib/ivis_opengl/textdraw.h"
41 #include "lib/netplay/netplay.h"
42 #include "lib/sound/audio.h"
43 #include "lib/sound/audio_id.h"
44 #include "modding.h"
45 #include "main.h"
46 #include "game.h"
47 #include "qtscript.h"
48 #include "fpath.h"
49 #include "difficulty.h"
50 #include "map.h"
51 #include "move.h"
52 #include "droid.h"
53 #include "order.h"
54 #include "action.h"
55 #include "research.h"
56 #include "power.h"
57 #include "projectile.h"
58 #include "loadsave.h"
59 #include "text.h"
60 #include "message.h"
61 #include "hci.h"
62 #include "display.h"
63 #include "display3d.h"
64 #include "map.h"
65 #include "effects.h"
66 #include "init.h"
67 #include "mission.h"
68 #include "scores.h"
69 #include "design.h"
70 #include "component.h"
71 #include "radar.h"
72 #include "cmddroid.h"
73 #include "warzoneconfig.h"
74 #include "multiplay.h"
75 #include "frontend.h"
76 #include "levels.h"
77 #include "mission.h"
78 #include "geometry.h"
79 #include "gateway.h"
80 #include "multistat.h"
81 #include "multiint.h"
82 #include "wrappers.h"
83 #include "challenge.h"
84 #include "combat.h"
85 #include "template.h"
86 #include "version.h"
87 #include "lib/ivis_opengl/screen.h"
88 #include "keymap.h"
89 #include <ctime>
90 #include "multimenu.h"
91 #include "console.h"
92 #include "wzscriptdebug.h"
93
94 #if defined(__clang__)
95 #pragma clang diagnostic ignored "-Wcast-align" // TODO: FIXME!
96 #elif defined(__GNUC__)
97 #pragma GCC diagnostic ignored "-Wcast-align" // TODO: FIXME!
98 #endif
99
100
gameScreenSizeDidChange(unsigned int oldWidth,unsigned int oldHeight,unsigned int newWidth,unsigned int newHeight)101 void gameScreenSizeDidChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight)
102 {
103 intScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
104 loadSaveScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
105 challengesScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
106 multiMenuScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
107 display3dScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
108 consoleScreenDidChangeSize(oldWidth, oldHeight, newWidth, newHeight);
109 scriptDebuggerScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
110 frontendScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight); // must be last!
111 }
112
gameDisplayScaleFactorDidChange(float newDisplayScaleFactor)113 void gameDisplayScaleFactorDidChange(float newDisplayScaleFactor)
114 {
115 // The text subsystem requires the game -> renderer scale factor, which potentially differs from
116 // the display scale factor.
117 float horizGameToRendererScaleFactor = 0.f, vertGameToRendererScaleFactor = 0.f;
118 wzGetGameToRendererScaleFactor(&horizGameToRendererScaleFactor, &vertGameToRendererScaleFactor);
119 iV_TextUpdateScaleFactor(horizGameToRendererScaleFactor, vertGameToRendererScaleFactor);
120 }
121
122
123 #define MAX_SAVE_NAME_SIZE_V19 40
124 #define MAX_SAVE_NAME_SIZE 60
125
126 static const UDWORD NULL_ID = UDWORD_MAX;
127 #define SAVEKEY_ONMISSION 0x100
128
129 static UDWORD RemapPlayerNumber(UDWORD OldNumber);
130 static void plotScriptFeature(ScriptMapData const &data, char *backDropSprite);
131 static void plotFeature(char *backDropSprite);
132 bool writeGameInfo(const char *pFileName);
133
134 /** struct used to store the data for retreating. */
135 struct RUN_DATA
136 {
137 Vector2i sPos = Vector2i(0, 0); ///< position to where units should flee to.
138 uint8_t forceLevel = 0; ///< number of units below which others might flee.
139 uint8_t healthLevel = 0; ///< health percentage value below which it might flee. This value is used for groups only.
140 uint8_t leadership = 0; ///< basic value that will be used on calculations of the flee probability.
141 };
142
143 // return positions for vtols, at one time.
144 Vector2i asVTOLReturnPos[MAX_PLAYERS];
145
146 struct GAME_SAVEHEADER
147 {
148 char aFileType[4];
149 uint32_t version;
150 };
151
serializeSaveGameHeader(PHYSFS_file * fileHandle,const GAME_SAVEHEADER * serializeHeader)152 static bool serializeSaveGameHeader(PHYSFS_file *fileHandle, const GAME_SAVEHEADER *serializeHeader)
153 {
154 if (WZ_PHYSFS_writeBytes(fileHandle, serializeHeader->aFileType, 4) != 4)
155 {
156 return false;
157 }
158
159 // Write version numbers below version 35 as little-endian, and those above as big-endian
160 if (serializeHeader->version < VERSION_35)
161 {
162 return PHYSFS_writeULE32(fileHandle, serializeHeader->version);
163 }
164 else
165 {
166 return PHYSFS_writeUBE32(fileHandle, serializeHeader->version);
167 }
168 }
169
deserializeSaveGameHeader(PHYSFS_file * fileHandle,GAME_SAVEHEADER * serializeHeader)170 static bool deserializeSaveGameHeader(PHYSFS_file *fileHandle, GAME_SAVEHEADER *serializeHeader)
171 {
172 // Read in the header from the file
173 if (WZ_PHYSFS_readBytes(fileHandle, serializeHeader->aFileType, 4) != 4
174 || WZ_PHYSFS_readBytes(fileHandle, &serializeHeader->version, sizeof(uint32_t)) != sizeof(uint32_t))
175 {
176 return false;
177 }
178
179 // All save game file versions below version 35 (i.e. _not_ version 35 itself)
180 // have their version numbers stored as little endian. Versions from 35 and
181 // onward use big-endian. This basically means that, because of endian
182 // swapping, numbers from 35 and onward will be ridiculously high if a
183 // little-endian byte-order is assumed.
184
185 // Convert from little endian to native byte-order and check if we get a
186 // ridiculously high number
187 endian_udword(&serializeHeader->version);
188
189 if (serializeHeader->version <= VERSION_34)
190 {
191 // Apparently we don't get a ridiculously high number if we assume
192 // little-endian, so lets assume our version number is 34 at max and return
193 debug(LOG_SAVE, "Version = %u (little-endian)", serializeHeader->version);
194
195 return true;
196 }
197 else
198 {
199 // Apparently we get a larger number than expected if using little-endian.
200 // So assume we have a version of 35 and onward
201
202 // Reverse the little-endian decoding
203 endian_udword(&serializeHeader->version);
204 }
205
206 // Considering that little-endian didn't work we now use big-endian instead
207 serializeHeader->version = PHYSFS_swapUBE32(serializeHeader->version);
208 debug(LOG_SAVE, "Version %u = (big-endian)", serializeHeader->version);
209
210 return true;
211 }
212
213 struct STRUCT_SAVEHEADER : public GAME_SAVEHEADER
214 {
215 UDWORD quantity;
216 };
217
218 struct FEATURE_SAVEHEADER : public GAME_SAVEHEADER
219 {
220 UDWORD quantity;
221 };
222
223 /* Structure definitions for loading and saving map data */
224 struct TILETYPE_SAVEHEADER : public GAME_SAVEHEADER
225 {
226 UDWORD quantity;
227 };
228
229 /* Sanity check definitions for the save struct file sizes */
230 #define DROIDINIT_HEADER_SIZE 12
231 #define STRUCT_HEADER_SIZE 12
232 #define FEATURE_HEADER_SIZE 12
233 #define TILETYPE_HEADER_SIZE 12
234
235 // general save definitions
236 #define MAX_LEVEL_SIZE 20
237
238 #define OBJECT_SAVE_V19 \
239 char name[MAX_SAVE_NAME_SIZE_V19]; \
240 UDWORD id; \
241 UDWORD x,y,z; \
242 UDWORD direction; \
243 UDWORD player; \
244 int32_t inFire; \
245 UDWORD periodicalDamageStart; \
246 UDWORD periodicalDamage
247
248 #define OBJECT_SAVE_V20 \
249 char name[MAX_SAVE_NAME_SIZE]; \
250 UDWORD id; \
251 UDWORD x,y,z; \
252 UDWORD direction; \
253 UDWORD player; \
254 int32_t inFire; \
255 UDWORD periodicalDamageStart; \
256 UDWORD periodicalDamage
257
258 struct SAVE_POWER
259 {
260 uint32_t currentPower;
261 uint32_t extractedPower; // used for hacks
262 };
263
serializeSavePowerData(PHYSFS_file * fileHandle,const SAVE_POWER * serializePower)264 static bool serializeSavePowerData(PHYSFS_file *fileHandle, const SAVE_POWER *serializePower)
265 {
266 return (PHYSFS_writeUBE32(fileHandle, serializePower->currentPower)
267 && PHYSFS_writeUBE32(fileHandle, serializePower->extractedPower));
268 }
269
deserializeSavePowerData(PHYSFS_file * fileHandle,SAVE_POWER * serializePower)270 static bool deserializeSavePowerData(PHYSFS_file *fileHandle, SAVE_POWER *serializePower)
271 {
272 return (PHYSFS_readUBE32(fileHandle, &serializePower->currentPower)
273 && PHYSFS_readUBE32(fileHandle, &serializePower->extractedPower));
274 }
275
serializeVector3i(PHYSFS_file * fileHandle,const Vector3i * serializeVector)276 static bool serializeVector3i(PHYSFS_file *fileHandle, const Vector3i *serializeVector)
277 {
278 return (PHYSFS_writeSBE32(fileHandle, serializeVector->x)
279 && PHYSFS_writeSBE32(fileHandle, serializeVector->y)
280 && PHYSFS_writeSBE32(fileHandle, serializeVector->z));
281 }
282
deserializeVector3i(PHYSFS_file * fileHandle,Vector3i * serializeVector)283 static bool deserializeVector3i(PHYSFS_file *fileHandle, Vector3i *serializeVector)
284 {
285 int32_t x, y, z;
286
287 if (!PHYSFS_readSBE32(fileHandle, &x)
288 || !PHYSFS_readSBE32(fileHandle, &y)
289 || !PHYSFS_readSBE32(fileHandle, &z))
290 {
291 return false;
292 }
293
294 serializeVector-> x = x;
295 serializeVector-> y = y;
296 serializeVector-> z = z;
297
298 return true;
299 }
300
serializeVector2i(PHYSFS_file * fileHandle,const Vector2i * serializeVector)301 static bool serializeVector2i(PHYSFS_file *fileHandle, const Vector2i *serializeVector)
302 {
303 return (PHYSFS_writeSBE32(fileHandle, serializeVector->x)
304 && PHYSFS_writeSBE32(fileHandle, serializeVector->y));
305 }
306
deserializeVector2i(PHYSFS_file * fileHandle,Vector2i * serializeVector)307 static bool deserializeVector2i(PHYSFS_file *fileHandle, Vector2i *serializeVector)
308 {
309 int32_t x, y;
310
311 if (!PHYSFS_readSBE32(fileHandle, &x)
312 || !PHYSFS_readSBE32(fileHandle, &y))
313 {
314 return false;
315 }
316
317 serializeVector-> x = x;
318 serializeVector-> y = y;
319
320 return true;
321 }
322
serializeiViewData(PHYSFS_file * fileHandle,const iView * serializeView)323 static bool serializeiViewData(PHYSFS_file *fileHandle, const iView *serializeView)
324 {
325 return (serializeVector3i(fileHandle, &serializeView->p)
326 && serializeVector3i(fileHandle, &serializeView->r));
327 }
328
deserializeiViewData(PHYSFS_file * fileHandle,iView * serializeView)329 static bool deserializeiViewData(PHYSFS_file *fileHandle, iView *serializeView)
330 {
331 return (deserializeVector3i(fileHandle, &serializeView->p)
332 && deserializeVector3i(fileHandle, &serializeView->r));
333 }
334
serializeRunData(PHYSFS_file * fileHandle,const RUN_DATA * serializeRun)335 static bool serializeRunData(PHYSFS_file *fileHandle, const RUN_DATA *serializeRun)
336 {
337 return (serializeVector2i(fileHandle, &serializeRun->sPos)
338 && PHYSFS_writeUBE8(fileHandle, serializeRun->forceLevel)
339 && PHYSFS_writeUBE8(fileHandle, serializeRun->healthLevel)
340 && PHYSFS_writeUBE8(fileHandle, serializeRun->leadership));
341 }
342
deserializeRunData(PHYSFS_file * fileHandle,RUN_DATA * serializeRun)343 static bool deserializeRunData(PHYSFS_file *fileHandle, RUN_DATA *serializeRun)
344 {
345 return (deserializeVector2i(fileHandle, &serializeRun->sPos)
346 && PHYSFS_readUBE8(fileHandle, &serializeRun->forceLevel)
347 && PHYSFS_readUBE8(fileHandle, &serializeRun->healthLevel)
348 && PHYSFS_readUBE8(fileHandle, &serializeRun->leadership));
349 }
350
serializeLandingZoneData(PHYSFS_file * fileHandle,const LANDING_ZONE * serializeLandZone)351 static bool serializeLandingZoneData(PHYSFS_file *fileHandle, const LANDING_ZONE *serializeLandZone)
352 {
353 return (PHYSFS_writeUBE8(fileHandle, serializeLandZone->x1)
354 && PHYSFS_writeUBE8(fileHandle, serializeLandZone->y1)
355 && PHYSFS_writeUBE8(fileHandle, serializeLandZone->x2)
356 && PHYSFS_writeUBE8(fileHandle, serializeLandZone->y2));
357 }
358
deserializeLandingZoneData(PHYSFS_file * fileHandle,LANDING_ZONE * serializeLandZone)359 static bool deserializeLandingZoneData(PHYSFS_file *fileHandle, LANDING_ZONE *serializeLandZone)
360 {
361 return (PHYSFS_readUBE8(fileHandle, &serializeLandZone->x1)
362 && PHYSFS_readUBE8(fileHandle, &serializeLandZone->y1)
363 && PHYSFS_readUBE8(fileHandle, &serializeLandZone->x2)
364 && PHYSFS_readUBE8(fileHandle, &serializeLandZone->y2));
365 }
366
serializeMultiplayerGame(PHYSFS_file * fileHandle,const MULTIPLAYERGAME * serializeMulti)367 static bool serializeMultiplayerGame(PHYSFS_file *fileHandle, const MULTIPLAYERGAME *serializeMulti)
368 {
369 const char *dummy8c = "DUMMYSTRING";
370
371 if (!PHYSFS_writeUBE8(fileHandle, static_cast<uint8_t>(serializeMulti->type))
372 || WZ_PHYSFS_writeBytes(fileHandle, serializeMulti->map, 128) != 128
373 || WZ_PHYSFS_writeBytes(fileHandle, dummy8c, 8) != 8
374 || !PHYSFS_writeUBE8(fileHandle, serializeMulti->maxPlayers)
375 || WZ_PHYSFS_writeBytes(fileHandle, serializeMulti->name, 128) != 128
376 || !PHYSFS_writeSBE32(fileHandle, 0)
377 || !PHYSFS_writeUBE32(fileHandle, serializeMulti->power)
378 || !PHYSFS_writeUBE8(fileHandle, serializeMulti->base)
379 || !PHYSFS_writeUBE8(fileHandle, serializeMulti->alliance)
380 || !PHYSFS_writeUBE8(fileHandle, serializeMulti->hash.Bytes)
381 || !WZ_PHYSFS_writeBytes(fileHandle, serializeMulti->hash.bytes, serializeMulti->hash.Bytes)
382 || !PHYSFS_writeUBE16(fileHandle, 0) // dummy, was bytesPerSec
383 || !PHYSFS_writeUBE8(fileHandle, 0) // dummy, was packetsPerSec
384 || !PHYSFS_writeUBE8(fileHandle, challengeActive)) // reuse available field, was encryptKey
385 {
386 return false;
387 }
388
389 for (unsigned int i = 0; i < MAX_PLAYERS; ++i)
390 {
391 // dummy, was `skDiff` for each player
392 if (!PHYSFS_writeUBE8(fileHandle, 0))
393 {
394 return false;
395 }
396 }
397
398 return true;
399 }
400
deserializeMultiplayerGame(PHYSFS_file * fileHandle,MULTIPLAYERGAME * serializeMulti)401 static bool deserializeMultiplayerGame(PHYSFS_file *fileHandle, MULTIPLAYERGAME *serializeMulti)
402 {
403 int32_t boolFog;
404 uint8_t dummy8;
405 uint16_t dummy16;
406 char dummy8c[8];
407 uint8_t hashSize;
408
409 serializeMulti->hash.setZero();
410
411 if (!PHYSFS_readUBE8(fileHandle, reinterpret_cast<uint8_t*>(&serializeMulti->type))
412 || WZ_PHYSFS_readBytes(fileHandle, serializeMulti->map, 128) != 128
413 || WZ_PHYSFS_readBytes(fileHandle, dummy8c, 8) != 8
414 || !PHYSFS_readUBE8(fileHandle, &serializeMulti->maxPlayers)
415 || WZ_PHYSFS_readBytes(fileHandle, serializeMulti->name, 128) != 128
416 || !PHYSFS_readSBE32(fileHandle, &boolFog)
417 || !PHYSFS_readUBE32(fileHandle, &serializeMulti->power)
418 || !PHYSFS_readUBE8(fileHandle, &serializeMulti->base)
419 || !PHYSFS_readUBE8(fileHandle, &serializeMulti->alliance)
420 || !PHYSFS_readUBE8(fileHandle, &hashSize)
421 || (hashSize == serializeMulti->hash.Bytes && !WZ_PHYSFS_readBytes(fileHandle, serializeMulti->hash.bytes, serializeMulti->hash.Bytes))
422 || !PHYSFS_readUBE16(fileHandle, &dummy16) // dummy, was bytesPerSec
423 || !PHYSFS_readUBE8(fileHandle, &dummy8) // dummy, was packetsPerSec
424 || !PHYSFS_readUBE8(fileHandle, &dummy8)) // reused for challenge, was encryptKey
425 {
426 return false;
427 }
428 challengeActive = dummy8; // hack
429
430 for (unsigned int i = 0; i < MAX_PLAYERS; ++i)
431 {
432 // dummy, was `skDiff` for each player
433 if (!PHYSFS_readUBE8(fileHandle, &dummy8))
434 {
435 return false;
436 }
437 }
438
439 return true;
440 }
441
serializePlayer(PHYSFS_file * fileHandle,const PLAYER * serializePlayer,int player)442 static bool serializePlayer(PHYSFS_file *fileHandle, const PLAYER *serializePlayer, int player)
443 {
444 return (PHYSFS_writeUBE32(fileHandle, serializePlayer->position)
445 && WZ_PHYSFS_writeBytes(fileHandle, serializePlayer->name, StringSize) == StringSize
446 && WZ_PHYSFS_writeBytes(fileHandle, getAIName(player), MAX_LEN_AI_NAME) == MAX_LEN_AI_NAME
447 && PHYSFS_writeSBE8(fileHandle, static_cast<int8_t>(serializePlayer->difficulty))
448 && PHYSFS_writeUBE8(fileHandle, (uint8_t)serializePlayer->allocated)
449 && PHYSFS_writeUBE32(fileHandle, serializePlayer->colour)
450 && PHYSFS_writeUBE32(fileHandle, serializePlayer->team));
451 }
452
deserializePlayer(PHYSFS_file * fileHandle,PLAYER * serializePlayer,int player)453 static bool deserializePlayer(PHYSFS_file *fileHandle, PLAYER *serializePlayer, int player)
454 {
455 char aiName[MAX_LEN_AI_NAME] = { "THEREISNOAI" };
456 uint32_t position = 0, colour = 0, team = 0;
457 bool retval = false;
458 uint8_t allocated = 0;
459
460 retval = (PHYSFS_readUBE32(fileHandle, &position)
461 && WZ_PHYSFS_readBytes(fileHandle, serializePlayer->name, StringSize) == StringSize
462 && WZ_PHYSFS_readBytes(fileHandle, aiName, MAX_LEN_AI_NAME) == MAX_LEN_AI_NAME
463 && PHYSFS_readSBE8(fileHandle, reinterpret_cast<int8_t*>(&serializePlayer->difficulty))
464 && PHYSFS_readUBE8(fileHandle, &allocated)
465 && PHYSFS_readUBE32(fileHandle, &colour)
466 && PHYSFS_readUBE32(fileHandle, &team));
467
468 serializePlayer->allocated = allocated;
469 if (player < game.maxPlayers)
470 {
471 serializePlayer->ai = matchAIbyName(aiName);
472 ASSERT(serializePlayer->ai != AI_NOT_FOUND, "AI \"%s\" not found -- script loading will fail (player %d / %d)",
473 aiName, player, game.maxPlayers);
474 }
475 serializePlayer->position = position;
476 serializePlayer->colour = colour;
477 serializePlayer->team = team;
478 return retval;
479 }
480
serializeNetPlay(PHYSFS_file * fileHandle,const NETPLAY * serializeNetPlay)481 static bool serializeNetPlay(PHYSFS_file *fileHandle, const NETPLAY *serializeNetPlay)
482 {
483 unsigned int i;
484
485 for (i = 0; i < MAX_PLAYERS; ++i)
486 {
487 if (!serializePlayer(fileHandle, &serializeNetPlay->players[i], i))
488 {
489 return false;
490 }
491 }
492
493 return (PHYSFS_writeUBE32(fileHandle, serializeNetPlay->bComms)
494 && PHYSFS_writeUBE32(fileHandle, serializeNetPlay->playercount)
495 && PHYSFS_writeUBE32(fileHandle, serializeNetPlay->hostPlayer)
496 && PHYSFS_writeUBE32(fileHandle, selectedPlayer)
497 && PHYSFS_writeUBE32(fileHandle, (uint32_t)game.scavengers)
498 && PHYSFS_writeUBE32(fileHandle, 0)
499 && PHYSFS_writeUBE32(fileHandle, 0));
500 }
501
deserializeNetPlay(PHYSFS_file * fileHandle,NETPLAY * serializeNetPlay)502 static bool deserializeNetPlay(PHYSFS_file *fileHandle, NETPLAY *serializeNetPlay)
503 {
504 unsigned int i;
505 uint32_t dummy, scavs = game.scavengers;
506 bool retv;
507
508 for (i = 0; i < MAX_PLAYERS; ++i)
509 {
510 if (!deserializePlayer(fileHandle, &serializeNetPlay->players[i], i))
511 {
512 return false;
513 }
514 }
515
516 serializeNetPlay->isHost = true; // only host can load
517 retv = (PHYSFS_readUBE32(fileHandle, &serializeNetPlay->bComms)
518 && PHYSFS_readUBE32(fileHandle, &serializeNetPlay->playercount)
519 && PHYSFS_readUBE32(fileHandle, &serializeNetPlay->hostPlayer)
520 && PHYSFS_readUBE32(fileHandle, &selectedPlayer)
521 && PHYSFS_readUBE32(fileHandle, &scavs)
522 && PHYSFS_readUBE32(fileHandle, &dummy)
523 && PHYSFS_readUBE32(fileHandle, &dummy));
524 game.scavengers = scavs;
525 return retv;
526 }
527
528 struct SAVE_GAME_V7
529 {
530 uint32_t gameTime;
531 uint32_t GameType; /* Type of game , one of the GTYPE_... enums. */
532 int32_t ScrollMinX; /* Scroll Limits */
533 int32_t ScrollMinY;
534 uint32_t ScrollMaxX;
535 uint32_t ScrollMaxY;
536 char levelName[MAX_LEVEL_SIZE]; //name of the level to load up when mid game
537 };
538
serializeSaveGameV7Data(PHYSFS_file * fileHandle,const SAVE_GAME_V7 * serializeGame)539 static bool serializeSaveGameV7Data(PHYSFS_file *fileHandle, const SAVE_GAME_V7 *serializeGame)
540 {
541 return (PHYSFS_writeUBE32(fileHandle, serializeGame->gameTime)
542 && PHYSFS_writeUBE32(fileHandle, serializeGame->GameType)
543 && PHYSFS_writeSBE32(fileHandle, serializeGame->ScrollMinX)
544 && PHYSFS_writeSBE32(fileHandle, serializeGame->ScrollMinY)
545 && PHYSFS_writeUBE32(fileHandle, serializeGame->ScrollMaxX)
546 && PHYSFS_writeUBE32(fileHandle, serializeGame->ScrollMaxY)
547 && WZ_PHYSFS_writeBytes(fileHandle, serializeGame->levelName, MAX_LEVEL_SIZE) == MAX_LEVEL_SIZE);
548 }
549
deserializeSaveGameV7Data(PHYSFS_file * fileHandle,SAVE_GAME_V7 * serializeGame)550 static bool deserializeSaveGameV7Data(PHYSFS_file *fileHandle, SAVE_GAME_V7 *serializeGame)
551 {
552 return (PHYSFS_readUBE32(fileHandle, &serializeGame->gameTime)
553 && PHYSFS_readUBE32(fileHandle, &serializeGame->GameType)
554 && PHYSFS_readSBE32(fileHandle, &serializeGame->ScrollMinX)
555 && PHYSFS_readSBE32(fileHandle, &serializeGame->ScrollMinY)
556 && PHYSFS_readUBE32(fileHandle, &serializeGame->ScrollMaxX)
557 && PHYSFS_readUBE32(fileHandle, &serializeGame->ScrollMaxY)
558 && WZ_PHYSFS_readBytes(fileHandle, serializeGame->levelName, MAX_LEVEL_SIZE) == MAX_LEVEL_SIZE);
559 }
560
561 struct SAVE_GAME_V10 : public SAVE_GAME_V7
562 {
563 SAVE_POWER power[MAX_PLAYERS];
564 };
565
serializeSaveGameV10Data(PHYSFS_file * fileHandle,const SAVE_GAME_V10 * serializeGame)566 static bool serializeSaveGameV10Data(PHYSFS_file *fileHandle, const SAVE_GAME_V10 *serializeGame)
567 {
568 unsigned int i;
569
570 if (!serializeSaveGameV7Data(fileHandle, (const SAVE_GAME_V7 *) serializeGame))
571 {
572 return false;
573 }
574
575 for (i = 0; i < MAX_PLAYERS; ++i)
576 {
577 if (!serializeSavePowerData(fileHandle, &serializeGame->power[i]))
578 {
579 return false;
580 }
581 }
582
583 return true;
584 }
585
deserializeSaveGameV10Data(PHYSFS_file * fileHandle,SAVE_GAME_V10 * serializeGame)586 static bool deserializeSaveGameV10Data(PHYSFS_file *fileHandle, SAVE_GAME_V10 *serializeGame)
587 {
588 unsigned int i;
589
590 if (!deserializeSaveGameV7Data(fileHandle, (SAVE_GAME_V7 *) serializeGame))
591 {
592 return false;
593 }
594
595 for (i = 0; i < MAX_PLAYERS; ++i)
596 {
597 if (!deserializeSavePowerData(fileHandle, &serializeGame->power[i]))
598 {
599 return false;
600 }
601 }
602
603 return true;
604 }
605
606 struct SAVE_GAME_V11 : public SAVE_GAME_V10
607 {
608 iView currentPlayerPos;
609 };
610
serializeSaveGameV11Data(PHYSFS_file * fileHandle,const SAVE_GAME_V11 * serializeGame)611 static bool serializeSaveGameV11Data(PHYSFS_file *fileHandle, const SAVE_GAME_V11 *serializeGame)
612 {
613 return (serializeSaveGameV10Data(fileHandle, (const SAVE_GAME_V10 *) serializeGame)
614 && serializeiViewData(fileHandle, &serializeGame->currentPlayerPos));
615 }
616
deserializeSaveGameV11Data(PHYSFS_file * fileHandle,SAVE_GAME_V11 * serializeGame)617 static bool deserializeSaveGameV11Data(PHYSFS_file *fileHandle, SAVE_GAME_V11 *serializeGame)
618 {
619 return (deserializeSaveGameV10Data(fileHandle, (SAVE_GAME_V10 *) serializeGame)
620 && deserializeiViewData(fileHandle, &serializeGame->currentPlayerPos));
621 }
622
623 struct SAVE_GAME_V12 : public SAVE_GAME_V11
624 {
625 uint32_t missionTime;
626 uint32_t saveKey;
627 };
628
serializeSaveGameV12Data(PHYSFS_file * fileHandle,const SAVE_GAME_V12 * serializeGame)629 static bool serializeSaveGameV12Data(PHYSFS_file *fileHandle, const SAVE_GAME_V12 *serializeGame)
630 {
631 return (serializeSaveGameV11Data(fileHandle, (const SAVE_GAME_V11 *) serializeGame)
632 && PHYSFS_writeUBE32(fileHandle, serializeGame->missionTime)
633 && PHYSFS_writeUBE32(fileHandle, serializeGame->saveKey));
634 }
635
deserializeSaveGameV12Data(PHYSFS_file * fileHandle,SAVE_GAME_V12 * serializeGame)636 static bool deserializeSaveGameV12Data(PHYSFS_file *fileHandle, SAVE_GAME_V12 *serializeGame)
637 {
638 return (deserializeSaveGameV11Data(fileHandle, (SAVE_GAME_V11 *) serializeGame)
639 && PHYSFS_readUBE32(fileHandle, &serializeGame->missionTime)
640 && PHYSFS_readUBE32(fileHandle, &serializeGame->saveKey));
641 }
642
643 struct SAVE_GAME_V14 : public SAVE_GAME_V12
644 {
645 int32_t missionOffTime;
646 int32_t missionETA;
647 uint16_t missionHomeLZ_X;
648 uint16_t missionHomeLZ_Y;
649 int32_t missionPlayerX;
650 int32_t missionPlayerY;
651 uint16_t iTranspEntryTileX[MAX_PLAYERS];
652 uint16_t iTranspEntryTileY[MAX_PLAYERS];
653 uint16_t iTranspExitTileX[MAX_PLAYERS];
654 uint16_t iTranspExitTileY[MAX_PLAYERS];
655 uint32_t aDefaultSensor[MAX_PLAYERS];
656 uint32_t aDefaultECM[MAX_PLAYERS];
657 uint32_t aDefaultRepair[MAX_PLAYERS];
658 };
659
serializeSaveGameV14Data(PHYSFS_file * fileHandle,const SAVE_GAME_V14 * serializeGame)660 static bool serializeSaveGameV14Data(PHYSFS_file *fileHandle, const SAVE_GAME_V14 *serializeGame)
661 {
662 unsigned int i;
663
664 if (!serializeSaveGameV12Data(fileHandle, (const SAVE_GAME_V12 *) serializeGame)
665 || !PHYSFS_writeSBE32(fileHandle, serializeGame->missionOffTime)
666 || !PHYSFS_writeSBE32(fileHandle, serializeGame->missionETA)
667 || !PHYSFS_writeUBE16(fileHandle, serializeGame->missionHomeLZ_X)
668 || !PHYSFS_writeUBE16(fileHandle, serializeGame->missionHomeLZ_Y)
669 || !PHYSFS_writeSBE32(fileHandle, serializeGame->missionPlayerX)
670 || !PHYSFS_writeSBE32(fileHandle, serializeGame->missionPlayerY))
671 {
672 return false;
673 }
674
675 for (i = 0; i < MAX_PLAYERS; ++i)
676 {
677 if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspEntryTileX[i]))
678 {
679 return false;
680 }
681 }
682
683 for (i = 0; i < MAX_PLAYERS; ++i)
684 {
685 if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspEntryTileY[i]))
686 {
687 return false;
688 }
689 }
690
691 for (i = 0; i < MAX_PLAYERS; ++i)
692 {
693 if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspExitTileX[i]))
694 {
695 return false;
696 }
697 }
698
699 for (i = 0; i < MAX_PLAYERS; ++i)
700 {
701 if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspExitTileY[i]))
702 {
703 return false;
704 }
705 }
706
707 for (i = 0; i < MAX_PLAYERS; ++i)
708 {
709 if (!PHYSFS_writeUBE32(fileHandle, serializeGame->aDefaultSensor[i]))
710 {
711 return false;
712 }
713 }
714
715 for (i = 0; i < MAX_PLAYERS; ++i)
716 {
717 if (!PHYSFS_writeUBE32(fileHandle, serializeGame->aDefaultECM[i]))
718 {
719 return false;
720 }
721 }
722
723 for (i = 0; i < MAX_PLAYERS; ++i)
724 {
725 if (!PHYSFS_writeUBE32(fileHandle, serializeGame->aDefaultRepair[i]))
726 {
727 return false;
728 }
729 }
730
731 return true;
732 }
733
deserializeSaveGameV14Data(PHYSFS_file * fileHandle,SAVE_GAME_V14 * serializeGame)734 static bool deserializeSaveGameV14Data(PHYSFS_file *fileHandle, SAVE_GAME_V14 *serializeGame)
735 {
736 unsigned int i;
737
738 if (!deserializeSaveGameV12Data(fileHandle, (SAVE_GAME_V12 *) serializeGame)
739 || !PHYSFS_readSBE32(fileHandle, &serializeGame->missionOffTime)
740 || !PHYSFS_readSBE32(fileHandle, &serializeGame->missionETA)
741 || !PHYSFS_readUBE16(fileHandle, &serializeGame->missionHomeLZ_X)
742 || !PHYSFS_readUBE16(fileHandle, &serializeGame->missionHomeLZ_Y)
743 || !PHYSFS_readSBE32(fileHandle, &serializeGame->missionPlayerX)
744 || !PHYSFS_readSBE32(fileHandle, &serializeGame->missionPlayerY))
745 {
746 return false;
747 }
748
749 for (i = 0; i < MAX_PLAYERS; ++i)
750 {
751 if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspEntryTileX[i]))
752 {
753 return false;
754 }
755 }
756
757 for (i = 0; i < MAX_PLAYERS; ++i)
758 {
759 if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspEntryTileY[i]))
760 {
761 return false;
762 }
763 }
764
765 for (i = 0; i < MAX_PLAYERS; ++i)
766 {
767 if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspExitTileX[i]))
768 {
769 return false;
770 }
771 }
772
773 for (i = 0; i < MAX_PLAYERS; ++i)
774 {
775 if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspExitTileY[i]))
776 {
777 return false;
778 }
779 }
780
781 for (i = 0; i < MAX_PLAYERS; ++i)
782 {
783 if (!PHYSFS_readUBE32(fileHandle, &serializeGame->aDefaultSensor[i]))
784 {
785 return false;
786 }
787 }
788
789 for (i = 0; i < MAX_PLAYERS; ++i)
790 {
791 if (!PHYSFS_readUBE32(fileHandle, &serializeGame->aDefaultECM[i]))
792 {
793 return false;
794 }
795 }
796
797 for (i = 0; i < MAX_PLAYERS; ++i)
798 {
799 if (!PHYSFS_readUBE32(fileHandle, &serializeGame->aDefaultRepair[i]))
800 {
801 return false;
802 }
803 }
804
805 return true;
806 }
807
808 struct SAVE_GAME_V15 : public SAVE_GAME_V14
809 {
810 int32_t offWorldKeepLists; // was BOOL (which was a int)
811 uint8_t aDroidExperience[MAX_PLAYERS][MAX_RECYCLED_DROIDS];
812 uint32_t RubbleTile;
813 uint32_t WaterTile;
814 uint32_t fogColour;
815 uint32_t fogState;
816 };
817
serializeSaveGameV15Data(PHYSFS_file * fileHandle,const SAVE_GAME_V15 * serializeGame)818 static bool serializeSaveGameV15Data(PHYSFS_file *fileHandle, const SAVE_GAME_V15 *serializeGame)
819 {
820 unsigned int i, j;
821
822 if (!serializeSaveGameV14Data(fileHandle, (const SAVE_GAME_V14 *) serializeGame)
823 || !PHYSFS_writeSBE32(fileHandle, serializeGame->offWorldKeepLists))
824 {
825 return false;
826 }
827
828 for (i = 0; i < MAX_PLAYERS; ++i)
829 {
830 for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
831 {
832 if (!PHYSFS_writeUBE8(fileHandle, 0)) // no longer saved in binary form
833 {
834 return false;
835 }
836 }
837 }
838
839 return (PHYSFS_writeUBE32(fileHandle, serializeGame->RubbleTile)
840 && PHYSFS_writeUBE32(fileHandle, serializeGame->WaterTile)
841 && PHYSFS_writeUBE32(fileHandle, 0)
842 && PHYSFS_writeUBE32(fileHandle, 0));
843 }
844
deserializeSaveGameV15Data(PHYSFS_file * fileHandle,SAVE_GAME_V15 * serializeGame)845 static bool deserializeSaveGameV15Data(PHYSFS_file *fileHandle, SAVE_GAME_V15 *serializeGame)
846 {
847 unsigned int i, j;
848 int32_t boolOffWorldKeepLists;
849
850 if (!deserializeSaveGameV14Data(fileHandle, (SAVE_GAME_V14 *) serializeGame)
851 || !PHYSFS_readSBE32(fileHandle, &boolOffWorldKeepLists))
852 {
853 return false;
854 }
855
856 serializeGame->offWorldKeepLists = boolOffWorldKeepLists;
857
858 for (i = 0; i < MAX_PLAYERS; ++i)
859 {
860 for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
861 {
862 uint8_t tmp;
863 if (!PHYSFS_readUBE8(fileHandle, &tmp))
864 {
865 return false;
866 }
867 if (tmp > 0)
868 {
869 add_to_experience_queue(i, tmp * 65536);
870 }
871 }
872 }
873
874 return (PHYSFS_readUBE32(fileHandle, &serializeGame->RubbleTile)
875 && PHYSFS_readUBE32(fileHandle, &serializeGame->WaterTile)
876 && PHYSFS_readUBE32(fileHandle, &serializeGame->fogColour)
877 && PHYSFS_readUBE32(fileHandle, &serializeGame->fogState));
878 }
879
880 struct SAVE_GAME_V16 : public SAVE_GAME_V15
881 {
882 LANDING_ZONE sLandingZone[MAX_NOGO_AREAS];
883 };
884
serializeSaveGameV16Data(PHYSFS_file * fileHandle,const SAVE_GAME_V16 * serializeGame)885 static bool serializeSaveGameV16Data(PHYSFS_file *fileHandle, const SAVE_GAME_V16 *serializeGame)
886 {
887 unsigned int i;
888
889 if (!serializeSaveGameV15Data(fileHandle, (const SAVE_GAME_V15 *) serializeGame))
890 {
891 return false;
892 }
893
894 for (i = 0; i < MAX_NOGO_AREAS; ++i)
895 {
896 if (!serializeLandingZoneData(fileHandle, &serializeGame->sLandingZone[i]))
897 {
898 return false;
899 }
900 }
901
902 return true;
903 }
904
deserializeSaveGameV16Data(PHYSFS_file * fileHandle,SAVE_GAME_V16 * serializeGame)905 static bool deserializeSaveGameV16Data(PHYSFS_file *fileHandle, SAVE_GAME_V16 *serializeGame)
906 {
907 unsigned int i;
908
909 if (!deserializeSaveGameV15Data(fileHandle, (SAVE_GAME_V15 *) serializeGame))
910 {
911 return false;
912 }
913
914 for (i = 0; i < MAX_NOGO_AREAS; ++i)
915 {
916 if (!deserializeLandingZoneData(fileHandle, &serializeGame->sLandingZone[i]))
917 {
918 return false;
919 }
920 }
921
922 return true;
923 }
924
925 struct SAVE_GAME_V17 : public SAVE_GAME_V16
926 {
927 uint32_t objId;
928 };
929
serializeSaveGameV17Data(PHYSFS_file * fileHandle,const SAVE_GAME_V17 * serializeGame)930 static bool serializeSaveGameV17Data(PHYSFS_file *fileHandle, const SAVE_GAME_V17 *serializeGame)
931 {
932 return (serializeSaveGameV16Data(fileHandle, (const SAVE_GAME_V16 *) serializeGame)
933 && PHYSFS_writeUBE32(fileHandle, serializeGame->objId));
934 }
935
deserializeSaveGameV17Data(PHYSFS_file * fileHandle,SAVE_GAME_V17 * serializeGame)936 static bool deserializeSaveGameV17Data(PHYSFS_file *fileHandle, SAVE_GAME_V17 *serializeGame)
937 {
938 return (deserializeSaveGameV16Data(fileHandle, (SAVE_GAME_V16 *) serializeGame)
939 && PHYSFS_readUBE32(fileHandle, &serializeGame->objId));
940 }
941
942 struct SAVE_GAME_V18 : public SAVE_GAME_V17
943 {
944 char buildDate[MAX_STR_LENGTH];
945 uint32_t oldestVersion;
946 uint32_t validityKey;
947 };
948
serializeSaveGameV18Data(PHYSFS_file * fileHandle,const SAVE_GAME_V18 * serializeGame)949 static bool serializeSaveGameV18Data(PHYSFS_file *fileHandle, const SAVE_GAME_V18 *serializeGame)
950 {
951 return (serializeSaveGameV17Data(fileHandle, (const SAVE_GAME_V17 *) serializeGame)
952 && WZ_PHYSFS_writeBytes(fileHandle, serializeGame->buildDate, MAX_STR_LENGTH) == MAX_STR_LENGTH
953 && PHYSFS_writeUBE32(fileHandle, serializeGame->oldestVersion)
954 && PHYSFS_writeUBE32(fileHandle, serializeGame->validityKey));
955 }
956
deserializeSaveGameV18Data(PHYSFS_file * fileHandle,SAVE_GAME_V18 * serializeGame)957 static bool deserializeSaveGameV18Data(PHYSFS_file *fileHandle, SAVE_GAME_V18 *serializeGame)
958 {
959 return (deserializeSaveGameV17Data(fileHandle, (SAVE_GAME_V17 *) serializeGame)
960 && WZ_PHYSFS_readBytes(fileHandle, serializeGame->buildDate, MAX_STR_LENGTH) == MAX_STR_LENGTH
961 && PHYSFS_readUBE32(fileHandle, &serializeGame->oldestVersion)
962 && PHYSFS_readUBE32(fileHandle, &serializeGame->validityKey));
963 }
964
965 struct SAVE_GAME_V19 : public SAVE_GAME_V18
966 {
967 uint8_t alliances[MAX_PLAYERS][MAX_PLAYERS];
968 uint8_t playerColour[MAX_PLAYERS];
969 uint8_t radarZoom;
970 };
971
serializeSaveGameV19Data(PHYSFS_file * fileHandle,const SAVE_GAME_V19 * serializeGame)972 static bool serializeSaveGameV19Data(PHYSFS_file *fileHandle, const SAVE_GAME_V19 *serializeGame)
973 {
974 unsigned int i, j;
975
976 if (!serializeSaveGameV18Data(fileHandle, (const SAVE_GAME_V18 *) serializeGame))
977 {
978 return false;
979 }
980
981 for (i = 0; i < MAX_PLAYERS; ++i)
982 {
983 for (j = 0; j < MAX_PLAYERS; ++j)
984 {
985 if (!PHYSFS_writeUBE8(fileHandle, serializeGame->alliances[i][j]))
986 {
987 return false;
988 }
989 }
990 }
991
992 for (i = 0; i < MAX_PLAYERS; ++i)
993 {
994 if (!PHYSFS_writeUBE8(fileHandle, serializeGame->playerColour[i]))
995 {
996 return false;
997 }
998 }
999
1000 return PHYSFS_writeUBE8(fileHandle, serializeGame->radarZoom);
1001 }
1002
deserializeSaveGameV19Data(PHYSFS_file * fileHandle,SAVE_GAME_V19 * serializeGame)1003 static bool deserializeSaveGameV19Data(PHYSFS_file *fileHandle, SAVE_GAME_V19 *serializeGame)
1004 {
1005 unsigned int i, j;
1006
1007 if (!deserializeSaveGameV18Data(fileHandle, (SAVE_GAME_V18 *) serializeGame))
1008 {
1009 return false;
1010 }
1011
1012 for (i = 0; i < MAX_PLAYERS; ++i)
1013 {
1014 for (j = 0; j < MAX_PLAYERS; ++j)
1015 {
1016 if (!PHYSFS_readUBE8(fileHandle, &serializeGame->alliances[i][j]))
1017 {
1018 return false;
1019 }
1020 }
1021 }
1022
1023 for (i = 0; i < MAX_PLAYERS; ++i)
1024 {
1025 if (!PHYSFS_readUBE8(fileHandle, &serializeGame->playerColour[i]))
1026 {
1027 return false;
1028 }
1029 }
1030
1031 return PHYSFS_readUBE8(fileHandle, &serializeGame->radarZoom);
1032 }
1033
1034 struct SAVE_GAME_V20 : public SAVE_GAME_V19
1035 {
1036 uint8_t bDroidsToSafetyFlag;
1037 Vector2i asVTOLReturnPos[MAX_PLAYERS];
1038 };
1039
serializeSaveGameV20Data(PHYSFS_file * fileHandle,const SAVE_GAME_V20 * serializeGame)1040 static bool serializeSaveGameV20Data(PHYSFS_file *fileHandle, const SAVE_GAME_V20 *serializeGame)
1041 {
1042 unsigned int i;
1043
1044 if (!serializeSaveGameV19Data(fileHandle, (const SAVE_GAME_V19 *) serializeGame)
1045 || !PHYSFS_writeUBE8(fileHandle, serializeGame->bDroidsToSafetyFlag))
1046 {
1047 return false;
1048 }
1049
1050 for (i = 0; i < MAX_PLAYERS; ++i)
1051 {
1052 if (!serializeVector2i(fileHandle, &serializeGame->asVTOLReturnPos[i]))
1053 {
1054 return false;
1055 }
1056 }
1057
1058 return true;
1059 }
1060
deserializeSaveGameV20Data(PHYSFS_file * fileHandle,SAVE_GAME_V20 * serializeGame)1061 static bool deserializeSaveGameV20Data(PHYSFS_file *fileHandle, SAVE_GAME_V20 *serializeGame)
1062 {
1063 unsigned int i;
1064
1065 if (!deserializeSaveGameV19Data(fileHandle, (SAVE_GAME_V19 *) serializeGame)
1066 || !PHYSFS_readUBE8(fileHandle, &serializeGame->bDroidsToSafetyFlag))
1067 {
1068 return false;
1069 }
1070
1071 for (i = 0; i < MAX_PLAYERS; ++i)
1072 {
1073 if (!deserializeVector2i(fileHandle, &serializeGame->asVTOLReturnPos[i]))
1074 {
1075 return false;
1076 }
1077 }
1078
1079 return true;
1080 }
1081
1082 struct SAVE_GAME_V22 : public SAVE_GAME_V20
1083 {
1084 RUN_DATA asRunData[MAX_PLAYERS];
1085 };
1086
serializeSaveGameV22Data(PHYSFS_file * fileHandle,const SAVE_GAME_V22 * serializeGame)1087 static bool serializeSaveGameV22Data(PHYSFS_file *fileHandle, const SAVE_GAME_V22 *serializeGame)
1088 {
1089 unsigned int i;
1090
1091 if (!serializeSaveGameV20Data(fileHandle, (const SAVE_GAME_V20 *) serializeGame))
1092 {
1093 return false;
1094 }
1095
1096 for (i = 0; i < MAX_PLAYERS; ++i)
1097 {
1098 if (!serializeRunData(fileHandle, &serializeGame->asRunData[i]))
1099 {
1100 return false;
1101 }
1102 }
1103
1104 return true;
1105 }
1106
deserializeSaveGameV22Data(PHYSFS_file * fileHandle,SAVE_GAME_V22 * serializeGame)1107 static bool deserializeSaveGameV22Data(PHYSFS_file *fileHandle, SAVE_GAME_V22 *serializeGame)
1108 {
1109 unsigned int i;
1110
1111 if (!deserializeSaveGameV20Data(fileHandle, (SAVE_GAME_V20 *) serializeGame))
1112 {
1113 return false;
1114 }
1115
1116 for (i = 0; i < MAX_PLAYERS; ++i)
1117 {
1118 if (!deserializeRunData(fileHandle, &serializeGame->asRunData[i]))
1119 {
1120 return false;
1121 }
1122 }
1123
1124 return true;
1125 }
1126
1127 struct SAVE_GAME_V24 : public SAVE_GAME_V22
1128 {
1129 uint32_t reinforceTime;
1130 uint8_t bPlayCountDown;
1131 uint8_t bPlayerHasWon;
1132 uint8_t bPlayerHasLost;
1133 uint8_t dummy3;
1134 };
1135
serializeSaveGameV24Data(PHYSFS_file * fileHandle,const SAVE_GAME_V24 * serializeGame)1136 static bool serializeSaveGameV24Data(PHYSFS_file *fileHandle, const SAVE_GAME_V24 *serializeGame)
1137 {
1138 return (serializeSaveGameV22Data(fileHandle, (const SAVE_GAME_V22 *) serializeGame)
1139 && PHYSFS_writeUBE32(fileHandle, serializeGame->reinforceTime)
1140 && PHYSFS_writeUBE8(fileHandle, serializeGame->bPlayCountDown)
1141 && PHYSFS_writeUBE8(fileHandle, serializeGame->bPlayerHasWon)
1142 && PHYSFS_writeUBE8(fileHandle, serializeGame->bPlayerHasLost)
1143 && PHYSFS_writeUBE8(fileHandle, serializeGame->dummy3));
1144 }
1145
deserializeSaveGameV24Data(PHYSFS_file * fileHandle,SAVE_GAME_V24 * serializeGame)1146 static bool deserializeSaveGameV24Data(PHYSFS_file *fileHandle, SAVE_GAME_V24 *serializeGame)
1147 {
1148 return (deserializeSaveGameV22Data(fileHandle, (SAVE_GAME_V22 *) serializeGame)
1149 && PHYSFS_readUBE32(fileHandle, &serializeGame->reinforceTime)
1150 && PHYSFS_readUBE8(fileHandle, &serializeGame->bPlayCountDown)
1151 && PHYSFS_readUBE8(fileHandle, &serializeGame->bPlayerHasWon)
1152 && PHYSFS_readUBE8(fileHandle, &serializeGame->bPlayerHasLost)
1153 && PHYSFS_readUBE8(fileHandle, &serializeGame->dummy3));
1154 }
1155
1156 struct SAVE_GAME_V27 : public SAVE_GAME_V24
1157 {
1158 uint16_t awDroidExperience[MAX_PLAYERS][MAX_RECYCLED_DROIDS];
1159 };
1160
serializeSaveGameV27Data(PHYSFS_file * fileHandle,const SAVE_GAME_V27 * serializeGame)1161 static bool serializeSaveGameV27Data(PHYSFS_file *fileHandle, const SAVE_GAME_V27 *serializeGame)
1162 {
1163 unsigned int i, j;
1164
1165 if (!serializeSaveGameV24Data(fileHandle, (const SAVE_GAME_V24 *) serializeGame))
1166 {
1167 return false;
1168 }
1169
1170 for (i = 0; i < MAX_PLAYERS; ++i)
1171 {
1172 for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
1173 {
1174 if (!PHYSFS_writeUBE16(fileHandle, 0))
1175 {
1176 return false;
1177 }
1178 }
1179 }
1180
1181 return true;
1182 }
1183
deserializeSaveGameV27Data(PHYSFS_file * fileHandle,SAVE_GAME_V27 * serializeGame)1184 static bool deserializeSaveGameV27Data(PHYSFS_file *fileHandle, SAVE_GAME_V27 *serializeGame)
1185 {
1186 unsigned int i, j;
1187
1188 if (!deserializeSaveGameV24Data(fileHandle, (SAVE_GAME_V24 *) serializeGame))
1189 {
1190 return false;
1191 }
1192
1193 for (i = 0; i < MAX_PLAYERS; ++i)
1194 {
1195 for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
1196 {
1197 uint16_t tmp;
1198 if (!PHYSFS_readUBE16(fileHandle, &tmp))
1199 {
1200 return false;
1201 }
1202 if (tmp > 0)
1203 {
1204 add_to_experience_queue(i, tmp * 65536);
1205 }
1206 }
1207 }
1208
1209 return true;
1210 }
1211
1212 struct SAVE_GAME_V29 : public SAVE_GAME_V27
1213 {
1214 uint16_t missionScrollMinX;
1215 uint16_t missionScrollMinY;
1216 uint16_t missionScrollMaxX;
1217 uint16_t missionScrollMaxY;
1218 };
1219
serializeSaveGameV29Data(PHYSFS_file * fileHandle,const SAVE_GAME_V29 * serializeGame)1220 static bool serializeSaveGameV29Data(PHYSFS_file *fileHandle, const SAVE_GAME_V29 *serializeGame)
1221 {
1222 return (serializeSaveGameV27Data(fileHandle, (const SAVE_GAME_V27 *) serializeGame)
1223 && PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMinX)
1224 && PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMinY)
1225 && PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMaxX)
1226 && PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMaxY));
1227 }
1228
deserializeSaveGameV29Data(PHYSFS_file * fileHandle,SAVE_GAME_V29 * serializeGame)1229 static bool deserializeSaveGameV29Data(PHYSFS_file *fileHandle, SAVE_GAME_V29 *serializeGame)
1230 {
1231 return (deserializeSaveGameV27Data(fileHandle, (SAVE_GAME_V27 *) serializeGame)
1232 && PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMinX)
1233 && PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMinY)
1234 && PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMaxX)
1235 && PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMaxY));
1236 }
1237
1238 struct SAVE_GAME_V30 : public SAVE_GAME_V29
1239 {
1240 int32_t scrGameLevel;
1241 uint8_t bExtraVictoryFlag;
1242 uint8_t bExtraFailFlag;
1243 uint8_t bTrackTransporter;
1244 };
1245
serializeSaveGameV30Data(PHYSFS_file * fileHandle,const SAVE_GAME_V30 * serializeGame)1246 static bool serializeSaveGameV30Data(PHYSFS_file *fileHandle, const SAVE_GAME_V30 *serializeGame)
1247 {
1248 return (serializeSaveGameV29Data(fileHandle, (const SAVE_GAME_V29 *) serializeGame)
1249 && PHYSFS_writeSBE32(fileHandle, serializeGame->scrGameLevel)
1250 && PHYSFS_writeUBE8(fileHandle, serializeGame->bExtraVictoryFlag)
1251 && PHYSFS_writeUBE8(fileHandle, serializeGame->bExtraFailFlag)
1252 && PHYSFS_writeUBE8(fileHandle, serializeGame->bTrackTransporter));
1253 }
1254
deserializeSaveGameV30Data(PHYSFS_file * fileHandle,SAVE_GAME_V30 * serializeGame)1255 static bool deserializeSaveGameV30Data(PHYSFS_file *fileHandle, SAVE_GAME_V30 *serializeGame)
1256 {
1257 return (deserializeSaveGameV29Data(fileHandle, (SAVE_GAME_V29 *) serializeGame)
1258 && PHYSFS_readSBE32(fileHandle, &serializeGame->scrGameLevel)
1259 && PHYSFS_readUBE8(fileHandle, &serializeGame->bExtraVictoryFlag)
1260 && PHYSFS_readUBE8(fileHandle, &serializeGame->bExtraFailFlag)
1261 && PHYSFS_readUBE8(fileHandle, &serializeGame->bTrackTransporter));
1262 }
1263
1264 //extra code for the patch - saves out whether cheated with the mission timer
1265 struct SAVE_GAME_V31 : public SAVE_GAME_V30
1266 {
1267 int32_t missionCheatTime;
1268 };
1269
1270
serializeSaveGameV31Data(PHYSFS_file * fileHandle,const SAVE_GAME_V31 * serializeGame)1271 static bool serializeSaveGameV31Data(PHYSFS_file *fileHandle, const SAVE_GAME_V31 *serializeGame)
1272 {
1273 return (serializeSaveGameV30Data(fileHandle, (const SAVE_GAME_V30 *) serializeGame)
1274 && PHYSFS_writeSBE32(fileHandle, serializeGame->missionCheatTime));
1275 }
1276
deserializeSaveGameV31Data(PHYSFS_file * fileHandle,SAVE_GAME_V31 * serializeGame)1277 static bool deserializeSaveGameV31Data(PHYSFS_file *fileHandle, SAVE_GAME_V31 *serializeGame)
1278 {
1279 return (deserializeSaveGameV30Data(fileHandle, (SAVE_GAME_V30 *) serializeGame)
1280 && PHYSFS_readSBE32(fileHandle, &serializeGame->missionCheatTime));
1281 }
1282
1283 // alexl. skirmish saves
1284 struct SAVE_GAME_V33 : public SAVE_GAME_V31
1285 {
1286 MULTIPLAYERGAME sGame;
1287 NETPLAY sNetPlay;
1288 uint32_t savePlayer;
1289 char sPName[32];
1290 int32_t multiPlayer; // was BOOL (int) ** see warning about conversion
1291 uint32_t sPlayerIndex[MAX_PLAYERS];
1292 };
1293
serializeSaveGameV33Data(PHYSFS_file * fileHandle,const SAVE_GAME_V33 * serializeGame)1294 static bool serializeSaveGameV33Data(PHYSFS_file *fileHandle, const SAVE_GAME_V33 *serializeGame)
1295 {
1296 unsigned int i;
1297
1298 if (!serializeSaveGameV31Data(fileHandle, (const SAVE_GAME_V31 *) serializeGame)
1299 || !serializeMultiplayerGame(fileHandle, &serializeGame->sGame)
1300 || !serializeNetPlay(fileHandle, &serializeGame->sNetPlay)
1301 || !PHYSFS_writeUBE32(fileHandle, serializeGame->savePlayer)
1302 || WZ_PHYSFS_writeBytes(fileHandle, serializeGame->sPName, 32) != 32
1303 || !PHYSFS_writeSBE32(fileHandle, serializeGame->multiPlayer))
1304 {
1305 return false;
1306 }
1307
1308 for (i = 0; i < MAX_PLAYERS; ++i)
1309 {
1310 if (!PHYSFS_writeUBE32(fileHandle, serializeGame->sPlayerIndex[i]))
1311 {
1312 return false;
1313 }
1314 }
1315
1316 return true;
1317 }
1318
deserializeSaveGameV33Data(PHYSFS_file * fileHandle,SAVE_GAME_V33 * serializeGame)1319 static bool deserializeSaveGameV33Data(PHYSFS_file *fileHandle, SAVE_GAME_V33 *serializeGame)
1320 {
1321 unsigned int i;
1322 int32_t boolMultiPlayer;
1323
1324 if (!deserializeSaveGameV31Data(fileHandle, (SAVE_GAME_V31 *) serializeGame)
1325 || !deserializeMultiplayerGame(fileHandle, &serializeGame->sGame)
1326 || !deserializeNetPlay(fileHandle, &serializeGame->sNetPlay)
1327 || !PHYSFS_readUBE32(fileHandle, &serializeGame->savePlayer)
1328 || WZ_PHYSFS_readBytes(fileHandle, serializeGame->sPName, 32) != 32
1329 || !PHYSFS_readSBE32(fileHandle, &boolMultiPlayer))
1330 {
1331 return false;
1332 }
1333
1334 serializeGame->multiPlayer = boolMultiPlayer;
1335
1336 for (i = 0; i < MAX_PLAYERS; ++i)
1337 {
1338 if (!PHYSFS_readUBE32(fileHandle, &serializeGame->sPlayerIndex[i]))
1339 {
1340 return false;
1341 }
1342 }
1343
1344 return true;
1345 }
1346
1347 //Now holds AI names for multiplayer
1348 struct SAVE_GAME_V34 : public SAVE_GAME_V33
1349 {
1350 char sPlayerName[MAX_PLAYERS][StringSize];
1351 };
1352
1353
serializeSaveGameV34Data(PHYSFS_file * fileHandle,const SAVE_GAME_V34 * serializeGame)1354 static bool serializeSaveGameV34Data(PHYSFS_file *fileHandle, const SAVE_GAME_V34 *serializeGame)
1355 {
1356 unsigned int i;
1357 if (!serializeSaveGameV33Data(fileHandle, (const SAVE_GAME_V33 *) serializeGame))
1358 {
1359 return false;
1360 }
1361
1362 for (i = 0; i < MAX_PLAYERS; ++i)
1363 {
1364 if (WZ_PHYSFS_writeBytes(fileHandle, serializeGame->sPlayerName[i], StringSize) != StringSize)
1365 {
1366 return false;
1367 }
1368 }
1369
1370 return true;
1371 }
1372
deserializeSaveGameV34Data(PHYSFS_file * fileHandle,SAVE_GAME_V34 * serializeGame)1373 static bool deserializeSaveGameV34Data(PHYSFS_file *fileHandle, SAVE_GAME_V34 *serializeGame)
1374 {
1375 unsigned int i;
1376 if (!deserializeSaveGameV33Data(fileHandle, (SAVE_GAME_V33 *) serializeGame))
1377 {
1378 return false;
1379 }
1380
1381 for (i = 0; i < MAX_PLAYERS; ++i)
1382 {
1383 if (WZ_PHYSFS_readBytes(fileHandle, serializeGame->sPlayerName[i], StringSize) != StringSize)
1384 {
1385 return false;
1386 }
1387 }
1388
1389 return true;
1390 }
1391
1392 // First version to utilize (de)serialization API and first to be big-endian (instead of little-endian)
1393 struct SAVE_GAME_V35 : public SAVE_GAME_V34
1394 {
1395 };
1396
serializeSaveGameV35Data(PHYSFS_file * fileHandle,const SAVE_GAME_V35 * serializeGame)1397 static bool serializeSaveGameV35Data(PHYSFS_file *fileHandle, const SAVE_GAME_V35 *serializeGame)
1398 {
1399 return serializeSaveGameV34Data(fileHandle, (const SAVE_GAME_V34 *) serializeGame);
1400 }
1401
deserializeSaveGameV35Data(PHYSFS_file * fileHandle,SAVE_GAME_V35 * serializeGame)1402 static bool deserializeSaveGameV35Data(PHYSFS_file *fileHandle, SAVE_GAME_V35 *serializeGame)
1403 {
1404 return deserializeSaveGameV34Data(fileHandle, (SAVE_GAME_V34 *) serializeGame);
1405 }
1406
1407 // Store loaded mods in savegame
1408 struct SAVE_GAME_V38 : public SAVE_GAME_V35
1409 {
1410 char modList[modlist_string_size];
1411 };
1412
serializeSaveGameV38Data(PHYSFS_file * fileHandle,const SAVE_GAME_V38 * serializeGame)1413 static bool serializeSaveGameV38Data(PHYSFS_file *fileHandle, const SAVE_GAME_V38 *serializeGame)
1414 {
1415 if (!serializeSaveGameV35Data(fileHandle, (const SAVE_GAME_V35 *) serializeGame))
1416 {
1417 return false;
1418 }
1419
1420 if (WZ_PHYSFS_writeBytes(fileHandle, serializeGame->modList, modlist_string_size) != modlist_string_size)
1421 {
1422 return false;
1423 }
1424
1425 return true;
1426 }
1427
deserializeSaveGameV38Data(PHYSFS_file * fileHandle,SAVE_GAME_V38 * serializeGame)1428 static bool deserializeSaveGameV38Data(PHYSFS_file *fileHandle, SAVE_GAME_V38 *serializeGame)
1429 {
1430 if (!deserializeSaveGameV35Data(fileHandle, (SAVE_GAME_V35 *) serializeGame))
1431 {
1432 return false;
1433 }
1434
1435 if (WZ_PHYSFS_readBytes(fileHandle, serializeGame->modList, modlist_string_size) != modlist_string_size)
1436 {
1437 return false;
1438 }
1439
1440 return true;
1441 }
1442
1443 // Current save game version
1444 typedef SAVE_GAME_V38 SAVE_GAME;
1445
serializeSaveGameData(PHYSFS_file * fileHandle,const SAVE_GAME * serializeGame)1446 static bool serializeSaveGameData(PHYSFS_file *fileHandle, const SAVE_GAME *serializeGame)
1447 {
1448 return serializeSaveGameV38Data(fileHandle, (const SAVE_GAME_V38 *) serializeGame);
1449 }
1450
deserializeSaveGameData(PHYSFS_file * fileHandle,SAVE_GAME * serializeGame)1451 static bool deserializeSaveGameData(PHYSFS_file *fileHandle, SAVE_GAME *serializeGame)
1452 {
1453 return deserializeSaveGameV38Data(fileHandle, (SAVE_GAME_V38 *) serializeGame);
1454 }
1455
1456 struct DROIDINIT_SAVEHEADER : public GAME_SAVEHEADER
1457 {
1458 UDWORD quantity;
1459 };
1460
1461 struct SAVE_DROIDINIT
1462 {
1463 OBJECT_SAVE_V19;
1464 };
1465
1466 /*
1467 * STRUCTURE Definitions
1468 */
1469
1470 #define STRUCTURE_SAVE_V2 \
1471 OBJECT_SAVE_V19; \
1472 UBYTE status; \
1473 SDWORD currentBuildPts; \
1474 UDWORD body; \
1475 UDWORD armour; \
1476 UDWORD resistance; \
1477 UDWORD dummy1; \
1478 UDWORD subjectInc; /*research inc or factory prod id*/\
1479 UDWORD timeStarted; \
1480 UDWORD output; \
1481 UDWORD capacity; \
1482 UDWORD quantity
1483
1484 struct SAVE_STRUCTURE_V2
1485 {
1486 STRUCTURE_SAVE_V2;
1487 };
1488
1489 #define STRUCTURE_SAVE_V12 \
1490 STRUCTURE_SAVE_V2; \
1491 UDWORD factoryInc; \
1492 UBYTE loopsPerformed; \
1493 UDWORD powerAccrued; \
1494 UDWORD dummy2; \
1495 UDWORD droidTimeStarted; \
1496 UDWORD timeToBuild; \
1497 UDWORD timeStartHold
1498
1499 struct SAVE_STRUCTURE_V12
1500 {
1501 STRUCTURE_SAVE_V12;
1502 };
1503
1504 #define STRUCTURE_SAVE_V14 \
1505 STRUCTURE_SAVE_V12; \
1506 UBYTE visible[MAX_PLAYERS]
1507
1508 struct SAVE_STRUCTURE_V14
1509 {
1510 STRUCTURE_SAVE_V14;
1511 };
1512
1513 #define STRUCTURE_SAVE_V15 \
1514 STRUCTURE_SAVE_V14; \
1515 char researchName[MAX_SAVE_NAME_SIZE_V19]
1516
1517 struct SAVE_STRUCTURE_V15
1518 {
1519 STRUCTURE_SAVE_V15;
1520 };
1521
1522 #define STRUCTURE_SAVE_V17 \
1523 STRUCTURE_SAVE_V15;\
1524 SWORD currentPowerAccrued
1525
1526 struct SAVE_STRUCTURE_V17
1527 {
1528 STRUCTURE_SAVE_V17;
1529 };
1530
1531 #define STRUCTURE_SAVE_V20 \
1532 OBJECT_SAVE_V20; \
1533 UBYTE status; \
1534 SDWORD currentBuildPts; \
1535 UDWORD body; \
1536 UDWORD armour; \
1537 UDWORD resistance; \
1538 UDWORD dummy1; \
1539 UDWORD subjectInc; /*research inc or factory prod id*/\
1540 UDWORD timeStarted; \
1541 UDWORD output; \
1542 UDWORD capacity; \
1543 UDWORD quantity; \
1544 UDWORD factoryInc; \
1545 UBYTE loopsPerformed; \
1546 UDWORD powerAccrued; \
1547 UDWORD dummy2; \
1548 UDWORD droidTimeStarted; \
1549 UDWORD timeToBuild; \
1550 UDWORD timeStartHold; \
1551 UBYTE visible[MAX_PLAYERS]; \
1552 char researchName[MAX_SAVE_NAME_SIZE]; \
1553 SWORD currentPowerAccrued
1554
1555 struct SAVE_STRUCTURE_V20
1556 {
1557 STRUCTURE_SAVE_V20;
1558 };
1559
1560 #define STRUCTURE_SAVE_V21 \
1561 STRUCTURE_SAVE_V20; \
1562 UDWORD commandId
1563
1564 struct SAVE_STRUCTURE_V21
1565 {
1566 STRUCTURE_SAVE_V21;
1567 };
1568
1569 struct SAVE_STRUCTURE
1570 {
1571 STRUCTURE_SAVE_V21;
1572 };
1573
1574 #define FEATURE_SAVE_V2 \
1575 OBJECT_SAVE_V19
1576
1577 struct SAVE_FEATURE_V2
1578 {
1579 FEATURE_SAVE_V2;
1580 };
1581
1582 #define FEATURE_SAVE_V14 \
1583 FEATURE_SAVE_V2; \
1584 UBYTE visible[MAX_PLAYERS]
1585
1586 struct SAVE_FEATURE_V14
1587 {
1588 FEATURE_SAVE_V14;
1589 };
1590
1591 /***************************************************************************/
1592 /*
1593 * Local Variables
1594 */
1595 /***************************************************************************/
1596 extern uint32_t unsynchObjID; // unique ID creation thing..
1597 extern uint32_t synchObjID; // unique ID creation thing..
1598
1599 static UDWORD saveGameVersion = 0;
1600 static bool saveGameOnMission = false;
1601 static SAVE_GAME saveGameData;
1602
1603 static UDWORD savedGameTime;
1604 static UDWORD savedObjId;
1605 static SDWORD startX, startY;
1606 static UDWORD width, height;
1607 static GAME_TYPE gameType;
1608 static bool IsScenario;
1609
1610 /***************************************************************************/
1611 /*
1612 * Local ProtoTypes
1613 */
1614 /***************************************************************************/
1615 static bool gameLoadV7(PHYSFS_file *fileHandle);
1616 static bool gameLoadV(PHYSFS_file *fileHandle, unsigned int version);
1617 static bool loadMainFile(const std::string &fileName);
1618 static bool loadMainFileFinal(const std::string &fileName);
1619 static bool writeMainFile(const std::string &fileName, SDWORD saveType);
1620 static bool writeGameFile(const char *fileName, SDWORD saveType);
1621 static bool writeMapFile(const char *fileName);
1622
1623 static bool loadSaveDroidInit(char *pFileData, UDWORD filesize);
1624
1625 static bool loadSaveDroid(const char *pFileName, DROID **ppsCurrentDroidLists);
1626 static bool loadScriptDroid(ScriptMapData const &data);
1627 static bool loadSaveDroidPointers(const WzString &pFileName, DROID **ppsCurrentDroidLists);
1628 static bool writeDroidFile(const char *pFileName, DROID **ppsCurrentDroidLists);
1629
1630 static bool loadSaveStructure(char *pFileData, UDWORD filesize);
1631 static bool loadSaveStructure2(const char *pFileName, STRUCTURE **ppList);
1632 static bool loadScriptStructure(ScriptMapData const &data);
1633 static bool loadSaveStructurePointers(const WzString& filename, STRUCTURE **ppList);
1634 static bool writeStructFile(const char *pFileName);
1635
1636 static bool loadSaveTemplate(const char *pFileName);
1637 static bool writeTemplateFile(const char *pFileName);
1638
1639 static bool loadSaveFeature(char *pFileData, UDWORD filesize);
1640 static bool writeFeatureFile(const char *pFileName);
1641 static bool loadSaveFeature2(const char *pFileName);
1642 static bool loadScriptFeature(ScriptMapData const &data);
1643
1644 static bool writeTerrainTypeMapFile(char *pFileName);
1645
1646 static bool loadSaveCompList(const char *pFileName);
1647 static bool writeCompListFile(const char *pFileName);
1648
1649 static bool loadSaveStructTypeList(const char *pFileName);
1650 static bool writeStructTypeListFile(const char *pFileName);
1651
1652 static bool loadSaveResearch(const char *pFileName);
1653 static bool writeResearchFile(char *pFileName);
1654
1655 static bool loadSaveMessage(const char* pFileName, LEVEL_TYPE levelType);
1656 static bool writeMessageFile(const char *pFileName);
1657
1658 static bool loadSaveStructLimits(const char *pFileName);
1659 static bool writeStructLimitsFile(const char *pFileName);
1660
1661 static bool readFiresupportDesignators(const char *pFileName);
1662 static bool writeFiresupportDesignators(const char *pFileName);
1663
1664 static bool writeScriptState(const char *pFileName);
1665
1666 static bool gameLoad(const char *fileName);
1667
1668 /* set the global scroll values to use for the save game */
1669 static void setMapScroll();
1670
getSaveStructNameV19(SAVE_STRUCTURE_V17 * psSaveStructure)1671 static char *getSaveStructNameV19(SAVE_STRUCTURE_V17 *psSaveStructure)
1672 {
1673 return (psSaveStructure->name);
1674 }
1675
1676 /*This just loads up the .gam file to determine which level data to set up - split up
1677 so can be called in levLoadData when starting a game from a load save game*/
1678
1679 // -----------------------------------------------------------------------------------------
loadGameInit(const char * fileName)1680 bool loadGameInit(const char *fileName)
1681 {
1682 if (!gameLoad(fileName))
1683 {
1684 debug(LOG_ERROR, "Corrupted / unsupported savegame file %s, Unable to load!", fileName);
1685 // NOTE: why do we start the game clock on a *failed* load?
1686 // Start the game clock
1687 gameTimeStart();
1688
1689 return false;
1690 }
1691
1692 return true;
1693 }
1694
1695
1696 // -----------------------------------------------------------------------------------------
1697 // Load a file from a save game into the psx.
1698 // This is divided up into 2 parts ...
1699 //
1700 // if it is a level loaded up from CD then UserSaveGame will by false
1701 // UserSaveGame ... Extra stuff to load after scripts
loadMissionExtras(const char * pGameToLoad,LEVEL_TYPE levelType)1702 bool loadMissionExtras(const char* pGameToLoad, LEVEL_TYPE levelType)
1703 {
1704 char aFileName[256];
1705 size_t fileExten;
1706
1707 sstrcpy(aFileName, pGameToLoad);
1708 fileExten = strlen(pGameToLoad) - 3;
1709 aFileName[fileExten - 1] = '\0';
1710 strcat(aFileName, "/");
1711
1712 if (saveGameVersion >= VERSION_11)
1713 {
1714 //if user save game then load up the messages AFTER any droids or structures are loaded
1715 if (gameType == GTYPE_SAVE_START || gameType == GTYPE_SAVE_MIDMISSION)
1716 {
1717 //load in the message list file
1718 aFileName[fileExten] = '\0';
1719 strcat(aFileName, "messtate.json");
1720 if (!loadSaveMessage(aFileName, levelType))
1721 {
1722 debug(LOG_ERROR, "Failed to load mission extras from %s", aFileName);
1723 return false;
1724 }
1725 }
1726 }
1727
1728 return true;
1729 }
1730
sanityUpdate()1731 static void sanityUpdate()
1732 {
1733 for (int player = 0; player < game.maxPlayers; player++)
1734 {
1735 for (DROID *psDroid = apsDroidLists[player]; psDroid; psDroid = psDroid->psNext)
1736 {
1737 orderCheckList(psDroid);
1738 actionSanity(psDroid);
1739 }
1740 }
1741 }
1742
getIniBaseObject(WzConfig & ini,WzString const & key,BASE_OBJECT * & object)1743 static void getIniBaseObject(WzConfig &ini, WzString const &key, BASE_OBJECT *&object)
1744 {
1745 object = nullptr;
1746 if (ini.contains(key + "/id"))
1747 {
1748 int tid = ini.value(key + "/id", -1).toInt();
1749 int tplayer = ini.value(key + "/player", -1).toInt();
1750 OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value(key + "/type", 0).toInt();
1751 ASSERT_OR_RETURN(, tid >= 0 && tplayer >= 0, "Bad ID");
1752 object = getBaseObjFromData(tid, tplayer, ttype);
1753 ASSERT(object != nullptr, "Failed to find target");
1754 }
1755 }
1756
getIniStructureStats(WzConfig & ini,WzString const & key,STRUCTURE_STATS * & stats)1757 static void getIniStructureStats(WzConfig &ini, WzString const &key, STRUCTURE_STATS *&stats)
1758 {
1759 stats = nullptr;
1760 if (ini.contains(key))
1761 {
1762 WzString statName = ini.value(key).toWzString();
1763 int tid = getStructStatFromName(statName);
1764 ASSERT_OR_RETURN(, tid >= 0, "Target stats not found %s", statName.toUtf8().c_str());
1765 stats = &asStructureStats[tid];
1766 }
1767 }
1768
getIniDroidOrder(WzConfig & ini,WzString const & key,DroidOrder & order)1769 static void getIniDroidOrder(WzConfig &ini, WzString const &key, DroidOrder &order)
1770 {
1771 order.type = (DroidOrderType)ini.value(key + "/type", DORDER_NONE).toInt();
1772 order.pos = ini.vector2i(key + "/pos");
1773 order.pos2 = ini.vector2i(key + "/pos2");
1774 order.direction = ini.value(key + "/direction").toInt();
1775 getIniBaseObject(ini, key + "/obj", order.psObj);
1776 getIniStructureStats(ini, key + "/stats", order.psStats);
1777 }
1778
setIniBaseObject(nlohmann::json & json,WzString const & key,BASE_OBJECT const * object)1779 static void setIniBaseObject(nlohmann::json &json, WzString const &key, BASE_OBJECT const *object)
1780 {
1781 if (object != nullptr && object->died <= 1)
1782 {
1783 const auto& keyStr = key.toStdString();
1784 json[keyStr + "/id"] = object->id;
1785 json[keyStr + "/player"] = object->player;
1786 json[keyStr + "/type"] = object->type;
1787 #ifdef DEBUG
1788 //ini.setValue(key + "/debugfunc", WzString::fromUtf8(psCurr->targetFunc));
1789 //ini.setValue(key + "/debugline", psCurr->targetLine);
1790 #endif
1791 }
1792 }
1793
setIniStructureStats(nlohmann::json & jsonObj,WzString const & key,STRUCTURE_STATS const * stats)1794 static inline void setIniStructureStats(nlohmann::json &jsonObj, WzString const &key, STRUCTURE_STATS const *stats)
1795 {
1796 if (stats != nullptr)
1797 {
1798 jsonObj[key.toStdString()] = stats->id;
1799 }
1800 }
1801
setIniDroidOrder(nlohmann::json & jsonObj,WzString const & key,DroidOrder const & order)1802 static inline void setIniDroidOrder(nlohmann::json &jsonObj, WzString const &key, DroidOrder const &order)
1803 {
1804 const auto& keyStr = key.toStdString();
1805 jsonObj[keyStr + "/type"] = order.type;
1806 jsonObj[keyStr + "/pos"] = order.pos;
1807 jsonObj[keyStr + "/pos2"] = order.pos2;
1808 jsonObj[keyStr + "/direction"] = order.direction;
1809 setIniBaseObject(jsonObj, key + "/obj", order.psObj);
1810 setIniStructureStats(jsonObj, key + "/stats", order.psStats);
1811 }
1812
allocatePlayers()1813 static void allocatePlayers()
1814 {
1815 for (int i = 0; i < MAX_PLAYERS; i++)
1816 {
1817 NetPlay.players[i].ai = saveGameData.sNetPlay.players[i].ai;
1818 NetPlay.players[i].difficulty = saveGameData.sNetPlay.players[i].difficulty;
1819 // NetPlay.players[i].faction; // read and initialized by loadMainFile
1820 sstrcpy(NetPlay.players[i].name, saveGameData.sNetPlay.players[i].name);
1821 NetPlay.players[i].position = saveGameData.sNetPlay.players[i].position;
1822 if (NetPlay.players[i].difficulty == AIDifficulty::HUMAN || (game.type == LEVEL_TYPE::CAMPAIGN && i == 0))
1823 {
1824 NetPlay.players[i].allocated = true;
1825 //processDebugMappings ensures game does not start in DEBUG mode
1826 processDebugMappings(i, false);
1827 }
1828 else
1829 {
1830 NetPlay.players[i].allocated = false;
1831 }
1832 }
1833 }
1834
getPlayerNames()1835 static void getPlayerNames()
1836 {
1837 /* Get human and AI players names */
1838 if (saveGameVersion >= VERSION_34)
1839 {
1840 for (unsigned i = 0; i < MAX_PLAYERS; ++i)
1841 {
1842 (void)setPlayerName(i, saveGameData.sPlayerName[i]);
1843 }
1844 }
1845 }
1846
1847 // -----------------------------------------------------------------------------------------
1848 // UserSaveGame ... this is true when you are loading a players save game
loadGame(const char * pGameToLoad,bool keepObjects,bool freeMem,bool UserSaveGame)1849 bool loadGame(const char *pGameToLoad, bool keepObjects, bool freeMem, bool UserSaveGame)
1850 {
1851 ScriptMapData data; // Only valid if generating from 'game.js'.
1852 std::map<WzString, DROID **> droidMap;
1853 std::map<WzString, STRUCTURE **> structMap;
1854 char aFileName[256];
1855 size_t fileExten;
1856 UDWORD fileSize;
1857 char *pFileData = nullptr;
1858 UDWORD player, inc, i, j;
1859 DROID *psCurr;
1860 UWORD missionScrollMinX = 0, missionScrollMinY = 0,
1861 missionScrollMaxX = 0, missionScrollMaxY = 0;
1862
1863 /* Stop the game clock */
1864 gameTimeStop();
1865
1866 if ((gameType == GTYPE_SAVE_START) ||
1867 (gameType == GTYPE_SAVE_MIDMISSION))
1868 {
1869 gameTimeReset(savedGameTime);//added 14 may 98 JPS to solve kev's problem with no firing droids
1870 }
1871
1872 /* Clear all the objects off the map and free up the map memory */
1873 proj_FreeAllProjectiles(); //always clear this
1874 if (freeMem)
1875 {
1876 //clear out the audio
1877 audio_StopAll();
1878
1879 freeAllDroids();
1880 freeAllStructs();
1881 freeAllFeatures();
1882
1883 //clear all the messages?
1884 releaseAllProxDisp();
1885 }
1886
1887 if (!keepObjects)
1888 {
1889 //initialise the lists
1890 for (player = 0; player < MAX_PLAYERS; player++)
1891 {
1892 apsDroidLists[player] = nullptr;
1893 apsStructLists[player] = nullptr;
1894 apsFeatureLists[player] = nullptr;
1895 apsFlagPosLists[player] = nullptr;
1896 //clear all the messages?
1897 apsProxDisp[player] = nullptr;
1898 apsSensorList[0] = nullptr;
1899 apsExtractorLists[player] = nullptr;
1900 }
1901 apsOilList[0] = nullptr;
1902 initFactoryNumFlag();
1903 }
1904
1905 if (UserSaveGame)//always !keepObjects
1906 {
1907 //initialise the lists
1908 for (player = 0; player < MAX_PLAYERS; player++)
1909 {
1910 apsLimboDroids[player] = nullptr;
1911 mission.apsDroidLists[player] = nullptr;
1912 mission.apsStructLists[player] = nullptr;
1913 mission.apsFeatureLists[player] = nullptr;
1914 mission.apsFlagPosLists[player] = nullptr;
1915 mission.apsExtractorLists[player] = nullptr;
1916 }
1917 mission.apsOilList[0] = nullptr;
1918 mission.apsSensorList[0] = nullptr;
1919
1920 // Stuff added after level load to avoid being reset or initialised during load
1921 // always !keepObjects
1922
1923 if (saveGameVersion >= VERSION_33)
1924 {
1925 bMultiPlayer = saveGameData.multiPlayer;
1926 }
1927
1928 if (saveGameVersion >= VERSION_12)
1929 {
1930 mission.startTime = saveGameData.missionTime;
1931 }
1932
1933 //set the scroll varaibles
1934 startX = saveGameData.ScrollMinX;
1935 startY = saveGameData.ScrollMinY;
1936 width = saveGameData.ScrollMaxX - saveGameData.ScrollMinX;
1937 height = saveGameData.ScrollMaxY - saveGameData.ScrollMinY;
1938 gameType = static_cast<GAME_TYPE>(saveGameData.GameType);
1939
1940 if (saveGameVersion >= VERSION_14)
1941 {
1942 //mission data
1943 mission.time = saveGameData.missionOffTime;
1944 mission.ETA = saveGameData.missionETA;
1945 mission.homeLZ_X = saveGameData.missionHomeLZ_X;
1946 mission.homeLZ_Y = saveGameData.missionHomeLZ_Y;
1947 mission.playerX = saveGameData.missionPlayerX;
1948 mission.playerY = saveGameData.missionPlayerY;
1949 //mission data
1950 for (player = 0; player < MAX_PLAYERS; player++)
1951 {
1952 aDefaultSensor[player] = saveGameData.aDefaultSensor[player];
1953 aDefaultECM[player] = saveGameData.aDefaultECM[player];
1954 aDefaultRepair[player] = saveGameData.aDefaultRepair[player];
1955 //check for self repair having been set
1956 if (aDefaultRepair[player] != 0
1957 && asRepairStats[aDefaultRepair[player]].location == LOC_DEFAULT)
1958 {
1959 enableSelfRepair((UBYTE)player);
1960 }
1961 mission.iTranspEntryTileX[player] = saveGameData.iTranspEntryTileX[player];
1962 mission.iTranspEntryTileY[player] = saveGameData.iTranspEntryTileY[player];
1963 mission.iTranspExitTileX[player] = saveGameData.iTranspExitTileX[player];
1964 mission.iTranspExitTileY[player] = saveGameData.iTranspExitTileY[player];
1965 }
1966 }
1967
1968 if (saveGameVersion >= VERSION_15)//V21
1969 {
1970 offWorldKeepLists = saveGameData.offWorldKeepLists;
1971 setRubbleTile(saveGameData.RubbleTile);
1972 setUnderwaterTile(saveGameData.WaterTile);
1973 }
1974 if (saveGameVersion >= VERSION_19)//V21
1975 {
1976 for (i = 0; i < MAX_PLAYERS; i++)
1977 {
1978 for (j = 0; j < MAX_PLAYERS; j++)
1979 {
1980 alliances[i][j] = saveGameData.alliances[i][j];
1981 if (i == j)
1982 {
1983 alliances[i][j] = ALLIANCE_FORMED; // hack to fix old savegames
1984 }
1985 if (bMultiPlayer && alliancesSharedVision(game.alliance) && alliances[i][j] == ALLIANCE_FORMED)
1986 {
1987 alliancebits[i] |= 1 << j;
1988 }
1989 }
1990 }
1991 for (i = 0; i < MAX_PLAYERS; i++)
1992 {
1993 setPlayerColour(i, saveGameData.playerColour[i]);
1994 }
1995 SetRadarZoom(saveGameData.radarZoom);
1996 }
1997
1998 if (saveGameVersion >= VERSION_20)//V21
1999 {
2000 setDroidsToSafetyFlag(saveGameData.bDroidsToSafetyFlag);
2001 }
2002
2003 if (saveGameVersion >= VERSION_24)//V24
2004 {
2005 missionSetReinforcementTime(saveGameData.reinforceTime);
2006 // horrible hack to catch savegames that were saving garbage into these fields
2007 if (saveGameData.bPlayCountDown <= 1)
2008 {
2009 setPlayCountDown(saveGameData.bPlayCountDown);
2010 }
2011 if (saveGameData.bPlayerHasWon <= 1)
2012 {
2013 setPlayerHasWon(saveGameData.bPlayerHasWon);
2014 }
2015 if (saveGameData.bPlayerHasLost <= 1)
2016 {
2017 setPlayerHasLost(saveGameData.bPlayerHasLost);
2018 }
2019 }
2020
2021 //extra code added for the first patch (v1.1) to save out if mission time is not being counted
2022 if (saveGameVersion >= VERSION_31)
2023 {
2024 //mission data
2025 mission.cheatTime = saveGameData.missionCheatTime;
2026 }
2027
2028 // skirmish saves.
2029 if (saveGameVersion >= VERSION_33)
2030 {
2031 PLAYERSTATS playerStats;
2032 bool scav = game.scavengers;
2033
2034 game = saveGameData.sGame;
2035 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
2036 productionPlayer = selectedPlayer;
2037 bMultiMessages = bMultiPlayer;
2038
2039 NetPlay.bComms = (saveGameData.sNetPlay).bComms;
2040
2041 allocatePlayers();
2042
2043 if (bMultiPlayer)
2044 {
2045 loadMultiStats(saveGameData.sPName, &playerStats); // stats stuff
2046 setMultiStats(selectedPlayer, playerStats, false);
2047 setMultiStats(selectedPlayer, playerStats, true);
2048 }
2049 }
2050 }
2051
2052 getPlayerNames();
2053
2054 //clear the player Power structs
2055 if ((gameType != GTYPE_SAVE_START) && (gameType != GTYPE_SAVE_MIDMISSION) &&
2056 (!keepObjects))
2057 {
2058 clearPlayerPower();
2059 }
2060
2061 //before loading the data - turn power off so don't get any power low warnings
2062 powerCalculated = false;
2063
2064 /* Load in the chosen file data */
2065 sstrcpy(aFileName, pGameToLoad);
2066 fileExten = strlen(aFileName) - 3; // hack - !
2067 aFileName[fileExten - 1] = '\0';
2068 strcat(aFileName, "/");
2069
2070 //the terrain type WILL only change with Campaign changes (well at the moment!)
2071 if (gameType != GTYPE_SCENARIO_EXPAND || UserSaveGame)
2072 {
2073 //load in the terrain type map
2074 aFileName[fileExten] = '\0';
2075 strcat(aFileName, "ttypes.ttp");
2076 /* Load in the chosen file data */
2077 pFileData = fileLoadBuffer;
2078 if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2079 {
2080 debug(LOG_ERROR, "Failed with: %s", aFileName);
2081 goto error;
2082 }
2083
2084 //load the terrain type data
2085 if (pFileData)
2086 {
2087 if (!loadTerrainTypeMap(pFileData, fileSize))
2088 {
2089 debug(LOG_ERROR, "Failed with: %s", aFileName);
2090 goto error;
2091 }
2092 }
2093 }
2094
2095 //load up the Droid Templates BEFORE any structures are loaded
2096 if (IsScenario == false)
2097 {
2098 if (bMultiPlayer)
2099 {
2100 droidTemplateShutDown();
2101 }
2102 else
2103 {
2104 clearTemplates(0);
2105 localTemplates.clear();
2106 }
2107
2108 aFileName[fileExten] = '\0';
2109 strcat(aFileName, "templates.json");
2110 if (!loadSaveTemplate(aFileName))
2111 {
2112 debug(LOG_ERROR, "Failed with: %s", aFileName);
2113 goto error;
2114 }
2115 }
2116
2117 if (saveGameOnMission && UserSaveGame)
2118 {
2119 //the scroll limits for the mission map have already been written
2120 if (saveGameVersion >= VERSION_29)
2121 {
2122 missionScrollMinX = (UWORD)mission.scrollMinX;
2123 missionScrollMinY = (UWORD)mission.scrollMinY;
2124 missionScrollMaxX = (UWORD)mission.scrollMaxX;
2125 missionScrollMaxY = (UWORD)mission.scrollMaxY;
2126 }
2127
2128 //load the map and the droids then swap pointers
2129
2130 //load in the map file
2131 aFileName[fileExten] = '\0';
2132 strcat(aFileName, "mission.map");
2133 if (!mapLoad(aFileName, false))
2134 {
2135 debug(LOG_ERROR, "Failed with: %s", aFileName);
2136 return false;
2137 }
2138
2139 //load in the visibility file
2140 aFileName[fileExten] = '\0';
2141 strcat(aFileName, "misvis.bjo");
2142
2143 // Load in the visibility data from the chosen file
2144 if (!readVisibilityData(aFileName))
2145 {
2146 debug(LOG_ERROR, "Failed with: %s", aFileName);
2147 goto error;
2148 }
2149
2150 // reload the objects that were in the mission list
2151 //load in the features -do before the structures
2152 aFileName[fileExten] = '\0';
2153 strcat(aFileName, "mfeature.json");
2154
2155 //load the data into apsFeatureLists
2156 if (!loadSaveFeature2(aFileName))
2157 {
2158 aFileName[fileExten] = '\0';
2159 strcat(aFileName, "mfeat.bjo");
2160 /* Load in the chosen file data */
2161 pFileData = fileLoadBuffer;
2162 if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2163 {
2164 debug(LOG_ERROR, "Failed with: %s", aFileName);
2165 goto error;
2166 }
2167 if (!loadSaveFeature(pFileData, fileSize))
2168 {
2169 debug(LOG_ERROR, "Failed with: %s", aFileName);
2170 goto error;
2171 }
2172 }
2173
2174 initStructLimits();
2175 aFileName[fileExten] = '\0';
2176 strcat(aFileName, "mstruct.json");
2177
2178 //load in the mission structures
2179 if (!loadSaveStructure2(aFileName, apsStructLists))
2180 {
2181 aFileName[fileExten] = '\0';
2182 strcat(aFileName, "mstruct.bjo");
2183 /* Load in the chosen file data */
2184 pFileData = fileLoadBuffer;
2185 if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2186 {
2187 debug(LOG_ERROR, "Failed with: %s", aFileName);
2188 goto error;
2189 }
2190 //load the data into apsStructLists
2191 if (!loadSaveStructure(pFileData, fileSize))
2192 {
2193 debug(LOG_ERROR, "Failed with: %s", aFileName);
2194 goto error;
2195 }
2196 }
2197 else
2198 {
2199 structMap[aFileName] = mission.apsStructLists; // we swap pointers below
2200 }
2201
2202 // load in the mission droids, if any
2203 aFileName[fileExten] = '\0';
2204 strcat(aFileName, "mdroid.json");
2205 if (loadSaveDroid(aFileName, apsDroidLists))
2206 {
2207 droidMap[aFileName] = mission.apsDroidLists; // need to swap here to read correct list later
2208 }
2209
2210 /* after we've loaded in the units we need to redo the orientation because
2211 * the direction may have been saved - we need to do it outside of the loop
2212 * whilst the current map is valid for the units
2213 */
2214 for (player = 0; player < MAX_PLAYERS; ++player)
2215 {
2216 for (psCurr = apsDroidLists[player]; psCurr != nullptr; psCurr = psCurr->psNext)
2217 {
2218 if (psCurr->droidType != DROID_PERSON
2219 // && psCurr->droidType != DROID_CYBORG
2220 && !cyborgDroid(psCurr)
2221 && (!isTransporter(psCurr))
2222 && psCurr->pos.x != INVALID_XY)
2223 {
2224 updateDroidOrientation(psCurr);
2225 }
2226 }
2227 }
2228
2229 swapMissionPointers();
2230
2231 //once the mission map has been loaded reset the mission scroll limits
2232 if (saveGameVersion >= VERSION_29)
2233 {
2234 mission.scrollMinX = missionScrollMinX;
2235 mission.scrollMinY = missionScrollMinY;
2236 mission.scrollMaxX = missionScrollMaxX;
2237 mission.scrollMaxY = missionScrollMaxY;
2238 }
2239 }
2240
2241 //if Campaign Expand then don't load in another map
2242 if (gameType != GTYPE_SCENARIO_EXPAND)
2243 {
2244 psMapTiles = nullptr;
2245 //load in the map file
2246 aFileName[fileExten] = '\0';
2247 strcat(aFileName, "game.js");
2248 bool haveScript = PHYSFS_exists(aFileName);
2249 if (haveScript) {
2250 data = runMapScript(aFileName, gameRandU32(), false);
2251 syncDebug("mapSize = [%d, %d]", data.mapWidth, data.mapHeight);
2252 syncDebug("crc(texture) = 0x%08x", crcSumU16(0, data.texture.data(), data.texture.size()));
2253 syncDebug("crc(height) = 0x%08x", crcSumI16(0, data.height.data(), data.height.size()));
2254 syncDebug("crc(structures) = 0x%08x", data.crcSumStructures(0));
2255 syncDebug("crc(droids) = 0x%08x", data.crcSumDroids(0));
2256 syncDebug("crc(features) = 0x%08x", data.crcSumFeatures(0));
2257 }
2258 else
2259 {
2260 aFileName[fileExten] = '\0';
2261 strcat(aFileName, "game.map");
2262 }
2263 if (haveScript? !mapLoadFromScriptData(data, false) : !mapLoad(aFileName, false))
2264 {
2265 debug(LOG_ERROR, "Failed with: %s", aFileName);
2266 return false;
2267 }
2268 }
2269
2270 // FIXME THIS FILE IS A HUGE MESS, this code should probably appear at another position...
2271 if (saveGameVersion > VERSION_12)
2272 {
2273 //if user save game then load up the FX
2274 if ((gameType == GTYPE_SAVE_START) ||
2275 (gameType == GTYPE_SAVE_MIDMISSION))
2276 {
2277 //load in the message list file
2278 aFileName[fileExten] = '\0';
2279 strcat(aFileName, "fxstate.json");
2280
2281 // load the fx data from the file
2282 if (!readFXData(aFileName))
2283 {
2284 debug(LOG_ERROR, "Failed with: %s", aFileName);
2285 goto error;
2286 }
2287 }
2288 }
2289
2290 //save game stuff added after map load
2291
2292 if (saveGameVersion >= VERSION_16)
2293 {
2294 for (inc = 0; inc < MAX_NOGO_AREAS; inc++)
2295 {
2296 setNoGoArea(saveGameData.sLandingZone[inc].x1, saveGameData.sLandingZone[inc].y1,
2297 saveGameData.sLandingZone[inc].x2, saveGameData.sLandingZone[inc].y2, (UBYTE)inc);
2298 }
2299 }
2300
2301 //adjust the scroll range for the new map or the expanded map
2302 setMapScroll();
2303
2304 //if user save game then load up the research BEFORE any droids or structures are loaded
2305 if (gameType == GTYPE_SAVE_START || gameType == GTYPE_SAVE_MIDMISSION)
2306 {
2307 //load in the research list file
2308 aFileName[fileExten] = '\0';
2309 strcat(aFileName, "resstate.json");
2310 if (!loadSaveResearch(aFileName))
2311 {
2312 debug(LOG_ERROR, "Failed to load research data from %s", aFileName);
2313 goto error;
2314 }
2315 }
2316
2317 if (IsScenario)
2318 {
2319 //load in the droids
2320 aFileName[fileExten] = '\0';
2321 strcat(aFileName, "droid.json");
2322
2323 //load the data into apsDroidLists
2324 if (data.valid && loadScriptDroid(data))
2325 {
2326 // Nothing to do here.
2327 }
2328 else if (loadSaveDroid(aFileName, apsDroidLists))
2329 {
2330 debug(LOG_SAVE, "Loaded new style droids");
2331 droidMap[aFileName] = apsDroidLists; // load pointers later
2332 }
2333 else
2334 {
2335 // load in the old style droid initialisation file
2336 aFileName[fileExten] = '\0';
2337 strcat(aFileName, "dinit.bjo");
2338 /* Load in the chosen file data */
2339 pFileData = fileLoadBuffer;
2340 if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2341 {
2342 debug(LOG_ERROR, "Failed with: %s", aFileName);
2343 goto error;
2344 }
2345 if (!loadSaveDroidInit(pFileData, fileSize))
2346 {
2347 debug(LOG_ERROR, "Failed with: %s", aFileName);
2348 goto error;
2349 }
2350 debug(LOG_SAVE, "Loaded old style droids");
2351 }
2352 }
2353 else
2354 {
2355 //load in the droids
2356 aFileName[fileExten] = '\0';
2357 strcat(aFileName, "droid.json");
2358
2359 //load the data into apsDroidLists
2360 if (!loadSaveDroid(aFileName, apsDroidLists))
2361 {
2362 debug(LOG_ERROR, "failed to load %s", aFileName);
2363 goto error;
2364 }
2365 droidMap[aFileName] = apsDroidLists; // load pointers later
2366
2367 /* after we've loaded in the units we need to redo the orientation because
2368 * the direction may have been saved - we need to do it outside of the loop
2369 * whilst the current map is valid for the units
2370 */
2371 for (player = 0; player < MAX_PLAYERS; ++player)
2372 {
2373 for (psCurr = apsDroidLists[player]; psCurr != nullptr; psCurr = psCurr->psNext)
2374 {
2375 if (psCurr->droidType != DROID_PERSON
2376 && !cyborgDroid(psCurr)
2377 && (!isTransporter(psCurr))
2378 && psCurr->pos.x != INVALID_XY)
2379 {
2380 updateDroidOrientation(psCurr);
2381 }
2382 }
2383 }
2384 if (!saveGameOnMission)
2385 {
2386 //load in the mission droids
2387 aFileName[fileExten] = '\0';
2388 strcat(aFileName, "mdroid.json");
2389
2390 // load the data into mission.apsDroidLists, if any
2391 if (loadSaveDroid(aFileName, mission.apsDroidLists))
2392 {
2393 droidMap[aFileName] = mission.apsDroidLists;
2394 }
2395 }
2396 }
2397
2398 if (saveGameVersion >= VERSION_23)
2399 {
2400 // load in the limbo droids, if any
2401 aFileName[fileExten] = '\0';
2402 strcat(aFileName, "limbo.json");
2403 if (loadSaveDroid(aFileName, apsLimboDroids))
2404 {
2405 droidMap[aFileName] = apsLimboDroids;
2406 }
2407 }
2408
2409 //load in the features -do before the structures
2410 aFileName[fileExten] = '\0';
2411 strcat(aFileName, "feature.json");
2412 if (data.valid && loadScriptFeature(data))
2413 {
2414 // Nothing to do here.
2415 }
2416 else if (!loadSaveFeature2(aFileName))
2417 {
2418 aFileName[fileExten] = '\0';
2419 strcat(aFileName, "feat.bjo");
2420 /* Load in the chosen file data */
2421 pFileData = fileLoadBuffer;
2422 if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2423 {
2424 debug(LOG_ERROR, "Failed with: %s", aFileName);
2425 goto error;
2426 }
2427
2428 //load the data into apsFeatureLists
2429 if (!loadSaveFeature(pFileData, fileSize))
2430 {
2431 debug(LOG_ERROR, "Failed with: %s", aFileName);
2432 goto error;
2433 }
2434 }
2435
2436 //load in the structures
2437 initStructLimits();
2438 aFileName[fileExten] = '\0';
2439 strcat(aFileName, "struct.json");
2440 if (data.valid && loadScriptStructure(data))
2441 {
2442 // Nothing to do here.
2443 }
2444 else if (!loadSaveStructure2(aFileName, apsStructLists))
2445 {
2446 aFileName[fileExten] = '\0';
2447 strcat(aFileName, "struct.bjo");
2448 /* Load in the chosen file data */
2449 pFileData = fileLoadBuffer;
2450 if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2451 {
2452 debug(LOG_ERROR, "Failed with: %s", aFileName);
2453 goto error;
2454 }
2455 //load the data into apsStructLists
2456 if (!loadSaveStructure(pFileData, fileSize))
2457 {
2458 debug(LOG_ERROR, "Failed with: %s", aFileName);
2459 goto error;
2460 }
2461 }
2462 else
2463 {
2464 structMap[aFileName] = apsStructLists;
2465 }
2466
2467 //if user save game then load up the current level for structs and components
2468 if (gameType == GTYPE_SAVE_START || gameType == GTYPE_SAVE_MIDMISSION)
2469 {
2470 //load in the component list file
2471 aFileName[fileExten] = '\0';
2472 strcat(aFileName, "complist.json");
2473 if (!loadSaveCompList(aFileName))
2474 {
2475 debug(LOG_ERROR, "failed to load %s", aFileName);
2476 goto error;
2477 }
2478 //load in the structure type list file
2479 aFileName[fileExten] = '\0';
2480 strcat(aFileName, "strtype.json");
2481 if (!loadSaveStructTypeList(aFileName))
2482 {
2483 debug(LOG_ERROR, "failed to load %s", aFileName);
2484 goto error;
2485 }
2486 }
2487
2488 if (saveGameVersion >= VERSION_11)
2489 {
2490 //if user save game then load up the Visibility
2491 if ((gameType == GTYPE_SAVE_START) ||
2492 (gameType == GTYPE_SAVE_MIDMISSION))
2493 {
2494 //load in the visibility file
2495 aFileName[fileExten] = '\0';
2496 strcat(aFileName, "visstate.bjo");
2497
2498 // Load in the visibility data from the chosen file
2499 if (!readVisibilityData(aFileName))
2500 {
2501 debug(LOG_ERROR, "Failed with: %s", aFileName);
2502 goto error;
2503 }
2504 }
2505 }
2506
2507 if (saveGameVersion >= VERSION_16)
2508 {
2509 //if user save game then load up the FX
2510 if ((gameType == GTYPE_SAVE_START) ||
2511 (gameType == GTYPE_SAVE_MIDMISSION))
2512 {
2513 aFileName[fileExten] = '\0';
2514 strcat(aFileName, "score.json");
2515
2516 // Load the fx data from the chosen file
2517 if (!readScoreData(aFileName))
2518 {
2519 debug(LOG_ERROR, "Failed with: %s", aFileName);
2520 goto error;
2521 }
2522 }
2523 }
2524
2525 if (saveGameVersion >= VERSION_21)
2526 {
2527 //rebuild the apsCommandDesignation AFTER all droids and structures are loaded
2528 if ((gameType == GTYPE_SAVE_START) ||
2529 (gameType == GTYPE_SAVE_MIDMISSION))
2530 {
2531 //load in the command list file
2532 aFileName[fileExten] = '\0';
2533 strcat(aFileName, "firesupport.json");
2534
2535 if (!readFiresupportDesignators(aFileName))
2536 {
2537 debug(LOG_ERROR, "Failed with: %s", aFileName);
2538 goto error;
2539 }
2540 }
2541 }
2542
2543 if ((saveGameVersion >= VERSION_15) && UserSaveGame)
2544 {
2545 //load in the mission structures
2546 aFileName[fileExten] = '\0';
2547 strcat(aFileName, "limits.json");
2548
2549 //load the data into apsStructLists
2550 if (!loadSaveStructLimits(aFileName))
2551 {
2552 debug(LOG_ERROR, "failed to load %s", aFileName);
2553 goto error;
2554 }
2555
2556 //set up the structure Limits
2557 setCurrentStructQuantity(false);
2558 }
2559 else
2560 {
2561 //set up the structure Limits
2562 setCurrentStructQuantity(true);
2563 }
2564
2565 //check that delivery points haven't been put down in invalid location
2566 checkDeliveryPoints(saveGameVersion);
2567
2568 //turn power on for rest of game
2569 powerCalculated = true;
2570
2571 if (!keepObjects)//only reset the pointers if they were set
2572 {
2573 // Reset the object pointers in the droid target lists
2574 for (auto it = droidMap.begin(); it != droidMap.end(); ++it)
2575 {
2576 const WzString& key = it->first;
2577 DROID **pList = it->second;
2578 loadSaveDroidPointers(key, pList);
2579 }
2580 for (auto it = structMap.begin(); it != structMap.end(); ++it)
2581 {
2582 const WzString& key = it->first;
2583 STRUCTURE **pList = it->second;
2584 loadSaveStructurePointers(key, pList);
2585 }
2586 }
2587
2588 // Load labels
2589 aFileName[fileExten] = '\0';
2590 strcat(aFileName, "labels.json");
2591 loadLabels(aFileName);
2592
2593 //if user save game then reset the time - BEWARE IF YOU USE IT
2594 if ((gameType == GTYPE_SAVE_START) ||
2595 (gameType == GTYPE_SAVE_MIDMISSION))
2596 {
2597 ASSERT(gameTime == savedGameTime, "loadGame; game time modified during load");
2598 gameTimeReset(savedGameTime);//added 14 may 98 JPS to solve kev's problem with no firing droids
2599
2600 //reset the objId for new objects
2601 if (saveGameVersion >= VERSION_17)
2602 {
2603 unsynchObjID = (savedObjId + 1) / 2; // Make new object ID start at savedObjId*8.
2604 synchObjID = savedObjId * 4; // Make new object ID start at savedObjId*8.
2605 }
2606
2607 if (getDroidsToSafetyFlag())
2608 {
2609 //The droids lists are "reversed" as they are loaded in loadSaveDroid().
2610 //Which later causes issues in saveCampaignData() which tries to extract
2611 //the first transporter group sent off at Beta-end by reversing this very list.
2612 reverseObjectList(&mission.apsDroidLists[selectedPlayer]);
2613 }
2614 }
2615
2616 //check the research button isn't flashing unnecessarily
2617 //cancel first
2618 stopReticuleButtonFlash(IDRET_RESEARCH);
2619 //then see if needs to be set
2620 intNotifyResearchButton(0);
2621
2622 //set up the mission countdown flag
2623 setMissionCountDown();
2624
2625 /* Start the game clock */
2626 gameTimeStart();
2627
2628 //check if limbo_expand mission has changed to an expand mission for user save game (mid-mission)
2629 if (gameType == GTYPE_SAVE_MIDMISSION && missionLimboExpand())
2630 {
2631 /* when all the units have moved from the mission.apsDroidList then the
2632 * campaign has been reset to an EXPAND type - OK so there should have
2633 * been another flag to indicate this state has changed but its late in
2634 * the day excuses...excuses...excuses
2635 */
2636 if (mission.apsDroidLists[selectedPlayer] == nullptr)
2637 {
2638 //set the mission type
2639 startMissionSave(LEVEL_TYPE::LDS_EXPAND);
2640 }
2641 }
2642
2643 //set this if come into a save game mid mission
2644 if (gameType == GTYPE_SAVE_MIDMISSION)
2645 {
2646 setScriptWinLoseVideo(PLAY_NONE);
2647 }
2648
2649 //need to clear before setting up
2650 clearMissionWidgets();
2651 //put any widgets back on for the missions
2652 resetMissionWidgets();
2653
2654 debug(LOG_NEVER, "Done loading");
2655
2656 return true;
2657
2658 error:
2659 debug(LOG_ERROR, "Game load failed for %s, FS:%s, params=%s,%s,%s", pGameToLoad, WZ_PHYSFS_getRealDir_String(pGameToLoad).c_str(),
2660 keepObjects ? "true" : "false", freeMem ? "true" : "false", UserSaveGame ? "true" : "false");
2661
2662 /* Clear all the objects off the map and free up the map memory */
2663 freeAllDroids();
2664 freeAllStructs();
2665 freeAllFeatures();
2666 droidTemplateShutDown();
2667 psMapTiles = nullptr;
2668
2669 /* Start the game clock */
2670 gameTimeStart();
2671
2672 return false;
2673 }
2674 // -----------------------------------------------------------------------------------------
2675
saveGame(const char * aFileName,GAME_TYPE saveType)2676 bool saveGame(const char *aFileName, GAME_TYPE saveType)
2677 {
2678 size_t fileExtension;
2679 DROID *psDroid, *psNext;
2680 char CurrentFileName[PATH_MAX] = {'\0'};
2681
2682 triggerEvent(TRIGGER_GAME_SAVING);
2683
2684 ASSERT_OR_RETURN(false, aFileName && strlen(aFileName) > 4, "Bad savegame filename");
2685 sstrcpy(CurrentFileName, aFileName);
2686 debug(LOG_WZ, "saveGame: %s", CurrentFileName);
2687
2688 fileExtension = strlen(CurrentFileName) - 3;
2689 gameTimeStop();
2690 sanityUpdate();
2691
2692 /* Write the data to the file */
2693 if (!writeGameFile(CurrentFileName, saveType))
2694 {
2695 debug(LOG_ERROR, "writeGameFile(\"%s\") failed", CurrentFileName);
2696 goto error;
2697 }
2698
2699 //remove the file extension
2700 CurrentFileName[strlen(CurrentFileName) - 4] = '\0';
2701
2702 //create dir will fail if directory already exists but don't care!
2703 (void) PHYSFS_mkdir(CurrentFileName);
2704
2705 writeMainFile(std::string(CurrentFileName) + "/main.json", saveType);
2706
2707 //save the map file
2708 strcat(CurrentFileName, "/game.map");
2709 /* Write the data to the file */
2710 if (!writeMapFile(CurrentFileName))
2711 {
2712 debug(LOG_ERROR, "saveGame: writeMapFile(\"%s\") failed", CurrentFileName);
2713 goto error;
2714 }
2715
2716 // Save some game info
2717 CurrentFileName[fileExtension] = '\0';
2718 strcat(CurrentFileName, "gameinfo.json");
2719 writeGameInfo(CurrentFileName);
2720
2721 // Save labels
2722 CurrentFileName[fileExtension] = '\0';
2723 strcat(CurrentFileName, "labels.json");
2724 writeLabels(CurrentFileName);
2725
2726 //create the droids filename
2727 CurrentFileName[fileExtension] = '\0';
2728 strcat(CurrentFileName, "droid.json");
2729 /*Write the current droid lists to the file*/
2730 if (!writeDroidFile(CurrentFileName, apsDroidLists))
2731 {
2732 debug(LOG_ERROR, "writeDroidFile(\"%s\") failed", CurrentFileName);
2733 goto error;
2734 }
2735
2736 //create the structures filename
2737 CurrentFileName[fileExtension] = '\0';
2738 strcat(CurrentFileName, "struct.json");
2739 /*Write the data to the file*/
2740 if (!writeStructFile(CurrentFileName))
2741 {
2742 debug(LOG_ERROR, "saveGame: writeStructFile(\"%s\") failed", CurrentFileName);
2743 goto error;
2744 }
2745
2746 //create the templates filename
2747 CurrentFileName[fileExtension] = '\0';
2748 strcat(CurrentFileName, "templates.json");
2749 /*Write the data to the file*/
2750 if (!writeTemplateFile(CurrentFileName))
2751 {
2752 debug(LOG_ERROR, "saveGame: writeTemplateFile(\"%s\") failed", CurrentFileName);
2753 goto error;
2754 }
2755
2756 //create the features filename
2757 CurrentFileName[fileExtension] = '\0';
2758 strcat(CurrentFileName, "feature.json");
2759 /*Write the data to the file*/
2760 if (!writeFeatureFile(CurrentFileName))
2761 {
2762 debug(LOG_ERROR, "saveGame: writeFeatureFile(\"%s\") failed", CurrentFileName);
2763 goto error;
2764 }
2765
2766 //create the terrain types filename
2767 CurrentFileName[fileExtension] = '\0';
2768 strcat(CurrentFileName, "ttypes.ttp");
2769 /*Write the data to the file*/
2770 if (!writeTerrainTypeMapFile(CurrentFileName))
2771 {
2772 debug(LOG_ERROR, "saveGame: writeTerrainTypeMapFile(\"%s\") failed", CurrentFileName);
2773 goto error;
2774 }
2775
2776 //create the strucutLimits filename
2777 CurrentFileName[fileExtension] = '\0';
2778 strcat(CurrentFileName, "limits.json");
2779 /*Write the data to the file*/
2780 if (!writeStructLimitsFile(CurrentFileName))
2781 {
2782 debug(LOG_ERROR, "saveGame: writeStructLimitsFile(\"%s\") failed", CurrentFileName);
2783 goto error;
2784 }
2785
2786 //create the component lists filename
2787 CurrentFileName[fileExtension] = '\0';
2788 strcat(CurrentFileName, "complist.json");
2789 /*Write the data to the file*/
2790 if (!writeCompListFile(CurrentFileName))
2791 {
2792 debug(LOG_ERROR, "saveGame: writeCompListFile(\"%s\") failed", CurrentFileName);
2793 goto error;
2794 }
2795 //create the structure type lists filename
2796 CurrentFileName[fileExtension] = '\0';
2797 strcat(CurrentFileName, "strtype.json");
2798 /*Write the data to the file*/
2799 if (!writeStructTypeListFile(CurrentFileName))
2800 {
2801 debug(LOG_ERROR, "saveGame: writeStructTypeListFile(\"%s\") failed", CurrentFileName);
2802 goto error;
2803 }
2804
2805 //create the research filename
2806 CurrentFileName[fileExtension] = '\0';
2807 strcat(CurrentFileName, "resstate.json");
2808 /*Write the data to the file*/
2809 if (!writeResearchFile(CurrentFileName))
2810 {
2811 debug(LOG_ERROR, "saveGame: writeResearchFile(\"%s\") failed", CurrentFileName);
2812 goto error;
2813 }
2814
2815 //create the message filename
2816 CurrentFileName[fileExtension] = '\0';
2817 strcat(CurrentFileName, "messtate.json");
2818 /*Write the data to the file*/
2819 if (!writeMessageFile(CurrentFileName))
2820 {
2821 debug(LOG_ERROR, "saveGame: writeMessageFile(\"%s\") failed", CurrentFileName);
2822 goto error;
2823 }
2824
2825 CurrentFileName[fileExtension] = '\0';
2826 strcat(CurrentFileName, "visstate.bjo");
2827 /*Write the data to the file*/
2828 if (!writeVisibilityData(CurrentFileName))
2829 {
2830 debug(LOG_ERROR, "saveGame: writeVisibilityData(\"%s\") failed", CurrentFileName);
2831 goto error;
2832 }
2833
2834 CurrentFileName[fileExtension] = '\0';
2835 strcat(CurrentFileName, "fxstate.json");
2836 /*Write the data to the file*/
2837 if (!writeFXData(CurrentFileName))
2838 {
2839 debug(LOG_ERROR, "saveGame: writeFXData(\"%s\") failed", CurrentFileName);
2840 goto error;
2841 }
2842
2843 //added at V15 save
2844 CurrentFileName[fileExtension] = '\0';
2845 strcat(CurrentFileName, "score.json");
2846 /*Write the data to the file*/
2847 if (!writeScoreData(CurrentFileName))
2848 {
2849 debug(LOG_ERROR, "saveGame: writeScoreData(\"%s\") failed", CurrentFileName);
2850 goto error;
2851 }
2852
2853 CurrentFileName[fileExtension] = '\0';
2854 strcat(CurrentFileName, "firesupport.json");
2855 /*Write the data to the file*/
2856 if (!writeFiresupportDesignators(CurrentFileName))
2857 {
2858 debug(LOG_ERROR, "saveGame: writeFiresupportDesignators(\"%s\") failed", CurrentFileName);
2859 goto error;
2860 }
2861
2862 // save the script state if necessary
2863 if (saveType == GTYPE_SAVE_MIDMISSION)
2864 {
2865 CurrentFileName[fileExtension] = '\0';
2866 strcat(CurrentFileName, "scriptstate.es");
2867 /*Write the data to the file*/
2868 if (!writeScriptState(CurrentFileName))
2869 {
2870 debug(LOG_ERROR, "saveGame: writeScriptState(\"%s\") failed", CurrentFileName);
2871 goto error;
2872 }
2873 }
2874
2875 //create the droids filename
2876 CurrentFileName[fileExtension] = '\0';
2877 strcat(CurrentFileName, "mdroid.json");
2878 /*Write the swapped droid lists to the file*/
2879 if (!writeDroidFile(CurrentFileName, mission.apsDroidLists))
2880 {
2881 debug(LOG_ERROR, "writeDroidFile(\"%s\") failed", CurrentFileName);
2882 goto error;
2883 }
2884
2885 //create the limbo filename
2886 //clear the list
2887 if (saveGameVersion < VERSION_25)
2888 {
2889 for (psDroid = apsLimboDroids[selectedPlayer]; psDroid != nullptr; psDroid = psNext)
2890 {
2891 psNext = psDroid->psNext;
2892 //limbo list invalidate XY
2893 psDroid->pos.x = INVALID_XY;
2894 psDroid->pos.y = INVALID_XY;
2895 //this is mainly for VTOLs
2896 setSaveDroidBase(psDroid, nullptr);
2897 orderDroid(psDroid, DORDER_STOP, ModeImmediate);
2898 }
2899 }
2900
2901 CurrentFileName[fileExtension] = '\0';
2902 strcat(CurrentFileName, "limbo.json");
2903 /*Write the swapped droid lists to the file*/
2904 if (!writeDroidFile(CurrentFileName, apsLimboDroids))
2905 {
2906 debug(LOG_ERROR, "saveGame: writeDroidFile(\"%s\") failed", CurrentFileName);
2907 goto error;
2908 }
2909
2910 if (saveGameOnMission)
2911 {
2912 //mission save swap the mission pointers and save the changes
2913 swapMissionPointers();
2914 //now save the map and droids
2915
2916 //save the map file
2917 CurrentFileName[fileExtension] = '\0';
2918 strcat(CurrentFileName, "mission.map");
2919 /* Write the data to the file */
2920 if (!writeMapFile(CurrentFileName))
2921 {
2922 debug(LOG_ERROR, "saveGame: writeMapFile(\"%s\") failed", CurrentFileName);
2923 goto error;
2924 }
2925
2926 //save the map file
2927 CurrentFileName[fileExtension] = '\0';
2928 strcat(CurrentFileName, "misvis.bjo");
2929 /* Write the data to the file */
2930 if (!writeVisibilityData(CurrentFileName))
2931 {
2932 debug(LOG_ERROR, "saveGame: writeVisibilityData(\"%s\") failed", CurrentFileName);
2933 goto error;
2934 }
2935
2936 //create the structures filename
2937 CurrentFileName[fileExtension] = '\0';
2938 strcat(CurrentFileName, "mstruct.json");
2939 /*Write the data to the file*/
2940 if (!writeStructFile(CurrentFileName))
2941 {
2942 debug(LOG_ERROR, "saveGame: writeStructFile(\"%s\") failed", CurrentFileName);
2943 goto error;
2944 }
2945
2946 //create the features filename
2947 CurrentFileName[fileExtension] = '\0';
2948 strcat(CurrentFileName, "mfeature.json");
2949 /*Write the data to the file*/
2950 if (!writeFeatureFile(CurrentFileName))
2951 {
2952 debug(LOG_ERROR, "saveGame: writeFeatureFile(\"%s\") failed", CurrentFileName);
2953 goto error;
2954 }
2955
2956 //mission save swap back so we can restart the game
2957 swapMissionPointers();
2958 }
2959
2960 // strip the last filename
2961 CurrentFileName[fileExtension - 1] = '\0';
2962
2963 /* Start the game clock */
2964 triggerEvent(TRIGGER_GAME_SAVED);
2965 gameTimeStart();
2966 return true;
2967
2968 error:
2969 /* Start the game clock */
2970 gameTimeStart();
2971
2972 return false;
2973 }
2974
2975 // -----------------------------------------------------------------------------------------
writeMapFile(const char * fileName)2976 static bool writeMapFile(const char *fileName)
2977 {
2978 char *pFileData = nullptr;
2979 UDWORD fileSize;
2980
2981 /* Get the save data */
2982 bool status = mapSave(&pFileData, &fileSize);
2983
2984 if (status)
2985 {
2986 /* Write the data to the file */
2987 status = saveFile(fileName, pFileData, fileSize);
2988 }
2989
2990 if (pFileData != nullptr)
2991 {
2992 free(pFileData);
2993 }
2994
2995 return status;
2996 }
2997
2998 // -----------------------------------------------------------------------------------------
gameLoad(const char * fileName)2999 static bool gameLoad(const char *fileName)
3000 {
3001 char CurrentFileName[PATH_MAX];
3002 strcpy(CurrentFileName, fileName);
3003 GAME_SAVEHEADER fileHeader;
3004
3005 PHYSFS_file *fileHandle = openLoadFile(fileName, true);
3006 if (!fileHandle)
3007 {
3008 // Failure to open the file is a failure to load the specified savegame
3009 return false;
3010 }
3011 debug(LOG_WZ, "gameLoad");
3012
3013 // Read the header from the file
3014 if (!deserializeSaveGameHeader(fileHandle, &fileHeader))
3015 {
3016 debug(LOG_ERROR, "gameLoad: error while reading header from file (%s): %s", fileName, WZ_PHYSFS_getLastError());
3017 PHYSFS_close(fileHandle);
3018 return false;
3019 }
3020
3021 // Check the header to see if we've been given a file of the right type
3022 if (fileHeader.aFileType[0] != 'g'
3023 || fileHeader.aFileType[1] != 'a'
3024 || fileHeader.aFileType[2] != 'm'
3025 || fileHeader.aFileType[3] != 'e')
3026 {
3027 debug(LOG_ERROR, "gameLoad: Weird file type found? Has header letters - '%c' '%c' '%c' '%c' (should be 'g' 'a' 'm' 'e')",
3028 fileHeader.aFileType[0],
3029 fileHeader.aFileType[1],
3030 fileHeader.aFileType[2],
3031 fileHeader.aFileType[3]);
3032
3033 PHYSFS_close(fileHandle);
3034
3035 return false;
3036 }
3037
3038 debug(LOG_NEVER, "gl .gam file is version %u\n", fileHeader.version);
3039
3040 // Prior to getting here, the directory structure has been set to wherever the
3041 // map or savegame is loaded from, so we will get the right ruleset file.
3042 if (!PHYSFS_exists("ruleset.json"))
3043 {
3044 debug(LOG_ERROR, "ruleset.json not found! User generated data will not work.");
3045 memset(rulesettag, 0, sizeof(rulesettag));
3046 }
3047 else
3048 {
3049 WzConfig ruleset(WzString::fromUtf8("ruleset.json"), WzConfig::ReadOnly);
3050 if (!ruleset.contains("tag"))
3051 {
3052 debug(LOG_ERROR, "ruleset tag not found in ruleset.json!"); // fall-through
3053 }
3054 WzString tag = ruleset.value("tag", "[]").toWzString();
3055 sstrcpy(rulesettag, tag.toUtf8().c_str());
3056 if (strspn(rulesettag, "abcdefghijklmnopqrstuvwxyz") != strlen(rulesettag)) // for safety
3057 {
3058 debug(LOG_ERROR, "ruleset.json userdata tag contains invalid characters!");
3059 debug(LOG_ERROR, "User generated data will not work.");
3060 memset(rulesettag, 0, sizeof(rulesettag));
3061 }
3062 }
3063
3064 //set main version Id from game file
3065 saveGameVersion = fileHeader.version;
3066 debug(LOG_SAVE, "file version is %u, (%s)", fileHeader.version, fileName);
3067 /* Check the file version */
3068 if (fileHeader.version < VERSION_7)
3069 {
3070 debug(LOG_ERROR, "gameLoad: unsupported save format version %d", fileHeader.version);
3071 PHYSFS_close(fileHandle);
3072
3073 return false;
3074 }
3075 else if (fileHeader.version < VERSION_9)
3076 {
3077 bool retVal = gameLoadV7(fileHandle);
3078 PHYSFS_close(fileHandle);
3079 return retVal;
3080 }
3081 else if (fileHeader.version <= CURRENT_VERSION_NUM)
3082 {
3083 //The in-game load menu was clearing level data AFTER loading in some savegame data.
3084 //Hacking this in here so things make a little more sense and so that data loaded
3085 //in from main.json is somewhat safe from being reset by mistake.
3086 if (!levReleaseAll())
3087 {
3088 debug(LOG_ERROR, "Failed to unload old data. Attempting to load anyway");
3089 }
3090
3091 //remove the file extension
3092 CurrentFileName[strlen(CurrentFileName) - 4] = '\0';
3093 loadMainFile(std::string(CurrentFileName) + "/main.json");
3094
3095 bool retVal = gameLoadV(fileHandle, fileHeader.version);
3096 PHYSFS_close(fileHandle);
3097
3098 loadMainFileFinal(std::string(CurrentFileName) + "/main.json");
3099
3100 challengeFileName = "";
3101 return retVal;
3102 }
3103 else
3104 {
3105 debug(LOG_ERROR, "Unsupported main save format version %u", fileHeader.version);
3106 PHYSFS_close(fileHandle);
3107
3108 return false;
3109 }
3110 }
3111
3112 // Fix endianness of a savegame
endian_SaveGameV(SAVE_GAME * psSaveGame,UDWORD version)3113 static void endian_SaveGameV(SAVE_GAME *psSaveGame, UDWORD version)
3114 {
3115 unsigned int i;
3116 /* SAVE_GAME is GAME_SAVE_V33 */
3117 /* GAME_SAVE_V33 includes GAME_SAVE_V31 */
3118 if (version >= VERSION_33)
3119 {
3120 endian_udword(&psSaveGame->sGame.power);
3121 endian_udword(&psSaveGame->sNetPlay.playercount);
3122 endian_udword(&psSaveGame->savePlayer);
3123 for (i = 0; i < MAX_PLAYERS; i++)
3124 {
3125 endian_udword(&psSaveGame->sPlayerIndex[i]);
3126 }
3127 }
3128 /* GAME_SAVE_V31 includes GAME_SAVE_V30 */
3129 if (version >= VERSION_31)
3130 {
3131 endian_sdword(&psSaveGame->missionCheatTime);
3132 }
3133 /* GAME_SAVE_V30 includes GAME_SAVE_V29 */
3134 if (version >= VERSION_30)
3135 {
3136 endian_sdword(&psSaveGame->scrGameLevel);
3137 }
3138 /* GAME_SAVE_V29 includes GAME_SAVE_V27 */
3139 if (version >= VERSION_29)
3140 {
3141 endian_uword(&psSaveGame->missionScrollMinX);
3142 endian_uword(&psSaveGame->missionScrollMinY);
3143 endian_uword(&psSaveGame->missionScrollMaxX);
3144 endian_uword(&psSaveGame->missionScrollMaxY);
3145 }
3146 /* GAME_SAVE_V24 includes GAME_SAVE_V22 */
3147 if (version >= VERSION_24)
3148 {
3149 endian_udword(&psSaveGame->reinforceTime);
3150 }
3151 /* GAME_SAVE_V19 includes GAME_SAVE_V18 */
3152 if (version >= VERSION_19)
3153 {
3154 }
3155 /* GAME_SAVE_V18 includes GAME_SAVE_V17 */
3156 if (version >= VERSION_18)
3157 {
3158 endian_udword(&psSaveGame->oldestVersion);
3159 endian_udword(&psSaveGame->validityKey);
3160 }
3161 /* GAME_SAVE_V17 includes GAME_SAVE_V16 */
3162 if (version >= VERSION_17)
3163 {
3164 endian_udword(&psSaveGame->objId);
3165 }
3166 /* GAME_SAVE_V16 includes GAME_SAVE_V15 */
3167 if (version >= VERSION_16)
3168 {
3169 }
3170 /* GAME_SAVE_V15 includes GAME_SAVE_V14 */
3171 if (version >= VERSION_15)
3172 {
3173 endian_udword(&psSaveGame->RubbleTile);
3174 endian_udword(&psSaveGame->WaterTile);
3175 }
3176 /* GAME_SAVE_V14 includes GAME_SAVE_V12 */
3177 if (version >= VERSION_14)
3178 {
3179 endian_sdword(&psSaveGame->missionOffTime);
3180 endian_sdword(&psSaveGame->missionETA);
3181 endian_uword(&psSaveGame->missionHomeLZ_X);
3182 endian_uword(&psSaveGame->missionHomeLZ_Y);
3183 endian_sdword(&psSaveGame->missionPlayerX);
3184 endian_sdword(&psSaveGame->missionPlayerY);
3185 for (i = 0; i < MAX_PLAYERS; i++)
3186 {
3187 endian_uword(&psSaveGame->iTranspEntryTileX[i]);
3188 endian_uword(&psSaveGame->iTranspEntryTileY[i]);
3189 endian_uword(&psSaveGame->iTranspExitTileX[i]);
3190 endian_uword(&psSaveGame->iTranspExitTileY[i]);
3191 endian_udword(&psSaveGame->aDefaultSensor[i]);
3192 endian_udword(&psSaveGame->aDefaultECM[i]);
3193 endian_udword(&psSaveGame->aDefaultRepair[i]);
3194 }
3195 }
3196 /* GAME_SAVE_V12 includes GAME_SAVE_V11 */
3197 if (version >= VERSION_12)
3198 {
3199 endian_udword(&psSaveGame->missionTime);
3200 endian_udword(&psSaveGame->saveKey);
3201 }
3202 /* GAME_SAVE_V11 includes GAME_SAVE_V10 */
3203 if (version >= VERSION_11)
3204 {
3205 endian_sdword(&psSaveGame->currentPlayerPos.p.x);
3206 endian_sdword(&psSaveGame->currentPlayerPos.p.y);
3207 endian_sdword(&psSaveGame->currentPlayerPos.p.z);
3208 endian_sdword(&psSaveGame->currentPlayerPos.r.x);
3209 endian_sdword(&psSaveGame->currentPlayerPos.r.y);
3210 endian_sdword(&psSaveGame->currentPlayerPos.r.z);
3211 }
3212 /* GAME_SAVE_V10 includes GAME_SAVE_V7 */
3213 if (version >= VERSION_10)
3214 {
3215 for (i = 0; i < MAX_PLAYERS; i++)
3216 {
3217 endian_udword(&psSaveGame->power[i].currentPower);
3218 endian_udword(&psSaveGame->power[i].extractedPower);
3219 }
3220 }
3221 /* GAME_SAVE_V7 */
3222 if (version >= VERSION_7)
3223 {
3224 endian_udword(&psSaveGame->gameTime);
3225 endian_udword(&psSaveGame->GameType);
3226 endian_sdword(&psSaveGame->ScrollMinX);
3227 endian_sdword(&psSaveGame->ScrollMinY);
3228 endian_udword(&psSaveGame->ScrollMaxX);
3229 endian_udword(&psSaveGame->ScrollMaxY);
3230 }
3231 }
3232
3233 // -----------------------------------------------------------------------------------------
3234 // Get campaign number stuff is not needed in this form on the PSX (thank you very much)
getCampaignV(PHYSFS_file * fileHandle,unsigned int version)3235 static UDWORD getCampaignV(PHYSFS_file *fileHandle, unsigned int version)
3236 {
3237 SAVE_GAME_V14 saveGame;
3238
3239 debug(LOG_SAVE, "getCampaignV: version = %u", version);
3240
3241 if (version < VERSION_14)
3242 {
3243 return 0;
3244 }
3245 // We only need VERSION 12 data (saveGame.saveKey)
3246 else if (version <= VERSION_34)
3247 {
3248 if (WZ_PHYSFS_readBytes(fileHandle, &saveGame, sizeof(SAVE_GAME_V14)) != sizeof(SAVE_GAME_V14))
3249 {
3250 debug(LOG_ERROR, "getCampaignV: error while reading file: %s", WZ_PHYSFS_getLastError());
3251
3252 return 0;
3253 }
3254
3255 // Convert from little-endian to native byte-order
3256 endian_SaveGameV((SAVE_GAME *)&saveGame, VERSION_14);
3257 }
3258 else if (version <= CURRENT_VERSION_NUM)
3259 {
3260 if (!deserializeSaveGameV14Data(fileHandle, &saveGame))
3261 {
3262 debug(LOG_ERROR, "getCampaignV: error while reading file: %s", WZ_PHYSFS_getLastError());
3263
3264 return 0;
3265 }
3266 }
3267 else
3268 {
3269 debug(LOG_ERROR, "Bad savegame version %u", version);
3270 return 0;
3271 }
3272
3273 return saveGame.saveKey & (SAVEKEY_ONMISSION - 1);
3274 }
3275
3276 // -----------------------------------------------------------------------------------------
3277 // Returns the campaign number --- apparently this is does alot less than it look like
3278 /// it now does even less than it looks like on the psx ... cause its pc only
getCampaign(const char * fileName)3279 UDWORD getCampaign(const char *fileName)
3280 {
3281 GAME_SAVEHEADER fileHeader;
3282
3283 PHYSFS_file *fileHandle = openLoadFile(fileName, true);
3284 if (!fileHandle)
3285 {
3286 // Failure to open the file is a failure to load the specified savegame
3287 return false;
3288 }
3289
3290 debug(LOG_WZ, "getCampaign: %s", fileName);
3291
3292 // Read the header from the file
3293 if (!deserializeSaveGameHeader(fileHandle, &fileHeader))
3294 {
3295 debug(LOG_ERROR, "getCampaign: error while reading header from file (%s): %s", fileName, WZ_PHYSFS_getLastError());
3296 PHYSFS_close(fileHandle);
3297 return false;
3298 }
3299
3300 // Check the header to see if we've been given a file of the right type
3301 if (fileHeader.aFileType[0] != 'g'
3302 || fileHeader.aFileType[1] != 'a'
3303 || fileHeader.aFileType[2] != 'm'
3304 || fileHeader.aFileType[3] != 'e')
3305 {
3306 debug(LOG_ERROR, "getCampaign: Weird file type found? Has header letters - '%c' '%c' '%c' '%c' (should be 'g' 'a' 'm' 'e')",
3307 fileHeader.aFileType[0],
3308 fileHeader.aFileType[1],
3309 fileHeader.aFileType[2],
3310 fileHeader.aFileType[3]);
3311
3312 PHYSFS_close(fileHandle);
3313
3314 return false;
3315 }
3316
3317 debug(LOG_NEVER, "gl .gam file is version %d\n", fileHeader.version);
3318
3319 //set main version Id from game file
3320 saveGameVersion = fileHeader.version;
3321
3322 debug(LOG_SAVE, "fileversion is %u, (%s) ", fileHeader.version, fileName);
3323 /* Check the file version */
3324 if (fileHeader.version < VERSION_14)
3325 {
3326 PHYSFS_close(fileHandle);
3327 return 0;
3328 }
3329
3330 // what the arse bollocks is this
3331 // the campaign number is fine prior to saving
3332 // you save it out in a skirmish save and
3333 // then don't bother putting it back in again
3334 // when loading so it screws loads of stuff?!?
3335 // don't check skirmish saves.
3336 if (fileHeader.version <= CURRENT_VERSION_NUM)
3337 {
3338 UDWORD retVal = getCampaignV(fileHandle, fileHeader.version);
3339 PHYSFS_close(fileHandle);
3340 return retVal;
3341 }
3342 else
3343 {
3344 debug(LOG_ERROR, "getCampaign: undefined save format version %d", fileHeader.version);
3345 PHYSFS_close(fileHandle);
3346
3347 return 0;
3348 }
3349 }
3350
3351 // -----------------------------------------------------------------------------------------
3352 /* code specific to version 7 of a save game */
gameLoadV7(PHYSFS_file * fileHandle)3353 bool gameLoadV7(PHYSFS_file *fileHandle)
3354 {
3355 SAVE_GAME_V7 saveGame;
3356
3357 if (WZ_PHYSFS_readBytes(fileHandle, &saveGame, sizeof(saveGame)) != sizeof(saveGame))
3358 {
3359 debug(LOG_ERROR, "gameLoadV7: error while reading file: %s", WZ_PHYSFS_getLastError());
3360
3361 return false;
3362 }
3363
3364 /* GAME_SAVE_V7 */
3365 endian_udword(&saveGame.gameTime);
3366 endian_udword(&saveGame.GameType);
3367 endian_sdword(&saveGame.ScrollMinX);
3368 endian_sdword(&saveGame.ScrollMinY);
3369 endian_udword(&saveGame.ScrollMaxX);
3370 endian_udword(&saveGame.ScrollMaxY);
3371
3372 savedGameTime = saveGame.gameTime;
3373
3374 //set the scroll varaibles
3375 startX = saveGame.ScrollMinX;
3376 startY = saveGame.ScrollMinY;
3377 width = saveGame.ScrollMaxX - saveGame.ScrollMinX;
3378 height = saveGame.ScrollMaxY - saveGame.ScrollMinY;
3379 gameType = static_cast<GAME_TYPE>(saveGame.GameType);
3380 //set IsScenario to true if not a user saved game
3381 if (gameType == GTYPE_SAVE_START)
3382 {
3383 LEVEL_DATASET *psNewLevel;
3384
3385 IsScenario = false;
3386 //copy the level name across
3387 sstrcpy(aLevelName, saveGame.levelName);
3388 //load up the level dataset
3389 if (!levLoadData(aLevelName, nullptr, saveGameName, (GAME_TYPE)gameType))
3390 {
3391 return false;
3392 }
3393 // find the level dataset
3394 psNewLevel = levFindDataSet(aLevelName);
3395 if (psNewLevel == nullptr)
3396 {
3397 debug(LOG_ERROR, "gameLoadV7: couldn't find level data");
3398
3399 return false;
3400 }
3401 //check to see whether mission automatically starts
3402 //shouldn't be able to be any other value at the moment!
3403 if (psNewLevel->type == LEVEL_TYPE::LDS_CAMSTART
3404 || psNewLevel->type == LEVEL_TYPE::LDS_BETWEEN
3405 || psNewLevel->type == LEVEL_TYPE::LDS_EXPAND
3406 || psNewLevel->type == LEVEL_TYPE::LDS_EXPAND_LIMBO)
3407 {
3408 launchMission();
3409 }
3410
3411 }
3412 else
3413 {
3414 IsScenario = true;
3415 }
3416
3417 return true;
3418 }
3419
3420 // -----------------------------------------------------------------------------------------
3421 /* non specific version of a save game */
gameLoadV(PHYSFS_file * fileHandle,unsigned int version)3422 bool gameLoadV(PHYSFS_file *fileHandle, unsigned int version)
3423 {
3424 unsigned int i, j;
3425 static SAVE_POWER powerSaved[MAX_PLAYERS];
3426 UDWORD player;
3427
3428 debug(LOG_WZ, "gameLoadV: version %u", version);
3429
3430 // Version 7 and earlier are loaded separately in gameLoadV7
3431
3432 //size is now variable so only check old save games
3433 if (version <= VERSION_10)
3434 {
3435 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V10)) != sizeof(SAVE_GAME_V10))
3436 {
3437 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3438
3439 return false;
3440 }
3441 }
3442 else if (version == VERSION_11)
3443 {
3444 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V11)) != sizeof(SAVE_GAME_V11))
3445 {
3446 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3447
3448 return false;
3449 }
3450 }
3451 else if (version <= VERSION_12)
3452 {
3453 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V12)) != sizeof(SAVE_GAME_V12))
3454 {
3455 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3456
3457 return false;
3458 }
3459 }
3460 else if (version <= VERSION_14)
3461 {
3462 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V14)) != sizeof(SAVE_GAME_V14))
3463 {
3464 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3465
3466 return false;
3467 }
3468 }
3469 else if (version <= VERSION_15)
3470 {
3471 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V15)) != sizeof(SAVE_GAME_V15))
3472 {
3473 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3474
3475 return false;
3476 }
3477 }
3478 else if (version <= VERSION_16)
3479 {
3480 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V16)) != sizeof(SAVE_GAME_V16))
3481 {
3482 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3483
3484 return false;
3485 }
3486 }
3487 else if (version <= VERSION_17)
3488 {
3489 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V17)) != sizeof(SAVE_GAME_V17))
3490 {
3491 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3492
3493 return false;
3494 }
3495 }
3496 else if (version <= VERSION_18)
3497 {
3498 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V18)) != sizeof(SAVE_GAME_V18))
3499 {
3500 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3501
3502 return false;
3503 }
3504 }
3505 else if (version <= VERSION_19)
3506 {
3507 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V19)) != sizeof(SAVE_GAME_V19))
3508 {
3509 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3510
3511 return false;
3512 }
3513 }
3514 else if (version <= VERSION_21)
3515 {
3516 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V20)) != sizeof(SAVE_GAME_V20))
3517 {
3518 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3519
3520 return false;
3521 }
3522 }
3523 else if (version <= VERSION_23)
3524 {
3525 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V22)) != sizeof(SAVE_GAME_V22))
3526 {
3527 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3528
3529 return false;
3530 }
3531 }
3532 else if (version <= VERSION_26)
3533 {
3534 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V24)) != sizeof(SAVE_GAME_V24))
3535 {
3536 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3537
3538 return false;
3539 }
3540 }
3541 else if (version <= VERSION_28)
3542 {
3543 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V27)) != sizeof(SAVE_GAME_V27))
3544 {
3545 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3546
3547 return false;
3548 }
3549 }
3550 else if (version <= VERSION_29)
3551 {
3552 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V29)) != sizeof(SAVE_GAME_V29))
3553 {
3554 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3555
3556 return false;
3557 }
3558 }
3559 else if (version <= VERSION_30)
3560 {
3561 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V30)) != sizeof(SAVE_GAME_V30))
3562 {
3563 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3564
3565 return false;
3566 }
3567 }
3568 else if (version <= VERSION_32)
3569 {
3570 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V31)) != sizeof(SAVE_GAME_V31))
3571 {
3572 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3573
3574 return false;
3575 }
3576 }
3577 else if (version <= VERSION_33)
3578 {
3579 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V33)) != sizeof(SAVE_GAME_V33))
3580 {
3581 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3582
3583 return false;
3584 }
3585 }
3586 else if (version <= VERSION_34)
3587 {
3588 if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V34)) != sizeof(SAVE_GAME_V34))
3589 {
3590 debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3591
3592 return false;
3593 }
3594 }
3595 else if (version < VERSION_39)
3596 {
3597 debug(LOG_ERROR, "Unsupported savegame version");
3598 return false;
3599 }
3600 else if (version <= CURRENT_VERSION_NUM)
3601 {
3602 if (!deserializeSaveGameData(fileHandle, &saveGameData))
3603 {
3604 debug(LOG_ERROR, "gameLoadV: error while reading data from file for deserialization (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3605
3606 return false;
3607 }
3608 }
3609 else
3610 {
3611 debug(LOG_ERROR, "Unsupported version number (%u) for savegame", version);
3612
3613 return false;
3614 }
3615
3616 debug(LOG_SAVE, "Savegame is of type: %u", static_cast<uint8_t>(saveGameData.sGame.type));
3617 game.type = saveGameData.sGame.type;
3618 /* Test mod list */
3619 if (version >= VERSION_38)
3620 {
3621 setOverrideMods(saveGameData.modList);
3622 }
3623
3624 // All savegames from version 34 or before are little endian so swap them. All
3625 // from version 35, and onward, are already swapped to the native byte-order
3626 // by the (de)serialization API
3627 if (version <= VERSION_34)
3628 {
3629 endian_SaveGameV(&saveGameData, version);
3630 }
3631
3632 savedGameTime = saveGameData.gameTime;
3633
3634 if (version >= VERSION_12)
3635 {
3636 mission.startTime = saveGameData.missionTime;
3637 if (saveGameData.saveKey & SAVEKEY_ONMISSION)
3638 {
3639 saveGameOnMission = true;
3640 }
3641 else
3642 {
3643 saveGameOnMission = false;
3644 }
3645
3646 }
3647 else
3648 {
3649 saveGameOnMission = false;
3650 }
3651 //set the scroll varaibles
3652 startX = saveGameData.ScrollMinX;
3653 startY = saveGameData.ScrollMinY;
3654 width = saveGameData.ScrollMaxX - saveGameData.ScrollMinX;
3655 height = saveGameData.ScrollMaxY - saveGameData.ScrollMinY;
3656 gameType = static_cast<GAME_TYPE>(saveGameData.GameType);
3657
3658 if (version >= VERSION_11)
3659 {
3660 //camera position
3661 disp3d_setView(&saveGameData.currentPlayerPos);
3662 }
3663 else
3664 {
3665 disp3d_oldView();
3666 }
3667
3668 //load mission data from save game these values reloaded after load game
3669
3670 if (version >= VERSION_14)
3671 {
3672 //mission data
3673 mission.time = saveGameData.missionOffTime;
3674 mission.ETA = saveGameData.missionETA;
3675 mission.homeLZ_X = saveGameData.missionHomeLZ_X;
3676 mission.homeLZ_Y = saveGameData.missionHomeLZ_Y;
3677 mission.playerX = saveGameData.missionPlayerX;
3678 mission.playerY = saveGameData.missionPlayerY;
3679
3680 for (player = 0; player < MAX_PLAYERS; player++)
3681 {
3682 mission.iTranspEntryTileX[player] = saveGameData.iTranspEntryTileX[player];
3683 mission.iTranspEntryTileY[player] = saveGameData.iTranspEntryTileY[player];
3684 mission.iTranspExitTileX[player] = saveGameData.iTranspExitTileX[player];
3685 mission.iTranspExitTileY[player] = saveGameData.iTranspExitTileY[player];
3686 aDefaultSensor[player] = saveGameData.aDefaultSensor[player];
3687 aDefaultECM[player] = saveGameData.aDefaultECM[player];
3688 aDefaultRepair[player] = saveGameData.aDefaultRepair[player];
3689 }
3690 }
3691
3692 if (version >= VERSION_15)
3693 {
3694 offWorldKeepLists = saveGameData.offWorldKeepLists;
3695 setRubbleTile(saveGameData.RubbleTile);
3696 setUnderwaterTile(saveGameData.WaterTile);
3697 }
3698
3699 if (version >= VERSION_17)
3700 {
3701 unsynchObjID = (saveGameData.objId + 1) / 2; // Make new object ID start at savedObjId*8.
3702 synchObjID = saveGameData.objId * 4; // Make new object ID start at savedObjId*8.
3703 savedObjId = saveGameData.objId;
3704 }
3705
3706 if (version >= VERSION_19)//version 19
3707 {
3708 for (i = 0; i < MAX_PLAYERS; i++)
3709 {
3710 for (j = 0; j < MAX_PLAYERS; j++)
3711 {
3712 alliances[i][j] = saveGameData.alliances[i][j];
3713 }
3714 }
3715 for (i = 0; i < MAX_PLAYERS; i++)
3716 {
3717 setPlayerColour(i, saveGameData.playerColour[i]);
3718 }
3719 }
3720
3721 if (version >= VERSION_20)//version 20
3722 {
3723 setDroidsToSafetyFlag(saveGameData.bDroidsToSafetyFlag);
3724 }
3725
3726 if (saveGameVersion >= VERSION_24)//V24
3727 {
3728 missionSetReinforcementTime(saveGameData.reinforceTime);
3729
3730 // horrible hack to catch savegames that were saving garbage into these fields
3731 if (saveGameData.bPlayCountDown <= 1)
3732 {
3733 setPlayCountDown(saveGameData.bPlayCountDown);
3734 }
3735 if (saveGameData.bPlayerHasWon <= 1)
3736 {
3737 setPlayerHasWon(saveGameData.bPlayerHasWon);
3738 }
3739 if (saveGameData.bPlayerHasLost <= 1)
3740 {
3741 setPlayerHasLost(saveGameData.bPlayerHasLost);
3742 }
3743 }
3744
3745 if (saveGameVersion >= VERSION_29)
3746 {
3747 mission.scrollMinX = saveGameData.missionScrollMinX;
3748 mission.scrollMinY = saveGameData.missionScrollMinY;
3749 mission.scrollMaxX = saveGameData.missionScrollMaxX;
3750 mission.scrollMaxY = saveGameData.missionScrollMaxY;
3751 }
3752
3753 if (saveGameVersion >= VERSION_31)
3754 {
3755 mission.cheatTime = saveGameData.missionCheatTime;
3756 }
3757
3758 droidInit();
3759
3760 //set IsScenario to true if not a user saved game
3761 if ((gameType == GTYPE_SAVE_START) ||
3762 (gameType == GTYPE_SAVE_MIDMISSION))
3763 {
3764 for (i = 0; i < MAX_PLAYERS; ++i)
3765 {
3766 powerSaved[i].currentPower = saveGameData.power[i].currentPower;
3767 powerSaved[i].extractedPower = saveGameData.power[i].extractedPower;
3768 }
3769
3770 allocatePlayers();
3771
3772 IsScenario = false;
3773 //copy the level name across
3774 sstrcpy(aLevelName, saveGameData.levelName);
3775 //load up the level dataset
3776 // 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.
3777 if (!levLoadData(aLevelName, &saveGameData.sGame.hash, saveGameName, (GAME_TYPE)gameType))
3778 {
3779 return false;
3780 }
3781
3782 if (saveGameVersion >= VERSION_33)
3783 {
3784 PLAYERSTATS playerStats;
3785 bool scav = game.scavengers; // loaded earlier, keep it over struct copy below
3786
3787 bMultiPlayer = saveGameData.multiPlayer;
3788 bMultiMessages = bMultiPlayer;
3789 productionPlayer = selectedPlayer;
3790 game = saveGameData.sGame; // why do this again????
3791 game.scavengers = scav;
3792 NetPlay.bComms = (saveGameData.sNetPlay).bComms;
3793 if (bMultiPlayer)
3794 {
3795 loadMultiStats(saveGameData.sPName, &playerStats); // stats stuff
3796 setMultiStats(selectedPlayer, playerStats, false);
3797 setMultiStats(selectedPlayer, playerStats, true);
3798 }
3799 }
3800 }
3801 else
3802 {
3803 IsScenario = true;
3804 }
3805
3806 getPlayerNames();
3807
3808 clearPlayerPower();
3809 //don't adjust any power if a camStart (gameType is set to GTYPE_SCENARIO_START when a camChange saveGame is loaded)
3810 if (gameType != GTYPE_SCENARIO_START)
3811 {
3812 //set the players power
3813 for (i = 0; i < MAX_PLAYERS; ++i)
3814 {
3815 //only overwrite selectedPlayer's power on a startMission save game
3816 if (gameType == GTYPE_SAVE_MIDMISSION || i == selectedPlayer)
3817 {
3818 setPower(i, powerSaved[i].currentPower);
3819 }
3820 }
3821 }
3822 radarPermitted = (bool)powerSaved[0].extractedPower; // nice hack, eh? don't want to break savegames now...
3823 allowDesign = (bool)powerSaved[1].extractedPower; // nice hack, eh? don't want to break savegames now...
3824
3825 return true;
3826 }
3827
3828 // -----------------------------------------------------------------------------------------
3829 // Load main game data from JSON. Only implement stuff here that we actually use instead of
3830 // the binary blobbery.
loadMainFile(const std::string & fileName)3831 static bool loadMainFile(const std::string &fileName)
3832 {
3833 WzConfig save(WzString::fromUtf8(fileName), WzConfig::ReadOnly);
3834
3835 if (save.contains("playerBuiltHQ"))
3836 {
3837 playerBuiltHQ = save.value("playerBuiltHQ").toBool();
3838 }
3839 if (save.contains("challengeFileName"))
3840 {
3841 challengeFileName = save.string("challengeFileName");
3842 }
3843 if (save.contains("builtInMap"))
3844 {
3845 builtInMap = save.value("builtInMap").toBool();
3846 }
3847
3848 save.beginArray("players");
3849 while (save.remainingArrayItems() > 0)
3850 {
3851 int index = save.value("index").toInt();
3852 if (!(index >= 0 && index < MAX_PLAYERS))
3853 {
3854 debug(LOG_ERROR, "Invalid player index: %d", index);
3855 save.nextArrayItem();
3856 continue;
3857 }
3858 unsigned int FactionValue = save.value("faction", static_cast<uint8_t>(FACTION_NORMAL)).toUInt();
3859 NetPlay.players[index].faction = static_cast<FactionID>(FactionValue);
3860 save.nextArrayItem();
3861 }
3862 save.endArray();
3863
3864 return true;
3865 }
3866
loadMainFileFinal(const std::string & fileName)3867 static bool loadMainFileFinal(const std::string &fileName)
3868 {
3869 WzConfig save(WzString::fromUtf8(fileName), WzConfig::ReadOnly);
3870
3871 if (save.contains("techLevel"))
3872 {
3873 game.techLevel = save.value("techLevel").toInt();
3874 }
3875
3876 save.beginArray("players");
3877 while (save.remainingArrayItems() > 0)
3878 {
3879 int index = save.value("index").toInt();
3880 if (!(index >= 0 && index < MAX_PLAYERS))
3881 {
3882 debug(LOG_ERROR, "Invalid player index: %d", index);
3883 save.nextArrayItem();
3884 continue;
3885 }
3886 auto value = save.value("recycled_droids").jsonValue();
3887 for (const auto &v : value)
3888 {
3889 add_to_experience_queue(index, json_variant(v).toInt());
3890 }
3891 save.nextArrayItem();
3892 }
3893 save.endArray();
3894
3895 return true;
3896 }
3897
3898 // -----------------------------------------------------------------------------------------
3899 // Save main game data to JSON. We save more here than we need to, and duplicate some of the
3900 // binary blobbery, for future usage.
writeMainFile(const std::string & fileName,SDWORD saveType)3901 static bool writeMainFile(const std::string &fileName, SDWORD saveType)
3902 {
3903 ASSERT(saveType == GTYPE_SAVE_START || saveType == GTYPE_SAVE_MIDMISSION, "invalid save type");
3904
3905 WzConfig save(WzString::fromUtf8(fileName), WzConfig::ReadAndWrite);
3906
3907 uint32_t saveKey = getCampaignNumber();
3908 if (missionIsOffworld())
3909 {
3910 saveKey |= SAVEKEY_ONMISSION;
3911 saveGameOnMission = true;
3912 }
3913 else
3914 {
3915 saveGameOnMission = false;
3916 }
3917
3918 /* Put the save game data into the buffer */
3919 save.setValue("version", 1); // version of this file
3920 save.setValue("saveKey", saveKey);
3921 save.setValue("gameTime", gameTime);
3922 save.setValue("missionTime", mission.startTime);
3923 save.setVector2i("scrollMin", Vector2i(scrollMinX, scrollMinY));
3924 save.setVector2i("scrollMax", Vector2i(scrollMaxX, scrollMaxY));
3925 save.setValue("saveType", saveType);
3926 save.setValue("levelName", aLevelName);
3927 save.setValue("radarPermitted", radarPermitted);
3928 save.setValue("allowDesign", allowDesign);
3929 save.setValue("missionOffTime", mission.time);
3930 save.setValue("missionETA", mission.ETA);
3931 save.setValue("missionCheatTime", mission.cheatTime);
3932 save.setVector2i("missionHomeLZ", Vector2i(mission.homeLZ_X, mission.homeLZ_Y));
3933 save.setVector2i("missionPlayerPos", Vector2i(mission.playerX, mission.playerY));
3934 save.setVector2i("missionScrollMin", Vector2i(mission.scrollMinX, mission.scrollMinY));
3935 save.setVector2i("missionScrollMax", Vector2i(mission.scrollMaxX, mission.scrollMaxY));
3936 save.setValue("offWorldKeepLists", offWorldKeepLists);
3937 save.setValue("rubbleTile", getRubbleTileNum());
3938 save.setValue("waterTile", getWaterTileNum());
3939 save.setValue("objId", MAX(unsynchObjID * 2, (synchObjID + 3) / 4));
3940 save.setValue("radarZoom", GetRadarZoom());
3941 save.setValue("droidsToSafetyFlag", getDroidsToSafetyFlag());
3942 save.setValue("reinforceTime", missionGetReinforcementTime());
3943 save.setValue("playCountDown", getPlayCountDown());
3944
3945 save.beginArray("players");
3946 for (int i = 0; i < MAX_PLAYERS; ++i)
3947 {
3948 save.setValue("index", i);
3949 save.setValue("power", getPower(i));
3950 save.setVector2i("iTranspEntryTile", Vector2i(mission.iTranspEntryTileX[i], mission.iTranspEntryTileY[i]));
3951 save.setVector2i("iTranspExitTile", Vector2i(mission.iTranspExitTileX[i], mission.iTranspExitTileY[i]));
3952 save.setValue("aDefaultSensor", aDefaultSensor[i]);
3953 save.setValue("aDefaultECM", aDefaultECM[i]);
3954 save.setValue("aDefaultRepair", aDefaultRepair[i]);
3955 save.setValue("colour", getPlayerColour(i));
3956
3957 std::priority_queue<int> experience = copy_experience_queue(i);
3958 nlohmann::json recycled_droids = nlohmann::json::array();
3959 while (!experience.empty())
3960 {
3961 recycled_droids.push_back(experience.top());
3962 experience.pop();
3963 }
3964 save.set("recycled_droids", recycled_droids);
3965
3966 nlohmann::json allies = nlohmann::json::array();
3967 for (int j = 0; j < MAX_PLAYERS; j++)
3968 {
3969 allies.push_back(alliances[i][j]);
3970 }
3971 save.set("alliances", allies);
3972 save.setValue("difficulty", NetPlay.players[i].difficulty);
3973
3974 save.setValue("position", NetPlay.players[i].position);
3975 save.setValue("colour", NetPlay.players[i].colour);
3976 save.setValue("allocated", NetPlay.players[i].allocated);
3977 save.setValue("faction", NetPlay.players[i].faction);
3978 save.setValue("team", NetPlay.players[i].team);
3979 save.setValue("ai", NetPlay.players[i].ai);
3980 save.setValue("autoGame", NetPlay.players[i].autoGame);
3981 save.setValue("ip", NetPlay.players[i].IPtextAddress);
3982 save.setValue("name", getPlayerName(selectedPlayer));
3983
3984 save.nextArrayItem();
3985 }
3986 save.endArray();
3987
3988 iView currPlayerPos;
3989 disp3d_getView(&currPlayerPos);
3990 save.setVector3i("camera_position", currPlayerPos.p);
3991 save.setVector3i("camera_rotation", currPlayerPos.r);
3992
3993 save.beginArray("landing_zones");
3994 for (int i = 0; i < MAX_NOGO_AREAS; ++i)
3995 {
3996 LANDING_ZONE *psLandingZone = getLandingZone(i);
3997 save.setVector2i("start", Vector2i(psLandingZone->x1, psLandingZone->x2));
3998 save.setVector2i("end", Vector2i(psLandingZone->y1, psLandingZone->y2));
3999 save.nextArrayItem();
4000 }
4001 save.endArray();
4002
4003 save.setValue("playerHasWon", testPlayerHasWon());
4004 save.setValue("playerHasLost", testPlayerHasLost());
4005 save.setValue("gameLevel", 0);
4006 save.setValue("failFlag", 0);
4007 save.setValue("trackTransporter", 0);
4008 save.setValue("gameType", game.type);
4009 save.setValue("scavengers", game.scavengers);
4010 save.setValue("mapName", game.map);
4011 save.setValue("maxPlayers", game.maxPlayers);
4012 save.setValue("gameName", game.name);
4013 save.setValue("powerSetting", game.power);
4014 save.setValue("baseSetting", game.base);
4015 save.setValue("allianceSetting", game.alliance);
4016 save.setValue("mapHasScavengers", game.mapHasScavengers);
4017 save.setValue("mapMod", game.isMapMod);
4018 save.setValue("selectedPlayer", selectedPlayer);
4019 save.setValue("multiplayer", bMultiPlayer);
4020 save.setValue("playerCount", NetPlay.playercount);
4021 save.setValue("hostPlayer", NetPlay.hostPlayer);
4022 save.setValue("bComms", NetPlay.bComms);
4023 save.setValue("modList", getModList().c_str());
4024 save.setValue("playerBuiltHQ", playerBuiltHQ);
4025 save.setValue("techLevel", game.techLevel);
4026 save.setValue("challengeFileName", challengeFileName.toUtf8().c_str());
4027 save.setValue("builtInMap", builtInMap);
4028
4029 return true;
4030 }
4031
writeGameFile(const char * fileName,SDWORD saveType)4032 static bool writeGameFile(const char *fileName, SDWORD saveType)
4033 {
4034 GAME_SAVEHEADER fileHeader;
4035 SAVE_GAME saveGame;
4036 bool status;
4037 unsigned int i, j;
4038
4039 PHYSFS_file *fileHandle = openSaveFile(fileName);
4040 if (!fileHandle)
4041 {
4042 debug(LOG_ERROR, "openSaveFile(\"%s\") failed", fileName);
4043 return false;
4044 }
4045
4046 WZ_PHYSFS_SETBUFFER(fileHandle, 4096)//;
4047
4048 fileHeader.aFileType[0] = 'g';
4049 fileHeader.aFileType[1] = 'a';
4050 fileHeader.aFileType[2] = 'm';
4051 fileHeader.aFileType[3] = 'e';
4052
4053 fileHeader.version = CURRENT_VERSION_NUM;
4054
4055 debug(LOG_SAVE, "fileversion is %u, (%s) ", fileHeader.version, fileName);
4056
4057 if (!serializeSaveGameHeader(fileHandle, &fileHeader))
4058 {
4059 debug(LOG_ERROR, "could not write header to %s; PHYSFS error: %s", fileName, WZ_PHYSFS_getLastError());
4060 PHYSFS_close(fileHandle);
4061 return false;
4062 }
4063
4064 ASSERT(saveType == GTYPE_SAVE_START || saveType == GTYPE_SAVE_MIDMISSION, "invalid save type");
4065 saveGame.saveKey = getCampaignNumber();
4066 if (missionIsOffworld())
4067 {
4068 saveGame.saveKey |= SAVEKEY_ONMISSION;
4069 saveGameOnMission = true;
4070 }
4071 else
4072 {
4073 saveGameOnMission = false;
4074 }
4075
4076
4077 /* Put the save game data into the buffer */
4078 saveGame.gameTime = gameTime;
4079 saveGame.missionTime = mission.startTime;
4080
4081 //put in the scroll data
4082 saveGame.ScrollMinX = scrollMinX;
4083 saveGame.ScrollMinY = scrollMinY;
4084 saveGame.ScrollMaxX = scrollMaxX;
4085 saveGame.ScrollMaxY = scrollMaxY;
4086
4087 saveGame.GameType = saveType;
4088
4089 //save the current level so we can load up the STARTING point of the mission
4090 ASSERT_OR_RETURN(false, strlen(aLevelName) < MAX_LEVEL_SIZE, "Unable to save level name - too long (max %d) - %s",
4091 (int)MAX_LEVEL_SIZE, aLevelName);
4092 sstrcpy(saveGame.levelName, aLevelName);
4093
4094 //save out the players power
4095 for (i = 0; i < MAX_PLAYERS; ++i)
4096 {
4097 saveGame.power[i].currentPower = getPower(i);
4098 }
4099 saveGame.power[0].extractedPower = radarPermitted; // hideous hack, don't want to break savegames now...
4100 saveGame.power[1].extractedPower = allowDesign; // hideous hack, don't want to break savegames now...
4101
4102 //camera position
4103 disp3d_getView(&(saveGame.currentPlayerPos));
4104
4105 //mission data
4106 saveGame.missionOffTime = mission.time;
4107 saveGame.missionETA = mission.ETA;
4108 saveGame.missionCheatTime = mission.cheatTime;
4109 saveGame.missionHomeLZ_X = mission.homeLZ_X;
4110 saveGame.missionHomeLZ_Y = mission.homeLZ_Y;
4111 saveGame.missionPlayerX = mission.playerX;
4112 saveGame.missionPlayerY = mission.playerY;
4113 saveGame.missionScrollMinX = (UWORD)mission.scrollMinX;
4114 saveGame.missionScrollMinY = (UWORD)mission.scrollMinY;
4115 saveGame.missionScrollMaxX = (UWORD)mission.scrollMaxX;
4116 saveGame.missionScrollMaxY = (UWORD)mission.scrollMaxY;
4117
4118 saveGame.offWorldKeepLists = offWorldKeepLists;
4119 saveGame.RubbleTile = getRubbleTileNum();
4120 saveGame.WaterTile = getWaterTileNum();
4121
4122 for (i = 0; i < MAX_PLAYERS; ++i)
4123 {
4124 saveGame.iTranspEntryTileX[i] = mission.iTranspEntryTileX[i];
4125 saveGame.iTranspEntryTileY[i] = mission.iTranspEntryTileY[i];
4126 saveGame.iTranspExitTileX[i] = mission.iTranspExitTileX[i];
4127 saveGame.iTranspExitTileY[i] = mission.iTranspExitTileY[i];
4128 saveGame.aDefaultSensor[i] = aDefaultSensor[i];
4129 saveGame.aDefaultECM[i] = aDefaultECM[i];
4130 saveGame.aDefaultRepair[i] = aDefaultRepair[i];
4131 }
4132
4133 for (i = 0; i < MAX_NOGO_AREAS; ++i)
4134 {
4135 LANDING_ZONE *psLandingZone = getLandingZone(i);
4136 saveGame.sLandingZone[i].x1 = psLandingZone->x1; // in case struct changes
4137 saveGame.sLandingZone[i].x2 = psLandingZone->x2;
4138 saveGame.sLandingZone[i].y1 = psLandingZone->y1;
4139 saveGame.sLandingZone[i].y2 = psLandingZone->y2;
4140 }
4141
4142 //version 17
4143 saveGame.objId = MAX(unsynchObjID * 2, (synchObjID + 3) / 4);
4144
4145 //version 18
4146 memset(saveGame.buildDate, 0, sizeof(saveGame.buildDate));
4147 saveGame.oldestVersion = 0;
4148 saveGame.validityKey = 0;
4149
4150 //version 19
4151 for (i = 0; i < MAX_PLAYERS; i++)
4152 {
4153 for (j = 0; j < MAX_PLAYERS; j++)
4154 {
4155 saveGame.alliances[i][j] = alliances[i][j];
4156 }
4157 }
4158 for (i = 0; i < MAX_PLAYERS; i++)
4159 {
4160 saveGame.playerColour[i] = getPlayerColour(i);
4161 }
4162 saveGame.radarZoom = (UBYTE)GetRadarZoom();
4163
4164 //version 20
4165 saveGame.bDroidsToSafetyFlag = (UBYTE)getDroidsToSafetyFlag();
4166
4167 //version 24
4168 saveGame.reinforceTime = missionGetReinforcementTime();
4169 saveGame.bPlayCountDown = (UBYTE)getPlayCountDown();
4170 saveGame.bPlayerHasWon = (UBYTE)testPlayerHasWon();
4171 saveGame.bPlayerHasLost = (UBYTE)testPlayerHasLost();
4172
4173 //version 30
4174 saveGame.scrGameLevel = 0;
4175 saveGame.bExtraFailFlag = 0;
4176 saveGame.bExtraVictoryFlag = 0;
4177 saveGame.bTrackTransporter = 0;
4178
4179 // version 33
4180 saveGame.sGame = game;
4181 saveGame.savePlayer = selectedPlayer;
4182 saveGame.multiPlayer = bMultiPlayer;
4183 saveGame.sNetPlay = NetPlay;
4184 sstrcpy(saveGame.sPName, getPlayerName(selectedPlayer));
4185 for (i = 0; i < MAX_PLAYERS; ++i)
4186 {
4187 saveGame.sPlayerIndex[i] = i;
4188 }
4189
4190 //version 34
4191 for (i = 0; i < MAX_PLAYERS; ++i)
4192 {
4193 sstrcpy(saveGame.sPlayerName[i], getPlayerName(i));
4194 }
4195
4196 //version 38
4197 sstrcpy(saveGame.modList, getModList().c_str());
4198 // Attempt to see if we have a corrupted game structure in campaigns.
4199 if (saveGame.sGame.type == LEVEL_TYPE::CAMPAIGN)
4200 {
4201 // player 0 is always a human in campaign games
4202 for (i = 1; i < MAX_PLAYERS; i++)
4203 {
4204 if (saveGame.sNetPlay.players[i].difficulty == AIDifficulty::HUMAN)
4205 {
4206 ASSERT(!"savegame corruption!", "savegame corruption!");
4207 debug(LOG_ERROR, "Savegame corruption detected, trying to salvage. Please Report this issue @ wz2100.net");
4208 debug(LOG_ERROR, "players[i].difficulty was %d, level %s / %s, ", (int) static_cast<int8_t>(saveGame.sNetPlay.players[i].difficulty), saveGame.levelName, saveGame.sGame.map);
4209 saveGame.sNetPlay.players[i].difficulty = AIDifficulty::DISABLED;
4210 }
4211 }
4212 }
4213
4214 status = serializeSaveGameData(fileHandle, &saveGame);
4215
4216 // Close the file
4217 PHYSFS_close(fileHandle);
4218
4219 // Return our success status with writing out the file!
4220 return status;
4221 }
4222
4223 // -----------------------------------------------------------------------------------------
4224 // Process the droid initialisation file (dinit.bjo). Creates droids for
4225 // the scenario being loaded. This is *NEVER* called for a user save game
4226 //
loadSaveDroidInit(char * pFileData,UDWORD filesize)4227 bool loadSaveDroidInit(char *pFileData, UDWORD filesize)
4228 {
4229 DROIDINIT_SAVEHEADER *psHeader;
4230 SAVE_DROIDINIT *pDroidInit;
4231 const DROID_TEMPLATE *psTemplate;
4232 DROID *psDroid;
4233 UDWORD i;
4234 UDWORD NumberOfSkippedDroids = 0;
4235
4236 /* Check the file type */
4237 psHeader = (DROIDINIT_SAVEHEADER *)pFileData;
4238 if (psHeader->aFileType[0] != 'd' || psHeader->aFileType[1] != 'i' || psHeader->aFileType[2] != 'n' || psHeader->aFileType[3] != 't')
4239 {
4240 debug(LOG_ERROR, "Incorrect file type");
4241 return false;
4242 }
4243
4244 /* DROIDINIT_SAVEHEADER */
4245 endian_udword(&psHeader->version);
4246 endian_udword(&psHeader->quantity);
4247
4248 //increment to the start of the data
4249 pFileData += DROIDINIT_HEADER_SIZE;
4250
4251 debug(LOG_SAVE, "fileversion is %u ", psHeader->version);
4252
4253 pDroidInit = (SAVE_DROIDINIT *)pFileData;
4254
4255 for (i = 0; i < psHeader->quantity; i++)
4256 {
4257 /* SAVE_DROIDINIT is OBJECT_SAVE_V19 */
4258 /* OBJECT_SAVE_V19 */
4259 endian_udword(&pDroidInit->id);
4260 endian_udword(&pDroidInit->x);
4261 endian_udword(&pDroidInit->y);
4262 endian_udword(&pDroidInit->z);
4263 endian_udword(&pDroidInit->direction);
4264 endian_udword(&pDroidInit->player);
4265 endian_udword(&pDroidInit->periodicalDamageStart);
4266 endian_udword(&pDroidInit->periodicalDamage);
4267
4268 pDroidInit->player = RemapPlayerNumber(pDroidInit->player);
4269 if (pDroidInit->player >= MAX_PLAYERS)
4270 {
4271 pDroidInit->player = MAX_PLAYERS - 1; // now don't lose any droids ... force them to be the last player
4272 NumberOfSkippedDroids++;
4273 }
4274
4275 psTemplate = getTemplateFromTranslatedNameNoPlayer(pDroidInit->name);
4276 if (psTemplate == nullptr)
4277 {
4278 debug(LOG_ERROR, "Unable to find template for %s for player %d", pDroidInit->name, pDroidInit->player);
4279 }
4280 else
4281 {
4282 psDroid = reallyBuildDroid(psTemplate, Position((pDroidInit->x & ~TILE_MASK) + TILE_UNITS / 2, (pDroidInit->y & ~TILE_MASK) + TILE_UNITS / 2, 0), pDroidInit->player, false);
4283 if (psDroid)
4284 {
4285 Vector2i startpos = getPlayerStartPosition(psDroid->player);
4286
4287 psDroid->id = pDroidInit->id > 0 ? pDroidInit->id : 0xFEDBCA98; // hack to remove droid id zero
4288 psDroid->rot.direction = DEG(pDroidInit->direction);
4289 addDroid(psDroid, apsDroidLists);
4290 if (psDroid->droidType == DROID_CONSTRUCT && startpos.x == 0 && startpos.y == 0)
4291 {
4292 scriptSetStartPos(psDroid->player, psDroid->pos.x, psDroid->pos.y);
4293 }
4294 }
4295 else
4296 {
4297 debug(LOG_ERROR, "This droid cannot be built - %s", pDroidInit->name);
4298 return false;
4299 }
4300 }
4301 pDroidInit++;
4302 }
4303 if (NumberOfSkippedDroids)
4304 {
4305 debug(LOG_ERROR, "Bad Player number in %d unit(s)... assigned to the last player!", NumberOfSkippedDroids);
4306 return false;
4307 }
4308 return true;
4309 }
4310
4311 // -----------------------------------------------------------------------------------------
4312 // Remaps old player number based on position on map to new owner
RemapPlayerNumber(UDWORD OldNumber)4313 static UDWORD RemapPlayerNumber(UDWORD OldNumber)
4314 {
4315 int i;
4316
4317 if (game.type == LEVEL_TYPE::CAMPAIGN) // don't remap for SP games
4318 {
4319 return OldNumber;
4320 }
4321
4322 for (i = 0; i < MAX_PLAYERS; i++)
4323 {
4324 if (OldNumber == NetPlay.players[i].position)
4325 {
4326 game.mapHasScavengers = game.mapHasScavengers || i == scavengerSlot();
4327 return i;
4328 }
4329 }
4330 ASSERT(false, "Found no player position for player %d", (int)OldNumber);
4331 return 0;
4332 }
4333
remapIntPlayerNumber(int oldNumber)4334 static int remapIntPlayerNumber(int oldNumber)
4335 {
4336 if (oldNumber < 0)
4337 {
4338 game.mapHasScavengers = true;
4339 return scavengerSlot();
4340 }
4341
4342 for (int i = 0; i < MAX_PLAYERS; ++i)
4343 {
4344 if (oldNumber == NetPlay.players[i].position)
4345 {
4346 return i;
4347 }
4348 }
4349 ASSERT(false, "Found no player position for player %d", oldNumber);
4350 return 0;
4351 }
4352
getPlayer(WzConfig & ini)4353 static int getPlayer(WzConfig &ini)
4354 {
4355 if (ini.contains("player"))
4356 {
4357 json_variant result = ini.value("player");
4358 if (result.toWzString().startsWith("scavenger"))
4359 {
4360 game.mapHasScavengers = true;
4361 return scavengerSlot();
4362 }
4363 return result.toInt();
4364 }
4365 else if (ini.contains("startpos"))
4366 {
4367 int position = ini.value("startpos").toInt();
4368 for (int i = 0; i < game.maxPlayers; i++)
4369 {
4370 if (NetPlay.players[i].position == position)
4371 {
4372 return i;
4373 }
4374 }
4375 }
4376 ASSERT(false, "No player info found!");
4377 return 0;
4378 }
4379
setPlayer(WzConfig & ini,int player)4380 static void setPlayer(WzConfig &ini, int player)
4381 {
4382 if (scavengerSlot() == player)
4383 {
4384 ini.setValue("player", "scavenger");
4385 }
4386 else
4387 {
4388 ini.setValue("player", player);
4389 }
4390 }
4391
setPlayerJSON(nlohmann::json & jsonObj,int player)4392 static inline void setPlayerJSON(nlohmann::json &jsonObj, int player)
4393 {
4394 if (scavengerSlot() == player)
4395 {
4396 jsonObj["player"] = "scavenger";
4397 }
4398 else
4399 {
4400 jsonObj["player"] = player;
4401 }
4402 }
4403
skipForDifficulty(WzConfig & ini,int player)4404 static bool skipForDifficulty(WzConfig &ini, int player)
4405 {
4406 if (ini.contains("difficulty")) // optionally skip this object
4407 {
4408 int difficulty = ini.value("difficulty").toInt();
4409 if ((game.type == LEVEL_TYPE::CAMPAIGN && difficulty > (int)getDifficultyLevel())
4410 || (game.type == LEVEL_TYPE::SKIRMISH && difficulty > static_cast<int8_t>(NetPlay.players[player].difficulty)))
4411 {
4412 return true;
4413 }
4414 }
4415 return false;
4416 }
4417
loadSaveDroidPointers(const WzString & pFileName,DROID ** ppsCurrentDroidLists)4418 static bool loadSaveDroidPointers(const WzString &pFileName, DROID **ppsCurrentDroidLists)
4419 {
4420 WzConfig ini(pFileName, WzConfig::ReadOnly);
4421 std::vector<WzString> list = ini.childGroups();
4422
4423 for (size_t i = 0; i < list.size(); ++i)
4424 {
4425 ini.beginGroup(list[i]);
4426 DROID *psDroid;
4427 int id = ini.value("id", -1).toInt();
4428 int player = getPlayer(ini);
4429
4430 if (id <= 0)
4431 {
4432 ini.endGroup();
4433 continue; // special hack for campaign missions, cannot have targets
4434 }
4435 if (skipForDifficulty(ini, player))
4436 {
4437 ini.endGroup();
4438 continue; // another hack for campaign missions, cannot have targets
4439 }
4440
4441 for (psDroid = ppsCurrentDroidLists[player]; psDroid && psDroid->id != id; psDroid = psDroid->psNext)
4442 {
4443 if (isTransporter(psDroid) && psDroid->psGroup != nullptr) // Check for droids in the transporter.
4444 {
4445 for (DROID *psTrDroid = psDroid->psGroup->psList; psTrDroid != nullptr; psTrDroid = psTrDroid->psGrpNext)
4446 {
4447 if (psTrDroid->id == id)
4448 {
4449 psDroid = psTrDroid;
4450 goto foundDroid;
4451 }
4452 }
4453 }
4454 }
4455 foundDroid:
4456 if (!psDroid)
4457 {
4458 for (psDroid = mission.apsDroidLists[player]; psDroid && psDroid->id != id; psDroid = psDroid->psNext) {}
4459 // FIXME
4460 if (psDroid)
4461 {
4462 debug(LOG_ERROR, "Droid %s (%d) was in wrong file/list (was in %s)...", objInfo(psDroid), id, pFileName.toUtf8().c_str());
4463 }
4464 }
4465 ASSERT_OR_RETURN(false, psDroid, "Droid %d not found", id);
4466
4467 getIniDroidOrder(ini, "order", psDroid->order);
4468 psDroid->listSize = clip(ini.value("orderList/size", 0).toInt(), 0, 10000);
4469 psDroid->asOrderList.resize(psDroid->listSize); // Must resize before setting any orders, and must set in-place, since pointers are updated later.
4470 for (int droidIdx = 0; droidIdx < psDroid->listSize; ++droidIdx)
4471 {
4472 getIniDroidOrder(ini, "orderList/" + WzString::number(droidIdx), psDroid->asOrderList[droidIdx]);
4473 }
4474 psDroid->listPendingBegin = 0;
4475 for (int j = 0; j < MAX_WEAPONS; j++)
4476 {
4477 objTrace(psDroid->id, "weapon %d, nStat %d", j, psDroid->asWeaps[j].nStat);
4478 getIniBaseObject(ini, "actionTarget/" + WzString::number(j), psDroid->psActionTarget[j]);
4479 }
4480 if (ini.contains("baseStruct/id"))
4481 {
4482 int tid = ini.value("baseStruct/id", -1).toInt();
4483 int tplayer = ini.value("baseStruct/player", -1).toInt();
4484 OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value("baseStruct/type", 0).toInt();
4485 ASSERT(tid >= 0 && tplayer >= 0, "Bad ID");
4486 BASE_OBJECT *psObj = getBaseObjFromData(tid, tplayer, ttype);
4487 ASSERT(psObj, "Failed to find droid base structure");
4488 ASSERT(psObj->type == OBJ_STRUCTURE, "Droid base structure not a structure");
4489 setSaveDroidBase(psDroid, (STRUCTURE *)psObj);
4490 }
4491 if (ini.contains("commander"))
4492 {
4493 int tid = ini.value("commander", -1).toInt();
4494 DROID *psCommander = (DROID *)getBaseObjFromData(tid, psDroid->player, OBJ_DROID);
4495 ASSERT(psCommander, "Failed to find droid commander");
4496 cmdDroidAddDroid(psCommander, psDroid);
4497 }
4498 ini.endGroup();
4499 }
4500 return true;
4501 }
4502
healthValue(WzConfig & ini,int defaultValue)4503 static int healthValue(WzConfig &ini, int defaultValue)
4504 {
4505 WzString health = ini.value("health").toWzString();
4506 if (health.isEmpty() || defaultValue == 0)
4507 {
4508 return defaultValue;
4509 }
4510 else if (health.contains(WzUniCodepoint::fromASCII('%')))
4511 {
4512 int perc = health.replace("%", "").toInt();
4513 return MAX(defaultValue * perc / 100, 1); //hp not supposed to be 0
4514 }
4515 else
4516 {
4517 return MIN(health.toInt(), defaultValue);
4518 }
4519 }
4520
loadSaveObject(WzConfig & ini,BASE_OBJECT * psObj)4521 static void loadSaveObject(WzConfig &ini, BASE_OBJECT *psObj)
4522 {
4523 psObj->died = ini.value("died", 0).toInt();
4524 memset(psObj->visible, 0, sizeof(psObj->visible));
4525 for (int j = 0; j < game.maxPlayers; j++)
4526 {
4527 psObj->visible[j] = ini.value("visible/" + WzString::number(j), 0).toInt();
4528 }
4529 psObj->periodicalDamage = ini.value("periodicalDamage", 0).toInt();
4530 psObj->periodicalDamageStart = ini.value("periodicalDamageStart", 0).toInt();
4531 psObj->timeAnimationStarted = ini.value("timeAnimationStarted", 0).toInt();
4532 psObj->animationEvent = ini.value("animationEvent", 0).toInt();
4533 psObj->timeLastHit = ini.value("timeLastHit", UDWORD_MAX).toInt();
4534 psObj->lastEmission = ini.value("lastEmission", 0).toInt();
4535 psObj->selected = ini.value("selected", false).toBool();
4536 psObj->born = ini.value("born", 2).toInt();
4537 }
4538
writeSaveObject(WzConfig & ini,BASE_OBJECT * psObj)4539 static void writeSaveObject(WzConfig &ini, BASE_OBJECT *psObj)
4540 {
4541 ini.setValue("id", psObj->id);
4542 setPlayer(ini, psObj->player);
4543 ini.setValue("health", psObj->body);
4544 ini.setVector3i("position", psObj->pos);
4545 ini.setVector3i("rotation", toVector(psObj->rot));
4546 if (psObj->timeAnimationStarted)
4547 {
4548 ini.setValue("timeAnimationStarted", psObj->timeAnimationStarted);
4549 }
4550 if (psObj->animationEvent)
4551 {
4552 ini.setValue("animationEvent", psObj->animationEvent);
4553 }
4554 ini.setValue("selected", psObj->selected); // third kind of group
4555 if (psObj->lastEmission)
4556 {
4557 ini.setValue("lastEmission", psObj->lastEmission);
4558 }
4559 if (psObj->periodicalDamageStart > 0)
4560 {
4561 ini.setValue("periodicalDamageStart", psObj->periodicalDamageStart);
4562 }
4563 if (psObj->periodicalDamage > 0)
4564 {
4565 ini.setValue("periodicalDamage", psObj->periodicalDamage);
4566 }
4567 ini.setValue("born", psObj->born);
4568 if (psObj->died > 0)
4569 {
4570 ini.setValue("died", psObj->died);
4571 }
4572 if (psObj->timeLastHit != UDWORD_MAX)
4573 {
4574 ini.setValue("timeLastHit", psObj->timeLastHit);
4575 }
4576 if (psObj->selected)
4577 {
4578 ini.setValue("selected", psObj->selected);
4579 }
4580 for (int i = 0; i < game.maxPlayers; i++)
4581 {
4582 if (psObj->visible[i])
4583 {
4584 ini.setValue("visible/" + WzString::number(i), psObj->visible[i]);
4585 }
4586 }
4587 }
4588
writeSaveObjectJSON(nlohmann::json & jsonObj,BASE_OBJECT * psObj)4589 static void writeSaveObjectJSON(nlohmann::json &jsonObj, BASE_OBJECT *psObj)
4590 {
4591 jsonObj["id"] = psObj->id;
4592 setPlayerJSON(jsonObj, psObj->player);
4593 jsonObj["health"] = psObj->body;
4594 jsonObj["position"] = psObj->pos;
4595 jsonObj["rotation"] = toVector(psObj->rot);
4596 if (psObj->timeAnimationStarted)
4597 {
4598 jsonObj["timeAnimationStarted"] = psObj->timeAnimationStarted;
4599 }
4600 if (psObj->animationEvent)
4601 {
4602 jsonObj["animationEvent"] = psObj->animationEvent;
4603 }
4604 jsonObj["selected"] = psObj->selected; // third kind of group
4605 if (psObj->lastEmission)
4606 {
4607 jsonObj["lastEmission"] = psObj->lastEmission;
4608 }
4609 if (psObj->periodicalDamageStart > 0)
4610 {
4611 jsonObj["periodicalDamageStart"] = psObj->periodicalDamageStart;
4612 }
4613 if (psObj->periodicalDamage > 0)
4614 {
4615 jsonObj["periodicalDamage"] = psObj->periodicalDamage;
4616 }
4617 jsonObj["born"] = psObj->born;
4618 if (psObj->died > 0)
4619 {
4620 jsonObj["died"] = psObj->died;
4621 }
4622 if (psObj->timeLastHit != UDWORD_MAX)
4623 {
4624 jsonObj["timeLastHit"] = psObj->timeLastHit;
4625 }
4626 if (psObj->selected)
4627 {
4628 jsonObj["selected"] = psObj->selected;
4629 }
4630 for (int i = 0; i < game.maxPlayers; i++)
4631 {
4632 if (psObj->visible[i])
4633 {
4634 jsonObj["visible/" + WzString::number(i).toStdString()] = psObj->visible[i];
4635 }
4636 }
4637 }
4638
loadScriptDroid(ScriptMapData const & data)4639 static bool loadScriptDroid(ScriptMapData const &data)
4640 {
4641 ASSERT_OR_RETURN(false, data.valid, "No data.");
4642
4643 for (auto &droid : data.droids)
4644 {
4645 unsigned player = remapIntPlayerNumber(droid.player);
4646 auto psTemplate = getTemplateFromTranslatedNameNoPlayer(droid.name.toUtf8().c_str());
4647 if (psTemplate == nullptr)
4648 {
4649 debug(LOG_ERROR, "Unable to find template for %s for player %d -- unit skipped", droid.name.toUtf8().c_str(), player);
4650 continue;
4651 }
4652 turnOffMultiMsg(true);
4653 auto psDroid = reallyBuildDroid(psTemplate, {droid.position, 0}, player, false, {droid.direction, 0, 0});
4654 turnOffMultiMsg(false);
4655 if (psDroid == nullptr)
4656 {
4657 debug(LOG_ERROR, "Failed to build unit %s", droid.name.toUtf8().c_str());
4658 continue;
4659 }
4660 addDroid(psDroid, apsDroidLists);
4661 }
4662
4663 return true;
4664 }
4665
loadSaveDroid(const char * pFileName,DROID ** ppsCurrentDroidLists)4666 static bool loadSaveDroid(const char *pFileName, DROID **ppsCurrentDroidLists)
4667 {
4668 if (!PHYSFS_exists(pFileName))
4669 {
4670 debug(LOG_SAVE, "No %s found -- use fallback method", pFileName);
4671 return false; // try to use fallback method
4672 }
4673 WzString fName = WzString::fromUtf8(pFileName);
4674 WzConfig ini(fName, WzConfig::ReadOnly);
4675 std::vector<WzString> list = ini.childGroups();
4676 // Sort list so transports are loaded first, since they must be loaded before the droids they contain.
4677 std::vector<std::pair<int, WzString>> sortedList;
4678 bool missionList = fName.compare("mdroid");
4679 for (size_t i = 0; i < list.size(); ++i)
4680 {
4681 ini.beginGroup(list[i]);
4682 DROID_TYPE droidType = (DROID_TYPE)ini.value("droidType").toInt();
4683 int priority = 0;
4684 switch (droidType)
4685 {
4686 case DROID_TRANSPORTER:
4687 ++priority; // fallthrough
4688 case DROID_SUPERTRANSPORTER:
4689 ++priority; // fallthrough
4690 case DROID_COMMAND:
4691 //Don't care about sorting commanders in the mission list for safety missions. They
4692 //don't have a group to command and it messes up the order of the list sorting them
4693 //which causes problems getting the first transporter group for Gamma-1.
4694 if (!missionList || (missionList && !getDroidsToSafetyFlag()))
4695 {
4696 ++priority;
4697 }
4698 default:
4699 break;
4700 }
4701 sortedList.push_back(std::make_pair(-priority, list[i]));
4702 ini.endGroup();
4703 }
4704 std::sort(sortedList.begin(), sortedList.end());
4705
4706 for (unsigned i = 0; i < sortedList.size(); ++i)
4707 {
4708 ini.beginGroup(sortedList[i].second);
4709 DROID *psDroid;
4710 int player = getPlayer(ini);
4711 int id = ini.value("id", -1).toInt();
4712 Position pos = ini.vector3i("position");
4713 Rotation rot = ini.vector3i("rotation");
4714 bool onMission = ini.value("onMission", false).toBool();
4715 DROID_TEMPLATE templ;
4716 const DROID_TEMPLATE *psTemplate = nullptr;
4717
4718 if (skipForDifficulty(ini, player))
4719 {
4720 ini.endGroup();
4721 continue;
4722 }
4723
4724 if (ini.contains("template"))
4725 {
4726 // Use real template (for maps)
4727 WzString templName(ini.value("template").toWzString());
4728 psTemplate = getTemplateFromTranslatedNameNoPlayer(templName.toUtf8().c_str());
4729 if (psTemplate == nullptr)
4730 {
4731 debug(LOG_ERROR, "Unable to find template for %s for player %d -- unit skipped", templName.toUtf8().c_str(), player);
4732 ini.endGroup();
4733 continue;
4734 }
4735 }
4736 else
4737 {
4738 // Create fake template
4739 templ.name = ini.string("name", "UNKNOWN");
4740 templ.droidType = (DROID_TYPE)ini.value("droidType").toInt();
4741 templ.numWeaps = ini.value("weapons", 0).toInt();
4742 ini.beginGroup("parts"); // the following is copy-pasted from loadSaveTemplate() -- fixme somehow
4743 templ.asParts[COMP_BODY] = getCompFromName(COMP_BODY, ini.value("body", "ZNULLBODY").toWzString());
4744 templ.asParts[COMP_BRAIN] = getCompFromName(COMP_BRAIN, ini.value("brain", "ZNULLBRAIN").toWzString());
4745 templ.asParts[COMP_PROPULSION] = getCompFromName(COMP_PROPULSION, ini.value("propulsion", "ZNULLPROP").toWzString());
4746 templ.asParts[COMP_REPAIRUNIT] = getCompFromName(COMP_REPAIRUNIT, ini.value("repair", "ZNULLREPAIR").toWzString());
4747 templ.asParts[COMP_ECM] = getCompFromName(COMP_ECM, ini.value("ecm", "ZNULLECM").toWzString());
4748 templ.asParts[COMP_SENSOR] = getCompFromName(COMP_SENSOR, ini.value("sensor", "ZNULLSENSOR").toWzString());
4749 templ.asParts[COMP_CONSTRUCT] = getCompFromName(COMP_CONSTRUCT, ini.value("construct", "ZNULLCONSTRUCT").toWzString());
4750 templ.asWeaps[0] = getCompFromName(COMP_WEAPON, ini.value("weapon/1", "ZNULLWEAPON").toWzString());
4751 templ.asWeaps[1] = getCompFromName(COMP_WEAPON, ini.value("weapon/2", "ZNULLWEAPON").toWzString());
4752 templ.asWeaps[2] = getCompFromName(COMP_WEAPON, ini.value("weapon/3", "ZNULLWEAPON").toWzString());
4753 ini.endGroup();
4754 psTemplate = &templ;
4755 }
4756
4757 // If droid is on a mission, calling with the saved position might cause an assertion. Or something like that.
4758 if (!onMission)
4759 {
4760 pos.x = clip(pos.x, world_coord(1), world_coord(mapWidth - 1));
4761 pos.y = clip(pos.y, world_coord(1), world_coord(mapHeight - 1));
4762 }
4763
4764 /* Create the Droid */
4765 turnOffMultiMsg(true);
4766 psDroid = reallyBuildDroid(psTemplate, pos, player, onMission, rot);
4767 ASSERT_OR_RETURN(false, psDroid != nullptr, "Failed to build unit %s", sortedList[i].second.toUtf8().c_str());
4768 turnOffMultiMsg(false);
4769
4770 // Copy the values across
4771 if (id > 0)
4772 {
4773 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)
4774 }
4775 ASSERT(id != 0, "Droid ID should never be zero here");
4776 psDroid->body = healthValue(ini, psDroid->originalBody);
4777 ASSERT(psDroid->body != 0, "%s : %d has zero hp!", pFileName, i);
4778 psDroid->experience = ini.value("experience", 0).toInt();
4779 psDroid->kills = ini.value("kills", 0).toInt();
4780 psDroid->secondaryOrder = ini.value("secondaryOrder", psDroid->secondaryOrder).toInt();
4781 psDroid->secondaryOrderPending = psDroid->secondaryOrder;
4782 psDroid->action = (DROID_ACTION)ini.value("action", DACTION_NONE).toInt();
4783 psDroid->actionPos = ini.vector2i("action/pos");
4784 psDroid->actionStarted = ini.value("actionStarted", 0).toInt();
4785 psDroid->actionPoints = ini.value("actionPoints", 0).toInt();
4786 psDroid->resistance = ini.value("resistance", 0).toInt(); // zero resistance == no electronic damage
4787 psDroid->lastFrustratedTime = ini.value("lastFrustratedTime", 0).toInt();
4788
4789 // common BASE_OBJECT info
4790 loadSaveObject(ini, psDroid);
4791
4792 // copy the droid's weapon stats
4793 for (int j = 0; j < psDroid->numWeaps; j++)
4794 {
4795 if (psDroid->asWeaps[j].nStat > 0)
4796 {
4797 psDroid->asWeaps[j].ammo = ini.value("ammo/" + WzString::number(j)).toInt();
4798 psDroid->asWeaps[j].lastFired = ini.value("lastFired/" + WzString::number(j)).toInt();
4799 psDroid->asWeaps[j].shotsFired = ini.value("shotsFired/" + WzString::number(j)).toInt();
4800 psDroid->asWeaps[j].rot = ini.vector3i("rotation/" + WzString::number(j));
4801 }
4802 }
4803
4804 psDroid->group = ini.value("group", UBYTE_MAX).toInt();
4805 int aigroup = ini.value("aigroup", -1).toInt();
4806 if (aigroup >= 0)
4807 {
4808 DROID_GROUP *psGroup = grpFind(aigroup);
4809 psGroup->add(psDroid);
4810 if (psGroup->type == GT_TRANSPORTER)
4811 {
4812 psDroid->selected = false; // Droid should be visible in the transporter interface.
4813 visRemoveVisibility(psDroid); // should not have visibility data when in a transporter
4814 }
4815 }
4816 else
4817 {
4818 if (isTransporter(psDroid) || psDroid->droidType == DROID_COMMAND)
4819 {
4820 DROID_GROUP *psGroup = grpCreate();
4821 psGroup->add(psDroid);
4822 }
4823 else
4824 {
4825 psDroid->psGroup = nullptr;
4826 }
4827 }
4828
4829 psDroid->sMove.Status = (MOVE_STATUS)ini.value("moveStatus", 0).toInt();
4830 psDroid->sMove.pathIndex = ini.value("pathIndex", 0).toInt();
4831 const int numPoints = ini.value("pathLength", 0).toInt();
4832 psDroid->sMove.asPath.resize(numPoints);
4833 for (int j = 0; j < numPoints; j++)
4834 {
4835 psDroid->sMove.asPath[j] = ini.vector2i("pathNode/" + WzString::number(j));
4836 }
4837 psDroid->sMove.destination = ini.vector2i("moveDestination");
4838 psDroid->sMove.src = ini.vector2i("moveSource");
4839 psDroid->sMove.target = ini.vector2i("moveTarget");
4840 psDroid->sMove.speed = ini.value("moveSpeed").toInt();
4841 psDroid->sMove.moveDir = ini.value("moveDirection").toInt();
4842 psDroid->sMove.bumpDir = ini.value("bumpDir").toInt();
4843 psDroid->sMove.iVertSpeed = ini.value("vertSpeed").toInt();
4844 psDroid->sMove.bumpTime = ini.value("bumpTime").toInt();
4845 psDroid->sMove.shuffleStart = ini.value("shuffleStart").toInt();
4846 for (int j = 0; j < MAX_WEAPONS; ++j)
4847 {
4848 psDroid->asWeaps[j].usedAmmo = ini.value("attackRun/" + WzString::number(j)).toInt();
4849 }
4850 psDroid->sMove.lastBump = ini.value("lastBump").toInt();
4851 psDroid->sMove.pauseTime = ini.value("pauseTime").toInt();
4852 Vector2i tmp = ini.vector2i("bumpPosition");
4853 psDroid->sMove.bumpPos = Vector3i(tmp.x, tmp.y, 0);
4854
4855 // Recreate path-finding jobs
4856 if (psDroid->sMove.Status == MOVEWAITROUTE)
4857 {
4858 psDroid->sMove.Status = MOVEINACTIVE;
4859 fpathDroidRoute(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y, FMT_MOVE);
4860 psDroid->sMove.Status = MOVEWAITROUTE;
4861
4862 // Droid might be on a mission, so finish pathfinding now, in case pointers swap and map size changes.
4863 FPATH_RETVAL dr = fpathDroidRoute(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y, FMT_MOVE);
4864 if (dr == FPR_OK)
4865 {
4866 psDroid->sMove.Status = MOVENAVIGATE;
4867 psDroid->sMove.pathIndex = 0;
4868 }
4869 else // if (retVal == FPR_FAILED)
4870 {
4871 psDroid->sMove.Status = MOVEINACTIVE;
4872 actionDroid(psDroid, DACTION_SULK);
4873 }
4874 ASSERT(dr != FPR_WAIT, " ");
4875 }
4876
4877 // HACK!!
4878 Vector2i startpos = getPlayerStartPosition(player);
4879 if (psDroid->droidType == DROID_CONSTRUCT && startpos.x == 0 && startpos.y == 0)
4880 {
4881 scriptSetStartPos(psDroid->player, psDroid->pos.x, psDroid->pos.y); // set map start position, FIXME - save properly elsewhere!
4882 }
4883
4884 if (psDroid->psGroup == nullptr || psDroid->psGroup->type != GT_TRANSPORTER || isTransporter(psDroid)) // do not add to list if on a transport, then the group list is used instead
4885 {
4886 addDroid(psDroid, ppsCurrentDroidLists);
4887 }
4888
4889 ini.endGroup();
4890 }
4891 return true;
4892 }
4893
4894 // -----------------------------------------------------------------------------------------
4895 /*
4896 Writes the linked list of droids for each player to a file
4897 */
writeDroid(DROID * psCurr,bool onMission,int & counter)4898 static nlohmann::json writeDroid(DROID *psCurr, bool onMission, int &counter)
4899 {
4900 nlohmann::json droidObj = nlohmann::json::object();
4901 droidObj["name"] = psCurr->aName;
4902
4903 // write common BASE_OBJECT info
4904 writeSaveObjectJSON(droidObj, psCurr);
4905
4906 for (int i = 0; i < psCurr->numWeaps; i++)
4907 {
4908 if (psCurr->asWeaps[i].nStat > 0)
4909 {
4910 const std::string& numStr = WzString::number(i).toStdString();
4911 droidObj["ammo/" + numStr] = psCurr->asWeaps[i].ammo;
4912 droidObj["lastFired/" + numStr] = psCurr->asWeaps[i].lastFired;
4913 droidObj["shotsFired/" + numStr] = psCurr->asWeaps[i].shotsFired;
4914 droidObj["rotation/" + numStr] = toVector(psCurr->asWeaps[i].rot);
4915 }
4916 }
4917 for (int i = 0; i < MAX_WEAPONS; i++)
4918 {
4919 setIniBaseObject(droidObj, "actionTarget/" + WzString::number(i), psCurr->psActionTarget[i]);
4920 }
4921 if (psCurr->lastFrustratedTime > 0)
4922 {
4923 droidObj["lastFrustratedTime"] = psCurr->lastFrustratedTime;
4924 }
4925 if (psCurr->experience > 0)
4926 {
4927 droidObj["experience"] = psCurr->experience;
4928 }
4929 if (psCurr->kills > 0)
4930 {
4931 droidObj["kills"] = psCurr->kills;
4932 }
4933
4934 setIniDroidOrder(droidObj, "order", psCurr->order);
4935 droidObj["orderList/size"] = psCurr->listSize;
4936 for (int i = 0; i < psCurr->listSize; ++i)
4937 {
4938 setIniDroidOrder(droidObj, "orderList/" + WzString::number(i), psCurr->asOrderList[i]);
4939 }
4940 if (psCurr->timeLastHit != UDWORD_MAX)
4941 {
4942 droidObj["timeLastHit"] = psCurr->timeLastHit;
4943 }
4944 droidObj["secondaryOrder"] = psCurr->secondaryOrder;
4945 droidObj["action"] = psCurr->action;
4946 droidObj["actionString"] = getDroidActionName(psCurr->action); // future-proofing
4947 droidObj["action/pos"] = psCurr->actionPos;
4948 droidObj["actionStarted"] = psCurr->actionStarted;
4949 droidObj["actionPoints"] = psCurr->actionPoints;
4950 if (psCurr->psBaseStruct != nullptr)
4951 {
4952 droidObj["baseStruct/id"] = psCurr->psBaseStruct->id;
4953 droidObj["baseStruct/player"] = psCurr->psBaseStruct->player; // always ours, but for completeness
4954 droidObj["baseStruct/type"] = psCurr->psBaseStruct->type; // always a building, but for completeness
4955 }
4956 if (psCurr->psGroup)
4957 {
4958 droidObj["aigroup"] = psCurr->psGroup->id; // AI and commander/transport group
4959 droidObj["aigroup/type"] = psCurr->psGroup->type;
4960 }
4961 droidObj["group"] = psCurr->group; // different kind of group. of course.
4962 if (hasCommander(psCurr) && psCurr->psGroup->psCommander->died <= 1)
4963 {
4964 droidObj["commander"] = psCurr->psGroup->psCommander->id;
4965 }
4966 if (psCurr->resistance > 0)
4967 {
4968 droidObj["resistance"] = psCurr->resistance;
4969 }
4970 droidObj["droidType"] = psCurr->droidType;
4971 droidObj["weapons"] = psCurr->numWeaps;
4972 nlohmann::json partsObj = nlohmann::json::object();
4973 partsObj["body"] = (asBodyStats + psCurr->asBits[COMP_BODY])->id;
4974 partsObj["propulsion"] = (asPropulsionStats + psCurr->asBits[COMP_PROPULSION])->id;
4975 partsObj["brain"] = (asBrainStats + psCurr->asBits[COMP_BRAIN])->id;
4976 partsObj["repair"] = (asRepairStats + psCurr->asBits[COMP_REPAIRUNIT])->id;
4977 partsObj["ecm"] = (asECMStats + psCurr->asBits[COMP_ECM])->id;
4978 partsObj["sensor"] = (asSensorStats + psCurr->asBits[COMP_SENSOR])->id;
4979 partsObj["construct"] = (asConstructStats + psCurr->asBits[COMP_CONSTRUCT])->id;
4980 for (int j = 0; j < psCurr->numWeaps; j++)
4981 {
4982 partsObj["weapon/" + WzString::number(j + 1).toStdString()] = (asWeaponStats + psCurr->asWeaps[j].nStat)->id;
4983 }
4984 droidObj["parts"] = partsObj;
4985 droidObj["moveStatus"] = psCurr->sMove.Status;
4986 droidObj["pathIndex"] = psCurr->sMove.pathIndex;
4987 droidObj["pathLength"] = psCurr->sMove.asPath.size();
4988 for (unsigned i = 0; i < psCurr->sMove.asPath.size(); i++)
4989 {
4990 droidObj["pathNode/" + WzString::number(i).toStdString()] = psCurr->sMove.asPath[i];
4991 }
4992 droidObj["moveDestination"] = psCurr->sMove.destination;
4993 droidObj["moveSource"] = psCurr->sMove.src;
4994 droidObj["moveTarget"] = psCurr->sMove.target;
4995 droidObj["moveSpeed"] = psCurr->sMove.speed;
4996 droidObj["moveDirection"] = psCurr->sMove.moveDir;
4997 droidObj["bumpDir"] = psCurr->sMove.bumpDir;
4998 droidObj["vertSpeed"] = psCurr->sMove.iVertSpeed;
4999 droidObj["bumpTime"] = psCurr->sMove.bumpTime;
5000 droidObj["shuffleStart"] = psCurr->sMove.shuffleStart;
5001 for (int i = 0; i < MAX_WEAPONS; ++i)
5002 {
5003 droidObj["attackRun/" + WzString::number(i).toStdString()] = psCurr->asWeaps[i].usedAmmo;
5004 }
5005 droidObj["lastBump"] = psCurr->sMove.lastBump;
5006 droidObj["pauseTime"] = psCurr->sMove.pauseTime;
5007 droidObj["bumpPosition"] = psCurr->sMove.bumpPos.xy();
5008 droidObj["onMission"] = onMission;
5009 return droidObj;
5010 }
5011
writeDroidFile(const char * pFileName,DROID ** ppsCurrentDroidLists)5012 static bool writeDroidFile(const char *pFileName, DROID **ppsCurrentDroidLists)
5013 {
5014 nlohmann::json mRoot = nlohmann::json::object();
5015 int counter = 0;
5016 bool onMission = (ppsCurrentDroidLists[0] == mission.apsDroidLists[0]);
5017
5018 for (int player = 0; player < MAX_PLAYERS; player++)
5019 {
5020 for (DROID *psCurr = ppsCurrentDroidLists[player]; psCurr != nullptr; psCurr = psCurr->psNext)
5021 {
5022 auto droidKey = "droid_" + (WzString::number(counter++).leftPadToMinimumLength(WzUniCodepoint::fromASCII('0'), 10)); // Zero padded so that alphabetical sort works.
5023 mRoot[droidKey.toStdString()] = writeDroid(psCurr, onMission, counter);
5024 if (isTransporter(psCurr)) // if transporter save any droids in the grp
5025 {
5026 for (DROID *psTrans = psCurr->psGroup->psList; psTrans != nullptr; psTrans = psTrans->psGrpNext)
5027 {
5028 if (psTrans != psCurr)
5029 {
5030 droidKey = "droid_" + (WzString::number(counter++).leftPadToMinimumLength(WzUniCodepoint::fromASCII('0'), 10)); // Zero padded so that alphabetical sort works.
5031 mRoot[droidKey.toStdString()] = writeDroid(psTrans, onMission, counter);
5032 }
5033 }
5034 //always save transporter droids that are in the mission list with an invalid value
5035 if (ppsCurrentDroidLists[player] == mission.apsDroidLists[player])
5036 {
5037 mRoot[droidKey.toStdString()]["position"] = Vector3i(-1, -1, -1); // was INVALID_XY
5038 }
5039 }
5040 }
5041 }
5042
5043 std::ostringstream stream;
5044 stream << mRoot.dump(4) << std::endl;
5045 std::string jsonString = stream.str();
5046 saveFile(pFileName, jsonString.c_str(), jsonString.size());
5047 debug(LOG_SAVE, "%s %s", "Saving", pFileName);
5048
5049 return true;
5050 }
5051
5052
5053 // -----------------------------------------------------------------------------------------
loadSaveStructure(char * pFileData,UDWORD filesize)5054 bool loadSaveStructure(char *pFileData, UDWORD filesize)
5055 {
5056 STRUCT_SAVEHEADER *psHeader;
5057 SAVE_STRUCTURE_V2 *psSaveStructure, sSaveStructure;
5058 STRUCTURE *psStructure;
5059 STRUCTURE_STATS *psStats = nullptr;
5060 UDWORD count, statInc;
5061 int32_t found;
5062 UDWORD NumberOfSkippedStructures = 0;
5063 UDWORD periodicalDamageTime;
5064
5065 /* Check the file type */
5066 psHeader = (STRUCT_SAVEHEADER *)pFileData;
5067 if (psHeader->aFileType[0] != 's' || psHeader->aFileType[1] != 't' ||
5068 psHeader->aFileType[2] != 'r' || psHeader->aFileType[3] != 'u')
5069 {
5070 debug(LOG_ERROR, "loadSaveStructure: Incorrect file type");
5071
5072 return false;
5073 }
5074
5075 /* STRUCT_SAVEHEADER */
5076 endian_udword(&psHeader->version);
5077 endian_udword(&psHeader->quantity);
5078
5079 //increment to the start of the data
5080 pFileData += STRUCT_HEADER_SIZE;
5081
5082 debug(LOG_SAVE, "file version is %u ", psHeader->version);
5083
5084 /* Check the file version */
5085 if (psHeader->version < VERSION_7 || psHeader->version > VERSION_8)
5086 {
5087 debug(LOG_ERROR, "StructLoad: unsupported save format version %d", psHeader->version);
5088
5089 return false;
5090 }
5091
5092 psSaveStructure = &sSaveStructure;
5093
5094 if ((sizeof(SAVE_STRUCTURE_V2) * psHeader->quantity + STRUCT_HEADER_SIZE) > filesize)
5095 {
5096 debug(LOG_ERROR, "structureLoad: unexpected end of file");
5097 return false;
5098 }
5099
5100 /* Load in the structure data */
5101 for (count = 0; count < psHeader->quantity; count ++, pFileData += sizeof(SAVE_STRUCTURE_V2))
5102 {
5103 memcpy(psSaveStructure, pFileData, sizeof(SAVE_STRUCTURE_V2));
5104
5105 /* STRUCTURE_SAVE_V2 includes OBJECT_SAVE_V19 */
5106 endian_sdword(&psSaveStructure->currentBuildPts);
5107 endian_udword(&psSaveStructure->body);
5108 endian_udword(&psSaveStructure->armour);
5109 endian_udword(&psSaveStructure->resistance);
5110 endian_udword(&psSaveStructure->dummy1);
5111 endian_udword(&psSaveStructure->subjectInc);
5112 endian_udword(&psSaveStructure->timeStarted);
5113 endian_udword(&psSaveStructure->output);
5114 endian_udword(&psSaveStructure->capacity);
5115 endian_udword(&psSaveStructure->quantity);
5116 /* OBJECT_SAVE_V19 */
5117 endian_udword(&psSaveStructure->id);
5118 endian_udword(&psSaveStructure->x);
5119 endian_udword(&psSaveStructure->y);
5120 endian_udword(&psSaveStructure->z);
5121 endian_udword(&psSaveStructure->direction);
5122 endian_udword(&psSaveStructure->player);
5123 endian_udword(&psSaveStructure->periodicalDamageStart);
5124 endian_udword(&psSaveStructure->periodicalDamage);
5125
5126 psSaveStructure->player = RemapPlayerNumber(psSaveStructure->player);
5127
5128 if (psSaveStructure->player >= MAX_PLAYERS)
5129 {
5130 psSaveStructure->player = MAX_PLAYERS - 1;
5131 NumberOfSkippedStructures++;
5132 }
5133 //get the stats for this structure
5134 found = false;
5135
5136 for (statInc = 0; statInc < numStructureStats; statInc++)
5137 {
5138 psStats = asStructureStats + statInc;
5139 //loop until find the same name
5140
5141 if (psStats->id.compare(psSaveStructure->name) == 0)
5142 {
5143 found = true;
5144 break;
5145 }
5146 }
5147 //if haven't found the structure - ignore this record!
5148 if (!found)
5149 {
5150 debug(LOG_ERROR, "This structure no longer exists - %s", getSaveStructNameV19((SAVE_STRUCTURE_V17 *)psSaveStructure));
5151 //ignore this
5152 continue;
5153 }
5154
5155 //for modules - need to check the base structure exists
5156 if (IsStatExpansionModule(psStats))
5157 {
5158 psStructure = getTileStructure(map_coord(psSaveStructure->x), map_coord(psSaveStructure->y));
5159 if (psStructure == nullptr)
5160 {
5161 debug(LOG_ERROR, "No owning structure for module - %s for player - %d", getSaveStructNameV19((SAVE_STRUCTURE_V17 *)psSaveStructure), psSaveStructure->player);
5162 //ignore this module
5163 continue;
5164 }
5165 }
5166
5167 //check not trying to build too near the edge
5168 if (map_coord(psSaveStructure->x) < TOO_NEAR_EDGE || map_coord(psSaveStructure->x) > mapWidth - TOO_NEAR_EDGE)
5169 {
5170 debug(LOG_ERROR, "Structure %s, x coord too near the edge of the map. id - %d", getSaveStructNameV19((SAVE_STRUCTURE_V17 *)psSaveStructure), psSaveStructure->id);
5171 //ignore this
5172 continue;
5173 }
5174 if (map_coord(psSaveStructure->y) < TOO_NEAR_EDGE || map_coord(psSaveStructure->y) > mapHeight - TOO_NEAR_EDGE)
5175 {
5176 debug(LOG_ERROR, "Structure %s, y coord too near the edge of the map. id - %d", getSaveStructNameV19((SAVE_STRUCTURE_V17 *)psSaveStructure), psSaveStructure->id);
5177 //ignore this
5178 continue;
5179 }
5180
5181 psStructure = buildStructureDir(psStats, psSaveStructure->x, psSaveStructure->y, DEG(psSaveStructure->direction), psSaveStructure->player, true);
5182 ASSERT(psStructure, "Unable to create structure");
5183 if (!psStructure)
5184 {
5185 continue;
5186 }
5187 // 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
5188 // the scripts -so in ALL CASES overwrite the ID!
5189 psStructure->id = psSaveStructure->id > 0 ? psSaveStructure->id : 0xFEDBCA98; // hack to remove struct id zero
5190 psStructure->periodicalDamage = psSaveStructure->periodicalDamage;
5191 periodicalDamageTime = psSaveStructure->periodicalDamageStart;
5192 psStructure->periodicalDamageStart = periodicalDamageTime;
5193 psStructure->status = (STRUCT_STATES)psSaveStructure->status;
5194 if (psStructure->status == SS_BUILT)
5195 {
5196 buildingComplete(psStructure);
5197 }
5198 if (psStructure->pStructureType->type == REF_HQ)
5199 {
5200 scriptSetStartPos(psSaveStructure->player, psStructure->pos.x, psStructure->pos.y);
5201 }
5202 else if (psStructure->pStructureType->type == REF_RESOURCE_EXTRACTOR)
5203 {
5204 scriptSetDerrickPos(psStructure->pos.x, psStructure->pos.y);
5205 }
5206 }
5207
5208 if (NumberOfSkippedStructures > 0)
5209 {
5210 debug(LOG_ERROR, "structureLoad: invalid player number in %d structures ... assigned to the last player!\n\n", NumberOfSkippedStructures);
5211 return false;
5212 }
5213
5214 return true;
5215 }
5216
5217 // -----------------------------------------------------------------------------------------
5218 //return id of a research topic based on the name
getResearchIdFromName(const WzString & name)5219 static UDWORD getResearchIdFromName(const WzString &name)
5220 {
5221 for (size_t inc = 0; inc < asResearch.size(); inc++)
5222 {
5223 if (asResearch[inc].id.compare(name) == 0)
5224 {
5225 return inc;
5226 }
5227 }
5228 debug(LOG_ERROR, "Unknown research - %s", name.toUtf8().c_str());
5229 return NULL_ID;
5230 }
5231
loadScriptStructure(ScriptMapData const & data)5232 bool loadScriptStructure(ScriptMapData const &data)
5233 {
5234 if (!data.valid)
5235 {
5236 return false;
5237 }
5238
5239 freeAllFlagPositions(); // clear any flags put in during level loads
5240
5241 for (auto &structure : data.structures) {
5242 auto psStats = std::find_if(asStructureStats, asStructureStats + numStructureStats, [&](STRUCTURE_STATS &stat) { return stat.id == structure.name; });
5243 if (psStats == asStructureStats + numStructureStats)
5244 {
5245 debug(LOG_ERROR, "Structure type \"%s\" unknown", structure.name.toStdString().c_str());
5246 continue; // ignore this
5247 }
5248 //for modules - need to check the base structure exists
5249 if (IsStatExpansionModule(psStats))
5250 {
5251 STRUCTURE *psStructure = getTileStructure(map_coord(structure.position.x), map_coord(structure.position.y));
5252 if (psStructure == nullptr)
5253 {
5254 debug(LOG_ERROR, "No owning structure for module - %s for player - %d", structure.name.toStdString().c_str(), structure.player);
5255 continue; // ignore this module
5256 }
5257 }
5258 //check not trying to build too near the edge
5259 if (map_coord(structure.position.x) < TOO_NEAR_EDGE || map_coord(structure.position.x) > mapWidth - TOO_NEAR_EDGE
5260 || map_coord(structure.position.y) < TOO_NEAR_EDGE || map_coord(structure.position.y) > mapHeight - TOO_NEAR_EDGE)
5261 {
5262 debug(LOG_ERROR, "Structure %s, coord too near the edge of the map", structure.name.toStdString().c_str());
5263 continue; // skip it
5264 }
5265 int player = remapIntPlayerNumber(structure.player);
5266 STRUCTURE *psStructure = buildStructureDir(psStats, structure.position.x, structure.position.y, structure.direction, player, true);
5267 if (psStructure == nullptr)
5268 {
5269 debug(LOG_ERROR, "Structure %s couldn't be built (probably on top of another structure).", structure.name.toStdString().c_str());
5270 continue;
5271 }
5272 if (structure.modules > 0)
5273 {
5274 auto moduleStat = getModuleStat(psStructure);
5275 if (moduleStat == nullptr)
5276 {
5277 debug(LOG_ERROR, "Structure %s can't have modules.", structure.name.toStdString().c_str());
5278 continue;
5279 }
5280 for (int i = 0; i < structure.modules; ++i)
5281 {
5282 buildStructure(moduleStat, structure.position.x, structure.position.y, player, true);
5283 }
5284 }
5285 buildingComplete(psStructure);
5286 if (psStructure->pStructureType->type == REF_HQ)
5287 {
5288 scriptSetStartPos(player, psStructure->pos.x, psStructure->pos.y);
5289 }
5290 else if (psStructure->pStructureType->type == REF_RESOURCE_EXTRACTOR)
5291 {
5292 scriptSetDerrickPos(psStructure->pos.x, psStructure->pos.y);
5293 }
5294 }
5295
5296 return true;
5297 }
5298
5299 // -----------------------------------------------------------------------------------------
5300 /* code for versions after version 20 of a save structure */
loadSaveStructure2(const char * pFileName,STRUCTURE ** ppList)5301 static bool loadSaveStructure2(const char *pFileName, STRUCTURE **ppList)
5302 {
5303 if (!PHYSFS_exists(pFileName))
5304 {
5305 debug(LOG_SAVE, "No %s found -- use fallback method", pFileName);
5306 return false; // try to use fallback method
5307 }
5308 WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadOnly);
5309
5310 freeAllFlagPositions(); //clear any flags put in during level loads
5311
5312 std::vector<WzString> list = ini.childGroups();
5313 for (size_t i = 0; i < list.size(); ++i)
5314 {
5315 FACTORY *psFactory;
5316 RESEARCH_FACILITY *psResearch;
5317 REPAIR_FACILITY *psRepair;
5318 REARM_PAD *psReArmPad;
5319 STRUCTURE_STATS *psModule;
5320 int capacity, researchId;
5321 STRUCTURE *psStructure;
5322
5323 ini.beginGroup(list[i]);
5324 int player = getPlayer(ini);
5325 int id = ini.value("id", -1).toInt();
5326 Position pos = ini.vector3i("position");
5327 Rotation rot = ini.vector3i("rotation");
5328 WzString name = ini.string("name");
5329
5330 //get the stats for this structure
5331 auto psStats = std::find_if(asStructureStats, asStructureStats + numStructureStats, [&](STRUCTURE_STATS &stat) { return stat.id == name; });
5332 //if haven't found the structure - ignore this record!
5333 ASSERT(psStats != asStructureStats + numStructureStats, "This structure no longer exists - %s", name.toUtf8().c_str());
5334 if (psStats == asStructureStats + numStructureStats)
5335 {
5336 ini.endGroup();
5337 continue; // ignore this
5338 }
5339 /*create the Structure */
5340 //for modules - need to check the base structure exists
5341 if (IsStatExpansionModule(psStats))
5342 {
5343 STRUCTURE *psTileStructure = getTileStructure(map_coord(pos.x), map_coord(pos.y));
5344 if (psTileStructure == nullptr)
5345 {
5346 debug(LOG_ERROR, "No owning structure for module - %s for player - %d", name.toUtf8().c_str(), player);
5347 ini.endGroup();
5348 continue; // ignore this module
5349 }
5350 }
5351 //check not trying to build too near the edge
5352 if (map_coord(pos.x) < TOO_NEAR_EDGE || map_coord(pos.x) > mapWidth - TOO_NEAR_EDGE
5353 || map_coord(pos.y) < TOO_NEAR_EDGE || map_coord(pos.y) > mapHeight - TOO_NEAR_EDGE)
5354 {
5355 debug(LOG_ERROR, "Structure %s (%s), coord too near the edge of the map", name.toUtf8().c_str(), list[i].toUtf8().c_str());
5356 ini.endGroup();
5357 continue; // skip it
5358 }
5359 psStructure = buildStructureDir(psStats, pos.x, pos.y, rot.direction, player, true);
5360 ASSERT(psStructure, "Unable to create structure");
5361 if (!psStructure)
5362 {
5363 ini.endGroup();
5364 continue;
5365 }
5366 if (id > 0)
5367 {
5368 psStructure->id = id; // force correct ID
5369 }
5370
5371 // common BASE_OBJECT info
5372 loadSaveObject(ini, psStructure);
5373
5374 if (psStructure->pStructureType->type == REF_HQ)
5375 {
5376 scriptSetStartPos(player, psStructure->pos.x, psStructure->pos.y);
5377 }
5378 psStructure->resistance = ini.value("resistance", psStructure->resistance).toInt();
5379 capacity = ini.value("modules", 0).toInt();
5380 psStructure->capacity = 0; // increased when modules are built
5381 switch (psStructure->pStructureType->type)
5382 {
5383 case REF_FACTORY:
5384 case REF_VTOL_FACTORY:
5385 case REF_CYBORG_FACTORY:
5386 //if factory save the current build info
5387 psFactory = ((FACTORY *)psStructure->pFunctionality);
5388 psFactory->productionLoops = ini.value("Factory/productionLoops", psFactory->productionLoops).toUInt();
5389 psFactory->timeStarted = ini.value("Factory/timeStarted", psFactory->timeStarted).toInt();
5390 psFactory->buildPointsRemaining = ini.value("Factory/buildPointsRemaining", psFactory->buildPointsRemaining).toInt();
5391 psFactory->timeStartHold = ini.value("Factory/timeStartHold", psFactory->timeStartHold).toInt();
5392 psFactory->loopsPerformed = ini.value("Factory/loopsPerformed", psFactory->loopsPerformed).toInt();
5393 // statusPending and pendingCount belong to the GUI, not the game state.
5394 psFactory->secondaryOrder = ini.value("Factory/secondaryOrder", psFactory->secondaryOrder).toInt();
5395 //adjust the module structures IMD
5396 if (capacity)
5397 {
5398 psModule = getModuleStat(psStructure);
5399 //build the appropriate number of modules
5400 for (int moduleIdx = 0; moduleIdx < capacity; moduleIdx++)
5401 {
5402 buildStructure(psModule, psStructure->pos.x, psStructure->pos.y, psStructure->player, true);
5403 }
5404 }
5405 if (ini.contains("Factory/template"))
5406 {
5407 int templId(ini.value("Factory/template").toInt());
5408 psFactory->psSubject = getTemplateFromMultiPlayerID(templId);
5409 }
5410 if (ini.contains("Factory/assemblyPoint/pos"))
5411 {
5412 Position point = ini.vector3i("Factory/assemblyPoint/pos");
5413 setAssemblyPoint(psFactory->psAssemblyPoint, point.x, point.y, player, true);
5414 psFactory->psAssemblyPoint->selected = ini.value("Factory/assemblyPoint/selected", false).toBool();
5415 }
5416 if (ini.contains("Factory/assemblyPoint/number"))
5417 {
5418 psFactory->psAssemblyPoint->factoryInc = ini.value("Factory/assemblyPoint/number", 42).toInt();
5419 }
5420 if (player == productionPlayer)
5421 {
5422 for (int runNum = 0; runNum < ini.value("Factory/productionRuns", 0).toInt(); runNum++)
5423 {
5424 ProductionRunEntry currentProd;
5425 currentProd.quantity = ini.value("Factory/Run/" + WzString::number(runNum) + "/quantity").toInt();
5426 currentProd.built = ini.value("Factory/Run/" + WzString::number(runNum) + "/built").toInt();
5427 if (ini.contains("Factory/Run/" + WzString::number(runNum) + "/template"))
5428 {
5429 int tid = ini.value("Factory/Run/" + WzString::number(runNum) + "/template").toInt();
5430 DROID_TEMPLATE *psTempl = getTemplateFromMultiPlayerID(tid);
5431 currentProd.psTemplate = psTempl;
5432 ASSERT(psTempl, "No template found for template ID %d for %s (%d)", tid, objInfo(psStructure), id);
5433 }
5434 if (psFactory->psAssemblyPoint->factoryInc >= asProductionRun[psFactory->psAssemblyPoint->factoryType].size())
5435 {
5436 asProductionRun[psFactory->psAssemblyPoint->factoryType].resize(psFactory->psAssemblyPoint->factoryInc + 1);
5437 }
5438 asProductionRun[psFactory->psAssemblyPoint->factoryType][psFactory->psAssemblyPoint->factoryInc].push_back(currentProd);
5439 }
5440 }
5441 break;
5442 case REF_RESEARCH:
5443 psResearch = ((RESEARCH_FACILITY *)psStructure->pFunctionality);
5444 //adjust the module structures IMD
5445 if (capacity)
5446 {
5447 psModule = getModuleStat(psStructure);
5448 buildStructure(psModule, psStructure->pos.x, psStructure->pos.y, psStructure->player, true);
5449 }
5450 //clear subject
5451 psResearch->psSubject = nullptr;
5452 psResearch->timeStartHold = 0;
5453 //set the subject
5454 if (ini.contains("Research/target"))
5455 {
5456 researchId = getResearchIdFromName(ini.value("Research/target").toWzString());
5457 if (researchId != NULL_ID)
5458 {
5459 psResearch->psSubject = &asResearch[researchId];
5460 psResearch->timeStartHold = ini.value("Research/timeStartHold").toInt();
5461 }
5462 else
5463 {
5464 debug(LOG_ERROR, "Failed to look up research target %s", ini.value("Research/target").toWzString().toUtf8().c_str());
5465 }
5466 }
5467 break;
5468 case REF_POWER_GEN:
5469 // adjust the module structures IMD
5470 if (capacity)
5471 {
5472 psModule = getModuleStat(psStructure);
5473 buildStructure(psModule, psStructure->pos.x, psStructure->pos.y, psStructure->player, true);
5474 }
5475 break;
5476 case REF_RESOURCE_EXTRACTOR:
5477 break;
5478 case REF_REPAIR_FACILITY:
5479 psRepair = ((REPAIR_FACILITY *)psStructure->pFunctionality);
5480 if (ini.contains("Repair/deliveryPoint/pos"))
5481 {
5482 Position point = ini.vector3i("Repair/deliveryPoint/pos");
5483 setAssemblyPoint(psRepair->psDeliveryPoint, point.x, point.y, player, true);
5484 psRepair->psDeliveryPoint->selected = ini.value("Repair/deliveryPoint/selected", false).toBool();
5485 }
5486 break;
5487 case REF_REARM_PAD:
5488 psReArmPad = ((REARM_PAD *)psStructure->pFunctionality);
5489 psReArmPad->timeStarted = ini.value("Rearm/timeStarted", psReArmPad->timeStarted).toInt();
5490 psReArmPad->timeLastUpdated = ini.value("Rearm/timeLastUpdated", psReArmPad->timeLastUpdated).toInt();
5491 break;
5492 case REF_WALL:
5493 case REF_GATE:
5494 psStructure->pFunctionality->wall.type = ini.value("Wall/type").toInt();
5495 psStructure->sDisplay.imd = psStructure->pStructureType->pIMD[std::min<unsigned>(psStructure->pFunctionality->wall.type, psStructure->pStructureType->pIMD.size() - 1)];
5496 break;
5497 default:
5498 break;
5499 }
5500 psStructure->body = healthValue(ini, structureBody(psStructure));
5501 psStructure->currentBuildPts = ini.value("currentBuildPts", structureBuildPointsToCompletion(*psStructure)).toInt();
5502 if (psStructure->status == SS_BUILT)
5503 {
5504 switch (psStructure->pStructureType->type)
5505 {
5506 case REF_POWER_GEN:
5507 checkForResExtractors(psStructure);
5508 if (selectedPlayer == psStructure->player)
5509 {
5510 audio_PlayObjStaticTrack(psStructure, ID_SOUND_POWER_HUM);
5511 }
5512 break;
5513 case REF_RESOURCE_EXTRACTOR:
5514 checkForPowerGen(psStructure);
5515 break;
5516 default:
5517 //do nothing for factories etc
5518 break;
5519 }
5520 }
5521 // weapons
5522 for (int j = 0; j < psStructure->pStructureType->numWeaps; j++)
5523 {
5524 if (psStructure->asWeaps[j].nStat > 0)
5525 {
5526 psStructure->asWeaps[j].ammo = ini.value("ammo/" + WzString::number(j)).toInt();
5527 psStructure->asWeaps[j].lastFired = ini.value("lastFired/" + WzString::number(j)).toInt();
5528 psStructure->asWeaps[j].shotsFired = ini.value("shotsFired/" + WzString::number(j)).toInt();
5529 psStructure->asWeaps[j].rot = ini.vector3i("rotation/" + WzString::number(j));
5530 }
5531 }
5532 psStructure->status = (STRUCT_STATES)ini.value("status", SS_BUILT).toInt();
5533 if (psStructure->status == SS_BUILT)
5534 {
5535 buildingComplete(psStructure);
5536 }
5537 ini.endGroup();
5538 }
5539 resetFactoryNumFlag(); //reset flags into the masks
5540
5541 return true;
5542 }
5543
5544 // -----------------------------------------------------------------------------------------
5545 /*
5546 Writes some version info
5547 */
writeGameInfo(const char * pFileName)5548 bool writeGameInfo(const char *pFileName)
5549 {
5550 WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
5551 char ourtime[100] = {'\0'};
5552 const time_t currentTime = time(nullptr);
5553 std::string time(ctime(¤tTime));
5554
5555 ini.beginGroup("GameProperties");
5556 ini.setValue("current_time", time.data());
5557 getAsciiTime(ourtime, graphicsTime);
5558 ini.setValue("graphics_time", ourtime);
5559 getAsciiTime(ourtime, gameTime);
5560 ini.setValue("game_time", ourtime);
5561 getAsciiTime(ourtime, gameTime - missionData.missionStarted);
5562 ini.setValue("playing_time", ourtime);
5563 ini.setValue("version", version_getVersionString());
5564 ini.setValue("full_version", version_getFormattedVersionString());
5565 ini.setValue("cheated", Cheated);
5566 ini.setValue("debug", getDebugMappingStatus());
5567 ini.setValue("level/map", getLevelName());
5568 ini.setValue("mods", !getModList().empty() ? getModList().c_str() : "None");
5569 auto backendInfo = gfx_api::context::get().getBackendGameInfo();
5570 for (auto& kv : backendInfo)
5571 {
5572 ini.setValue(WzString::fromUtf8(kv.first), WzString::fromUtf8(kv.second));
5573 }
5574 ini.endGroup();
5575 return true;
5576 }
5577
5578 /*
5579 Writes the linked list of structure for each player to a file
5580 */
writeStructFile(const char * pFileName)5581 bool writeStructFile(const char *pFileName)
5582 {
5583 WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
5584 int counter = 0;
5585
5586 for (int player = 0; player < MAX_PLAYERS; player++)
5587 {
5588 for (STRUCTURE *psCurr = apsStructLists[player]; psCurr != nullptr; psCurr = psCurr->psNext)
5589 {
5590 ini.beginGroup("structure_" + (WzString::number(counter++).leftPadToMinimumLength(WzUniCodepoint::fromASCII('0'), 10))); // Zero padded so that alphabetical sort works.
5591 ini.setValue("name", psCurr->pStructureType->id);
5592
5593 writeSaveObject(ini, psCurr);
5594
5595 if (psCurr->resistance > 0)
5596 {
5597 ini.setValue("resistance", psCurr->resistance);
5598 }
5599 if (psCurr->status != SS_BUILT)
5600 {
5601 ini.setValue("status", psCurr->status);
5602 }
5603 ini.setValue("weapons", psCurr->numWeaps);
5604 for (unsigned j = 0; j < psCurr->numWeaps; j++)
5605 {
5606 ini.setValue("parts/weapon/" + WzString::number(j + 1), (asWeaponStats + psCurr->asWeaps[j].nStat)->id);
5607 if (psCurr->asWeaps[j].nStat > 0)
5608 {
5609 ini.setValue("ammo/" + WzString::number(j), psCurr->asWeaps[j].ammo);
5610 ini.setValue("lastFired/" + WzString::number(j), psCurr->asWeaps[j].lastFired);
5611 ini.setValue("shotsFired/" + WzString::number(j), psCurr->asWeaps[j].shotsFired);
5612 ini.setVector3i("rotation/" + WzString::number(j), toVector(psCurr->asWeaps[j].rot));
5613 }
5614 }
5615 for (unsigned i = 0; i < psCurr->numWeaps; i++)
5616 {
5617 if (psCurr->psTarget[i] && !psCurr->psTarget[i]->died)
5618 {
5619 ini.setValue("target/" + WzString::number(i) + "/id", psCurr->psTarget[i]->id);
5620 ini.setValue("target/" + WzString::number(i) + "/player", psCurr->psTarget[i]->player);
5621 ini.setValue("target/" + WzString::number(i) + "/type", psCurr->psTarget[i]->type);
5622 #ifdef DEBUG
5623 ini.setValue("target/" + WzString::number(i) + "/debugfunc", WzString::fromUtf8(psCurr->targetFunc[i]));
5624 ini.setValue("target/" + WzString::number(i) + "/debugline", psCurr->targetLine[i]);
5625 #endif
5626 }
5627 }
5628 ini.setValue("currentBuildPts", psCurr->currentBuildPts);
5629 if (psCurr->pFunctionality)
5630 {
5631 if (psCurr->pStructureType->type == REF_FACTORY || psCurr->pStructureType->type == REF_CYBORG_FACTORY
5632 || psCurr->pStructureType->type == REF_VTOL_FACTORY)
5633 {
5634 FACTORY *psFactory = (FACTORY *)psCurr->pFunctionality;
5635 ini.setValue("modules", psCurr->capacity);
5636 ini.setValue("Factory/productionLoops", psFactory->productionLoops);
5637 ini.setValue("Factory/timeStarted", psFactory->timeStarted);
5638 ini.setValue("Factory/buildPointsRemaining", psFactory->buildPointsRemaining);
5639 ini.setValue("Factory/timeStartHold", psFactory->timeStartHold);
5640 ini.setValue("Factory/loopsPerformed", psFactory->loopsPerformed);
5641 // statusPending and pendingCount belong to the GUI, not the game state.
5642 ini.setValue("Factory/secondaryOrder", psFactory->secondaryOrder);
5643
5644 if (psFactory->psSubject != nullptr)
5645 {
5646 ini.setValue("Factory/template", psFactory->psSubject->multiPlayerID);
5647 }
5648 FLAG_POSITION *psFlag = ((FACTORY *)psCurr->pFunctionality)->psAssemblyPoint;
5649 if (psFlag != nullptr)
5650 {
5651 ini.setVector3i("Factory/assemblyPoint/pos", psFlag->coords);
5652 if (psFlag->selected)
5653 {
5654 ini.setValue("Factory/assemblyPoint/selected", psFlag->selected);
5655 }
5656 ini.setValue("Factory/assemblyPoint/number", psFlag->factoryInc);
5657 }
5658 if (psFactory->psCommander)
5659 {
5660 ini.setValue("Factory/commander/id", psFactory->psCommander->id);
5661 ini.setValue("Factory/commander/player", psFactory->psCommander->player);
5662 }
5663 ini.setValue("Factory/secondaryOrder", psFactory->secondaryOrder);
5664 if (player == productionPlayer)
5665 {
5666 ProductionRun emptyRun;
5667 bool haveRun = psFactory->psAssemblyPoint->factoryInc < asProductionRun[psFactory->psAssemblyPoint->factoryType].size();
5668 ProductionRun const &productionRun = haveRun ? asProductionRun[psFactory->psAssemblyPoint->factoryType][psFactory->psAssemblyPoint->factoryInc] : emptyRun;
5669 ini.setValue("Factory/productionRuns", (int)productionRun.size());
5670 for (size_t runNum = 0; runNum < productionRun.size(); runNum++)
5671 {
5672 ProductionRunEntry psCurrentProd = productionRun.at(runNum);
5673 ini.setValue("Factory/Run/" + WzString::number(runNum) + "/quantity", psCurrentProd.quantity);
5674 ini.setValue("Factory/Run/" + WzString::number(runNum) + "/built", psCurrentProd.built);
5675 if (psCurrentProd.psTemplate) ini.setValue("Factory/Run/" + WzString::number(runNum) + "/template",
5676 psCurrentProd.psTemplate->multiPlayerID);
5677 }
5678 }
5679 else
5680 {
5681 ini.setValue("Factory/productionRuns", 0);
5682 }
5683 }
5684 else if (psCurr->pStructureType->type == REF_RESEARCH)
5685 {
5686 ini.setValue("modules", psCurr->capacity);
5687 ini.setValue("Research/timeStartHold", ((RESEARCH_FACILITY *)psCurr->pFunctionality)->timeStartHold);
5688 if (((RESEARCH_FACILITY *)psCurr->pFunctionality)->psSubject)
5689 {
5690 ini.setValue("Research/target", ((RESEARCH_FACILITY *)psCurr->pFunctionality)->psSubject->id);
5691 }
5692 }
5693 else if (psCurr->pStructureType->type == REF_POWER_GEN)
5694 {
5695 ini.setValue("modules", psCurr->capacity);
5696 }
5697 else if (psCurr->pStructureType->type == REF_REPAIR_FACILITY)
5698 {
5699 REPAIR_FACILITY *psRepair = ((REPAIR_FACILITY *)psCurr->pFunctionality);
5700 if (psRepair->psObj)
5701 {
5702 ini.setValue("Repair/target/id", psRepair->psObj->id);
5703 ini.setValue("Repair/target/player", psRepair->psObj->player);
5704 ini.setValue("Repair/target/type", psRepair->psObj->type);
5705 }
5706 FLAG_POSITION *psFlag = psRepair->psDeliveryPoint;
5707 if (psFlag)
5708 {
5709 ini.setVector3i("Repair/deliveryPoint/pos", psFlag->coords);
5710 if (psFlag->selected)
5711 {
5712 ini.setValue("Repair/deliveryPoint/selected", psFlag->selected);
5713 }
5714 }
5715 }
5716 else if (psCurr->pStructureType->type == REF_REARM_PAD)
5717 {
5718 REARM_PAD *psReArmPad = ((REARM_PAD *)psCurr->pFunctionality);
5719 ini.setValue("Rearm/timeStarted", psReArmPad->timeStarted);
5720 ini.setValue("Rearm/timeLastUpdated", psReArmPad->timeLastUpdated);
5721 if (psReArmPad->psObj)
5722 {
5723 ini.setValue("Rearm/target/id", psReArmPad->psObj->id);
5724 ini.setValue("Rearm/target/player", psReArmPad->psObj->player);
5725 ini.setValue("Rearm/target/type", psReArmPad->psObj->type);
5726 }
5727 }
5728 else if (psCurr->pStructureType->type == REF_WALL || psCurr->pStructureType->type == REF_GATE)
5729 {
5730 ini.setValue("Wall/type", psCurr->pFunctionality->wall.type);
5731 }
5732 }
5733 ini.endGroup();
5734 }
5735 }
5736 return true;
5737 }
5738
5739 // -----------------------------------------------------------------------------------------
loadSaveStructurePointers(const WzString & filename,STRUCTURE ** ppList)5740 bool loadSaveStructurePointers(const WzString& filename, STRUCTURE **ppList)
5741 {
5742 WzConfig ini(filename, WzConfig::ReadOnly);
5743 std::vector<WzString> list = ini.childGroups();
5744
5745 for (size_t i = 0; i < list.size(); ++i)
5746 {
5747 ini.beginGroup(list[i]);
5748 STRUCTURE *psStruct;
5749 int player = getPlayer(ini);
5750 int id = ini.value("id", -1).toInt();
5751 for (psStruct = ppList[player]; psStruct && psStruct->id != id; psStruct = psStruct->psNext) { }
5752 if (!psStruct)
5753 {
5754 ini.endGroup();
5755 continue; // it is not unusual for a structure to 'disappear' like this; it can happen eg because of module upgrades
5756 }
5757 for (int j = 0; j < MAX_WEAPONS; j++)
5758 {
5759 objTrace(psStruct->id, "weapon %d, nStat %d", j, psStruct->asWeaps[j].nStat);
5760 if (ini.contains("target/" + WzString::number(j) + "/id"))
5761 {
5762 int tid = ini.value("target/" + WzString::number(j) + "/id", -1).toInt();
5763 int tplayer = ini.value("target/" + WzString::number(j) + "/player", -1).toInt();
5764 OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value("target/" + WzString::number(j) + "/type", 0).toInt();
5765 ASSERT(tid >= 0 && tplayer >= 0, "Bad ID");
5766 setStructureTarget(psStruct, getBaseObjFromData(tid, tplayer, ttype), j, ORIGIN_UNKNOWN);
5767 ASSERT(psStruct->psTarget[j], "Failed to find target");
5768 }
5769 if (ini.contains("Factory/commander/id"))
5770 {
5771 ASSERT(psStruct->pStructureType->type == REF_FACTORY || psStruct->pStructureType->type == REF_CYBORG_FACTORY
5772 || psStruct->pStructureType->type == REF_VTOL_FACTORY, "Bad type");
5773 FACTORY *psFactory = (FACTORY *)psStruct->pFunctionality;
5774 OBJECT_TYPE ttype = OBJ_DROID;
5775 int tid = ini.value("Factory/commander/id", -1).toInt();
5776 int tplayer = ini.value("Factory/commander/player", -1).toInt();
5777 ASSERT(tid >= 0 && tplayer >= 0, "Bad commander ID %d for player %d for building %d", tid, tplayer, id);
5778 DROID *psCommander = (DROID *)getBaseObjFromData(tid, tplayer, ttype);
5779 ASSERT(psCommander, "Commander %d not found for building %d", tid, id);
5780 if (ppList == mission.apsStructLists)
5781 {
5782 psFactory->psCommander = psCommander;
5783 }
5784 else
5785 {
5786 assignFactoryCommandDroid(psStruct, psCommander);
5787 }
5788 }
5789 if (ini.contains("Repair/target/id"))
5790 {
5791 ASSERT(psStruct->pStructureType->type == REF_REPAIR_FACILITY, "Bad type");
5792 REPAIR_FACILITY *psRepair = ((REPAIR_FACILITY *)psStruct->pFunctionality);
5793 OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value("Repair/target/type", OBJ_DROID).toInt();
5794 int tid = ini.value("Repair/target/id", -1).toInt();
5795 int tplayer = ini.value("Repair/target/player", -1).toInt();
5796 ASSERT(tid >= 0 && tplayer >= 0, "Bad repair ID %d for player %d for building %d", tid, tplayer, id);
5797 psRepair->psObj = getBaseObjFromData(tid, tplayer, ttype);
5798 ASSERT(psRepair->psObj, "Repair target %d not found for building %d", tid, id);
5799 }
5800 if (ini.contains("Rearm/target/id"))
5801 {
5802 ASSERT(psStruct->pStructureType->type == REF_REARM_PAD, "Bad type");
5803 REARM_PAD *psReArmPad = ((REARM_PAD *)psStruct->pFunctionality);
5804 OBJECT_TYPE ttype = OBJ_DROID; // always, for now
5805 int tid = ini.value("Rearm/target/id", -1).toInt();
5806 int tplayer = ini.value("Rearm/target/player", -1).toInt();
5807 ASSERT(tid >= 0 && tplayer >= 0, "Bad rearm ID %d for player %d for building %d", tid, tplayer, id);
5808 psReArmPad->psObj = getBaseObjFromData(tid, tplayer, ttype);
5809 ASSERT(psReArmPad->psObj, "Rearm target %d not found for building %d", tid, id);
5810 }
5811 }
5812 ini.endGroup();
5813 }
5814 return true;
5815 }
5816
5817 // -----------------------------------------------------------------------------------------
loadSaveFeature(char * pFileData,UDWORD filesize)5818 bool loadSaveFeature(char *pFileData, UDWORD filesize)
5819 {
5820 FEATURE_SAVEHEADER *psHeader;
5821 SAVE_FEATURE_V14 *psSaveFeature;
5822 FEATURE *pFeature;
5823 UDWORD count, i, statInc;
5824 FEATURE_STATS *psStats = nullptr;
5825 bool found;
5826 UDWORD sizeOfSaveFeature;
5827
5828 /* Check the file type */
5829 psHeader = (FEATURE_SAVEHEADER *)pFileData;
5830 if (psHeader->aFileType[0] != 'f' || psHeader->aFileType[1] != 'e' ||
5831 psHeader->aFileType[2] != 'a' || psHeader->aFileType[3] != 't')
5832 {
5833 debug(LOG_ERROR, "loadSaveFeature: Incorrect file type");
5834 return false;
5835 }
5836
5837 /* FEATURE_SAVEHEADER */
5838 endian_udword(&psHeader->version);
5839 endian_udword(&psHeader->quantity);
5840
5841 debug(LOG_SAVE, "Feature file version is %u ", psHeader->version);
5842
5843 //increment to the start of the data
5844 pFileData += FEATURE_HEADER_SIZE;
5845
5846 /* Check the file version */
5847 if (psHeader->version < VERSION_7 || psHeader->version > VERSION_19)
5848 {
5849 debug(LOG_ERROR, "Unsupported save format version %u", psHeader->version);
5850 return false;
5851 }
5852 if (psHeader->version < VERSION_14)
5853 {
5854 sizeOfSaveFeature = sizeof(SAVE_FEATURE_V2);
5855 }
5856 else
5857 {
5858 sizeOfSaveFeature = sizeof(SAVE_FEATURE_V14);
5859 }
5860 if ((sizeOfSaveFeature * psHeader->quantity + FEATURE_HEADER_SIZE) > filesize)
5861 {
5862 debug(LOG_ERROR, "featureLoad: unexpected end of file");
5863 return false;
5864 }
5865
5866 /* Load in the feature data */
5867 for (count = 0; count < psHeader->quantity; count ++, pFileData += sizeOfSaveFeature)
5868 {
5869 psSaveFeature = (SAVE_FEATURE_V14 *) pFileData;
5870
5871 /* FEATURE_SAVE_V14 is FEATURE_SAVE_V2 */
5872 /* FEATURE_SAVE_V2 is OBJECT_SAVE_V19 */
5873 /* OBJECT_SAVE_V19 */
5874 endian_udword(&psSaveFeature->id);
5875 endian_udword(&psSaveFeature->x);
5876 endian_udword(&psSaveFeature->y);
5877 endian_udword(&psSaveFeature->z);
5878 endian_udword(&psSaveFeature->direction);
5879 endian_udword(&psSaveFeature->player);
5880 endian_udword(&psSaveFeature->periodicalDamageStart);
5881 endian_udword(&psSaveFeature->periodicalDamage);
5882
5883 //get the stats for this feature
5884 found = false;
5885
5886 for (statInc = 0; statInc < numFeatureStats; statInc++)
5887 {
5888 psStats = asFeatureStats + statInc;
5889 //loop until find the same name
5890 if (psStats->id.compare(psSaveFeature->name) == 0)
5891 {
5892 found = true;
5893 break;
5894 }
5895 }
5896 //if haven't found the feature - ignore this record!
5897 if (!found)
5898 {
5899 debug(LOG_ERROR, "This feature no longer exists - %s", psSaveFeature->name);
5900 //ignore this
5901 continue;
5902 }
5903 //create the Feature
5904 pFeature = buildFeature(psStats, psSaveFeature->x, psSaveFeature->y, true);
5905 if (!pFeature)
5906 {
5907 debug(LOG_ERROR, "Unable to create feature %s", psSaveFeature->name);
5908 continue;
5909 }
5910 if (pFeature->psStats->subType == FEAT_OIL_RESOURCE)
5911 {
5912 scriptSetDerrickPos(pFeature->pos.x, pFeature->pos.y);
5913 }
5914 //restore values
5915 pFeature->id = psSaveFeature->id;
5916 pFeature->rot.direction = DEG(psSaveFeature->direction);
5917 pFeature->periodicalDamage = psSaveFeature->periodicalDamage;
5918 if (psHeader->version >= VERSION_14)
5919 {
5920 for (i = 0; i < MAX_PLAYERS; i++)
5921 {
5922 pFeature->visible[i] = psSaveFeature->visible[i];
5923 }
5924 }
5925 }
5926
5927 return true;
5928 }
5929
loadScriptFeature(ScriptMapData const & data)5930 static bool loadScriptFeature(ScriptMapData const &data)
5931 {
5932 if (!data.valid)
5933 {
5934 return false;
5935 }
5936
5937 for (auto &feature : data.features)
5938 {
5939 auto psStats = std::find_if(asFeatureStats, asFeatureStats + numFeatureStats, [&](FEATURE_STATS &stat) { return stat.id == feature.name; });
5940 if (psStats == asFeatureStats + numFeatureStats)
5941 {
5942 debug(LOG_ERROR, "Feature type \"%s\" unknown", feature.name.toStdString().c_str());
5943 continue; // ignore this
5944 }
5945 // Create the Feature
5946 auto pFeature = buildFeature(psStats, feature.position.x, feature.position.y, true);
5947 if (!pFeature)
5948 {
5949 debug(LOG_ERROR, "Unable to create feature %s", feature.name.toStdString().c_str());
5950 continue;
5951 }
5952 if (pFeature->psStats->subType == FEAT_OIL_RESOURCE)
5953 {
5954 scriptSetDerrickPos(pFeature->pos.x, pFeature->pos.y);
5955 }
5956 //restore values
5957 pFeature->id = generateSynchronisedObjectId();
5958 pFeature->rot.direction = feature.direction;
5959 }
5960
5961 return true;
5962 }
5963
loadSaveFeature2(const char * pFileName)5964 bool loadSaveFeature2(const char *pFileName)
5965 {
5966 if (!PHYSFS_exists(pFileName))
5967 {
5968 debug(LOG_SAVE, "No %s found -- use fallback method", pFileName);
5969 return false;
5970 }
5971 WzConfig ini(pFileName, WzConfig::ReadOnly);
5972 std::vector<WzString> list = ini.childGroups();
5973 debug(LOG_SAVE, "Loading new style features (%zu found)", list.size());
5974
5975 for (size_t i = 0; i < list.size(); ++i)
5976 {
5977 FEATURE *pFeature;
5978 ini.beginGroup(list[i]);
5979 WzString name = ini.string("name");
5980 Position pos = ini.vector3i("position");
5981 int statInc;
5982 bool found = false;
5983 FEATURE_STATS *psStats = nullptr;
5984
5985 //get the stats for this feature
5986 for (statInc = 0; statInc < numFeatureStats; statInc++)
5987 {
5988 psStats = asFeatureStats + statInc;
5989 //loop until find the same name
5990 if (psStats->id.compare(name) == 0)
5991 {
5992 found = true;
5993 break;
5994 }
5995 }
5996 //if haven't found the feature - ignore this record!
5997 if (!found)
5998 {
5999 debug(LOG_ERROR, "This feature no longer exists - %s", name.toUtf8().c_str());
6000 //ignore this
6001 continue;
6002 }
6003 //create the Feature
6004 pFeature = buildFeature(psStats, pos.x, pos.y, true);
6005 if (!pFeature)
6006 {
6007 debug(LOG_ERROR, "Unable to create feature %s", name.toUtf8().c_str());
6008 continue;
6009 }
6010 if (pFeature->psStats->subType == FEAT_OIL_RESOURCE)
6011 {
6012 scriptSetDerrickPos(pFeature->pos.x, pFeature->pos.y);
6013 }
6014 //restore values
6015 int id = ini.value("id", -1).toInt();
6016 if (id > 0)
6017 {
6018 pFeature->id = id;
6019 }
6020 else
6021 {
6022 pFeature->id = generateSynchronisedObjectId();
6023 }
6024 pFeature->rot = ini.vector3i("rotation");
6025 pFeature->player = ini.value("player", PLAYER_FEATURE).toInt();
6026
6027 // common BASE_OBJECT info
6028 loadSaveObject(ini, pFeature);
6029
6030 pFeature->body = healthValue(ini, pFeature->psStats->body);
6031
6032 ini.endGroup();
6033 }
6034 return true;
6035 }
6036
6037 // -----------------------------------------------------------------------------------------
6038 /*
6039 Writes the linked list of features to a file
6040 */
writeFeatureFile(const char * pFileName)6041 bool writeFeatureFile(const char *pFileName)
6042 {
6043 WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
6044 int counter = 0;
6045
6046 for (FEATURE *psCurr = apsFeatureLists[0]; psCurr != nullptr; psCurr = psCurr->psNext)
6047 {
6048 ini.beginGroup("feature_" + (WzString::number(counter++).leftPadToMinimumLength(WzUniCodepoint::fromASCII('0'), 10))); // Zero padded so that alphabetical sort works.
6049 ini.setValue("name", psCurr->psStats->id);
6050 writeSaveObject(ini, psCurr);
6051 ini.endGroup();
6052 }
6053 return true;
6054 }
6055
6056 // -----------------------------------------------------------------------------------------
loadSaveTemplate(const char * pFileName)6057 bool loadSaveTemplate(const char *pFileName)
6058 {
6059 WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadOnly);
6060 std::vector<WzString> list = ini.childGroups();
6061
6062 auto loadTemplate = [&]() {
6063 DROID_TEMPLATE t;
6064 if (!loadTemplateCommon(ini, t))
6065 {
6066 debug(LOG_ERROR, "Stored template \"%s\" contains an unknown component.", ini.string("name").toUtf8().c_str());
6067 }
6068 t.name = ini.string("name");
6069 t.multiPlayerID = ini.value("multiPlayerID", generateNewObjectId()).toInt();
6070 t.enabled = ini.value("enabled", false).toBool();
6071 t.stored = ini.value("stored", false).toBool();
6072 t.prefab = ini.value("prefab", false).toBool();
6073 ini.nextArrayItem();
6074 return t;
6075 };
6076
6077 int version = ini.value("version", 0).toInt();
6078 if (version == 0)
6079 {
6080 return false;
6081 }
6082 for (size_t i = 0; i < list.size(); ++i)
6083 {
6084 ini.beginGroup(list[i]);
6085 int player = getPlayer(ini);
6086 ini.beginArray("templates");
6087 while (ini.remainingArrayItems() > 0)
6088 {
6089 addTemplate(player, std::unique_ptr<DROID_TEMPLATE>(new DROID_TEMPLATE(loadTemplate())));
6090 }
6091 ini.endArray();
6092 ini.endGroup();
6093 }
6094
6095 if (ini.contains("localTemplates"))
6096 {
6097 ini.beginArray("localTemplates");
6098 while (ini.remainingArrayItems() > 0)
6099 {
6100 localTemplates.emplace_back(loadTemplate());
6101 }
6102 ini.endArray();
6103 }
6104 else
6105 {
6106 // Old savegame compatibility, should remove this branch sometime.
6107 enumerateTemplates(selectedPlayer, [](DROID_TEMPLATE * psTempl) {
6108 localTemplates.push_back(*psTempl);
6109 return true;
6110 });
6111 }
6112
6113 return true;
6114 }
6115
writeTemplateFile(const char * pFileName)6116 bool writeTemplateFile(const char *pFileName)
6117 {
6118 WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6119
6120 auto writeTemplate = [&](DROID_TEMPLATE *psCurr) {
6121 saveTemplateCommon(ini, psCurr);
6122 ini.setValue("ref", psCurr->ref);
6123 ini.setValue("multiPlayerID", psCurr->multiPlayerID);
6124 ini.setValue("enabled", psCurr->enabled);
6125 ini.setValue("stored", psCurr->stored);
6126 ini.setValue("prefab", psCurr->prefab);
6127 ini.nextArrayItem();
6128 };
6129
6130 ini.setValue("version", 1);
6131 for (int player = 0; player < MAX_PLAYERS; player++)
6132 {
6133 if (!apsDroidLists[player] && !apsStructLists[player]) // only write out templates of players that are still 'alive'
6134 {
6135 continue;
6136 }
6137 ini.beginGroup("player_" + WzString::number(player));
6138 setPlayer(ini, player);
6139 ini.beginArray("templates");
6140 enumerateTemplates(player, [&writeTemplate](DROID_TEMPLATE* psTemplate) {
6141 writeTemplate(psTemplate);
6142 return true;
6143 });
6144 ini.endArray();
6145 ini.endGroup();
6146 }
6147 ini.beginArray("localTemplates");
6148 for (auto &psCurr : localTemplates)
6149 {
6150 writeTemplate(&psCurr);
6151 }
6152 ini.endArray();
6153 return true;
6154 }
6155
6156 // -----------------------------------------------------------------------------------------
6157 // load up a terrain tile type map file
6158 // note: This function modifies pFileData directly while loading! (FIXME?)
loadTerrainTypeMap(char * pFileData,UDWORD filesize)6159 bool loadTerrainTypeMap(char *pFileData, UDWORD filesize)
6160 {
6161 TILETYPE_SAVEHEADER *psHeader;
6162 UDWORD i;
6163 UWORD *pType;
6164
6165 if (filesize < TILETYPE_HEADER_SIZE)
6166 {
6167 debug(LOG_ERROR, "loadTerrainTypeMap: file too small");
6168 return false;
6169 }
6170
6171 // Check the header
6172 psHeader = (TILETYPE_SAVEHEADER *)pFileData;
6173 if (psHeader->aFileType[0] != 't' || psHeader->aFileType[1] != 't' ||
6174 psHeader->aFileType[2] != 'y' || psHeader->aFileType[3] != 'p')
6175 {
6176 debug(LOG_ERROR, "loadTerrainTypeMap: Incorrect file type");
6177
6178 return false;
6179 }
6180
6181 /* TILETYPE_SAVEHEADER */
6182 endian_udword(&psHeader->version);
6183 endian_udword(&psHeader->quantity);
6184
6185 // reset the terrain table
6186 memset(terrainTypes, 0, sizeof(terrainTypes));
6187
6188 // Load the terrain type mapping
6189 pType = (UWORD *)(pFileData + TILETYPE_HEADER_SIZE);
6190 endian_uword(pType);
6191 if (psHeader->quantity >= MAX_TILE_TEXTURES)
6192 {
6193 // Workaround for fugly map editor bug, since we can't fix the map editor
6194 psHeader->quantity = MAX_TILE_TEXTURES - 1;
6195 }
6196 for (i = 0; i < psHeader->quantity; i++)
6197 {
6198 if (*pType > TER_MAX)
6199 {
6200 debug(LOG_ERROR, "loadTerrainTypeMap: terrain type out of range");
6201
6202 return false;
6203 }
6204
6205 terrainTypes[i] = (UBYTE) * pType;
6206 pType++;
6207 endian_uword(pType);
6208 }
6209
6210 return true;
6211 }
6212
loadTerrainTypeMapOverride(unsigned int tileSet)6213 bool loadTerrainTypeMapOverride(unsigned int tileSet)
6214 {
6215 resForceBaseDir("/data/base/");
6216 WzString iniName = "tileset/tileTypes.json";
6217 if (!PHYSFS_exists(iniName.toUtf8().c_str()))
6218 {
6219 return false;
6220 }
6221
6222 WzConfig ini(iniName, WzConfig::ReadOnly);
6223 WzString tileTypeKey;
6224
6225 if (tileSet == ARIZONA)
6226 {
6227 tileTypeKey = "Arizona";
6228 }
6229 else if (tileSet == URBAN)
6230 {
6231 tileTypeKey = "Urban";
6232 }
6233 else if (tileSet == ROCKIE)
6234 {
6235 tileTypeKey = "Rockies";
6236 }
6237 else
6238 {
6239 debug(LOG_ERROR, "Unknown tile type");
6240 resForceBaseDir("");
6241 return false;
6242 }
6243
6244 std::vector<WzString> list = ini.childGroups();
6245 for (size_t i = 0; i < list.size(); ++i)
6246 {
6247 if (list[i].compare(tileTypeKey) == 0)
6248 {
6249 ini.beginGroup(list[i]);
6250
6251 debug(LOG_TERRAIN, "Looking at tileset type: %s", tileTypeKey.toUtf8().c_str());
6252 unsigned int counter = 0;
6253
6254 std::vector<WzString> keys = ini.childKeys();
6255 for (size_t j = 0; j < keys.size(); ++j)
6256 {
6257 unsigned int tileType = ini.value(keys.at(j)).toUInt();
6258
6259 if (tileType > TER_MAX)
6260 {
6261 debug(LOG_ERROR, "loadTerrainTypeMapOverride: terrain type out of range");
6262 resForceBaseDir("");
6263 return false;
6264 }
6265 // Workaround for fugly map editor bug, since we can't fix the map editor
6266 if (counter > (MAX_TILE_TEXTURES - 1))
6267 {
6268 debug(LOG_ERROR, "loadTerrainTypeMapOverride: too many textures!");
6269 resForceBaseDir("");
6270 return false;
6271 }
6272 // Log the output for the override value.
6273 if (terrainTypes[counter] != tileType)
6274 {
6275 debug(LOG_TERRAIN, "Upgrading map tile %d (type %d) to type %d", counter, terrainTypes[counter], tileType);
6276 }
6277 terrainTypes[counter] = tileType;
6278 ++counter;
6279 debug(LOG_TERRAIN, "Tile %d at value: %d", counter - 1, tileType);
6280 }
6281 ini.endGroup();
6282 }
6283 }
6284
6285 resForceBaseDir("");
6286
6287 return true;
6288 }
6289
6290 // -----------------------------------------------------------------------------------------
6291 // Write out the terrain type map
writeTerrainTypeMapFile(char * pFileName)6292 static bool writeTerrainTypeMapFile(char *pFileName)
6293 {
6294 TILETYPE_SAVEHEADER *psHeader;
6295 char *pFileData;
6296 UDWORD fileSize, i;
6297 UWORD *pType;
6298
6299 // Calculate the file size
6300 fileSize = TILETYPE_HEADER_SIZE + sizeof(UWORD) * MAX_TILE_TEXTURES;
6301 pFileData = (char *)malloc(fileSize);
6302 if (!pFileData)
6303 {
6304 debug(LOG_FATAL, "writeTerrainTypeMapFile: Out of memory");
6305 abort();
6306 return false;
6307 }
6308
6309 // Put the file header on the file
6310 psHeader = (TILETYPE_SAVEHEADER *)pFileData;
6311 psHeader->aFileType[0] = 't';
6312 psHeader->aFileType[1] = 't';
6313 psHeader->aFileType[2] = 'y';
6314 psHeader->aFileType[3] = 'p';
6315 psHeader->version = CURRENT_VERSION_NUM;
6316 psHeader->quantity = MAX_TILE_TEXTURES;
6317
6318 pType = (UWORD *)(pFileData + TILETYPE_HEADER_SIZE);
6319 for (i = 0; i < MAX_TILE_TEXTURES; i++)
6320 {
6321 *pType = terrainTypes[i];
6322 endian_uword(pType);
6323 pType += 1;
6324 }
6325
6326 /* TILETYPE_SAVEHEADER */
6327 endian_udword(&psHeader->version);
6328 endian_udword(&psHeader->quantity);
6329
6330 if (!saveFile(pFileName, pFileData, fileSize))
6331 {
6332 return false;
6333 }
6334 free(pFileData);
6335
6336 return true;
6337 }
6338
6339 // -----------------------------------------------------------------------------------------
loadSaveCompList(const char * pFileName)6340 bool loadSaveCompList(const char *pFileName)
6341 {
6342 WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadOnly);
6343
6344 for (int player = 0; player < MAX_PLAYERS; player++)
6345 {
6346 ini.beginGroup("player_" + WzString::number(player));
6347 std::vector<WzString> list = ini.childKeys();
6348 for (size_t i = 0; i < list.size(); ++i)
6349 {
6350 WzString name = list[i];
6351 int state = ini.value(name, UNAVAILABLE).toInt();
6352 COMPONENT_STATS *psComp = getCompStatsFromName(name);
6353 ASSERT_OR_RETURN(false, psComp, "Bad component %s", name.toUtf8().c_str());
6354 ASSERT_OR_RETURN(false, psComp->compType >= 0 && psComp->compType != COMP_NUMCOMPONENTS, "Bad type %d", psComp->compType);
6355 ASSERT_OR_RETURN(false, state == UNAVAILABLE || state == AVAILABLE || state == FOUND || state == REDUNDANT,
6356 "Bad state %d for %s", state, name.toUtf8().c_str());
6357 apCompLists[player][psComp->compType][psComp->index] = state;
6358 }
6359 ini.endGroup();
6360 }
6361 return true;
6362 }
6363
6364 // -----------------------------------------------------------------------------------------
6365 // Write out the current state of the Comp lists per player
writeCompListFile(const char * pFileName)6366 static bool writeCompListFile(const char *pFileName)
6367 {
6368 WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
6369
6370 // Save each type of struct type
6371 for (int player = 0; player < MAX_PLAYERS; player++)
6372 {
6373 ini.beginGroup("player_" + WzString::number(player));
6374 for (int i = 0; i < numBodyStats; i++)
6375 {
6376 COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asBodyStats + i);
6377 const int state = apCompLists[player][COMP_BODY][i];
6378 if (state != UNAVAILABLE)
6379 {
6380 ini.setValue(psStats->id, state);
6381 }
6382 }
6383 for (int i = 0; i < numWeaponStats; i++)
6384 {
6385 COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asWeaponStats + i);
6386 const int state = apCompLists[player][COMP_WEAPON][i];
6387 if (state != UNAVAILABLE)
6388 {
6389 ini.setValue(psStats->id, state);
6390 }
6391 }
6392 for (int i = 0; i < numConstructStats; i++)
6393 {
6394 COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asConstructStats + i);
6395 const int state = apCompLists[player][COMP_CONSTRUCT][i];
6396 if (state != UNAVAILABLE)
6397 {
6398 ini.setValue(psStats->id, state);
6399 }
6400 }
6401 for (int i = 0; i < numECMStats; i++)
6402 {
6403 COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asECMStats + i);
6404 const int state = apCompLists[player][COMP_ECM][i];
6405 if (state != UNAVAILABLE)
6406 {
6407 ini.setValue(psStats->id, state);
6408 }
6409 }
6410 for (int i = 0; i < numPropulsionStats; i++)
6411 {
6412 COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asPropulsionStats + i);
6413 const int state = apCompLists[player][COMP_PROPULSION][i];
6414 if (state != UNAVAILABLE)
6415 {
6416 ini.setValue(psStats->id, state);
6417 }
6418 }
6419 for (int i = 0; i < numSensorStats; i++)
6420 {
6421 COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asSensorStats + i);
6422 const int state = apCompLists[player][COMP_SENSOR][i];
6423 if (state != UNAVAILABLE)
6424 {
6425 ini.setValue(psStats->id, state);
6426 }
6427 }
6428 for (int i = 0; i < numRepairStats; i++)
6429 {
6430 COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asRepairStats + i);
6431 const int state = apCompLists[player][COMP_REPAIRUNIT][i];
6432 if (state != UNAVAILABLE)
6433 {
6434 ini.setValue(psStats->id, state);
6435 }
6436 }
6437 for (int i = 0; i < numBrainStats; i++)
6438 {
6439 COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asBrainStats + i);
6440 const int state = apCompLists[player][COMP_BRAIN][i];
6441 if (state != UNAVAILABLE)
6442 {
6443 ini.setValue(psStats->id, state);
6444 }
6445 }
6446 ini.endGroup();
6447 }
6448 return true;
6449 }
6450
6451 // -----------------------------------------------------------------------------------------
6452 // load up structure type list file
loadSaveStructTypeList(const char * pFileName)6453 static bool loadSaveStructTypeList(const char *pFileName)
6454 {
6455 WzConfig ini(pFileName, WzConfig::ReadOnly);
6456
6457 for (int player = 0; player < MAX_PLAYERS; player++)
6458 {
6459 ini.beginGroup("player_" + WzString::number(player));
6460 std::vector<WzString> list = ini.childKeys();
6461 for (size_t i = 0; i < list.size(); ++i)
6462 {
6463 WzString name = list[i];
6464 int state = ini.value(name, UNAVAILABLE).toInt();
6465 int statInc;
6466
6467 ASSERT_OR_RETURN(false, state == UNAVAILABLE || state == AVAILABLE || state == FOUND || state == REDUNDANT,
6468 "Bad state %d for %s", state, name.toUtf8().c_str());
6469 for (statInc = 0; statInc < numStructureStats; statInc++) // loop until find the same name
6470 {
6471 STRUCTURE_STATS *psStats = asStructureStats + statInc;
6472
6473 if (name.compare(psStats->id) == 0)
6474 {
6475 apStructTypeLists[player][statInc] = state;
6476 break;
6477 }
6478 }
6479 ASSERT_OR_RETURN(false, statInc != numStructureStats, "Did not find structure %s", name.toUtf8().c_str());
6480 }
6481 ini.endGroup();
6482 }
6483 return true;
6484 }
6485
6486 // -----------------------------------------------------------------------------------------
6487 // Write out the current state of the Struct Type List per player
writeStructTypeListFile(const char * pFileName)6488 static bool writeStructTypeListFile(const char *pFileName)
6489 {
6490 WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6491
6492 // Save each type of struct type
6493 for (int player = 0; player < MAX_PLAYERS; player++)
6494 {
6495 ini.beginGroup("player_" + WzString::number(player));
6496 STRUCTURE_STATS *psStats = asStructureStats;
6497 for (int i = 0; i < numStructureStats; i++, psStats++)
6498 {
6499 if (apStructTypeLists[player][i] != UNAVAILABLE)
6500 {
6501 ini.setValue(psStats->id, apStructTypeLists[player][i]);
6502 }
6503 }
6504 ini.endGroup();
6505 }
6506 return true;
6507 }
6508
6509 // -----------------------------------------------------------------------------------------
6510 // load up saved research file
loadSaveResearch(const char * pFileName)6511 bool loadSaveResearch(const char *pFileName)
6512 {
6513 WzConfig ini(pFileName, WzConfig::ReadOnly);
6514 const int players = game.maxPlayers;
6515 std::vector<WzString> list = ini.childGroups();
6516 for (size_t i = 0; i < list.size(); ++i)
6517 {
6518 ini.beginGroup(list[i]);
6519 bool found = false;
6520 WzString name = ini.value("name").toWzString();
6521 int statInc;
6522 for (statInc = 0; statInc < asResearch.size(); statInc++)
6523 {
6524 RESEARCH *psStats = &asResearch[statInc];
6525 //loop until find the same name
6526 if (psStats->id.compare(name) == 0)
6527
6528 {
6529 found = true;
6530 break;
6531 }
6532 }
6533 if (!found)
6534 {
6535 //ignore this record
6536 continue;
6537 }
6538 auto researchedList = ini.value("researched").jsonValue();
6539 auto possiblesList = ini.value("possible").jsonValue();
6540 auto pointsList = ini.value("currentPoints").jsonValue();
6541 ASSERT(researchedList.is_array(), "Bad (non-array) researched list for %s", name.toUtf8().c_str());
6542 ASSERT(possiblesList.is_array(), "Bad (non-array) possible list for %s", name.toUtf8().c_str());
6543 ASSERT(pointsList.is_array(), "Bad (non-array) points list for %s", name.toUtf8().c_str());
6544 ASSERT(researchedList.size() == players, "Bad researched list for %s", name.toUtf8().c_str());
6545 ASSERT(possiblesList.size() == players, "Bad possible list for %s", name.toUtf8().c_str());
6546 ASSERT(pointsList.size() == players, "Bad points list for %s", name.toUtf8().c_str());
6547 for (int plr = 0; plr < players; plr++)
6548 {
6549 PLAYER_RESEARCH *psPlRes;
6550 int researched = json_getValue(researchedList, plr).toInt();
6551 int possible = json_getValue(possiblesList, plr).toInt();
6552 int points = json_getValue(pointsList, plr).toInt();
6553
6554 psPlRes = &asPlayerResList[plr][statInc];
6555 // Copy the research status
6556 psPlRes->ResearchStatus = (researched & RESBITS);
6557 SetResearchPossible(psPlRes, possible);
6558 psPlRes->currentPoints = points;
6559 //for any research that has been completed - perform so that upgrade values are set up
6560 if (researched == RESEARCHED)
6561 {
6562 researchResult(statInc, plr, false, nullptr, false);
6563 }
6564 }
6565 ini.endGroup();
6566 }
6567 return true;
6568 }
6569
6570 // -----------------------------------------------------------------------------------------
6571 // Write out the current state of the Research per player
writeResearchFile(char * pFileName)6572 static bool writeResearchFile(char *pFileName)
6573 {
6574 WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
6575
6576 for (size_t i = 0; i < asResearch.size(); ++i)
6577 {
6578 RESEARCH *psStats = &asResearch[i];
6579 bool valid = false;
6580 std::vector<WzString> possibles, researched, points;
6581 for (int player = 0; player < game.maxPlayers; player++)
6582 {
6583 possibles.push_back(WzString::number(GetResearchPossible(&asPlayerResList[player][i])));
6584 researched.push_back(WzString::number(asPlayerResList[player][i].ResearchStatus & RESBITS));
6585 points.push_back(WzString::number(asPlayerResList[player][i].currentPoints));
6586 if (IsResearchPossible(&asPlayerResList[player][i]) || (asPlayerResList[player][i].ResearchStatus & RESBITS) || asPlayerResList[player][i].currentPoints)
6587 {
6588 valid = true; // write this entry
6589 }
6590 }
6591 if (valid)
6592 {
6593 ini.beginGroup("research_" + WzString::number(i));
6594 ini.setValue("name", psStats->id);
6595 ini.setValue("possible", possibles);
6596 ini.setValue("researched", researched);
6597 ini.setValue("currentPoints", points);
6598 ini.endGroup();
6599 }
6600 }
6601 return true;
6602 }
6603
6604
6605 // -----------------------------------------------------------------------------------------
6606 // load up saved message file
loadSaveMessage(const char * pFileName,LEVEL_TYPE levelType)6607 bool loadSaveMessage(const char* pFileName, LEVEL_TYPE levelType)
6608 {
6609 // Only clear the messages if its a mid save game
6610 if (gameType == GTYPE_SAVE_MIDMISSION)
6611 {
6612 freeMessages();
6613 }
6614 else if (gameType == GTYPE_SAVE_START)
6615 {
6616 // If we are loading in a CamStart or a CamChange then we are not interested in any saved messages
6617 if (levelType == LEVEL_TYPE::LDS_CAMSTART || levelType == LEVEL_TYPE::LDS_CAMCHANGE)
6618 {
6619 return true;
6620 }
6621 }
6622
6623 WzConfig ini(pFileName, WzConfig::ReadOnly);
6624 std::vector<WzString> list = ini.childGroups();
6625 for (size_t i = 0; i < list.size(); ++i)
6626 {
6627 ini.beginGroup(list[i]);
6628 MESSAGE_TYPE type = (MESSAGE_TYPE)ini.value("type").toInt();
6629 bool bObj = ini.contains("obj/id");
6630 int player = ini.value("player").toInt();
6631 int id = ini.value("id").toInt();
6632 int dataType = ini.value("dataType").toInt();
6633
6634 if (type == MSG_PROXIMITY)
6635 {
6636 //only load proximity if a mid-mission save game
6637 if (gameType == GTYPE_SAVE_MIDMISSION)
6638 {
6639 if (bObj)
6640 {
6641 // Proximity object so create get the obj from saved idy
6642 int objId = ini.value("obj/id").toInt();
6643 int objPlayer = ini.value("obj/player").toInt();
6644 OBJECT_TYPE objType = (OBJECT_TYPE)ini.value("obj/type").toInt();
6645 MESSAGE *psMessage = addMessage(type, true, player);
6646 if (psMessage)
6647 {
6648 psMessage->psObj = getBaseObjFromData(objId, objPlayer, objType);
6649 ASSERT(psMessage->psObj, "Viewdata object id %d not found for message %d", objId, id);
6650 }
6651 else
6652 {
6653 debug(LOG_ERROR, "Proximity object could not be created (type=%d, player=%d, message=%d)", type, player, id);
6654 }
6655 }
6656 else
6657 {
6658 VIEWDATA* psViewData = nullptr;
6659
6660 // Proximity position so get viewdata pointer from the name
6661 MESSAGE* psMessage = addMessage(type, false, player);
6662
6663 if (psMessage)
6664 {
6665 if (dataType == MSG_DATA_BEACON)
6666 {
6667 //See addBeaconMessage(). psMessage->dataType is wrong here because
6668 //addMessage() calls createMessage() which defaults dataType to MSG_DATA_DEFAULT.
6669 //Later when findBeaconMsg() attempts to find a placed beacon it can't because
6670 //the dataType is wrong.
6671 psMessage->dataType = MSG_DATA_BEACON;
6672 Vector2i pos = ini.vector2i("position");
6673 int sender = ini.value("sender").toInt();
6674 psViewData = CreateBeaconViewData(sender, pos.x, pos.y);
6675 ASSERT(psViewData, "Could not create view data for message %d", id);
6676 if (psViewData == nullptr)
6677 {
6678 // Skip this message
6679 removeMessage(psMessage, player);
6680 continue;
6681 }
6682 }
6683 else if (ini.contains("name"))
6684 {
6685 psViewData = getViewData(ini.value("name").toWzString());
6686 ASSERT(psViewData, "Failed to find view data for proximity position - skipping message %d", id);
6687 if (psViewData == nullptr)
6688 {
6689 // Skip this message
6690 removeMessage(psMessage, player);
6691 continue;
6692 }
6693 }
6694 else
6695 {
6696 debug(LOG_ERROR, "Proximity position with empty name skipped (message %d)", id);
6697 removeMessage(psMessage, player);
6698 continue;
6699 }
6700
6701 psMessage->pViewData = psViewData;
6702 // Check the z value is at least the height of the terrain
6703 const int terrainHeight = map_Height(((VIEW_PROXIMITY*)psViewData->pData)->x, ((VIEW_PROXIMITY*)psViewData->pData)->y);
6704 if (((VIEW_PROXIMITY*)psViewData->pData)->z < terrainHeight)
6705 {
6706 ((VIEW_PROXIMITY*)psViewData->pData)->z = terrainHeight;
6707 }
6708 }
6709 else
6710 {
6711 debug(LOG_ERROR, "Proximity position could not be created (type=%d, player=%d, message=%d)", type, player, id);
6712 }
6713 }
6714 }
6715 }
6716 else
6717 {
6718 // Only load Campaign/Mission messages if a mid-mission save game; always load research messages
6719 if (type == MSG_RESEARCH || gameType == GTYPE_SAVE_MIDMISSION)
6720 {
6721 MESSAGE* psMessage = addMessage(type, false, player);
6722 ASSERT(psMessage, "Could not create message %d", id);
6723 if (psMessage)
6724 {
6725 psMessage->pViewData = getViewData(ini.value("name").toWzString());
6726 ASSERT(psMessage->pViewData, "Failed to find view data for message %d", id);
6727 }
6728 }
6729 }
6730 ini.endGroup();
6731 }
6732 jsDebugMessageUpdate();
6733 return true;
6734 }
6735
6736 // -----------------------------------------------------------------------------------------
6737 // Write out the current messages per player
writeMessageFile(const char * pFileName)6738 static bool writeMessageFile(const char *pFileName)
6739 {
6740 WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6741 int numMessages = 0;
6742
6743 // save each type of research
6744 for (int player = 0; player < game.maxPlayers; player++)
6745 {
6746 for (MESSAGE *psMessage = apsMessages[player]; psMessage != nullptr; psMessage = psMessage->psNext)
6747 {
6748 ini.beginGroup("message_" + WzString::number(numMessages++));
6749 ini.setValue("id", numMessages - 1); // for future use
6750 ini.setValue("player", player);
6751 ini.setValue("type", psMessage->type);
6752 ini.setValue("dataType", psMessage->dataType);
6753 if (psMessage->type == MSG_PROXIMITY)
6754 {
6755 //get the matching proximity message
6756 PROXIMITY_DISPLAY *psProx = nullptr;
6757 for (psProx = apsProxDisp[player]; psProx != nullptr; psProx = psProx->psNext)
6758 {
6759 //compare the pointers
6760 if (psProx->psMessage == psMessage)
6761 {
6762 break;
6763 }
6764 }
6765 ASSERT(psProx != nullptr, "Save message; proximity display not found for message");
6766 if (psProx && psProx->type == POS_PROXDATA)
6767 {
6768 //message has viewdata so store the name
6769 VIEWDATA *pViewData = psMessage->pViewData;
6770 ini.setValue("name", pViewData->name);
6771
6772 // save beacon data
6773 if (psMessage->dataType == MSG_DATA_BEACON)
6774 {
6775 VIEW_PROXIMITY *viewData = (VIEW_PROXIMITY *)psMessage->pViewData->pData;
6776 ini.setVector2i("position", Vector2i(viewData->x, viewData->y));
6777 ini.setValue("sender", viewData->sender);
6778 }
6779 }
6780 else
6781 {
6782 // message has object so store Object Id
6783 const BASE_OBJECT *psObj = psMessage->psObj;
6784 if (psObj)
6785 {
6786 ini.setValue("obj/id", psObj->id);
6787 ini.setValue("obj/player", psObj->player);
6788 ini.setValue("obj/type", psObj->type);
6789 }
6790 else
6791 {
6792 ASSERT(false, "Message type has no object data to save ?");
6793 }
6794 }
6795 }
6796 else
6797 {
6798 const VIEWDATA *pViewData = psMessage->pViewData;
6799 ini.setValue("name", pViewData != nullptr ? pViewData->name : "NULL");
6800 }
6801 ini.setValue("read", psMessage->read); // flag to indicate whether message has been read; not that this is/was _not_ read by loading code!?
6802 ASSERT(player == psMessage->player, "Bad player number (%d == %d)", player, psMessage->player);
6803 ini.endGroup();
6804 }
6805 }
6806 return true;
6807 }
6808
6809 // -----------------------------------------------------------------------------------------
loadSaveStructLimits(const char * pFileName)6810 bool loadSaveStructLimits(const char *pFileName)
6811 {
6812 WzConfig ini(pFileName, WzConfig::ReadOnly);
6813
6814 for (int player = 0; player < game.maxPlayers; player++)
6815 {
6816 ini.beginGroup("player_" + WzString::number(player));
6817 std::vector<WzString> list = ini.childKeys();
6818 for (size_t i = 0; i < list.size(); ++i)
6819 {
6820 WzString name = list[i];
6821 int limit = ini.value(name, 0).toInt();
6822
6823 if (name.compare("@Droid") == 0)
6824 {
6825 setMaxDroids(player, limit);
6826 }
6827 else if (name.compare("@Commander") == 0)
6828 {
6829 setMaxCommanders(player, limit);
6830 }
6831 else if (name.compare("@Constructor") == 0)
6832 {
6833 setMaxConstructors(player, limit);
6834 }
6835 else
6836 {
6837 unsigned statInc;
6838 for (statInc = 0; statInc < numStructureStats; ++statInc)
6839 {
6840 STRUCTURE_STATS *psStats = asStructureStats + statInc;
6841 if (name.compare(psStats->id) == 0)
6842 {
6843 asStructureStats[statInc].upgrade[player].limit = limit != 255 ? limit : LOTS_OF;
6844 break;
6845 }
6846 }
6847 ASSERT_OR_RETURN(false, statInc != numStructureStats, "Did not find structure %s", name.toUtf8().c_str());
6848 }
6849 }
6850 ini.endGroup();
6851 }
6852 return true;
6853 }
6854
6855 // -----------------------------------------------------------------------------------------
6856 /*
6857 Writes the list of structure limits to a file
6858 */
writeStructLimitsFile(const char * pFileName)6859 bool writeStructLimitsFile(const char *pFileName)
6860 {
6861 WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6862
6863 // Save each type of struct type
6864 for (int player = 0; player < game.maxPlayers; player++)
6865 {
6866 ini.beginGroup("player_" + WzString::number(player));
6867
6868 ini.setValue("@Droid", getMaxDroids(player));
6869 ini.setValue("@Commander", getMaxCommanders(player));
6870 ini.setValue("@Constructor", getMaxConstructors(player));
6871
6872 STRUCTURE_STATS *psStats = asStructureStats;
6873 for (int i = 0; i < numStructureStats; i++, psStats++)
6874 {
6875 const int limit = MIN(asStructureStats[i].upgrade[player].limit, 255);
6876 if (limit != 255)
6877 {
6878 ini.setValue(psStats->id, limit);
6879 }
6880 }
6881 ini.endGroup();
6882 }
6883 return true;
6884 }
6885
6886 /*!
6887 * Load the current fire-support designated commanders (the one who has fire-support enabled)
6888 */
readFiresupportDesignators(const char * pFileName)6889 bool readFiresupportDesignators(const char *pFileName)
6890 {
6891 WzConfig ini(pFileName, WzConfig::ReadOnly);
6892 std::vector<WzString> list = ini.childGroups();
6893
6894 for (size_t i = 0; i < list.size(); ++i)
6895 {
6896 uint32_t id = ini.value("Player_" + WzString::number(i) + "/id", NULL_ID).toInt();
6897 if (id != NULL_ID)
6898 {
6899 cmdDroidSetDesignator((DROID *)getBaseObjFromId(id));
6900 }
6901 }
6902 return true;
6903 }
6904
6905 /*!
6906 * Save the current fire-support designated commanders (the one who has fire-support enabled)
6907 */
writeFiresupportDesignators(const char * pFileName)6908 bool writeFiresupportDesignators(const char *pFileName)
6909 {
6910 int player;
6911 WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6912
6913 for (player = 0; player < MAX_PLAYERS; player++)
6914 {
6915 DROID *psDroid = cmdDroidGetDesignator(player);
6916 if (psDroid != nullptr)
6917 {
6918 ini.setValue("Player_" + WzString::number(player) + "/id", psDroid->id);
6919 }
6920 }
6921 return true;
6922 }
6923
6924
6925 // -----------------------------------------------------------------------------------------
6926 // write the event state to a file on disk
writeScriptState(const char * pFileName)6927 static bool writeScriptState(const char *pFileName)
6928 {
6929 char jsFilename[PATH_MAX], *ext;
6930
6931 // The below belongs to the new javascript stuff
6932 sstrcpy(jsFilename, pFileName);
6933 ext = strrchr(jsFilename, '/');
6934 *ext = '\0';
6935 strcat(jsFilename, "/scriptstate.json");
6936 saveScriptStates(jsFilename);
6937
6938 return true;
6939 }
6940
6941 // -----------------------------------------------------------------------------------------
6942 // load the script state given a .gam name
loadScriptState(char * pFileName)6943 bool loadScriptState(char *pFileName)
6944 {
6945 char jsFilename[PATH_MAX];
6946
6947 pFileName[strlen(pFileName) - 4] = '\0';
6948
6949 // The below belongs to the new javascript stuff
6950 sstrcpy(jsFilename, pFileName);
6951 strcat(jsFilename, "/scriptstate.json");
6952 loadScriptStates(jsFilename);
6953
6954 // change the file extension
6955 strcat(pFileName, "/scriptstate.es");
6956
6957 return true;
6958 }
6959
6960
6961 // -----------------------------------------------------------------------------------------
6962 /* set the global scroll values to use for the save game */
setMapScroll()6963 static void setMapScroll()
6964 {
6965 //if loading in a pre version5 then scroll values will not have been set up so set to max poss
6966 if (width == 0 && height == 0)
6967 {
6968 scrollMinX = 0;
6969 scrollMaxX = mapWidth;
6970 scrollMinY = 0;
6971 scrollMaxY = mapHeight;
6972 return;
6973 }
6974 scrollMinX = startX;
6975 scrollMinY = startY;
6976 scrollMaxX = startX + width;
6977 scrollMaxY = startY + height;
6978 //check not going beyond width/height of map
6979 if (scrollMaxX > (SDWORD)mapWidth)
6980 {
6981 scrollMaxX = mapWidth;
6982 debug(LOG_NEVER, "scrollMaxX was too big It has been set to map width");
6983 }
6984 if (scrollMaxY > (SDWORD)mapHeight)
6985 {
6986 scrollMaxY = mapHeight;
6987 debug(LOG_NEVER, "scrollMaxY was too big It has been set to map height");
6988 }
6989 }
6990
6991
6992 // -----------------------------------------------------------------------------------------
6993 /*returns the current type of save game being loaded*/
getSaveGameType()6994 GAME_TYPE getSaveGameType()
6995 {
6996 return gameType;
6997 }
6998
plotBackdropPixel(char * backDropSprite,int xx,int yy,PIELIGHT const & colour)6999 static void plotBackdropPixel(char *backDropSprite, int xx, int yy, PIELIGHT const &colour)
7000 {
7001 xx = clip(xx, 0, BACKDROP_HACK_WIDTH - 1);
7002 yy = clip(yy, 0, BACKDROP_HACK_HEIGHT - 1);
7003 char *pixel = backDropSprite + (yy * BACKDROP_HACK_WIDTH + xx) * 3;
7004 pixel[0] = colour.byte.r;
7005 pixel[1] = colour.byte.g;
7006 pixel[2] = colour.byte.b;
7007 }
7008
plotStructurePreviewScript(ScriptMapData const & data,char * backDropSprite,Vector2i playeridpos[])7009 bool plotStructurePreviewScript(ScriptMapData const &data, char *backDropSprite, Vector2i playeridpos[])
7010 {
7011 ASSERT_OR_RETURN(false, data.valid, "Missing map data");
7012
7013 for (auto &structure : data.structures)
7014 {
7015 unsigned player = structure.player == -1? scavengerSlot() : structure.player;
7016 if (player >= MAX_PLAYERS)
7017 {
7018 debug(LOG_ERROR, "Bad player");
7019 continue;
7020 }
7021 bool HQ = structure.name.startsWith("A0CommandCentre");
7022 Vector2i pos = map_coord(structure.position);
7023 if (HQ)
7024 {
7025 playeridpos[player] = pos;
7026 }
7027 unsigned playerid = getPlayerColour(RemapPlayerNumber(player));
7028 // kludge to fix black, so you can see it on some maps.
7029 PIELIGHT color = playerid == 3? WZCOL_GREY : clanColours[playerid];
7030 if (HQ)
7031 {
7032 // This shows where the HQ is on the map in a special color.
7033 // We could do the same for anything else (oil/whatever) also.
7034 // Possible future enhancement?
7035 color = WZCOL_MAP_PREVIEW_HQ;
7036 }
7037 // and now we blit the color to the texture
7038 plotBackdropPixel(backDropSprite, pos.x, pos.y, color);
7039 }
7040
7041 plotScriptFeature(data, backDropSprite);
7042
7043 return true;
7044 }
7045
7046 /**
7047 * \param[out] backDropSprite The premade map texture.
7048 * \param scale Scale of the map texture.
7049 * \param[out] playeridpos Will contain the position on the map where the player's HQ are located.
7050 *
7051 * Reads the current map and colours the map preview for any structures
7052 * present. Additionally we load the player's HQ location into playeridpos so
7053 * we know the player's starting location.
7054 */
plotStructurePreview16(char * backDropSprite,Vector2i playeridpos[])7055 bool plotStructurePreview16(char *backDropSprite, Vector2i playeridpos[])
7056 {
7057 union
7058 {
7059 SAVE_STRUCTURE_V2 v2;
7060 SAVE_STRUCTURE_V20 v20;
7061 } sSave; // close eyes now.
7062 SAVE_STRUCTURE_V2 *psSaveStructure2 = &sSave.v2;
7063 SAVE_STRUCTURE_V20 *psSaveStructure20 = &sSave.v20;
7064 // ok you can open them again..
7065
7066 STRUCT_SAVEHEADER *psHeader;
7067 char aFileName[256];
7068 UDWORD xx, yy, count, fileSize, sizeOfSaveStructure = sizeof(SAVE_STRUCTURE);
7069 UDWORD playerid = 0;
7070 char *pFileData = nullptr;
7071 LEVEL_DATASET *psLevel;
7072 PIELIGHT color = WZCOL_BLACK ;
7073 bool HQ = false;
7074
7075 psLevel = levFindDataSet(game.map, &game.hash);
7076 ASSERT_OR_RETURN(false, psLevel, "No level found for %s", game.map);
7077 sstrcpy(aFileName, psLevel->apDataFiles[0]);
7078 aFileName[strlen(aFileName) - 4] = '\0';
7079 strcat(aFileName, "/struct.bjo");
7080
7081 if (!PHYSFS_exists(aFileName)) // use new version of structure data
7082 {
7083 sstrcpy(aFileName, psLevel->apDataFiles[0]);
7084 aFileName[strlen(aFileName) - 4] = '\0';
7085 strcat(aFileName, "/struct.json");
7086 WzConfig ini(aFileName, WzConfig::ReadOnly);
7087 std::vector<WzString> list = ini.childGroups();
7088 for (size_t i = 0; i < list.size(); ++i)
7089 {
7090 ini.beginGroup(list[i]);
7091 WzString name = ini.value("name").toWzString();
7092 Position pos = ini.vector3i("position");
7093 playerid = ini.value("startpos", scavengerSlot()).toInt(); // No conversion should be going on, this is the map makers position when player X should be.
7094 ASSERT_OR_RETURN(false, playerid < MAX_PLAYERS, "Invalid player number");
7095 if (name.startsWith("A0CommandCentre"))
7096 {
7097 HQ = true;
7098 xx = playeridpos[playerid].x = map_coord(pos.x);
7099 yy = playeridpos[playerid].y = map_coord(pos.y);
7100 }
7101 else
7102 {
7103 HQ = false;
7104 xx = map_coord(pos.x);
7105 yy = map_coord(pos.y);
7106 }
7107 playerid = getPlayerColour(RemapPlayerNumber(playerid));
7108 // kludge to fix black, so you can see it on some maps.
7109 if (playerid == 3) // in this case 3 = palette entry for black.
7110 {
7111 color = WZCOL_GREY;
7112 }
7113 else
7114 {
7115 color.rgba = clanColours[playerid].rgba;
7116 }
7117 if (HQ)
7118 {
7119 // This shows where the HQ is on the map in a special color.
7120 // We could do the same for anything else (oil/whatever) also.
7121 // Possible future enhancement?
7122 color = WZCOL_MAP_PREVIEW_HQ;
7123 }
7124 // and now we blit the color to the texture
7125 plotBackdropPixel(backDropSprite, xx, yy, color);
7126 ini.endGroup();
7127 }
7128 // And now we need to show features.
7129 plotFeature(backDropSprite);
7130
7131 return true;
7132 }
7133
7134
7135 pFileData = fileLoadBuffer;
7136 if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
7137 {
7138 debug(LOG_NEVER, "Failed to load file to buffer.");
7139 }
7140
7141 /* Check the file type */
7142 psHeader = (STRUCT_SAVEHEADER *)pFileData;
7143 if (psHeader->aFileType[0] != 's' || psHeader->aFileType[1] != 't' ||
7144 psHeader->aFileType[2] != 'r' || psHeader->aFileType[3] != 'u')
7145 {
7146 debug(LOG_ERROR, "Invalid file type.");
7147 return false;
7148 }
7149
7150 /* STRUCT_SAVEHEADER */
7151 endian_udword(&psHeader->version);
7152 endian_udword(&psHeader->quantity);
7153
7154 //increment to the start of the data
7155 pFileData += STRUCT_HEADER_SIZE;
7156
7157 if (psHeader->version < VERSION_12)
7158 {
7159 sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V2);
7160 }
7161 else if (psHeader->version < VERSION_14)
7162 {
7163 sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V12);
7164 }
7165 else if (psHeader->version <= VERSION_14)
7166 {
7167 sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V14);
7168 }
7169 else if (psHeader->version <= VERSION_16)
7170 {
7171 sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V15);
7172 }
7173 else if (psHeader->version <= VERSION_19)
7174 {
7175 sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V17);
7176 }
7177 else if (psHeader->version <= VERSION_20)
7178 {
7179 sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V20);
7180 }
7181
7182 /* Load in the structure data */
7183 for (count = 0; count < psHeader->quantity; count++, pFileData += sizeOfSaveStructure)
7184 {
7185 // we are specifically looking for the HQ, and it seems this is the only way to
7186 // find it via parsing map.
7187 // We store the coordinates of the structure, into a array for as many players as are on the map.
7188
7189 if (psHeader->version <= VERSION_19)
7190 {
7191 // All versions up to 19 are compatible with V2.
7192 memcpy(psSaveStructure2, pFileData, sizeof(SAVE_STRUCTURE_V2));
7193
7194 endian_udword(&psSaveStructure2->x);
7195 endian_udword(&psSaveStructure2->y);
7196 endian_udword(&psSaveStructure2->player);
7197 playerid = psSaveStructure2->player;
7198 ASSERT_OR_RETURN(false, playerid < MAX_PLAYERS, "Invalid player number");
7199 if (strncmp(psSaveStructure2->name, "A0CommandCentre", 15) == 0)
7200 {
7201 HQ = true;
7202 xx = playeridpos[playerid].x = map_coord(psSaveStructure2->x);
7203 yy = playeridpos[playerid].y = map_coord(psSaveStructure2->y);
7204 }
7205 else
7206 {
7207 HQ = false;
7208 xx = map_coord(psSaveStructure2->x);
7209 yy = map_coord(psSaveStructure2->y);
7210 }
7211 }
7212 else
7213 {
7214 // All newer versions are compatible with V20.
7215 memcpy(psSaveStructure20, pFileData, sizeof(SAVE_STRUCTURE_V20));
7216
7217 endian_udword(&psSaveStructure20->x);
7218 endian_udword(&psSaveStructure20->y);
7219 endian_udword(&psSaveStructure20->player);
7220 playerid = psSaveStructure20->player;
7221 ASSERT_OR_RETURN(false, playerid < MAX_PLAYERS, "Invalid player number");
7222 if (strncmp(psSaveStructure20->name, "A0CommandCentre", 15) == 0)
7223 {
7224 HQ = true;
7225 xx = playeridpos[playerid].x = map_coord(psSaveStructure20->x);
7226 yy = playeridpos[playerid].y = map_coord(psSaveStructure20->y);
7227 }
7228 else
7229 {
7230 HQ = false;
7231 xx = map_coord(psSaveStructure20->x);
7232 yy = map_coord(psSaveStructure20->y);
7233 }
7234 }
7235 playerid = getPlayerColour(RemapPlayerNumber(playerid));
7236 // kludge to fix black, so you can see it on some maps.
7237 if (playerid == 3) // in this case 3 = palette entry for black.
7238 {
7239 color = WZCOL_GREY;
7240 }
7241 else
7242 {
7243 color.rgba = clanColours[playerid].rgba;
7244 }
7245
7246 if (HQ)
7247 {
7248 // This shows where the HQ is on the map in a special color.
7249 // We could do the same for anything else (oil/whatever) also.
7250 // Possible future enhancement?
7251 color = WZCOL_MAP_PREVIEW_HQ;
7252 }
7253
7254 // and now we blit the color to the texture
7255 plotBackdropPixel(backDropSprite, xx, yy, color);
7256 }
7257
7258 // And now we need to show features.
7259 plotFeature(backDropSprite);
7260 return true;
7261 }
7262
plotScriptFeature(ScriptMapData const & data,char * backDropSprite)7263 static void plotScriptFeature(ScriptMapData const &data, char *backDropSprite) {
7264 ASSERT_OR_RETURN(, data.valid, "Missing map data");
7265
7266 for (auto &feature : data.features)
7267 {
7268 PIELIGHT colour;
7269 if (feature.name.startsWith("OilResource"))
7270 {
7271 colour = WZCOL_MAP_PREVIEW_OIL;
7272 }
7273 else if (feature.name.startsWith("OilDrum"))
7274 {
7275 colour = WZCOL_MAP_PREVIEW_BARREL;
7276 }
7277 else
7278 {
7279 continue;
7280 }
7281 // and now we blit the color to the texture
7282 auto pos = map_coord(feature.position);
7283 plotBackdropPixel(backDropSprite, pos.x, pos.y, colour);
7284 }
7285 }
7286
7287 // Show location of (at this time) oil on the map preview
plotFeature(char * backDropSprite)7288 static void plotFeature(char *backDropSprite)
7289 {
7290 FEATURE_SAVEHEADER *psHeader;
7291 SAVE_FEATURE_V2 *psSaveFeature;
7292 LEVEL_DATASET *psLevel;
7293 UDWORD xx, yy, count, fileSize;
7294 UDWORD sizeOfSaveFeature = 0;
7295 char *pFileData = nullptr;
7296 char aFileName[256];
7297 const PIELIGHT colourOil = WZCOL_MAP_PREVIEW_OIL;
7298 const PIELIGHT colourBarrel = WZCOL_MAP_PREVIEW_BARREL;
7299
7300 psLevel = levFindDataSet(game.map, &game.hash);
7301 ASSERT_OR_RETURN(, psLevel, "No level found for %s", game.map);
7302 sstrcpy(aFileName, psLevel->apDataFiles[0]);
7303 aFileName[strlen(aFileName) - 4] = '\0';
7304 strcat(aFileName, "/feat.bjo");
7305 if (!PHYSFS_exists(aFileName)) // use new version of feature data
7306 {
7307 sstrcpy(aFileName, psLevel->apDataFiles[0]);
7308 aFileName[strlen(aFileName) - 4] = '\0';
7309 strcat(aFileName, "/feature.json");
7310 WzConfig ini(aFileName, WzConfig::ReadOnly);
7311 if (!ini.status())
7312 {
7313 debug(LOG_ERROR, "Could not open %s", aFileName);
7314 return;
7315 }
7316 std::vector<WzString> list = ini.childGroups();
7317 for (size_t i = 0; i < list.size(); ++i)
7318 {
7319 ini.beginGroup(list[i]);
7320 WzString name = ini.value("name").toWzString();
7321 Position pos = ini.vector3i("position");
7322
7323 // we only care about oil
7324 PIELIGHT const *colour = nullptr;
7325 if (name.startsWith("OilResource"))
7326 {
7327 colour = &colourOil;
7328 }
7329 else if (name.startsWith("OilDrum"))
7330 {
7331 colour = &colourBarrel;
7332 }
7333 if (colour != nullptr)
7334 {
7335 // and now we blit the color to the texture
7336 xx = map_coord(pos.x);
7337 yy = map_coord(pos.y);
7338 plotBackdropPixel(backDropSprite, xx, yy, *colour);
7339 }
7340 ini.endGroup();
7341 }
7342 return;
7343 }
7344
7345 // Load in the chosen file data/
7346 pFileData = fileLoadBuffer;
7347 if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
7348 {
7349 debug(LOG_ERROR, "Unable to load file %s?", aFileName);
7350 return;
7351 }
7352
7353 // Check the file type
7354 psHeader = (FEATURE_SAVEHEADER *)pFileData;
7355 if (psHeader->aFileType[0] != 'f' || psHeader->aFileType[1] != 'e' || psHeader->aFileType[2] != 'a' || psHeader->aFileType[3] != 't')
7356 {
7357 debug(LOG_ERROR, "Incorrect file type, looking at %s", aFileName);
7358 return;
7359 }
7360
7361 endian_udword(&psHeader->version);
7362 endian_udword(&psHeader->quantity);
7363
7364 //increment to the start of the data
7365 pFileData += FEATURE_HEADER_SIZE;
7366
7367 sizeOfSaveFeature = sizeof(SAVE_FEATURE_V2);
7368
7369 if ((sizeOfSaveFeature * psHeader->quantity + FEATURE_HEADER_SIZE) > fileSize)
7370 {
7371 debug(LOG_ERROR, "Unexpected end of file ?");
7372 return;
7373 }
7374
7375 // Load in the feature data
7376 for (count = 0; count < psHeader->quantity; count++, pFileData += sizeOfSaveFeature)
7377 {
7378 // All versions up to 19 are compatible with V2.
7379 psSaveFeature = (SAVE_FEATURE_V2 *) pFileData;
7380
7381 // we only care about oil
7382 PIELIGHT const *colour = nullptr;
7383 if (strncmp(psSaveFeature->name, "OilResource", 11) == 0)
7384 {
7385 colour = &colourOil;
7386 }
7387 else if (strncmp(psSaveFeature->name, "OilDrum", 7) == 0)
7388 {
7389 colour = &colourBarrel;
7390 }
7391 else
7392 {
7393 continue;
7394 }
7395 endian_udword(&psSaveFeature->x);
7396 endian_udword(&psSaveFeature->y);
7397 xx = map_coord(psSaveFeature->x);
7398 yy = map_coord(psSaveFeature->y);
7399 // and now we blit the color to the texture
7400 plotBackdropPixel(backDropSprite, xx, yy, *colour);
7401 }
7402 }
7403