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