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.
1584 lines
42 KiB
1584 lines
42 KiB
//========= 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 |
|
#include "inputsystem/iinputsystem.h" |
|
#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 |
|
|
|
#ifdef CLIENT_DLL |
|
// Ensure this is declared in the client dll so everyone finds the same one. |
|
ConVar dev_loadtime_mainmenu("dev_loadtime_mainmenu", "0.0", FCVAR_HIDDEN ); |
|
#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(); |
|
} |
|
|
|
#ifdef CLIENT_DLL |
|
// Adds the main menu load time to the specified key values, but only ever does the work once. |
|
static void AddLoadTimeMainMenu( KeyValues* pKV ) |
|
{ |
|
Assert( pKV ); |
|
float loadTimeMainMenu = dev_loadtime_mainmenu.GetFloat(); |
|
if ( loadTimeMainMenu > 0.0f ) { |
|
pKV->SetFloat( "LoadTimeMainMenu", loadTimeMainMenu ); |
|
// Only want to set this once, clear it to 0.0 here. The other code will only ever set it once. |
|
dev_loadtime_mainmenu.SetValue( 0.0f ); |
|
} |
|
} |
|
|
|
// Adds the map load time to the specified key values, but clears the elapsed data to 0.0 for next computation. |
|
static void AddLoadTimeMap(KeyValues* pKV) |
|
{ |
|
static ConVarRef dev_loadtime_map_elapsed( "dev_loadtime_map_elapsed" ); |
|
float loadTimeMap = dev_loadtime_map_elapsed.GetFloat(); |
|
if ( loadTimeMap > 0.0f ) |
|
{ |
|
pKV->SetFloat( "LoadTimeMap", loadTimeMap ); |
|
dev_loadtime_map_elapsed.SetValue( 0.0f ); |
|
} |
|
} |
|
|
|
#endif |
|
|
|
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 ); |
|
|
|
AddLoadTimeMainMenu( pKV ); |
|
// If the user quits directly from the map, we still want to (possibly) capture their map load time, so |
|
// do add it here. It will not be added if it was already attached to a session. |
|
AddLoadTimeMap( pKV ); |
|
|
|
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 ); |
|
|
|
pKV->SetBool( "SteamControllerActive", g_pInputSystem->IsSteamControllerActive() ); |
|
|
|
AddLoadTimeMainMenu(pKV); |
|
AddLoadTimeMap(pKV); |
|
|
|
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->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 ); |
|
|
|
// Capture memory stats as well. |
|
MemoryInformation memInfo; |
|
if ( GetMemoryInformation( &memInfo ) ) |
|
{ |
|
pKV->SetInt( "PhysicalRamMbTotal", memInfo.m_nPhysicalRamMbTotal ); |
|
pKV->SetInt( "PhysicalRamMbAvailable", memInfo.m_nPhysicalRamMbAvailable ); |
|
pKV->SetInt( "VirtualRamMbTotal", memInfo.m_nVirtualRamMbTotal ); |
|
pKV->SetInt( "VirtualRamMbAvailable", memInfo.m_nVirtualRamMbAvailable ); |
|
} |
|
|
|
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 ); |
|
pKV->SetInt( "MaxDxLevel", g_pMaterialSystemHardwareConfig->GetMaxDXSupportLevel() ); |
|
|
|
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
|
|
|