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.

820 lines
25 KiB

  1. //====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======
  2. //
  3. // Purpose: Uploads KeyValue stats to the new SteamWorks gamestats system.
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #if !defined( _GAMECONSOLE )
  8. #include "cdll_int.h"
  9. #include "tier2/tier2.h"
  10. #include <time.h>
  11. #ifdef GAME_DLL
  12. #include "gameinterface.h"
  13. #include "steamworks_gamestats_server.h"
  14. #elif CLIENT_DLL
  15. #include "c_playerresource.h"
  16. #include "steamworks_gamestats_client.h"
  17. #endif
  18. #include "steam/isteamutils.h"
  19. #include "steamworks_gamestats.h"
  20. #include "achievementmgr.h"
  21. // NOTE: This has to be the last file included!
  22. #include "tier0/memdbgon.h"
  23. // This is used to replicate our server id to the client so that client data can be associated with the server's.
  24. ConVar steamworks_sessionid_server( "steamworks_sessionid_server", "0", FCVAR_REPLICATED | FCVAR_HIDDEN, "The server session ID for the new steamworks gamestats." );
  25. // This is used to show when the steam works is uploading stats
  26. #define steamworks_show_uploads 0
  27. // This is a stop gap to disable the steam works game stats from ever initializing in the event that we need it
  28. #define steamworks_stats_disable 0
  29. // This is used to control when the stats get uploaded. If we wait until the end of the session, we miss out on all the stats if the server crashes. If we upload as we go, then we will have the data
  30. #define steamworks_immediate_upload 1
  31. //-----------------------------------------------------------------------------
  32. // Purpose: Returns the time since the epoch
  33. //-----------------------------------------------------------------------------
  34. time_t CSteamWorksGameStatsUploader::GetTimeSinceEpoch( void )
  35. {
  36. #if !defined( NO_STEAM )
  37. if ( steamapicontext && steamapicontext->SteamUtils() )
  38. return steamapicontext->SteamUtils()->GetServerRealTime();
  39. else
  40. #endif
  41. {
  42. // Default to system time.
  43. time_t aclock;
  44. time( &aclock );
  45. return aclock;
  46. }
  47. }
  48. //-----------------------------------------------------------------------------
  49. // Purpose: Constructor. Sets up the steam callbacks accordingly depending on client/server dll
  50. //-----------------------------------------------------------------------------
  51. CSteamWorksGameStatsUploader::CSteamWorksGameStatsUploader( const char *pszSystemName, const char *pszSessionConVarName ) : CAutoGameSystemPerFrame( pszSystemName )
  52. {
  53. m_sSessionConVarName = pszSessionConVarName;
  54. m_pSessionConVar = NULL;
  55. }
  56. CSteamWorksGameStatsUploader::~CSteamWorksGameStatsUploader()
  57. {
  58. if ( m_pSessionConVar!=NULL )
  59. {
  60. delete m_pSessionConVar;
  61. }
  62. }
  63. //-----------------------------------------------------------------------------
  64. // Purpose: Reset uploader state.
  65. //-----------------------------------------------------------------------------
  66. void CSteamWorksGameStatsUploader::Reset()
  67. {
  68. ClearSessionID();
  69. m_ServiceTicking = false;
  70. m_LastServiceTick = 0;
  71. m_SessionIDRequestUnsent = false;
  72. m_SessionIDRequestPending = false;
  73. m_SessionID = 0;
  74. m_UploadedStats = false;
  75. m_bCollectingAny = false;
  76. m_bCollectingDetails = false;
  77. m_UserID = 0;
  78. m_iAppID = 0;
  79. //Note: Reset() no longer clears serverIP, Map, or HostName data
  80. //Previously, this was appropriate because sessions only began at server connect/map change
  81. //Today, sessions are generated every time a match restarts, leading to OGS sessions without server info
  82. //Instead, we only nuke/initialize during Init(), and selectively wherever ResetServerState() is called.
  83. m_StartTime = 0;
  84. m_EndTime = 0;
  85. m_ActiveSession.Reset();
  86. m_iServerConnectCount = 0;
  87. for ( int i=0; i<m_StatsToSend.Count(); ++i )
  88. {
  89. m_StatsToSend[i]->deleteThis();
  90. }
  91. m_StatsToSend.RemoveAll();
  92. }
  93. //------------------------------------------------------------------------------------------
  94. // Purpose: Supplemental reset function to return server IP/host name/map to default state
  95. // during Init(), "cs_game_disconnected" event, and available if relevant for other events.
  96. //------------------------------------------------------------------------------------------
  97. void CSteamWorksGameStatsUploader::ResetServerState()
  98. {
  99. m_iServerIP = 0;
  100. memset( m_pzServerIP, 0, ARRAYSIZE(m_pzServerIP) );
  101. memset( m_pzMapStart, 0, ARRAYSIZE(m_pzMapStart) );
  102. memset( m_pzHostName, 0, ARRAYSIZE(m_pzHostName) );
  103. }
  104. //-----------------------------------------------------------------------------
  105. // Purpose: Init function from CAutoGameSystemPerFrame and must return true.
  106. //-----------------------------------------------------------------------------
  107. bool CSteamWorksGameStatsUploader::Init()
  108. {
  109. if ( !m_sSessionConVarName.IsEmpty() )
  110. {
  111. m_pSessionConVar = new ConVarRef( m_sSessionConVarName.Get() );
  112. if ( !m_pSessionConVar->IsValid() )
  113. {
  114. delete m_pSessionConVar;
  115. m_pSessionConVar = NULL;
  116. }
  117. }
  118. else
  119. {
  120. m_pSessionConVar = NULL;
  121. }
  122. Reset();
  123. ResetServerState();
  124. ListenForGameEvent( "server_spawn" );
  125. ListenForGameEvent( "hostname_changed" );
  126. #ifdef CLIENT_DLL
  127. ListenForGameEvent( "player_changename" );
  128. #endif
  129. return true;
  130. }
  131. //-----------------------------------------------------------------------------
  132. // Purpose: Event handler for gathering basic info as well as ending sessions.
  133. //-----------------------------------------------------------------------------
  134. void CSteamWorksGameStatsUploader::FireGameEvent( IGameEvent *event )
  135. {
  136. if ( !event )
  137. {
  138. return;
  139. }
  140. const char *pEventName = event->GetName();
  141. if ( FStrEq( "hostname_changed", pEventName ) )
  142. {
  143. const char *pzHostname = event->GetString( "hostname" );
  144. if ( pzHostname )
  145. {
  146. V_strncpy( m_pzHostName, pzHostname, sizeof( m_pzHostName ) );
  147. }
  148. else
  149. {
  150. V_strncpy( m_pzHostName, "No Host Name", sizeof( m_pzHostName ) );
  151. }
  152. }
  153. else if ( FStrEq( "server_spawn", pEventName ) )
  154. {
  155. const char *pzAddress = event->GetString( "address" );
  156. if ( pzAddress )
  157. {
  158. V_snprintf( m_pzServerIP, ARRAYSIZE(m_pzServerIP), "%s:%d", pzAddress, event->GetInt( "port" ) );
  159. ServerAddressToInt();
  160. }
  161. else
  162. {
  163. V_strncpy( m_pzServerIP, "No Server Address", sizeof( m_pzServerIP ) );
  164. m_iServerIP = 0;
  165. }
  166. const char *pzHostname = event->GetString( "hostname" );
  167. if ( pzHostname )
  168. {
  169. V_strncpy( m_pzHostName, pzHostname, sizeof( m_pzHostName ) );
  170. }
  171. else
  172. {
  173. V_strncpy( m_pzHostName, "No Host Name", sizeof( m_pzHostName ) );
  174. }
  175. const char *pzMapName = event->GetString( "mapname" );
  176. if ( pzMapName )
  177. {
  178. V_strncpy( m_pzMapStart, pzMapName, sizeof( m_pzMapStart ) );
  179. }
  180. else
  181. {
  182. V_strncpy( m_pzMapStart, "No Map Name", sizeof( m_pzMapStart ) );
  183. }
  184. m_bPassword = event->GetBool( "password" );
  185. }
  186. }
  187. //-----------------------------------------------------------------------------
  188. // Purpose: Requests a session ID from steam.
  189. //-----------------------------------------------------------------------------
  190. EResult CSteamWorksGameStatsUploader::RequestSessionID()
  191. {
  192. // If we have disabled steam works game stats, don't request ids.
  193. if ( steamworks_stats_disable )
  194. {
  195. DevMsg( "Steamworks Stats: %s No stats collection because steamworks_stats_disable is set to 1.\n", Name() );
  196. return k_EResultAccessDenied;
  197. }
  198. // Do not continue if we already have a session id.
  199. // We must end a session before we can begin a new one.
  200. if ( m_SessionID )
  201. {
  202. return k_EResultOK;
  203. }
  204. // Do not continue if we are waiting a server response for this session request.
  205. if ( m_SessionIDRequestPending )
  206. {
  207. return k_EResultPending;
  208. }
  209. // If a request is unsent, it will be sent as soon as the steam API is available.
  210. m_SessionIDRequestUnsent = true;
  211. // Turn on polling.
  212. m_ServiceTicking = true;
  213. // If we can't use Steam at the moment, we need to wait.
  214. if ( !AccessToSteamAPI() )
  215. {
  216. return k_EResultNoConnection;
  217. }
  218. m_SteamWorksInterface = GetInterface();
  219. if ( m_SteamWorksInterface )
  220. {
  221. int accountType = GetGameStatsAccountType();
  222. DevMsg( "Steamworks Stats: %s Requesting session id.\n", Name() );
  223. m_SessionIDRequestUnsent = false;
  224. m_SessionIDRequestPending = true;
  225. // This initiates a callback that will get us our session ID.
  226. // Callback: Steam_OnSteamSessionInfoIssued
  227. SteamAPICall_t hSteamAPICall = m_SteamWorksInterface->GetNewSession( accountType, m_UserID, m_iAppID, GetTimeSinceEpoch() );
  228. m_CallbackSteamSessionInfoIssued.Set( hSteamAPICall, this, &CSteamWorksGameStatsUploader::Steam_OnSteamSessionInfoIssued );
  229. }
  230. return k_EResultOK;
  231. }
  232. //-----------------------------------------------------------------------------
  233. // Purpose: Clears our session id and session id convar.
  234. //-----------------------------------------------------------------------------
  235. void CSteamWorksGameStatsUploader::ClearSessionID()
  236. {
  237. m_SessionID = 0;
  238. if ( m_pSessionConVar != NULL )
  239. {
  240. m_pSessionConVar->SetValue( 0 );
  241. }
  242. }
  243. #ifndef NO_STEAM
  244. //-----------------------------------------------------------------------------
  245. // Purpose: The steam callback used to get our session IDs.
  246. //-----------------------------------------------------------------------------
  247. void CSteamWorksGameStatsUploader::Steam_OnSteamSessionInfoIssued( GameStatsSessionIssued_t *pGameStatsSessionInfo, bool bError )
  248. {
  249. OnSteamSessionIssued( pGameStatsSessionInfo, bError );
  250. }
  251. void CSteamWorksGameStatsUploader::OnSteamSessionIssued( GameStatsSessionIssued_t *pGameStatsSessionInfo, bool bError )
  252. {
  253. if ( !m_SessionIDRequestPending )
  254. {
  255. // There is no request outstanding.
  256. return;
  257. }
  258. m_SessionIDRequestPending = false;
  259. if ( !pGameStatsSessionInfo )
  260. {
  261. // Empty callback!
  262. ClearSessionID();
  263. return;
  264. }
  265. if ( pGameStatsSessionInfo->m_eResult != k_EResultOK )
  266. {
  267. DevMsg( "Steamworks Stats: %s session id not available.\n", Name() );
  268. m_SessionIDRequestUnsent = true; // Queue to re-request a session ID.
  269. ClearSessionID();
  270. return;
  271. }
  272. DevMsg( "Steamworks Stats: %s Received CLIENT session id: %llu\n", Name(), pGameStatsSessionInfo->m_ulSessionID );
  273. m_StartTime = GetTimeSinceEpoch();
  274. m_SessionID = pGameStatsSessionInfo->m_ulSessionID;
  275. m_bCollectingAny = pGameStatsSessionInfo->m_bCollectingAny;
  276. m_bCollectingDetails = pGameStatsSessionInfo->m_bCollectingDetails;
  277. char sessionIDString[ 32 ];
  278. Q_snprintf( sessionIDString, sizeof( sessionIDString ), "%llu", m_SessionID );
  279. if ( m_pSessionConVar != NULL )
  280. {
  281. m_pSessionConVar->SetValue( sessionIDString );
  282. }
  283. }
  284. //-----------------------------------------------------------------------------
  285. // Purpose: The steam callback to notify us that we've submitted stats.
  286. //-----------------------------------------------------------------------------
  287. void CSteamWorksGameStatsUploader::Steam_OnSteamSessionInfoClosed( GameStatsSessionClosed_t *pGameStatsSessionInfo, bool bError )
  288. {
  289. OnSteamSessionClosed( pGameStatsSessionInfo, bError );
  290. }
  291. void CSteamWorksGameStatsUploader::OnSteamSessionClosed( GameStatsSessionClosed_t *pGameStatsSessionInfo, bool bError )
  292. {
  293. if ( !m_UploadedStats )
  294. return;
  295. m_UploadedStats = false;
  296. }
  297. //-----------------------------------------------------------------------------
  298. // Purpose: Per frame think. Used to periodically check if we have queued operations.
  299. // For example: we may request a session id before steam is ready.
  300. //-----------------------------------------------------------------------------
  301. #if defined ( GAME_DLL )
  302. void CSteamWorksGameStatsUploader::FrameUpdatePostEntityThink()
  303. {
  304. if ( !m_ServiceTicking )
  305. return;
  306. if ( gpGlobals->realtime - m_LastServiceTick < 3 )
  307. return;
  308. m_LastServiceTick = gpGlobals->realtime;
  309. if ( !AccessToSteamAPI() )
  310. return;
  311. // Try to resend our request.
  312. if ( m_SessionIDRequestUnsent )
  313. {
  314. RequestSessionID();
  315. return;
  316. }
  317. // If we had nothing to resend, stop ticking.
  318. m_ServiceTicking = false;
  319. }
  320. #endif // GAME_DLL
  321. #endif // !NO_STEAM
  322. //-----------------------------------------------------------------------------
  323. // Purpose: Opens a session: requests the session id, etc.
  324. //-----------------------------------------------------------------------------
  325. void CSteamWorksGameStatsUploader::StartSession()
  326. {
  327. RequestSessionID();
  328. }
  329. //-----------------------------------------------------------------------------
  330. // Purpose: Completes a session for the given type.
  331. //-----------------------------------------------------------------------------
  332. void CSteamWorksGameStatsUploader::EndSession()
  333. {
  334. m_EndTime = GetTimeSinceEpoch();
  335. if ( !m_SessionID )
  336. {
  337. // No session to end.
  338. return;
  339. }
  340. m_SteamWorksInterface = GetInterface();
  341. if ( m_SteamWorksInterface )
  342. {
  343. DevMsg( "Steamworks Stats: %s Ending CLIENT session id: %llu\n", Name(), m_SessionID );
  344. // Flush any stats that haven't been sent yet.
  345. FlushStats();
  346. // Always need some data in the session row or we'll crash steam.
  347. WriteSessionRow();
  348. SteamAPICall_t hSteamAPICall = m_SteamWorksInterface->EndSession( m_SessionID, m_EndTime, 0 );
  349. m_CallbackSteamSessionInfoClosed.Set( hSteamAPICall, this, &CSteamWorksGameStatsUploader::Steam_OnSteamSessionInfoClosed );
  350. Reset();
  351. }
  352. }
  353. //-----------------------------------------------------------------------------
  354. // Purpose: Flush any unsent rows.
  355. //-----------------------------------------------------------------------------
  356. void CSteamWorksGameStatsUploader::FlushStats()
  357. {
  358. for ( int i=0; i<m_StatsToSend.Count(); ++i )
  359. {
  360. ParseKeyValuesAndSendStats( m_StatsToSend[i] );
  361. m_StatsToSend[i]->deleteThis();
  362. }
  363. m_StatsToSend.RemoveAll();
  364. }
  365. //-----------------------------------------------------------------------------
  366. // Purpose: Uploads any end of session rows.
  367. //-----------------------------------------------------------------------------
  368. void CSteamWorksGameStatsUploader::WriteSessionRow()
  369. {
  370. m_SteamWorksInterface = GetInterface();
  371. if ( !m_SteamWorksInterface )
  372. return;
  373. // The Session row is common to both client and server sessions.
  374. // It enables keying to other tables.
  375. m_SteamWorksInterface->AddSessionAttributeInt( m_SessionID, "AppID", m_iAppID );
  376. m_SteamWorksInterface->AddSessionAttributeInt( m_SessionID, "StartTime", m_StartTime );
  377. m_SteamWorksInterface->AddSessionAttributeInt( m_SessionID, "EndTime", m_EndTime );
  378. }
  379. //-----------------------------------------------------------------------------
  380. // DATA ACCESS UTILITIES
  381. //-----------------------------------------------------------------------------
  382. //-----------------------------------------------------------------------------
  383. // Purpose: Verifies that we have a valid interface and will attempt to obtain a new one if we don't.
  384. //-----------------------------------------------------------------------------
  385. bool CSteamWorksGameStatsUploader::VerifyInterface( void )
  386. {
  387. if ( !m_SteamWorksInterface )
  388. {
  389. m_SteamWorksInterface = GetInterface();
  390. if ( !m_SteamWorksInterface )
  391. {
  392. return false;
  393. }
  394. }
  395. return true;
  396. }
  397. //-----------------------------------------------------------------------------
  398. // Purpose: Wrapper function to write an int32 to a table given the row name
  399. //-----------------------------------------------------------------------------
  400. EResult CSteamWorksGameStatsUploader::WriteIntToTable( const int value, uint64 iTableID, const char *pzRow )
  401. {
  402. if ( !VerifyInterface() )
  403. return k_EResultNoConnection;
  404. return m_SteamWorksInterface->AddRowAttributeInt( iTableID, pzRow, value );
  405. }
  406. //-----------------------------------------------------------------------------
  407. // Purpose: Wrapper function to write an int64 to a table given the row name
  408. //-----------------------------------------------------------------------------
  409. EResult CSteamWorksGameStatsUploader::WriteInt64ToTable( const uint64 value, uint64 iTableID, const char *pzRow )
  410. {
  411. if ( !VerifyInterface() )
  412. return k_EResultNoConnection;
  413. return m_SteamWorksInterface->AddRowAttributeInt64( iTableID, pzRow, value );
  414. }
  415. //-----------------------------------------------------------------------------
  416. // Purpose: Wrapper function to write an float to a table given the row name
  417. //-----------------------------------------------------------------------------
  418. EResult CSteamWorksGameStatsUploader::WriteFloatToTable( const float value, uint64 iTableID, const char *pzRow )
  419. {
  420. if ( !VerifyInterface() )
  421. return k_EResultNoConnection;
  422. return m_SteamWorksInterface->AddRowAttributeFloat( iTableID, pzRow, value );
  423. }
  424. //-----------------------------------------------------------------------------
  425. // Purpose: Wrapper function to write an string to a table given the row name
  426. //-----------------------------------------------------------------------------
  427. EResult CSteamWorksGameStatsUploader::WriteStringToTable( const char *value, uint64 iTableID, const char *pzRow )
  428. {
  429. if ( !VerifyInterface() )
  430. return k_EResultNoConnection;
  431. return m_SteamWorksInterface->AddRowAtributeString( iTableID, pzRow, value );
  432. }
  433. //-----------------------------------------------------------------------------
  434. // STEAM ACCESS UTILITIES
  435. //-----------------------------------------------------------------------------
  436. //-----------------------------------------------------------------------------
  437. // Purpose: Determines if the system can connect to steam
  438. //-----------------------------------------------------------------------------
  439. bool CSteamWorksGameStatsUploader::AccessToSteamAPI( void )
  440. {
  441. #if !defined( NO_STEAM )
  442. #ifdef GAME_DLL
  443. return ( steamgameserverapicontext && steamgameserverapicontext->SteamGameServer() && steamgameserverapicontext->SteamClient() && steamgameserverapicontext->SteamGameServerUtils() );
  444. #elif CLIENT_DLL
  445. return ( steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUser()->BLoggedOn() && steamapicontext->SteamFriends() && steamapicontext->SteamMatchmaking() );
  446. #endif
  447. #endif
  448. return false;
  449. }
  450. //-----------------------------------------------------------------------------
  451. // Purpose: There's no guarantee that your interface pointer will persist across level transitions,
  452. // so this function will update your interface.
  453. //-----------------------------------------------------------------------------
  454. ISteamGameStats* CSteamWorksGameStatsUploader::GetInterface( void )
  455. {
  456. #if !defined( NO_STEAM )
  457. HSteamUser hSteamUser = 0;
  458. HSteamPipe hSteamPipe = 0;
  459. #ifdef GAME_DLL
  460. if ( steamgameserverapicontext && steamgameserverapicontext->SteamGameServer() && steamgameserverapicontext->SteamGameServerUtils() )
  461. {
  462. m_UserID = steamgameserverapicontext->SteamGameServer()->GetSteamID().ConvertToUint64();
  463. m_iAppID = steamgameserverapicontext->SteamGameServerUtils()->GetAppID();
  464. hSteamUser = SteamGameServer_GetHSteamUser();
  465. hSteamPipe = SteamGameServer_GetHSteamPipe();
  466. }
  467. // Now let's get the interface for dedicated servers
  468. if ( steamgameserverapicontext && steamgameserverapicontext->SteamClient() && engine && engine->IsDedicatedServer() )
  469. {
  470. return (ISteamGameStats*) steamgameserverapicontext->SteamClient()->GetISteamGenericInterface( hSteamUser, hSteamPipe, STEAMGAMESTATS_INTERFACE_VERSION );
  471. }
  472. #elif CLIENT_DLL
  473. if ( steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUtils() )
  474. {
  475. m_UserID = steamapicontext->SteamUser()->GetSteamID().ConvertToUint64();
  476. m_iAppID = steamapicontext->SteamUtils()->GetAppID();
  477. hSteamUser = steamapicontext->SteamUser()->GetHSteamUser();
  478. hSteamPipe = GetHSteamPipe();
  479. }
  480. #endif
  481. // Listen server have access to SteamClient
  482. if ( steamapicontext && steamapicontext->SteamClient() )
  483. {
  484. return (ISteamGameStats*)steamapicontext->SteamClient()->GetISteamGenericInterface( hSteamUser, hSteamPipe, STEAMGAMESTATS_INTERFACE_VERSION );
  485. }
  486. #endif // !NO_STEAM
  487. // If we haven't returned already, then we can't get access to the interface
  488. return NULL;
  489. }
  490. //-----------------------------------------------------------------------------
  491. // Purpose: Creates a table from the KeyValue file. Do NOT send nested KeyValue objects into this function!
  492. //-----------------------------------------------------------------------------
  493. EResult CSteamWorksGameStatsUploader::AddStatsForUpload( KeyValues *pKV, bool bSendImmediately )
  494. {
  495. // If the stat system is disabled, then don't accept the keyvalue
  496. if ( steamworks_stats_disable )
  497. {
  498. if ( pKV )
  499. {
  500. pKV->deleteThis();
  501. }
  502. return k_EResultNoConnection;
  503. }
  504. if ( pKV )
  505. {
  506. // Do we want to immediately upload the stats?
  507. if ( bSendImmediately && steamworks_immediate_upload )
  508. {
  509. ParseKeyValuesAndSendStats( pKV );
  510. pKV->deleteThis();
  511. }
  512. else
  513. {
  514. m_StatsToSend.AddToTail( pKV );
  515. }
  516. return k_EResultOK;
  517. }
  518. return k_EResultFail;
  519. }
  520. //-----------------------------------------------------------------------------
  521. // Purpose: Parses all the keyvalue files we've been sent and creates tables from them and uploads them
  522. //-----------------------------------------------------------------------------
  523. double g_rowCommitTime = 0.0f;
  524. double g_rowWriteTime = 0.0f;
  525. EResult CSteamWorksGameStatsUploader::ParseKeyValuesAndSendStats( KeyValues *pKV )
  526. {
  527. if ( !pKV )
  528. {
  529. return k_EResultFail;
  530. }
  531. if ( !IsCollectingAnyData() )
  532. {
  533. return k_EResultFail;
  534. }
  535. // Refresh the interface in case steam has unloaded
  536. m_SteamWorksInterface = GetInterface();
  537. if ( !m_SteamWorksInterface )
  538. {
  539. DevMsg( "WARNING: Attempted to send a steamworks gamestats row when the steamworks interface was not available!" );
  540. return k_EResultNoConnection;
  541. }
  542. const char *pzTable = pKV->GetName();
  543. if ( steamworks_show_uploads )
  544. {
  545. #ifdef CLIENT_DLL
  546. DevMsg( "Client submitting row (%s).\n", pzTable );
  547. #elif GAME_DLL
  548. DevMsg( "Server submitting row (%s).\n", pzTable );
  549. #endif
  550. KeyValuesDumpAsDevMsg( pKV, 1 );
  551. }
  552. uint64 iTableID = 0;
  553. m_SteamWorksInterface->AddNewRow( &iTableID, m_SessionID, pzTable );
  554. if ( !iTableID )
  555. {
  556. return k_EResultFail;
  557. }
  558. AddSessionIDsToTable( iTableID );
  559. // Now we need to loop over all the keys in pKV and add the name and value
  560. for ( KeyValues *pData = pKV->GetFirstSubKey() ; pData != NULL ; pData = pData->GetNextKey() )
  561. {
  562. const char *name = pData->GetName();
  563. CFastTimer writeTimer;
  564. writeTimer.Start();
  565. switch ( pData->GetDataType() )
  566. {
  567. case KeyValues::TYPE_STRING: WriteStringToTable( pKV->GetString( name ), iTableID, name );
  568. break;
  569. case KeyValues::TYPE_INT: WriteIntToTable( pKV->GetInt( name ), iTableID, name );
  570. break;
  571. case KeyValues::TYPE_FLOAT: WriteFloatToTable( pKV->GetFloat( name ), iTableID, name );
  572. break;
  573. case KeyValues::TYPE_UINT64: WriteInt64ToTable( pKV->GetUint64( name ), iTableID, name );
  574. break;
  575. };
  576. writeTimer.End();
  577. g_rowWriteTime += writeTimer.GetDuration().GetMillisecondsF();
  578. }
  579. CFastTimer commitTimer;
  580. commitTimer.Start();
  581. EResult res = m_SteamWorksInterface->CommitRow( iTableID );
  582. commitTimer.End();
  583. g_rowCommitTime += commitTimer.GetDuration().GetMillisecondsF();
  584. if ( res != k_EResultOK )
  585. {
  586. char pzMessage[MAX_PATH] = {0};
  587. V_snprintf( pzMessage, ARRAYSIZE(pzMessage), "Failed To Submit table %s", pzTable );
  588. Assert( pzMessage );
  589. }
  590. return res;
  591. }
  592. #ifdef CLIENT_DLL
  593. #endif
  594. void CSteamWorksGameStatsUploader::ServerAddressToInt()
  595. {
  596. CUtlStringList IPs;
  597. V_SplitString( m_pzServerIP, ".", IPs );
  598. if ( IPs.Count() < 4 )
  599. {
  600. // Not an actual IP.
  601. m_iServerIP = 0;
  602. return;
  603. }
  604. byte ip[4];
  605. m_iServerIP = 0;
  606. for ( int i=0; i<IPs.Count() && i<4; ++i )
  607. {
  608. ip[i] = (byte) Q_atoi( IPs[i] );
  609. }
  610. m_iServerIP = (ip[0]<<24) + (ip[1]<<16) + (ip[2]<<8) + ip[3];
  611. }
  612. //=============================================================================
  613. //
  614. // Helper functions for creating key values
  615. //
  616. void AddDataToKV( KeyValues* pKV, const char* name, int data )
  617. {
  618. pKV->SetInt( name, data );
  619. }
  620. void AddDataToKV( KeyValues* pKV, const char* name, uint64 data )
  621. {
  622. pKV->SetUint64( name, data );
  623. }
  624. void AddDataToKV( KeyValues* pKV, const char* name, float data )
  625. {
  626. pKV->SetFloat( name, data );
  627. }
  628. void AddDataToKV( KeyValues* pKV, const char* name, bool data )
  629. {
  630. pKV->SetInt( name, data ? true : false );
  631. }
  632. void AddDataToKV( KeyValues* pKV, const char* name, const char* data )
  633. {
  634. pKV->SetString( name, data );
  635. }
  636. void AddDataToKV( KeyValues* pKV, const char* name, const Color& data )
  637. {
  638. pKV->SetColor( name, data );
  639. }
  640. void AddDataToKV( KeyValues* pKV, const char* name, short data )
  641. {
  642. pKV->SetInt( name, data );
  643. }
  644. void AddDataToKV( KeyValues* pKV, const char* name, unsigned data )
  645. {
  646. pKV->SetInt( name, data );
  647. }
  648. void AddPositionDataToKV( KeyValues* pKV, const char* name, const Vector &data )
  649. {
  650. // Append the data name to the member
  651. pKV->SetFloat( CFmtStr("%s%s", name, "_X"), data.x );
  652. pKV->SetFloat( CFmtStr("%s%s", name, "_Y"), data.y );
  653. pKV->SetFloat( CFmtStr("%s%s", name, "_Z"), data.z );
  654. }
  655. //=============================================================================//
  656. //=============================================================================
  657. //
  658. // Helper functions for creating key values from arrays
  659. //
  660. void AddArrayDataToKV( KeyValues* pKV, const char* name, const short *data, unsigned size )
  661. {
  662. for( unsigned i=0; i<size; ++i )
  663. pKV->SetInt( CFmtStr("%s_%d", name, i) , data[i] );
  664. }
  665. void AddArrayDataToKV( KeyValues* pKV, const char* name, const byte *data, unsigned size )
  666. {
  667. for( unsigned i=0; i<size; ++i )
  668. pKV->SetInt( CFmtStr("%s_%d", name, i), data[i] );
  669. }
  670. void AddArrayDataToKV( KeyValues* pKV, const char* name, const unsigned *data, unsigned size )
  671. {
  672. for( unsigned i=0; i<size; ++i )
  673. pKV->SetInt( CFmtStr("%s_%d", name, i), data[i] );
  674. }
  675. void AddStringDataToKV( KeyValues* pKV, const char* name, const char*data )
  676. {
  677. if( name == NULL )
  678. return;
  679. pKV->SetString( name, data );
  680. }
  681. //=============================================================================//
  682. void IGameStatTracker::PrintGamestatMemoryUsage( void )
  683. {
  684. StatContainerList_t* pStatList = GetStatContainerList();
  685. if( !pStatList )
  686. return;
  687. int iListSize = pStatList->Count();
  688. // For every stat list being tracked, print out its memory usage
  689. for( int i=0; i < iListSize; ++i )
  690. {
  691. pStatList->operator []( i )->PrintMemoryUsage();
  692. }
  693. }
  694. #endif