You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1900 lines
53 KiB
1900 lines
53 KiB
//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======
|
|
//
|
|
// Purpose:
|
|
//
|
|
//=============================================================================
|
|
|
|
#include "cbase.h"
|
|
|
|
#include "igamesystem.h"
|
|
#include "GameStats.h"
|
|
#include "tier1/utlstring.h"
|
|
#include "filesystem.h"
|
|
#include "tier1/utlbuffer.h"
|
|
#include "fmtstr.h"
|
|
#include "tier2/tier2.h"
|
|
#include "tier0/cpumonitoring.h"
|
|
#include "materialsystem/imaterialsystemhardwareconfig.h"
|
|
|
|
#ifndef DEDICATED
|
|
#include "iregistry.h"
|
|
#endif
|
|
|
|
#include "tier1/utldict.h"
|
|
#include "tier0/icommandline.h"
|
|
#include "tier0/perfstats.h"
|
|
#include <time.h>
|
|
#ifdef GAME_DLL
|
|
#include "vehicle_base.h"
|
|
#endif
|
|
|
|
#ifdef CLIENT_DLL
|
|
#include "vgui_int.h"
|
|
#include "igameresources.h"
|
|
#include "voice_status.h"
|
|
extern const ConVar *sv_cheats;
|
|
#ifndef NO_STEAM
|
|
#include "steamworks_gamestats_client.h"
|
|
#endif
|
|
#include "materialsystem/materialsystem_config.h"
|
|
#endif
|
|
|
|
#if defined( _X360 )
|
|
#include "xbox/xbox_win32stubs.h"
|
|
#endif
|
|
|
|
// NOTE: This has to be the last file included!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
|
|
#define GAMESTATS_LOG_FILE "gamestats.log"
|
|
#define GAMESTATS_PATHID "MOD"
|
|
|
|
/*
|
|
#define ONE_DAY_IN_SECONDS 86400
|
|
|
|
// Lower threshold in debug for testing...
|
|
#if defined( _DEBUG )
|
|
#define WALKED_AWAY_FROM_KEYBOARD_SECONDS 15.0f // 15 seconds of movement == might be paused
|
|
#else
|
|
#define WALKED_AWAY_FROM_KEYBOARD_SECONDS 300.0f // 5 minutes of no movement == might be paused
|
|
#endif
|
|
*/
|
|
|
|
extern IUploadGameStats *gamestatsuploader;
|
|
|
|
static char s_szPseudoUniqueID[20] = "";
|
|
|
|
static inline char const *SafeString( char const *pStr )
|
|
{
|
|
return ( pStr ) ? pStr : "?";
|
|
}
|
|
|
|
static CBaseGameStats s_GameStats_Singleton;
|
|
CBaseGameStats *gamestats = &s_GameStats_Singleton; //start out pointing at the basic version which does nothing by default
|
|
extern ConVar skill;
|
|
void OverWriteCharsWeHate( char *pStr );
|
|
|
|
bool StatsTrackingIsFullyEnabled( void );
|
|
|
|
class CGamestatsData
|
|
{
|
|
public:
|
|
CGamestatsData()
|
|
{
|
|
m_pKVData = NULL;
|
|
m_bHaveData = false;
|
|
AllocData();
|
|
}
|
|
|
|
~CGamestatsData()
|
|
{
|
|
FreeData();
|
|
}
|
|
|
|
void AllocData()
|
|
{
|
|
FreeData();
|
|
char buffer[ 128 ];
|
|
Q_snprintf( buffer, sizeof( buffer ), "gamestats_" );
|
|
UTIL_GetModDir( buffer + 10, sizeof( buffer ) - 10 );
|
|
m_pKVData = new KeyValues( buffer );
|
|
}
|
|
void FreeData()
|
|
{
|
|
if ( m_pKVData != NULL )
|
|
{
|
|
m_pKVData->deleteThis();
|
|
m_pKVData = NULL;
|
|
}
|
|
}
|
|
|
|
KeyValues *m_pKVData;
|
|
bool m_bHaveData;
|
|
};
|
|
|
|
//used to drive most of the game stat event handlers as well as track basic stats under the hood of CBaseGameStats
|
|
class CBaseGameStats_Driver : public CAutoGameSystemPerFrame
|
|
{
|
|
public:
|
|
CBaseGameStats_Driver( void );
|
|
|
|
typedef CAutoGameSystemPerFrame BaseClass;
|
|
|
|
// IGameSystem overloads
|
|
virtual bool Init();
|
|
virtual void Shutdown();
|
|
|
|
// Level init, shutdown
|
|
virtual void LevelInitPreEntity();
|
|
virtual void LevelShutdownPreEntity();
|
|
// Called during game save
|
|
virtual void OnSave();
|
|
// Called during game restore, after the local player has connected and entities have been fully restored
|
|
virtual void OnRestore();
|
|
|
|
virtual void FrameUpdatePostEntityThink();
|
|
|
|
void PossibleMapChange( void );
|
|
|
|
void CollectData( StatSendType_t sendType );
|
|
void SendData();
|
|
void ResetData();
|
|
bool AddBaseDataForSend( KeyValues *pKV, StatSendType_t sendType );
|
|
|
|
StatsBufferRecord_t m_StatsBuffer[STATS_WINDOW_SIZE];
|
|
bool m_bBufferFull;
|
|
int m_nWriteIndex;
|
|
double m_flLastRealTime;
|
|
double m_flLastSampleTime;
|
|
float m_flTotalTimeInLevels;
|
|
int m_iNumLevels;
|
|
bool m_bDidVoiceChat; // Did the player use voice chat at ALL this map?
|
|
|
|
// Track how often the CPU is reported as running at full speed (above kCPUMonitoringWarning1),
|
|
// medium speed (above kCPUMonitoringWarning2) or low speed (other).
|
|
int m_nCPUUnthrottledCount;
|
|
int m_nCPUThrottle1Count;
|
|
int m_nCPUThrottle2Count;
|
|
// Track when we last received data.
|
|
double m_lastCPUFrequencyTimestamp;
|
|
|
|
// Generate a buffer that will keep trailing observations prior to adding them to the main m_StatsBuffer array
|
|
StatsBufferRecord_t m_TrailingStatsBuffer[STATS_TRAILING_WINDOW_SIZE];
|
|
int m_nTrailingWriteIndex;
|
|
int m_nTrailingSamplesWritten;
|
|
|
|
|
|
template<class T> T AverageStat( T StatsBufferRecord_t::*field ) const
|
|
{
|
|
T sum = 0;
|
|
int iMax = m_bBufferFull ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex;
|
|
for( int i = 0; i < iMax; i++ )
|
|
sum += m_StatsBuffer[i].*field;
|
|
return sum / iMax;
|
|
}
|
|
|
|
template<class T> T MaxStat( T StatsBufferRecord_t::*field ) const
|
|
{
|
|
T maxsofar = -16000000;
|
|
int iMax = m_bBufferFull ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex;
|
|
for( int i = 0; i < iMax; i++ )
|
|
maxsofar = MAX( maxsofar, m_StatsBuffer[i].*field );
|
|
return maxsofar;
|
|
}
|
|
|
|
template<class T> T MinStat( T StatsBufferRecord_t::*field ) const
|
|
{
|
|
T minsofar = 16000000;
|
|
int iMax = m_bBufferFull ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex;
|
|
for( int i = 0; i < iMax; i++ )
|
|
minsofar = MIN( minsofar, m_StatsBuffer[i].*field );
|
|
return minsofar;
|
|
}
|
|
|
|
template<class T> T StdDevStat( T StatsBufferRecord_t::*field, T average ) const
|
|
{
|
|
T flDeviationsquared = 0;
|
|
int iMax = m_bBufferFull ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex;
|
|
|
|
// Compute std deviation
|
|
for (int i = 0; i < iMax; i++)
|
|
{
|
|
T val = m_StatsBuffer[i].*field - average;
|
|
flDeviationsquared += (val * val);
|
|
}
|
|
|
|
return ( ( iMax > 1 ) ? sqrt( (flDeviationsquared / T(iMax - 1) ) ) : 0 );
|
|
}
|
|
|
|
void SortFramerate()
|
|
{
|
|
int iMax = ( ( m_bBufferFull ) ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex ) - 1;
|
|
|
|
// Sort members of m_StatsBuffer
|
|
std::sort(&m_StatsBuffer[0],&m_StatsBuffer[iMax]);
|
|
}
|
|
|
|
float PercentileFramerate( int nPercent ) const
|
|
{
|
|
int iMax = ( ( m_bBufferFull ) ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex ) - 1;
|
|
|
|
// Return the first or last member if nPercent is out of bounds
|
|
if ( nPercent >= 100 )
|
|
return m_StatsBuffer[iMax].m_flFrameRate;
|
|
if ( nPercent <= 0 )
|
|
return m_StatsBuffer[0].m_flFrameRate;
|
|
|
|
// Otherwise determine the desired index and return its field.
|
|
// NOTE: nPercent = 50 is close to (but is NEVER) the median - special code would be needed to handle odd-item arrays, or averaging two elements for even-item arrays.
|
|
int nIdx = (iMax * nPercent) / 100;
|
|
return m_StatsBuffer[nIdx].m_flFrameRate;
|
|
}
|
|
|
|
inline void AdvanceIndex( void )
|
|
{
|
|
m_nWriteIndex++;
|
|
if ( m_nWriteIndex == STATS_WINDOW_SIZE )
|
|
{
|
|
m_nWriteIndex = 0;
|
|
m_bBufferFull = true;
|
|
}
|
|
}
|
|
|
|
void UpdatePerfStats( void )
|
|
{
|
|
double flCurTime = Plat_FloatTime();
|
|
|
|
if ( ( m_flLastRealTime > 0.0f ) &&
|
|
( flCurTime > m_flLastRealTime ) )
|
|
{
|
|
float flFps = 1.0 / ( flCurTime - m_flLastRealTime );
|
|
float flCpuWait = g_PerfStats.m_Slots[ PERF_STATS_SLOT_END_FRAME ].m_PrevFrameTime.GetMillisecondsF();
|
|
if ( g_pGameRules ) // are we in the game? if not, it's not worth collecting histogram info
|
|
{
|
|
uint8 nBin = m_FpsHistory.Update( flFps, flCpuWait );
|
|
m_FpsHistogram.Update( nBin );
|
|
for ( int nEvent = 0; nEvent < PERF_STATS_EVENT_COUNT; ++nEvent )
|
|
{
|
|
m_FpsHistogramGame[ nEvent ].Update( nBin );
|
|
}
|
|
}
|
|
|
|
m_TrailingStatsBuffer[m_nTrailingWriteIndex].m_flFrameRate = flFps;
|
|
|
|
m_nTrailingWriteIndex++;
|
|
if ( m_nTrailingWriteIndex == STATS_TRAILING_WINDOW_SIZE )
|
|
m_nTrailingWriteIndex = 0;
|
|
|
|
if ( m_nTrailingSamplesWritten < STATS_TRAILING_WINDOW_SIZE )
|
|
m_nTrailingSamplesWritten++;
|
|
}
|
|
|
|
if ( ( m_flLastSampleTime >= 0.0f ) &&
|
|
( flCurTime - m_flLastSampleTime >= STATS_RECORD_INTERVAL ) )
|
|
{
|
|
if ( ( m_flLastRealTime > 0 ) && ( flCurTime > m_flLastRealTime ) )
|
|
{
|
|
|
|
StatsBufferRecord_t &stat = m_StatsBuffer[m_nWriteIndex];
|
|
|
|
float flSumFrameRate = 0;
|
|
for ( int i = 0; i < m_nTrailingSamplesWritten; i++ )
|
|
{
|
|
flSumFrameRate += m_TrailingStatsBuffer[i].m_flFrameRate;
|
|
}
|
|
float flFrameRate = flSumFrameRate / m_nTrailingSamplesWritten;
|
|
stat.m_flFrameRate = flFrameRate;
|
|
|
|
m_nTrailingSamplesWritten = 0;
|
|
m_nTrailingWriteIndex = 0;
|
|
|
|
#ifdef CLIENT_DLL
|
|
// The stat system isn't designed to handle split screen players. Until it get's
|
|
// redesigned, let's take the first player's split screen ping, since all other stats
|
|
// will be based on the first player
|
|
IGameResources *gr = GameResources();
|
|
int ping = 0;
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer( FirstValidSplitScreenSlot() );
|
|
if ( pPlayer && gr )
|
|
{
|
|
ping = gr->GetPing( pPlayer->entindex() );
|
|
}
|
|
stat.m_flServerPing = ping;
|
|
#endif
|
|
|
|
stat.m_flMainThreadTimeMS = g_PerfStats.m_Slots[PERF_STATS_SLOT_MAINTHREAD].m_PrevFrameTime.GetMillisecondsF();
|
|
stat.m_flMainThreadWaitTimeMS = g_PerfStats.m_Slots[PERF_STATS_SLOT_END_FRAME].m_PrevFrameTime.GetMillisecondsF();
|
|
stat.m_flRenderThreadTimeMS = g_PerfStats.m_Slots[PERF_STATS_SLOT_RENDERTHREAD].m_PrevFrameTime.GetMillisecondsF();
|
|
stat.m_flRenderThreadWaitTimeMS = g_PerfStats.m_Slots[PERF_STATS_SLOT_FORCE_HARDWARE_SYNC].m_PrevFrameTime.GetMillisecondsF();
|
|
|
|
AdvanceIndex();
|
|
m_flLastSampleTime = flCurTime;
|
|
}
|
|
}
|
|
if ( m_flLastSampleTime == -1 )
|
|
{
|
|
m_flLastSampleTime = flCurTime;
|
|
}
|
|
m_flLastRealTime = flCurTime;
|
|
|
|
#ifdef CLIENT_DLL
|
|
if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
|
|
{
|
|
m_bDidVoiceChat |= GetClientVoiceMgr()->IsLocalPlayerSpeaking( FirstValidSplitScreenSlot() );
|
|
}
|
|
#endif
|
|
|
|
const CPUFrequencyResults results = GetCPUFrequencyResults( true );
|
|
if ( results.m_timeStamp > m_lastCPUFrequencyTimestamp )
|
|
{
|
|
m_lastCPUFrequencyTimestamp = results.m_timeStamp;
|
|
// Increment the appropriate counter for how much the CPU is throttled (if at all)
|
|
if ( results.m_percentage > kCPUMonitoringWarning1 )
|
|
++m_nCPUUnthrottledCount;
|
|
else if ( results.m_percentage > kCPUMonitoringWarning2 )
|
|
++m_nCPUThrottle1Count;
|
|
else
|
|
++m_nCPUThrottle2Count;
|
|
}
|
|
}
|
|
|
|
|
|
void FirePerfStatsEvent( PerfStatsEventEnum_t nEvent, int nLookAhead = CFpsHistory::HISTORY_BUF_SIZE, int nLookBack = CFpsHistory::HISTORY_BUF_SIZE )
|
|
{
|
|
m_FpsHistogramGame[ nEvent ].Fire( nLookAhead, nLookBack, m_FpsHistory );
|
|
}
|
|
|
|
CUtlString m_PrevMapName; //used to track "OnMapChange" events
|
|
int m_iLoadedVersion;
|
|
char m_szLoadedUserID[ 17 ]; // GUID
|
|
|
|
bool m_bEnabled; //false if incapable of uploading or the user doesn't want to enable stat tracking
|
|
bool m_bShuttingDown;
|
|
bool m_bInLevel;
|
|
bool m_bFirstLevel;
|
|
time_t m_tLastUpload;
|
|
|
|
float m_flLevelStartTime;
|
|
|
|
bool m_bStationary;
|
|
float m_flLastMovementTime;
|
|
CUserCmd m_LastUserCmd;
|
|
bool m_bGamePaused;
|
|
float m_flPauseStartTime;
|
|
|
|
CGamestatsData *m_pGamestatsData;
|
|
|
|
CFpsHistory m_FpsHistory;
|
|
CFpsHistogram m_FpsHistogram;
|
|
CFpsSelectiveHistogram m_FpsHistogramGame[ PERF_STATS_EVENT_COUNT ];
|
|
};
|
|
static CBaseGameStats_Driver CBGSDriver;
|
|
|
|
void UpdatePerfStats( void )
|
|
{
|
|
CBGSDriver.UpdatePerfStats();
|
|
}
|
|
|
|
void FirePerfStatsEvent( PerfStatsEventEnum_t nEvent, int nLookAhead, int nLookBack )
|
|
{
|
|
CBGSDriver.FirePerfStatsEvent( nEvent, nLookAhead, nLookBack );
|
|
}
|
|
|
|
uint8 CFpsHistory::Update( float flFps, float flCpuWait )
|
|
{
|
|
uint8 nBin = 0;
|
|
if ( flFps >= 45 )
|
|
{
|
|
if ( flFps >= 90 )
|
|
{
|
|
if ( flFps >= 120 )
|
|
{
|
|
nBin = FPS_120;
|
|
}
|
|
else
|
|
{
|
|
nBin = FPS_90;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( flFps >= 60 )
|
|
{
|
|
nBin = FPS_60;
|
|
}
|
|
else
|
|
{
|
|
nBin = FPS_45;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( flFps >= 20 )
|
|
{
|
|
if ( flFps >= 30 )
|
|
{
|
|
nBin = FPS_30;
|
|
}
|
|
else
|
|
{
|
|
nBin = FPS_20;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( flFps >= 10 )
|
|
{
|
|
nBin = FPS_10;
|
|
}
|
|
else
|
|
{
|
|
nBin = FPS_LOW;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( flCpuWait > 0.5f )
|
|
{
|
|
nBin |= 0x80;
|
|
}
|
|
|
|
return nBin;
|
|
}
|
|
|
|
int64 CFpsHistogram::Encode()const
|
|
{
|
|
uint nMaxBin = 0;
|
|
for ( int i = 0; i < FPS_BIN_COUNT; ++i )
|
|
{
|
|
nMaxBin = Max( nMaxBin, m_nFps[ i ] );
|
|
}
|
|
|
|
if ( nMaxBin == 0 )
|
|
return 0;
|
|
|
|
int64 nEncode = 0;
|
|
for ( int i = 0; i < FPS_BIN_COUNT / 2; ++i )
|
|
{
|
|
nEncode <<= 4;
|
|
if ( uint nDenominator = m_nFps[ i * 2 ] + m_nFps[ i * 2 + 1 ] )
|
|
{
|
|
uint nNormalized = ( 15ll * ( m_nCpuWaits[ i * 2 ] + m_nCpuWaits[ i * 2 + 1 ] ) + nDenominator / 2 ) / nDenominator;
|
|
nEncode |= nNormalized;
|
|
}
|
|
}
|
|
for ( int i = 0; i < FPS_BIN_COUNT; ++i )
|
|
{
|
|
nEncode <<= 6;
|
|
uint nNormalized = ( ( 0x3Fll * m_nFps[ i ] + nMaxBin / 2 ) / nMaxBin ); // round to nearest
|
|
nEncode |= nNormalized;
|
|
}
|
|
return nEncode;
|
|
}
|
|
|
|
|
|
CBaseGameStats_Driver::CBaseGameStats_Driver( void ) :
|
|
BaseClass( "CGameStats" ),
|
|
m_iLoadedVersion( -1 ),
|
|
m_bEnabled( false ),
|
|
m_bShuttingDown( false ),
|
|
m_bInLevel( false ),
|
|
m_bFirstLevel( true ),
|
|
m_flLevelStartTime( 0.0f ),
|
|
m_bStationary( false ),
|
|
m_flLastMovementTime( 0.0f ),
|
|
m_bGamePaused( false ),
|
|
m_pGamestatsData( NULL ),
|
|
m_bBufferFull( false ),
|
|
m_nWriteIndex( 0 ),
|
|
m_flLastRealTime( -1 ),
|
|
m_flLastSampleTime( -1 ),
|
|
m_flTotalTimeInLevels( 0 ),
|
|
m_iNumLevels( 0 ),
|
|
m_bDidVoiceChat( false ),
|
|
m_nTrailingWriteIndex( 0 ),
|
|
m_nTrailingSamplesWritten( 0 )
|
|
{
|
|
m_szLoadedUserID[0] = 0;
|
|
m_tLastUpload = 0;
|
|
m_LastUserCmd.Reset();
|
|
}
|
|
|
|
static FileHandle_t g_LogFileHandle = FILESYSTEM_INVALID_HANDLE;
|
|
|
|
CBaseGameStats::CBaseGameStats() :
|
|
m_bLogging( false ),
|
|
m_bLoggingToFile( false )
|
|
{
|
|
}
|
|
|
|
bool CBaseGameStats::StatTrackingAllowed( void )
|
|
{
|
|
return CBGSDriver.m_bEnabled;
|
|
}
|
|
|
|
// Don't care about vcr hooks here...
|
|
#undef localtime
|
|
#undef asctime
|
|
|
|
#include <time.h>
|
|
|
|
void CBaseGameStats::StatsLog( char const *fmt, ... )
|
|
{
|
|
if ( !m_bLogging && !m_bLoggingToFile )
|
|
return;
|
|
|
|
char buf[ 2048 ];
|
|
va_list argptr;
|
|
va_start( argptr, fmt );
|
|
Q_vsnprintf( buf, sizeof( buf ), fmt, argptr );
|
|
va_end( argptr );
|
|
|
|
// Prepend timestamp and spew it
|
|
|
|
// Prepend the time.
|
|
time_t aclock;
|
|
time( &aclock );
|
|
struct tm *newtime = localtime( &aclock );
|
|
|
|
char timeString[ 128 ];
|
|
Q_strncpy( timeString, asctime( newtime ), sizeof( timeString ) );
|
|
// Get rid of the \n.
|
|
char *pEnd = strstr( timeString, "\n" );
|
|
if ( pEnd )
|
|
{
|
|
*pEnd = 0;
|
|
}
|
|
|
|
if ( m_bLogging )
|
|
{
|
|
DevMsg( "[GS %s - %7.2f] %s", timeString, gpGlobals->realtime, buf );
|
|
}
|
|
|
|
if ( m_bLoggingToFile )
|
|
{
|
|
if ( FILESYSTEM_INVALID_HANDLE == g_LogFileHandle )
|
|
{
|
|
g_LogFileHandle = filesystem->Open( GAMESTATS_LOG_FILE, "a", GAMESTATS_PATHID );
|
|
}
|
|
|
|
if ( FILESYSTEM_INVALID_HANDLE != g_LogFileHandle )
|
|
{
|
|
filesystem->FPrintf( g_LogFileHandle, "[GS %s - %7.2f] %s", timeString, gpGlobals->realtime, buf );
|
|
filesystem->Flush( g_LogFileHandle );
|
|
}
|
|
}
|
|
}
|
|
|
|
static char s_szSaveFileName[256] = "";
|
|
static char s_szStatUploadRegistryKeyName[256] = "";
|
|
|
|
const char *CBaseGameStats::GetStatSaveFileName( void )
|
|
{
|
|
AssertMsg( s_szSaveFileName[0] != '\0', "Don't know what file to save stats to." );
|
|
return s_szSaveFileName;
|
|
}
|
|
|
|
const char *CBaseGameStats::GetStatUploadRegistryKeyName( void )
|
|
{
|
|
AssertMsg( s_szStatUploadRegistryKeyName[0] != '\0', "Don't know the registry key to use to mark stats uploads." );
|
|
return s_szStatUploadRegistryKeyName;
|
|
}
|
|
|
|
const char *CBaseGameStats::GetUserPseudoUniqueID( void )
|
|
{
|
|
AssertMsg( s_szPseudoUniqueID[0] != '\0', "Don't have a pseudo unique ID." );
|
|
return s_szPseudoUniqueID;
|
|
}
|
|
|
|
void CBaseGameStats::Event_Init( void )
|
|
{
|
|
#ifdef GAME_DLL
|
|
SetSteamStatistic( filesystem->IsSteam() );
|
|
SetCyberCafeStatistic( gamestatsuploader->IsCyberCafeUser() );
|
|
SetDXLevelStatistic( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() );
|
|
++m_BasicStats.m_Summary.m_nCount;
|
|
|
|
StatsLog( "CBaseGameStats::Event_Init [%dth session]\n", m_BasicStats.m_Summary.m_nCount );
|
|
#endif // GAME_DLL
|
|
}
|
|
|
|
void CBaseGameStats::Event_Shutdown( void )
|
|
{
|
|
#ifdef GAME_DLL
|
|
StatsLog( "CBaseGameStats::Event_Shutdown [%dth session]\n", m_BasicStats.m_Summary.m_nCount );
|
|
|
|
StatsLog( "\n====================================================================\n\n" );
|
|
#endif
|
|
}
|
|
|
|
void CBaseGameStats::Event_MapChange( const char *szOldMapName, const char *szNewMapName )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_MapChange to [%s]\n", szNewMapName );
|
|
}
|
|
|
|
void CBaseGameStats::Event_LevelInit( void )
|
|
{
|
|
#ifdef GAME_DLL
|
|
StatsLog( "CBaseGameStats::Event_LevelInit [%s]\n", CBGSDriver.m_PrevMapName.String() );
|
|
|
|
BasicGameStatsRecord_t *map = gamestats->m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
|
|
++map->m_nCount;
|
|
|
|
// HACK HACK: Punching this hole through only works in single player!!!
|
|
if ( gpGlobals->maxClients == 1 )
|
|
{
|
|
ConVarRef closecaption( "closecaption" );
|
|
if( closecaption.IsValid() )
|
|
SetCaptionsStatistic( closecaption.GetBool() );
|
|
|
|
SetHDRStatistic( gamestatsuploader->IsHDREnabled() );
|
|
|
|
SetSkillStatistic( skill.GetInt() );
|
|
SetSteamStatistic( filesystem->IsSteam() );
|
|
SetCyberCafeStatistic( gamestatsuploader->IsCyberCafeUser() );
|
|
}
|
|
#endif // GAME_DLL
|
|
}
|
|
|
|
void CBaseGameStats::Event_LevelShutdown( float flElapsed )
|
|
{
|
|
#ifdef GAME_DLL
|
|
BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
|
|
Assert( map );
|
|
map->m_nSeconds += (int)flElapsed;
|
|
gamestats->m_BasicStats.m_Summary.m_nSeconds += (int)flElapsed;
|
|
|
|
StatsLog( "CBaseGameStats::Event_LevelShutdown [%s] %.2f elapsed %d total\n", CBGSDriver.m_PrevMapName.String(), flElapsed, gamestats->m_BasicStats.m_Summary.m_nSeconds );
|
|
#endif // GAME_DLL
|
|
}
|
|
|
|
void CBaseGameStats::Event_SaveGame( void )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_SaveGame [%s]\n", CBGSDriver.m_PrevMapName.String() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_LoadGame( void )
|
|
{
|
|
#ifdef GAME_DLL
|
|
char const *pchSaveFile = engine->GetMostRecentlyLoadedFileName();
|
|
StatsLog( "CBaseGameStats::Event_LoadGame [%s] from %s\n", CBGSDriver.m_PrevMapName.String(), pchSaveFile );
|
|
#endif
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
|
|
void CBaseGameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
|
|
{
|
|
++m_BasicStats.m_Summary.m_nDeaths;
|
|
|
|
if( CBGSDriver.m_bInLevel )
|
|
{
|
|
BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
|
|
++map->m_nDeaths;
|
|
StatsLog( " Player died %dth time in level [%s]!!!\n", map->m_nDeaths, CBGSDriver.m_PrevMapName.String() );
|
|
}
|
|
else
|
|
{
|
|
StatsLog( " Player died, but not in a level!!!\n" );
|
|
Assert( 0 );
|
|
}
|
|
|
|
StatsLog( "CBaseGameStats::Event_PlayerKilled [%s] [%dth death]\n", pPlayer->GetPlayerName(), m_BasicStats.m_Summary.m_nDeaths );
|
|
}
|
|
|
|
void CBaseGameStats::Event_Commentary()
|
|
{
|
|
if( CBGSDriver.m_bInLevel )
|
|
{
|
|
BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
|
|
++map->m_nCommentary;
|
|
}
|
|
|
|
++m_BasicStats.m_Summary.m_nCommentary;
|
|
|
|
StatsLog( "CBaseGameStats::Event_Commentary [%d]\n", m_BasicStats.m_Summary.m_nCommentary );
|
|
}
|
|
|
|
void CBaseGameStats::Event_Credits()
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_Credits\n" );
|
|
|
|
float elapsed = 0.0f;
|
|
if( CBGSDriver.m_bInLevel )
|
|
{
|
|
elapsed = gpGlobals->realtime - CBGSDriver.m_flLevelStartTime;
|
|
}
|
|
|
|
if( elapsed < 0.0f )
|
|
{
|
|
Assert( 0 );
|
|
Warning( "EVENT_CREDITS with negative elapsed time (rt %f starttime %f)\n", gpGlobals->realtime, CBGSDriver.m_flLevelStartTime );
|
|
elapsed = 0.0f;
|
|
}
|
|
|
|
// Only set this one time!!!
|
|
if( gamestats->m_BasicStats.m_nSecondsToCompleteGame == 0 )
|
|
{
|
|
if( gamestats->UserPlayedAllTheMaps() )
|
|
{
|
|
gamestats->m_BasicStats.m_nSecondsToCompleteGame = elapsed + gamestats->m_BasicStats.m_Summary.m_nSeconds;
|
|
gamestats->SaveToFileNOW();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBaseGameStats::Event_CrateSmashed()
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_CrateSmashed\n" );
|
|
}
|
|
|
|
void CBaseGameStats::Event_Punted( CBaseEntity *pObject )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_Punted [%s]\n", pObject->GetClassname() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting )
|
|
{
|
|
}
|
|
|
|
void CBaseGameStats::Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_FlippedVehicle [%s] flipped [%s]\n", pDriver->GetPlayerName(), pVehicle->GetClassname() );
|
|
}
|
|
|
|
// Called before .sav file is actually loaded (player should still be in previous level, if any)
|
|
void CBaseGameStats::Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_PreSaveGameLoaded [%s] %s\n", pSaveName, bInGame ? "in-game" : "at console" );
|
|
}
|
|
|
|
bool CBaseGameStats::SaveToFileNOW( bool bForceSyncWrite /* = false */ )
|
|
{
|
|
if ( !StatsTrackingIsFullyEnabled() )
|
|
return false;
|
|
|
|
// this code path is only for old format stats. Products that use new format take a different path.
|
|
if ( !gamestats->UseOldFormat() )
|
|
return false;
|
|
|
|
CUtlBuffer buf;
|
|
buf.PutShort( GAMESTATS_FILE_VERSION );
|
|
buf.Put( s_szPseudoUniqueID, 16 );
|
|
|
|
if( ShouldTrackStandardStats() )
|
|
m_BasicStats.SaveToBuffer( buf );
|
|
else
|
|
buf.PutInt( GAMESTATS_STANDARD_NOT_SAVED );
|
|
|
|
gamestats->AppendCustomDataToSaveBuffer( buf );
|
|
|
|
char fullpath[ 512 ] = { 0 };
|
|
if ( filesystem->FileExists( GetStatSaveFileName(), GAMESTATS_PATHID ) )
|
|
{
|
|
filesystem->RelativePathToFullPath( GetStatSaveFileName(), GAMESTATS_PATHID, fullpath, sizeof( fullpath ) );
|
|
}
|
|
else
|
|
{
|
|
// filename is local to game dir for Steam, so we need to prepend game dir for regular file save
|
|
char gamePath[256];
|
|
engine->GetGameDir( gamePath, 256 );
|
|
Q_StripTrailingSlash( gamePath );
|
|
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", gamePath, GetStatSaveFileName() );
|
|
Q_strlower( fullpath );
|
|
Q_FixSlashes( fullpath );
|
|
}
|
|
|
|
// StatsLog( "SaveToFileNOW '%s'\n", fullpath );
|
|
|
|
if( CBGSDriver.m_bShuttingDown || bForceSyncWrite ) //write synchronously
|
|
{
|
|
filesystem->WriteFile( fullpath, GAMESTATS_PATHID, buf );
|
|
|
|
StatsLog( "Shut down wrote to '%s'\n", fullpath );
|
|
}
|
|
else
|
|
{
|
|
// Allocate memory for async system to use (and free afterward!!!)
|
|
size_t nBufferSize = buf.TellPut();
|
|
void *pMem = malloc(nBufferSize);
|
|
CUtlBuffer statsBuffer( pMem, nBufferSize );
|
|
statsBuffer.Put( buf.Base(), nBufferSize );
|
|
|
|
// Write data async
|
|
filesystem->AsyncWrite( fullpath, statsBuffer.Base(), statsBuffer.TellPut(), true, false );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CBaseGameStats::Event_PlayerConnected( CBasePlayer *pBasePlayer )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_PlayerConnected [%s]\n", pBasePlayer->GetPlayerName() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_PlayerDisconnected( CBasePlayer *pBasePlayer )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_PlayerDisconnected [%s]\n", pBasePlayer->GetPlayerName() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info )
|
|
{
|
|
//StatsLog( "CBaseGameStats::Event_PlayerDamage [%s] took %.2f damage\n", pBasePlayer->GetPlayerName(), info.GetDamage() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_PlayerKilledOther [%s] killed [%s]\n", pAttacker->GetPlayerName(), pVictim->GetClassname() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_WeaponFired [%s] %s weapon [%s]\n", pShooter->GetPlayerName(), bPrimary ? "primary" : "secondary", pchWeaponName );
|
|
}
|
|
|
|
void CBaseGameStats::Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_WeaponHit [%s] %s weapon [%s] damage [%f]\n", pShooter->GetPlayerName(), bPrimary ? "primary" : "secondary", pchWeaponName, info.GetDamage() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_PlayerEnteredGodMode [%s] entered GOD mode\n", pBasePlayer->GetPlayerName() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_PlayerEnteredNoClip [%s] entered NOCLIPe\n", pBasePlayer->GetPlayerName() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer )
|
|
{
|
|
StatsLog( "CBaseGameStats::Event_DecrementPlayerEnteredNoClip [%s] decrementing NOCLIPe\n", pBasePlayer->GetPlayerName() );
|
|
}
|
|
|
|
void CBaseGameStats::Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount )
|
|
{
|
|
StatsLog( "Incrementing %s by %f at pos (%d, %d, %d)\n", pchStatisticName, flIncrementAmount, (int)vecAbsOrigin.x, (int)vecAbsOrigin.y, (int)vecAbsOrigin.z );
|
|
}
|
|
|
|
// [dwenger] Needed for CS window-breaking stat
|
|
void CBaseGameStats::Event_WindowShattered( CBasePlayer *pPlayer )
|
|
{
|
|
StatsLog( "In Event_WindowShattered\n" );
|
|
}
|
|
|
|
bool CBaseGameStats::UploadStatsFileNOW( void )
|
|
{
|
|
if( !StatsTrackingIsFullyEnabled() || !HaveValidData() || !gamestats->UseOldFormat() )
|
|
return false;
|
|
|
|
if ( !filesystem->FileExists( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int curtime = Plat_FloatTime();
|
|
|
|
CBGSDriver.m_tLastUpload = curtime;
|
|
|
|
// Update the registry
|
|
#ifndef DEDICATED
|
|
IRegistry *reg = InstanceRegistry( "Steam" );
|
|
Assert( reg );
|
|
reg->WriteInt( GetStatUploadRegistryKeyName(), CBGSDriver.m_tLastUpload );
|
|
ReleaseInstancedRegistry( reg );
|
|
#endif
|
|
|
|
CUtlBuffer buf;
|
|
filesystem->ReadFile( GetStatSaveFileName(), GAMESTATS_PATHID, buf );
|
|
unsigned int uBlobSize = buf.TellPut();
|
|
if ( uBlobSize == 0 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const void *pvBlobData = ( const void * )buf.Base();
|
|
|
|
if( gamestatsuploader )
|
|
{
|
|
return gamestatsuploader->UploadGameStats( "",
|
|
1,
|
|
uBlobSize,
|
|
pvBlobData );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void CBaseGameStats::LoadingEvent_PlayerIDDifferentThanLoadedStats( void )
|
|
{
|
|
StatsLog( "CBaseGameStats::LoadingEvent_PlayerIDDifferentThanLoadedStats\n" );
|
|
}
|
|
|
|
|
|
bool CBaseGameStats::LoadFromFile( void )
|
|
{
|
|
if ( filesystem->FileExists( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID ) )
|
|
{
|
|
char fullpath[ 512 ];
|
|
filesystem->RelativePathToFullPath( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID, fullpath, sizeof( fullpath ) );
|
|
StatsLog( "Loading stats from '%s'\n", fullpath );
|
|
}
|
|
|
|
CUtlBuffer buf;
|
|
if ( filesystem->ReadFile( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID, buf ) )
|
|
{
|
|
bool bRetVal = true;
|
|
|
|
int version = buf.GetShort();
|
|
if ( version > GAMESTATS_FILE_VERSION )
|
|
return false; //file is beyond our comprehension
|
|
|
|
// Set global parse version
|
|
CBGSDriver.m_iLoadedVersion = version;
|
|
|
|
buf.Get( CBGSDriver.m_szLoadedUserID, 16 );
|
|
CBGSDriver.m_szLoadedUserID[ sizeof( CBGSDriver.m_szLoadedUserID ) - 1 ] = 0;
|
|
|
|
if ( s_szPseudoUniqueID[ 0 ] != 0 )
|
|
{
|
|
if ( Q_stricmp( CBGSDriver.m_szLoadedUserID, s_szPseudoUniqueID ) )
|
|
{
|
|
//UserID changed, blow away log!!!
|
|
filesystem->RemoveFile( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID );
|
|
filesystem->RemoveFile( GAMESTATS_LOG_FILE, GAMESTATS_PATHID );
|
|
Warning( "Userid changed, clearing stats file\n" );
|
|
CBGSDriver.m_szLoadedUserID[0] = '\0';
|
|
CBGSDriver.m_iLoadedVersion = -1;
|
|
gamestats->m_BasicStats.Clear();
|
|
gamestats->LoadingEvent_PlayerIDDifferentThanLoadedStats();
|
|
bRetVal = false;
|
|
}
|
|
|
|
if ( version <= GAMESTATS_FILE_VERSION_OLD5 )
|
|
{
|
|
gamestats->m_BasicStats.Clear();
|
|
bRetVal = false;
|
|
}
|
|
else
|
|
{
|
|
// Peek ahead in buffer to see if we have the "no default stats" secret flag set.
|
|
int iCheckForStandardStatsInFile = *( int * )buf.PeekGet();
|
|
bool bValid = true;
|
|
|
|
if ( iCheckForStandardStatsInFile != GAMESTATS_STANDARD_NOT_SAVED )
|
|
{
|
|
//the GAMESTATS_STANDARD_NOT_SAVED flag coincides with user completion time, rewind so the gamestats parser can grab it
|
|
bValid = gamestats->m_BasicStats.ParseFromBuffer( buf, version );
|
|
}
|
|
else
|
|
{
|
|
// skip over the flag
|
|
buf.GetInt();
|
|
}
|
|
|
|
if( !bValid )
|
|
{
|
|
m_BasicStats.Clear();
|
|
}
|
|
|
|
if( ( buf.TellPut() - buf.TellGet() ) != 0 ) //more data left, must be custom data
|
|
{
|
|
gamestats->LoadCustomDataFromBuffer( buf );
|
|
}
|
|
}
|
|
}
|
|
|
|
return bRetVal;
|
|
}
|
|
else
|
|
{
|
|
filesystem->RemoveFile( GAMESTATS_LOG_FILE, GAMESTATS_PATHID );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endif // GAME_DLL
|
|
|
|
bool CBaseGameStats_Driver::Init()
|
|
{
|
|
const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" );
|
|
|
|
//standardizing is a good thing
|
|
char szLoweredGameDir[256];
|
|
Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) );
|
|
Q_strlower( szLoweredGameDir );
|
|
|
|
gamestats = gamestats->OnInit( gamestats, szLoweredGameDir );
|
|
|
|
//determine constant strings needed for saving and uploading
|
|
Q_strncpy( s_szSaveFileName, szLoweredGameDir, sizeof( s_szSaveFileName ) );
|
|
Q_strncat( s_szSaveFileName, "_gamestats.dat", sizeof( s_szSaveFileName ) );
|
|
|
|
Q_strncpy( s_szStatUploadRegistryKeyName, "GameStatsUpload_", sizeof( s_szStatUploadRegistryKeyName ) );
|
|
Q_strncat( s_szStatUploadRegistryKeyName, szLoweredGameDir, sizeof( s_szStatUploadRegistryKeyName ) );
|
|
|
|
gamestats->m_bLoggingToFile = CommandLine()->FindParm( "-gamestatsloggingtofile" ) ? true : false;
|
|
gamestats->m_bLogging = CommandLine()->FindParm( "-gamestatslogging" ) ? true : false;
|
|
|
|
if ( gamestatsuploader )
|
|
{
|
|
m_bEnabled = gamestatsuploader->IsGameStatsLoggingEnabled();
|
|
if ( m_bEnabled )
|
|
{
|
|
gamestatsuploader->GetPseudoUniqueId( s_szPseudoUniqueID, sizeof( s_szPseudoUniqueID ) );
|
|
}
|
|
}
|
|
|
|
ResetData();
|
|
|
|
#ifdef GAME_DLL
|
|
if ( StatsTrackingIsFullyEnabled() )
|
|
{
|
|
// FIXME: Load m_tLastUpload from registry and save it back out, too
|
|
#ifndef DEDICATED
|
|
IRegistry *reg = InstanceRegistry( "Steam" );
|
|
Assert( reg );
|
|
m_tLastUpload = reg->ReadInt( gamestats->GetStatUploadRegistryKeyName(), 0 );
|
|
ReleaseInstancedRegistry( reg );
|
|
#endif
|
|
//load existing stats
|
|
gamestats->LoadFromFile();
|
|
}
|
|
#endif // GAME_DLL
|
|
|
|
if ( s_szPseudoUniqueID[ 0 ] != 0 )
|
|
{
|
|
gamestats->Event_Init();
|
|
#ifdef GAME_DLL
|
|
if ( gamestats->UseOldFormat() )
|
|
{
|
|
if( gamestats->AutoSave_OnInit() )
|
|
gamestats->SaveToFileNOW();
|
|
|
|
if( gamestats->AutoUpload_OnInit() )
|
|
gamestats->UploadStatsFileNOW();
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
m_bEnabled = false; //unable to generate a pseudo-unique ID, disable tracking
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CBaseGameStats_Driver::Shutdown()
|
|
{
|
|
m_bShuttingDown = true;
|
|
|
|
gamestats->Event_Shutdown();
|
|
|
|
if ( gamestats->UseOldFormat() )
|
|
{
|
|
#ifdef GAME_DLL
|
|
if( gamestats->AutoSave_OnShutdown() )
|
|
gamestats->SaveToFileNOW();
|
|
|
|
if( gamestats->AutoUpload_OnShutdown() )
|
|
gamestats->UploadStatsFileNOW();
|
|
#endif // GAME_DLL
|
|
}
|
|
else
|
|
{
|
|
// code path for new format game stats
|
|
if ( gamestats->ShouldSendDataOnAppShutdown() )
|
|
{
|
|
CollectData( STATSEND_APPSHUTDOWN );
|
|
SendData();
|
|
}
|
|
}
|
|
if ( FILESYSTEM_INVALID_HANDLE != g_LogFileHandle )
|
|
{
|
|
filesystem->Close( g_LogFileHandle );
|
|
g_LogFileHandle = FILESYSTEM_INVALID_HANDLE;
|
|
}
|
|
|
|
if ( m_pGamestatsData != NULL )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
engine->SetGamestatsData( NULL );
|
|
#endif
|
|
delete m_pGamestatsData;
|
|
m_pGamestatsData = NULL;
|
|
}
|
|
}
|
|
|
|
void CBaseGameStats_Driver::PossibleMapChange( void )
|
|
{
|
|
#ifdef GAME_DLL
|
|
//detect and copy map changes
|
|
if ( Q_stricmp( m_PrevMapName.String(), STRING( gpGlobals->mapname ) ) )
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
CUtlString PrevMapBackup = m_PrevMapName;
|
|
|
|
m_PrevMapName = STRING( gpGlobals->mapname );
|
|
|
|
gamestats->Event_MapChange( PrevMapBackup.String(), STRING( gpGlobals->mapname ) );
|
|
|
|
if ( gamestats->UseOldFormat() )
|
|
{
|
|
if( gamestats->AutoSave_OnMapChange() )
|
|
gamestats->SaveToFileNOW();
|
|
|
|
if( gamestats->AutoUpload_OnMapChange() )
|
|
gamestats->UploadStatsFileNOW();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
void CBaseGameStats_Driver::LevelInitPreEntity()
|
|
{
|
|
m_bInLevel = true;
|
|
m_bFirstLevel = false;
|
|
|
|
if ( Q_stricmp( s_szPseudoUniqueID, "unknown" ) == 0 )
|
|
{
|
|
// "unknown" means this is a dedicated server and we weren't able to generate a unique ID (e.g. Linux server).
|
|
// Change the unique ID to be a hash of IP & port. We couldn't do this earlier because IP is not known until level
|
|
// init time.
|
|
ConVar *hostip = cvar->FindVar( "hostip" );
|
|
ConVar *hostport = cvar->FindVar( "hostport" );
|
|
if ( hostip && hostport )
|
|
{
|
|
int crcInput[2];
|
|
crcInput[0] = hostip->GetInt();
|
|
crcInput[1] = hostport->GetInt();
|
|
if ( crcInput[0] && crcInput[1] )
|
|
{
|
|
CRC32_t crc = CRC32_ProcessSingleBuffer( crcInput, sizeof( crcInput ) );
|
|
Q_snprintf( s_szPseudoUniqueID, ARRAYSIZE( s_szPseudoUniqueID ), "H:%x", ( int ) crc );
|
|
}
|
|
}
|
|
}
|
|
|
|
PossibleMapChange();
|
|
|
|
m_flPauseStartTime = 0.0f;
|
|
m_flLevelStartTime = gpGlobals->realtime;
|
|
|
|
gamestats->Event_LevelInit();
|
|
|
|
#ifdef GAME_DLL
|
|
if ( gamestats->UseOldFormat() )
|
|
{
|
|
if( gamestats->AutoSave_OnLevelInit() )
|
|
gamestats->SaveToFileNOW();
|
|
|
|
if( gamestats->AutoUpload_OnLevelInit() )
|
|
gamestats->UploadStatsFileNOW();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void CBaseGameStats_Driver::LevelShutdownPreEntity()
|
|
{
|
|
float flElapsed = gpGlobals->realtime - m_flLevelStartTime;
|
|
|
|
if ( flElapsed < 0.0f )
|
|
{
|
|
Assert( 0 );
|
|
Warning( "EVENT_LEVELSHUTDOWN: with negative elapsed time (rt %f starttime %f)\n", gpGlobals->realtime, m_flLevelStartTime );
|
|
flElapsed = 0.0f;
|
|
}
|
|
|
|
//Assert( m_bInLevel ); //so, apparently shutdowns can happen before inits
|
|
|
|
#ifdef GAME_DLL
|
|
if ( m_bInLevel && ( gpGlobals->eLoadType != MapLoad_Background ) )
|
|
#else
|
|
if ( m_bInLevel )
|
|
#endif
|
|
{
|
|
m_flTotalTimeInLevels += flElapsed;
|
|
m_iNumLevels ++;
|
|
|
|
gamestats->Event_LevelShutdown( flElapsed );
|
|
|
|
if ( gamestats->UseOldFormat() )
|
|
{
|
|
#ifdef GAME_DLL
|
|
if( gamestats->AutoSave_OnLevelShutdown() )
|
|
gamestats->SaveToFileNOW( true );
|
|
|
|
if( gamestats->AutoUpload_OnLevelShutdown() )
|
|
gamestats->UploadStatsFileNOW();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// code path for new format game stats
|
|
CollectData( STATSEND_LEVELSHUTDOWN );
|
|
if ( gamestats->ShouldSendDataOnLevelShutdown() )
|
|
{
|
|
SendData();
|
|
}
|
|
}
|
|
m_bInLevel = false;
|
|
}
|
|
}
|
|
|
|
void CBaseGameStats_Driver::OnSave()
|
|
{
|
|
gamestats->Event_SaveGame();
|
|
}
|
|
|
|
|
|
void CBaseGameStats_Driver::CollectData( StatSendType_t sendType )
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
// The base stat system holds memory until "APPSHUTDOWN" which on the Xbox isn't good.
|
|
// When on the Xbox, neither server nor clients collect stats, but the base system still does.
|
|
#ifdef _X360
|
|
return;
|
|
#endif
|
|
|
|
CGamestatsData *pGamestatsData = NULL;
|
|
#ifdef GAME_DLL
|
|
// for server, check with the engine to see if there already a gamestats data container registered. (There will be if there is a client
|
|
// running in the same process.)
|
|
pGamestatsData = engine->GetGamestatsData();
|
|
if ( pGamestatsData )
|
|
{
|
|
// use the registered gamestats container, so free the one we allocated
|
|
if ( m_pGamestatsData != NULL )
|
|
{
|
|
delete m_pGamestatsData;
|
|
m_pGamestatsData = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pGamestatsData = m_pGamestatsData;
|
|
}
|
|
#else
|
|
pGamestatsData = m_pGamestatsData;
|
|
#endif
|
|
Assert( pGamestatsData || //valid stats
|
|
(!gpGlobals->IsClient() && (sendType == STATSEND_APPSHUTDOWN) && gamestats->ShouldSendDataOnLevelShutdown()) ); //or already sent off stats during level shutdown (now in app shutdown)
|
|
|
|
// If a server is told to send stats on STATSEND_APPSHUTDOWN, we need to check to make sure we still have a valid object.
|
|
if ( !pGamestatsData )
|
|
return;
|
|
|
|
KeyValues *pKV = pGamestatsData->m_pKVData;
|
|
|
|
int iAppID = engine->GetAppID();
|
|
pKV->SetInt( "appid", iAppID );
|
|
|
|
switch ( sendType )
|
|
{
|
|
case STATSEND_LEVELSHUTDOWN:
|
|
{
|
|
// make a map node in the KeyValues to use for this level
|
|
char szMap[MAX_PATH+1]="";
|
|
#ifdef CLIENT_DLL
|
|
Q_FileBase( engine->GetLevelName(), szMap, ARRAYSIZE( szMap ) );
|
|
#else
|
|
Q_strncpy( szMap, gpGlobals->mapname.ToCStr(), ARRAYSIZE( szMap ) );
|
|
#endif // CLIENT_DLL
|
|
if ( !szMap[0] )
|
|
{
|
|
Q_FileBase( MapName(), szMap, ARRAYSIZE( szMap ) );
|
|
}
|
|
|
|
KeyValues *pKVMap = new KeyValues( "map" );
|
|
pKV->AddSubKey( pKVMap );
|
|
pKVMap->SetString( "mapname", szMap );
|
|
pKV = pKVMap;
|
|
|
|
|
|
}
|
|
break;
|
|
case STATSEND_APPSHUTDOWN:
|
|
break;
|
|
default:
|
|
Assert( false );
|
|
break;
|
|
}
|
|
|
|
// add common data
|
|
pGamestatsData->m_bHaveData |= AddBaseDataForSend( pKV, sendType );
|
|
|
|
#if defined(CLIENT_DLL) && !defined(NO_STEAM)
|
|
#if !defined( _GAMECONSOLE )
|
|
// At the end of every map, clients submit their perfdata for the map
|
|
if ( sendType == STATSEND_LEVELSHUTDOWN && pGamestatsData && pGamestatsData->m_bHaveData )
|
|
{
|
|
// dgoodenough - Remove this for now, since this fails on PS3
|
|
// PS3_BUILDFIX
|
|
GetSteamWorksGameStatsClient().AddClientPerfData( pGamestatsData->m_pKVData );
|
|
}
|
|
// Time to reset ClientPerfData so we're ready for future recording. We clear the data
|
|
// whether we submitted or not so that perf data has a clear meaning.
|
|
ResetData();
|
|
// The ResetData call realloced m_pGamestatsData. Need to point pGamestatsData to the new m_pGamestatsData !
|
|
pGamestatsData = m_pGamestatsData;
|
|
#endif
|
|
if ( sendType == STATSEND_LEVELSHUTDOWN )
|
|
{
|
|
#if !defined( _GAMECONSOLE )
|
|
KeyValues *pKVFileStats = new KeyValues( "FileSystemStats" );
|
|
filesystem->GetVPKFileStatisticsKV( pKVFileStats );
|
|
GetSteamWorksGameStatsClient().AddVPKLoadStats( pKVFileStats );
|
|
GetSteamWorksGameStatsClient().AddVPKFileLoadErrorData( pKVFileStats );
|
|
pKVFileStats->deleteThis();
|
|
|
|
// On Server Disconnect, session is ended later than when a match restarts. This allows ClientPerfData and VPK stats to report properly
|
|
// Alternately you could place ClientPerfData and VPK in the cs_game_disconnected event, but this event may fire more frequently than we want for CPD/VPK.
|
|
GetSteamWorksGameStatsClient().EndSession();
|
|
GetSteamWorksGameStatsClient().ResetServerState();
|
|
#endif
|
|
}
|
|
#endif
|
|
// add game-specific data
|
|
pGamestatsData->m_bHaveData |= gamestats->AddDataForSend( pKV, sendType );
|
|
}
|
|
#ifndef CLIENT_DLL
|
|
ConVar gamestats_file_output_directory( "gamestats_file_output_directory", "", FCVAR_NONE, "When -gamestatsfileoutputonly is specified, file will be emitted here instead of to modpath\n" );
|
|
#endif
|
|
void CBaseGameStats_Driver::SendData()
|
|
{
|
|
// if we don't own the data container or there's no valid data, nothing to do
|
|
if ( !m_pGamestatsData || !m_pGamestatsData->m_bHaveData )
|
|
return;
|
|
|
|
// save the data to a buffer
|
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
|
|
m_pGamestatsData->m_pKVData->RecursiveSaveToFile( buf, 0 );
|
|
|
|
#ifdef CLIENT_DLL
|
|
const bool bOutDirOverriden = false;
|
|
const char *szOverrideDirectory = NULL;
|
|
#else
|
|
const bool bOutDirOverriden = gamestats_file_output_directory.GetRawValue().m_StringLength > 1;
|
|
const char *szOverrideDirectory = gamestats_file_output_directory.GetString();
|
|
#endif
|
|
if ( CommandLine()->FindParm( "-gamestatsfileoutputonly" ) || bOutDirOverriden )
|
|
{
|
|
// write file for debugging
|
|
if ( !bOutDirOverriden ) // if there isn't an override path, put it in the game dir.
|
|
{
|
|
const char szFileName[] = "gamestats.dat";
|
|
filesystem->WriteFile( szFileName, GAMESTATS_PATHID, buf );
|
|
}
|
|
else
|
|
{
|
|
char szPathName[MAX_PATH] = {0};
|
|
V_strncat( szPathName, szOverrideDirectory, MAX_PATH );
|
|
V_AppendSlash( szPathName, MAX_PATH );
|
|
int pathLen = V_strlen( szPathName );
|
|
|
|
char szFileName[MAX_PATH] = {0};
|
|
struct tm timeinfo;
|
|
Plat_GetLocalTime( &timeinfo );
|
|
int fileLen = V_snprintf( szFileName, MAX_PATH, "%s__%2d_%3d_%2d_%2d_%2d.txt", s_szPseudoUniqueID,
|
|
timeinfo.tm_year % 100, timeinfo.tm_yday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec );
|
|
|
|
Assert( pathLen + fileLen < MAX_PATH ); fileLen;
|
|
|
|
V_strncat( szPathName + pathLen, szFileName, MAX_PATH - pathLen );
|
|
|
|
filesystem->WriteFile( szPathName, NULL, buf );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// upload the file to Steam
|
|
if ( gamestatsuploader )
|
|
gamestatsuploader->UploadGameStats( "", 1, buf.TellPut(), buf.Base() );
|
|
}
|
|
|
|
ResetData();
|
|
}
|
|
|
|
bool CBaseGameStats_Driver::AddBaseDataForSend( KeyValues *pKV, StatSendType_t sendType )
|
|
{
|
|
switch ( sendType )
|
|
{
|
|
case STATSEND_APPSHUTDOWN:
|
|
#ifdef CLIENT_DLL
|
|
if ( m_iNumLevels > 0 )
|
|
{
|
|
// add playtime data
|
|
KeyValues *pKVData = new KeyValues( "playtime" );
|
|
pKVData->SetInt( "TotalLevelTime", m_flTotalTimeInLevels );
|
|
pKVData->SetInt( "NumLevels", m_iNumLevels );
|
|
pKV->AddSubKey( pKVData );
|
|
return true;
|
|
}
|
|
#endif
|
|
break;
|
|
case STATSEND_LEVELSHUTDOWN:
|
|
#ifdef CLIENT_DLL
|
|
const int MIN_PERF_STAT_COLLECT_TIME = 60 * 5; // send perf data if we have collected at least this many seconds
|
|
bool bSend = m_bBufferFull || m_nWriteIndex >= MIN_PERF_STAT_COLLECT_TIME;
|
|
if ( bSend )
|
|
{
|
|
// add perf data
|
|
KeyValues *pKVPerf = new KeyValues( "perfdata" );
|
|
|
|
SortFramerate();
|
|
|
|
float flAverageFrameRate = AverageStat( &StatsBufferRecord_t::m_flFrameRate );
|
|
float flMinFrameRate = PercentileFramerate( 20 );
|
|
float flMaxFrameRate = PercentileFramerate( 80 );
|
|
float flStandardDeviationFrameRate = StdDevStat( &StatsBufferRecord_t::m_flFrameRate, flAverageFrameRate );
|
|
|
|
pKVPerf->SetFloat( "AvgFPS", flAverageFrameRate );
|
|
pKVPerf->SetFloat( "MinFPS", flMinFrameRate );
|
|
pKVPerf->SetFloat( "MaxFPS", flMaxFrameRate );
|
|
pKVPerf->SetFloat( "StdDevFPS", flStandardDeviationFrameRate );
|
|
// if encoded histogram is 0, it just means we have no samples, so we don't really need to report it (save on bandwidth)
|
|
if ( uint64 nHist = m_FpsHistogram.Encode() )
|
|
{
|
|
pKVPerf->SetUint64( "FrameHistAll", nHist );
|
|
}
|
|
if ( uint64 nHistSmoke = m_FpsHistogramGame[ PERF_STATS_SMOKE ].Encode() )
|
|
{
|
|
pKVPerf->SetUint64( "FrameHistGame1", nHistSmoke );
|
|
}
|
|
if ( uint64 nHistBullet = m_FpsHistogramGame[ PERF_STATS_BULLET ].Encode() )
|
|
{
|
|
pKVPerf->SetUint64( "FrameHistGame2", nHistBullet );
|
|
}
|
|
if ( uint64 nHistPlayer = m_FpsHistogramGame[ PERF_STATS_PLAYER ].Encode() )
|
|
{
|
|
pKVPerf->SetUint64( "FrameHistGame3", nHistPlayer );
|
|
}
|
|
if ( uint64 nHistPlayerSpawn = m_FpsHistogramGame[ PERF_STATS_PLAYER_SPAWN ].Encode() )
|
|
{
|
|
pKVPerf->SetUint64( "FrameHistGame4", nHistPlayerSpawn );
|
|
}
|
|
m_FpsHistogram.Reset();
|
|
m_FpsHistogramGame[ PERF_STATS_SMOKE ].Reset();
|
|
m_FpsHistogramGame[ PERF_STATS_BULLET ].Reset();
|
|
m_FpsHistogramGame[ PERF_STATS_PLAYER ].Reset();
|
|
m_FpsHistogramGame[ PERF_STATS_PLAYER_SPAWN ].Reset();
|
|
|
|
// Determine the min/avg/max Server Ping and store the results
|
|
float flAverageServerPing = AverageStat( &StatsBufferRecord_t::m_flServerPing );
|
|
pKVPerf->SetFloat( "AvgServerPing", flAverageServerPing );
|
|
|
|
// Avg/StdDev main thread time
|
|
float flAvgMainThreadTime = AverageStat( &StatsBufferRecord_t::m_flMainThreadTimeMS );
|
|
float flStdDevMainThreadTime = StdDevStat( &StatsBufferRecord_t::m_flMainThreadTimeMS, flAvgMainThreadTime );
|
|
pKVPerf->SetFloat( "AvgMainThreadTime", flAvgMainThreadTime );
|
|
pKVPerf->SetFloat( "StdDevMainThreadTime", flStdDevMainThreadTime );
|
|
|
|
// Avg/StdDev main thread wait time (time waiting for the render thread)
|
|
float flAvgMainThreadWaitTime = AverageStat( &StatsBufferRecord_t::m_flMainThreadWaitTimeMS );
|
|
float flStdDevMainThreadWaitTime = StdDevStat( &StatsBufferRecord_t::m_flMainThreadWaitTimeMS, flAvgMainThreadWaitTime );
|
|
pKVPerf->SetFloat( "AvgMainThreadWaitTime", flAvgMainThreadWaitTime );
|
|
pKVPerf->SetFloat( "StdDevMainThreadWaitTime", flStdDevMainThreadWaitTime );
|
|
|
|
// Avg/StdDev render thread time
|
|
float flAvgRenderThreadTime = AverageStat( &StatsBufferRecord_t::m_flRenderThreadTimeMS );
|
|
float flStdDevRenderThreadTime = StdDevStat( &StatsBufferRecord_t::m_flRenderThreadTimeMS, flAvgRenderThreadTime );
|
|
pKVPerf->SetFloat( "AvgRenderThreadTime", flAvgRenderThreadTime );
|
|
pKVPerf->SetFloat( "StdDevRenderThreadTime", flStdDevRenderThreadTime );
|
|
|
|
// Avg/StdDev render thread wait time (time waiting for the gpu, ForceHardwareSync)
|
|
float flAvgRenderThreadWaitTime = AverageStat( &StatsBufferRecord_t::m_flRenderThreadWaitTimeMS );
|
|
float flStdDevRenderThreadWaitTime = StdDevStat( &StatsBufferRecord_t::m_flRenderThreadWaitTimeMS, flAvgRenderThreadWaitTime );
|
|
pKVPerf->SetFloat( "AvgRenderThreadWaitTime", flAvgRenderThreadWaitTime );
|
|
pKVPerf->SetFloat( "StdDevRenderThreadWaitTime", flStdDevRenderThreadWaitTime );
|
|
|
|
int nCPUThrottlingSamples = m_nCPUUnthrottledCount + m_nCPUThrottle1Count + m_nCPUThrottle2Count;
|
|
if ( nCPUThrottlingSamples )
|
|
{
|
|
float fThrottledToLevel1 = m_nCPUThrottle1Count * 100.0f / nCPUThrottlingSamples;
|
|
float fThrottledToLevel2 = m_nCPUThrottle2Count * 100.0f / nCPUThrottlingSamples;
|
|
pKVPerf->SetFloat( "PercentageCPUThrottledToLevel1", fThrottledToLevel1 );
|
|
pKVPerf->SetFloat( "PercentageCPUThrottledToLevel2", fThrottledToLevel2 );
|
|
}
|
|
|
|
pKV->AddSubKey( pKVPerf );
|
|
}
|
|
|
|
// Only keeping track of using voice in a multiplayer game
|
|
if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
|
|
{
|
|
pKV->SetInt( "UsedVoice", m_bDidVoiceChat );
|
|
}
|
|
|
|
extern ConVar closecaption;
|
|
pKV->SetInt( "Caption", closecaption.GetInt() );
|
|
|
|
// Set language to "unknown", then check to see if we can obtain the language.
|
|
char language[64] = "unknown";
|
|
if ( engine )
|
|
{
|
|
engine->GetUILanguage( language, sizeof( language ) );
|
|
pKV->SetInt( "BugSub", engine->GetBugSubmissionCount() );
|
|
engine->ClearBugSubmissionCount();
|
|
}
|
|
pKV->SetString( "Language", language );
|
|
|
|
if ( materials )
|
|
{
|
|
const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard();
|
|
pKV->SetBool( "Windowed", config.Windowed() );
|
|
pKV->SetBool( "WindowedNoBorder", config.NoWindowBorder() );
|
|
}
|
|
|
|
// We want to know if a user is running with cheats on so that we can filter out their data
|
|
// depending on what we're looking for.
|
|
if ( NULL != sv_cheats )
|
|
{
|
|
pKV->SetInt( "Cheats", sv_cheats->GetInt() );
|
|
}
|
|
|
|
|
|
int mapTime = gpGlobals->realtime - m_flLevelStartTime;
|
|
pKV->SetInt( "MapTime", mapTime );
|
|
|
|
// reset buffer for next level. This is also done by ResetData().
|
|
m_bBufferFull = false;
|
|
m_nWriteIndex = 0;
|
|
// Reset last realtime timestamp so that we don't compute the fps
|
|
// the first time we re-enter UpdatePerfStats
|
|
m_flLastRealTime = -1.0f;
|
|
m_flLastSampleTime = -1.0f;
|
|
|
|
return bSend;
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void CBaseGameStats_Driver::ResetData()
|
|
{
|
|
#ifdef GAME_DLL
|
|
// on the server, if there is a gamestats data container registered (by a client in the same process), they're in charge of resetting it, nothing for us to do
|
|
if ( engine->GetGamestatsData() != NULL )
|
|
return;
|
|
#endif
|
|
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
if ( m_pGamestatsData != NULL )
|
|
{
|
|
delete m_pGamestatsData;
|
|
m_pGamestatsData = NULL;
|
|
}
|
|
|
|
m_bBufferFull = false;
|
|
m_nWriteIndex = 0;
|
|
m_bDidVoiceChat = false;
|
|
|
|
// Reset the CPU throttling statistics.
|
|
m_nCPUUnthrottledCount = 0;
|
|
m_nCPUThrottle1Count = 0;
|
|
m_nCPUThrottle2Count = 0;
|
|
// Track when we last received data.
|
|
m_lastCPUFrequencyTimestamp = 0.0;
|
|
|
|
m_pGamestatsData = new CGamestatsData();
|
|
KeyValues *pKV = m_pGamestatsData->m_pKVData;
|
|
pKV->SetInt( "IsPc", IsPC() );
|
|
pKV->SetInt( "version", GAMESTATS_VERSION );
|
|
pKV->SetString( "srcid", s_szPseudoUniqueID );
|
|
|
|
#ifdef CLIENT_DLL
|
|
const CPUInformation &cpu = GetCPUInformation();
|
|
OverWriteCharsWeHate( cpu.m_szProcessorID );
|
|
pKV->SetString( "CPUID", cpu.m_szProcessorID );
|
|
pKV->SetFloat( "CPUGhz", cpu.m_Speed * ( 1.0 / 1.0e9 ) );
|
|
pKV->SetUint64( "CPUModel", cpu.m_nModel );
|
|
pKV->SetUint64( "CPUFeatures0", cpu.m_nFeatures[ 0 ] );
|
|
pKV->SetUint64( "CPUFeatures1", cpu.m_nFeatures[ 1 ] );
|
|
pKV->SetUint64( "CPUFeatures2", cpu.m_nFeatures[ 2 ] );
|
|
pKV->SetInt( "NumCores", cpu.m_nPhysicalProcessors );
|
|
|
|
MaterialAdapterInfo_t gpu;
|
|
materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), gpu );
|
|
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
int dest_width,dest_height;
|
|
pRenderContext->GetRenderTargetDimensions( dest_width, dest_height );
|
|
|
|
if ( gpu.m_pDriverName )
|
|
{
|
|
OverWriteCharsWeHate( gpu.m_pDriverName );
|
|
}
|
|
pKV->SetString( "GPUDrv", SafeString( gpu.m_pDriverName ) );
|
|
pKV->SetInt( "GPUVendor", gpu.m_VendorID );
|
|
pKV->SetInt( "GPUDeviceID", gpu.m_DeviceID );
|
|
pKV->SetString( "GPUDriverVersion", CFmtStr( "%d.%d", gpu.m_nDriverVersionHigh, gpu.m_nDriverVersionLow ) );
|
|
pKV->SetInt( "DxLvl", g_pMaterialSystemHardwareConfig->GetDXSupportLevel() );
|
|
pKV->SetInt( "Width", dest_width );
|
|
pKV->SetInt( "Height", dest_height );
|
|
pKV->SetInt( "IsSplitScreen", VGui_IsSplitScreen() );
|
|
|
|
engine->SetGamestatsData( m_pGamestatsData );
|
|
|
|
#endif
|
|
}
|
|
|
|
void CBaseGameStats_Driver::OnRestore()
|
|
{
|
|
PossibleMapChange();
|
|
|
|
gamestats->Event_LoadGame();
|
|
}
|
|
|
|
|
|
void CBaseGameStats_Driver::FrameUpdatePostEntityThink()
|
|
{
|
|
bool bGamePaused = ( gpGlobals->frametime == 0.0f );
|
|
|
|
if ( !m_bInLevel )
|
|
{
|
|
m_flPauseStartTime = 0.0f;
|
|
}
|
|
else if ( m_bGamePaused != bGamePaused )
|
|
{
|
|
if ( bGamePaused )
|
|
{
|
|
m_flPauseStartTime = gpGlobals->realtime;
|
|
}
|
|
else if ( m_flPauseStartTime != 0.0f )
|
|
{
|
|
float flPausedTime = gpGlobals->realtime - m_flPauseStartTime;
|
|
if ( flPausedTime < 0.0f )
|
|
{
|
|
Assert( 0 );
|
|
Warning( "Game paused time showing up negative (rt %f pausestart %f)\n", gpGlobals->realtime, m_flPauseStartTime );
|
|
flPausedTime = 0.0f;
|
|
}
|
|
|
|
// Remove this from global time counters
|
|
|
|
// Msg( "Pause: adding %f to level starttime\n", flPausedTime );
|
|
|
|
m_flLevelStartTime += flPausedTime;
|
|
m_flPauseStartTime = 0.0f;
|
|
|
|
// Msg( "Paused for %.2f seconds\n", flPausedTime );
|
|
}
|
|
m_bGamePaused = bGamePaused;
|
|
}
|
|
}
|
|
|
|
bool StatsTrackingIsFullyEnabled( void )
|
|
{
|
|
return CBGSDriver.m_bEnabled && gamestats->StatTrackingEnabledForMod();
|
|
}
|
|
|
|
void CBaseGameStats::Clear( void )
|
|
{
|
|
#ifdef GAME_DLL
|
|
gamestats->m_BasicStats.Clear();
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Nukes any dangerous characters and replaces w/space char
|
|
//-----------------------------------------------------------------------------
|
|
void OverWriteCharsWeHate( char *pStr )
|
|
{
|
|
while( *pStr )
|
|
{
|
|
switch( *pStr )
|
|
{
|
|
case '\n':
|
|
case '\r':
|
|
case '\\':
|
|
case '\"':
|
|
case '\'':
|
|
case '\032':
|
|
case ';':
|
|
*pStr = ' ';
|
|
}
|
|
pStr++;
|
|
}
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
|
|
void CBaseGameStats::SetSteamStatistic( bool bUsingSteam )
|
|
{
|
|
if( CBGSDriver.m_bFirstLevel )
|
|
{
|
|
m_BasicStats.m_Summary.m_bSteam = bUsingSteam;
|
|
}
|
|
|
|
if( CBGSDriver.m_bInLevel )
|
|
{
|
|
BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
|
|
map->m_bSteam = bUsingSteam;
|
|
}
|
|
|
|
m_BasicStats.m_bSteam = bUsingSteam;
|
|
}
|
|
|
|
void CBaseGameStats::SetCyberCafeStatistic( bool bIsCyberCafeUser )
|
|
{
|
|
if( CBGSDriver.m_bFirstLevel )
|
|
{
|
|
m_BasicStats.m_Summary.m_bCyberCafe = bIsCyberCafeUser;
|
|
}
|
|
|
|
if( CBGSDriver.m_bInLevel )
|
|
{
|
|
BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
|
|
map->m_bCyberCafe = bIsCyberCafeUser;
|
|
}
|
|
|
|
m_BasicStats.m_bCyberCafe = bIsCyberCafeUser;
|
|
}
|
|
|
|
void CBaseGameStats::SetHDRStatistic( bool bHDREnabled )
|
|
{
|
|
if( bHDREnabled )
|
|
{
|
|
if( CBGSDriver.m_bInLevel )
|
|
{
|
|
BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
|
|
++map->m_nHDR;
|
|
}
|
|
|
|
if( CBGSDriver.m_bFirstLevel )
|
|
{
|
|
++m_BasicStats.m_Summary.m_nHDR;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBaseGameStats::SetCaptionsStatistic( bool bClosedCaptionsEnabled )
|
|
{
|
|
if( CBGSDriver.m_bInLevel )
|
|
{
|
|
BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
|
|
++map->m_nCaptions;
|
|
}
|
|
|
|
if( CBGSDriver.m_bFirstLevel )
|
|
{
|
|
++m_BasicStats.m_Summary.m_nCaptions;
|
|
}
|
|
}
|
|
|
|
void CBaseGameStats::SetSkillStatistic( int iSkillSetting )
|
|
{
|
|
int skill = clamp( iSkillSetting, 1, 3 ) - 1;
|
|
|
|
if( CBGSDriver.m_bInLevel )
|
|
{
|
|
BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
|
|
++map->m_nSkill[ skill ];
|
|
}
|
|
|
|
if ( CBGSDriver. m_bFirstLevel )
|
|
{
|
|
++m_BasicStats.m_Summary.m_nSkill[ skill ];
|
|
}
|
|
}
|
|
|
|
void CBaseGameStats::SetDXLevelStatistic( int iDXLevel )
|
|
{
|
|
m_BasicStats.m_nDXLevel = iDXLevel;
|
|
}
|
|
|
|
static void CC_ResetGameStats( const CCommand &args )
|
|
{
|
|
#if defined ( TF_DLL ) || defined ( TF_CLIENT_DLL )
|
|
// Disabled this until we fix the TF gamestat crashes that result
|
|
return;
|
|
#endif
|
|
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
gamestats->Clear();
|
|
gamestats->SaveToFileNOW();
|
|
gamestats->StatsLog( "CC_ResetGameStats : Server cleared game stats\n" );
|
|
}
|
|
|
|
static ConCommand resetGameStats("_resetgamestats", CC_ResetGameStats, "Erases current game stats and writes out a blank stats file", 0 );
|
|
|
|
class CPointGamestatsCounter : public CPointEntity
|
|
{
|
|
public:
|
|
DECLARE_CLASS( CPointGamestatsCounter, CPointEntity );
|
|
DECLARE_DATADESC();
|
|
|
|
CPointGamestatsCounter();
|
|
|
|
protected:
|
|
|
|
void InputSetName( inputdata_t &inputdata );
|
|
void InputIncrement( inputdata_t &inputdata );
|
|
|
|
void InputEnable( inputdata_t &inputdata );
|
|
void InputDisable( inputdata_t &inputdata );
|
|
private:
|
|
|
|
string_t m_strStatisticName;
|
|
bool m_bDisabled;
|
|
};
|
|
|
|
BEGIN_DATADESC( CPointGamestatsCounter )
|
|
|
|
DEFINE_KEYFIELD( m_strStatisticName, FIELD_STRING, "Name" ),
|
|
DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ),
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetName", InputSetName ),
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "Increment", InputIncrement ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
|
|
|
|
END_DATADESC()
|
|
|
|
LINK_ENTITY_TO_CLASS( point_gamestats_counter, CPointGamestatsCounter )
|
|
|
|
|
|
CPointGamestatsCounter::CPointGamestatsCounter() :
|
|
m_strStatisticName( NULL_STRING ),
|
|
m_bDisabled( false )
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Changes name of statistic
|
|
//-----------------------------------------------------------------------------
|
|
void CPointGamestatsCounter::InputSetName( inputdata_t &inputdata )
|
|
{
|
|
m_strStatisticName = inputdata.value.StringID();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Changes name of statistic
|
|
//-----------------------------------------------------------------------------
|
|
void CPointGamestatsCounter::InputIncrement( inputdata_t &inputdata )
|
|
{
|
|
if ( m_bDisabled )
|
|
return;
|
|
|
|
if ( NULL_STRING == m_strStatisticName )
|
|
{
|
|
DevMsg( 1, "CPointGamestatsCounter::InputIncrement: No stat name specified for point_gamestats_counter @%f, %f, %f [ent index %d]\n",
|
|
GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, entindex() );
|
|
return;
|
|
}
|
|
|
|
gamestats->Event_IncrementCountedStatistic( GetAbsOrigin(), STRING( m_strStatisticName ), inputdata.value.Float() );
|
|
}
|
|
|
|
void CPointGamestatsCounter::InputEnable( inputdata_t &inputdata )
|
|
{
|
|
m_bDisabled = false;
|
|
}
|
|
|
|
void CPointGamestatsCounter::InputDisable( inputdata_t &inputdata )
|
|
{
|
|
m_bDisabled = true;
|
|
}
|
|
|
|
#endif // GAME_DLL
|
|
|