Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1900 lines
53 KiB

  1. //====== Copyright © 1996-2005, 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. #include "tier2/tier2.h"
  14. #include "tier0/cpumonitoring.h"
  15. #include "materialsystem/imaterialsystemhardwareconfig.h"
  16. #ifndef DEDICATED
  17. #include "iregistry.h"
  18. #endif
  19. #include "tier1/utldict.h"
  20. #include "tier0/icommandline.h"
  21. #include "tier0/perfstats.h"
  22. #include <time.h>
  23. #ifdef GAME_DLL
  24. #include "vehicle_base.h"
  25. #endif
  26. #ifdef CLIENT_DLL
  27. #include "vgui_int.h"
  28. #include "igameresources.h"
  29. #include "voice_status.h"
  30. extern const ConVar *sv_cheats;
  31. #ifndef NO_STEAM
  32. #include "steamworks_gamestats_client.h"
  33. #endif
  34. #include "materialsystem/materialsystem_config.h"
  35. #endif
  36. #if defined( _X360 )
  37. #include "xbox/xbox_win32stubs.h"
  38. #endif
  39. // NOTE: This has to be the last file included!
  40. #include "tier0/memdbgon.h"
  41. #define GAMESTATS_LOG_FILE "gamestats.log"
  42. #define GAMESTATS_PATHID "MOD"
  43. /*
  44. #define ONE_DAY_IN_SECONDS 86400
  45. // Lower threshold in debug for testing...
  46. #if defined( _DEBUG )
  47. #define WALKED_AWAY_FROM_KEYBOARD_SECONDS 15.0f // 15 seconds of movement == might be paused
  48. #else
  49. #define WALKED_AWAY_FROM_KEYBOARD_SECONDS 300.0f // 5 minutes of no movement == might be paused
  50. #endif
  51. */
  52. extern IUploadGameStats *gamestatsuploader;
  53. static char s_szPseudoUniqueID[20] = "";
  54. static inline char const *SafeString( char const *pStr )
  55. {
  56. return ( pStr ) ? pStr : "?";
  57. }
  58. static CBaseGameStats s_GameStats_Singleton;
  59. CBaseGameStats *gamestats = &s_GameStats_Singleton; //start out pointing at the basic version which does nothing by default
  60. extern ConVar skill;
  61. void OverWriteCharsWeHate( char *pStr );
  62. bool StatsTrackingIsFullyEnabled( void );
  63. class CGamestatsData
  64. {
  65. public:
  66. CGamestatsData()
  67. {
  68. m_pKVData = NULL;
  69. m_bHaveData = false;
  70. AllocData();
  71. }
  72. ~CGamestatsData()
  73. {
  74. FreeData();
  75. }
  76. void AllocData()
  77. {
  78. FreeData();
  79. char buffer[ 128 ];
  80. Q_snprintf( buffer, sizeof( buffer ), "gamestats_" );
  81. UTIL_GetModDir( buffer + 10, sizeof( buffer ) - 10 );
  82. m_pKVData = new KeyValues( buffer );
  83. }
  84. void FreeData()
  85. {
  86. if ( m_pKVData != NULL )
  87. {
  88. m_pKVData->deleteThis();
  89. m_pKVData = NULL;
  90. }
  91. }
  92. KeyValues *m_pKVData;
  93. bool m_bHaveData;
  94. };
  95. //used to drive most of the game stat event handlers as well as track basic stats under the hood of CBaseGameStats
  96. class CBaseGameStats_Driver : public CAutoGameSystemPerFrame
  97. {
  98. public:
  99. CBaseGameStats_Driver( void );
  100. typedef CAutoGameSystemPerFrame BaseClass;
  101. // IGameSystem overloads
  102. virtual bool Init();
  103. virtual void Shutdown();
  104. // Level init, shutdown
  105. virtual void LevelInitPreEntity();
  106. virtual void LevelShutdownPreEntity();
  107. // Called during game save
  108. virtual void OnSave();
  109. // Called during game restore, after the local player has connected and entities have been fully restored
  110. virtual void OnRestore();
  111. virtual void FrameUpdatePostEntityThink();
  112. void PossibleMapChange( void );
  113. void CollectData( StatSendType_t sendType );
  114. void SendData();
  115. void ResetData();
  116. bool AddBaseDataForSend( KeyValues *pKV, StatSendType_t sendType );
  117. StatsBufferRecord_t m_StatsBuffer[STATS_WINDOW_SIZE];
  118. bool m_bBufferFull;
  119. int m_nWriteIndex;
  120. double m_flLastRealTime;
  121. double m_flLastSampleTime;
  122. float m_flTotalTimeInLevels;
  123. int m_iNumLevels;
  124. bool m_bDidVoiceChat; // Did the player use voice chat at ALL this map?
  125. // Track how often the CPU is reported as running at full speed (above kCPUMonitoringWarning1),
  126. // medium speed (above kCPUMonitoringWarning2) or low speed (other).
  127. int m_nCPUUnthrottledCount;
  128. int m_nCPUThrottle1Count;
  129. int m_nCPUThrottle2Count;
  130. // Track when we last received data.
  131. double m_lastCPUFrequencyTimestamp;
  132. // Generate a buffer that will keep trailing observations prior to adding them to the main m_StatsBuffer array
  133. StatsBufferRecord_t m_TrailingStatsBuffer[STATS_TRAILING_WINDOW_SIZE];
  134. int m_nTrailingWriteIndex;
  135. int m_nTrailingSamplesWritten;
  136. template<class T> T AverageStat( T StatsBufferRecord_t::*field ) const
  137. {
  138. T sum = 0;
  139. int iMax = m_bBufferFull ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex;
  140. for( int i = 0; i < iMax; i++ )
  141. sum += m_StatsBuffer[i].*field;
  142. return sum / iMax;
  143. }
  144. template<class T> T MaxStat( T StatsBufferRecord_t::*field ) const
  145. {
  146. T maxsofar = -16000000;
  147. int iMax = m_bBufferFull ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex;
  148. for( int i = 0; i < iMax; i++ )
  149. maxsofar = MAX( maxsofar, m_StatsBuffer[i].*field );
  150. return maxsofar;
  151. }
  152. template<class T> T MinStat( T StatsBufferRecord_t::*field ) const
  153. {
  154. T minsofar = 16000000;
  155. int iMax = m_bBufferFull ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex;
  156. for( int i = 0; i < iMax; i++ )
  157. minsofar = MIN( minsofar, m_StatsBuffer[i].*field );
  158. return minsofar;
  159. }
  160. template<class T> T StdDevStat( T StatsBufferRecord_t::*field, T average ) const
  161. {
  162. T flDeviationsquared = 0;
  163. int iMax = m_bBufferFull ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex;
  164. // Compute std deviation
  165. for (int i = 0; i < iMax; i++)
  166. {
  167. T val = m_StatsBuffer[i].*field - average;
  168. flDeviationsquared += (val * val);
  169. }
  170. return ( ( iMax > 1 ) ? sqrt( (flDeviationsquared / T(iMax - 1) ) ) : 0 );
  171. }
  172. void SortFramerate()
  173. {
  174. int iMax = ( ( m_bBufferFull ) ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex ) - 1;
  175. // Sort members of m_StatsBuffer
  176. std::sort(&m_StatsBuffer[0],&m_StatsBuffer[iMax]);
  177. }
  178. float PercentileFramerate( int nPercent ) const
  179. {
  180. int iMax = ( ( m_bBufferFull ) ? ARRAYSIZE( m_StatsBuffer ) : m_nWriteIndex ) - 1;
  181. // Return the first or last member if nPercent is out of bounds
  182. if ( nPercent >= 100 )
  183. return m_StatsBuffer[iMax].m_flFrameRate;
  184. if ( nPercent <= 0 )
  185. return m_StatsBuffer[0].m_flFrameRate;
  186. // Otherwise determine the desired index and return its field.
  187. // NOTE: nPercent = 50 is close to (but is NEVER) the median - special code would be needed to handle odd-item arrays, or averaging two elements for even-item arrays.
  188. int nIdx = (iMax * nPercent) / 100;
  189. return m_StatsBuffer[nIdx].m_flFrameRate;
  190. }
  191. inline void AdvanceIndex( void )
  192. {
  193. m_nWriteIndex++;
  194. if ( m_nWriteIndex == STATS_WINDOW_SIZE )
  195. {
  196. m_nWriteIndex = 0;
  197. m_bBufferFull = true;
  198. }
  199. }
  200. void UpdatePerfStats( void )
  201. {
  202. double flCurTime = Plat_FloatTime();
  203. if ( ( m_flLastRealTime > 0.0f ) &&
  204. ( flCurTime > m_flLastRealTime ) )
  205. {
  206. float flFps = 1.0 / ( flCurTime - m_flLastRealTime );
  207. float flCpuWait = g_PerfStats.m_Slots[ PERF_STATS_SLOT_END_FRAME ].m_PrevFrameTime.GetMillisecondsF();
  208. if ( g_pGameRules ) // are we in the game? if not, it's not worth collecting histogram info
  209. {
  210. uint8 nBin = m_FpsHistory.Update( flFps, flCpuWait );
  211. m_FpsHistogram.Update( nBin );
  212. for ( int nEvent = 0; nEvent < PERF_STATS_EVENT_COUNT; ++nEvent )
  213. {
  214. m_FpsHistogramGame[ nEvent ].Update( nBin );
  215. }
  216. }
  217. m_TrailingStatsBuffer[m_nTrailingWriteIndex].m_flFrameRate = flFps;
  218. m_nTrailingWriteIndex++;
  219. if ( m_nTrailingWriteIndex == STATS_TRAILING_WINDOW_SIZE )
  220. m_nTrailingWriteIndex = 0;
  221. if ( m_nTrailingSamplesWritten < STATS_TRAILING_WINDOW_SIZE )
  222. m_nTrailingSamplesWritten++;
  223. }
  224. if ( ( m_flLastSampleTime >= 0.0f ) &&
  225. ( flCurTime - m_flLastSampleTime >= STATS_RECORD_INTERVAL ) )
  226. {
  227. if ( ( m_flLastRealTime > 0 ) && ( flCurTime > m_flLastRealTime ) )
  228. {
  229. StatsBufferRecord_t &stat = m_StatsBuffer[m_nWriteIndex];
  230. float flSumFrameRate = 0;
  231. for ( int i = 0; i < m_nTrailingSamplesWritten; i++ )
  232. {
  233. flSumFrameRate += m_TrailingStatsBuffer[i].m_flFrameRate;
  234. }
  235. float flFrameRate = flSumFrameRate / m_nTrailingSamplesWritten;
  236. stat.m_flFrameRate = flFrameRate;
  237. m_nTrailingSamplesWritten = 0;
  238. m_nTrailingWriteIndex = 0;
  239. #ifdef CLIENT_DLL
  240. // The stat system isn't designed to handle split screen players. Until it get's
  241. // redesigned, let's take the first player's split screen ping, since all other stats
  242. // will be based on the first player
  243. IGameResources *gr = GameResources();
  244. int ping = 0;
  245. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer( FirstValidSplitScreenSlot() );
  246. if ( pPlayer && gr )
  247. {
  248. ping = gr->GetPing( pPlayer->entindex() );
  249. }
  250. stat.m_flServerPing = ping;
  251. #endif
  252. stat.m_flMainThreadTimeMS = g_PerfStats.m_Slots[PERF_STATS_SLOT_MAINTHREAD].m_PrevFrameTime.GetMillisecondsF();
  253. stat.m_flMainThreadWaitTimeMS = g_PerfStats.m_Slots[PERF_STATS_SLOT_END_FRAME].m_PrevFrameTime.GetMillisecondsF();
  254. stat.m_flRenderThreadTimeMS = g_PerfStats.m_Slots[PERF_STATS_SLOT_RENDERTHREAD].m_PrevFrameTime.GetMillisecondsF();
  255. stat.m_flRenderThreadWaitTimeMS = g_PerfStats.m_Slots[PERF_STATS_SLOT_FORCE_HARDWARE_SYNC].m_PrevFrameTime.GetMillisecondsF();
  256. AdvanceIndex();
  257. m_flLastSampleTime = flCurTime;
  258. }
  259. }
  260. if ( m_flLastSampleTime == -1 )
  261. {
  262. m_flLastSampleTime = flCurTime;
  263. }
  264. m_flLastRealTime = flCurTime;
  265. #ifdef CLIENT_DLL
  266. if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
  267. {
  268. m_bDidVoiceChat |= GetClientVoiceMgr()->IsLocalPlayerSpeaking( FirstValidSplitScreenSlot() );
  269. }
  270. #endif
  271. const CPUFrequencyResults results = GetCPUFrequencyResults( true );
  272. if ( results.m_timeStamp > m_lastCPUFrequencyTimestamp )
  273. {
  274. m_lastCPUFrequencyTimestamp = results.m_timeStamp;
  275. // Increment the appropriate counter for how much the CPU is throttled (if at all)
  276. if ( results.m_percentage > kCPUMonitoringWarning1 )
  277. ++m_nCPUUnthrottledCount;
  278. else if ( results.m_percentage > kCPUMonitoringWarning2 )
  279. ++m_nCPUThrottle1Count;
  280. else
  281. ++m_nCPUThrottle2Count;
  282. }
  283. }
  284. void FirePerfStatsEvent( PerfStatsEventEnum_t nEvent, int nLookAhead = CFpsHistory::HISTORY_BUF_SIZE, int nLookBack = CFpsHistory::HISTORY_BUF_SIZE )
  285. {
  286. m_FpsHistogramGame[ nEvent ].Fire( nLookAhead, nLookBack, m_FpsHistory );
  287. }
  288. CUtlString m_PrevMapName; //used to track "OnMapChange" events
  289. int m_iLoadedVersion;
  290. char m_szLoadedUserID[ 17 ]; // GUID
  291. bool m_bEnabled; //false if incapable of uploading or the user doesn't want to enable stat tracking
  292. bool m_bShuttingDown;
  293. bool m_bInLevel;
  294. bool m_bFirstLevel;
  295. time_t m_tLastUpload;
  296. float m_flLevelStartTime;
  297. bool m_bStationary;
  298. float m_flLastMovementTime;
  299. CUserCmd m_LastUserCmd;
  300. bool m_bGamePaused;
  301. float m_flPauseStartTime;
  302. CGamestatsData *m_pGamestatsData;
  303. CFpsHistory m_FpsHistory;
  304. CFpsHistogram m_FpsHistogram;
  305. CFpsSelectiveHistogram m_FpsHistogramGame[ PERF_STATS_EVENT_COUNT ];
  306. };
  307. static CBaseGameStats_Driver CBGSDriver;
  308. void UpdatePerfStats( void )
  309. {
  310. CBGSDriver.UpdatePerfStats();
  311. }
  312. void FirePerfStatsEvent( PerfStatsEventEnum_t nEvent, int nLookAhead, int nLookBack )
  313. {
  314. CBGSDriver.FirePerfStatsEvent( nEvent, nLookAhead, nLookBack );
  315. }
  316. uint8 CFpsHistory::Update( float flFps, float flCpuWait )
  317. {
  318. uint8 nBin = 0;
  319. if ( flFps >= 45 )
  320. {
  321. if ( flFps >= 90 )
  322. {
  323. if ( flFps >= 120 )
  324. {
  325. nBin = FPS_120;
  326. }
  327. else
  328. {
  329. nBin = FPS_90;
  330. }
  331. }
  332. else
  333. {
  334. if ( flFps >= 60 )
  335. {
  336. nBin = FPS_60;
  337. }
  338. else
  339. {
  340. nBin = FPS_45;
  341. }
  342. }
  343. }
  344. else
  345. {
  346. if ( flFps >= 20 )
  347. {
  348. if ( flFps >= 30 )
  349. {
  350. nBin = FPS_30;
  351. }
  352. else
  353. {
  354. nBin = FPS_20;
  355. }
  356. }
  357. else
  358. {
  359. if ( flFps >= 10 )
  360. {
  361. nBin = FPS_10;
  362. }
  363. else
  364. {
  365. nBin = FPS_LOW;
  366. }
  367. }
  368. }
  369. if ( flCpuWait > 0.5f )
  370. {
  371. nBin |= 0x80;
  372. }
  373. return nBin;
  374. }
  375. int64 CFpsHistogram::Encode()const
  376. {
  377. uint nMaxBin = 0;
  378. for ( int i = 0; i < FPS_BIN_COUNT; ++i )
  379. {
  380. nMaxBin = Max( nMaxBin, m_nFps[ i ] );
  381. }
  382. if ( nMaxBin == 0 )
  383. return 0;
  384. int64 nEncode = 0;
  385. for ( int i = 0; i < FPS_BIN_COUNT / 2; ++i )
  386. {
  387. nEncode <<= 4;
  388. if ( uint nDenominator = m_nFps[ i * 2 ] + m_nFps[ i * 2 + 1 ] )
  389. {
  390. uint nNormalized = ( 15ll * ( m_nCpuWaits[ i * 2 ] + m_nCpuWaits[ i * 2 + 1 ] ) + nDenominator / 2 ) / nDenominator;
  391. nEncode |= nNormalized;
  392. }
  393. }
  394. for ( int i = 0; i < FPS_BIN_COUNT; ++i )
  395. {
  396. nEncode <<= 6;
  397. uint nNormalized = ( ( 0x3Fll * m_nFps[ i ] + nMaxBin / 2 ) / nMaxBin ); // round to nearest
  398. nEncode |= nNormalized;
  399. }
  400. return nEncode;
  401. }
  402. CBaseGameStats_Driver::CBaseGameStats_Driver( void ) :
  403. BaseClass( "CGameStats" ),
  404. m_iLoadedVersion( -1 ),
  405. m_bEnabled( false ),
  406. m_bShuttingDown( false ),
  407. m_bInLevel( false ),
  408. m_bFirstLevel( true ),
  409. m_flLevelStartTime( 0.0f ),
  410. m_bStationary( false ),
  411. m_flLastMovementTime( 0.0f ),
  412. m_bGamePaused( false ),
  413. m_pGamestatsData( NULL ),
  414. m_bBufferFull( false ),
  415. m_nWriteIndex( 0 ),
  416. m_flLastRealTime( -1 ),
  417. m_flLastSampleTime( -1 ),
  418. m_flTotalTimeInLevels( 0 ),
  419. m_iNumLevels( 0 ),
  420. m_bDidVoiceChat( false ),
  421. m_nTrailingWriteIndex( 0 ),
  422. m_nTrailingSamplesWritten( 0 )
  423. {
  424. m_szLoadedUserID[0] = 0;
  425. m_tLastUpload = 0;
  426. m_LastUserCmd.Reset();
  427. }
  428. static FileHandle_t g_LogFileHandle = FILESYSTEM_INVALID_HANDLE;
  429. CBaseGameStats::CBaseGameStats() :
  430. m_bLogging( false ),
  431. m_bLoggingToFile( false )
  432. {
  433. }
  434. bool CBaseGameStats::StatTrackingAllowed( void )
  435. {
  436. return CBGSDriver.m_bEnabled;
  437. }
  438. // Don't care about vcr hooks here...
  439. #undef localtime
  440. #undef asctime
  441. #include <time.h>
  442. void CBaseGameStats::StatsLog( char const *fmt, ... )
  443. {
  444. if ( !m_bLogging && !m_bLoggingToFile )
  445. return;
  446. char buf[ 2048 ];
  447. va_list argptr;
  448. va_start( argptr, fmt );
  449. Q_vsnprintf( buf, sizeof( buf ), fmt, argptr );
  450. va_end( argptr );
  451. // Prepend timestamp and spew it
  452. // Prepend the time.
  453. time_t aclock;
  454. time( &aclock );
  455. struct tm *newtime = localtime( &aclock );
  456. char timeString[ 128 ];
  457. Q_strncpy( timeString, asctime( newtime ), sizeof( timeString ) );
  458. // Get rid of the \n.
  459. char *pEnd = strstr( timeString, "\n" );
  460. if ( pEnd )
  461. {
  462. *pEnd = 0;
  463. }
  464. if ( m_bLogging )
  465. {
  466. DevMsg( "[GS %s - %7.2f] %s", timeString, gpGlobals->realtime, buf );
  467. }
  468. if ( m_bLoggingToFile )
  469. {
  470. if ( FILESYSTEM_INVALID_HANDLE == g_LogFileHandle )
  471. {
  472. g_LogFileHandle = filesystem->Open( GAMESTATS_LOG_FILE, "a", GAMESTATS_PATHID );
  473. }
  474. if ( FILESYSTEM_INVALID_HANDLE != g_LogFileHandle )
  475. {
  476. filesystem->FPrintf( g_LogFileHandle, "[GS %s - %7.2f] %s", timeString, gpGlobals->realtime, buf );
  477. filesystem->Flush( g_LogFileHandle );
  478. }
  479. }
  480. }
  481. static char s_szSaveFileName[256] = "";
  482. static char s_szStatUploadRegistryKeyName[256] = "";
  483. const char *CBaseGameStats::GetStatSaveFileName( void )
  484. {
  485. AssertMsg( s_szSaveFileName[0] != '\0', "Don't know what file to save stats to." );
  486. return s_szSaveFileName;
  487. }
  488. const char *CBaseGameStats::GetStatUploadRegistryKeyName( void )
  489. {
  490. AssertMsg( s_szStatUploadRegistryKeyName[0] != '\0', "Don't know the registry key to use to mark stats uploads." );
  491. return s_szStatUploadRegistryKeyName;
  492. }
  493. const char *CBaseGameStats::GetUserPseudoUniqueID( void )
  494. {
  495. AssertMsg( s_szPseudoUniqueID[0] != '\0', "Don't have a pseudo unique ID." );
  496. return s_szPseudoUniqueID;
  497. }
  498. void CBaseGameStats::Event_Init( void )
  499. {
  500. #ifdef GAME_DLL
  501. SetSteamStatistic( filesystem->IsSteam() );
  502. SetCyberCafeStatistic( gamestatsuploader->IsCyberCafeUser() );
  503. SetDXLevelStatistic( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() );
  504. ++m_BasicStats.m_Summary.m_nCount;
  505. StatsLog( "CBaseGameStats::Event_Init [%dth session]\n", m_BasicStats.m_Summary.m_nCount );
  506. #endif // GAME_DLL
  507. }
  508. void CBaseGameStats::Event_Shutdown( void )
  509. {
  510. #ifdef GAME_DLL
  511. StatsLog( "CBaseGameStats::Event_Shutdown [%dth session]\n", m_BasicStats.m_Summary.m_nCount );
  512. StatsLog( "\n====================================================================\n\n" );
  513. #endif
  514. }
  515. void CBaseGameStats::Event_MapChange( const char *szOldMapName, const char *szNewMapName )
  516. {
  517. StatsLog( "CBaseGameStats::Event_MapChange to [%s]\n", szNewMapName );
  518. }
  519. void CBaseGameStats::Event_LevelInit( void )
  520. {
  521. #ifdef GAME_DLL
  522. StatsLog( "CBaseGameStats::Event_LevelInit [%s]\n", CBGSDriver.m_PrevMapName.String() );
  523. BasicGameStatsRecord_t *map = gamestats->m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  524. ++map->m_nCount;
  525. // HACK HACK: Punching this hole through only works in single player!!!
  526. if ( gpGlobals->maxClients == 1 )
  527. {
  528. ConVarRef closecaption( "closecaption" );
  529. if( closecaption.IsValid() )
  530. SetCaptionsStatistic( closecaption.GetBool() );
  531. SetHDRStatistic( gamestatsuploader->IsHDREnabled() );
  532. SetSkillStatistic( skill.GetInt() );
  533. SetSteamStatistic( filesystem->IsSteam() );
  534. SetCyberCafeStatistic( gamestatsuploader->IsCyberCafeUser() );
  535. }
  536. #endif // GAME_DLL
  537. }
  538. void CBaseGameStats::Event_LevelShutdown( float flElapsed )
  539. {
  540. #ifdef GAME_DLL
  541. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  542. Assert( map );
  543. map->m_nSeconds += (int)flElapsed;
  544. gamestats->m_BasicStats.m_Summary.m_nSeconds += (int)flElapsed;
  545. StatsLog( "CBaseGameStats::Event_LevelShutdown [%s] %.2f elapsed %d total\n", CBGSDriver.m_PrevMapName.String(), flElapsed, gamestats->m_BasicStats.m_Summary.m_nSeconds );
  546. #endif // GAME_DLL
  547. }
  548. void CBaseGameStats::Event_SaveGame( void )
  549. {
  550. StatsLog( "CBaseGameStats::Event_SaveGame [%s]\n", CBGSDriver.m_PrevMapName.String() );
  551. }
  552. void CBaseGameStats::Event_LoadGame( void )
  553. {
  554. #ifdef GAME_DLL
  555. char const *pchSaveFile = engine->GetMostRecentlyLoadedFileName();
  556. StatsLog( "CBaseGameStats::Event_LoadGame [%s] from %s\n", CBGSDriver.m_PrevMapName.String(), pchSaveFile );
  557. #endif
  558. }
  559. #ifdef GAME_DLL
  560. void CBaseGameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
  561. {
  562. ++m_BasicStats.m_Summary.m_nDeaths;
  563. if( CBGSDriver.m_bInLevel )
  564. {
  565. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  566. ++map->m_nDeaths;
  567. StatsLog( " Player died %dth time in level [%s]!!!\n", map->m_nDeaths, CBGSDriver.m_PrevMapName.String() );
  568. }
  569. else
  570. {
  571. StatsLog( " Player died, but not in a level!!!\n" );
  572. Assert( 0 );
  573. }
  574. StatsLog( "CBaseGameStats::Event_PlayerKilled [%s] [%dth death]\n", pPlayer->GetPlayerName(), m_BasicStats.m_Summary.m_nDeaths );
  575. }
  576. void CBaseGameStats::Event_Commentary()
  577. {
  578. if( CBGSDriver.m_bInLevel )
  579. {
  580. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  581. ++map->m_nCommentary;
  582. }
  583. ++m_BasicStats.m_Summary.m_nCommentary;
  584. StatsLog( "CBaseGameStats::Event_Commentary [%d]\n", m_BasicStats.m_Summary.m_nCommentary );
  585. }
  586. void CBaseGameStats::Event_Credits()
  587. {
  588. StatsLog( "CBaseGameStats::Event_Credits\n" );
  589. float elapsed = 0.0f;
  590. if( CBGSDriver.m_bInLevel )
  591. {
  592. elapsed = gpGlobals->realtime - CBGSDriver.m_flLevelStartTime;
  593. }
  594. if( elapsed < 0.0f )
  595. {
  596. Assert( 0 );
  597. Warning( "EVENT_CREDITS with negative elapsed time (rt %f starttime %f)\n", gpGlobals->realtime, CBGSDriver.m_flLevelStartTime );
  598. elapsed = 0.0f;
  599. }
  600. // Only set this one time!!!
  601. if( gamestats->m_BasicStats.m_nSecondsToCompleteGame == 0 )
  602. {
  603. if( gamestats->UserPlayedAllTheMaps() )
  604. {
  605. gamestats->m_BasicStats.m_nSecondsToCompleteGame = elapsed + gamestats->m_BasicStats.m_Summary.m_nSeconds;
  606. gamestats->SaveToFileNOW();
  607. }
  608. }
  609. }
  610. void CBaseGameStats::Event_CrateSmashed()
  611. {
  612. StatsLog( "CBaseGameStats::Event_CrateSmashed\n" );
  613. }
  614. void CBaseGameStats::Event_Punted( CBaseEntity *pObject )
  615. {
  616. StatsLog( "CBaseGameStats::Event_Punted [%s]\n", pObject->GetClassname() );
  617. }
  618. void CBaseGameStats::Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting )
  619. {
  620. }
  621. void CBaseGameStats::Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle )
  622. {
  623. StatsLog( "CBaseGameStats::Event_FlippedVehicle [%s] flipped [%s]\n", pDriver->GetPlayerName(), pVehicle->GetClassname() );
  624. }
  625. // Called before .sav file is actually loaded (player should still be in previous level, if any)
  626. void CBaseGameStats::Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame )
  627. {
  628. StatsLog( "CBaseGameStats::Event_PreSaveGameLoaded [%s] %s\n", pSaveName, bInGame ? "in-game" : "at console" );
  629. }
  630. bool CBaseGameStats::SaveToFileNOW( bool bForceSyncWrite /* = false */ )
  631. {
  632. if ( !StatsTrackingIsFullyEnabled() )
  633. return false;
  634. // this code path is only for old format stats. Products that use new format take a different path.
  635. if ( !gamestats->UseOldFormat() )
  636. return false;
  637. CUtlBuffer buf;
  638. buf.PutShort( GAMESTATS_FILE_VERSION );
  639. buf.Put( s_szPseudoUniqueID, 16 );
  640. if( ShouldTrackStandardStats() )
  641. m_BasicStats.SaveToBuffer( buf );
  642. else
  643. buf.PutInt( GAMESTATS_STANDARD_NOT_SAVED );
  644. gamestats->AppendCustomDataToSaveBuffer( buf );
  645. char fullpath[ 512 ] = { 0 };
  646. if ( filesystem->FileExists( GetStatSaveFileName(), GAMESTATS_PATHID ) )
  647. {
  648. filesystem->RelativePathToFullPath( GetStatSaveFileName(), GAMESTATS_PATHID, fullpath, sizeof( fullpath ) );
  649. }
  650. else
  651. {
  652. // filename is local to game dir for Steam, so we need to prepend game dir for regular file save
  653. char gamePath[256];
  654. engine->GetGameDir( gamePath, 256 );
  655. Q_StripTrailingSlash( gamePath );
  656. Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", gamePath, GetStatSaveFileName() );
  657. Q_strlower( fullpath );
  658. Q_FixSlashes( fullpath );
  659. }
  660. // StatsLog( "SaveToFileNOW '%s'\n", fullpath );
  661. if( CBGSDriver.m_bShuttingDown || bForceSyncWrite ) //write synchronously
  662. {
  663. filesystem->WriteFile( fullpath, GAMESTATS_PATHID, buf );
  664. StatsLog( "Shut down wrote to '%s'\n", fullpath );
  665. }
  666. else
  667. {
  668. // Allocate memory for async system to use (and free afterward!!!)
  669. size_t nBufferSize = buf.TellPut();
  670. void *pMem = malloc(nBufferSize);
  671. CUtlBuffer statsBuffer( pMem, nBufferSize );
  672. statsBuffer.Put( buf.Base(), nBufferSize );
  673. // Write data async
  674. filesystem->AsyncWrite( fullpath, statsBuffer.Base(), statsBuffer.TellPut(), true, false );
  675. }
  676. return true;
  677. }
  678. void CBaseGameStats::Event_PlayerConnected( CBasePlayer *pBasePlayer )
  679. {
  680. StatsLog( "CBaseGameStats::Event_PlayerConnected [%s]\n", pBasePlayer->GetPlayerName() );
  681. }
  682. void CBaseGameStats::Event_PlayerDisconnected( CBasePlayer *pBasePlayer )
  683. {
  684. StatsLog( "CBaseGameStats::Event_PlayerDisconnected [%s]\n", pBasePlayer->GetPlayerName() );
  685. }
  686. void CBaseGameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info )
  687. {
  688. //StatsLog( "CBaseGameStats::Event_PlayerDamage [%s] took %.2f damage\n", pBasePlayer->GetPlayerName(), info.GetDamage() );
  689. }
  690. void CBaseGameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
  691. {
  692. StatsLog( "CBaseGameStats::Event_PlayerKilledOther [%s] killed [%s]\n", pAttacker->GetPlayerName(), pVictim->GetClassname() );
  693. }
  694. void CBaseGameStats::Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName )
  695. {
  696. StatsLog( "CBaseGameStats::Event_WeaponFired [%s] %s weapon [%s]\n", pShooter->GetPlayerName(), bPrimary ? "primary" : "secondary", pchWeaponName );
  697. }
  698. void CBaseGameStats::Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info )
  699. {
  700. StatsLog( "CBaseGameStats::Event_WeaponHit [%s] %s weapon [%s] damage [%f]\n", pShooter->GetPlayerName(), bPrimary ? "primary" : "secondary", pchWeaponName, info.GetDamage() );
  701. }
  702. void CBaseGameStats::Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer )
  703. {
  704. StatsLog( "CBaseGameStats::Event_PlayerEnteredGodMode [%s] entered GOD mode\n", pBasePlayer->GetPlayerName() );
  705. }
  706. void CBaseGameStats::Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer )
  707. {
  708. StatsLog( "CBaseGameStats::Event_PlayerEnteredNoClip [%s] entered NOCLIPe\n", pBasePlayer->GetPlayerName() );
  709. }
  710. void CBaseGameStats::Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer )
  711. {
  712. StatsLog( "CBaseGameStats::Event_DecrementPlayerEnteredNoClip [%s] decrementing NOCLIPe\n", pBasePlayer->GetPlayerName() );
  713. }
  714. void CBaseGameStats::Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount )
  715. {
  716. StatsLog( "Incrementing %s by %f at pos (%d, %d, %d)\n", pchStatisticName, flIncrementAmount, (int)vecAbsOrigin.x, (int)vecAbsOrigin.y, (int)vecAbsOrigin.z );
  717. }
  718. // [dwenger] Needed for CS window-breaking stat
  719. void CBaseGameStats::Event_WindowShattered( CBasePlayer *pPlayer )
  720. {
  721. StatsLog( "In Event_WindowShattered\n" );
  722. }
  723. bool CBaseGameStats::UploadStatsFileNOW( void )
  724. {
  725. if( !StatsTrackingIsFullyEnabled() || !HaveValidData() || !gamestats->UseOldFormat() )
  726. return false;
  727. if ( !filesystem->FileExists( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID ) )
  728. {
  729. return false;
  730. }
  731. int curtime = Plat_FloatTime();
  732. CBGSDriver.m_tLastUpload = curtime;
  733. // Update the registry
  734. #ifndef DEDICATED
  735. IRegistry *reg = InstanceRegistry( "Steam" );
  736. Assert( reg );
  737. reg->WriteInt( GetStatUploadRegistryKeyName(), CBGSDriver.m_tLastUpload );
  738. ReleaseInstancedRegistry( reg );
  739. #endif
  740. CUtlBuffer buf;
  741. filesystem->ReadFile( GetStatSaveFileName(), GAMESTATS_PATHID, buf );
  742. unsigned int uBlobSize = buf.TellPut();
  743. if ( uBlobSize == 0 )
  744. {
  745. return false;
  746. }
  747. const void *pvBlobData = ( const void * )buf.Base();
  748. if( gamestatsuploader )
  749. {
  750. return gamestatsuploader->UploadGameStats( "",
  751. 1,
  752. uBlobSize,
  753. pvBlobData );
  754. }
  755. return false;
  756. }
  757. void CBaseGameStats::LoadingEvent_PlayerIDDifferentThanLoadedStats( void )
  758. {
  759. StatsLog( "CBaseGameStats::LoadingEvent_PlayerIDDifferentThanLoadedStats\n" );
  760. }
  761. bool CBaseGameStats::LoadFromFile( void )
  762. {
  763. if ( filesystem->FileExists( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID ) )
  764. {
  765. char fullpath[ 512 ];
  766. filesystem->RelativePathToFullPath( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID, fullpath, sizeof( fullpath ) );
  767. StatsLog( "Loading stats from '%s'\n", fullpath );
  768. }
  769. CUtlBuffer buf;
  770. if ( filesystem->ReadFile( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID, buf ) )
  771. {
  772. bool bRetVal = true;
  773. int version = buf.GetShort();
  774. if ( version > GAMESTATS_FILE_VERSION )
  775. return false; //file is beyond our comprehension
  776. // Set global parse version
  777. CBGSDriver.m_iLoadedVersion = version;
  778. buf.Get( CBGSDriver.m_szLoadedUserID, 16 );
  779. CBGSDriver.m_szLoadedUserID[ sizeof( CBGSDriver.m_szLoadedUserID ) - 1 ] = 0;
  780. if ( s_szPseudoUniqueID[ 0 ] != 0 )
  781. {
  782. if ( Q_stricmp( CBGSDriver.m_szLoadedUserID, s_szPseudoUniqueID ) )
  783. {
  784. //UserID changed, blow away log!!!
  785. filesystem->RemoveFile( gamestats->GetStatSaveFileName(), GAMESTATS_PATHID );
  786. filesystem->RemoveFile( GAMESTATS_LOG_FILE, GAMESTATS_PATHID );
  787. Warning( "Userid changed, clearing stats file\n" );
  788. CBGSDriver.m_szLoadedUserID[0] = '\0';
  789. CBGSDriver.m_iLoadedVersion = -1;
  790. gamestats->m_BasicStats.Clear();
  791. gamestats->LoadingEvent_PlayerIDDifferentThanLoadedStats();
  792. bRetVal = false;
  793. }
  794. if ( version <= GAMESTATS_FILE_VERSION_OLD5 )
  795. {
  796. gamestats->m_BasicStats.Clear();
  797. bRetVal = false;
  798. }
  799. else
  800. {
  801. // Peek ahead in buffer to see if we have the "no default stats" secret flag set.
  802. int iCheckForStandardStatsInFile = *( int * )buf.PeekGet();
  803. bool bValid = true;
  804. if ( iCheckForStandardStatsInFile != GAMESTATS_STANDARD_NOT_SAVED )
  805. {
  806. //the GAMESTATS_STANDARD_NOT_SAVED flag coincides with user completion time, rewind so the gamestats parser can grab it
  807. bValid = gamestats->m_BasicStats.ParseFromBuffer( buf, version );
  808. }
  809. else
  810. {
  811. // skip over the flag
  812. buf.GetInt();
  813. }
  814. if( !bValid )
  815. {
  816. m_BasicStats.Clear();
  817. }
  818. if( ( buf.TellPut() - buf.TellGet() ) != 0 ) //more data left, must be custom data
  819. {
  820. gamestats->LoadCustomDataFromBuffer( buf );
  821. }
  822. }
  823. }
  824. return bRetVal;
  825. }
  826. else
  827. {
  828. filesystem->RemoveFile( GAMESTATS_LOG_FILE, GAMESTATS_PATHID );
  829. }
  830. return false;
  831. }
  832. #endif // GAME_DLL
  833. bool CBaseGameStats_Driver::Init()
  834. {
  835. const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" );
  836. //standardizing is a good thing
  837. char szLoweredGameDir[256];
  838. Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) );
  839. Q_strlower( szLoweredGameDir );
  840. gamestats = gamestats->OnInit( gamestats, szLoweredGameDir );
  841. //determine constant strings needed for saving and uploading
  842. Q_strncpy( s_szSaveFileName, szLoweredGameDir, sizeof( s_szSaveFileName ) );
  843. Q_strncat( s_szSaveFileName, "_gamestats.dat", sizeof( s_szSaveFileName ) );
  844. Q_strncpy( s_szStatUploadRegistryKeyName, "GameStatsUpload_", sizeof( s_szStatUploadRegistryKeyName ) );
  845. Q_strncat( s_szStatUploadRegistryKeyName, szLoweredGameDir, sizeof( s_szStatUploadRegistryKeyName ) );
  846. gamestats->m_bLoggingToFile = CommandLine()->FindParm( "-gamestatsloggingtofile" ) ? true : false;
  847. gamestats->m_bLogging = CommandLine()->FindParm( "-gamestatslogging" ) ? true : false;
  848. if ( gamestatsuploader )
  849. {
  850. m_bEnabled = gamestatsuploader->IsGameStatsLoggingEnabled();
  851. if ( m_bEnabled )
  852. {
  853. gamestatsuploader->GetPseudoUniqueId( s_szPseudoUniqueID, sizeof( s_szPseudoUniqueID ) );
  854. }
  855. }
  856. ResetData();
  857. #ifdef GAME_DLL
  858. if ( StatsTrackingIsFullyEnabled() )
  859. {
  860. // FIXME: Load m_tLastUpload from registry and save it back out, too
  861. #ifndef DEDICATED
  862. IRegistry *reg = InstanceRegistry( "Steam" );
  863. Assert( reg );
  864. m_tLastUpload = reg->ReadInt( gamestats->GetStatUploadRegistryKeyName(), 0 );
  865. ReleaseInstancedRegistry( reg );
  866. #endif
  867. //load existing stats
  868. gamestats->LoadFromFile();
  869. }
  870. #endif // GAME_DLL
  871. if ( s_szPseudoUniqueID[ 0 ] != 0 )
  872. {
  873. gamestats->Event_Init();
  874. #ifdef GAME_DLL
  875. if ( gamestats->UseOldFormat() )
  876. {
  877. if( gamestats->AutoSave_OnInit() )
  878. gamestats->SaveToFileNOW();
  879. if( gamestats->AutoUpload_OnInit() )
  880. gamestats->UploadStatsFileNOW();
  881. }
  882. #endif
  883. }
  884. else
  885. {
  886. m_bEnabled = false; //unable to generate a pseudo-unique ID, disable tracking
  887. }
  888. return true;
  889. }
  890. void CBaseGameStats_Driver::Shutdown()
  891. {
  892. m_bShuttingDown = true;
  893. gamestats->Event_Shutdown();
  894. if ( gamestats->UseOldFormat() )
  895. {
  896. #ifdef GAME_DLL
  897. if( gamestats->AutoSave_OnShutdown() )
  898. gamestats->SaveToFileNOW();
  899. if( gamestats->AutoUpload_OnShutdown() )
  900. gamestats->UploadStatsFileNOW();
  901. #endif // GAME_DLL
  902. }
  903. else
  904. {
  905. // code path for new format game stats
  906. if ( gamestats->ShouldSendDataOnAppShutdown() )
  907. {
  908. CollectData( STATSEND_APPSHUTDOWN );
  909. SendData();
  910. }
  911. }
  912. if ( FILESYSTEM_INVALID_HANDLE != g_LogFileHandle )
  913. {
  914. filesystem->Close( g_LogFileHandle );
  915. g_LogFileHandle = FILESYSTEM_INVALID_HANDLE;
  916. }
  917. if ( m_pGamestatsData != NULL )
  918. {
  919. #ifdef CLIENT_DLL
  920. engine->SetGamestatsData( NULL );
  921. #endif
  922. delete m_pGamestatsData;
  923. m_pGamestatsData = NULL;
  924. }
  925. }
  926. void CBaseGameStats_Driver::PossibleMapChange( void )
  927. {
  928. #ifdef GAME_DLL
  929. //detect and copy map changes
  930. if ( Q_stricmp( m_PrevMapName.String(), STRING( gpGlobals->mapname ) ) )
  931. {
  932. MEM_ALLOC_CREDIT();
  933. CUtlString PrevMapBackup = m_PrevMapName;
  934. m_PrevMapName = STRING( gpGlobals->mapname );
  935. gamestats->Event_MapChange( PrevMapBackup.String(), STRING( gpGlobals->mapname ) );
  936. if ( gamestats->UseOldFormat() )
  937. {
  938. if( gamestats->AutoSave_OnMapChange() )
  939. gamestats->SaveToFileNOW();
  940. if( gamestats->AutoUpload_OnMapChange() )
  941. gamestats->UploadStatsFileNOW();
  942. }
  943. }
  944. #endif
  945. }
  946. void CBaseGameStats_Driver::LevelInitPreEntity()
  947. {
  948. m_bInLevel = true;
  949. m_bFirstLevel = false;
  950. if ( Q_stricmp( s_szPseudoUniqueID, "unknown" ) == 0 )
  951. {
  952. // "unknown" means this is a dedicated server and we weren't able to generate a unique ID (e.g. Linux server).
  953. // Change the unique ID to be a hash of IP & port. We couldn't do this earlier because IP is not known until level
  954. // init time.
  955. ConVar *hostip = cvar->FindVar( "hostip" );
  956. ConVar *hostport = cvar->FindVar( "hostport" );
  957. if ( hostip && hostport )
  958. {
  959. int crcInput[2];
  960. crcInput[0] = hostip->GetInt();
  961. crcInput[1] = hostport->GetInt();
  962. if ( crcInput[0] && crcInput[1] )
  963. {
  964. CRC32_t crc = CRC32_ProcessSingleBuffer( crcInput, sizeof( crcInput ) );
  965. Q_snprintf( s_szPseudoUniqueID, ARRAYSIZE( s_szPseudoUniqueID ), "H:%x", ( int ) crc );
  966. }
  967. }
  968. }
  969. PossibleMapChange();
  970. m_flPauseStartTime = 0.0f;
  971. m_flLevelStartTime = gpGlobals->realtime;
  972. gamestats->Event_LevelInit();
  973. #ifdef GAME_DLL
  974. if ( gamestats->UseOldFormat() )
  975. {
  976. if( gamestats->AutoSave_OnLevelInit() )
  977. gamestats->SaveToFileNOW();
  978. if( gamestats->AutoUpload_OnLevelInit() )
  979. gamestats->UploadStatsFileNOW();
  980. }
  981. #endif
  982. }
  983. void CBaseGameStats_Driver::LevelShutdownPreEntity()
  984. {
  985. float flElapsed = gpGlobals->realtime - m_flLevelStartTime;
  986. if ( flElapsed < 0.0f )
  987. {
  988. Assert( 0 );
  989. Warning( "EVENT_LEVELSHUTDOWN: with negative elapsed time (rt %f starttime %f)\n", gpGlobals->realtime, m_flLevelStartTime );
  990. flElapsed = 0.0f;
  991. }
  992. //Assert( m_bInLevel ); //so, apparently shutdowns can happen before inits
  993. #ifdef GAME_DLL
  994. if ( m_bInLevel && ( gpGlobals->eLoadType != MapLoad_Background ) )
  995. #else
  996. if ( m_bInLevel )
  997. #endif
  998. {
  999. m_flTotalTimeInLevels += flElapsed;
  1000. m_iNumLevels ++;
  1001. gamestats->Event_LevelShutdown( flElapsed );
  1002. if ( gamestats->UseOldFormat() )
  1003. {
  1004. #ifdef GAME_DLL
  1005. if( gamestats->AutoSave_OnLevelShutdown() )
  1006. gamestats->SaveToFileNOW( true );
  1007. if( gamestats->AutoUpload_OnLevelShutdown() )
  1008. gamestats->UploadStatsFileNOW();
  1009. #endif
  1010. }
  1011. else
  1012. {
  1013. // code path for new format game stats
  1014. CollectData( STATSEND_LEVELSHUTDOWN );
  1015. if ( gamestats->ShouldSendDataOnLevelShutdown() )
  1016. {
  1017. SendData();
  1018. }
  1019. }
  1020. m_bInLevel = false;
  1021. }
  1022. }
  1023. void CBaseGameStats_Driver::OnSave()
  1024. {
  1025. gamestats->Event_SaveGame();
  1026. }
  1027. void CBaseGameStats_Driver::CollectData( StatSendType_t sendType )
  1028. {
  1029. MEM_ALLOC_CREDIT();
  1030. // The base stat system holds memory until "APPSHUTDOWN" which on the Xbox isn't good.
  1031. // When on the Xbox, neither server nor clients collect stats, but the base system still does.
  1032. #ifdef _X360
  1033. return;
  1034. #endif
  1035. CGamestatsData *pGamestatsData = NULL;
  1036. #ifdef GAME_DLL
  1037. // for server, check with the engine to see if there already a gamestats data container registered. (There will be if there is a client
  1038. // running in the same process.)
  1039. pGamestatsData = engine->GetGamestatsData();
  1040. if ( pGamestatsData )
  1041. {
  1042. // use the registered gamestats container, so free the one we allocated
  1043. if ( m_pGamestatsData != NULL )
  1044. {
  1045. delete m_pGamestatsData;
  1046. m_pGamestatsData = NULL;
  1047. }
  1048. }
  1049. else
  1050. {
  1051. pGamestatsData = m_pGamestatsData;
  1052. }
  1053. #else
  1054. pGamestatsData = m_pGamestatsData;
  1055. #endif
  1056. Assert( pGamestatsData || //valid stats
  1057. (!gpGlobals->IsClient() && (sendType == STATSEND_APPSHUTDOWN) && gamestats->ShouldSendDataOnLevelShutdown()) ); //or already sent off stats during level shutdown (now in app shutdown)
  1058. // If a server is told to send stats on STATSEND_APPSHUTDOWN, we need to check to make sure we still have a valid object.
  1059. if ( !pGamestatsData )
  1060. return;
  1061. KeyValues *pKV = pGamestatsData->m_pKVData;
  1062. int iAppID = engine->GetAppID();
  1063. pKV->SetInt( "appid", iAppID );
  1064. switch ( sendType )
  1065. {
  1066. case STATSEND_LEVELSHUTDOWN:
  1067. {
  1068. // make a map node in the KeyValues to use for this level
  1069. char szMap[MAX_PATH+1]="";
  1070. #ifdef CLIENT_DLL
  1071. Q_FileBase( engine->GetLevelName(), szMap, ARRAYSIZE( szMap ) );
  1072. #else
  1073. Q_strncpy( szMap, gpGlobals->mapname.ToCStr(), ARRAYSIZE( szMap ) );
  1074. #endif // CLIENT_DLL
  1075. if ( !szMap[0] )
  1076. {
  1077. Q_FileBase( MapName(), szMap, ARRAYSIZE( szMap ) );
  1078. }
  1079. KeyValues *pKVMap = new KeyValues( "map" );
  1080. pKV->AddSubKey( pKVMap );
  1081. pKVMap->SetString( "mapname", szMap );
  1082. pKV = pKVMap;
  1083. }
  1084. break;
  1085. case STATSEND_APPSHUTDOWN:
  1086. break;
  1087. default:
  1088. Assert( false );
  1089. break;
  1090. }
  1091. // add common data
  1092. pGamestatsData->m_bHaveData |= AddBaseDataForSend( pKV, sendType );
  1093. #if defined(CLIENT_DLL) && !defined(NO_STEAM)
  1094. #if !defined( _GAMECONSOLE )
  1095. // At the end of every map, clients submit their perfdata for the map
  1096. if ( sendType == STATSEND_LEVELSHUTDOWN && pGamestatsData && pGamestatsData->m_bHaveData )
  1097. {
  1098. // dgoodenough - Remove this for now, since this fails on PS3
  1099. // PS3_BUILDFIX
  1100. GetSteamWorksGameStatsClient().AddClientPerfData( pGamestatsData->m_pKVData );
  1101. }
  1102. // Time to reset ClientPerfData so we're ready for future recording. We clear the data
  1103. // whether we submitted or not so that perf data has a clear meaning.
  1104. ResetData();
  1105. // The ResetData call realloced m_pGamestatsData. Need to point pGamestatsData to the new m_pGamestatsData !
  1106. pGamestatsData = m_pGamestatsData;
  1107. #endif
  1108. if ( sendType == STATSEND_LEVELSHUTDOWN )
  1109. {
  1110. #if !defined( _GAMECONSOLE )
  1111. KeyValues *pKVFileStats = new KeyValues( "FileSystemStats" );
  1112. filesystem->GetVPKFileStatisticsKV( pKVFileStats );
  1113. GetSteamWorksGameStatsClient().AddVPKLoadStats( pKVFileStats );
  1114. GetSteamWorksGameStatsClient().AddVPKFileLoadErrorData( pKVFileStats );
  1115. pKVFileStats->deleteThis();
  1116. // On Server Disconnect, session is ended later than when a match restarts. This allows ClientPerfData and VPK stats to report properly
  1117. // Alternately you could place ClientPerfData and VPK in the cs_game_disconnected event, but this event may fire more frequently than we want for CPD/VPK.
  1118. GetSteamWorksGameStatsClient().EndSession();
  1119. GetSteamWorksGameStatsClient().ResetServerState();
  1120. #endif
  1121. }
  1122. #endif
  1123. // add game-specific data
  1124. pGamestatsData->m_bHaveData |= gamestats->AddDataForSend( pKV, sendType );
  1125. }
  1126. #ifndef CLIENT_DLL
  1127. ConVar gamestats_file_output_directory( "gamestats_file_output_directory", "", FCVAR_NONE, "When -gamestatsfileoutputonly is specified, file will be emitted here instead of to modpath\n" );
  1128. #endif
  1129. void CBaseGameStats_Driver::SendData()
  1130. {
  1131. // if we don't own the data container or there's no valid data, nothing to do
  1132. if ( !m_pGamestatsData || !m_pGamestatsData->m_bHaveData )
  1133. return;
  1134. // save the data to a buffer
  1135. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  1136. m_pGamestatsData->m_pKVData->RecursiveSaveToFile( buf, 0 );
  1137. #ifdef CLIENT_DLL
  1138. const bool bOutDirOverriden = false;
  1139. const char *szOverrideDirectory = NULL;
  1140. #else
  1141. const bool bOutDirOverriden = gamestats_file_output_directory.GetRawValue().m_StringLength > 1;
  1142. const char *szOverrideDirectory = gamestats_file_output_directory.GetString();
  1143. #endif
  1144. if ( CommandLine()->FindParm( "-gamestatsfileoutputonly" ) || bOutDirOverriden )
  1145. {
  1146. // write file for debugging
  1147. if ( !bOutDirOverriden ) // if there isn't an override path, put it in the game dir.
  1148. {
  1149. const char szFileName[] = "gamestats.dat";
  1150. filesystem->WriteFile( szFileName, GAMESTATS_PATHID, buf );
  1151. }
  1152. else
  1153. {
  1154. char szPathName[MAX_PATH] = {0};
  1155. V_strncat( szPathName, szOverrideDirectory, MAX_PATH );
  1156. V_AppendSlash( szPathName, MAX_PATH );
  1157. int pathLen = V_strlen( szPathName );
  1158. char szFileName[MAX_PATH] = {0};
  1159. struct tm timeinfo;
  1160. Plat_GetLocalTime( &timeinfo );
  1161. int fileLen = V_snprintf( szFileName, MAX_PATH, "%s__%2d_%3d_%2d_%2d_%2d.txt", s_szPseudoUniqueID,
  1162. timeinfo.tm_year % 100, timeinfo.tm_yday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec );
  1163. Assert( pathLen + fileLen < MAX_PATH ); fileLen;
  1164. V_strncat( szPathName + pathLen, szFileName, MAX_PATH - pathLen );
  1165. filesystem->WriteFile( szPathName, NULL, buf );
  1166. }
  1167. }
  1168. else
  1169. {
  1170. // upload the file to Steam
  1171. if ( gamestatsuploader )
  1172. gamestatsuploader->UploadGameStats( "", 1, buf.TellPut(), buf.Base() );
  1173. }
  1174. ResetData();
  1175. }
  1176. bool CBaseGameStats_Driver::AddBaseDataForSend( KeyValues *pKV, StatSendType_t sendType )
  1177. {
  1178. switch ( sendType )
  1179. {
  1180. case STATSEND_APPSHUTDOWN:
  1181. #ifdef CLIENT_DLL
  1182. if ( m_iNumLevels > 0 )
  1183. {
  1184. // add playtime data
  1185. KeyValues *pKVData = new KeyValues( "playtime" );
  1186. pKVData->SetInt( "TotalLevelTime", m_flTotalTimeInLevels );
  1187. pKVData->SetInt( "NumLevels", m_iNumLevels );
  1188. pKV->AddSubKey( pKVData );
  1189. return true;
  1190. }
  1191. #endif
  1192. break;
  1193. case STATSEND_LEVELSHUTDOWN:
  1194. #ifdef CLIENT_DLL
  1195. const int MIN_PERF_STAT_COLLECT_TIME = 60 * 5; // send perf data if we have collected at least this many seconds
  1196. bool bSend = m_bBufferFull || m_nWriteIndex >= MIN_PERF_STAT_COLLECT_TIME;
  1197. if ( bSend )
  1198. {
  1199. // add perf data
  1200. KeyValues *pKVPerf = new KeyValues( "perfdata" );
  1201. SortFramerate();
  1202. float flAverageFrameRate = AverageStat( &StatsBufferRecord_t::m_flFrameRate );
  1203. float flMinFrameRate = PercentileFramerate( 20 );
  1204. float flMaxFrameRate = PercentileFramerate( 80 );
  1205. float flStandardDeviationFrameRate = StdDevStat( &StatsBufferRecord_t::m_flFrameRate, flAverageFrameRate );
  1206. pKVPerf->SetFloat( "AvgFPS", flAverageFrameRate );
  1207. pKVPerf->SetFloat( "MinFPS", flMinFrameRate );
  1208. pKVPerf->SetFloat( "MaxFPS", flMaxFrameRate );
  1209. pKVPerf->SetFloat( "StdDevFPS", flStandardDeviationFrameRate );
  1210. // if encoded histogram is 0, it just means we have no samples, so we don't really need to report it (save on bandwidth)
  1211. if ( uint64 nHist = m_FpsHistogram.Encode() )
  1212. {
  1213. pKVPerf->SetUint64( "FrameHistAll", nHist );
  1214. }
  1215. if ( uint64 nHistSmoke = m_FpsHistogramGame[ PERF_STATS_SMOKE ].Encode() )
  1216. {
  1217. pKVPerf->SetUint64( "FrameHistGame1", nHistSmoke );
  1218. }
  1219. if ( uint64 nHistBullet = m_FpsHistogramGame[ PERF_STATS_BULLET ].Encode() )
  1220. {
  1221. pKVPerf->SetUint64( "FrameHistGame2", nHistBullet );
  1222. }
  1223. if ( uint64 nHistPlayer = m_FpsHistogramGame[ PERF_STATS_PLAYER ].Encode() )
  1224. {
  1225. pKVPerf->SetUint64( "FrameHistGame3", nHistPlayer );
  1226. }
  1227. if ( uint64 nHistPlayerSpawn = m_FpsHistogramGame[ PERF_STATS_PLAYER_SPAWN ].Encode() )
  1228. {
  1229. pKVPerf->SetUint64( "FrameHistGame4", nHistPlayerSpawn );
  1230. }
  1231. m_FpsHistogram.Reset();
  1232. m_FpsHistogramGame[ PERF_STATS_SMOKE ].Reset();
  1233. m_FpsHistogramGame[ PERF_STATS_BULLET ].Reset();
  1234. m_FpsHistogramGame[ PERF_STATS_PLAYER ].Reset();
  1235. m_FpsHistogramGame[ PERF_STATS_PLAYER_SPAWN ].Reset();
  1236. // Determine the min/avg/max Server Ping and store the results
  1237. float flAverageServerPing = AverageStat( &StatsBufferRecord_t::m_flServerPing );
  1238. pKVPerf->SetFloat( "AvgServerPing", flAverageServerPing );
  1239. // Avg/StdDev main thread time
  1240. float flAvgMainThreadTime = AverageStat( &StatsBufferRecord_t::m_flMainThreadTimeMS );
  1241. float flStdDevMainThreadTime = StdDevStat( &StatsBufferRecord_t::m_flMainThreadTimeMS, flAvgMainThreadTime );
  1242. pKVPerf->SetFloat( "AvgMainThreadTime", flAvgMainThreadTime );
  1243. pKVPerf->SetFloat( "StdDevMainThreadTime", flStdDevMainThreadTime );
  1244. // Avg/StdDev main thread wait time (time waiting for the render thread)
  1245. float flAvgMainThreadWaitTime = AverageStat( &StatsBufferRecord_t::m_flMainThreadWaitTimeMS );
  1246. float flStdDevMainThreadWaitTime = StdDevStat( &StatsBufferRecord_t::m_flMainThreadWaitTimeMS, flAvgMainThreadWaitTime );
  1247. pKVPerf->SetFloat( "AvgMainThreadWaitTime", flAvgMainThreadWaitTime );
  1248. pKVPerf->SetFloat( "StdDevMainThreadWaitTime", flStdDevMainThreadWaitTime );
  1249. // Avg/StdDev render thread time
  1250. float flAvgRenderThreadTime = AverageStat( &StatsBufferRecord_t::m_flRenderThreadTimeMS );
  1251. float flStdDevRenderThreadTime = StdDevStat( &StatsBufferRecord_t::m_flRenderThreadTimeMS, flAvgRenderThreadTime );
  1252. pKVPerf->SetFloat( "AvgRenderThreadTime", flAvgRenderThreadTime );
  1253. pKVPerf->SetFloat( "StdDevRenderThreadTime", flStdDevRenderThreadTime );
  1254. // Avg/StdDev render thread wait time (time waiting for the gpu, ForceHardwareSync)
  1255. float flAvgRenderThreadWaitTime = AverageStat( &StatsBufferRecord_t::m_flRenderThreadWaitTimeMS );
  1256. float flStdDevRenderThreadWaitTime = StdDevStat( &StatsBufferRecord_t::m_flRenderThreadWaitTimeMS, flAvgRenderThreadWaitTime );
  1257. pKVPerf->SetFloat( "AvgRenderThreadWaitTime", flAvgRenderThreadWaitTime );
  1258. pKVPerf->SetFloat( "StdDevRenderThreadWaitTime", flStdDevRenderThreadWaitTime );
  1259. int nCPUThrottlingSamples = m_nCPUUnthrottledCount + m_nCPUThrottle1Count + m_nCPUThrottle2Count;
  1260. if ( nCPUThrottlingSamples )
  1261. {
  1262. float fThrottledToLevel1 = m_nCPUThrottle1Count * 100.0f / nCPUThrottlingSamples;
  1263. float fThrottledToLevel2 = m_nCPUThrottle2Count * 100.0f / nCPUThrottlingSamples;
  1264. pKVPerf->SetFloat( "PercentageCPUThrottledToLevel1", fThrottledToLevel1 );
  1265. pKVPerf->SetFloat( "PercentageCPUThrottledToLevel2", fThrottledToLevel2 );
  1266. }
  1267. pKV->AddSubKey( pKVPerf );
  1268. }
  1269. // Only keeping track of using voice in a multiplayer game
  1270. if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
  1271. {
  1272. pKV->SetInt( "UsedVoice", m_bDidVoiceChat );
  1273. }
  1274. extern ConVar closecaption;
  1275. pKV->SetInt( "Caption", closecaption.GetInt() );
  1276. // Set language to "unknown", then check to see if we can obtain the language.
  1277. char language[64] = "unknown";
  1278. if ( engine )
  1279. {
  1280. engine->GetUILanguage( language, sizeof( language ) );
  1281. pKV->SetInt( "BugSub", engine->GetBugSubmissionCount() );
  1282. engine->ClearBugSubmissionCount();
  1283. }
  1284. pKV->SetString( "Language", language );
  1285. if ( materials )
  1286. {
  1287. const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard();
  1288. pKV->SetBool( "Windowed", config.Windowed() );
  1289. pKV->SetBool( "WindowedNoBorder", config.NoWindowBorder() );
  1290. }
  1291. // We want to know if a user is running with cheats on so that we can filter out their data
  1292. // depending on what we're looking for.
  1293. if ( NULL != sv_cheats )
  1294. {
  1295. pKV->SetInt( "Cheats", sv_cheats->GetInt() );
  1296. }
  1297. int mapTime = gpGlobals->realtime - m_flLevelStartTime;
  1298. pKV->SetInt( "MapTime", mapTime );
  1299. // reset buffer for next level. This is also done by ResetData().
  1300. m_bBufferFull = false;
  1301. m_nWriteIndex = 0;
  1302. // Reset last realtime timestamp so that we don't compute the fps
  1303. // the first time we re-enter UpdatePerfStats
  1304. m_flLastRealTime = -1.0f;
  1305. m_flLastSampleTime = -1.0f;
  1306. return bSend;
  1307. #endif
  1308. break;
  1309. }
  1310. return false;
  1311. }
  1312. void CBaseGameStats_Driver::ResetData()
  1313. {
  1314. #ifdef GAME_DLL
  1315. // 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
  1316. if ( engine->GetGamestatsData() != NULL )
  1317. return;
  1318. #endif
  1319. MEM_ALLOC_CREDIT();
  1320. if ( m_pGamestatsData != NULL )
  1321. {
  1322. delete m_pGamestatsData;
  1323. m_pGamestatsData = NULL;
  1324. }
  1325. m_bBufferFull = false;
  1326. m_nWriteIndex = 0;
  1327. m_bDidVoiceChat = false;
  1328. // Reset the CPU throttling statistics.
  1329. m_nCPUUnthrottledCount = 0;
  1330. m_nCPUThrottle1Count = 0;
  1331. m_nCPUThrottle2Count = 0;
  1332. // Track when we last received data.
  1333. m_lastCPUFrequencyTimestamp = 0.0;
  1334. m_pGamestatsData = new CGamestatsData();
  1335. KeyValues *pKV = m_pGamestatsData->m_pKVData;
  1336. pKV->SetInt( "IsPc", IsPC() );
  1337. pKV->SetInt( "version", GAMESTATS_VERSION );
  1338. pKV->SetString( "srcid", s_szPseudoUniqueID );
  1339. #ifdef CLIENT_DLL
  1340. const CPUInformation &cpu = GetCPUInformation();
  1341. OverWriteCharsWeHate( cpu.m_szProcessorID );
  1342. pKV->SetString( "CPUID", cpu.m_szProcessorID );
  1343. pKV->SetFloat( "CPUGhz", cpu.m_Speed * ( 1.0 / 1.0e9 ) );
  1344. pKV->SetUint64( "CPUModel", cpu.m_nModel );
  1345. pKV->SetUint64( "CPUFeatures0", cpu.m_nFeatures[ 0 ] );
  1346. pKV->SetUint64( "CPUFeatures1", cpu.m_nFeatures[ 1 ] );
  1347. pKV->SetUint64( "CPUFeatures2", cpu.m_nFeatures[ 2 ] );
  1348. pKV->SetInt( "NumCores", cpu.m_nPhysicalProcessors );
  1349. MaterialAdapterInfo_t gpu;
  1350. materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), gpu );
  1351. CMatRenderContextPtr pRenderContext( materials );
  1352. int dest_width,dest_height;
  1353. pRenderContext->GetRenderTargetDimensions( dest_width, dest_height );
  1354. if ( gpu.m_pDriverName )
  1355. {
  1356. OverWriteCharsWeHate( gpu.m_pDriverName );
  1357. }
  1358. pKV->SetString( "GPUDrv", SafeString( gpu.m_pDriverName ) );
  1359. pKV->SetInt( "GPUVendor", gpu.m_VendorID );
  1360. pKV->SetInt( "GPUDeviceID", gpu.m_DeviceID );
  1361. pKV->SetString( "GPUDriverVersion", CFmtStr( "%d.%d", gpu.m_nDriverVersionHigh, gpu.m_nDriverVersionLow ) );
  1362. pKV->SetInt( "DxLvl", g_pMaterialSystemHardwareConfig->GetDXSupportLevel() );
  1363. pKV->SetInt( "Width", dest_width );
  1364. pKV->SetInt( "Height", dest_height );
  1365. pKV->SetInt( "IsSplitScreen", VGui_IsSplitScreen() );
  1366. engine->SetGamestatsData( m_pGamestatsData );
  1367. #endif
  1368. }
  1369. void CBaseGameStats_Driver::OnRestore()
  1370. {
  1371. PossibleMapChange();
  1372. gamestats->Event_LoadGame();
  1373. }
  1374. void CBaseGameStats_Driver::FrameUpdatePostEntityThink()
  1375. {
  1376. bool bGamePaused = ( gpGlobals->frametime == 0.0f );
  1377. if ( !m_bInLevel )
  1378. {
  1379. m_flPauseStartTime = 0.0f;
  1380. }
  1381. else if ( m_bGamePaused != bGamePaused )
  1382. {
  1383. if ( bGamePaused )
  1384. {
  1385. m_flPauseStartTime = gpGlobals->realtime;
  1386. }
  1387. else if ( m_flPauseStartTime != 0.0f )
  1388. {
  1389. float flPausedTime = gpGlobals->realtime - m_flPauseStartTime;
  1390. if ( flPausedTime < 0.0f )
  1391. {
  1392. Assert( 0 );
  1393. Warning( "Game paused time showing up negative (rt %f pausestart %f)\n", gpGlobals->realtime, m_flPauseStartTime );
  1394. flPausedTime = 0.0f;
  1395. }
  1396. // Remove this from global time counters
  1397. // Msg( "Pause: adding %f to level starttime\n", flPausedTime );
  1398. m_flLevelStartTime += flPausedTime;
  1399. m_flPauseStartTime = 0.0f;
  1400. // Msg( "Paused for %.2f seconds\n", flPausedTime );
  1401. }
  1402. m_bGamePaused = bGamePaused;
  1403. }
  1404. }
  1405. bool StatsTrackingIsFullyEnabled( void )
  1406. {
  1407. return CBGSDriver.m_bEnabled && gamestats->StatTrackingEnabledForMod();
  1408. }
  1409. void CBaseGameStats::Clear( void )
  1410. {
  1411. #ifdef GAME_DLL
  1412. gamestats->m_BasicStats.Clear();
  1413. #endif
  1414. }
  1415. //-----------------------------------------------------------------------------
  1416. // Nukes any dangerous characters and replaces w/space char
  1417. //-----------------------------------------------------------------------------
  1418. void OverWriteCharsWeHate( char *pStr )
  1419. {
  1420. while( *pStr )
  1421. {
  1422. switch( *pStr )
  1423. {
  1424. case '\n':
  1425. case '\r':
  1426. case '\\':
  1427. case '\"':
  1428. case '\'':
  1429. case '\032':
  1430. case ';':
  1431. *pStr = ' ';
  1432. }
  1433. pStr++;
  1434. }
  1435. }
  1436. #ifdef GAME_DLL
  1437. void CBaseGameStats::SetSteamStatistic( bool bUsingSteam )
  1438. {
  1439. if( CBGSDriver.m_bFirstLevel )
  1440. {
  1441. m_BasicStats.m_Summary.m_bSteam = bUsingSteam;
  1442. }
  1443. if( CBGSDriver.m_bInLevel )
  1444. {
  1445. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1446. map->m_bSteam = bUsingSteam;
  1447. }
  1448. m_BasicStats.m_bSteam = bUsingSteam;
  1449. }
  1450. void CBaseGameStats::SetCyberCafeStatistic( bool bIsCyberCafeUser )
  1451. {
  1452. if( CBGSDriver.m_bFirstLevel )
  1453. {
  1454. m_BasicStats.m_Summary.m_bCyberCafe = bIsCyberCafeUser;
  1455. }
  1456. if( CBGSDriver.m_bInLevel )
  1457. {
  1458. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1459. map->m_bCyberCafe = bIsCyberCafeUser;
  1460. }
  1461. m_BasicStats.m_bCyberCafe = bIsCyberCafeUser;
  1462. }
  1463. void CBaseGameStats::SetHDRStatistic( bool bHDREnabled )
  1464. {
  1465. if( bHDREnabled )
  1466. {
  1467. if( CBGSDriver.m_bInLevel )
  1468. {
  1469. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1470. ++map->m_nHDR;
  1471. }
  1472. if( CBGSDriver.m_bFirstLevel )
  1473. {
  1474. ++m_BasicStats.m_Summary.m_nHDR;
  1475. }
  1476. }
  1477. }
  1478. void CBaseGameStats::SetCaptionsStatistic( bool bClosedCaptionsEnabled )
  1479. {
  1480. if( CBGSDriver.m_bInLevel )
  1481. {
  1482. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1483. ++map->m_nCaptions;
  1484. }
  1485. if( CBGSDriver.m_bFirstLevel )
  1486. {
  1487. ++m_BasicStats.m_Summary.m_nCaptions;
  1488. }
  1489. }
  1490. void CBaseGameStats::SetSkillStatistic( int iSkillSetting )
  1491. {
  1492. int skill = clamp( iSkillSetting, 1, 3 ) - 1;
  1493. if( CBGSDriver.m_bInLevel )
  1494. {
  1495. BasicGameStatsRecord_t *map = m_BasicStats.FindOrAddRecordForMap( CBGSDriver.m_PrevMapName.String() );
  1496. ++map->m_nSkill[ skill ];
  1497. }
  1498. if ( CBGSDriver. m_bFirstLevel )
  1499. {
  1500. ++m_BasicStats.m_Summary.m_nSkill[ skill ];
  1501. }
  1502. }
  1503. void CBaseGameStats::SetDXLevelStatistic( int iDXLevel )
  1504. {
  1505. m_BasicStats.m_nDXLevel = iDXLevel;
  1506. }
  1507. static void CC_ResetGameStats( const CCommand &args )
  1508. {
  1509. #if defined ( TF_DLL ) || defined ( TF_CLIENT_DLL )
  1510. // Disabled this until we fix the TF gamestat crashes that result
  1511. return;
  1512. #endif
  1513. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  1514. return;
  1515. gamestats->Clear();
  1516. gamestats->SaveToFileNOW();
  1517. gamestats->StatsLog( "CC_ResetGameStats : Server cleared game stats\n" );
  1518. }
  1519. static ConCommand resetGameStats("_resetgamestats", CC_ResetGameStats, "Erases current game stats and writes out a blank stats file", 0 );
  1520. class CPointGamestatsCounter : public CPointEntity
  1521. {
  1522. public:
  1523. DECLARE_CLASS( CPointGamestatsCounter, CPointEntity );
  1524. DECLARE_DATADESC();
  1525. CPointGamestatsCounter();
  1526. protected:
  1527. void InputSetName( inputdata_t &inputdata );
  1528. void InputIncrement( inputdata_t &inputdata );
  1529. void InputEnable( inputdata_t &inputdata );
  1530. void InputDisable( inputdata_t &inputdata );
  1531. private:
  1532. string_t m_strStatisticName;
  1533. bool m_bDisabled;
  1534. };
  1535. BEGIN_DATADESC( CPointGamestatsCounter )
  1536. DEFINE_KEYFIELD( m_strStatisticName, FIELD_STRING, "Name" ),
  1537. DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ),
  1538. // Inputs
  1539. DEFINE_INPUTFUNC( FIELD_STRING, "SetName", InputSetName ),
  1540. DEFINE_INPUTFUNC( FIELD_FLOAT, "Increment", InputIncrement ),
  1541. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  1542. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  1543. END_DATADESC()
  1544. LINK_ENTITY_TO_CLASS( point_gamestats_counter, CPointGamestatsCounter )
  1545. CPointGamestatsCounter::CPointGamestatsCounter() :
  1546. m_strStatisticName( NULL_STRING ),
  1547. m_bDisabled( false )
  1548. {
  1549. }
  1550. //-----------------------------------------------------------------------------
  1551. // Purpose: Changes name of statistic
  1552. //-----------------------------------------------------------------------------
  1553. void CPointGamestatsCounter::InputSetName( inputdata_t &inputdata )
  1554. {
  1555. m_strStatisticName = inputdata.value.StringID();
  1556. }
  1557. //-----------------------------------------------------------------------------
  1558. // Purpose: Changes name of statistic
  1559. //-----------------------------------------------------------------------------
  1560. void CPointGamestatsCounter::InputIncrement( inputdata_t &inputdata )
  1561. {
  1562. if ( m_bDisabled )
  1563. return;
  1564. if ( NULL_STRING == m_strStatisticName )
  1565. {
  1566. DevMsg( 1, "CPointGamestatsCounter::InputIncrement: No stat name specified for point_gamestats_counter @%f, %f, %f [ent index %d]\n",
  1567. GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, entindex() );
  1568. return;
  1569. }
  1570. gamestats->Event_IncrementCountedStatistic( GetAbsOrigin(), STRING( m_strStatisticName ), inputdata.value.Float() );
  1571. }
  1572. void CPointGamestatsCounter::InputEnable( inputdata_t &inputdata )
  1573. {
  1574. m_bDisabled = false;
  1575. }
  1576. void CPointGamestatsCounter::InputDisable( inputdata_t &inputdata )
  1577. {
  1578. m_bDisabled = true;
  1579. }
  1580. #endif // GAME_DLL