/*
===========================================================================
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 "../idlib/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::LaunchExternalTitle
Launches an external title ( Doom 1, or 2 ) based on title index.
for PS3, a device number is sent in, for the game to register as a local
user by default, when title initializes.
================
*/
void idCommonLocal::LaunchExternalTitle( int titleIndex, int device, const lobbyConnectInfo_t * const connectInfo ) {
idStr deviceString( device );
// We want to pass in the current executable, so that the launching title knows which title to return to.
// as of right now, this feature is TBD.
const char * currentExecutablePath = "ImNotSureYet";
idStr launchingExecutablePath;
idCmdArgs cmdArgs;
cmdArgs.AppendArg( currentExecutablePath );
if ( titleIndex == LAUNCH_TITLE_DOOM ) {
launchingExecutablePath.Format("%s%s", Sys_DefaultBasePath(), LAUNCH_TITLE_DOOM_EXECUTABLE );
cmdArgs.AppendArg( "d1bfg" );
} else if ( titleIndex == LAUNCH_TITLE_DOOM2 ) {
launchingExecutablePath.Format("%s%s", Sys_DefaultBasePath(), LAUNCH_TITLE_DOOM2_EXECUTABLE );
cmdArgs.AppendArg( "d2bfg" );
} else {
idLib::Warning("Unhandled Launch Title %d \n", titleIndex );
}
cmdArgs.AppendArg( deviceString.c_str() );
// Add an argument so that the new process knows whether or not to read exitspawn data.
if ( connectInfo != NULL ) {
cmdArgs.AppendArg( "exitspawnInvite" );
}
// Add arguments so that the new process will know which command line to invoke to relaunch this process
// if necessary.
const int launchDataSize = ( connectInfo == NULL ) ? 0 : sizeof( *connectInfo );
Sys_Launch( launchingExecutablePath.c_str() , cmdArgs, const_cast< lobbyConnectInfo_t * const >( connectInfo ), launchDataSize );
}
/*
================
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;
}
if ( GetCurrentGame() != DOOM3_BFG ) {
return false;
}
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 ) {
if ( parms.GetError() == SAVEGAME_E_NONE ) {
return true;
}
common->Dialog().ShowSaveIndicator( false );
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 );
}