1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (“RTCW SP Source Code”).
8 
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 RTCW SP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 /*
30  * name:		g_save.c
31  *
32  */
33 
34 #include "g_local.h"
35 #include "../qcommon/q_shared.h"
36 #include "../botlib/botlib.h"      //bot lib interface
37 #include "../botlib/be_aas.h"
38 #include "../botlib/be_ea.h"
39 #include "../botlib/be_ai_gen.h"
40 #include "../botlib/be_ai_goal.h"
41 #include "../botlib/be_ai_move.h"
42 #include "../botlib/botai.h"          //bot ai interface
43 
44 #include "ai_cast.h"
45 
46 /*
47 Wolf savegame system.
48 
49 Using the "checkpoint" system, we only need to save at specific locations, but various entities
50 may have changed behind us, so therefore we need to save as much as possible, but without going
51 overboard.
52 
53 For now, everything is saved from the entity, client and cast_state structures, except the fields
54 defined in the ignoreField structures below. Any pointer fields need to be specified in the
55 saveField structures below.
56 
57 !! NOTE: when working on Wolf patches, make sure you only add fields to the very end of the three
58   main structures saved here (entity, client, cast_state). If any fields are inserted in the middle
59   of these structures, savegames will become corrupted, and there is no way of checking for this,
60   so it'll just crash.
61 
62 NOTE TTimo: see show_bug.cgi?id=434 for v17 -> v18 savegames
63 */
64 
65 
66 vmCvar_t musicCvar;
67 char musicString[MAX_STRING_CHARS];
68 
69 static int ver;
70 
71 typedef enum {
72 	F_NONE,
73 	F_STRING,
74 	F_ENTITY,           // index on disk, pointer in memory
75 	F_ITEM,             // index on disk, pointer in memory
76 	F_CLIENT,           // index on disk, pointer in memory
77 	F_FUNCTION
78 } saveFieldtype_t;
79 
80 typedef struct {
81 	size_t ofs;
82 	saveFieldtype_t type;
83 } saveField_t;
84 
85 //.......................................................................................
86 // these are the fields that cannot be saved directly, so they need to be converted
87 static saveField_t gentityFields_17[] = {
88 	{FOFS( client ),      F_CLIENT},
89 	{FOFS( classname ),   F_STRING},
90 	{FOFS( model ),       F_STRING},
91 	{FOFS( model2 ),      F_STRING},
92 	{FOFS( parent ),      F_ENTITY},
93 	{FOFS( nextTrain ),   F_ENTITY},
94 	{FOFS( prevTrain ),   F_ENTITY},
95 	{FOFS( message ),     F_STRING},
96 	{FOFS( target ),      F_STRING},
97 	{FOFS( targetname ),  F_STRING},
98 	{FOFS( team ),        F_STRING},
99 	{FOFS( target_ent ),  F_ENTITY},
100 	{FOFS( think ),       F_FUNCTION},
101 	{FOFS( reached ),     F_FUNCTION},
102 	{FOFS( blocked ),     F_FUNCTION},
103 	{FOFS( touch ),       F_FUNCTION},
104 	{FOFS( use ),         F_FUNCTION},
105 	{FOFS( pain ),        F_FUNCTION},
106 	{FOFS( die ),         F_FUNCTION},
107 	{FOFS( chain ),       F_ENTITY},
108 	{FOFS( enemy ),       F_ENTITY},
109 	{FOFS( activator ),   F_ENTITY},
110 	{FOFS( teamchain ),   F_ENTITY},
111 	{FOFS( teammaster ),  F_ENTITY},
112 	{FOFS( item ),        F_ITEM},
113 	{FOFS( aiAttributes ),F_STRING},
114 	{FOFS( aiName ),      F_STRING},
115 	{FOFS( AIScript_AlertEntity ), F_FUNCTION},
116 	{FOFS( aiSkin ),      F_STRING},
117 	{FOFS( aihSkin ),     F_STRING},
118 	{FOFS( dl_stylestring ),  F_STRING},
119 	{FOFS( dl_shader ),   F_STRING},
120 	{FOFS( melee ),       F_ENTITY},
121 	{FOFS( spawnitem ),   F_STRING},
122 	{FOFS( track ),       F_STRING},
123 	{FOFS( scriptName ),  F_STRING},
124 	{FOFS( scriptStatus.animatingParams ),    F_STRING},
125 	{FOFS( tagName ),     F_STRING},
126 	{FOFS( tagParent ),   F_ENTITY},
127 
128 	{0, 0}
129 };
130 
131 // TTimo
132 // show_bug.cgi?id=434
133 // new field for v18 saved games
134 // not in gentityField to keep backward compatibility loading v17
135 static saveField_t gentityFields_18[] = {
136 	{FOFS( targetdeath ), F_STRING},
137 	{0, 0}
138 };
139 
140 
141 static saveField_t gclientFields[] = {
142 	{CFOFS( hook ),       F_ENTITY},
143 
144 	{0, 0}
145 };
146 
147 static saveField_t castStateFields[] = {
148 	{CSFOFS( aifunc ),    F_FUNCTION},
149 	{CSFOFS( oldAifunc ), F_FUNCTION},
150 	{CSFOFS( painfunc ),  F_FUNCTION},
151 	{CSFOFS( deathfunc ), F_FUNCTION},
152 	{CSFOFS( sightfunc ), F_FUNCTION},
153 	{CSFOFS( sightEnemy ),    F_FUNCTION},
154 	{CSFOFS( sightFriend ),   F_FUNCTION},
155 	{CSFOFS( activate ),  F_FUNCTION},
156 	{CSFOFS( aifuncAttack1 ), F_FUNCTION},
157 	{CSFOFS( aifuncAttack2 ), F_FUNCTION},
158 	{CSFOFS( aifuncAttack3 ), F_FUNCTION},
159 
160 	{0, 0}
161 };
162 
163 //.......................................................................................
164 // this is where we define fields or sections of structures that we should totally ignore
165 typedef struct {
166 	size_t ofs;
167 	int len;
168 } ignoreField_t;
169 
170 static ignoreField_t gentityIgnoreFields[] = {
171 	// don't process events that have already occured before the game was saved
172 	//{FOFS(s.events[0]),		sizeof(int) * MAX_EVENTS},
173 	//{FOFS(s.eventParms[0]),	sizeof(int) * MAX_EVENTS},
174 	//{FOFS(s.eventSequence),	sizeof(int)},
175 
176 	{FOFS( numScriptEvents ), sizeof( int )},
177 	{FOFS( scriptEvents ),    sizeof( g_script_event_t * ) },   // gets created upon parsing the script file, this is static while playing
178 
179 	{0, 0}
180 };
181 
182 static ignoreField_t gclientIgnoreFields[] = {
183 	// don't process events that have already occured before the game was saved
184 	//{CFOFS(ps.events[0]),		sizeof(int) * MAX_EVENTS},
185 	//{CFOFS(ps.eventParms[0]),	sizeof(int) * MAX_EVENTS},
186 	//{CFOFS(ps.eventSequence),	sizeof(int)},
187 	//{CFOFS(ps.oldEventSequence),sizeof(int)},
188 
189 	{0, 0}
190 };
191 
192 static ignoreField_t castStateIgnoreFields[] = {
193 	{CSFOFS( bs ),    sizeof( bot_state_t * )},
194 	{CSFOFS( numCastScriptEvents ),   sizeof( int )},
195 	{CSFOFS( castScriptEvents ), sizeof( cast_script_event_t * ) }, // gets created upon parsing the script file, this is static while playing
196 	{CSFOFS( weaponInfo ),    sizeof( cast_weapon_info_t * )},
197 
198 	{0, 0}
199 };
200 
201 
202 
203 //.......................................................................................
204 // persistant data is optionally carried across level changes
205 // !! WARNING: cannot save pointer or string variables
206 typedef struct {
207 	size_t ofs;
208 	int len;
209 } persField_t;
210 
211 static persField_t gentityPersFields[] = {
212 	{FOFS( health ),              sizeof( int )},
213 	{0, 0}
214 };
215 
216 static persField_t gclientPersFields[] = {
217 	{CFOFS( ps.weapon ),          sizeof( int )},
218 	{CFOFS( ps.ammo[0] ),         sizeof( int ) * MAX_WEAPONS},
219 	{CFOFS( ps.ammoclip[0] ),     sizeof( int ) * MAX_WEAPONS}, //----(SA)	added for ammo in clip
220 	{CFOFS( ps.persistant[0] ),   sizeof( int ) * MAX_PERSISTANT},
221 	{CFOFS( ps.stats[0] ),        sizeof( int ) * MAX_STATS},
222 	{CFOFS( ps.weapons[0] ),      sizeof( int ) * MAX_WEAPONS / ( sizeof( int ) * 8 )}, // //----(SA)	added.  weapons owned got moved outside stats[]
223 	{CFOFS( ps.powerups[0] ),     sizeof( int ) * MAX_POWERUPS},
224 	{CFOFS( ps.holdable[0] ),     sizeof( int ) * MAX_HOLDABLE},    //----(SA)	added
225 	{CFOFS( ps.holding ),         sizeof( int )},                   //----(SA)	added
226 
227 	{0, 0}
228 };
229 
230 static persField_t castStatePersFields[] = {
231 	// TODO: will we be transporting AI's between levels?
232 	// FIXME: if so, we can't save strings in here, so how are we going to create the new AI
233 	// in the next level, with all his strings and pointers attached?
234 	{0, 0}
235 };
236 
237 
238 //.......................................................................................
239 // this stores all functions in the game code
240 typedef struct {
241 	char *funcStr;
242 	byte *funcPtr;
243 } funcList_t;
244 
245 
246 #include "g_func_decs.h" // declare all game functions
247 
248 funcList_t funcList[] = {
249 	#include "g_funcs.h"
250 };
251 
252 //=========================================================
253 
254 /*
255 ===============
256 G_SaveWriteError
257 ===============
258 */
G_SaveWriteError(void)259 void G_SaveWriteError( void ) {
260 // TTimo
261 #ifdef __linux__
262 	G_Error( "Unable to save game.\n\nPlease check that you have at least 5mb free of disk space in your home directory." );
263 #else
264 	G_Error( "Insufficient free disk space.\n\nPlease free at least 5mb of free space on game drive." );
265 #endif
266 }
267 
268 static int saveByteCount;
269 
270 /*
271 ===============
272 G_SaveWrite
273 
274   FS_Write doesnt always accurately return how many bytes were written, so tally them up, and
275   check them before we rename to the real file
276 ===============
277 */
G_SaveWrite(const void * buffer,int len,fileHandle_t f)278 int G_SaveWrite( const void *buffer, int len, fileHandle_t f ) {
279 	saveByteCount += len;
280 
281 	return trap_FS_Write( buffer, len, f );
282 }
283 
284 //=========================================================
285 
G_FindFuncAtAddress(byte * adr)286 funcList_t *G_FindFuncAtAddress( byte *adr ) {
287 	int i;
288 
289 	for ( i = 0; funcList[i].funcStr; i++ ) {
290 		if ( funcList[i].funcPtr == adr ) {
291 			return &funcList[i];
292 		}
293 	}
294 	return NULL;
295 }
296 
G_FindFuncByName(char * name)297 byte *G_FindFuncByName( char *name ) {
298 	int i;
299 
300 	for ( i = 0; funcList[i].funcStr; i++ ) {
301 		if ( !strcmp( name, funcList[i].funcStr ) ) {
302 			return funcList[i].funcPtr;
303 		}
304 	}
305 	return NULL;
306 }
307 
WriteField1(saveField_t * field,byte * base)308 void WriteField1( saveField_t *field, byte *base ) {
309 	void        *p;
310 	int len;
311 	int index;
312 	funcList_t  *func;
313 
314 	p = ( void * )( base + field->ofs );
315 	switch ( field->type )
316 	{
317 	case F_STRING:
318 		if ( *(char **)p ) {
319 			len = strlen( *(char **)p ) + 1;
320 		} else {
321 			len = 0;
322 		}
323 		*(int *)p = len;
324 		break;
325 	case F_ENTITY:
326 		if ( *(gentity_t **)p == NULL ) {
327 			index = -1;
328 		} else {
329 			index = *(gentity_t **)p - g_entities;
330 		}
331 		if ( index >= MAX_GENTITIES || index < -1 ) {
332 			G_Error( "WriteField1: entity out of range (%i)", index );
333 		}
334 		*(int *)p = index;
335 		break;
336 	case F_CLIENT:
337 		if ( *(gclient_t **)p == NULL ) {
338 			index = -1;
339 		} else {
340 			index = *(gclient_t **)p - level.clients;
341 		}
342 		if ( index >= MAX_CLIENTS || index < -1 ) {
343 			G_Error( "WriteField1: client out of range (%i)", index );
344 		}
345 		*(int *)p = index;
346 		break;
347 	case F_ITEM:
348 		if ( *(gitem_t **)p == NULL ) {
349 			index = -1;
350 		} else {
351 			index = *(gitem_t **)p - bg_itemlist;
352 		}
353 		*(int *)p = index;
354 		break;
355 
356 		//	match this with a function address in the function list, which is built using the
357 		//	"extractfuncs.bat" in the utils folder. We then save the string equivalent
358 		//	of the function. This effectively gives us cross-version save games.
359 	case F_FUNCTION:
360 		if ( *(byte **)p == NULL ) {
361 			len = 0;
362 		} else {
363 			func = G_FindFuncAtAddress( *(byte **)p );
364 			if ( !func ) {
365 				G_Error( "WriteField1: unknown function, cannot save game" );
366 			}
367 			len = strlen( func->funcStr ) + 1;
368 		}
369 		*(int *)p = len;
370 		break;
371 
372 	default:
373 		G_Error( "WriteField1: unknown field type" );
374 	}
375 }
376 
377 
WriteField2(fileHandle_t f,saveField_t * field,byte * base)378 void WriteField2( fileHandle_t f, saveField_t *field, byte *base ) {
379 	int len;
380 	void        *p;
381 	funcList_t  *func;
382 
383 	p = ( void * )( base + field->ofs );
384 	switch ( field->type )
385 	{
386 	case F_STRING:
387 		if ( *(char **)p ) {
388 			len = strlen( *(char **)p ) + 1;
389 			if ( !G_SaveWrite( *(char **)p, len, f ) ) {
390 				G_SaveWriteError();
391 			}
392 		}
393 		break;
394 	case F_FUNCTION:
395 		if ( *(byte **)p ) {
396 			func = G_FindFuncAtAddress( *(byte **)p );
397 			if ( !func ) {
398 				G_Error( "WriteField1: unknown function, cannot save game" );
399 			}
400 			len = strlen( func->funcStr ) + 1;
401 			if ( !G_SaveWrite( func->funcStr, len, f ) ) {
402 				G_SaveWriteError();
403 			}
404 		}
405 		break;
406 	default:
407 		break;
408 	}
409 }
410 
ReadField(fileHandle_t f,saveField_t * field,byte * base)411 void ReadField( fileHandle_t f, saveField_t *field, byte *base ) {
412 	void        *p;
413 	int len;
414 	int index;
415 	char funcStr[512];
416 
417 	p = ( void * )( base + field->ofs );
418 	switch ( field->type )
419 	{
420 	case F_STRING:
421 		len = *(int *)p;
422 		if ( !len ) {
423 			*(char **)p = NULL;
424 		} else
425 		{
426 			*(char **)p = G_Alloc( len );
427 			trap_FS_Read( *(char **)p, len, f );
428 		}
429 		break;
430 	case F_ENTITY:
431 		index = *(int *)p;
432 		if ( index >= MAX_GENTITIES || index < -1 ) {
433 			G_Error( "ReadField: entity out of range (%i)", index );
434 		}
435 		if ( index == -1 ) {
436 			*(gentity_t **)p = NULL;
437 		} else {
438 			*(gentity_t **)p = &g_entities[index];
439 		}
440 		break;
441 	case F_CLIENT:
442 		index = *(int *)p;
443 		if ( index >= MAX_CLIENTS || index < -1 ) {
444 			G_Error( "ReadField: client out of range (%i)", index );
445 		}
446 		if ( index == -1 ) {
447 			*(gclient_t **)p = NULL;
448 		} else {
449 			*(gclient_t **)p = &level.clients[index];
450 		}
451 		break;
452 	case F_ITEM:
453 		index = *(int *)p;
454 		if ( index == -1 ) {
455 			*(gitem_t **)p = NULL;
456 		} else {
457 			*(gitem_t **)p = &bg_itemlist[index];
458 		}
459 		break;
460 
461 		//relative to code segment
462 	case F_FUNCTION:
463 		len = *(int *)p;
464 		if ( !len ) {
465 			*(byte **)p = NULL;
466 		} else
467 		{
468 			//funcStr = G_Alloc (len);
469 			if ( len > sizeof( funcStr ) ) {
470 				G_Error( "ReadField: function name is greater than buffer (%li chars)", (long unsigned int)sizeof( funcStr ) );
471 			}
472 			trap_FS_Read( funcStr, len, f );
473 			if ( !( *(byte **)p = G_FindFuncByName( funcStr ) ) ) {
474 				G_Error( "ReadField: unknown function '%s'\ncannot load game", funcStr );
475 			}
476 		}
477 		break;
478 
479 	default:
480 		G_Error( "ReadField: unknown field type" );
481 	}
482 }
483 
484 //=========================================================
485 
486 #define SAVE_ENCODE_COUNT_BYTES     1
487 
488 /*
489 ===============
490 G_Save_Encode
491 
492   returns the number of bytes written to "out"
493 ===============
494 */
G_Save_Encode(byte * raw,byte * out,int rawsize,int outsize)495 int G_Save_Encode( byte *raw, byte *out, int rawsize, int outsize ) {
496 	int rawcount, oldrawcount, outcount;
497 	int mode;
498 	byte count;     //DAJ was int but caused endian bugs
499 
500 	rawcount = 0;
501 	outcount = 0;
502 	while ( rawcount < rawsize ) {
503 		oldrawcount = rawcount;
504 		// is this a non-zero?
505 		if ( raw[rawcount] ) {
506 			mode = 1;
507 		} else {
508 			mode = 0;
509 		}
510 		// calc the count
511 		count = 0;
512 		while ( rawcount < rawsize && ( raw[rawcount] != 0 ) == mode && count < ( ( ( 1 << ( SAVE_ENCODE_COUNT_BYTES * 8 - 1 ) ) - 1 ) ) ) {
513 			rawcount++;
514 			count++;
515 		}
516 		// write the count, followed by data if required
517 		memcpy( out + outcount, &count, SAVE_ENCODE_COUNT_BYTES );
518 		// switch the sign bit if zeros
519 		if ( !mode ) {
520 			out[outcount + SAVE_ENCODE_COUNT_BYTES - 1] |= ( 1 << 7 );
521 			outcount += SAVE_ENCODE_COUNT_BYTES;
522 		} else {
523 			outcount += SAVE_ENCODE_COUNT_BYTES;
524 			// write the data
525 			memcpy( out + outcount, raw + oldrawcount, count );
526 			outcount += count;
527 		}
528 	}
529 
530 	return outcount;
531 }
532 
533 /*
534 ===============
535 G_Save_Decode
536 ===============
537 */
G_Save_Decode(byte * in,int insize,byte * out,int outsize)538 void G_Save_Decode( byte *in, int insize, byte *out, int outsize ) {
539 	int incount, outcount;
540 	byte count;     //DAJ was in but caused endian bugs
541 	//
542 	incount = 0;
543 	outcount = 0;
544 	while ( incount < insize ) {
545 		// read the count
546 		count = 0;
547 		memcpy( &count, in + incount, SAVE_ENCODE_COUNT_BYTES );
548 		incount += SAVE_ENCODE_COUNT_BYTES;
549 		// if it's negative, zero it out
550 		if ( count & ( 1 << ( ( SAVE_ENCODE_COUNT_BYTES * 8 ) - 1 ) ) ) {
551 			count &= ~( 1 << ( ( SAVE_ENCODE_COUNT_BYTES * 8 ) - 1 ) );
552 			memset( out + outcount, 0, count );
553 			outcount += count;
554 		} else {
555 			// copy the data from "in"
556 			memcpy( out + outcount, in + incount, count );
557 			outcount += count;
558 			incount += count;
559 		}
560 	}
561 }
562 
563 //=========================================================
564 
565 byte clientBuf[ 2 * sizeof( gentity_t ) ];
566 
567 /*
568 ===============
569 WriteClient
570 ===============
571 */
WriteClient(fileHandle_t f,gclient_t * cl)572 void WriteClient( fileHandle_t f, gclient_t *cl ) {
573 	saveField_t *field;
574 	gclient_t temp;
575 	int length;
576 
577 	// copy the structure across, then process the fields
578 	temp = *cl;
579 
580 	// first, kill all events (assume they have been processed)
581 	memset( temp.ps.events, 0, sizeof( temp.ps.events ) );
582 	memset( temp.ps.eventParms, 0, sizeof( temp.ps.eventParms ) );
583 	temp.ps.eventSequence = 0;
584 	temp.ps.oldEventSequence = 0;
585 	temp.ps.entityEventSequence = 0;
586 
587 	// change the pointers to lengths or indexes
588 	for ( field = gclientFields ; field->type ; field++ )
589 	{
590 		WriteField1( field, (byte *)&temp );
591 	}
592 
593 	// write the block
594 	//if (!G_SaveWrite (&temp, sizeof(temp), f)) G_SaveWriteError();
595 	length = G_Save_Encode( (byte *)&temp, clientBuf, sizeof( temp ), sizeof( clientBuf ) );
596 	if ( !G_SaveWrite( &length, sizeof( length ), f ) ) {
597 		G_SaveWriteError();
598 	}
599 	if ( !G_SaveWrite( &clientBuf, length, f ) ) {
600 		G_SaveWriteError();
601 	}
602 
603 	// now write any allocated data following the edict
604 	for ( field = gclientFields ; field->type ; field++ )
605 	{
606 		WriteField2( f, field, (byte *)cl );
607 	}
608 
609 }
610 
611 /*
612 ===============
613 ReadClient
614 ===============
615 */
ReadClient(fileHandle_t f,gclient_t * client,int size)616 void ReadClient( fileHandle_t f, gclient_t *client, int size ) {
617 	saveField_t *field;
618 	ignoreField_t *ifield;
619 	gclient_t temp;
620 	gentity_t   *ent;
621 	int decodedSize;
622 
623 	if ( ver == 10 ) {
624 		trap_FS_Read( &temp, size, f );
625 	} else {
626 		// read the encoded chunk
627 		trap_FS_Read( &decodedSize, sizeof( int ), f );
628 		if ( decodedSize > sizeof( clientBuf ) ) {
629 			G_Error( "G_LoadGame: encoded chunk is greater than buffer" );
630 		}
631 		trap_FS_Read( clientBuf, decodedSize, f ); \
632 		// decode it
633 		G_Save_Decode( clientBuf, decodedSize, (byte *)&temp, sizeof( temp ) );
634 	}
635 
636 	// convert any feilds back to the correct data
637 	for ( field = gclientFields ; field->type ; field++ )
638 	{
639 		ReadField( f, field, (byte *)&temp );
640 	}
641 
642 	// backup any fields that we don't want to read in
643 	for ( ifield = gclientIgnoreFields ; ifield->len ; ifield++ )
644 	{
645 		memcpy( ( (byte *)&temp ) + ifield->ofs, ( (byte *)client ) + ifield->ofs, ifield->len );
646 	}
647 
648 	// now copy the temp structure into the existing structure
649 	memcpy( client, &temp, size );
650 
651 	// make sure they face the right way
652 	//client->ps.pm_flags |= PMF_RESPAWNED;
653 	// don't allow full run speed for a bit
654 	//if (client->ps.clientNum == 0) {	// only set this for the player
655 	client->ps.pm_flags |= PMF_TIME_LOAD;
656 	client->ps.pm_time = 1000;
657 	if ( client->ps.aiChar ) {
658 		client->ps.pm_time = 800;
659 	}
660 	//}
661 
662 	ent = &g_entities[client->ps.clientNum];
663 
664 	// make sure they face the right way
665 	// if it's the player, see if we need to put them at a mission marker
666 
667 // (SA) I think this should never be hit at all, but as a precaution I'm commenting it out anyway
668 /*
669 	if (!(ent->r.svFlags & SVF_CASTAI) && ent->missionObjectives > 0) {
670 		gentity_t *trav;
671 
672 		for (trav=NULL; trav = G_Find(trav, FOFS(classname), "info_player_checkpoint"); ) {
673 			if (trav->missionObjectives == ent->missionObjectives && Distance(trav->s.origin, ent->r.currentOrigin) < 800) {
674 				G_SetOrigin( ent, trav->s.origin );
675 				VectorCopy( trav->s.origin, ent->client->ps.origin );
676 
677 				trap_GetUsercmd( ent->client - level.clients, &ent->client->pers.cmd );
678 				SetClientViewAngle( ent, trav->s.angles );
679 				break;
680 			}
681 		}
682 
683 		if (!trav) {
684 			trap_GetUsercmd( ent->client - level.clients, &ent->client->pers.cmd );
685 			SetClientViewAngle( ent, ent->client->ps.viewangles );
686 		}
687 	} else {
688 */
689 	trap_GetUsercmd( ent->client - level.clients, &ent->client->pers.cmd );
690 	SetClientViewAngle( ent, ent->client->ps.viewangles );
691 //	}
692 
693 	// dead characters should stay on last frame after a loadgame
694 	if ( client->ps.eFlags & EF_DEAD ) {
695 		client->ps.eFlags |= EF_FORCE_END_FRAME;
696 	}
697 
698 	// RF, disabled, not required now with screen fading, causes characters to possibly spawn events
699 	// before they are known in the cgame
700 	// run a client frame to drop exactly to the floor,
701 	// initialize animations and other things
702 	//trap_GetUsercmd( ent-g_entities, &ent->client->pers.cmd );
703 	//ent->client->ps.commandTime = ent->client->pers.cmd.serverTime - 100;
704 	//ClientThink( ent-g_entities );
705 
706 	// tell the client to reset it's cgame stuff
707 	if ( !( ent->r.svFlags & SVF_CASTAI ) ) {
708 		vmCvar_t cvar;
709 		// tell it which weapon to use after spawning in
710 		trap_Cvar_Register( &cvar, "cg_loadWeaponSelect", "0", CVAR_ROM );
711 		trap_Cvar_Set( "cg_loadWeaponSelect", va( "%i", client->ps.weapon ) );
712 		//
713 		trap_SendServerCommand( client->ps.clientNum, "map_restart\n" );
714 	}
715 }
716 
717 //=========================================================
718 
719 byte entityBuf[ 2 * sizeof( gentity_t ) ];
720 
721 /*
722 ===============
723 WriteEntity
724 ===============
725 */
WriteEntity(fileHandle_t f,gentity_t * ent)726 void WriteEntity( fileHandle_t f, gentity_t *ent ) {
727 	saveField_t *field;
728 	gentity_t temp;
729 	int length;
730 
731 	// copy the structure across, then process the fields
732 	temp = *ent;
733 
734 	// first, kill all events (assume they have been processed)
735 	memset( temp.s.events, 0, sizeof( temp.s.events ) );
736 	memset( temp.s.eventParms, 0, sizeof( temp.s.eventParms ) );
737 	temp.s.eventSequence = 0;
738 
739 	// change the pointers to lengths or indexes
740 	for ( field = gentityFields_17 ; field->type ; field++ )
741 	{
742 		WriteField1( field, (byte *)&temp );
743 	}
744 	// TTimo
745 	// show_bug.cgi?id=434
746 	WriteField1( gentityFields_18, (byte *)&temp );
747 
748 	// write the block
749 	//if (!G_SaveWrite (&temp, sizeof(temp), f)) G_SaveWriteError();
750 	length = G_Save_Encode( (byte *)&temp, entityBuf, sizeof( temp ), sizeof( entityBuf ) );
751 	if ( !G_SaveWrite( &length, sizeof( length ), f ) ) {
752 		G_SaveWriteError();
753 	}
754 	if ( !G_SaveWrite( &entityBuf, length, f ) ) {
755 		G_SaveWriteError();
756 	}
757 
758 	// now write any allocated data following the edict
759 	for ( field = gentityFields_17 ; field->type ; field++ )
760 	{
761 		WriteField2( f, field, (byte *)ent );
762 	}
763 	// TTimo
764 	// show_bug.cgi?id=434
765 	WriteField2( f, gentityFields_18, (byte *)ent );
766 
767 }
768 
769 /*
770 ===============
771 ReadEntity
772 ===============
773 */
ReadEntity(fileHandle_t f,gentity_t * ent,int size)774 void ReadEntity( fileHandle_t f, gentity_t *ent, int size ) {
775 	saveField_t *field;
776 	ignoreField_t *ifield;
777 	gentity_t temp = {{0}}, backup, backup2;
778 	vmCvar_t cvar;
779 	int decodedSize;
780 
781 	backup = *ent;
782 
783 	if ( ver == 10 ) {
784 		trap_FS_Read( &temp, size, f );
785 	} else {
786 		// read the encoded chunk
787 		trap_FS_Read( &decodedSize, sizeof( int ), f );
788 		if ( decodedSize > sizeof( entityBuf ) ) {
789 			G_Error( "G_LoadGame: encoded chunk is greater than buffer" );
790 		}
791 		trap_FS_Read( entityBuf, decodedSize, f );
792 		// decode it
793 		G_Save_Decode( entityBuf, decodedSize, (byte *)&temp, sizeof( temp ) );
794 	}
795 
796 	// convert any fields back to the correct data
797 	for ( field = gentityFields_17 ; field->type ; field++ )
798 	{
799 		ReadField( f, field, (byte *)&temp );
800 	}
801 	// TTimo
802 	// show_bug.cgi?id=434
803 	if ( ver >= 18 ) {
804 		ReadField( f, gentityFields_18, (byte *)&temp );
805 	}
806 
807 	// backup any fields that we don't want to read in
808 	for ( ifield = gentityIgnoreFields ; ifield->len ; ifield++ )
809 	{
810 		memcpy( ( (byte *)&temp ) + ifield->ofs, ( (byte *)ent ) + ifield->ofs, ifield->len );
811 	}
812 
813 	// kill all events (assume they have been processed)
814 	if ( !temp.freeAfterEvent ) {
815 		temp.s.event = 0;
816 		memset( temp.s.events, 0, sizeof( temp.s.events ) );
817 		memset( temp.s.eventParms, 0, sizeof( temp.s.eventParms ) );
818 		temp.s.eventSequence = 0;
819 		temp.eventTime = 0;
820 	}
821 
822 	// now copy the temp structure into the existing structure
823 	memcpy( ent, &temp, size );
824 
825 	// notify server of changes in position/orientation
826 	if ( ent->r.linked && ( !( ent->r.svFlags & SVF_CASTAI ) || !ent->aiInactive ) ) {
827 		trap_LinkEntity( ent );
828 	} else {
829 		trap_UnlinkEntity( ent );
830 	}
831 
832 	// if this is a mover, check areaportals
833 	if ( ent->s.eType == ET_MOVER && ent->moverState != backup.moverState ) {
834 		if ( ent->teammaster == ent || !ent->teammaster ) {
835 			if ( ent->moverState == MOVER_POS1ROTATE || ent->moverState == MOVER_POS1 ) {
836 				// closed areaportal
837 				trap_AdjustAreaPortalState( ent, qfalse );
838 			} else {    // must be open
839 				// portals are always opened before the mover starts to open, so we must move
840 				// it back to the start position, link, set portals, then move it back
841 				backup2 = *ent;
842 				*ent = backup;
843 				// link it at original position
844 				trap_LinkEntity( ent );
845 				// set portals
846 				trap_AdjustAreaPortalState( ent, qtrue );
847 				// put it back
848 				*ent = backup2;
849 				trap_LinkEntity( ent );
850 			}
851 		}
852 	}
853 
854 	// check for blocking AAS at save time
855 	if ( ent->AASblocking ) {
856 		G_SetAASBlockingEntity( ent, qtrue );
857 	}
858 
859 	// check for this being a tagconnect entity
860 	if ( ent->tagName && ent->tagParent ) {   // the parent might not be there yet
861 		G_ProcessTagConnect( ent, qfalse );
862 	}
863 
864 	// if this is a camera, then make it the current global camera (silly global variables..)
865 	if ( ent->s.eType == ET_CAMERA ) {
866 		g_camEnt = ent;
867 	}
868 
869 	// if this is the player
870 	if ( ent->s.number == 0 ) {
871 		int i;
872 
873 		trap_Cvar_Set( "cg_yougotMail", "0" );
874 
875 		// set up met objectives
876 		for ( i = 0; i < sizeof( ent->missionObjectives ) * 8; i++ ) {
877 			if ( ent->missionObjectives & ( 1 << i ) ) {
878 				trap_Cvar_Register( &cvar, va( "g_objective%i", i + 1 ), "1", CVAR_ROM ); //set g_objective<n> cvar
879 				trap_Cvar_Set( va( "g_objective%i", i + 1 ), "1" );                           // set it to make sure
880 			} else {
881 				trap_Cvar_Set( va( "g_objective%i", i + 1 ), "0" );                           // make sure it's clear
882 			}
883 		}
884 
885 /*
886 		// set up current episode (for notebook de-briefing tabs)
887 		trap_Cvar_Register( &cvar, "g_episode", "0", CVAR_ROM );
888 		trap_Cvar_Set( "g_episode", va( "%d", ent->missionLevel ) );
889 */
890 	}
891 
892 }
893 
894 //=========================================================
895 
896 byte castStateBuf[ 2 * sizeof( cast_state_t ) ];
897 
898 /*
899 ===============
900 WriteCastState
901 ===============
902 */
WriteCastState(fileHandle_t f,cast_state_t * cs)903 void WriteCastState( fileHandle_t f, cast_state_t *cs ) {
904 	saveField_t *field;
905 	cast_state_t temp;
906 	int length;
907 
908 	// copy the structure across, then process the fields
909 	temp = *cs;
910 
911 	// change the pointers to lengths or indexes
912 	for ( field = castStateFields ; field->type ; field++ )
913 	{
914 		WriteField1( field, (byte *)&temp );
915 	}
916 
917 	// write the block
918 	//if (!G_SaveWrite (&temp, sizeof(temp), f)) G_SaveWriteError();
919 	length = G_Save_Encode( (byte *)&temp, castStateBuf, sizeof( temp ), sizeof( castStateBuf ) );
920 	if ( !G_SaveWrite( &length, sizeof( length ), f ) ) {
921 		G_SaveWriteError();
922 	}
923 	if ( !G_SaveWrite( &castStateBuf, length, f ) ) {
924 		G_SaveWriteError();
925 	}
926 
927 	// now write any allocated data following the edict
928 	for ( field = castStateFields ; field->type ; field++ )
929 	{
930 		WriteField2( f, field, (byte *)cs );
931 	}
932 
933 }
934 
935 /*
936 ===============
937 ReadCastState
938 ===============
939 */
ReadCastState(fileHandle_t f,cast_state_t * cs,int size)940 void ReadCastState( fileHandle_t f, cast_state_t *cs, int size ) {
941 	saveField_t *field;
942 	ignoreField_t *ifield;
943 	cast_state_t temp;
944 	int decodedSize;
945 
946 	if ( ver == 10 ) {
947 		trap_FS_Read( &temp, size, f );
948 	} else {
949 		// read the encoded chunk
950 		trap_FS_Read( &decodedSize, sizeof( int ), f );
951 		if ( decodedSize > sizeof( castStateBuf ) ) {
952 			G_Error( "G_LoadGame: encoded chunk is greater than buffer" );
953 		}
954 		trap_FS_Read( castStateBuf, decodedSize, f ); \
955 		// decode it
956 		G_Save_Decode( castStateBuf, decodedSize, (byte *)&temp, sizeof( temp ) );
957 	}
958 
959 	// convert any feilds back to the correct data
960 	for ( field = castStateFields ; field->type ; field++ )
961 	{
962 		ReadField( f, field, (byte *)&temp );
963 	}
964 
965 	// backup any fields that we don't want to read in
966 	for ( ifield = castStateIgnoreFields ; ifield->len ; ifield++ )
967 	{
968 		memcpy( ( (byte *)&temp ) + ifield->ofs, ( (byte *)cs ) + ifield->ofs, ifield->len );
969 	}
970 
971 	// now copy the temp structure into the existing structure
972 	memcpy( cs, &temp, size );
973 
974 	// if this is an AI, init the cur_ps
975 	if ( cs->bs && !cs->deathTime ) {
976 		// clear out the delta_angles
977 		memset( g_entities[cs->entityNum].client->ps.delta_angles, 0, sizeof( g_entities[cs->entityNum].client->ps.delta_angles ) );
978 		VectorCopy( cs->ideal_viewangles, cs->viewangles );
979 		VectorCopy( cs->ideal_viewangles, g_entities[cs->entityNum].client->ps.viewangles );
980 		// copy the ps
981 		memcpy( &cs->bs->cur_ps, &g_entities[cs->entityNum].client->ps, sizeof( playerState_t ) );
982 		// make sure they think right away
983 		cs->lastThink = -9999;
984 		// reset the input
985 		trap_EA_ResetInput( cs->entityNum, NULL );
986 	}
987 
988 }
989 
990 
991 /*
992 ==============
993 WriteTime
994 ==============
995 */
WriteTime(fileHandle_t f)996 void WriteTime( fileHandle_t f ) {
997 	qtime_t tm;
998 
999 	// just save it all so it can be interpreted as desired
1000 	trap_RealTime( &tm );
1001 	G_SaveWrite( &tm.tm_sec,       sizeof( tm.tm_sec ),  f );     /* seconds after the minute - [0,59] */
1002 	G_SaveWrite( &tm.tm_min,       sizeof( tm.tm_min ),  f );     /* minutes after the hour - [0,59] */
1003 	G_SaveWrite( &tm.tm_hour,  sizeof( tm.tm_hour ), f );     /* hours since midnight - [0,23] */
1004 	G_SaveWrite( &tm.tm_mday,  sizeof( tm.tm_mday ), f );     /* day of the month - [1,31] */
1005 	G_SaveWrite( &tm.tm_mon,       sizeof( tm.tm_mon ),  f );     /* months since January - [0,11] */
1006 	G_SaveWrite( &tm.tm_year,  sizeof( tm.tm_year ), f );     /* years since 1900 */
1007 	G_SaveWrite( &tm.tm_wday,  sizeof( tm.tm_wday ), f );     /* days since Sunday - [0,6] */
1008 	G_SaveWrite( &tm.tm_yday,  sizeof( tm.tm_yday ), f );     /* days since January 1 - [0,365] */
1009 	G_SaveWrite( &tm.tm_isdst, sizeof( tm.tm_isdst ),f );     /* daylight savings time flag */
1010 }
1011 
1012 /*
1013 ==============
1014 ReadTime
1015 ==============
1016 */
ReadTime(fileHandle_t f,qtime_t * tm)1017 void ReadTime( fileHandle_t f, qtime_t *tm ) {
1018 	trap_FS_Read( &tm->tm_sec, sizeof( tm->tm_sec ), f );
1019 	trap_FS_Read( &tm->tm_min, sizeof( tm->tm_min ), f );
1020 	trap_FS_Read( &tm->tm_hour, sizeof( tm->tm_hour ), f );
1021 	trap_FS_Read( &tm->tm_mday, sizeof( tm->tm_mday ), f );
1022 	trap_FS_Read( &tm->tm_mon, sizeof( tm->tm_mon ), f );
1023 	trap_FS_Read( &tm->tm_year, sizeof( tm->tm_year ), f );
1024 	trap_FS_Read( &tm->tm_wday, sizeof( tm->tm_wday ), f );
1025 	trap_FS_Read( &tm->tm_yday, sizeof( tm->tm_yday ), f );
1026 	trap_FS_Read( &tm->tm_isdst, sizeof( tm->tm_isdst ), f );
1027 }
1028 
1029 /*
1030 ==============
1031 G_Save_TimeStr
1032 ==============
1033 */
G_Save_TimeStr(void)1034 char *G_Save_TimeStr( void ) {
1035 	qtime_t tm;
1036 	//
1037 	trap_RealTime( &tm );
1038 	//
1039 	return va( "%2i:%s%i:%s%i %s",
1040 			   ( 1 + ( tm.tm_hour + 11 ) % 12 ), // 12 hour format
1041 			   ( tm.tm_min > 9 ? "" : "0" ),    // minute padding
1042 			   tm.tm_min,
1043 			   ( tm.tm_sec > 9 ? "" : "0" ),    // second padding
1044 			   tm.tm_sec,
1045 			   ( tm.tm_hour < 12 ? "am" : "pm" ) );
1046 }
1047 
1048 static char *monthStr[12] =
1049 {
1050 	"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
1051 };
1052 
1053 /*
1054 ==============
1055 G_Save_DateStr
1056 ==============
1057 */
G_Save_DateStr(void)1058 char *G_Save_DateStr( void ) {
1059 	qtime_t tm;
1060 	//
1061 	trap_RealTime( &tm );
1062 	//
1063 	return va( "%s %i, %i",
1064 			   monthStr[tm.tm_mon],
1065 			   tm.tm_mday,
1066 			   1900 + tm.tm_year );
1067 }
1068 
1069 //=========================================================
1070 
1071 static char infoString[SAVE_INFOSTRING_LENGTH];
1072 
1073 
1074 #define SA_MOVEDSTUFF   15  // moved time/music/skill in ver 15
1075 #define SA_ADDEDMUSIC   8   //
1076 #define SA_ADDEDFOG     16  //
1077 
1078 /*
1079 ===============
1080 G_SaveGame
1081 
1082   returns qtrue if successful
1083 
1084   TODO: have G_SaveWrite return the number of byte's written, so if it doesn't
1085   succeed, we can abort the save, and not save the file. This means we should
1086   save to a temporary name, then copy it across to the real name after success,
1087   so full disks don't result in lost saved games.
1088 ===============
1089 */
G_SaveGame(char * username)1090 qboolean G_SaveGame( char *username ) {
1091 	char filename[MAX_QPATH];
1092 	char mapstr[MAX_QPATH];
1093 	char leveltime[MAX_QPATH];
1094 	char healthstr[MAX_QPATH];
1095 	vmCvar_t mapname, episode;
1096 	fileHandle_t f;
1097 	int i, len;
1098 	gentity_t   *ent;
1099 	gclient_t   *cl;
1100 	cast_state_t    *cs;
1101 	int playtime, minutes;
1102 
1103 	//if (reloading)
1104 	//	return qtrue;	// actually this should be qtrue, but we should make it silent during reloading
1105 
1106 	if ( g_entities[0].health <= 0 ) { // no save when dead
1107 		return qtrue;
1108 	}
1109 
1110 	if ( g_gametype.integer != GT_SINGLE_PLAYER ) {    // don't allow saves in MP
1111 		return qtrue;
1112 	}
1113 
1114 	G_DPrintf( "G_SaveGame '%s'\n", username );
1115 
1116 	// update the playtime
1117 	AICast_AgePlayTime( 0 );
1118 
1119 	if ( !username ) {
1120 		username = "current";
1121 	}
1122 
1123 	// validate the filename
1124 	for ( i = 0; i < strlen( username ); i++ ) {
1125 		if ( !Q_isforfilename( username[i] ) && username[i] != '\\' ) { // (allow '\\' so games can be saved in subdirs)
1126 			G_Printf( "G_SaveGame: '%s'.  Invalid character (%c) in filename. Must use alphanumeric characters only.\n", username, username[i] );
1127 			return qtrue;
1128 		}
1129 	}
1130 
1131 	saveByteCount = 0;
1132 
1133 	// open the file
1134 	Com_sprintf( filename, MAX_QPATH, "save\\temp.svg" );
1135 	if ( trap_FS_FOpenFile( filename, &f, FS_WRITE ) < 0 ) {
1136 		G_Error( "G_SaveGame: cannot open file for saving\n" );
1137 	}
1138 
1139 	// write the version
1140 	i = SAVE_VERSION;
1141 	// TTimo
1142 	// show_bug.cgi?id=434
1143 	// make sure we keep the global version number consistent with what we are doing
1144 	ver = SAVE_VERSION;
1145 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1146 		G_SaveWriteError();
1147 	}
1148 
1149 	// write the mapname
1150 	trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
1151 	Com_sprintf( mapstr, MAX_QPATH, "%s", mapname.string );
1152 	if ( !G_SaveWrite( mapstr, MAX_QPATH, f ) ) {
1153 		G_SaveWriteError();
1154 	}
1155 
1156 	// write out the level time
1157 	if ( !G_SaveWrite( &level.time, sizeof( level.time ), f ) ) {
1158 		G_SaveWriteError();
1159 	}
1160 
1161 	// write the totalPlayTime
1162 	i = caststates[0].totalPlayTime;
1163 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1164 		G_SaveWriteError();
1165 	}
1166 
1167 //----(SA)	had to add 'episode' tracking.
1168 	// this is only set in the map scripts, and was previously only handled in the menu's
1169 
1170 	// write the 'episode'
1171 	if ( SAVE_VERSION >= 13 ) {
1172 		trap_Cvar_Register( &episode, "g_episode", "0", CVAR_ROM );
1173 
1174 		i = episode.integer;
1175 		if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1176 			G_SaveWriteError();
1177 		}
1178 
1179 	}
1180 //----(SA)	end
1181 
1182 
1183 
1184 	playtime = caststates[0].totalPlayTime;
1185 	if ( playtime < 3600000 ) {
1186 		minutes = ( playtime / 1000 ) / 60;
1187 	} else {
1188 		minutes = ( ( playtime % 3600000 ) / 1000 ) / 60; // handle hours in a map
1189 
1190 	}
1191 	// create and write the info string
1192 	// (SA) I made another cvar so there's no confusion
1193 	Q_strncpyz( mapstr, mapname.string, sizeof( mapstr ) );
1194 	for ( i = 0; i < strlen( mapstr ); i++ ) mapstr[i] = toupper( mapstr[i] );
1195 	memset( infoString, 0, sizeof( infoString ) );
1196 
1197 	trap_Cvar_VariableStringBuffer( "svg_timestring", leveltime, sizeof( leveltime ) );
1198 	if ( !strlen( leveltime ) ) {
1199 		Com_sprintf( leveltime, sizeof( leveltime ), "Leveltime" );
1200 	}
1201 
1202 	trap_Cvar_VariableStringBuffer( "svg_healthstring", healthstr, sizeof( healthstr ) );
1203 	if ( !strlen( healthstr ) ) {
1204 		Com_sprintf( healthstr, sizeof( healthstr ), "Health" );
1205 	}
1206 
1207 
1208 //	Com_sprintf( infoString, sizeof(infoString), "Mission: %s\nDate: %s\nTime: %s\nGametime: %s\nHealth: %i",
1209 	Com_sprintf( infoString, sizeof( infoString ), "%s\n%s: %s\n%s: %i",
1210 				 mapstr,
1211 				 leveltime,
1212 //		G_Save_DateStr(),
1213 //		G_Save_TimeStr(),
1214 				 va( "%2ih%s%im%s%is",
1215 					 ( ( ( playtime / 1000 ) / 60 ) / 60 ), // hour
1216 					 ( minutes > 9 ? "" : "0" ), // minute padding
1217 					 minutes,
1218 					 ( ( playtime / 1000 ) % 60 > 9 ? "" : "0" ), // second padding
1219 					 ( ( playtime / 1000 ) % 60 ) ),
1220 				 healthstr,
1221 				 g_entities[0].health );
1222 	// write it out
1223 	// length
1224 	i = strlen( infoString );
1225 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1226 		G_SaveWriteError();
1227 	}
1228 	// string
1229 	if ( !G_SaveWrite( infoString, strlen( infoString ), f ) ) {
1230 		G_SaveWriteError();
1231 	}
1232 
1233 
1234 	// write out current time/date info
1235 	WriteTime( f );
1236 
1237 //----(SA)	added
1238 
1239 //----(SA)	end
1240 
1241 	// write music
1242 	trap_Cvar_Register( &musicCvar, "s_currentMusic", "", CVAR_ROM );
1243 	if ( !G_SaveWrite( musicCvar.string, MAX_QPATH, f ) ) {
1244 		G_SaveWriteError();
1245 	}
1246 
1247 //----(SA)	write fog
1248 //	trap_Cvar_VariableStringBuffer( "sg_fog", infoString, sizeof(infoString) );
1249 	trap_GetConfigstring( CS_FOGVARS, infoString, sizeof( infoString ) );
1250 
1251 	i = strlen( infoString );
1252 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1253 		G_SaveWriteError();
1254 	}
1255 	// if there's fog info to save
1256 	if ( !i ) {
1257 		Q_strncpyz( &infoString[0], "none", sizeof( infoString ) );
1258 	}
1259 
1260 	if ( !G_SaveWrite( infoString, strlen( infoString ), f ) ) {
1261 		G_SaveWriteError();
1262 	}
1263 //----(SA)	end
1264 
1265 	// save the skill level
1266 	if ( !G_SaveWrite( &g_gameskill.integer, sizeof( g_gameskill.integer ), f ) ) {
1267 		G_SaveWriteError();
1268 	}
1269 
1270 	// write out the entity structures
1271 	i = sizeof( gentity_t );
1272 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1273 		G_SaveWriteError();
1274 	}
1275 	for ( i = 0 ; i < level.num_entities ; i++ )
1276 	{
1277 		ent = &g_entities[i];
1278 		if ( !ent->inuse || ent->s.number == ENTITYNUM_WORLD ) {
1279 			continue;
1280 		}
1281 		if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1282 			G_SaveWriteError();
1283 		}
1284 		WriteEntity( f, ent );
1285 	}
1286 	i = -1;
1287 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1288 		G_SaveWriteError();
1289 	}
1290 
1291 	// write out the client structures
1292 	i = sizeof( gclient_t );
1293 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1294 		G_SaveWriteError();
1295 	}
1296 	for ( i = 0 ; i < MAX_CLIENTS ; i++ )
1297 	{
1298 		cl = &level.clients[i];
1299 		if ( cl->pers.connected != CON_CONNECTED ) {
1300 			continue;
1301 		}
1302 		if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1303 			G_SaveWriteError();
1304 		}
1305 		WriteClient( f, cl );
1306 	}
1307 	i = -1;
1308 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1309 		G_SaveWriteError();
1310 	}
1311 
1312 	// write out the cast_state structures
1313 	i = sizeof( cast_state_t );
1314 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1315 		G_SaveWriteError();
1316 	}
1317 	for ( i = 0 ; i < level.numConnectedClients ; i++ )
1318 	{
1319 		cs = &caststates[i];
1320 		if ( !g_entities[i].inuse ) {
1321 			continue;
1322 		}
1323 		if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1324 			G_SaveWriteError();
1325 		}
1326 		WriteCastState( f, cs );
1327 	}
1328 
1329 	i = -1;
1330 	if ( !G_SaveWrite( &i, sizeof( i ), f ) ) {
1331 		G_SaveWriteError();
1332 	}
1333 
1334 	trap_FS_FCloseFile( f );
1335 
1336 	// check the byte count
1337 	if ( ( len = trap_FS_FOpenFile( filename, &f, FS_READ ) ) != saveByteCount ) {
1338 		trap_FS_FCloseFile( f );
1339 		G_SaveWriteError();
1340 		return qfalse;
1341 	}
1342 
1343 	trap_FS_FCloseFile( f );
1344 
1345 	// now rename the file to the actual file
1346 	Com_sprintf( mapstr, MAX_QPATH, "save\\%s.svg", username );
1347 	trap_FS_Rename( filename, mapstr );
1348 
1349 	// double check that it saved ok
1350 	if ( ( len = trap_FS_FOpenFile( mapstr, &f, FS_READ ) ) != saveByteCount ) {
1351 		trap_FS_FCloseFile( f );
1352 		G_SaveWriteError();
1353 		return qfalse;
1354 	}
1355 
1356 	trap_FS_FCloseFile( f );
1357 
1358 	return qtrue;
1359 }
1360 
1361 
1362 
1363 
1364 
1365 /*
1366 ===============
1367 G_LoadGame
1368 
1369   Always loads in "current.svg". So if loading a specific savegame, first copy it to that.
1370 ===============
1371 */
G_LoadGame(char * filename)1372 void G_LoadGame( char *filename ) {
1373 	char mapname[MAX_QPATH];
1374 	char mapstr[MAX_QPATH];
1375 	fileHandle_t f;
1376 	int i, leveltime, size, last;
1377 	gentity_t   *ent;
1378 	gclient_t   *cl;
1379 	cast_state_t    *cs;
1380 	qtime_t tm;
1381 	qboolean serverEntityUpdate = qfalse;
1382 	vmCvar_t episode;
1383 
1384 	if ( g_gametype.integer != GT_SINGLE_PLAYER ) {    // don't allow loads in MP
1385 		return;
1386 	}
1387 
1388 	if ( saveGamePending ) {
1389 		return;
1390 	}
1391 
1392 	G_DPrintf( "G_LoadGame '%s'\n", filename );
1393 
1394 	// enforce the "current" savegame, since that is used for all loads
1395 	filename = "save\\current.svg";
1396 
1397 	// open the file
1398 	if ( trap_FS_FOpenFile( filename, &f, FS_READ ) < 0 ) {
1399 		G_Error( "G_LoadGame: savegame '%s' not found\n", filename );
1400 	}
1401 
1402 	// read the version
1403 	trap_FS_Read( &i, sizeof( i ), f );
1404 	// TTimo
1405 	// show_bug.cgi?id=434
1406 	// 17 is the only version actually out in the wild
1407 	if ( i != SAVE_VERSION && i != 17 && i != 13 && i != 14 && i != 15 ) {    // 13 is beta7, 14 is pre "SA_MOVEDSTUFF"
1408 		trap_FS_FCloseFile( f );
1409 		G_Error( "G_LoadGame: savegame '%s' is wrong version (%i, should be %i)\n", filename, i, SAVE_VERSION );
1410 	}
1411 	ver = i;
1412 
1413 	if ( ver == 17 ) {
1414 		// 17 saved games can be buggy (bug #434), let's just warn about it
1415 		G_Printf( "WARNING: backward compatibility, loading a version 17 saved game.\n"
1416 				  "some version 17 saved games may cause crashes during play.\n" );
1417 	}
1418 
1419 	// read the mapname (this is only used in the sever exe, so just discard it)
1420 	trap_FS_Read( mapname, MAX_QPATH, f );
1421 	Com_sprintf( mapstr, MAX_QPATH, "%s", mapname );
1422 
1423 	// read the level time
1424 	trap_FS_Read( &i, sizeof( i ), f );
1425 	leveltime = i;
1426 
1427 	// read the totalPlayTime
1428 	trap_FS_Read( &i, sizeof( i ), f );
1429 	if ( i > g_totalPlayTime.integer ) {
1430 		trap_Cvar_Set( "g_totalPlayTime", va( "%i", i ) );
1431 	}
1432 
1433 //----(SA)	had to add 'episode' tracking.
1434 	// this is only set in the map scripts, and was previously only handled in the menu's
1435 	// read the 'episode'
1436 	if ( ver >= 13 ) {
1437 		trap_FS_Read( &i, sizeof( i ), f );
1438 		trap_Cvar_Register( &episode, "g_episode", "0", CVAR_ROM );
1439 		trap_Cvar_Set( "g_episode", va( "%i", i ) );
1440 	}
1441 //----(SA)	end
1442 
1443 	// NOTE: do not change the above order without also changing the server code
1444 
1445 	// read the info string length
1446 	trap_FS_Read( &i, sizeof( i ), f );
1447 
1448 	// read the info string
1449 	trap_FS_Read( infoString, i, f );
1450 
1451 	if ( ver >= SA_MOVEDSTUFF ) {
1452 		if ( ver > SA_ADDEDMUSIC ) {
1453 			// read current time/date info
1454 			ReadTime( f, &tm );
1455 
1456 			// read music
1457 			trap_FS_Read( musicString, MAX_QPATH, f );
1458 
1459 			if ( strlen( musicString ) ) {
1460 				trap_Cvar_Register( &musicCvar, "s_currentMusic", "", CVAR_ROM ); // get current music
1461 				if ( Q_stricmp( musicString, musicCvar.string ) ) {      // it's different than what's playing, so fade out and queue up
1462 //					trap_SendServerCommand(-1, "mu_fade 0 1000\n");
1463 //					trap_SetConfigstring( CS_MUSIC_QUEUE, musicString);
1464 					trap_SendServerCommand( -1, va( "mu_start %s 1000\n", musicString ) );       // (SA) trying this instead
1465 				}
1466 			}
1467 
1468 		}
1469 
1470 //----(SA)	added
1471 		if ( ver >= SA_ADDEDFOG ) {
1472 			char *p;
1473 			int k;
1474 
1475 			// get length
1476 			trap_FS_Read( &i, sizeof( i ), f );
1477 			// get fog string
1478 			trap_FS_Read( infoString, i, f );
1479 			infoString[i] = 0;
1480 
1481 			// set the configstring so the 'savegame current' has good fog
1482 
1483 			if ( !Q_stricmp( infoString, "0" ) ) { // no fog
1484 				trap_Cvar_Set( "r_savegameFogColor", "none" );
1485 			} else {
1486 
1487 				// send it off to get set on the client
1488 				for ( p = &infoString[0],k = 0; *p; p++ ) {
1489 					if ( *p == ' ' ) {
1490 						k++;
1491 					}
1492 					if ( k == 6 ) {  // the last parameter
1493 						infoString[p - infoString + 1] = '0';
1494 						infoString[p - infoString + 2] = 0;
1495 						break;
1496 					}
1497 				}
1498 				trap_Cvar_Set( "r_savegameFogColor", infoString );
1499 			}
1500 			trap_SetConfigstring( CS_FOGVARS, infoString );
1501 		}
1502 //----(SA)	end
1503 
1504 		if ( ver > 13 ) {
1505 			// read the game skill
1506 			trap_FS_Read( &i, sizeof( i ), f );
1507 			// set the skill level
1508 			trap_Cvar_Set( "g_gameskill", va( "%i",i ) );
1509 			// update this
1510 			aicast_skillscale = (float)i / (float)GSKILL_MAX;
1511 		}
1512 	}
1513 
1514 	// reset all AAS blocking entities
1515 	trap_AAS_SetAASBlockingEntity( vec3_origin, vec3_origin, -1 );
1516 
1517 	// Don't read in this stuff for mid-game cutscenes
1518 	if ( !( !Q_stricmpn( mapstr, "cutscene6", 9 ) || !Q_stricmpn( mapstr, "cutscene9", 9 ) || !Q_stricmpn( mapstr, "cutscene11", 10 ) || !Q_stricmpn( mapstr, "cutscene14", 10 ) ) ) {
1519 		// read the entity structures
1520 		trap_FS_Read( &i, sizeof( i ), f );
1521 		size = i;
1522 		last = 0;
1523 		while ( 1 )
1524 		{
1525 			trap_FS_Read( &i, sizeof( i ), f );
1526 			if ( i < 0 ) {
1527 				break;
1528 			}
1529 			if ( i >= MAX_GENTITIES ) {
1530 				trap_FS_FCloseFile( f );
1531 				G_Error( "G_LoadGame: entitynum out of range (%i, MAX = %i)\n", i, MAX_GENTITIES );
1532 			}
1533 			if ( i >= level.num_entities ) {  // notify server
1534 				level.num_entities = i;
1535 				serverEntityUpdate = qtrue;
1536 			}
1537 			ent = &g_entities[i];
1538 			ReadEntity( f, ent, size );
1539 			// free all entities that we skipped
1540 			for ( ; last < i; last++ ) {
1541 				if ( g_entities[last].inuse && i != ENTITYNUM_WORLD ) {
1542 					if ( last < MAX_CLIENTS ) {
1543 						trap_DropClient( last, "" );
1544 					} else {
1545 						G_FreeEntity( &g_entities[last] );
1546 					}
1547 				}
1548 			}
1549 			last = i + 1;
1550 		}
1551 
1552 		// clear all remaining entities
1553 		for ( ent = &g_entities[last] ; last < MAX_GENTITIES ; last++, ent++ ) {
1554 			memset( ent, 0, sizeof( *ent ) );
1555 			ent->classname = "freed";
1556 			ent->freetime = level.time;
1557 			ent->inuse = qfalse;
1558 		}
1559 
1560 		// read the client structures
1561 		trap_FS_Read( &i, sizeof( i ), f );
1562 		size = i;
1563 		while ( 1 )
1564 		{
1565 			trap_FS_Read( &i, sizeof( i ), f );
1566 			if ( i < 0 ) {
1567 				break;
1568 			}
1569 			if ( i > MAX_CLIENTS ) {
1570 				trap_FS_FCloseFile( f );
1571 				G_Error( "G_LoadGame: clientnum out of range\n" );
1572 			}
1573 			cl = &level.clients[i];
1574 			if ( cl->pers.connected == CON_DISCONNECTED ) {
1575 				trap_FS_FCloseFile( f );
1576 				G_Error( "G_LoadGame: client mis-match in savegame" );
1577 			}
1578 			ReadClient( f, cl, size );
1579 		}
1580 
1581 		// read the cast_state structures
1582 		trap_FS_Read( &i, sizeof( i ), f );
1583 		size = i;
1584 		while ( 1 )
1585 		{
1586 			trap_FS_Read( &i, sizeof( i ), f );
1587 			if ( i < 0 ) {
1588 				break;
1589 			}
1590 			if ( i > MAX_CLIENTS ) {
1591 				trap_FS_FCloseFile( f );
1592 				G_Error( "G_LoadGame: clientnum out of range\n" );
1593 			}
1594 			cs = &caststates[i];
1595 			ReadCastState( f, cs, size );
1596 		}
1597 
1598 		// inform server of entity count if it has increased
1599 		if ( serverEntityUpdate ) {
1600 			// let the server system know that there are more entities
1601 			trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
1602 								 &level.clients[0].ps, sizeof( level.clients[0] ) );
1603 		}
1604 
1605 	}
1606 
1607 //----(SA)	moved these up in ver 15
1608 	if ( ver < SA_MOVEDSTUFF ) {
1609 		if ( ver > SA_ADDEDMUSIC ) {
1610 			// read current time/date info
1611 			ReadTime( f, &tm );
1612 
1613 			// read music
1614 			trap_FS_Read( musicString, MAX_QPATH, f );
1615 
1616 			if ( strlen( musicString ) ) {
1617 				trap_Cvar_Register( &musicCvar, "s_currentMusic", "", CVAR_ROM ); // get current music
1618 				if ( Q_stricmp( musicString, musicCvar.string ) ) {      // it's different than what's playing, so fade out and queue up
1619 					trap_SendServerCommand( -1, "mu_fade 0 1000\n" );
1620 					trap_SetConfigstring( CS_MUSIC_QUEUE, musicString );
1621 				}
1622 			}
1623 		}
1624 
1625 		if ( ver > 13 ) {
1626 			// read the game skill
1627 			trap_FS_Read( &i, sizeof( i ), f );
1628 			// set the skill level
1629 			trap_Cvar_Set( "g_gameskill", va( "%i",i ) );
1630 			// update this
1631 			aicast_skillscale = (float)i / (float)GSKILL_MAX;
1632 		}
1633 	}
1634 //----(SA)	end moved
1635 
1636 	trap_FS_FCloseFile( f );
1637 
1638 	// now increment the attempts field and update totalplaytime according to cvar
1639 	trap_Cvar_Update( &g_attempts );
1640 	trap_Cvar_Set( "g_attempts", va( "%i", g_attempts.integer + 1 ) );
1641 	caststates[0].attempts = g_attempts.integer + 1;
1642 	caststates[0].lastLoadTime = level.time;
1643 	if ( caststates[0].totalPlayTime < g_totalPlayTime.integer ) {
1644 		caststates[0].totalPlayTime = g_totalPlayTime.integer;
1645 	}
1646 
1647 	level.lastLoadTime = leveltime;
1648 
1649 /*
1650 	// always save to the "current" savegame
1651 	last = level.time;
1652 	level.time = leveltime;	// use the value we just for the save time
1653 	G_SaveGame(NULL);
1654 	// additionally update the last game that was loaded
1655 	trap_Cvar_VariableStringBuffer( "savegame_filename", mapname, sizeof(mapname) );
1656 	if (strlen( mapname ) > 0 && !strstr( mapname, "autosave" )) {
1657 		// clear it out so we dont lose it after a map_restart
1658 		trap_Cvar_Set( "savegame_filename", "" );
1659 		if (strstr(mapname, ".svg")) mapname[strstr(mapname, ".svg") - mapname] = '\0';
1660 		if (strstr(mapname, "/")) {
1661 			G_SaveGame( strstr(mapname, "/") + 1 );
1662 		} else {
1663 			G_SaveGame( mapname );
1664 		}
1665 	}
1666 	// restore the correct level.time
1667 	level.time = last;
1668 */
1669 }
1670 
1671 //=========================================================
1672 
1673 /*
1674 ===============
1675 PersWriteClient
1676 ===============
1677 */
PersWriteClient(fileHandle_t f,gclient_t * cl)1678 void PersWriteClient( fileHandle_t f, gclient_t *cl ) {
1679 	persField_t *field;
1680 
1681 	// save the fields
1682 	for ( field = gclientPersFields ; field->len ; field++ )
1683 	{   // write the block
1684 		G_SaveWrite( ( void * )( (byte *)cl + field->ofs ), field->len, f );
1685 	}
1686 }
1687 
1688 /*
1689 ===============
1690 PersReadClient
1691 ===============
1692 */
PersReadClient(fileHandle_t f,gclient_t * cl)1693 void PersReadClient( fileHandle_t f, gclient_t *cl ) {
1694 	persField_t *field;
1695 
1696 	// read the fields
1697 	for ( field = gclientPersFields ; field->len ; field++ )
1698 	{   // read the block
1699 		trap_FS_Read( ( void * )( (byte *)cl + field->ofs ), field->len, f );
1700 	}
1701 }
1702 
1703 //=========================================================
1704 
1705 /*
1706 ===============
1707 PersWriteEntity
1708 ===============
1709 */
PersWriteEntity(fileHandle_t f,gentity_t * ent)1710 void PersWriteEntity( fileHandle_t f, gentity_t *ent ) {
1711 	persField_t *field;
1712 
1713 	// save the fields
1714 	for ( field = gentityPersFields ; field->len ; field++ )
1715 	{   // write the block
1716 		G_SaveWrite( ( void * )( (byte *)ent + field->ofs ), field->len, f );
1717 	}
1718 }
1719 
1720 /*
1721 ===============
1722 PersReadEntity
1723 ===============
1724 */
PersReadEntity(fileHandle_t f,gentity_t * cl)1725 void PersReadEntity( fileHandle_t f, gentity_t *cl ) {
1726 	persField_t *field;
1727 
1728 	// read the fields
1729 	for ( field = gentityPersFields ; field->len ; field++ )
1730 	{   // read the block
1731 		trap_FS_Read( ( void * )( (byte *)cl + field->ofs ), field->len, f );
1732 	}
1733 }
1734 
1735 
1736 
1737 //=========================================================
1738 
1739 /*
1740 ===============
1741 PersWriteCastState
1742 ===============
1743 */
PersWriteCastState(fileHandle_t f,cast_state_t * cs)1744 void PersWriteCastState( fileHandle_t f, cast_state_t *cs ) {
1745 	persField_t *field;
1746 
1747 	// save the fields
1748 	for ( field = castStatePersFields ; field->len ; field++ )
1749 	{   // write the block
1750 		G_SaveWrite( ( void * )( (byte *)cs + field->ofs ), field->len, f );
1751 	}
1752 }
1753 
1754 /*
1755 ===============
1756 PersReadCastState
1757 ===============
1758 */
PersReadCastState(fileHandle_t f,cast_state_t * cs)1759 void PersReadCastState( fileHandle_t f, cast_state_t *cs ) {
1760 	persField_t *field;
1761 
1762 	// read the fields
1763 	for ( field = castStatePersFields ; field->len ; field++ )
1764 	{   // read the block
1765 		trap_FS_Read( ( void * )( (byte *)cs + field->ofs ), field->len, f );
1766 	}
1767 }
1768 
1769 //=========================================================
1770 
1771 /*
1772 ===============
1773 G_SavePersistant
1774 
1775   returns qtrue if successful
1776 
1777   NOTE: only saves the local player's data, doesn't support AI characters
1778 
1779   TODO: have G_SaveWrite return the number of byte's written, so if it doesn't
1780   succeed, we can abort the save, and not save the file. This means we should
1781   save to a temporary name, then copy it across to the real name after success,
1782   so full disks don't result in lost saved games.
1783 ===============
1784 */
G_SavePersistant(char * nextmap)1785 qboolean G_SavePersistant( char *nextmap ) {
1786 	char filename[MAX_QPATH];
1787 	fileHandle_t f;
1788 	int persid;
1789 
1790 	saveByteCount = 0;
1791 
1792 	// open the file
1793 	Com_sprintf( filename, MAX_QPATH, "save\\temp.psw" );
1794 	if ( trap_FS_FOpenFile( filename, &f, FS_WRITE ) < 0 ) {
1795 		G_Error( "G_SavePersistant: cannot open '%s' for saving\n", filename );
1796 	}
1797 
1798 	// write the mapname
1799 	G_SaveWrite( nextmap, MAX_QPATH, f );
1800 
1801 	// save out the pers id
1802 	persid = trap_Milliseconds() + ( rand() & 0xffff );
1803 	G_SaveWrite( &persid, sizeof( persid ), f );
1804 	trap_Cvar_Set( "persid", va( "%i", persid ) );
1805 
1806 	// write out the entity structure
1807 	PersWriteEntity( f, &g_entities[0] );
1808 
1809 	// write out the client structure
1810 	PersWriteClient( f, &level.clients[0] );
1811 
1812 	// write out the cast_state structure
1813 	PersWriteCastState( f, AICast_GetCastState( 0 ) );
1814 
1815 	trap_FS_FCloseFile( f );
1816 
1817 	// now check that it is the correct size
1818 	Com_sprintf( filename, MAX_QPATH, "save\\temp.psw" );
1819 	if ( trap_FS_FOpenFile( filename, &f, FS_READ ) < saveByteCount ) {
1820 		trap_FS_FCloseFile( f );
1821 		G_SaveWriteError();
1822 		return qfalse;
1823 	}
1824 	trap_FS_FCloseFile( f );
1825 
1826 	// rename it to the real file
1827 	trap_FS_Rename( "save\\temp.psw", "save\\current.psw" );
1828 
1829 	// now check that it is the correct size
1830 	Com_sprintf( filename, MAX_QPATH, "save\\current.psw" );
1831 	if ( trap_FS_FOpenFile( filename, &f, FS_READ ) < saveByteCount ) {
1832 		trap_FS_FCloseFile( f );
1833 		G_SaveWriteError();
1834 		return qfalse;
1835 	}
1836 	trap_FS_FCloseFile( f );
1837 
1838 	return qtrue;
1839 }
1840 
1841 /*
1842 ===============
1843 G_LoadPersistant
1844 ===============
1845 */
G_LoadPersistant(void)1846 void G_LoadPersistant( void ) {
1847 	fileHandle_t f;
1848 	char *filename;
1849 	char mapstr[MAX_QPATH];
1850 	vmCvar_t cvar_mapname;
1851 	int persid;
1852 
1853 	filename = "save\\current.psw";
1854 
1855 	// open the file
1856 	if ( trap_FS_FOpenFile( filename, &f, FS_READ ) < 0 ) {
1857 		// not here, we shall assume they didn't want one
1858 		return;
1859 	}
1860 
1861 	// read the mapname, if it's not the same, then ignore the file
1862 	trap_FS_Read( mapstr, MAX_QPATH, f );
1863 	trap_Cvar_Register( &cvar_mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
1864 	if ( Q_strcasecmp( cvar_mapname.string, mapstr ) ) {
1865 		trap_FS_FCloseFile( f );
1866 		return;
1867 	}
1868 
1869 	// check the pers id
1870 	trap_FS_Read( &persid, sizeof( persid ), f );
1871 	if ( persid != trap_Cvar_VariableIntegerValue( "persid" ) ) {
1872 		trap_FS_FCloseFile( f );
1873 		return;
1874 	}
1875 
1876 	// read the entity structure
1877 	PersReadEntity( f, &g_entities[0] );
1878 
1879 	// read the client structure
1880 	PersReadClient( f, &level.clients[0] );
1881 
1882 	// read the cast_state structure
1883 	PersReadCastState( f, AICast_GetCastState( 0 ) );
1884 
1885 	trap_FS_FCloseFile( f );
1886 
1887 	// clear out the persid, since the persistent data has been read
1888 	trap_Cvar_Set( "persid", "0" );
1889 }
1890