mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-25 22:01:09 +00:00
cf5d10f4e6
also, only generate pseudo-mouse-move events for gamecode (by modifying idUsercmdGenLocal::continuousMouseX/Y) when an interactive ingame UI is active
3441 lines
91 KiB
C++
3441 lines
91 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 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 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 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 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 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 "sys/platform.h"
|
|
#include "idlib/hashing/CRC32.h"
|
|
#include "idlib/LangDict.h"
|
|
#include "framework/async/AsyncNetwork.h"
|
|
#include "framework/Console.h"
|
|
#include "framework/Game.h"
|
|
#include "framework/EventLoop.h"
|
|
#include "renderer/ModelManager.h"
|
|
|
|
#include "framework/Session_local.h"
|
|
|
|
#if defined(__AROS__)
|
|
#define CDKEY_FILEPATH CDKEY_FILE
|
|
#define XPKEY_FILEPATH XPKEY_FILE
|
|
#else
|
|
#define CDKEY_FILEPATH "../" BASE_GAMEDIR "/" CDKEY_FILE
|
|
#define XPKEY_FILEPATH "../" BASE_GAMEDIR "/" XPKEY_FILE
|
|
#endif
|
|
|
|
idCVar idSessionLocal::com_showAngles( "com_showAngles", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
|
|
idCVar idSessionLocal::com_minTics( "com_minTics", "1", CVAR_SYSTEM, "" );
|
|
idCVar idSessionLocal::com_showTics( "com_showTics", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
|
|
idCVar idSessionLocal::com_fixedTic( "com_fixedTic", "0", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE, "", -1, 10 );
|
|
idCVar idSessionLocal::com_showDemo( "com_showDemo", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
|
|
idCVar idSessionLocal::com_skipGameDraw( "com_skipGameDraw", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
|
|
idCVar idSessionLocal::com_aviDemoSamples( "com_aviDemoSamples", "16", CVAR_SYSTEM, "" );
|
|
idCVar idSessionLocal::com_aviDemoWidth( "com_aviDemoWidth", "256", CVAR_SYSTEM, "" );
|
|
idCVar idSessionLocal::com_aviDemoHeight( "com_aviDemoHeight", "256", CVAR_SYSTEM, "" );
|
|
idCVar idSessionLocal::com_aviDemoTics( "com_aviDemoTics", "2", CVAR_SYSTEM | CVAR_INTEGER, "", 1, 60 );
|
|
idCVar idSessionLocal::com_wipeSeconds( "com_wipeSeconds", "1", CVAR_SYSTEM, "" );
|
|
idCVar idSessionLocal::com_guid( "com_guid", "", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_ROM, "" );
|
|
|
|
idCVar idSessionLocal::com_numQuicksaves( "com_numQuicksaves", "4", CVAR_SYSTEM|CVAR_ARCHIVE|CVAR_INTEGER,
|
|
"number of quicksaves to keep before overwriting the oldest", 1, 99 );
|
|
|
|
idSessionLocal sessLocal;
|
|
idSession *session = &sessLocal;
|
|
|
|
// these must be kept up to date with window Levelshot in guis/mainmenu.gui
|
|
const int PREVIEW_X = 211;
|
|
const int PREVIEW_Y = 31;
|
|
const int PREVIEW_WIDTH = 398;
|
|
const int PREVIEW_HEIGHT = 298;
|
|
|
|
void RandomizeStack( void ) {
|
|
// attempt to force uninitialized stack memory bugs
|
|
int bytes = 4000000;
|
|
byte *buf = (byte *)_alloca( bytes );
|
|
|
|
int fill = rand()&255;
|
|
for ( int i = 0 ; i < bytes ; i++ ) {
|
|
buf[i] = fill;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Session_RescanSI_f
|
|
=================
|
|
*/
|
|
void Session_RescanSI_f( const idCmdArgs &args ) {
|
|
sessLocal.mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
|
|
if ( game && idAsyncNetwork::server.IsActive() ) {
|
|
game->SetServerInfo( sessLocal.mapSpawnData.serverInfo );
|
|
}
|
|
}
|
|
|
|
#ifndef ID_DEDICATED
|
|
/*
|
|
==================
|
|
Session_Map_f
|
|
|
|
Restart the server on a different map
|
|
==================
|
|
*/
|
|
static void Session_Map_f( const idCmdArgs &args ) {
|
|
idStr map, string;
|
|
findFile_t ff;
|
|
idCmdArgs rl_args;
|
|
|
|
map = args.Argv(1);
|
|
if ( !map.Length() ) {
|
|
return;
|
|
}
|
|
map.StripFileExtension();
|
|
|
|
// make sure the level exists before trying to change, so that
|
|
// a typo at the server console won't end the game
|
|
// handle addon packs through reloadEngine
|
|
sprintf( string, "maps/%s.map", map.c_str() );
|
|
ff = fileSystem->FindFile( string, true );
|
|
switch ( ff ) {
|
|
case FIND_NO:
|
|
common->Printf( "Can't find map %s\n", string.c_str() );
|
|
return;
|
|
case FIND_ADDON:
|
|
common->Printf( "map %s is in an addon pak - reloading\n", string.c_str() );
|
|
rl_args.AppendArg( "map" );
|
|
rl_args.AppendArg( map );
|
|
cmdSystem->SetupReloadEngine( rl_args );
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cvarSystem->SetCVarBool( "developer", false );
|
|
sessLocal.StartNewGame( map, true );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Session_DevMap_f
|
|
|
|
Restart the server on a different map in developer mode
|
|
==================
|
|
*/
|
|
static void Session_DevMap_f( const idCmdArgs &args ) {
|
|
idStr map, string;
|
|
findFile_t ff;
|
|
idCmdArgs rl_args;
|
|
|
|
map = args.Argv(1);
|
|
if ( !map.Length() ) {
|
|
return;
|
|
}
|
|
map.StripFileExtension();
|
|
|
|
// make sure the level exists before trying to change, so that
|
|
// a typo at the server console won't end the game
|
|
// handle addon packs through reloadEngine
|
|
sprintf( string, "maps/%s.map", map.c_str() );
|
|
ff = fileSystem->FindFile( string, true );
|
|
switch ( ff ) {
|
|
case FIND_NO:
|
|
common->Printf( "Can't find map %s\n", string.c_str() );
|
|
return;
|
|
case FIND_ADDON:
|
|
common->Printf( "map %s is in an addon pak - reloading\n", string.c_str() );
|
|
rl_args.AppendArg( "devmap" );
|
|
rl_args.AppendArg( map );
|
|
cmdSystem->SetupReloadEngine( rl_args );
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cvarSystem->SetCVarBool( "developer", true );
|
|
sessLocal.StartNewGame( map, true );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Session_TestMap_f
|
|
==================
|
|
*/
|
|
static void Session_TestMap_f( const idCmdArgs &args ) {
|
|
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 );
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
==================
|
|
Sess_WritePrecache_f
|
|
==================
|
|
*/
|
|
static void Sess_WritePrecache_f( const idCmdArgs &args ) {
|
|
if ( args.Argc() != 2 ) {
|
|
common->Printf( "USAGE: writePrecache <execFile>\n" );
|
|
return;
|
|
}
|
|
idStr str = args.Argv(1);
|
|
str.DefaultFileExtension( ".cfg" );
|
|
idFile *f = fileSystem->OpenFileWrite( str, "fs_configpath" );
|
|
declManager->WritePrecacheCommands( f );
|
|
renderModelManager->WritePrecacheCommands( f );
|
|
uiManager->WritePrecacheCommands( f );
|
|
|
|
fileSystem->CloseFile( f );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::MaybeWaitOnCDKey
|
|
===============
|
|
*/
|
|
bool idSessionLocal::MaybeWaitOnCDKey( void ) {
|
|
if ( authEmitTimeout > 0 ) {
|
|
authWaitBox = true;
|
|
sessLocal.MessageBox( MSG_WAIT, common->GetLanguageDict()->GetString( "#str_07191" ), NULL, true, NULL, NULL, true );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Session_PromptKey_f
|
|
===================
|
|
*/
|
|
static void Session_PromptKey_f( const idCmdArgs &args ) {
|
|
const char *retkey;
|
|
bool valid[ 2 ];
|
|
static bool recursed = false;
|
|
|
|
if ( recursed ) {
|
|
common->Warning( "promptKey recursed - aborted" );
|
|
return;
|
|
}
|
|
recursed = true;
|
|
|
|
do {
|
|
// in case we're already waiting for an auth to come back to us ( may happen exceptionally )
|
|
if ( sessLocal.MaybeWaitOnCDKey() ) {
|
|
if ( sessLocal.CDKeysAreValid( true ) ) {
|
|
recursed = false;
|
|
return;
|
|
}
|
|
}
|
|
// the auth server may have replied and set an error message, otherwise use a default
|
|
const char *prompt_msg = sessLocal.GetAuthMsg();
|
|
if ( prompt_msg[ 0 ] == '\0' ) {
|
|
prompt_msg = common->GetLanguageDict()->GetString( "#str_04308" );
|
|
}
|
|
retkey = sessLocal.MessageBox( MSG_CDKEY, prompt_msg, common->GetLanguageDict()->GetString( "#str_04305" ), true, NULL, NULL, true );
|
|
if ( retkey ) {
|
|
if ( sessLocal.CheckKey( retkey, false, valid ) ) {
|
|
// if all went right, then we may have sent an auth request to the master ( unless the prompt is used during a net connect )
|
|
bool canExit = true;
|
|
if ( sessLocal.MaybeWaitOnCDKey() ) {
|
|
// wait on auth reply, and got denied, prompt again
|
|
if ( !sessLocal.CDKeysAreValid( true ) ) {
|
|
// server says key is invalid - MaybeWaitOnCDKey was interrupted by a CDKeysAuthReply call, which has set the right error message
|
|
// the invalid keys have also been cleared in the process
|
|
sessLocal.MessageBox( MSG_OK, sessLocal.GetAuthMsg(), common->GetLanguageDict()->GetString( "#str_04310" ), true, NULL, NULL, true );
|
|
canExit = false;
|
|
}
|
|
}
|
|
if ( canExit ) {
|
|
// make sure that's saved on file
|
|
sessLocal.WriteCDKey();
|
|
sessLocal.MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_04307" ), common->GetLanguageDict()->GetString( "#str_04305" ), true, NULL, NULL, true );
|
|
break;
|
|
}
|
|
} else {
|
|
// offline check sees key invalid
|
|
// build a message about keys being wrong. do not attempt to change the current key state though
|
|
// ( the keys may be valid, but user would have clicked on the dialog anyway, that kind of thing )
|
|
idStr msg;
|
|
idAsyncNetwork::BuildInvalidKeyMsg( msg, valid );
|
|
sessLocal.MessageBox( MSG_OK, msg, common->GetLanguageDict()->GetString( "#str_04310" ), true, NULL, NULL, true );
|
|
}
|
|
} else if ( args.Argc() == 2 && idStr::Icmp( args.Argv(1), "force" ) == 0 ) {
|
|
// cancelled in force mode
|
|
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" );
|
|
cmdSystem->ExecuteCommandBuffer();
|
|
}
|
|
} while ( retkey );
|
|
recursed = false;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
SESSION LOCAL
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::Clear
|
|
===============
|
|
*/
|
|
void idSessionLocal::Clear() {
|
|
|
|
insideUpdateScreen = false;
|
|
insideExecuteMapChange = false;
|
|
|
|
loadingSaveGame = false;
|
|
savegameFile = NULL;
|
|
savegameVersion = 0;
|
|
|
|
currentMapName.Clear();
|
|
aviDemoShortName.Clear();
|
|
msgFireBack[ 0 ].Clear();
|
|
msgFireBack[ 1 ].Clear();
|
|
|
|
timeHitch = 0;
|
|
|
|
rw = NULL;
|
|
sw = NULL;
|
|
menuSoundWorld = NULL;
|
|
readDemo = NULL;
|
|
writeDemo = NULL;
|
|
renderdemoVersion = 0;
|
|
cmdDemoFile = NULL;
|
|
|
|
syncNextGameFrame = false;
|
|
mapSpawned = false;
|
|
guiActive = NULL;
|
|
aviCaptureMode = false;
|
|
timeDemo = TD_NO;
|
|
waitingOnBind = false;
|
|
lastPacifierTime = 0;
|
|
|
|
msgRunning = false;
|
|
guiMsgRestore = NULL;
|
|
msgIgnoreButtons = false;
|
|
|
|
bytesNeededForMapLoad = 0;
|
|
|
|
#if ID_CONSOLE_LOCK
|
|
emptyDrawCount = 0;
|
|
#endif
|
|
ClearWipe();
|
|
|
|
loadGameList.Clear();
|
|
modsList.Clear();
|
|
|
|
authEmitTimeout = 0;
|
|
authWaitBox = false;
|
|
|
|
authMsg.Clear();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::idSessionLocal
|
|
===============
|
|
*/
|
|
idSessionLocal::idSessionLocal() {
|
|
guiInGame = guiMainMenu = guiIntro \
|
|
= guiRestartMenu = guiLoading = guiGameOver = guiActive \
|
|
= guiTest = guiMsg = guiMsgRestore = guiTakeNotes = NULL;
|
|
|
|
menuSoundWorld = NULL;
|
|
|
|
demoversion=false;
|
|
|
|
Clear();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::~idSessionLocal
|
|
===============
|
|
*/
|
|
idSessionLocal::~idSessionLocal() {
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::Stop
|
|
|
|
called on errors and game exits
|
|
===============
|
|
*/
|
|
void idSessionLocal::Stop() {
|
|
ClearWipe();
|
|
|
|
// clear mapSpawned and demo playing flags
|
|
UnloadMap();
|
|
|
|
// disconnect async client
|
|
idAsyncNetwork::client.DisconnectFromServer();
|
|
|
|
// kill async server
|
|
idAsyncNetwork::server.Kill();
|
|
|
|
if ( sw ) {
|
|
sw->StopAllSounds();
|
|
}
|
|
|
|
insideUpdateScreen = false;
|
|
insideExecuteMapChange = false;
|
|
|
|
// drop all guis
|
|
SetGUI( NULL, NULL );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::Shutdown
|
|
===============
|
|
*/
|
|
void idSessionLocal::Shutdown() {
|
|
int i;
|
|
|
|
if ( aviCaptureMode ) {
|
|
EndAVICapture();
|
|
}
|
|
|
|
if(timeDemo == TD_YES) {
|
|
// else the game freezes when showing the timedemo results
|
|
timeDemo = TD_YES_THEN_QUIT;
|
|
}
|
|
|
|
Stop();
|
|
|
|
if ( rw ) {
|
|
delete rw;
|
|
rw = NULL;
|
|
}
|
|
|
|
if ( sw ) {
|
|
delete sw;
|
|
sw = NULL;
|
|
}
|
|
|
|
if ( menuSoundWorld ) {
|
|
delete menuSoundWorld;
|
|
menuSoundWorld = NULL;
|
|
}
|
|
|
|
mapSpawnData.serverInfo.Clear();
|
|
mapSpawnData.syncedCVars.Clear();
|
|
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
|
|
mapSpawnData.userInfo[i].Clear();
|
|
mapSpawnData.persistentPlayerInfo[i].Clear();
|
|
}
|
|
|
|
if ( guiMainMenu_MapList != NULL ) {
|
|
guiMainMenu_MapList->Shutdown();
|
|
uiManager->FreeListGUI( guiMainMenu_MapList );
|
|
guiMainMenu_MapList = NULL;
|
|
}
|
|
|
|
Clear();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::IsMultiplayer
|
|
===============
|
|
*/
|
|
bool idSessionLocal::IsMultiplayer() {
|
|
return idAsyncNetwork::IsActive();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::StartWipe
|
|
|
|
Draws and captures the current state, then starts a wipe with that image
|
|
================
|
|
*/
|
|
void idSessionLocal::StartWipe( const char *_wipeMaterial, bool hold ) {
|
|
console->Close();
|
|
|
|
// render the current screen into a texture for the wipe model
|
|
renderSystem->CropRenderSize( 640, 480, true );
|
|
|
|
Draw();
|
|
|
|
renderSystem->CaptureRenderToImage( "_scratch");
|
|
renderSystem->UnCrop();
|
|
|
|
wipeMaterial = declManager->FindMaterial( _wipeMaterial, false );
|
|
|
|
wipeStartTic = com_ticNumber;
|
|
wipeStopTic = wipeStartTic + 1000.0f / USERCMD_MSEC * com_wipeSeconds.GetFloat();
|
|
wipeHold = hold;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::CompleteWipe
|
|
================
|
|
*/
|
|
void idSessionLocal::CompleteWipe() {
|
|
if ( com_ticNumber == 0 ) {
|
|
// if the async thread hasn't started, we would hang here
|
|
wipeStopTic = 0;
|
|
UpdateScreen( true );
|
|
return;
|
|
}
|
|
while ( com_ticNumber < wipeStopTic ) {
|
|
#if ID_CONSOLE_LOCK
|
|
emptyDrawCount = 0;
|
|
#endif
|
|
UpdateScreen( true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::ShowLoadingGui
|
|
================
|
|
*/
|
|
void idSessionLocal::ShowLoadingGui() {
|
|
if ( com_ticNumber == 0 ) {
|
|
return;
|
|
}
|
|
console->Close();
|
|
|
|
// introduced in D3XP code. don't think it actually fixes anything, but doesn't hurt either
|
|
#if 1
|
|
// Try and prevent the while loop from being skipped over (long hitch on the main thread?)
|
|
int stop = Sys_Milliseconds() + 1000;
|
|
int force = 10;
|
|
while ( Sys_Milliseconds() < stop || force-- > 0 ) {
|
|
com_frameTime = com_ticNumber * USERCMD_MSEC;
|
|
session->Frame();
|
|
session->UpdateScreen( false );
|
|
}
|
|
#else
|
|
int stop = com_ticNumber + 1000.0f / USERCMD_MSEC * 1.0f;
|
|
while ( com_ticNumber < stop ) {
|
|
com_frameTime = com_ticNumber * USERCMD_MSEC;
|
|
session->Frame();
|
|
session->UpdateScreen( false );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::ClearWipe
|
|
================
|
|
*/
|
|
void idSessionLocal::ClearWipe( void ) {
|
|
wipeHold = false;
|
|
wipeStopTic = 0;
|
|
wipeStartTic = wipeStopTic + 1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_TestGUI_f
|
|
================
|
|
*/
|
|
static void Session_TestGUI_f( const idCmdArgs &args ) {
|
|
sessLocal.TestGUI( args.Argv(1) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::TestGUI
|
|
================
|
|
*/
|
|
void idSessionLocal::TestGUI( const char *guiName ) {
|
|
if ( guiName && *guiName ) {
|
|
guiTest = uiManager->FindGui( guiName, true, false, true );
|
|
} else {
|
|
guiTest = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
FindUnusedFileName
|
|
================
|
|
*/
|
|
static idStr FindUnusedFileName( const char *format ) {
|
|
int i;
|
|
char filename[1024];
|
|
|
|
for ( i = 0 ; i < 999 ; i++ ) {
|
|
sprintf( filename, format, i );
|
|
int len = fileSystem->ReadFile( filename, NULL, NULL );
|
|
if ( len <= 0 ) {
|
|
return filename; // file doesn't exist
|
|
}
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_DemoShot_f
|
|
================
|
|
*/
|
|
static void Session_DemoShot_f( const idCmdArgs &args ) {
|
|
if ( args.Argc() != 2 ) {
|
|
idStr filename = FindUnusedFileName( "demos/shot%03i.demo" );
|
|
sessLocal.DemoShot( filename );
|
|
} else {
|
|
sessLocal.DemoShot( va( "demos/shot_%s.demo", args.Argv(1) ) );
|
|
}
|
|
}
|
|
|
|
#ifndef ID_DEDICATED
|
|
/*
|
|
================
|
|
Session_RecordDemo_f
|
|
================
|
|
*/
|
|
static void Session_RecordDemo_f( const idCmdArgs &args ) {
|
|
if ( args.Argc() != 2 ) {
|
|
idStr filename = FindUnusedFileName( "demos/demo%03i.demo" );
|
|
sessLocal.StartRecordingRenderDemo( filename );
|
|
} else {
|
|
sessLocal.StartRecordingRenderDemo( va( "demos/%s.demo", args.Argv(1) ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_CompressDemo_f
|
|
================
|
|
*/
|
|
static void Session_CompressDemo_f( const idCmdArgs &args ) {
|
|
if ( args.Argc() == 2 ) {
|
|
sessLocal.CompressDemoFile( "2", args.Argv(1) );
|
|
} else if ( args.Argc() == 3 ) {
|
|
sessLocal.CompressDemoFile( args.Argv(2), args.Argv(1) );
|
|
} else {
|
|
common->Printf("use: CompressDemo <file> [scheme]\nscheme is the same as com_compressDemo, defaults to 2" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_StopRecordingDemo_f
|
|
================
|
|
*/
|
|
static void Session_StopRecordingDemo_f( const idCmdArgs &args ) {
|
|
sessLocal.StopRecordingRenderDemo();
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_PlayDemo_f
|
|
================
|
|
*/
|
|
static void Session_PlayDemo_f( const idCmdArgs &args ) {
|
|
if ( args.Argc() >= 2 ) {
|
|
sessLocal.StartPlayingRenderDemo( va( "demos/%s", args.Argv(1) ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_TimeDemo_f
|
|
================
|
|
*/
|
|
static void Session_TimeDemo_f( const idCmdArgs &args ) {
|
|
if ( args.Argc() >= 2 ) {
|
|
sessLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ), ( args.Argc() > 2 ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_TimeDemoQuit_f
|
|
================
|
|
*/
|
|
static void Session_TimeDemoQuit_f( const idCmdArgs &args ) {
|
|
sessLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ) );
|
|
if ( sessLocal.timeDemo == TD_YES ) {
|
|
// this allows hardware vendors to automate some testing
|
|
sessLocal.timeDemo = TD_YES_THEN_QUIT;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_AVIDemo_f
|
|
================
|
|
*/
|
|
static void Session_AVIDemo_f( const idCmdArgs &args ) {
|
|
sessLocal.AVIRenderDemo( va( "demos/%s", args.Argv(1) ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_AVIGame_f
|
|
================
|
|
*/
|
|
static void Session_AVIGame_f( const idCmdArgs &args ) {
|
|
sessLocal.AVIGame( args.Argv(1) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_AVICmdDemo_f
|
|
================
|
|
*/
|
|
static void Session_AVICmdDemo_f( const idCmdArgs &args ) {
|
|
sessLocal.AVICmdDemo( args.Argv(1) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_WriteCmdDemo_f
|
|
================
|
|
*/
|
|
static void Session_WriteCmdDemo_f( const idCmdArgs &args ) {
|
|
if ( args.Argc() == 1 ) {
|
|
idStr filename = FindUnusedFileName( "demos/cmdDemo%03i.cdemo" );
|
|
sessLocal.WriteCmdDemo( filename );
|
|
} else if ( args.Argc() == 2 ) {
|
|
sessLocal.WriteCmdDemo( va( "demos/%s.cdemo", args.Argv( 1 ) ) );
|
|
} else {
|
|
common->Printf( "usage: writeCmdDemo [demoName]\n" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_PlayCmdDemo_f
|
|
================
|
|
*/
|
|
static void Session_PlayCmdDemo_f( const idCmdArgs &args ) {
|
|
sessLocal.StartPlayingCmdDemo( args.Argv(1) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
Session_TimeCmdDemo_f
|
|
================
|
|
*/
|
|
static void Session_TimeCmdDemo_f( const idCmdArgs &args ) {
|
|
sessLocal.TimeCmdDemo( args.Argv(1) );
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
================
|
|
Session_Disconnect_f
|
|
================
|
|
*/
|
|
static void Session_Disconnect_f( const idCmdArgs &args ) {
|
|
sessLocal.Stop();
|
|
sessLocal.StartMenu();
|
|
if ( soundSystem ) {
|
|
soundSystem->SetMute( false );
|
|
}
|
|
}
|
|
|
|
#ifndef ID_DEDICATED
|
|
/*
|
|
================
|
|
Session_ExitCmdDemo_f
|
|
================
|
|
*/
|
|
static void Session_ExitCmdDemo_f( const idCmdArgs &args ) {
|
|
if ( !sessLocal.cmdDemoFile ) {
|
|
common->Printf( "not reading from a cmdDemo\n" );
|
|
return;
|
|
}
|
|
fileSystem->CloseFile( sessLocal.cmdDemoFile );
|
|
common->Printf( "Command demo exited at logIndex %i\n", sessLocal.logIndex );
|
|
sessLocal.cmdDemoFile = NULL;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::StartRecordingRenderDemo
|
|
================
|
|
*/
|
|
void idSessionLocal::StartRecordingRenderDemo( const char *demoName ) {
|
|
if ( writeDemo ) {
|
|
// allow it to act like a toggle
|
|
StopRecordingRenderDemo();
|
|
return;
|
|
}
|
|
|
|
if ( !demoName[0] ) {
|
|
common->Printf( "idSessionLocal::StartRecordingRenderDemo: no name specified\n" );
|
|
return;
|
|
}
|
|
|
|
console->Close();
|
|
|
|
writeDemo = new idDemoFile;
|
|
if ( !writeDemo->OpenForWriting( demoName ) ) {
|
|
common->Printf( "error opening %s\n", demoName );
|
|
delete writeDemo;
|
|
writeDemo = NULL;
|
|
return;
|
|
}
|
|
|
|
common->Printf( "recording to %s\n", writeDemo->GetName() );
|
|
|
|
writeDemo->WriteInt( DS_VERSION );
|
|
writeDemo->WriteInt( RENDERDEMO_VERSION );
|
|
|
|
// if we are in a map already, dump the current state
|
|
sw->StartWritingDemo( writeDemo );
|
|
rw->StartWritingDemo( writeDemo );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::StopRecordingRenderDemo
|
|
================
|
|
*/
|
|
void idSessionLocal::StopRecordingRenderDemo() {
|
|
if ( !writeDemo ) {
|
|
common->Printf( "idSessionLocal::StopRecordingRenderDemo: not recording\n" );
|
|
return;
|
|
}
|
|
sw->StopWritingDemo();
|
|
rw->StopWritingDemo();
|
|
|
|
writeDemo->Close();
|
|
common->Printf( "stopped recording %s.\n", writeDemo->GetName() );
|
|
delete writeDemo;
|
|
writeDemo = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::StopPlayingRenderDemo
|
|
|
|
Reports timeDemo numbers and finishes any avi recording
|
|
================
|
|
*/
|
|
void idSessionLocal::StopPlayingRenderDemo() {
|
|
if ( !readDemo ) {
|
|
timeDemo = TD_NO;
|
|
return;
|
|
}
|
|
|
|
// Record the stop time before doing anything that could be time consuming
|
|
int timeDemoStopTime = Sys_Milliseconds();
|
|
|
|
EndAVICapture();
|
|
|
|
readDemo->Close();
|
|
|
|
sw->StopAllSounds();
|
|
soundSystem->SetPlayingSoundWorld( menuSoundWorld );
|
|
|
|
common->Printf( "stopped playing %s.\n", readDemo->GetName() );
|
|
delete readDemo;
|
|
readDemo = NULL;
|
|
|
|
if ( timeDemo ) {
|
|
// report the stats
|
|
float demoSeconds = ( timeDemoStopTime - timeDemoStartTime ) * 0.001f;
|
|
float demoFPS = numDemoFrames / demoSeconds;
|
|
idStr message = va( "%i frames rendered in %3.1f seconds = %3.1f fps\n", numDemoFrames, demoSeconds, demoFPS );
|
|
|
|
common->Printf( "%s", message.c_str() );
|
|
if ( timeDemo == TD_YES_THEN_QUIT ) {
|
|
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" );
|
|
} else {
|
|
soundSystem->SetMute( true );
|
|
MessageBox( MSG_OK, message, "Time Demo Results", true );
|
|
soundSystem->SetMute( false );
|
|
}
|
|
timeDemo = TD_NO;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::DemoShot
|
|
|
|
A demoShot is a single frame demo
|
|
================
|
|
*/
|
|
void idSessionLocal::DemoShot( const char *demoName ) {
|
|
StartRecordingRenderDemo( demoName );
|
|
|
|
// force draw one frame
|
|
UpdateScreen();
|
|
|
|
StopRecordingRenderDemo();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::StartPlayingRenderDemo
|
|
================
|
|
*/
|
|
void idSessionLocal::StartPlayingRenderDemo( idStr demoName ) {
|
|
if ( !demoName[0] ) {
|
|
common->Printf( "idSessionLocal::StartPlayingRenderDemo: no name specified\n" );
|
|
return;
|
|
}
|
|
|
|
// make sure localSound / GUI intro music shuts up
|
|
sw->StopAllSounds();
|
|
sw->PlayShaderDirectly( "", 0 );
|
|
menuSoundWorld->StopAllSounds();
|
|
menuSoundWorld->PlayShaderDirectly( "", 0 );
|
|
|
|
// exit any current game
|
|
Stop();
|
|
|
|
// automatically put the console away
|
|
console->Close();
|
|
|
|
// bring up the loading screen manually, since demos won't
|
|
// call ExecuteMapChange()
|
|
guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true );
|
|
guiLoading->SetStateString( "demo", common->GetLanguageDict()->GetString( "#str_02087" ) );
|
|
readDemo = new idDemoFile;
|
|
demoName.DefaultFileExtension( ".demo" );
|
|
if ( !readDemo->OpenForReading( demoName ) ) {
|
|
common->Printf( "couldn't open %s\n", demoName.c_str() );
|
|
delete readDemo;
|
|
readDemo = NULL;
|
|
Stop();
|
|
StartMenu();
|
|
soundSystem->SetMute( false );
|
|
return;
|
|
}
|
|
|
|
insideExecuteMapChange = true;
|
|
UpdateScreen();
|
|
insideExecuteMapChange = false;
|
|
guiLoading->SetStateString( "demo", "" );
|
|
|
|
// setup default render demo settings
|
|
// that's default for <= Doom3 v1.1
|
|
renderdemoVersion = 1;
|
|
savegameVersion = 16;
|
|
|
|
AdvanceRenderDemo( true );
|
|
|
|
numDemoFrames = 1;
|
|
|
|
lastDemoTic = -1;
|
|
timeDemoStartTime = Sys_Milliseconds();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::TimeRenderDemo
|
|
================
|
|
*/
|
|
void idSessionLocal::TimeRenderDemo( const char *demoName, bool twice ) {
|
|
idStr demo = demoName;
|
|
|
|
// no sound in time demos
|
|
soundSystem->SetMute( true );
|
|
|
|
StartPlayingRenderDemo( demo );
|
|
|
|
if ( twice && readDemo ) {
|
|
// cycle through once to precache everything
|
|
guiLoading->SetStateString( "demo", common->GetLanguageDict()->GetString( "#str_04852" ) );
|
|
guiLoading->StateChanged( com_frameTime );
|
|
while ( readDemo ) {
|
|
insideExecuteMapChange = true;
|
|
UpdateScreen();
|
|
insideExecuteMapChange = false;
|
|
AdvanceRenderDemo( true );
|
|
}
|
|
guiLoading->SetStateString( "demo", "" );
|
|
StartPlayingRenderDemo( demo );
|
|
}
|
|
|
|
|
|
if ( !readDemo ) {
|
|
return;
|
|
}
|
|
|
|
timeDemo = TD_YES;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::BeginAVICapture
|
|
================
|
|
*/
|
|
void idSessionLocal::BeginAVICapture( const char *demoName ) {
|
|
idStr name = demoName;
|
|
name.ExtractFileBase( aviDemoShortName );
|
|
aviCaptureMode = true;
|
|
aviDemoFrameCount = 0;
|
|
aviTicStart = 0;
|
|
sw->AVIOpen( va( "demos/%s/", aviDemoShortName.c_str() ), aviDemoShortName.c_str() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::EndAVICapture
|
|
================
|
|
*/
|
|
void idSessionLocal::EndAVICapture() {
|
|
if ( !aviCaptureMode ) {
|
|
return;
|
|
}
|
|
|
|
sw->AVIClose();
|
|
|
|
// write a .roqParam file so the demo can be converted to a roq file
|
|
idFile *f = fileSystem->OpenFileWrite( va( "demos/%s/%s.roqParam",
|
|
aviDemoShortName.c_str(), aviDemoShortName.c_str() ) );
|
|
f->Printf( "INPUT_DIR demos/%s\n", aviDemoShortName.c_str() );
|
|
f->Printf( "FILENAME demos/%s/%s.RoQ\n", aviDemoShortName.c_str(), aviDemoShortName.c_str() );
|
|
f->Printf( "\nINPUT\n" );
|
|
f->Printf( "%s_*.tga [00000-%05i]\n", aviDemoShortName.c_str(), (int)( aviDemoFrameCount-1 ) );
|
|
f->Printf( "END_INPUT\n" );
|
|
delete f;
|
|
|
|
common->Printf( "captured %i frames for %s.\n", ( int )aviDemoFrameCount, aviDemoShortName.c_str() );
|
|
|
|
aviCaptureMode = false;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::AVIRenderDemo
|
|
================
|
|
*/
|
|
void idSessionLocal::AVIRenderDemo( const char *_demoName ) {
|
|
idStr demoName = _demoName; // copy off from va() buffer
|
|
|
|
StartPlayingRenderDemo( demoName );
|
|
if ( !readDemo ) {
|
|
return;
|
|
}
|
|
|
|
BeginAVICapture( demoName.c_str() ) ;
|
|
|
|
// I don't understand why I need to do this twice, something
|
|
// strange with the nvidia swapbuffers?
|
|
UpdateScreen();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::AVICmdDemo
|
|
================
|
|
*/
|
|
void idSessionLocal::AVICmdDemo( const char *demoName ) {
|
|
StartPlayingCmdDemo( demoName );
|
|
|
|
BeginAVICapture( demoName ) ;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::AVIGame
|
|
|
|
Start AVI recording the current game session
|
|
================
|
|
*/
|
|
void idSessionLocal::AVIGame( const char *demoName ) {
|
|
if ( aviCaptureMode ) {
|
|
EndAVICapture();
|
|
return;
|
|
}
|
|
|
|
if ( !mapSpawned ) {
|
|
common->Printf( "No map spawned.\n" );
|
|
}
|
|
|
|
if ( !demoName || !demoName[0] ) {
|
|
idStr filename = FindUnusedFileName( "demos/game%03i.game" );
|
|
demoName = filename.c_str();
|
|
|
|
// write a one byte stub .game file just so the FindUnusedFileName works,
|
|
fileSystem->WriteFile( demoName, demoName, 1 );
|
|
}
|
|
|
|
BeginAVICapture( demoName ) ;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::CompressDemoFile
|
|
================
|
|
*/
|
|
void idSessionLocal::CompressDemoFile( const char *scheme, const char *demoName ) {
|
|
idStr fullDemoName = "demos/";
|
|
fullDemoName += demoName;
|
|
fullDemoName.DefaultFileExtension( ".demo" );
|
|
idStr compressedName = fullDemoName;
|
|
compressedName.StripFileExtension();
|
|
compressedName.Append( "_compressed.demo" );
|
|
|
|
int savedCompression = cvarSystem->GetCVarInteger("com_compressDemos");
|
|
bool savedPreload = cvarSystem->GetCVarBool("com_preloadDemos");
|
|
cvarSystem->SetCVarBool( "com_preloadDemos", false );
|
|
cvarSystem->SetCVarInteger("com_compressDemos", atoi(scheme) );
|
|
|
|
idDemoFile demoread, demowrite;
|
|
if ( !demoread.OpenForReading( fullDemoName ) ) {
|
|
common->Printf( "Could not open %s for reading\n", fullDemoName.c_str() );
|
|
return;
|
|
}
|
|
if ( !demowrite.OpenForWriting( compressedName ) ) {
|
|
common->Printf( "Could not open %s for writing\n", compressedName.c_str() );
|
|
demoread.Close();
|
|
cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload );
|
|
cvarSystem->SetCVarInteger("com_compressDemos", savedCompression);
|
|
return;
|
|
}
|
|
common->SetRefreshOnPrint( true );
|
|
common->Printf( "Compressing %s to %s...\n", fullDemoName.c_str(), compressedName.c_str() );
|
|
|
|
static const int bufferSize = 65535;
|
|
char buffer[bufferSize];
|
|
int bytesRead;
|
|
while ( 0 != (bytesRead = demoread.Read( buffer, bufferSize ) ) ) {
|
|
demowrite.Write( buffer, bytesRead );
|
|
common->Printf( "." );
|
|
}
|
|
|
|
demoread.Close();
|
|
demowrite.Close();
|
|
|
|
cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload );
|
|
cvarSystem->SetCVarInteger("com_compressDemos", savedCompression);
|
|
|
|
common->Printf( "Done\n" );
|
|
common->SetRefreshOnPrint( false );
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::StartNewGame
|
|
===============
|
|
*/
|
|
void idSessionLocal::StartNewGame( const char *mapName, bool devmap ) {
|
|
#ifdef ID_DEDICATED
|
|
common->Printf( "Dedicated servers cannot start singleplayer games.\n" );
|
|
return;
|
|
#else
|
|
#if ID_ENFORCE_KEY
|
|
// strict check. don't let a game start without a definitive answer
|
|
if ( !CDKeysAreValid( true ) ) {
|
|
bool prompt = true;
|
|
if ( MaybeWaitOnCDKey() ) {
|
|
// check again, maybe we just needed more time
|
|
if ( CDKeysAreValid( true ) ) {
|
|
// can continue directly
|
|
prompt = false;
|
|
}
|
|
}
|
|
if ( prompt ) {
|
|
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "promptKey force" );
|
|
cmdSystem->ExecuteCommandBuffer();
|
|
}
|
|
}
|
|
#endif
|
|
if ( idAsyncNetwork::server.IsActive() ) {
|
|
common->Printf("Server running, use si_map / serverMapRestart\n");
|
|
return;
|
|
}
|
|
if ( idAsyncNetwork::client.IsActive() ) {
|
|
common->Printf("Client running, disconnect from server first\n");
|
|
return;
|
|
}
|
|
|
|
// clear the userInfo so the player starts out with the defaults
|
|
mapSpawnData.userInfo[0].Clear();
|
|
mapSpawnData.persistentPlayerInfo[0].Clear();
|
|
mapSpawnData.userInfo[0] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
|
|
|
|
mapSpawnData.serverInfo.Clear();
|
|
mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
|
|
mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" );
|
|
|
|
// set the devmap key so any play testing items will be given at
|
|
// spawn time to set approximately the right weapons and ammo
|
|
if(devmap) {
|
|
mapSpawnData.serverInfo.Set( "devmap", "1" );
|
|
}
|
|
|
|
mapSpawnData.syncedCVars.Clear();
|
|
mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC );
|
|
|
|
MoveToNewMap( mapName );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::GetAutoSaveName
|
|
===============
|
|
*/
|
|
idStr idSessionLocal::GetAutoSaveName( const char *mapName ) const {
|
|
const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false );
|
|
const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( mapDecl );
|
|
if ( mapDef ) {
|
|
mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) );
|
|
}
|
|
// Fixme: Localization
|
|
return va( "^3AutoSave:^0 %s", mapName );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::MoveToNewMap
|
|
|
|
Leaves the existing userinfo and serverinfo
|
|
===============
|
|
*/
|
|
void idSessionLocal::MoveToNewMap( const char *mapName ) {
|
|
mapSpawnData.serverInfo.Set( "si_map", mapName );
|
|
|
|
ExecuteMapChange();
|
|
|
|
if ( !mapSpawnData.serverInfo.GetBool("devmap") ) {
|
|
// Autosave at the beginning of the level
|
|
|
|
// DG: set an explicit savename to avoid problems with autosave names
|
|
// (they were translated which caused problems like all alpha labs parts
|
|
// getting the same filename in spanish, probably because the strings contained
|
|
// dots and everything behind them was cut off as "file extension".. see #305)
|
|
idStr saveFileName = "Autosave_";
|
|
saveFileName += mapName;
|
|
SaveGame( GetAutoSaveName( mapName ), true, saveFileName );
|
|
}
|
|
|
|
SetGUI( NULL, NULL );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
SaveCmdDemoFromFile
|
|
==============
|
|
*/
|
|
void idSessionLocal::SaveCmdDemoToFile( idFile *file ) {
|
|
|
|
mapSpawnData.serverInfo.WriteToFileHandle( file );
|
|
|
|
for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) {
|
|
mapSpawnData.userInfo[i].WriteToFileHandle( file );
|
|
mapSpawnData.persistentPlayerInfo[i].WriteToFileHandle( file );
|
|
}
|
|
|
|
file->Write( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) );
|
|
|
|
if ( numClients < 1 ) {
|
|
numClients = 1;
|
|
}
|
|
file->Write( loggedUsercmds, numClients * logIndex * sizeof( loggedUsercmds[0] ) );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idSessionLocal::LoadCmdDemoFromFile
|
|
==============
|
|
*/
|
|
void idSessionLocal::LoadCmdDemoFromFile( idFile *file ) {
|
|
|
|
mapSpawnData.serverInfo.ReadFromFileHandle( file );
|
|
|
|
for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) {
|
|
mapSpawnData.userInfo[i].ReadFromFileHandle( file );
|
|
mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( file );
|
|
}
|
|
file->Read( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idSessionLocal::WriteCmdDemo
|
|
|
|
Dumps the accumulated commands for the current level.
|
|
This should still work after disconnecting from a level
|
|
==============
|
|
*/
|
|
void idSessionLocal::WriteCmdDemo( const char *demoName, bool save ) {
|
|
|
|
if ( !demoName[0] ) {
|
|
common->Printf( "idSessionLocal::WriteCmdDemo: no name specified\n" );
|
|
return;
|
|
}
|
|
|
|
idStr statsName;
|
|
if (save) {
|
|
statsName = demoName;
|
|
statsName.StripFileExtension();
|
|
statsName.DefaultFileExtension(".stats");
|
|
}
|
|
|
|
common->Printf( "writing save data to %s\n", demoName );
|
|
|
|
idFile *cmdDemoFile = fileSystem->OpenFileWrite( demoName );
|
|
if ( !cmdDemoFile ) {
|
|
common->Printf( "Couldn't open for writing %s\n", demoName );
|
|
return;
|
|
}
|
|
|
|
if ( save ) {
|
|
cmdDemoFile->Write( &logIndex, sizeof( logIndex ) );
|
|
}
|
|
|
|
SaveCmdDemoToFile( cmdDemoFile );
|
|
|
|
if ( save ) {
|
|
idFile *statsFile = fileSystem->OpenFileWrite( statsName );
|
|
if ( statsFile ) {
|
|
statsFile->Write( &statIndex, sizeof( statIndex ) );
|
|
statsFile->Write( loggedStats, numClients * statIndex * sizeof( loggedStats[0] ) );
|
|
fileSystem->CloseFile( statsFile );
|
|
}
|
|
}
|
|
|
|
fileSystem->CloseFile( cmdDemoFile );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::FinishCmdLoad
|
|
===============
|
|
*/
|
|
void idSessionLocal::FinishCmdLoad() {
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::StartPlayingCmdDemo
|
|
===============
|
|
*/
|
|
void idSessionLocal::StartPlayingCmdDemo(const char *demoName) {
|
|
// exit any current game
|
|
Stop();
|
|
|
|
idStr fullDemoName = "demos/";
|
|
fullDemoName += demoName;
|
|
fullDemoName.DefaultFileExtension( ".cdemo" );
|
|
cmdDemoFile = fileSystem->OpenFileRead(fullDemoName);
|
|
|
|
if ( cmdDemoFile == NULL ) {
|
|
common->Printf( "Couldn't open %s\n", fullDemoName.c_str() );
|
|
return;
|
|
}
|
|
|
|
guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true );
|
|
//cmdDemoFile->Read(&loadGameTime, sizeof(loadGameTime));
|
|
|
|
LoadCmdDemoFromFile(cmdDemoFile);
|
|
|
|
// start the map
|
|
ExecuteMapChange();
|
|
|
|
cmdDemoFile = fileSystem->OpenFileRead(fullDemoName);
|
|
|
|
// have to do this twice as the execmapchange clears the cmddemofile
|
|
LoadCmdDemoFromFile(cmdDemoFile);
|
|
|
|
// run one frame to get the view angles correct
|
|
RunGameTic();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::TimeCmdDemo
|
|
===============
|
|
*/
|
|
void idSessionLocal::TimeCmdDemo( const char *demoName ) {
|
|
StartPlayingCmdDemo( demoName );
|
|
ClearWipe();
|
|
UpdateScreen();
|
|
|
|
int startTime = Sys_Milliseconds();
|
|
int count = 0;
|
|
int minuteStart, minuteEnd;
|
|
float sec;
|
|
|
|
// run all the frames in sequence
|
|
minuteStart = startTime;
|
|
|
|
while( cmdDemoFile ) {
|
|
RunGameTic();
|
|
count++;
|
|
|
|
if ( count / 3600 != ( count - 1 ) / 3600 ) {
|
|
minuteEnd = Sys_Milliseconds();
|
|
sec = ( minuteEnd - minuteStart ) / 1000.0;
|
|
minuteStart = minuteEnd;
|
|
common->Printf( "minute %i took %3.1f seconds\n", count / 3600, sec );
|
|
UpdateScreen();
|
|
}
|
|
}
|
|
|
|
int endTime = Sys_Milliseconds();
|
|
sec = ( endTime - startTime ) / 1000.0;
|
|
common->Printf( "%i seconds of game, replayed in %5.1f seconds\n", count / 60, sec );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::UnloadMap
|
|
|
|
Performs cleanup that needs to happen between maps, or when a
|
|
game is exited.
|
|
Exits with mapSpawned = false
|
|
===============
|
|
*/
|
|
void idSessionLocal::UnloadMap() {
|
|
StopPlayingRenderDemo();
|
|
|
|
// end the current map in the game
|
|
if ( game ) {
|
|
game->MapShutdown();
|
|
}
|
|
|
|
if ( cmdDemoFile ) {
|
|
fileSystem->CloseFile( cmdDemoFile );
|
|
cmdDemoFile = NULL;
|
|
}
|
|
|
|
if ( writeDemo ) {
|
|
StopRecordingRenderDemo();
|
|
}
|
|
|
|
mapSpawned = false;
|
|
|
|
// DG: that state needs to be reset now
|
|
Sys_SetInteractiveIngameGuiActive( false, NULL );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::LoadLoadingGui
|
|
===============
|
|
*/
|
|
void idSessionLocal::LoadLoadingGui( const char *mapName ) {
|
|
// load / program a gui to stay up on the screen while loading
|
|
idStr stripped = mapName;
|
|
stripped.StripFileExtension();
|
|
stripped.StripPath();
|
|
|
|
char guiMap[ MAX_STRING_CHARS ];
|
|
idStr::Copynz( guiMap, va( "guis/map/%s.gui", stripped.c_str() ), MAX_STRING_CHARS );
|
|
// give the gamecode a chance to override
|
|
game->GetMapLoadingGUI( guiMap );
|
|
|
|
if ( uiManager->CheckGui( guiMap ) ) {
|
|
guiLoading = uiManager->FindGui( guiMap, true, false, true );
|
|
} else {
|
|
guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true );
|
|
}
|
|
guiLoading->SetStateFloat( "map_loading", 0.0f );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::GetBytesNeededForMapLoad
|
|
===============
|
|
*/
|
|
int idSessionLocal::GetBytesNeededForMapLoad( const char *mapName ) {
|
|
const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false );
|
|
const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( mapDecl );
|
|
if ( mapDef ) {
|
|
return mapDef->dict.GetInt( va("size%d", Max( 0, com_machineSpec.GetInteger() ) ) );
|
|
} else {
|
|
if ( com_machineSpec.GetInteger() < 2 ) {
|
|
return 200 * 1024 * 1024;
|
|
} else {
|
|
return 400 * 1024 * 1024;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::SetBytesNeededForMapLoad
|
|
===============
|
|
*/
|
|
void idSessionLocal::SetBytesNeededForMapLoad( const char *mapName, int bytesNeeded ) {
|
|
idDecl *mapDecl = const_cast<idDecl *>(declManager->FindType( DECL_MAPDEF, mapName, false ));
|
|
idDeclEntityDef *mapDef = static_cast<idDeclEntityDef *>( mapDecl );
|
|
|
|
if ( com_updateLoadSize.GetBool() && mapDef ) {
|
|
// we assume that if com_updateLoadSize is true then the file is writable
|
|
|
|
mapDef->dict.SetInt( va("size%d", com_machineSpec.GetInteger()), bytesNeeded );
|
|
|
|
idStr declText = "\nmapDef ";
|
|
declText += mapDef->GetName();
|
|
declText += " {\n";
|
|
for (int i=0; i<mapDef->dict.GetNumKeyVals(); i++) {
|
|
const idKeyValue *kv = mapDef->dict.GetKeyVal( i );
|
|
if ( kv && (kv->GetKey().Cmp("classname") != 0 ) ) {
|
|
declText += "\t\"" + kv->GetKey() + "\"\t\t\"" + kv->GetValue() + "\"\n";
|
|
}
|
|
}
|
|
declText += "}";
|
|
mapDef->SetText( declText );
|
|
mapDef->ReplaceSourceFileText();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::ExecuteMapChange
|
|
|
|
Performs the initialization of a game based on mapSpawnData, used for both single
|
|
player and multiplayer, but not for renderDemos, which don't
|
|
create a game at all.
|
|
Exits with mapSpawned = true
|
|
===============
|
|
*/
|
|
void idSessionLocal::ExecuteMapChange( bool noFadeWipe ) {
|
|
int i;
|
|
bool reloadingSameMap;
|
|
|
|
// close console and remove any prints from the notify lines
|
|
console->Close();
|
|
|
|
if ( IsMultiplayer() ) {
|
|
// make sure the mp GUI isn't up, or when players get back in the
|
|
// map, mpGame's menu and the gui will be out of sync.
|
|
SetGUI( NULL, NULL );
|
|
}
|
|
|
|
// mute sound
|
|
soundSystem->SetMute( true );
|
|
|
|
// clear all menu sounds
|
|
menuSoundWorld->ClearAllSoundEmitters();
|
|
|
|
// unpause the game sound world
|
|
// NOTE: we UnPause again later down. not sure this is needed
|
|
if ( sw->IsPaused() ) {
|
|
sw->UnPause();
|
|
}
|
|
|
|
if ( !noFadeWipe ) {
|
|
// capture the current screen and start a wipe
|
|
StartWipe( "wipeMaterial", true );
|
|
|
|
// immediately complete the wipe to fade out the level transition
|
|
// run the wipe to completion
|
|
CompleteWipe();
|
|
}
|
|
|
|
// extract the map name from serverinfo
|
|
idStr mapString = mapSpawnData.serverInfo.GetString( "si_map" );
|
|
|
|
idStr fullMapName = "maps/";
|
|
fullMapName += mapString;
|
|
fullMapName.StripFileExtension();
|
|
|
|
// shut down the existing game if it is running
|
|
UnloadMap();
|
|
|
|
// don't do the deferred caching if we are reloading the same map
|
|
if ( fullMapName == currentMapName ) {
|
|
reloadingSameMap = true;
|
|
} else {
|
|
reloadingSameMap = false;
|
|
currentMapName = fullMapName;
|
|
}
|
|
|
|
// note which media we are going to need to load
|
|
if ( !reloadingSameMap ) {
|
|
declManager->BeginLevelLoad();
|
|
renderSystem->BeginLevelLoad();
|
|
soundSystem->BeginLevelLoad();
|
|
}
|
|
|
|
uiManager->BeginLevelLoad();
|
|
uiManager->Reload( true );
|
|
|
|
// set the loading gui that we will wipe to
|
|
LoadLoadingGui( mapString );
|
|
|
|
// cause prints to force screen updates as a pacifier,
|
|
// and draw the loading gui instead of game draws
|
|
insideExecuteMapChange = true;
|
|
|
|
// if this works out we will probably want all the sizes in a def file although this solution will
|
|
// work for new maps etc. after the first load. we can also drop the sizes into the default.cfg
|
|
fileSystem->ResetReadCount();
|
|
if ( !reloadingSameMap ) {
|
|
bytesNeededForMapLoad = GetBytesNeededForMapLoad( mapString.c_str() );
|
|
} else {
|
|
bytesNeededForMapLoad = 30 * 1024 * 1024;
|
|
}
|
|
|
|
ClearWipe();
|
|
|
|
// let the loading gui spin for 1 second to animate out
|
|
ShowLoadingGui();
|
|
|
|
// note any warning prints that happen during the load process
|
|
common->ClearWarnings( mapString );
|
|
|
|
// release the mouse cursor
|
|
// before we do this potentially long operation
|
|
Sys_GrabMouseCursor( false );
|
|
|
|
// if net play, we get the number of clients during mapSpawnInfo processing
|
|
if ( !idAsyncNetwork::IsActive() ) {
|
|
numClients = 1;
|
|
}
|
|
|
|
int start = Sys_Milliseconds();
|
|
|
|
common->Printf( "----- Map Initialization -----\n" );
|
|
common->Printf( "Map: %s\n", mapString.c_str() );
|
|
|
|
// let the renderSystem load all the geometry
|
|
if ( !rw->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();
|
|
memset( &mapSpawnData.mapSpawnUsercmd, 0, sizeof( mapSpawnData.mapSpawnUsercmd ) );
|
|
|
|
// set the user info
|
|
for ( i = 0; i < numClients; i++ ) {
|
|
game->SetUserInfo( i, mapSpawnData.userInfo[i], idAsyncNetwork::client.IsActive(), false );
|
|
game->SetPersistentPlayerInfo( i, mapSpawnData.persistentPlayerInfo[i] );
|
|
}
|
|
|
|
// load and spawn all other entities ( from a savegame possibly )
|
|
if ( loadingSaveGame && savegameFile ) {
|
|
if ( game->InitFromSaveGame( fullMapName + ".map", rw, sw, savegameFile ) == false ) {
|
|
// If the loadgame failed, restart the map with the player persistent data
|
|
loadingSaveGame = false;
|
|
fileSystem->CloseFile( savegameFile );
|
|
savegameFile = NULL;
|
|
|
|
common->Warning( "WARNING: Loading savegame failed, will restart the map with the player persistent data!" );
|
|
|
|
game->SetServerInfo( mapSpawnData.serverInfo );
|
|
game->InitFromNewMap( fullMapName + ".map", rw, sw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() );
|
|
}
|
|
} else {
|
|
game->SetServerInfo( mapSpawnData.serverInfo );
|
|
game->InitFromNewMap( fullMapName + ".map", rw, sw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() );
|
|
}
|
|
|
|
if ( !idAsyncNetwork::IsActive() && !loadingSaveGame ) {
|
|
// spawn players
|
|
for ( i = 0; i < numClients; i++ ) {
|
|
game->SpawnPlayer( i );
|
|
}
|
|
}
|
|
|
|
// actually purge/load the media
|
|
if ( !reloadingSameMap ) {
|
|
renderSystem->EndLevelLoad();
|
|
soundSystem->EndLevelLoad( mapString.c_str() );
|
|
declManager->EndLevelLoad();
|
|
SetBytesNeededForMapLoad( mapString.c_str(), fileSystem->GetReadCount() );
|
|
}
|
|
uiManager->EndLevelLoad();
|
|
|
|
if ( !idAsyncNetwork::IsActive() && !loadingSaveGame ) {
|
|
// run a few frames to allow everything to settle
|
|
for ( i = 0; i < 10; i++ ) {
|
|
game->RunFrame( mapSpawnData.mapSpawnUsercmd );
|
|
}
|
|
}
|
|
|
|
int msec = Sys_Milliseconds() - start;
|
|
common->Printf( "%6d msec to load %s\n", msec, mapString.c_str() );
|
|
|
|
// let the renderSystem generate interactions now that everything is spawned
|
|
rw->GenerateAllInteractions();
|
|
|
|
common->PrintWarnings();
|
|
|
|
if ( guiLoading && bytesNeededForMapLoad ) {
|
|
float pct = guiLoading->State().GetFloat( "map_loading" );
|
|
if ( pct < 0.0f ) {
|
|
pct = 0.0f;
|
|
}
|
|
while ( pct < 1.0f ) {
|
|
guiLoading->SetStateFloat( "map_loading", pct );
|
|
guiLoading->StateChanged( com_frameTime );
|
|
Sys_GenerateEvents();
|
|
UpdateScreen();
|
|
pct += 0.05f;
|
|
}
|
|
}
|
|
|
|
// capture the current screen and start a wipe
|
|
StartWipe( "wipe2Material" );
|
|
|
|
usercmdGen->Clear();
|
|
|
|
// start saving commands for possible writeCmdDemo usage
|
|
logIndex = 0;
|
|
statIndex = 0;
|
|
lastSaveIndex = 0;
|
|
|
|
// don't bother spinning over all the tics we spent loading
|
|
lastGameTic = latchedTicNumber = com_ticNumber;
|
|
|
|
// remove any prints from the notify lines
|
|
console->ClearNotifyLines();
|
|
|
|
// stop drawing the laoding screen
|
|
insideExecuteMapChange = false;
|
|
|
|
Sys_SetPhysicalWorkMemory( -1, -1 );
|
|
|
|
// set the game sound world for playback
|
|
soundSystem->SetPlayingSoundWorld( sw );
|
|
|
|
// when loading a save game the sound is paused
|
|
if ( sw->IsPaused() ) {
|
|
// unpause the game sound world
|
|
sw->UnPause();
|
|
}
|
|
|
|
// restart entity sound playback
|
|
soundSystem->SetMute( false );
|
|
|
|
// we are valid for game draws now
|
|
mapSpawned = true;
|
|
Sys_ClearEvents();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
LoadGame_f
|
|
===============
|
|
*/
|
|
void LoadGame_f( const idCmdArgs &args ) {
|
|
console->Close();
|
|
if ( args.Argc() < 2 || idStr::Icmp(args.Argv(1), "quick" ) == 0 ) {
|
|
sessLocal.QuickLoad();
|
|
} else {
|
|
sessLocal.LoadGame( args.Argv(1) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
SaveGame_f
|
|
===============
|
|
*/
|
|
void SaveGame_f( const idCmdArgs &args ) {
|
|
if ( args.Argc() < 2 || idStr::Icmp( args.Argv(1), "quick" ) == 0 ) {
|
|
sessLocal.QuickSave();
|
|
} else {
|
|
if ( sessLocal.SaveGame( args.Argv(1) ) ) {
|
|
common->Printf( "Saved %s\n", args.Argv(1) );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
TakeViewNotes_f
|
|
===============
|
|
*/
|
|
void TakeViewNotes_f( const idCmdArgs &args ) {
|
|
const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : "";
|
|
sessLocal.TakeNotes( p );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
TakeViewNotes2_f
|
|
===============
|
|
*/
|
|
void TakeViewNotes2_f( const idCmdArgs &args ) {
|
|
const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : "";
|
|
sessLocal.TakeNotes( p, true );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::TakeNotes
|
|
===============
|
|
*/
|
|
void idSessionLocal::TakeNotes( const char *p, bool extended ) {
|
|
if ( !mapSpawned ) {
|
|
common->Printf( "No map loaded!\n" );
|
|
return;
|
|
}
|
|
|
|
if ( extended ) {
|
|
guiTakeNotes = uiManager->FindGui( "guis/takeNotes2.gui", true, false, true );
|
|
|
|
#if 0
|
|
const char *people[] = {
|
|
"Nobody", "Adam", "Brandon", "David", "PHook", "Jay", "Jake",
|
|
"PatJ", "Brett", "Ted", "Darin", "Brian", "Sean"
|
|
};
|
|
#else
|
|
const char *people[] = {
|
|
"Tim", "Kenneth", "Robert",
|
|
"Matt", "Mal", "Jerry", "Steve", "Pat",
|
|
"Xian", "Ed", "Fred", "James", "Eric", "Andy", "Seneca", "Patrick", "Kevin",
|
|
"MrElusive", "Jim", "Brian", "John", "Adrian", "Nobody"
|
|
};
|
|
#endif
|
|
const int numPeople = sizeof( people ) / sizeof( people[0] );
|
|
|
|
idListGUI * guiList_people = uiManager->AllocListGUI();
|
|
guiList_people->Config( guiTakeNotes, "person" );
|
|
for ( int i = 0; i < numPeople; i++ ) {
|
|
guiList_people->Push( people[i] );
|
|
}
|
|
uiManager->FreeListGUI( guiList_people );
|
|
|
|
} else {
|
|
guiTakeNotes = uiManager->FindGui( "guis/takeNotes.gui", true, false, true );
|
|
}
|
|
|
|
SetGUI( guiTakeNotes, NULL );
|
|
guiActive->SetStateString( "note", "" );
|
|
guiActive->SetStateString( "notefile", p );
|
|
guiActive->SetStateBool( "extended", extended );
|
|
guiActive->Activate( true, com_frameTime );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Session_Hitch_f
|
|
===============
|
|
*/
|
|
void Session_Hitch_f( const idCmdArgs &args ) {
|
|
idSoundWorld *sw = soundSystem->GetPlayingSoundWorld();
|
|
if ( sw ) {
|
|
soundSystem->SetMute(true);
|
|
sw->Pause();
|
|
Sys_EnterCriticalSection();
|
|
}
|
|
if ( args.Argc() == 2 ) {
|
|
Sys_Sleep( atoi(args.Argv(1)) );
|
|
} else {
|
|
Sys_Sleep( 100 );
|
|
}
|
|
if ( sw ) {
|
|
Sys_LeaveCriticalSection();
|
|
sw->UnPause();
|
|
soundSystem->SetMute(false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::ScrubSaveGameFileName
|
|
|
|
Turns a bad file name into a good one or your money back
|
|
===============
|
|
*/
|
|
void idSessionLocal::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];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::SaveGame
|
|
===============
|
|
*/
|
|
bool idSessionLocal::SaveGame( const char *saveName, bool autosave, const char* saveFileName ) {
|
|
#ifdef ID_DEDICATED
|
|
common->Printf( "Dedicated servers cannot save games.\n" );
|
|
return false;
|
|
#else
|
|
int i;
|
|
idStr previewFile, descriptionFile, mapName;
|
|
// DG: support setting an explicit savename to avoid problems with autosave names
|
|
idStr gameFile = (saveFileName != NULL) ? saveFileName : saveName;
|
|
|
|
if ( !mapSpawned ) {
|
|
common->Printf( "Not playing a game.\n" );
|
|
return false;
|
|
}
|
|
|
|
if ( IsMultiplayer() ) {
|
|
common->Printf( "Can't save during net play.\n" );
|
|
return false;
|
|
}
|
|
|
|
if ( game->GetPersistentPlayerInfo( 0 ).GetInt( "health" ) <= 0 ) {
|
|
MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04311" ), common->GetLanguageDict()->GetString ( "#str_04312" ), true );
|
|
common->Printf( "You must be alive to save the game\n" );
|
|
return false;
|
|
}
|
|
|
|
if ( Sys_GetDriveFreeSpace( cvarSystem->GetCVarString( "fs_savepath" ) ) < 25 ) {
|
|
MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04313" ), common->GetLanguageDict()->GetString ( "#str_04314" ), true );
|
|
common->Printf( "Not enough drive space to save the game\n" );
|
|
return false;
|
|
}
|
|
|
|
idSoundWorld *pauseWorld = soundSystem->GetPlayingSoundWorld();
|
|
if ( pauseWorld ) {
|
|
pauseWorld->Pause();
|
|
soundSystem->SetPlayingSoundWorld( NULL );
|
|
}
|
|
|
|
// setup up filenames and paths
|
|
ScrubSaveGameFileName( gameFile );
|
|
|
|
gameFile = "savegames/" + gameFile;
|
|
gameFile.SetFileExtension( ".save" );
|
|
|
|
previewFile = gameFile;
|
|
previewFile.SetFileExtension( ".tga" );
|
|
|
|
descriptionFile = gameFile;
|
|
descriptionFile.SetFileExtension( ".txt" );
|
|
|
|
// Open savegame file
|
|
idFile *fileOut = fileSystem->OpenFileWrite( gameFile );
|
|
if ( fileOut == NULL ) {
|
|
common->Warning( "Failed to open save file '%s'\n", gameFile.c_str() );
|
|
if ( pauseWorld ) {
|
|
soundSystem->SetPlayingSoundWorld( pauseWorld );
|
|
pauseWorld->UnPause();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Write SaveGame Header:
|
|
// Game Name / Version / Map Name / Persistant Player Info
|
|
|
|
// game
|
|
const char *gamename = GAME_NAME;
|
|
fileOut->WriteString( gamename );
|
|
|
|
// version
|
|
fileOut->WriteInt( SAVEGAME_VERSION );
|
|
|
|
// map
|
|
mapName = mapSpawnData.serverInfo.GetString( "si_map" );
|
|
fileOut->WriteString( mapName );
|
|
|
|
// persistent player info
|
|
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
|
|
mapSpawnData.persistentPlayerInfo[i] = game->GetPersistentPlayerInfo( i );
|
|
mapSpawnData.persistentPlayerInfo[i].WriteToFileHandle( fileOut );
|
|
}
|
|
|
|
// let the game save its state
|
|
game->SaveGame( fileOut );
|
|
|
|
// close the sava game file
|
|
fileSystem->CloseFile( fileOut );
|
|
|
|
// Write screenshot
|
|
if ( !autosave ) {
|
|
renderSystem->CropRenderSize( 320, 240, false );
|
|
game->Draw( 0 );
|
|
renderSystem->CaptureRenderToFile( previewFile, true );
|
|
renderSystem->UnCrop();
|
|
}
|
|
|
|
// Write description, which is just a text file with
|
|
// the unclean save name on line 1, map name on line 2, screenshot on line 3
|
|
idFile *fileDesc = fileSystem->OpenFileWrite( descriptionFile );
|
|
if ( fileDesc == NULL ) {
|
|
common->Warning( "Failed to open description file '%s'\n", descriptionFile.c_str() );
|
|
if ( pauseWorld ) {
|
|
soundSystem->SetPlayingSoundWorld( pauseWorld );
|
|
pauseWorld->UnPause();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
idStr description = saveName;
|
|
description.Replace( "\\", "\\\\" );
|
|
description.Replace( "\"", "\\\"" );
|
|
|
|
const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>(declManager->FindType( DECL_MAPDEF, mapName, false ));
|
|
if ( mapDef ) {
|
|
mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) );
|
|
}
|
|
|
|
fileDesc->Printf( "\"%s\"\n", description.c_str() );
|
|
fileDesc->Printf( "\"%s\"\n", mapName.c_str());
|
|
|
|
if ( autosave ) {
|
|
idStr sshot = mapSpawnData.serverInfo.GetString( "si_map" );
|
|
sshot.StripPath();
|
|
sshot.StripFileExtension();
|
|
fileDesc->Printf( "\"guis/assets/autosave/%s\"\n", sshot.c_str() );
|
|
} else {
|
|
fileDesc->Printf( "\"\"\n" );
|
|
}
|
|
|
|
fileSystem->CloseFile( fileDesc );
|
|
|
|
if ( pauseWorld ) {
|
|
soundSystem->SetPlayingSoundWorld( pauseWorld );
|
|
pauseWorld->UnPause();
|
|
}
|
|
|
|
syncNextGameFrame = true;
|
|
|
|
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::LoadGame
|
|
===============
|
|
*/
|
|
bool idSessionLocal::LoadGame( const char *saveName ) {
|
|
#ifdef ID_DEDICATED
|
|
common->Printf( "Dedicated servers cannot load games.\n" );
|
|
return false;
|
|
#else
|
|
int i;
|
|
idStr in, loadFile, saveMap, gamename;
|
|
|
|
if ( IsMultiplayer() ) {
|
|
common->Printf( "Can't load during net play.\n" );
|
|
return false;
|
|
}
|
|
|
|
//Hide the dialog box if it is up.
|
|
StopBox();
|
|
|
|
loadFile = saveName;
|
|
ScrubSaveGameFileName( loadFile );
|
|
loadFile.SetFileExtension( ".save" );
|
|
|
|
in = "savegames/";
|
|
in += loadFile;
|
|
|
|
// Open savegame file
|
|
// only allow loads from the game directory because we don't want a base game to load
|
|
idStr game = cvarSystem->GetCVarString( "fs_game" );
|
|
savegameFile = fileSystem->OpenFileRead( in, true, game.Length() ? game : NULL );
|
|
|
|
if ( savegameFile == NULL ) {
|
|
common->Warning( "Couldn't open savegame file %s", in.c_str() );
|
|
return false;
|
|
}
|
|
|
|
loadingSaveGame = true;
|
|
|
|
// Read in save game header
|
|
// Game Name / Version / Map Name / Persistant Player Info
|
|
|
|
// game
|
|
savegameFile->ReadString( gamename );
|
|
|
|
// if this isn't a savegame for the correct game, abort loadgame
|
|
if ( ! (gamename == GAME_NAME || gamename == "DOOM 3") ) {
|
|
common->Warning( "Attempted to load an invalid savegame: %s", in.c_str() );
|
|
|
|
loadingSaveGame = false;
|
|
fileSystem->CloseFile( savegameFile );
|
|
savegameFile = NULL;
|
|
return false;
|
|
}
|
|
|
|
// version
|
|
savegameFile->ReadInt( savegameVersion );
|
|
|
|
// map
|
|
savegameFile->ReadString( saveMap );
|
|
|
|
// persistent player info
|
|
for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
|
|
mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( savegameFile );
|
|
}
|
|
|
|
// check the version, if it doesn't match, cancel the loadgame,
|
|
// but still load the map with the persistant playerInfo from the header
|
|
// so that the player doesn't lose too much progress.
|
|
if ( savegameVersion != SAVEGAME_VERSION &&
|
|
!( savegameVersion == 16 && SAVEGAME_VERSION == 17 ) ) { // handle savegame v16 in v17
|
|
common->Warning( "Savegame Version mismatch: aborting loadgame and starting level with persistent data" );
|
|
loadingSaveGame = false;
|
|
fileSystem->CloseFile( savegameFile );
|
|
savegameFile = NULL;
|
|
}
|
|
|
|
common->DPrintf( "loading a v%d savegame\n", savegameVersion );
|
|
|
|
if ( saveMap.Length() > 0 ) {
|
|
|
|
// Start loading map
|
|
mapSpawnData.serverInfo.Clear();
|
|
|
|
mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
|
|
mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" );
|
|
|
|
mapSpawnData.serverInfo.Set( "si_map", saveMap );
|
|
|
|
mapSpawnData.syncedCVars.Clear();
|
|
mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC );
|
|
|
|
mapSpawnData.mapSpawnUsercmd[0] = usercmdGen->TicCmd( latchedTicNumber );
|
|
// make sure no buttons are pressed
|
|
mapSpawnData.mapSpawnUsercmd[0].buttons = 0;
|
|
|
|
ExecuteMapChange();
|
|
|
|
SetGUI( NULL, NULL );
|
|
}
|
|
|
|
if ( loadingSaveGame ) {
|
|
fileSystem->CloseFile( savegameFile );
|
|
loadingSaveGame = false;
|
|
savegameFile = NULL;
|
|
}
|
|
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool idSessionLocal::QuickSave()
|
|
{
|
|
idStr saveName = common->GetLanguageDict()->GetString( "#str_07178" );
|
|
|
|
idStr saveFilePathBase = saveName;
|
|
ScrubSaveGameFileName( saveFilePathBase );
|
|
saveFilePathBase = "savegames/" + saveFilePathBase;
|
|
|
|
const char* game = cvarSystem->GetCVarString( "fs_game" );
|
|
if ( game != NULL && game[0] == '\0' ) {
|
|
game = NULL;
|
|
}
|
|
|
|
const int maxNum = com_numQuicksaves.GetInteger();
|
|
int indexToUse = 1;
|
|
ID_TIME_T oldestTime = 0;
|
|
for( int i = 1; i <= maxNum; ++i ) {
|
|
idStr saveFilePath = saveFilePathBase;
|
|
if ( i > 1 ) {
|
|
// the first one is just called "QuickSave" without a number, like before.
|
|
// the others are called "QuickSave2" "QuickSave3" etc
|
|
saveFilePath += i;
|
|
}
|
|
saveFilePath.SetFileExtension( ".save" );
|
|
|
|
idFile *f = fileSystem->OpenFileRead( saveFilePath, true, game );
|
|
if ( f == NULL ) {
|
|
// this savegame doesn't exist yet => we can use this index for the name
|
|
indexToUse = i;
|
|
break;
|
|
} else {
|
|
ID_TIME_T ts = f->Timestamp();
|
|
assert( ts != 0 );
|
|
if ( ts < oldestTime || oldestTime == 0 ) {
|
|
// this is the oldest quicksave we found so far => a candidate to be overwritten
|
|
indexToUse = i;
|
|
oldestTime = ts;
|
|
}
|
|
delete f;
|
|
}
|
|
}
|
|
|
|
if ( indexToUse > 1 ) {
|
|
saveName += indexToUse;
|
|
}
|
|
|
|
if ( SaveGame( saveName ) ) {
|
|
common->Printf( "%s\n", saveName.c_str() );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool idSessionLocal::QuickLoad()
|
|
{
|
|
idStr saveName = common->GetLanguageDict()->GetString( "#str_07178" );
|
|
|
|
idStr saveFilePathBase = saveName;
|
|
ScrubSaveGameFileName( saveFilePathBase );
|
|
saveFilePathBase = "savegames/" + saveFilePathBase;
|
|
|
|
const char* game = cvarSystem->GetCVarString( "fs_game" );
|
|
if ( game != NULL && game[0] == '\0' ) {
|
|
game = NULL;
|
|
}
|
|
|
|
// find the newest QuickSave (or QuickSave2, QuickSave3, ...)
|
|
const int maxNum = com_numQuicksaves.GetInteger();
|
|
int indexToUse = 1;
|
|
ID_TIME_T newestTime = 0;
|
|
for( int i = 1; i <= maxNum; ++i ) {
|
|
idStr saveFilePath = saveFilePathBase;
|
|
if ( i > 1 ) {
|
|
// the first one is just called "QuickSave" without a number, like before.
|
|
// the others are called "QuickSave2" "QuickSave3" etc
|
|
saveFilePath += i;
|
|
}
|
|
saveFilePath.SetFileExtension( ".save" );
|
|
|
|
idFile *f = fileSystem->OpenFileRead( saveFilePath, true, game );
|
|
if ( f != NULL ) {
|
|
ID_TIME_T ts = f->Timestamp();
|
|
assert( ts != 0 );
|
|
if ( ts > newestTime ) {
|
|
indexToUse = i;
|
|
newestTime = ts;
|
|
}
|
|
delete f;
|
|
}
|
|
}
|
|
|
|
if ( indexToUse > 1 ) {
|
|
saveName += indexToUse;
|
|
}
|
|
|
|
return sessLocal.LoadGame( saveName );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::ProcessEvent
|
|
===============
|
|
*/
|
|
bool idSessionLocal::ProcessEvent( const sysEvent_t *event ) {
|
|
// hitting escape anywhere brings up the menu
|
|
// DG: but shift-escape should bring up console instead so ignore that
|
|
if ( !guiActive && event->evType == SE_KEY && event->evValue2 == 1
|
|
&& event->evValue == K_ESCAPE && !idKeyInput::IsDown( K_SHIFT ) ) {
|
|
console->Close();
|
|
if ( game ) {
|
|
idUserInterface *gui = NULL;
|
|
escReply_t op;
|
|
op = game->HandleESC( &gui );
|
|
if ( op == ESC_IGNORE ) {
|
|
return true;
|
|
} else if ( op == ESC_GUI ) {
|
|
SetGUI( gui, NULL );
|
|
return true;
|
|
}
|
|
}
|
|
StartMenu();
|
|
return true;
|
|
}
|
|
|
|
// let the pull-down console take it if desired
|
|
if ( console->ProcessEvent( event, false ) ) {
|
|
return true;
|
|
}
|
|
|
|
// if we are testing a GUI, send all events to it
|
|
if ( guiTest ) {
|
|
// hitting escape exits the testgui
|
|
if ( event->evType == SE_KEY && event->evValue2 == 1 && event->evValue == K_ESCAPE ) {
|
|
guiTest = NULL;
|
|
return true;
|
|
}
|
|
|
|
static const char *cmd;
|
|
cmd = guiTest->HandleEvent( event, com_frameTime );
|
|
if ( cmd && cmd[0] ) {
|
|
common->Printf( "testGui event returned: '%s'\n", cmd );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// menus / etc
|
|
if ( guiActive ) {
|
|
MenuEvent( event );
|
|
return true;
|
|
}
|
|
|
|
// if we aren't in a game, force the console to take it
|
|
if ( !mapSpawned ) {
|
|
console->ProcessEvent( event, true );
|
|
return true;
|
|
}
|
|
|
|
// in game, exec bindings for all key downs
|
|
if ( event->evType == SE_KEY && event->evValue2 == 1 ) {
|
|
idKeyInput::ExecKeyBinding( event->evValue );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::DrawWipeModel
|
|
|
|
Draw the fade material over everything that has been drawn
|
|
===============
|
|
*/
|
|
void idSessionLocal::DrawWipeModel() {
|
|
int latchedTic = com_ticNumber;
|
|
|
|
if ( wipeStartTic >= wipeStopTic ) {
|
|
return;
|
|
}
|
|
|
|
if ( !wipeHold && latchedTic >= wipeStopTic ) {
|
|
return;
|
|
}
|
|
|
|
float fade = ( float )( latchedTic - wipeStartTic ) / ( wipeStopTic - wipeStartTic );
|
|
renderSystem->SetColor4( 1, 1, 1, fade );
|
|
renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, wipeMaterial );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::AdvanceRenderDemo
|
|
===============
|
|
*/
|
|
void idSessionLocal::AdvanceRenderDemo( bool singleFrameOnly ) {
|
|
if ( lastDemoTic == -1 ) {
|
|
lastDemoTic = latchedTicNumber - 1;
|
|
}
|
|
|
|
int skipFrames = 0;
|
|
|
|
if ( !aviCaptureMode && !timeDemo && !singleFrameOnly ) {
|
|
skipFrames = ( (latchedTicNumber - lastDemoTic) / USERCMD_PER_DEMO_FRAME ) - 1;
|
|
// never skip too many frames, just let it go into slightly slow motion
|
|
if ( skipFrames > 4 ) {
|
|
skipFrames = 4;
|
|
}
|
|
lastDemoTic = latchedTicNumber - latchedTicNumber % USERCMD_PER_DEMO_FRAME;
|
|
} else {
|
|
// always advance a single frame with avidemo and timedemo
|
|
lastDemoTic = latchedTicNumber;
|
|
}
|
|
|
|
while( skipFrames > -1 ) {
|
|
int ds = DS_FINISHED;
|
|
|
|
readDemo->ReadInt( ds );
|
|
if ( ds == DS_FINISHED ) {
|
|
if ( numDemoFrames != 1 ) {
|
|
// if the demo has a single frame (a demoShot), continuously replay
|
|
// the renderView that has already been read
|
|
Stop();
|
|
StartMenu();
|
|
}
|
|
break;
|
|
}
|
|
if ( ds == DS_RENDER ) {
|
|
if ( rw->ProcessDemoCommand( readDemo, ¤tDemoRenderView, &demoTimeOffset ) ) {
|
|
// a view is ready to render
|
|
skipFrames--;
|
|
numDemoFrames++;
|
|
}
|
|
continue;
|
|
}
|
|
if ( ds == DS_SOUND ) {
|
|
sw->ProcessDemoCommand( readDemo );
|
|
continue;
|
|
}
|
|
// appears in v1.2, with savegame format 17
|
|
if ( ds == DS_VERSION ) {
|
|
readDemo->ReadInt( renderdemoVersion );
|
|
common->Printf( "reading a v%d render demo\n", renderdemoVersion );
|
|
// set the savegameVersion to current for render demo paths that share the savegame paths
|
|
savegameVersion = SAVEGAME_VERSION;
|
|
continue;
|
|
}
|
|
common->Error( "Bad render demo token" );
|
|
}
|
|
|
|
if ( com_showDemo.GetBool() ) {
|
|
common->Printf( "frame:%i DemoTic:%i latched:%i skip:%i\n", numDemoFrames, lastDemoTic, latchedTicNumber, skipFrames );
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::DrawCmdGraph
|
|
|
|
Graphs yaw angle for testing smoothness
|
|
===============
|
|
*/
|
|
static const int ANGLE_GRAPH_HEIGHT = 128;
|
|
static const int ANGLE_GRAPH_STRETCH = 3;
|
|
void idSessionLocal::DrawCmdGraph() {
|
|
if ( !com_showAngles.GetBool() ) {
|
|
return;
|
|
}
|
|
renderSystem->SetColor4( 0.1f, 0.1f, 0.1f, 1.0f );
|
|
renderSystem->DrawStretchPic( 0, 480-ANGLE_GRAPH_HEIGHT, MAX_BUFFERED_USERCMD*ANGLE_GRAPH_STRETCH, ANGLE_GRAPH_HEIGHT, 0, 0, 1, 1, whiteMaterial );
|
|
renderSystem->SetColor4( 0.9f, 0.9f, 0.9f, 1.0f );
|
|
for ( int i = 0 ; i < MAX_BUFFERED_USERCMD-4 ; i++ ) {
|
|
usercmd_t cmd = usercmdGen->TicCmd( latchedTicNumber - (MAX_BUFFERED_USERCMD-4) + i );
|
|
int h = cmd.angles[1];
|
|
h >>= 8;
|
|
h &= (ANGLE_GRAPH_HEIGHT-1);
|
|
renderSystem->DrawStretchPic( i* ANGLE_GRAPH_STRETCH, 480-h, 1, h, 0, 0, 1, 1, whiteMaterial );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::PacifierUpdate
|
|
===============
|
|
*/
|
|
void idSessionLocal::PacifierUpdate() {
|
|
if ( !insideExecuteMapChange ) {
|
|
return;
|
|
}
|
|
|
|
// never do pacifier screen updates while inside the
|
|
// drawing code, or we can have various recursive problems
|
|
if ( insideUpdateScreen ) {
|
|
return;
|
|
}
|
|
|
|
int time = eventLoop->Milliseconds();
|
|
|
|
if ( time - lastPacifierTime < 100 ) {
|
|
return;
|
|
}
|
|
lastPacifierTime = time;
|
|
|
|
if ( guiLoading && bytesNeededForMapLoad ) {
|
|
float n = fileSystem->GetReadCount();
|
|
float pct = ( n / bytesNeededForMapLoad );
|
|
// pct = idMath::ClampFloat( 0.0f, 100.0f, pct );
|
|
guiLoading->SetStateFloat( "map_loading", pct );
|
|
guiLoading->StateChanged( com_frameTime );
|
|
}
|
|
|
|
Sys_GenerateEvents();
|
|
|
|
UpdateScreen();
|
|
|
|
idAsyncNetwork::client.PacifierUpdate();
|
|
idAsyncNetwork::server.PacifierUpdate();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::Draw
|
|
===============
|
|
*/
|
|
void idSessionLocal::Draw() {
|
|
bool fullConsole = false;
|
|
|
|
if ( insideExecuteMapChange ) {
|
|
if ( guiLoading ) {
|
|
guiLoading->Redraw( com_frameTime );
|
|
}
|
|
if ( guiActive == guiMsg ) {
|
|
guiMsg->Redraw( com_frameTime );
|
|
}
|
|
} else if ( guiTest ) {
|
|
// if testing a gui, clear the screen and draw it
|
|
// clear the background, in case the tested gui is transparent
|
|
// NOTE that you can't use this for aviGame recording, it will tick at real com_frameTime between screenshots..
|
|
renderSystem->SetColor( colorBlack );
|
|
renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) );
|
|
guiTest->Redraw( com_frameTime );
|
|
} else if ( guiActive && !guiActive->State().GetBool( "gameDraw" ) ) {
|
|
|
|
// draw the frozen gui in the background
|
|
if ( guiActive == guiMsg && guiMsgRestore ) {
|
|
guiMsgRestore->Redraw( com_frameTime );
|
|
}
|
|
|
|
// draw the menus full screen
|
|
if ( guiActive == guiTakeNotes && !com_skipGameDraw.GetBool() ) {
|
|
game->Draw( GetLocalClientNum() );
|
|
}
|
|
|
|
guiActive->Redraw( com_frameTime );
|
|
} else if ( readDemo ) {
|
|
rw->RenderScene( ¤tDemoRenderView );
|
|
renderSystem->DrawDemoPics();
|
|
} else if ( mapSpawned ) {
|
|
bool gameDraw = false;
|
|
// normal drawing for both single and multi player
|
|
if ( !com_skipGameDraw.GetBool() && GetLocalClientNum() >= 0 ) {
|
|
// draw the game view
|
|
int start = Sys_Milliseconds();
|
|
gameDraw = game->Draw( GetLocalClientNum() );
|
|
int end = Sys_Milliseconds();
|
|
time_gameDraw += ( end - start ); // note time used for com_speeds
|
|
}
|
|
if ( !gameDraw ) {
|
|
renderSystem->SetColor( colorBlack );
|
|
renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) );
|
|
}
|
|
|
|
// save off the 2D drawing from the game
|
|
if ( writeDemo ) {
|
|
renderSystem->WriteDemoPics();
|
|
}
|
|
} else {
|
|
#if ID_CONSOLE_LOCK
|
|
if ( com_allowConsole.GetBool() ) {
|
|
console->Draw( true );
|
|
} else {
|
|
emptyDrawCount++;
|
|
if ( emptyDrawCount > 5 ) {
|
|
// it's best if you can avoid triggering the watchgod by doing the right thing somewhere else
|
|
assert( false );
|
|
common->Warning( "idSession: triggering mainmenu watchdog" );
|
|
emptyDrawCount = 0;
|
|
StartMenu();
|
|
}
|
|
renderSystem->SetColor4( 0, 0, 0, 1 );
|
|
renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) );
|
|
}
|
|
#else
|
|
// draw the console full screen - this should only ever happen in developer builds
|
|
console->Draw( true );
|
|
#endif
|
|
fullConsole = true;
|
|
}
|
|
|
|
#if ID_CONSOLE_LOCK
|
|
if ( !fullConsole && emptyDrawCount ) {
|
|
common->DPrintf( "idSession: %d empty frame draws\n", emptyDrawCount );
|
|
emptyDrawCount = 0;
|
|
}
|
|
fullConsole = false;
|
|
#endif
|
|
|
|
// draw the wipe material on top of this if it hasn't completed yet
|
|
DrawWipeModel();
|
|
|
|
// draw debug graphs
|
|
DrawCmdGraph();
|
|
|
|
// draw the half console / notify console on top of everything
|
|
if ( !fullConsole ) {
|
|
console->Draw( false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::UpdateScreen
|
|
===============
|
|
*/
|
|
void idSessionLocal::UpdateScreen( bool outOfSequence ) {
|
|
|
|
#ifdef _WIN32
|
|
|
|
if ( com_editors ) {
|
|
if ( !Sys_IsWindowVisible() ) {
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( insideUpdateScreen ) {
|
|
return;
|
|
// common->FatalError( "idSessionLocal::UpdateScreen: recursively called" );
|
|
}
|
|
|
|
insideUpdateScreen = true;
|
|
|
|
// if this is a long-operation update and we are in windowed mode,
|
|
// release the mouse capture back to the desktop
|
|
if ( outOfSequence ) {
|
|
Sys_GrabMouseCursor( false );
|
|
}
|
|
|
|
renderSystem->BeginFrame( renderSystem->GetScreenWidth(), renderSystem->GetScreenHeight() );
|
|
|
|
// draw everything
|
|
Draw();
|
|
|
|
if ( com_speeds.GetBool() ) {
|
|
renderSystem->EndFrame( &time_frontend, &time_backend );
|
|
} else {
|
|
renderSystem->EndFrame( NULL, NULL );
|
|
}
|
|
|
|
insideUpdateScreen = false;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::Frame
|
|
===============
|
|
*/
|
|
extern bool CheckOpenALDeviceAndRecoverIfNeeded();
|
|
extern int g_screenshotFormat;
|
|
void idSessionLocal::Frame() {
|
|
|
|
if ( com_asyncSound.GetInteger() == 0 ) {
|
|
soundSystem->AsyncUpdateWrite( Sys_Milliseconds() );
|
|
}
|
|
|
|
// DG: periodically check if sound device is still there and try to reset it if not
|
|
// (calling this from idSoundSystem::AsyncUpdate(), which runs in a separate thread
|
|
// by default, causes a deadlock when calling idCommon->Warning())
|
|
CheckOpenALDeviceAndRecoverIfNeeded();
|
|
|
|
// Editors that completely take over the game
|
|
if ( com_editorActive && ( com_editors & ( EDITOR_RADIANT | EDITOR_GUI ) ) ) {
|
|
return;
|
|
}
|
|
|
|
#if 0 // handled via Sys_GenerateEvents() -> handleMouseGrab()
|
|
// if the console is down, we don't need to hold
|
|
// the mouse cursor
|
|
if ( console->Active() || com_editorActive ) {
|
|
Sys_GrabMouseCursor( false );
|
|
} else {
|
|
Sys_GrabMouseCursor( true );
|
|
}
|
|
#endif
|
|
|
|
// save the screenshot and audio from the last draw if needed
|
|
if ( aviCaptureMode ) {
|
|
idStr name;
|
|
|
|
name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviTicStart );
|
|
|
|
float ratio = 30.0f / ( 1000.0f / USERCMD_MSEC / com_aviDemoTics.GetInteger() );
|
|
aviDemoFrameCount += ratio;
|
|
if ( aviTicStart + 1 != ( int )aviDemoFrameCount ) {
|
|
// skipped frames so write them out
|
|
int c = aviDemoFrameCount - aviTicStart;
|
|
while ( c-- ) {
|
|
g_screenshotFormat = 0;
|
|
renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL );
|
|
name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), ++aviTicStart );
|
|
}
|
|
}
|
|
aviTicStart = aviDemoFrameCount;
|
|
|
|
// remove any printed lines at the top before taking the screenshot
|
|
console->ClearNotifyLines();
|
|
|
|
// this will call Draw, possibly multiple times if com_aviDemoSamples is > 1
|
|
g_screenshotFormat = 0;
|
|
renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL );
|
|
}
|
|
|
|
// at startup, we may be backwards
|
|
if ( latchedTicNumber > com_ticNumber ) {
|
|
latchedTicNumber = com_ticNumber;
|
|
}
|
|
|
|
// se how many tics we should have before continuing
|
|
int minTic = latchedTicNumber + 1;
|
|
if ( com_minTics.GetInteger() > 1 ) {
|
|
minTic = lastGameTic + com_minTics.GetInteger();
|
|
}
|
|
|
|
if ( readDemo ) {
|
|
if ( !timeDemo && numDemoFrames != 1 ) {
|
|
minTic = lastDemoTic + USERCMD_PER_DEMO_FRAME;
|
|
} else {
|
|
// timedemos and demoshots will run as fast as they can, other demos
|
|
// will not run more than 30 hz
|
|
minTic = latchedTicNumber;
|
|
}
|
|
} else if ( writeDemo ) {
|
|
minTic = lastGameTic + USERCMD_PER_DEMO_FRAME; // demos are recorded at 30 hz
|
|
}
|
|
|
|
// fixedTic lets us run a forced number of usercmd each frame without timing
|
|
if ( com_fixedTic.GetInteger() ) {
|
|
minTic = latchedTicNumber;
|
|
}
|
|
|
|
while( 1 ) {
|
|
latchedTicNumber = com_ticNumber;
|
|
if ( latchedTicNumber >= minTic ) {
|
|
break;
|
|
}
|
|
Sys_WaitForEvent( TRIGGER_EVENT_ONE );
|
|
}
|
|
|
|
if ( authEmitTimeout ) {
|
|
// waiting for a game auth
|
|
if ( Sys_Milliseconds() > authEmitTimeout ) {
|
|
// expired with no reply
|
|
// means that if a firewall is blocking the master, we will let through
|
|
common->DPrintf( "no reply from auth\n" );
|
|
if ( authWaitBox ) {
|
|
// close the wait box
|
|
StopBox();
|
|
authWaitBox = false;
|
|
}
|
|
if ( cdkey_state == CDKEY_CHECKING ) {
|
|
cdkey_state = CDKEY_OK;
|
|
}
|
|
if ( xpkey_state == CDKEY_CHECKING ) {
|
|
xpkey_state = CDKEY_OK;
|
|
}
|
|
// maintain this empty as it's set by auth denials
|
|
authMsg.Empty();
|
|
authEmitTimeout = 0;
|
|
SetCDKeyGuiVars();
|
|
}
|
|
}
|
|
|
|
// send frame and mouse events to active guis
|
|
GuiFrameEvents();
|
|
|
|
// advance demos
|
|
if ( readDemo ) {
|
|
AdvanceRenderDemo( false );
|
|
return;
|
|
}
|
|
|
|
//------------ single player game tics --------------
|
|
|
|
if ( !mapSpawned || guiActive ) {
|
|
if ( !com_asyncInput.GetBool() ) {
|
|
// early exit, won't do RunGameTic .. but still need to update mouse position for GUIs
|
|
usercmdGen->GetDirectUsercmd();
|
|
}
|
|
}
|
|
|
|
if ( !mapSpawned ) {
|
|
return;
|
|
}
|
|
|
|
if ( guiActive ) {
|
|
lastGameTic = latchedTicNumber;
|
|
return;
|
|
}
|
|
|
|
// in message box / GUIFrame, idSessionLocal::Frame is used for GUI interactivity
|
|
// but we early exit to avoid running game frames
|
|
if ( idAsyncNetwork::IsActive() ) {
|
|
return;
|
|
}
|
|
|
|
// check for user info changes
|
|
if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) {
|
|
mapSpawnData.userInfo[0] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO );
|
|
game->SetUserInfo( 0, mapSpawnData.userInfo[0], false, false );
|
|
cvarSystem->ClearModifiedFlags( CVAR_USERINFO );
|
|
}
|
|
|
|
// see how many usercmds we are going to run
|
|
int numCmdsToRun = latchedTicNumber - lastGameTic;
|
|
|
|
// don't let a long onDemand sound load unsync everything
|
|
if ( timeHitch ) {
|
|
int skip = timeHitch / USERCMD_MSEC;
|
|
lastGameTic += skip;
|
|
numCmdsToRun -= skip;
|
|
timeHitch = 0;
|
|
}
|
|
|
|
// don't get too far behind after a hitch
|
|
if ( numCmdsToRun > 10 ) {
|
|
lastGameTic = latchedTicNumber - 10;
|
|
}
|
|
|
|
// never use more than USERCMD_PER_DEMO_FRAME,
|
|
// which makes it go into slow motion when recording
|
|
if ( writeDemo ) {
|
|
int fixedTic = USERCMD_PER_DEMO_FRAME;
|
|
// we should have waited long enough
|
|
if ( numCmdsToRun < fixedTic ) {
|
|
common->Error( "idSessionLocal::Frame: numCmdsToRun < fixedTic" );
|
|
}
|
|
// we may need to dump older commands
|
|
lastGameTic = latchedTicNumber - fixedTic;
|
|
} else if ( com_fixedTic.GetInteger() > 0 ) {
|
|
// this may cause commands run in a previous frame to
|
|
// be run again if we are going at above the real time rate
|
|
lastGameTic = latchedTicNumber - com_fixedTic.GetInteger();
|
|
} else if ( aviCaptureMode ) {
|
|
lastGameTic = latchedTicNumber - com_aviDemoTics.GetInteger();
|
|
}
|
|
|
|
// force only one game frame update this frame. the game code requests this after skipping cinematics
|
|
// so we come back immediately after the cinematic is done instead of a few frames later which can
|
|
// cause sounds played right after the cinematic to not play.
|
|
if ( syncNextGameFrame ) {
|
|
lastGameTic = latchedTicNumber - 1;
|
|
syncNextGameFrame = false;
|
|
}
|
|
|
|
// create client commands, which will be sent directly
|
|
// to the game
|
|
if ( com_showTics.GetBool() ) {
|
|
common->Printf( "%i ", latchedTicNumber - lastGameTic );
|
|
}
|
|
|
|
int gameTicsToRun = latchedTicNumber - lastGameTic;
|
|
int i;
|
|
for ( i = 0 ; i < gameTicsToRun ; i++ ) {
|
|
RunGameTic();
|
|
if ( !mapSpawned ) {
|
|
// exited game play
|
|
break;
|
|
}
|
|
if ( syncNextGameFrame ) {
|
|
// long game frame, so break out and continue executing as if there was no hitch
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::RunGameTic
|
|
================
|
|
*/
|
|
void idSessionLocal::RunGameTic() {
|
|
logCmd_t logCmd;
|
|
usercmd_t cmd;
|
|
|
|
// if we are doing a command demo, read or write from the file
|
|
if ( cmdDemoFile ) {
|
|
if ( !cmdDemoFile->Read( &logCmd, sizeof( logCmd ) ) ) {
|
|
common->Printf( "Command demo completed at logIndex %i\n", logIndex );
|
|
fileSystem->CloseFile( cmdDemoFile );
|
|
cmdDemoFile = NULL;
|
|
if ( aviCaptureMode ) {
|
|
EndAVICapture();
|
|
Shutdown();
|
|
}
|
|
// we fall out of the demo to normal commands
|
|
// the impulse and chat character toggles may not be correct, and the view
|
|
// angle will definitely be wrong
|
|
} else {
|
|
cmd = logCmd.cmd;
|
|
cmd.ByteSwap();
|
|
logCmd.consistencyHash = LittleInt( logCmd.consistencyHash );
|
|
}
|
|
}
|
|
|
|
// if we didn't get one from the file, get it locally
|
|
if ( !cmdDemoFile ) {
|
|
// get a locally created command
|
|
if ( com_asyncInput.GetBool() ) {
|
|
cmd = usercmdGen->TicCmd( lastGameTic );
|
|
} else {
|
|
cmd = usercmdGen->GetDirectUsercmd();
|
|
}
|
|
lastGameTic++;
|
|
}
|
|
|
|
// run the game logic every player move
|
|
int start = Sys_Milliseconds();
|
|
gameReturn_t ret = game->RunFrame( &cmd );
|
|
|
|
int end = Sys_Milliseconds();
|
|
time_gameFrame += end - start; // note time used for com_speeds
|
|
|
|
// check for constency failure from a recorded command
|
|
if ( cmdDemoFile ) {
|
|
if ( ret.consistencyHash != logCmd.consistencyHash ) {
|
|
common->Printf( "Consistency failure on logIndex %i\n", logIndex );
|
|
Stop();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// save the cmd for cmdDemo archiving
|
|
if ( logIndex < MAX_LOGGED_USERCMDS ) {
|
|
loggedUsercmds[logIndex].cmd = cmd;
|
|
// save the consistencyHash for demo playback verification
|
|
loggedUsercmds[logIndex].consistencyHash = ret.consistencyHash;
|
|
if (logIndex % 30 == 0 && statIndex < MAX_LOGGED_STATS) {
|
|
loggedStats[statIndex].health = ret.health;
|
|
loggedStats[statIndex].heartRate = ret.heartRate;
|
|
loggedStats[statIndex].stamina = ret.stamina;
|
|
loggedStats[statIndex].combat = ret.combat;
|
|
statIndex++;
|
|
}
|
|
logIndex++;
|
|
}
|
|
|
|
syncNextGameFrame = ret.syncNextGameFrame;
|
|
|
|
if ( ret.sessionCommand[0] ) {
|
|
idCmdArgs args;
|
|
|
|
args.TokenizeString( ret.sessionCommand, false );
|
|
|
|
if ( !idStr::Icmp( args.Argv(0), "map" ) ) {
|
|
// get current player states
|
|
for ( int i = 0 ; i < numClients ; i++ ) {
|
|
mapSpawnData.persistentPlayerInfo[i] = game->GetPersistentPlayerInfo( i );
|
|
}
|
|
// clear the devmap key on serverinfo, so player spawns
|
|
// won't get the map testing items
|
|
mapSpawnData.serverInfo.Delete( "devmap" );
|
|
|
|
// go to the next map
|
|
MoveToNewMap( args.Argv(1) );
|
|
} else if ( !idStr::Icmp( args.Argv(0), "devmap" ) ) {
|
|
mapSpawnData.serverInfo.Set( "devmap", "1" );
|
|
MoveToNewMap( args.Argv(1) );
|
|
} else if ( !idStr::Icmp( args.Argv(0), "died" ) ) {
|
|
// restart on the same map
|
|
UnloadMap();
|
|
SetGUI(guiRestartMenu, NULL);
|
|
} else if ( !idStr::Icmp( args.Argv(0), "disconnect" ) ) {
|
|
cmdSystem->BufferCommandText( CMD_EXEC_INSERT, "stoprecording ; disconnect" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::Init
|
|
|
|
Called in an orderly fashion at system startup,
|
|
so commands, cvars, files, etc are all available
|
|
===============
|
|
*/
|
|
void idSessionLocal::Init() {
|
|
|
|
common->Printf( "----- Initializing Session -----\n" );
|
|
|
|
cmdSystem->AddCommand( "writePrecache", Sess_WritePrecache_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "writes precache commands" );
|
|
|
|
#ifndef ID_DEDICATED
|
|
cmdSystem->AddCommand( "map", Session_Map_f, CMD_FL_SYSTEM, "loads a map", idCmdSystem::ArgCompletion_MapName );
|
|
cmdSystem->AddCommand( "devmap", Session_DevMap_f, CMD_FL_SYSTEM, "loads a map in developer mode", idCmdSystem::ArgCompletion_MapName );
|
|
cmdSystem->AddCommand( "testmap", Session_TestMap_f, CMD_FL_SYSTEM, "tests a map", idCmdSystem::ArgCompletion_MapName );
|
|
|
|
cmdSystem->AddCommand( "writeCmdDemo", Session_WriteCmdDemo_f, CMD_FL_SYSTEM, "writes a command demo" );
|
|
cmdSystem->AddCommand( "playCmdDemo", Session_PlayCmdDemo_f, CMD_FL_SYSTEM, "plays back a command demo" );
|
|
cmdSystem->AddCommand( "timeCmdDemo", Session_TimeCmdDemo_f, CMD_FL_SYSTEM, "times a command demo" );
|
|
cmdSystem->AddCommand( "exitCmdDemo", Session_ExitCmdDemo_f, CMD_FL_SYSTEM, "exits a command demo" );
|
|
cmdSystem->AddCommand( "aviCmdDemo", Session_AVICmdDemo_f, CMD_FL_SYSTEM, "writes AVIs for a command demo" );
|
|
cmdSystem->AddCommand( "aviGame", Session_AVIGame_f, CMD_FL_SYSTEM, "writes AVIs for the current game" );
|
|
|
|
cmdSystem->AddCommand( "recordDemo", Session_RecordDemo_f, CMD_FL_SYSTEM, "records a demo" );
|
|
cmdSystem->AddCommand( "stopRecording", Session_StopRecordingDemo_f, CMD_FL_SYSTEM, "stops demo recording" );
|
|
cmdSystem->AddCommand( "playDemo", Session_PlayDemo_f, CMD_FL_SYSTEM, "plays back a demo", idCmdSystem::ArgCompletion_DemoName );
|
|
cmdSystem->AddCommand( "timeDemo", Session_TimeDemo_f, CMD_FL_SYSTEM, "times a demo", idCmdSystem::ArgCompletion_DemoName );
|
|
cmdSystem->AddCommand( "timeDemoQuit", Session_TimeDemoQuit_f, CMD_FL_SYSTEM, "times a demo and quits", idCmdSystem::ArgCompletion_DemoName );
|
|
cmdSystem->AddCommand( "aviDemo", Session_AVIDemo_f, CMD_FL_SYSTEM, "writes AVIs for a demo", idCmdSystem::ArgCompletion_DemoName );
|
|
cmdSystem->AddCommand( "compressDemo", Session_CompressDemo_f, CMD_FL_SYSTEM, "compresses a demo file", idCmdSystem::ArgCompletion_DemoName );
|
|
#endif
|
|
|
|
cmdSystem->AddCommand( "disconnect", Session_Disconnect_f, CMD_FL_SYSTEM, "disconnects from a game" );
|
|
|
|
cmdSystem->AddCommand( "demoShot", Session_DemoShot_f, CMD_FL_SYSTEM, "writes a screenshot for a demo" );
|
|
cmdSystem->AddCommand( "testGUI", Session_TestGUI_f, CMD_FL_SYSTEM, "tests a gui" );
|
|
|
|
#ifndef ID_DEDICATED
|
|
cmdSystem->AddCommand( "saveGame", SaveGame_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "saves a game" );
|
|
cmdSystem->AddCommand( "loadGame", LoadGame_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "loads a game", idCmdSystem::ArgCompletion_SaveGame );
|
|
#endif
|
|
|
|
cmdSystem->AddCommand( "takeViewNotes", TakeViewNotes_f, CMD_FL_SYSTEM, "take notes about the current map from the current view" );
|
|
cmdSystem->AddCommand( "takeViewNotes2", TakeViewNotes2_f, CMD_FL_SYSTEM, "extended take view notes" );
|
|
|
|
cmdSystem->AddCommand( "rescanSI", Session_RescanSI_f, CMD_FL_SYSTEM, "internal - rescan serverinfo cvars and tell game" );
|
|
|
|
cmdSystem->AddCommand( "promptKey", Session_PromptKey_f, CMD_FL_SYSTEM, "prompt and sets the CD Key" );
|
|
|
|
cmdSystem->AddCommand( "hitch", Session_Hitch_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "hitches the game" );
|
|
|
|
// the same idRenderWorld will be used for all games
|
|
// and demos, insuring that level specific models
|
|
// will be freed
|
|
rw = renderSystem->AllocRenderWorld();
|
|
sw = soundSystem->AllocSoundWorld( rw );
|
|
|
|
menuSoundWorld = soundSystem->AllocSoundWorld( rw );
|
|
|
|
// we have a single instance of the main menu
|
|
guiMainMenu = uiManager->FindGui( "guis/mainmenu.gui", true, false, true );
|
|
if (!guiMainMenu) {
|
|
guiMainMenu = uiManager->FindGui( "guis/demo_mainmenu.gui", true, false, true );
|
|
demoversion = (guiMainMenu != NULL);
|
|
}
|
|
guiMainMenu_MapList = uiManager->AllocListGUI();
|
|
guiMainMenu_MapList->Config( guiMainMenu, "mapList" );
|
|
idAsyncNetwork::client.serverList.GUIConfig( guiMainMenu, "serverList" );
|
|
guiRestartMenu = uiManager->FindGui( "guis/restart.gui", true, false, true );
|
|
guiGameOver = uiManager->FindGui( "guis/gameover.gui", true, false, true );
|
|
guiMsg = uiManager->FindGui( "guis/msg.gui", true, false, true );
|
|
guiTakeNotes = uiManager->FindGui( "guis/takeNotes.gui", true, false, true );
|
|
guiIntro = uiManager->FindGui( "guis/intro.gui", true, false, true );
|
|
|
|
whiteMaterial = declManager->FindMaterial( "_white" );
|
|
|
|
guiInGame = NULL;
|
|
guiTest = NULL;
|
|
|
|
guiActive = NULL;
|
|
guiHandle = NULL;
|
|
|
|
ReadCDKey();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::GetLocalClientNum
|
|
===============
|
|
*/
|
|
int idSessionLocal::GetLocalClientNum() {
|
|
if ( idAsyncNetwork::client.IsActive() ) {
|
|
return idAsyncNetwork::client.GetLocalClientNum();
|
|
} else if ( idAsyncNetwork::server.IsActive() ) {
|
|
if ( idAsyncNetwork::serverDedicated.GetInteger() == 0 ) {
|
|
return 0;
|
|
} else if ( idAsyncNetwork::server.IsClientInGame( idAsyncNetwork::serverDrawClient.GetInteger() ) ) {
|
|
return idAsyncNetwork::serverDrawClient.GetInteger();
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::SetPlayingSoundWorld
|
|
===============
|
|
*/
|
|
void idSessionLocal::SetPlayingSoundWorld() {
|
|
if ( guiActive && ( guiActive == guiMainMenu || guiActive == guiIntro || guiActive == guiLoading || ( guiActive == guiMsg && !mapSpawned ) ) ) {
|
|
soundSystem->SetPlayingSoundWorld( menuSoundWorld );
|
|
} else {
|
|
soundSystem->SetPlayingSoundWorld( sw );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::TimeHitch
|
|
|
|
this is used by the sound system when an OnDemand sound is loaded, so the game action
|
|
doesn't advance and get things out of sync
|
|
===============
|
|
*/
|
|
void idSessionLocal::TimeHitch( int msec ) {
|
|
timeHitch += msec;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idSessionLocal::ReadCDKey
|
|
=================
|
|
*/
|
|
void idSessionLocal::ReadCDKey( void ) {
|
|
idStr filename;
|
|
idFile *f;
|
|
char buffer[32];
|
|
|
|
cdkey_state = CDKEY_UNKNOWN;
|
|
|
|
filename = CDKEY_FILEPATH;
|
|
f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_configpath" ) );
|
|
|
|
// try the install path, which is where the cd installer and steam put it
|
|
if ( !f )
|
|
f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_basepath" ) );
|
|
|
|
if ( !f ) {
|
|
common->Printf( "Couldn't read %s.\n", filename.c_str() );
|
|
cdkey[ 0 ] = '\0';
|
|
} else {
|
|
memset( buffer, 0, sizeof(buffer) );
|
|
f->Read( buffer, CDKEY_BUF_LEN - 1 );
|
|
fileSystem->CloseFile( f );
|
|
idStr::Copynz( cdkey, buffer, CDKEY_BUF_LEN );
|
|
}
|
|
|
|
xpkey_state = CDKEY_UNKNOWN;
|
|
|
|
filename = XPKEY_FILEPATH;
|
|
f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_configpath" ) );
|
|
|
|
// try the install path, which is where the cd installer and steam put it
|
|
if ( !f )
|
|
f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_basepath" ) );
|
|
|
|
if ( !f ) {
|
|
common->Printf( "Couldn't read %s.\n", filename.c_str() );
|
|
xpkey[ 0 ] = '\0';
|
|
} else {
|
|
memset( buffer, 0, sizeof(buffer) );
|
|
f->Read( buffer, CDKEY_BUF_LEN - 1 );
|
|
fileSystem->CloseFile( f );
|
|
idStr::Copynz( xpkey, buffer, CDKEY_BUF_LEN );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::WriteCDKey
|
|
================
|
|
*/
|
|
void idSessionLocal::WriteCDKey( void ) {
|
|
idStr filename;
|
|
idFile *f;
|
|
const char *OSPath;
|
|
|
|
filename = CDKEY_FILEPATH;
|
|
// OpenFileWrite advertises creating directories to the path if needed, but that won't work with a '..' in the path
|
|
// occasionally on windows, but mostly on Linux and OSX, the fs_configpath/base may not exist in full
|
|
OSPath = fileSystem->BuildOSPath( cvarSystem->GetCVarString( "fs_configpath" ), BASE_GAMEDIR, CDKEY_FILE );
|
|
fileSystem->CreateOSPath( OSPath );
|
|
f = fileSystem->OpenFileWrite( filename, "fs_configpath" );
|
|
if ( !f ) {
|
|
common->Printf( "Couldn't write %s.\n", filename.c_str() );
|
|
return;
|
|
}
|
|
f->Printf( "%s%s", cdkey, CDKEY_TEXT );
|
|
fileSystem->CloseFile( f );
|
|
|
|
filename = XPKEY_FILEPATH;
|
|
f = fileSystem->OpenFileWrite( filename, "fs_configpath" );
|
|
if ( !f ) {
|
|
common->Printf( "Couldn't write %s.\n", filename.c_str() );
|
|
return;
|
|
}
|
|
f->Printf( "%s%s", xpkey, CDKEY_TEXT );
|
|
fileSystem->CloseFile( f );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::ClearKey
|
|
===============
|
|
*/
|
|
void idSessionLocal::ClearCDKey( bool valid[ 2 ] ) {
|
|
if ( !valid[ 0 ] ) {
|
|
memset( cdkey, 0, CDKEY_BUF_LEN );
|
|
cdkey_state = CDKEY_UNKNOWN;
|
|
} else if ( cdkey_state == CDKEY_CHECKING ) {
|
|
// if a key was in checking and not explicitely asked for clearing, put it back to ok
|
|
cdkey_state = CDKEY_OK;
|
|
}
|
|
if ( !valid[ 1 ] ) {
|
|
memset( xpkey, 0, CDKEY_BUF_LEN );
|
|
xpkey_state = CDKEY_UNKNOWN;
|
|
} else if ( xpkey_state == CDKEY_CHECKING ) {
|
|
xpkey_state = CDKEY_OK;
|
|
}
|
|
WriteCDKey( );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::GetCDKey
|
|
================
|
|
*/
|
|
const char *idSessionLocal::GetCDKey( bool xp ) {
|
|
if ( !xp ) {
|
|
return cdkey;
|
|
}
|
|
if ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING ) {
|
|
return xpkey;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// digits to letters table
|
|
#define CDKEY_DIGITS "TWSBJCGD7PA23RLH"
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::EmitGameAuth
|
|
we toggled some key state to CDKEY_CHECKING. send a standalone auth packet to validate
|
|
===============
|
|
*/
|
|
void idSessionLocal::EmitGameAuth( void ) {
|
|
// make sure the auth reply is empty, we use it to indicate an auth reply
|
|
authMsg.Empty();
|
|
if ( idAsyncNetwork::client.SendAuthCheck( cdkey_state == CDKEY_CHECKING ? cdkey : NULL, xpkey_state == CDKEY_CHECKING ? xpkey : NULL ) ) {
|
|
authEmitTimeout = Sys_Milliseconds() + CDKEY_AUTH_TIMEOUT;
|
|
common->DPrintf( "authing with the master..\n" );
|
|
} else {
|
|
// net is not available
|
|
common->DPrintf( "sendAuthCheck failed\n" );
|
|
if ( cdkey_state == CDKEY_CHECKING ) {
|
|
cdkey_state = CDKEY_OK;
|
|
}
|
|
if ( xpkey_state == CDKEY_CHECKING ) {
|
|
xpkey_state = CDKEY_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idSessionLocal::CheckKey
|
|
the function will only modify keys to _OK or _CHECKING if the offline checks are passed
|
|
if the function returns false, the offline checks failed, and offline_valid holds which keys are bad
|
|
================
|
|
*/
|
|
bool idSessionLocal::CheckKey( const char *key, bool netConnect, bool offline_valid[ 2 ] ) {
|
|
char lkey[ 2 ][ CDKEY_BUF_LEN ];
|
|
char l_chk[ 2 ][ 3 ];
|
|
char s_chk[ 3 ];
|
|
int imax,i_key;
|
|
unsigned int checksum, chk8;
|
|
bool edited_key[ 2 ];
|
|
|
|
// make sure have a right input string
|
|
assert( strlen( key ) == ( CDKEY_BUF_LEN - 1 ) * 2 + 4 + 3 + 4 );
|
|
|
|
edited_key[ 0 ] = ( key[0] == '1' );
|
|
idStr::Copynz( lkey[0], key + 2, CDKEY_BUF_LEN );
|
|
idStr::ToUpper( lkey[0] );
|
|
idStr::Copynz( l_chk[0], key + CDKEY_BUF_LEN + 2, 3 );
|
|
idStr::ToUpper( l_chk[0] );
|
|
edited_key[ 1 ] = ( key[ CDKEY_BUF_LEN + 2 + 3 ] == '1' );
|
|
idStr::Copynz( lkey[1], key + CDKEY_BUF_LEN + 7, CDKEY_BUF_LEN );
|
|
idStr::ToUpper( lkey[1] );
|
|
idStr::Copynz( l_chk[1], key + CDKEY_BUF_LEN * 2 + 7, 3 );
|
|
idStr::ToUpper( l_chk[1] );
|
|
|
|
if ( fileSystem->HasD3XP() ) {
|
|
imax = 2;
|
|
} else {
|
|
imax = 1;
|
|
}
|
|
offline_valid[ 0 ] = offline_valid[ 1 ] = true;
|
|
for( i_key = 0; i_key < imax; i_key++ ) {
|
|
// check that the characters are from the valid set
|
|
int i;
|
|
for ( i = 0; i < CDKEY_BUF_LEN - 1; i++ ) {
|
|
if ( !strchr( CDKEY_DIGITS, lkey[i_key][i] ) ) {
|
|
offline_valid[ i_key ] = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( edited_key[ i_key ] ) {
|
|
// verify the checksum for edited keys only
|
|
checksum = CRC32_BlockChecksum( lkey[i_key], CDKEY_BUF_LEN - 1 );
|
|
chk8 = ( checksum & 0xff ) ^ ( ( ( checksum & 0xff00 ) >> 8 ) ^ ( ( ( checksum & 0xff0000 ) >> 16 ) ^ ( ( checksum & 0xff000000 ) >> 24 ) ) );
|
|
idStr::snPrintf( s_chk, 3, "%02X", chk8 );
|
|
if ( idStr::Icmp( l_chk[i_key], s_chk ) != 0 ) {
|
|
offline_valid[ i_key ] = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !offline_valid[ 0 ] || !offline_valid[1] ) {
|
|
return false;
|
|
}
|
|
|
|
// offline checks passed, we'll return true and optionally emit key check requests
|
|
// the function should only modify the key states if the offline checks passed successfully
|
|
|
|
// set the keys, don't send a game auth if we are net connecting
|
|
idStr::Copynz( cdkey, lkey[0], CDKEY_BUF_LEN );
|
|
netConnect ? cdkey_state = CDKEY_OK : cdkey_state = CDKEY_CHECKING;
|
|
if ( fileSystem->HasD3XP() ) {
|
|
idStr::Copynz( xpkey, lkey[1], CDKEY_BUF_LEN );
|
|
netConnect ? xpkey_state = CDKEY_OK : xpkey_state = CDKEY_CHECKING;
|
|
} else {
|
|
xpkey_state = CDKEY_NA;
|
|
}
|
|
if ( !netConnect ) {
|
|
EmitGameAuth();
|
|
}
|
|
SetCDKeyGuiVars();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::CDKeysAreValid
|
|
checking that the key is present and uses only valid characters
|
|
if d3xp is installed, check for a valid xpkey as well
|
|
emit an auth packet to the master if possible and needed
|
|
===============
|
|
*/
|
|
bool idSessionLocal::CDKeysAreValid( bool strict ) {
|
|
int i;
|
|
bool emitAuth = false;
|
|
|
|
if ( cdkey_state == CDKEY_UNKNOWN ) {
|
|
if ( strlen( cdkey ) != CDKEY_BUF_LEN - 1 ) {
|
|
cdkey_state = CDKEY_INVALID;
|
|
} else {
|
|
for ( i = 0; i < CDKEY_BUF_LEN-1; i++ ) {
|
|
if ( !strchr( CDKEY_DIGITS, cdkey[i] ) ) {
|
|
cdkey_state = CDKEY_INVALID;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( cdkey_state == CDKEY_UNKNOWN ) {
|
|
cdkey_state = CDKEY_CHECKING;
|
|
emitAuth = true;
|
|
}
|
|
}
|
|
if ( xpkey_state == CDKEY_UNKNOWN ) {
|
|
if ( fileSystem->HasD3XP() ) {
|
|
if ( strlen( xpkey ) != CDKEY_BUF_LEN -1 ) {
|
|
xpkey_state = CDKEY_INVALID;
|
|
} else {
|
|
for ( i = 0; i < CDKEY_BUF_LEN-1; i++ ) {
|
|
if ( !strchr( CDKEY_DIGITS, xpkey[i] ) ) {
|
|
xpkey_state = CDKEY_INVALID;
|
|
}
|
|
}
|
|
}
|
|
if ( xpkey_state == CDKEY_UNKNOWN ) {
|
|
xpkey_state = CDKEY_CHECKING;
|
|
emitAuth = true;
|
|
}
|
|
} else {
|
|
xpkey_state = CDKEY_NA;
|
|
}
|
|
}
|
|
if ( emitAuth ) {
|
|
EmitGameAuth();
|
|
}
|
|
// make sure to keep the mainmenu gui up to date in case we made state changes
|
|
SetCDKeyGuiVars();
|
|
if ( strict ) {
|
|
return cdkey_state == CDKEY_OK && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_NA );
|
|
} else {
|
|
return ( cdkey_state == CDKEY_OK || cdkey_state == CDKEY_CHECKING ) && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING || xpkey_state == CDKEY_NA );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::WaitingForGameAuth
|
|
===============
|
|
*/
|
|
bool idSessionLocal::WaitingForGameAuth( void ) {
|
|
return authEmitTimeout != 0;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::CDKeysAuthReply
|
|
===============
|
|
*/
|
|
void idSessionLocal::CDKeysAuthReply( bool valid, const char *auth_msg ) {
|
|
//assert( authEmitTimeout > 0 );
|
|
if ( authWaitBox ) {
|
|
// close the wait box
|
|
StopBox();
|
|
authWaitBox = false;
|
|
}
|
|
if ( !valid ) {
|
|
common->DPrintf( "auth key is invalid\n" );
|
|
authMsg = auth_msg;
|
|
if ( cdkey_state == CDKEY_CHECKING ) {
|
|
cdkey_state = CDKEY_INVALID;
|
|
}
|
|
if ( xpkey_state == CDKEY_CHECKING ) {
|
|
xpkey_state = CDKEY_INVALID;
|
|
}
|
|
} else {
|
|
common->DPrintf( "client is authed in\n" );
|
|
if ( cdkey_state == CDKEY_CHECKING ) {
|
|
cdkey_state = CDKEY_OK;
|
|
}
|
|
if ( xpkey_state == CDKEY_CHECKING ) {
|
|
xpkey_state = CDKEY_OK;
|
|
}
|
|
}
|
|
authEmitTimeout = 0;
|
|
SetCDKeyGuiVars();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::GetCurrentMapName
|
|
===============
|
|
*/
|
|
const char *idSessionLocal::GetCurrentMapName() {
|
|
return currentMapName.c_str();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::GetSaveGameVersion
|
|
===============
|
|
*/
|
|
int idSessionLocal::GetSaveGameVersion( void ) {
|
|
return savegameVersion;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::GetAuthMsg
|
|
===============
|
|
*/
|
|
const char *idSessionLocal::GetAuthMsg( void ) {
|
|
return authMsg.c_str();
|
|
}
|