/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see . 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. 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 "precompiled.h" #pragma hdrstop #include "Common_local.h" #include "../sys/sys_lobby_backend.h" #define LAUNCH_TITLE_DOOM_EXECUTABLE "doom1.exe" #define LAUNCH_TITLE_DOOM2_EXECUTABLE "doom2.exe" idCVar com_wipeSeconds( "com_wipeSeconds", "1", CVAR_SYSTEM, "" ); idCVar com_disableAutoSaves( "com_disableAutoSaves", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); idCVar com_disableAllSaves( "com_disableAllSaves", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); extern idCVar sys_lang; extern idCVar g_demoMode; // This is for the dirty hack to get a dialog to show up before we capture the screen for autorender. const int NumScreenUpdatesToShowDialog = 25; /* ================ idCommonLocal::StartWipe Draws and captures the current state, then starts a wipe with that image ================ */ void idCommonLocal::StartWipe( const char* _wipeMaterial, bool hold ) { console->Close(); Draw(); renderSystem->CaptureRenderToImage( "_currentRender" ); wipeMaterial = declManager->FindMaterial( _wipeMaterial, false ); wipeStartTime = Sys_Milliseconds(); wipeStopTime = wipeStartTime + SEC2MS( com_wipeSeconds.GetFloat() ); wipeHold = hold; } /* ================ idCommonLocal::CompleteWipe ================ */ void idCommonLocal::CompleteWipe() { while( Sys_Milliseconds() < wipeStopTime ) { BusyWait(); Sys_Sleep( 10 ); } // ensure it is completely faded out wipeStopTime = Sys_Milliseconds(); BusyWait(); } /* ================ idCommonLocal::ClearWipe ================ */ void idCommonLocal::ClearWipe() { wipeHold = false; wipeStopTime = 0; wipeStartTime = 0; wipeForced = false; } /* =============== idCommonLocal::StartNewGame =============== */ void idCommonLocal::StartNewGame( const char* mapName, bool devmap, int gameMode ) { if( session->GetSignInManager().GetMasterLocalUser() == NULL ) { // For development make sure a controller is registered // Can't just register the local user because it will be removed because of it's persistent state session->GetSignInManager().SetDesiredLocalUsers( 1, 1 ); session->GetSignInManager().Pump(); } idStr mapNameClean = mapName; mapNameClean.StripFileExtension(); mapNameClean.BackSlashesToSlashes(); idMatchParameters matchParameters; matchParameters.mapName = mapNameClean; if( gameMode == GAME_MODE_SINGLEPLAYER ) { matchParameters.numSlots = 1; matchParameters.gameMode = GAME_MODE_SINGLEPLAYER; matchParameters.gameMap = GAME_MAP_SINGLEPLAYER; } else { 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) matchParameters.gameMode = gameMode; matchParameters.matchFlags = DefaultPartyFlags; for( int i = 0; i < mpGameMaps.Num(); i++ ) { if( idStr::Icmp( mpGameMaps[i].mapFile, mapNameClean ) == 0 ) { matchParameters.gameMap = i; break; } } matchParameters.numSlots = session->GetTitleStorageInt( "MAX_PLAYERS_ALLOWED", 4 ); } cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO, matchParameters.serverInfo ); if( devmap ) { matchParameters.serverInfo.Set( "devmap", "1" ); } else { matchParameters.serverInfo.Delete( "devmap" ); } session->QuitMatchToTitle(); if( WaitForSessionState( idSession::IDLE ) ) { session->CreatePartyLobby( matchParameters ); if( WaitForSessionState( idSession::PARTY_LOBBY ) ) { session->CreateMatch( matchParameters ); if( WaitForSessionState( idSession::GAME_LOBBY ) ) { cvarSystem->SetCVarBool( "developer", devmap ); session->StartMatch(); } } } } /* =============== idCommonLocal::MoveToNewMap Single player transition from one map to another =============== */ void idCommonLocal::MoveToNewMap( const char* mapName, bool devmap ) { idMatchParameters matchParameters; matchParameters.numSlots = 1; matchParameters.gameMode = GAME_MODE_SINGLEPLAYER; matchParameters.gameMap = GAME_MAP_SINGLEPLAYER; matchParameters.mapName = mapName; cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO, matchParameters.serverInfo ); if( devmap ) { matchParameters.serverInfo.Set( "devmap", "1" ); mapSpawnData.persistentPlayerInfo.Clear(); } else { matchParameters.serverInfo.Delete( "devmap" ); mapSpawnData.persistentPlayerInfo = game->GetPersistentPlayerInfo( 0 ); } session->QuitMatchToTitle(); if( WaitForSessionState( idSession::IDLE ) ) { session->CreatePartyLobby( matchParameters ); if( WaitForSessionState( idSession::PARTY_LOBBY ) ) { session->CreateMatch( matchParameters ); if( WaitForSessionState( idSession::GAME_LOBBY ) ) { session->StartMatch(); } } } } /* =============== idCommonLocal::UnloadMap Performs cleanup that needs to happen between maps, or when a game is exited. Exits with mapSpawned = false =============== */ void idCommonLocal::UnloadMap() { StopPlayingRenderDemo(); // end the current map in the game if( game ) { game->MapShutdown(); } if( writeDemo ) { StopRecordingRenderDemo(); } mapSpawned = false; } /* =============== idCommonLocal::LoadLoadingGui =============== */ void idCommonLocal::LoadLoadingGui( const char* mapName, bool& hellMap ) { defaultLoadscreen = false; loadGUI = new idSWF( "loading/default", NULL ); if( g_demoMode.GetBool() ) { hellMap = false; if( loadGUI != NULL ) { const idMaterial* defaultMat = declManager->FindMaterial( "guis/assets/loadscreens/default" ); renderSystem->LoadLevelImages(); loadGUI->Activate( true ); idSWFSpriteInstance* bgImg = loadGUI->GetRootObject().GetSprite( "bgImage" ); if( bgImg != NULL ) { bgImg->SetMaterial( defaultMat ); } } defaultLoadscreen = true; return; } // load / program a gui to stay up on the screen while loading idStrStatic< MAX_OSPATH > stripped = mapName; stripped.StripFileExtension(); stripped.StripPath(); // use default load screen for demo idStrStatic< MAX_OSPATH > matName = "guis/assets/loadscreens/"; matName.Append( stripped ); const idMaterial* mat = declManager->FindMaterial( matName ); renderSystem->LoadLevelImages(); if( mat->GetImageWidth() < 32 ) { mat = declManager->FindMaterial( "guis/assets/loadscreens/default" ); renderSystem->LoadLevelImages(); } loadTipList.SetNum( loadTipList.Max() ); for( int i = 0; i < loadTipList.Max(); ++i ) { loadTipList[i] = i; } if( loadGUI != NULL ) { loadGUI->Activate( true ); nextLoadTip = Sys_Milliseconds() + LOAD_TIP_CHANGE_INTERVAL; idSWFSpriteInstance* bgImg = loadGUI->GetRootObject().GetSprite( "bgImage" ); if( bgImg != NULL ) { bgImg->SetMaterial( mat ); } idSWFSpriteInstance* overlay = loadGUI->GetRootObject().GetSprite( "overlay" ); const idDeclEntityDef* mapDef = static_cast( declManager->FindType( DECL_MAPDEF, mapName, false ) ); if( mapDef != NULL ) { isHellMap = mapDef->dict.GetBool( "hellMap", false ); if( isHellMap && overlay != NULL ) { overlay->SetVisible( false ); } idStr desc; idStr subTitle; idStr displayName; idSWFTextInstance* txtVal = NULL; txtVal = loadGUI->GetRootObject().GetNestedText( "txtRegLoad" ); displayName = idLocalization::GetString( mapDef->dict.GetString( "name", mapName ) ); if( txtVal != NULL ) { txtVal->SetText( "#str_00408" ); txtVal->SetStrokeInfo( true, 2.0f, 1.0f ); } const idMatchParameters& matchParameters = session->GetActingGameStateLobbyBase().GetMatchParms(); if( matchParameters.gameMode == GAME_MODE_SINGLEPLAYER ) { desc = idLocalization::GetString( mapDef->dict.GetString( "desc", "" ) ); subTitle = idLocalization::GetString( mapDef->dict.GetString( "subTitle", "" ) ); } else { const idStrList& modes = common->GetModeDisplayList(); subTitle = modes[ idMath::ClampInt( 0, modes.Num() - 1, matchParameters.gameMode ) ]; const char* modeDescs[] = { "#str_swf_deathmatch_desc", "#str_swf_tourney_desc", "#str_swf_team_deathmatch_desc", "#str_swf_lastman_desc", "#str_swf_ctf_desc" }; desc = idLocalization::GetString( modeDescs[matchParameters.gameMode] ); } if( !isHellMap ) { txtVal = loadGUI->GetRootObject().GetNestedText( "txtName" ); } else { txtVal = loadGUI->GetRootObject().GetNestedText( "txtHellName" ); } if( txtVal != NULL ) { txtVal->SetText( displayName ); txtVal->SetStrokeInfo( true, 2.0f, 1.0f ); } txtVal = loadGUI->GetRootObject().GetNestedText( "txtSub" ); if( txtVal != NULL && !isHellMap ) { txtVal->SetText( subTitle ); txtVal->SetStrokeInfo( true, 1.75f, 0.75f ); } txtVal = loadGUI->GetRootObject().GetNestedText( "txtDesc" ); if( txtVal != NULL ) { if( isHellMap ) { txtVal->SetText( va( "\n%s", desc.c_str() ) ); } else { txtVal->SetText( desc ); } txtVal->SetStrokeInfo( true, 1.75f, 0.75f ); } } } } /* =============== idCommonLocal::ExecuteMapChange Performs the initialization of a game based on session match parameters, used for both single player and multiplayer, but not for renderDemos, which don't create a game at all. Exits with mapSpawned = true =============== */ void idCommonLocal::ExecuteMapChange() { if( session->GetState() != idSession::LOADING ) { idLib::Warning( "Session state is not LOADING in ExecuteMapChange" ); return; } // Clear all dialogs before beginning the load common->Dialog().ClearDialogs( true ); // Remember the current load ID. // This is so we can tell if we had a new loadmap request from within an existing loadmap call const int cachedLoadingID = session->GetLoadingID(); const idMatchParameters& matchParameters = session->GetActingGameStateLobbyBase().GetMatchParms(); if( matchParameters.numSlots <= 0 ) { idLib::Warning( "numSlots <= 0 in ExecuteMapChange" ); return; } insideExecuteMapChange = true; common->Printf( "--------- Execute Map Change ---------\n" ); common->Printf( "Map: %s\n", matchParameters.mapName.c_str() ); // ensure that r_znear is reset to the default value // this fixes issues with the projection matrix getting messed up when switching maps or loading a saved game // while an in-game cinematic is playing. cvarSystem->SetCVarFloat( "r_znear", 3.0f ); // reset all cheat cvars for a multiplayer game if( IsMultiplayer() ) { cvarSystem->ResetFlaggedVariables( CVAR_CHEAT ); } int start = Sys_Milliseconds(); for( int i = 0; i < MAX_INPUT_DEVICES; i++ ) { Sys_SetRumble( i, 0, 0 ); } // close console and remove any prints from the notify lines console->Close(); // clear all menu sounds soundWorld->Pause(); menuSoundWorld->ClearAllSoundEmitters(); soundSystem->SetPlayingSoundWorld( menuSoundWorld ); soundSystem->Render(); // extract the map name from serverinfo currentMapName = matchParameters.mapName; currentMapName.StripFileExtension(); idStrStatic< MAX_OSPATH > fullMapName = "maps/"; fullMapName += currentMapName; fullMapName.SetFileExtension( "map" ); if( mapSpawnData.savegameFile ) { fileSystem->BeginLevelLoad( currentMapName, NULL, 0 ); } else { fileSystem->BeginLevelLoad( currentMapName, saveFile.GetDataPtr(), saveFile.GetAllocated() ); } // capture the current screen and start a wipe // immediately complete the wipe to fade out the level transition // run the wipe to completion StartWipe( "wipeMaterial", true ); CompleteWipe(); int sm = Sys_Milliseconds(); // shut down the existing game if it is running UnloadMap(); int ms = Sys_Milliseconds() - sm; common->Printf( "%6d msec to unload map\n", ms ); // Free media from previous level and // note which media we are going to need to load sm = Sys_Milliseconds(); renderSystem->BeginLevelLoad(); soundSystem->BeginLevelLoad(); declManager->BeginLevelLoad(); uiManager->BeginLevelLoad(); ms = Sys_Milliseconds() - sm; common->Printf( "%6d msec to free assets\n", ms ); //Sys_DumpMemory( true ); // load / program a gui to stay up on the screen while loading // set the loading gui that we will wipe to bool hellMap = false; LoadLoadingGui( currentMapName, hellMap ); // Stop rendering the wipe ClearWipe(); if( fileSystem->UsingResourceFiles() ) { idStrStatic< MAX_OSPATH > manifestName = currentMapName; manifestName.Replace( "game/", "maps/" ); manifestName.Replace( "/mp/", "/" ); manifestName += ".preload"; idPreloadManifest manifest; manifest.LoadManifest( manifestName ); renderSystem->Preload( manifest, currentMapName ); soundSystem->Preload( manifest ); game->Preload( manifest ); } if( common->IsMultiplayer() ) { // In multiplayer, make sure the player is either 60Hz or 120Hz // to avoid potential issues. const float mpEngineHz = ( com_engineHz.GetFloat() < 90.0f ) ? 60.0f : 120.0f; com_engineHz_denominator = 100LL * mpEngineHz; com_engineHz_latched = mpEngineHz; } else { // allow com_engineHz to be changed between map loads com_engineHz_denominator = 100LL * com_engineHz.GetFloat(); com_engineHz_latched = com_engineHz.GetFloat(); } // note any warning prints that happen during the load process common->ClearWarnings( currentMapName ); // release the mouse cursor // before we do this potentially long operation Sys_GrabMouseCursor( false ); // let the renderSystem load all the geometry if( !renderWorld->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(); // load and spawn all other entities ( from a savegame possibly ) if( mapSpawnData.savegameFile ) { if( !game->InitFromSaveGame( fullMapName, renderWorld, soundWorld, mapSpawnData.savegameFile, mapSpawnData.stringTableFile, mapSpawnData.savegameVersion ) ) { // If the loadgame failed, end the session, which will force us to go back to the main menu session->QuitMatchToTitle(); } } else { if( !IsMultiplayer() ) { assert( game->GetLocalClientNum() == 0 ); assert( matchParameters.gameMode == GAME_MODE_SINGLEPLAYER ); assert( matchParameters.gameMap == GAME_MAP_SINGLEPLAYER ); game->SetPersistentPlayerInfo( 0, mapSpawnData.persistentPlayerInfo ); } game->SetServerInfo( matchParameters.serverInfo ); game->InitFromNewMap( fullMapName, renderWorld, soundWorld, matchParameters.gameMode, Sys_Milliseconds() ); } game->Shell_CreateMenu( true ); // Reset some values important to multiplayer ResetNetworkingState(); // If the session state is not loading here, something went wrong. if( session->GetState() == idSession::LOADING && session->GetLoadingID() == cachedLoadingID ) { // Notify session we are done loading session->LoadingFinished(); while( session->GetState() == idSession::LOADING ) { Sys_GenerateEvents(); session->UpdateSignInManager(); session->Pump(); Sys_Sleep( 10 ); } } if( !mapSpawnData.savegameFile ) { // run a single frame to catch any resources that are referenced by events posted in spawn idUserCmdMgr emptyCommandManager; gameReturn_t emptyGameReturn; for( int playerIndex = 0; playerIndex < MAX_PLAYERS; ++playerIndex ) { emptyCommandManager.PutUserCmdForPlayer( playerIndex, usercmd_t() ); } if( IsClient() ) { game->ClientRunFrame( emptyCommandManager, false, emptyGameReturn ); } else { game->RunFrame( emptyCommandManager, emptyGameReturn ); } } renderSystem->EndLevelLoad(); soundSystem->EndLevelLoad(); declManager->EndLevelLoad(); uiManager->EndLevelLoad( currentMapName ); fileSystem->EndLevelLoad(); if( !mapSpawnData.savegameFile && !IsMultiplayer() ) { common->Printf( "----- Running initial game frames -----\n" ); // In single player, run a bunch of frames to make sure ragdolls are settled idUserCmdMgr emptyCommandManager; gameReturn_t emptyGameReturn; for( int i = 0; i < 100; i++ ) { for( int playerIndex = 0; playerIndex < MAX_PLAYERS; ++playerIndex ) { emptyCommandManager.PutUserCmdForPlayer( playerIndex, usercmd_t() ); } game->RunFrame( emptyCommandManager, emptyGameReturn ); } // kick off an auto-save of the game (so we can always continue in this map if we die before hitting an autosave) common->Printf( "----- Saving Game -----\n" ); SaveGame( "autosave" ); } common->Printf( "----- Generating Interactions -----\n" ); // let the renderSystem generate interactions now that everything is spawned renderWorld->GenerateAllInteractions(); { int vertexMemUsedKB = vertexCache.staticData.vertexMemUsed.GetValue() / 1024; int indexMemUsedKB = vertexCache.staticData.indexMemUsed.GetValue() / 1024; idLib::Printf( "Used %dkb of static vertex memory (%d%%)\n", vertexMemUsedKB, vertexMemUsedKB * 100 / ( STATIC_VERTEX_MEMORY / 1024 ) ); idLib::Printf( "Used %dkb of static index memory (%d%%)\n", indexMemUsedKB, indexMemUsedKB * 100 / ( STATIC_INDEX_MEMORY / 1024 ) ); } if( common->JapaneseCensorship() ) { if( currentMapName.Icmp( "game/mp/d3xpdm3" ) == 0 ) { const idMaterial* gizpool2 = declManager->FindMaterial( "textures/hell/gizpool2" ); idMaterial* chiglass1bluex = const_cast( declManager->FindMaterial( "textures/sfx/chiglass1bluex" ) ); idTempArray text( gizpool2->GetTextLength() ); gizpool2->GetText( text.Ptr() ); chiglass1bluex->Parse( text.Ptr(), text.Num(), false ); } } common->PrintWarnings(); session->Pump(); if( session->GetState() != idSession::INGAME ) { // Something went wrong, don't process stale reliables that have been queued up. reliableQueue.Clear(); } usercmdGen->Clear(); // remove any prints from the notify lines console->ClearNotifyLines(); Sys_SetPhysicalWorkMemory( -1, -1 ); // at this point we should be done with the loading gui so we kill it delete loadGUI; loadGUI = NULL; // capture the current screen and start a wipe StartWipe( "wipe2Material" ); // we are valid for game draws now insideExecuteMapChange = false; mapSpawned = true; Sys_ClearEvents(); int msec = Sys_Milliseconds() - start; common->Printf( "%6d msec to load %s\n", msec, currentMapName.c_str() ); //Sys_DumpMemory( false ); // Issue a render at the very end of the load process to update soundTime before the first frame soundSystem->Render(); } /* =============== idCommonLocal::UpdateLevelLoadPacifier Pumps the session and if multiplayer, displays dialogs during the loading process. =============== */ void idCommonLocal::UpdateLevelLoadPacifier() { autoRenderIconType_t icon = AUTORENDER_DEFAULTICON; bool autoswapsRunning = renderSystem->AreAutomaticBackgroundSwapsRunning( &icon ); if( !insideExecuteMapChange && !autoswapsRunning ) { return; } const int sessionUpdateTime = common->IsMultiplayer() ? 16 : 100; const int time = Sys_Milliseconds(); // Throttle session pumps. if( time - lastPacifierSessionTime >= sessionUpdateTime ) { lastPacifierSessionTime = time; Sys_GenerateEvents(); session->UpdateSignInManager(); session->Pump(); session->ProcessSnapAckQueue(); } if( autoswapsRunning ) { // If autoswaps are running, only update if a Dialog is shown/dismissed bool dialogState = Dialog().HasAnyActiveDialog(); if( lastPacifierDialogState != dialogState ) { lastPacifierDialogState = dialogState; renderSystem->EndAutomaticBackgroundSwaps(); if( dialogState ) { icon = AUTORENDER_DIALOGICON; // Done this way to handle the rare case of a tip changing at the same time a dialog comes up for( int i = 0; i < NumScreenUpdatesToShowDialog; ++i ) { UpdateScreen( false ); } } renderSystem->BeginAutomaticBackgroundSwaps( icon ); } } else { // On the PC just update at a constant rate for the Steam overlay if( time - lastPacifierGuiTime >= 50 ) { lastPacifierGuiTime = time; UpdateScreen( false ); } } if( time >= nextLoadTip && loadGUI != NULL && loadTipList.Num() > 0 && !defaultLoadscreen ) { if( autoswapsRunning ) { renderSystem->EndAutomaticBackgroundSwaps(); } nextLoadTip = time + LOAD_TIP_CHANGE_INTERVAL; const int rnd = time % loadTipList.Num(); idStrStatic<20> tipId; tipId.Format( "#str_loadtip_%d", loadTipList[ rnd ] ); loadTipList.RemoveIndex( rnd ); idSWFTextInstance* txtVal = loadGUI->GetRootObject().GetNestedText( "txtDesc" ); if( txtVal != NULL ) { if( isHellMap ) { txtVal->SetText( va( "\n%s", idLocalization::GetString( tipId ) ) ); } else { txtVal->SetText( idLocalization::GetString( tipId ) ); } txtVal->SetStrokeInfo( true, 1.75f, 0.75f ); } UpdateScreen( false ); if( autoswapsRunning ) { renderSystem->BeginAutomaticBackgroundSwaps( icon ); } } } /* =============== idCommonLocal::ScrubSaveGameFileName Turns a bad file name into a good one or your money back =============== */ void idCommonLocal::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]; } } } /* =============== idCommonLocal::SaveGame =============== */ bool idCommonLocal::SaveGame( const char* saveName ) { if( pipelineFile != NULL ) { // We're already in the middle of a save. Leave us alone. return false; } if( com_disableAllSaves.GetBool() || ( com_disableAutoSaves.GetBool() && ( idStr::Icmp( saveName, "autosave" ) == 0 ) ) ) { return false; } if( IsMultiplayer() ) { common->Printf( "Can't save during net play.\n" ); return false; } if( mapSpawnData.savegameFile != NULL ) { return false; } const idDict& persistentPlayerInfo = game->GetPersistentPlayerInfo( 0 ); if( persistentPlayerInfo.GetInt( "health" ) <= 0 ) { common->Printf( "You must be alive to save the game\n" ); return false; } soundWorld->Pause(); soundSystem->SetPlayingSoundWorld( menuSoundWorld ); soundSystem->Render(); Dialog().ShowSaveIndicator( true ); if( insideExecuteMapChange ) { UpdateLevelLoadPacifier(); } else { // Heremake sure we pump the gui enough times to show the 'saving' dialog const bool captureToImage = false; for( int i = 0; i < NumScreenUpdatesToShowDialog; ++i ) { UpdateScreen( captureToImage ); } renderSystem->BeginAutomaticBackgroundSwaps( AUTORENDER_DIALOGICON ); } // Make sure the file is writable and the contents are cleared out (Set to write from the start of file) saveFile.MakeWritable(); saveFile.Clear( false ); stringsFile.MakeWritable(); stringsFile.Clear( false ); // Setup the save pipeline pipelineFile = new( TAG_SAVEGAMES ) idFile_SaveGamePipelined(); pipelineFile->OpenForWriting( &saveFile ); // Write SaveGame Header: // Game Name / Version / Map Name / Persistant Player Info // game const char* gamename = GAME_NAME; saveFile.WriteString( gamename ); // map saveFile.WriteString( currentMapName ); saveFile.WriteBool( consoleUsed ); game->GetServerInfo().WriteToFileHandle( &saveFile ); // let the game save its state game->SaveGame( pipelineFile, &stringsFile ); pipelineFile->Finish(); idSaveGameDetails gameDetails; game->GetSaveGameDetails( gameDetails ); gameDetails.descriptors.Set( SAVEGAME_DETAIL_FIELD_LANGUAGE, sys_lang.GetString() ); gameDetails.descriptors.SetInt( SAVEGAME_DETAIL_FIELD_CHECKSUM, ( int )gameDetails.descriptors.Checksum() ); gameDetails.slotName = saveName; ScrubSaveGameFileName( gameDetails.slotName ); saveFileEntryList_t files; files.Append( &stringsFile ); files.Append( &saveFile ); session->SaveGameSync( gameDetails.slotName, files, gameDetails ); if( !insideExecuteMapChange ) { renderSystem->EndAutomaticBackgroundSwaps(); } syncNextGameFrame = true; return true; } /* =============== idCommonLocal::LoadGame =============== */ bool idCommonLocal::LoadGame( const char* saveName ) { if( IsMultiplayer() ) { common->Printf( "Can't load during net play.\n" ); if( wipeForced ) { ClearWipe(); } return false; } // RB begin #if defined(USE_DOOMCLASSIC) if( GetCurrentGame() != DOOM3_BFG ) { return false; } #endif // RB end if( session->GetSignInManager().GetMasterLocalUser() == NULL ) { return false; } if( mapSpawnData.savegameFile != NULL ) { return false; } bool found = false; const saveGameDetailsList_t& sgdl = session->GetSaveGameManager().GetEnumeratedSavegames(); for( int i = 0; i < sgdl.Num(); i++ ) { if( sgdl[i].slotName == saveName ) { if( sgdl[i].GetLanguage() != sys_lang.GetString() ) { idStaticList< idSWFScriptFunction*, 4 > callbacks; idStaticList< idStrId, 4 > optionText; optionText.Append( idStrId( "#str_swf_continue" ) ); idStrStatic<256> langName = "#str_lang_" + sgdl[i].GetLanguage(); idStrStatic<256> msg; msg.Format( idLocalization::GetString( "#str_dlg_wrong_language" ), idLocalization::GetString( langName ) ); Dialog().AddDynamicDialog( GDM_SAVEGAME_WRONG_LANGUAGE, callbacks, optionText, true, msg, false, true ); if( wipeForced ) { ClearWipe(); } return false; } found = true; break; } } if( !found ) { common->Printf( "Could not find save '%s'\n", saveName ); if( wipeForced ) { ClearWipe(); } return false; } mapSpawnData.savegameFile = &saveFile; mapSpawnData.stringTableFile = &stringsFile; saveFileEntryList_t files; files.Append( mapSpawnData.stringTableFile ); files.Append( mapSpawnData.savegameFile ); idStr slotName = saveName; ScrubSaveGameFileName( slotName ); saveFile.Clear( false ); stringsFile.Clear( false ); saveGameHandle_t loadGameHandle = session->LoadGameSync( slotName, files ); if( loadGameHandle != 0 ) { return true; } mapSpawnData.savegameFile = NULL; if( wipeForced ) { ClearWipe(); } return false; } /* ======================== HandleInsufficientStorage ======================== */ void HandleInsufficientStorage( const idSaveLoadParms& parms ) { session->GetSaveGameManager().ShowRetySaveDialog( parms.directory, parms.requiredSpaceInBytes ); } /* ======================== HandleCommonErrors ======================== */ bool HandleCommonErrors( const idSaveLoadParms& parms ) { common->Dialog().ShowSaveIndicator( false ); if( parms.GetError() == SAVEGAME_E_NONE ) { return true; } if( parms.GetError() & SAVEGAME_E_CORRUPTED ) { // This one might need to be handled by the game common->Dialog().AddDialog( GDM_CORRUPT_CONTINUE, DIALOG_CONTINUE, NULL, NULL, false ); // Find the game in the enumerated details, mark as corrupt so the menus can show as corrupt saveGameDetailsList_t& list = session->GetSaveGameManager().GetEnumeratedSavegamesNonConst(); for( int i = 0; i < list.Num(); i++ ) { if( idStr::Icmp( list[i].slotName, parms.description.slotName ) == 0 ) { list[i].damaged = true; } } return true; } else if( parms.GetError() & SAVEGAME_E_INSUFFICIENT_ROOM ) { HandleInsufficientStorage( parms ); return true; } else if( parms.GetError() & SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE && saveGame_enable.GetBool() ) { common->Dialog().AddDialog( GDM_UNABLE_TO_USE_SELECTED_STORAGE_DEVICE, DIALOG_CONTINUE, NULL, NULL, false ); return true; } else if( parms.GetError() & SAVEGAME_E_INVALID_FILENAME ) { idLib::Warning( va( "Invalid savegame filename [%s]!", parms.directory.c_str() ) ); return true; } else if( parms.GetError() & SAVEGAME_E_DLC_NOT_FOUND ) { common->Dialog().AddDialog( GDM_DLC_ERROR_MISSING_GENERIC, DIALOG_CONTINUE, NULL, NULL, false ); return true; } else if( parms.GetError() & SAVEGAME_E_DISC_SWAP ) { common->Dialog().AddDialog( GDM_DISC_SWAP, DIALOG_CONTINUE, NULL, NULL, false ); return true; } else if( parms.GetError() & SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION ) { common->Dialog().AddDialog( GDM_INCOMPATIBLE_NEWER_SAVE, DIALOG_CONTINUE, NULL, NULL, false ); return true; } return false; } /* ======================== idCommonLocal::OnSaveCompleted ======================== */ void idCommonLocal::OnSaveCompleted( idSaveLoadParms& parms ) { assert( pipelineFile != NULL ); delete pipelineFile; pipelineFile = NULL; if( parms.GetError() == SAVEGAME_E_NONE ) { game->Shell_UpdateSavedGames(); } if( !HandleCommonErrors( parms ) ) { common->Dialog().AddDialog( GDM_ERROR_SAVING_SAVEGAME, DIALOG_CONTINUE, NULL, NULL, false ); } } /* ======================== idCommonLocal::OnLoadCompleted ======================== */ void idCommonLocal::OnLoadCompleted( idSaveLoadParms& parms ) { if( !HandleCommonErrors( parms ) ) { common->Dialog().AddDialog( GDM_ERROR_LOADING_SAVEGAME, DIALOG_CONTINUE, NULL, NULL, false ); } } /* ======================== idCommonLocal::OnLoadFilesCompleted ======================== */ void idCommonLocal::OnLoadFilesCompleted( idSaveLoadParms& parms ) { if( ( mapSpawnData.savegameFile != NULL ) && ( parms.GetError() == SAVEGAME_E_NONE ) ) { // just need to make the file readable ( ( idFile_Memory* )mapSpawnData.savegameFile )->MakeReadOnly(); ( ( idFile_Memory* )mapSpawnData.stringTableFile )->MakeReadOnly(); idStr gamename; idStr mapname; mapSpawnData.savegameVersion = parms.description.GetSaveVersion(); mapSpawnData.savegameFile->ReadString( gamename ); mapSpawnData.savegameFile->ReadString( mapname ); if( ( gamename != GAME_NAME ) || ( mapname.IsEmpty() ) || ( parms.description.GetSaveVersion() > BUILD_NUMBER ) ) { // if this isn't a savegame for the correct game, abort loadgame common->Warning( "Attempted to load an invalid savegame" ); } else { common->DPrintf( "loading savegame\n" ); mapSpawnData.savegameFile->ReadBool( consoleUsed ); consoleUsed = consoleUsed || com_allowConsole.GetBool(); idMatchParameters matchParameters; matchParameters.numSlots = 1; matchParameters.gameMode = GAME_MODE_SINGLEPLAYER; matchParameters.gameMap = GAME_MAP_SINGLEPLAYER; matchParameters.mapName = mapname; matchParameters.serverInfo.ReadFromFileHandle( mapSpawnData.savegameFile ); session->QuitMatchToTitle(); if( WaitForSessionState( idSession::IDLE ) ) { session->CreatePartyLobby( matchParameters ); if( WaitForSessionState( idSession::PARTY_LOBBY ) ) { session->CreateMatch( matchParameters ); if( WaitForSessionState( idSession::GAME_LOBBY ) ) { session->StartMatch(); return; } } } } } // If we got here then we didn't actually load the save game for some reason mapSpawnData.savegameFile = NULL; } /* ======================== idCommonLocal::TriggerScreenWipe ======================== */ void idCommonLocal::TriggerScreenWipe( const char* _wipeMaterial, bool hold ) { StartWipe( _wipeMaterial, hold ); CompleteWipe(); wipeForced = true; renderSystem->BeginAutomaticBackgroundSwaps( AUTORENDER_DEFAULTICON ); } /* ======================== idCommonLocal::OnEnumerationCompleted ======================== */ void idCommonLocal::OnEnumerationCompleted( idSaveLoadParms& parms ) { if( parms.GetError() == SAVEGAME_E_NONE ) { game->Shell_UpdateSavedGames(); } } /* ======================== idCommonLocal::OnDeleteCompleted ======================== */ void idCommonLocal::OnDeleteCompleted( idSaveLoadParms& parms ) { if( parms.GetError() == SAVEGAME_E_NONE ) { game->Shell_UpdateSavedGames(); } } /* =============== LoadGame_f =============== */ CONSOLE_COMMAND_SHIP( loadGame, "loads a game", idCmdSystem::ArgCompletion_SaveGame ) { console->Close(); commonLocal.LoadGame( ( args.Argc() > 1 ) ? args.Argv( 1 ) : "quick" ); } /* =============== SaveGame_f =============== */ CONSOLE_COMMAND_SHIP( saveGame, "saves a game", NULL ) { const char* savename = ( args.Argc() > 1 ) ? args.Argv( 1 ) : "quick"; if( commonLocal.SaveGame( savename ) ) { common->Printf( "Saved: %s\n", savename ); } } /* ================== Common_Map_f Restart the server on a different map ================== */ CONSOLE_COMMAND_SHIP( map, "loads a map", idCmdSystem::ArgCompletion_MapName ) { commonLocal.StartNewGame( args.Argv( 1 ), false, GAME_MODE_SINGLEPLAYER ); } /* ================== Common_RestartMap_f ================== */ CONSOLE_COMMAND_SHIP( restartMap, "restarts the current map", NULL ) { if( g_demoMode.GetBool() ) { cmdSystem->AppendCommandText( va( "devmap %s %d\n", commonLocal.GetCurrentMapName(), 0 ) ); } } /* ================== Common_DevMap_f Restart the server on a different map in developer mode ================== */ CONSOLE_COMMAND_SHIP( devmap, "loads a map in developer mode", idCmdSystem::ArgCompletion_MapName ) { commonLocal.StartNewGame( args.Argv( 1 ), true, GAME_MODE_SINGLEPLAYER ); } /* ================== Common_NetMap_f Restart the server on a different map in multiplayer mode ================== */ CONSOLE_COMMAND_SHIP( netmap, "loads a map in multiplayer mode", idCmdSystem::ArgCompletion_MapName ) { int gameMode = 0; // Default to deathmatch if( args.Argc() > 2 ) { gameMode = atoi( args.Argv( 2 ) ); } commonLocal.StartNewGame( args.Argv( 1 ), true, gameMode ); } /* ================== Common_TestMap_f ================== */ CONSOLE_COMMAND( testmap, "tests a map", idCmdSystem::ArgCompletion_MapName ) { 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 ); }