Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1507 lines
45 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Client side stat tracking
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "c_tf_gamestats.h"
  8. #include <vgui_controls/Controls.h>
  9. #include "vgui/ISystem.h"
  10. #include "steam/steam_api.h"
  11. #include "steamworks_gamestats.h"
  12. #include "c_tf_player.h"
  13. #include "tf_hud_statpanel.h"
  14. #include "econ_item_system.h"
  15. #include "econ_ui.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. // Must run with -gamestats to be able to turn on/off stats with ConVar below.
  19. static ConVar tf_stats_track( "tf_stats_track",
  20. #ifdef _DEBUG
  21. "0",
  22. #else
  23. "1",
  24. #endif
  25. FCVAR_DEVELOPMENTONLY, "Turn on//off tf stats tracking." );
  26. ConVar tf_matchmaking_ogs_odds( "tf_matchmaking_ogs_odds", "0.05", FCVAR_HIDDEN, "Percentage (0...1) of quickplay queries that will report to OGS" );
  27. const char *g_ItemEventNames[] =
  28. {
  29. // STORE EVENTS
  30. "store_entered",
  31. "store_exited",
  32. "store_tab_changed",
  33. "store_item_selected",
  34. "store_item_previewed",
  35. "store_item_added_to_cart",
  36. "store_item_removed_from_cart",
  37. "store_checkout_attempt",
  38. "store_checkout_failure",
  39. "store_checkout_success",
  40. "store_checkout_item",
  41. // LOADOUT EVENTS
  42. "loadout_entered",
  43. "loadout_exited",
  44. // TRADE EVENTS
  45. "trading_entered",
  46. "trading_exited",
  47. "trading_went_to_armory",
  48. "trading_returned_from_armory",
  49. "trading_request_sent",
  50. "trading_request_received",
  51. "trading_request_rejected",
  52. "trading_request_accepted",
  53. "trading_trade_negotiated",
  54. "trading_trade_success",
  55. "trading_trade_failure",
  56. "trading_item_given",
  57. "trading_item_received",
  58. "trading_item_gifted",
  59. // CRAFTING EVENTS
  60. "crafting_entered",
  61. "crafting_exited",
  62. "crafting_went_to_armory",
  63. "crafting_returned_from_armory",
  64. "crafting_view_blueprints",
  65. "crafting_timeout",
  66. "crafting_failure",
  67. "crafting_success",
  68. "crafting_no_recipe_match",
  69. "crafting_attempt",
  70. "crafting_recipe_found",
  71. // ARMORY EVENTS
  72. "armory_entered",
  73. "armory_exited",
  74. "armory_select_item",
  75. "armory_browse_wiki",
  76. "armory_change_filter",
  77. // GENERAL EVENTS
  78. "item_received",
  79. "item_discarded",
  80. "item_deleted",
  81. "item_used_tool",
  82. "item_used_consumable",
  83. "item_removed_attrib",
  84. "item_changed_style"
  85. // NEW STORE EVENTS
  86. "store2_entered", // This gets written *in addition* to IE_STORE_ENTERED
  87. // THESE STORED AS INTEGERS IN THE DATABASE SO THESE ARE NEW
  88. "item_reset_counters",
  89. "item_put_into_collection",
  90. "" // IE_COUNT
  91. };
  92. COMPILE_TIME_ASSERT( ARRAYSIZE( g_ItemEventNames ) == IE_COUNT );
  93. C_CTFGameStats C_CTF_GameStats;
  94. static bool WeaponInfoLessFunc( const int& e1, const int&e2 )
  95. {
  96. return e1 < e2;
  97. }
  98. //-----------------------------------------------------------------------------
  99. // Purpose: Constructor
  100. // Input : -
  101. //-----------------------------------------------------------------------------
  102. C_CTFGameStats::C_CTFGameStats()
  103. {
  104. m_ulExperimentValue = (uint64) ~0;
  105. gamestats = this;
  106. Clear();
  107. m_mapWeaponInfo.SetLessFunc( WeaponInfoLessFunc );
  108. }
  109. //-----------------------------------------------------------------------------
  110. // Purpose: Destructor
  111. // Input : -
  112. //-----------------------------------------------------------------------------
  113. C_CTFGameStats::~C_CTFGameStats()
  114. {
  115. }
  116. //-----------------------------------------------------------------------------
  117. // Purpose: Sets all game stats to their default value
  118. // Input : -
  119. //-----------------------------------------------------------------------------
  120. void C_CTFGameStats::Clear( void )
  121. {
  122. V_strncpy( m_szCountryCode, "unknown", ARRAYSIZE( m_szCountryCode ) );
  123. V_strncpy( m_szAudioLanguage, "unknown", ARRAYSIZE( m_szAudioLanguage ) );
  124. V_strncpy( m_szTextLanguage, "unknown", ARRAYSIZE( m_szTextLanguage ) );
  125. m_bRoundActive = false;
  126. m_bIsDisconnecting = false;
  127. }
  128. //-----------------------------------------------------------------------------
  129. // Purpose: Adds our data to the gamestats data that gets uploaded.
  130. // Returns true if we added data, false if we didn't
  131. //-----------------------------------------------------------------------------
  132. bool C_CTFGameStats::AddDataForSend( KeyValues *pKV, StatSendType_t sendType )
  133. {
  134. // we only have data to send at level shutdown
  135. if ( sendType != STATSEND_APPSHUTDOWN || 0 == tf_stats_track.GetInt() )
  136. {
  137. return false;
  138. }
  139. KeyValues *pKVData = new KeyValues( "tf_configdata" );
  140. if ( NULL == pKVData )
  141. {
  142. Clear();
  143. return false;
  144. }
  145. static ConVarRef sb_quick_list_bit_field( "sb_quick_list_bit_field" );
  146. if ( sb_quick_list_bit_field.IsValid() )
  147. {
  148. pKVData->SetInt( "QuickListBitField", sb_quick_list_bit_field.GetInt() );
  149. }
  150. else
  151. {
  152. pKVData->SetInt( "QuickListBitField", -1 );
  153. }
  154. pKVData->SetString( "TextLanguage", m_szTextLanguage );
  155. pKVData->SetString( "AudioLanguage", m_szAudioLanguage );
  156. pKVData->SetString( "CountryCode", m_szCountryCode );
  157. // Add our tf_configdata as a subkey to the main stat key
  158. pKV->AddSubKey( pKVData );
  159. return true;
  160. }
  161. //-----------------------------------------------------------------------------
  162. // Purpose: Since this is a very lean client stat reporter, we can grab all the info
  163. // that we need right in the init call
  164. //-----------------------------------------------------------------------------
  165. bool C_CTFGameStats::Init( void )
  166. {
  167. // If we are on the PC and have access to all the interfaces we need
  168. if ( !IsX360() && engine && vgui::system() && steamapicontext && steamapicontext->SteamUtils() )
  169. {
  170. // We want to track the country code to help with localization. The countrycode is empty when testing on SteamBeta, so we won't get
  171. // data until users in the wild play
  172. const char * countryCode = steamapicontext->SteamUtils()->GetIPCountry();
  173. if ( countryCode != NULL )
  174. {
  175. V_strncpy( m_szCountryCode, countryCode, ARRAYSIZE( m_szCountryCode ) );
  176. }
  177. // Now lets get the text language that Steam is in (If the game supports the language, then the UI is changed to that language).
  178. engine->GetUILanguage( m_szTextLanguage, sizeof( m_szTextLanguage ) );
  179. V_strcpy_safe( m_szAudioLanguage, steamapicontext->SteamApps()->GetCurrentGameLanguage() );
  180. m_currentSession.m_SessionStart = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  181. ListenForGameEvent( "server_spawn" );
  182. // ListenForGameEvent( "host_quit" );
  183. ListenForGameEvent( "player_stats_updated" );
  184. ListenForGameEvent( "teamplay_round_win" );
  185. ListenForGameEvent( "teamplay_round_active" );
  186. ListenForGameEvent( "player_changeclass" );
  187. ListenForGameEvent( "player_hurt" );
  188. ListenForGameEvent( "client_disconnect" );
  189. // A client session lasts from when the application starts to when it is exited.
  190. GetSteamWorksSGameStatsUploader().StartSession();
  191. }
  192. return true;
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Purpose: This system is shutting down.
  196. //-----------------------------------------------------------------------------
  197. void C_CTFGameStats::Shutdown()
  198. {
  199. if ( !steamapicontext )
  200. return;
  201. if ( !steamapicontext->SteamUser() )
  202. return;
  203. SW_GameStats_WriteClientSessionSummary();
  204. SW_GameStats_WriteClientWeapons();
  205. GetSteamWorksSGameStatsUploader().EndSession();
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose:
  209. //-----------------------------------------------------------------------------
  210. void C_CTFGameStats::Event_LevelInit( void )
  211. {
  212. m_currentMap.Init( engine->GetLevelName(), engine->GetLevelVersion(), 0, 0, GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  213. m_currentRound.m_iNumRounds = 0;
  214. m_currentSession.m_iMapsPlayed++;
  215. }
  216. //-----------------------------------------------------------------------------
  217. // Purpose:
  218. //-----------------------------------------------------------------------------
  219. void C_CTFGameStats::Event_LevelShutdown( float flElapsed )
  220. {
  221. if ( !m_bIsDisconnecting )
  222. {
  223. SW_GameStats_WriteClientRound( 0, 0, RE_SERVER_MAP_CHANGE );
  224. }
  225. SW_GameStats_WriteClientMap();
  226. }
  227. //-----------------------------------------------------------------------------
  228. // Purpose: Called when the server shuts down or the client disconnects from the server.
  229. //-----------------------------------------------------------------------------
  230. void C_CTFGameStats::ClientDisconnect( int iReason )
  231. {
  232. m_bIsDisconnecting = true;
  233. SW_GameStats_WriteClientRound( 0, 0, iReason );
  234. }
  235. //-----------------------------------------------------------------------------
  236. // Purpose:
  237. //-----------------------------------------------------------------------------
  238. void C_CTFGameStats::FireGameEvent( IGameEvent *event )
  239. {
  240. const char *pEventName = event->GetName();
  241. if ( FStrEq( "server_spawn", pEventName ) )
  242. {
  243. if ( m_currentSession.m_FirstConnect == 0 )
  244. {
  245. m_currentSession.m_FirstConnect = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  246. }
  247. // In case we join a round that is active, stuff the current time as the round start time.
  248. m_currentRound.m_iRoundStartTime = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  249. m_currentMap.m_iRoundStartTime = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  250. m_bIsDisconnecting = false;
  251. }
  252. else if ( FStrEq( "host_quit", pEventName ) )
  253. {
  254. // We need this event because client_disconnect is called after the level unloads
  255. // when we're dealing with the client-host of a listen server. Frustrating exception to
  256. // the normal disconnect/shutdown rules.
  257. ClientDisconnect( RE_CLIENT_QUIT );
  258. }
  259. else if ( FStrEq( "client_disconnect", pEventName ) )
  260. {
  261. ClientDisconnect( RE_CLIENT_DISCONNECT );
  262. }
  263. else if ( FStrEq( "teamplay_round_win", pEventName ) )
  264. {
  265. int winningTeam = event->GetInt( "team" );
  266. float roundTime = event->GetFloat( "round_time" );
  267. int fullRound = event->GetInt( "full_round" );
  268. Event_RoundEnd( winningTeam, roundTime, fullRound );
  269. }
  270. else if ( FStrEq( "teamplay_round_active", pEventName ) )
  271. {
  272. Event_RoundActive();
  273. }
  274. else if ( FStrEq( "player_changeclass", pEventName ) )
  275. {
  276. int userid = event->GetInt( "userid" );
  277. int classid = event->GetInt( "class" );
  278. Event_PlayerChangeClass( userid, classid );
  279. }
  280. else if ( FStrEq( "player_hurt", pEventName ) )
  281. {
  282. Event_PlayerHurt( event );
  283. }
  284. }
  285. //-----------------------------------------------------------------------------
  286. // Purpose:
  287. //-----------------------------------------------------------------------------
  288. void C_CTFGameStats::Event_RoundActive()
  289. {
  290. m_currentRound.m_iRoundStartTime = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  291. m_currentMap.m_iRoundStartTime = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  292. m_bRoundActive = true;
  293. }
  294. //-----------------------------------------------------------------------------
  295. // Purpose:
  296. //-----------------------------------------------------------------------------
  297. void C_CTFGameStats::Event_RoundEnd( int winningTeam, float roundTime, int fullRound )
  298. {
  299. SW_GameStats_WriteClientRound( winningTeam, fullRound, RE_ROUND_END );
  300. }
  301. //-----------------------------------------------------------------------------
  302. // Purpose:
  303. //-----------------------------------------------------------------------------
  304. void C_CTFGameStats::Event_PlayerChangeClass( int userid, int classid )
  305. {
  306. C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  307. if ( !pTFPlayer )
  308. return;
  309. if ( pTFPlayer != UTIL_PlayerByIndex( engine->GetPlayerForUserID( userid ) ) )
  310. return;
  311. m_currentSession.m_ClassesPlayed.Set( classid );
  312. }
  313. //-----------------------------------------------------------------------------
  314. // Purpose:
  315. //-----------------------------------------------------------------------------
  316. void C_CTFGameStats::Event_PlayerHurt( IGameEvent* event /*player_hurt*/ )
  317. {
  318. C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  319. if ( !pTFPlayer )
  320. return;
  321. int attackerid = event->GetInt( "attacker" );
  322. if ( pTFPlayer != UTIL_PlayerByIndex( engine->GetPlayerForUserID( attackerid ) ) )
  323. return;
  324. if ( !m_bRoundActive )
  325. return;
  326. // My kingdom for a proper damage effects system.
  327. // We need to ignore DOTS in some stats.
  328. int custom = event->GetInt( "custom" );
  329. bool bIsDamageOverTime = IsDOTDmg( custom );
  330. bool bIsTauntDamage = IsTauntDmg( custom );
  331. // Update weapon info with this hit.
  332. int weaponid = event->GetInt( "weaponid" );
  333. int damageamount = event->GetInt( "damageamount" );
  334. TF_Gamestats_WeaponInfo_t info;
  335. int idx = m_mapWeaponInfo.Find( weaponid );
  336. if ( idx == m_mapWeaponInfo.InvalidIndex() )
  337. {
  338. info.weaponID = weaponid;
  339. idx = m_mapWeaponInfo.Insert( weaponid, info );
  340. }
  341. else
  342. {
  343. info = m_mapWeaponInfo[idx];
  344. }
  345. info.totalDamage += damageamount;
  346. if ( !bIsDamageOverTime && !bIsTauntDamage && (gpGlobals->curtime != info.lastUpdateTime) )
  347. {
  348. bool crit = event->GetInt( "crit" );
  349. if ( crit )
  350. {
  351. info.critHits++;
  352. }
  353. info.shotsHit++;
  354. info.shotsMissed--;
  355. }
  356. if ( info.shotsHit )
  357. {
  358. info.avgDamage = info.totalDamage / info.shotsHit;
  359. }
  360. else
  361. {
  362. info.avgDamage = 0;
  363. }
  364. info.lastUpdateTime = gpGlobals->curtime;
  365. m_mapWeaponInfo[idx] = info;
  366. }
  367. //-----------------------------------------------------------------------------
  368. // Purpose:
  369. //-----------------------------------------------------------------------------
  370. void C_CTFGameStats::Event_PlayerFiredWeapon( C_TFPlayer *pPlayer, bool bCritical )
  371. {
  372. C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  373. if ( !pPlayer || (pTFPlayer != pPlayer) )
  374. return;
  375. if ( !m_bRoundActive )
  376. return;
  377. CTFWeaponBase *pTFWeapon = pPlayer->GetActiveTFWeapon();
  378. if ( pTFWeapon )
  379. {
  380. // all shots are assumed to be misses until they hit
  381. int iWeaponID = pTFWeapon->GetWeaponID();
  382. TF_Gamestats_WeaponInfo_t info;
  383. int idx = m_mapWeaponInfo.Find( iWeaponID );
  384. if ( idx == m_mapWeaponInfo.InvalidIndex() )
  385. {
  386. info.weaponID = iWeaponID;
  387. idx = m_mapWeaponInfo.Insert( iWeaponID, info );
  388. }
  389. else
  390. {
  391. info = m_mapWeaponInfo[idx];
  392. }
  393. info.shotsFired++;
  394. info.shotsMissed++;
  395. if ( bCritical )
  396. {
  397. info.critsFired++;
  398. }
  399. m_mapWeaponInfo[idx] = info;
  400. }
  401. }
  402. //-----------------------------------------------------------------------------
  403. // Purpose: New Steamworks Database Client Data
  404. // Sends the client's session summary report.
  405. //-----------------------------------------------------------------------------
  406. void C_CTFGameStats::SW_GameStats_WriteClientSessionSummary()
  407. {
  408. #if !defined(NO_STEAM)
  409. KeyValues* pKVData = new KeyValues( "TF2ClientSessionDetails" );
  410. pKVData->SetUint64( "AccountID", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() );
  411. RTime32 currentClock = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  412. pKVData->SetInt( "StartTime", m_currentSession.m_SessionStart );
  413. pKVData->SetInt( "EndTime", currentClock );
  414. if ( GetSteamWorksSGameStatsUploader().GetNumServerConnects() > 0 )
  415. {
  416. pKVData->SetInt( "ServerConnects", GetSteamWorksSGameStatsUploader().GetNumServerConnects() );
  417. }
  418. ConVarRef sb_firstopentime( "sb_firstopentime" );
  419. if ( m_currentSession.m_FirstConnect > 0 )
  420. {
  421. int iTimeFromStartToJoin = m_currentSession.m_FirstConnect - m_currentSession.m_SessionStart;
  422. pKVData->SetInt( "TimeFromStartToJoin", iTimeFromStartToJoin );
  423. }
  424. if ( sb_firstopentime.GetInt() > 0 &&
  425. m_currentSession.m_FirstConnect > 0 )
  426. {
  427. int iTimeFromBrowseToJoin = m_currentSession.m_FirstConnect - sb_firstopentime.GetInt();
  428. if ( iTimeFromBrowseToJoin > 0 )
  429. {
  430. pKVData->SetInt( "TimeFromBrowseToJoin", iTimeFromBrowseToJoin );
  431. }
  432. }
  433. if ( sb_firstopentime.GetInt() > 0 )
  434. {
  435. int iTimeFromStartToBrowse = sb_firstopentime.GetInt() - m_currentSession.m_SessionStart;
  436. pKVData->SetInt( "TimeFromStartToBrowse", iTimeFromStartToBrowse );
  437. }
  438. ConVarRef sb_numtimesopened( "sb_numtimesopened" );
  439. pKVData->SetInt( "TimesOpenedServerBrowser", sb_numtimesopened.GetInt() );
  440. int iClassesPlayed = 0;
  441. for ( int i=TF_FIRST_NORMAL_CLASS; i<TF_LAST_NORMAL_CLASS; ++i )
  442. {
  443. if ( m_currentSession.m_ClassesPlayed.IsBitSet( i ) )
  444. {
  445. iClassesPlayed++;
  446. }
  447. }
  448. if ( iClassesPlayed > 0 )
  449. {
  450. pKVData->SetInt( "ClassesPlayed", iClassesPlayed );
  451. }
  452. if ( m_currentSession.m_iMapsPlayed > 0 )
  453. {
  454. pKVData->SetInt( "MapsPlayed", m_currentSession.m_iMapsPlayed );
  455. }
  456. if ( m_currentSession.m_iRoundsPlayed > 0 )
  457. {
  458. pKVData->SetInt( "RoundsPlayed", m_currentSession.m_iRoundsPlayed );
  459. }
  460. /*
  461. FavoriteClass
  462. FavoriteWeapon
  463. FavoriteMap
  464. */
  465. if ( m_currentSession.m_Summary.iKills > 0 )
  466. {
  467. pKVData->SetInt( "Kills", m_currentSession.m_Summary.iKills );
  468. }
  469. if ( m_currentSession.m_Summary.iDeaths > 0 )
  470. {
  471. pKVData->SetInt( "Deaths", m_currentSession.m_Summary.iDeaths );
  472. }
  473. if ( m_currentSession.m_Summary.iSuicides > 0 )
  474. {
  475. pKVData->SetInt( "Suicides", m_currentSession.m_Summary.iSuicides );
  476. }
  477. if ( m_currentSession.m_Summary.iAssists > 0 )
  478. {
  479. pKVData->SetInt( "Assists", m_currentSession.m_Summary.iAssists );
  480. }
  481. if ( m_currentSession.m_Summary.iBuildingsBuilt > 0 )
  482. {
  483. pKVData->SetInt( "BuildingsBuilt", m_currentSession.m_Summary.iBuildingsBuilt );
  484. }
  485. if ( m_currentSession.m_Summary.iBuildingsDestroyed > 0 )
  486. {
  487. pKVData->SetInt( "BuildingsDestroyed", m_currentSession.m_Summary.iBuildingsDestroyed );
  488. }
  489. if ( m_currentSession.m_Summary.iHeadshots > 0 )
  490. {
  491. pKVData->SetInt( "Headshots", m_currentSession.m_Summary.iHeadshots );
  492. }
  493. if ( m_currentSession.m_Summary.iDominations > 0 )
  494. {
  495. pKVData->SetInt( "Dominations", m_currentSession.m_Summary.iDominations );
  496. }
  497. if ( m_currentSession.m_Summary.iRevenges > 0 )
  498. {
  499. pKVData->SetInt( "Revenges", m_currentSession.m_Summary.iRevenges );
  500. }
  501. if ( m_currentSession.m_Summary.iInvulns > 0 )
  502. {
  503. pKVData->SetInt( "Invulns", m_currentSession.m_Summary.iInvulns );
  504. }
  505. if ( m_currentSession.m_Summary.iTeleports > 0 )
  506. {
  507. pKVData->SetInt( "Teleports", m_currentSession.m_Summary.iTeleports );
  508. }
  509. if ( m_currentSession.m_Summary.iDamageDone > 0 )
  510. {
  511. pKVData->SetInt( "DamageDone", m_currentSession.m_Summary.iDamageDone );
  512. }
  513. if ( m_currentSession.m_Summary.iHealingDone > 0 )
  514. {
  515. pKVData->SetInt( "HealingDone", m_currentSession.m_Summary.iHealingDone );
  516. }
  517. if ( m_currentSession.m_Summary.iCrits > 0 )
  518. {
  519. pKVData->SetInt( "Crits", m_currentSession.m_Summary.iCrits );
  520. }
  521. if ( m_currentSession.m_Summary.iBackstabs > 0 )
  522. {
  523. pKVData->SetInt( "Backstabs", m_currentSession.m_Summary.iBackstabs );
  524. }
  525. if ( m_currentSession.m_Summary.iAchievementsEarned > 0 )
  526. {
  527. pKVData->SetInt( "AchievementsEarned", m_currentSession.m_Summary.iAchievementsEarned );
  528. }
  529. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  530. #endif
  531. }
  532. extern CBaseGameStats_Driver CBGSDriver;
  533. //-----------------------------------------------------------------------------
  534. // Purpose: New Steamworks Database Client Map Data
  535. //-----------------------------------------------------------------------------
  536. void C_CTFGameStats::SW_GameStats_WriteClientMap()
  537. {
  538. #if !defined(NO_STEAM)
  539. KeyValues* pKVData = new KeyValues( "TF2ClientMaps" );
  540. pKVData->SetInt( "MapIndex", m_currentSession.m_iMapsPlayed );
  541. pKVData->SetInt( "StartTime", m_currentMap.m_iMapStartTime );
  542. pKVData->SetInt( "EndTime", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  543. pKVData->SetString( "MapID", m_currentMap.m_Header.m_szMapName );
  544. const char* pszGameTypeID = GetGameTypeID();
  545. if ( pszGameTypeID )
  546. {
  547. pKVData->SetString( "GameTypeID", pszGameTypeID );
  548. }
  549. pKVData->SetInt( "RoundsPlayed", m_currentMap.m_Header.m_iRoundsPlayed );
  550. pKVData->SetInt( "MapVersion", m_currentMap.m_Header.m_nMapRevision );
  551. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  552. #endif
  553. }
  554. //-----------------------------------------------------------------------------
  555. // Purpose: New Steamworks Database Client Round Data
  556. //-----------------------------------------------------------------------------
  557. void C_CTFGameStats::SW_GameStats_WriteClientRound( int winningTeam, int fullRound, int endReason )
  558. {
  559. if ( !m_bRoundActive )
  560. return;
  561. m_bRoundActive = false;
  562. m_currentSession.m_iRoundsPlayed++;
  563. m_currentMap.m_Header.m_iRoundsPlayed++;
  564. #if !defined(NO_STEAM)
  565. KeyValues* pKVData = new KeyValues( "TF2ClientRounds" );
  566. C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  567. if ( !pTFPlayer )
  568. return;
  569. localplayerscoring_t *pData = pTFPlayer->m_Shared.GetRoundScoringData();
  570. if ( !pData )
  571. return;
  572. pKVData->SetInt( "MapIndex", m_currentSession.m_iMapsPlayed );
  573. pKVData->SetInt( "RoundIndex", m_currentMap.m_Header.m_iRoundsPlayed );
  574. pKVData->SetInt( "StartTime", m_currentRound.m_iRoundStartTime );
  575. pKVData->SetInt( "EndTime", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  576. pKVData->SetString( "EndReason", ClampedArrayElement( g_aRoundEndReasons, endReason ) );
  577. winningTeam = clamp( winningTeam, 0, TF_TEAM_COUNT - 1 );
  578. pKVData->SetString( "WinningTeam", ClampedArrayElement( g_aTeamNames, winningTeam ) );
  579. if ( fullRound > 0 )
  580. {
  581. pKVData->SetInt( "FullRound", fullRound );
  582. }
  583. if ( pData->m_iPoints > 0 )
  584. {
  585. pKVData->SetInt( "PointsScored", pData->m_iPoints );
  586. }
  587. if ( pData->m_iBonusPoints > 0 )
  588. {
  589. pKVData->SetInt( "BonusPointsScored", pData->m_iBonusPoints );
  590. }
  591. if ( pData->m_iKills > 0 )
  592. {
  593. pKVData->SetInt( "Kills", pData->m_iKills );
  594. }
  595. if ( pData->m_iDeaths > 0 )
  596. {
  597. pKVData->SetInt( "Deaths", pData->m_iDeaths );
  598. }
  599. if ( pData->m_iSuicides > 0 )
  600. {
  601. pKVData->SetInt( "Suicides", pData->m_iSuicides );
  602. }
  603. if ( pData->m_iKillAssists > 0 )
  604. {
  605. pKVData->SetInt( "Assists", pData->m_iKillAssists );
  606. }
  607. if ( pData->m_iBuildingsBuilt > 0 )
  608. {
  609. pKVData->SetInt( "BuildingsBuilt", pData->m_iBuildingsBuilt );
  610. }
  611. if ( pData->m_iBuildingsDestroyed > 0 )
  612. {
  613. pKVData->SetInt( "BuildingsDestroyed", pData->m_iBuildingsDestroyed );
  614. }
  615. if ( pData->m_iHeadshots > 0 )
  616. {
  617. pKVData->SetInt( "Headshots", pData->m_iHeadshots );
  618. }
  619. if ( pData->m_iDominations > 0 )
  620. {
  621. pKVData->SetInt( "Dominations", pData->m_iDominations );
  622. }
  623. if ( pData->m_iRevenge > 0 )
  624. {
  625. pKVData->SetInt( "Revenges", pData->m_iRevenge );
  626. }
  627. if ( pData->m_iInvulns > 0 )
  628. {
  629. pKVData->SetInt( "Invulns", pData->m_iInvulns );
  630. }
  631. if ( pData->m_iTeleports > 0 )
  632. {
  633. pKVData->SetInt( "Teleports", pData->m_iTeleports );
  634. }
  635. if ( pData->m_iDamageDone > 0 )
  636. {
  637. pKVData->SetInt( "DamageDone", pData->m_iDamageDone );
  638. }
  639. if ( pData->m_iHealPoints > 0 )
  640. {
  641. pKVData->SetInt( "HealingDone", pData->m_iHealPoints );
  642. }
  643. if ( pData->m_iCrits > 0 )
  644. {
  645. pKVData->SetInt( "Crits", pData->m_iCrits );
  646. }
  647. if ( pData->m_iBackstabs > 0 )
  648. {
  649. pKVData->SetInt( "Backstabs", pData->m_iBackstabs );
  650. }
  651. // Add totals to the current session.
  652. m_currentSession.m_Summary.iKills += pData->m_iKills;
  653. m_currentSession.m_Summary.iDeaths += pData->m_iDeaths;
  654. m_currentSession.m_Summary.iSuicides += pData->m_iSuicides;
  655. m_currentSession.m_Summary.iAssists += pData->m_iKillAssists;
  656. m_currentSession.m_Summary.iBuildingsBuilt += pData->m_iBuildingsBuilt;
  657. m_currentSession.m_Summary.iBuildingsDestroyed += pData->m_iBuildingsDestroyed;
  658. m_currentSession.m_Summary.iHeadshots += pData->m_iHeadshots;
  659. m_currentSession.m_Summary.iDominations += pData->m_iDominations;
  660. m_currentSession.m_Summary.iRevenges += pData->m_iRevenge;
  661. m_currentSession.m_Summary.iInvulns += pData->m_iInvulns;
  662. m_currentSession.m_Summary.iTeleports += pData->m_iTeleports;
  663. m_currentSession.m_Summary.iDamageDone += pData->m_iDamageDone;
  664. m_currentSession.m_Summary.iHealingDone += pData->m_iHealPoints;
  665. m_currentSession.m_Summary.iCrits += pData->m_iCrits;
  666. m_currentSession.m_Summary.iBackstabs += pData->m_iBackstabs;
  667. m_currentRound.Reset(); // Not used anymore?
  668. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  669. #endif
  670. }
  671. //-----------------------------------------------------------------------------
  672. // Purpose: New Steamworks Database Weapons Data
  673. //-----------------------------------------------------------------------------
  674. void C_CTFGameStats::SW_GameStats_WriteClientWeapons()
  675. {
  676. #if !defined(NO_STEAM)
  677. for ( unsigned int i=0; i<m_mapWeaponInfo.Count(); ++i )
  678. {
  679. KeyValues* pKVData = new KeyValues( "TF2ClientWeapons" );
  680. TF_Gamestats_WeaponInfo_t info = m_mapWeaponInfo[i];
  681. pKVData->SetString( "WeaponID", ClampedArrayElement( g_aWeaponNames, info.weaponID ) );
  682. if ( info.shotsFired > 0 )
  683. {
  684. pKVData->SetInt( "ShotsFired", info.shotsFired );
  685. }
  686. if ( info.shotsHit > 0 )
  687. {
  688. pKVData->SetInt( "ShotsHit", info.shotsHit );
  689. }
  690. if ( info.shotsMissed > 0 )
  691. {
  692. pKVData->SetInt( "ShotsMissed", info.shotsMissed );
  693. }
  694. if ( info.critsFired > 0 )
  695. {
  696. pKVData->SetInt( "CritsFired", info.critsFired );
  697. }
  698. if ( info.critHits > 0 )
  699. {
  700. pKVData->SetInt( "CritHits", info.critHits );
  701. }
  702. if ( info.avgDamage > 0 )
  703. {
  704. pKVData->SetInt( "AvgDamage", info.avgDamage );
  705. }
  706. if ( info.totalDamage > 0 )
  707. {
  708. pKVData->SetInt( "TotalDamage", info.totalDamage );
  709. }
  710. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  711. }
  712. #endif
  713. }
  714. //-----------------------------------------------------------------------------
  715. // Purpose: Records client achievement events for reporting to steam.
  716. //-----------------------------------------------------------------------------
  717. void C_CTFGameStats::Event_AchievementProgress( int achievementID, const char* achievementName )
  718. {
  719. TF_Gamestats_AchievementEvent_t event( achievementID, achievementName );
  720. m_vecAchievementEvents.AddToTail( event );
  721. m_currentSession.m_Summary.iAchievementsEarned++;
  722. KeyValues* pKVData = new KeyValues( "TF2ClientAchievements" );
  723. pKVData->SetInt( "TimeEarned", event.eventTime );
  724. pKVData->SetInt( "AchievementNum", event.achievementNum );
  725. pKVData->SetString( "AchievementID", event.achievementID );
  726. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  727. }
  728. //-----------------------------------------------------------------------------
  729. // Purpose: Weapon stats
  730. //-----------------------------------------------------------------------------
  731. TF_Gamestats_WeaponInfo_t::TF_Gamestats_WeaponInfo_t()
  732. {
  733. weaponID = 0;
  734. critsFired = 0;
  735. shotsFired = 0;
  736. shotsHit = 0;
  737. shotsMissed = 0;
  738. avgDamage = 0;
  739. totalDamage = 0;
  740. critHits = 0;
  741. lastUpdateTime = 0;
  742. }
  743. //-----------------------------------------------------------------------------
  744. // Purpose: Achievement progress recorder.
  745. //-----------------------------------------------------------------------------
  746. TF_Gamestats_AchievementEvent_t::TF_Gamestats_AchievementEvent_t( int in_achievementNum, const char* in_achievementID )
  747. {
  748. eventTime = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  749. achievementNum = in_achievementNum;
  750. achievementID = in_achievementID;
  751. }
  752. //-----------------------------------------------------------------------------
  753. // Purpose:
  754. //-----------------------------------------------------------------------------
  755. TF_Gamestats_ClientSession_t::TF_Gamestats_ClientSession_t()
  756. {
  757. Reset();
  758. }
  759. //-----------------------------------------------------------------------------
  760. // Purpose: Reset the client session data.
  761. //-----------------------------------------------------------------------------
  762. void TF_Gamestats_ClientSession_t::Reset()
  763. {
  764. Q_memset( &m_Summary, 0, sizeof( m_Summary ) );
  765. m_SessionStart = 0;
  766. m_FirstConnect = 0;
  767. m_iMapsPlayed = 0;
  768. m_iRoundsPlayed = 0;
  769. m_ClassesPlayed.ClearAll();
  770. }
  771. //-----------------------------------------------------------------------------
  772. // Purpose: Item event recorder.
  773. //-----------------------------------------------------------------------------
  774. TF_Gamestats_ItemEvent::TF_Gamestats_ItemEvent( int in_eventNum, CEconItemView* in_item )
  775. {
  776. static CSchemaAttributeDefHandle pAttrDef_SupplyCrateSeries( "set supply crate series" );
  777. bUseNameBuf = false;
  778. eventTime = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  779. eventNum = in_eventNum;
  780. eventID = g_ItemEventNames[eventNum];
  781. if ( in_item )
  782. {
  783. itemDefIndex = in_item->GetItemDefIndex();
  784. itemID = in_item->GetItemID();
  785. itemName = in_item->GetStaticData()->GetDefinitionName();
  786. // If this is a crate, quality is the series.
  787. float fSeries;
  788. if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( in_item, pAttrDef_SupplyCrateSeries, &fSeries ) )
  789. {
  790. bUseNameBuf = true;
  791. Q_snprintf( itemNameBuf, sizeof( itemNameBuf ), "%s, Series %i", itemName, (int)fSeries );
  792. }
  793. }
  794. else
  795. {
  796. itemDefIndex = 0;
  797. itemID = 0;
  798. itemName = NULL;
  799. }
  800. }
  801. //----------------------------------------------------------------------------
  802. // Purpose: Records a catalog event for reporting.
  803. //-----------------------------------------------------------------------------
  804. void C_CTFGameStats::Event_Catalog( int eventID, const char* filter, CEconItemView* item )
  805. {
  806. TF_Gamestats_CatalogEvent event( eventID, item, filter );
  807. m_vecCatalogEvents.AddToTail( event );
  808. KeyValues* pKVData = new KeyValues( "TF2ClientCatalogEvents" );
  809. pKVData->SetUint64( "AccountID", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() );
  810. pKVData->SetInt( "EventCount", m_vecCatalogEvents.Count() );
  811. pKVData->SetInt( "EventTime", event.eventTime );
  812. pKVData->SetString( "ItemEventID", event.eventID );
  813. if ( event.catalogFilter )
  814. {
  815. pKVData->SetString( "CatFilterID", event.catalogFilter );
  816. }
  817. if ( event.itemID > 0 )
  818. {
  819. pKVData->SetUint64( "GlobalIndex", event.itemID );
  820. }
  821. if ( event.itemDefIndex > 0 )
  822. {
  823. pKVData->SetInt( "DefIndex", event.itemDefIndex );
  824. }
  825. if ( event.GetItemName() != NULL )
  826. {
  827. pKVData->SetString( "ItemNameID", event.GetItemName() );
  828. }
  829. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  830. }
  831. //-----------------------------------------------------------------------------
  832. // Purpose: Catalog event recorder.
  833. //-----------------------------------------------------------------------------
  834. TF_Gamestats_CatalogEvent::TF_Gamestats_CatalogEvent( int in_eventNum, CEconItemView* in_item, const char* filter )
  835. : TF_Gamestats_ItemEvent( in_eventNum, in_item )
  836. {
  837. catalogFilter = filter;
  838. }
  839. //----------------------------------------------------------------------------
  840. // Purpose: Records a catalog event for reporting.
  841. //-----------------------------------------------------------------------------
  842. void C_CTFGameStats::Event_Crafting( int eventID, CEconItemView* item, int numAttempts, int recipeFound )
  843. {
  844. TF_Gamestats_CraftingEvent event( eventID, item, numAttempts, recipeFound );
  845. m_vecCraftingEvents.AddToTail( event );
  846. KeyValues* pKVData = new KeyValues( "TF2ClientCraftingEvents" );
  847. pKVData->SetUint64( "AccountID", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() );
  848. pKVData->SetInt( "EventCount", m_vecCraftingEvents.Count() );
  849. pKVData->SetInt( "EventTime", event.eventTime );
  850. pKVData->SetString( "ItemEventID", event.eventID );
  851. if ( event.itemID > 0 )
  852. {
  853. pKVData->SetUint64( "GlobalIndex", event.itemID );
  854. }
  855. if ( event.itemDefIndex > 0 )
  856. {
  857. pKVData->SetInt( "DefIndex", event.itemDefIndex );
  858. }
  859. if ( event.GetItemName() != NULL )
  860. {
  861. pKVData->SetString( "ItemNameID", event.GetItemName() );
  862. }
  863. if ( event.numAttempts > 0 )
  864. {
  865. pKVData->SetInt( "CraftAttemptNum", event.numAttempts );
  866. }
  867. if ( event.recipeFound > 0 )
  868. {
  869. pKVData->SetInt( "RecipeFound", event.recipeFound );
  870. }
  871. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  872. }
  873. //-----------------------------------------------------------------------------
  874. // Purpose: Crafting event recorder.
  875. //-----------------------------------------------------------------------------
  876. TF_Gamestats_CraftingEvent::TF_Gamestats_CraftingEvent( int in_eventNum, CEconItemView* in_item, int in_numAttempts, int in_recipe )
  877. : TF_Gamestats_ItemEvent( in_eventNum, in_item )
  878. {
  879. numAttempts = in_numAttempts;
  880. recipeFound = in_recipe;
  881. }
  882. //----------------------------------------------------------------------------
  883. // Purpose: Records a store event for reporting.
  884. //-----------------------------------------------------------------------------
  885. void C_CTFGameStats::Event_Store( int eventID, CEconItemView* item, const char* panelName, int classId,
  886. const cart_item_t* cartItem, int checkoutAttempts, const char* storeError, int totalPrice, int currencyCode )
  887. {
  888. TF_Gamestats_StoreEvent event( eventID, item, panelName, classId, cartItem, checkoutAttempts, storeError, totalPrice, currencyCode );
  889. m_vecStoreEvents.AddToTail( event );
  890. KeyValues* pKVData = new KeyValues( "TF2ClientStoreEvents" );
  891. pKVData->SetUint64( "AccountID", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() );
  892. pKVData->SetInt( "EventCount", m_vecStoreEvents.Count() );
  893. pKVData->SetInt( "EventTime", event.eventTime );
  894. pKVData->SetString( "ItemEventID", event.eventID );
  895. if ( event.eventNum == IE_STORE_ENTERED || event.eventNum == IE_STORE2_ENTERED )
  896. {
  897. if ( m_ulExperimentValue != ((int64) ~0) )
  898. {
  899. pKVData->SetUint64( "GlobalIndex", m_ulExperimentValue );
  900. }
  901. }
  902. else
  903. {
  904. if ( event.itemID > 0 )
  905. {
  906. pKVData->SetUint64( "GlobalIndex", event.itemID );
  907. }
  908. }
  909. if ( event.itemDefIndex > 0 )
  910. {
  911. pKVData->SetInt( "DefIndex", event.itemDefIndex );
  912. }
  913. if ( event.GetItemName() != NULL )
  914. {
  915. pKVData->SetString( "ItemNameID", event.GetItemName() );
  916. }
  917. if ( event.panelName != NULL )
  918. {
  919. pKVData->SetString( "StorePanelID", event.panelName );
  920. }
  921. if ( event.classId > 0 )
  922. {
  923. int iClass = clamp( event.classId, 0, TF_CLASS_COUNT-1 );
  924. pKVData->SetString( "ClassID", g_aPlayerClassNames_NonLocalized[iClass] );
  925. }
  926. if ( event.cartQuantity > 0 )
  927. {
  928. pKVData->SetInt( "NewQuantity", event.cartQuantity );
  929. }
  930. if ( event.cartItemCost > 0 )
  931. {
  932. pKVData->SetInt( "Price", event.cartItemCost );
  933. }
  934. if ( event.currencyCode > 0 )
  935. {
  936. pKVData->SetInt( "CurrencyCode", event.currencyCode-1 );
  937. }
  938. if ( event.checkoutAttempt > 0 )
  939. {
  940. pKVData->SetInt( "CheckoutAttemptNum", event.checkoutAttempt );
  941. }
  942. if ( event.storeError != NULL )
  943. {
  944. pKVData->SetString( "StoreErrorID", event.storeError );
  945. }
  946. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  947. }
  948. //-----------------------------------------------------------------------------
  949. // Purpose: Store event recorder.
  950. //-----------------------------------------------------------------------------
  951. TF_Gamestats_StoreEvent::TF_Gamestats_StoreEvent( int in_eventNum, CEconItemView* in_item, const char* in_panelName, int in_classId,
  952. const cart_item_t* in_cartItem, int in_checkoutAttempts, const char* in_storeError, int in_totalPrice, int in_currencyCode )
  953. : TF_Gamestats_ItemEvent( in_eventNum, in_item )
  954. {
  955. classId = in_classId;
  956. checkoutAttempt = in_checkoutAttempts;
  957. storeError = NULL;
  958. if ( in_storeError )
  959. {
  960. storeError = in_storeError;
  961. }
  962. panelName = NULL;
  963. if ( in_panelName )
  964. {
  965. panelName = in_panelName;
  966. }
  967. cartQuantity = 0;
  968. cartItemCost = 0;
  969. currencyCode = in_currencyCode;
  970. if ( in_cartItem )
  971. {
  972. CEconItemDefinition *pItemDef = ItemSystem()->GetStaticDataForItemByDefIndex( in_cartItem->pEntry->GetItemDefinitionIndex() );
  973. if ( pItemDef )
  974. {
  975. itemDefIndex = in_cartItem->pEntry->GetItemDefinitionIndex();
  976. itemName = pItemDef->GetDefinitionName();
  977. cartQuantity = in_cartItem->iQuantity;
  978. cartItemCost = in_cartItem->pEntry->GetCurrentPrice( (ECurrency)in_currencyCode );
  979. }
  980. }
  981. if ( in_eventNum == IE_STORE_CHECKOUT_SUCCESS && in_totalPrice > 0 )
  982. {
  983. cartItemCost = in_totalPrice;
  984. }
  985. }
  986. //----------------------------------------------------------------------------
  987. // Purpose: Records a general item transaction event.
  988. //-----------------------------------------------------------------------------
  989. void C_CTFGameStats::Event_ItemTransaction( int eventID, CEconItemView* item, const char* pszReason, int iQuality )
  990. {
  991. TF_Gamestats_ItemTransactionEvent event( eventID, item, pszReason, iQuality );
  992. m_vecItemTransactionEvents.AddToTail( event );
  993. KeyValues* pKVData = new KeyValues( "TF2ClientItemTransactionEvents" );
  994. pKVData->SetUint64( "AccountID", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() );
  995. pKVData->SetInt( "EventCount", m_vecItemTransactionEvents.Count() );
  996. pKVData->SetInt( "EventTime", event.eventTime );
  997. pKVData->SetString( "ItemEventID", event.eventID );
  998. if ( event.itemID > 0 )
  999. {
  1000. pKVData->SetUint64( "GlobalIndex", event.itemID );
  1001. }
  1002. if ( event.itemDefIndex > 0 )
  1003. {
  1004. pKVData->SetInt( "DefIndex", event.itemDefIndex );
  1005. }
  1006. if ( event.GetItemName() != NULL )
  1007. {
  1008. pKVData->SetString( "ItemNameID", event.GetItemName() );
  1009. }
  1010. if ( event.reason )
  1011. {
  1012. pKVData->SetString( "ItemTransReasonID", event.reason );
  1013. }
  1014. if ( event.itemQuality > 0 )
  1015. {
  1016. pKVData->SetInt( "ItemQuality", event.itemQuality );
  1017. }
  1018. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  1019. }
  1020. //-----------------------------------------------------------------------------
  1021. // Purpose: Transaction event recorder.
  1022. //-----------------------------------------------------------------------------
  1023. TF_Gamestats_ItemTransactionEvent::TF_Gamestats_ItemTransactionEvent( int in_eventNum, CEconItemView* in_item, const char* in_reason, int in_quality )
  1024. : TF_Gamestats_ItemEvent( in_eventNum, in_item )
  1025. {
  1026. static CSchemaAttributeDefHandle pAttrDef_SupplyCrateSeries( "set supply crate series" );
  1027. reason = in_reason;
  1028. if ( in_item )
  1029. {
  1030. itemQuality = in_item->GetItemQuality();
  1031. // If this is a crate, quality is the series.
  1032. float fSeries;
  1033. if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( in_item, pAttrDef_SupplyCrateSeries, &fSeries ) )
  1034. {
  1035. in_quality = fSeries;
  1036. }
  1037. }
  1038. if ( in_quality > 0 )
  1039. {
  1040. // For crates, indicates the series.
  1041. itemQuality = in_quality;
  1042. }
  1043. }
  1044. void C_CTFGameStats::Event_Trading( TF_Gamestats_TradeEvent& event )
  1045. {
  1046. KeyValues* pKVData = new KeyValues( "TF2ClientTradeEvents" );
  1047. pKVData->SetUint64( "AccountID", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() );
  1048. pKVData->SetInt( "EventCount", m_vecTradeEvents.Count() );
  1049. pKVData->SetInt( "EventTime", event.eventTime );
  1050. pKVData->SetString( "ItemEventID", event.eventID );
  1051. if ( event.itemID > 0 )
  1052. {
  1053. pKVData->SetUint64( "GlobalIndex", event.itemID );
  1054. }
  1055. if ( event.itemDefIndex > 0 )
  1056. {
  1057. pKVData->SetInt( "DefIndex", event.itemDefIndex );
  1058. }
  1059. if ( event.GetItemName() != NULL )
  1060. {
  1061. pKVData->SetString( "ItemNameID", event.GetItemName() );
  1062. }
  1063. if ( event.localPlayerPartyMatters )
  1064. {
  1065. pKVData->SetInt( "IsLocalPlayerPartyA", event.localPlayerIsPartyA );
  1066. }
  1067. if ( event.steamIDPartyA > 0 )
  1068. {
  1069. pKVData->SetUint64( "SteamIDPartyA", event.steamIDPartyA );
  1070. }
  1071. if ( event.steamIDPartyB > 0 )
  1072. {
  1073. pKVData->SetUint64( "SteamIDPartyB", event.steamIDPartyB );
  1074. }
  1075. if ( event.steamIDRequested > 0 )
  1076. {
  1077. pKVData->SetUint64( "RequestedTradeWithSteamID", event.steamIDRequested );
  1078. }
  1079. if ( event.tradeRequests > 0 )
  1080. {
  1081. pKVData->SetInt( "TradeRequestIndex", event.tradeRequests );
  1082. }
  1083. if ( event.tradeAttempts > 0 )
  1084. {
  1085. pKVData->SetInt( "TradeAttemptIndex", event.tradeAttempts );
  1086. }
  1087. if ( event.reason )
  1088. {
  1089. pKVData->SetString( "TradeReasonID", event.reason );
  1090. }
  1091. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  1092. }
  1093. //----------------------------------------------------------------------------
  1094. // Purpose: Records an item trade event.
  1095. //-----------------------------------------------------------------------------
  1096. void C_CTFGameStats::Event_Trading( int eventID, CEconItemView* item, bool localPlayerIsPartyA,
  1097. uint64 steamIDPartyA, uint64 steamIDPartyB, int iTradeRequests, int iTradeAttempts )
  1098. {
  1099. TF_Gamestats_TradeEvent event( eventID, item, localPlayerIsPartyA,
  1100. steamIDPartyA, steamIDPartyB, iTradeRequests, iTradeAttempts );
  1101. m_vecTradeEvents.AddToTail( event );
  1102. Event_Trading( event );
  1103. }
  1104. void C_CTFGameStats::Event_Trading( int eventID, uint64 steamIDRequested, int iTradeRequests, int iTradeAttempts )
  1105. {
  1106. TF_Gamestats_TradeEvent event( eventID, steamIDRequested, iTradeRequests, iTradeAttempts );
  1107. m_vecTradeEvents.AddToTail( event );
  1108. Event_Trading( event );
  1109. }
  1110. void C_CTFGameStats::Event_Trading( int eventID, int iTradeRequests, const char* reason, int iTradeAttempts )
  1111. {
  1112. TF_Gamestats_TradeEvent event( eventID, iTradeRequests, reason, iTradeAttempts );
  1113. m_vecTradeEvents.AddToTail( event );
  1114. Event_Trading( event );
  1115. }
  1116. //-----------------------------------------------------------------------------
  1117. // Purpose: Trade event recorder.
  1118. //-----------------------------------------------------------------------------
  1119. TF_Gamestats_TradeEvent::TF_Gamestats_TradeEvent( int in_eventNum, CEconItemView* in_item, bool in_localPlayerIsPartyA,
  1120. uint64 in_steamIDPartyA, uint64 in_steamIDPartyB,
  1121. int in_tradeRequests, int in_tradeAttempts )
  1122. : TF_Gamestats_ItemEvent( in_eventNum, in_item )
  1123. {
  1124. localPlayerPartyMatters = true;
  1125. localPlayerIsPartyA = in_localPlayerIsPartyA;
  1126. steamIDPartyA = in_steamIDPartyA;
  1127. steamIDPartyB = in_steamIDPartyB;
  1128. steamIDRequested = 0;
  1129. tradeRequests = in_tradeRequests;
  1130. tradeAttempts = in_tradeAttempts;
  1131. reason = NULL;
  1132. }
  1133. TF_Gamestats_TradeEvent::TF_Gamestats_TradeEvent( int in_eventNum, uint64 in_steamIDRequested,
  1134. int in_tradeRequests, int in_tradeAttempts )
  1135. : TF_Gamestats_ItemEvent( in_eventNum, NULL )
  1136. {
  1137. localPlayerPartyMatters = false;
  1138. localPlayerIsPartyA = false;
  1139. steamIDPartyA = 0;
  1140. steamIDPartyB = 0;
  1141. steamIDRequested = in_steamIDRequested;
  1142. tradeRequests = in_tradeRequests;
  1143. tradeAttempts = in_tradeAttempts;
  1144. reason = NULL;
  1145. }
  1146. TF_Gamestats_TradeEvent::TF_Gamestats_TradeEvent( int in_eventNum, int in_tradeRequests, const char* in_reason, int in_tradeAttempts )
  1147. : TF_Gamestats_ItemEvent( in_eventNum, NULL )
  1148. {
  1149. localPlayerPartyMatters = false;
  1150. localPlayerIsPartyA = false;
  1151. steamIDPartyA = 0;
  1152. steamIDPartyB = 0;
  1153. steamIDRequested = 0;
  1154. tradeRequests = in_tradeRequests;
  1155. tradeAttempts = in_tradeAttempts;
  1156. reason = in_reason;
  1157. }
  1158. //-----------------------------------------------------------------------------
  1159. // Purpose: Interface stats recorder.
  1160. //-----------------------------------------------------------------------------
  1161. static const bool g_bRecordClientInterfaceEventsToOGS = false;
  1162. /*static*/ void C_CTFGameStats::ImmediateWriteInterfaceEvent( const char *pszEventType, const char *pszEventDesc )
  1163. {
  1164. if ( !g_bRecordClientInterfaceEventsToOGS )
  1165. return;
  1166. static uint32 s_unEventCount = 0;
  1167. KeyValues* pKVData = new KeyValues( "TF2UIEvents" );
  1168. pKVData->SetInt( "EventCounter", s_unEventCount++ );
  1169. if ( pszEventType )
  1170. {
  1171. pKVData->SetString( "EventTypeID", pszEventType );
  1172. }
  1173. if ( pszEventDesc )
  1174. {
  1175. pKVData->SetString( "EventDescID", pszEventDesc );
  1176. }
  1177. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  1178. }
  1179. //-----------------------------------------------------------------------------
  1180. // Purpose: post results from first step of quickplay
  1181. //-----------------------------------------------------------------------------
  1182. int g_iQuickplaySessionIndex = 0;
  1183. void C_CTFGameStats::QuickplayResults( const TF_Gamestats_QuickPlay_t &info )
  1184. {
  1185. // Update index
  1186. ++g_iQuickplaySessionIndex;
  1187. // Determine sampling rate
  1188. float odds = tf_matchmaking_ogs_odds.GetFloat();
  1189. Assert( odds >= 0.0f && odds <= 1.0f );
  1190. if ( GetUniverse() != k_EUniversePublic )
  1191. {
  1192. odds = 1.0f;
  1193. }
  1194. // Send full or abbreviated stats?
  1195. float r = RandomFloat();
  1196. int iDetailLevel = ( r < odds ) ? TF_Gamestats_QuickPlay_t::k_Server_Ineligible : TF_Gamestats_QuickPlay_t::k_Server_Pinged;
  1197. // Count up servers that meet various criteria
  1198. int nServersResponded = info.m_vecServers.Count();
  1199. int nServersEligible = 0;
  1200. int nServersSentToGC = 0;
  1201. int nServersScoredByGC = 0;
  1202. int nServersPinged = 0;
  1203. for ( int i = 0 ; i < info.m_vecServers.Count() ; ++i )
  1204. {
  1205. const TF_Gamestats_QuickPlay_t::eServerStatus &s = info.m_vecServers[i].m_eStatus;
  1206. if ( s >= TF_Gamestats_QuickPlay_t::k_Server_Eligible )
  1207. {
  1208. ++nServersEligible;
  1209. }
  1210. if ( s >= TF_Gamestats_QuickPlay_t::k_Server_RequestedScore )
  1211. {
  1212. ++nServersSentToGC;
  1213. }
  1214. if ( s >= TF_Gamestats_QuickPlay_t::k_Server_Scored && info.m_vecServers[i].m_fScoreGC > -999.0f )
  1215. {
  1216. ++nServersScoredByGC;
  1217. }
  1218. if ( s >= TF_Gamestats_QuickPlay_t::k_Server_Pinged )
  1219. {
  1220. ++nServersPinged;
  1221. }
  1222. }
  1223. KeyValues* pKVData = new KeyValues( "TF2QuickPlaySession" );
  1224. pKVData->SetInt( "QuickPlaySessionID", g_iQuickplaySessionIndex );
  1225. pKVData->SetFloat( "UserHoursPlayed", info.m_fUserHoursPlayed );
  1226. pKVData->SetString( "TF2GameModeIDSearched", info.m_sUserGameMode );
  1227. pKVData->SetInt( "Result", info.m_eResultCode );
  1228. pKVData->SetInt( "ReportDetail", iDetailLevel );
  1229. pKVData->SetFloat( "SearchTime", info.m_fSearchTime );
  1230. pKVData->SetInt( "ExperimentGroup", info.m_iExperimentGroup );
  1231. pKVData->SetInt( "ServersResponded", nServersResponded );
  1232. pKVData->SetInt( "ServersEligible", nServersEligible );
  1233. pKVData->SetInt( "ServersSentToGC", nServersSentToGC );
  1234. pKVData->SetInt( "ServersScoredByGC", nServersScoredByGC );
  1235. pKVData->SetInt( "ServersPinged", nServersPinged );
  1236. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  1237. for ( int i = 0 ; i < info.m_vecServers.Count() ; ++i )
  1238. {
  1239. const TF_Gamestats_QuickPlay_t::Server_t &s = info.m_vecServers[i];
  1240. // Check if he's not important enough to report
  1241. if ( s.m_eStatus < iDetailLevel )
  1242. {
  1243. continue;
  1244. }
  1245. pKVData = new KeyValues( "TF2QuickPlayResult" );
  1246. pKVData->SetInt( "QuickPlaySessionID", g_iQuickplaySessionIndex );
  1247. pKVData->SetInt( "ServerIP", s.m_ip );
  1248. pKVData->SetInt( "ServerPort", s.m_port );
  1249. pKVData->SetInt( "Status", s.m_eStatus );
  1250. pKVData->SetInt( "ServerIsRegistered", s.m_bRegistered ? 1 : 0 );
  1251. pKVData->SetInt( "ServerIsValve", s.m_bValve ? 1 : 0 );
  1252. pKVData->SetInt( "ServerMapIsNewUserFriendly", s.m_bMapIsNewUserFriendly ? 1 : 0 );
  1253. pKVData->SetInt( "ServerMapIsQuickPlayOK", s.m_bMapIsQuickPlayOK ? 1 : 0 );
  1254. pKVData->SetInt( "ServerIsSecure", s.m_bSecure ? 1 : 0 );
  1255. pKVData->SetInt( "ServerPlayers", s.m_nPlayers );
  1256. pKVData->SetInt( "ServerMaxPlayers", s.m_nMaxPlayers );
  1257. pKVData->SetString( "MapID", s.m_sMapName );
  1258. pKVData->SetInt( "Ping", s.m_iPing );
  1259. pKVData->SetFloat( "ScoreServer", s.m_fScoreServer );
  1260. pKVData->SetFloat( "ScoreClient", s.m_fScoreClient );
  1261. if ( s.m_eStatus >= TF_Gamestats_QuickPlay_t::k_Server_Scored && s.m_fScoreGC > -999.0f )
  1262. {
  1263. pKVData->SetFloat( "ScoreGC", s.m_fScoreGC );
  1264. }
  1265. pKVData->SetString( "TF2ServerTagsID", s.m_sTags );
  1266. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  1267. }
  1268. }