/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . 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. 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. =========================================================================== */ #include "sys/platform.h" #include "idlib/hashing/CRC32.h" #include "idlib/LangDict.h" #include "framework/async/AsyncNetwork.h" #include "framework/Console.h" #include "framework/Game.h" #include "framework/EventLoop.h" #include "renderer/ModelManager.h" #include "framework/Session_local.h" idCVar idSessionLocal::com_showAngles( "com_showAngles", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); idCVar idSessionLocal::com_minTics( "com_minTics", "1", CVAR_SYSTEM, "" ); idCVar idSessionLocal::com_showTics( "com_showTics", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); idCVar idSessionLocal::com_fixedTic( "com_fixedTic", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 10 ); idCVar idSessionLocal::com_showDemo( "com_showDemo", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); idCVar idSessionLocal::com_skipGameDraw( "com_skipGameDraw", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); idCVar idSessionLocal::com_aviDemoSamples( "com_aviDemoSamples", "16", CVAR_SYSTEM, "" ); idCVar idSessionLocal::com_aviDemoWidth( "com_aviDemoWidth", "256", CVAR_SYSTEM, "" ); idCVar idSessionLocal::com_aviDemoHeight( "com_aviDemoHeight", "256", CVAR_SYSTEM, "" ); idCVar idSessionLocal::com_aviDemoTics( "com_aviDemoTics", "2", CVAR_SYSTEM | CVAR_INTEGER, "", 1, 60 ); idCVar idSessionLocal::com_wipeSeconds( "com_wipeSeconds", "1", CVAR_SYSTEM, "" ); idCVar idSessionLocal::com_guid( "com_guid", "", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_ROM, "" ); idSessionLocal sessLocal; idSession *session = &sessLocal; // these must be kept up to date with window Levelshot in guis/mainmenu.gui const int PREVIEW_X = 211; const int PREVIEW_Y = 31; const int PREVIEW_WIDTH = 398; const int PREVIEW_HEIGHT = 298; void RandomizeStack( void ) { // attempt to force uninitialized stack memory bugs int bytes = 4000000; byte *buf = (byte *)_alloca( bytes ); int fill = rand()&255; for ( int i = 0 ; i < bytes ; i++ ) { buf[i] = fill; } } /* ================= Session_RescanSI_f ================= */ void Session_RescanSI_f( const idCmdArgs &args ) { sessLocal.mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); if ( game && idAsyncNetwork::server.IsActive() ) { game->SetServerInfo( sessLocal.mapSpawnData.serverInfo ); } } #ifndef ID_DEDICATED /* ================== Session_Map_f Restart the server on a different map ================== */ static void Session_Map_f( const idCmdArgs &args ) { idStr map, string; findFile_t ff; idCmdArgs rl_args; map = args.Argv(1); if ( !map.Length() ) { return; } map.StripFileExtension(); // make sure the level exists before trying to change, so that // a typo at the server console won't end the game // handle addon packs through reloadEngine sprintf( string, "maps/%s.map", map.c_str() ); ff = fileSystem->FindFile( string, true ); switch ( ff ) { case FIND_NO: common->Printf( "Can't find map %s\n", string.c_str() ); return; case FIND_ADDON: common->Printf( "map %s is in an addon pak - reloading\n", string.c_str() ); rl_args.AppendArg( "map" ); rl_args.AppendArg( map ); cmdSystem->SetupReloadEngine( rl_args ); return; default: break; } cvarSystem->SetCVarBool( "developer", false ); sessLocal.StartNewGame( map, true ); } /* ================== Session_DevMap_f Restart the server on a different map in developer mode ================== */ static void Session_DevMap_f( const idCmdArgs &args ) { idStr map, string; findFile_t ff; idCmdArgs rl_args; map = args.Argv(1); if ( !map.Length() ) { return; } map.StripFileExtension(); // make sure the level exists before trying to change, so that // a typo at the server console won't end the game // handle addon packs through reloadEngine sprintf( string, "maps/%s.map", map.c_str() ); ff = fileSystem->FindFile( string, true ); switch ( ff ) { case FIND_NO: common->Printf( "Can't find map %s\n", string.c_str() ); return; case FIND_ADDON: common->Printf( "map %s is in an addon pak - reloading\n", string.c_str() ); rl_args.AppendArg( "devmap" ); rl_args.AppendArg( map ); cmdSystem->SetupReloadEngine( rl_args ); return; default: break; } cvarSystem->SetCVarBool( "developer", true ); sessLocal.StartNewGame( map, true ); } /* ================== Session_TestMap_f ================== */ static void Session_TestMap_f( const idCmdArgs &args ) { idStr map, string; map = args.Argv(1); if ( !map.Length() ) { return; } map.StripFileExtension(); cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); sprintf( string, "dmap maps/%s.map", map.c_str() ); cmdSystem->BufferCommandText( CMD_EXEC_NOW, string ); sprintf( string, "devmap %s", map.c_str() ); cmdSystem->BufferCommandText( CMD_EXEC_NOW, string ); } #endif /* ================== Sess_WritePrecache_f ================== */ static void Sess_WritePrecache_f( const idCmdArgs &args ) { if ( args.Argc() != 2 ) { common->Printf( "USAGE: writePrecache \n" ); return; } idStr str = args.Argv(1); str.DefaultFileExtension( ".cfg" ); idFile *f = fileSystem->OpenFileWrite( str, "fs_configpath" ); declManager->WritePrecacheCommands( f ); renderModelManager->WritePrecacheCommands( f ); uiManager->WritePrecacheCommands( f ); fileSystem->CloseFile( f ); } /* =============== idSessionLocal::MaybeWaitOnCDKey =============== */ bool idSessionLocal::MaybeWaitOnCDKey( void ) { if ( authEmitTimeout > 0 ) { authWaitBox = true; sessLocal.MessageBox( MSG_WAIT, common->GetLanguageDict()->GetString( "#str_07191" ), NULL, true, NULL, NULL, true ); return true; } return false; } /* =================== Session_PromptKey_f =================== */ static void Session_PromptKey_f( const idCmdArgs &args ) { const char *retkey; bool valid[ 2 ]; static bool recursed = false; if ( recursed ) { common->Warning( "promptKey recursed - aborted" ); return; } recursed = true; do { // in case we're already waiting for an auth to come back to us ( may happen exceptionally ) if ( sessLocal.MaybeWaitOnCDKey() ) { if ( sessLocal.CDKeysAreValid( true ) ) { recursed = false; return; } } // the auth server may have replied and set an error message, otherwise use a default const char *prompt_msg = sessLocal.GetAuthMsg(); if ( prompt_msg[ 0 ] == '\0' ) { prompt_msg = common->GetLanguageDict()->GetString( "#str_04308" ); } retkey = sessLocal.MessageBox( MSG_CDKEY, prompt_msg, common->GetLanguageDict()->GetString( "#str_04305" ), true, NULL, NULL, true ); if ( retkey ) { if ( sessLocal.CheckKey( retkey, false, valid ) ) { // if all went right, then we may have sent an auth request to the master ( unless the prompt is used during a net connect ) bool canExit = true; if ( sessLocal.MaybeWaitOnCDKey() ) { // wait on auth reply, and got denied, prompt again if ( !sessLocal.CDKeysAreValid( true ) ) { // server says key is invalid - MaybeWaitOnCDKey was interrupted by a CDKeysAuthReply call, which has set the right error message // the invalid keys have also been cleared in the process sessLocal.MessageBox( MSG_OK, sessLocal.GetAuthMsg(), common->GetLanguageDict()->GetString( "#str_04310" ), true, NULL, NULL, true ); canExit = false; } } if ( canExit ) { // make sure that's saved on file sessLocal.WriteCDKey(); sessLocal.MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_04307" ), common->GetLanguageDict()->GetString( "#str_04305" ), true, NULL, NULL, true ); break; } } else { // offline check sees key invalid // build a message about keys being wrong. do not attempt to change the current key state though // ( the keys may be valid, but user would have clicked on the dialog anyway, that kind of thing ) idStr msg; idAsyncNetwork::BuildInvalidKeyMsg( msg, valid ); sessLocal.MessageBox( MSG_OK, msg, common->GetLanguageDict()->GetString( "#str_04310" ), true, NULL, NULL, true ); } } else if ( args.Argc() == 2 && idStr::Icmp( args.Argv(1), "force" ) == 0 ) { // cancelled in force mode cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); cmdSystem->ExecuteCommandBuffer(); } } while ( retkey ); recursed = false; } /* =============================================================================== SESSION LOCAL =============================================================================== */ /* =============== idSessionLocal::Clear =============== */ void idSessionLocal::Clear() { insideUpdateScreen = false; insideExecuteMapChange = false; loadingSaveGame = false; savegameFile = NULL; savegameVersion = 0; currentMapName.Clear(); aviDemoShortName.Clear(); msgFireBack[ 0 ].Clear(); msgFireBack[ 1 ].Clear(); timeHitch = 0; rw = NULL; sw = NULL; menuSoundWorld = NULL; readDemo = NULL; writeDemo = NULL; renderdemoVersion = 0; cmdDemoFile = NULL; syncNextGameFrame = false; mapSpawned = false; guiActive = NULL; aviCaptureMode = false; timeDemo = TD_NO; waitingOnBind = false; lastPacifierTime = 0; msgRunning = false; guiMsgRestore = NULL; msgIgnoreButtons = false; bytesNeededForMapLoad = 0; #if ID_CONSOLE_LOCK emptyDrawCount = 0; #endif ClearWipe(); loadGameList.Clear(); modsList.Clear(); authEmitTimeout = 0; authWaitBox = false; authMsg.Clear(); } /* =============== idSessionLocal::idSessionLocal =============== */ idSessionLocal::idSessionLocal() { guiInGame = guiMainMenu = guiIntro \ = guiRestartMenu = guiLoading = guiGameOver = guiActive \ = guiTest = guiMsg = guiMsgRestore = guiTakeNotes = NULL; menuSoundWorld = NULL; Clear(); } /* =============== idSessionLocal::~idSessionLocal =============== */ idSessionLocal::~idSessionLocal() { } /* =============== idSessionLocal::Stop called on errors and game exits =============== */ void idSessionLocal::Stop() { ClearWipe(); // clear mapSpawned and demo playing flags UnloadMap(); // disconnect async client idAsyncNetwork::client.DisconnectFromServer(); // kill async server idAsyncNetwork::server.Kill(); if ( sw ) { sw->StopAllSounds(); } insideUpdateScreen = false; insideExecuteMapChange = false; // drop all guis SetGUI( NULL, NULL ); } /* =============== idSessionLocal::Shutdown =============== */ void idSessionLocal::Shutdown() { int i; if ( aviCaptureMode ) { EndAVICapture(); } if(timeDemo == TD_YES) { // else the game freezes when showing the timedemo results timeDemo = TD_YES_THEN_QUIT; } Stop(); if ( rw ) { delete rw; rw = NULL; } if ( sw ) { delete sw; sw = NULL; } if ( menuSoundWorld ) { delete menuSoundWorld; menuSoundWorld = NULL; } mapSpawnData.serverInfo.Clear(); mapSpawnData.syncedCVars.Clear(); for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { mapSpawnData.userInfo[i].Clear(); mapSpawnData.persistentPlayerInfo[i].Clear(); } if ( guiMainMenu_MapList != NULL ) { guiMainMenu_MapList->Shutdown(); uiManager->FreeListGUI( guiMainMenu_MapList ); guiMainMenu_MapList = NULL; } Clear(); } /* =============== idSessionLocal::IsMultiplayer =============== */ bool idSessionLocal::IsMultiplayer() { return idAsyncNetwork::IsActive(); } /* ================ idSessionLocal::StartWipe Draws and captures the current state, then starts a wipe with that image ================ */ void idSessionLocal::StartWipe( const char *_wipeMaterial, bool hold ) { console->Close(); // render the current screen into a texture for the wipe model renderSystem->CropRenderSize( 640, 480, true ); Draw(); renderSystem->CaptureRenderToImage( "_scratch"); renderSystem->UnCrop(); wipeMaterial = declManager->FindMaterial( _wipeMaterial, false ); wipeStartTic = com_ticNumber; wipeStopTic = wipeStartTic + 1000.0f / USERCMD_MSEC * com_wipeSeconds.GetFloat(); wipeHold = hold; } /* ================ idSessionLocal::CompleteWipe ================ */ void idSessionLocal::CompleteWipe() { if ( com_ticNumber == 0 ) { // if the async thread hasn't started, we would hang here wipeStopTic = 0; UpdateScreen( true ); return; } while ( com_ticNumber < wipeStopTic ) { #if ID_CONSOLE_LOCK emptyDrawCount = 0; #endif UpdateScreen( true ); } } /* ================ idSessionLocal::ShowLoadingGui ================ */ void idSessionLocal::ShowLoadingGui() { if ( com_ticNumber == 0 ) { return; } console->Close(); // introduced in D3XP code. don't think it actually fixes anything, but doesn't hurt either #if 1 // Try and prevent the while loop from being skipped over (long hitch on the main thread?) int stop = Sys_Milliseconds() + 1000; int force = 10; while ( Sys_Milliseconds() < stop || force-- > 0 ) { com_frameTime = com_ticNumber * USERCMD_MSEC; session->Frame(); session->UpdateScreen( false ); } #else int stop = com_ticNumber + 1000.0f / USERCMD_MSEC * 1.0f; while ( com_ticNumber < stop ) { com_frameTime = com_ticNumber * USERCMD_MSEC; session->Frame(); session->UpdateScreen( false ); } #endif } /* ================ idSessionLocal::ClearWipe ================ */ void idSessionLocal::ClearWipe( void ) { wipeHold = false; wipeStopTic = 0; wipeStartTic = wipeStopTic + 1; } /* ================ Session_TestGUI_f ================ */ static void Session_TestGUI_f( const idCmdArgs &args ) { sessLocal.TestGUI( args.Argv(1) ); } /* ================ idSessionLocal::TestGUI ================ */ void idSessionLocal::TestGUI( const char *guiName ) { if ( guiName && *guiName ) { guiTest = uiManager->FindGui( guiName, true, false, true ); } else { guiTest = NULL; } } /* ================ FindUnusedFileName ================ */ static idStr FindUnusedFileName( const char *format ) { int i; char filename[1024]; for ( i = 0 ; i < 999 ; i++ ) { sprintf( filename, format, i ); int len = fileSystem->ReadFile( filename, NULL, NULL ); if ( len <= 0 ) { return filename; // file doesn't exist } } return filename; } /* ================ Session_DemoShot_f ================ */ static void Session_DemoShot_f( const idCmdArgs &args ) { if ( args.Argc() != 2 ) { idStr filename = FindUnusedFileName( "demos/shot%03i.demo" ); sessLocal.DemoShot( filename ); } else { sessLocal.DemoShot( va( "demos/shot_%s.demo", args.Argv(1) ) ); } } #ifndef ID_DEDICATED /* ================ Session_RecordDemo_f ================ */ static void Session_RecordDemo_f( const idCmdArgs &args ) { if ( args.Argc() != 2 ) { idStr filename = FindUnusedFileName( "demos/demo%03i.demo" ); sessLocal.StartRecordingRenderDemo( filename ); } else { sessLocal.StartRecordingRenderDemo( va( "demos/%s.demo", args.Argv(1) ) ); } } /* ================ Session_CompressDemo_f ================ */ static void Session_CompressDemo_f( const idCmdArgs &args ) { if ( args.Argc() == 2 ) { sessLocal.CompressDemoFile( "2", args.Argv(1) ); } else if ( args.Argc() == 3 ) { sessLocal.CompressDemoFile( args.Argv(2), args.Argv(1) ); } else { common->Printf("use: CompressDemo [scheme]\nscheme is the same as com_compressDemo, defaults to 2" ); } } /* ================ Session_StopRecordingDemo_f ================ */ static void Session_StopRecordingDemo_f( const idCmdArgs &args ) { sessLocal.StopRecordingRenderDemo(); } /* ================ Session_PlayDemo_f ================ */ static void Session_PlayDemo_f( const idCmdArgs &args ) { if ( args.Argc() >= 2 ) { sessLocal.StartPlayingRenderDemo( va( "demos/%s", args.Argv(1) ) ); } } /* ================ Session_TimeDemo_f ================ */ static void Session_TimeDemo_f( const idCmdArgs &args ) { if ( args.Argc() >= 2 ) { sessLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ), ( args.Argc() > 2 ) ); } } /* ================ Session_TimeDemoQuit_f ================ */ static void Session_TimeDemoQuit_f( const idCmdArgs &args ) { sessLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ) ); if ( sessLocal.timeDemo == TD_YES ) { // this allows hardware vendors to automate some testing sessLocal.timeDemo = TD_YES_THEN_QUIT; } } /* ================ Session_AVIDemo_f ================ */ static void Session_AVIDemo_f( const idCmdArgs &args ) { sessLocal.AVIRenderDemo( va( "demos/%s", args.Argv(1) ) ); } /* ================ Session_AVIGame_f ================ */ static void Session_AVIGame_f( const idCmdArgs &args ) { sessLocal.AVIGame( args.Argv(1) ); } /* ================ Session_AVICmdDemo_f ================ */ static void Session_AVICmdDemo_f( const idCmdArgs &args ) { sessLocal.AVICmdDemo( args.Argv(1) ); } /* ================ Session_WriteCmdDemo_f ================ */ static void Session_WriteCmdDemo_f( const idCmdArgs &args ) { if ( args.Argc() == 1 ) { idStr filename = FindUnusedFileName( "demos/cmdDemo%03i.cdemo" ); sessLocal.WriteCmdDemo( filename ); } else if ( args.Argc() == 2 ) { sessLocal.WriteCmdDemo( va( "demos/%s.cdemo", args.Argv( 1 ) ) ); } else { common->Printf( "usage: writeCmdDemo [demoName]\n" ); } } /* ================ Session_PlayCmdDemo_f ================ */ static void Session_PlayCmdDemo_f( const idCmdArgs &args ) { sessLocal.StartPlayingCmdDemo( args.Argv(1) ); } /* ================ Session_TimeCmdDemo_f ================ */ static void Session_TimeCmdDemo_f( const idCmdArgs &args ) { sessLocal.TimeCmdDemo( args.Argv(1) ); } #endif /* ================ Session_Disconnect_f ================ */ static void Session_Disconnect_f( const idCmdArgs &args ) { sessLocal.Stop(); sessLocal.StartMenu(); if ( soundSystem ) { soundSystem->SetMute( false ); } } #ifndef ID_DEDICATED /* ================ Session_ExitCmdDemo_f ================ */ static void Session_ExitCmdDemo_f( const idCmdArgs &args ) { if ( !sessLocal.cmdDemoFile ) { common->Printf( "not reading from a cmdDemo\n" ); return; } fileSystem->CloseFile( sessLocal.cmdDemoFile ); common->Printf( "Command demo exited at logIndex %i\n", sessLocal.logIndex ); sessLocal.cmdDemoFile = NULL; } #endif /* ================ idSessionLocal::StartRecordingRenderDemo ================ */ void idSessionLocal::StartRecordingRenderDemo( const char *demoName ) { if ( writeDemo ) { // allow it to act like a toggle StopRecordingRenderDemo(); return; } if ( !demoName[0] ) { common->Printf( "idSessionLocal::StartRecordingRenderDemo: no name specified\n" ); return; } console->Close(); writeDemo = new idDemoFile; if ( !writeDemo->OpenForWriting( demoName ) ) { common->Printf( "error opening %s\n", demoName ); delete writeDemo; writeDemo = NULL; return; } common->Printf( "recording to %s\n", writeDemo->GetName() ); writeDemo->WriteInt( DS_VERSION ); writeDemo->WriteInt( RENDERDEMO_VERSION ); // if we are in a map already, dump the current state sw->StartWritingDemo( writeDemo ); rw->StartWritingDemo( writeDemo ); } /* ================ idSessionLocal::StopRecordingRenderDemo ================ */ void idSessionLocal::StopRecordingRenderDemo() { if ( !writeDemo ) { common->Printf( "idSessionLocal::StopRecordingRenderDemo: not recording\n" ); return; } sw->StopWritingDemo(); rw->StopWritingDemo(); writeDemo->Close(); common->Printf( "stopped recording %s.\n", writeDemo->GetName() ); delete writeDemo; writeDemo = NULL; } /* ================ idSessionLocal::StopPlayingRenderDemo Reports timeDemo numbers and finishes any avi recording ================ */ void idSessionLocal::StopPlayingRenderDemo() { if ( !readDemo ) { timeDemo = TD_NO; return; } // Record the stop time before doing anything that could be time consuming int timeDemoStopTime = Sys_Milliseconds(); EndAVICapture(); readDemo->Close(); sw->StopAllSounds(); soundSystem->SetPlayingSoundWorld( menuSoundWorld ); common->Printf( "stopped playing %s.\n", readDemo->GetName() ); delete readDemo; readDemo = NULL; if ( timeDemo ) { // report the stats float demoSeconds = ( timeDemoStopTime - timeDemoStartTime ) * 0.001f; float demoFPS = numDemoFrames / demoSeconds; idStr message = va( "%i frames rendered in %3.1f seconds = %3.1f fps\n", numDemoFrames, demoSeconds, demoFPS ); common->Printf( message ); if ( timeDemo == TD_YES_THEN_QUIT ) { cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); } else { soundSystem->SetMute( true ); MessageBox( MSG_OK, message, "Time Demo Results", true ); soundSystem->SetMute( false ); } timeDemo = TD_NO; } } /* ================ idSessionLocal::DemoShot A demoShot is a single frame demo ================ */ void idSessionLocal::DemoShot( const char *demoName ) { StartRecordingRenderDemo( demoName ); // force draw one frame UpdateScreen(); StopRecordingRenderDemo(); } /* ================ idSessionLocal::StartPlayingRenderDemo ================ */ void idSessionLocal::StartPlayingRenderDemo( idStr demoName ) { if ( !demoName[0] ) { common->Printf( "idSessionLocal::StartPlayingRenderDemo: no name specified\n" ); return; } // make sure localSound / GUI intro music shuts up sw->StopAllSounds(); sw->PlayShaderDirectly( "", 0 ); menuSoundWorld->StopAllSounds(); menuSoundWorld->PlayShaderDirectly( "", 0 ); // exit any current game Stop(); // automatically put the console away console->Close(); // bring up the loading screen manually, since demos won't // call ExecuteMapChange() guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true ); guiLoading->SetStateString( "demo", common->GetLanguageDict()->GetString( "#str_02087" ) ); readDemo = new idDemoFile; demoName.DefaultFileExtension( ".demo" ); if ( !readDemo->OpenForReading( demoName ) ) { common->Printf( "couldn't open %s\n", demoName.c_str() ); delete readDemo; readDemo = NULL; Stop(); StartMenu(); soundSystem->SetMute( false ); return; } insideExecuteMapChange = true; UpdateScreen(); insideExecuteMapChange = false; guiLoading->SetStateString( "demo", "" ); // setup default render demo settings // that's default for <= Doom3 v1.1 renderdemoVersion = 1; savegameVersion = 16; AdvanceRenderDemo( true ); numDemoFrames = 1; lastDemoTic = -1; timeDemoStartTime = Sys_Milliseconds(); } /* ================ idSessionLocal::TimeRenderDemo ================ */ void idSessionLocal::TimeRenderDemo( const char *demoName, bool twice ) { idStr demo = demoName; // no sound in time demos soundSystem->SetMute( true ); StartPlayingRenderDemo( demo ); if ( twice && readDemo ) { // cycle through once to precache everything guiLoading->SetStateString( "demo", common->GetLanguageDict()->GetString( "#str_04852" ) ); guiLoading->StateChanged( com_frameTime ); while ( readDemo ) { insideExecuteMapChange = true; UpdateScreen(); insideExecuteMapChange = false; AdvanceRenderDemo( true ); } guiLoading->SetStateString( "demo", "" ); StartPlayingRenderDemo( demo ); } if ( !readDemo ) { return; } timeDemo = TD_YES; } /* ================ idSessionLocal::BeginAVICapture ================ */ void idSessionLocal::BeginAVICapture( const char *demoName ) { idStr name = demoName; name.ExtractFileBase( aviDemoShortName ); aviCaptureMode = true; aviDemoFrameCount = 0; aviTicStart = 0; sw->AVIOpen( va( "demos/%s/", aviDemoShortName.c_str() ), aviDemoShortName.c_str() ); } /* ================ idSessionLocal::EndAVICapture ================ */ void idSessionLocal::EndAVICapture() { if ( !aviCaptureMode ) { return; } sw->AVIClose(); // write a .roqParam file so the demo can be converted to a roq file idFile *f = fileSystem->OpenFileWrite( va( "demos/%s/%s.roqParam", aviDemoShortName.c_str(), aviDemoShortName.c_str() ) ); f->Printf( "INPUT_DIR demos/%s\n", aviDemoShortName.c_str() ); f->Printf( "FILENAME demos/%s/%s.RoQ\n", aviDemoShortName.c_str(), aviDemoShortName.c_str() ); f->Printf( "\nINPUT\n" ); f->Printf( "%s_*.tga [00000-%05i]\n", aviDemoShortName.c_str(), (int)( aviDemoFrameCount-1 ) ); f->Printf( "END_INPUT\n" ); delete f; common->Printf( "captured %i frames for %s.\n", ( int )aviDemoFrameCount, aviDemoShortName.c_str() ); aviCaptureMode = false; } /* ================ idSessionLocal::AVIRenderDemo ================ */ void idSessionLocal::AVIRenderDemo( const char *_demoName ) { idStr demoName = _demoName; // copy off from va() buffer StartPlayingRenderDemo( demoName ); if ( !readDemo ) { return; } BeginAVICapture( demoName.c_str() ) ; // I don't understand why I need to do this twice, something // strange with the nvidia swapbuffers? UpdateScreen(); } /* ================ idSessionLocal::AVICmdDemo ================ */ void idSessionLocal::AVICmdDemo( const char *demoName ) { StartPlayingCmdDemo( demoName ); BeginAVICapture( demoName ) ; } /* ================ idSessionLocal::AVIGame Start AVI recording the current game session ================ */ void idSessionLocal::AVIGame( const char *demoName ) { if ( aviCaptureMode ) { EndAVICapture(); return; } if ( !mapSpawned ) { common->Printf( "No map spawned.\n" ); } if ( !demoName || !demoName[0] ) { idStr filename = FindUnusedFileName( "demos/game%03i.game" ); demoName = filename.c_str(); // write a one byte stub .game file just so the FindUnusedFileName works, fileSystem->WriteFile( demoName, demoName, 1 ); } BeginAVICapture( demoName ) ; } /* ================ idSessionLocal::CompressDemoFile ================ */ void idSessionLocal::CompressDemoFile( const char *scheme, const char *demoName ) { idStr fullDemoName = "demos/"; fullDemoName += demoName; fullDemoName.DefaultFileExtension( ".demo" ); idStr compressedName = fullDemoName; compressedName.StripFileExtension(); compressedName.Append( "_compressed.demo" ); int savedCompression = cvarSystem->GetCVarInteger("com_compressDemos"); bool savedPreload = cvarSystem->GetCVarBool("com_preloadDemos"); cvarSystem->SetCVarBool( "com_preloadDemos", false ); cvarSystem->SetCVarInteger("com_compressDemos", atoi(scheme) ); idDemoFile demoread, demowrite; if ( !demoread.OpenForReading( fullDemoName ) ) { common->Printf( "Could not open %s for reading\n", fullDemoName.c_str() ); return; } if ( !demowrite.OpenForWriting( compressedName ) ) { common->Printf( "Could not open %s for writing\n", compressedName.c_str() ); demoread.Close(); cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload ); cvarSystem->SetCVarInteger("com_compressDemos", savedCompression); return; } common->SetRefreshOnPrint( true ); common->Printf( "Compressing %s to %s...\n", fullDemoName.c_str(), compressedName.c_str() ); static const int bufferSize = 65535; char buffer[bufferSize]; int bytesRead; while ( 0 != (bytesRead = demoread.Read( buffer, bufferSize ) ) ) { demowrite.Write( buffer, bytesRead ); common->Printf( "." ); } demoread.Close(); demowrite.Close(); cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload ); cvarSystem->SetCVarInteger("com_compressDemos", savedCompression); common->Printf( "Done\n" ); common->SetRefreshOnPrint( false ); } /* =============== idSessionLocal::StartNewGame =============== */ void idSessionLocal::StartNewGame( const char *mapName, bool devmap ) { #ifdef ID_DEDICATED common->Printf( "Dedicated servers cannot start singleplayer games.\n" ); return; #else #if ID_ENFORCE_KEY // strict check. don't let a game start without a definitive answer if ( !CDKeysAreValid( true ) ) { bool prompt = true; if ( MaybeWaitOnCDKey() ) { // check again, maybe we just needed more time if ( CDKeysAreValid( true ) ) { // can continue directly prompt = false; } } if ( prompt ) { cmdSystem->BufferCommandText( CMD_EXEC_NOW, "promptKey force" ); cmdSystem->ExecuteCommandBuffer(); } } #endif if ( idAsyncNetwork::server.IsActive() ) { common->Printf("Server running, use si_map / serverMapRestart\n"); return; } if ( idAsyncNetwork::client.IsActive() ) { common->Printf("Client running, disconnect from server first\n"); return; } // clear the userInfo so the player starts out with the defaults mapSpawnData.userInfo[0].Clear(); mapSpawnData.persistentPlayerInfo[0].Clear(); mapSpawnData.userInfo[0] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO ); mapSpawnData.serverInfo.Clear(); mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" ); // set the devmap key so any play testing items will be given at // spawn time to set approximately the right weapons and ammo if(devmap) { mapSpawnData.serverInfo.Set( "devmap", "1" ); } mapSpawnData.syncedCVars.Clear(); mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC ); MoveToNewMap( mapName ); #endif } /* =============== idSessionLocal::GetAutoSaveName =============== */ idStr idSessionLocal::GetAutoSaveName( const char *mapName ) const { const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false ); const idDeclEntityDef *mapDef = static_cast( mapDecl ); if ( mapDef ) { mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) ); } // Fixme: Localization return va( "^3AutoSave:^0 %s", mapName ); } /* =============== idSessionLocal::MoveToNewMap Leaves the existing userinfo and serverinfo =============== */ void idSessionLocal::MoveToNewMap( const char *mapName ) { mapSpawnData.serverInfo.Set( "si_map", mapName ); ExecuteMapChange(); if ( !mapSpawnData.serverInfo.GetBool("devmap") ) { // Autosave at the beginning of the level SaveGame( GetAutoSaveName( mapName ), true ); } SetGUI( NULL, NULL ); } /* ============== SaveCmdDemoFromFile ============== */ void idSessionLocal::SaveCmdDemoToFile( idFile *file ) { mapSpawnData.serverInfo.WriteToFileHandle( file ); for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) { mapSpawnData.userInfo[i].WriteToFileHandle( file ); mapSpawnData.persistentPlayerInfo[i].WriteToFileHandle( file ); } file->Write( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) ); if ( numClients < 1 ) { numClients = 1; } file->Write( loggedUsercmds, numClients * logIndex * sizeof( loggedUsercmds[0] ) ); } /* ============== idSessionLocal::LoadCmdDemoFromFile ============== */ void idSessionLocal::LoadCmdDemoFromFile( idFile *file ) { mapSpawnData.serverInfo.ReadFromFileHandle( file ); for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) { mapSpawnData.userInfo[i].ReadFromFileHandle( file ); mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( file ); } file->Read( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) ); } /* ============== idSessionLocal::WriteCmdDemo Dumps the accumulated commands for the current level. This should still work after disconnecting from a level ============== */ void idSessionLocal::WriteCmdDemo( const char *demoName, bool save ) { if ( !demoName[0] ) { common->Printf( "idSessionLocal::WriteCmdDemo: no name specified\n" ); return; } idStr statsName; if (save) { statsName = demoName; statsName.StripFileExtension(); statsName.DefaultFileExtension(".stats"); } common->Printf( "writing save data to %s\n", demoName ); idFile *cmdDemoFile = fileSystem->OpenFileWrite( demoName ); if ( !cmdDemoFile ) { common->Printf( "Couldn't open for writing %s\n", demoName ); return; } if ( save ) { cmdDemoFile->Write( &logIndex, sizeof( logIndex ) ); } SaveCmdDemoToFile( cmdDemoFile ); if ( save ) { idFile *statsFile = fileSystem->OpenFileWrite( statsName ); if ( statsFile ) { statsFile->Write( &statIndex, sizeof( statIndex ) ); statsFile->Write( loggedStats, numClients * statIndex * sizeof( loggedStats[0] ) ); fileSystem->CloseFile( statsFile ); } } fileSystem->CloseFile( cmdDemoFile ); } /* =============== idSessionLocal::FinishCmdLoad =============== */ void idSessionLocal::FinishCmdLoad() { } /* =============== idSessionLocal::StartPlayingCmdDemo =============== */ void idSessionLocal::StartPlayingCmdDemo(const char *demoName) { // exit any current game Stop(); idStr fullDemoName = "demos/"; fullDemoName += demoName; fullDemoName.DefaultFileExtension( ".cdemo" ); cmdDemoFile = fileSystem->OpenFileRead(fullDemoName); if ( cmdDemoFile == NULL ) { common->Printf( "Couldn't open %s\n", fullDemoName.c_str() ); return; } guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true ); //cmdDemoFile->Read(&loadGameTime, sizeof(loadGameTime)); LoadCmdDemoFromFile(cmdDemoFile); // start the map ExecuteMapChange(); cmdDemoFile = fileSystem->OpenFileRead(fullDemoName); // have to do this twice as the execmapchange clears the cmddemofile LoadCmdDemoFromFile(cmdDemoFile); // run one frame to get the view angles correct RunGameTic(); } /* =============== idSessionLocal::TimeCmdDemo =============== */ void idSessionLocal::TimeCmdDemo( const char *demoName ) { StartPlayingCmdDemo( demoName ); ClearWipe(); UpdateScreen(); int startTime = Sys_Milliseconds(); int count = 0; int minuteStart, minuteEnd; float sec; // run all the frames in sequence minuteStart = startTime; while( cmdDemoFile ) { RunGameTic(); count++; if ( count / 3600 != ( count - 1 ) / 3600 ) { minuteEnd = Sys_Milliseconds(); sec = ( minuteEnd - minuteStart ) / 1000.0; minuteStart = minuteEnd; common->Printf( "minute %i took %3.1f seconds\n", count / 3600, sec ); UpdateScreen(); } } int endTime = Sys_Milliseconds(); sec = ( endTime - startTime ) / 1000.0; common->Printf( "%i seconds of game, replayed in %5.1f seconds\n", count / 60, sec ); } /* =============== idSessionLocal::UnloadMap Performs cleanup that needs to happen between maps, or when a game is exited. Exits with mapSpawned = false =============== */ void idSessionLocal::UnloadMap() { StopPlayingRenderDemo(); // end the current map in the game if ( game ) { game->MapShutdown(); } if ( cmdDemoFile ) { fileSystem->CloseFile( cmdDemoFile ); cmdDemoFile = NULL; } if ( writeDemo ) { StopRecordingRenderDemo(); } mapSpawned = false; } /* =============== idSessionLocal::LoadLoadingGui =============== */ void idSessionLocal::LoadLoadingGui( const char *mapName ) { // load / program a gui to stay up on the screen while loading idStr stripped = mapName; stripped.StripFileExtension(); stripped.StripPath(); char guiMap[ MAX_STRING_CHARS ]; strncpy( guiMap, va( "guis/map/%s.gui", stripped.c_str() ), MAX_STRING_CHARS ); // give the gamecode a chance to override game->GetMapLoadingGUI( guiMap ); if ( uiManager->CheckGui( guiMap ) ) { guiLoading = uiManager->FindGui( guiMap, true, false, true ); } else { guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true ); } guiLoading->SetStateFloat( "map_loading", 0.0f ); } /* =============== idSessionLocal::GetBytesNeededForMapLoad =============== */ int idSessionLocal::GetBytesNeededForMapLoad( const char *mapName ) { const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false ); const idDeclEntityDef *mapDef = static_cast( mapDecl ); if ( mapDef ) { return mapDef->dict.GetInt( va("size%d", Max( 0, com_machineSpec.GetInteger() ) ) ); } else { if ( com_machineSpec.GetInteger() < 2 ) { return 200 * 1024 * 1024; } else { return 400 * 1024 * 1024; } } } /* =============== idSessionLocal::SetBytesNeededForMapLoad =============== */ void idSessionLocal::SetBytesNeededForMapLoad( const char *mapName, int bytesNeeded ) { idDecl *mapDecl = const_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); idDeclEntityDef *mapDef = static_cast( mapDecl ); if ( com_updateLoadSize.GetBool() && mapDef ) { // we assume that if com_updateLoadSize is true then the file is writable mapDef->dict.SetInt( va("size%d", com_machineSpec.GetInteger()), bytesNeeded ); idStr declText = "\nmapDef "; declText += mapDef->GetName(); declText += " {\n"; for (int i=0; idict.GetNumKeyVals(); i++) { const idKeyValue *kv = mapDef->dict.GetKeyVal( i ); if ( kv && (kv->GetKey().Cmp("classname") != 0 ) ) { declText += "\t\"" + kv->GetKey() + "\"\t\t\"" + kv->GetValue() + "\"\n"; } } declText += "}"; mapDef->SetText( declText ); mapDef->ReplaceSourceFileText(); } } /* =============== idSessionLocal::ExecuteMapChange Performs the initialization of a game based on mapSpawnData, used for both single player and multiplayer, but not for renderDemos, which don't create a game at all. Exits with mapSpawned = true =============== */ void idSessionLocal::ExecuteMapChange( bool noFadeWipe ) { int i; bool reloadingSameMap; // close console and remove any prints from the notify lines console->Close(); if ( IsMultiplayer() ) { // make sure the mp GUI isn't up, or when players get back in the // map, mpGame's menu and the gui will be out of sync. SetGUI( NULL, NULL ); } // mute sound soundSystem->SetMute( true ); // clear all menu sounds menuSoundWorld->ClearAllSoundEmitters(); // unpause the game sound world // NOTE: we UnPause again later down. not sure this is needed if ( sw->IsPaused() ) { sw->UnPause(); } if ( !noFadeWipe ) { // capture the current screen and start a wipe StartWipe( "wipeMaterial", true ); // immediately complete the wipe to fade out the level transition // run the wipe to completion CompleteWipe(); } // extract the map name from serverinfo idStr mapString = mapSpawnData.serverInfo.GetString( "si_map" ); idStr fullMapName = "maps/"; fullMapName += mapString; fullMapName.StripFileExtension(); // shut down the existing game if it is running UnloadMap(); // don't do the deferred caching if we are reloading the same map if ( fullMapName == currentMapName ) { reloadingSameMap = true; } else { reloadingSameMap = false; currentMapName = fullMapName; } // note which media we are going to need to load if ( !reloadingSameMap ) { declManager->BeginLevelLoad(); renderSystem->BeginLevelLoad(); soundSystem->BeginLevelLoad(); } uiManager->BeginLevelLoad(); uiManager->Reload( true ); // set the loading gui that we will wipe to LoadLoadingGui( mapString ); // cause prints to force screen updates as a pacifier, // and draw the loading gui instead of game draws insideExecuteMapChange = true; // if this works out we will probably want all the sizes in a def file although this solution will // work for new maps etc. after the first load. we can also drop the sizes into the default.cfg fileSystem->ResetReadCount(); if ( !reloadingSameMap ) { bytesNeededForMapLoad = GetBytesNeededForMapLoad( mapString.c_str() ); } else { bytesNeededForMapLoad = 30 * 1024 * 1024; } ClearWipe(); // let the loading gui spin for 1 second to animate out ShowLoadingGui(); // note any warning prints that happen during the load process common->ClearWarnings( mapString ); // release the mouse cursor // before we do this potentially long operation Sys_GrabMouseCursor( false ); // if net play, we get the number of clients during mapSpawnInfo processing if ( !idAsyncNetwork::IsActive() ) { numClients = 1; } int start = Sys_Milliseconds(); common->Printf( "----- Map Initialization -----\n" ); common->Printf( "Map: %s\n", mapString.c_str() ); // let the renderSystem load all the geometry if ( !rw->InitFromMap( fullMapName ) ) { common->Error( "couldn't load %s", fullMapName.c_str() ); } // for the synchronous networking we needed to roll the angles over from // level to level, but now we can just clear everything usercmdGen->InitForNewMap(); memset( &mapSpawnData.mapSpawnUsercmd, 0, sizeof( mapSpawnData.mapSpawnUsercmd ) ); // set the user info for ( i = 0; i < numClients; i++ ) { game->SetUserInfo( i, mapSpawnData.userInfo[i], idAsyncNetwork::client.IsActive(), false ); game->SetPersistentPlayerInfo( i, mapSpawnData.persistentPlayerInfo[i] ); } // load and spawn all other entities ( from a savegame possibly ) if ( loadingSaveGame && savegameFile ) { if ( game->InitFromSaveGame( fullMapName + ".map", rw, sw, savegameFile ) == false ) { // If the loadgame failed, restart the map with the player persistent data loadingSaveGame = false; fileSystem->CloseFile( savegameFile ); savegameFile = NULL; game->SetServerInfo( mapSpawnData.serverInfo ); game->InitFromNewMap( fullMapName + ".map", rw, sw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() ); } } else { game->SetServerInfo( mapSpawnData.serverInfo ); game->InitFromNewMap( fullMapName + ".map", rw, sw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() ); } if ( !idAsyncNetwork::IsActive() && !loadingSaveGame ) { // spawn players for ( i = 0; i < numClients; i++ ) { game->SpawnPlayer( i ); } } // actually purge/load the media if ( !reloadingSameMap ) { renderSystem->EndLevelLoad(); soundSystem->EndLevelLoad( mapString.c_str() ); declManager->EndLevelLoad(); SetBytesNeededForMapLoad( mapString.c_str(), fileSystem->GetReadCount() ); } uiManager->EndLevelLoad(); if ( !idAsyncNetwork::IsActive() && !loadingSaveGame ) { // run a few frames to allow everything to settle for ( i = 0; i < 10; i++ ) { game->RunFrame( mapSpawnData.mapSpawnUsercmd ); } } int msec = Sys_Milliseconds() - start; common->Printf( "%6d msec to load %s\n", msec, mapString.c_str() ); // let the renderSystem generate interactions now that everything is spawned rw->GenerateAllInteractions(); common->PrintWarnings(); if ( guiLoading && bytesNeededForMapLoad ) { float pct = guiLoading->State().GetFloat( "map_loading" ); if ( pct < 0.0f ) { pct = 0.0f; } while ( pct < 1.0f ) { guiLoading->SetStateFloat( "map_loading", pct ); guiLoading->StateChanged( com_frameTime ); Sys_GenerateEvents(); UpdateScreen(); pct += 0.05f; } } // capture the current screen and start a wipe StartWipe( "wipe2Material" ); usercmdGen->Clear(); // start saving commands for possible writeCmdDemo usage logIndex = 0; statIndex = 0; lastSaveIndex = 0; // don't bother spinning over all the tics we spent loading lastGameTic = latchedTicNumber = com_ticNumber; // remove any prints from the notify lines console->ClearNotifyLines(); // stop drawing the laoding screen insideExecuteMapChange = false; Sys_SetPhysicalWorkMemory( -1, -1 ); // set the game sound world for playback soundSystem->SetPlayingSoundWorld( sw ); // when loading a save game the sound is paused if ( sw->IsPaused() ) { // unpause the game sound world sw->UnPause(); } // restart entity sound playback soundSystem->SetMute( false ); // we are valid for game draws now mapSpawned = true; Sys_ClearEvents(); } /* =============== LoadGame_f =============== */ void LoadGame_f( const idCmdArgs &args ) { console->Close(); if ( args.Argc() < 2 || idStr::Icmp(args.Argv(1), "quick" ) == 0 ) { idStr saveName = common->GetLanguageDict()->GetString( "#str_07178" ); sessLocal.LoadGame( saveName ); } else { sessLocal.LoadGame( args.Argv(1) ); } } /* =============== SaveGame_f =============== */ void SaveGame_f( const idCmdArgs &args ) { if ( args.Argc() < 2 || idStr::Icmp( args.Argv(1), "quick" ) == 0 ) { idStr saveName = common->GetLanguageDict()->GetString( "#str_07178" ); if ( sessLocal.SaveGame( saveName ) ) { common->Printf( "%s\n", saveName.c_str() ); } } else { if ( sessLocal.SaveGame( args.Argv(1) ) ) { common->Printf( "Saved %s\n", args.Argv(1) ); } } } /* =============== TakeViewNotes_f =============== */ void TakeViewNotes_f( const idCmdArgs &args ) { const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : ""; sessLocal.TakeNotes( p ); } /* =============== TakeViewNotes2_f =============== */ void TakeViewNotes2_f( const idCmdArgs &args ) { const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : ""; sessLocal.TakeNotes( p, true ); } /* =============== idSessionLocal::TakeNotes =============== */ void idSessionLocal::TakeNotes( const char *p, bool extended ) { if ( !mapSpawned ) { common->Printf( "No map loaded!\n" ); return; } if ( extended ) { guiTakeNotes = uiManager->FindGui( "guis/takeNotes2.gui", true, false, true ); #if 0 const char *people[] = { "Nobody", "Adam", "Brandon", "David", "PHook", "Jay", "Jake", "PatJ", "Brett", "Ted", "Darin", "Brian", "Sean" }; #else const char *people[] = { "Tim", "Kenneth", "Robert", "Matt", "Mal", "Jerry", "Steve", "Pat", "Xian", "Ed", "Fred", "James", "Eric", "Andy", "Seneca", "Patrick", "Kevin", "MrElusive", "Jim", "Brian", "John", "Adrian", "Nobody" }; #endif const int numPeople = sizeof( people ) / sizeof( people[0] ); idListGUI * guiList_people = uiManager->AllocListGUI(); guiList_people->Config( guiTakeNotes, "person" ); for ( int i = 0; i < numPeople; i++ ) { guiList_people->Push( people[i] ); } uiManager->FreeListGUI( guiList_people ); } else { guiTakeNotes = uiManager->FindGui( "guis/takeNotes.gui", true, false, true ); } SetGUI( guiTakeNotes, NULL ); guiActive->SetStateString( "note", "" ); guiActive->SetStateString( "notefile", p ); guiActive->SetStateBool( "extended", extended ); guiActive->Activate( true, com_frameTime ); } /* =============== Session_Hitch_f =============== */ void Session_Hitch_f( const idCmdArgs &args ) { idSoundWorld *sw = soundSystem->GetPlayingSoundWorld(); if ( sw ) { soundSystem->SetMute(true); sw->Pause(); Sys_EnterCriticalSection(); } if ( args.Argc() == 2 ) { Sys_Sleep( atoi(args.Argv(1)) ); } else { Sys_Sleep( 100 ); } if ( sw ) { Sys_LeaveCriticalSection(); sw->UnPause(); soundSystem->SetMute(false); } } /* =============== idSessionLocal::ScrubSaveGameFileName Turns a bad file name into a good one or your money back =============== */ void idSessionLocal::ScrubSaveGameFileName( idStr &saveFileName ) const { int i; idStr inFileName; inFileName = saveFileName; inFileName.RemoveColors(); inFileName.StripFileExtension(); saveFileName.Clear(); int len = inFileName.Length(); for ( i = 0; i < len; i++ ) { if ( strchr( "',.~!@#$%^&*()[]{}<>\\|/=?+;:-\'\"", inFileName[i] ) ) { // random junk saveFileName += '_'; } else if ( (const unsigned char)inFileName[i] >= 128 ) { // high ascii chars saveFileName += '_'; } else if ( inFileName[i] == ' ' ) { saveFileName += '_'; } else { saveFileName += inFileName[i]; } } } /* =============== idSessionLocal::SaveGame =============== */ bool idSessionLocal::SaveGame( const char *saveName, bool autosave ) { #ifdef ID_DEDICATED common->Printf( "Dedicated servers cannot save games.\n" ); return false; #else int i; idStr gameFile, previewFile, descriptionFile, mapName; if ( !mapSpawned ) { common->Printf( "Not playing a game.\n" ); return false; } if ( IsMultiplayer() ) { common->Printf( "Can't save during net play.\n" ); return false; } if ( game->GetPersistentPlayerInfo( 0 ).GetInt( "health" ) <= 0 ) { MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04311" ), common->GetLanguageDict()->GetString ( "#str_04312" ), true ); common->Printf( "You must be alive to save the game\n" ); return false; } if ( Sys_GetDriveFreeSpace( cvarSystem->GetCVarString( "fs_savepath" ) ) < 25 ) { MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04313" ), common->GetLanguageDict()->GetString ( "#str_04314" ), true ); common->Printf( "Not enough drive space to save the game\n" ); return false; } idSoundWorld *pauseWorld = soundSystem->GetPlayingSoundWorld(); if ( pauseWorld ) { pauseWorld->Pause(); soundSystem->SetPlayingSoundWorld( NULL ); } // setup up filenames and paths gameFile = saveName; ScrubSaveGameFileName( gameFile ); gameFile = "savegames/" + gameFile; gameFile.SetFileExtension( ".save" ); previewFile = gameFile; previewFile.SetFileExtension( ".tga" ); descriptionFile = gameFile; descriptionFile.SetFileExtension( ".txt" ); // Open savegame file idFile *fileOut = fileSystem->OpenFileWrite( gameFile ); if ( fileOut == NULL ) { common->Warning( "Failed to open save file '%s'\n", gameFile.c_str() ); if ( pauseWorld ) { soundSystem->SetPlayingSoundWorld( pauseWorld ); pauseWorld->UnPause(); } return false; } // Write SaveGame Header: // Game Name / Version / Map Name / Persistant Player Info // game const char *gamename = GAME_NAME; fileOut->WriteString( gamename ); // version fileOut->WriteInt( SAVEGAME_VERSION ); // map mapName = mapSpawnData.serverInfo.GetString( "si_map" ); fileOut->WriteString( mapName ); // persistent player info for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { mapSpawnData.persistentPlayerInfo[i] = game->GetPersistentPlayerInfo( i ); mapSpawnData.persistentPlayerInfo[i].WriteToFileHandle( fileOut ); } // let the game save its state game->SaveGame( fileOut ); // close the sava game file fileSystem->CloseFile( fileOut ); // Write screenshot if ( !autosave ) { renderSystem->CropRenderSize( 320, 240, false ); game->Draw( 0 ); renderSystem->CaptureRenderToFile( previewFile, true ); renderSystem->UnCrop(); } // Write description, which is just a text file with // the unclean save name on line 1, map name on line 2, screenshot on line 3 idFile *fileDesc = fileSystem->OpenFileWrite( descriptionFile ); if ( fileDesc == NULL ) { common->Warning( "Failed to open description file '%s'\n", descriptionFile.c_str() ); if ( pauseWorld ) { soundSystem->SetPlayingSoundWorld( pauseWorld ); pauseWorld->UnPause(); } return false; } idStr description = saveName; description.Replace( "\\", "\\\\" ); description.Replace( "\"", "\\\"" ); const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); if ( mapDef ) { mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) ); } fileDesc->Printf( "\"%s\"\n", description.c_str() ); fileDesc->Printf( "\"%s\"\n", mapName.c_str()); if ( autosave ) { idStr sshot = mapSpawnData.serverInfo.GetString( "si_map" ); sshot.StripPath(); sshot.StripFileExtension(); fileDesc->Printf( "\"guis/assets/autosave/%s\"\n", sshot.c_str() ); } else { fileDesc->Printf( "\"\"\n" ); } fileSystem->CloseFile( fileDesc ); if ( pauseWorld ) { soundSystem->SetPlayingSoundWorld( pauseWorld ); pauseWorld->UnPause(); } syncNextGameFrame = true; return true; #endif } /* =============== idSessionLocal::LoadGame =============== */ bool idSessionLocal::LoadGame( const char *saveName ) { #ifdef ID_DEDICATED common->Printf( "Dedicated servers cannot load games.\n" ); return false; #else int i; idStr in, loadFile, saveMap, gamename; if ( IsMultiplayer() ) { common->Printf( "Can't load during net play.\n" ); return false; } //Hide the dialog box if it is up. StopBox(); loadFile = saveName; ScrubSaveGameFileName( loadFile ); loadFile.SetFileExtension( ".save" ); in = "savegames/"; in += loadFile; // Open savegame file // only allow loads from the game directory because we don't want a base game to load idStr game = cvarSystem->GetCVarString( "fs_game" ); savegameFile = fileSystem->OpenFileRead( in, true, game.Length() ? game : NULL ); if ( savegameFile == NULL ) { common->Warning( "Couldn't open savegame file %s", in.c_str() ); return false; } loadingSaveGame = true; // Read in save game header // Game Name / Version / Map Name / Persistant Player Info // game savegameFile->ReadString( gamename ); // if this isn't a savegame for the correct game, abort loadgame if ( ! (gamename == GAME_NAME || gamename == "DOOM 3") ) { common->Warning( "Attempted to load an invalid savegame: %s", in.c_str() ); loadingSaveGame = false; fileSystem->CloseFile( savegameFile ); savegameFile = NULL; return false; } // version savegameFile->ReadInt( savegameVersion ); // map savegameFile->ReadString( saveMap ); // persistent player info for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( savegameFile ); } // check the version, if it doesn't match, cancel the loadgame, // but still load the map with the persistant playerInfo from the header // so that the player doesn't lose too much progress. if ( savegameVersion != SAVEGAME_VERSION && !( savegameVersion == 16 && SAVEGAME_VERSION == 17 ) ) { // handle savegame v16 in v17 common->Warning( "Savegame Version mismatch: aborting loadgame and starting level with persistent data" ); loadingSaveGame = false; fileSystem->CloseFile( savegameFile ); savegameFile = NULL; } common->DPrintf( "loading a v%d savegame\n", savegameVersion ); if ( saveMap.Length() > 0 ) { // Start loading map mapSpawnData.serverInfo.Clear(); mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" ); mapSpawnData.serverInfo.Set( "si_map", saveMap ); mapSpawnData.syncedCVars.Clear(); mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC ); mapSpawnData.mapSpawnUsercmd[0] = usercmdGen->TicCmd( latchedTicNumber ); // make sure no buttons are pressed mapSpawnData.mapSpawnUsercmd[0].buttons = 0; ExecuteMapChange(); SetGUI( NULL, NULL ); } if ( loadingSaveGame ) { fileSystem->CloseFile( savegameFile ); loadingSaveGame = false; savegameFile = NULL; } return true; #endif } /* =============== idSessionLocal::ProcessEvent =============== */ bool idSessionLocal::ProcessEvent( const sysEvent_t *event ) { // hitting escape anywhere brings up the menu // DG: but shift-escape should bring up console instead so ignore that if ( !guiActive && event->evType == SE_KEY && event->evValue2 == 1 && event->evValue == K_ESCAPE && !idKeyInput::IsDown( K_SHIFT ) ) { console->Close(); if ( game ) { idUserInterface *gui = NULL; escReply_t op; op = game->HandleESC( &gui ); if ( op == ESC_IGNORE ) { return true; } else if ( op == ESC_GUI ) { SetGUI( gui, NULL ); return true; } } StartMenu(); return true; } // let the pull-down console take it if desired if ( console->ProcessEvent( event, false ) ) { return true; } // if we are testing a GUI, send all events to it if ( guiTest ) { // hitting escape exits the testgui if ( event->evType == SE_KEY && event->evValue2 == 1 && event->evValue == K_ESCAPE ) { guiTest = NULL; return true; } static const char *cmd; cmd = guiTest->HandleEvent( event, com_frameTime ); if ( cmd && cmd[0] ) { common->Printf( "testGui event returned: '%s'\n", cmd ); } return true; } // menus / etc if ( guiActive ) { MenuEvent( event ); return true; } // if we aren't in a game, force the console to take it if ( !mapSpawned ) { console->ProcessEvent( event, true ); return true; } // in game, exec bindings for all key downs if ( event->evType == SE_KEY && event->evValue2 == 1 ) { idKeyInput::ExecKeyBinding( event->evValue ); return true; } return false; } /* =============== idSessionLocal::DrawWipeModel Draw the fade material over everything that has been drawn =============== */ void idSessionLocal::DrawWipeModel() { int latchedTic = com_ticNumber; if ( wipeStartTic >= wipeStopTic ) { return; } if ( !wipeHold && latchedTic >= wipeStopTic ) { return; } float fade = ( float )( latchedTic - wipeStartTic ) / ( wipeStopTic - wipeStartTic ); renderSystem->SetColor4( 1, 1, 1, fade ); renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, wipeMaterial ); } /* =============== idSessionLocal::AdvanceRenderDemo =============== */ void idSessionLocal::AdvanceRenderDemo( bool singleFrameOnly ) { if ( lastDemoTic == -1 ) { lastDemoTic = latchedTicNumber - 1; } int skipFrames = 0; if ( !aviCaptureMode && !timeDemo && !singleFrameOnly ) { skipFrames = ( (latchedTicNumber - lastDemoTic) / USERCMD_PER_DEMO_FRAME ) - 1; // never skip too many frames, just let it go into slightly slow motion if ( skipFrames > 4 ) { skipFrames = 4; } lastDemoTic = latchedTicNumber - latchedTicNumber % USERCMD_PER_DEMO_FRAME; } else { // always advance a single frame with avidemo and timedemo lastDemoTic = latchedTicNumber; } while( skipFrames > -1 ) { int ds = DS_FINISHED; readDemo->ReadInt( ds ); if ( ds == DS_FINISHED ) { if ( numDemoFrames != 1 ) { // if the demo has a single frame (a demoShot), continuously replay // the renderView that has already been read Stop(); StartMenu(); } break; } if ( ds == DS_RENDER ) { if ( rw->ProcessDemoCommand( readDemo, ¤tDemoRenderView, &demoTimeOffset ) ) { // a view is ready to render skipFrames--; numDemoFrames++; } continue; } if ( ds == DS_SOUND ) { sw->ProcessDemoCommand( readDemo ); continue; } // appears in v1.2, with savegame format 17 if ( ds == DS_VERSION ) { readDemo->ReadInt( renderdemoVersion ); common->Printf( "reading a v%d render demo\n", renderdemoVersion ); // set the savegameVersion to current for render demo paths that share the savegame paths savegameVersion = SAVEGAME_VERSION; continue; } common->Error( "Bad render demo token" ); } if ( com_showDemo.GetBool() ) { common->Printf( "frame:%i DemoTic:%i latched:%i skip:%i\n", numDemoFrames, lastDemoTic, latchedTicNumber, skipFrames ); } } /* =============== idSessionLocal::DrawCmdGraph Graphs yaw angle for testing smoothness =============== */ static const int ANGLE_GRAPH_HEIGHT = 128; static const int ANGLE_GRAPH_STRETCH = 3; void idSessionLocal::DrawCmdGraph() { if ( !com_showAngles.GetBool() ) { return; } renderSystem->SetColor4( 0.1f, 0.1f, 0.1f, 1.0f ); renderSystem->DrawStretchPic( 0, 480-ANGLE_GRAPH_HEIGHT, MAX_BUFFERED_USERCMD*ANGLE_GRAPH_STRETCH, ANGLE_GRAPH_HEIGHT, 0, 0, 1, 1, whiteMaterial ); renderSystem->SetColor4( 0.9f, 0.9f, 0.9f, 1.0f ); for ( int i = 0 ; i < MAX_BUFFERED_USERCMD-4 ; i++ ) { usercmd_t cmd = usercmdGen->TicCmd( latchedTicNumber - (MAX_BUFFERED_USERCMD-4) + i ); int h = cmd.angles[1]; h >>= 8; h &= (ANGLE_GRAPH_HEIGHT-1); renderSystem->DrawStretchPic( i* ANGLE_GRAPH_STRETCH, 480-h, 1, h, 0, 0, 1, 1, whiteMaterial ); } } /* =============== idSessionLocal::PacifierUpdate =============== */ void idSessionLocal::PacifierUpdate() { if ( !insideExecuteMapChange ) { return; } // never do pacifier screen updates while inside the // drawing code, or we can have various recursive problems if ( insideUpdateScreen ) { return; } int time = eventLoop->Milliseconds(); if ( time - lastPacifierTime < 100 ) { return; } lastPacifierTime = time; if ( guiLoading && bytesNeededForMapLoad ) { float n = fileSystem->GetReadCount(); float pct = ( n / bytesNeededForMapLoad ); // pct = idMath::ClampFloat( 0.0f, 100.0f, pct ); guiLoading->SetStateFloat( "map_loading", pct ); guiLoading->StateChanged( com_frameTime ); } Sys_GenerateEvents(); UpdateScreen(); idAsyncNetwork::client.PacifierUpdate(); idAsyncNetwork::server.PacifierUpdate(); } /* =============== idSessionLocal::Draw =============== */ void idSessionLocal::Draw() { bool fullConsole = false; if ( insideExecuteMapChange ) { if ( guiLoading ) { guiLoading->Redraw( com_frameTime ); } if ( guiActive == guiMsg ) { guiMsg->Redraw( com_frameTime ); } } else if ( guiTest ) { // if testing a gui, clear the screen and draw it // clear the background, in case the tested gui is transparent // NOTE that you can't use this for aviGame recording, it will tick at real com_frameTime between screenshots.. renderSystem->SetColor( colorBlack ); renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); guiTest->Redraw( com_frameTime ); } else if ( guiActive && !guiActive->State().GetBool( "gameDraw" ) ) { // draw the frozen gui in the background if ( guiActive == guiMsg && guiMsgRestore ) { guiMsgRestore->Redraw( com_frameTime ); } // draw the menus full screen if ( guiActive == guiTakeNotes && !com_skipGameDraw.GetBool() ) { game->Draw( GetLocalClientNum() ); } guiActive->Redraw( com_frameTime ); } else if ( readDemo ) { rw->RenderScene( ¤tDemoRenderView ); renderSystem->DrawDemoPics(); } else if ( mapSpawned ) { bool gameDraw = false; // normal drawing for both single and multi player if ( !com_skipGameDraw.GetBool() && GetLocalClientNum() >= 0 ) { // draw the game view int start = Sys_Milliseconds(); gameDraw = game->Draw( GetLocalClientNum() ); int end = Sys_Milliseconds(); time_gameDraw += ( end - start ); // note time used for com_speeds } if ( !gameDraw ) { renderSystem->SetColor( colorBlack ); renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); } // save off the 2D drawing from the game if ( writeDemo ) { renderSystem->WriteDemoPics(); } } else { #if ID_CONSOLE_LOCK if ( com_allowConsole.GetBool() ) { console->Draw( true ); } else { emptyDrawCount++; if ( emptyDrawCount > 5 ) { // it's best if you can avoid triggering the watchgod by doing the right thing somewhere else assert( false ); common->Warning( "idSession: triggering mainmenu watchdog" ); emptyDrawCount = 0; StartMenu(); } renderSystem->SetColor4( 0, 0, 0, 1 ); renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); } #else // draw the console full screen - this should only ever happen in developer builds console->Draw( true ); #endif fullConsole = true; } #if ID_CONSOLE_LOCK if ( !fullConsole && emptyDrawCount ) { common->DPrintf( "idSession: %d empty frame draws\n", emptyDrawCount ); emptyDrawCount = 0; } fullConsole = false; #endif // draw the wipe material on top of this if it hasn't completed yet DrawWipeModel(); // draw debug graphs DrawCmdGraph(); // draw the half console / notify console on top of everything if ( !fullConsole ) { console->Draw( false ); } } /* =============== idSessionLocal::UpdateScreen =============== */ void idSessionLocal::UpdateScreen( bool outOfSequence ) { #ifdef _WIN32 if ( com_editors ) { if ( !Sys_IsWindowVisible() ) { return; } } #endif if ( insideUpdateScreen ) { return; // common->FatalError( "idSessionLocal::UpdateScreen: recursively called" ); } insideUpdateScreen = true; // if this is a long-operation update and we are in windowed mode, // release the mouse capture back to the desktop if ( outOfSequence ) { Sys_GrabMouseCursor( false ); } renderSystem->BeginFrame( renderSystem->GetScreenWidth(), renderSystem->GetScreenHeight() ); // draw everything Draw(); if ( com_speeds.GetBool() ) { renderSystem->EndFrame( &time_frontend, &time_backend ); } else { renderSystem->EndFrame( NULL, NULL ); } insideUpdateScreen = false; } /* =============== idSessionLocal::Frame =============== */ void idSessionLocal::Frame() { if ( com_asyncSound.GetInteger() == 0 ) { soundSystem->AsyncUpdate( Sys_Milliseconds() ); } // Editors that completely take over the game if ( com_editorActive && ( com_editors & ( EDITOR_RADIANT | EDITOR_GUI ) ) ) { return; } // if the console is down, we don't need to hold // the mouse cursor if ( console->Active() || com_editorActive ) { Sys_GrabMouseCursor( false ); } else { Sys_GrabMouseCursor( true ); } // save the screenshot and audio from the last draw if needed if ( aviCaptureMode ) { idStr name; name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviTicStart ); float ratio = 30.0f / ( 1000.0f / USERCMD_MSEC / com_aviDemoTics.GetInteger() ); aviDemoFrameCount += ratio; if ( aviTicStart + 1 != ( int )aviDemoFrameCount ) { // skipped frames so write them out int c = aviDemoFrameCount - aviTicStart; while ( c-- ) { renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), ++aviTicStart ); } } aviTicStart = aviDemoFrameCount; // remove any printed lines at the top before taking the screenshot console->ClearNotifyLines(); // this will call Draw, possibly multiple times if com_aviDemoSamples is > 1 renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); } // at startup, we may be backwards if ( latchedTicNumber > com_ticNumber ) { latchedTicNumber = com_ticNumber; } // se how many tics we should have before continuing int minTic = latchedTicNumber + 1; if ( com_minTics.GetInteger() > 1 ) { minTic = lastGameTic + com_minTics.GetInteger(); } if ( readDemo ) { if ( !timeDemo && numDemoFrames != 1 ) { minTic = lastDemoTic + USERCMD_PER_DEMO_FRAME; } else { // timedemos and demoshots will run as fast as they can, other demos // will not run more than 30 hz minTic = latchedTicNumber; } } else if ( writeDemo ) { minTic = lastGameTic + USERCMD_PER_DEMO_FRAME; // demos are recorded at 30 hz } // fixedTic lets us run a forced number of usercmd each frame without timing if ( com_fixedTic.GetInteger() ) { minTic = latchedTicNumber; } while( 1 ) { latchedTicNumber = com_ticNumber; if ( latchedTicNumber >= minTic ) { break; } Sys_WaitForEvent( TRIGGER_EVENT_ONE ); } if ( authEmitTimeout ) { // waiting for a game auth if ( Sys_Milliseconds() > authEmitTimeout ) { // expired with no reply // means that if a firewall is blocking the master, we will let through common->DPrintf( "no reply from auth\n" ); if ( authWaitBox ) { // close the wait box StopBox(); authWaitBox = false; } if ( cdkey_state == CDKEY_CHECKING ) { cdkey_state = CDKEY_OK; } if ( xpkey_state == CDKEY_CHECKING ) { xpkey_state = CDKEY_OK; } // maintain this empty as it's set by auth denials authMsg.Empty(); authEmitTimeout = 0; SetCDKeyGuiVars(); } } // send frame and mouse events to active guis GuiFrameEvents(); // advance demos if ( readDemo ) { AdvanceRenderDemo( false ); return; } //------------ single player game tics -------------- if ( !mapSpawned || guiActive ) { if ( !com_asyncInput.GetBool() ) { // early exit, won't do RunGameTic .. but still need to update mouse position for GUIs usercmdGen->GetDirectUsercmd(); } } if ( !mapSpawned ) { return; } if ( guiActive ) { lastGameTic = latchedTicNumber; return; } // in message box / GUIFrame, idSessionLocal::Frame is used for GUI interactivity // but we early exit to avoid running game frames if ( idAsyncNetwork::IsActive() ) { return; } // check for user info changes if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) { mapSpawnData.userInfo[0] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO ); game->SetUserInfo( 0, mapSpawnData.userInfo[0], false, false ); cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); } // see how many usercmds we are going to run int numCmdsToRun = latchedTicNumber - lastGameTic; // don't let a long onDemand sound load unsync everything if ( timeHitch ) { int skip = timeHitch / USERCMD_MSEC; lastGameTic += skip; numCmdsToRun -= skip; timeHitch = 0; } // don't get too far behind after a hitch if ( numCmdsToRun > 10 ) { lastGameTic = latchedTicNumber - 10; } // never use more than USERCMD_PER_DEMO_FRAME, // which makes it go into slow motion when recording if ( writeDemo ) { int fixedTic = USERCMD_PER_DEMO_FRAME; // we should have waited long enough if ( numCmdsToRun < fixedTic ) { common->Error( "idSessionLocal::Frame: numCmdsToRun < fixedTic" ); } // we may need to dump older commands lastGameTic = latchedTicNumber - fixedTic; } else if ( com_fixedTic.GetInteger() > 0 ) { // this may cause commands run in a previous frame to // be run again if we are going at above the real time rate lastGameTic = latchedTicNumber - com_fixedTic.GetInteger(); } else if ( aviCaptureMode ) { lastGameTic = latchedTicNumber - com_aviDemoTics.GetInteger(); } // force only one game frame update this frame. the game code requests this after skipping cinematics // so we come back immediately after the cinematic is done instead of a few frames later which can // cause sounds played right after the cinematic to not play. if ( syncNextGameFrame ) { lastGameTic = latchedTicNumber - 1; syncNextGameFrame = false; } // create client commands, which will be sent directly // to the game if ( com_showTics.GetBool() ) { common->Printf( "%i ", latchedTicNumber - lastGameTic ); } int gameTicsToRun = latchedTicNumber - lastGameTic; int i; for ( i = 0 ; i < gameTicsToRun ; i++ ) { RunGameTic(); if ( !mapSpawned ) { // exited game play break; } if ( syncNextGameFrame ) { // long game frame, so break out and continue executing as if there was no hitch break; } } } /* ================ idSessionLocal::RunGameTic ================ */ void idSessionLocal::RunGameTic() { logCmd_t logCmd; usercmd_t cmd; // if we are doing a command demo, read or write from the file if ( cmdDemoFile ) { if ( !cmdDemoFile->Read( &logCmd, sizeof( logCmd ) ) ) { common->Printf( "Command demo completed at logIndex %i\n", logIndex ); fileSystem->CloseFile( cmdDemoFile ); cmdDemoFile = NULL; if ( aviCaptureMode ) { EndAVICapture(); Shutdown(); } // we fall out of the demo to normal commands // the impulse and chat character toggles may not be correct, and the view // angle will definitely be wrong } else { cmd = logCmd.cmd; cmd.ByteSwap(); logCmd.consistencyHash = LittleInt( logCmd.consistencyHash ); } } // if we didn't get one from the file, get it locally if ( !cmdDemoFile ) { // get a locally created command if ( com_asyncInput.GetBool() ) { cmd = usercmdGen->TicCmd( lastGameTic ); } else { cmd = usercmdGen->GetDirectUsercmd(); } lastGameTic++; } // run the game logic every player move int start = Sys_Milliseconds(); gameReturn_t ret = game->RunFrame( &cmd ); int end = Sys_Milliseconds(); time_gameFrame += end - start; // note time used for com_speeds // check for constency failure from a recorded command if ( cmdDemoFile ) { if ( ret.consistencyHash != logCmd.consistencyHash ) { common->Printf( "Consistency failure on logIndex %i\n", logIndex ); Stop(); return; } } // save the cmd for cmdDemo archiving if ( logIndex < MAX_LOGGED_USERCMDS ) { loggedUsercmds[logIndex].cmd = cmd; // save the consistencyHash for demo playback verification loggedUsercmds[logIndex].consistencyHash = ret.consistencyHash; if (logIndex % 30 == 0 && statIndex < MAX_LOGGED_STATS) { loggedStats[statIndex].health = ret.health; loggedStats[statIndex].heartRate = ret.heartRate; loggedStats[statIndex].stamina = ret.stamina; loggedStats[statIndex].combat = ret.combat; statIndex++; } logIndex++; } syncNextGameFrame = ret.syncNextGameFrame; if ( ret.sessionCommand[0] ) { idCmdArgs args; args.TokenizeString( ret.sessionCommand, false ); if ( !idStr::Icmp( args.Argv(0), "map" ) ) { // get current player states for ( int i = 0 ; i < numClients ; i++ ) { mapSpawnData.persistentPlayerInfo[i] = game->GetPersistentPlayerInfo( i ); } // clear the devmap key on serverinfo, so player spawns // won't get the map testing items mapSpawnData.serverInfo.Delete( "devmap" ); // go to the next map MoveToNewMap( args.Argv(1) ); } else if ( !idStr::Icmp( args.Argv(0), "devmap" ) ) { mapSpawnData.serverInfo.Set( "devmap", "1" ); MoveToNewMap( args.Argv(1) ); } else if ( !idStr::Icmp( args.Argv(0), "died" ) ) { // restart on the same map UnloadMap(); SetGUI(guiRestartMenu, NULL); } else if ( !idStr::Icmp( args.Argv(0), "disconnect" ) ) { cmdSystem->BufferCommandText( CMD_EXEC_INSERT, "stoprecording ; disconnect" ); } } } /* =============== idSessionLocal::Init Called in an orderly fashion at system startup, so commands, cvars, files, etc are all available =============== */ void idSessionLocal::Init() { common->Printf( "----- Initializing Session -----\n" ); cmdSystem->AddCommand( "writePrecache", Sess_WritePrecache_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "writes precache commands" ); #ifndef ID_DEDICATED cmdSystem->AddCommand( "map", Session_Map_f, CMD_FL_SYSTEM, "loads a map", idCmdSystem::ArgCompletion_MapName ); cmdSystem->AddCommand( "devmap", Session_DevMap_f, CMD_FL_SYSTEM, "loads a map in developer mode", idCmdSystem::ArgCompletion_MapName ); cmdSystem->AddCommand( "testmap", Session_TestMap_f, CMD_FL_SYSTEM, "tests a map", idCmdSystem::ArgCompletion_MapName ); cmdSystem->AddCommand( "writeCmdDemo", Session_WriteCmdDemo_f, CMD_FL_SYSTEM, "writes a command demo" ); cmdSystem->AddCommand( "playCmdDemo", Session_PlayCmdDemo_f, CMD_FL_SYSTEM, "plays back a command demo" ); cmdSystem->AddCommand( "timeCmdDemo", Session_TimeCmdDemo_f, CMD_FL_SYSTEM, "times a command demo" ); cmdSystem->AddCommand( "exitCmdDemo", Session_ExitCmdDemo_f, CMD_FL_SYSTEM, "exits a command demo" ); cmdSystem->AddCommand( "aviCmdDemo", Session_AVICmdDemo_f, CMD_FL_SYSTEM, "writes AVIs for a command demo" ); cmdSystem->AddCommand( "aviGame", Session_AVIGame_f, CMD_FL_SYSTEM, "writes AVIs for the current game" ); cmdSystem->AddCommand( "recordDemo", Session_RecordDemo_f, CMD_FL_SYSTEM, "records a demo" ); cmdSystem->AddCommand( "stopRecording", Session_StopRecordingDemo_f, CMD_FL_SYSTEM, "stops demo recording" ); cmdSystem->AddCommand( "playDemo", Session_PlayDemo_f, CMD_FL_SYSTEM, "plays back a demo", idCmdSystem::ArgCompletion_DemoName ); cmdSystem->AddCommand( "timeDemo", Session_TimeDemo_f, CMD_FL_SYSTEM, "times a demo", idCmdSystem::ArgCompletion_DemoName ); cmdSystem->AddCommand( "timeDemoQuit", Session_TimeDemoQuit_f, CMD_FL_SYSTEM, "times a demo and quits", idCmdSystem::ArgCompletion_DemoName ); cmdSystem->AddCommand( "aviDemo", Session_AVIDemo_f, CMD_FL_SYSTEM, "writes AVIs for a demo", idCmdSystem::ArgCompletion_DemoName ); cmdSystem->AddCommand( "compressDemo", Session_CompressDemo_f, CMD_FL_SYSTEM, "compresses a demo file", idCmdSystem::ArgCompletion_DemoName ); #endif cmdSystem->AddCommand( "disconnect", Session_Disconnect_f, CMD_FL_SYSTEM, "disconnects from a game" ); cmdSystem->AddCommand( "demoShot", Session_DemoShot_f, CMD_FL_SYSTEM, "writes a screenshot for a demo" ); cmdSystem->AddCommand( "testGUI", Session_TestGUI_f, CMD_FL_SYSTEM, "tests a gui" ); #ifndef ID_DEDICATED cmdSystem->AddCommand( "saveGame", SaveGame_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "saves a game" ); cmdSystem->AddCommand( "loadGame", LoadGame_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "loads a game", idCmdSystem::ArgCompletion_SaveGame ); #endif cmdSystem->AddCommand( "takeViewNotes", TakeViewNotes_f, CMD_FL_SYSTEM, "take notes about the current map from the current view" ); cmdSystem->AddCommand( "takeViewNotes2", TakeViewNotes2_f, CMD_FL_SYSTEM, "extended take view notes" ); cmdSystem->AddCommand( "rescanSI", Session_RescanSI_f, CMD_FL_SYSTEM, "internal - rescan serverinfo cvars and tell game" ); cmdSystem->AddCommand( "promptKey", Session_PromptKey_f, CMD_FL_SYSTEM, "prompt and sets the CD Key" ); cmdSystem->AddCommand( "hitch", Session_Hitch_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "hitches the game" ); // the same idRenderWorld will be used for all games // and demos, insuring that level specific models // will be freed rw = renderSystem->AllocRenderWorld(); sw = soundSystem->AllocSoundWorld( rw ); menuSoundWorld = soundSystem->AllocSoundWorld( rw ); // we have a single instance of the main menu guiMainMenu = uiManager->FindGui( "guis/mainmenu.gui", true, false, true ); guiMainMenu_MapList = uiManager->AllocListGUI(); guiMainMenu_MapList->Config( guiMainMenu, "mapList" ); idAsyncNetwork::client.serverList.GUIConfig( guiMainMenu, "serverList" ); guiRestartMenu = uiManager->FindGui( "guis/restart.gui", true, false, true ); guiGameOver = uiManager->FindGui( "guis/gameover.gui", true, false, true ); guiMsg = uiManager->FindGui( "guis/msg.gui", true, false, true ); guiTakeNotes = uiManager->FindGui( "guis/takeNotes.gui", true, false, true ); guiIntro = uiManager->FindGui( "guis/intro.gui", true, false, true ); whiteMaterial = declManager->FindMaterial( "_white" ); guiInGame = NULL; guiTest = NULL; guiActive = NULL; guiHandle = NULL; ReadCDKey(); } /* =============== idSessionLocal::GetLocalClientNum =============== */ int idSessionLocal::GetLocalClientNum() { if ( idAsyncNetwork::client.IsActive() ) { return idAsyncNetwork::client.GetLocalClientNum(); } else if ( idAsyncNetwork::server.IsActive() ) { if ( idAsyncNetwork::serverDedicated.GetInteger() == 0 ) { return 0; } else if ( idAsyncNetwork::server.IsClientInGame( idAsyncNetwork::serverDrawClient.GetInteger() ) ) { return idAsyncNetwork::serverDrawClient.GetInteger(); } else { return -1; } } else { return 0; } } /* =============== idSessionLocal::SetPlayingSoundWorld =============== */ void idSessionLocal::SetPlayingSoundWorld() { if ( guiActive && ( guiActive == guiMainMenu || guiActive == guiIntro || guiActive == guiLoading || ( guiActive == guiMsg && !mapSpawned ) ) ) { soundSystem->SetPlayingSoundWorld( menuSoundWorld ); } else { soundSystem->SetPlayingSoundWorld( sw ); } } /* =============== idSessionLocal::TimeHitch this is used by the sound system when an OnDemand sound is loaded, so the game action doesn't advance and get things out of sync =============== */ void idSessionLocal::TimeHitch( int msec ) { timeHitch += msec; } /* ================= idSessionLocal::ReadCDKey ================= */ void idSessionLocal::ReadCDKey( void ) { idStr filename; idFile *f; char buffer[32]; cdkey_state = CDKEY_UNKNOWN; filename = "../" BASE_GAMEDIR "/" CDKEY_FILE; f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_configpath" ) ); // try the install path, which is where the cd installer and steam put it if ( !f ) f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_basepath" ) ); if ( !f ) { common->Printf( "Couldn't read %s.\n", filename.c_str() ); cdkey[ 0 ] = '\0'; } else { memset( buffer, 0, sizeof(buffer) ); f->Read( buffer, CDKEY_BUF_LEN - 1 ); fileSystem->CloseFile( f ); idStr::Copynz( cdkey, buffer, CDKEY_BUF_LEN ); } xpkey_state = CDKEY_UNKNOWN; filename = "../" BASE_GAMEDIR "/" XPKEY_FILE; f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_configpath" ) ); // try the install path, which is where the cd installer and steam put it if ( !f ) f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_basepath" ) ); if ( !f ) { common->Printf( "Couldn't read %s.\n", filename.c_str() ); xpkey[ 0 ] = '\0'; } else { memset( buffer, 0, sizeof(buffer) ); f->Read( buffer, CDKEY_BUF_LEN - 1 ); fileSystem->CloseFile( f ); idStr::Copynz( xpkey, buffer, CDKEY_BUF_LEN ); } } /* ================ idSessionLocal::WriteCDKey ================ */ void idSessionLocal::WriteCDKey( void ) { idStr filename; idFile *f; const char *OSPath; filename = "../" BASE_GAMEDIR "/" CDKEY_FILE; // OpenFileWrite advertises creating directories to the path if needed, but that won't work with a '..' in the path // occasionally on windows, but mostly on Linux and OSX, the fs_configpath/base may not exist in full OSPath = fileSystem->BuildOSPath( cvarSystem->GetCVarString( "fs_configpath" ), BASE_GAMEDIR, CDKEY_FILE ); fileSystem->CreateOSPath( OSPath ); f = fileSystem->OpenFileWrite( filename, "fs_configpath" ); if ( !f ) { common->Printf( "Couldn't write %s.\n", filename.c_str() ); return; } f->Printf( "%s%s", cdkey, CDKEY_TEXT ); fileSystem->CloseFile( f ); filename = "../" BASE_GAMEDIR "/" XPKEY_FILE; f = fileSystem->OpenFileWrite( filename, "fs_configpath" ); if ( !f ) { common->Printf( "Couldn't write %s.\n", filename.c_str() ); return; } f->Printf( "%s%s", xpkey, CDKEY_TEXT ); fileSystem->CloseFile( f ); } /* =============== idSessionLocal::ClearKey =============== */ void idSessionLocal::ClearCDKey( bool valid[ 2 ] ) { if ( !valid[ 0 ] ) { memset( cdkey, 0, CDKEY_BUF_LEN ); cdkey_state = CDKEY_UNKNOWN; } else if ( cdkey_state == CDKEY_CHECKING ) { // if a key was in checking and not explicitely asked for clearing, put it back to ok cdkey_state = CDKEY_OK; } if ( !valid[ 1 ] ) { memset( xpkey, 0, CDKEY_BUF_LEN ); xpkey_state = CDKEY_UNKNOWN; } else if ( xpkey_state == CDKEY_CHECKING ) { xpkey_state = CDKEY_OK; } WriteCDKey( ); } /* ================ idSessionLocal::GetCDKey ================ */ const char *idSessionLocal::GetCDKey( bool xp ) { if ( !xp ) { return cdkey; } if ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING ) { return xpkey; } return NULL; } // digits to letters table #define CDKEY_DIGITS "TWSBJCGD7PA23RLH" /* =============== idSessionLocal::EmitGameAuth we toggled some key state to CDKEY_CHECKING. send a standalone auth packet to validate =============== */ void idSessionLocal::EmitGameAuth( void ) { // make sure the auth reply is empty, we use it to indicate an auth reply authMsg.Empty(); if ( idAsyncNetwork::client.SendAuthCheck( cdkey_state == CDKEY_CHECKING ? cdkey : NULL, xpkey_state == CDKEY_CHECKING ? xpkey : NULL ) ) { authEmitTimeout = Sys_Milliseconds() + CDKEY_AUTH_TIMEOUT; common->DPrintf( "authing with the master..\n" ); } else { // net is not available common->DPrintf( "sendAuthCheck failed\n" ); if ( cdkey_state == CDKEY_CHECKING ) { cdkey_state = CDKEY_OK; } if ( xpkey_state == CDKEY_CHECKING ) { xpkey_state = CDKEY_OK; } } } /* ================ idSessionLocal::CheckKey the function will only modify keys to _OK or _CHECKING if the offline checks are passed if the function returns false, the offline checks failed, and offline_valid holds which keys are bad ================ */ bool idSessionLocal::CheckKey( const char *key, bool netConnect, bool offline_valid[ 2 ] ) { char lkey[ 2 ][ CDKEY_BUF_LEN ]; char l_chk[ 2 ][ 3 ]; char s_chk[ 3 ]; int imax,i_key; unsigned int checksum, chk8; bool edited_key[ 2 ]; // make sure have a right input string assert( strlen( key ) == ( CDKEY_BUF_LEN - 1 ) * 2 + 4 + 3 + 4 ); edited_key[ 0 ] = ( key[0] == '1' ); idStr::Copynz( lkey[0], key + 2, CDKEY_BUF_LEN ); idStr::ToUpper( lkey[0] ); idStr::Copynz( l_chk[0], key + CDKEY_BUF_LEN + 2, 3 ); idStr::ToUpper( l_chk[0] ); edited_key[ 1 ] = ( key[ CDKEY_BUF_LEN + 2 + 3 ] == '1' ); idStr::Copynz( lkey[1], key + CDKEY_BUF_LEN + 7, CDKEY_BUF_LEN ); idStr::ToUpper( lkey[1] ); idStr::Copynz( l_chk[1], key + CDKEY_BUF_LEN * 2 + 7, 3 ); idStr::ToUpper( l_chk[1] ); if ( fileSystem->HasD3XP() ) { imax = 2; } else { imax = 1; } offline_valid[ 0 ] = offline_valid[ 1 ] = true; for( i_key = 0; i_key < imax; i_key++ ) { // check that the characters are from the valid set int i; for ( i = 0; i < CDKEY_BUF_LEN - 1; i++ ) { if ( !strchr( CDKEY_DIGITS, lkey[i_key][i] ) ) { offline_valid[ i_key ] = false; continue; } } if ( edited_key[ i_key ] ) { // verify the checksum for edited keys only checksum = CRC32_BlockChecksum( lkey[i_key], CDKEY_BUF_LEN - 1 ); chk8 = ( checksum & 0xff ) ^ ( ( ( checksum & 0xff00 ) >> 8 ) ^ ( ( ( checksum & 0xff0000 ) >> 16 ) ^ ( ( checksum & 0xff000000 ) >> 24 ) ) ); idStr::snPrintf( s_chk, 3, "%02X", chk8 ); if ( idStr::Icmp( l_chk[i_key], s_chk ) != 0 ) { offline_valid[ i_key ] = false; continue; } } } if ( !offline_valid[ 0 ] || !offline_valid[1] ) { return false; } // offline checks passed, we'll return true and optionally emit key check requests // the function should only modify the key states if the offline checks passed successfully // set the keys, don't send a game auth if we are net connecting idStr::Copynz( cdkey, lkey[0], CDKEY_BUF_LEN ); netConnect ? cdkey_state = CDKEY_OK : cdkey_state = CDKEY_CHECKING; if ( fileSystem->HasD3XP() ) { idStr::Copynz( xpkey, lkey[1], CDKEY_BUF_LEN ); netConnect ? xpkey_state = CDKEY_OK : xpkey_state = CDKEY_CHECKING; } else { xpkey_state = CDKEY_NA; } if ( !netConnect ) { EmitGameAuth(); } SetCDKeyGuiVars(); return true; } /* =============== idSessionLocal::CDKeysAreValid checking that the key is present and uses only valid characters if d3xp is installed, check for a valid xpkey as well emit an auth packet to the master if possible and needed =============== */ bool idSessionLocal::CDKeysAreValid( bool strict ) { int i; bool emitAuth = false; if ( cdkey_state == CDKEY_UNKNOWN ) { if ( strlen( cdkey ) != CDKEY_BUF_LEN - 1 ) { cdkey_state = CDKEY_INVALID; } else { for ( i = 0; i < CDKEY_BUF_LEN-1; i++ ) { if ( !strchr( CDKEY_DIGITS, cdkey[i] ) ) { cdkey_state = CDKEY_INVALID; break; } } } if ( cdkey_state == CDKEY_UNKNOWN ) { cdkey_state = CDKEY_CHECKING; emitAuth = true; } } if ( xpkey_state == CDKEY_UNKNOWN ) { if ( fileSystem->HasD3XP() ) { if ( strlen( xpkey ) != CDKEY_BUF_LEN -1 ) { xpkey_state = CDKEY_INVALID; } else { for ( i = 0; i < CDKEY_BUF_LEN-1; i++ ) { if ( !strchr( CDKEY_DIGITS, xpkey[i] ) ) { xpkey_state = CDKEY_INVALID; } } } if ( xpkey_state == CDKEY_UNKNOWN ) { xpkey_state = CDKEY_CHECKING; emitAuth = true; } } else { xpkey_state = CDKEY_NA; } } if ( emitAuth ) { EmitGameAuth(); } // make sure to keep the mainmenu gui up to date in case we made state changes SetCDKeyGuiVars(); if ( strict ) { return cdkey_state == CDKEY_OK && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_NA ); } else { return ( cdkey_state == CDKEY_OK || cdkey_state == CDKEY_CHECKING ) && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING || xpkey_state == CDKEY_NA ); } } /* =============== idSessionLocal::WaitingForGameAuth =============== */ bool idSessionLocal::WaitingForGameAuth( void ) { return authEmitTimeout != 0; } /* =============== idSessionLocal::CDKeysAuthReply =============== */ void idSessionLocal::CDKeysAuthReply( bool valid, const char *auth_msg ) { //assert( authEmitTimeout > 0 ); if ( authWaitBox ) { // close the wait box StopBox(); authWaitBox = false; } if ( !valid ) { common->DPrintf( "auth key is invalid\n" ); authMsg = auth_msg; if ( cdkey_state == CDKEY_CHECKING ) { cdkey_state = CDKEY_INVALID; } if ( xpkey_state == CDKEY_CHECKING ) { xpkey_state = CDKEY_INVALID; } } else { common->DPrintf( "client is authed in\n" ); if ( cdkey_state == CDKEY_CHECKING ) { cdkey_state = CDKEY_OK; } if ( xpkey_state == CDKEY_CHECKING ) { xpkey_state = CDKEY_OK; } } authEmitTimeout = 0; SetCDKeyGuiVars(); } /* =============== idSessionLocal::GetCurrentMapName =============== */ const char *idSessionLocal::GetCurrentMapName() { return currentMapName.c_str(); } /* =============== idSessionLocal::GetSaveGameVersion =============== */ int idSessionLocal::GetSaveGameVersion( void ) { return savegameVersion; } /* =============== idSessionLocal::GetAuthMsg =============== */ const char *idSessionLocal::GetAuthMsg( void ) { return authMsg.c_str(); }