DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Common_load.cpp (37142B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition 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 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
     25 
     26 ===========================================================================
     27 */
     28 
     29 #include "../idlib/precompiled.h"
     30 #pragma hdrstop
     31 
     32 #include "Common_local.h"
     33 #include "../sys/sys_lobby_backend.h"
     34 
     35 
     36 #define LAUNCH_TITLE_DOOM_EXECUTABLE		"doom1.exe"
     37 #define LAUNCH_TITLE_DOOM2_EXECUTABLE		"doom2.exe"
     38 
     39 idCVar com_wipeSeconds( "com_wipeSeconds", "1", CVAR_SYSTEM, "" );
     40 idCVar com_disableAutoSaves( "com_disableAutoSaves", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
     41 idCVar com_disableAllSaves( "com_disableAllSaves", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
     42 
     43 
     44 extern idCVar sys_lang;
     45 
     46 extern idCVar g_demoMode;
     47 
     48 // This is for the dirty hack to get a dialog to show up before we capture the screen for autorender.
     49 const int NumScreenUpdatesToShowDialog = 25;
     50 
     51 /*
     52 ================
     53 idCommonLocal::LaunchExternalTitle
     54 
     55 Launches an external title  ( Doom 1, or 2 ) based on title index. 
     56 for PS3, a device number is sent in, for the game to register as a local 
     57 user by default, when title initializes.
     58 ================
     59 */
     60 void idCommonLocal::LaunchExternalTitle( int titleIndex, int device, const lobbyConnectInfo_t * const connectInfo ) {
     61 
     62 	idStr deviceString( device );
     63 
     64 	// We want to pass in the current executable, so that the launching title knows which title to return to.
     65 	// as of right now, this feature is TBD. 
     66 	const char * currentExecutablePath = "ImNotSureYet";
     67 	idStr launchingExecutablePath;
     68 
     69 	idCmdArgs cmdArgs;
     70 	cmdArgs.AppendArg( currentExecutablePath );
     71 	
     72 	if ( titleIndex == LAUNCH_TITLE_DOOM ) {
     73 			launchingExecutablePath.Format("%s%s", Sys_DefaultBasePath(), LAUNCH_TITLE_DOOM_EXECUTABLE );
     74 		cmdArgs.AppendArg( "d1bfg" );
     75 	} else if ( titleIndex == LAUNCH_TITLE_DOOM2 ) {
     76 		launchingExecutablePath.Format("%s%s", Sys_DefaultBasePath(), LAUNCH_TITLE_DOOM2_EXECUTABLE );
     77 		cmdArgs.AppendArg( "d2bfg" );
     78 
     79 	} else {
     80 
     81 		idLib::Warning("Unhandled Launch Title %d \n", titleIndex );
     82 	}
     83 
     84 	cmdArgs.AppendArg( deviceString.c_str() );
     85 
     86 	// Add an argument so that the new process knows whether or not to read exitspawn data.
     87 	if ( connectInfo != NULL ) {
     88 		cmdArgs.AppendArg( "exitspawnInvite" );
     89 	}
     90 
     91 	// Add arguments so that the new process will know which command line to invoke to relaunch this process
     92 	// if necessary.
     93 
     94 	const int launchDataSize = ( connectInfo == NULL ) ? 0 : sizeof( *connectInfo );
     95 
     96 	Sys_Launch(  launchingExecutablePath.c_str() , cmdArgs, const_cast< lobbyConnectInfo_t * const >( connectInfo ), launchDataSize );
     97 }
     98 
     99 /*
    100 ================
    101 idCommonLocal::StartWipe
    102 
    103 Draws and captures the current state, then starts a wipe with that image
    104 ================
    105 */
    106 void idCommonLocal::StartWipe( const char *_wipeMaterial, bool hold ) {
    107 	console->Close();
    108 
    109 	Draw();
    110 
    111 	renderSystem->CaptureRenderToImage( "_currentRender" );
    112 
    113 	wipeMaterial = declManager->FindMaterial( _wipeMaterial, false );
    114 
    115 	wipeStartTime = Sys_Milliseconds();
    116 	wipeStopTime = wipeStartTime + SEC2MS( com_wipeSeconds.GetFloat() );
    117 	wipeHold = hold;
    118 }
    119 
    120 /*
    121 ================
    122 idCommonLocal::CompleteWipe
    123 ================
    124 */
    125 void idCommonLocal::CompleteWipe() {
    126 	while ( Sys_Milliseconds() < wipeStopTime ) {
    127 		BusyWait();
    128 		Sys_Sleep( 10 );
    129 	}
    130 
    131 	// ensure it is completely faded out
    132 	wipeStopTime = Sys_Milliseconds();
    133 	BusyWait();
    134 }
    135 
    136 /*
    137 ================
    138 idCommonLocal::ClearWipe
    139 ================
    140 */
    141 void idCommonLocal::ClearWipe() {
    142 	wipeHold = false;
    143 	wipeStopTime = 0;
    144 	wipeStartTime = 0;
    145 	wipeForced = false;
    146 }
    147 
    148 /*
    149 ===============
    150 idCommonLocal::StartNewGame
    151 ===============
    152 */
    153 void idCommonLocal::StartNewGame( const char * mapName, bool devmap, int gameMode ) {
    154 	if ( session->GetSignInManager().GetMasterLocalUser() == NULL ) {
    155 		// For development make sure a controller is registered
    156 		// Can't just register the local user because it will be removed because of it's persistent state
    157 		session->GetSignInManager().SetDesiredLocalUsers( 1, 1 );
    158 		session->GetSignInManager().Pump();
    159 	}
    160 
    161 	idStr mapNameClean = mapName;
    162 	mapNameClean.StripFileExtension();
    163 	mapNameClean.BackSlashesToSlashes();
    164 
    165 	idMatchParameters matchParameters;
    166 	matchParameters.mapName = mapNameClean;
    167 	if ( gameMode == GAME_MODE_SINGLEPLAYER ) {
    168 		matchParameters.numSlots = 1;
    169 		matchParameters.gameMode = GAME_MODE_SINGLEPLAYER;
    170 		matchParameters.gameMap = GAME_MAP_SINGLEPLAYER;
    171 	} else {
    172 		matchParameters.gameMap = mpGameMaps.Num();	// If this map isn't found in mpGameMaps, then set it to some undefined value (this happens when, for example, we load a box map with netmap)
    173 		matchParameters.gameMode = gameMode;
    174 		matchParameters.matchFlags = DefaultPartyFlags;
    175 		for ( int i = 0; i < mpGameMaps.Num(); i++ ) {
    176 			if ( idStr::Icmp( mpGameMaps[i].mapFile, mapNameClean ) == 0 ) {
    177 				matchParameters.gameMap = i;
    178 				break;
    179 			}
    180 		}
    181 		matchParameters.numSlots = session->GetTitleStorageInt("MAX_PLAYERS_ALLOWED", 4 );
    182 	}
    183 
    184 	cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO, matchParameters.serverInfo );
    185 	if ( devmap ) {
    186 		matchParameters.serverInfo.Set( "devmap", "1" );
    187 	} else {
    188 		matchParameters.serverInfo.Delete( "devmap" );
    189 	}
    190 
    191 	session->QuitMatchToTitle();
    192 	if ( WaitForSessionState( idSession::IDLE ) ) {
    193 		session->CreatePartyLobby( matchParameters );
    194 		if ( WaitForSessionState( idSession::PARTY_LOBBY ) ) {
    195 			session->CreateMatch( matchParameters );
    196 			if ( WaitForSessionState( idSession::GAME_LOBBY ) ) {
    197 				cvarSystem->SetCVarBool( "developer", devmap );
    198 				session->StartMatch();
    199 			}
    200 		}
    201 	}
    202 }
    203 
    204 /*
    205 ===============
    206 idCommonLocal::MoveToNewMap
    207 Single player transition from one map to another
    208 ===============
    209 */
    210 void idCommonLocal::MoveToNewMap( const char *mapName, bool devmap ) {
    211 	idMatchParameters matchParameters;
    212 	matchParameters.numSlots = 1;
    213 	matchParameters.gameMode = GAME_MODE_SINGLEPLAYER;
    214 	matchParameters.gameMap = GAME_MAP_SINGLEPLAYER;
    215 	matchParameters.mapName = mapName;
    216 	cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO, matchParameters.serverInfo );
    217 	if ( devmap ) {
    218 		matchParameters.serverInfo.Set( "devmap", "1" );
    219 		mapSpawnData.persistentPlayerInfo.Clear();
    220 	} else {
    221 		matchParameters.serverInfo.Delete( "devmap" );
    222 		mapSpawnData.persistentPlayerInfo = game->GetPersistentPlayerInfo( 0 );
    223 	}
    224 	session->QuitMatchToTitle();
    225 	if ( WaitForSessionState( idSession::IDLE ) ) {
    226 		session->CreatePartyLobby( matchParameters );
    227 		if ( WaitForSessionState( idSession::PARTY_LOBBY ) ) {
    228 			session->CreateMatch( matchParameters );
    229 			if ( WaitForSessionState( idSession::GAME_LOBBY ) ) {
    230 				session->StartMatch();
    231 			}
    232 		}
    233 	}
    234 }
    235 
    236 /*
    237 ===============
    238 idCommonLocal::UnloadMap
    239 
    240 Performs cleanup that needs to happen between maps, or when a
    241 game is exited.
    242 Exits with mapSpawned = false
    243 ===============
    244 */
    245 void idCommonLocal::UnloadMap() {
    246 	StopPlayingRenderDemo();
    247 
    248 	// end the current map in the game
    249 	if ( game ) {
    250 		game->MapShutdown();
    251 	}
    252 
    253 	if ( writeDemo ) {
    254 		StopRecordingRenderDemo();
    255 	}
    256 
    257 	mapSpawned = false;
    258 }
    259 
    260 /*
    261 ===============
    262 idCommonLocal::LoadLoadingGui
    263 ===============
    264 */
    265 void idCommonLocal::LoadLoadingGui( const char *mapName, bool & hellMap ) {
    266 
    267 	defaultLoadscreen = false;
    268 	loadGUI = new idSWF( "loading/default", NULL );	
    269 	
    270 	if ( g_demoMode.GetBool() ) {
    271 		hellMap = false;
    272 		if ( loadGUI != NULL ) {					
    273 			const idMaterial * defaultMat = declManager->FindMaterial( "guis/assets/loadscreens/default" );
    274 			renderSystem->LoadLevelImages();
    275 			
    276 			loadGUI->Activate( true );
    277 			idSWFSpriteInstance * bgImg = loadGUI->GetRootObject().GetSprite( "bgImage" );
    278 			if ( bgImg != NULL ) {
    279 				bgImg->SetMaterial( defaultMat );
    280 			}
    281 		}
    282 		defaultLoadscreen = true;
    283 		return;
    284 	}
    285 
    286 	// load / program a gui to stay up on the screen while loading
    287 	idStrStatic< MAX_OSPATH > stripped = mapName;
    288 	stripped.StripFileExtension();
    289 	stripped.StripPath();
    290 
    291 	// use default load screen for demo
    292 	idStrStatic< MAX_OSPATH > matName = "guis/assets/loadscreens/";
    293 	matName.Append( stripped );
    294 	const idMaterial * mat = declManager->FindMaterial( matName );
    295 
    296 	renderSystem->LoadLevelImages();
    297 
    298 	if ( mat->GetImageWidth() < 32 ) {
    299 		mat = declManager->FindMaterial( "guis/assets/loadscreens/default" );
    300 		renderSystem->LoadLevelImages();
    301 	}
    302 
    303 	loadTipList.SetNum( loadTipList.Max() );
    304 	for ( int i = 0; i < loadTipList.Max(); ++i ) {
    305 		loadTipList[i] = i;
    306 	}
    307 
    308 	if ( loadGUI != NULL ) {
    309 		loadGUI->Activate( true );
    310 		nextLoadTip = Sys_Milliseconds() + LOAD_TIP_CHANGE_INTERVAL;
    311 
    312 		idSWFSpriteInstance * bgImg = loadGUI->GetRootObject().GetSprite( "bgImage" );
    313 		if ( bgImg != NULL ) {
    314 			bgImg->SetMaterial( mat );
    315 		}
    316 
    317 		idSWFSpriteInstance * overlay = loadGUI->GetRootObject().GetSprite( "overlay" );
    318 
    319 		const idDeclEntityDef * mapDef = static_cast<const idDeclEntityDef *>(declManager->FindType( DECL_MAPDEF, mapName, false ));
    320 		if ( mapDef != NULL ) {
    321 			isHellMap = mapDef->dict.GetBool( "hellMap", false );
    322 
    323 			if ( isHellMap && overlay != NULL ) {
    324 				overlay->SetVisible( false );
    325 			}
    326 
    327 			idStr desc;	
    328 			idStr subTitle;
    329 			idStr displayName;
    330 			idSWFTextInstance * txtVal = NULL;
    331 
    332 			txtVal = loadGUI->GetRootObject().GetNestedText( "txtRegLoad" );
    333 			displayName = idLocalization::GetString( mapDef->dict.GetString( "name", mapName ) );
    334 
    335 			if ( txtVal != NULL ) {
    336 				txtVal->SetText( "#str_00408" );
    337 				txtVal->SetStrokeInfo( true, 2.0f, 1.0f );
    338 			}			
    339 
    340 			const idMatchParameters & matchParameters = session->GetActingGameStateLobbyBase().GetMatchParms();
    341 			if ( matchParameters.gameMode == GAME_MODE_SINGLEPLAYER ) {
    342 				desc = idLocalization::GetString( mapDef->dict.GetString( "desc", "" ) );
    343 				subTitle = idLocalization::GetString( mapDef->dict.GetString( "subTitle", "" ) );
    344 			} else {
    345 				const idStrList & modes = common->GetModeDisplayList();
    346 				subTitle = modes[ idMath::ClampInt( 0, modes.Num() - 1, matchParameters.gameMode ) ];
    347 
    348 				const char * modeDescs[] = { "#str_swf_deathmatch_desc", "#str_swf_tourney_desc", "#str_swf_team_deathmatch_desc", "#str_swf_lastman_desc", "#str_swf_ctf_desc" };
    349 				desc = idLocalization::GetString( modeDescs[matchParameters.gameMode] );
    350 			}
    351 			
    352 			if ( !isHellMap ) {
    353 				txtVal = loadGUI->GetRootObject().GetNestedText( "txtName" );
    354 			} else {
    355 				txtVal = loadGUI->GetRootObject().GetNestedText( "txtHellName" );
    356 			}
    357 			if ( txtVal != NULL ) {
    358 				txtVal->SetText( displayName );
    359 				txtVal->SetStrokeInfo( true, 2.0f, 1.0f );
    360 			}
    361 
    362 			txtVal = loadGUI->GetRootObject().GetNestedText( "txtSub" );
    363 			if ( txtVal != NULL && !isHellMap ) {
    364 				txtVal->SetText( subTitle );
    365 				txtVal->SetStrokeInfo( true, 1.75f, 0.75f );
    366 			}
    367 
    368 			txtVal = loadGUI->GetRootObject().GetNestedText( "txtDesc" );
    369 			if ( txtVal != NULL ) {
    370 				if ( isHellMap ) {
    371 					txtVal->SetText( va( "\n%s", desc.c_str() ) );
    372 				} else {
    373 					txtVal->SetText( desc );
    374 				}
    375 				txtVal->SetStrokeInfo( true, 1.75f, 0.75f );
    376 			}
    377 		}
    378 	}
    379 }
    380 
    381 /*
    382 ===============
    383 idCommonLocal::ExecuteMapChange
    384 
    385 Performs the initialization of a game based on session match parameters, used for both single
    386 player and multiplayer, but not for renderDemos, which don't create a game at all.
    387 Exits with mapSpawned = true
    388 ===============
    389 */
    390 void idCommonLocal::ExecuteMapChange() {
    391 	if ( session->GetState() != idSession::LOADING ) {
    392 		idLib::Warning( "Session state is not LOADING in ExecuteMapChange" );
    393 		return;
    394 	}
    395 
    396 	// Clear all dialogs before beginning the load
    397 	common->Dialog().ClearDialogs( true );
    398 	
    399 	// Remember the current load ID.
    400 	// This is so we can tell if we had a new loadmap request from within an existing loadmap call
    401 	const int cachedLoadingID = session->GetLoadingID();
    402 
    403 	const idMatchParameters & matchParameters = session->GetActingGameStateLobbyBase().GetMatchParms();
    404 
    405 	if ( matchParameters.numSlots <= 0 ) {
    406 		idLib::Warning( "numSlots <= 0 in ExecuteMapChange" );
    407 		return;
    408 	}
    409 
    410 	insideExecuteMapChange = true;
    411 
    412 	common->Printf( "--------- Execute Map Change ---------\n" );
    413 	common->Printf( "Map: %s\n", matchParameters.mapName.c_str() );
    414 
    415 	// ensure that r_znear is reset to the default value
    416 	// this fixes issues with the projection matrix getting messed up when switching maps or loading a saved game
    417 	// while an in-game cinematic is playing.
    418 	cvarSystem->SetCVarFloat( "r_znear", 3.0f );
    419 
    420 	// reset all cheat cvars for a multiplayer game
    421 	if ( IsMultiplayer() ) {
    422 		cvarSystem->ResetFlaggedVariables( CVAR_CHEAT );
    423 	}
    424 
    425 	int start = Sys_Milliseconds();
    426 
    427 	for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) {
    428 		Sys_SetRumble( i, 0, 0 );
    429 	}
    430 
    431 	// close console and remove any prints from the notify lines
    432 	console->Close();
    433 
    434 	// clear all menu sounds
    435 	soundWorld->Pause();
    436 	menuSoundWorld->ClearAllSoundEmitters();
    437 	soundSystem->SetPlayingSoundWorld( menuSoundWorld );
    438 	soundSystem->Render();
    439 
    440 	// extract the map name from serverinfo
    441 	currentMapName = matchParameters.mapName;
    442 	currentMapName.StripFileExtension();
    443 
    444 	idStrStatic< MAX_OSPATH > fullMapName = "maps/";
    445 	fullMapName += currentMapName;
    446 	fullMapName.SetFileExtension( "map" );
    447 
    448 	if ( mapSpawnData.savegameFile ) {
    449 		fileSystem->BeginLevelLoad( currentMapName, NULL, 0 );
    450 	} else {
    451 		fileSystem->BeginLevelLoad( currentMapName, saveFile.GetDataPtr(), saveFile.GetAllocated() );
    452 	}
    453 
    454 	// capture the current screen and start a wipe
    455 	// immediately complete the wipe to fade out the level transition
    456 	// run the wipe to completion
    457 	StartWipe( "wipeMaterial", true );
    458 	CompleteWipe();
    459 
    460 	int sm = Sys_Milliseconds();
    461 	// shut down the existing game if it is running
    462 	UnloadMap();
    463 	int ms = Sys_Milliseconds() - sm;
    464 	common->Printf( "%6d msec to unload map\n", ms );
    465 
    466 	// Free media from previous level and
    467 	// note which media we are going to need to load
    468 	sm = Sys_Milliseconds();
    469 	renderSystem->BeginLevelLoad();
    470 	soundSystem->BeginLevelLoad();
    471 	declManager->BeginLevelLoad();
    472 	uiManager->BeginLevelLoad();
    473 	ms = Sys_Milliseconds() - sm;
    474 	common->Printf( "%6d msec to free assets\n", ms );
    475 
    476 	//Sys_DumpMemory( true );
    477 
    478 	// load / program a gui to stay up on the screen while loading
    479 	// set the loading gui that we will wipe to
    480 	bool hellMap = false;
    481 	LoadLoadingGui( currentMapName, hellMap );
    482 
    483 	// Stop rendering the wipe
    484 	ClearWipe();
    485 
    486 
    487 	if ( fileSystem->UsingResourceFiles() ) {
    488 		idStrStatic< MAX_OSPATH > manifestName = currentMapName;
    489 		manifestName.Replace( "game/", "maps/" );
    490 		manifestName.Replace( "/mp/", "/" );
    491 		manifestName += ".preload";
    492 		idPreloadManifest manifest;
    493 		manifest.LoadManifest( manifestName );
    494 		renderSystem->Preload( manifest, currentMapName );
    495 		soundSystem->Preload( manifest );
    496 		game->Preload( manifest );
    497 	}
    498 
    499 	if ( common->IsMultiplayer() ) {
    500 		// In multiplayer, make sure the player is either 60Hz or 120Hz
    501 		// to avoid potential issues.
    502 		const float mpEngineHz = ( com_engineHz.GetFloat() < 90.0f ) ? 60.0f : 120.0f;
    503 		com_engineHz_denominator = 100LL * mpEngineHz;
    504 		com_engineHz_latched = mpEngineHz;
    505 	} else {
    506 		// allow com_engineHz to be changed between map loads
    507 		com_engineHz_denominator = 100LL * com_engineHz.GetFloat();
    508 		com_engineHz_latched = com_engineHz.GetFloat();
    509 	}
    510 
    511 	// note any warning prints that happen during the load process
    512 	common->ClearWarnings( currentMapName );
    513 
    514 	// release the mouse cursor
    515 	// before we do this potentially long operation
    516 	Sys_GrabMouseCursor( false );
    517 
    518 	// let the renderSystem load all the geometry
    519 	if ( !renderWorld->InitFromMap( fullMapName ) ) {
    520 		common->Error( "couldn't load %s", fullMapName.c_str() );
    521 	}
    522 
    523 	// for the synchronous networking we needed to roll the angles over from
    524 	// level to level, but now we can just clear everything
    525 	usercmdGen->InitForNewMap();
    526 
    527 	// load and spawn all other entities ( from a savegame possibly )
    528 	if ( mapSpawnData.savegameFile ) {
    529 		if ( !game->InitFromSaveGame( fullMapName, renderWorld, soundWorld, mapSpawnData.savegameFile, mapSpawnData.stringTableFile, mapSpawnData.savegameVersion ) ) {
    530 			// If the loadgame failed, end the session, which will force us to go back to the main menu
    531 			session->QuitMatchToTitle();
    532 		}
    533 	} else {
    534 		if ( !IsMultiplayer() ) {
    535 			assert( game->GetLocalClientNum() == 0 );
    536 			assert( matchParameters.gameMode == GAME_MODE_SINGLEPLAYER );
    537 			assert( matchParameters.gameMap == GAME_MAP_SINGLEPLAYER );
    538 			game->SetPersistentPlayerInfo( 0, mapSpawnData.persistentPlayerInfo );
    539 		}
    540 		game->SetServerInfo( matchParameters.serverInfo );
    541 		game->InitFromNewMap( fullMapName, renderWorld, soundWorld, matchParameters.gameMode, Sys_Milliseconds() );
    542 	}
    543 
    544 	game->Shell_CreateMenu( true );
    545 
    546 	// Reset some values important to multiplayer
    547 	ResetNetworkingState();
    548 
    549 	// If the session state is not loading here, something went wrong.
    550 	if ( session->GetState() == idSession::LOADING && session->GetLoadingID() == cachedLoadingID ) {
    551 		// Notify session we are done loading
    552 		session->LoadingFinished();
    553 
    554 		while ( session->GetState() == idSession::LOADING ) {
    555 			Sys_GenerateEvents();
    556 			session->UpdateSignInManager();
    557 			session->Pump();
    558 			Sys_Sleep( 10 );
    559 		}
    560 	}
    561 
    562 	if ( !mapSpawnData.savegameFile ) {
    563 		// run a single frame to catch any resources that are referenced by events posted in spawn
    564 		idUserCmdMgr emptyCommandManager;
    565 		gameReturn_t emptyGameReturn;
    566 		for ( int playerIndex = 0; playerIndex < MAX_PLAYERS; ++playerIndex ) {
    567 			emptyCommandManager.PutUserCmdForPlayer( playerIndex, usercmd_t() );
    568 		}
    569 		if ( IsClient() ) {
    570 			game->ClientRunFrame( emptyCommandManager, false, emptyGameReturn );
    571 		} else {
    572 			game->RunFrame( emptyCommandManager, emptyGameReturn );
    573 		}
    574 	}
    575 
    576 	renderSystem->EndLevelLoad();
    577 	soundSystem->EndLevelLoad();
    578 	declManager->EndLevelLoad();
    579 	uiManager->EndLevelLoad( currentMapName );
    580 	fileSystem->EndLevelLoad();
    581 
    582 	if ( !mapSpawnData.savegameFile && !IsMultiplayer() ) {
    583 		common->Printf( "----- Running initial game frames -----\n" );
    584 
    585 		// In single player, run a bunch of frames to make sure ragdolls are settled
    586 		idUserCmdMgr emptyCommandManager;
    587 		gameReturn_t emptyGameReturn;
    588 		for ( int i = 0; i < 100; i++ ) {
    589 			for ( int playerIndex = 0; playerIndex < MAX_PLAYERS; ++playerIndex ) {
    590 				emptyCommandManager.PutUserCmdForPlayer( playerIndex, usercmd_t() );
    591 			}
    592 			game->RunFrame( emptyCommandManager, emptyGameReturn );
    593 		}
    594 
    595 		// kick off an auto-save of the game (so we can always continue in this map if we die before hitting an autosave)
    596 		common->Printf( "----- Saving Game -----\n" );
    597 		SaveGame( "autosave" );
    598 	}
    599 
    600 	common->Printf( "----- Generating Interactions -----\n" );
    601 
    602 	// let the renderSystem generate interactions now that everything is spawned
    603 	renderWorld->GenerateAllInteractions();
    604 
    605 	{
    606 		int vertexMemUsedKB = vertexCache.staticData.vertexMemUsed.GetValue() / 1024;
    607 		int indexMemUsedKB = vertexCache.staticData.indexMemUsed.GetValue() / 1024;
    608 		idLib::Printf( "Used %dkb of static vertex memory (%d%%)\n", vertexMemUsedKB, vertexMemUsedKB * 100 / ( STATIC_VERTEX_MEMORY / 1024 ) );
    609 		idLib::Printf( "Used %dkb of static index memory (%d%%)\n", indexMemUsedKB, indexMemUsedKB * 100 / ( STATIC_INDEX_MEMORY / 1024 ) );
    610 	}
    611 
    612 	if ( common->JapaneseCensorship() ) {
    613 		if ( currentMapName.Icmp( "game/mp/d3xpdm3" ) == 0 ) {
    614 			const idMaterial * gizpool2 = declManager->FindMaterial( "textures/hell/gizpool2" );
    615 			idMaterial * chiglass1bluex = const_cast<idMaterial *>( declManager->FindMaterial( "textures/sfx/chiglass1bluex" ) );
    616 			idTempArray<char> text( gizpool2->GetTextLength() );
    617 			gizpool2->GetText( text.Ptr() );
    618 			chiglass1bluex->Parse( text.Ptr(), text.Num(), false );
    619 		}
    620 	}
    621 
    622 	common->PrintWarnings();
    623 
    624 	session->Pump();
    625 
    626 	if ( session->GetState() != idSession::INGAME ) {
    627 		// Something went wrong, don't process stale reliables that have been queued up.
    628 		reliableQueue.Clear();
    629 	}
    630 
    631 	usercmdGen->Clear();
    632 
    633 	// remove any prints from the notify lines
    634 	console->ClearNotifyLines();
    635 
    636 	Sys_SetPhysicalWorkMemory( -1, -1 );
    637 
    638 	// at this point we should be done with the loading gui so we kill it
    639 	delete loadGUI;
    640 	loadGUI = NULL;
    641 
    642 
    643 	// capture the current screen and start a wipe
    644 	StartWipe( "wipe2Material" );
    645 
    646 	// we are valid for game draws now
    647 	insideExecuteMapChange = false;
    648 	mapSpawned = true;
    649 	Sys_ClearEvents();
    650 
    651 
    652 	int	msec = Sys_Milliseconds() - start;
    653 	common->Printf( "%6d msec to load %s\n", msec, currentMapName.c_str() );
    654 	//Sys_DumpMemory( false );	
    655 
    656 	// Issue a render at the very end of the load process to update soundTime before the first frame
    657 	soundSystem->Render();
    658 }
    659 
    660 /*
    661 ===============
    662 idCommonLocal::UpdateLevelLoadPacifier
    663 
    664 Pumps the session and if multiplayer, displays dialogs during the loading process.
    665 ===============
    666 */
    667 void idCommonLocal::UpdateLevelLoadPacifier() {	
    668 	autoRenderIconType_t icon = AUTORENDER_DEFAULTICON;
    669 	bool autoswapsRunning = renderSystem->AreAutomaticBackgroundSwapsRunning( &icon );
    670 	if ( !insideExecuteMapChange && !autoswapsRunning ) {
    671 		return;
    672 	}
    673 
    674 	const int sessionUpdateTime = common->IsMultiplayer() ? 16 : 100;
    675 
    676 	const int time = Sys_Milliseconds();
    677 
    678 	// Throttle session pumps.
    679 	if ( time - lastPacifierSessionTime >= sessionUpdateTime ) {
    680 		lastPacifierSessionTime = time;
    681 
    682 		Sys_GenerateEvents();
    683 
    684 		session->UpdateSignInManager();
    685 		session->Pump();
    686 		session->ProcessSnapAckQueue();
    687 	}
    688 
    689 	if ( autoswapsRunning ) {
    690 		// If autoswaps are running, only update if a Dialog is shown/dismissed
    691 		bool dialogState = Dialog().HasAnyActiveDialog();
    692 		if ( lastPacifierDialogState != dialogState ) {
    693 			lastPacifierDialogState = dialogState;
    694 			renderSystem->EndAutomaticBackgroundSwaps();
    695 			if ( dialogState ) {
    696 				icon = AUTORENDER_DIALOGICON; // Done this way to handle the rare case of a tip changing at the same time a dialog comes up
    697 				for ( int i = 0; i < NumScreenUpdatesToShowDialog; ++i ) {
    698 					UpdateScreen( false );
    699 				}
    700 			}
    701 			renderSystem->BeginAutomaticBackgroundSwaps( icon );
    702 		}
    703 	} else {
    704 		// On the PC just update at a constant rate for the Steam overlay
    705 		if ( time - lastPacifierGuiTime >= 50 ) {
    706 			lastPacifierGuiTime = time;
    707 			UpdateScreen( false );
    708 		}
    709 	}
    710 
    711 	if ( time >= nextLoadTip && loadGUI != NULL && loadTipList.Num() > 0 && !defaultLoadscreen ) {
    712 		if ( autoswapsRunning ) {
    713 			renderSystem->EndAutomaticBackgroundSwaps();
    714 		}
    715 		nextLoadTip = time + LOAD_TIP_CHANGE_INTERVAL;
    716 		const int rnd = time % loadTipList.Num();
    717 		idStrStatic<20> tipId;
    718 		tipId.Format( "#str_loadtip_%d", loadTipList[ rnd ] );
    719 		loadTipList.RemoveIndex( rnd );
    720 
    721 		idSWFTextInstance * txtVal = loadGUI->GetRootObject().GetNestedText( "txtDesc" );
    722 		if ( txtVal != NULL ) {
    723 			if ( isHellMap ) {
    724 				txtVal->SetText( va( "\n%s", idLocalization::GetString( tipId ) ) );
    725 			} else {
    726 				txtVal->SetText( idLocalization::GetString( tipId ) );
    727 			}
    728 			txtVal->SetStrokeInfo( true, 1.75f, 0.75f );
    729 		}
    730 		UpdateScreen( false );
    731 		if ( autoswapsRunning ) {
    732 			renderSystem->BeginAutomaticBackgroundSwaps( icon );
    733 		}
    734 	}
    735 }
    736 
    737 /*
    738 ===============
    739 idCommonLocal::ScrubSaveGameFileName
    740 
    741 Turns a bad file name into a good one or your money back
    742 ===============
    743 */
    744 void idCommonLocal::ScrubSaveGameFileName( idStr &saveFileName ) const {
    745 	int i;
    746 	idStr inFileName;
    747 
    748 	inFileName = saveFileName;
    749 	inFileName.RemoveColors();
    750 	inFileName.StripFileExtension();
    751 
    752 	saveFileName.Clear();
    753 
    754 	int len = inFileName.Length();
    755 	for ( i = 0; i < len; i++ ) {
    756 		if ( strchr( "',.~!@#$%^&*()[]{}<>\\|/=?+;:-\'\"", inFileName[i] ) ) {
    757 			// random junk
    758 			saveFileName += '_';
    759 		} else if ( (const unsigned char)inFileName[i] >= 128 ) {
    760 			// high ascii chars
    761 			saveFileName += '_';
    762 		} else if ( inFileName[i] == ' ' ) {
    763 			saveFileName += '_';
    764 		} else {
    765 			saveFileName += inFileName[i];
    766 		}
    767 	}
    768 }
    769 
    770 /*
    771 ===============
    772 idCommonLocal::SaveGame
    773 ===============
    774 */
    775 bool idCommonLocal::SaveGame( const char * saveName ) {
    776 	if ( pipelineFile != NULL ) {
    777 		// We're already in the middle of a save. Leave us alone.
    778 		return false;
    779 	}
    780 
    781 	if ( com_disableAllSaves.GetBool() || ( com_disableAutoSaves.GetBool() && ( idStr::Icmp( saveName, "autosave" ) == 0 ) ) ) {
    782 		return false;
    783 	}
    784 
    785 	if ( IsMultiplayer() ) {
    786 		common->Printf( "Can't save during net play.\n" );
    787 		return false;
    788 	}
    789 
    790 	if (mapSpawnData.savegameFile != NULL ) {
    791 		return false;
    792 	}
    793 
    794 	const idDict & persistentPlayerInfo = game->GetPersistentPlayerInfo( 0 );
    795 	if ( persistentPlayerInfo.GetInt( "health" ) <= 0 ) {
    796 		common->Printf( "You must be alive to save the game\n" );
    797 		return false;
    798 	}
    799 
    800 	soundWorld->Pause();
    801 	soundSystem->SetPlayingSoundWorld( menuSoundWorld );
    802 	soundSystem->Render();
    803 
    804 	Dialog().ShowSaveIndicator( true );
    805 	if ( insideExecuteMapChange ) {
    806 		UpdateLevelLoadPacifier();
    807 	} else {
    808 		// Heremake sure we pump the gui enough times to show the 'saving' dialog
    809 		const bool captureToImage = false;
    810 		for ( int i = 0; i < NumScreenUpdatesToShowDialog; ++i ) {
    811 			UpdateScreen( captureToImage );
    812 		}
    813 		renderSystem->BeginAutomaticBackgroundSwaps( AUTORENDER_DIALOGICON );
    814 	}
    815 
    816 	// Make sure the file is writable and the contents are cleared out (Set to write from the start of file)
    817 	saveFile.MakeWritable();
    818 	saveFile.Clear( false );
    819 	stringsFile.MakeWritable();
    820 	stringsFile.Clear( false );
    821 
    822 	// Setup the save pipeline
    823 	pipelineFile = new (TAG_SAVEGAMES) idFile_SaveGamePipelined();
    824 	pipelineFile->OpenForWriting( &saveFile );
    825 
    826 	// Write SaveGame Header: 
    827 	// Game Name / Version / Map Name / Persistant Player Info
    828 
    829 	// game
    830 	const char *gamename = GAME_NAME;
    831 	saveFile.WriteString( gamename );
    832 
    833 	// map
    834 	saveFile.WriteString( currentMapName );
    835 
    836 	saveFile.WriteBool( consoleUsed );
    837 
    838 	game->GetServerInfo().WriteToFileHandle( &saveFile );
    839 
    840 	// let the game save its state
    841 	game->SaveGame( pipelineFile, &stringsFile );
    842 
    843 	pipelineFile->Finish();
    844 
    845 	idSaveGameDetails gameDetails;
    846 	game->GetSaveGameDetails( gameDetails );
    847 
    848 	gameDetails.descriptors.Set( SAVEGAME_DETAIL_FIELD_LANGUAGE, sys_lang.GetString() );
    849 	gameDetails.descriptors.SetInt( SAVEGAME_DETAIL_FIELD_CHECKSUM, (int)gameDetails.descriptors.Checksum() );
    850 
    851 	gameDetails.slotName = saveName;
    852 	ScrubSaveGameFileName( gameDetails.slotName );
    853 
    854 	saveFileEntryList_t files;
    855 	files.Append( &stringsFile );
    856 	files.Append( &saveFile );
    857 
    858 	session->SaveGameSync( gameDetails.slotName, files, gameDetails );
    859 
    860 	if ( !insideExecuteMapChange ) {
    861 		renderSystem->EndAutomaticBackgroundSwaps();
    862 	}
    863 
    864 	syncNextGameFrame = true;
    865 
    866 	return true;
    867 }
    868 
    869 /*
    870 ===============
    871 idCommonLocal::LoadGame
    872 ===============
    873 */
    874 bool idCommonLocal::LoadGame( const char * saveName ) { 
    875 	if ( IsMultiplayer() ) {
    876 		common->Printf( "Can't load during net play.\n" );
    877 		if ( wipeForced ) {
    878 			ClearWipe();
    879 		}
    880 		return false;
    881 	}
    882 
    883 	if ( GetCurrentGame() != DOOM3_BFG ) {
    884 		return false;
    885 	}
    886 
    887 	if ( session->GetSignInManager().GetMasterLocalUser() == NULL ) {
    888 		return false;
    889 	}
    890 	if (mapSpawnData.savegameFile != NULL ) {
    891 		return false;
    892 	}
    893 
    894 	bool found = false;
    895 	const saveGameDetailsList_t & sgdl = session->GetSaveGameManager().GetEnumeratedSavegames();
    896 	for ( int i = 0; i < sgdl.Num(); i++ ) {
    897 		if ( sgdl[i].slotName == saveName ) {
    898 			if ( sgdl[i].GetLanguage() != sys_lang.GetString() ) {
    899 				idStaticList< idSWFScriptFunction *, 4 > callbacks;
    900 				idStaticList< idStrId, 4 > optionText;
    901 				optionText.Append( idStrId( "#str_swf_continue" ) );
    902 				idStrStatic<256> langName = "#str_lang_" + sgdl[i].GetLanguage();
    903 				idStrStatic<256> msg;
    904 				msg.Format( idLocalization::GetString( "#str_dlg_wrong_language" ), idLocalization::GetString( langName ) );
    905 				Dialog().AddDynamicDialog( GDM_SAVEGAME_WRONG_LANGUAGE, callbacks, optionText, true, msg, false, true );
    906 				if ( wipeForced ) {
    907 					ClearWipe();
    908 				}
    909 				return false;
    910 			}
    911 			found = true;
    912 			break;
    913 		}
    914 	}
    915 	if ( !found ) {
    916 		common->Printf( "Could not find save '%s'\n", saveName );
    917 		if ( wipeForced ) {
    918 			ClearWipe();
    919 		}
    920 		return false;
    921 	}
    922 
    923 	mapSpawnData.savegameFile = &saveFile;
    924 	mapSpawnData.stringTableFile = &stringsFile;
    925 
    926 	saveFileEntryList_t files;
    927 	files.Append( mapSpawnData.stringTableFile );
    928 	files.Append( mapSpawnData.savegameFile );
    929 
    930 	idStr slotName = saveName;
    931 	ScrubSaveGameFileName( slotName );
    932 	saveFile.Clear( false );
    933 	stringsFile.Clear( false );
    934 
    935 	saveGameHandle_t loadGameHandle = session->LoadGameSync( slotName, files );
    936 	if ( loadGameHandle != 0 ) {
    937 		return true;
    938 	}
    939 	mapSpawnData.savegameFile = NULL;
    940 	if ( wipeForced ) {
    941 		ClearWipe();
    942 	}
    943 	return false;
    944 }
    945 
    946 /*
    947 ========================
    948 HandleInsufficientStorage
    949 ========================
    950 */
    951 void HandleInsufficientStorage( const idSaveLoadParms & parms ) {
    952 	session->GetSaveGameManager().ShowRetySaveDialog( parms.directory, parms.requiredSpaceInBytes );
    953 }
    954 
    955 /*
    956 ========================
    957 HandleCommonErrors
    958 ========================
    959 */
    960 bool HandleCommonErrors( const idSaveLoadParms & parms ) {
    961 	if ( parms.GetError() == SAVEGAME_E_NONE ) {
    962 		return true;
    963 	}
    964 
    965 	common->Dialog().ShowSaveIndicator( false );
    966 
    967 	if ( parms.GetError() & SAVEGAME_E_CORRUPTED ) {
    968 		// This one might need to be handled by the game
    969 		common->Dialog().AddDialog( GDM_CORRUPT_CONTINUE, DIALOG_CONTINUE, NULL, NULL, false );
    970 
    971 		// Find the game in the enumerated details, mark as corrupt so the menus can show as corrupt
    972 		saveGameDetailsList_t & list = session->GetSaveGameManager().GetEnumeratedSavegamesNonConst();
    973 		for ( int i = 0; i < list.Num(); i++ ) {
    974 			if ( idStr::Icmp( list[i].slotName, parms.description.slotName ) == 0 ) {
    975 				list[i].damaged = true;
    976 			}
    977 		}
    978 		return true;
    979 	} else if ( parms.GetError() & SAVEGAME_E_INSUFFICIENT_ROOM ) {
    980 		HandleInsufficientStorage( parms );
    981 		return true;
    982 	} else if ( parms.GetError() & SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE && saveGame_enable.GetBool() ) {
    983 		common->Dialog().AddDialog( GDM_UNABLE_TO_USE_SELECTED_STORAGE_DEVICE, DIALOG_CONTINUE, NULL, NULL, false );
    984 		return true;
    985 	} else if ( parms.GetError() & SAVEGAME_E_INVALID_FILENAME ) {
    986 		idLib::Warning( va( "Invalid savegame filename [%s]!", parms.directory.c_str() ) );
    987 		return true;
    988 	} else if ( parms.GetError() & SAVEGAME_E_DLC_NOT_FOUND ) {
    989 		common->Dialog().AddDialog( GDM_DLC_ERROR_MISSING_GENERIC, DIALOG_CONTINUE, NULL, NULL, false );
    990 		return true;
    991 	} else if ( parms.GetError() & SAVEGAME_E_DISC_SWAP ) {
    992 		common->Dialog().AddDialog( GDM_DISC_SWAP, DIALOG_CONTINUE, NULL, NULL, false );
    993 		return true;
    994 	} else if ( parms.GetError() & SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION ) {
    995 		common->Dialog().AddDialog( GDM_INCOMPATIBLE_NEWER_SAVE, DIALOG_CONTINUE, NULL, NULL, false );
    996 		return true;
    997 	}
    998 
    999 	return false;
   1000 }
   1001 
   1002 /*
   1003 ========================
   1004 idCommonLocal::OnSaveCompleted
   1005 ========================
   1006 */
   1007 void idCommonLocal::OnSaveCompleted( idSaveLoadParms & parms ) {
   1008 	assert( pipelineFile != NULL );
   1009 	delete pipelineFile;
   1010 	pipelineFile = NULL;
   1011 
   1012 	if ( parms.GetError() == SAVEGAME_E_NONE ) {
   1013 		game->Shell_UpdateSavedGames();
   1014 	}
   1015 
   1016 	if ( !HandleCommonErrors( parms ) ) {
   1017 		common->Dialog().AddDialog( GDM_ERROR_SAVING_SAVEGAME, DIALOG_CONTINUE, NULL, NULL, false );
   1018 	}
   1019 }
   1020 
   1021 /*
   1022 ========================
   1023 idCommonLocal::OnLoadCompleted
   1024 ========================
   1025 */
   1026 void idCommonLocal::OnLoadCompleted( idSaveLoadParms & parms ) {
   1027 	if ( !HandleCommonErrors( parms ) ) {
   1028 		common->Dialog().AddDialog( GDM_ERROR_LOADING_SAVEGAME, DIALOG_CONTINUE, NULL, NULL, false );
   1029 	}
   1030 }
   1031 
   1032 /*
   1033 ========================
   1034 idCommonLocal::OnLoadFilesCompleted
   1035 ========================
   1036 */
   1037 void idCommonLocal::OnLoadFilesCompleted( idSaveLoadParms & parms ) {
   1038 	if ( ( mapSpawnData.savegameFile != NULL ) && ( parms.GetError() == SAVEGAME_E_NONE ) ) {
   1039 		// just need to make the file readable
   1040 		((idFile_Memory *)mapSpawnData.savegameFile)->MakeReadOnly();
   1041 		((idFile_Memory *)mapSpawnData.stringTableFile)->MakeReadOnly();
   1042 
   1043 		idStr gamename;
   1044 		idStr mapname;
   1045 
   1046 		mapSpawnData.savegameVersion = parms.description.GetSaveVersion();
   1047 		mapSpawnData.savegameFile->ReadString( gamename );
   1048 		mapSpawnData.savegameFile->ReadString( mapname );
   1049 
   1050 		if ( ( gamename != GAME_NAME ) || ( mapname.IsEmpty() ) || ( parms.description.GetSaveVersion() > BUILD_NUMBER ) ) {
   1051 			// if this isn't a savegame for the correct game, abort loadgame
   1052 			common->Warning( "Attempted to load an invalid savegame" );
   1053 		} else {
   1054 			common->DPrintf( "loading savegame\n" );
   1055 
   1056 			mapSpawnData.savegameFile->ReadBool( consoleUsed );
   1057 			consoleUsed = consoleUsed || com_allowConsole.GetBool();
   1058 
   1059 			idMatchParameters matchParameters;
   1060 			matchParameters.numSlots = 1;
   1061 			matchParameters.gameMode = GAME_MODE_SINGLEPLAYER;
   1062 			matchParameters.gameMap = GAME_MAP_SINGLEPLAYER;
   1063 			matchParameters.mapName = mapname;
   1064 			matchParameters.serverInfo.ReadFromFileHandle( mapSpawnData.savegameFile );
   1065 
   1066 			session->QuitMatchToTitle();
   1067 			if ( WaitForSessionState( idSession::IDLE ) ) {
   1068 				session->CreatePartyLobby( matchParameters );
   1069 				if ( WaitForSessionState( idSession::PARTY_LOBBY ) ) {
   1070 					session->CreateMatch( matchParameters );
   1071 					if ( WaitForSessionState( idSession::GAME_LOBBY ) ) {
   1072 						session->StartMatch();
   1073 						return;
   1074 					}
   1075 				}
   1076 			}
   1077 		}
   1078 	}
   1079 	// If we got here then we didn't actually load the save game for some reason
   1080 	mapSpawnData.savegameFile = NULL;
   1081 }
   1082 
   1083 /*
   1084 ========================
   1085 idCommonLocal::TriggerScreenWipe
   1086 ========================
   1087 */
   1088 void idCommonLocal::TriggerScreenWipe( const char * _wipeMaterial, bool hold ) {
   1089 	StartWipe( _wipeMaterial, hold );
   1090 	CompleteWipe();
   1091 	wipeForced = true;
   1092 	renderSystem->BeginAutomaticBackgroundSwaps( AUTORENDER_DEFAULTICON );
   1093 }
   1094 
   1095 /*
   1096 ========================
   1097 idCommonLocal::OnEnumerationCompleted
   1098 ========================
   1099 */
   1100 void idCommonLocal::OnEnumerationCompleted( idSaveLoadParms & parms ) {
   1101 	if ( parms.GetError() == SAVEGAME_E_NONE ) {
   1102 		game->Shell_UpdateSavedGames();
   1103 	}
   1104 }
   1105 
   1106 /*
   1107 ========================
   1108 idCommonLocal::OnDeleteCompleted
   1109 ========================
   1110 */
   1111 void idCommonLocal::OnDeleteCompleted( idSaveLoadParms & parms ) {
   1112 	if ( parms.GetError() == SAVEGAME_E_NONE ) {
   1113 		game->Shell_UpdateSavedGames();
   1114 	}
   1115 }
   1116 
   1117 /*
   1118 ===============
   1119 LoadGame_f
   1120 ===============
   1121 */
   1122 CONSOLE_COMMAND_SHIP( loadGame, "loads a game", idCmdSystem::ArgCompletion_SaveGame ) {
   1123 	console->Close();
   1124 	commonLocal.LoadGame( ( args.Argc() > 1 ) ? args.Argv(1) : "quick" );
   1125 }
   1126 
   1127 /*
   1128 ===============
   1129 SaveGame_f
   1130 ===============
   1131 */
   1132 CONSOLE_COMMAND_SHIP( saveGame, "saves a game", NULL ) {
   1133 	const char * savename = ( args.Argc() > 1 ) ? args.Argv(1) : "quick";
   1134 	if ( commonLocal.SaveGame( savename ) ) {
   1135 		common->Printf( "Saved: %s\n", savename );
   1136 	}
   1137 }
   1138 
   1139 /*
   1140 ==================
   1141 Common_Map_f
   1142 
   1143 Restart the server on a different map
   1144 ==================
   1145 */
   1146 CONSOLE_COMMAND_SHIP( map, "loads a map", idCmdSystem::ArgCompletion_MapName ) {
   1147 	commonLocal.StartNewGame( args.Argv(1), false, GAME_MODE_SINGLEPLAYER );
   1148 }
   1149 
   1150 /*
   1151 ==================
   1152 Common_RestartMap_f
   1153 ==================
   1154 */
   1155 CONSOLE_COMMAND_SHIP( restartMap, "restarts the current map", NULL ) {
   1156 	if ( g_demoMode.GetBool() ) {
   1157 		cmdSystem->AppendCommandText( va( "devmap %s %d\n", commonLocal.GetCurrentMapName(), 0 ) );
   1158 	}
   1159 }
   1160 
   1161 /*
   1162 ==================
   1163 Common_DevMap_f
   1164 
   1165 Restart the server on a different map in developer mode
   1166 ==================
   1167 */
   1168 CONSOLE_COMMAND_SHIP( devmap, "loads a map in developer mode", idCmdSystem::ArgCompletion_MapName ) {
   1169 	commonLocal.StartNewGame( args.Argv(1), true, GAME_MODE_SINGLEPLAYER );
   1170 }
   1171 
   1172 /*
   1173 ==================
   1174 Common_NetMap_f
   1175 
   1176 Restart the server on a different map in multiplayer mode
   1177 ==================
   1178 */
   1179 CONSOLE_COMMAND_SHIP( netmap, "loads a map in multiplayer mode", idCmdSystem::ArgCompletion_MapName ) {
   1180 	int gameMode = 0; // Default to deathmatch
   1181 	if ( args.Argc() > 2 ) {
   1182 		gameMode = atoi( args.Argv(2) );
   1183 	}
   1184 	commonLocal.StartNewGame( args.Argv(1), true, gameMode );
   1185 }
   1186 
   1187 /*
   1188 ==================
   1189 Common_TestMap_f
   1190 ==================
   1191 */
   1192 CONSOLE_COMMAND( testmap, "tests a map", idCmdSystem::ArgCompletion_MapName ) {
   1193 	idStr map, string;
   1194 
   1195 	map = args.Argv(1);
   1196 	if ( !map.Length() ) {
   1197 		return;
   1198 	}
   1199 	map.StripFileExtension();
   1200 
   1201 	cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" );
   1202 
   1203 	sprintf( string, "dmap maps/%s.map", map.c_str() );
   1204 	cmdSystem->BufferCommandText( CMD_EXEC_NOW, string );
   1205 
   1206 	sprintf( string, "devmap %s", map.c_str() );
   1207 	cmdSystem->BufferCommandText( CMD_EXEC_NOW, string );
   1208 }