2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
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.
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.
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/>.
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.
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.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
32 #include "Session_local.h"
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, "" );
47 idSessionLocal sessLocal;
48 idSession *session = &sessLocal;
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;
56 void RandomizeStack( void ) {
57 // attempt to force uninitialized stack memory bugs
59 byte *buf = (byte *)_alloca( bytes );
61 int fill = rand()&255;
62 for ( int i = 0 ; i < bytes ; i++ ) {
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 );
83 Restart the server on a different map
86 static void Session_Map_f( const idCmdArgs &args ) {
92 if ( !map.Length() ) {
95 map.StripFileExtension();
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 );
104 common->Printf( "Can't find map %s\n", string.c_str() );
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 );
116 cvarSystem->SetCVarBool( "developer", false );
117 sessLocal.StartNewGame( map, true );
124 Restart the server on a different map in developer mode
127 static void Session_DevMap_f( const idCmdArgs &args ) {
133 if ( !map.Length() ) {
136 map.StripFileExtension();
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 );
145 common->Printf( "Can't find map %s\n", string.c_str() );
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 );
157 cvarSystem->SetCVarBool( "developer", true );
158 sessLocal.StartNewGame( map, true );
166 static void Session_TestMap_f( const idCmdArgs &args ) {
170 if ( !map.Length() ) {
173 map.StripFileExtension();
175 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
177 sprintf( string, "dmap maps/%s.map", map.c_str() );
178 cmdSystem->BufferCommandText( CMD_EXEC_NOW, string );
180 sprintf( string, "devmap %s", map.c_str() );
181 cmdSystem->BufferCommandText( CMD_EXEC_NOW, string );
189 static void Sess_WritePrecache_f( const idCmdArgs &args ) {
190 if ( args.Argc() != 2 ) {
191 common->Printf( "USAGE: writePrecache <execFile>\n" );
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 );
201 fileSystem->CloseFile( f );
206 idSessionLocal::MaybeWaitOnCDKey
209 bool idSessionLocal::MaybeWaitOnCDKey( void ) {
210 if ( authEmitTimeout > 0 ) {
212 sessLocal.MessageBox( MSG_WAIT, common->GetLanguageDict()->GetString( "#str_07191" ), NULL, true, NULL, NULL, true );
223 static void Session_PromptKey_f( const idCmdArgs &args ) {
226 static bool recursed = false;
229 common->Warning( "promptKey recursed - aborted" );
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 ) ) {
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" );
247 retkey = sessLocal.MessageBox( MSG_CDKEY, prompt_msg, common->GetLanguageDict()->GetString( "#str_04305" ), true, NULL, NULL, true );
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 )
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 );
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 );
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 )
272 idAsyncNetwork::BuildInvalidKeyMsg( msg, valid );
273 sessLocal.MessageBox( MSG_OK, msg, common->GetLanguageDict()->GetString( "#str_04310" ), true, NULL, NULL, true );
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();
285 ===============================================================================
289 ===============================================================================
294 idSessionLocal::Clear
297 void idSessionLocal::Clear() {
299 insideUpdateScreen = false;
300 insideExecuteMapChange = false;
302 loadingSaveGame = false;
306 currentMapName.Clear();
307 aviDemoShortName.Clear();
308 msgFireBack[ 0 ].Clear();
309 msgFireBack[ 1 ].Clear();
315 menuSoundWorld = NULL;
318 renderdemoVersion = 0;
321 syncNextGameFrame = false;
324 aviCaptureMode = false;
326 waitingOnBind = false;
327 lastPacifierTime = 0;
330 guiMsgRestore = NULL;
331 msgIgnoreButtons = false;
333 bytesNeededForMapLoad = 0;
340 loadGameList.Clear();
351 idSessionLocal::idSessionLocal
354 idSessionLocal::idSessionLocal() {
355 guiInGame = guiMainMenu = guiIntro \
356 = guiRestartMenu = guiLoading = guiGameOver = guiActive \
357 = guiTest = guiMsg = guiMsgRestore = guiTakeNotes = NULL;
359 menuSoundWorld = NULL;
366 idSessionLocal::~idSessionLocal
369 idSessionLocal::~idSessionLocal() {
376 called on errors and game exits
379 void idSessionLocal::Stop() {
382 // clear mapSpawned and demo playing flags
385 // disconnect async client
386 idAsyncNetwork::client.DisconnectFromServer();
389 idAsyncNetwork::server.Kill();
395 insideUpdateScreen = false;
396 insideExecuteMapChange = false;
399 SetGUI( NULL, NULL );
404 idSessionLocal::Shutdown
407 void idSessionLocal::Shutdown() {
410 if ( aviCaptureMode ) {
426 if ( menuSoundWorld ) {
427 delete menuSoundWorld;
428 menuSoundWorld = NULL;
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();
438 if ( guiMainMenu_MapList != NULL ) {
439 guiMainMenu_MapList->Shutdown();
440 uiManager->FreeListGUI( guiMainMenu_MapList );
441 guiMainMenu_MapList = NULL;
449 idSessionLocal::IsMultiplayer
452 bool idSessionLocal::IsMultiplayer() {
453 return idAsyncNetwork::IsActive();
458 idSessionLocal::StartWipe
460 Draws and captures the current state, then starts a wipe with that image
463 void idSessionLocal::StartWipe( const char *_wipeMaterial, bool hold ) {
466 // render the current screen into a texture for the wipe model
467 renderSystem->CropRenderSize( 640, 480, true );
471 renderSystem->CaptureRenderToImage( "_scratch");
472 renderSystem->UnCrop();
474 wipeMaterial = declManager->FindMaterial( _wipeMaterial, false );
476 wipeStartTic = com_ticNumber;
477 wipeStopTic = wipeStartTic + 1000.0f / USERCMD_MSEC * com_wipeSeconds.GetFloat();
483 idSessionLocal::CompleteWipe
486 void idSessionLocal::CompleteWipe() {
487 if ( com_ticNumber == 0 ) {
488 // if the async thread hasn't started, we would hang here
490 UpdateScreen( true );
493 while ( com_ticNumber < wipeStopTic ) {
497 UpdateScreen( true );
503 idSessionLocal::ShowLoadingGui
506 void idSessionLocal::ShowLoadingGui() {
507 if ( com_ticNumber == 0 ) {
512 // introduced in D3XP code. don't think it actually fixes anything, but doesn't hurt either
514 // Try and prevent the while loop from being skipped over (long hitch on the main thread?)
515 int stop = Sys_Milliseconds() + 1000;
517 while ( Sys_Milliseconds() < stop || force-- > 0 ) {
518 com_frameTime = com_ticNumber * USERCMD_MSEC;
520 session->UpdateScreen( false );
523 int stop = com_ticNumber + 1000.0f / USERCMD_MSEC * 1.0f;
524 while ( com_ticNumber < stop ) {
525 com_frameTime = com_ticNumber * USERCMD_MSEC;
527 session->UpdateScreen( false );
536 idSessionLocal::ClearWipe
539 void idSessionLocal::ClearWipe( void ) {
542 wipeStartTic = wipeStopTic + 1;
550 static void Session_TestGUI_f( const idCmdArgs &args ) {
551 sessLocal.TestGUI( args.Argv(1) );
556 idSessionLocal::TestGUI
559 void idSessionLocal::TestGUI( const char *guiName ) {
560 if ( guiName && *guiName ) {
561 guiTest = uiManager->FindGui( guiName, true, false, true );
572 static idStr FindUnusedFileName( const char *format ) {
576 for ( i = 0 ; i < 999 ; i++ ) {
577 sprintf( filename, format, i );
578 int len = fileSystem->ReadFile( filename, NULL, NULL );
580 return filename; // file doesn't exist
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 );
597 sessLocal.DemoShot( va( "demos/shot_%s.demo", args.Argv(1) ) );
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 );
611 sessLocal.StartRecordingRenderDemo( va( "demos/%s.demo", args.Argv(1) ) );
617 Session_CompressDemo_f
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) );
626 common->Printf("use: CompressDemo <file> [scheme]\nscheme is the same as com_compressDemo, defaults to 2" );
632 Session_StopRecordingDemo_f
635 static void Session_StopRecordingDemo_f( const idCmdArgs &args ) {
636 sessLocal.StopRecordingRenderDemo();
644 static void Session_PlayDemo_f( const idCmdArgs &args ) {
645 if ( args.Argc() >= 2 ) {
646 sessLocal.StartPlayingRenderDemo( va( "demos/%s", args.Argv(1) ) );
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 ) );
663 Session_TimeDemoQuit_f
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;
679 static void Session_AVIDemo_f( const idCmdArgs &args ) {
680 sessLocal.AVIRenderDemo( va( "demos/%s", args.Argv(1) ) );
688 static void Session_AVIGame_f( const idCmdArgs &args ) {
689 sessLocal.AVIGame( args.Argv(1) );
697 static void Session_AVICmdDemo_f( const idCmdArgs &args ) {
698 sessLocal.AVICmdDemo( args.Argv(1) );
703 Session_WriteCmdDemo_f
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 ) ) );
713 common->Printf( "usage: writeCmdDemo [demoName]\n" );
719 Session_PlayCmdDemo_f
722 static void Session_PlayCmdDemo_f( const idCmdArgs &args ) {
723 sessLocal.StartPlayingCmdDemo( args.Argv(1) );
728 Session_TimeCmdDemo_f
731 static void Session_TimeCmdDemo_f( const idCmdArgs &args ) {
732 sessLocal.TimeCmdDemo( args.Argv(1) );
740 static void Session_Disconnect_f( const idCmdArgs &args ) {
742 sessLocal.StartMenu();
744 soundSystem->SetMute( false );
754 static void Session_EndOfDemo_f( const idCmdArgs &args ) {
756 sessLocal.StartMenu();
758 soundSystem->SetMute( false );
760 if ( sessLocal.guiActive ) {
761 sessLocal.guiActive->HandleNamedEvent( "endOfDemo" );
768 Session_ExitCmdDemo_f
771 static void Session_ExitCmdDemo_f( const idCmdArgs &args ) {
772 if ( !sessLocal.cmdDemoFile ) {
773 common->Printf( "not reading from a cmdDemo\n" );
776 fileSystem->CloseFile( sessLocal.cmdDemoFile );
777 common->Printf( "Command demo exited at logIndex %i\n", sessLocal.logIndex );
778 sessLocal.cmdDemoFile = NULL;
783 idSessionLocal::StartRecordingRenderDemo
786 void idSessionLocal::StartRecordingRenderDemo( const char *demoName ) {
788 // allow it to act like a toggle
789 StopRecordingRenderDemo();
793 if ( !demoName[0] ) {
794 common->Printf( "idSessionLocal::StartRecordingRenderDemo: no name specified\n" );
800 writeDemo = new idDemoFile;
801 if ( !writeDemo->OpenForWriting( demoName ) ) {
802 common->Printf( "error opening %s\n", demoName );
808 common->Printf( "recording to %s\n", writeDemo->GetName() );
810 writeDemo->WriteInt( DS_VERSION );
811 writeDemo->WriteInt( RENDERDEMO_VERSION );
813 // if we are in a map already, dump the current state
814 sw->StartWritingDemo( writeDemo );
815 rw->StartWritingDemo( writeDemo );
820 idSessionLocal::StopRecordingRenderDemo
823 void idSessionLocal::StopRecordingRenderDemo() {
825 common->Printf( "idSessionLocal::StopRecordingRenderDemo: not recording\n" );
828 sw->StopWritingDemo();
829 rw->StopWritingDemo();
832 common->Printf( "stopped recording %s.\n", writeDemo->GetName() );
839 idSessionLocal::StopPlayingRenderDemo
841 Reports timeDemo numbers and finishes any avi recording
844 void idSessionLocal::StopPlayingRenderDemo() {
850 // Record the stop time before doing anything that could be time consuming
851 int timeDemoStopTime = Sys_Milliseconds();
858 soundSystem->SetPlayingSoundWorld( menuSoundWorld );
860 common->Printf( "stopped playing %s.\n", readDemo->GetName() );
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 );
870 common->Printf( message );
871 if ( timeDemo == TD_YES_THEN_QUIT ) {
872 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" );
874 soundSystem->SetMute( true );
875 MessageBox( MSG_OK, message, "Time Demo Results", true );
876 soundSystem->SetMute( false );
884 idSessionLocal::DemoShot
886 A demoShot is a single frame demo
889 void idSessionLocal::DemoShot( const char *demoName ) {
890 StartRecordingRenderDemo( demoName );
892 // force draw one frame
895 StopRecordingRenderDemo();
900 idSessionLocal::StartPlayingRenderDemo
903 void idSessionLocal::StartPlayingRenderDemo( idStr demoName ) {
904 if ( !demoName[0] ) {
905 common->Printf( "idSessionLocal::StartPlayingRenderDemo: no name specified\n" );
909 // make sure localSound / GUI intro music shuts up
911 sw->PlayShaderDirectly( "", 0 );
912 menuSoundWorld->StopAllSounds();
913 menuSoundWorld->PlayShaderDirectly( "", 0 );
915 // exit any current game
918 // automatically put the console away
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() );
933 soundSystem->SetMute( false );
937 insideExecuteMapChange = true;
939 insideExecuteMapChange = false;
940 guiLoading->SetStateString( "demo", "" );
942 // setup default render demo settings
943 // that's default for <= Doom3 v1.1
944 renderdemoVersion = 1;
945 savegameVersion = 16;
947 AdvanceRenderDemo( true );
952 timeDemoStartTime = Sys_Milliseconds();
957 idSessionLocal::TimeRenderDemo
960 void idSessionLocal::TimeRenderDemo( const char *demoName, bool twice ) {
961 idStr demo = demoName;
963 // no sound in time demos
964 soundSystem->SetMute( true );
966 StartPlayingRenderDemo( demo );
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 );
973 insideExecuteMapChange = true;
975 insideExecuteMapChange = false;
976 AdvanceRenderDemo( true );
978 guiLoading->SetStateString( "demo", "" );
979 StartPlayingRenderDemo( demo );
993 idSessionLocal::BeginAVICapture
996 void idSessionLocal::BeginAVICapture( const char *demoName ) {
997 idStr name = demoName;
998 name.ExtractFileBase( aviDemoShortName );
999 aviCaptureMode = true;
1000 aviDemoFrameCount = 0;
1002 sw->AVIOpen( va( "demos/%s/", aviDemoShortName.c_str() ), aviDemoShortName.c_str() );
1007 idSessionLocal::EndAVICapture
1010 void idSessionLocal::EndAVICapture() {
1011 if ( !aviCaptureMode ) {
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" );
1027 common->Printf( "captured %i frames for %s.\n", ( int )aviDemoFrameCount, aviDemoShortName.c_str() );
1029 aviCaptureMode = false;
1035 idSessionLocal::AVIRenderDemo
1038 void idSessionLocal::AVIRenderDemo( const char *_demoName ) {
1039 idStr demoName = _demoName; // copy off from va() buffer
1041 StartPlayingRenderDemo( demoName );
1046 BeginAVICapture( demoName.c_str() ) ;
1048 // I don't understand why I need to do this twice, something
1049 // strange with the nvidia swapbuffers?
1055 idSessionLocal::AVICmdDemo
1058 void idSessionLocal::AVICmdDemo( const char *demoName ) {
1059 StartPlayingCmdDemo( demoName );
1061 BeginAVICapture( demoName ) ;
1066 idSessionLocal::AVIGame
1068 Start AVI recording the current game session
1071 void idSessionLocal::AVIGame( const char *demoName ) {
1072 if ( aviCaptureMode ) {
1077 if ( !mapSpawned ) {
1078 common->Printf( "No map spawned.\n" );
1081 if ( !demoName || !demoName[0] ) {
1082 idStr filename = FindUnusedFileName( "demos/game%03i.game" );
1083 demoName = filename.c_str();
1085 // write a one byte stub .game file just so the FindUnusedFileName works,
1086 fileSystem->WriteFile( demoName, demoName, 1 );
1089 BeginAVICapture( demoName ) ;
1094 idSessionLocal::CompressDemoFile
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" );
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) );
1110 idDemoFile demoread, demowrite;
1111 if ( !demoread.OpenForReading( fullDemoName ) ) {
1112 common->Printf( "Could not open %s for reading\n", fullDemoName.c_str() );
1115 if ( !demowrite.OpenForWriting( compressedName ) ) {
1116 common->Printf( "Could not open %s for writing\n", compressedName.c_str() );
1118 cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload );
1119 cvarSystem->SetCVarInteger("com_compressDemos", savedCompression);
1122 common->SetRefreshOnPrint( true );
1123 common->Printf( "Compressing %s to %s...\n", fullDemoName.c_str(), compressedName.c_str() );
1125 static const int bufferSize = 65535;
1126 char buffer[bufferSize];
1128 while ( 0 != (bytesRead = demoread.Read( buffer, bufferSize ) ) ) {
1129 demowrite.Write( buffer, bytesRead );
1130 common->Printf( "." );
1136 cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload );
1137 cvarSystem->SetCVarInteger("com_compressDemos", savedCompression);
1139 common->Printf( "Done\n" );
1140 common->SetRefreshOnPrint( false );
1147 idSessionLocal::StartNewGame
1150 void idSessionLocal::StartNewGame( const char *mapName, bool devmap ) {
1152 common->Printf( "Dedicated servers cannot start singleplayer games.\n" );
1156 // strict check. don't let a game start without a definitive answer
1157 if ( !CDKeysAreValid( true ) ) {
1159 if ( MaybeWaitOnCDKey() ) {
1160 // check again, maybe we just needed more time
1161 if ( CDKeysAreValid( true ) ) {
1162 // can continue directly
1167 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "promptKey force" );
1168 cmdSystem->ExecuteCommandBuffer();
1172 if ( idAsyncNetwork::server.IsActive() ) {
1173 common->Printf("Server running, use si_map / serverMapRestart\n");
1176 if ( idAsyncNetwork::client.IsActive() ) {
1177 common->Printf("Client running, disconnect from server first\n");
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 );
1186 mapSpawnData.serverInfo.Clear();
1187 mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
1188 mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" );
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
1193 mapSpawnData.serverInfo.Set( "devmap", "1" );
1196 mapSpawnData.syncedCVars.Clear();
1197 mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC );
1199 MoveToNewMap( mapName );
1205 idSessionLocal::GetAutoSaveName
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 );
1212 mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) );
1214 // Fixme: Localization
1215 return va( "^3AutoSave:^0 %s", mapName );
1220 idSessionLocal::MoveToNewMap
1222 Leaves the existing userinfo and serverinfo
1225 void idSessionLocal::MoveToNewMap( const char *mapName ) {
1226 mapSpawnData.serverInfo.Set( "si_map", mapName );
1230 if ( !mapSpawnData.serverInfo.GetBool("devmap") ) {
1231 // Autosave at the beginning of the level
1232 SaveGame( GetAutoSaveName( mapName ), true );
1235 SetGUI( NULL, NULL );
1243 void idSessionLocal::SaveCmdDemoToFile( idFile *file ) {
1245 mapSpawnData.serverInfo.WriteToFileHandle( file );
1247 for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) {
1248 mapSpawnData.userInfo[i].WriteToFileHandle( file );
1249 mapSpawnData.persistentPlayerInfo[i].WriteToFileHandle( file );
1252 file->Write( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) );
1254 if ( numClients < 1 ) {
1257 file->Write( loggedUsercmds, numClients * logIndex * sizeof( loggedUsercmds[0] ) );
1262 idSessionLocal::LoadCmdDemoFromFile
1265 void idSessionLocal::LoadCmdDemoFromFile( idFile *file ) {
1267 mapSpawnData.serverInfo.ReadFromFileHandle( file );
1269 for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) {
1270 mapSpawnData.userInfo[i].ReadFromFileHandle( file );
1271 mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( file );
1273 file->Read( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) );
1278 idSessionLocal::WriteCmdDemo
1280 Dumps the accumulated commands for the current level.
1281 This should still work after disconnecting from a level
1284 void idSessionLocal::WriteCmdDemo( const char *demoName, bool save ) {
1286 if ( !demoName[0] ) {
1287 common->Printf( "idSessionLocal::WriteCmdDemo: no name specified\n" );
1293 statsName = demoName;
1294 statsName.StripFileExtension();
1295 statsName.DefaultFileExtension(".stats");
1298 common->Printf( "writing save data to %s\n", demoName );
1300 idFile *cmdDemoFile = fileSystem->OpenFileWrite( demoName );
1301 if ( !cmdDemoFile ) {
1302 common->Printf( "Couldn't open for writing %s\n", demoName );
1307 cmdDemoFile->Write( &logIndex, sizeof( logIndex ) );
1310 SaveCmdDemoToFile( cmdDemoFile );
1313 idFile *statsFile = fileSystem->OpenFileWrite( statsName );
1315 statsFile->Write( &statIndex, sizeof( statIndex ) );
1316 statsFile->Write( loggedStats, numClients * statIndex * sizeof( loggedStats[0] ) );
1317 fileSystem->CloseFile( statsFile );
1321 fileSystem->CloseFile( cmdDemoFile );
1326 idSessionLocal::FinishCmdLoad
1329 void idSessionLocal::FinishCmdLoad() {
1334 idSessionLocal::StartPlayingCmdDemo
1337 void idSessionLocal::StartPlayingCmdDemo(const char *demoName) {
1338 // exit any current game
1341 idStr fullDemoName = "demos/";
1342 fullDemoName += demoName;
1343 fullDemoName.DefaultFileExtension( ".cdemo" );
1344 cmdDemoFile = fileSystem->OpenFileRead(fullDemoName);
1346 if ( cmdDemoFile == NULL ) {
1347 common->Printf( "Couldn't open %s\n", fullDemoName.c_str() );
1351 guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true );
1352 //cmdDemoFile->Read(&loadGameTime, sizeof(loadGameTime));
1354 LoadCmdDemoFromFile(cmdDemoFile);
1359 cmdDemoFile = fileSystem->OpenFileRead(fullDemoName);
1361 // have to do this twice as the execmapchange clears the cmddemofile
1362 LoadCmdDemoFromFile(cmdDemoFile);
1364 // run one frame to get the view angles correct
1370 idSessionLocal::TimeCmdDemo
1373 void idSessionLocal::TimeCmdDemo( const char *demoName ) {
1374 StartPlayingCmdDemo( demoName );
1378 int startTime = Sys_Milliseconds();
1380 int minuteStart, minuteEnd;
1383 // run all the frames in sequence
1384 minuteStart = startTime;
1386 while( cmdDemoFile ) {
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 );
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 );
1406 idSessionLocal::UnloadMap
1408 Performs cleanup that needs to happen between maps, or when a
1410 Exits with mapSpawned = false
1413 void idSessionLocal::UnloadMap() {
1414 StopPlayingRenderDemo();
1416 // end the current map in the game
1418 game->MapShutdown();
1421 if ( cmdDemoFile ) {
1422 fileSystem->CloseFile( cmdDemoFile );
1427 StopRecordingRenderDemo();
1435 idSessionLocal::LoadLoadingGui
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();
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 );
1449 if ( uiManager->CheckGui( guiMap ) ) {
1450 guiLoading = uiManager->FindGui( guiMap, true, false, true );
1452 guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true );
1454 guiLoading->SetStateFloat( "map_loading", 0.0f );
1459 idSessionLocal::GetBytesNeededForMapLoad
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 );
1466 return mapDef->dict.GetInt( va("size%d", Max( 0, com_machineSpec.GetInteger() ) ) );
1468 if ( com_machineSpec.GetInteger() < 2 ) {
1469 return 200 * 1024 * 1024;
1471 return 400 * 1024 * 1024;
1478 idSessionLocal::SetBytesNeededForMapLoad
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 );
1485 if ( com_updateLoadSize.GetBool() && mapDef ) {
1486 // we assume that if com_updateLoadSize is true then the file is writable
1488 mapDef->dict.SetInt( va("size%d", com_machineSpec.GetInteger()), bytesNeeded );
1490 idStr declText = "\nmapDef ";
1491 declText += mapDef->GetName();
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";
1500 mapDef->SetText( declText );
1501 mapDef->ReplaceSourceFileText();
1507 idSessionLocal::ExecuteMapChange
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
1515 void idSessionLocal::ExecuteMapChange( bool noFadeWipe ) {
1517 bool reloadingSameMap;
1519 // close console and remove any prints from the notify lines
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 );
1529 soundSystem->SetMute( true );
1531 // clear all menu sounds
1532 menuSoundWorld->ClearAllSoundEmitters();
1534 // unpause the game sound world
1535 // NOTE: we UnPause again later down. not sure this is needed
1536 if ( sw->IsPaused() ) {
1540 if ( !noFadeWipe ) {
1541 // capture the current screen and start a wipe
1542 StartWipe( "wipeMaterial", true );
1544 // immediately complete the wipe to fade out the level transition
1545 // run the wipe to completion
1549 // extract the map name from serverinfo
1550 idStr mapString = mapSpawnData.serverInfo.GetString( "si_map" );
1552 idStr fullMapName = "maps/";
1553 fullMapName += mapString;
1554 fullMapName.StripFileExtension();
1556 // shut down the existing game if it is running
1559 // don't do the deferred caching if we are reloading the same map
1560 if ( fullMapName == currentMapName ) {
1561 reloadingSameMap = true;
1563 reloadingSameMap = false;
1564 currentMapName = fullMapName;
1567 // note which media we are going to need to load
1568 if ( !reloadingSameMap ) {
1569 declManager->BeginLevelLoad();
1570 renderSystem->BeginLevelLoad();
1571 soundSystem->BeginLevelLoad();
1574 uiManager->BeginLevelLoad();
1575 uiManager->Reload( true );
1577 // set the loading gui that we will wipe to
1578 LoadLoadingGui( mapString );
1580 // cause prints to force screen updates as a pacifier,
1581 // and draw the loading gui instead of game draws
1582 insideExecuteMapChange = true;
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() );
1590 bytesNeededForMapLoad = 30 * 1024 * 1024;
1595 // let the loading gui spin for 1 second to animate out
1598 // note any warning prints that happen during the load process
1599 common->ClearWarnings( mapString );
1601 // release the mouse cursor
1602 // before we do this potentially long operation
1603 Sys_GrabMouseCursor( false );
1605 // if net play, we get the number of clients during mapSpawnInfo processing
1606 if ( !idAsyncNetwork::IsActive() ) {
1610 int start = Sys_Milliseconds();
1612 common->Printf( "--------- Map Initialization ---------\n" );
1613 common->Printf( "Map: %s\n", mapString.c_str() );
1615 // let the renderSystem load all the geometry
1616 if ( !rw->InitFromMap( fullMapName ) ) {
1617 common->Error( "couldn't load %s", fullMapName.c_str() );
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 ) );
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] );
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;
1639 game->SetServerInfo( mapSpawnData.serverInfo );
1640 game->InitFromNewMap( fullMapName + ".map", rw, sw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() );
1643 game->SetServerInfo( mapSpawnData.serverInfo );
1644 game->InitFromNewMap( fullMapName + ".map", rw, sw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() );
1647 if ( !idAsyncNetwork::IsActive() && !loadingSaveGame ) {
1649 for ( i = 0; i < numClients; i++ ) {
1650 game->SpawnPlayer( i );
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() );
1661 uiManager->EndLevelLoad();
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 );
1670 common->Printf ("-----------------------------------\n");
1672 int msec = Sys_Milliseconds() - start;
1673 common->Printf( "%6d msec to load %s\n", msec, mapString.c_str() );
1675 // let the renderSystem generate interactions now that everything is spawned
1676 rw->GenerateAllInteractions();
1678 common->PrintWarnings();
1680 if ( guiLoading && bytesNeededForMapLoad ) {
1681 float pct = guiLoading->State().GetFloat( "map_loading" );
1685 while ( pct < 1.0f ) {
1686 guiLoading->SetStateFloat( "map_loading", pct );
1687 guiLoading->StateChanged( com_frameTime );
1688 Sys_GenerateEvents();
1694 // capture the current screen and start a wipe
1695 StartWipe( "wipe2Material" );
1697 usercmdGen->Clear();
1699 // start saving commands for possible writeCmdDemo usage
1704 // don't bother spinning over all the tics we spent loading
1705 lastGameTic = latchedTicNumber = com_ticNumber;
1707 // remove any prints from the notify lines
1708 console->ClearNotifyLines();
1710 // stop drawing the laoding screen
1711 insideExecuteMapChange = false;
1713 Sys_SetPhysicalWorkMemory( -1, -1 );
1715 // set the game sound world for playback
1716 soundSystem->SetPlayingSoundWorld( sw );
1718 // when loading a save game the sound is paused
1719 if ( sw->IsPaused() ) {
1720 // unpause the game sound world
1724 // restart entity sound playback
1725 soundSystem->SetMute( false );
1727 // we are valid for game draws now
1737 void LoadGame_f( const idCmdArgs &args ) {
1739 if ( args.Argc() < 2 || idStr::Icmp(args.Argv(1), "quick" ) == 0 ) {
1740 idStr saveName = common->GetLanguageDict()->GetString( "#str_07178" );
1741 sessLocal.LoadGame( saveName );
1743 sessLocal.LoadGame( args.Argv(1) );
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() );
1759 if ( sessLocal.SaveGame( args.Argv(1) ) ) {
1760 common->Printf( "Saved %s\n", args.Argv(1) );
1770 void TakeViewNotes_f( const idCmdArgs &args ) {
1771 const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : "";
1772 sessLocal.TakeNotes( p );
1780 void TakeViewNotes2_f( const idCmdArgs &args ) {
1781 const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : "";
1782 sessLocal.TakeNotes( p, true );
1787 idSessionLocal::TakeNotes
1790 void idSessionLocal::TakeNotes( const char *p, bool extended ) {
1791 if ( !mapSpawned ) {
1792 common->Printf( "No map loaded!\n" );
1797 guiTakeNotes = uiManager->FindGui( "guis/takeNotes2.gui", true, false, true );
1800 const char *people[] = {
1801 "Nobody", "Adam", "Brandon", "David", "PHook", "Jay", "Jake",
1802 "PatJ", "Brett", "Ted", "Darin", "Brian", "Sean"
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"
1812 const int numPeople = sizeof( people ) / sizeof( people[0] );
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] );
1819 uiManager->FreeListGUI( guiList_people );
1822 guiTakeNotes = uiManager->FindGui( "guis/takeNotes.gui", true, false, true );
1825 SetGUI( guiTakeNotes, NULL );
1826 guiActive->SetStateString( "note", "" );
1827 guiActive->SetStateString( "notefile", p );
1828 guiActive->SetStateBool( "extended", extended );
1829 guiActive->Activate( true, com_frameTime );
1837 void Session_Hitch_f( const idCmdArgs &args ) {
1838 idSoundWorld *sw = soundSystem->GetPlayingSoundWorld();
1840 soundSystem->SetMute(true);
1842 Sys_EnterCriticalSection();
1844 if ( args.Argc() == 2 ) {
1845 Sys_Sleep( atoi(args.Argv(1)) );
1850 Sys_LeaveCriticalSection();
1852 soundSystem->SetMute(false);
1858 idSessionLocal::ScrubSaveGameFileName
1860 Turns a bad file name into a good one or your money back
1863 void idSessionLocal::ScrubSaveGameFileName( idStr &saveFileName ) const {
1867 inFileName = saveFileName;
1868 inFileName.RemoveColors();
1869 inFileName.StripFileExtension();
1871 saveFileName.Clear();
1873 int len = inFileName.Length();
1874 for ( i = 0; i < len; i++ ) {
1875 if ( strchr( "',.~!@#$%^&*()[]{}<>\\|/=?+;:-\'\"", inFileName[i] ) ) {
1877 saveFileName += '_';
1878 } else if ( (const unsigned char)inFileName[i] >= 128 ) {
1880 saveFileName += '_';
1881 } else if ( inFileName[i] == ' ' ) {
1882 saveFileName += '_';
1884 saveFileName += inFileName[i];
1891 idSessionLocal::SaveGame
1894 bool idSessionLocal::SaveGame( const char *saveName, bool autosave ) {
1896 common->Printf( "Dedicated servers cannot save games.\n" );
1900 idStr gameFile, previewFile, descriptionFile, mapName;
1902 if ( !mapSpawned ) {
1903 common->Printf( "Not playing a game.\n" );
1907 if ( IsMultiplayer() ) {
1908 common->Printf( "Can't save during net play.\n" );
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" );
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" );
1924 idSoundWorld *pauseWorld = soundSystem->GetPlayingSoundWorld();
1926 pauseWorld->Pause();
1927 soundSystem->SetPlayingSoundWorld( NULL );
1930 // setup up filenames and paths
1931 gameFile = saveName;
1932 ScrubSaveGameFileName( gameFile );
1934 gameFile = "savegames/" + gameFile;
1935 gameFile.SetFileExtension( ".save" );
1937 previewFile = gameFile;
1938 previewFile.SetFileExtension( ".tga" );
1940 descriptionFile = gameFile;
1941 descriptionFile.SetFileExtension( ".txt" );
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() );
1948 soundSystem->SetPlayingSoundWorld( pauseWorld );
1949 pauseWorld->UnPause();
1954 // Write SaveGame Header:
1955 // Game Name / Version / Map Name / Persistant Player Info
1958 const char *gamename = GAME_NAME;
1959 fileOut->WriteString( gamename );
1962 fileOut->WriteInt( SAVEGAME_VERSION );
1965 mapName = mapSpawnData.serverInfo.GetString( "si_map" );
1966 fileOut->WriteString( mapName );
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 );
1974 // let the game save its state
1975 game->SaveGame( fileOut );
1977 // close the sava game file
1978 fileSystem->CloseFile( fileOut );
1982 renderSystem->CropRenderSize( 320, 240, false );
1984 renderSystem->CaptureRenderToFile( previewFile, true );
1985 renderSystem->UnCrop();
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() );
1994 soundSystem->SetPlayingSoundWorld( pauseWorld );
1995 pauseWorld->UnPause();
2000 idStr description = saveName;
2001 description.Replace( "\\", "\\\\" );
2002 description.Replace( "\"", "\\\"" );
2004 const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>(declManager->FindType( DECL_MAPDEF, mapName, false ));
2006 mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) );
2009 fileDesc->Printf( "\"%s\"\n", description.c_str() );
2010 fileDesc->Printf( "\"%s\"\n", mapName.c_str());
2013 idStr sshot = mapSpawnData.serverInfo.GetString( "si_map" );
2015 sshot.StripFileExtension();
2016 fileDesc->Printf( "\"guis/assets/autosave/%s\"\n", sshot.c_str() );
2018 fileDesc->Printf( "\"\"\n" );
2021 fileSystem->CloseFile( fileDesc );
2024 soundSystem->SetPlayingSoundWorld( pauseWorld );
2025 pauseWorld->UnPause();
2028 syncNextGameFrame = true;
2037 idSessionLocal::LoadGame
2040 bool idSessionLocal::LoadGame( const char *saveName ) {
2042 common->Printf( "Dedicated servers cannot load games.\n" );
2046 idStr in, loadFile, saveMap, gamename;
2048 if ( IsMultiplayer() ) {
2049 common->Printf( "Can't load during net play.\n" );
2053 //Hide the dialog box if it is up.
2056 loadFile = saveName;
2057 ScrubSaveGameFileName( loadFile );
2058 loadFile.SetFileExtension( ".save" );
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 );
2068 if ( savegameFile == NULL ) {
2069 common->Warning( "Couldn't open savegame file %s", in.c_str() );
2073 loadingSaveGame = true;
2075 // Read in save game header
2076 // Game Name / Version / Map Name / Persistant Player Info
2079 savegameFile->ReadString( gamename );
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() );
2085 loadingSaveGame = false;
2086 fileSystem->CloseFile( savegameFile );
2087 savegameFile = NULL;
2092 savegameFile->ReadInt( savegameVersion );
2095 savegameFile->ReadString( saveMap );
2097 // persistent player info
2098 for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
2099 mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( savegameFile );
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;
2113 common->DPrintf( "loading a v%d savegame\n", savegameVersion );
2115 if ( saveMap.Length() > 0 ) {
2117 // Start loading map
2118 mapSpawnData.serverInfo.Clear();
2120 mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
2121 mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" );
2123 mapSpawnData.serverInfo.Set( "si_map", saveMap );
2125 mapSpawnData.syncedCVars.Clear();
2126 mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC );
2128 mapSpawnData.mapSpawnUsercmd[0] = usercmdGen->TicCmd( latchedTicNumber );
2129 // make sure no buttons are pressed
2130 mapSpawnData.mapSpawnUsercmd[0].buttons = 0;
2134 SetGUI( NULL, NULL );
2137 if ( loadingSaveGame ) {
2138 fileSystem->CloseFile( savegameFile );
2139 loadingSaveGame = false;
2140 savegameFile = NULL;
2149 idSessionLocal::ProcessEvent
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 ) {
2157 idUserInterface *gui = NULL;
2159 op = game->HandleESC( &gui );
2160 if ( op == ESC_IGNORE ) {
2162 } else if ( op == ESC_GUI ) {
2163 SetGUI( gui, NULL );
2171 // let the pull-down console take it if desired
2172 if ( console->ProcessEvent( event, false ) ) {
2176 // if we are testing a GUI, send all events to it
2178 // hitting escape exits the testgui
2179 if ( event->evType == SE_KEY && event->evValue2 == 1 && event->evValue == K_ESCAPE ) {
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 );
2198 // if we aren't in a game, force the console to take it
2199 if ( !mapSpawned ) {
2200 console->ProcessEvent( event, true );
2204 // in game, exec bindings for all key downs
2205 if ( event->evType == SE_KEY && event->evValue2 == 1 ) {
2206 idKeyInput::ExecKeyBinding( event->evValue );
2215 idSessionLocal::DrawWipeModel
2217 Draw the fade material over everything that has been drawn
2220 void idSessionLocal::DrawWipeModel() {
2221 int latchedTic = com_ticNumber;
2223 if ( wipeStartTic >= wipeStopTic ) {
2227 if ( !wipeHold && latchedTic >= wipeStopTic ) {
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 );
2238 idSessionLocal::AdvanceRenderDemo
2241 void idSessionLocal::AdvanceRenderDemo( bool singleFrameOnly ) {
2242 if ( lastDemoTic == -1 ) {
2243 lastDemoTic = latchedTicNumber - 1;
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 ) {
2254 lastDemoTic = latchedTicNumber - latchedTicNumber % USERCMD_PER_DEMO_FRAME;
2256 // always advance a single frame with avidemo and timedemo
2257 lastDemoTic = latchedTicNumber;
2260 while( skipFrames > -1 ) {
2261 int ds = DS_FINISHED;
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
2273 if ( ds == DS_RENDER ) {
2274 if ( rw->ProcessDemoCommand( readDemo, ¤tDemoRenderView, &demoTimeOffset ) ) {
2275 // a view is ready to render
2281 if ( ds == DS_SOUND ) {
2282 sw->ProcessDemoCommand( readDemo );
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;
2293 common->Error( "Bad render demo token" );
2296 if ( com_showDemo.GetBool() ) {
2297 common->Printf( "frame:%i DemoTic:%i latched:%i skip:%i\n", numDemoFrames, lastDemoTic, latchedTicNumber, skipFrames );
2304 idSessionLocal::DrawCmdGraph
2306 Graphs yaw angle for testing smoothness
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() ) {
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];
2322 h &= (ANGLE_GRAPH_HEIGHT-1);
2323 renderSystem->DrawStretchPic( i* ANGLE_GRAPH_STRETCH, 480-h, 1, h, 0, 0, 1, 1, whiteMaterial );
2329 idSessionLocal::PacifierUpdate
2332 void idSessionLocal::PacifierUpdate() {
2333 if ( !insideExecuteMapChange ) {
2337 // never do pacifier screen updates while inside the
2338 // drawing code, or we can have various recursive problems
2339 if ( insideUpdateScreen ) {
2343 int time = eventLoop->Milliseconds();
2345 if ( time - lastPacifierTime < 100 ) {
2348 lastPacifierTime = time;
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 );
2358 Sys_GenerateEvents();
2362 idAsyncNetwork::client.PacifierUpdate();
2363 idAsyncNetwork::server.PacifierUpdate();
2368 idSessionLocal::Draw
2371 void idSessionLocal::Draw() {
2372 bool fullConsole = false;
2374 if ( insideExecuteMapChange ) {
2376 guiLoading->Redraw( com_frameTime );
2378 if ( guiActive == guiMsg ) {
2379 guiMsg->Redraw( com_frameTime );
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" ) ) {
2390 // draw the frozen gui in the background
2391 if ( guiActive == guiMsg && guiMsgRestore ) {
2392 guiMsgRestore->Redraw( com_frameTime );
2395 // draw the menus full screen
2396 if ( guiActive == guiTakeNotes && !com_skipGameDraw.GetBool() ) {
2397 game->Draw( GetLocalClientNum() );
2400 guiActive->Redraw( com_frameTime );
2401 } else if ( readDemo ) {
2402 rw->RenderScene( ¤tDemoRenderView );
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
2415 renderSystem->SetColor( colorBlack );
2416 renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) );
2419 // save off the 2D drawing from the game
2421 renderSystem->WriteDemoPics();
2425 if ( com_allowConsole.GetBool() ) {
2426 console->Draw( true );
2429 if ( emptyDrawCount > 5 ) {
2430 // it's best if you can avoid triggering the watchgod by doing the right thing somewhere else
2432 common->Warning( "idSession: triggering mainmenu watchdog" );
2436 renderSystem->SetColor4( 0, 0, 0, 1 );
2437 renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) );
2440 // draw the console full screen - this should only ever happen in developer builds
2441 console->Draw( true );
2447 if ( !fullConsole && emptyDrawCount ) {
2448 common->DPrintf( "idSession: %d empty frame draws\n", emptyDrawCount );
2451 fullConsole = false;
2454 // draw the wipe material on top of this if it hasn't completed yet
2457 // draw debug graphs
2460 // draw the half console / notify console on top of everything
2461 if ( !fullConsole ) {
2462 console->Draw( false );
2468 idSessionLocal::UpdateScreen
2471 void idSessionLocal::UpdateScreen( bool outOfSequence ) {
2475 if ( com_editors ) {
2476 if ( !Sys_IsWindowVisible() ) {
2482 if ( insideUpdateScreen ) {
2484 // common->FatalError( "idSessionLocal::UpdateScreen: recursively called" );
2487 insideUpdateScreen = true;
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 );
2495 renderSystem->BeginFrame( renderSystem->GetScreenWidth(), renderSystem->GetScreenHeight() );
2500 if ( com_speeds.GetBool() ) {
2501 renderSystem->EndFrame( &time_frontend, &time_backend );
2503 renderSystem->EndFrame( NULL, NULL );
2506 insideUpdateScreen = false;
2511 idSessionLocal::Frame
2514 void idSessionLocal::Frame() {
2516 if ( com_asyncSound.GetInteger() == 0 ) {
2517 soundSystem->AsyncUpdate( Sys_Milliseconds() );
2520 // Editors that completely take over the game
2521 if ( com_editorActive && ( com_editors & ( EDITOR_RADIANT | EDITOR_GUI ) ) ) {
2525 // if the console is down, we don't need to hold
2527 if ( console->Active() || com_editorActive ) {
2528 Sys_GrabMouseCursor( false );
2530 Sys_GrabMouseCursor( true );
2533 // save the screenshot and audio from the last draw if needed
2534 if ( aviCaptureMode ) {
2537 name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviTicStart );
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;
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 );
2549 aviTicStart = aviDemoFrameCount;
2551 // remove any printed lines at the top before taking the screenshot
2552 console->ClearNotifyLines();
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 );
2558 // at startup, we may be backwards
2559 if ( latchedTicNumber > com_ticNumber ) {
2560 latchedTicNumber = com_ticNumber;
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();
2570 if ( !timeDemo && numDemoFrames != 1 ) {
2571 minTic = lastDemoTic + USERCMD_PER_DEMO_FRAME;
2573 // timedemos and demoshots will run as fast as they can, other demos
2574 // will not run more than 30 hz
2575 minTic = latchedTicNumber;
2577 } else if ( writeDemo ) {
2578 minTic = lastGameTic + USERCMD_PER_DEMO_FRAME; // demos are recorded at 30 hz
2581 // fixedTic lets us run a forced number of usercmd each frame without timing
2582 if ( com_fixedTic.GetInteger() ) {
2583 minTic = latchedTicNumber;
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.
2592 latchedTicNumber = com_ticNumber;
2593 if ( latchedTicNumber >= minTic ) {
2600 latchedTicNumber = com_ticNumber;
2601 if ( latchedTicNumber >= minTic ) {
2604 Sys_WaitForEvent( TRIGGER_EVENT_ONE );
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
2617 authWaitBox = false;
2619 if ( cdkey_state == CDKEY_CHECKING ) {
2620 cdkey_state = CDKEY_OK;
2622 if ( xpkey_state == CDKEY_CHECKING ) {
2623 xpkey_state = CDKEY_OK;
2625 // maintain this empty as it's set by auth denials
2627 authEmitTimeout = 0;
2632 // send frame and mouse events to active guis
2637 AdvanceRenderDemo( false );
2641 //------------ single player game tics --------------
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();
2650 if ( !mapSpawned ) {
2655 lastGameTic = latchedTicNumber;
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() ) {
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 );
2672 // see how many usercmds we are going to run
2673 int numCmdsToRun = latchedTicNumber - lastGameTic;
2675 // don't let a long onDemand sound load unsync everything
2677 int skip = timeHitch / USERCMD_MSEC;
2678 lastGameTic += skip;
2679 numCmdsToRun -= skip;
2683 // don't get too far behind after a hitch
2684 if ( numCmdsToRun > 10 ) {
2685 lastGameTic = latchedTicNumber - 10;
2688 // never use more than USERCMD_PER_DEMO_FRAME,
2689 // which makes it go into slow motion when recording
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" );
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();
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;
2714 // create client commands, which will be sent directly
2716 if ( com_showTics.GetBool() ) {
2717 common->Printf( "%i ", latchedTicNumber - lastGameTic );
2720 int gameTicsToRun = latchedTicNumber - lastGameTic;
2722 for ( i = 0 ; i < gameTicsToRun ; i++ ) {
2724 if ( !mapSpawned ) {
2728 if ( syncNextGameFrame ) {
2729 // long game frame, so break out and continue executing as if there was no hitch
2737 idSessionLocal::RunGameTic
2740 void idSessionLocal::RunGameTic() {
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 );
2750 if ( aviCaptureMode ) {
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
2760 logCmd.consistencyHash = LittleLong( logCmd.consistencyHash );
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 );
2770 cmd = usercmdGen->GetDirectUsercmd();
2775 // run the game logic every player move
2776 int start = Sys_Milliseconds();
2777 gameReturn_t ret = game->RunFrame( &cmd );
2779 int end = Sys_Milliseconds();
2780 time_gameFrame += end - start; // note time used for com_speeds
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 );
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;
2806 syncNextGameFrame = ret.syncNextGameFrame;
2808 if ( ret.sessionCommand[0] ) {
2811 args.TokenizeString( ret.sessionCommand, false );
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 );
2818 // clear the devmap key on serverinfo, so player spawns
2819 // won't get the map testing items
2820 mapSpawnData.serverInfo.Delete( "devmap" );
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
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" );
2841 idSessionLocal::Init
2843 Called in an orderly fashion at system startup,
2844 so commands, cvars, files, etc are all available
2847 void idSessionLocal::Init() {
2849 common->Printf( "-------- Initializing Session --------\n" );
2851 cmdSystem->AddCommand( "writePrecache", Sess_WritePrecache_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "writes precache commands" );
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 );
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" );
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 );
2874 cmdSystem->AddCommand( "disconnect", Session_Disconnect_f, CMD_FL_SYSTEM, "disconnects from a game" );
2876 #ifdef ID_DEMO_BUILD
2877 cmdSystem->AddCommand( "endOfDemo", Session_EndOfDemo_f, CMD_FL_SYSTEM, "ends the demo version of the game" );
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" );
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 );
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" );
2891 cmdSystem->AddCommand( "rescanSI", Session_RescanSI_f, CMD_FL_SYSTEM, "internal - rescan serverinfo cvars and tell game" );
2893 cmdSystem->AddCommand( "promptKey", Session_PromptKey_f, CMD_FL_SYSTEM, "prompt and sets the CD Key" );
2895 cmdSystem->AddCommand( "hitch", Session_Hitch_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "hitches the game" );
2897 // the same idRenderWorld will be used for all games
2898 // and demos, insuring that level specific models
2900 rw = renderSystem->AllocRenderWorld();
2901 sw = soundSystem->AllocSoundWorld( rw );
2903 menuSoundWorld = soundSystem->AllocSoundWorld( rw );
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 );
2909 guiMainMenu = uiManager->FindGui( "guis/demo_mainmenu.gui", true, false, true );
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 );
2920 whiteMaterial = declManager->FindMaterial( "_white" );
2930 common->Printf( "session initialized\n" );
2931 common->Printf( "--------------------------------------\n" );
2936 idSessionLocal::GetLocalClientNum
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 ) {
2945 } else if ( idAsyncNetwork::server.IsClientInGame( idAsyncNetwork::serverDrawClient.GetInteger() ) ) {
2946 return idAsyncNetwork::serverDrawClient.GetInteger();
2957 idSessionLocal::SetPlayingSoundWorld
2960 void idSessionLocal::SetPlayingSoundWorld() {
2961 if ( guiActive && ( guiActive == guiMainMenu || guiActive == guiIntro || guiActive == guiLoading || ( guiActive == guiMsg && !mapSpawned ) ) ) {
2962 soundSystem->SetPlayingSoundWorld( menuSoundWorld );
2964 soundSystem->SetPlayingSoundWorld( sw );
2970 idSessionLocal::TimeHitch
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
2976 void idSessionLocal::TimeHitch( int msec ) {
2982 idSessionLocal::ReadCDKey
2985 void idSessionLocal::ReadCDKey( void ) {
2990 cdkey_state = CDKEY_UNKNOWN;
2992 filename = "../" BASE_GAMEDIR "/" CDKEY_FILE;
2993 f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_savepath" ) );
2995 common->Printf( "Couldn't read %s.\n", filename.c_str() );
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 );
3004 xpkey_state = CDKEY_UNKNOWN;
3006 filename = "../" BASE_GAMEDIR "/" XPKEY_FILE;
3007 f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_savepath" ) );
3009 common->Printf( "Couldn't read %s.\n", filename.c_str() );
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 );
3021 idSessionLocal::WriteCDKey
3024 void idSessionLocal::WriteCDKey( void ) {
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 );
3036 common->Printf( "Couldn't write %s.\n", filename.c_str() );
3039 f->Printf( "%s%s", cdkey, CDKEY_TEXT );
3040 fileSystem->CloseFile( f );
3042 filename = "../" BASE_GAMEDIR "/" XPKEY_FILE;
3043 f = fileSystem->OpenFileWrite( filename );
3045 common->Printf( "Couldn't write %s.\n", filename.c_str() );
3048 f->Printf( "%s%s", xpkey, CDKEY_TEXT );
3049 fileSystem->CloseFile( f );
3054 idSessionLocal::ClearKey
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;
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;
3076 idSessionLocal::GetCDKey
3079 const char *idSessionLocal::GetCDKey( bool xp ) {
3083 if ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING ) {
3089 // digits to letters table
3090 #define CDKEY_DIGITS "TWSBJCGD7PA23RLH"
3094 idSessionLocal::EmitGameAuth
3095 we toggled some key state to CDKEY_CHECKING. send a standalone auth packet to validate
3098 void idSessionLocal::EmitGameAuth( void ) {
3099 // make sure the auth reply is empty, we use it to indicate an auth reply
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" );
3105 // net is not available
3106 common->DPrintf( "sendAuthCheck failed\n" );
3107 if ( cdkey_state == CDKEY_CHECKING ) {
3108 cdkey_state = CDKEY_OK;
3110 if ( xpkey_state == CDKEY_CHECKING ) {
3111 xpkey_state = CDKEY_OK;
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
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 ];
3128 unsigned int checksum, chk8;
3129 bool edited_key[ 2 ];
3131 // make sure have a right input string
3132 assert( strlen( key ) == ( CDKEY_BUF_LEN - 1 ) * 2 + 4 + 3 + 4 );
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] );
3145 if ( fileSystem->HasD3XP() ) {
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
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;
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;
3173 if ( !offline_valid[ 0 ] || !offline_valid[1] ) {
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
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;
3187 xpkey_state = CDKEY_NA;
3189 if ( !netConnect ) {
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
3205 bool idSessionLocal::CDKeysAreValid( bool strict ) {
3207 bool emitAuth = false;
3209 if ( cdkey_state == CDKEY_UNKNOWN ) {
3210 if ( strlen( cdkey ) != CDKEY_BUF_LEN - 1 ) {
3211 cdkey_state = CDKEY_INVALID;
3213 for ( i = 0; i < CDKEY_BUF_LEN-1; i++ ) {
3214 if ( !strchr( CDKEY_DIGITS, cdkey[i] ) ) {
3215 cdkey_state = CDKEY_INVALID;
3220 if ( cdkey_state == CDKEY_UNKNOWN ) {
3221 cdkey_state = CDKEY_CHECKING;
3225 if ( xpkey_state == CDKEY_UNKNOWN ) {
3226 if ( fileSystem->HasD3XP() ) {
3227 if ( strlen( xpkey ) != CDKEY_BUF_LEN -1 ) {
3228 xpkey_state = CDKEY_INVALID;
3230 for ( i = 0; i < CDKEY_BUF_LEN-1; i++ ) {
3231 if ( !strchr( CDKEY_DIGITS, xpkey[i] ) ) {
3232 xpkey_state = CDKEY_INVALID;
3236 if ( xpkey_state == CDKEY_UNKNOWN ) {
3237 xpkey_state = CDKEY_CHECKING;
3241 xpkey_state = CDKEY_NA;
3247 // make sure to keep the mainmenu gui up to date in case we made state changes
3250 return cdkey_state == CDKEY_OK && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_NA );
3252 return ( cdkey_state == CDKEY_OK || cdkey_state == CDKEY_CHECKING ) && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING || xpkey_state == CDKEY_NA );
3258 idSessionLocal::WaitingForGameAuth
3261 bool idSessionLocal::WaitingForGameAuth( void ) {
3262 return authEmitTimeout != 0;
3267 idSessionLocal::CDKeysAuthReply
3270 void idSessionLocal::CDKeysAuthReply( bool valid, const char *auth_msg ) {
3271 assert( authEmitTimeout > 0 );
3272 if ( authWaitBox ) {
3273 // close the wait box
3275 authWaitBox = false;
3278 common->DPrintf( "auth key is invalid\n" );
3280 if ( cdkey_state == CDKEY_CHECKING ) {
3281 cdkey_state = CDKEY_INVALID;
3283 if ( xpkey_state == CDKEY_CHECKING ) {
3284 xpkey_state = CDKEY_INVALID;
3287 common->DPrintf( "client is authed in\n" );
3288 if ( cdkey_state == CDKEY_CHECKING ) {
3289 cdkey_state = CDKEY_OK;
3291 if ( xpkey_state == CDKEY_CHECKING ) {
3292 xpkey_state = CDKEY_OK;
3295 authEmitTimeout = 0;
3301 idSessionLocal::GetCurrentMapName
3304 const char *idSessionLocal::GetCurrentMapName() {
3305 return currentMapName.c_str();
3310 idSessionLocal::GetSaveGameVersion
3313 int idSessionLocal::GetSaveGameVersion( void ) {
3314 return savegameVersion;
3319 idSessionLocal::GetAuthMsg
3322 const char *idSessionLocal::GetAuthMsg( void ) {
3323 return authMsg.c_str();