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.

789 lines
25 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //-------------------------------------------------------------
  3. // File: cs_client_gamestats.cpp
  4. // Desc: Manages client side stat storage, accumulation, and access
  5. // Author: Peter Freese <[email protected]>
  6. // Date: 2009/09/11
  7. // Copyright: � 2009 Hidden Path Entertainment
  8. //
  9. // Keywords:
  10. //-------------------------------------------------------------
  11. #include "cbase.h"
  12. #include "cs_client_gamestats.h"
  13. #include "achievementmgr.h"
  14. #include "engine/imatchmaking.h"
  15. #include "ipresence.h"
  16. #include "usermessages.h"
  17. #include "c_cs_player.h"
  18. #include "achievements_cs.h"
  19. #include "vgui/ILocalize.h"
  20. #include "c_team.h"
  21. #include "../shared/steamworks_gamestats.h"
  22. CCSClientGameStats g_CSClientGameStats;
  23. void MsgFunc_PlayerStatsUpdate( bf_read &msg )
  24. {
  25. g_CSClientGameStats.MsgFunc_PlayerStatsUpdate(msg);
  26. }
  27. void MsgFunc_MatchStatsUpdate( bf_read &msg )
  28. {
  29. g_CSClientGameStats.MsgFunc_MatchStatsUpdate(msg);
  30. }
  31. CCSClientGameStats::StatContainerList_t* CCSClientGameStats::s_StatLists = new CCSClientGameStats::StatContainerList_t();
  32. //-----------------------------------------------------------------------------
  33. // Purpose: Constructor
  34. //-----------------------------------------------------------------------------
  35. CCSClientGameStats::CCSClientGameStats()
  36. {
  37. m_bSteamStatsDownload = false;
  38. }
  39. //-----------------------------------------------------------------------------
  40. // Purpose: called at init time after all systems are init'd. We have to
  41. // do this in PostInit because the Steam app ID is not available earlier
  42. //-----------------------------------------------------------------------------
  43. void CCSClientGameStats::PostInit()
  44. {
  45. // listen for events
  46. ListenForGameEvent( "player_stats_updated" );
  47. ListenForGameEvent( "user_data_downloaded" );
  48. ListenForGameEvent( "round_end" );
  49. ListenForGameEvent( "round_start" );
  50. usermessages->HookMessage( "PlayerStatsUpdate", ::MsgFunc_PlayerStatsUpdate );
  51. usermessages->HookMessage( "MatchStatsUpdate", ::MsgFunc_MatchStatsUpdate );
  52. GetSteamWorksSGameStatsUploader().StartSession();
  53. m_RoundEndReason = Invalid_Round_End_Reason;
  54. }
  55. //-----------------------------------------------------------------------------
  56. // Purpose: called at level shutdown
  57. //-----------------------------------------------------------------------------
  58. void CCSClientGameStats::LevelShutdownPreEntity()
  59. {
  60. // This is a good opportunity to update our last match stats
  61. UpdateLastMatchStats();
  62. // upload user stats to Steam on every map change
  63. UpdateSteamStats();
  64. }
  65. //-----------------------------------------------------------------------------
  66. // Purpose: called at app shutdown
  67. //-----------------------------------------------------------------------------
  68. void CCSClientGameStats::Shutdown()
  69. {
  70. GetSteamWorksSGameStatsUploader().EndSession();
  71. }
  72. //-----------------------------------------------------------------------------
  73. // Purpose:
  74. //-----------------------------------------------------------------------------
  75. void CCSClientGameStats::LevelShutdownPreClearSteamAPIContext( void )
  76. {
  77. UploadRoundData();
  78. }
  79. //-----------------------------------------------------------------------------
  80. // Purpose: called when the stats have changed in-game
  81. //-----------------------------------------------------------------------------
  82. void CCSClientGameStats::FireGameEvent( IGameEvent *event )
  83. {
  84. const char *pEventName = event->GetName();
  85. if ( 0 == Q_strcmp( pEventName, "player_stats_updated" ) )
  86. {
  87. UpdateSteamStats();
  88. }
  89. else if ( 0 == Q_strcmp( pEventName, "user_data_downloaded" ) )
  90. {
  91. RetrieveSteamStats();
  92. }
  93. else if ( Q_strcmp( pEventName, "round_end" ) == 0 )
  94. {
  95. // Store off the reason the round ended. Used with the OGS data.
  96. m_RoundEndReason = event->GetInt( "reason", Invalid_Round_End_Reason );
  97. // update player count for last match stats
  98. int iCurrentPlayerCount = 0;
  99. if ( GetGlobalTeam(TEAM_CT) != NULL )
  100. iCurrentPlayerCount += GetGlobalTeam(TEAM_CT)->Get_Number_Players();
  101. if ( GetGlobalTeam(TEAM_TERRORIST) != NULL )
  102. iCurrentPlayerCount += GetGlobalTeam(TEAM_TERRORIST)->Get_Number_Players();
  103. m_matchMaxPlayerCount = MAX(m_matchMaxPlayerCount, iCurrentPlayerCount);
  104. }
  105. // The user stats for a round get sent piece meal, so we'll wait until a new round starts
  106. // before sending the previous round stats.
  107. else if ( Q_strcmp( pEventName, "round_start" ) == 0 && m_roundStats[CSSTAT_PLAYTIME] > 0 )
  108. {
  109. SRoundData *pRoundStatData = new SRoundData( &m_roundStats);
  110. C_CSPlayer *pPlayer = ToCSPlayer( C_BasePlayer::GetLocalPlayer() );
  111. if ( pPlayer )
  112. {
  113. // Our current money + what we spent is what we started with at the beginning of round
  114. pRoundStatData->nStartingMoney = pPlayer->GetAccount() + m_roundStats[CSSTAT_MONEY_SPENT];
  115. }
  116. m_RoundStatData.AddToTail( pRoundStatData );
  117. UploadRoundData();
  118. m_roundStats.Reset();
  119. }
  120. }
  121. void CCSClientGameStats::RetrieveSteamStats()
  122. {
  123. Assert( steamapicontext->SteamUserStats() );
  124. if ( !steamapicontext->SteamUserStats() )
  125. return;
  126. // we shouldn't be downloading stats more than once
  127. Assert(m_bSteamStatsDownload == false);
  128. if (m_bSteamStatsDownload)
  129. return;
  130. int nStatFailCount = 0;
  131. for ( int i = 0; i < CSSTAT_MAX; ++i )
  132. {
  133. if ( CSStatProperty_Table[i].szSteamName == NULL )
  134. continue;
  135. int iData;
  136. if ( steamapicontext->SteamUserStats()->GetStat( CSStatProperty_Table[i].szSteamName, &iData ) )
  137. {
  138. m_lifetimeStats[CSStatProperty_Table[i].statId] = iData;
  139. }
  140. else
  141. {
  142. ++nStatFailCount;
  143. }
  144. }
  145. if ( nStatFailCount > 0 )
  146. {
  147. Msg("RetrieveSteamStats: failed to get %i stats\n", nStatFailCount);
  148. return;
  149. }
  150. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  151. if ( event )
  152. {
  153. gameeventmanager->FireEventClientSide( event );
  154. }
  155. m_bSteamStatsDownload = true;
  156. }
  157. //-----------------------------------------------------------------------------
  158. // Purpose: Uploads stats for current Steam user to Steam
  159. //-----------------------------------------------------------------------------
  160. void CCSClientGameStats::UpdateSteamStats()
  161. {
  162. // only upload if Steam is running
  163. if ( !steamapicontext->SteamUserStats() )
  164. return;
  165. CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() );
  166. Assert(pAchievementMgr != NULL);
  167. if (!pAchievementMgr)
  168. return;
  169. // don't upload any stats if we haven't successfully download stats yet
  170. if ( !m_bSteamStatsDownload )
  171. {
  172. // try to periodically download stats from Steam if we haven't gotten them yet
  173. static float fLastStatsRetrieveTime = 0.0f;
  174. const float kRetrieveInterval = 30.0f;
  175. if ( gpGlobals->curtime > fLastStatsRetrieveTime + kRetrieveInterval )
  176. {
  177. pAchievementMgr->DownloadUserData();
  178. fLastStatsRetrieveTime = gpGlobals->curtime;
  179. }
  180. return;
  181. }
  182. for ( int i = 0; i < CSSTAT_MAX; ++i )
  183. {
  184. if ( CSStatProperty_Table[i].szSteamName == NULL )
  185. continue;
  186. // set the stats locally in Steam client
  187. steamapicontext->SteamUserStats()->SetStat( CSStatProperty_Table[i].szSteamName, m_lifetimeStats[CSStatProperty_Table[i].statId]);
  188. }
  189. // let the achievement manager know the stats have changed
  190. pAchievementMgr->SetDirty(true);
  191. }
  192. CON_COMMAND_F( stats_reset, "Resets all player stats", FCVAR_CLIENTDLL )
  193. {
  194. g_CSClientGameStats.ResetAllStats();
  195. }
  196. CON_COMMAND_F( stats_dump, "Dumps all player stats", FCVAR_DEVELOPMENTONLY )
  197. {
  198. Msg( "Accumulated stats on Steam\n");
  199. const StatsCollection_t& accumulatedStats = g_CSClientGameStats.GetLifetimeStats();
  200. for ( int i = 0; i < CSSTAT_MAX; ++i )
  201. {
  202. if ( CSStatProperty_Table[i].szSteamName == NULL )
  203. continue;
  204. Msg( "%42s: %i\n", CSStatProperty_Table[i].szSteamName, accumulatedStats[CSStatProperty_Table[i].statId]);
  205. }
  206. }
  207. #if defined(_DEBUG)
  208. CON_COMMAND_F( stats_preload, "Load stats with data ripe for getting achievmenets", FCVAR_DEVELOPMENTONLY )
  209. {
  210. struct DataSet
  211. {
  212. CSStatType_t statId;
  213. int value;
  214. };
  215. DataSet statData[] =
  216. {
  217. { CSSTAT_KILLS, 9999},
  218. { CSSTAT_ROUNDS_WON, 4999},
  219. { CSSTAT_PISTOLROUNDS_WON, 249},
  220. { CSSTAT_MONEY_EARNED, 49999999},
  221. { CSSTAT_DAMAGE, 999999},
  222. { CSSTAT_KILLS_DEAGLE, 199},
  223. { CSSTAT_KILLS_USP, 199},
  224. { CSSTAT_KILLS_GLOCK, 199},
  225. { CSSTAT_KILLS_P228, 199},
  226. { CSSTAT_KILLS_ELITE, 99},
  227. { CSSTAT_KILLS_FIVESEVEN, 99},
  228. { CSSTAT_KILLS_AWP, 999},
  229. { CSSTAT_KILLS_AK47, 999},
  230. { CSSTAT_KILLS_M4A1, 999},
  231. { CSSTAT_KILLS_AUG, 499},
  232. { CSSTAT_KILLS_SG552, 499},
  233. { CSSTAT_KILLS_SG550, 499},
  234. { CSSTAT_KILLS_GALIL, 499},
  235. { CSSTAT_KILLS_FAMAS, 499},
  236. { CSSTAT_KILLS_SCOUT, 999},
  237. { CSSTAT_KILLS_G3SG1, 499},
  238. { CSSTAT_KILLS_P90, 999},
  239. { CSSTAT_KILLS_MP5NAVY, 999},
  240. { CSSTAT_KILLS_TMP, 499},
  241. { CSSTAT_KILLS_MAC10, 499},
  242. { CSSTAT_KILLS_UMP45, 999},
  243. { CSSTAT_KILLS_M3, 199},
  244. { CSSTAT_KILLS_XM1014, 199},
  245. { CSSTAT_KILLS_M249, 499},
  246. { CSSTAT_KILLS_KNIFE, 99},
  247. { CSSTAT_KILLS_HEGRENADE, 499},
  248. { CSSTAT_KILLS_HEADSHOT, 249},
  249. { CSSTAT_KILLS_ENEMY_WEAPON, 99},
  250. { CSSTAT_KILLS_ENEMY_BLINDED, 24},
  251. { CSSTAT_NUM_BOMBS_DEFUSED, 99},
  252. { CSSTAT_NUM_BOMBS_PLANTED, 99},
  253. { CSSTAT_NUM_HOSTAGES_RESCUED, 499},
  254. { CSSTAT_KILLS_KNIFE_FIGHT, 99},
  255. { CSSTAT_DECAL_SPRAYS, 99},
  256. { CSSTAT_NIGHTVISION_DAMAGE, 4999},
  257. { CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, 99},
  258. { CSSTAT_MAP_WINS_CS_ASSAULT, 99},
  259. { CSSTAT_MAP_WINS_CS_COMPOUND, 99},
  260. { CSSTAT_MAP_WINS_CS_HAVANA, 99},
  261. { CSSTAT_MAP_WINS_CS_ITALY, 99},
  262. { CSSTAT_MAP_WINS_CS_MILITIA, 99},
  263. { CSSTAT_MAP_WINS_CS_OFFICE, 99},
  264. { CSSTAT_MAP_WINS_DE_AZTEC, 99},
  265. { CSSTAT_MAP_WINS_DE_CBBLE, 99},
  266. { CSSTAT_MAP_WINS_DE_CHATEAU, 99},
  267. { CSSTAT_MAP_WINS_DE_DUST2, 99},
  268. { CSSTAT_MAP_WINS_DE_DUST, 99},
  269. { CSSTAT_MAP_WINS_DE_INFERNO, 99},
  270. { CSSTAT_MAP_WINS_DE_NUKE, 99},
  271. { CSSTAT_MAP_WINS_DE_PIRANESI, 99},
  272. { CSSTAT_MAP_WINS_DE_PORT, 99},
  273. { CSSTAT_MAP_WINS_DE_PRODIGY, 99},
  274. { CSSTAT_MAP_WINS_DE_TIDES, 99},
  275. { CSSTAT_MAP_WINS_DE_TRAIN, 99},
  276. { CSSTAT_WEAPONS_DONATED, 99},
  277. { CSSTAT_DOMINATIONS, 9},
  278. { CSSTAT_DOMINATION_OVERKILLS, 99},
  279. { CSSTAT_REVENGES, 19},
  280. };
  281. StatsCollection_t& lifetimeStats = const_cast<StatsCollection_t&>(g_CSClientGameStats.GetLifetimeStats());
  282. for ( int i = 0; i < ARRAYSIZE(statData); ++i )
  283. {
  284. CSStatType_t statId = statData[i].statId;
  285. lifetimeStats[statId] = statData[i].value;
  286. }
  287. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  288. if ( event )
  289. {
  290. gameeventmanager->FireEventClientSide( event );
  291. }
  292. }
  293. #endif
  294. #if defined(_DEBUG)
  295. CON_COMMAND_F( stats_corrupt, "Load stats with corrupt values", FCVAR_DEVELOPMENTONLY )
  296. {
  297. struct DataSet
  298. {
  299. CSStatType_t statId;
  300. int value;
  301. };
  302. DataSet badData[] =
  303. {
  304. { CSSTAT_SHOTS_HIT, 0x40000089 },
  305. { CSSTAT_SHOTS_FIRED, 0x400002BE },
  306. { CSSTAT_KILLS, 0x40000021 },
  307. { CSSTAT_DEATHS, 0x00000056 },
  308. { CSSTAT_DAMAGE, 0x00000FE3 },
  309. { CSSTAT_NUM_BOMBS_PLANTED, 0x00000004 },
  310. { CSSTAT_NUM_BOMBS_DEFUSED, 0x00000000 },
  311. { CSSTAT_PLAYTIME, 0x40000F46 },
  312. { CSSTAT_ROUNDS_WON, 0x40000028 },
  313. { CSSTAT_ROUNDS_PLAYED, 0x40001019 },
  314. { CSSTAT_PISTOLROUNDS_WON, 0x00000001 },
  315. { CSSTAT_MONEY_EARNED, 0x00021E94 },
  316. { CSSTAT_KILLS_DEAGLE, 0x00000009 },
  317. { CSSTAT_KILLS_USP, 0x00000000 },
  318. { CSSTAT_KILLS_GLOCK, 0x00000002 },
  319. { CSSTAT_KILLS_P228, 0x00000000 },
  320. { CSSTAT_KILLS_ELITE, 0x00000000 },
  321. { CSSTAT_KILLS_FIVESEVEN, 0x00000000 },
  322. { CSSTAT_KILLS_AWP, 0x00000000 },
  323. { CSSTAT_KILLS_AK47, 0x00000001 },
  324. { CSSTAT_KILLS_M4A1, 0x00000000 },
  325. { CSSTAT_KILLS_AUG, 0x00000000 },
  326. { CSSTAT_KILLS_SG552, 0x00000000 },
  327. { CSSTAT_KILLS_SG550, 0x00000000 },
  328. { CSSTAT_KILLS_GALIL, 0x00000000 },
  329. { CSSTAT_KILLS_FAMAS, 0x00000001 },
  330. { CSSTAT_KILLS_SCOUT, 0x00000000 },
  331. { CSSTAT_KILLS_G3SG1, 0x00000000 },
  332. { CSSTAT_KILLS_P90, 0x00000001 },
  333. { CSSTAT_KILLS_MP5NAVY, 0x00000000 },
  334. { CSSTAT_KILLS_TMP, 0x00000002 },
  335. { CSSTAT_KILLS_MAC10, 0x00000000 },
  336. { CSSTAT_KILLS_UMP45, 0x00000001 },
  337. { CSSTAT_KILLS_M3, 0x00000000 },
  338. { CSSTAT_KILLS_XM1014, 0x0000000A },
  339. { CSSTAT_KILLS_M249, 0x00000000 },
  340. { CSSTAT_KILLS_KNIFE, 0x00000000 },
  341. { CSSTAT_KILLS_HEGRENADE, 0x00000000 },
  342. { CSSTAT_SHOTS_DEAGLE, 0x0000004C },
  343. { CSSTAT_SHOTS_USP, 0x00000001 },
  344. { CSSTAT_SHOTS_GLOCK, 0x00000017 },
  345. { CSSTAT_SHOTS_P228, 0x00000000 },
  346. { CSSTAT_SHOTS_ELITE, 0x00000000 },
  347. { CSSTAT_SHOTS_FIVESEVEN, 0x00000000 },
  348. { CSSTAT_SHOTS_AWP, 0x00000000 },
  349. { CSSTAT_SHOTS_AK47, 0x0000000E },
  350. { CSSTAT_SHOTS_M4A1, 0x00000000 },
  351. { CSSTAT_SHOTS_AUG, 0x00000000 },
  352. { CSSTAT_SHOTS_SG552, 0x00000000 },
  353. { CSSTAT_SHOTS_SG550, 0x00000008 },
  354. { CSSTAT_SHOTS_GALIL, 0x00000000 },
  355. { CSSTAT_SHOTS_FAMAS, 0x00000010 },
  356. { CSSTAT_SHOTS_SCOUT, 0x00000000 },
  357. { CSSTAT_SHOTS_G3SG1, 0x00000000 },
  358. { CSSTAT_SHOTS_P90, 0x0000007F },
  359. { CSSTAT_SHOTS_MP5NAVY, 0x00000000 },
  360. { CSSTAT_SHOTS_TMP, 0x00000010 },
  361. { CSSTAT_SHOTS_MAC10, 0x00000000 },
  362. { CSSTAT_SHOTS_UMP45, 0x00000015 },
  363. { CSSTAT_SHOTS_M3, 0x00000009 },
  364. { CSSTAT_SHOTS_XM1014, 0x0000024C },
  365. { CSSTAT_SHOTS_M249, 0x00000000 },
  366. { CSSTAT_HITS_DEAGLE, 0x00000019 },
  367. { CSSTAT_HITS_USP, 0x00000000 },
  368. { CSSTAT_HITS_GLOCK, 0x0000000A },
  369. { CSSTAT_HITS_P228, 0x00000000 },
  370. { CSSTAT_HITS_ELITE, 0x00000000 },
  371. { CSSTAT_HITS_FIVESEVEN, 0x00000000 },
  372. { CSSTAT_HITS_AWP, 0x00000000 },
  373. { CSSTAT_HITS_AK47, 0x00000003 },
  374. { CSSTAT_HITS_M4A1, 0x00000000 },
  375. { CSSTAT_HITS_AUG, 0x00000000 },
  376. { CSSTAT_HITS_SG552, 0x00000000 },
  377. { CSSTAT_HITS_SG550, 0x00000001 },
  378. { CSSTAT_HITS_GALIL, 0x00000000 },
  379. { CSSTAT_HITS_FAMAS, 0x00000007 },
  380. { CSSTAT_HITS_SCOUT, 0x00000000 },
  381. { CSSTAT_HITS_G3SG1, 0x00000000 },
  382. { CSSTAT_HITS_P90, 0x0000000D },
  383. { CSSTAT_HITS_MP5NAVY, 0x00000000 },
  384. { CSSTAT_HITS_TMP, 0x00000006 },
  385. { CSSTAT_HITS_MAC10, 0x00000000 },
  386. { CSSTAT_HITS_UMP45, 0x00000006 },
  387. { CSSTAT_HITS_M3, 0x00000000 },
  388. { CSSTAT_HITS_XM1014, 0x0000004C },
  389. { CSSTAT_HITS_M249, 0x00000000 },
  390. { CSSTAT_KILLS_HEADSHOT, 0x00000013 },
  391. { CSSTAT_KILLS_ENEMY_BLINDED, 0x00000002 },
  392. { CSSTAT_KILLS_ENEMY_WEAPON, 0x00000002 },
  393. { CSSTAT_KILLS_KNIFE_FIGHT, 0x00000000 },
  394. { CSSTAT_DECAL_SPRAYS, 0x00000000 },
  395. { CSSTAT_NIGHTVISION_DAMAGE, 0x00000000 },
  396. { CSSTAT_NUM_HOSTAGES_RESCUED, 0x00000000 },
  397. { CSSTAT_NUM_BROKEN_WINDOWS, 0x00000000 },
  398. { CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, 0x00000000 },
  399. { CSSTAT_WEAPONS_DONATED, 0x00000000 },
  400. { CSSTAT_DOMINATIONS, 0x00000001 },
  401. { CSSTAT_DOMINATION_OVERKILLS, 0x00000000 },
  402. { CSSTAT_REVENGES, 0x00000000 },
  403. { CSSTAT_MVPS, 0x00000005 },
  404. { CSSTAT_MAP_WINS_CS_ASSAULT, 0x00000000 },
  405. { CSSTAT_MAP_WINS_CS_COMPOUND, 0x00000000 },
  406. { CSSTAT_MAP_WINS_CS_HAVANA, 0x00000000 },
  407. { CSSTAT_MAP_WINS_CS_ITALY, 0x40000002 },
  408. { CSSTAT_MAP_WINS_CS_MILITIA, 0x00000000 },
  409. { CSSTAT_MAP_WINS_CS_OFFICE, 0x00000000 },
  410. { CSSTAT_MAP_WINS_DE_AZTEC, 0x0000000A },
  411. { CSSTAT_MAP_WINS_DE_CBBLE, 0x40000000 },
  412. { CSSTAT_MAP_WINS_DE_CHATEAU, 0x00000000 },
  413. { CSSTAT_MAP_WINS_DE_DUST2, 0x0000000B },
  414. { CSSTAT_MAP_WINS_DE_DUST, 0x00000000 },
  415. { CSSTAT_MAP_WINS_DE_INFERNO, 0x00000000 },
  416. { CSSTAT_MAP_WINS_DE_NUKE, 0x00000000 },
  417. { CSSTAT_MAP_WINS_DE_PIRANESI, 0x00000000 },
  418. { CSSTAT_MAP_WINS_DE_PORT, 0x00000000 },
  419. { CSSTAT_MAP_WINS_DE_PRODIGY, 0x00000000 },
  420. { CSSTAT_MAP_WINS_DE_TIDES, 0x00000000 },
  421. { CSSTAT_MAP_WINS_DE_TRAIN, 0x00000000 },
  422. { CSSTAT_MAP_ROUNDS_CS_ASSAULT, 0x00000000 },
  423. { CSSTAT_MAP_ROUNDS_CS_COMPOUND, 0x00000000 },
  424. { CSSTAT_MAP_ROUNDS_CS_HAVANA, 0x00000000 },
  425. { CSSTAT_MAP_ROUNDS_CS_ITALY, 0x00000000 },
  426. { CSSTAT_MAP_ROUNDS_CS_MILITIA, 0x00000000 },
  427. { CSSTAT_MAP_ROUNDS_CS_OFFICE, 0x00000003 },
  428. { CSSTAT_MAP_ROUNDS_DE_AZTEC, 0x00000019 },
  429. { CSSTAT_MAP_ROUNDS_DE_CBBLE, 0x00000000 },
  430. { CSSTAT_MAP_ROUNDS_DE_CHATEAU, 0x00000000 },
  431. { CSSTAT_MAP_ROUNDS_DE_DUST2, 0x00000014 },
  432. { CSSTAT_MAP_ROUNDS_DE_DUST, 0x00000000 },
  433. { CSSTAT_MAP_ROUNDS_DE_INFERNO, 0x00000000 },
  434. { CSSTAT_MAP_ROUNDS_DE_NUKE, 0x00000000 },
  435. { CSSTAT_MAP_ROUNDS_DE_PIRANESI, 0x00000000 },
  436. { CSSTAT_MAP_ROUNDS_DE_PORT, 0x00000000 },
  437. { CSSTAT_MAP_ROUNDS_DE_PRODIGY, 0x00000000 },
  438. { CSSTAT_MAP_ROUNDS_DE_TIDES, 0x00000000 },
  439. { CSSTAT_MAP_ROUNDS_DE_TRAIN, 0x00000000 },
  440. { CSSTAT_LASTMATCH_T_ROUNDS_WON, 0x00000000 },
  441. { CSSTAT_LASTMATCH_CT_ROUNDS_WON, 0x00000000 },
  442. { CSSTAT_LASTMATCH_ROUNDS_WON, 0x40000000 },
  443. { CSSTAT_LASTMATCH_KILLS, 0x00000000 },
  444. { CSSTAT_LASTMATCH_DEATHS, 0x00000000 },
  445. { CSSTAT_LASTMATCH_MVPS, 0x00000000 },
  446. { CSSTAT_LASTMATCH_DAMAGE, 0x00000000 },
  447. { CSSTAT_LASTMATCH_MONEYSPENT, 0x00000000 },
  448. { CSSTAT_LASTMATCH_DOMINATIONS, 0x00000000 },
  449. { CSSTAT_LASTMATCH_REVENGES, 0x00000000 },
  450. { CSSTAT_LASTMATCH_MAX_PLAYERS, 0x0000001B },
  451. { CSSTAT_LASTMATCH_FAVWEAPON_ID, 0x00000000 },
  452. { CSSTAT_LASTMATCH_FAVWEAPON_SHOTS, 0x00000000 },
  453. { CSSTAT_LASTMATCH_FAVWEAPON_HITS, 0x00000000 },
  454. { CSSTAT_LASTMATCH_FAVWEAPON_KILLS, 0x00000000 },
  455. };
  456. StatsCollection_t& lifetimeStats = const_cast<StatsCollection_t&>(g_CSClientGameStats.GetLifetimeStats());
  457. for ( int i = 0; i < ARRAYSIZE(badData); ++i )
  458. {
  459. CSStatType_t statId = badData[i].statId;
  460. lifetimeStats[statId] = badData[i].value;
  461. }
  462. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  463. if ( event )
  464. {
  465. gameeventmanager->FireEventClientSide( event );
  466. }
  467. }
  468. #endif
  469. int CCSClientGameStats::GetStatCount()
  470. {
  471. return CSSTAT_MAX;
  472. }
  473. PlayerStatData_t CCSClientGameStats::GetStatByIndex( int index )
  474. {
  475. PlayerStatData_t statData;
  476. statData.iStatId = CSStatProperty_Table[index].statId;
  477. statData.iStatValue = m_lifetimeStats[statData.iStatId];
  478. // we can make this more efficient by caching the localized names
  479. statData.pStatDisplayName = g_pVGuiLocalize->Find( CSStatProperty_Table[index].szLocalizationToken );
  480. return statData;
  481. }
  482. PlayerStatData_t CCSClientGameStats::GetStatById( int id )
  483. {
  484. Assert(id >= 0 && id < CSSTAT_MAX);
  485. if ( id >= 0 && id < CSSTAT_MAX)
  486. {
  487. return GetStatByIndex(id);
  488. }
  489. else
  490. {
  491. PlayerStatData_t dummy;
  492. dummy.pStatDisplayName = NULL;
  493. dummy.iStatId = CSSTAT_UNDEFINED;
  494. dummy.iStatValue = 0;
  495. return dummy;
  496. }
  497. }
  498. void CCSClientGameStats::UpdateStats( const StatsCollection_t &stats )
  499. {
  500. C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer();
  501. if ( !pPlayer )
  502. return;
  503. // don't count stats if cheats on, commentary mode, etc
  504. if ( !g_AchievementMgrCS.CheckAchievementsEnabled() )
  505. return;
  506. // Update the accumulated stats
  507. m_lifetimeStats.Aggregate(stats);
  508. m_matchStats.Aggregate(stats);
  509. m_roundStats.Aggregate(stats);
  510. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  511. if ( event )
  512. {
  513. gameeventmanager->FireEventClientSide( event );
  514. }
  515. }
  516. void CCSClientGameStats::ResetAllStats( void )
  517. {
  518. m_lifetimeStats.Reset();
  519. m_matchStats.Reset();
  520. m_roundStats.Reset();
  521. // reset the stats on Steam
  522. if (steamapicontext && steamapicontext->SteamUserStats())
  523. {
  524. steamapicontext->SteamUserStats()->ResetAllStats(false);
  525. }
  526. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  527. if ( event )
  528. {
  529. gameeventmanager->FireEventClientSide( event );
  530. }
  531. }
  532. void CCSClientGameStats::MsgFunc_MatchStatsUpdate( bf_read &msg )
  533. {
  534. int firstStat = msg.ReadShort();
  535. for (int iStat = firstStat; iStat < CSSTAT_MAX && msg.GetNumBytesLeft() > 0; iStat++ )
  536. {
  537. m_directTStatAverages.m_fStat[iStat] = msg.ReadFloat();
  538. m_directCTStatAverages.m_fStat[iStat] = msg.ReadFloat();
  539. m_directPlayerStatAverages.m_fStat[iStat] = msg.ReadFloat();
  540. }
  541. // sanity check: the message should contain exactly the # of bytes we expect based on the bit field
  542. Assert( !msg.IsOverflowed() );
  543. Assert( 0 == msg.GetNumBytesLeft() );
  544. }
  545. void CCSClientGameStats::MsgFunc_PlayerStatsUpdate( bf_read &msg )
  546. {
  547. // Note: if any check fails while decoding this message, bail out and disregard this data to avoid
  548. // potentially polluting player stats
  549. StatsCollection_t deltaStats;
  550. CRC32_t crc;
  551. CRC32_Init( &crc );
  552. const uint32 key = 0x82DA9F4C; // this key should match the key in cs_gamestats.cpp
  553. CRC32_ProcessBuffer( &crc, &key, sizeof(key));
  554. const byte version = 0x01;
  555. CRC32_ProcessBuffer( &crc, &version, sizeof(version));
  556. if (msg.ReadByte() != version)
  557. {
  558. Warning("PlayerStatsUpdate message: ignoring unsupported version\n");
  559. return;
  560. }
  561. byte iStatsToRead = msg.ReadByte();
  562. CRC32_ProcessBuffer( &crc, &iStatsToRead, sizeof(iStatsToRead));
  563. for ( int i = 0; i < iStatsToRead; ++i)
  564. {
  565. byte iStat = msg.ReadByte();
  566. CRC32_ProcessBuffer( &crc, &iStat, sizeof(iStat));
  567. if (iStat >= CSSTAT_MAX)
  568. {
  569. Warning("PlayerStatsUpdate: invalid statId encountered; ignoring stats update\n");
  570. return;
  571. }
  572. short delta = msg.ReadShort();
  573. deltaStats[iStat] = delta;
  574. CRC32_ProcessBuffer( &crc, &delta, sizeof(delta));
  575. }
  576. CRC32_Final( &crc );
  577. CRC32_t readCRC = msg.ReadLong();
  578. if (readCRC != crc || msg.IsOverflowed() || ( 0 != msg.GetNumBytesLeft() ) )
  579. {
  580. Warning("PlayerStatsUpdate message from server is corrupt; ignoring\n");
  581. return;
  582. }
  583. // do one additional pass for out of band values
  584. for ( int iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
  585. {
  586. if (deltaStats[iStat] < 0 || deltaStats[iStat] >= 0x4000)
  587. {
  588. Warning("PlayerStatsUpdate message from server has out of band values; ignoring\n");
  589. return;
  590. }
  591. }
  592. // everything looks okay at this point; add these stats for the player's round, match, and lifetime stats
  593. UpdateStats(deltaStats);
  594. }
  595. void CCSClientGameStats::UploadRoundData()
  596. {
  597. // If there's nothing to send, don't bother
  598. if ( !m_RoundStatData.Count() )
  599. return;
  600. // Temporary ConVar to disable stats
  601. if ( sv_noroundstats.GetBool() )
  602. {
  603. m_RoundStatData.PurgeAndDeleteElements();
  604. return;
  605. }
  606. // Send off all OGS stats at level shutdown
  607. KeyValues *pKV = new KeyValues( "basedata" );
  608. if ( !pKV )
  609. return;
  610. pKV->SetString( "MapID", MapName() );
  611. // Add all the vector based stats
  612. for ( int k=0 ; k < m_RoundStatData.Count() ; ++k )
  613. {
  614. m_RoundStatData[ k ] ->nRoundEndReason = m_RoundEndReason;
  615. SubmitStat( m_RoundStatData[ k ] );
  616. }
  617. // Perform the actual submission
  618. SubmitGameStats( pKV );
  619. // Clear out the per map stats
  620. m_RoundStatData.Purge();
  621. pKV->deleteThis();
  622. // Reset the last round's ending status.
  623. m_RoundEndReason = Invalid_Round_End_Reason;
  624. }
  625. void CCSClientGameStats::ResetMatchStats()
  626. {
  627. m_roundStats.Reset();
  628. m_matchStats.Reset();
  629. m_matchMaxPlayerCount = 0;
  630. }
  631. void CCSClientGameStats::UpdateLastMatchStats()
  632. {
  633. // only update that last match if we actually have valid data
  634. if ( m_matchStats[CSSTAT_ROUNDS_PLAYED] == 0 )
  635. return;
  636. // check to see if the player materially participate; they could have been spectating or joined just in time for the ending.
  637. int s = 0;
  638. s += m_matchStats[CSSTAT_ROUNDS_WON];
  639. s += m_matchStats[CSSTAT_KILLS];
  640. s += m_matchStats[CSSTAT_DEATHS];
  641. s += m_matchStats[CSSTAT_MVPS];
  642. s += m_matchStats[CSSTAT_DAMAGE];
  643. s += m_matchStats[CSSTAT_MONEY_SPENT];
  644. if ( s == 0 )
  645. return;
  646. m_lifetimeStats[CSSTAT_LASTMATCH_T_ROUNDS_WON] = m_matchStats[CSSTAT_T_ROUNDS_WON];
  647. m_lifetimeStats[CSSTAT_LASTMATCH_CT_ROUNDS_WON] = m_matchStats[CSSTAT_CT_ROUNDS_WON];
  648. m_lifetimeStats[CSSTAT_LASTMATCH_ROUNDS_WON] = m_matchStats[CSSTAT_ROUNDS_WON];
  649. m_lifetimeStats[CSSTAT_LASTMATCH_KILLS] = m_matchStats[CSSTAT_KILLS];
  650. m_lifetimeStats[CSSTAT_LASTMATCH_DEATHS] = m_matchStats[CSSTAT_DEATHS];
  651. m_lifetimeStats[CSSTAT_LASTMATCH_MVPS] = m_matchStats[CSSTAT_MVPS];
  652. m_lifetimeStats[CSSTAT_LASTMATCH_DAMAGE] = m_matchStats[CSSTAT_DAMAGE];
  653. m_lifetimeStats[CSSTAT_LASTMATCH_MONEYSPENT] = m_matchStats[CSSTAT_MONEY_SPENT];
  654. m_lifetimeStats[CSSTAT_LASTMATCH_DOMINATIONS] = m_matchStats[CSSTAT_DOMINATIONS];
  655. m_lifetimeStats[CSSTAT_LASTMATCH_REVENGES] = m_matchStats[CSSTAT_REVENGES];
  656. m_lifetimeStats[CSSTAT_LASTMATCH_MAX_PLAYERS] = m_matchMaxPlayerCount;
  657. CalculateMatchFavoriteWeapons();
  658. }
  659. //-----------------------------------------------------------------------------
  660. // Purpose: Calculate and store the match favorite weapon for each player as only deltaStats for that weapon are stored on Steam
  661. //-----------------------------------------------------------------------------
  662. void CCSClientGameStats::CalculateMatchFavoriteWeapons()
  663. {
  664. int maxKills = 0, maxKillId = -1;
  665. for( int j = CSSTAT_KILLS_DEAGLE; j <= CSSTAT_KILLS_M249; ++j )
  666. {
  667. if ( m_matchStats[j] > maxKills )
  668. {
  669. maxKills = m_matchStats[j];
  670. maxKillId = j;
  671. }
  672. }
  673. if ( maxKillId == -1 )
  674. {
  675. m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_ID] = WEAPON_NONE;
  676. m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_SHOTS] = 0;
  677. m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_HITS] = 0;
  678. m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_KILLS] = 0;
  679. }
  680. else
  681. {
  682. int statTableID = -1;
  683. for (int j = 0; WeaponName_StatId_Table[j].killStatId != CSSTAT_UNDEFINED; ++j)
  684. {
  685. if ( WeaponName_StatId_Table[j].killStatId == maxKillId )
  686. {
  687. statTableID = j;
  688. break;
  689. }
  690. }
  691. Assert( statTableID != -1 );
  692. m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_ID] = WeaponName_StatId_Table[statTableID].weaponId;
  693. m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_SHOTS] = m_matchStats[WeaponName_StatId_Table[statTableID].shotStatId];
  694. m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_HITS] = m_matchStats[WeaponName_StatId_Table[statTableID].hitStatId];
  695. m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_KILLS] = m_matchStats[WeaponName_StatId_Table[statTableID].killStatId];
  696. }
  697. }