This check permits to fix interpolation problems on the local player that valve has been (fucking finally) caring about on counter-strike 2. To recall the original issue, the problem that Valve cared about is that interpolation had some problems with interpolating the local player because the screen would never in the first place match the tick "screen", because interpolation amount could never reach 0.0 or 1.0 Valve solution was to introduce bugs with lag compensating the local player and made the game worse, introducing a new way for cheaters to cheat even more on their games. I'm joking, but you can clearly see the outcome anyway. My solution is to simply set interpolation amount to 0.0 when a tick arrives. So when we shoot, we get the frame we shot with an interpolation amount at 0.0, perfectly aligned to user commands which is ideal for us. It might look a bit more unsmooth with lower fps but with high enough fps, the issue goes away anyway. It's not very noticeable which is very nice for us. No need to lag compensate the local player anymore !
3170 lines
86 KiB
C++
3170 lines
86 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $Workfile: $
|
|
// $Date: $
|
|
// $NoKeywords: $
|
|
//===========================================================================//
|
|
|
|
#include "client_pch.h"
|
|
#include "sound.h"
|
|
#include <inetchannel.h>
|
|
#include "checksum_engine.h"
|
|
#include "con_nprint.h"
|
|
#include "r_local.h"
|
|
#include "gl_lightmap.h"
|
|
#include "console.h"
|
|
#include "sv_main.h"
|
|
#include "traceinit.h"
|
|
#include "cl_demo.h"
|
|
#include "cdll_engine_int.h"
|
|
#include "debugoverlay.h"
|
|
#include "filesystem_engine.h"
|
|
#include "icliententity.h"
|
|
#include "dt_recv_eng.h"
|
|
#include "vgui_baseui_interface.h"
|
|
#include "testscriptmgr.h"
|
|
#include <tier0/vprof.h>
|
|
#include <proto_oob.h>
|
|
#include "materialsystem/imaterialsystemhardwareconfig.h"
|
|
#include "gl_matsysiface.h"
|
|
#include "staticpropmgr.h"
|
|
#include "ispatialpartitioninternal.h"
|
|
#include "cbenchmark.h"
|
|
#include "vox.h"
|
|
#include "LocalNetworkBackdoor.h"
|
|
#include <tier0/icommandline.h>
|
|
#include "GameEventManager.h"
|
|
#include "host_saverestore.h"
|
|
#include "ivideomode.h"
|
|
#include "host_phonehome.h"
|
|
#include "decal.h"
|
|
#include "sv_rcon.h"
|
|
#include "cl_rcon.h"
|
|
#include "vgui_baseui_interface.h"
|
|
#include "snd_audio_source.h"
|
|
#include "iregistry.h"
|
|
#include "sys.h"
|
|
#include <vstdlib/random.h>
|
|
#include "tier0/etwprof.h"
|
|
#include "tier0/vcrmode.h"
|
|
#include "sys_dll.h"
|
|
#include "video/ivideoservices.h"
|
|
#include "cl_steamauth.h"
|
|
#include "filesystem/IQueuedLoader.h"
|
|
#include "tier2/tier2.h"
|
|
#include "host_state.h"
|
|
#include "enginethreads.h"
|
|
#include "vgui/ISystem.h"
|
|
#include "pure_server.h"
|
|
#include "SoundEmitterSystem/isoundemittersystembase.h"
|
|
#include "LoadScreenUpdate.h"
|
|
#include "tier0/systeminformation.h"
|
|
#include "steam/steam_api.h"
|
|
#include "SourceAppInfo.h"
|
|
#include "cl_steamauth.h"
|
|
#include "sv_steamauth.h"
|
|
#include "engine/ivmodelinfo.h"
|
|
#ifdef _X360
|
|
#include "xbox/xbox_launch.h"
|
|
#endif
|
|
#if defined( REPLAY_ENABLED )
|
|
#include "replay_internal.h"
|
|
#endif
|
|
|
|
#include "language.h"
|
|
#include "igame.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
extern IVEngineClient *engineClient;
|
|
|
|
void R_UnloadSkys( void );
|
|
void CL_ResetEntityBits( void );
|
|
void EngineTool_UpdateScreenshot();
|
|
void WriteConfig_f( ConVar *var, const char *pOldString );
|
|
|
|
// If we get more than 250 messages in the incoming buffer queue, dump any above this #
|
|
#define MAX_INCOMING_MESSAGES 250
|
|
// Size of command send buffer
|
|
#define MAX_CMD_BUFFER 4000
|
|
|
|
CGlobalVarsBase g_ClientGlobalVariables( true );
|
|
IVideoRecorder *g_pVideoRecorder = NULL;
|
|
|
|
extern ConVar rcon_password;
|
|
extern ConVar host_framerate;
|
|
extern ConVar cl_clanid;
|
|
|
|
ConVar sv_unlockedchapters( "sv_unlockedchapters", "1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Highest unlocked game chapter." );
|
|
|
|
static ConVar tv_nochat ( "tv_nochat", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Don't receive chat messages from other SourceTV spectators" );
|
|
static ConVar cl_LocalNetworkBackdoor( "cl_localnetworkbackdoor", "1", 0, "Enable network optimizations for single player games." );
|
|
static ConVar cl_ignorepackets( "cl_ignorepackets", "0", FCVAR_CHEAT, "Force client to ignore packets (for debugging)." );
|
|
static ConVar cl_playback_screenshots( "cl_playback_screenshots", "0", 0, "Allows the client to playback screenshot and jpeg commands in demos." );
|
|
|
|
#if defined( STAGING_ONLY ) || defined( _DEBUG )
|
|
static ConVar cl_block_usercommand( "cl_block_usercommand", "0", FCVAR_CHEAT, "Force client to not send usercommand (for debugging)." );
|
|
#endif // STAGING_ONLY || _DEBUG
|
|
|
|
ConVar dev_loadtime_map_start( "dev_loadtime_map_start", "0.0", FCVAR_HIDDEN);
|
|
ConVar dev_loadtime_map_elapsed( "dev_loadtime_map_elapsed", "0.0", FCVAR_HIDDEN );
|
|
|
|
MovieInfo_t cl_movieinfo;
|
|
|
|
// FIXME: put these on hunk?
|
|
dlight_t cl_dlights[MAX_DLIGHTS];
|
|
dlight_t cl_elights[MAX_ELIGHTS];
|
|
CFastPointLeafNum g_DLightLeafAccessors[MAX_DLIGHTS];
|
|
CFastPointLeafNum g_ELightLeafAccessors[MAX_ELIGHTS];
|
|
|
|
bool cl_takesnapshot = false;
|
|
static bool cl_takejpeg = false;
|
|
static bool cl_takesnapshot_internal = false;
|
|
|
|
static int cl_jpegquality = DEFAULT_JPEG_QUALITY;
|
|
static ConVar jpeg_quality( "jpeg_quality", "90", 0, "jpeg screenshot quality." );
|
|
|
|
static int cl_snapshotnum = 0;
|
|
static char cl_snapshotname[MAX_OSPATH];
|
|
static char cl_snapshot_subdirname[MAX_OSPATH];
|
|
|
|
// Must match game .dll definition
|
|
// HACK HACK FOR E3 -- Remove this after E3
|
|
#define HIDEHUD_ALL ( 1<<2 )
|
|
|
|
void PhonemeMP3Shutdown( void );
|
|
|
|
struct ResourceLocker
|
|
{
|
|
ResourceLocker()
|
|
{
|
|
g_pFileSystem->AsyncFinishAll();
|
|
g_pFileSystem->AsyncSuspend();
|
|
|
|
// Need to temporarily disable queued material system, then lock it
|
|
m_QMS = Host_AllowQueuedMaterialSystem( false );
|
|
m_MatLock = g_pMaterialSystem->Lock();
|
|
}
|
|
|
|
~ResourceLocker()
|
|
{
|
|
// Restore QMS
|
|
materials->Unlock( m_MatLock );
|
|
Host_AllowQueuedMaterialSystem( m_QMS );
|
|
g_pFileSystem->AsyncResume();
|
|
|
|
// ??? What? Why?
|
|
//// Need to purge cached materials due to a sv_pure change.
|
|
//g_pMaterialSystem->UncacheAllMaterials();
|
|
}
|
|
|
|
bool m_QMS;
|
|
MaterialLock_t m_MatLock;
|
|
};
|
|
|
|
// Reloads a list of files if they are still loaded
|
|
void CL_ReloadFilesInList( IFileList *pFilesToReload )
|
|
{
|
|
if ( !pFilesToReload )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ResourceLocker crashPreventer;
|
|
|
|
// Handle materials..
|
|
materials->ReloadFilesInList( pFilesToReload );
|
|
|
|
// Handle models.. NOTE: this MUST come after materials->ReloadFilesInList because the
|
|
// models need to know which materials got flushed.
|
|
modelloader->ReloadFilesInList( pFilesToReload );
|
|
|
|
S_ReloadFilesInList( pFilesToReload );
|
|
|
|
// Let the client at it (for particles)
|
|
if ( g_ClientDLL )
|
|
{
|
|
g_ClientDLL->ReloadFilesInList( pFilesToReload );
|
|
}
|
|
}
|
|
|
|
void CL_HandlePureServerWhitelist( CPureServerWhitelist *pWhitelist, /* out */ IFileList *&pFilesToReload )
|
|
{
|
|
// Free the old whitelist and get the new one.
|
|
if ( cl.m_pPureServerWhitelist )
|
|
cl.m_pPureServerWhitelist->Release();
|
|
|
|
cl.m_pPureServerWhitelist = pWhitelist;
|
|
if ( cl.m_pPureServerWhitelist )
|
|
cl.m_pPureServerWhitelist->AddRef();
|
|
|
|
g_pFileSystem->RegisterFileWhitelist( pWhitelist, &pFilesToReload );
|
|
|
|
// Now that we've flushed any files that shouldn't have been on disk, we should have a CRC
|
|
// set that we can check with the server.
|
|
cl.m_bCheckCRCsWithServer = true;
|
|
}
|
|
|
|
void PrintSvPureWhitelistClassification( const CPureServerWhitelist *pWhiteList )
|
|
{
|
|
if ( pWhiteList == NULL )
|
|
{
|
|
Msg( "The server is using sv_pure -1 (no file checking).\n" );
|
|
return;
|
|
}
|
|
|
|
// Load up the default whitelist
|
|
CPureServerWhitelist *pStandardList = CPureServerWhitelist::Create( g_pFullFileSystem );
|
|
pStandardList->Load( 0 );
|
|
if ( *pStandardList == *pWhiteList )
|
|
{
|
|
Msg( "The server is using sv_pure 0. (Enforcing consistency for select files only)\n" );
|
|
}
|
|
else
|
|
{
|
|
pStandardList->Load( 2 );
|
|
if ( *pStandardList == *pWhiteList )
|
|
{
|
|
Msg( "The server is using sv_pure 2. (Fully pure)\n" );
|
|
}
|
|
else
|
|
{
|
|
Msg( "The server is using sv_pure 1. (Custom pure server rules.)\n" );
|
|
}
|
|
}
|
|
pStandardList->Release();
|
|
}
|
|
|
|
void CL_PrintWhitelistInfo()
|
|
{
|
|
PrintSvPureWhitelistClassification( cl.m_pPureServerWhitelist );
|
|
if ( cl.m_pPureServerWhitelist )
|
|
{
|
|
cl.m_pPureServerWhitelist->PrintWhitelistContents();
|
|
}
|
|
}
|
|
|
|
// Console command to force a whitelist on the system.
|
|
#ifdef _DEBUG
|
|
void whitelist_f( const CCommand &args )
|
|
{
|
|
int pureLevel = 2;
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
pureLevel = atoi( args[1] );
|
|
}
|
|
else
|
|
{
|
|
Warning( "Whitelist 0, 1, or 2\n" );
|
|
}
|
|
|
|
if ( pureLevel == 0 )
|
|
{
|
|
Warning( "whitelist 0: CL_HandlePureServerWhitelist( NULL )\n" );
|
|
IFileList *pFilesToReload = NULL;
|
|
CL_HandlePureServerWhitelist( NULL, pFilesToReload );
|
|
CL_ReloadFilesInList( pFilesToReload );
|
|
}
|
|
else
|
|
{
|
|
CPureServerWhitelist *pWhitelist = CPureServerWhitelist::Create( g_pFileSystem );
|
|
pWhitelist->Load( pureLevel == 1 );
|
|
IFileList *pFilesToReload = NULL;
|
|
CL_HandlePureServerWhitelist( pWhitelist, pFilesToReload );
|
|
CL_ReloadFilesInList( pFilesToReload );
|
|
pWhitelist->Release();
|
|
}
|
|
}
|
|
ConCommand whitelist( "whitelist", whitelist_f );
|
|
#endif
|
|
|
|
const CPrecacheUserData* CL_GetPrecacheUserData( INetworkStringTable *table, int index )
|
|
{
|
|
int testLength;
|
|
const CPrecacheUserData *data = ( CPrecacheUserData * )table->GetStringUserData( index, &testLength );
|
|
if ( data )
|
|
{
|
|
ErrorIfNot(
|
|
testLength == sizeof( *data ),
|
|
("CL_GetPrecacheUserData(%d,%d) - length (%d) invalid.", table->GetTableId(), index, testLength)
|
|
);
|
|
|
|
}
|
|
return data;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: setup the demo flag, split from CL_IsHL2Demo so CL_IsHL2Demo can be inline
|
|
//-----------------------------------------------------------------------------
|
|
static bool s_bIsHL2Demo = false;
|
|
void CL_InitHL2DemoFlag()
|
|
{
|
|
#if defined(_X360)
|
|
s_bIsHL2Demo = false;
|
|
#else
|
|
static bool initialized = false;
|
|
if ( !initialized )
|
|
{
|
|
if ( Steam3Client().SteamApps() && !Q_stricmp( COM_GetModDirectory(), "hl2" ) )
|
|
{
|
|
initialized = true;
|
|
|
|
// if user didn't buy HL2 yet, this must be the free demo
|
|
if ( VCRGetMode() != VCR_Playback )
|
|
{
|
|
s_bIsHL2Demo = !Steam3Client().SteamApps()->BIsSubscribedApp( GetAppSteamAppId( k_App_HL2 ) );
|
|
}
|
|
#if !defined( NO_VCR )
|
|
VCRGenericValue( "e", &s_bIsHL2Demo, sizeof( s_bIsHL2Demo ) );
|
|
#endif
|
|
}
|
|
|
|
if ( !Q_stricmp( COM_GetModDirectory(), "hl2" ) && CommandLine()->CheckParm( "-demo" ) )
|
|
{
|
|
s_bIsHL2Demo = true;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the user is playing the HL2 Demo (rather than the full game)
|
|
//-----------------------------------------------------------------------------
|
|
bool CL_IsHL2Demo()
|
|
{
|
|
CL_InitHL2DemoFlag();
|
|
return s_bIsHL2Demo;
|
|
}
|
|
|
|
static bool s_bIsPortalDemo = false;
|
|
void CL_InitPortalDemoFlag()
|
|
{
|
|
#if defined(_X360)
|
|
s_bIsPortalDemo = false;
|
|
#else
|
|
static bool initialized = false;
|
|
if ( !initialized )
|
|
{
|
|
if ( Steam3Client().SteamApps() && !Q_stricmp( COM_GetModDirectory(), "portal" ) )
|
|
{
|
|
initialized = true;
|
|
|
|
// if user didn't buy Portal yet, this must be the free demo
|
|
if ( VCRGetMode() != VCR_Playback )
|
|
{
|
|
s_bIsPortalDemo = !Steam3Client().SteamApps()->BIsSubscribedApp( GetAppSteamAppId( k_App_PORTAL ) );
|
|
}
|
|
|
|
#if !defined( NO_VCR )
|
|
VCRGenericValue( "e", &s_bIsPortalDemo, sizeof( s_bIsPortalDemo ) );
|
|
#endif
|
|
}
|
|
|
|
if ( !Q_stricmp( COM_GetModDirectory(), "portal" ) && CommandLine()->CheckParm( "-demo" ) )
|
|
{
|
|
s_bIsPortalDemo = true;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the user is playing the Portal Demo (rather than the full game)
|
|
//-----------------------------------------------------------------------------
|
|
bool CL_IsPortalDemo()
|
|
{
|
|
CL_InitPortalDemoFlag();
|
|
return s_bIsPortalDemo;
|
|
}
|
|
|
|
|
|
#ifdef _XBOX
|
|
extern void Host_WriteConfiguration( const char *dirname, const char *filename );
|
|
//-----------------------------------------------------------------------------
|
|
// Convar callback to write the user configuration
|
|
//-----------------------------------------------------------------------------
|
|
void WriteConfig_f( ConVar *var, const char *pOldString )
|
|
{
|
|
Host_WriteConfiguration( "xboxuser.cfg" );
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: If the client is in the process of connecting and the cl.signon hits
|
|
// is complete, make sure the client thinks its totally connected.
|
|
//-----------------------------------------------------------------------------
|
|
void CL_CheckClientState( void )
|
|
{
|
|
// Setup the local network backdoor (we do this each frame so it can be toggled on and off).
|
|
bool useBackdoor = cl_LocalNetworkBackdoor.GetInt() &&
|
|
(cl.m_NetChannel ? cl.m_NetChannel->IsLoopback() : false) &&
|
|
sv.IsActive() &&
|
|
!demorecorder->IsRecording() &&
|
|
!demoplayer->IsPlayingBack() &&
|
|
Host_IsSinglePlayerGame();
|
|
|
|
CL_SetupLocalNetworkBackDoor( useBackdoor );
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// bool CL_CheckCRCs( const char *pszMap )
|
|
//-----------------------------------------------------------------------------
|
|
bool CL_CheckCRCs( const char *pszMap )
|
|
{
|
|
CRC32_t mapCRC; // If this is the worldmap, CRC against server's map
|
|
MD5Value_t mapMD5;
|
|
V_memset( mapMD5.bits, 0, MD5_DIGEST_LENGTH );
|
|
|
|
// Don't verify CRC if we are running a local server (i.e., we are playing single player, or we are the server in multiplay
|
|
if ( sv.IsActive() ) // Single player
|
|
return true;
|
|
|
|
if ( IsX360() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool couldHash = false;
|
|
if ( g_ClientGlobalVariables.network_protocol > PROTOCOL_VERSION_17 )
|
|
{
|
|
couldHash = MD5_MapFile( &mapMD5, pszMap );
|
|
}
|
|
else
|
|
{
|
|
CRC32_Init(&mapCRC);
|
|
couldHash = CRC_MapFile( &mapCRC, pszMap );
|
|
}
|
|
|
|
if (!couldHash )
|
|
{
|
|
// Does the file exist?
|
|
FileHandle_t fp = 0;
|
|
int nSize = -1;
|
|
|
|
nSize = COM_OpenFile( pszMap, &fp );
|
|
if ( fp )
|
|
g_pFileSystem->Close( fp );
|
|
|
|
if ( nSize != -1 )
|
|
{
|
|
COM_ExplainDisconnection( true, "Couldn't CRC map %s, disconnecting\n", pszMap);
|
|
Host_Error( "Bad map" );
|
|
}
|
|
else
|
|
{
|
|
COM_ExplainDisconnection( true, "Missing map %s, disconnecting\n", pszMap);
|
|
Host_Error( "Map is missing" );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool hashValid = false;
|
|
if ( g_ClientGlobalVariables.network_protocol > PROTOCOL_VERSION_17 )
|
|
{
|
|
hashValid = MD5_Compare( cl.serverMD5, mapMD5 );
|
|
}
|
|
|
|
// Hacked map
|
|
if ( !hashValid && !demoplayer->IsPlayingBack())
|
|
{
|
|
if ( IsX360() )
|
|
{
|
|
Warning( "Disconnect: BSP CRC failed!\n" );
|
|
}
|
|
COM_ExplainDisconnection( true, "Your map [%s] differs from the server's.\n", pszMap );
|
|
Host_Error( "Client's map differs from the server's" );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : nMaxClients -
|
|
//-----------------------------------------------------------------------------
|
|
void CL_ReallocateDynamicData( int maxclients )
|
|
{
|
|
Assert( entitylist );
|
|
if ( entitylist )
|
|
{
|
|
entitylist->SetMaxEntities( MAX_EDICTS );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_ReadPackets
|
|
|
|
Updates the local time and reads/handles messages on client net connection.
|
|
=================
|
|
*/
|
|
|
|
void CL_ReadPackets ( bool bFinalTick )
|
|
{
|
|
VPROF_BUDGET( "CL_ReadPackets", VPROF_BUDGETGROUP_OTHER_NETWORKING );
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
|
|
|
if ( !Host_ShouldRun() )
|
|
return;
|
|
|
|
// update client times/tick
|
|
|
|
cl.oldtickcount = cl.GetServerTickCount();
|
|
if ( !cl.IsPaused() )
|
|
{
|
|
cl.SetClientTickCount( cl.GetClientTickCount() + 1 );
|
|
|
|
// While clock correction is off, we have the old behavior of matching the client and server clocks.
|
|
if ( !CClockDriftMgr::IsClockCorrectionEnabled() )
|
|
cl.SetServerTickCount( cl.GetClientTickCount() );
|
|
|
|
g_ClientGlobalVariables.tickcount = cl.GetClientTickCount();
|
|
g_ClientGlobalVariables.curtime = cl.GetTime();
|
|
}
|
|
// 0 or tick_rate if simulating
|
|
g_ClientGlobalVariables.frametime = cl.GetFrameTime();
|
|
|
|
// read packets, if any in queue
|
|
if ( demoplayer->IsPlayingBack() && cl.m_NetChannel )
|
|
{
|
|
tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "ReadPacket" );
|
|
|
|
// process data from demo file
|
|
cl.m_NetChannel->ProcessPlayback();
|
|
}
|
|
else
|
|
{
|
|
if ( !cl_ignorepackets.GetInt() )
|
|
{
|
|
tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "ProcessSocket" );
|
|
// process data from net socket
|
|
NET_ProcessSocket( NS_CLIENT, &cl );
|
|
}
|
|
}
|
|
|
|
// check timeout, but not if running _DEBUG engine
|
|
#if !defined( _DEBUG )
|
|
// Only check on final frame because that's when the server might send us a packet in single player. This avoids
|
|
// a bug where if you sit in the game code in the debugger then you get a timeout here on resuming the engine
|
|
// because the timestep is > 1 tick because of the debugging delay but the server hasn't sent the next packet yet. ywb 9/5/03
|
|
if ( (cl.m_NetChannel?cl.m_NetChannel->IsTimedOut():false) &&
|
|
bFinalTick &&
|
|
!demoplayer->IsPlayingBack() &&
|
|
cl.IsConnected() )
|
|
{
|
|
ConMsg ("\nServer connection timed out.\n");
|
|
|
|
// Show the vgui dialog on timeout
|
|
COM_ExplainDisconnection( false, "Lost connection to server.");
|
|
if ( IsPC() )
|
|
{
|
|
EngineVGui()->ShowErrorMessage();
|
|
}
|
|
|
|
Host_Disconnect( true, "Lost connection" );
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CL_ClearState ( void )
|
|
{
|
|
// clear out the current whitelist
|
|
IFileList *pFilesToReload = NULL;
|
|
CL_HandlePureServerWhitelist( NULL, pFilesToReload );
|
|
CL_ReloadFilesInList( pFilesToReload );
|
|
|
|
CL_ResetEntityBits();
|
|
|
|
R_UnloadSkys();
|
|
|
|
// clear decal index directories
|
|
Decal_Init();
|
|
|
|
StaticPropMgr()->LevelShutdownClient();
|
|
|
|
// shutdown this level in the client DLL
|
|
if ( g_ClientDLL )
|
|
{
|
|
if ( host_state.worldmodel )
|
|
{
|
|
char mapname[256];
|
|
CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), mapname, sizeof( mapname ) );
|
|
phonehome->Message( IPhoneHome::PHONE_MSG_MAPEND, mapname );
|
|
}
|
|
audiosourcecache->LevelShutdown();
|
|
g_ClientDLL->LevelShutdown();
|
|
}
|
|
|
|
R_LevelShutdown();
|
|
if ( IsX360() )
|
|
{
|
|
// Reset material system temporary memory (frees up memory for map loading)
|
|
bool bOnLevelShutdown = true;
|
|
materials->ResetTempHWMemory( bOnLevelShutdown );
|
|
}
|
|
|
|
if ( g_pLocalNetworkBackdoor )
|
|
g_pLocalNetworkBackdoor->ClearState();
|
|
|
|
// clear other arrays
|
|
memset (cl_dlights, 0, sizeof(cl_dlights));
|
|
memset (cl_elights, 0, sizeof(cl_elights));
|
|
|
|
// Wipe the hunk ( unless the server is active )
|
|
Host_FreeStateAndWorld( false );
|
|
Host_FreeToLowMark( false );
|
|
|
|
PhonemeMP3Shutdown();
|
|
|
|
// Wipe the remainder of the structure.
|
|
cl.Clear();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Used for sorting sounds
|
|
// Input : &sound1 -
|
|
// &sound2 -
|
|
// Output : static bool
|
|
//-----------------------------------------------------------------------------
|
|
static bool CL_SoundMessageLessFunc( SoundInfo_t const &sound1, SoundInfo_t const &sound2 )
|
|
{
|
|
return sound1.nSequenceNumber < sound2.nSequenceNumber;
|
|
}
|
|
|
|
static CUtlRBTree< SoundInfo_t, int > g_SoundMessages( 0, 0, CL_SoundMessageLessFunc );
|
|
extern ConVar snd_show;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Add sound to queue
|
|
// Input : sound -
|
|
//-----------------------------------------------------------------------------
|
|
void CL_AddSound( const SoundInfo_t &sound )
|
|
{
|
|
g_SoundMessages.Insert( sound );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Play sound packet
|
|
// Input : sound -
|
|
//-----------------------------------------------------------------------------
|
|
void CL_DispatchSound( const SoundInfo_t &sound )
|
|
{
|
|
int nSoundNum = sound.nSoundNum;
|
|
|
|
CSfxTable *pSfx;
|
|
|
|
char name[ MAX_QPATH ];
|
|
|
|
name[ 0 ] = 0;
|
|
if ( sound.bIsSentence )
|
|
{
|
|
// make dummy sfx for sentences
|
|
const char *pSentenceName = VOX_SentenceNameFromIndex( sound.nSoundNum );
|
|
if ( !pSentenceName )
|
|
{
|
|
pSentenceName = "";
|
|
}
|
|
|
|
V_snprintf( name, sizeof( name ), "%c%s", CHAR_SENTENCE, pSentenceName );
|
|
pSfx = S_DummySfx( name );
|
|
}
|
|
else
|
|
{
|
|
V_strncpy( name, cl.GetSoundName( sound.nSoundNum ), sizeof( name ) );
|
|
|
|
const char *pchTranslatedName = g_ClientDLL->TranslateEffectForVisionFilter( "sounds", name );
|
|
if ( V_strcmp( pchTranslatedName, name ) != 0 )
|
|
{
|
|
V_strncpy( name, pchTranslatedName, sizeof( name ) );
|
|
nSoundNum = cl.LookupSoundIndex( name );
|
|
}
|
|
|
|
pSfx = cl.GetSound( nSoundNum );
|
|
}
|
|
|
|
if ( snd_show.GetInt() >= 2 )
|
|
{
|
|
DevMsg( "%i (seq %i) %s : src %d : ch %d : %d dB : vol %.2f : time %.3f (%.4f delay) @%.1f %.1f %.1f\n",
|
|
host_framecount,
|
|
sound.nSequenceNumber,
|
|
name,
|
|
sound.nEntityIndex,
|
|
sound.nChannel,
|
|
sound.Soundlevel,
|
|
sound.fVolume,
|
|
cl.GetTime(),
|
|
sound.fDelay,
|
|
sound.vOrigin.x,
|
|
sound.vOrigin.y,
|
|
sound.vOrigin.z );
|
|
}
|
|
|
|
StartSoundParams_t params;
|
|
params.staticsound = (sound.nChannel == CHAN_STATIC) ? true : false;
|
|
params.soundsource = sound.nEntityIndex;
|
|
params.entchannel = params.staticsound ? CHAN_STATIC : sound.nChannel;
|
|
params.pSfx = pSfx;
|
|
params.origin = sound.vOrigin;
|
|
params.fvol = sound.fVolume;
|
|
params.soundlevel = sound.Soundlevel;
|
|
params.flags = sound.nFlags;
|
|
params.pitch = sound.nPitch;
|
|
params.specialdsp = sound.nSpecialDSP;
|
|
params.fromserver = true;
|
|
params.delay = sound.fDelay;
|
|
// we always want to do this when this flag is set - even if the delay is zero we need to precisely
|
|
// schedule this sound
|
|
if ( sound.nFlags & SND_DELAY )
|
|
{
|
|
// anything adjusted less than 100ms forward was probably scheduled this frame
|
|
if ( sound.fDelay > -0.100f )
|
|
{
|
|
float soundtime = cl.m_flLastServerTickTime + sound.fDelay;
|
|
// this adjusts for host_thread_mode or any other cases where we're running more than one
|
|
// tick at a time, but we get network updates on the first tick
|
|
soundtime -= ((g_ClientGlobalVariables.simTicksThisFrame-1) * host_state.interval_per_tick);
|
|
// this sound was networked over from the server, use server clock
|
|
params.delay = S_ComputeDelayForSoundtime( soundtime, CLOCK_SYNC_SERVER );
|
|
#if 0
|
|
static float lastSoundTime = 0;
|
|
Msg("[%.3f] Play %s at %.3f %.1fsms delay\n", soundtime - lastSoundTime, name, soundtime, params.delay * 1000.0f );
|
|
lastSoundTime = soundtime;
|
|
#endif
|
|
if ( params.delay <= 0 )
|
|
{
|
|
// leave a little delay to flag the channel in the low-level sound system
|
|
params.delay = 1e-6f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
params.delay = sound.fDelay;
|
|
}
|
|
}
|
|
params.speakerentity = sound.nSpeakerEntity;
|
|
|
|
// Give the client DLL a chance to run arbitrary code to affect the sound parameters before we
|
|
// play.
|
|
g_ClientDLL->ClientAdjustStartSoundParams( params );
|
|
|
|
if ( params.staticsound )
|
|
{
|
|
S_StartSound( params );
|
|
}
|
|
else
|
|
{
|
|
// Don't actually play non-static sounds if playing a demo and skipping ahead
|
|
// but always stop sounds
|
|
if ( demoplayer->IsSkipping() && !(sound.nFlags&SND_STOP) )
|
|
{
|
|
return;
|
|
}
|
|
S_StartSound( params );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called after reading network messages to play sounds encoded in the network packet
|
|
//-----------------------------------------------------------------------------
|
|
void CL_DispatchSounds( void )
|
|
{
|
|
int i;
|
|
// Walk list in sequence order
|
|
i = g_SoundMessages.FirstInorder();
|
|
while ( i != g_SoundMessages.InvalidIndex() )
|
|
{
|
|
SoundInfo_t const *msg = &g_SoundMessages[ i ];
|
|
Assert( msg );
|
|
if ( msg )
|
|
{
|
|
// Play the sound
|
|
CL_DispatchSound( *msg );
|
|
}
|
|
i = g_SoundMessages.NextInorder( i );
|
|
}
|
|
|
|
// Reset the queue each time we empty it!!!
|
|
g_SoundMessages.RemoveAll();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Retry last connection (e.g., after we enter a password)
|
|
//-----------------------------------------------------------------------------
|
|
void CL_Retry()
|
|
{
|
|
if ( !cl.m_szRetryAddress[ 0 ] )
|
|
{
|
|
ConMsg( "Can't retry, no previous connection\n" );
|
|
return;
|
|
}
|
|
|
|
// Check that we can add the two execution markers
|
|
bool bCanAddExecutionMarkers = Cbuf_HasRoomForExecutionMarkers( 2 );
|
|
|
|
ConMsg( "Commencing connection retry to %s\n", cl.m_szRetryAddress );
|
|
|
|
// We need to temporarily disable this execution marker so the connect command succeeds if it was executed by the server.
|
|
// We would still need this even if we called CL_Connect directly because the connect process may execute commands which we want to succeed.
|
|
const char *pszCommand = va( "connect %s %s\n", cl.m_szRetryAddress, cl.m_sRetrySourceTag.String() );
|
|
if ( cl.m_bRestrictServerCommands && bCanAddExecutionMarkers )
|
|
Cbuf_AddTextWithMarkers( eCmdExecutionMarker_Disable_FCVAR_SERVER_CAN_EXECUTE, pszCommand, eCmdExecutionMarker_Enable_FCVAR_SERVER_CAN_EXECUTE );
|
|
else
|
|
Cbuf_AddText( pszCommand );
|
|
}
|
|
|
|
CON_COMMAND_F( retry, "Retry connection to last server.", FCVAR_DONTRECORD | FCVAR_SERVER_CAN_EXECUTE | FCVAR_CLIENTCMD_CAN_EXECUTE )
|
|
{
|
|
CL_Retry();
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
CL_Connect_f
|
|
|
|
User command to connect to server
|
|
=====================
|
|
*/
|
|
|
|
void CL_Connect( const char *address, const char *pszSourceTag )
|
|
{
|
|
// If it's not a single player connection to "localhost", initialize networking & stop listenserver
|
|
if ( Q_strncmp( address, "localhost", 9 ) )
|
|
{
|
|
Host_Disconnect(false);
|
|
|
|
// allow remote
|
|
NET_SetMutiplayer( true );
|
|
|
|
// start progress bar immediately for remote connection
|
|
EngineVGui()->EnabledProgressBarForNextLoad();
|
|
|
|
SCR_BeginLoadingPlaque();
|
|
|
|
EngineVGui()->UpdateProgressBar(PROGRESS_BEGINCONNECT);
|
|
}
|
|
else
|
|
{
|
|
// we are connecting/reconnecting to local game
|
|
// so don't stop listenserver
|
|
cl.Disconnect( "Connecting to local host", false );
|
|
}
|
|
|
|
// This happens as part of the load process anyway, but on slower systems it causes the server to timeout the
|
|
// connection. Use the opportunity to flush anything before starting a new connection.
|
|
UpdateMaterialSystemConfig();
|
|
|
|
cl.Connect( address, pszSourceTag );
|
|
|
|
// Reset error conditions
|
|
gfExtendedError = false;
|
|
}
|
|
|
|
CON_COMMAND_F( connect, "Connect to specified server.", FCVAR_DONTRECORD )
|
|
{
|
|
// Default command processing considers ':' a command separator,
|
|
// and we donly want spaces to count. So we'll need to re-split the arg string
|
|
CUtlVector<char*> vecArgs;
|
|
V_SplitString( args.ArgS(), " ", vecArgs );
|
|
|
|
// How many arguments?
|
|
if ( vecArgs.Count() == 1 )
|
|
{
|
|
CL_Connect( vecArgs[0], "" );
|
|
}
|
|
else if ( vecArgs.Count() == 2 )
|
|
{
|
|
CL_Connect( vecArgs[0], vecArgs[1] );
|
|
}
|
|
else
|
|
{
|
|
ConMsg( "Usage: connect <server>\n" );
|
|
}
|
|
vecArgs.PurgeAndDeleteElementsArray();
|
|
}
|
|
|
|
CON_COMMAND_F( redirect, "Redirect client to specified server.", FCVAR_DONTRECORD | FCVAR_SERVER_CAN_EXECUTE )
|
|
{
|
|
if ( !CBaseClientState::ConnectMethodAllowsRedirects() )
|
|
{
|
|
ConMsg( "redirect: Current connection method does not allow silent redirects.\n");
|
|
return;
|
|
}
|
|
|
|
// Default command processing considers ':' a command separator,
|
|
// and we donly want spaces to count. So we'll need to re-split the arg string
|
|
CUtlVector<char*> vecArgs;
|
|
V_SplitString( args.ArgS(), " ", vecArgs );
|
|
|
|
if ( vecArgs.Count() == 1 )
|
|
{
|
|
CL_Connect( vecArgs[0], "redirect" );
|
|
}
|
|
else
|
|
{
|
|
ConMsg( "Usage: redirect <server>\n" );
|
|
}
|
|
vecArgs.PurgeAndDeleteElements();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Takes the map name, strips path and extension
|
|
//-----------------------------------------------------------------------------
|
|
void CL_SetupMapName( const char* pName, char* pFixedName, int maxlen )
|
|
{
|
|
const char* pSlash = strrchr( pName, '\\' );
|
|
const char* pSlash2 = strrchr( pName, '/' );
|
|
if (pSlash2 > pSlash)
|
|
pSlash = pSlash2;
|
|
if (pSlash)
|
|
++pSlash;
|
|
else
|
|
pSlash = pName;
|
|
|
|
Q_strncpy( pFixedName, pSlash, maxlen );
|
|
char* pExt = strchr( pFixedName, '.' );
|
|
if (pExt)
|
|
*pExt = 0;
|
|
}
|
|
|
|
CPureServerWhitelist* CL_LoadWhitelist( INetworkStringTable *pTable, const char *pName )
|
|
{
|
|
// If there is no entry for the pure server whitelist, then sv_pure is off and the client can do whatever it wants.
|
|
int iString = pTable->FindStringIndex( pName );
|
|
if ( iString == INVALID_STRING_INDEX )
|
|
return NULL;
|
|
|
|
int dataLen;
|
|
const void *pData = pTable->GetStringUserData( iString, &dataLen );
|
|
if ( pData )
|
|
{
|
|
CUtlBuffer buf( pData, dataLen, CUtlBuffer::READ_ONLY );
|
|
|
|
CPureServerWhitelist *pWhitelist = CPureServerWhitelist::Create( g_pFullFileSystem );
|
|
pWhitelist->Decode( buf );
|
|
return pWhitelist;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void CL_CheckForPureServerWhitelist( /* out */ IFileList *&pFilesToReload )
|
|
{
|
|
#ifdef DISABLE_PURE_SERVER_STUFF
|
|
return;
|
|
#endif
|
|
|
|
// Don't do sv_pure stuff in SP games or HLTV/replay
|
|
if ( cl.m_nMaxClients <= 1 || cl.ishltv || demoplayer->IsPlayingBack()
|
|
#ifdef REPLAY_ENABLED
|
|
|| cl.isreplay
|
|
#endif // ifdef REPLAY_ENABLED
|
|
)
|
|
return;
|
|
|
|
CPureServerWhitelist *pWhitelist = NULL;
|
|
if ( cl.m_pServerStartupTable )
|
|
pWhitelist = CL_LoadWhitelist( cl.m_pServerStartupTable, "PureServerWhitelist" );
|
|
|
|
PrintSvPureWhitelistClassification( pWhitelist );
|
|
CL_HandlePureServerWhitelist( pWhitelist, pFilesToReload );
|
|
if ( pWhitelist )
|
|
{
|
|
pWhitelist->Release();
|
|
}
|
|
}
|
|
|
|
int CL_GetServerQueryPort()
|
|
{
|
|
// Yes, this is ugly getting this data out of a string table. Would be better to have it in our network protocol,
|
|
// but we don't have a way to change the protocol without breaking things for people.
|
|
if ( !cl.m_pServerStartupTable )
|
|
return 0;
|
|
|
|
int iString = cl.m_pServerStartupTable->FindStringIndex( "QueryPort" );
|
|
if ( iString == INVALID_STRING_INDEX )
|
|
return 0;
|
|
|
|
int dataLen;
|
|
const void *pData = cl.m_pServerStartupTable->GetStringUserData( iString, &dataLen );
|
|
if ( pData && dataLen == sizeof( int ) )
|
|
return *((const int*)pData);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_RegisterResources
|
|
|
|
Clean up and move to next part of sequence.
|
|
==================
|
|
*/
|
|
void CL_RegisterResources( void )
|
|
{
|
|
// All done precaching.
|
|
host_state.SetWorldModel( cl.GetModel( 1 ) );
|
|
if ( !host_state.worldmodel )
|
|
{
|
|
Host_Error( "CL_RegisterResources: host_state.worldmodel/cl.GetModel( 1 )==NULL\n" );
|
|
}
|
|
|
|
// Force main window to repaint... (only does something if running shaderapi
|
|
videomode->InvalidateWindow();
|
|
}
|
|
|
|
void CL_FullyConnected( void )
|
|
{
|
|
CETWScope timer( "CL_FullyConnected" );
|
|
|
|
EngineVGui()->UpdateProgressBar( PROGRESS_FULLYCONNECTED );
|
|
|
|
// This has to happen here, in phase 3, because it is in this phase
|
|
// that raycasts against the world is supported (owing to the fact
|
|
// that the world entity has been created by this point)
|
|
StaticPropMgr()->LevelInitClient();
|
|
|
|
if ( IsX360() )
|
|
{
|
|
// Notify the loader the end of the loading context, preloads are about to be purged
|
|
g_pQueuedLoader->EndMapLoading( false );
|
|
}
|
|
|
|
// flush client-side dynamic models that have no refcount
|
|
modelloader->FlushDynamicModels();
|
|
|
|
// loading completed
|
|
// can NOW safely purge unused models and their data hierarchy (materials, shaders, etc)
|
|
modelloader->PurgeUnusedModels();
|
|
|
|
// Purge the preload stores, oreder is critical
|
|
g_pMDLCache->ShutdownPreloadData();
|
|
|
|
// NOTE: purposely disabling for singleplayer, memory spike causing issues, preload's stay in
|
|
// UNDONE: discard preload for TF to save memory
|
|
// g_pFileSystem->DiscardPreloadData();
|
|
|
|
// We initialize this list before the load, but don't perform reloads until after flushes have happened to avoid
|
|
// unnecessary reloads of items that wont be used on this map.
|
|
if ( cl.m_pPendingPureFileReloads )
|
|
{
|
|
CL_ReloadFilesInList( cl.m_pPendingPureFileReloads );
|
|
cl.m_pPendingPureFileReloads->Release();
|
|
cl.m_pPendingPureFileReloads = NULL;
|
|
}
|
|
|
|
// ***************************************************************
|
|
// NO MORE PRELOAD DATA AVAILABLE PAST THIS POINT!!!
|
|
// ***************************************************************
|
|
|
|
g_ClientDLL->LevelInitPostEntity();
|
|
|
|
// communicate to tracker that we're in a game
|
|
int ip = cl.m_NetChannel->GetRemoteAddress().GetIPNetworkByteOrder();
|
|
short port = cl.m_NetChannel->GetRemoteAddress().GetPort();
|
|
if (!port)
|
|
{
|
|
ip = net_local_adr.GetIPNetworkByteOrder();
|
|
port = net_local_adr.GetPort();
|
|
}
|
|
|
|
int iQueryPort = CL_GetServerQueryPort();
|
|
EngineVGui()->NotifyOfServerConnect(com_gamedir, ip, port, iQueryPort);
|
|
|
|
GetTestScriptMgr()->CheckPoint( "FinishedMapLoad" );
|
|
|
|
EngineVGui()->UpdateProgressBar( PROGRESS_READYTOPLAY );
|
|
|
|
if ( !IsX360() || cl.m_nMaxClients == 1 )
|
|
{
|
|
// Need this to persist for multiplayer respawns, 360 can't reload
|
|
CM_DiscardEntityString();
|
|
}
|
|
|
|
g_pMDLCache->EndMapLoad();
|
|
|
|
#if defined( _MEMTEST )
|
|
Cbuf_AddText( "mem_dump\n" );
|
|
#endif
|
|
|
|
if ( developer.GetInt() > 0 )
|
|
{
|
|
ConDMsg( "Signon traffic \"%s\": incoming %s, outgoing %s\n",
|
|
cl.m_NetChannel->GetName(),
|
|
Q_pretifymem( cl.m_NetChannel->GetTotalData( FLOW_INCOMING ), 3 ),
|
|
Q_pretifymem( cl.m_NetChannel->GetTotalData( FLOW_OUTGOING ), 3 ) );
|
|
}
|
|
|
|
if ( IsX360() )
|
|
{
|
|
// Reset material system temporary memory (once loading is complete), ready for in-map use
|
|
bool bOnLevelShutdown = false;
|
|
materials->ResetTempHWMemory( bOnLevelShutdown );
|
|
}
|
|
|
|
// allow normal screen updates
|
|
SCR_EndLoadingPlaque();
|
|
EndLoadingUpdates();
|
|
|
|
// FIXME: Please oh please move this out of this spot...
|
|
// It so does not belong here. Instead, we want some phase of the
|
|
// client DLL where it knows its read in all entities
|
|
if ( IsPC() )
|
|
{
|
|
int i;
|
|
if( (i = CommandLine()->FindParm( "-buildcubemaps" )) != 0 )
|
|
{
|
|
int numIterations = 1;
|
|
if( CommandLine()->ParmCount() > i + 1 )
|
|
{
|
|
numIterations = atoi( CommandLine()->GetParm(i+1) );
|
|
}
|
|
if( numIterations == 0 )
|
|
{
|
|
numIterations = 1;
|
|
}
|
|
char cmd[1024] = { 0 };
|
|
V_snprintf( cmd, sizeof( cmd ), "buildcubemaps %u\nquit\n", numIterations );
|
|
Cbuf_AddText( cmd );
|
|
}
|
|
else if( CommandLine()->FindParm( "-navanalyze" ) )
|
|
{
|
|
Cbuf_AddText( "nav_edit 1;nav_analyze_scripted\n" );
|
|
}
|
|
else if( CommandLine()->FindParm( "-navforceanalyze" ) )
|
|
{
|
|
Cbuf_AddText( "nav_edit 1;nav_analyze_scripted force\n" );
|
|
}
|
|
else if ( CommandLine()->FindParm("-exit") )
|
|
{
|
|
Cbuf_AddText( "quit\n" );
|
|
}
|
|
}
|
|
|
|
// background maps are for main menu UI, QMS not needed or used, easier context
|
|
if ( !engineClient->IsLevelMainMenuBackground() )
|
|
{
|
|
// map load complete, safe to allow QMS
|
|
Host_AllowQueuedMaterialSystem( true );
|
|
}
|
|
|
|
// This is a Hack, but we need to suppress rendering for a bit in single player to let values settle on the client
|
|
if ( (cl.m_nMaxClients == 1) && !demoplayer->IsPlayingBack() )
|
|
{
|
|
scr_nextdrawtick = host_tickcount + TIME_TO_TICKS( 0.25f );
|
|
}
|
|
|
|
#ifdef _X360
|
|
// At this point, check for a valid controller connection. If it's been lost, then we need to pop our game UI up
|
|
XINPUT_CAPABILITIES caps;
|
|
if ( XInputGetCapabilities( XBX_GetPrimaryUserId(), XINPUT_FLAG_GAMEPAD, &caps ) == ERROR_DEVICE_NOT_CONNECTED )
|
|
{
|
|
EngineVGui()->ActivateGameUI();
|
|
}
|
|
#endif // _X360
|
|
|
|
// Now that we're connected, toggle the clan tag so it gets sent to the server
|
|
int id = cl_clanid.GetInt();
|
|
cl_clanid.SetValue( 0 );
|
|
cl_clanid.SetValue( id );
|
|
|
|
MemAlloc_CompactHeap();
|
|
|
|
extern double g_flAccumulatedModelLoadTime;
|
|
extern double g_flAccumulatedSoundLoadTime;
|
|
extern double g_flAccumulatedModelLoadTimeStudio;
|
|
extern double g_flAccumulatedModelLoadTimeVCollideSync;
|
|
extern double g_flAccumulatedModelLoadTimeVCollideAsync;
|
|
extern double g_flAccumulatedModelLoadTimeVirtualModel;
|
|
extern double g_flAccumulatedModelLoadTimeStaticMesh;
|
|
extern double g_flAccumulatedModelLoadTimeBrush;
|
|
extern double g_flAccumulatedModelLoadTimeSprite;
|
|
extern double g_flAccumulatedModelLoadTimeMaterialNamesOnly;
|
|
// extern double g_flLoadStudioHdr;
|
|
|
|
COM_TimestampedLog( "Sound Loading time %.4f", g_flAccumulatedSoundLoadTime );
|
|
COM_TimestampedLog( "Model Loading time %.4f", g_flAccumulatedModelLoadTime );
|
|
COM_TimestampedLog( " Model Loading time studio %.4f", g_flAccumulatedModelLoadTimeStudio );
|
|
COM_TimestampedLog( " Model Loading time GetVCollide %.4f -sync", g_flAccumulatedModelLoadTimeVCollideSync );
|
|
COM_TimestampedLog( " Model Loading time GetVCollide %.4f -async", g_flAccumulatedModelLoadTimeVCollideAsync );
|
|
COM_TimestampedLog( " Model Loading time GetVirtualModel %.4f", g_flAccumulatedModelLoadTimeVirtualModel );
|
|
COM_TimestampedLog( " Model loading time Mod_GetModelMaterials only %.4f", g_flAccumulatedModelLoadTimeMaterialNamesOnly );
|
|
COM_TimestampedLog( " Model Loading time world %.4f", g_flAccumulatedModelLoadTimeBrush );
|
|
COM_TimestampedLog( " Model Loading time sprites %.4f", g_flAccumulatedModelLoadTimeSprite );
|
|
COM_TimestampedLog( " Model Loading time meshes %.4f", g_flAccumulatedModelLoadTimeStaticMesh );
|
|
// COM_TimestampedLog( " Model Loading time meshes studiohdr load %.4f", g_flLoadStudioHdr );
|
|
|
|
COM_TimestampedLog( "*** Map Load Complete" );
|
|
|
|
float map_loadtime_start = dev_loadtime_map_start.GetFloat();
|
|
if (map_loadtime_start > 0.0)
|
|
{
|
|
float elapsed = Plat_FloatTime() - map_loadtime_start;
|
|
dev_loadtime_map_elapsed.SetValue( elapsed );
|
|
|
|
// Clear this for next time so we know we did.
|
|
dev_loadtime_map_start.SetValue( 0.0f );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
CL_NextDemo
|
|
|
|
Called to play the next demo in the demo loop
|
|
=====================
|
|
*/
|
|
void CL_NextDemo (void)
|
|
{
|
|
char str[1024];
|
|
|
|
if (cl.demonum == -1)
|
|
return; // don't play demos
|
|
|
|
SCR_BeginLoadingPlaque ();
|
|
|
|
if ( cl.demos[cl.demonum].IsEmpty() || cl.demonum == MAX_DEMOS )
|
|
{
|
|
cl.demonum = 0;
|
|
if ( cl.demos[cl.demonum].IsEmpty() )
|
|
{
|
|
scr_disabled_for_loading = false;
|
|
|
|
ConMsg ("No demos listed with startdemos\n");
|
|
cl.demonum = -1;
|
|
return;
|
|
}
|
|
else if ( !demoplayer->ShouldLoopDemos() )
|
|
{
|
|
cl.demonum = -1;
|
|
scr_disabled_for_loading = false;
|
|
Host_Disconnect( true );
|
|
|
|
demoplayer->OnLastDemoInLoopPlayed();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
Q_snprintf (str,sizeof( str ), "%s %s", CommandLine()->FindParm("-timedemoloop") ? "timedemo" : "playdemo", cl.demos[cl.demonum].Get());
|
|
Cbuf_AddText (str);
|
|
cl.demonum++;
|
|
}
|
|
|
|
ConVar cl_screenshotname( "cl_screenshotname", "", 0, "Custom Screenshot name" );
|
|
|
|
// We'll take a snapshot at the next available opportunity
|
|
void CL_TakeScreenshot(const char *name)
|
|
{
|
|
cl_takesnapshot = true;
|
|
cl_takejpeg = false;
|
|
cl_takesnapshot_internal = false;
|
|
|
|
if ( name != NULL )
|
|
{
|
|
Q_strncpy( cl_snapshotname, name, sizeof( cl_snapshotname ) );
|
|
}
|
|
else
|
|
{
|
|
cl_snapshotname[0] = 0;
|
|
|
|
if ( Q_strlen( cl_screenshotname.GetString() ) > 0 )
|
|
{
|
|
Q_snprintf( cl_snapshotname, sizeof( cl_snapshotname ), "%s", cl_screenshotname.GetString() );
|
|
}
|
|
}
|
|
|
|
cl_snapshot_subdirname[0] = 0;
|
|
}
|
|
|
|
CON_COMMAND_F( screenshot, "Take a screenshot.", FCVAR_CLIENTCMD_CAN_EXECUTE )
|
|
{
|
|
GetTestScriptMgr()->SetWaitCheckPoint( "screenshot" );
|
|
|
|
// Don't playback screenshots unless specifically requested.
|
|
if ( demoplayer->IsPlayingBack() && !cl_playback_screenshots.GetBool() )
|
|
return;
|
|
|
|
if( args.ArgC() == 2 )
|
|
{
|
|
CL_TakeScreenshot( args[ 1 ] );
|
|
}
|
|
else
|
|
{
|
|
CL_TakeScreenshot( NULL );
|
|
}
|
|
}
|
|
|
|
CON_COMMAND_F( devshots_screenshot, "Used by the -makedevshots system to take a screenshot. For taking your own screenshots, use the 'screenshot' command instead.", FCVAR_DONTRECORD )
|
|
{
|
|
CL_TakeScreenshot( NULL );
|
|
|
|
// See if we got a subdirectory to store the devshots in
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
Q_strncpy( cl_snapshot_subdirname, args[1], sizeof( cl_snapshot_subdirname ) );
|
|
|
|
// Use the first available shot in each subdirectory
|
|
cl_snapshotnum = 0;
|
|
}
|
|
}
|
|
|
|
// We'll take a snapshot at the next available opportunity
|
|
void CL_TakeJpeg(const char *name, int quality)
|
|
{
|
|
// Don't playback screenshots unless specifically requested.
|
|
if ( demoplayer->IsPlayingBack() && !cl_playback_screenshots.GetBool() )
|
|
return;
|
|
|
|
cl_takesnapshot = true;
|
|
cl_takejpeg = true;
|
|
cl_jpegquality = clamp( quality, 1, 100 );
|
|
cl_takesnapshot_internal = false;
|
|
|
|
if ( name != NULL )
|
|
{
|
|
Q_strncpy( cl_snapshotname, name, sizeof( cl_snapshotname ) );
|
|
}
|
|
else
|
|
{
|
|
cl_snapshotname[0] = 0;
|
|
}
|
|
}
|
|
|
|
CON_COMMAND( jpeg, "Take a jpeg screenshot: jpeg <filename> <quality 1-100>." )
|
|
{
|
|
if( args.ArgC() >= 2 )
|
|
{
|
|
if ( args.ArgC() == 3 )
|
|
{
|
|
CL_TakeJpeg( args[ 1 ], Q_atoi( args[2] ) );
|
|
}
|
|
else
|
|
{
|
|
CL_TakeJpeg( args[ 1 ], jpeg_quality.GetInt() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CL_TakeJpeg( NULL, jpeg_quality.GetInt() );
|
|
}
|
|
}
|
|
|
|
static void screenshot_internal( const CCommand &args )
|
|
{
|
|
|
|
if( args.ArgC() != 2 )
|
|
{
|
|
Assert( args.ArgC() >= 2 );
|
|
Warning( "__screenshot_internal - wrong number of arguments" );
|
|
return;
|
|
}
|
|
Q_strncpy( cl_snapshotname, args[1], ARRAYSIZE(cl_snapshotname) );
|
|
cl_takesnapshot = true;
|
|
cl_takejpeg = true;
|
|
cl_jpegquality = 70;
|
|
cl_takesnapshot_internal = true;
|
|
}
|
|
|
|
ConCommand screenshot_internal_command( "__screenshot_internal", screenshot_internal, "Internal command to take a screenshot without renumbering or notifying Steam.", FCVAR_DONTRECORD | FCVAR_HIDDEN );
|
|
|
|
void CL_TakeSnapshotAndSwap()
|
|
{
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
|
|
|
bool bReadPixelsFromFrontBuffer = g_pMaterialSystemHardwareConfig->ReadPixelsFromFrontBuffer();
|
|
if ( bReadPixelsFromFrontBuffer )
|
|
{
|
|
Shader_SwapBuffers();
|
|
}
|
|
|
|
if (g_ClientGlobalVariables.client_taking_screenshot
|
|
|| g_ServerGlobalVariables.client_taking_screenshot)
|
|
{
|
|
CL_TakeScreenshot(NULL);
|
|
g_ClientGlobalVariables.client_taking_screenshot = false;
|
|
g_ServerGlobalVariables.client_taking_screenshot = false;
|
|
}
|
|
|
|
if (cl_takesnapshot)
|
|
{
|
|
// Disable threading for the duration of the screenshots, because we need to get pointers to the (complete)
|
|
// back buffer right now.
|
|
bool bEnabled = materials->AllowThreading( false, g_nMaterialSystemThread );
|
|
|
|
char base[MAX_OSPATH];
|
|
char filename[MAX_OSPATH];
|
|
IClientEntity *world = entitylist->GetClientEntity( 0 );
|
|
|
|
g_pFileSystem->CreateDirHierarchy( "screenshots", "DEFAULT_WRITE_PATH" );
|
|
|
|
if ( cl_takesnapshot_internal )
|
|
{
|
|
|
|
// !KLUDGE! Don't save this screenshot to steam
|
|
ConVarRef cl_savescreenshotstosteam( "cl_savescreenshotstosteam" );
|
|
bool bSaveValue = cl_savescreenshotstosteam.GetBool();
|
|
cl_savescreenshotstosteam.SetValue( false );
|
|
|
|
Q_snprintf( filename, sizeof( filename ), "screenshots/%s.jpg", cl_snapshotname );
|
|
videomode->TakeSnapshotJPEG( filename, cl_jpegquality );
|
|
|
|
cl_savescreenshotstosteam.SetValue( bSaveValue );
|
|
}
|
|
else
|
|
{
|
|
if ( world && world->GetModel() )
|
|
{
|
|
Q_FileBase( modelloader->GetName( ( model_t *)world->GetModel() ), base, sizeof( base ) );
|
|
|
|
if ( IsX360() )
|
|
{
|
|
// map name has an additional extension
|
|
V_StripExtension( base, base, sizeof( base ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Q_strncpy( base, "Snapshot", sizeof( base ) );
|
|
}
|
|
|
|
char extension[MAX_OSPATH];
|
|
Q_snprintf( extension, sizeof( extension ), "%s.%s", GetPlatformExt(), cl_takejpeg ? "jpg" : "tga" );
|
|
|
|
// Using a subdir? If so, create it
|
|
if ( cl_snapshot_subdirname[0] )
|
|
{
|
|
Q_snprintf( filename, sizeof( filename ), "screenshots/%s/%s", base, cl_snapshot_subdirname );
|
|
g_pFileSystem->CreateDirHierarchy( filename, "DEFAULT_WRITE_PATH" );
|
|
}
|
|
|
|
if ( cl_snapshotname[0] )
|
|
{
|
|
Q_strncpy( base, cl_snapshotname, sizeof( base ) );
|
|
Q_snprintf( filename, sizeof( filename ), "screenshots/%s%s", base, extension );
|
|
|
|
int iNumber = 0;
|
|
char renamedfile[MAX_OSPATH];
|
|
|
|
while ( 1 )
|
|
{
|
|
Q_snprintf( renamedfile, sizeof( renamedfile ), "screenshots/%s_%04d%s", base, iNumber++, extension );
|
|
if( !g_pFileSystem->GetFileTime( renamedfile ) )
|
|
break;
|
|
}
|
|
|
|
if ( iNumber > 0 && g_pFileSystem->GetFileTime( filename ) )
|
|
{
|
|
g_pFileSystem->RenameFile(filename, renamedfile);
|
|
}
|
|
|
|
cl_screenshotname.SetValue( "" );
|
|
}
|
|
else
|
|
{
|
|
while( 1 )
|
|
{
|
|
if ( cl_snapshot_subdirname[0] )
|
|
{
|
|
Q_snprintf( filename, sizeof( filename ), "screenshots/%s/%s/%s%04d%s", base, cl_snapshot_subdirname, base, cl_snapshotnum++, extension );
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( filename, sizeof( filename ), "screenshots/%s%04d%s", base, cl_snapshotnum++, extension );
|
|
}
|
|
|
|
if( !g_pFileSystem->GetFileTime( filename ) )
|
|
{
|
|
// woo hoo! The file doesn't exist already, so use it.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( cl_takejpeg )
|
|
{
|
|
videomode->TakeSnapshotJPEG( filename, cl_jpegquality );
|
|
g_ServerRemoteAccess.UploadScreenshot( filename );
|
|
}
|
|
else
|
|
{
|
|
videomode->TakeSnapshotTGA( filename );
|
|
}
|
|
}
|
|
cl_takesnapshot = false;
|
|
cl_takesnapshot_internal = false;
|
|
GetTestScriptMgr()->CheckPoint( "screenshot" );
|
|
|
|
// Restore threading if it was previously enabled (if it wasn't this will do nothing).
|
|
materials->AllowThreading( bEnabled, g_nMaterialSystemThread );
|
|
}
|
|
|
|
// If recording movie and the console is totally up, then write out this frame to movie file.
|
|
if ( cl_movieinfo.IsRecording() && !Con_IsVisible() && !scr_drawloading )
|
|
{
|
|
videomode->WriteMovieFrame( cl_movieinfo );
|
|
++cl_movieinfo.movieframe;
|
|
}
|
|
|
|
if( !bReadPixelsFromFrontBuffer )
|
|
{
|
|
Shader_SwapBuffers();
|
|
}
|
|
|
|
// take a screenshot for savegames if necessary
|
|
saverestore->UpdateSaveGameScreenshots();
|
|
|
|
// take screenshot for bx movie maker
|
|
EngineTool_UpdateScreenshot();
|
|
}
|
|
|
|
static float s_flPreviousHostFramerate = 0;
|
|
ConVar cl_simulate_no_quicktime( "cl_simulate_no_quicktime", "0", FCVAR_HIDDEN );
|
|
void CL_StartMovie( const char *filename, int flags, int nWidth, int nHeight, float flFrameRate, int nJpegQuality, VideoSystem_t videoSystem )
|
|
{
|
|
Assert( g_pVideoRecorder == NULL );
|
|
|
|
// StartMove depends on host_framerate not being 0.
|
|
s_flPreviousHostFramerate = host_framerate.GetFloat();
|
|
host_framerate.SetValue( flFrameRate );
|
|
|
|
cl_movieinfo.Reset();
|
|
Q_strncpy( cl_movieinfo.moviename, filename, sizeof( cl_movieinfo.moviename ) );
|
|
cl_movieinfo.type = flags;
|
|
cl_movieinfo.jpeg_quality = nJpegQuality;
|
|
|
|
bool bSuccess = true;
|
|
if ( cl_movieinfo.DoVideo() || cl_movieinfo.DoVideoSound() )
|
|
{
|
|
// HACK: THIS MUST MATCH snd_device.h. Should be exposed more cleanly!!!
|
|
#define SOUND_DMA_SPEED 44100 // hardware playback rate
|
|
|
|
// MGP - switched over to using valve video services from avi
|
|
if ( videoSystem == VideoSystem::NONE && g_pVideo )
|
|
{
|
|
// Find a video system based on features if they didn't specify a specific one.
|
|
VideoSystemFeature_t neededFeatures = VideoSystemFeature::NO_FEATURES;
|
|
if ( cl_movieinfo.DoVideo() )
|
|
neededFeatures |= VideoSystemFeature::ENCODE_VIDEO_TO_FILE;
|
|
if ( cl_movieinfo.DoVideoSound() )
|
|
neededFeatures |= VideoSystemFeature::ENCODE_AUDIO_TO_FILE;
|
|
|
|
videoSystem = g_pVideo->FindNextSystemWithFeature( neededFeatures );
|
|
}
|
|
|
|
if ( !cl_simulate_no_quicktime.GetBool() && g_pVideo && videoSystem != VideoSystem::NONE )
|
|
{
|
|
g_pVideoRecorder = g_pVideo->CreateVideoRecorder( videoSystem );
|
|
if ( g_pVideoRecorder != NULL )
|
|
{
|
|
VideoFrameRate_t theFps;
|
|
if ( IsIntegralValue( flFrameRate ) )
|
|
{
|
|
theFps.SetFPS( RoundFloatToInt( flFrameRate ), false );
|
|
}
|
|
else if ( IsIntegralValue( flFrameRate * 1001.0f / 1000.0f ) ) // 1001 is the ntsc divisor (30*1000/1001 = 29.97, etc)
|
|
{
|
|
theFps.SetFPS( RoundFloatToInt( flFrameRate + 0.05f ), true );
|
|
}
|
|
else
|
|
{
|
|
theFps.SetFPS( RoundFloatToInt( flFrameRate ), false );
|
|
}
|
|
|
|
const int nSize = 256;
|
|
CFmtStrN<nSize> fmtFullFilename( "%s%c%s", com_gamedir, CORRECT_PATH_SEPARATOR, filename );
|
|
|
|
char szFullFilename[nSize];
|
|
V_FixupPathName( szFullFilename, nSize, fmtFullFilename.Access() );
|
|
#ifdef USE_WEBM_FOR_REPLAY
|
|
V_DefaultExtension( szFullFilename, ".webm", sizeof( szFullFilename ) );
|
|
#else
|
|
V_DefaultExtension( szFullFilename, ".mp4", sizeof( szFullFilename ) );
|
|
#endif
|
|
|
|
g_pVideoRecorder->CreateNewMovieFile( szFullFilename, cl_movieinfo.DoVideoSound() );
|
|
#ifdef USE_WEBM_FOR_REPLAY
|
|
g_pVideoRecorder->SetMovieVideoParameters( VideoEncodeCodec::WEBM_CODEC, nJpegQuality, nWidth, nHeight, theFps );
|
|
#else
|
|
g_pVideoRecorder->SetMovieVideoParameters( VideoEncodeCodec::DEFAULT_CODEC, nJpegQuality, nWidth, nHeight, theFps );
|
|
#endif
|
|
|
|
if ( cl_movieinfo.DoVideo() )
|
|
{
|
|
g_pVideoRecorder->SetMovieSourceImageParameters( VideoEncodeSourceFormat::BGR_24BIT, nWidth, nHeight );
|
|
}
|
|
|
|
if ( cl_movieinfo.DoVideoSound() )
|
|
{
|
|
g_pVideoRecorder->SetMovieSourceAudioParameters( AudioEncodeSourceFormat::AUDIO_16BIT_PCMStereo, SOUND_DMA_SPEED, AudioEncodeOptions::LIMIT_AUDIO_TRACK_TO_VIDEO_DURATION );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
}
|
|
|
|
if ( bSuccess )
|
|
{
|
|
SND_MovieStart();
|
|
}
|
|
else
|
|
{
|
|
#ifdef USE_WEBM_FOR_REPLAY
|
|
Warning( "Failed to launch startmovie!\n" );
|
|
#else
|
|
Warning( "Failed to launch startmovie! If you are trying to use h264, please make sure you have QuickTime installed.\n" );
|
|
#endif
|
|
CL_EndMovie();
|
|
}
|
|
}
|
|
|
|
void CL_EndMovie()
|
|
{
|
|
if ( !CL_IsRecordingMovie() )
|
|
return;
|
|
|
|
host_framerate.SetValue( s_flPreviousHostFramerate );
|
|
s_flPreviousHostFramerate = 0.0f;
|
|
|
|
SND_MovieEnd();
|
|
|
|
if ( g_pVideoRecorder && ( cl_movieinfo.DoVideo() || cl_movieinfo.DoVideoSound() ) )
|
|
{
|
|
g_pVideoRecorder->FinishMovie();
|
|
|
|
g_pVideo->DestroyVideoRecorder( g_pVideoRecorder );
|
|
g_pVideoRecorder = NULL;
|
|
}
|
|
|
|
cl_movieinfo.Reset();
|
|
}
|
|
|
|
bool CL_IsRecordingMovie()
|
|
{
|
|
return cl_movieinfo.IsRecording();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_StartMovie_f
|
|
|
|
Sets the engine up to dump frames
|
|
===============
|
|
*/
|
|
|
|
CON_COMMAND_F( startmovie, "Start recording movie frames.", FCVAR_DONTRECORD )
|
|
{
|
|
if ( cmd_source != src_command )
|
|
return;
|
|
|
|
if( args.ArgC() < 2 )
|
|
{
|
|
ConMsg( "startmovie <filename>\n [\n" );
|
|
ConMsg( " (default = TGAs + .wav file)\n" );
|
|
#ifdef USE_WEBM_FOR_REPLAY
|
|
ConMsg( " webm = WebM encoded audio and video\n" );
|
|
#else
|
|
ConMsg( " h264 = H.264-encoded audio and video (must have QuickTime installed!)\n" );
|
|
#endif
|
|
ConMsg( " raw = TGAs + .wav file, same as default\n" );
|
|
ConMsg( " tga = TGAs\n" );
|
|
ConMsg( " jpg/jpeg = JPegs\n" );
|
|
ConMsg( " wav = Write .wav audio file\n" );
|
|
ConMsg( " jpeg_quality nnn = set jpeq quality to nnn (range 1 to 100), default %d\n", DEFAULT_JPEG_QUALITY );
|
|
ConMsg( " ]\n" );
|
|
ConMsg( "examples:\n" );
|
|
ConMsg( " startmovie testmovie jpg wav jpeg_qality 75\n" );
|
|
#ifdef USE_WEBM_FOR_REPLAY
|
|
ConMsg( " startmovie testmovie webm\n" );
|
|
#else
|
|
ConMsg( " startmovie testmovie h264 <--- requires QuickTime\n" );
|
|
ConMsg( "AVI is no longer supported.\n" );
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if ( CL_IsRecordingMovie() )
|
|
{
|
|
ConMsg( "Already recording movie!\n" );
|
|
return;
|
|
}
|
|
|
|
int flags = MovieInfo_t::FMOVIE_TGA | MovieInfo_t::FMOVIE_WAV;
|
|
VideoSystem_t videoSystem = VideoSystem::NONE;
|
|
int nJpegQuality = DEFAULT_JPEG_QUALITY;
|
|
|
|
if ( args.ArgC() > 2 )
|
|
{
|
|
flags = 0;
|
|
for ( int i = 2; i < args.ArgC(); ++i )
|
|
{
|
|
if ( !Q_stricmp( args[ i ], "avi" ) )
|
|
{
|
|
//flags |= MovieInfo_t::FMOVIE_VID | MovieInfo_t::FMOVIE_VIDSOUND;
|
|
//videoSystem = VideoSystem::AVI;
|
|
#ifdef USE_WEBM_FOR_REPLAY
|
|
Warning( "AVI is not supported on this platform! Use \"webm\".\n" );
|
|
#else
|
|
Warning( "AVI is no longer supported! Make sure QuickTime is installed and use \"h264\" - if you install QuickTime, you will need to reboot before using startmovie.\n" );
|
|
#endif
|
|
return;
|
|
}
|
|
#ifdef USE_WEBM_FOR_REPLAY
|
|
else if ( !Q_stricmp( args[ i ], "webm" ) )
|
|
{
|
|
flags |= MovieInfo_t::FMOVIE_VID | MovieInfo_t::FMOVIE_VIDSOUND;
|
|
videoSystem = VideoSystem::WEBM;
|
|
}
|
|
else if ( !Q_stricmp( args[ i ], "h264" ) )
|
|
{
|
|
Warning( "h264 is not supported on this platform! Use \"webm\".\n" );
|
|
return;
|
|
}
|
|
#else
|
|
else if ( !Q_stricmp( args[ i ], "h264" ) )
|
|
{
|
|
flags |= MovieInfo_t::FMOVIE_VID | MovieInfo_t::FMOVIE_VIDSOUND;
|
|
videoSystem = VideoSystem::QUICKTIME;
|
|
}
|
|
else if ( !Q_stricmp( args[ i ], "webm" ) )
|
|
{
|
|
Warning( "WebM is not supported on this platform! Make sure QuickTime is installed and use \"h264\" - if you install QuickTime, you will need to reboot before using startmovie.\n" );
|
|
return;
|
|
}
|
|
#endif
|
|
if ( !Q_stricmp( args[ i ], "raw" ) )
|
|
{
|
|
flags |= MovieInfo_t::FMOVIE_TGA | MovieInfo_t::FMOVIE_WAV;
|
|
}
|
|
if ( !Q_stricmp( args[ i ], "tga" ) )
|
|
{
|
|
flags |= MovieInfo_t::FMOVIE_TGA;
|
|
}
|
|
if ( !Q_stricmp( args[ i ], "jpeg" ) || !Q_stricmp( args[ i ], "jpg" ) )
|
|
{
|
|
flags &= ~MovieInfo_t::FMOVIE_TGA;
|
|
flags |= MovieInfo_t::FMOVIE_JPG;
|
|
}
|
|
if ( !Q_stricmp( args[ i ], "jpeg_quality" ) )
|
|
{
|
|
nJpegQuality = clamp( Q_atoi( args[ ++i ] ), 1, 100 );
|
|
}
|
|
if ( !Q_stricmp( args[ i ], "wav" ) )
|
|
{
|
|
flags |= MovieInfo_t::FMOVIE_WAV;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if ( flags == 0 )
|
|
{
|
|
#ifdef USE_WEBM_FOR_REPLAY
|
|
Warning( "Missing or unknown recording types, must specify one or both of 'webm' or 'raw'\n" );
|
|
#else
|
|
Warning( "Missing or unknown recording types, must specify one or both of 'h264' or 'raw'\n" );
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
float flFrameRate = host_framerate.GetFloat();
|
|
if ( flFrameRate == 0.0f )
|
|
{
|
|
flFrameRate = 30.0f;
|
|
}
|
|
CL_StartMovie( args[ 1 ], flags, videomode->GetModeStereoWidth(), videomode->GetModeStereoHeight(), flFrameRate, nJpegQuality, videoSystem );
|
|
ConMsg( "Started recording movie, frames will record after console is cleared...\n" );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Ends frame dumping
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND_F( endmovie, "Stop recording movie frames.", FCVAR_DONTRECORD )
|
|
{
|
|
if( !CL_IsRecordingMovie() )
|
|
{
|
|
ConMsg( "No movie started.\n" );
|
|
}
|
|
else
|
|
{
|
|
CL_EndMovie();
|
|
ConMsg( "Stopped recording movie...\n" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CL_Rcon_f
|
|
|
|
Send the rest of the command line over as
|
|
an unconnected command.
|
|
=====================
|
|
*/
|
|
CON_COMMAND_F( rcon, "Issue an rcon command.", FCVAR_DONTRECORD )
|
|
{
|
|
char message[1024]; // Command message
|
|
char szParam[ 256 ];
|
|
message[0] = 0;
|
|
for (int i=1 ; i<args.ArgC() ; i++)
|
|
{
|
|
const char *pParam = args[i];
|
|
// put quotes around empty arguments so we can pass things like this: rcon sv_password ""
|
|
// otherwise the "" on the end is lost
|
|
if ( strchr( pParam, ' ' ) || ( Q_strlen( pParam ) == 0 ) )
|
|
{
|
|
Q_snprintf( szParam, sizeof( szParam ), "\"%s\"", pParam );
|
|
Q_strncat( message, szParam, sizeof( message ), COPY_ALL_CHARACTERS );
|
|
}
|
|
else
|
|
{
|
|
Q_strncat( message, pParam, sizeof( message ), COPY_ALL_CHARACTERS );
|
|
}
|
|
if ( i != ( args.ArgC() - 1 ) )
|
|
{
|
|
Q_strncat (message, " ", sizeof( message ), COPY_ALL_CHARACTERS);
|
|
}
|
|
}
|
|
|
|
RCONClient().SendCmd( message );
|
|
}
|
|
|
|
|
|
CON_COMMAND_F( box, "Draw a debug box.", FCVAR_CHEAT )
|
|
{
|
|
if( args.ArgC() != 7 )
|
|
{
|
|
ConMsg ("box x1 y1 z1 x2 y2 z2\n");
|
|
return;
|
|
}
|
|
|
|
Vector mins, maxs;
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
mins[i] = atof(args[i + 1]);
|
|
maxs[i] = atof(args[i + 4]);
|
|
}
|
|
CDebugOverlay::AddBoxOverlay( vec3_origin, mins, maxs, vec3_angle, 255, 0, 0, 0, 100 );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_View_f
|
|
|
|
Debugging changes the view entity to the specified index
|
|
===============
|
|
*/
|
|
CON_COMMAND_F( cl_view, "Set the view entity index.", FCVAR_CHEAT )
|
|
{
|
|
int nNewView;
|
|
|
|
if( args.ArgC() != 2 )
|
|
{
|
|
ConMsg ("cl_view entity#\nCurrent %i\n", cl.m_nViewEntity );
|
|
return;
|
|
}
|
|
|
|
if ( cl.m_nMaxClients > 1 )
|
|
return;
|
|
|
|
nNewView = atoi( args[1] );
|
|
if (!nNewView)
|
|
return;
|
|
|
|
if ( nNewView > entitylist->GetHighestEntityIndex() )
|
|
return;
|
|
|
|
cl.m_nViewEntity = nNewView;
|
|
videomode->MarkClientViewRectDirty(); // Force recalculation
|
|
ConMsg("View entity set to %i\n", nNewView);
|
|
}
|
|
|
|
|
|
static int CL_AllocLightFromArray( dlight_t *pLights, int lightCount, int key )
|
|
{
|
|
int i;
|
|
|
|
// first look for an exact key match
|
|
if (key)
|
|
{
|
|
for ( i = 0; i < lightCount; i++ )
|
|
{
|
|
if (pLights[i].key == key)
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// then look for anything else
|
|
for ( i = 0; i < lightCount; i++ )
|
|
{
|
|
if (pLights[i].die < cl.GetTime())
|
|
return i;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool g_bActiveDlights = false;
|
|
bool g_bActiveElights = false;
|
|
/*
|
|
===============
|
|
CL_AllocDlight
|
|
|
|
===============
|
|
*/
|
|
dlight_t *CL_AllocDlight (int key)
|
|
{
|
|
int i = CL_AllocLightFromArray( cl_dlights, MAX_DLIGHTS, key );
|
|
dlight_t *dl = &cl_dlights[i];
|
|
R_MarkDLightNotVisible( i );
|
|
memset (dl, 0, sizeof(*dl));
|
|
dl->key = key;
|
|
r_dlightchanged |= (1 << i);
|
|
r_dlightactive |= (1 << i);
|
|
g_bActiveDlights = true;
|
|
return dl;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CL_AllocElight
|
|
|
|
===============
|
|
*/
|
|
dlight_t *CL_AllocElight (int key)
|
|
{
|
|
int i = CL_AllocLightFromArray( cl_elights, MAX_ELIGHTS, key );
|
|
dlight_t *el = &cl_elights[i];
|
|
memset (el, 0, sizeof(*el));
|
|
el->key = key;
|
|
g_bActiveElights = true;
|
|
return el;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CL_DecayLights
|
|
|
|
===============
|
|
*/
|
|
void CL_DecayLights (void)
|
|
{
|
|
int i;
|
|
dlight_t *dl;
|
|
float time;
|
|
|
|
time = cl.GetFrameTime();
|
|
if ( time <= 0.0f )
|
|
return;
|
|
|
|
g_bActiveDlights = false;
|
|
g_bActiveElights = false;
|
|
dl = cl_dlights;
|
|
|
|
r_dlightchanged = 0;
|
|
r_dlightactive = 0;
|
|
|
|
for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
|
|
{
|
|
if (!dl->IsRadiusGreaterThanZero())
|
|
{
|
|
R_MarkDLightNotVisible( i );
|
|
continue;
|
|
}
|
|
|
|
if ( dl->die < cl.GetTime() )
|
|
{
|
|
r_dlightchanged |= (1 << i);
|
|
dl->radius = 0;
|
|
}
|
|
else if (dl->decay)
|
|
{
|
|
r_dlightchanged |= (1 << i);
|
|
|
|
dl->radius -= time*dl->decay;
|
|
if (dl->radius < 0)
|
|
{
|
|
dl->radius = 0;
|
|
}
|
|
}
|
|
|
|
if (dl->IsRadiusGreaterThanZero())
|
|
{
|
|
g_bActiveDlights = true;
|
|
r_dlightactive |= (1 << i);
|
|
}
|
|
else
|
|
{
|
|
R_MarkDLightNotVisible( i );
|
|
}
|
|
}
|
|
|
|
dl = cl_elights;
|
|
for (i=0 ; i<MAX_ELIGHTS ; i++, dl++)
|
|
{
|
|
if (!dl->IsRadiusGreaterThanZero())
|
|
continue;
|
|
|
|
if (dl->die < cl.GetTime())
|
|
{
|
|
dl->radius = 0;
|
|
continue;
|
|
}
|
|
|
|
dl->radius -= time*dl->decay;
|
|
if (dl->radius < 0)
|
|
{
|
|
dl->radius = 0;
|
|
}
|
|
if ( dl->IsRadiusGreaterThanZero() )
|
|
{
|
|
g_bActiveElights = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CL_ExtraMouseUpdate( float frametime )
|
|
{
|
|
// Not ready for commands yet.
|
|
if ( !cl.IsActive() )
|
|
return;
|
|
|
|
if ( !Host_ShouldRun() )
|
|
return;
|
|
|
|
// Don't create usercmds here during playback, they were encoded into the packet already
|
|
#if defined( REPLAY_ENABLED )
|
|
if ( demoplayer->IsPlayingBack() && !cl.ishltv && !cl.isreplay )
|
|
return;
|
|
#else
|
|
if ( demoplayer->IsPlayingBack() && !cl.ishltv )
|
|
return;
|
|
#endif
|
|
|
|
// Have client .dll create and store usercmd structure
|
|
g_ClientDLL->ExtraMouseSample( frametime, !cl.m_bPaused );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_SendMove
|
|
|
|
Constructs the movement command and sends it to the server if it's time.
|
|
=================
|
|
*/
|
|
void CL_SendMove( void )
|
|
{
|
|
#if defined( STAGING_ONLY ) || defined( _DEBUG )
|
|
if ( cl_block_usercommand.GetBool() )
|
|
return;
|
|
#endif // STAGING_ONLY || _DEBUG
|
|
|
|
byte data[ MAX_CMD_BUFFER ];
|
|
|
|
int nextcommandnr = cl.lastoutgoingcommand + cl.chokedcommands + 1;
|
|
|
|
// send the client update packet
|
|
|
|
CLC_Move moveMsg;
|
|
|
|
moveMsg.m_DataOut.StartWriting( data, sizeof( data ) );
|
|
|
|
// Determine number of backup commands to send along
|
|
int cl_cmdbackup = 2;
|
|
moveMsg.m_nBackupCommands = clamp( cl_cmdbackup, 0, MAX_BACKUP_COMMANDS );
|
|
|
|
// How many real new commands have queued up
|
|
moveMsg.m_nNewCommands = 1 + cl.chokedcommands;
|
|
moveMsg.m_nNewCommands = clamp( moveMsg.m_nNewCommands, 0, MAX_NEW_COMMANDS );
|
|
|
|
int numcmds = moveMsg.m_nNewCommands + moveMsg.m_nBackupCommands;
|
|
|
|
int from = -1; // first command is deltaed against zeros
|
|
|
|
bool bOK = true;
|
|
|
|
for ( int to = nextcommandnr - numcmds + 1; to <= nextcommandnr; to++ )
|
|
{
|
|
bool isnewcmd = to >= (nextcommandnr - moveMsg.m_nNewCommands + 1);
|
|
|
|
// first valid command number is 1
|
|
bOK = bOK && g_ClientDLL->WriteUsercmdDeltaToBuffer( &moveMsg.m_DataOut, from, to, isnewcmd );
|
|
from = to;
|
|
}
|
|
|
|
if ( bOK )
|
|
{
|
|
// only write message if all usercmds were written correctly, otherwise parsing would fail
|
|
cl.m_NetChannel->SendNetMsg( moveMsg );
|
|
}
|
|
}
|
|
|
|
void CL_Move(float accumulated_extra_samples, bool bFinalTick )
|
|
{
|
|
if ( !cl.IsConnected() )
|
|
return;
|
|
|
|
if ( !Host_ShouldRun() )
|
|
return;
|
|
|
|
tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s", __FUNCTION__ );
|
|
|
|
// only send packets on the final tick in one engine frame
|
|
bool bSendPacket = true;
|
|
|
|
// Don't create usercmds here during playback, they were encoded into the packet already
|
|
if ( demoplayer->IsPlayingBack() )
|
|
{
|
|
#if defined( REPLAY_ENABLED )
|
|
if ( cl.ishltv || cl.isreplay )
|
|
#else
|
|
if ( cl.ishltv )
|
|
#endif
|
|
{
|
|
// still do it when playing back a HLTV/replay demo
|
|
bSendPacket = false;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// don't send packets if update time not reached or chnnel still sending
|
|
// in loopback mode don't send only if host_limitlocal is enabled
|
|
|
|
if ( ( !cl.m_NetChannel->IsLoopback() || host_limitlocal.GetInt() ) &&
|
|
( ( net_time < cl.m_flNextCmdTime ) || !cl.m_NetChannel->CanPacket() || !bFinalTick ) )
|
|
{
|
|
bSendPacket = false;
|
|
}
|
|
|
|
if ( cl.IsActive() )
|
|
{
|
|
VPROF( "CL_Move" );
|
|
|
|
int nextcommandnr = cl.lastoutgoingcommand + cl.chokedcommands + 1;
|
|
|
|
// Have client .dll create and store usercmd structure
|
|
g_ClientDLL->CreateMove(
|
|
nextcommandnr,
|
|
host_state.interval_per_tick - accumulated_extra_samples,
|
|
!cl.IsPaused() );
|
|
|
|
// Store new usercmd to dem file
|
|
if ( demorecorder->IsRecording() )
|
|
{
|
|
// Back up one because we've incremented outgoing_sequence each frame by 1 unit
|
|
demorecorder->RecordUserInput( nextcommandnr );
|
|
}
|
|
|
|
if ( bSendPacket )
|
|
{
|
|
CL_SendMove();
|
|
}
|
|
else
|
|
{
|
|
// netchanll will increase internal outgoing sequnce number too
|
|
cl.m_NetChannel->SetChoked();
|
|
// Mark command as held back so we'll send it next time
|
|
cl.chokedcommands++;
|
|
}
|
|
}
|
|
|
|
if ( !bSendPacket )
|
|
return;
|
|
|
|
// Request non delta compression if high packet loss, show warning message
|
|
bool hasProblem = cl.m_NetChannel->IsTimingOut() && !demoplayer->IsPlayingBack() && cl.IsActive();
|
|
|
|
// Request non delta compression if high packet loss, show warning message
|
|
if ( hasProblem )
|
|
{
|
|
con_nprint_t np;
|
|
np.time_to_live = 1.0;
|
|
np.index = 2;
|
|
np.fixed_width_font = false;
|
|
np.color[ 0 ] = 1.0;
|
|
np.color[ 1 ] = 0.2;
|
|
np.color[ 2 ] = 0.2;
|
|
|
|
float flTimeOut = cl.m_NetChannel->GetTimeoutSeconds();
|
|
Assert( flTimeOut != -1.0f );
|
|
float flRemainingTime = flTimeOut - cl.m_NetChannel->GetTimeSinceLastReceived();
|
|
Con_NXPrintf( &np, "WARNING: Connection Problem" );
|
|
np.index = 3;
|
|
Con_NXPrintf( &np, "Auto-disconnect in %.1f seconds", flRemainingTime );
|
|
|
|
cl.ForceFullUpdate(); // sets m_nDeltaTick to -1
|
|
}
|
|
|
|
if ( cl.IsActive() )
|
|
{
|
|
NET_Tick mymsg( cl.m_nDeltaTick, host_frametime_unbounded, host_frametime_stddeviation );
|
|
cl.m_NetChannel->SendNetMsg( mymsg );
|
|
}
|
|
|
|
//COM_Log( "cl.log", "Sending command number %i(%i) to server\n", cl.m_NetChan->m_nOutSequenceNr, cl.m_NetChan->m_nOutSequenceNr & CL_UPDATE_MASK );
|
|
|
|
// Remember outgoing command that we are sending
|
|
cl.lastoutgoingcommand = cl.m_NetChannel->SendDatagram( NULL );
|
|
|
|
cl.chokedcommands = 0;
|
|
|
|
// calc next packet send time
|
|
|
|
if ( cl.IsActive() )
|
|
{
|
|
// use full update rate when active
|
|
float commandInterval = 1.0f / cl_cmdrate->GetFloat();
|
|
float maxDelta = min ( host_state.interval_per_tick, commandInterval );
|
|
float delta = clamp( (float)(net_time - cl.m_flNextCmdTime), 0.0f, maxDelta );
|
|
cl.m_flNextCmdTime = net_time + commandInterval - delta;
|
|
}
|
|
else
|
|
{
|
|
// during signon process send only 5 packets/second
|
|
cl.m_flNextCmdTime = net_time + ( 1.0f / 5.0f );
|
|
}
|
|
|
|
}
|
|
|
|
#define TICK_INTERVAL (host_state.interval_per_tick)
|
|
#define ROUND_TO_TICKS( t ) ( TICK_INTERVAL * TIME_TO_TICKS( t ) )
|
|
|
|
void CL_LatchInterpolationAmount()
|
|
{
|
|
if ( !cl.IsConnected() )
|
|
return;
|
|
|
|
float dt = cl.m_NetChannel->GetTimeSinceLastReceived();
|
|
float flClientInterpolationAmount = ROUND_TO_TICKS( cl.GetClientInterpAmount() );
|
|
|
|
float flInterp = 0.0f;
|
|
if ( flClientInterpolationAmount > 0.001 )
|
|
{
|
|
flInterp = clamp( dt / flClientInterpolationAmount, 0.0f, 3.0f );
|
|
}
|
|
cl.m_NetChannel->SetInterpolationAmount( flInterp );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pMessage -
|
|
//-----------------------------------------------------------------------------
|
|
void CL_HudMessage( const char *pMessage )
|
|
{
|
|
if ( g_ClientDLL )
|
|
{
|
|
g_ClientDLL->HudText( pMessage );
|
|
}
|
|
}
|
|
|
|
CON_COMMAND_F( cl_showents, "Dump entity list to console.", FCVAR_CHEAT )
|
|
{
|
|
for ( int i = 0; i < entitylist->GetMaxEntities(); i++ )
|
|
{
|
|
char entStr[256], classStr[256];
|
|
IClientNetworkable *pEnt;
|
|
|
|
if((pEnt = entitylist->GetClientNetworkable(i)) != NULL)
|
|
{
|
|
entStr[0] = 0;
|
|
Q_snprintf(classStr, sizeof( classStr ), "'%s'", pEnt->GetClientClass()->m_pNetworkName);
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf(entStr, sizeof( entStr ), "(missing), ");
|
|
Q_snprintf(classStr, sizeof( classStr ), "(missing)");
|
|
}
|
|
|
|
if ( pEnt )
|
|
ConMsg("Ent %3d: %s class %s\n", i, entStr, classStr);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: returns true if the background level should be loaded on startup
|
|
//-----------------------------------------------------------------------------
|
|
bool CL_ShouldLoadBackgroundLevel( const CCommand &args )
|
|
{
|
|
if ( InEditMode() )
|
|
return false;
|
|
|
|
// If TF2 and PC we don't want to load the background map.
|
|
bool bIsTF2 = false;
|
|
if ( ( Q_stricmp( COM_GetModDirectory(), "tf" ) == 0 ) || ( Q_stricmp( COM_GetModDirectory(), "tf_beta" ) == 0 ) )
|
|
{
|
|
bIsTF2 = true;
|
|
}
|
|
|
|
if ( bIsTF2 && IsPC() )
|
|
return false;
|
|
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
// presence of args identifies an end-of-game situation
|
|
if ( IsX360() )
|
|
{
|
|
// 360 needs to get UI in the correct state to transition to the Background level
|
|
// from the credits.
|
|
EngineVGui()->OnCreditsFinished();
|
|
return true;
|
|
}
|
|
|
|
if ( !Q_stricmp( args[1], "force" ) )
|
|
{
|
|
// Adrian: Have to do this so the menu shows up if we ever call this while in a level.
|
|
Host_Disconnect( true );
|
|
// pc can't get into background maps fast enough, so just show main menu
|
|
return false;
|
|
}
|
|
|
|
if ( !Q_stricmp( args[1], "playendgamevid" ) )
|
|
{
|
|
// Bail back to the menu and play the end game video.
|
|
CommandLine()->AppendParm( "-endgamevid", NULL );
|
|
CommandLine()->RemoveParm( "-recapvid" );
|
|
game->PlayStartupVideos();
|
|
CommandLine()->RemoveParm( "-endgamevid" );
|
|
cl.Disconnect( "Finished playing end game videos", true );
|
|
return false;
|
|
}
|
|
|
|
if ( !Q_stricmp( args[1], "playrecapvid" ) )
|
|
{
|
|
// Bail back to the menu and play the recap video
|
|
CommandLine()->AppendParm( "-recapvid", NULL );
|
|
CommandLine()->RemoveParm( "-endgamevid" );
|
|
HostState_Restart();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// if force is set, then always return true
|
|
if (CommandLine()->CheckParm("-forcestartupmenu"))
|
|
return true;
|
|
|
|
// don't load the map in developer or console mode
|
|
if ( developer.GetInt() ||
|
|
CommandLine()->CheckParm("-console") ||
|
|
CommandLine()->CheckParm("-dev") ||
|
|
CommandLine()->CheckParm("-nobackgroundlevel") )
|
|
return false;
|
|
|
|
// don't load the map if we're going straight into a level
|
|
if ( CommandLine()->CheckParm("+map") ||
|
|
CommandLine()->CheckParm("+connect") ||
|
|
CommandLine()->CheckParm("+playdemo") ||
|
|
CommandLine()->CheckParm("+timedemo") ||
|
|
CommandLine()->CheckParm("+timedemoquit") ||
|
|
CommandLine()->CheckParm("+load") ||
|
|
CommandLine()->CheckParm("-makereslists"))
|
|
return false;
|
|
|
|
#ifdef _X360
|
|
// check if we are accepting an invite
|
|
if ( XboxLaunch()->GetLaunchFlags() & LF_INVITERESTART )
|
|
return false;
|
|
#endif
|
|
|
|
// nothing else is going on, so load the startup level
|
|
|
|
return true;
|
|
}
|
|
|
|
#define DEFAULT_BACKGROUND_NAME "background01"
|
|
|
|
int g_iRandomChapterIndex = -1;
|
|
|
|
int CL_GetBackgroundLevelIndex( int nNumChapters )
|
|
{
|
|
if ( g_iRandomChapterIndex != -1 )
|
|
return g_iRandomChapterIndex;
|
|
|
|
int iChapterIndex = sv_unlockedchapters.GetInt();
|
|
if ( iChapterIndex <= 0 )
|
|
{
|
|
// expected to be [1..N]
|
|
iChapterIndex = 1;
|
|
}
|
|
|
|
if ( sv_unlockedchapters.GetInt() >= ( nNumChapters-1 ) )
|
|
{
|
|
RandomSeed( Plat_MSTime() );
|
|
g_iRandomChapterIndex = iChapterIndex = RandomInt( 1, nNumChapters );
|
|
}
|
|
|
|
return iChapterIndex;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: returns the name of the background level to load
|
|
//-----------------------------------------------------------------------------
|
|
void CL_GetBackgroundLevelName( char *pszBackgroundName, int bufSize, bool bMapName )
|
|
{
|
|
Q_strncpy( pszBackgroundName, DEFAULT_BACKGROUND_NAME, bufSize );
|
|
|
|
KeyValues *pChapterFile = new KeyValues( pszBackgroundName );
|
|
|
|
if ( pChapterFile->LoadFromFile( g_pFileSystem, "scripts/ChapterBackgrounds.txt" ) )
|
|
{
|
|
KeyValues *pChapterRoot = pChapterFile;
|
|
|
|
const char *szChapterIndex;
|
|
int nNumChapters = 1;
|
|
KeyValues *pChapters = pChapterFile->GetNextKey();
|
|
if ( bMapName && pChapters )
|
|
{
|
|
const char *pszName = pChapters->GetName();
|
|
if ( pszName && pszName[0] && !Q_strncmp( "BackgroundMaps", pszName, 14 ) )
|
|
{
|
|
pChapterRoot = pChapters;
|
|
pChapters = pChapters->GetFirstSubKey();
|
|
}
|
|
else
|
|
{
|
|
pChapters = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pChapters = NULL;
|
|
}
|
|
|
|
if ( !pChapters )
|
|
{
|
|
pChapters = pChapterFile->GetFirstSubKey();
|
|
}
|
|
|
|
// Find the highest indexed chapter
|
|
while ( pChapters )
|
|
{
|
|
szChapterIndex = pChapters->GetName();
|
|
|
|
if ( szChapterIndex )
|
|
{
|
|
int nChapter = atoi(szChapterIndex);
|
|
|
|
if( nChapter > nNumChapters )
|
|
nNumChapters = nChapter;
|
|
}
|
|
|
|
pChapters = pChapters->GetNextKey();
|
|
}
|
|
|
|
int nChapterToLoad = CL_GetBackgroundLevelIndex( nNumChapters );
|
|
|
|
// Find the chapter background with this index
|
|
char buf[4];
|
|
Q_snprintf( buf, sizeof(buf), "%d", nChapterToLoad );
|
|
KeyValues *pLoadChapter = pChapterRoot->FindKey(buf);
|
|
|
|
// Copy the background name
|
|
if ( pLoadChapter )
|
|
{
|
|
Q_strncpy( pszBackgroundName, pLoadChapter->GetString(), bufSize );
|
|
}
|
|
}
|
|
|
|
pChapterFile->deleteThis();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Callback to open the game menus
|
|
//-----------------------------------------------------------------------------
|
|
void CL_CheckToDisplayStartupMenus( const CCommand &args )
|
|
{
|
|
if ( CL_ShouldLoadBackgroundLevel( args ) )
|
|
{
|
|
char szBackgroundName[_MAX_PATH];
|
|
CL_GetBackgroundLevelName( szBackgroundName, sizeof(szBackgroundName), true );
|
|
|
|
char cmd[_MAX_PATH];
|
|
Q_snprintf( cmd, sizeof(cmd), "map_background %s\n", szBackgroundName );
|
|
Cbuf_AddText( cmd );
|
|
}
|
|
}
|
|
|
|
static float s_fDemoRevealGameUITime = -1;
|
|
float s_fDemoPlayMusicTime = -1;
|
|
static bool s_bIsRavenHolmn = false;
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: run the special demo logic when transitioning from the trainstation levels
|
|
//----------------------------------------------------------------------------
|
|
void CL_DemoTransitionFromTrainstation()
|
|
{
|
|
// kick them out to GameUI instead and bring up the chapter page with raveholm unlocked
|
|
sv_unlockedchapters.SetValue(6); // unlock ravenholm
|
|
Cbuf_AddText( "sv_cheats 1; fadeout 1.5; sv_cheats 0;");
|
|
Cbuf_Execute();
|
|
s_fDemoRevealGameUITime = Sys_FloatTime() + 1.5;
|
|
s_bIsRavenHolmn = false;
|
|
}
|
|
|
|
void CL_DemoTransitionFromRavenholm()
|
|
{
|
|
Cbuf_AddText( "sv_cheats 1; fadeout 2; sv_cheats 0;");
|
|
Cbuf_Execute();
|
|
s_fDemoRevealGameUITime = Sys_FloatTime() + 1.9;
|
|
s_bIsRavenHolmn = true;
|
|
}
|
|
|
|
void CL_DemoTransitionFromTestChmb()
|
|
{
|
|
Cbuf_AddText( "sv_cheats 1; fadeout 2; sv_cheats 0;");
|
|
Cbuf_Execute();
|
|
s_fDemoRevealGameUITime = Sys_FloatTime() + 1.9;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: make the gameui appear after a certain interval
|
|
//----------------------------------------------------------------------------
|
|
void V_RenderVGuiOnly();
|
|
bool V_CheckGamma();
|
|
void CL_DemoCheckGameUIRevealTime( )
|
|
{
|
|
if ( s_fDemoRevealGameUITime > 0 )
|
|
{
|
|
if ( s_fDemoRevealGameUITime < Sys_FloatTime() )
|
|
{
|
|
s_fDemoRevealGameUITime = -1;
|
|
|
|
SCR_BeginLoadingPlaque();
|
|
Cbuf_AddText( "disconnect;");
|
|
|
|
CCommand args;
|
|
CL_CheckToDisplayStartupMenus( args );
|
|
|
|
s_fDemoPlayMusicTime = Sys_FloatTime() + 1.0;
|
|
}
|
|
}
|
|
|
|
if ( s_fDemoPlayMusicTime > 0 )
|
|
{
|
|
V_CheckGamma();
|
|
V_RenderVGuiOnly();
|
|
if ( s_fDemoPlayMusicTime < Sys_FloatTime() )
|
|
{
|
|
s_fDemoPlayMusicTime = -1;
|
|
EngineVGui()->ActivateGameUI();
|
|
|
|
if ( CL_IsHL2Demo() )
|
|
{
|
|
if ( s_bIsRavenHolmn )
|
|
{
|
|
Cbuf_AddText( "play music/ravenholm_1.mp3;" );
|
|
}
|
|
else
|
|
{
|
|
EngineVGui()->ShowNewGameDialog(6);// bring up the new game dialog in game UI
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: setup a debug string that is uploaded on crash
|
|
//----------------------------------------------------------------------------
|
|
extern bool g_bV3SteamInterface;
|
|
char g_minidumpinfo[ 4096 ] = {0};
|
|
PAGED_POOL_INFO_t g_pagedpoolinfo = { 0 };
|
|
void DisplaySystemVersion( char *osversion, int maxlen );
|
|
|
|
void CL_SetPagedPoolInfo()
|
|
{
|
|
if ( IsX360() )
|
|
return;
|
|
#if !defined( _X360 ) && !defined(NO_STEAM) && !defined(SWDS)
|
|
Plat_GetPagedPoolInfo( &g_pagedpoolinfo );
|
|
#endif
|
|
}
|
|
|
|
void CL_SetSteamCrashComment()
|
|
{
|
|
if ( IsX360() )
|
|
return;
|
|
|
|
char map[ 80 ];
|
|
char videoinfo[ 2048 ];
|
|
char misc[ 256 ];
|
|
char driverinfo[ 2048 ];
|
|
char osversion[ 256 ];
|
|
|
|
map[ 0 ] = 0;
|
|
driverinfo[ 0 ] = 0;
|
|
videoinfo[ 0 ] = 0;
|
|
misc[ 0 ] = 0;
|
|
osversion[ 0 ] = 0;
|
|
|
|
if ( host_state.worldmodel )
|
|
{
|
|
CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), map, sizeof( map ) );
|
|
}
|
|
|
|
DisplaySystemVersion( osversion, sizeof( osversion ) );
|
|
|
|
MaterialAdapterInfo_t info;
|
|
materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), info );
|
|
|
|
const char *dxlevel = "Unk";
|
|
int nDxLevel = g_pMaterialSystemHardwareConfig->GetDXSupportLevel();
|
|
if ( g_pMaterialSystemHardwareConfig )
|
|
{
|
|
dxlevel = COM_DXLevelToString( nDxLevel ) ;
|
|
}
|
|
|
|
// Make a string out of the high part and low parts of driver version
|
|
char szDXDriverVersion[ 64 ];
|
|
Q_snprintf( szDXDriverVersion, sizeof( szDXDriverVersion ), "%ld.%ld.%ld.%ld",
|
|
( long )( info.m_nDriverVersionHigh>>16 ),
|
|
( long )( info.m_nDriverVersionHigh & 0xffff ),
|
|
( long )( info.m_nDriverVersionLow>>16 ),
|
|
( long )( info.m_nDriverVersionLow & 0xffff ) );
|
|
|
|
Q_snprintf( driverinfo, sizeof(driverinfo), "Driver Name: %s\nDriver Version: %s\nVendorId / DeviceId: 0x%x / 0x%x\nSubSystem / Rev: 0x%x / 0x%x\nDXLevel: %s [%d]\nVid: %i x %i",
|
|
info.m_pDriverName,
|
|
szDXDriverVersion,
|
|
info.m_VendorID,
|
|
info.m_DeviceID,
|
|
info.m_SubSysID,
|
|
info.m_Revision,
|
|
dxlevel ? dxlevel : "Unk", nDxLevel,
|
|
videomode->GetModeWidth(), videomode->GetModeHeight() );
|
|
|
|
ConVarRef mat_picmip( "mat_picmip" );
|
|
ConVarRef mat_forceaniso( "mat_forceaniso" );
|
|
ConVarRef mat_trilinear( "mat_trilinear" );
|
|
ConVarRef mat_antialias( "mat_antialias" );
|
|
ConVarRef mat_aaquality( "mat_aaquality" );
|
|
ConVarRef r_shadowrendertotexture( "r_shadowrendertotexture" );
|
|
ConVarRef r_flashlightdepthtexture( "r_flashlightdepthtexture" );
|
|
#ifndef _X360
|
|
ConVarRef r_waterforceexpensive( "r_waterforceexpensive" );
|
|
#endif
|
|
ConVarRef r_waterforcereflectentities( "r_waterforcereflectentities" );
|
|
ConVarRef mat_vsync( "mat_vsync" );
|
|
ConVarRef r_rootlod( "r_rootlod" );
|
|
ConVarRef mat_reducefillrate( "mat_reducefillrate" );
|
|
ConVarRef mat_motion_blur_enabled( "mat_motion_blur_enabled" );
|
|
ConVarRef mat_queue_mode( "mat_queue_mode" );
|
|
|
|
#ifdef _X360
|
|
Q_snprintf( videoinfo, sizeof(videoinfo), "picmip: %i forceansio: %i trilinear: %i antialias: %i vsync: %i rootlod: %i reducefillrate: %i\n"\
|
|
"shadowrendertotexture: %i r_flashlightdepthtexture %i waterforcereflectentities: %i mat_motion_blur_enabled: %i",
|
|
mat_picmip.GetInt(), mat_forceaniso.GetInt(), mat_trilinear.GetInt(), mat_antialias.GetInt(), mat_aaquality.GetInt(),
|
|
mat_vsync.GetInt(), r_rootlod.GetInt(), mat_reducefillrate.GetInt(),
|
|
r_shadowrendertotexture.GetInt(), r_flashlightdepthtexture.GetInt(),
|
|
r_waterforcereflectentities.GetInt(),
|
|
mat_motion_blur_enabled.GetInt() );
|
|
#else
|
|
Q_snprintf( videoinfo, sizeof(videoinfo), "picmip: %i forceansio: %i trilinear: %i antialias: %i vsync: %i rootlod: %i reducefillrate: %i\n"\
|
|
"shadowrendertotexture: %i r_flashlightdepthtexture %i waterforceexpensive: %i waterforcereflectentities: %i mat_motion_blur_enabled: %i mat_queue_mode %i",
|
|
mat_picmip.GetInt(), mat_forceaniso.GetInt(), mat_trilinear.GetInt(), mat_antialias.GetInt(),
|
|
mat_vsync.GetInt(), r_rootlod.GetInt(), mat_reducefillrate.GetInt(),
|
|
r_shadowrendertotexture.GetInt(), r_flashlightdepthtexture.GetInt(),
|
|
r_waterforceexpensive.GetInt(), r_waterforcereflectentities.GetInt(),
|
|
mat_motion_blur_enabled.GetInt(), mat_queue_mode.GetInt() );
|
|
#endif
|
|
int latency = 0;
|
|
if ( cl.m_NetChannel )
|
|
{
|
|
latency = (int)( 1000.0f * cl.m_NetChannel->GetAvgLatency( FLOW_OUTGOING ) );
|
|
}
|
|
|
|
Q_snprintf( misc, sizeof( misc ), "skill:%i rate %i update %i cmd %i latency %i msec",
|
|
skill.GetInt(),
|
|
cl_rate->GetInt(),
|
|
(int)cl_updaterate->GetFloat(),
|
|
(int)cl_cmdrate->GetFloat(),
|
|
latency
|
|
);
|
|
|
|
const char *pNetChannel = "Not Connected";
|
|
if ( cl.m_NetChannel )
|
|
{
|
|
pNetChannel = cl.m_NetChannel->GetRemoteAddress().ToString();
|
|
}
|
|
|
|
CL_SetPagedPoolInfo();
|
|
|
|
Q_snprintf( g_minidumpinfo, sizeof(g_minidumpinfo),
|
|
"Map: %s\n"\
|
|
"Game: %s\n"\
|
|
"Build: %i\n"\
|
|
"Misc: %s\n"\
|
|
"Net: %s\n"\
|
|
"cmdline:%s\n"\
|
|
"driver: %s\n"\
|
|
"video: %s\n"\
|
|
"OS: %s\n",
|
|
map, com_gamedir, build_number(), misc, pNetChannel, CommandLine()->GetCmdLine(), driverinfo, videoinfo, osversion );
|
|
|
|
char full[ 4096 ];
|
|
Q_snprintf( full, sizeof( full ), "%sPP PAGES: used: %d, free %d\n", g_minidumpinfo, (int)g_pagedpoolinfo.numPagesUsed, (int)g_pagedpoolinfo.numPagesFree );
|
|
|
|
#ifndef NO_STEAM
|
|
SteamAPI_SetMiniDumpComment( full );
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
// register commands
|
|
//
|
|
static ConCommand startupmenu( "startupmenu", &CL_CheckToDisplayStartupMenus, "Opens initial menu screen and loads the background bsp, but only if no other level is being loaded, and we're not in developer mode." );
|
|
|
|
ConVar cl_language( "cl_language", "english", FCVAR_USERINFO, "Language (from HKCU\\Software\\Valve\\Steam\\Language)" );
|
|
void CL_InitLanguageCvar()
|
|
{
|
|
Msg("CL_InitLanguageCvar\n");
|
|
if ( Steam3Client().SteamApps() )
|
|
{
|
|
cl_language.SetValue( Steam3Client().SteamApps()->GetCurrentGameLanguage() );
|
|
}
|
|
else
|
|
{
|
|
char *szLang = getenv("LANG");
|
|
|
|
if ( CommandLine()->CheckParm( "-language" ) )
|
|
{
|
|
cl_language.SetValue( CommandLine()->ParmValue( "-language", "english") );
|
|
return;
|
|
}
|
|
else if( szLang )
|
|
{
|
|
ELanguage lang = PchLanguageICUCodeToELanguage(szLang, k_Lang_English);
|
|
const char *szShortLang = GetLanguageShortName(lang);
|
|
if( Q_strncmp(szShortLang, "none", 4) != 0 )
|
|
{
|
|
cl_language.SetValue( szShortLang );
|
|
return;
|
|
}
|
|
}
|
|
|
|
cl_language.SetValue( "english" );
|
|
}
|
|
}
|
|
|
|
void CL_ChangeCloudSettingsCvar( IConVar *var, const char *pOldValue, float flOldValue );
|
|
ConVar cl_cloud_settings( "cl_cloud_settings", "1", FCVAR_HIDDEN, "Cloud enabled from (from HKCU\\Software\\Valve\\Steam\\Apps\\appid\\Cloud)", CL_ChangeCloudSettingsCvar );
|
|
void CL_ChangeCloudSettingsCvar( IConVar *var, const char *pOldValue, float flOldValue )
|
|
{
|
|
// !! bug do i need to do something linux-wise here.
|
|
if ( IsPC() && Steam3Client().SteamRemoteStorage() )
|
|
{
|
|
ConVarRef ref( var->GetName() );
|
|
Steam3Client().SteamRemoteStorage()->SetCloudEnabledForApp( ref.GetBool() );
|
|
if ( cl_cloud_settings.GetInt() == STEAMREMOTESTORAGE_CLOUD_ON && flOldValue == STEAMREMOTESTORAGE_CLOUD_OFF )
|
|
{
|
|
// If we were just turned on, get our configuration from remote storage.
|
|
engineClient->ReadConfiguration( false );
|
|
engineClient->ClientCmd_Unrestricted( "refresh_options_dialog" );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void CL_InitCloudSettingsCvar()
|
|
{
|
|
if ( IsPC() && Steam3Client().SteamRemoteStorage() )
|
|
{
|
|
int iCloudSettings = STEAMREMOTESTORAGE_CLOUD_OFF;
|
|
if ( Steam3Client().SteamRemoteStorage()->IsCloudEnabledForApp() )
|
|
iCloudSettings = STEAMREMOTESTORAGE_CLOUD_ON;
|
|
|
|
cl_cloud_settings.SetValue( iCloudSettings );
|
|
}
|
|
else
|
|
{
|
|
// If not on PC or steam not available, set to 0 to make sure no replication occurs or is attempted
|
|
cl_cloud_settings.SetValue( STEAMREMOTESTORAGE_CLOUD_OFF );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
CL_Init
|
|
=================
|
|
*/
|
|
void CL_Init (void)
|
|
{
|
|
cl.Clear();
|
|
|
|
CL_InitLanguageCvar();
|
|
CL_InitCloudSettingsCvar();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CL_Shutdown( void )
|
|
{
|
|
}
|
|
|
|
CON_COMMAND_F( cl_fullupdate, "Forces the server to send a full update packet", FCVAR_CHEAT )
|
|
{
|
|
cl.ForceFullUpdate();
|
|
}
|
|
|
|
|
|
#ifdef STAGING_ONLY
|
|
|
|
CON_COMMAND( cl_download, "Downloads a file from server." )
|
|
{
|
|
if ( args.ArgC() != 2 )
|
|
return;
|
|
|
|
if ( !cl.m_NetChannel )
|
|
return;
|
|
|
|
cl.m_NetChannel->RequestFile( args[ 1 ] ); // just for testing stuff
|
|
}
|
|
|
|
#endif // STAGING_ONLY
|
|
|
|
|
|
CON_COMMAND_F( setinfo, "Adds a new user info value", FCVAR_CLIENTCMD_CAN_EXECUTE )
|
|
{
|
|
if ( args.ArgC() != 3 )
|
|
{
|
|
Msg("Syntax: setinfo <key> <value>\n");
|
|
return;
|
|
}
|
|
|
|
const char *name = args[ 1 ];
|
|
const char *value = args[ 2 ];
|
|
|
|
// Prevent players manually changing their name (their Steam account provides it now)
|
|
if ( Q_stricmp( name, "name" ) == 0 )
|
|
return;
|
|
|
|
// Discard any convar change request if contains funky characters
|
|
bool bFunky = false;
|
|
for (const char *s = name ; *s != '\0' ; ++s )
|
|
{
|
|
if ( !V_isalnum(*s) && *s != '_' )
|
|
{
|
|
bFunky = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( bFunky )
|
|
{
|
|
Msg( "Ignoring convar change request for variable '%s', which contains invalid character(s)\n", name );
|
|
return;
|
|
}
|
|
|
|
ConCommandBase *pCommand = g_pCVar->FindCommandBase( name );
|
|
|
|
ConVarRef sv_cheats( "sv_cheats" );
|
|
|
|
if ( pCommand )
|
|
{
|
|
if ( pCommand->IsCommand() )
|
|
{
|
|
Msg("Name %s is already registered as console command\n", name );
|
|
return;
|
|
}
|
|
|
|
if ( !pCommand->IsFlagSet(FCVAR_USERINFO) )
|
|
{
|
|
Msg("Convar %s is already registered but not as user info value\n", name );
|
|
return;
|
|
}
|
|
|
|
if ( pCommand->IsFlagSet( FCVAR_NOT_CONNECTED ) )
|
|
{
|
|
#ifndef DEDICATED
|
|
// Connected to server?
|
|
if ( cl.IsConnected() )
|
|
{
|
|
extern IBaseClientDLL *g_ClientDLL;
|
|
if ( pCommand->IsFlagSet( FCVAR_USERINFO ) && g_ClientDLL && g_ClientDLL->IsConnectedUserInfoChangeAllowed( NULL ) )
|
|
{
|
|
// Client.dll is allowing the convar change
|
|
}
|
|
else
|
|
{
|
|
ConMsg( "Can't change %s when playing, disconnect from the server or switch team to spectators\n", pCommand->GetName() );
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ( IsPC() )
|
|
{
|
|
#if !defined(NO_STEAM)
|
|
EUniverse eUniverse = GetSteamUniverse();
|
|
if ( (( eUniverse != k_EUniverseBeta ) && ( eUniverse != k_EUniverseDev )) && pCommand->IsFlagSet( FCVAR_DEVELOPMENTONLY ) )
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
if ( pCommand->IsFlagSet( FCVAR_CHEAT ) && sv_cheats.GetBool() == 0 )
|
|
{
|
|
Msg("Convar %s is marked as cheat and cheats are off\n", name );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// cvar not found, create it now
|
|
char *pszString = V_strdup( name );
|
|
|
|
pCommand = new ConVar( pszString, "", FCVAR_USERINFO, "Custom user info value" );
|
|
}
|
|
|
|
ConVar *pConVar = (ConVar*)pCommand;
|
|
|
|
pConVar->SetValue( value );
|
|
|
|
if ( cl.IsConnected() )
|
|
{
|
|
// send changed cvar to server
|
|
NET_SetConVar convar( name, value );
|
|
cl.m_NetChannel->SendNetMsg( convar );
|
|
}
|
|
}
|
|
|
|
|
|
CON_COMMAND( cl_precacheinfo, "Show precache info (client)." )
|
|
{
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
cl.DumpPrecacheStats( args[ 1 ] );
|
|
return;
|
|
}
|
|
|
|
// Show all data
|
|
cl.DumpPrecacheStats( MODEL_PRECACHE_TABLENAME );
|
|
cl.DumpPrecacheStats( DECAL_PRECACHE_TABLENAME );
|
|
cl.DumpPrecacheStats( SOUND_PRECACHE_TABLENAME );
|
|
cl.DumpPrecacheStats( GENERIC_PRECACHE_TABLENAME );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *object -
|
|
// stringTable -
|
|
// stringNumber -
|
|
// *newString -
|
|
// *newData -
|
|
//-----------------------------------------------------------------------------
|
|
void Callback_ModelChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
|
|
{
|
|
if ( stringTable == cl.m_pModelPrecacheTable )
|
|
{
|
|
// Index 0 is always NULL, just ignore it
|
|
// Index 1 == the world, don't
|
|
if ( stringNumber > 1 )
|
|
{
|
|
// DevMsg( "Preloading model %s\n", newString );
|
|
cl.SetModel( stringNumber );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( 0 ) ; // Callback_*Changed called with wrong stringtable
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *object -
|
|
// stringTable -
|
|
// stringNumber -
|
|
// *newString -
|
|
// *newData -
|
|
//-----------------------------------------------------------------------------
|
|
void Callback_GenericChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
|
|
{
|
|
if ( stringTable == cl.m_pGenericPrecacheTable )
|
|
{
|
|
// Index 0 is always NULL, just ignore it
|
|
if ( stringNumber >= 1 )
|
|
{
|
|
cl.SetGeneric( stringNumber );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( 0 ) ; // Callback_*Changed called with wrong stringtable
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *object -
|
|
// stringTable -
|
|
// stringNumber -
|
|
// *newString -
|
|
// *newData -
|
|
//-----------------------------------------------------------------------------
|
|
void Callback_SoundChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
|
|
{
|
|
if ( stringTable == cl.m_pSoundPrecacheTable )
|
|
{
|
|
// Index 0 is always NULL, just ignore it
|
|
if ( stringNumber >= 1 )
|
|
{
|
|
cl.SetSound( stringNumber );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( 0 ) ; // Callback_*Changed called with wrong stringtable
|
|
}
|
|
}
|
|
|
|
void Callback_DecalChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
|
|
{
|
|
if ( stringTable == cl.m_pDecalPrecacheTable )
|
|
{
|
|
cl.SetDecal( stringNumber );
|
|
}
|
|
else
|
|
{
|
|
Assert( 0 ) ; // Callback_*Changed called with wrong stringtable
|
|
}
|
|
}
|
|
|
|
|
|
void Callback_InstanceBaselineChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
|
|
{
|
|
Assert( stringTable == cl.m_pInstanceBaselineTable );
|
|
// cl.UpdateInstanceBaseline( stringNumber );
|
|
}
|
|
|
|
void Callback_UserInfoChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
|
|
{
|
|
Assert( stringTable == cl.m_pUserInfoTable );
|
|
|
|
// stringnumber == player slot
|
|
|
|
player_info_t *player = (player_info_t*)newData;
|
|
|
|
if ( !player )
|
|
return; // player left the game
|
|
|
|
// request custom user files if necessary
|
|
for ( int i=0; i<MAX_CUSTOM_FILES; i++ )
|
|
{
|
|
cl.CheckOthersCustomFile( player->customFiles[i] );
|
|
}
|
|
|
|
// fire local client event game event
|
|
IGameEvent * event = g_GameEventManager.CreateEvent( "player_info" );
|
|
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", player->userID );
|
|
event->SetInt( "friendsid", player->friendsID );
|
|
event->SetInt( "index", stringNumber );
|
|
event->SetString( "name", player->name );
|
|
event->SetString( "networkid", player->guid );
|
|
event->SetBool( "bot", player->fakeplayer );
|
|
|
|
g_GameEventManager.FireEventClientSide( event );
|
|
}
|
|
}
|
|
|
|
void Callback_DynamicModelsChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
|
|
{
|
|
#ifndef SWDS
|
|
extern IVModelInfoClient *modelinfoclient;
|
|
if ( modelinfoclient )
|
|
{
|
|
modelinfoclient->OnDynamicModelsStringTableChange( stringNumber, newString, newData );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CL_HookClientStringTables()
|
|
{
|
|
// install hooks
|
|
int numTables = cl.m_StringTableContainer->GetNumTables();
|
|
|
|
for ( int i =0; i<numTables; i++)
|
|
{
|
|
// iterate through server tables
|
|
CNetworkStringTable *pTable =
|
|
(CNetworkStringTable*)cl.m_StringTableContainer->GetTable( i );
|
|
|
|
if ( !pTable )
|
|
continue;
|
|
|
|
cl.HookClientStringTable( pTable->GetTableName() );
|
|
}
|
|
}
|
|
// Installs the all, and invokes cb for all existing items
|
|
void CL_InstallAndInvokeClientStringTableCallbacks()
|
|
{
|
|
// install hooks
|
|
int numTables = cl.m_StringTableContainer->GetNumTables();
|
|
|
|
for ( int i =0; i<numTables; i++)
|
|
{
|
|
// iterate through server tables
|
|
CNetworkStringTable *pTable =
|
|
(CNetworkStringTable*)cl.m_StringTableContainer->GetTable( i );
|
|
|
|
if ( !pTable )
|
|
continue;
|
|
|
|
pfnStringChanged pOldFunction = pTable->GetCallback();
|
|
|
|
cl.InstallStringTableCallback( pTable->GetTableName() );
|
|
|
|
pfnStringChanged pNewFunction = pTable->GetCallback();
|
|
if ( !pNewFunction )
|
|
continue;
|
|
|
|
// We already had it installed (e.g., from client .dll) so all of the callbacks have been called and don't need a second dose
|
|
if ( pNewFunction == pOldFunction )
|
|
continue;
|
|
|
|
for ( int j = 0; j < pTable->GetNumStrings(); ++j )
|
|
{
|
|
int userDataSize;
|
|
const void *pUserData = pTable->GetStringUserData( j, &userDataSize );
|
|
(*pNewFunction)( NULL, pTable, j, pTable->GetString( j ), pUserData );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Singleton client state
|
|
CClientState cl;
|