]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/framework/Session.cpp
hello world
[icculus/iodoom3.git] / neo / framework / Session.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
9 Doom 3 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 Doom 3 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 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 Doom 3 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 #include "../idlib/precompiled.h"
30 #pragma hdrstop
31
32 #include "Session_local.h"
33
34 idCVar  idSessionLocal::com_showAngles( "com_showAngles", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
35 idCVar  idSessionLocal::com_minTics( "com_minTics", "1", CVAR_SYSTEM, "" );
36 idCVar  idSessionLocal::com_showTics( "com_showTics", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
37 idCVar  idSessionLocal::com_fixedTic( "com_fixedTic", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 10 );
38 idCVar  idSessionLocal::com_showDemo( "com_showDemo", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
39 idCVar  idSessionLocal::com_skipGameDraw( "com_skipGameDraw", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
40 idCVar  idSessionLocal::com_aviDemoSamples( "com_aviDemoSamples", "16", CVAR_SYSTEM, "" );
41 idCVar  idSessionLocal::com_aviDemoWidth( "com_aviDemoWidth", "256", CVAR_SYSTEM, "" );
42 idCVar  idSessionLocal::com_aviDemoHeight( "com_aviDemoHeight", "256", CVAR_SYSTEM, "" );
43 idCVar  idSessionLocal::com_aviDemoTics( "com_aviDemoTics", "2", CVAR_SYSTEM | CVAR_INTEGER, "", 1, 60 );
44 idCVar  idSessionLocal::com_wipeSeconds( "com_wipeSeconds", "1", CVAR_SYSTEM, "" );
45 idCVar  idSessionLocal::com_guid( "com_guid", "", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_ROM, "" );
46
47 idSessionLocal          sessLocal;
48 idSession                       *session = &sessLocal;
49
50 // these must be kept up to date with window Levelshot in guis/mainmenu.gui
51 const int PREVIEW_X = 211;
52 const int PREVIEW_Y = 31;
53 const int PREVIEW_WIDTH = 398;
54 const int PREVIEW_HEIGHT = 298;
55
56 void RandomizeStack( void ) {
57         // attempt to force uninitialized stack memory bugs
58         int             bytes = 4000000;
59         byte    *buf = (byte *)_alloca( bytes );
60
61         int     fill = rand()&255;
62         for ( int i = 0 ; i < bytes ; i++ ) {
63                 buf[i] = fill;
64         }
65 }
66
67 /*
68 =================
69 Session_RescanSI_f
70 =================
71 */
72 void Session_RescanSI_f( const idCmdArgs &args ) {
73         sessLocal.mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
74         if ( game && idAsyncNetwork::server.IsActive() ) {
75                 game->SetServerInfo( sessLocal.mapSpawnData.serverInfo );
76         }
77 }
78
79 /*
80 ==================
81 Session_Map_f
82
83 Restart the server on a different map
84 ==================
85 */
86 static void Session_Map_f( const idCmdArgs &args ) {
87         idStr           map, string;
88         findFile_t      ff;
89         idCmdArgs       rl_args;
90
91         map = args.Argv(1);
92         if ( !map.Length() ) {
93                 return;
94         }
95         map.StripFileExtension();
96
97         // make sure the level exists before trying to change, so that
98         // a typo at the server console won't end the game
99         // handle addon packs through reloadEngine
100         sprintf( string, "maps/%s.map", map.c_str() );
101         ff = fileSystem->FindFile( string, true );
102         switch ( ff ) {
103         case FIND_NO:
104                 common->Printf( "Can't find map %s\n", string.c_str() );
105                 return;
106         case FIND_ADDON:
107                 common->Printf( "map %s is in an addon pak - reloading\n", string.c_str() );
108                 rl_args.AppendArg( "map" );
109                 rl_args.AppendArg( map );
110                 cmdSystem->SetupReloadEngine( rl_args );
111                 return;
112         default:
113                 break;
114         }
115
116         cvarSystem->SetCVarBool( "developer", false );
117         sessLocal.StartNewGame( map, true );
118 }
119
120 /*
121 ==================
122 Session_DevMap_f
123
124 Restart the server on a different map in developer mode
125 ==================
126 */
127 static void Session_DevMap_f( const idCmdArgs &args ) {
128         idStr map, string;
129         findFile_t      ff;
130         idCmdArgs       rl_args;        
131
132         map = args.Argv(1);
133         if ( !map.Length() ) {
134                 return;
135         }
136         map.StripFileExtension();
137
138         // make sure the level exists before trying to change, so that
139         // a typo at the server console won't end the game
140         // handle addon packs through reloadEngine
141         sprintf( string, "maps/%s.map", map.c_str() );
142         ff = fileSystem->FindFile( string, true );
143         switch ( ff ) {
144         case FIND_NO:
145                 common->Printf( "Can't find map %s\n", string.c_str() );
146                 return;
147         case FIND_ADDON:
148                 common->Printf( "map %s is in an addon pak - reloading\n", string.c_str() );
149                 rl_args.AppendArg( "devmap" );
150                 rl_args.AppendArg( map );
151                 cmdSystem->SetupReloadEngine( rl_args );
152                 return;
153         default:
154                 break;
155         }
156
157         cvarSystem->SetCVarBool( "developer", true );
158         sessLocal.StartNewGame( map, true );
159 }
160
161 /*
162 ==================
163 Session_TestMap_f
164 ==================
165 */
166 static void Session_TestMap_f( const idCmdArgs &args ) {
167         idStr map, string;
168
169         map = args.Argv(1);
170         if ( !map.Length() ) {
171                 return;
172         }
173         map.StripFileExtension();
174
175         cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
176
177         sprintf( string, "dmap maps/%s.map", map.c_str() );
178         cmdSystem->BufferCommandText( CMD_EXEC_NOW, string );
179
180         sprintf( string, "devmap %s", map.c_str() );
181         cmdSystem->BufferCommandText( CMD_EXEC_NOW, string );
182 }
183
184 /*
185 ==================
186 Sess_WritePrecache_f
187 ==================
188 */
189 static void Sess_WritePrecache_f( const idCmdArgs &args ) {
190         if ( args.Argc() != 2 ) {
191                 common->Printf( "USAGE: writePrecache <execFile>\n" );
192                 return;
193         }
194         idStr   str = args.Argv(1);
195         str.DefaultFileExtension( ".cfg" );
196         idFile *f = fileSystem->OpenFileWrite( str );
197         declManager->WritePrecacheCommands( f );
198         renderModelManager->WritePrecacheCommands( f );
199         uiManager->WritePrecacheCommands( f );
200
201         fileSystem->CloseFile( f );
202 }
203
204 /*
205 ===============
206 idSessionLocal::MaybeWaitOnCDKey
207 ===============
208 */
209 bool idSessionLocal::MaybeWaitOnCDKey( void ) {
210         if ( authEmitTimeout > 0 ) {
211                 authWaitBox = true;
212                 sessLocal.MessageBox( MSG_WAIT, common->GetLanguageDict()->GetString( "#str_07191" ), NULL, true, NULL, NULL, true );
213                 return true;
214         }
215         return false;
216 }
217
218 /*
219 ===================
220 Session_PromptKey_f
221 ===================
222 */
223 static void Session_PromptKey_f( const idCmdArgs &args ) {
224         const char      *retkey;
225         bool            valid[ 2 ];
226         static bool recursed = false;
227
228         if ( recursed ) {
229                 common->Warning( "promptKey recursed - aborted" );
230                 return;
231         }
232         recursed = true;
233
234         do {
235                 // in case we're already waiting for an auth to come back to us ( may happen exceptionally )
236                 if ( sessLocal.MaybeWaitOnCDKey() ) {
237                         if ( sessLocal.CDKeysAreValid( true ) ) {
238                                 recursed = false;
239                                 return;
240                         }
241                 }
242                 // the auth server may have replied and set an error message, otherwise use a default
243                 const char *prompt_msg = sessLocal.GetAuthMsg();
244                 if ( prompt_msg[ 0 ] == '\0' ) {
245                         prompt_msg = common->GetLanguageDict()->GetString( "#str_04308" );
246                 }
247                 retkey = sessLocal.MessageBox( MSG_CDKEY, prompt_msg, common->GetLanguageDict()->GetString( "#str_04305" ), true, NULL, NULL, true );
248                 if ( retkey ) {
249                         if ( sessLocal.CheckKey( retkey, false, valid ) ) {
250                                 // if all went right, then we may have sent an auth request to the master ( unless the prompt is used during a net connect )
251                                 bool canExit = true;
252                                 if ( sessLocal.MaybeWaitOnCDKey() ) {
253                                         // wait on auth reply, and got denied, prompt again
254                                         if ( !sessLocal.CDKeysAreValid( true ) ) {
255                                                 // server says key is invalid - MaybeWaitOnCDKey was interrupted by a CDKeysAuthReply call, which has set the right error message
256                                                 // the invalid keys have also been cleared in the process
257                                                 sessLocal.MessageBox( MSG_OK, sessLocal.GetAuthMsg(), common->GetLanguageDict()->GetString( "#str_04310" ), true, NULL, NULL, true );
258                                                 canExit = false;
259                                         }
260                                 }
261                                 if ( canExit ) {
262                                         // make sure that's saved on file
263                                         sessLocal.WriteCDKey();
264                                         sessLocal.MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_04307" ), common->GetLanguageDict()->GetString( "#str_04305" ), true, NULL, NULL, true );
265                                         break;
266                                 }
267                         } else {
268                                 // offline check sees key invalid
269                                 // build a message about keys being wrong. do not attempt to change the current key state though
270                                 // ( the keys may be valid, but user would have clicked on the dialog anyway, that kind of thing )
271                                 idStr msg;
272                                 idAsyncNetwork::BuildInvalidKeyMsg( msg, valid );
273                                 sessLocal.MessageBox( MSG_OK, msg, common->GetLanguageDict()->GetString( "#str_04310" ), true, NULL, NULL, true );
274                         }
275                 } else if ( args.Argc() == 2 && idStr::Icmp( args.Argv(1), "force" ) == 0 ) {
276                         // cancelled in force mode
277                         cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" );
278                         cmdSystem->ExecuteCommandBuffer();
279                 }
280         } while ( retkey );
281         recursed = false;
282 }
283
284 /*
285 ===============================================================================
286
287 SESSION LOCAL
288   
289 ===============================================================================
290 */
291
292 /*
293 ===============
294 idSessionLocal::Clear
295 ===============
296 */
297 void idSessionLocal::Clear() {
298         
299         insideUpdateScreen = false;
300         insideExecuteMapChange = false;
301
302         loadingSaveGame = false;
303         savegameFile = NULL;
304         savegameVersion = 0;
305
306         currentMapName.Clear();
307         aviDemoShortName.Clear();
308         msgFireBack[ 0 ].Clear();
309         msgFireBack[ 1 ].Clear();
310
311         timeHitch = 0;
312
313         rw = NULL;
314         sw = NULL;
315         menuSoundWorld = NULL;
316         readDemo = NULL;
317         writeDemo = NULL;
318         renderdemoVersion = 0;
319         cmdDemoFile = NULL;
320
321         syncNextGameFrame = false;
322         mapSpawned = false;
323         guiActive = NULL;
324         aviCaptureMode = false;
325         timeDemo = TD_NO;
326         waitingOnBind = false;
327         lastPacifierTime = 0;
328         
329         msgRunning = false;
330         guiMsgRestore = NULL;
331         msgIgnoreButtons = false;
332
333         bytesNeededForMapLoad = 0;
334
335 #if ID_CONSOLE_LOCK
336         emptyDrawCount = 0;
337 #endif
338         ClearWipe();
339
340         loadGameList.Clear();
341         modsList.Clear();
342
343         authEmitTimeout = 0;
344         authWaitBox = false;
345
346         authMsg.Clear();
347 }
348
349 /*
350 ===============
351 idSessionLocal::idSessionLocal
352 ===============
353 */
354 idSessionLocal::idSessionLocal() {
355         guiInGame = guiMainMenu = guiIntro \
356                 = guiRestartMenu = guiLoading = guiGameOver = guiActive \
357                 = guiTest = guiMsg = guiMsgRestore = guiTakeNotes = NULL;       
358         
359         menuSoundWorld = NULL;
360         
361         Clear();
362 }
363
364 /*
365 ===============
366 idSessionLocal::~idSessionLocal
367 ===============
368 */
369 idSessionLocal::~idSessionLocal() {
370 }
371
372 /*
373 ===============
374 idSessionLocal::Stop
375
376 called on errors and game exits
377 ===============
378 */
379 void idSessionLocal::Stop() {
380         ClearWipe();
381
382         // clear mapSpawned and demo playing flags
383         UnloadMap();
384
385         // disconnect async client
386         idAsyncNetwork::client.DisconnectFromServer();
387
388         // kill async server
389         idAsyncNetwork::server.Kill();
390
391         if ( sw ) {
392                 sw->StopAllSounds();
393         }
394
395         insideUpdateScreen = false;
396         insideExecuteMapChange = false;
397
398         // drop all guis
399         SetGUI( NULL, NULL );
400 }
401
402 /*
403 ===============
404 idSessionLocal::Shutdown
405 ===============
406 */
407 void idSessionLocal::Shutdown() {
408         int i;
409
410         if ( aviCaptureMode ) {
411                 EndAVICapture();
412         }
413
414         Stop();
415
416         if ( rw ) {
417                 delete rw;
418                 rw = NULL;
419         }
420
421         if ( sw ) {
422                 delete sw;
423                 sw = NULL;
424         }
425
426         if ( menuSoundWorld ) {
427                 delete menuSoundWorld;
428                 menuSoundWorld = NULL;
429         }
430                 
431         mapSpawnData.serverInfo.Clear();
432         mapSpawnData.syncedCVars.Clear();
433         for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
434                 mapSpawnData.userInfo[i].Clear();
435                 mapSpawnData.persistentPlayerInfo[i].Clear();
436         }
437
438         if ( guiMainMenu_MapList != NULL ) {
439                 guiMainMenu_MapList->Shutdown();
440                 uiManager->FreeListGUI( guiMainMenu_MapList );
441                 guiMainMenu_MapList = NULL;
442         }
443
444         Clear();
445 }
446
447 /*
448 ===============
449 idSessionLocal::IsMultiplayer
450 ===============
451 */
452 bool    idSessionLocal::IsMultiplayer() {
453         return idAsyncNetwork::IsActive();
454 }
455
456 /*
457 ================
458 idSessionLocal::StartWipe
459
460 Draws and captures the current state, then starts a wipe with that image
461 ================
462 */
463 void idSessionLocal::StartWipe( const char *_wipeMaterial, bool hold ) {
464         console->Close();
465
466         // render the current screen into a texture for the wipe model
467         renderSystem->CropRenderSize( 640, 480, true );
468
469         Draw();
470
471         renderSystem->CaptureRenderToImage( "_scratch");
472         renderSystem->UnCrop();
473
474         wipeMaterial = declManager->FindMaterial( _wipeMaterial, false );
475
476         wipeStartTic = com_ticNumber;
477         wipeStopTic = wipeStartTic + 1000.0f / USERCMD_MSEC * com_wipeSeconds.GetFloat();
478         wipeHold = hold;
479 }
480
481 /*
482 ================
483 idSessionLocal::CompleteWipe
484 ================
485 */
486 void idSessionLocal::CompleteWipe() {
487         if ( com_ticNumber == 0 ) {
488                 // if the async thread hasn't started, we would hang here
489                 wipeStopTic = 0;
490                 UpdateScreen( true );
491                 return;
492         }
493         while ( com_ticNumber < wipeStopTic ) {
494 #if ID_CONSOLE_LOCK
495                 emptyDrawCount = 0;
496 #endif
497                 UpdateScreen( true );
498         }
499 }
500
501 /*
502 ================
503 idSessionLocal::ShowLoadingGui
504 ================
505 */
506 void idSessionLocal::ShowLoadingGui() {
507         if ( com_ticNumber == 0 ) {
508                 return;
509         }
510         console->Close();
511
512         // introduced in D3XP code. don't think it actually fixes anything, but doesn't hurt either
513 #if 1
514         // Try and prevent the while loop from being skipped over (long hitch on the main thread?)
515         int stop = Sys_Milliseconds() + 1000;
516         int force = 10;
517         while ( Sys_Milliseconds() < stop || force-- > 0 ) {
518                 com_frameTime = com_ticNumber * USERCMD_MSEC;
519                 session->Frame();
520                 session->UpdateScreen( false );
521         }
522 #else
523         int stop = com_ticNumber + 1000.0f / USERCMD_MSEC * 1.0f;
524         while ( com_ticNumber < stop ) {
525                 com_frameTime = com_ticNumber * USERCMD_MSEC;
526                 session->Frame();
527                 session->UpdateScreen( false );
528         }
529 #endif
530 }
531
532
533
534 /*
535 ================
536 idSessionLocal::ClearWipe
537 ================
538 */
539 void idSessionLocal::ClearWipe( void ) {
540         wipeHold = false;
541         wipeStopTic = 0;
542         wipeStartTic = wipeStopTic + 1;
543 }
544
545 /*
546 ================
547 Session_TestGUI_f
548 ================
549 */
550 static void Session_TestGUI_f( const idCmdArgs &args ) {
551         sessLocal.TestGUI( args.Argv(1) );
552 }
553
554 /*
555 ================
556 idSessionLocal::TestGUI
557 ================
558 */
559 void idSessionLocal::TestGUI( const char *guiName ) {
560         if ( guiName && *guiName ) {
561                 guiTest = uiManager->FindGui( guiName, true, false, true );
562         } else {
563                 guiTest = NULL;
564         }
565 }
566
567 /*
568 ================
569 FindUnusedFileName
570 ================
571 */
572 static idStr FindUnusedFileName( const char *format ) {
573         int i;
574         char    filename[1024];
575
576         for ( i = 0 ; i < 999 ; i++ ) {
577                 sprintf( filename, format, i );
578                 int len = fileSystem->ReadFile( filename, NULL, NULL );
579                 if ( len <= 0 ) {
580                         return filename;        // file doesn't exist
581                 }
582         }
583
584         return filename;
585 }
586
587 /*
588 ================
589 Session_DemoShot_f
590 ================
591 */
592 static void Session_DemoShot_f( const idCmdArgs &args ) {
593         if ( args.Argc() != 2 ) {
594                 idStr filename = FindUnusedFileName( "demos/shot%03i.demo" );
595                 sessLocal.DemoShot( filename );
596         } else {
597                 sessLocal.DemoShot( va( "demos/shot_%s.demo", args.Argv(1) ) );
598         }
599 }
600
601 /*
602 ================
603 Session_RecordDemo_f
604 ================
605 */
606 static void Session_RecordDemo_f( const idCmdArgs &args ) {
607         if ( args.Argc() != 2 ) {
608                 idStr filename = FindUnusedFileName( "demos/demo%03i.demo" );
609                 sessLocal.StartRecordingRenderDemo( filename );
610         } else {
611                 sessLocal.StartRecordingRenderDemo( va( "demos/%s.demo", args.Argv(1) ) );
612         }
613 }
614
615 /*
616 ================
617 Session_CompressDemo_f
618 ================
619 */
620 static void Session_CompressDemo_f( const idCmdArgs &args ) {
621         if ( args.Argc() == 2 ) {
622                 sessLocal.CompressDemoFile( "2", args.Argv(1) );
623         } else if ( args.Argc() == 3 ) {
624                 sessLocal.CompressDemoFile( args.Argv(2), args.Argv(1) );
625         } else {
626                 common->Printf("use: CompressDemo <file> [scheme]\nscheme is the same as com_compressDemo, defaults to 2" );
627         }
628 }
629
630 /*
631 ================
632 Session_StopRecordingDemo_f
633 ================
634 */
635 static void Session_StopRecordingDemo_f( const idCmdArgs &args ) {
636         sessLocal.StopRecordingRenderDemo();
637 }
638
639 /*
640 ================
641 Session_PlayDemo_f
642 ================
643 */
644 static void Session_PlayDemo_f( const idCmdArgs &args ) {
645         if ( args.Argc() >= 2 ) {
646                 sessLocal.StartPlayingRenderDemo( va( "demos/%s", args.Argv(1) ) );
647         }
648 }
649
650 /*
651 ================
652 Session_TimeDemo_f
653 ================
654 */
655 static void Session_TimeDemo_f( const idCmdArgs &args ) {
656         if ( args.Argc() >= 2 ) {
657                 sessLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ), ( args.Argc() > 2 ) );
658         }
659 }
660
661 /*
662 ================
663 Session_TimeDemoQuit_f
664 ================
665 */
666 static void Session_TimeDemoQuit_f( const idCmdArgs &args ) {
667         sessLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ) );
668         if ( sessLocal.timeDemo == TD_YES ) {
669                 // this allows hardware vendors to automate some testing
670                 sessLocal.timeDemo = TD_YES_THEN_QUIT;
671         }
672 }
673
674 /*
675 ================
676 Session_AVIDemo_f
677 ================
678 */
679 static void Session_AVIDemo_f( const idCmdArgs &args ) {
680         sessLocal.AVIRenderDemo( va( "demos/%s", args.Argv(1) ) );
681 }
682
683 /*
684 ================
685 Session_AVIGame_f
686 ================
687 */
688 static void Session_AVIGame_f( const idCmdArgs &args ) {
689         sessLocal.AVIGame( args.Argv(1) );
690 }
691
692 /*
693 ================
694 Session_AVICmdDemo_f
695 ================
696 */
697 static void Session_AVICmdDemo_f( const idCmdArgs &args ) {
698         sessLocal.AVICmdDemo( args.Argv(1) );
699 }
700
701 /*
702 ================
703 Session_WriteCmdDemo_f
704 ================
705 */
706 static void Session_WriteCmdDemo_f( const idCmdArgs &args ) {
707         if ( args.Argc() == 1 ) {
708                 idStr   filename = FindUnusedFileName( "demos/cmdDemo%03i.cdemo" );
709                 sessLocal.WriteCmdDemo( filename );
710         } else if ( args.Argc() == 2 ) {
711                 sessLocal.WriteCmdDemo( va( "demos/%s.cdemo", args.Argv( 1 ) ) );
712         } else {
713                 common->Printf( "usage: writeCmdDemo [demoName]\n" );
714         }
715 }
716
717 /*
718 ================
719 Session_PlayCmdDemo_f
720 ================
721 */
722 static void Session_PlayCmdDemo_f( const idCmdArgs &args ) {
723         sessLocal.StartPlayingCmdDemo( args.Argv(1) );
724 }
725
726 /*
727 ================
728 Session_TimeCmdDemo_f
729 ================
730 */
731 static void Session_TimeCmdDemo_f( const idCmdArgs &args ) {
732         sessLocal.TimeCmdDemo( args.Argv(1) );
733 }
734
735 /*
736 ================
737 Session_Disconnect_f
738 ================
739 */
740 static void Session_Disconnect_f( const idCmdArgs &args ) {
741         sessLocal.Stop();
742         sessLocal.StartMenu();
743         if ( soundSystem ) {
744                 soundSystem->SetMute( false );
745         }
746 }
747
748 #ifdef ID_DEMO_BUILD
749 /*
750 ================
751 Session_EndOfDemo_f
752 ================
753 */
754 static void Session_EndOfDemo_f( const idCmdArgs &args ) {
755         sessLocal.Stop();
756         sessLocal.StartMenu();
757         if ( soundSystem ) {
758                 soundSystem->SetMute( false );
759         }
760         if ( sessLocal.guiActive ) {
761                 sessLocal.guiActive->HandleNamedEvent( "endOfDemo" );
762         }
763 }
764 #endif
765
766 /*
767 ================
768 Session_ExitCmdDemo_f
769 ================
770 */
771 static void Session_ExitCmdDemo_f( const idCmdArgs &args ) {
772         if ( !sessLocal.cmdDemoFile ) {
773                 common->Printf( "not reading from a cmdDemo\n" );
774                 return;
775         }
776         fileSystem->CloseFile( sessLocal.cmdDemoFile );
777         common->Printf( "Command demo exited at logIndex %i\n", sessLocal.logIndex );
778         sessLocal.cmdDemoFile = NULL;
779 }
780
781 /*
782 ================
783 idSessionLocal::StartRecordingRenderDemo
784 ================
785 */
786 void idSessionLocal::StartRecordingRenderDemo( const char *demoName ) {
787         if ( writeDemo ) {
788                 // allow it to act like a toggle
789                 StopRecordingRenderDemo();
790                 return;
791         }
792
793         if ( !demoName[0] ) {
794                 common->Printf( "idSessionLocal::StartRecordingRenderDemo: no name specified\n" );
795                 return;
796         }
797
798         console->Close();
799
800         writeDemo = new idDemoFile;
801         if ( !writeDemo->OpenForWriting( demoName ) ) {
802                 common->Printf( "error opening %s\n", demoName );
803                 delete writeDemo;
804                 writeDemo = NULL;
805                 return;
806         }
807
808         common->Printf( "recording to %s\n", writeDemo->GetName() );
809
810         writeDemo->WriteInt( DS_VERSION );
811         writeDemo->WriteInt( RENDERDEMO_VERSION );
812
813         // if we are in a map already, dump the current state
814         sw->StartWritingDemo( writeDemo );
815         rw->StartWritingDemo( writeDemo );
816 }
817
818 /*
819 ================
820 idSessionLocal::StopRecordingRenderDemo
821 ================
822 */
823 void idSessionLocal::StopRecordingRenderDemo() {
824         if ( !writeDemo ) {
825                 common->Printf( "idSessionLocal::StopRecordingRenderDemo: not recording\n" );
826                 return;
827         }
828         sw->StopWritingDemo();
829         rw->StopWritingDemo();
830
831         writeDemo->Close();
832         common->Printf( "stopped recording %s.\n", writeDemo->GetName() );
833         delete writeDemo;
834         writeDemo = NULL;
835 }
836
837 /*
838 ================
839 idSessionLocal::StopPlayingRenderDemo
840
841 Reports timeDemo numbers and finishes any avi recording
842 ================
843 */
844 void idSessionLocal::StopPlayingRenderDemo() {
845         if ( !readDemo ) {
846                 timeDemo = TD_NO;
847                 return;
848         }
849
850         // Record the stop time before doing anything that could be time consuming 
851         int timeDemoStopTime = Sys_Milliseconds();
852
853         EndAVICapture();
854
855         readDemo->Close();
856
857         sw->StopAllSounds();
858         soundSystem->SetPlayingSoundWorld( menuSoundWorld );
859
860         common->Printf( "stopped playing %s.\n", readDemo->GetName() );
861         delete readDemo;
862         readDemo = NULL;
863
864         if ( timeDemo ) {
865                 // report the stats
866                 float   demoSeconds = ( timeDemoStopTime - timeDemoStartTime ) * 0.001f;
867                 float   demoFPS = numDemoFrames / demoSeconds;
868                 idStr   message = va( "%i frames rendered in %3.1f seconds = %3.1f fps\n", numDemoFrames, demoSeconds, demoFPS );
869
870                 common->Printf( message );
871                 if ( timeDemo == TD_YES_THEN_QUIT ) {
872                         cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" );
873                 } else {
874                         soundSystem->SetMute( true );
875                         MessageBox( MSG_OK, message, "Time Demo Results", true );
876                         soundSystem->SetMute( false );
877                 }
878                 timeDemo = TD_NO;
879         }
880 }
881
882 /*
883 ================
884 idSessionLocal::DemoShot
885
886 A demoShot is a single frame demo
887 ================
888 */
889 void idSessionLocal::DemoShot( const char *demoName ) {
890         StartRecordingRenderDemo( demoName );
891
892         // force draw one frame
893         UpdateScreen();
894
895         StopRecordingRenderDemo();
896 }
897
898 /*
899 ================
900 idSessionLocal::StartPlayingRenderDemo
901 ================
902 */
903 void idSessionLocal::StartPlayingRenderDemo( idStr demoName ) {
904         if ( !demoName[0] ) {
905                 common->Printf( "idSessionLocal::StartPlayingRenderDemo: no name specified\n" );
906                 return;
907         }
908
909         // make sure localSound / GUI intro music shuts up
910         sw->StopAllSounds();
911         sw->PlayShaderDirectly( "", 0 );        
912         menuSoundWorld->StopAllSounds();
913         menuSoundWorld->PlayShaderDirectly( "", 0 );
914
915         // exit any current game
916         Stop();
917
918         // automatically put the console away
919         console->Close();
920
921         // bring up the loading screen manually, since demos won't
922         // call ExecuteMapChange()
923         guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true );
924         guiLoading->SetStateString( "demo", common->GetLanguageDict()->GetString( "#str_02087" ) );
925         readDemo = new idDemoFile;
926         demoName.DefaultFileExtension( ".demo" );
927         if ( !readDemo->OpenForReading( demoName ) ) {
928                 common->Printf( "couldn't open %s\n", demoName.c_str() );
929                 delete readDemo;
930                 readDemo = NULL;
931                 Stop();
932                 StartMenu();
933                 soundSystem->SetMute( false );
934                 return;
935         }
936
937         insideExecuteMapChange = true;
938         UpdateScreen();
939         insideExecuteMapChange = false;
940         guiLoading->SetStateString( "demo", "" );
941
942         // setup default render demo settings
943         // that's default for <= Doom3 v1.1
944         renderdemoVersion = 1;
945         savegameVersion = 16;
946
947         AdvanceRenderDemo( true );
948
949         numDemoFrames = 1;
950
951         lastDemoTic = -1;
952         timeDemoStartTime = Sys_Milliseconds();
953 }
954
955 /*
956 ================
957 idSessionLocal::TimeRenderDemo
958 ================
959 */
960 void idSessionLocal::TimeRenderDemo( const char *demoName, bool twice ) {
961         idStr demo = demoName;
962         
963         // no sound in time demos
964         soundSystem->SetMute( true );
965
966         StartPlayingRenderDemo( demo );
967         
968         if ( twice && readDemo ) {
969                 // cycle through once to precache everything
970                 guiLoading->SetStateString( "demo", common->GetLanguageDict()->GetString( "#str_04852" ) );
971                 guiLoading->StateChanged( com_frameTime );
972                 while ( readDemo ) {
973                         insideExecuteMapChange = true;
974                         UpdateScreen();
975                         insideExecuteMapChange = false;
976                         AdvanceRenderDemo( true );
977                 }
978                 guiLoading->SetStateString( "demo", "" );
979                 StartPlayingRenderDemo( demo );
980         }
981         
982
983         if ( !readDemo ) {
984                 return;
985         }
986
987         timeDemo = TD_YES;
988 }
989
990
991 /*
992 ================
993 idSessionLocal::BeginAVICapture
994 ================
995 */
996 void idSessionLocal::BeginAVICapture( const char *demoName ) {
997         idStr name = demoName;
998         name.ExtractFileBase( aviDemoShortName );
999         aviCaptureMode = true;
1000         aviDemoFrameCount = 0;
1001         aviTicStart = 0;
1002         sw->AVIOpen( va( "demos/%s/", aviDemoShortName.c_str() ), aviDemoShortName.c_str() );
1003 }
1004
1005 /*
1006 ================
1007 idSessionLocal::EndAVICapture
1008 ================
1009 */
1010 void idSessionLocal::EndAVICapture() {
1011         if ( !aviCaptureMode ) {
1012                 return;
1013         }
1014
1015         sw->AVIClose();
1016
1017         // write a .roqParam file so the demo can be converted to a roq file
1018         idFile *f = fileSystem->OpenFileWrite( va( "demos/%s/%s.roqParam", 
1019                 aviDemoShortName.c_str(), aviDemoShortName.c_str() ) );
1020         f->Printf( "INPUT_DIR demos/%s\n", aviDemoShortName.c_str() );
1021         f->Printf( "FILENAME demos/%s/%s.RoQ\n", aviDemoShortName.c_str(), aviDemoShortName.c_str() );
1022         f->Printf( "\nINPUT\n" );
1023         f->Printf( "%s_*.tga [00000-%05i]\n", aviDemoShortName.c_str(), (int)( aviDemoFrameCount-1 ) );
1024         f->Printf( "END_INPUT\n" );
1025         delete f;
1026
1027         common->Printf( "captured %i frames for %s.\n", ( int )aviDemoFrameCount, aviDemoShortName.c_str() );
1028
1029         aviCaptureMode = false;
1030 }
1031
1032
1033 /*
1034 ================
1035 idSessionLocal::AVIRenderDemo
1036 ================
1037 */
1038 void idSessionLocal::AVIRenderDemo( const char *_demoName ) {
1039         idStr   demoName = _demoName;   // copy off from va() buffer
1040
1041         StartPlayingRenderDemo( demoName );
1042         if ( !readDemo ) {
1043                 return;
1044         }
1045
1046         BeginAVICapture( demoName.c_str() ) ;
1047
1048         // I don't understand why I need to do this twice, something
1049         // strange with the nvidia swapbuffers?
1050         UpdateScreen();
1051 }
1052
1053 /*
1054 ================
1055 idSessionLocal::AVICmdDemo
1056 ================
1057 */
1058 void idSessionLocal::AVICmdDemo( const char *demoName ) {
1059         StartPlayingCmdDemo( demoName );
1060
1061         BeginAVICapture( demoName ) ;
1062 }
1063
1064 /*
1065 ================
1066 idSessionLocal::AVIGame
1067
1068 Start AVI recording the current game session
1069 ================
1070 */
1071 void idSessionLocal::AVIGame( const char *demoName ) {
1072         if ( aviCaptureMode ) {
1073                 EndAVICapture();
1074                 return;
1075         }
1076
1077         if ( !mapSpawned ) {
1078                 common->Printf( "No map spawned.\n" );
1079         }
1080
1081         if ( !demoName || !demoName[0] ) {
1082                 idStr filename = FindUnusedFileName( "demos/game%03i.game" );
1083                 demoName = filename.c_str();
1084
1085                 // write a one byte stub .game file just so the FindUnusedFileName works,
1086                 fileSystem->WriteFile( demoName, demoName, 1 );
1087         }
1088
1089         BeginAVICapture( demoName ) ;
1090 }
1091
1092 /*
1093 ================
1094 idSessionLocal::CompressDemoFile
1095 ================
1096 */
1097 void idSessionLocal::CompressDemoFile( const char *scheme, const char *demoName ) {
1098         idStr   fullDemoName = "demos/";
1099         fullDemoName += demoName;
1100         fullDemoName.DefaultFileExtension( ".demo" );
1101         idStr compressedName = fullDemoName;
1102         compressedName.StripFileExtension();
1103         compressedName.Append( "_compressed.demo" );
1104
1105         int savedCompression = cvarSystem->GetCVarInteger("com_compressDemos");
1106         bool savedPreload = cvarSystem->GetCVarBool("com_preloadDemos");
1107         cvarSystem->SetCVarBool( "com_preloadDemos", false );
1108         cvarSystem->SetCVarInteger("com_compressDemos", atoi(scheme) );
1109
1110         idDemoFile demoread, demowrite;
1111         if ( !demoread.OpenForReading( fullDemoName ) ) {
1112                 common->Printf( "Could not open %s for reading\n", fullDemoName.c_str() );
1113                 return;
1114         }
1115         if ( !demowrite.OpenForWriting( compressedName ) ) {
1116                 common->Printf( "Could not open %s for writing\n", compressedName.c_str() );
1117                 demoread.Close();
1118                 cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload );
1119                 cvarSystem->SetCVarInteger("com_compressDemos", savedCompression);
1120                 return;
1121         }
1122         common->SetRefreshOnPrint( true );
1123         common->Printf( "Compressing %s to %s...\n", fullDemoName.c_str(), compressedName.c_str() );
1124
1125         static const int bufferSize = 65535;
1126         char buffer[bufferSize];
1127         int bytesRead;
1128         while ( 0 != (bytesRead = demoread.Read( buffer, bufferSize ) ) ) {
1129                 demowrite.Write( buffer, bytesRead );
1130                 common->Printf( "." );
1131         }
1132
1133         demoread.Close();
1134         demowrite.Close();
1135
1136         cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload );
1137         cvarSystem->SetCVarInteger("com_compressDemos", savedCompression);
1138
1139         common->Printf( "Done\n" );
1140         common->SetRefreshOnPrint( false );
1141
1142 }
1143
1144
1145 /*
1146 ===============
1147 idSessionLocal::StartNewGame
1148 ===============
1149 */
1150 void idSessionLocal::StartNewGame( const char *mapName, bool devmap ) {
1151 #ifdef  ID_DEDICATED
1152         common->Printf( "Dedicated servers cannot start singleplayer games.\n" );
1153         return;
1154 #else
1155 #if ID_ENFORCE_KEY
1156         // strict check. don't let a game start without a definitive answer
1157         if ( !CDKeysAreValid( true ) ) {
1158                 bool prompt = true;
1159                 if ( MaybeWaitOnCDKey() ) {
1160                         // check again, maybe we just needed more time
1161                         if ( CDKeysAreValid( true ) ) {
1162                                 // can continue directly
1163                                 prompt = false;
1164                         }
1165                 }
1166                 if ( prompt ) {
1167                         cmdSystem->BufferCommandText( CMD_EXEC_NOW, "promptKey force" );
1168                         cmdSystem->ExecuteCommandBuffer();
1169                 }
1170         }
1171 #endif
1172         if ( idAsyncNetwork::server.IsActive() ) {
1173                 common->Printf("Server running, use si_map / serverMapRestart\n");
1174                 return;
1175         }
1176         if ( idAsyncNetwork::client.IsActive() ) {
1177                 common->Printf("Client running, disconnect from server first\n");
1178                 return;
1179         }
1180
1181         // clear the userInfo so the player starts out with the defaults
1182         mapSpawnData.userInfo[0].Clear();
1183         mapSpawnData.persistentPlayerInfo[0].Clear();
1184         mapSpawnData.userInfo[0] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
1185
1186         mapSpawnData.serverInfo.Clear();
1187         mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
1188         mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" );
1189
1190         // set the devmap key so any play testing items will be given at
1191         // spawn time to set approximately the right weapons and ammo
1192         if(devmap) {
1193                 mapSpawnData.serverInfo.Set( "devmap", "1" );
1194         }
1195
1196         mapSpawnData.syncedCVars.Clear();
1197         mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC );
1198
1199         MoveToNewMap( mapName );
1200 #endif
1201 }
1202
1203 /*
1204 ===============
1205 idSessionLocal::GetAutoSaveName
1206 ===============
1207 */
1208 idStr idSessionLocal::GetAutoSaveName( const char *mapName ) const {
1209         const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false );
1210         const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( mapDecl );
1211         if ( mapDef ) {
1212                 mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) );
1213         }
1214         // Fixme: Localization
1215         return va( "^3AutoSave:^0 %s", mapName );
1216 }
1217
1218 /*
1219 ===============
1220 idSessionLocal::MoveToNewMap
1221
1222 Leaves the existing userinfo and serverinfo
1223 ===============
1224 */
1225 void idSessionLocal::MoveToNewMap( const char *mapName ) {
1226         mapSpawnData.serverInfo.Set( "si_map", mapName );
1227
1228         ExecuteMapChange();
1229
1230         if ( !mapSpawnData.serverInfo.GetBool("devmap") ) {
1231                 // Autosave at the beginning of the level
1232                 SaveGame( GetAutoSaveName( mapName ), true );
1233         }
1234
1235         SetGUI( NULL, NULL );
1236 }
1237
1238 /*
1239 ==============
1240 SaveCmdDemoFromFile
1241 ==============
1242 */
1243 void idSessionLocal::SaveCmdDemoToFile( idFile *file ) {
1244
1245         mapSpawnData.serverInfo.WriteToFileHandle( file );
1246
1247         for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) {
1248                 mapSpawnData.userInfo[i].WriteToFileHandle( file );
1249                 mapSpawnData.persistentPlayerInfo[i].WriteToFileHandle( file );
1250         }
1251
1252         file->Write( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) );
1253
1254         if ( numClients < 1 ) {
1255                 numClients = 1;
1256         }
1257         file->Write( loggedUsercmds, numClients * logIndex * sizeof( loggedUsercmds[0] ) );
1258 }
1259
1260 /*
1261 ==============
1262 idSessionLocal::LoadCmdDemoFromFile
1263 ==============
1264 */
1265 void idSessionLocal::LoadCmdDemoFromFile( idFile *file ) {
1266
1267         mapSpawnData.serverInfo.ReadFromFileHandle( file );
1268
1269         for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) {
1270                 mapSpawnData.userInfo[i].ReadFromFileHandle( file );
1271                 mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( file );
1272         }
1273         file->Read( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) );
1274 }
1275
1276 /*
1277 ==============
1278 idSessionLocal::WriteCmdDemo
1279
1280 Dumps the accumulated commands for the current level.
1281 This should still work after disconnecting from a level
1282 ==============
1283 */
1284 void idSessionLocal::WriteCmdDemo( const char *demoName, bool save ) {
1285         
1286         if ( !demoName[0] ) {
1287                 common->Printf( "idSessionLocal::WriteCmdDemo: no name specified\n" );
1288                 return;
1289         }
1290
1291         idStr statsName;
1292         if (save) {
1293                 statsName = demoName;
1294                 statsName.StripFileExtension();
1295                 statsName.DefaultFileExtension(".stats");
1296         }
1297
1298         common->Printf( "writing save data to %s\n", demoName );
1299
1300         idFile *cmdDemoFile = fileSystem->OpenFileWrite( demoName );
1301         if ( !cmdDemoFile ) {
1302                 common->Printf( "Couldn't open for writing %s\n", demoName );
1303                 return;
1304         }
1305
1306         if ( save ) {
1307                 cmdDemoFile->Write( &logIndex, sizeof( logIndex ) );
1308         }
1309         
1310         SaveCmdDemoToFile( cmdDemoFile );
1311
1312         if ( save ) {
1313                 idFile *statsFile = fileSystem->OpenFileWrite( statsName );
1314                 if ( statsFile ) {
1315                         statsFile->Write( &statIndex, sizeof( statIndex ) );
1316                         statsFile->Write( loggedStats, numClients * statIndex * sizeof( loggedStats[0] ) );
1317                         fileSystem->CloseFile( statsFile );
1318                 }
1319         }
1320
1321         fileSystem->CloseFile( cmdDemoFile );
1322 }
1323
1324 /*
1325 ===============
1326 idSessionLocal::FinishCmdLoad
1327 ===============
1328 */
1329 void idSessionLocal::FinishCmdLoad() {
1330 }
1331
1332 /*
1333 ===============
1334 idSessionLocal::StartPlayingCmdDemo
1335 ===============
1336 */
1337 void idSessionLocal::StartPlayingCmdDemo(const char *demoName) {
1338         // exit any current game
1339         Stop();
1340
1341         idStr fullDemoName = "demos/";
1342         fullDemoName += demoName;
1343         fullDemoName.DefaultFileExtension( ".cdemo" );
1344         cmdDemoFile = fileSystem->OpenFileRead(fullDemoName);
1345
1346         if ( cmdDemoFile == NULL ) {
1347                 common->Printf( "Couldn't open %s\n", fullDemoName.c_str() );
1348                 return;
1349         }
1350
1351         guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true );
1352         //cmdDemoFile->Read(&loadGameTime, sizeof(loadGameTime));
1353
1354         LoadCmdDemoFromFile(cmdDemoFile);
1355
1356         // start the map
1357         ExecuteMapChange();
1358
1359         cmdDemoFile = fileSystem->OpenFileRead(fullDemoName);
1360
1361         // have to do this twice as the execmapchange clears the cmddemofile
1362         LoadCmdDemoFromFile(cmdDemoFile);
1363
1364         // run one frame to get the view angles correct
1365         RunGameTic();
1366 }
1367
1368 /*
1369 ===============
1370 idSessionLocal::TimeCmdDemo
1371 ===============
1372 */
1373 void idSessionLocal::TimeCmdDemo( const char *demoName ) {
1374         StartPlayingCmdDemo( demoName );
1375         ClearWipe();
1376         UpdateScreen();
1377
1378         int             startTime = Sys_Milliseconds();
1379         int             count = 0;
1380         int             minuteStart, minuteEnd;
1381         float   sec;
1382
1383         // run all the frames in sequence
1384         minuteStart = startTime;
1385
1386         while( cmdDemoFile ) {
1387                 RunGameTic();
1388                 count++;
1389
1390                 if ( count / 3600 != ( count - 1 ) / 3600 ) {
1391                         minuteEnd = Sys_Milliseconds();
1392                         sec = ( minuteEnd - minuteStart ) / 1000.0;
1393                         minuteStart = minuteEnd;
1394                         common->Printf( "minute %i took %3.1f seconds\n", count / 3600, sec );
1395                         UpdateScreen();
1396                 }
1397         }
1398
1399         int             endTime = Sys_Milliseconds();
1400         sec = ( endTime - startTime ) / 1000.0;
1401         common->Printf( "%i seconds of game, replayed in %5.1f seconds\n", count / 60, sec );
1402 }
1403
1404 /*
1405 ===============
1406 idSessionLocal::UnloadMap
1407
1408 Performs cleanup that needs to happen between maps, or when a
1409 game is exited.
1410 Exits with mapSpawned = false
1411 ===============
1412 */
1413 void idSessionLocal::UnloadMap() {
1414         StopPlayingRenderDemo();
1415
1416         // end the current map in the game
1417         if ( game ) {
1418                 game->MapShutdown();
1419         }
1420
1421         if ( cmdDemoFile ) {
1422                 fileSystem->CloseFile( cmdDemoFile );
1423                 cmdDemoFile = NULL;
1424         }
1425
1426         if ( writeDemo ) {
1427                 StopRecordingRenderDemo();
1428         }
1429
1430         mapSpawned = false;
1431 }
1432
1433 /*
1434 ===============
1435 idSessionLocal::LoadLoadingGui
1436 ===============
1437 */
1438 void idSessionLocal::LoadLoadingGui( const char *mapName ) {
1439         // load / program a gui to stay up on the screen while loading
1440         idStr stripped = mapName;
1441         stripped.StripFileExtension();
1442         stripped.StripPath();
1443
1444         char guiMap[ MAX_STRING_CHARS ];
1445         strncpy( guiMap, va( "guis/map/%s.gui", stripped.c_str() ), MAX_STRING_CHARS );
1446         // give the gamecode a chance to override
1447         game->GetMapLoadingGUI( guiMap );
1448
1449         if ( uiManager->CheckGui( guiMap ) ) {
1450                 guiLoading = uiManager->FindGui( guiMap, true, false, true );
1451         } else {
1452                 guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true );
1453         }
1454         guiLoading->SetStateFloat( "map_loading", 0.0f );
1455 }
1456
1457 /*
1458 ===============
1459 idSessionLocal::GetBytesNeededForMapLoad
1460 ===============
1461 */
1462 int idSessionLocal::GetBytesNeededForMapLoad( const char *mapName ) {
1463         const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false );
1464         const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( mapDecl );
1465         if ( mapDef ) {
1466                 return mapDef->dict.GetInt( va("size%d", Max( 0, com_machineSpec.GetInteger() ) ) );
1467         } else {
1468                 if ( com_machineSpec.GetInteger() < 2 ) {
1469                         return 200 * 1024 * 1024;
1470                 } else {
1471                         return 400 * 1024 * 1024;
1472                 }
1473         }
1474 }
1475
1476 /*
1477 ===============
1478 idSessionLocal::SetBytesNeededForMapLoad
1479 ===============
1480 */
1481 void idSessionLocal::SetBytesNeededForMapLoad( const char *mapName, int bytesNeeded ) {
1482         idDecl *mapDecl = const_cast<idDecl *>(declManager->FindType( DECL_MAPDEF, mapName, false ));
1483         idDeclEntityDef *mapDef = static_cast<idDeclEntityDef *>( mapDecl );
1484
1485         if ( com_updateLoadSize.GetBool() && mapDef ) {
1486                 // we assume that if com_updateLoadSize is true then the file is writable
1487
1488                 mapDef->dict.SetInt( va("size%d", com_machineSpec.GetInteger()), bytesNeeded );
1489
1490                 idStr declText = "\nmapDef ";
1491                 declText += mapDef->GetName();
1492                 declText += " {\n";
1493                 for (int i=0; i<mapDef->dict.GetNumKeyVals(); i++) {
1494                         const idKeyValue *kv = mapDef->dict.GetKeyVal( i );
1495                         if ( kv && (kv->GetKey().Cmp("classname") != 0 ) ) {
1496                                 declText += "\t\"" + kv->GetKey() + "\"\t\t\"" + kv->GetValue() + "\"\n";
1497                         }
1498                 }
1499                 declText += "}";
1500                 mapDef->SetText( declText );
1501                 mapDef->ReplaceSourceFileText();
1502         }
1503 }
1504
1505 /*
1506 ===============
1507 idSessionLocal::ExecuteMapChange
1508
1509 Performs the initialization of a game based on mapSpawnData, used for both single
1510 player and multiplayer, but not for renderDemos, which don't
1511 create a game at all.
1512 Exits with mapSpawned = true
1513 ===============
1514 */
1515 void idSessionLocal::ExecuteMapChange( bool noFadeWipe ) {
1516         int             i;
1517         bool    reloadingSameMap;
1518
1519         // close console and remove any prints from the notify lines
1520         console->Close();
1521
1522         if ( IsMultiplayer() ) {
1523                 // make sure the mp GUI isn't up, or when players get back in the
1524                 // map, mpGame's menu and the gui will be out of sync.
1525                 SetGUI( NULL, NULL );
1526         }
1527
1528         // mute sound
1529         soundSystem->SetMute( true );
1530
1531         // clear all menu sounds
1532         menuSoundWorld->ClearAllSoundEmitters();
1533
1534         // unpause the game sound world
1535         // NOTE: we UnPause again later down. not sure this is needed
1536         if ( sw->IsPaused() ) {
1537                 sw->UnPause();
1538         }
1539
1540         if ( !noFadeWipe ) {
1541                 // capture the current screen and start a wipe
1542                 StartWipe( "wipeMaterial", true );
1543
1544                 // immediately complete the wipe to fade out the level transition
1545                 // run the wipe to completion
1546                 CompleteWipe();
1547         }
1548
1549         // extract the map name from serverinfo
1550         idStr mapString = mapSpawnData.serverInfo.GetString( "si_map" );
1551
1552         idStr fullMapName = "maps/";
1553         fullMapName += mapString;
1554         fullMapName.StripFileExtension();
1555
1556         // shut down the existing game if it is running
1557         UnloadMap();
1558
1559         // don't do the deferred caching if we are reloading the same map
1560         if ( fullMapName == currentMapName ) {
1561                 reloadingSameMap = true;
1562         } else {
1563                 reloadingSameMap = false;
1564                 currentMapName = fullMapName;
1565         }
1566
1567         // note which media we are going to need to load
1568         if ( !reloadingSameMap ) {
1569                 declManager->BeginLevelLoad();
1570                 renderSystem->BeginLevelLoad();
1571                 soundSystem->BeginLevelLoad();
1572         }
1573
1574         uiManager->BeginLevelLoad();
1575         uiManager->Reload( true );
1576
1577         // set the loading gui that we will wipe to
1578         LoadLoadingGui( mapString );
1579
1580         // cause prints to force screen updates as a pacifier,
1581         // and draw the loading gui instead of game draws
1582         insideExecuteMapChange = true;
1583
1584         // if this works out we will probably want all the sizes in a def file although this solution will 
1585         // work for new maps etc. after the first load. we can also drop the sizes into the default.cfg
1586         fileSystem->ResetReadCount();
1587         if ( !reloadingSameMap  ) {
1588                 bytesNeededForMapLoad = GetBytesNeededForMapLoad( mapString.c_str() );
1589         } else {
1590                 bytesNeededForMapLoad = 30 * 1024 * 1024;
1591         }
1592
1593         ClearWipe();
1594
1595         // let the loading gui spin for 1 second to animate out
1596         ShowLoadingGui();
1597
1598         // note any warning prints that happen during the load process
1599         common->ClearWarnings( mapString );
1600
1601         // release the mouse cursor
1602         // before we do this potentially long operation
1603         Sys_GrabMouseCursor( false );
1604
1605         // if net play, we get the number of clients during mapSpawnInfo processing
1606         if ( !idAsyncNetwork::IsActive() ) {
1607                 numClients = 1;
1608         } 
1609         
1610         int start = Sys_Milliseconds();
1611
1612         common->Printf( "--------- Map Initialization ---------\n" );
1613         common->Printf( "Map: %s\n", mapString.c_str() );
1614
1615         // let the renderSystem load all the geometry
1616         if ( !rw->InitFromMap( fullMapName ) ) {
1617                 common->Error( "couldn't load %s", fullMapName.c_str() );
1618         }
1619
1620         // for the synchronous networking we needed to roll the angles over from
1621         // level to level, but now we can just clear everything
1622         usercmdGen->InitForNewMap();
1623         memset( &mapSpawnData.mapSpawnUsercmd, 0, sizeof( mapSpawnData.mapSpawnUsercmd ) );
1624
1625         // set the user info
1626         for ( i = 0; i < numClients; i++ ) {
1627                 game->SetUserInfo( i, mapSpawnData.userInfo[i], idAsyncNetwork::client.IsActive(), false );
1628                 game->SetPersistentPlayerInfo( i, mapSpawnData.persistentPlayerInfo[i] );
1629         }
1630
1631         // load and spawn all other entities ( from a savegame possibly )
1632         if ( loadingSaveGame && savegameFile ) {
1633                 if ( game->InitFromSaveGame( fullMapName + ".map", rw, sw, savegameFile ) == false ) {
1634                         // If the loadgame failed, restart the map with the player persistent data
1635                         loadingSaveGame = false;
1636                         fileSystem->CloseFile( savegameFile );
1637                         savegameFile = NULL;
1638
1639                         game->SetServerInfo( mapSpawnData.serverInfo );
1640                         game->InitFromNewMap( fullMapName + ".map", rw, sw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() );
1641                 }
1642         } else {
1643                 game->SetServerInfo( mapSpawnData.serverInfo );
1644                 game->InitFromNewMap( fullMapName + ".map", rw, sw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() );
1645         }
1646
1647         if ( !idAsyncNetwork::IsActive() && !loadingSaveGame ) {
1648                 // spawn players
1649                 for ( i = 0; i < numClients; i++ ) {
1650                         game->SpawnPlayer( i );
1651                 }
1652         }
1653
1654         // actually purge/load the media
1655         if ( !reloadingSameMap ) {
1656                 renderSystem->EndLevelLoad();
1657                 soundSystem->EndLevelLoad( mapString.c_str() );
1658                 declManager->EndLevelLoad();
1659                 SetBytesNeededForMapLoad( mapString.c_str(), fileSystem->GetReadCount() );
1660         }
1661         uiManager->EndLevelLoad();
1662
1663         if ( !idAsyncNetwork::IsActive() && !loadingSaveGame ) {
1664                 // run a few frames to allow everything to settle
1665                 for ( i = 0; i < 10; i++ ) {
1666                         game->RunFrame( mapSpawnData.mapSpawnUsercmd );
1667                 }
1668         }
1669
1670         common->Printf ("-----------------------------------\n");
1671
1672         int     msec = Sys_Milliseconds() - start;
1673         common->Printf( "%6d msec to load %s\n", msec, mapString.c_str() );
1674
1675         // let the renderSystem generate interactions now that everything is spawned
1676         rw->GenerateAllInteractions();
1677
1678         common->PrintWarnings();
1679
1680         if ( guiLoading && bytesNeededForMapLoad ) {
1681                 float pct = guiLoading->State().GetFloat( "map_loading" );
1682                 if ( pct < 0.0f ) {
1683                         pct = 0.0f;
1684                 }
1685                 while ( pct < 1.0f ) {
1686                         guiLoading->SetStateFloat( "map_loading", pct );
1687                         guiLoading->StateChanged( com_frameTime );
1688                         Sys_GenerateEvents();
1689                         UpdateScreen();
1690                         pct += 0.05f;
1691                 }
1692         }
1693
1694         // capture the current screen and start a wipe
1695         StartWipe( "wipe2Material" );
1696
1697         usercmdGen->Clear();
1698
1699         // start saving commands for possible writeCmdDemo usage
1700         logIndex = 0;
1701         statIndex = 0;
1702         lastSaveIndex = 0;
1703
1704         // don't bother spinning over all the tics we spent loading
1705         lastGameTic = latchedTicNumber = com_ticNumber;
1706
1707         // remove any prints from the notify lines
1708         console->ClearNotifyLines();
1709
1710         // stop drawing the laoding screen
1711         insideExecuteMapChange = false;
1712
1713         Sys_SetPhysicalWorkMemory( -1, -1 );
1714
1715         // set the game sound world for playback
1716         soundSystem->SetPlayingSoundWorld( sw );
1717
1718         // when loading a save game the sound is paused
1719         if ( sw->IsPaused() ) {
1720                 // unpause the game sound world
1721                 sw->UnPause();
1722         }
1723
1724         // restart entity sound playback
1725         soundSystem->SetMute( false );
1726
1727         // we are valid for game draws now
1728         mapSpawned = true;
1729         Sys_ClearEvents();
1730 }
1731
1732 /*
1733 ===============
1734 LoadGame_f
1735 ===============
1736 */
1737 void LoadGame_f( const idCmdArgs &args ) {
1738         console->Close();
1739         if ( args.Argc() < 2 || idStr::Icmp(args.Argv(1), "quick" ) == 0 ) {
1740                 idStr saveName = common->GetLanguageDict()->GetString( "#str_07178" );
1741                 sessLocal.LoadGame( saveName );
1742         } else {
1743                 sessLocal.LoadGame( args.Argv(1) );
1744         }
1745 }
1746
1747 /*
1748 ===============
1749 SaveGame_f
1750 ===============
1751 */
1752 void SaveGame_f( const idCmdArgs &args ) {
1753         if ( args.Argc() < 2 || idStr::Icmp( args.Argv(1), "quick" ) == 0 ) {
1754                 idStr saveName = common->GetLanguageDict()->GetString( "#str_07178" );
1755                 if ( sessLocal.SaveGame( saveName ) ) {
1756                         common->Printf( "%s\n", saveName.c_str() );
1757                 }
1758         } else {
1759                 if ( sessLocal.SaveGame( args.Argv(1) ) ) {
1760                         common->Printf( "Saved %s\n", args.Argv(1) );
1761                 }
1762         }
1763 }
1764
1765 /*
1766 ===============
1767 TakeViewNotes_f
1768 ===============
1769 */
1770 void TakeViewNotes_f( const idCmdArgs &args ) {
1771         const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : "";
1772         sessLocal.TakeNotes( p );
1773 }
1774
1775 /*
1776 ===============
1777 TakeViewNotes2_f
1778 ===============
1779 */
1780 void TakeViewNotes2_f( const idCmdArgs &args ) {
1781         const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : "";
1782         sessLocal.TakeNotes( p, true );
1783 }
1784
1785 /*
1786 ===============
1787 idSessionLocal::TakeNotes
1788 ===============
1789 */
1790 void idSessionLocal::TakeNotes( const char *p, bool extended ) {
1791         if ( !mapSpawned ) {
1792                 common->Printf( "No map loaded!\n" );
1793                 return;
1794         }
1795
1796         if ( extended ) {
1797                 guiTakeNotes = uiManager->FindGui( "guis/takeNotes2.gui", true, false, true );
1798
1799 #if 0
1800                 const char *people[] = {
1801                         "Nobody", "Adam", "Brandon", "David", "PHook", "Jay", "Jake",
1802                                 "PatJ", "Brett", "Ted", "Darin", "Brian", "Sean"
1803                 };
1804 #else
1805                 const char *people[] = {
1806                         "Tim", "Kenneth", "Robert", 
1807                         "Matt", "Mal", "Jerry", "Steve", "Pat",
1808                         "Xian", "Ed", "Fred", "James", "Eric", "Andy", "Seneca", "Patrick", "Kevin",
1809                         "MrElusive", "Jim", "Brian", "John", "Adrian", "Nobody"
1810                 };
1811 #endif
1812                 const int numPeople = sizeof( people ) / sizeof( people[0] );
1813
1814                 idListGUI * guiList_people = uiManager->AllocListGUI();
1815                 guiList_people->Config( guiTakeNotes, "person" );
1816                 for ( int i = 0; i < numPeople; i++ ) {
1817                         guiList_people->Push( people[i] );
1818                 }
1819                 uiManager->FreeListGUI( guiList_people );
1820
1821         } else {
1822                 guiTakeNotes = uiManager->FindGui( "guis/takeNotes.gui", true, false, true );
1823         }
1824
1825         SetGUI( guiTakeNotes, NULL );
1826         guiActive->SetStateString( "note", "" );
1827         guiActive->SetStateString( "notefile", p );
1828         guiActive->SetStateBool( "extended", extended );
1829         guiActive->Activate( true, com_frameTime );
1830 }
1831
1832 /*
1833 ===============
1834 Session_Hitch_f
1835 ===============
1836 */
1837 void Session_Hitch_f( const idCmdArgs &args ) {
1838         idSoundWorld *sw = soundSystem->GetPlayingSoundWorld();
1839         if ( sw ) {
1840                 soundSystem->SetMute(true);
1841                 sw->Pause();
1842                 Sys_EnterCriticalSection();
1843         }
1844         if ( args.Argc() == 2 ) {
1845                 Sys_Sleep( atoi(args.Argv(1)) );
1846         } else {
1847                 Sys_Sleep( 100 );
1848         }
1849         if ( sw ) {
1850                 Sys_LeaveCriticalSection();
1851                 sw->UnPause();
1852                 soundSystem->SetMute(false);
1853         }
1854 }
1855
1856 /*
1857 ===============
1858 idSessionLocal::ScrubSaveGameFileName
1859
1860 Turns a bad file name into a good one or your money back
1861 ===============
1862 */
1863 void idSessionLocal::ScrubSaveGameFileName( idStr &saveFileName ) const {
1864         int i;
1865         idStr inFileName;
1866
1867         inFileName = saveFileName;
1868         inFileName.RemoveColors();
1869         inFileName.StripFileExtension();
1870
1871         saveFileName.Clear();
1872
1873         int len = inFileName.Length();
1874         for ( i = 0; i < len; i++ ) {
1875                 if ( strchr( "',.~!@#$%^&*()[]{}<>\\|/=?+;:-\'\"", inFileName[i] ) ) {
1876                         // random junk
1877                         saveFileName += '_';
1878                 } else if ( (const unsigned char)inFileName[i] >= 128 ) {
1879                         // high ascii chars
1880                         saveFileName += '_';
1881                 } else if ( inFileName[i] == ' ' ) {
1882                         saveFileName += '_';
1883                 } else {
1884                         saveFileName += inFileName[i];
1885                 }
1886         }
1887 }
1888
1889 /*
1890 ===============
1891 idSessionLocal::SaveGame
1892 ===============
1893 */
1894 bool idSessionLocal::SaveGame( const char *saveName, bool autosave ) {
1895 #ifdef  ID_DEDICATED
1896         common->Printf( "Dedicated servers cannot save games.\n" );
1897         return false;
1898 #else
1899         int i;
1900         idStr gameFile, previewFile, descriptionFile, mapName;
1901
1902         if ( !mapSpawned ) {
1903                 common->Printf( "Not playing a game.\n" );
1904                 return false;
1905         }
1906
1907         if ( IsMultiplayer() ) {
1908                 common->Printf( "Can't save during net play.\n" );
1909                 return false;
1910         }
1911
1912         if ( game->GetPersistentPlayerInfo( 0 ).GetInt( "health" ) <= 0 ) {
1913                 MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04311" ), common->GetLanguageDict()->GetString ( "#str_04312" ), true );
1914                 common->Printf( "You must be alive to save the game\n" );
1915                 return false;
1916         }
1917
1918         if ( Sys_GetDriveFreeSpace( cvarSystem->GetCVarString( "fs_savepath" ) ) < 25 ) {
1919                 MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04313" ), common->GetLanguageDict()->GetString ( "#str_04314" ), true );
1920                 common->Printf( "Not enough drive space to save the game\n" );
1921                 return false;
1922         }
1923
1924         idSoundWorld *pauseWorld = soundSystem->GetPlayingSoundWorld();
1925         if ( pauseWorld ) {
1926                 pauseWorld->Pause();
1927                 soundSystem->SetPlayingSoundWorld( NULL );
1928         }
1929
1930         // setup up filenames and paths
1931         gameFile = saveName;
1932         ScrubSaveGameFileName( gameFile );
1933
1934         gameFile = "savegames/" + gameFile;
1935         gameFile.SetFileExtension( ".save" );
1936
1937         previewFile = gameFile;
1938         previewFile.SetFileExtension( ".tga" );
1939
1940         descriptionFile = gameFile;
1941         descriptionFile.SetFileExtension( ".txt" );
1942
1943         // Open savegame file
1944         idFile *fileOut = fileSystem->OpenFileWrite( gameFile );
1945         if ( fileOut == NULL ) {
1946                 common->Warning( "Failed to open save file '%s'\n", gameFile.c_str() );
1947                 if ( pauseWorld ) {
1948                         soundSystem->SetPlayingSoundWorld( pauseWorld );
1949                         pauseWorld->UnPause();
1950                 }
1951                 return false;
1952         }
1953
1954         // Write SaveGame Header: 
1955         // Game Name / Version / Map Name / Persistant Player Info
1956
1957         // game
1958         const char *gamename = GAME_NAME;
1959         fileOut->WriteString( gamename );
1960
1961         // version
1962         fileOut->WriteInt( SAVEGAME_VERSION );
1963
1964         // map
1965         mapName = mapSpawnData.serverInfo.GetString( "si_map" );
1966         fileOut->WriteString( mapName );
1967
1968         // persistent player info
1969         for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
1970                 mapSpawnData.persistentPlayerInfo[i] = game->GetPersistentPlayerInfo( i );
1971                 mapSpawnData.persistentPlayerInfo[i].WriteToFileHandle( fileOut );
1972         }
1973
1974         // let the game save its state
1975         game->SaveGame( fileOut );
1976
1977         // close the sava game file
1978         fileSystem->CloseFile( fileOut );
1979
1980         // Write screenshot
1981         if ( !autosave ) {
1982                 renderSystem->CropRenderSize( 320, 240, false );
1983                 game->Draw( 0 );
1984                 renderSystem->CaptureRenderToFile( previewFile, true );
1985                 renderSystem->UnCrop();
1986         }
1987
1988         // Write description, which is just a text file with
1989         // the unclean save name on line 1, map name on line 2, screenshot on line 3
1990         idFile *fileDesc = fileSystem->OpenFileWrite( descriptionFile );
1991         if ( fileDesc == NULL ) {
1992                 common->Warning( "Failed to open description file '%s'\n", descriptionFile.c_str() );
1993                 if ( pauseWorld ) {
1994                         soundSystem->SetPlayingSoundWorld( pauseWorld );
1995                         pauseWorld->UnPause();
1996                 }
1997                 return false;
1998         }
1999
2000         idStr description = saveName;
2001         description.Replace( "\\", "\\\\" );
2002         description.Replace( "\"", "\\\"" );
2003
2004         const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>(declManager->FindType( DECL_MAPDEF, mapName, false ));
2005         if ( mapDef ) {
2006                 mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) );
2007         }
2008
2009         fileDesc->Printf( "\"%s\"\n", description.c_str() );
2010         fileDesc->Printf( "\"%s\"\n", mapName.c_str());
2011
2012         if ( autosave ) {
2013                 idStr sshot = mapSpawnData.serverInfo.GetString( "si_map" );
2014                 sshot.StripPath();
2015                 sshot.StripFileExtension();
2016                 fileDesc->Printf( "\"guis/assets/autosave/%s\"\n", sshot.c_str() );
2017         } else {
2018                 fileDesc->Printf( "\"\"\n" );
2019         }
2020
2021         fileSystem->CloseFile( fileDesc );
2022
2023         if ( pauseWorld ) {
2024                 soundSystem->SetPlayingSoundWorld( pauseWorld );
2025                 pauseWorld->UnPause();
2026         }
2027
2028         syncNextGameFrame = true;
2029
2030
2031         return true;
2032 #endif
2033 }
2034
2035 /*
2036 ===============
2037 idSessionLocal::LoadGame
2038 ===============
2039 */
2040 bool idSessionLocal::LoadGame( const char *saveName ) { 
2041 #ifdef  ID_DEDICATED
2042         common->Printf( "Dedicated servers cannot load games.\n" );
2043         return false;
2044 #else
2045         int i;
2046         idStr in, loadFile, saveMap, gamename;
2047
2048         if ( IsMultiplayer() ) {
2049                 common->Printf( "Can't load during net play.\n" );
2050                 return false;
2051         }
2052
2053         //Hide the dialog box if it is up.
2054         StopBox();
2055
2056         loadFile = saveName;
2057         ScrubSaveGameFileName( loadFile );
2058         loadFile.SetFileExtension( ".save" );
2059
2060         in = "savegames/";
2061         in += loadFile;
2062
2063         // Open savegame file
2064         // only allow loads from the game directory because we don't want a base game to load
2065         idStr game = cvarSystem->GetCVarString( "fs_game" );
2066         savegameFile = fileSystem->OpenFileRead( in, true, game.Length() ? game : NULL );
2067
2068         if ( savegameFile == NULL ) {
2069                 common->Warning( "Couldn't open savegame file %s", in.c_str() );
2070                 return false;
2071         }
2072
2073         loadingSaveGame = true;
2074
2075         // Read in save game header
2076         // Game Name / Version / Map Name / Persistant Player Info
2077
2078         // game
2079         savegameFile->ReadString( gamename );
2080
2081         // if this isn't a savegame for the correct game, abort loadgame
2082         if ( gamename != GAME_NAME ) {
2083                 common->Warning( "Attempted to load an invalid savegame: %s", in.c_str() );
2084
2085                 loadingSaveGame = false;
2086                 fileSystem->CloseFile( savegameFile );
2087                 savegameFile = NULL;
2088                 return false;
2089         }
2090
2091         // version
2092         savegameFile->ReadInt( savegameVersion );
2093
2094         // map
2095         savegameFile->ReadString( saveMap );
2096
2097         // persistent player info
2098         for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2099                 mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( savegameFile );
2100         }
2101
2102         // check the version, if it doesn't match, cancel the loadgame,
2103         // but still load the map with the persistant playerInfo from the header
2104         // so that the player doesn't lose too much progress.
2105         if ( savegameVersion != SAVEGAME_VERSION &&
2106                  !( savegameVersion == 16 && SAVEGAME_VERSION == 17 ) ) {       // handle savegame v16 in v17
2107                 common->Warning( "Savegame Version mismatch: aborting loadgame and starting level with persistent data" );
2108                 loadingSaveGame = false;
2109                 fileSystem->CloseFile( savegameFile );
2110                 savegameFile = NULL;
2111         }
2112
2113         common->DPrintf( "loading a v%d savegame\n", savegameVersion );
2114
2115         if ( saveMap.Length() > 0 ) {
2116
2117                 // Start loading map
2118                 mapSpawnData.serverInfo.Clear();
2119
2120                 mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
2121                 mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" );
2122
2123                 mapSpawnData.serverInfo.Set( "si_map", saveMap );
2124
2125                 mapSpawnData.syncedCVars.Clear();
2126                 mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC );
2127
2128                 mapSpawnData.mapSpawnUsercmd[0] = usercmdGen->TicCmd( latchedTicNumber );
2129                 // make sure no buttons are pressed
2130                 mapSpawnData.mapSpawnUsercmd[0].buttons = 0;
2131
2132                 ExecuteMapChange();
2133
2134                 SetGUI( NULL, NULL );
2135         }
2136
2137         if ( loadingSaveGame ) {
2138                 fileSystem->CloseFile( savegameFile );
2139                 loadingSaveGame = false;
2140                 savegameFile = NULL;
2141         }
2142
2143         return true;
2144 #endif
2145 }
2146
2147 /*
2148 ===============
2149 idSessionLocal::ProcessEvent
2150 ===============
2151 */
2152 bool idSessionLocal::ProcessEvent( const sysEvent_t *event ) {
2153         // hitting escape anywhere brings up the menu
2154         if ( !guiActive && event->evType == SE_KEY && event->evValue2 == 1 && event->evValue == K_ESCAPE ) {
2155                 console->Close();
2156                 if ( game ) {
2157                         idUserInterface *gui = NULL;
2158                         escReply_t              op;
2159                         op = game->HandleESC( &gui );
2160                         if ( op == ESC_IGNORE ) {
2161                                 return true;
2162                         } else if ( op == ESC_GUI ) {
2163                                 SetGUI( gui, NULL );
2164                                 return true;
2165                         }
2166                 }
2167                 StartMenu();
2168                 return true;
2169         }
2170
2171         // let the pull-down console take it if desired
2172         if ( console->ProcessEvent( event, false ) ) {
2173                 return true;
2174         }
2175
2176         // if we are testing a GUI, send all events to it
2177         if ( guiTest ) {
2178                 // hitting escape exits the testgui
2179                 if ( event->evType == SE_KEY && event->evValue2 == 1 && event->evValue == K_ESCAPE ) {
2180                         guiTest = NULL;
2181                         return true;
2182                 }
2183                 
2184                 static const char *cmd;
2185                 cmd = guiTest->HandleEvent( event, com_frameTime );
2186                 if ( cmd && cmd[0] ) {
2187                         common->Printf( "testGui event returned: '%s'\n", cmd );
2188                 }
2189                 return true;
2190         }
2191
2192         // menus / etc
2193         if ( guiActive ) {
2194                 MenuEvent( event );
2195                 return true;
2196         }
2197
2198         // if we aren't in a game, force the console to take it
2199         if ( !mapSpawned ) {
2200                 console->ProcessEvent( event, true );
2201                 return true;
2202         }
2203
2204         // in game, exec bindings for all key downs
2205         if ( event->evType == SE_KEY && event->evValue2 == 1 ) {
2206                 idKeyInput::ExecKeyBinding( event->evValue );
2207                 return true;
2208         }
2209
2210         return false;
2211 }
2212
2213 /*
2214 ===============
2215 idSessionLocal::DrawWipeModel
2216
2217 Draw the fade material over everything that has been drawn
2218 ===============
2219 */
2220 void    idSessionLocal::DrawWipeModel() {
2221         int             latchedTic = com_ticNumber;
2222
2223         if (  wipeStartTic >= wipeStopTic ) {
2224                 return;
2225         }
2226
2227         if ( !wipeHold && latchedTic >= wipeStopTic ) {
2228                 return;
2229         }
2230
2231         float fade = ( float )( latchedTic - wipeStartTic ) / ( wipeStopTic - wipeStartTic );
2232         renderSystem->SetColor4( 1, 1, 1, fade );
2233         renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, wipeMaterial );
2234 }
2235
2236 /*
2237 ===============
2238 idSessionLocal::AdvanceRenderDemo
2239 ===============
2240 */
2241 void idSessionLocal::AdvanceRenderDemo( bool singleFrameOnly ) {
2242         if ( lastDemoTic == -1 ) {
2243                 lastDemoTic = latchedTicNumber - 1;
2244         }
2245
2246         int skipFrames = 0;
2247
2248         if ( !aviCaptureMode && !timeDemo && !singleFrameOnly ) {
2249                 skipFrames = ( (latchedTicNumber - lastDemoTic) / USERCMD_PER_DEMO_FRAME ) - 1;
2250                 // never skip too many frames, just let it go into slightly slow motion
2251                 if ( skipFrames > 4 ) {
2252                         skipFrames = 4;
2253                 }
2254                 lastDemoTic = latchedTicNumber - latchedTicNumber % USERCMD_PER_DEMO_FRAME;
2255         } else {
2256                 // always advance a single frame with avidemo and timedemo
2257                 lastDemoTic = latchedTicNumber; 
2258         }
2259
2260         while( skipFrames > -1 ) {
2261                 int             ds = DS_FINISHED;
2262
2263                 readDemo->ReadInt( ds );
2264                 if ( ds == DS_FINISHED ) {
2265                         if ( numDemoFrames != 1 ) {
2266                                 // if the demo has a single frame (a demoShot), continuously replay
2267                                 // the renderView that has already been read
2268                                 Stop();
2269                                 StartMenu();
2270                         }
2271                         break;
2272                 }
2273                 if ( ds == DS_RENDER ) {
2274                         if ( rw->ProcessDemoCommand( readDemo, &currentDemoRenderView, &demoTimeOffset ) ) {
2275                                 // a view is ready to render
2276                                 skipFrames--;
2277                                 numDemoFrames++;
2278                         }
2279                         continue;
2280                 }
2281                 if ( ds == DS_SOUND ) {
2282                         sw->ProcessDemoCommand( readDemo );
2283                         continue;
2284                 }
2285                 // appears in v1.2, with savegame format 17
2286                 if ( ds == DS_VERSION ) {
2287                         readDemo->ReadInt( renderdemoVersion );
2288                         common->Printf( "reading a v%d render demo\n", renderdemoVersion );
2289                         // set the savegameVersion to current for render demo paths that share the savegame paths
2290                         savegameVersion = SAVEGAME_VERSION;
2291                         continue;
2292                 }
2293                 common->Error( "Bad render demo token" );
2294         }
2295
2296         if ( com_showDemo.GetBool() ) {
2297                 common->Printf( "frame:%i DemoTic:%i latched:%i skip:%i\n", numDemoFrames, lastDemoTic, latchedTicNumber, skipFrames );
2298         }
2299
2300 }
2301
2302 /*
2303 ===============
2304 idSessionLocal::DrawCmdGraph
2305
2306 Graphs yaw angle for testing smoothness
2307 ===============
2308 */
2309 static const int        ANGLE_GRAPH_HEIGHT = 128;
2310 static const int        ANGLE_GRAPH_STRETCH = 3;
2311 void idSessionLocal::DrawCmdGraph() {
2312         if ( !com_showAngles.GetBool() ) {
2313                 return;
2314         }
2315         renderSystem->SetColor4( 0.1f, 0.1f, 0.1f, 1.0f );
2316         renderSystem->DrawStretchPic( 0, 480-ANGLE_GRAPH_HEIGHT, MAX_BUFFERED_USERCMD*ANGLE_GRAPH_STRETCH, ANGLE_GRAPH_HEIGHT, 0, 0, 1, 1, whiteMaterial );
2317         renderSystem->SetColor4( 0.9f, 0.9f, 0.9f, 1.0f );
2318         for ( int i = 0 ; i < MAX_BUFFERED_USERCMD-4 ; i++ ) {
2319                 usercmd_t       cmd = usercmdGen->TicCmd( latchedTicNumber - (MAX_BUFFERED_USERCMD-4) + i );
2320                 int h = cmd.angles[1];
2321                 h >>= 8;
2322                 h &= (ANGLE_GRAPH_HEIGHT-1);
2323                 renderSystem->DrawStretchPic( i* ANGLE_GRAPH_STRETCH, 480-h, 1, h, 0, 0, 1, 1, whiteMaterial );
2324         }
2325 }
2326
2327 /*
2328 ===============
2329 idSessionLocal::PacifierUpdate
2330 ===============
2331 */
2332 void idSessionLocal::PacifierUpdate() {
2333         if ( !insideExecuteMapChange ) {
2334                 return;
2335         }
2336
2337         // never do pacifier screen updates while inside the
2338         // drawing code, or we can have various recursive problems
2339         if ( insideUpdateScreen ) {
2340                 return;
2341         }
2342
2343         int     time = eventLoop->Milliseconds();
2344
2345         if ( time - lastPacifierTime < 100 ) {
2346                 return;
2347         }
2348         lastPacifierTime = time;
2349
2350         if ( guiLoading && bytesNeededForMapLoad ) {
2351                 float n = fileSystem->GetReadCount();
2352                 float pct = ( n / bytesNeededForMapLoad );
2353                 // pct = idMath::ClampFloat( 0.0f, 100.0f, pct );
2354                 guiLoading->SetStateFloat( "map_loading", pct );
2355                 guiLoading->StateChanged( com_frameTime );
2356         }
2357
2358         Sys_GenerateEvents();
2359
2360         UpdateScreen();
2361
2362         idAsyncNetwork::client.PacifierUpdate();
2363         idAsyncNetwork::server.PacifierUpdate();
2364 }
2365
2366 /*
2367 ===============
2368 idSessionLocal::Draw
2369 ===============
2370 */
2371 void idSessionLocal::Draw() {
2372         bool fullConsole = false;
2373
2374         if ( insideExecuteMapChange ) {
2375                 if ( guiLoading ) {
2376                         guiLoading->Redraw( com_frameTime );
2377                 }
2378                 if ( guiActive == guiMsg ) {
2379                         guiMsg->Redraw( com_frameTime );
2380                 } 
2381         } else if ( guiTest ) {
2382                 // if testing a gui, clear the screen and draw it
2383                 // clear the background, in case the tested gui is transparent
2384                 // NOTE that you can't use this for aviGame recording, it will tick at real com_frameTime between screenshots..
2385                 renderSystem->SetColor( colorBlack );
2386                 renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) );
2387                 guiTest->Redraw( com_frameTime );
2388         } else if ( guiActive && !guiActive->State().GetBool( "gameDraw" ) ) {
2389                 
2390                 // draw the frozen gui in the background
2391                 if ( guiActive == guiMsg && guiMsgRestore ) {
2392                         guiMsgRestore->Redraw( com_frameTime );
2393                 }
2394                 
2395                 // draw the menus full screen
2396                 if ( guiActive == guiTakeNotes && !com_skipGameDraw.GetBool() ) {
2397                         game->Draw( GetLocalClientNum() );
2398                 }
2399
2400                 guiActive->Redraw( com_frameTime );
2401         } else if ( readDemo ) {
2402                 rw->RenderScene( &currentDemoRenderView );
2403                 renderSystem->DrawDemoPics();
2404         } else if ( mapSpawned ) {
2405                 bool gameDraw = false;
2406                 // normal drawing for both single and multi player
2407                 if ( !com_skipGameDraw.GetBool() && GetLocalClientNum() >= 0 ) {
2408                         // draw the game view
2409                         int     start = Sys_Milliseconds();
2410                         gameDraw = game->Draw( GetLocalClientNum() );
2411                         int end = Sys_Milliseconds();
2412                         time_gameDraw += ( end - start );       // note time used for com_speeds
2413                 }
2414                 if ( !gameDraw ) {
2415                         renderSystem->SetColor( colorBlack );
2416                         renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) );
2417                 }
2418
2419                 // save off the 2D drawing from the game
2420                 if ( writeDemo ) {
2421                         renderSystem->WriteDemoPics();
2422                 }
2423         } else {
2424 #if ID_CONSOLE_LOCK
2425                 if ( com_allowConsole.GetBool() ) {
2426                         console->Draw( true );
2427                 } else {
2428                         emptyDrawCount++;
2429                         if ( emptyDrawCount > 5 ) {
2430                                 // it's best if you can avoid triggering the watchgod by doing the right thing somewhere else
2431                                 assert( false );
2432                                 common->Warning( "idSession: triggering mainmenu watchdog" );
2433                                 emptyDrawCount = 0;
2434                                 StartMenu();
2435                         }
2436                         renderSystem->SetColor4( 0, 0, 0, 1 );
2437                         renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) );
2438                 }
2439 #else
2440                 // draw the console full screen - this should only ever happen in developer builds
2441                 console->Draw( true );
2442 #endif
2443                 fullConsole = true;
2444         }
2445
2446 #if ID_CONSOLE_LOCK
2447         if ( !fullConsole && emptyDrawCount ) {
2448                 common->DPrintf( "idSession: %d empty frame draws\n", emptyDrawCount );
2449                 emptyDrawCount = 0;
2450         }
2451         fullConsole = false;
2452 #endif
2453
2454         // draw the wipe material on top of this if it hasn't completed yet
2455         DrawWipeModel();
2456         
2457         // draw debug graphs
2458         DrawCmdGraph();
2459
2460         // draw the half console / notify console on top of everything
2461         if ( !fullConsole ) {
2462                 console->Draw( false );
2463         }
2464 }
2465
2466 /*
2467 ===============
2468 idSessionLocal::UpdateScreen
2469 ===============
2470 */
2471 void idSessionLocal::UpdateScreen( bool outOfSequence ) {
2472
2473 #ifdef _WIN32
2474
2475         if ( com_editors ) {
2476                 if ( !Sys_IsWindowVisible() ) {
2477                         return;
2478                 }
2479         }
2480 #endif
2481
2482         if ( insideUpdateScreen ) {
2483                 return;
2484 //              common->FatalError( "idSessionLocal::UpdateScreen: recursively called" );
2485         }
2486
2487         insideUpdateScreen = true;
2488
2489         // if this is a long-operation update and we are in windowed mode,
2490         // release the mouse capture back to the desktop
2491         if ( outOfSequence ) {
2492                 Sys_GrabMouseCursor( false );
2493         }
2494
2495         renderSystem->BeginFrame( renderSystem->GetScreenWidth(), renderSystem->GetScreenHeight() );
2496
2497         // draw everything
2498         Draw();
2499
2500         if ( com_speeds.GetBool() ) {
2501                 renderSystem->EndFrame( &time_frontend, &time_backend );
2502         } else {
2503                 renderSystem->EndFrame( NULL, NULL );
2504         }
2505
2506         insideUpdateScreen = false;
2507 }
2508
2509 /*
2510 ===============
2511 idSessionLocal::Frame
2512 ===============
2513 */
2514 void idSessionLocal::Frame() {
2515
2516         if ( com_asyncSound.GetInteger() == 0 ) {
2517                 soundSystem->AsyncUpdate( Sys_Milliseconds() );
2518         }
2519
2520         // Editors that completely take over the game
2521         if ( com_editorActive && ( com_editors & ( EDITOR_RADIANT | EDITOR_GUI ) ) ) {
2522                 return;
2523         }
2524
2525         // if the console is down, we don't need to hold
2526         // the mouse cursor
2527         if ( console->Active() || com_editorActive ) {
2528                 Sys_GrabMouseCursor( false );
2529         } else {
2530                 Sys_GrabMouseCursor( true );
2531         }
2532
2533         // save the screenshot and audio from the last draw if needed
2534         if ( aviCaptureMode ) {
2535                 idStr   name;
2536
2537                 name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviTicStart );
2538
2539                 float ratio = 30.0f / ( 1000.0f / USERCMD_MSEC / com_aviDemoTics.GetInteger() );
2540                 aviDemoFrameCount += ratio;
2541                 if ( aviTicStart + 1 != ( int )aviDemoFrameCount ) {
2542                         // skipped frames so write them out
2543                         int c = aviDemoFrameCount - aviTicStart;
2544                         while ( c-- ) {
2545                                 renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL );
2546                                 name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), ++aviTicStart );
2547                         }
2548                 }
2549                 aviTicStart = aviDemoFrameCount;
2550
2551                 // remove any printed lines at the top before taking the screenshot
2552                 console->ClearNotifyLines();
2553
2554                 // this will call Draw, possibly multiple times if com_aviDemoSamples is > 1
2555                 renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL );
2556         }
2557
2558         // at startup, we may be backwards
2559         if ( latchedTicNumber > com_ticNumber ) {
2560                 latchedTicNumber = com_ticNumber;
2561         }
2562
2563         // se how many tics we should have before continuing
2564         int     minTic = latchedTicNumber + 1;
2565         if ( com_minTics.GetInteger() > 1 ) {
2566                 minTic = lastGameTic + com_minTics.GetInteger();
2567         }
2568         
2569         if ( readDemo ) {
2570                 if ( !timeDemo && numDemoFrames != 1 ) {
2571                         minTic = lastDemoTic + USERCMD_PER_DEMO_FRAME;
2572                 } else {
2573                         // timedemos and demoshots will run as fast as they can, other demos
2574                         // will not run more than 30 hz
2575                         minTic = latchedTicNumber;
2576                 }
2577         } else if ( writeDemo ) {
2578                 minTic = lastGameTic + USERCMD_PER_DEMO_FRAME;          // demos are recorded at 30 hz
2579         }
2580         
2581         // fixedTic lets us run a forced number of usercmd each frame without timing
2582         if ( com_fixedTic.GetInteger() ) {
2583                 minTic = latchedTicNumber;
2584         }
2585
2586         // FIXME: deserves a cleanup and abstraction
2587 #if defined( _WIN32 )
2588         // Spin in place if needed.  The game should yield the cpu if
2589         // it is running over 60 hz, because there is fundamentally
2590         // nothing useful for it to do.
2591         while( 1 ) {
2592                 latchedTicNumber = com_ticNumber;
2593                 if ( latchedTicNumber >= minTic ) {
2594                         break;
2595                 }
2596                 Sys_Sleep( 1 );
2597         }
2598 #else
2599         while( 1 ) {
2600                 latchedTicNumber = com_ticNumber;
2601                 if ( latchedTicNumber >= minTic ) {
2602                         break;
2603                 }
2604                 Sys_WaitForEvent( TRIGGER_EVENT_ONE );
2605         }
2606 #endif
2607
2608         if ( authEmitTimeout ) {
2609                 // waiting for a game auth
2610                 if ( Sys_Milliseconds() > authEmitTimeout ) {
2611                         // expired with no reply
2612                         // means that if a firewall is blocking the master, we will let through
2613                         common->DPrintf( "no reply from auth\n" );
2614                         if ( authWaitBox ) {
2615                                 // close the wait box
2616                                 StopBox();
2617                                 authWaitBox = false;
2618                         }
2619                         if ( cdkey_state == CDKEY_CHECKING ) {
2620                                 cdkey_state = CDKEY_OK;
2621                         }
2622                         if ( xpkey_state == CDKEY_CHECKING ) {
2623                                 xpkey_state = CDKEY_OK;
2624                         }
2625                         // maintain this empty as it's set by auth denials
2626                         authMsg.Empty();
2627                         authEmitTimeout = 0;
2628                         SetCDKeyGuiVars();
2629                 }
2630         }
2631
2632         // send frame and mouse events to active guis
2633         GuiFrameEvents();
2634
2635         // advance demos
2636         if ( readDemo ) {
2637                 AdvanceRenderDemo( false );
2638                 return;
2639         }
2640
2641         //------------ single player game tics --------------
2642
2643         if ( !mapSpawned || guiActive ) {
2644                 if ( !com_asyncInput.GetBool() ) {
2645                         // early exit, won't do RunGameTic .. but still need to update mouse position for GUIs
2646                         usercmdGen->GetDirectUsercmd();
2647                 }
2648         }
2649
2650         if ( !mapSpawned ) {
2651                 return;
2652         }
2653
2654         if ( guiActive ) {
2655                 lastGameTic = latchedTicNumber;
2656                 return;
2657         }
2658
2659         // in message box / GUIFrame, idSessionLocal::Frame is used for GUI interactivity
2660         // but we early exit to avoid running game frames
2661         if ( idAsyncNetwork::IsActive() ) {
2662                 return;
2663         }
2664
2665         // check for user info changes
2666         if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) {
2667                 mapSpawnData.userInfo[0] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
2668                 game->SetUserInfo( 0, mapSpawnData.userInfo[0], false, false );
2669                 cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
2670         }
2671
2672         // see how many usercmds we are going to run
2673         int     numCmdsToRun = latchedTicNumber - lastGameTic;
2674
2675         // don't let a long onDemand sound load unsync everything
2676         if ( timeHitch ) {
2677                 int     skip = timeHitch / USERCMD_MSEC;
2678                 lastGameTic += skip;
2679                 numCmdsToRun -= skip;
2680                 timeHitch = 0;
2681         }
2682
2683         // don't get too far behind after a hitch
2684         if ( numCmdsToRun > 10 ) {
2685                 lastGameTic = latchedTicNumber - 10;
2686         }
2687
2688         // never use more than USERCMD_PER_DEMO_FRAME,
2689         // which makes it go into slow motion when recording
2690         if ( writeDemo ) {
2691                 int fixedTic = USERCMD_PER_DEMO_FRAME;
2692                 // we should have waited long enough
2693                 if ( numCmdsToRun < fixedTic ) {
2694                         common->Error( "idSessionLocal::Frame: numCmdsToRun < fixedTic" );
2695                 }
2696                 // we may need to dump older commands
2697                 lastGameTic = latchedTicNumber - fixedTic;
2698         } else if ( com_fixedTic.GetInteger() > 0 ) {
2699                 // this may cause commands run in a previous frame to
2700                 // be run again if we are going at above the real time rate
2701                 lastGameTic = latchedTicNumber - com_fixedTic.GetInteger();
2702         } else if (     aviCaptureMode ) {
2703                 lastGameTic = latchedTicNumber - com_aviDemoTics.GetInteger();
2704         }
2705
2706         // force only one game frame update this frame.  the game code requests this after skipping cinematics
2707         // so we come back immediately after the cinematic is done instead of a few frames later which can
2708         // cause sounds played right after the cinematic to not play.
2709         if ( syncNextGameFrame ) {
2710                 lastGameTic = latchedTicNumber - 1;
2711                 syncNextGameFrame = false;
2712         }
2713
2714         // create client commands, which will be sent directly
2715         // to the game
2716         if ( com_showTics.GetBool() ) {
2717                 common->Printf( "%i ", latchedTicNumber - lastGameTic );
2718         }
2719
2720         int     gameTicsToRun = latchedTicNumber - lastGameTic;
2721         int i;
2722         for ( i = 0 ; i < gameTicsToRun ; i++ ) {
2723                 RunGameTic();
2724                 if ( !mapSpawned ) {
2725                         // exited game play
2726                         break;
2727                 }
2728                 if ( syncNextGameFrame ) {
2729                         // long game frame, so break out and continue executing as if there was no hitch
2730                         break;
2731                 }
2732         }
2733 }
2734
2735 /*
2736 ================
2737 idSessionLocal::RunGameTic
2738 ================
2739 */
2740 void idSessionLocal::RunGameTic() {
2741         logCmd_t        logCmd;
2742         usercmd_t       cmd;
2743
2744         // if we are doing a command demo, read or write from the file
2745         if ( cmdDemoFile ) {
2746                 if ( !cmdDemoFile->Read( &logCmd, sizeof( logCmd ) ) ) {
2747                         common->Printf( "Command demo completed at logIndex %i\n", logIndex );
2748                         fileSystem->CloseFile( cmdDemoFile );
2749                         cmdDemoFile = NULL;
2750                         if ( aviCaptureMode ) {
2751                                 EndAVICapture();
2752                                 Shutdown();
2753                         }
2754                         // we fall out of the demo to normal commands
2755                         // the impulse and chat character toggles may not be correct, and the view
2756                         // angle will definitely be wrong
2757                 } else {
2758                         cmd = logCmd.cmd;
2759                         cmd.ByteSwap();
2760                         logCmd.consistencyHash = LittleLong( logCmd.consistencyHash );
2761                 }
2762         }
2763         
2764         // if we didn't get one from the file, get it locally
2765         if ( !cmdDemoFile ) {
2766                 // get a locally created command
2767                 if ( com_asyncInput.GetBool() ) {
2768                         cmd = usercmdGen->TicCmd( lastGameTic );
2769                 } else {
2770                         cmd = usercmdGen->GetDirectUsercmd();
2771                 }
2772                 lastGameTic++;
2773         }
2774
2775         // run the game logic every player move
2776         int     start = Sys_Milliseconds();
2777         gameReturn_t    ret = game->RunFrame( &cmd );
2778
2779         int end = Sys_Milliseconds();
2780         time_gameFrame += end - start;  // note time used for com_speeds
2781
2782         // check for constency failure from a recorded command
2783         if ( cmdDemoFile ) {
2784                 if ( ret.consistencyHash != logCmd.consistencyHash ) {
2785                         common->Printf( "Consistency failure on logIndex %i\n", logIndex );
2786                         Stop();
2787                         return;
2788                 }
2789         }
2790
2791         // save the cmd for cmdDemo archiving
2792         if ( logIndex < MAX_LOGGED_USERCMDS ) {
2793                 loggedUsercmds[logIndex].cmd = cmd;
2794                 // save the consistencyHash for demo playback verification
2795                 loggedUsercmds[logIndex].consistencyHash = ret.consistencyHash;
2796                 if (logIndex % 30 == 0 && statIndex < MAX_LOGGED_STATS) {
2797                         loggedStats[statIndex].health = ret.health;
2798                         loggedStats[statIndex].heartRate = ret.heartRate;
2799                         loggedStats[statIndex].stamina = ret.stamina;
2800                         loggedStats[statIndex].combat = ret.combat;
2801                         statIndex++;
2802                 }
2803                 logIndex++;
2804         }
2805
2806         syncNextGameFrame = ret.syncNextGameFrame;
2807
2808         if ( ret.sessionCommand[0] ) {
2809                 idCmdArgs args;
2810
2811                 args.TokenizeString( ret.sessionCommand, false );
2812
2813                 if ( !idStr::Icmp( args.Argv(0), "map" ) ) {
2814                         // get current player states
2815                         for ( int i = 0 ; i < numClients ; i++ ) {
2816                                 mapSpawnData.persistentPlayerInfo[i] = game->GetPersistentPlayerInfo( i );
2817                         }
2818                         // clear the devmap key on serverinfo, so player spawns
2819                         // won't get the map testing items
2820                         mapSpawnData.serverInfo.Delete( "devmap" );
2821
2822                         // go to the next map
2823                         MoveToNewMap( args.Argv(1) );
2824                 } else if ( !idStr::Icmp( args.Argv(0), "devmap" ) ) {
2825                         mapSpawnData.serverInfo.Set( "devmap", "1" );
2826                         MoveToNewMap( args.Argv(1) );
2827                 } else if ( !idStr::Icmp( args.Argv(0), "died" ) ) {
2828                         // restart on the same map
2829                         UnloadMap();
2830                         SetGUI(guiRestartMenu, NULL);
2831                 } else if ( !idStr::Icmp( args.Argv(0), "disconnect" ) ) {
2832                         cmdSystem->BufferCommandText( CMD_EXEC_INSERT, "stoprecording ; disconnect" );
2833                 } else if ( !idStr::Icmp( args.Argv(0), "endOfDemo" ) ) {
2834                         cmdSystem->BufferCommandText( CMD_EXEC_NOW, "endOfDemo" );
2835                 }
2836         }
2837 }
2838
2839 /*
2840 ===============
2841 idSessionLocal::Init
2842
2843 Called in an orderly fashion at system startup,
2844 so commands, cvars, files, etc are all available
2845 ===============
2846 */
2847 void idSessionLocal::Init() {
2848
2849         common->Printf( "-------- Initializing Session --------\n" );
2850
2851         cmdSystem->AddCommand( "writePrecache", Sess_WritePrecache_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "writes precache commands" );
2852
2853 #ifndef ID_DEDICATED
2854         cmdSystem->AddCommand( "map", Session_Map_f, CMD_FL_SYSTEM, "loads a map", idCmdSystem::ArgCompletion_MapName );
2855         cmdSystem->AddCommand( "devmap", Session_DevMap_f, CMD_FL_SYSTEM, "loads a map in developer mode", idCmdSystem::ArgCompletion_MapName );
2856         cmdSystem->AddCommand( "testmap", Session_TestMap_f, CMD_FL_SYSTEM, "tests a map", idCmdSystem::ArgCompletion_MapName );
2857
2858         cmdSystem->AddCommand( "writeCmdDemo", Session_WriteCmdDemo_f, CMD_FL_SYSTEM, "writes a command demo" );
2859         cmdSystem->AddCommand( "playCmdDemo", Session_PlayCmdDemo_f, CMD_FL_SYSTEM, "plays back a command demo" );
2860         cmdSystem->AddCommand( "timeCmdDemo", Session_TimeCmdDemo_f, CMD_FL_SYSTEM, "times a command demo" );
2861         cmdSystem->AddCommand( "exitCmdDemo", Session_ExitCmdDemo_f, CMD_FL_SYSTEM, "exits a command demo" );
2862         cmdSystem->AddCommand( "aviCmdDemo", Session_AVICmdDemo_f, CMD_FL_SYSTEM, "writes AVIs for a command demo" );
2863         cmdSystem->AddCommand( "aviGame", Session_AVIGame_f, CMD_FL_SYSTEM, "writes AVIs for the current game" );
2864
2865         cmdSystem->AddCommand( "recordDemo", Session_RecordDemo_f, CMD_FL_SYSTEM, "records a demo" );
2866         cmdSystem->AddCommand( "stopRecording", Session_StopRecordingDemo_f, CMD_FL_SYSTEM, "stops demo recording" );
2867         cmdSystem->AddCommand( "playDemo", Session_PlayDemo_f, CMD_FL_SYSTEM, "plays back a demo", idCmdSystem::ArgCompletion_DemoName );
2868         cmdSystem->AddCommand( "timeDemo", Session_TimeDemo_f, CMD_FL_SYSTEM, "times a demo", idCmdSystem::ArgCompletion_DemoName );
2869         cmdSystem->AddCommand( "timeDemoQuit", Session_TimeDemoQuit_f, CMD_FL_SYSTEM, "times a demo and quits", idCmdSystem::ArgCompletion_DemoName );
2870         cmdSystem->AddCommand( "aviDemo", Session_AVIDemo_f, CMD_FL_SYSTEM, "writes AVIs for a demo", idCmdSystem::ArgCompletion_DemoName );
2871         cmdSystem->AddCommand( "compressDemo", Session_CompressDemo_f, CMD_FL_SYSTEM, "compresses a demo file", idCmdSystem::ArgCompletion_DemoName );
2872 #endif
2873
2874         cmdSystem->AddCommand( "disconnect", Session_Disconnect_f, CMD_FL_SYSTEM, "disconnects from a game" );
2875
2876 #ifdef ID_DEMO_BUILD
2877         cmdSystem->AddCommand( "endOfDemo", Session_EndOfDemo_f, CMD_FL_SYSTEM, "ends the demo version of the game" );
2878 #endif
2879
2880         cmdSystem->AddCommand( "demoShot", Session_DemoShot_f, CMD_FL_SYSTEM, "writes a screenshot for a demo" );
2881         cmdSystem->AddCommand( "testGUI", Session_TestGUI_f, CMD_FL_SYSTEM, "tests a gui" );
2882
2883 #ifndef ID_DEDICATED
2884         cmdSystem->AddCommand( "saveGame", SaveGame_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "saves a game" );
2885         cmdSystem->AddCommand( "loadGame", LoadGame_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "loads a game", idCmdSystem::ArgCompletion_SaveGame );
2886 #endif
2887
2888         cmdSystem->AddCommand( "takeViewNotes", TakeViewNotes_f, CMD_FL_SYSTEM, "take notes about the current map from the current view" );
2889         cmdSystem->AddCommand( "takeViewNotes2", TakeViewNotes2_f, CMD_FL_SYSTEM, "extended take view notes" );
2890
2891         cmdSystem->AddCommand( "rescanSI", Session_RescanSI_f, CMD_FL_SYSTEM, "internal - rescan serverinfo cvars and tell game" );
2892
2893         cmdSystem->AddCommand( "promptKey", Session_PromptKey_f, CMD_FL_SYSTEM, "prompt and sets the CD Key" );
2894
2895         cmdSystem->AddCommand( "hitch", Session_Hitch_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "hitches the game" );
2896
2897         // the same idRenderWorld will be used for all games
2898         // and demos, insuring that level specific models
2899         // will be freed
2900         rw = renderSystem->AllocRenderWorld();
2901         sw = soundSystem->AllocSoundWorld( rw );
2902
2903         menuSoundWorld = soundSystem->AllocSoundWorld( rw );
2904
2905         // we have a single instance of the main menu
2906 #ifndef ID_DEMO_BUILD
2907         guiMainMenu = uiManager->FindGui( "guis/mainmenu.gui", true, false, true );
2908 #else
2909         guiMainMenu = uiManager->FindGui( "guis/demo_mainmenu.gui", true, false, true );
2910 #endif
2911         guiMainMenu_MapList = uiManager->AllocListGUI();
2912         guiMainMenu_MapList->Config( guiMainMenu, "mapList" );
2913         idAsyncNetwork::client.serverList.GUIConfig( guiMainMenu, "serverList" );
2914         guiRestartMenu = uiManager->FindGui( "guis/restart.gui", true, false, true );
2915         guiGameOver = uiManager->FindGui( "guis/gameover.gui", true, false, true );
2916         guiMsg = uiManager->FindGui( "guis/msg.gui", true, false, true );
2917         guiTakeNotes = uiManager->FindGui( "guis/takeNotes.gui", true, false, true );
2918         guiIntro = uiManager->FindGui( "guis/intro.gui", true, false, true );
2919
2920         whiteMaterial = declManager->FindMaterial( "_white" );
2921
2922         guiInGame = NULL;
2923         guiTest = NULL;
2924
2925         guiActive = NULL;
2926         guiHandle = NULL;
2927
2928         ReadCDKey();
2929
2930         common->Printf( "session initialized\n" );
2931         common->Printf( "--------------------------------------\n" );
2932 }
2933
2934 /*
2935 ===============
2936 idSessionLocal::GetLocalClientNum
2937 ===============
2938 */
2939 int idSessionLocal::GetLocalClientNum() {
2940         if ( idAsyncNetwork::client.IsActive() ) {
2941                 return idAsyncNetwork::client.GetLocalClientNum();
2942         } else if ( idAsyncNetwork::server.IsActive() ) {
2943                 if ( idAsyncNetwork::serverDedicated.GetInteger() == 0 ) {
2944                         return 0;
2945                 } else if ( idAsyncNetwork::server.IsClientInGame( idAsyncNetwork::serverDrawClient.GetInteger() ) ) {
2946                         return idAsyncNetwork::serverDrawClient.GetInteger();
2947                 } else {
2948                         return -1;
2949                 }
2950         } else {
2951                 return 0;
2952         }
2953 }
2954
2955 /*
2956 ===============
2957 idSessionLocal::SetPlayingSoundWorld
2958 ===============
2959 */
2960 void idSessionLocal::SetPlayingSoundWorld() {
2961         if ( guiActive && ( guiActive == guiMainMenu || guiActive == guiIntro || guiActive == guiLoading || ( guiActive == guiMsg && !mapSpawned ) ) ) {
2962                 soundSystem->SetPlayingSoundWorld( menuSoundWorld );
2963         } else {
2964                 soundSystem->SetPlayingSoundWorld( sw );
2965         }
2966 }
2967
2968 /*
2969 ===============
2970 idSessionLocal::TimeHitch
2971
2972 this is used by the sound system when an OnDemand sound is loaded, so the game action
2973 doesn't advance and get things out of sync
2974 ===============
2975 */
2976 void idSessionLocal::TimeHitch( int msec ) {
2977         timeHitch += msec;
2978 }
2979
2980 /*
2981 =================
2982 idSessionLocal::ReadCDKey
2983 =================
2984 */
2985 void idSessionLocal::ReadCDKey( void ) {
2986         idStr filename;
2987         idFile *f;
2988         char buffer[32];
2989
2990         cdkey_state = CDKEY_UNKNOWN;
2991
2992         filename = "../" BASE_GAMEDIR "/" CDKEY_FILE;
2993         f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_savepath" ) );
2994         if ( !f ) {
2995                 common->Printf( "Couldn't read %s.\n", filename.c_str() );
2996                 cdkey[ 0 ] = '\0';
2997         } else {
2998                 memset( buffer, 0, sizeof(buffer) );
2999                 f->Read( buffer, CDKEY_BUF_LEN - 1 );
3000                 fileSystem->CloseFile( f );
3001                 idStr::Copynz( cdkey, buffer, CDKEY_BUF_LEN );
3002         }
3003
3004         xpkey_state = CDKEY_UNKNOWN;
3005
3006         filename = "../" BASE_GAMEDIR "/" XPKEY_FILE;
3007         f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_savepath" ) );
3008         if ( !f ) {
3009                 common->Printf( "Couldn't read %s.\n", filename.c_str() );
3010                 xpkey[ 0 ] = '\0';
3011         } else {
3012                 memset( buffer, 0, sizeof(buffer) );
3013                 f->Read( buffer, CDKEY_BUF_LEN - 1 );
3014                 fileSystem->CloseFile( f );
3015                 idStr::Copynz( xpkey, buffer, CDKEY_BUF_LEN );
3016         }
3017 }
3018
3019 /*
3020 ================
3021 idSessionLocal::WriteCDKey
3022 ================
3023 */
3024 void idSessionLocal::WriteCDKey( void ) {
3025         idStr filename;
3026         idFile *f;
3027         const char *OSPath;
3028
3029         filename = "../" BASE_GAMEDIR "/" CDKEY_FILE;
3030         // OpenFileWrite advertises creating directories to the path if needed, but that won't work with a '..' in the path
3031         // occasionally on windows, but mostly on Linux and OSX, the fs_savepath/base may not exist in full
3032         OSPath = fileSystem->BuildOSPath( cvarSystem->GetCVarString( "fs_savepath" ), BASE_GAMEDIR, CDKEY_FILE );
3033         fileSystem->CreateOSPath( OSPath );
3034         f = fileSystem->OpenFileWrite( filename );
3035         if ( !f ) {
3036                 common->Printf( "Couldn't write %s.\n", filename.c_str() );
3037                 return;
3038         }
3039         f->Printf( "%s%s", cdkey, CDKEY_TEXT );
3040         fileSystem->CloseFile( f );
3041
3042         filename = "../" BASE_GAMEDIR "/" XPKEY_FILE;
3043         f = fileSystem->OpenFileWrite( filename );
3044         if ( !f ) {
3045                 common->Printf( "Couldn't write %s.\n", filename.c_str() );
3046                 return;
3047         }
3048         f->Printf( "%s%s", xpkey, CDKEY_TEXT );
3049         fileSystem->CloseFile( f );
3050 }
3051
3052 /*
3053 ===============
3054 idSessionLocal::ClearKey
3055 ===============
3056 */
3057 void idSessionLocal::ClearCDKey( bool valid[ 2 ] ) {
3058         if ( !valid[ 0 ] ) {
3059                 memset( cdkey, 0, CDKEY_BUF_LEN );
3060                 cdkey_state = CDKEY_UNKNOWN;
3061         } else if ( cdkey_state == CDKEY_CHECKING ) {
3062                 // if a key was in checking and not explicitely asked for clearing, put it back to ok
3063                 cdkey_state = CDKEY_OK;
3064         }
3065         if ( !valid[ 1 ] ) {
3066                 memset( xpkey, 0, CDKEY_BUF_LEN );
3067                 xpkey_state = CDKEY_UNKNOWN;
3068         } else if ( xpkey_state == CDKEY_CHECKING ) {
3069                 xpkey_state = CDKEY_OK;
3070         }
3071         WriteCDKey( );
3072 }
3073
3074 /*
3075 ================
3076 idSessionLocal::GetCDKey
3077 ================
3078 */
3079 const char *idSessionLocal::GetCDKey( bool xp ) {
3080         if ( !xp ) {
3081                 return cdkey;
3082         }
3083         if ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING ) {
3084                 return xpkey;
3085         }
3086         return NULL;
3087 }
3088
3089 // digits to letters table
3090 #define CDKEY_DIGITS "TWSBJCGD7PA23RLH"
3091
3092 /*
3093 ===============
3094 idSessionLocal::EmitGameAuth
3095 we toggled some key state to CDKEY_CHECKING. send a standalone auth packet to validate
3096 ===============
3097 */
3098 void idSessionLocal::EmitGameAuth( void ) {
3099         // make sure the auth reply is empty, we use it to indicate an auth reply
3100         authMsg.Empty();
3101         if ( idAsyncNetwork::client.SendAuthCheck( cdkey_state == CDKEY_CHECKING ? cdkey : NULL, xpkey_state == CDKEY_CHECKING ? xpkey : NULL ) ) {             
3102                 authEmitTimeout = Sys_Milliseconds() + CDKEY_AUTH_TIMEOUT;
3103                 common->DPrintf( "authing with the master..\n" );
3104         } else {
3105                 // net is not available
3106                 common->DPrintf( "sendAuthCheck failed\n" );
3107                 if ( cdkey_state == CDKEY_CHECKING ) {
3108                         cdkey_state = CDKEY_OK;
3109                 }
3110                 if ( xpkey_state == CDKEY_CHECKING ) {
3111                         xpkey_state = CDKEY_OK;
3112                 }
3113         }       
3114 }
3115
3116 /*
3117 ================
3118 idSessionLocal::CheckKey
3119 the function will only modify keys to _OK or _CHECKING if the offline checks are passed
3120 if the function returns false, the offline checks failed, and offline_valid holds which keys are bad
3121 ================
3122 */
3123 bool idSessionLocal::CheckKey( const char *key, bool netConnect, bool offline_valid[ 2 ] ) {
3124         char lkey[ 2 ][ CDKEY_BUF_LEN ];
3125         char l_chk[ 2 ][ 3 ];
3126         char s_chk[ 3 ];
3127         int imax,i_key;
3128         unsigned int checksum, chk8;
3129         bool edited_key[ 2 ];
3130
3131         // make sure have a right input string
3132         assert( strlen( key ) == ( CDKEY_BUF_LEN - 1 ) * 2 + 4 + 3 + 4 );
3133
3134         edited_key[ 0 ] = ( key[0] == '1' );
3135         idStr::Copynz( lkey[0], key + 2, CDKEY_BUF_LEN );
3136         idStr::ToUpper( lkey[0] );
3137         idStr::Copynz( l_chk[0], key + CDKEY_BUF_LEN + 2, 3 );
3138         idStr::ToUpper( l_chk[0] );
3139         edited_key[ 1 ] = ( key[ CDKEY_BUF_LEN + 2 + 3 ] == '1' );
3140         idStr::Copynz( lkey[1], key + CDKEY_BUF_LEN + 7, CDKEY_BUF_LEN );
3141         idStr::ToUpper( lkey[1] );
3142         idStr::Copynz( l_chk[1], key + CDKEY_BUF_LEN * 2 + 7, 3 );
3143         idStr::ToUpper( l_chk[1] );
3144
3145         if ( fileSystem->HasD3XP() ) {
3146                 imax = 2;
3147         } else {
3148                 imax = 1;
3149         }
3150         offline_valid[ 0 ] = offline_valid[ 1 ] = true;
3151         for( i_key = 0; i_key < imax; i_key++ ) {
3152                 // check that the characters are from the valid set
3153                 int i;
3154                 for ( i = 0; i < CDKEY_BUF_LEN - 1; i++ ) {
3155                         if ( !strchr( CDKEY_DIGITS, lkey[i_key][i] ) ) {
3156                                 offline_valid[ i_key ] = false;
3157                                 continue;
3158                         }
3159                 }
3160
3161                 if ( edited_key[ i_key ] ) {
3162                         // verify the checksum for edited keys only
3163                         checksum = CRC32_BlockChecksum( lkey[i_key], CDKEY_BUF_LEN - 1 );
3164                         chk8 = ( checksum & 0xff ) ^ ( ( ( checksum & 0xff00 ) >> 8 ) ^ ( ( ( checksum & 0xff0000 ) >> 16 ) ^ ( ( checksum & 0xff000000 ) >> 24 ) ) );
3165                         idStr::snPrintf( s_chk, 3, "%02X", chk8 );
3166                         if ( idStr::Icmp( l_chk[i_key], s_chk ) != 0 ) {
3167                                 offline_valid[ i_key ] = false;
3168                                 continue;
3169                         }
3170                 }
3171         }
3172         
3173         if ( !offline_valid[ 0 ] || !offline_valid[1] ) {
3174                 return false;
3175         }
3176
3177         // offline checks passed, we'll return true and optionally emit key check requests
3178         // the function should only modify the key states if the offline checks passed successfully
3179
3180         // set the keys, don't send a game auth if we are net connecting
3181         idStr::Copynz( cdkey, lkey[0], CDKEY_BUF_LEN );
3182         netConnect ? cdkey_state = CDKEY_OK : cdkey_state = CDKEY_CHECKING;
3183         if ( fileSystem->HasD3XP() ) {
3184                 idStr::Copynz( xpkey, lkey[1], CDKEY_BUF_LEN );
3185                 netConnect ? xpkey_state = CDKEY_OK : xpkey_state = CDKEY_CHECKING;
3186         } else {
3187                 xpkey_state = CDKEY_NA;
3188         }
3189         if ( !netConnect ) {
3190                 EmitGameAuth();
3191         }
3192         SetCDKeyGuiVars();
3193
3194         return true;
3195 }
3196
3197 /*
3198 ===============
3199 idSessionLocal::CDKeysAreValid
3200 checking that the key is present and uses only valid characters
3201 if d3xp is installed, check for a valid xpkey as well
3202 emit an auth packet to the master if possible and needed
3203 ===============
3204 */
3205 bool idSessionLocal::CDKeysAreValid( bool strict ) {
3206         int i;
3207         bool emitAuth = false;
3208
3209         if ( cdkey_state == CDKEY_UNKNOWN ) {
3210                 if ( strlen( cdkey ) != CDKEY_BUF_LEN - 1 ) {
3211                         cdkey_state = CDKEY_INVALID;
3212                 } else {
3213                         for ( i = 0; i < CDKEY_BUF_LEN-1; i++ ) {
3214                                 if ( !strchr( CDKEY_DIGITS, cdkey[i] ) ) {
3215                                         cdkey_state = CDKEY_INVALID;
3216                                         break;
3217                                 }
3218                         }
3219                 }               
3220                 if ( cdkey_state == CDKEY_UNKNOWN ) {
3221                         cdkey_state = CDKEY_CHECKING;
3222                         emitAuth = true;
3223                 }
3224         }
3225         if ( xpkey_state == CDKEY_UNKNOWN ) {
3226                 if ( fileSystem->HasD3XP() ) {
3227                         if ( strlen( xpkey ) != CDKEY_BUF_LEN -1 ) {
3228                                 xpkey_state = CDKEY_INVALID;
3229                         } else {
3230                                 for ( i = 0; i < CDKEY_BUF_LEN-1; i++ ) {
3231                                         if ( !strchr( CDKEY_DIGITS, xpkey[i] ) ) {
3232                                                 xpkey_state = CDKEY_INVALID;
3233                                         }
3234                                 }
3235                         }
3236                         if ( xpkey_state == CDKEY_UNKNOWN ) {
3237                                 xpkey_state = CDKEY_CHECKING;
3238                                 emitAuth = true;
3239                         }
3240                 } else {
3241                         xpkey_state = CDKEY_NA;
3242                 }
3243         }
3244         if ( emitAuth ) {
3245                 EmitGameAuth();
3246         }
3247         // make sure to keep the mainmenu gui up to date in case we made state changes
3248         SetCDKeyGuiVars();
3249         if ( strict ) {
3250                 return cdkey_state == CDKEY_OK && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_NA );
3251         } else {
3252                 return ( cdkey_state == CDKEY_OK || cdkey_state == CDKEY_CHECKING ) && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING || xpkey_state == CDKEY_NA );
3253         }
3254 }
3255
3256 /*
3257 ===============
3258 idSessionLocal::WaitingForGameAuth
3259 ===============
3260 */
3261 bool idSessionLocal::WaitingForGameAuth( void ) {
3262         return authEmitTimeout != 0;
3263 }
3264
3265 /*
3266 ===============
3267 idSessionLocal::CDKeysAuthReply
3268 ===============
3269 */
3270 void idSessionLocal::CDKeysAuthReply( bool valid, const char *auth_msg ) {
3271         assert( authEmitTimeout > 0 );
3272         if ( authWaitBox ) {
3273                 // close the wait box
3274                 StopBox();
3275                 authWaitBox = false;
3276         }
3277         if ( !valid ) {
3278                 common->DPrintf( "auth key is invalid\n" );
3279                 authMsg = auth_msg;
3280                 if ( cdkey_state == CDKEY_CHECKING ) {
3281                         cdkey_state = CDKEY_INVALID;
3282                 }
3283                 if ( xpkey_state == CDKEY_CHECKING ) {
3284                         xpkey_state = CDKEY_INVALID;
3285                 }
3286         } else {
3287                 common->DPrintf( "client is authed in\n" );
3288                 if ( cdkey_state == CDKEY_CHECKING ) {
3289                         cdkey_state = CDKEY_OK;
3290                 }
3291                 if ( xpkey_state == CDKEY_CHECKING ) {
3292                         xpkey_state = CDKEY_OK;
3293                 }
3294         }
3295         authEmitTimeout = 0;
3296         SetCDKeyGuiVars();
3297 }
3298
3299 /*
3300 ===============
3301 idSessionLocal::GetCurrentMapName
3302 ===============
3303 */
3304 const char *idSessionLocal::GetCurrentMapName() {
3305         return currentMapName.c_str();
3306 }
3307
3308 /*
3309 ===============
3310 idSessionLocal::GetSaveGameVersion
3311 ===============
3312 */
3313 int idSessionLocal::GetSaveGameVersion( void ) {
3314         return savegameVersion;
3315 }
3316
3317 /*
3318 ===============
3319 idSessionLocal::GetAuthMsg
3320 ===============
3321 */
3322 const char *idSessionLocal::GetAuthMsg( void ) {
3323         return authMsg.c_str();
3324 }