aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/shared/GameStats.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/shared/GameStats.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/shared/GameStats.cpp')
-rw-r--r--mp/src/game/shared/GameStats.cpp1526
1 files changed, 1526 insertions, 0 deletions
diff --git a/mp/src/game/shared/GameStats.cpp b/mp/src/game/shared/GameStats.cpp
new file mode 100644
index 00000000..3571827d
--- /dev/null
+++ b/mp/src/game/shared/GameStats.cpp
@@ -0,0 +1,1526 @@
+//========= Copyright 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"
+
+#ifndef SWDS
+#include "iregistry.h"
+#endif
+
+#include "tier1/utldict.h"
+#include "tier0/icommandline.h"
+#include <time.h>
+#ifdef GAME_DLL
+#include "vehicle_base.h"
+#endif
+
+#if defined( _X360 )
+#include "xbox/xbox_win32stubs.h"
+#endif
+
+#ifdef CLIENT_DLL
+#include "materialsystem/materialsystem_config.h"
+#include "vgui_int.h"
+#include "igameresources.h"
+#include "voice_status.h"
+extern const ConVar *sv_cheats;
+#if !defined(NO_STEAM)
+#include "steam/steam_api.h"
+#endif
+#endif
+
+
+#if !defined(NO_STEAM) && defined(CLIENT_DLL)
+#if defined(TF_CLIENT_DLL) || defined(CSTRIKE_DLL)
+#define STEAMWORKS_GAMESTATS_ACTIVE
+#include "steamworks_gamestats.h"
+#endif
+#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();
+ m_pKVData = new KeyValues( "gamestats" );
+ }
+ void FreeData()
+ {
+ if ( m_pKVData != NULL )
+ {
+ m_pKVData->deleteThis();
+ m_pKVData = NULL;
+ }
+ }
+
+ KeyValues *m_pKVData;
+ bool m_bHaveData;
+};
+
+CBaseGameStats_Driver CBGSDriver;
+
+void UpdatePerfStats( void )
+{
+ CBGSDriver.UpdatePerfStats();
+}
+
+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_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
+ SetHL2UnlockedChapterStatistic();
+ SetSteamStatistic( filesystem->IsSteam() );
+ SetCyberCafeStatistic( gamestatsuploader->IsCyberCafeUser() );
+ ConVarRef pDXLevel( "mat_dxlevel" );
+ if( pDXLevel.IsValid() )
+ {
+ SetDXLevelStatistic( pDXLevel.GetInt() );
+ }
+ ++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\n" );
+}
+
+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 );
+}
+
+//=============================================================================
+// HPE_BEGIN
+// [dwenger] Needed for CS window-breaking stat
+//=============================================================================
+void CBaseGameStats::Event_WindowShattered( CBasePlayer *pPlayer )
+{
+ StatsLog( "In Event_WindowShattered\n" );
+}
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+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 SWDS
+ 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;
+}
+
+void CBaseGameStats::CollectData( StatSendType_t sendType )
+{
+ CBGSDriver.CollectData( sendType );
+}
+
+void CBaseGameStats::SendData()
+{
+ CBGSDriver.SendData();
+}
+
+
+#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 SWDS
+ 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::UpdatePerfStats( void )
+{
+ float flCurTime = Plat_FloatTime();
+ if (
+ ( m_flLastSampleTime == -1 ) ||
+ ( flCurTime - m_flLastSampleTime >= STATS_RECORD_INTERVAL ) )
+ {
+ if ( ( m_flLastRealTime > 0 ) && ( flCurTime > m_flLastRealTime ) )
+ {
+ float flFrameRate = 1.0 / ( flCurTime - m_flLastRealTime );
+ StatsBufferRecord_t &stat = m_StatsBuffer[m_nWriteIndex];
+ stat.m_flFrameRate = flFrameRate;
+#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(); // GetLocalPlayer( FirstValidSplitScreenSlot() );
+ if ( pPlayer && gr )
+ {
+ ping = gr->GetPing( pPlayer->entindex() );
+ }
+ stat.m_flServerPing = ping;
+#endif
+ AdvanceIndex();
+ m_flLastSampleTime = flCurTime;
+ }
+ }
+ m_flLastRealTime = flCurTime;
+
+#ifdef CLIENT_DLL
+ if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
+ {
+ m_bDidVoiceChat |= GetClientVoiceMgr()->IsLocalPlayerSpeaking();
+ }
+#endif
+}
+
+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", 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()
+{
+#ifdef CLIENT_DLL
+ LevelShutdown();
+#endif
+}
+
+void CBaseGameStats_Driver::LevelShutdownPreClearSteamAPIContext()
+{
+#ifdef GAME_DLL
+ LevelShutdown();
+#endif
+}
+
+void CBaseGameStats_Driver::LevelShutdown()
+{
+ 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 )
+{
+ 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 );
+ 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( MapName(), szMap, ARRAYSIZE( szMap ) );
+#else
+ Q_strncpy( szMap, gpGlobals->mapname.ToCStr(), ARRAYSIZE( szMap ) );
+#endif // CLIENT_DLL
+ if ( !szMap[0] )
+ return;
+ 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(STEAMWORKS_GAMESTATS_ACTIVE)
+ // At the end of every map, clients submit their perfdata for the map
+ if ( sendType == STATSEND_LEVELSHUTDOWN && pGamestatsData && pGamestatsData->m_bHaveData )
+ {
+ GetSteamWorksSGameStatsUploader().AddClientPerfData( pGamestatsData->m_pKVData );
+ }
+ GetSteamWorksSGameStatsUploader().LevelShutdown();
+#endif
+
+ // add game-specific data
+ pGamestatsData->m_bHaveData |= gamestats->AddDataForSend( pKV, sendType );
+
+// Need to initialiate a reset since cs isn't using the gamestat system to add data
+#if defined(CSTRIKE_DLL) && defined(CLIENT_DLL)
+ ResetData();
+#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 );
+
+ if ( CommandLine()->FindParm( "-gamestatsfileoutputonly" ) )
+ {
+ // write file for debugging
+ const char szFileName[] = "gamestats.dat";
+ filesystem->WriteFile( szFileName, GAMESTATS_PATHID, 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
+ if ( m_bBufferFull )
+ {
+ // add perf data
+ KeyValues *pKVPerf = new KeyValues( "perfdata" );
+ float flAverageFrameRate = AverageStat( &StatsBufferRecord_t::m_flFrameRate );
+ float flMinFrameRate = MinStat( &StatsBufferRecord_t::m_flFrameRate );
+ float flMaxFrameRate = MaxStat( &StatsBufferRecord_t::m_flFrameRate );
+
+
+ float flDeviationsquared = 0;
+ // Compute std deviation
+ for( int i = 0; i < STATS_WINDOW_SIZE; i++ )
+ {
+ float val = m_StatsBuffer[i].m_flFrameRate - flAverageFrameRate;
+ flDeviationsquared += ( val * val );
+ }
+
+ float var = flDeviationsquared / (float)( STATS_WINDOW_SIZE - 1 );
+ float flStandardDeviationFrameRate = sqrt( var );
+
+ pKVPerf->SetFloat( "AvgFPS", flAverageFrameRate );
+ pKVPerf->SetFloat( "MinFPS", flMinFrameRate );
+ pKVPerf->SetFloat( "MaxFPS", flMaxFrameRate );
+ pKVPerf->SetFloat( "StdDevFPS", flStandardDeviationFrameRate );
+
+ // Determine the min/avg/max Server Ping and store the results
+ float flAverageServerPing = AverageStat( &StatsBufferRecord_t::m_flServerPing );
+ pKVPerf->SetFloat( "AvgServerPing", flAverageServerPing );
+
+ 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() );
+
+#ifndef NO_STEAM
+ // We can now get the game language from steam :)
+ if ( steamapicontext && steamapicontext->SteamApps() )
+ {
+ const char *currentLanguage = steamapicontext->SteamApps()->GetCurrentGameLanguage();
+ pKV->SetString( "Language", currentLanguage ? currentLanguage : "unknown" );
+ }
+#endif
+
+ // We need to filter out client side dev work from playtest work for the stat reporting.
+ // The simplest way is to check for sv_cheats, since we also do NOT want client stat reports
+ // where the player has cheated.
+ if ( NULL != sv_cheats )
+ {
+ int iCheats = sv_cheats->GetInt();
+ pKV->SetInt( "Cheats", iCheats );
+ }
+
+ int mapTime = gpGlobals->realtime - m_flLevelStartTime;
+ pKV->SetInt( "MapTime", mapTime );
+
+ return true;
+ }
+#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
+
+ if ( m_pGamestatsData != NULL )
+ {
+ delete m_pGamestatsData;
+ m_pGamestatsData = NULL;
+ }
+
+ m_bDidVoiceChat = false;
+ 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->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 );
+ const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard();
+ pKV->SetInt( "Windowed", config.Windowed() == true );
+
+ engine->SetGamestatsData( m_pGamestatsData );
+#endif
+
+#if defined(CSTRIKE_DLL) && defined(CLIENT_DLL)
+ // reset perf buffer for next map
+ for( int i = 0; i < STATS_WINDOW_SIZE; i++ )
+ {
+ m_StatsBuffer[i].m_flFrameRate = 0;
+ m_StatsBuffer[i].m_flServerPing = 0;
+ }
+
+ m_bBufferFull = false;
+ m_nWriteIndex = 0;
+#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;
+}
+
+void CBaseGameStats::SetHL2UnlockedChapterStatistic( void )
+{
+ // Now grab the hl2/cfg/config.cfg and suss out the sv_unlockedchapters cvar to estimate how far they got in HL2
+ char const *relative = "cfg/config.cfg";
+ char fullpath[ 512 ];
+ char gamedir[256];
+ engine->GetGameDir( gamedir, 256 );
+ Q_snprintf( fullpath, sizeof( fullpath ), "%s/../hl2/%s", gamedir, relative );
+
+ if ( filesystem->FileExists( fullpath ) )
+ {
+ FileHandle_t fh = filesystem->Open( fullpath, "rb" );
+ if ( FILESYSTEM_INVALID_HANDLE != fh )
+ {
+ // read file into memory
+ int size = filesystem->Size(fh);
+ char *configBuffer = new char[ size + 1 ];
+ filesystem->Read( configBuffer, size, fh );
+ configBuffer[size] = 0;
+ filesystem->Close( fh );
+
+ // loop through looking for all the cvars to apply
+ const char *search = Q_stristr(configBuffer, "sv_unlockedchapters" );
+ if ( search )
+ {
+ // read over the token
+ search = strtok( (char *)search, " \n" );
+ search = strtok( NULL, " \n" );
+
+ if ( search[0]== '\"' )
+ ++search;
+
+ // read the value
+ int iChapter = Q_atoi( search );
+ m_BasicStats.m_nHL2ChaptureUnlocked = iChapter;
+ }
+
+ // free
+ delete [] configBuffer;
+ }
+ }
+}
+
+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