/*
===========================================================================
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 )
{
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 );
}