Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "igamesystem.h"
  8. #include "gamestats.h"
  9. #include "tier1/utlstring.h"
  10. #include "filesystem.h"
  11. #include "tier1/utlbuffer.h"
  12. #include "fmtstr.h"
  13. #ifndef SWDS
  14. #include "iregistry.h"
  15. #endif
  16. #include "tier1/utldict.h"
  17. #include "tier0/icommandline.h"
  18. #include <time.h>
  19. #ifdef GAME_DLL
  20. #include "vehicle_base.h"
  21. #endif
  22. #if defined( _X360 )
  23. #include "xbox/xbox_win32stubs.h"
  24. #endif
  25. #ifdef CLIENT_DLL
  26. #include "materialsystem/materialsystem_config.h"
  27. #include "vgui_int.h"
  28. #include "igameresources.h"
  29. #include "voice_status.h"
  30. extern const ConVar *sv_cheats;
  31. #if !defined(NO_STEAM)
  32. #include "steam/steam_api.h"
  33. #endif
  34. #include "inputsystem/iinputsystem.h"
  35. #endif
  36. #if !defined(NO_STEAM) && defined(CLIENT_DLL)
  37. #if defined(TF_CLIENT_DLL) || defined(CSTRIKE_DLL)
  38. #define STEAMWORKS_GAMESTATS_ACTIVE
  39. #include "steamworks_gamestats.h"
  40. #endif
  41. #endif
  42. #ifdef CLIENT_DLL
  43. // Ensure this is declared in the client dll so everyone finds the same one.
  44. ConVar dev_loadtime_mainmenu("dev_loadtime_mainmenu", "0.0", FCVAR_HIDDEN );
  45. #endif
  46. // NOTE: This has to be the last file included!
  47. #include "tier0/memdbgon.h"
  48. #define GAMESTATS_LOG_FILE "gamestats.log"
  49. #define GAMESTATS_PATHID "MOD"
  50. /*
  51. #define ONE_DAY_IN_SECONDS 86400
  52. // Lower threshold in debug for testing...
  53. #if defined( _DEBUG )
  54. #define WALKED_AWAY_FROM_KEYBOARD_SECONDS 15.0f // 15 seconds of movement == might be paused
  55. #else
  56. #define WALKED_AWAY_FROM_KEYBOARD_SECONDS 300.0f // 5 minutes of no movement == might be paused
  57. #endif
  58. */
  59. extern IUploadGameStats *gamestatsuploader;
  60. static char s_szPseudoUniqueID[20] = "";
  61. static inline char const *SafeString( char const *pStr )
  62. {
  63. return ( pStr ) ? pStr : "?";
  64. }
  65. static CBaseGameStats s_GameStats_Singleton;
  66. CBaseGameStats *gamestats = &s_GameStats_Singleton; //start out pointing at the basic version which does nothing by default
  67. extern ConVar skill;
  68. void OverWriteCharsWeHate( char *pStr );
  69. bool StatsTrackingIsFullyEnabled( void );
  70. class CGamestatsData
  71. {
  72. public:
  73. CGamestatsData()
  74. {
  75. m_pKVData = NULL;
  76. m_bHaveData = false;
  77. AllocData();
  78. }
  79. ~CGamestatsData()
  80. {
  81. FreeData();
  82. }
  83. void AllocData()
  84. {
  85. FreeData();
  86. m_pKVData = new KeyValues( "gamestats" );
  87. }
  88. void FreeData()
  89. {
  90. if ( m_pKVData != NULL )
  91. {
  92. m_pKVData->deleteThis();
  93. m_pKVData = NULL;
  94. }
  95. }
  96. KeyValues *m_pKVData;
  97. bool m_bHaveData;
  98. };
  99. CBaseGameStats_Driver CBGSDriver;
  100. void UpdatePerfStats( void )
  101. {
  102. CBGSDriver.UpdatePerfStats();
  103. }
  104. CBaseGameStats_Driver::CBaseGameStats_Driver( void ) :
  105. BaseClass( "CGameStats" ),
  106. m_iLoadedVersion( -1 ),
  107. m_bEnabled( false ),
  108. m_bShuttingDown( false ),
  109. m_bInLevel( false ),
  110. m_bFirstLevel( true ),
  111. m_flLevelStartTime( 0.0f ),
  112. m_bStationary( false ),
  113. m_flLastMovementTime( 0.0f ),
  114. m_bGamePaused( false ),
  115. m_pGamestatsData( NULL ),
  116. m_bBufferFull( false ),
  117. m_nWriteIndex( 0 ),
  118. m_flLastRealTime( -1 ),
  119. m_flLastSampleTime( -1 ),
  120. m_flTotalTimeInLevels( 0 ),
  121. m_iNumLevels( 0 ),
  122. m_bDidVoiceChat( false )
  123. {
  124. m_szLoadedUserID[0] = 0;
  125. m_tLastUpload = 0;
  126. m_LastUserCmd.Reset();
  127. }
  128. static FileHandle_t g_LogFileHandle = FILESYSTEM_INVALID_HANDLE;
  129. CBaseGameStats::CBaseGameStats() :
  130. m_bLogging( false ),
  131. m_bLoggingToFile( false )
  132. {
  133. }
  134. bool CBaseGameStats::StatTrackingAllowed( void )
  135. {
  136. return CBGSDriver.m_bEnabled;
  137. }
  138. // Don't care about vcr hooks here...
  139. #undef localtime
  140. #undef asctime
  141. #include <time.h>
  142. void CBaseGameStats::StatsLog( char const *fmt, ... )
  143. {
  144. if ( !m_bLogging && !m_bLoggingToFile )
  145. return;
  146. char buf[ 2048 ];
  147. va_list argptr;
  148. va_start( argptr, fmt );
  149. Q_vsnprintf( buf, sizeof( buf ), fmt, argptr );
  150. va_end( argptr );
  151. // Prepend timestamp and spew it
  152. // Prepend the time.
  153. time_t aclock;
  154. time( &aclock );
  155. struct tm *newtime = localtime( &aclock );
  156. char timeString[ 128 ];
  157. Q_strncpy( timeString, asctime( newtime ), sizeof( timeString ) );
  158. // Get rid of the \n.
  159. char *pEnd = strstr( timeString, "\n" );
  160. if ( pEnd )
  161. {
  162. *pEnd = 0;
  163. }
  164. if ( m_bLogging )
  165. {
  166. DevMsg( "[GS %s - %7.2f] %s", timeString, gpGlobals->realtime, buf );
  167. }
  168. if ( m_bLoggingToFile )
  169. {
  170. if ( FILESYSTEM_INVALID_HANDLE == g_LogFileHandle )
  171. {
  172. g_LogFileHandle = filesystem->Open( GAMESTATS_LOG_FILE, "a", GAMESTATS_PATHID );
  173. }
  174. if ( FILESYSTEM_INVALID_HANDLE != g_LogFileHandle )
  175. {
  176. filesystem->FPrintf( g_LogFileHandle, "[GS %s - %7.2f] %s", timeString, gpGlobals->realtime, buf );
  177. filesystem->Flush( g_LogFileHandle );
  178. }
  179. }
  180. }
  181. static char s_szSaveFileName[256] = "";
  182. static char s_szStatUploadRegistryKeyName[256] = "";
  183. const char *CBaseGameStats::GetStatSaveFileName( void )
  184. {
  185. AssertMsg( s_szSaveFileName[0] != '\0', "Don't know what file to save stats to." );
  186. return s_szSaveFileName;
  187. }
  188. const char *CBaseGameStats::GetStatUploadRegistryKeyName( void )
  189. {
  190. AssertMsg( s_szStatUploadRegistryKeyName[0] != '\0', "Don't know the registry key to use to mark stats uploads." );
  191. return s_szStatUploadRegistryKeyName;
  192. }
  193. const char *CBaseGameStats::GetUserPseudoUniqueID( void )
  194. {
  195. AssertMsg( s_szPseudoUniqueID[0] != '\0', "Don't have a pseudo unique ID." );
  196. return s_szPseudoUniqueID;
  197. }
  198. void CBaseGameStats::Event_Init( void )
  199. {
  200. #ifdef GAME_DLL
  201. SetHL2UnlockedChapterStatistic();
  202. SetSteamStatistic( filesystem->IsSteam() );
  203. SetCyberCafeStatistic( gamestatsuploader->IsCyberCafeUser() );
  204. ConVarRef pDXLevel( "mat_dxlevel" );
  205. if( pDXLevel.IsValid() )
  206. {
  207. SetDXLevelStatistic( pDXLevel.GetInt() );
  208. }
  209. ++m_BasicStats.m_Summary.m_nCount;
  210. StatsLog( "CBaseGameStats::Event_Init [%dth session]\n", m_BasicStats.m_Summary.m_nCount );
  211. #endif // GAME_DLL
  212. }
  213. void CBaseGameStats::Event_Shutdown( void )
  214. {
  215. #ifdef GAME_DLL
  216. StatsLog( "CBaseGameStats::Event_Shutdown [%dth session]\n", m_BasicStats.m_Summary.m_nCount );
  217. StatsLog( "\n====================================================================\n\n" );
  218. #endif
  219. }
  220. void CBaseGameStats::Event_MapChange( const char *szOldMapName, const char *szNewMapName )
  221. {
  222. StatsLog( "CBaseGameStats::Event_MapChange to [%s]\n", szNewMapName );
  223. }
  224. void CBaseGameStats::Event_LevelInit( void )
  225. {
  226. #ifdef GAME_DLL
  227. StatsLog( "CBaseGameStats::Event_LevelInit [%s]\n", CBGSDriver.m_PrevMapName.String() );
  228. BasicGameStatsRecord_t *map = gamestats->m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  229. ++map->m_nCount;
  230. // HACK HACK: Punching this hole through only works in single player!!!
  231. if ( gpGlobals->maxClients == 1 )
  232. {
  233. ConVarRef closecaption( "closecaption" );
  234. if( closecaption.IsValid() )
  235. SetCaptionsStatistic( closecaption.GetBool() );
  236. SetHDRStatistic( gamestatsuploader->IsHDREnabled() );
  237. SetSkillStatistic( skill.GetInt() );
  238. SetSteamStatistic( filesystem->IsSteam() );
  239. SetCyberCafeStatistic( gamestatsuploader->IsCyberCafeUser() );
  240. }
  241. #endif // GAME_DLL
  242. }
  243. void CBaseGameStats::Event_LevelShutdown( float flElapsed )
  244. {
  245. #ifdef GAME_DLL
  246. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  247. Assert( map );
  248. map->m_nSeconds += (int)flElapsed;
  249. gamestats->m_BasicStats.m_Summary.m_nSeconds += (int)flElapsed;
  250. StatsLog( "CBaseGameStats::Event_LevelShutdown [%s] %.2f elapsed %d total\n", CBGSDriver.m_PrevMapName.String(), flElapsed, gamestats->m_BasicStats.m_Summary.m_nSeconds );
  251. #endif // GAME_DLL
  252. }
  253. void CBaseGameStats::Event_SaveGame( void )
  254. {
  255. StatsLog( "CBaseGameStats::Event_SaveGame [%s]\n", CBGSDriver.m_PrevMapName.String() );
  256. }
  257. void CBaseGameStats::Event_LoadGame( void )
  258. {
  259. #ifdef GAME_DLL
  260. char const *pchSaveFile = engine->GetMostRecentlyLoadedFileName();
  261. StatsLog( "CBaseGameStats::Event_LoadGame [%s] from %s\n", CBGSDriver.m_PrevMapName.String(), pchSaveFile );
  262. #endif
  263. }
  264. #ifdef GAME_DLL
  265. void CBaseGameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
  266. {
  267. ++m_BasicStats.m_Summary.m_nDeaths;
  268. if( CBGSDriver.m_bInLevel )
  269. {
  270. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  271. ++map->m_nDeaths;
  272. StatsLog( " Player died %dth time in level [%s]!!!\n", map->m_nDeaths, CBGSDriver.m_PrevMapName.String() );
  273. }
  274. else
  275. {
  276. StatsLog( " Player died, but not in a level!!!\n" );
  277. Assert( 0 );
  278. }
  279. StatsLog( "CBaseGameStats::Event_PlayerKilled [%s] [%dth death]\n", pPlayer->GetPlayerName(), m_BasicStats.m_Summary.m_nDeaths );
  280. }
  281. void CBaseGameStats::Event_Commentary()
  282. {
  283. if( CBGSDriver.m_bInLevel )
  284. {
  285. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  286. ++map->m_nCommentary;
  287. }
  288. ++m_BasicStats.m_Summary.m_nCommentary;
  289. StatsLog( "CBaseGameStats::Event_Commentary [%d]\n", m_BasicStats.m_Summary.m_nCommentary );
  290. }
  291. void CBaseGameStats::Event_Credits()
  292. {
  293. StatsLog( "CBaseGameStats::Event_Credits\n" );
  294. float elapsed = 0.0f;
  295. if( CBGSDriver.m_bInLevel )
  296. {
  297. elapsed = gpGlobals->realtime - CBGSDriver.m_flLevelStartTime;
  298. }
  299. if( elapsed < 0.0f )
  300. {
  301. Assert( 0 );
  302. Warning( "EVENT_CREDITS with negative elapsed time (rt %f starttime %f)\n", gpGlobals->realtime, CBGSDriver.m_flLevelStartTime );
  303. elapsed = 0.0f;
  304. }
  305. // Only set this one time!!!
  306. if( gamestats->m_BasicStats.m_nSecondsToCompleteGame == 0 )
  307. {
  308. if( gamestats->UserPlayedAllTheMaps() )
  309. {
  310. gamestats->m_BasicStats.m_nSecondsToCompleteGame = elapsed + gamestats->m_BasicStats.m_Summary.m_nSeconds;
  311. gamestats->SaveToFileNOW();
  312. }
  313. }
  314. }
  315. void CBaseGameStats::Event_CrateSmashed()
  316. {
  317. StatsLog( "CBaseGameStats::Event_CrateSmashed\n" );
  318. }
  319. void CBaseGameStats::Event_Punted( CBaseEntity *pObject )
  320. {
  321. StatsLog( "CBaseGameStats::Event_Punted [%s]\n", pObject->GetClassname() );
  322. }
  323. void CBaseGameStats::Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting )
  324. {
  325. }
  326. void CBaseGameStats::Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle )
  327. {
  328. StatsLog( "CBaseGameStats::Event_FlippedVehicle [%s] flipped [%s]\n", pDriver->GetPlayerName(), pVehicle->GetClassname() );
  329. }
  330. // Called before .sav file is actually loaded (player should still be in previous level, if any)
  331. void CBaseGameStats::Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame )
  332. {
  333. StatsLog( "CBaseGameStats::Event_PreSaveGameLoaded [%s] %s\n", pSaveName, bInGame ? "in-game" : "at console" );
  334. }
  335. bool CBaseGameStats::SaveToFileNOW( bool bForceSyncWrite /* = false */ )
  336. {
  337. if ( !StatsTrackingIsFullyEnabled() )
  338. return false;
  339. // this code path is only for old format stats. Products that use new format take a different path.
  340. if ( !gamestats->UseOldFormat() )
  341. return false;
  342. CUtlBuffer buf;
  343. buf.PutShort( GAMESTATS_FILE_VERSION );
  344. buf.Put( s_szPseudoUniqueID, 16 );
  345. if( ShouldTrackStandardStats() )
  346. m_BasicStats.SaveToBuffer( buf );
  347. else
  348. buf.PutInt( GAMESTATS_STANDARD_NOT_SAVED );
  349. gamestats->AppendCustomDataToSaveBuffer( buf );
  350. char fullpath[ 512 ] = { 0 };
  351. if ( filesystem->FileExists( GetStatSaveFileName(), GAMESTATS_PATHID ) )
  352. {
  353. filesystem->RelativePathToFullPath( GetStatSaveFileName(), GAMESTATS_PATHID, fullpath, sizeof( fullpath ) );
  354. }
  355. else
  356. {
  357. // filename is local to game dir for Steam, so we need to prepend game dir for regular file save
  358. char gamePath[256];
  359. engine->GetGameDir( gamePath, 256 );
  360. Q_StripTrailingSlash( gamePath );
  361. Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", gamePath, GetStatSaveFileName() );
  362. Q_strlower( fullpath );
  363. Q_FixSlashes( fullpath );
  364. }
  365. // StatsLog( "SaveToFileNOW '%s'\n", fullpath );
  366. if( CBGSDriver.m_bShuttingDown || bForceSyncWrite ) //write synchronously
  367. {
  368. filesystem->WriteFile( fullpath, GAMESTATS_PATHID, buf );
  369. StatsLog( "Shut down wrote to '%s'\n", fullpath );
  370. }
  371. else
  372. {
  373. // Allocate memory for async system to use (and free afterward!!!)
  374. size_t nBufferSize = buf.TellPut();
  375. void *pMem = malloc(nBufferSize);
  376. CUtlBuffer statsBuffer( pMem, nBufferSize );
  377. statsBuffer.Put( buf.Base(), nBufferSize );
  378. // Write data async
  379. filesystem->AsyncWrite( fullpath, statsBuffer.Base(), statsBuffer.TellPut(), true, false );
  380. }
  381. return true;
  382. }
  383. void CBaseGameStats::Event_PlayerConnected( CBasePlayer *pBasePlayer )
  384. {
  385. StatsLog( "CBaseGameStats::Event_PlayerConnected [%s]\n", pBasePlayer->GetPlayerName() );
  386. }
  387. void CBaseGameStats::Event_PlayerDisconnected( CBasePlayer *pBasePlayer )
  388. {
  389. StatsLog( "CBaseGameStats::Event_PlayerDisconnected\n" );
  390. }
  391. void CBaseGameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info )
  392. {
  393. //StatsLog( "CBaseGameStats::Event_PlayerDamage [%s] took %.2f damage\n", pBasePlayer->GetPlayerName(), info.GetDamage() );
  394. }
  395. void CBaseGameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
  396. {
  397. StatsLog( "CBaseGameStats::Event_PlayerKilledOther [%s] killed [%s]\n", pAttacker->GetPlayerName(), pVictim->GetClassname() );
  398. }
  399. void CBaseGameStats::Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName )
  400. {
  401. StatsLog( "CBaseGameStats::Event_WeaponFired [%s] %s weapon [%s]\n", pShooter->GetPlayerName(), bPrimary ? "primary" : "secondary", pchWeaponName );
  402. }
  403. void CBaseGameStats::Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info )
  404. {
  405. StatsLog( "CBaseGameStats::Event_WeaponHit [%s] %s weapon [%s] damage [%f]\n", pShooter->GetPlayerName(), bPrimary ? "primary" : "secondary", pchWeaponName, info.GetDamage() );
  406. }
  407. void CBaseGameStats::Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer )
  408. {
  409. StatsLog( "CBaseGameStats::Event_PlayerEnteredGodMode [%s] entered GOD mode\n", pBasePlayer->GetPlayerName() );
  410. }
  411. void CBaseGameStats::Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer )
  412. {
  413. StatsLog( "CBaseGameStats::Event_PlayerEnteredNoClip [%s] entered NOCLIPe\n", pBasePlayer->GetPlayerName() );
  414. }
  415. void CBaseGameStats::Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer )
  416. {
  417. StatsLog( "CBaseGameStats::Event_DecrementPlayerEnteredNoClip [%s] decrementing NOCLIPe\n", pBasePlayer->GetPlayerName() );
  418. }
  419. void CBaseGameStats::Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount )
  420. {
  421. StatsLog( "Incrementing %s by %f at pos (%d, %d, %d)\n", pchStatisticName, flIncrementAmount, (int)vecAbsOrigin.x, (int)vecAbsOrigin.y, (int)vecAbsOrigin.z );
  422. }
  423. //=============================================================================
  424. // HPE_BEGIN
  425. // [dwenger] Needed for CS window-breaking stat
  426. //=============================================================================
  427. void CBaseGameStats::Event_WindowShattered( CBasePlayer *pPlayer )
  428. {
  429. StatsLog( "In Event_WindowShattered\n" );
  430. }
  431. //=============================================================================
  432. // HPE_END
  433. //=============================================================================
  434. bool CBaseGameStats::UploadStatsFileNOW( void )
  435. {
  436. if( !StatsTrackingIsFullyEnabled() || !HaveValidData() || !gamestats->UseOldFormat() )
  437. return false;
  438. if ( !filesystem->FileExists( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID ) )
  439. {
  440. return false;
  441. }
  442. int curtime = Plat_FloatTime();
  443. CBGSDriver.m_tLastUpload = curtime;
  444. // Update the registry
  445. #ifndef SWDS
  446. IRegistry *reg = InstanceRegistry( "Steam" );
  447. Assert( reg );
  448. reg->WriteInt( GetStatUploadRegistryKeyName(), CBGSDriver.m_tLastUpload );
  449. ReleaseInstancedRegistry( reg );
  450. #endif
  451. CUtlBuffer buf;
  452. filesystem->ReadFile( GetStatSaveFileName(), GAMESTATS_PATHID, buf );
  453. unsigned int uBlobSize = buf.TellPut();
  454. if ( uBlobSize == 0 )
  455. {
  456. return false;
  457. }
  458. const void *pvBlobData = ( const void * )buf.Base();
  459. if( gamestatsuploader )
  460. {
  461. return gamestatsuploader->UploadGameStats( "",
  462. 1,
  463. uBlobSize,
  464. pvBlobData );
  465. }
  466. return false;
  467. }
  468. void CBaseGameStats::LoadingEvent_PlayerIDDifferentThanLoadedStats( void )
  469. {
  470. StatsLog( "CBaseGameStats::LoadingEvent_PlayerIDDifferentThanLoadedStats\n" );
  471. }
  472. bool CBaseGameStats::LoadFromFile( void )
  473. {
  474. if ( filesystem->FileExists( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID ) )
  475. {
  476. char fullpath[ 512 ];
  477. filesystem->RelativePathToFullPath( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID, fullpath, sizeof( fullpath ) );
  478. StatsLog( "Loading stats from '%s'\n", fullpath );
  479. }
  480. CUtlBuffer buf;
  481. if ( filesystem->ReadFile( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID, buf ) )
  482. {
  483. bool bRetVal = true;
  484. int version = buf.GetShort();
  485. if ( version > GAMESTATS_FILE_VERSION )
  486. return false; //file is beyond our comprehension
  487. // Set global parse version
  488. CBGSDriver.m_iLoadedVersion = version;
  489. buf.Get( CBGSDriver.m_szLoadedUserID, 16 );
  490. CBGSDriver.m_szLoadedUserID[ sizeof( CBGSDriver.m_szLoadedUserID ) - 1 ] = 0;
  491. if ( s_szPseudoUniqueID[ 0 ] != 0 )
  492. {
  493. if ( Q_stricmp( CBGSDriver.m_szLoadedUserID, s_szPseudoUniqueID ) )
  494. {
  495. //UserID changed, blow away log!!!
  496. filesystem->RemoveFile( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID );
  497. filesystem->RemoveFile( GAMESTATS_LOG_FILE, GAMESTATS_PATHID );
  498. Warning( "Userid changed, clearing stats file\n" );
  499. CBGSDriver.m_szLoadedUserID[0] = '\0';
  500. CBGSDriver.m_iLoadedVersion = -1;
  501. gamestats->m_BasicStats.Clear();
  502. gamestats->LoadingEvent_PlayerIDDifferentThanLoadedStats();
  503. bRetVal = false;
  504. }
  505. if ( version <= GAMESTATS_FILE_VERSION_OLD5 )
  506. {
  507. gamestats->m_BasicStats.Clear();
  508. bRetVal = false;
  509. }
  510. else
  511. {
  512. // Peek ahead in buffer to see if we have the "no default stats" secret flag set.
  513. int iCheckForStandardStatsInFile = *( int * )buf.PeekGet();
  514. bool bValid = true;
  515. if ( iCheckForStandardStatsInFile != GAMESTATS_STANDARD_NOT_SAVED )
  516. {
  517. //the GAMESTATS_STANDARD_NOT_SAVED flag coincides with user completion time, rewind so the gamestats parser can grab it
  518. bValid = gamestats->m_BasicStats.ParseFromBuffer( buf, version );
  519. }
  520. else
  521. {
  522. // skip over the flag
  523. buf.GetInt();
  524. }
  525. if( !bValid )
  526. {
  527. m_BasicStats.Clear();
  528. }
  529. if( ( buf.TellPut() - buf.TellGet() ) != 0 ) //more data left, must be custom data
  530. {
  531. gamestats->LoadCustomDataFromBuffer( buf );
  532. }
  533. }
  534. }
  535. return bRetVal;
  536. }
  537. else
  538. {
  539. filesystem->RemoveFile( GAMESTATS_LOG_FILE, GAMESTATS_PATHID );
  540. }
  541. return false;
  542. }
  543. void CBaseGameStats::CollectData( StatSendType_t sendType )
  544. {
  545. CBGSDriver.CollectData( sendType );
  546. }
  547. void CBaseGameStats::SendData()
  548. {
  549. CBGSDriver.SendData();
  550. }
  551. #endif // GAME_DLL
  552. bool CBaseGameStats_Driver::Init()
  553. {
  554. const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" );
  555. //standardizing is a good thing
  556. char szLoweredGameDir[256];
  557. Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) );
  558. Q_strlower( szLoweredGameDir );
  559. gamestats = gamestats->OnInit( gamestats, szLoweredGameDir );
  560. //determine constant strings needed for saving and uploading
  561. Q_strncpy( s_szSaveFileName, szLoweredGameDir, sizeof( s_szSaveFileName ) );
  562. Q_strncat( s_szSaveFileName, "_gamestats.dat", sizeof( s_szSaveFileName ) );
  563. Q_strncpy( s_szStatUploadRegistryKeyName, "GameStatsUpload_", sizeof( s_szStatUploadRegistryKeyName ) );
  564. Q_strncat( s_szStatUploadRegistryKeyName, szLoweredGameDir, sizeof( s_szStatUploadRegistryKeyName ) );
  565. gamestats->m_bLoggingToFile = CommandLine()->FindParm( "-gamestatsloggingtofile" ) ? true : false;
  566. gamestats->m_bLogging = CommandLine()->FindParm( "-gamestatslogging" ) ? true : false;
  567. if ( gamestatsuploader )
  568. {
  569. m_bEnabled = gamestatsuploader->IsGameStatsLoggingEnabled();
  570. if ( m_bEnabled )
  571. {
  572. gamestatsuploader->GetPseudoUniqueId( s_szPseudoUniqueID, sizeof( s_szPseudoUniqueID ) );
  573. }
  574. }
  575. ResetData();
  576. #ifdef GAME_DLL
  577. if ( StatsTrackingIsFullyEnabled() )
  578. {
  579. // FIXME: Load m_tLastUpload from registry and save it back out, too
  580. #ifndef SWDS
  581. IRegistry *reg = InstanceRegistry( "Steam" );
  582. Assert( reg );
  583. m_tLastUpload = reg->ReadInt( gamestats->GetStatUploadRegistryKeyName(), 0 );
  584. ReleaseInstancedRegistry( reg );
  585. #endif
  586. //load existing stats
  587. gamestats->LoadFromFile();
  588. }
  589. #endif // GAME_DLL
  590. if ( s_szPseudoUniqueID[ 0 ] != 0 )
  591. {
  592. gamestats->Event_Init();
  593. #ifdef GAME_DLL
  594. if ( gamestats->UseOldFormat() )
  595. {
  596. if( gamestats->AutoSave_OnInit() )
  597. gamestats->SaveToFileNOW();
  598. if( gamestats->AutoUpload_OnInit() )
  599. gamestats->UploadStatsFileNOW();
  600. }
  601. #endif
  602. }
  603. else
  604. {
  605. m_bEnabled = false; //unable to generate a pseudo-unique ID, disable tracking
  606. }
  607. return true;
  608. }
  609. void CBaseGameStats_Driver::Shutdown()
  610. {
  611. m_bShuttingDown = true;
  612. gamestats->Event_Shutdown();
  613. if ( gamestats->UseOldFormat() )
  614. {
  615. #ifdef GAME_DLL
  616. if( gamestats->AutoSave_OnShutdown() )
  617. gamestats->SaveToFileNOW();
  618. if( gamestats->AutoUpload_OnShutdown() )
  619. gamestats->UploadStatsFileNOW();
  620. #endif // GAME_DLL
  621. }
  622. else
  623. {
  624. // code path for new format game stats
  625. if ( gamestats->ShouldSendDataOnAppShutdown() )
  626. {
  627. CollectData( STATSEND_APPSHUTDOWN );
  628. SendData();
  629. }
  630. }
  631. if ( FILESYSTEM_INVALID_HANDLE != g_LogFileHandle )
  632. {
  633. filesystem->Close( g_LogFileHandle );
  634. g_LogFileHandle = FILESYSTEM_INVALID_HANDLE;
  635. }
  636. if ( m_pGamestatsData != NULL )
  637. {
  638. #ifdef CLIENT_DLL
  639. engine->SetGamestatsData( NULL );
  640. #endif
  641. delete m_pGamestatsData;
  642. m_pGamestatsData = NULL;
  643. }
  644. }
  645. void CBaseGameStats_Driver::UpdatePerfStats( void )
  646. {
  647. float flCurTime = Plat_FloatTime();
  648. if (
  649. ( m_flLastSampleTime == -1 ) ||
  650. ( flCurTime - m_flLastSampleTime >= STATS_RECORD_INTERVAL ) )
  651. {
  652. if ( ( m_flLastRealTime > 0 ) && ( flCurTime > m_flLastRealTime ) )
  653. {
  654. float flFrameRate = 1.0 / ( flCurTime - m_flLastRealTime );
  655. StatsBufferRecord_t &stat = m_StatsBuffer[m_nWriteIndex];
  656. stat.m_flFrameRate = flFrameRate;
  657. #ifdef CLIENT_DLL
  658. // The stat system isn't designed to handle split screen players. Until it get's
  659. // redesigned, let's take the first player's split screen ping, since all other stats
  660. // will be based on the first player
  661. IGameResources *gr = GameResources();
  662. int ping = 0;
  663. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); // GetLocalPlayer( FirstValidSplitScreenSlot() );
  664. if ( pPlayer && gr )
  665. {
  666. ping = gr->GetPing( pPlayer->entindex() );
  667. }
  668. stat.m_flServerPing = ping;
  669. #endif
  670. AdvanceIndex();
  671. m_flLastSampleTime = flCurTime;
  672. }
  673. }
  674. m_flLastRealTime = flCurTime;
  675. #ifdef CLIENT_DLL
  676. if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
  677. {
  678. m_bDidVoiceChat |= GetClientVoiceMgr()->IsLocalPlayerSpeaking();
  679. }
  680. #endif
  681. }
  682. void CBaseGameStats_Driver::PossibleMapChange( void )
  683. {
  684. #ifdef GAME_DLL
  685. //detect and copy map changes
  686. if ( Q_stricmp( m_PrevMapName.String(), STRING( gpGlobals->mapname ) ) )
  687. {
  688. MEM_ALLOC_CREDIT();
  689. CUtlString PrevMapBackup = m_PrevMapName;
  690. m_PrevMapName = STRING( gpGlobals->mapname );
  691. gamestats->Event_MapChange( PrevMapBackup.String(), STRING( gpGlobals->mapname ) );
  692. if ( gamestats->UseOldFormat() )
  693. {
  694. if( gamestats->AutoSave_OnMapChange() )
  695. gamestats->SaveToFileNOW();
  696. if( gamestats->AutoUpload_OnMapChange() )
  697. gamestats->UploadStatsFileNOW();
  698. }
  699. }
  700. #endif
  701. }
  702. void CBaseGameStats_Driver::LevelInitPreEntity()
  703. {
  704. m_bInLevel = true;
  705. m_bFirstLevel = false;
  706. if ( Q_stricmp( s_szPseudoUniqueID, "unknown" ) == 0 )
  707. {
  708. // "unknown" means this is a dedicated server and we weren't able to generate a unique ID (e.g. Linux server).
  709. // Change the unique ID to be a hash of IP & port. We couldn't do this earlier because IP is not known until level
  710. // init time.
  711. ConVar *hostip = cvar->FindVar( "hostip" );
  712. ConVar *hostport = cvar->FindVar( "hostport" );
  713. if ( hostip && hostport )
  714. {
  715. int crcInput[2];
  716. crcInput[0] = hostip->GetInt();
  717. crcInput[1] = hostport->GetInt();
  718. if ( crcInput[0] && crcInput[1] )
  719. {
  720. CRC32_t crc = CRC32_ProcessSingleBuffer( crcInput, sizeof( crcInput ) );
  721. Q_snprintf( s_szPseudoUniqueID, ARRAYSIZE( s_szPseudoUniqueID ), "H:%x", crc );
  722. }
  723. }
  724. }
  725. PossibleMapChange();
  726. m_flPauseStartTime = 0.0f;
  727. m_flLevelStartTime = gpGlobals->realtime;
  728. gamestats->Event_LevelInit();
  729. #ifdef GAME_DLL
  730. if ( gamestats->UseOldFormat() )
  731. {
  732. if( gamestats->AutoSave_OnLevelInit() )
  733. gamestats->SaveToFileNOW();
  734. if( gamestats->AutoUpload_OnLevelInit() )
  735. gamestats->UploadStatsFileNOW();
  736. }
  737. #endif
  738. }
  739. void CBaseGameStats_Driver::LevelShutdownPreEntity()
  740. {
  741. #ifdef CLIENT_DLL
  742. LevelShutdown();
  743. #endif
  744. }
  745. void CBaseGameStats_Driver::LevelShutdownPreClearSteamAPIContext()
  746. {
  747. #ifdef GAME_DLL
  748. LevelShutdown();
  749. #endif
  750. }
  751. void CBaseGameStats_Driver::LevelShutdown()
  752. {
  753. float flElapsed = gpGlobals->realtime - m_flLevelStartTime;
  754. if ( flElapsed < 0.0f )
  755. {
  756. Assert( 0 );
  757. Warning( "EVENT_LEVELSHUTDOWN: with negative elapsed time (rt %f starttime %f)\n", gpGlobals->realtime, m_flLevelStartTime );
  758. flElapsed = 0.0f;
  759. }
  760. //Assert( m_bInLevel ); //so, apparently shutdowns can happen before inits
  761. #ifdef GAME_DLL
  762. if ( m_bInLevel && ( gpGlobals->eLoadType != MapLoad_Background ) )
  763. #else
  764. if ( m_bInLevel )
  765. #endif
  766. {
  767. m_flTotalTimeInLevels += flElapsed;
  768. m_iNumLevels ++;
  769. gamestats->Event_LevelShutdown( flElapsed );
  770. if ( gamestats->UseOldFormat() )
  771. {
  772. #ifdef GAME_DLL
  773. if( gamestats->AutoSave_OnLevelShutdown() )
  774. gamestats->SaveToFileNOW( true );
  775. if( gamestats->AutoUpload_OnLevelShutdown() )
  776. gamestats->UploadStatsFileNOW();
  777. #endif
  778. }
  779. else
  780. {
  781. // code path for new format game stats
  782. CollectData( STATSEND_LEVELSHUTDOWN );
  783. if ( gamestats->ShouldSendDataOnLevelShutdown() )
  784. {
  785. SendData();
  786. }
  787. }
  788. m_bInLevel = false;
  789. }
  790. }
  791. void CBaseGameStats_Driver::OnSave()
  792. {
  793. gamestats->Event_SaveGame();
  794. }
  795. void CBaseGameStats_Driver::CollectData( StatSendType_t sendType )
  796. {
  797. CGamestatsData *pGamestatsData = NULL;
  798. #ifdef GAME_DLL
  799. // for server, check with the engine to see if there already a gamestats data container registered. (There will be if there is a client
  800. // running in the same process.)
  801. pGamestatsData = engine->GetGamestatsData();
  802. if ( pGamestatsData )
  803. {
  804. // use the registered gamestats container, so free the one we allocated
  805. if ( m_pGamestatsData != NULL )
  806. {
  807. delete m_pGamestatsData;
  808. m_pGamestatsData = NULL;
  809. }
  810. }
  811. else
  812. {
  813. pGamestatsData = m_pGamestatsData;
  814. }
  815. #else
  816. pGamestatsData = m_pGamestatsData;
  817. #endif
  818. Assert( pGamestatsData );
  819. KeyValues *pKV = pGamestatsData->m_pKVData;
  820. int iAppID = engine->GetAppID();
  821. pKV->SetInt( "appid", iAppID );
  822. switch ( sendType )
  823. {
  824. case STATSEND_LEVELSHUTDOWN:
  825. {
  826. // make a map node in the KeyValues to use for this level
  827. char szMap[MAX_PATH+1]="";
  828. #ifdef CLIENT_DLL
  829. Q_FileBase( MapName(), szMap, ARRAYSIZE( szMap ) );
  830. #else
  831. Q_strncpy( szMap, gpGlobals->mapname.ToCStr(), ARRAYSIZE( szMap ) );
  832. #endif // CLIENT_DLL
  833. if ( !szMap[0] )
  834. return;
  835. KeyValues *pKVMap = new KeyValues( "map" );
  836. pKV->AddSubKey( pKVMap );
  837. pKVMap->SetString( "mapname", szMap );
  838. pKV = pKVMap;
  839. }
  840. break;
  841. case STATSEND_APPSHUTDOWN:
  842. break;
  843. default:
  844. Assert( false );
  845. break;
  846. }
  847. // add common data
  848. pGamestatsData->m_bHaveData |= AddBaseDataForSend( pKV, sendType );
  849. #if defined(STEAMWORKS_GAMESTATS_ACTIVE)
  850. // At the end of every map, clients submit their perfdata for the map
  851. if ( sendType == STATSEND_LEVELSHUTDOWN && pGamestatsData && pGamestatsData->m_bHaveData )
  852. {
  853. GetSteamWorksSGameStatsUploader().AddClientPerfData( pGamestatsData->m_pKVData );
  854. }
  855. GetSteamWorksSGameStatsUploader().LevelShutdown();
  856. #endif
  857. // add game-specific data
  858. pGamestatsData->m_bHaveData |= gamestats->AddDataForSend( pKV, sendType );
  859. // Need to initialiate a reset since cs isn't using the gamestat system to add data
  860. #if defined(CSTRIKE_DLL) && defined(CLIENT_DLL)
  861. ResetData();
  862. #endif
  863. }
  864. void CBaseGameStats_Driver::SendData()
  865. {
  866. // if we don't own the data container or there's no valid data, nothing to do
  867. if ( !m_pGamestatsData || !m_pGamestatsData->m_bHaveData )
  868. return;
  869. // save the data to a buffer
  870. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  871. m_pGamestatsData->m_pKVData->RecursiveSaveToFile( buf, 0 );
  872. if ( CommandLine()->FindParm( "-gamestatsfileoutputonly" ) )
  873. {
  874. // write file for debugging
  875. const char szFileName[] = "gamestats.dat";
  876. filesystem->WriteFile( szFileName, GAMESTATS_PATHID, buf );
  877. }
  878. else
  879. {
  880. // upload the file to Steam
  881. if ( gamestatsuploader )
  882. gamestatsuploader->UploadGameStats( "", 1, buf.TellPut(), buf.Base() );
  883. }
  884. ResetData();
  885. }
  886. #ifdef CLIENT_DLL
  887. // Adds the main menu load time to the specified key values, but only ever does the work once.
  888. static void AddLoadTimeMainMenu( KeyValues* pKV )
  889. {
  890. Assert( pKV );
  891. float loadTimeMainMenu = dev_loadtime_mainmenu.GetFloat();
  892. if ( loadTimeMainMenu > 0.0f ) {
  893. pKV->SetFloat( "LoadTimeMainMenu", loadTimeMainMenu );
  894. // Only want to set this once, clear it to 0.0 here. The other code will only ever set it once.
  895. dev_loadtime_mainmenu.SetValue( 0.0f );
  896. }
  897. }
  898. // Adds the map load time to the specified key values, but clears the elapsed data to 0.0 for next computation.
  899. static void AddLoadTimeMap(KeyValues* pKV)
  900. {
  901. static ConVarRef dev_loadtime_map_elapsed( "dev_loadtime_map_elapsed" );
  902. float loadTimeMap = dev_loadtime_map_elapsed.GetFloat();
  903. if ( loadTimeMap > 0.0f )
  904. {
  905. pKV->SetFloat( "LoadTimeMap", loadTimeMap );
  906. dev_loadtime_map_elapsed.SetValue( 0.0f );
  907. }
  908. }
  909. #endif
  910. bool CBaseGameStats_Driver::AddBaseDataForSend( KeyValues *pKV, StatSendType_t sendType )
  911. {
  912. switch ( sendType )
  913. {
  914. case STATSEND_APPSHUTDOWN:
  915. #ifdef CLIENT_DLL
  916. if ( m_iNumLevels > 0 )
  917. {
  918. // add playtime data
  919. KeyValues *pKVData = new KeyValues( "playtime" );
  920. pKVData->SetInt( "TotalLevelTime", m_flTotalTimeInLevels );
  921. pKVData->SetInt( "NumLevels", m_iNumLevels );
  922. pKV->AddSubKey( pKVData );
  923. AddLoadTimeMainMenu( pKV );
  924. // If the user quits directly from the map, we still want to (possibly) capture their map load time, so
  925. // do add it here. It will not be added if it was already attached to a session.
  926. AddLoadTimeMap( pKV );
  927. return true;
  928. }
  929. #endif
  930. break;
  931. case STATSEND_LEVELSHUTDOWN:
  932. #ifdef CLIENT_DLL
  933. if ( m_bBufferFull )
  934. {
  935. // add perf data
  936. KeyValues *pKVPerf = new KeyValues( "perfdata" );
  937. float flAverageFrameRate = AverageStat( &StatsBufferRecord_t::m_flFrameRate );
  938. float flMinFrameRate = MinStat( &StatsBufferRecord_t::m_flFrameRate );
  939. float flMaxFrameRate = MaxStat( &StatsBufferRecord_t::m_flFrameRate );
  940. float flDeviationsquared = 0;
  941. // Compute std deviation
  942. for( int i = 0; i < STATS_WINDOW_SIZE; i++ )
  943. {
  944. float val = m_StatsBuffer[i].m_flFrameRate - flAverageFrameRate;
  945. flDeviationsquared += ( val * val );
  946. }
  947. float var = flDeviationsquared / (float)( STATS_WINDOW_SIZE - 1 );
  948. float flStandardDeviationFrameRate = sqrt( var );
  949. pKVPerf->SetFloat( "AvgFPS", flAverageFrameRate );
  950. pKVPerf->SetFloat( "MinFPS", flMinFrameRate );
  951. pKVPerf->SetFloat( "MaxFPS", flMaxFrameRate );
  952. pKVPerf->SetFloat( "StdDevFPS", flStandardDeviationFrameRate );
  953. // Determine the min/avg/max Server Ping and store the results
  954. float flAverageServerPing = AverageStat( &StatsBufferRecord_t::m_flServerPing );
  955. pKVPerf->SetFloat( "AvgServerPing", flAverageServerPing );
  956. pKV->AddSubKey( pKVPerf );
  957. // Only keeping track of using voice in a multiplayer game
  958. if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
  959. {
  960. pKV->SetInt( "UsedVoice", m_bDidVoiceChat );
  961. }
  962. extern ConVar closecaption;
  963. pKV->SetInt( "Caption", closecaption.GetInt() );
  964. #ifndef NO_STEAM
  965. // We can now get the game language from steam :)
  966. if ( steamapicontext && steamapicontext->SteamApps() )
  967. {
  968. const char *currentLanguage = steamapicontext->SteamApps()->GetCurrentGameLanguage();
  969. pKV->SetString( "Language", currentLanguage ? currentLanguage : "unknown" );
  970. }
  971. #endif
  972. // We need to filter out client side dev work from playtest work for the stat reporting.
  973. // The simplest way is to check for sv_cheats, since we also do NOT want client stat reports
  974. // where the player has cheated.
  975. if ( NULL != sv_cheats )
  976. {
  977. int iCheats = sv_cheats->GetInt();
  978. pKV->SetInt( "Cheats", iCheats );
  979. }
  980. int mapTime = gpGlobals->realtime - m_flLevelStartTime;
  981. pKV->SetInt( "MapTime", mapTime );
  982. pKV->SetBool( "SteamControllerActive", g_pInputSystem->IsSteamControllerActive() );
  983. AddLoadTimeMainMenu(pKV);
  984. AddLoadTimeMap(pKV);
  985. return true;
  986. }
  987. #endif
  988. break;
  989. }
  990. return false;
  991. }
  992. void CBaseGameStats_Driver::ResetData()
  993. {
  994. #ifdef GAME_DLL
  995. // 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
  996. if ( engine->GetGamestatsData() != NULL )
  997. return;
  998. #endif
  999. if ( m_pGamestatsData != NULL )
  1000. {
  1001. delete m_pGamestatsData;
  1002. m_pGamestatsData = NULL;
  1003. }
  1004. m_bDidVoiceChat = false;
  1005. m_pGamestatsData = new CGamestatsData();
  1006. KeyValues *pKV = m_pGamestatsData->m_pKVData;
  1007. pKV->SetInt( "IsPc", IsPC() );
  1008. pKV->SetInt( "version", GAMESTATS_VERSION );
  1009. pKV->SetString( "srcid", s_szPseudoUniqueID );
  1010. #ifdef CLIENT_DLL
  1011. const CPUInformation &cpu = *GetCPUInformation();
  1012. OverWriteCharsWeHate( cpu.m_szProcessorID );
  1013. pKV->SetString( "CPUID", cpu.m_szProcessorID );
  1014. pKV->SetFloat( "CPUGhz", cpu.m_Speed * ( 1.0 / 1.0e9 ) );
  1015. pKV->SetUint64( "CPUModel", cpu.m_nModel );
  1016. pKV->SetUint64( "CPUFeatures0", cpu.m_nFeatures[ 0 ] );
  1017. pKV->SetUint64( "CPUFeatures1", cpu.m_nFeatures[ 1 ] );
  1018. pKV->SetUint64( "CPUFeatures2", cpu.m_nFeatures[ 2 ] );
  1019. pKV->SetInt( "NumCores", cpu.m_nPhysicalProcessors );
  1020. // Capture memory stats as well.
  1021. MemoryInformation memInfo;
  1022. if ( GetMemoryInformation( &memInfo ) )
  1023. {
  1024. pKV->SetInt( "PhysicalRamMbTotal", memInfo.m_nPhysicalRamMbTotal );
  1025. pKV->SetInt( "PhysicalRamMbAvailable", memInfo.m_nPhysicalRamMbAvailable );
  1026. pKV->SetInt( "VirtualRamMbTotal", memInfo.m_nVirtualRamMbTotal );
  1027. pKV->SetInt( "VirtualRamMbAvailable", memInfo.m_nVirtualRamMbAvailable );
  1028. }
  1029. MaterialAdapterInfo_t gpu;
  1030. materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), gpu );
  1031. CMatRenderContextPtr pRenderContext( materials );
  1032. int dest_width,dest_height;
  1033. pRenderContext->GetRenderTargetDimensions( dest_width, dest_height );
  1034. if ( gpu.m_pDriverName )
  1035. {
  1036. OverWriteCharsWeHate( gpu.m_pDriverName );
  1037. }
  1038. pKV->SetString( "GPUDrv", SafeString( gpu.m_pDriverName ) );
  1039. pKV->SetInt( "GPUVendor", gpu.m_VendorID );
  1040. pKV->SetInt( "GPUDeviceID", gpu.m_DeviceID );
  1041. pKV->SetString( "GPUDriverVersion", CFmtStr( "%d.%d", gpu.m_nDriverVersionHigh, gpu.m_nDriverVersionLow ) );
  1042. pKV->SetInt( "DxLvl", g_pMaterialSystemHardwareConfig->GetDXSupportLevel() );
  1043. pKV->SetInt( "Width", dest_width );
  1044. pKV->SetInt( "Height", dest_height );
  1045. const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard();
  1046. pKV->SetInt( "Windowed", config.Windowed() == true );
  1047. pKV->SetInt( "MaxDxLevel", g_pMaterialSystemHardwareConfig->GetMaxDXSupportLevel() );
  1048. engine->SetGamestatsData( m_pGamestatsData );
  1049. #endif
  1050. #if defined(CSTRIKE_DLL) && defined(CLIENT_DLL)
  1051. // reset perf buffer for next map
  1052. for( int i = 0; i < STATS_WINDOW_SIZE; i++ )
  1053. {
  1054. m_StatsBuffer[i].m_flFrameRate = 0;
  1055. m_StatsBuffer[i].m_flServerPing = 0;
  1056. }
  1057. m_bBufferFull = false;
  1058. m_nWriteIndex = 0;
  1059. #endif
  1060. }
  1061. void CBaseGameStats_Driver::OnRestore()
  1062. {
  1063. PossibleMapChange();
  1064. gamestats->Event_LoadGame();
  1065. }
  1066. void CBaseGameStats_Driver::FrameUpdatePostEntityThink()
  1067. {
  1068. bool bGamePaused = ( gpGlobals->frametime == 0.0f );
  1069. if ( !m_bInLevel )
  1070. {
  1071. m_flPauseStartTime = 0.0f;
  1072. }
  1073. else if ( m_bGamePaused != bGamePaused )
  1074. {
  1075. if ( bGamePaused )
  1076. {
  1077. m_flPauseStartTime = gpGlobals->realtime;
  1078. }
  1079. else if ( m_flPauseStartTime != 0.0f )
  1080. {
  1081. float flPausedTime = gpGlobals->realtime - m_flPauseStartTime;
  1082. if ( flPausedTime < 0.0f )
  1083. {
  1084. Assert( 0 );
  1085. Warning( "Game paused time showing up negative (rt %f pausestart %f)\n", gpGlobals->realtime, m_flPauseStartTime );
  1086. flPausedTime = 0.0f;
  1087. }
  1088. // Remove this from global time counters
  1089. // Msg( "Pause: adding %f to level starttime\n", flPausedTime );
  1090. m_flLevelStartTime += flPausedTime;
  1091. m_flPauseStartTime = 0.0f;
  1092. // Msg( "Paused for %.2f seconds\n", flPausedTime );
  1093. }
  1094. m_bGamePaused = bGamePaused;
  1095. }
  1096. }
  1097. bool StatsTrackingIsFullyEnabled( void )
  1098. {
  1099. return CBGSDriver.m_bEnabled && gamestats->StatTrackingEnabledForMod();
  1100. }
  1101. void CBaseGameStats::Clear( void )
  1102. {
  1103. #ifdef GAME_DLL
  1104. gamestats->m_BasicStats.Clear();
  1105. #endif
  1106. }
  1107. //-----------------------------------------------------------------------------
  1108. // Nukes any dangerous characters and replaces w/space char
  1109. //-----------------------------------------------------------------------------
  1110. void OverWriteCharsWeHate( char *pStr )
  1111. {
  1112. while( *pStr )
  1113. {
  1114. switch( *pStr )
  1115. {
  1116. case '\n':
  1117. case '\r':
  1118. case '\\':
  1119. case '\"':
  1120. case '\'':
  1121. case '\032':
  1122. case ';':
  1123. *pStr = ' ';
  1124. }
  1125. pStr++;
  1126. }
  1127. }
  1128. #ifdef GAME_DLL
  1129. void CBaseGameStats::SetSteamStatistic( bool bUsingSteam )
  1130. {
  1131. if( CBGSDriver.m_bFirstLevel )
  1132. {
  1133. m_BasicStats.m_Summary.m_bSteam = bUsingSteam;
  1134. }
  1135. if( CBGSDriver.m_bInLevel )
  1136. {
  1137. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1138. map->m_bSteam = bUsingSteam;
  1139. }
  1140. m_BasicStats.m_bSteam = bUsingSteam;
  1141. }
  1142. void CBaseGameStats::SetCyberCafeStatistic( bool bIsCyberCafeUser )
  1143. {
  1144. if( CBGSDriver.m_bFirstLevel )
  1145. {
  1146. m_BasicStats.m_Summary.m_bCyberCafe = bIsCyberCafeUser;
  1147. }
  1148. if( CBGSDriver.m_bInLevel )
  1149. {
  1150. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1151. map->m_bCyberCafe = bIsCyberCafeUser;
  1152. }
  1153. m_BasicStats.m_bCyberCafe = bIsCyberCafeUser;
  1154. }
  1155. void CBaseGameStats::SetHDRStatistic( bool bHDREnabled )
  1156. {
  1157. if( bHDREnabled )
  1158. {
  1159. if( CBGSDriver.m_bInLevel )
  1160. {
  1161. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1162. ++map->m_nHDR;
  1163. }
  1164. if( CBGSDriver.m_bFirstLevel )
  1165. {
  1166. ++m_BasicStats.m_Summary.m_nHDR;
  1167. }
  1168. }
  1169. }
  1170. void CBaseGameStats::SetCaptionsStatistic( bool bClosedCaptionsEnabled )
  1171. {
  1172. if( CBGSDriver.m_bInLevel )
  1173. {
  1174. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1175. ++map->m_nCaptions;
  1176. }
  1177. if( CBGSDriver.m_bFirstLevel )
  1178. {
  1179. ++m_BasicStats.m_Summary.m_nCaptions;
  1180. }
  1181. }
  1182. void CBaseGameStats::SetSkillStatistic( int iSkillSetting )
  1183. {
  1184. int skill = clamp( iSkillSetting, 1, 3 ) - 1;
  1185. if( CBGSDriver.m_bInLevel )
  1186. {
  1187. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1188. ++map->m_nSkill[ skill ];
  1189. }
  1190. if ( CBGSDriver. m_bFirstLevel )
  1191. {
  1192. ++m_BasicStats.m_Summary.m_nSkill[ skill ];
  1193. }
  1194. }
  1195. void CBaseGameStats::SetDXLevelStatistic( int iDXLevel )
  1196. {
  1197. m_BasicStats.m_nDXLevel = iDXLevel;
  1198. }
  1199. void CBaseGameStats::SetHL2UnlockedChapterStatistic( void )
  1200. {
  1201. // Now grab the hl2/cfg/config.cfg and suss out the sv_unlockedchapters cvar to estimate how far they got in HL2
  1202. char const *relative = "cfg/config.cfg";
  1203. char fullpath[ 512 ];
  1204. char gamedir[256];
  1205. engine->GetGameDir( gamedir, 256 );
  1206. Q_snprintf( fullpath, sizeof( fullpath ), "%s/../hl2/%s", gamedir, relative );
  1207. if ( filesystem->FileExists( fullpath ) )
  1208. {
  1209. FileHandle_t fh = filesystem->Open( fullpath, "rb" );
  1210. if ( FILESYSTEM_INVALID_HANDLE != fh )
  1211. {
  1212. // read file into memory
  1213. int size = filesystem->Size(fh);
  1214. char *configBuffer = new char[ size + 1 ];
  1215. filesystem->Read( configBuffer, size, fh );
  1216. configBuffer[size] = 0;
  1217. filesystem->Close( fh );
  1218. // loop through looking for all the cvars to apply
  1219. const char *search = Q_stristr(configBuffer, "sv_unlockedchapters" );
  1220. if ( search )
  1221. {
  1222. // read over the token
  1223. search = strtok( (char *)search, " \n" );
  1224. search = strtok( NULL, " \n" );
  1225. if ( search[0]== '\"' )
  1226. ++search;
  1227. // read the value
  1228. int iChapter = Q_atoi( search );
  1229. m_BasicStats.m_nHL2ChaptureUnlocked = iChapter;
  1230. }
  1231. // free
  1232. delete [] configBuffer;
  1233. }
  1234. }
  1235. }
  1236. static void CC_ResetGameStats( const CCommand &args )
  1237. {
  1238. #if defined ( TF_DLL ) || defined ( TF_CLIENT_DLL )
  1239. // Disabled this until we fix the TF gamestat crashes that result
  1240. return;
  1241. #endif
  1242. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  1243. return;
  1244. gamestats->Clear();
  1245. gamestats->SaveToFileNOW();
  1246. gamestats->StatsLog( "CC_ResetGameStats : Server cleared game stats\n" );
  1247. }
  1248. static ConCommand resetGameStats("_resetgamestats", CC_ResetGameStats, "Erases current game stats and writes out a blank stats file", 0 );
  1249. class CPointGamestatsCounter : public CPointEntity
  1250. {
  1251. public:
  1252. DECLARE_CLASS( CPointGamestatsCounter, CPointEntity );
  1253. DECLARE_DATADESC();
  1254. CPointGamestatsCounter();
  1255. protected:
  1256. void InputSetName( inputdata_t &inputdata );
  1257. void InputIncrement( inputdata_t &inputdata );
  1258. void InputEnable( inputdata_t &inputdata );
  1259. void InputDisable( inputdata_t &inputdata );
  1260. private:
  1261. string_t m_strStatisticName;
  1262. bool m_bDisabled;
  1263. };
  1264. BEGIN_DATADESC( CPointGamestatsCounter )
  1265. DEFINE_KEYFIELD( m_strStatisticName, FIELD_STRING, "Name" ),
  1266. DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ),
  1267. // Inputs
  1268. DEFINE_INPUTFUNC( FIELD_STRING, "SetName", InputSetName ),
  1269. DEFINE_INPUTFUNC( FIELD_FLOAT, "Increment", InputIncrement ),
  1270. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  1271. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  1272. END_DATADESC()
  1273. LINK_ENTITY_TO_CLASS( point_gamestats_counter, CPointGamestatsCounter )
  1274. CPointGamestatsCounter::CPointGamestatsCounter() :
  1275. m_strStatisticName( NULL_STRING ),
  1276. m_bDisabled( false )
  1277. {
  1278. }
  1279. //-----------------------------------------------------------------------------
  1280. // Purpose: Changes name of statistic
  1281. //-----------------------------------------------------------------------------
  1282. void CPointGamestatsCounter::InputSetName( inputdata_t &inputdata )
  1283. {
  1284. m_strStatisticName = inputdata.value.StringID();
  1285. }
  1286. //-----------------------------------------------------------------------------
  1287. // Purpose: Changes name of statistic
  1288. //-----------------------------------------------------------------------------
  1289. void CPointGamestatsCounter::InputIncrement( inputdata_t &inputdata )
  1290. {
  1291. if ( m_bDisabled )
  1292. return;
  1293. if ( NULL_STRING == m_strStatisticName )
  1294. {
  1295. DevMsg( 1, "CPointGamestatsCounter::InputIncrement: No stat name specified for point_gamestats_counter @%f, %f, %f [ent index %d]\n",
  1296. GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, entindex() );
  1297. return;
  1298. }
  1299. gamestats->Event_IncrementCountedStatistic( GetAbsOrigin(), STRING( m_strStatisticName ), inputdata.value.Float() );
  1300. }
  1301. void CPointGamestatsCounter::InputEnable( inputdata_t &inputdata )
  1302. {
  1303. m_bDisabled = false;
  1304. }
  1305. void CPointGamestatsCounter::InputDisable( inputdata_t &inputdata )
  1306. {
  1307. m_bDisabled = true;
  1308. }
  1309. #endif // GAME_DLL