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 }