css_enhanced_waf/engine/cl_main.cpp
Kamay Xutax cfa5b12eea Fixed local player interpolation and added debug_screenshot_bullet_position
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 !
2024-07-14 00:54:57 +02:00

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;