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.

2042 lines
71 KiB

  1. //-------------------------------------------------------------
  2. // File: cs_client_gamestats.cpp
  3. // Desc: Manages client side stat storage, accumulation, and access
  4. // Author: Peter Freese <[email protected]>
  5. // Date: 2009/09/11
  6. // Copyright: � 2009 Hidden Path Entertainment
  7. //
  8. // Keywords:
  9. //-------------------------------------------------------------
  10. #include "cbase.h"
  11. #include "cs_client_gamestats.h"
  12. #include "achievementmgr.h"
  13. #include "usermessages.h"
  14. #include "c_cs_player.h"
  15. #include "achievements_cs.h"
  16. #include "vgui/ILocalize.h"
  17. #include "c_team.h"
  18. #include "engineinterface.h"
  19. #include "matchmaking/mm_helpers.h"
  20. #include "cstrikeloadout.h"
  21. #include "gametypes.h"
  22. #include "cs_gamerules.h"
  23. #include "matchmaking/iplayerrankingdata.h"
  24. #include "inputsystem/iinputsystem.h"
  25. #include "platforminputdevice.h"
  26. #include "cs_player_rank_mgr.h"
  27. #include "hltvreplaysystem.h"
  28. #if defined (_X360)
  29. #include "ixboxsystem.h"
  30. #include "../common/xlast_csgo/csgo.spa.h"
  31. #endif
  32. #ifdef _PS3
  33. #include "ps3/ps3_helpers.h"
  34. #endif
  35. // NOTE: This has to be the last file included!
  36. #include "tier0/memdbgon.h"
  37. // Added to facilitate data collection below. Add and remove to match current experimental column data
  38. extern ConVar cl_crosshairstyle;
  39. extern ConVar cl_hud_color;
  40. extern ConVar cl_hud_healthammo_style;
  41. extern ConVar cl_hud_bomb_under_radar;
  42. extern ConVar cl_hud_playercount_pos;
  43. extern ConVar cl_hud_playercount_showcount;
  44. extern ConVar cl_radar_rotate;
  45. CCSClientGameStats g_CSClientGameStats;
  46. bool MsgFunc_PlayerStatsUpdate( const CCSUsrMsg_PlayerStatsUpdate &msg )
  47. {
  48. return g_CSClientGameStats.MsgFunc_PlayerStatsUpdate(msg);
  49. }
  50. #ifdef _X360
  51. static CAsyncLeaderboardWriteThread g_AsyncLeaderboardWriteThread;
  52. #endif
  53. struct MapName_LBStatID
  54. {
  55. char *szMapName;
  56. DWORD mapLeaderboardStat;
  57. };
  58. // [jason] Map name -> Leaderboard property mapping. Should be kept in sync with maplist.txt
  59. const MapName_LBStatID MapName_LBStatId_Table[] =
  60. {
  61. {"cs_italy", PROPERTY_CSS_LB_GP_TIME_MAP_ITALY },
  62. {"cs_office", PROPERTY_CSS_LB_GP_TIME_MAP_OFFICE },
  63. {"de_aztec", PROPERTY_CSS_LB_GP_TIME_MAP_AZTEC },
  64. {"de_dust", PROPERTY_CSS_LB_GP_TIME_MAP_DUST },
  65. {"de_dust2", PROPERTY_CSS_LB_GP_TIME_MAP_DUST2 },
  66. {"de_inferno", PROPERTY_CSS_LB_GP_TIME_MAP_INFERNO },
  67. {"de_nuke", PROPERTY_CSS_LB_GP_TIME_MAP_NUKE },
  68. {"de_shorttrain", PROPERTY_CSS_LB_GP_TIME_MAP_SHORTTRAIN },
  69. {"ar_baggage", PROPERTY_CSS_LB_GP_TIME_MAP_BAGGAGE },
  70. {"ar_shoots", PROPERTY_CSS_LB_GP_TIME_MAP_SHOOTS },
  71. {"de_bank", PROPERTY_CSS_LB_GP_TIME_MAP_BANK },
  72. {"de_lake", PROPERTY_CSS_LB_GP_TIME_MAP_LAKE },
  73. {"de_safehouse", PROPERTY_CSS_LB_GP_TIME_MAP_SAFEHOUSE },
  74. {"de_sugarcane", PROPERTY_CSS_LB_GP_TIME_MAP_SUGARCANE },
  75. {"de_stmarc", PROPERTY_CSS_LB_GP_TIME_MAP_STMARC },
  76. {"de_train", PROPERTY_CSS_LB_GP_TIME_MAP_TRAIN },
  77. {"training1", PROPERTY_CSS_LB_GP_TIME_MAP_TRAINING },
  78. {"", DWORD(-1) },
  79. };
  80. const int kNumMapLeaderboardEntries = sizeof(MapName_LBStatId_Table)/sizeof(MapName_LBStatId_Table[0]);
  81. struct LeaderboardMap_t
  82. {
  83. DWORD winsId;
  84. char* winsName;
  85. DWORD csId;
  86. char* csName;
  87. DWORD kdId;
  88. char* kdName;
  89. DWORD starsId;
  90. char* starsName;
  91. DWORD gpId;
  92. char* gpName;
  93. };
  94. // Mapping of game mode/type to leaderboard id
  95. LeaderboardMap_t g_LeaderboardIDMap[] =
  96. {
  97. { STATS_VIEW_WINS_ONLINE_CASUAL, "WINS_ONLINE_CASUAL", STATS_VIEW_CS_ONLINE_CASUAL, "CS_ONLINE_CASUAL", STATS_VIEW_KD_ONLINE_CASUAL, "KD_ONLINE_CASUAL", STATS_VIEW_STARS_ONLINE_CASUAL, "STARS_ONLINE_CASUAL", STATS_VIEW_GP_ONLINE_CASUAL, "GP_ONLINE_CASUAL" },
  98. { STATS_VIEW_WINS_ONLINE_COMPETITIVE, "WINS_ONLINE_COMPETITIVE", STATS_VIEW_CS_ONLINE_COMPETITIVE, "CS_ONLINE_COMPETITIVE", STATS_VIEW_KD_ONLINE_COMPETITIVE, "KD_ONLINE_COMPETITIVE", STATS_VIEW_STARS_ONLINE_COMPETITIVE, "STARS_ONLINE_COMPETITIVE", STATS_VIEW_GP_ONLINE_COMPETITIVE, "GP_ONLINE_COMPETITIVE" },
  99. { STATS_VIEW_WINS_ONLINE_GG_PROG, "WINS_ONLINE_GG_PROG", STATS_VIEW_CS_ONLINE_GG_PROG, "CS_ONLINE_GG_PROG", STATS_VIEW_KD_ONLINE_GG_PROG, "KD_ONLINE_GG_PROG", STATS_VIEW_STARS_ONLINE_GG_PROG, "STARS_ONLINE_GG_PROG", STATS_VIEW_GP_ONLINE_GG_PROG, "GP_ONLINE_GG_PROG" },
  100. { STATS_VIEW_WINS_ONLINE_GG_BOMB, "WINS_ONLINE_GG_BOMB", STATS_VIEW_CS_ONLINE_GG_BOMB, "CS_ONLINE_GG_BOMB", STATS_VIEW_KD_ONLINE_GG_BOMB, "KD_ONLINE_GG_BOMB", STATS_VIEW_STARS_ONLINE_GG_BOMB, "STARS_ONLINE_GG_BOMB", STATS_VIEW_GP_ONLINE_GG_BOMB, "GP_ONLINE_GG_BOMB" },
  101. };
  102. const int kNumLeaderboardIDs = sizeof(g_LeaderboardIDMap)/sizeof(g_LeaderboardIDMap[0]);
  103. //-----------------------------------------------------------------------------
  104. // Purpose: Constructor
  105. //-----------------------------------------------------------------------------
  106. CCSClientGameStats::CCSClientGameStats()
  107. {
  108. m_bSteamStatsDownload = false;
  109. }
  110. //-----------------------------------------------------------------------------
  111. // Purpose: called at init time after all systems are init'd. We have to
  112. // do this in PostInit because the Steam app ID is not available earlier
  113. //-----------------------------------------------------------------------------
  114. void CCSClientGameStats::PostInit()
  115. {
  116. ACTIVE_SPLITSCREEN_PLAYER_GUARD( 0 );
  117. // listen for events
  118. ListenForGameEvent( "player_stats_updated" );
  119. ListenForGameEvent( "user_data_downloaded" );
  120. ListenForGameEvent( "round_end_upload_stats" );
  121. ListenForGameEvent( "round_end" );
  122. ListenForGameEvent( "cs_game_disconnected" );
  123. ListenForGameEvent( "read_game_titledata" );
  124. ListenForGameEvent( "write_game_titledata" );
  125. ListenForGameEvent( "reset_game_titledata" );
  126. ListenForGameEvent( "update_matchmaking_stats" );
  127. ListenForGameEvent( "begin_new_match" );
  128. ListenForGameEvent( "bomb_planted" );
  129. ListenForGameEvent( "hostage_follows" );
  130. // Client info messages
  131. for ( int hh = 0; hh < MAX_SPLITSCREEN_PLAYERS; ++hh )
  132. {
  133. ACTIVE_SPLITSCREEN_PLAYER_GUARD( hh );
  134. m_UMCMsgPlayerStatsUpdate.Bind< CS_UM_PlayerStatsUpdate, CCSUsrMsg_PlayerStatsUpdate>( UtlMakeDelegate( ::MsgFunc_PlayerStatsUpdate ));
  135. }
  136. #if !defined( _GAMECONSOLE )
  137. m_RoundEndReason = Invalid_Round_End_Reason;
  138. m_bObjectiveAttempted = false;
  139. #endif
  140. }
  141. void CCSClientGameStats::LevelInitPostEntity()
  142. {
  143. #if !defined( _GAMECONSOLE )
  144. // Need this for players who join mid-match to have a client session
  145. if ( CSGameRules()->HasMatchStarted() )
  146. {
  147. GetSteamWorksGameStatsClient().StartSession();
  148. }
  149. #endif
  150. }
  151. //-----------------------------------------------------------------------------
  152. // Purpose: called at level shutdown
  153. //-----------------------------------------------------------------------------
  154. void CCSClientGameStats::LevelShutdownPreEntity()
  155. {
  156. #if !defined( _GAMECONSOLE )
  157. UploadRoundStats();
  158. GetSteamWorksGameStatsClient().EndSession();
  159. #else
  160. // round stats are reset when we upload stats on PC, but we still need to reset on consoles as well so do it here
  161. m_roundStats[0].Reset();
  162. #endif
  163. // This is a good opportunity to update our last match stats
  164. UpdateLastMatchStats();
  165. // upload user stats to Steam on every map change
  166. UpdateSteamStats();
  167. }
  168. static inline int GetNumPlayers( C_Team *pTeam )
  169. {
  170. return pTeam ? pTeam->Get_Number_Players() : 0;
  171. }
  172. //-----------------------------------------------------------------------------
  173. // Purpose: called when the stats have changed in-game
  174. //-----------------------------------------------------------------------------
  175. CEG_NOINLINE void CCSClientGameStats::FireGameEvent( IGameEvent *event )
  176. {
  177. const char *pEventName = event->GetName();
  178. if ( 0 == Q_strcmp( pEventName, "player_stats_updated" ) )
  179. {
  180. UpdateSteamStats();
  181. }
  182. else if ( 0 == Q_strcmp( pEventName, "user_data_downloaded" ) )
  183. {
  184. RetrieveSteamStats();
  185. }
  186. else if ( 0 == Q_strcmp( pEventName, "read_game_titledata" ) )
  187. {
  188. SyncCSStatsToTitleData( event->GetInt( "controllerId" ), CSSTAT_READ_STAT );
  189. SyncCSLoadoutsToTitleData( event->GetInt( "controllerId" ), CSSTAT_READ_STAT );
  190. SyncCSMatchmakingDataToTitleData( event->GetInt( "controllerId" ), CSSTAT_READ_STAT );
  191. SyncCSRankingDataToTitleData( event->GetInt( "controllerId" ), CSSTAT_READ_STAT );
  192. }
  193. else if ( 0 == Q_strcmp( pEventName, "write_game_titledata" ) )
  194. {
  195. SyncCSStatsToTitleData( event->GetInt( "controllerId" ), CSSTAT_WRITE_STAT );
  196. SyncCSLoadoutsToTitleData( event->GetInt( "controllerId" ), CSSTAT_WRITE_STAT );
  197. SyncCSMatchmakingDataToTitleData( event->GetInt( "controllerId" ), CSSTAT_WRITE_STAT );
  198. SyncCSRankingDataToTitleData( event->GetInt( "controllerId" ), CSSTAT_WRITE_STAT );
  199. }
  200. else if ( 0 == Q_strcmp( pEventName, "reset_game_titledata" ) )
  201. {
  202. // $TODO(hpe) need to get controllerID and use it when stats handle splitscreen and loadouts handle splitscreen
  203. ResetMatchmakingData( MMDATA_SCOPE_LIFETIME );
  204. ResetMatchmakingData( MMDATA_SCOPE_ROUND );
  205. // clear stats
  206. int userSlot = XBX_GetSlotByUserId( event->GetInt( "controllerId" ) );
  207. if ( userSlot < 0 || userSlot >= MAX_SPLITSCREEN_PLAYERS )
  208. {
  209. AssertMsg( false, "CCSClientGameStats::FireGameEvent:: reset_game_titledata invalid userSlot\n" );
  210. userSlot = STEAM_PLAYER_SLOT;
  211. }
  212. g_CSClientGameStats.ResetAllStats( userSlot );
  213. }
  214. else if ( Q_strcmp( pEventName, "update_matchmaking_stats" ) == 0 )
  215. {
  216. UpdateMatchmakingData();
  217. }
  218. else if ( Q_strcmp( pEventName, "round_end_upload_stats" ) == 0 )
  219. {
  220. // [jhail] Write leaderboard stats at pre-start, before our stats collection gets reset
  221. WriteLeaderboardStats();
  222. #if !defined( _GAMECONSOLE )
  223. UploadRoundStats();
  224. #else
  225. // round stats are reset when we upload stats on PC, but we still need to reset on consoles as well so do it here
  226. m_roundStats[0].Reset();
  227. #endif
  228. }
  229. else if ( Q_strcmp( pEventName, "round_end" ) == 0 )
  230. {
  231. #ifdef _PS3
  232. g_pGcmSharedData->m_bDeFrag = 1; // Flag for a defrag at round end
  233. #endif
  234. m_RoundEndReason = event->GetInt( "reason", Invalid_Round_End_Reason );
  235. int iCurrentPlayerCount = event->GetInt( "player_count", 0 );
  236. #ifdef DBGFLAG_ASSERT
  237. int nPlayerCountOnClient = GetNumPlayers( GetGlobalTeam( TEAM_CT ) ) + GetNumPlayers( GetGlobalTeam( TEAM_TERRORIST ) );
  238. // don't collect stats at the wrong point in time if round_end is passed through during replay
  239. if ( g_HltvReplaySystem.GetHltvReplayDelay() )
  240. Assert( iCurrentPlayerCount <= nPlayerCountOnClient ); // the number of players at round end can shrink, but cannot grow comparing to the replayed state
  241. else
  242. Assert( nPlayerCountOnClient == 0 || iCurrentPlayerCount == nPlayerCountOnClient ); // if we are not replaying, the number of player must be the same on server and client
  243. #endif
  244. m_matchMaxPlayerCount = Max( m_matchMaxPlayerCount, iCurrentPlayerCount );
  245. }
  246. else if ( 0 == Q_strcmp( pEventName, "cs_game_disconnected" ) )
  247. {
  248. #if !defined( _GAMECONSOLE )
  249. UploadRoundStats();
  250. #else
  251. // round stats are reset when we upload stats on PC, but we still need to reset on consoles as well so do it here
  252. m_roundStats[0].Reset();
  253. #endif
  254. }
  255. else if ( 0 == Q_strcmp( pEventName, "begin_new_match" ) )
  256. {
  257. GetSteamWorksGameStatsClient().EndSession();
  258. GetSteamWorksGameStatsClient().StartSession();
  259. }
  260. else if ( 0 == Q_strcmp( pEventName, "bomb_planted" ) || 0 == Q_strcmp( pEventName, "hostage_follows" ) )
  261. {
  262. //ignore events after round end (planting for cash after CT elimination, picking up a hostage after T elimination
  263. if ( m_RoundEndReason == Invalid_Round_End_Reason || m_RoundEndReason == Game_Commencing )
  264. {
  265. m_bObjectiveAttempted = true;
  266. }
  267. }
  268. }
  269. void CCSClientGameStats::RetrieveSteamStats()
  270. {
  271. Assert( steamapicontext->SteamUserStats() );
  272. if ( !steamapicontext->SteamUserStats() )
  273. return;
  274. // we shouldn't be downloading stats more than once
  275. Assert(m_bSteamStatsDownload == false);
  276. if (m_bSteamStatsDownload)
  277. return;
  278. int nStatFailCount = 0;
  279. for ( int i = 0; i < CSSTAT_MAX; ++i )
  280. {
  281. if ( CSStatProperty_Table[i].szSteamName == NULL )
  282. continue;
  283. int iData;
  284. if ( steamapicontext->SteamUserStats()->GetStat( CSStatProperty_Table[i].szSteamName, &iData ) )
  285. {
  286. m_lifetimeStats[STEAM_PLAYER_SLOT][i] = iData;
  287. // Init our 'last upload' values to those we got from steam.
  288. m_lifetimeStatsLastUpload[STEAM_PLAYER_SLOT][i] = iData;
  289. }
  290. else
  291. {
  292. ++nStatFailCount;
  293. }
  294. }
  295. if ( nStatFailCount > 0 )
  296. {
  297. Msg("RetrieveSteamStats: failed to get %i stats\n", nStatFailCount);
  298. return;
  299. }
  300. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  301. if ( event )
  302. {
  303. gameeventmanager->FireEventClientSide( event );
  304. }
  305. m_bSteamStatsDownload = true;
  306. }
  307. //-----------------------------------------------------------------------------
  308. // Purpose: Uploads stats for current Steam user to Steam
  309. //-----------------------------------------------------------------------------
  310. void CCSClientGameStats::UpdateSteamStats()
  311. {
  312. // only upload if Steam is running
  313. if ( !steamapicontext->SteamUserStats() )
  314. return;
  315. // don't upload any stats if we haven't successfully download stats yet
  316. if ( !m_bSteamStatsDownload )
  317. {
  318. // this used to request stats periodically which is now handled in stats request heartbeat in PlayerLocal::Update
  319. return;
  320. }
  321. for ( int i = 0; i < CSSTAT_MAX; ++i )
  322. {
  323. if ( CSStatProperty_Table[i].szSteamName == NULL )
  324. continue;
  325. if ( m_lifetimeStatsLastUpload[ STEAM_PLAYER_SLOT ][ i ] != m_lifetimeStats[ STEAM_PLAYER_SLOT ][ i ] )
  326. {
  327. // set the stats locally in Steam client
  328. steamapicontext->SteamUserStats()->SetStat( CSStatProperty_Table[ i ].szSteamName, m_lifetimeStats[ STEAM_PLAYER_SLOT ][ i ] );
  329. m_lifetimeStatsLastUpload[ STEAM_PLAYER_SLOT ][ i ] = m_lifetimeStats[ STEAM_PLAYER_SLOT ][ i ];
  330. }
  331. }
  332. // let the achievement manager know the stats have changed
  333. g_AchievementMgrCS.SetDirty( true, STEAM_PLAYER_SLOT );
  334. }
  335. int CCSClientGameStats::GetStatCount()
  336. {
  337. return CSSTAT_MAX;
  338. }
  339. PlayerStatData_t CCSClientGameStats::GetStatById( int id, int nUserSlot )
  340. {
  341. Assert(id >= 0 && id < CSSTAT_MAX);
  342. if ( id >= 0 && id < CSSTAT_MAX)
  343. {
  344. PlayerStatData_t statData;
  345. statData.iStatId = id;
  346. statData.iStatValue = m_lifetimeStats[nUserSlot][statData.iStatId];
  347. // we can make this more efficient by caching the localized names
  348. statData.pStatDisplayName = g_pVGuiLocalize->Find( CSStatProperty_Table[id].szLocalizationToken );
  349. return statData;
  350. }
  351. else
  352. {
  353. PlayerStatData_t dummy;
  354. dummy.pStatDisplayName = NULL;
  355. dummy.iStatId = CSSTAT_UNDEFINED;
  356. dummy.iStatValue = 0;
  357. return dummy;
  358. }
  359. }
  360. const StatsCollection_t& CCSClientGameStats::GetLifetimeStats( int nUserSlot )
  361. {
  362. if ( nUserSlot < 0 || nUserSlot >= MAX_SPLITSCREEN_PLAYERS )
  363. {
  364. AssertMsg( false, "CCSClientGameStats::GetLifetimeStats nUserSlot out of range; using 0\n" );
  365. return m_lifetimeStats[STEAM_PLAYER_SLOT];
  366. }
  367. return m_lifetimeStats[nUserSlot];
  368. }
  369. const StatsCollection_t& CCSClientGameStats::GetMatchStats( int nUserSlot )
  370. {
  371. if ( nUserSlot < 0 || nUserSlot >= MAX_SPLITSCREEN_PLAYERS )
  372. {
  373. AssertMsg( false, "CCSClientGameStats::GetMatchStats nUserSlot out of range; using 0\n" );
  374. return m_matchStats[STEAM_PLAYER_SLOT];
  375. }
  376. return m_matchStats[nUserSlot];
  377. }
  378. const StatsCollection_t& CCSClientGameStats::GetRoundStats( int nUserSlot )
  379. {
  380. if ( nUserSlot < 0 || nUserSlot >= MAX_SPLITSCREEN_PLAYERS )
  381. {
  382. AssertMsg( false, "CCSClientGameStats::GetRoundStats nUserSlot out of range; using 0\n" );
  383. return m_roundStats[STEAM_PLAYER_SLOT];
  384. }
  385. return m_roundStats[nUserSlot];
  386. }
  387. void CCSClientGameStats::UpdateStats( const StatsCollection_t &stats, int nUserSlot )
  388. {
  389. C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer();
  390. if ( !pPlayer )
  391. return;
  392. // don't count stats if cheats on, commentary mode, etc
  393. if ( !g_AchievementMgrCS.CheckAchievementsEnabled() )
  394. return;
  395. // Update matchmaking related stats.
  396. IncrementMatchmakingData( stats );
  397. // Update the accumulated stats
  398. // We don't aggregate stats in Offline Games with "Dumb" or No Bots
  399. if ( CSGameRules() && CSGameRules()->IsAwardsProgressAllowedForBotDifficulty() )
  400. {
  401. m_lifetimeStats[nUserSlot].Aggregate(stats);
  402. m_matchStats[nUserSlot].Aggregate(stats);
  403. m_roundStats[nUserSlot].Aggregate(stats);
  404. }
  405. // $TODO: hpe: sb: pass along the userSlot in the player_stats_updated message
  406. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  407. if ( event )
  408. {
  409. gameeventmanager->FireEventClientSide( event );
  410. }
  411. }
  412. void CCSClientGameStats::ResetAllStats( int nUSerSlot )
  413. {
  414. ISteamUserStats* pSteamUserStats = steamapicontext->SteamUserStats();
  415. if ( pSteamUserStats )
  416. {
  417. pSteamUserStats->ResetAllStats(false);
  418. }
  419. else
  420. {
  421. // need to pass along user slot reset into the player_stats_updated message
  422. int userSlot = nUSerSlot;
  423. if ( userSlot < 0 || userSlot >= MAX_SPLITSCREEN_PLAYERS )
  424. {
  425. AssertMsg( false, "CCSClientGameStats::ResetAllStats invalid userSlot\n");
  426. userSlot = STEAM_PLAYER_SLOT;
  427. }
  428. m_lifetimeStats[userSlot].Reset();
  429. m_matchStats[userSlot].Reset();
  430. m_roundStats[userSlot].Reset();
  431. UpdateSteamStats();
  432. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  433. if ( event )
  434. {
  435. gameeventmanager->FireEventClientSide( event );
  436. }
  437. }
  438. }
  439. void CCSClientGameStats::ResetAllStatsAndAchievements( )
  440. {
  441. ISteamUserStats* pSteamUserStats = steamapicontext->SteamUserStats();
  442. if ( pSteamUserStats )
  443. {
  444. pSteamUserStats->ResetAllStats(true);
  445. }
  446. }
  447. void CRC32Helper_ProcessInt16( CRC32_t &crc, int16 n )
  448. {
  449. int16 plat_n = LittleShort( n );
  450. CRC32_ProcessBuffer( &crc, &plat_n, sizeof(plat_n) );
  451. }
  452. void CRC32Helper_ProcessInt32( CRC32_t &crc, int32 n )
  453. {
  454. int32 plat_n = LittleDWord( n );
  455. CRC32_ProcessBuffer( &crc, &plat_n, sizeof(plat_n) );
  456. }
  457. void CRC32Helper_ProcessUInt32( CRC32_t &crc, uint32 n )
  458. {
  459. uint32 plat_n = LittleDWord( n );
  460. CRC32_ProcessBuffer( &crc, &plat_n, sizeof(plat_n) );
  461. }
  462. bool CCSClientGameStats::MsgFunc_PlayerStatsUpdate( const CCSUsrMsg_PlayerStatsUpdate &msg )
  463. {
  464. // Note: if any check fails while decoding this message, bail out and disregard this data to avoid
  465. // potentially polluting player stats
  466. StatsCollection_t deltaStats;
  467. CRC32_t crc;
  468. CRC32_Init( &crc );
  469. const uint32 key = 0x82DA9F4C; // this key should match the key in cs_gamestats.cpp
  470. CRC32Helper_ProcessUInt32( crc, key );
  471. const byte version = 0x03;
  472. CRC32_ProcessBuffer( &crc, &version, sizeof(version));
  473. if (msg.version() != version)
  474. {
  475. Warning("PlayerStatsUpdate message: ignoring unsupported version\n");
  476. return true;
  477. }
  478. short iStatsToRead = msg.stats_size();
  479. CRC32Helper_ProcessInt16( crc, iStatsToRead );
  480. for ( int i = 0; i < iStatsToRead; ++i)
  481. {
  482. const CCSUsrMsg_PlayerStatsUpdate::Stat &stat = msg.stats(i);
  483. short iStat = stat.idx();
  484. CRC32Helper_ProcessInt16( crc, iStat );
  485. if (iStat >= CSSTAT_MAX)
  486. {
  487. Warning("PlayerStatsUpdate: invalid statId encountered; ignoring stats update\n");
  488. return true;
  489. }
  490. short delta = stat.delta();
  491. deltaStats[iStat] = delta;
  492. CRC32Helper_ProcessInt16( crc, delta );
  493. }
  494. int userID = msg.user_id();
  495. CRC32Helper_ProcessInt32( crc, userID );
  496. CRC32_Final( &crc );
  497. CRC32_t readCRC = msg.crc();
  498. if ( readCRC != crc )
  499. {
  500. Warning("PlayerStatsUpdate message from server is corrupt; ignoring\n");
  501. return true;
  502. }
  503. // do one additional pass for out of band values
  504. for ( int iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
  505. {
  506. if (deltaStats[iStat] < 0 || deltaStats[iStat] >= 0x4000)
  507. {
  508. Warning("PlayerStatsUpdate message from server has out of band values; ignoring\n");
  509. return true;
  510. }
  511. }
  512. // everything looks okay at this point; add these stats for the player's round, match, and lifetime stats
  513. int userSlot = STEAM_PLAYER_SLOT;
  514. #if defined ( _X360 )
  515. for ( int i = 0; i < MAX_SPLITSCREEN_PLAYERS; ++i )
  516. {
  517. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(i);
  518. if ( pLocalPlayer && !pLocalPlayer->IsNPC() )
  519. {
  520. if ( pLocalPlayer->GetUserID() == userID )
  521. {
  522. userSlot = i;
  523. }
  524. }
  525. }
  526. #endif
  527. UpdateStats(deltaStats, userSlot );
  528. return true;
  529. }
  530. void CCSClientGameStats::ResetMatchStats()
  531. {
  532. for ( int userSlot = 0; userSlot < MAX_SPLITSCREEN_PLAYERS; ++userSlot)
  533. {
  534. m_matchStats[userSlot].Reset();
  535. }
  536. m_matchMaxPlayerCount = 0;
  537. }
  538. void CCSClientGameStats::ResetRoundStats( void )
  539. {
  540. for ( int userSlot = 0; userSlot < MAX_SPLITSCREEN_PLAYERS; ++userSlot)
  541. {
  542. m_roundStats[userSlot].Reset();
  543. }
  544. }
  545. // note, since we now reset the stats after we update them each time, this can be called multiple times without overwriting match stats
  546. void CCSClientGameStats::UpdateLastMatchStats( void )
  547. {
  548. for ( int userSlot = 0; userSlot < MAX_SPLITSCREEN_PLAYERS; ++userSlot )
  549. {
  550. // only update that last match if we actually have valid data
  551. if ( m_matchStats[userSlot][CSSTAT_ROUNDS_PLAYED] == 0 )
  552. return;
  553. // check to see if the player materially participate; they could have been spectating or joined just in time for the ending.
  554. int s = 0;
  555. s += m_matchStats[userSlot][CSSTAT_ROUNDS_WON];
  556. s += m_matchStats[userSlot][CSSTAT_KILLS];
  557. s += m_matchStats[userSlot][CSSTAT_DEATHS];
  558. s += m_matchStats[userSlot][CSSTAT_MVPS];
  559. s += m_matchStats[userSlot][CSSTAT_DAMAGE];
  560. s += m_matchStats[userSlot][CSSTAT_MONEY_SPENT];
  561. if( s != 0 && CSGameRules() && CSGameRules()->IsAwardsProgressAllowedForBotDifficulty() )
  562. {
  563. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_CONTRIBUTION_SCORE] = m_matchStats[userSlot][CSSTAT_CONTRIBUTION_SCORE];
  564. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_GG_PROGRESSIVE_CONTRIBUTION_SCORE] = m_matchStats[userSlot][CSSTAT_GG_PROGRESSIVE_CONTRIBUTION_SCORE];
  565. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_T_ROUNDS_WON] = m_matchStats[userSlot][CSSTAT_T_ROUNDS_WON];
  566. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_CT_ROUNDS_WON] = m_matchStats[userSlot][CSSTAT_CT_ROUNDS_WON];
  567. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_ROUNDS_WON] = m_matchStats[userSlot][CSSTAT_ROUNDS_WON];
  568. m_lifetimeStats[userSlot][CSTAT_LASTMATCH_ROUNDS_PLAYED] = m_matchStats[userSlot][CSSTAT_ROUNDS_PLAYED];
  569. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_KILLS] = m_matchStats[userSlot][CSSTAT_KILLS];
  570. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_DEATHS] = m_matchStats[userSlot][CSSTAT_DEATHS];
  571. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_MVPS] = m_matchStats[userSlot][CSSTAT_MVPS];
  572. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_DAMAGE] = m_matchStats[userSlot][CSSTAT_DAMAGE];
  573. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_MONEYSPENT] = m_matchStats[userSlot][CSSTAT_MONEY_SPENT];
  574. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_DOMINATIONS] = m_matchStats[userSlot][CSSTAT_DOMINATIONS];
  575. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_REVENGES] = m_matchStats[userSlot][CSSTAT_REVENGES];
  576. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_MAX_PLAYERS] = m_matchMaxPlayerCount;
  577. CalculateMatchFavoriteWeapons();
  578. }
  579. }
  580. ResetMatchStats();
  581. }
  582. //-----------------------------------------------------------------------------
  583. // Purpose: Calculate and store the match favorite weapon for each player as only deltaStats for that weapon are stored on Steam
  584. //-----------------------------------------------------------------------------
  585. void CCSClientGameStats::CalculateMatchFavoriteWeapons()
  586. {
  587. for ( int userSlot = 0; userSlot < MAX_SPLITSCREEN_PLAYERS; ++userSlot )
  588. {
  589. int maxKills = 0, maxKillId = -1;
  590. for( int j = CSSTAT_KILLS_DEAGLE; j <= CSSTAT_KILLS_M249; ++j )
  591. {
  592. if ( m_matchStats[userSlot][j] > maxKills )
  593. {
  594. maxKills = m_matchStats[userSlot][j];
  595. maxKillId = j;
  596. }
  597. }
  598. if ( maxKillId == -1 )
  599. {
  600. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_FAVWEAPON_ID] = WEAPON_NONE;
  601. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_FAVWEAPON_SHOTS] = 0;
  602. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_FAVWEAPON_HITS] = 0;
  603. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_FAVWEAPON_KILLS] = 0;
  604. }
  605. else
  606. {
  607. int statTableID = -1;
  608. for (int j = 0; WeaponName_StatId_Table[j].killStatId != CSSTAT_UNDEFINED; ++j)
  609. {
  610. if ( WeaponName_StatId_Table[j].killStatId == maxKillId )
  611. {
  612. statTableID = j;
  613. break;
  614. }
  615. }
  616. Assert( statTableID != -1 );
  617. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_FAVWEAPON_ID] = WeaponName_StatId_Table[statTableID].weaponId;
  618. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_FAVWEAPON_SHOTS] = m_matchStats[userSlot][WeaponName_StatId_Table[statTableID].shotStatId];
  619. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_FAVWEAPON_HITS] = m_matchStats[userSlot][WeaponName_StatId_Table[statTableID].hitStatId];
  620. m_lifetimeStats[userSlot][CSSTAT_LASTMATCH_FAVWEAPON_KILLS] = m_matchStats[userSlot][WeaponName_StatId_Table[statTableID].killStatId];
  621. }
  622. }
  623. }
  624. bool CCSClientGameStats::ValidateTitleBlockVersion( TitleDataFieldsDescription_t const *pFields, IPlayerLocal *pPlayerLocal, CSSyncStatValueDirection_t eOp, int titleBlockNo )
  625. {
  626. #if defined ( _X360 )
  627. if ( titleBlockNo < 1 || titleBlockNo > 3 )
  628. return false;
  629. char versionIdentifier[32];
  630. char convarIdenifier[32];
  631. V_snprintf( versionIdentifier, sizeof(versionIdentifier), "TITLEDATA.BLOCK%d.VERSION", titleBlockNo );
  632. V_snprintf( convarIdenifier, sizeof(convarIdenifier), "cl_titledataversionblock%d", titleBlockNo );
  633. // check version number of the specified title block
  634. TitleDataFieldsDescription_t const *versionField = TitleDataFieldsDescriptionFindByString( pFields, versionIdentifier );
  635. if ( !versionField || versionField->m_eDataType != TitleDataFieldsDescription_t::DT_uint16 )
  636. {
  637. Warning( "%s is expected to be defined as DT_uint16\n", versionIdentifier );
  638. return false;
  639. }
  640. ConVarRef cl_titledataversionblock( convarIdenifier );
  641. if ( eOp == CSSTAT_READ_STAT )
  642. {
  643. int versionNumber = TitleDataFieldsDescriptionGetValue<uint16>( versionField, pPlayerLocal );
  644. if ( versionNumber != cl_titledataversionblock.GetInt() )
  645. {
  646. Warning( "ValidateTitleBlockVersion unexpected version number for %s; got %d, expected %d\n", versionIdentifier, versionNumber, cl_titledataversionblock.GetInt() );
  647. return false;
  648. }
  649. }
  650. else // we always set the version field
  651. {
  652. TitleDataFieldsDescriptionSetValue<uint16>( versionField, pPlayerLocal, cl_titledataversionblock.GetInt() );
  653. }
  654. return true;
  655. #else
  656. return false; // no title data for non Xbox systems
  657. #endif
  658. }
  659. //-----------------------------------------------------------------------------
  660. // Purpose: Serialize lifetime stats to the user profile title data
  661. //-----------------------------------------------------------------------------
  662. bool CCSClientGameStats::SyncCSStatsToTitleData( int iController, CSSyncStatValueDirection_t eOp )
  663. {
  664. #if defined ( _X360 )
  665. // we need to hook up a console version of m_bSteamStatsDownload
  666. //// we shouldn't be downloading stats more than once
  667. //Assert(m_bSteamStatsDownload == false);
  668. //if (m_bSteamStatsDownload)
  669. // return;
  670. // get the local player
  671. IPlayerLocal *pPlayerLocal = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( iController );
  672. if ( !pPlayerLocal )
  673. return false;
  674. int userSlot = XBX_GetSlotByUserId( iController );
  675. if ( userSlot < 0 || userSlot >= MAX_SPLITSCREEN_PLAYERS )
  676. {
  677. userSlot = STEAM_PLAYER_SLOT;
  678. }
  679. // we are writing values directly here since we know they are int32 and int16; we add checks to verify data files don't change the data types
  680. // otherwise, we would need to use keyvalue or convar or write extra code we don't need to handle all data types
  681. TitleDataFieldsDescription_t const *pFields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
  682. if ( !ValidateTitleBlockVersion( pFields, pPlayerLocal, eOp, 1 ) )
  683. return false;
  684. char statName[ 256 ];
  685. for ( int i = 0, titleDataStat=0; i < CSSTAT_MAX; ++i )
  686. {
  687. if ( CSStatProperty_Table[i].szSteamName == NULL )
  688. continue;
  689. Q_snprintf( statName, 255, "STATS.usr.stat%.3d", titleDataStat++ );
  690. if ( TitleDataFieldsDescription_t const *pField = TitleDataFieldsDescriptionFindByString( pFields, statName ) )
  691. {
  692. if ( pField->m_eDataType != TitleDataFieldsDescription_t::DT_uint32 )
  693. {
  694. Warning( "%s is expected to be defined as DT_uint32\n", statName );
  695. continue;
  696. }
  697. if ( eOp == CSSTAT_READ_STAT )
  698. m_lifetimeStats[userSlot][i] = TitleDataFieldsDescriptionGetValue<uint32>( pField, pPlayerLocal );
  699. else
  700. TitleDataFieldsDescriptionSetValue<uint32>( pField, pPlayerLocal, m_lifetimeStats[userSlot][i] );
  701. }
  702. else
  703. {
  704. Warning( "Could not find TitleDataField for %s\n", statName );
  705. }
  706. }
  707. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  708. if ( event )
  709. {
  710. gameeventmanager->FireEventClientSide( event );
  711. }
  712. //m_bSteamStatsDownload = true;
  713. #endif
  714. return true;
  715. }
  716. bool CCSClientGameStats::SyncCSLoadoutsToTitleData( int iController, CSSyncStatValueDirection_t eOp )
  717. {
  718. // get the local player
  719. IPlayerLocal *pPlayerLocal = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( iController );
  720. if ( !pPlayerLocal )
  721. return false;
  722. #if defined ( _X360 )
  723. // verify inc file matches hardcoded values
  724. #define CFG( loadoutnum, equipmentnum ) \
  725. int numLoadouts = loadoutnum; \
  726. int numEquipmentSlots = equipmentnum;
  727. #include "xlast_csgo/inc_loadouts_usr.inc"
  728. #undef CFG
  729. if ( numLoadouts != cMaxLoadouts || numEquipmentSlots != cMaxEquipment )
  730. {
  731. Warning( "CCSClientGameStats::SyncCSLoadoutsToTitleData mismatch between inc_loadouts_usr.inc and cMaxLoadouts/Equipment\n" );
  732. return false;
  733. }
  734. // verify version number
  735. TitleDataFieldsDescription_t const *pFields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
  736. if ( !ValidateTitleBlockVersion( pFields, pPlayerLocal, eOp, 3 ) )
  737. return false;
  738. char loadoutName[30];
  739. for (int teamcount = 0; teamcount<2; ++teamcount)
  740. {
  741. CCSLoadout *pLoadoutArray = NULL;
  742. char teamName[10];
  743. if (teamcount)
  744. {
  745. pLoadoutArray = GetBuyMenuLoadoutData(TEAM_TERRORIST);
  746. Q_snprintf( teamName, 10, "T" );
  747. }
  748. else
  749. {
  750. pLoadoutArray = GetBuyMenuLoadoutData(TEAM_CT);
  751. Q_snprintf( teamName, 10, "CT" );
  752. }
  753. for(int i=0; i<cMaxLoadouts; ++i)
  754. {
  755. CCSLoadout &pLoadout = pLoadoutArray[i];
  756. // we can write bytes for the equipment info since we have less than 256 weapons
  757. for (int j=0; j<cMaxEquipment; ++j)
  758. {
  759. Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.EQUIP%.1d.ID", teamName, i, j );
  760. if ( TitleDataFieldsDescription_t const *pField = TitleDataFieldsDescriptionFindByString( pFields, loadoutName ) )
  761. {
  762. if ( eOp == CSSTAT_READ_STAT )
  763. pLoadout.m_EquipmentArray[j].m_EquipmentID = (CSWeaponID)TitleDataFieldsDescriptionGetValue<uint8>( pField, pPlayerLocal );
  764. else
  765. TitleDataFieldsDescriptionSetValue<uint8>( pField, pPlayerLocal, pLoadout.m_EquipmentArray[j].m_EquipmentID );
  766. }
  767. Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.EQUIP%.1d.QUANTITY", teamName, i, j );
  768. if ( TitleDataFieldsDescription_t const *pField = TitleDataFieldsDescriptionFindByString( pFields, loadoutName ) )
  769. {
  770. if ( eOp == CSSTAT_READ_STAT )
  771. pLoadout.m_EquipmentArray[j].m_Quantity = TitleDataFieldsDescriptionGetValue<uint8>( pField, pPlayerLocal );
  772. else
  773. TitleDataFieldsDescriptionSetValue<uint8>( pField, pPlayerLocal, pLoadout.m_EquipmentArray[j].m_Quantity );
  774. }
  775. }
  776. Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.PRIMARY", teamName, i );
  777. if ( TitleDataFieldsDescription_t const *pField = TitleDataFieldsDescriptionFindByString( pFields, loadoutName ) )
  778. {
  779. if ( eOp == CSSTAT_READ_STAT )
  780. pLoadout.m_primaryWeaponID = (CSWeaponID)TitleDataFieldsDescriptionGetValue<uint8>( pField, pPlayerLocal );
  781. else
  782. TitleDataFieldsDescriptionSetValue<uint8>( pField, pPlayerLocal, pLoadout.m_primaryWeaponID );
  783. }
  784. Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.SECONDARY", teamName, i );
  785. if ( TitleDataFieldsDescription_t const *pField = TitleDataFieldsDescriptionFindByString( pFields, loadoutName ) )
  786. {
  787. if ( eOp == CSSTAT_READ_STAT )
  788. pLoadout.m_secondaryWeaponID = (CSWeaponID)TitleDataFieldsDescriptionGetValue<uint8>( pField, pPlayerLocal );
  789. else
  790. TitleDataFieldsDescriptionSetValue<uint8>( pField, pPlayerLocal, pLoadout.m_secondaryWeaponID );
  791. }
  792. Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.FLAGS", teamName, i );
  793. if ( TitleDataFieldsDescription_t const *pField = TitleDataFieldsDescriptionFindByString( pFields, loadoutName ) )
  794. {
  795. if ( eOp == CSSTAT_READ_STAT )
  796. pLoadout.m_flags = TitleDataFieldsDescriptionGetValue<uint8>( pField, pPlayerLocal );
  797. else
  798. TitleDataFieldsDescriptionSetValue<uint8>( pField, pPlayerLocal, pLoadout.m_flags );
  799. }
  800. }
  801. }
  802. #endif
  803. return true;
  804. }
  805. #if defined( _X360 )
  806. // Purpose: Helper function to write properties to the XUSER_PROPERTY stream for each leaderboard
  807. static void WriteProperty( XUSER_PROPERTY *props, int index, DWORD propId, BYTE type, void* data )
  808. {
  809. XUSER_PROPERTY &property = props[index];
  810. property.dwPropertyId = propId;
  811. property.value.type = type;
  812. switch ( type )
  813. {
  814. default:
  815. Warning( "CS_CLIENT_GAMESTATS: WriteProperty error: unknown data type: %d!\n", type );
  816. break;
  817. case XUSER_DATA_TYPE_FLOAT:
  818. property.value.type = XUSER_DATA_TYPE_INT64; // Float isn't supported on Leaderboards: Convert to a 64-bit int and write it out scaled-up
  819. property.value.i64Data = 10000000 * *((float*)data);
  820. break;
  821. case XUSER_DATA_TYPE_INT64:
  822. property.value.i64Data = *((LONGLONG*)data);
  823. break;
  824. case XUSER_DATA_TYPE_INT32:
  825. property.value.nData = *((LONG*)data);
  826. break;
  827. }
  828. }
  829. #endif // #if defined( _X360 )
  830. // Purpose: resets the server-side leaderboards for testing purposes
  831. void CCSClientGameStats::ResetLeaderboardStats( void )
  832. {
  833. #if defined ( _X360 )
  834. #if !defined ( _CERT )
  835. for ( int id=STATS_VIEW_WINS_ONLINE_CASUAL; id<=STATS_VIEW_GP_ONLINE_GG_BOMB; ++id )
  836. XUserResetStatsViewAllUsers( id, NULL );
  837. #endif // !_CERT
  838. #endif // _X360
  839. }
  840. CEG_NOINLINE void CCSClientGameStats::WriteLeaderboardStats( void )
  841. {
  842. return; // disabling client-writing leaderboards for now
  843. #if !defined( _X360 )
  844. for ( int userSlot = 0; userSlot < MAX_SPLITSCREEN_PLAYERS; ++userSlot )
  845. {
  846. ACTIVE_SPLITSCREEN_PLAYER_GUARD( userSlot );
  847. int userID = XBX_GetUserId( userSlot );
  848. // Skip writing if we haven't completed a round
  849. if ( m_roundStats[userSlot][CSSTAT_ROUNDS_PLAYED] == 0 )
  850. continue;
  851. IPlayerLocal *pProfile = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( userID );
  852. if ( !pProfile )
  853. {
  854. Warning( "CCSClientGameStats::WriteLeaderboardStats failed to find the PlayerLocal Profile for the Active User!\n" );
  855. return;
  856. }
  857. // Retrieve online status from our matchmaking framework.
  858. bool bMultiplayerGame = false;
  859. bool bPublicGame = false;
  860. // Check if this is already a public game
  861. IMatchSession *pMatchSession = g_pMatchFramework ? g_pMatchFramework->GetMatchSession() : NULL;
  862. if ( pMatchSession )
  863. {
  864. KeyValues *pSystemData = pMatchSession->GetSessionSystemData();
  865. if ( pSystemData )
  866. {
  867. KeyValues *kv = pMatchSession->GetSessionSettings();
  868. if ( kv )
  869. {
  870. char const *szOnline = kv->GetString( "system/network", NULL );
  871. if ( szOnline &&
  872. !V_stricmp( "LIVE", szOnline ) )
  873. {
  874. bMultiplayerGame = true;
  875. }
  876. char const *szAccess = kv->GetString( "system/access", NULL );
  877. if ( szAccess &&
  878. !V_stricmp( "public", szAccess ) )
  879. {
  880. bPublicGame = true;
  881. }
  882. char const *szQueue = kv->GetString( "game/mmqueue", NULL );
  883. if ( szQueue && *szQueue )
  884. { // Queue games are always public
  885. bPublicGame = true;
  886. }
  887. }
  888. }
  889. }
  890. // We don't write to leaderboards for Offline or Online Private games
  891. if ( !bMultiplayerGame || !bPublicGame )
  892. return;
  893. // Online write to leaderboard if we played as CT/T during this round
  894. C_CSPlayer *pPlayer = (C_CSPlayer*)C_BasePlayer::GetLocalPlayer( userSlot );
  895. bool bIsCT = true;
  896. if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_CT )
  897. {
  898. bIsCT = true;
  899. }
  900. else if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_TERRORIST )
  901. {
  902. bIsCT = false;
  903. }
  904. else
  905. {
  906. return;
  907. }
  908. // Calculate which set of leaderboards in the g_LeaderboardIDMap we write to, based on the current game mode/type
  909. int boardSetIndex = -1;
  910. switch ( g_pGameTypes->GetCurrentGameType() )
  911. {
  912. case CS_GameType_Classic:
  913. {
  914. if ( bMultiplayerGame && bPublicGame )
  915. {
  916. switch ( g_pGameTypes->GetCurrentGameMode() )
  917. {
  918. case CS_GameMode::Classic_Casual:
  919. boardSetIndex = 0; // ONLINE_CASUAL
  920. break;
  921. case CS_GameMode::Classic_Competitive:
  922. boardSetIndex = 1; // ONLINE_COMPETITIVE
  923. break;
  924. default:
  925. Warning( "Leaderboard Write Error: Unknown CurrentGameMode value: %d!\n", g_pGameTypes->GetCurrentGameMode() );
  926. break;
  927. }
  928. }
  929. }
  930. break;
  931. case CS_GameType_GunGame:
  932. {
  933. if ( bMultiplayerGame && bPublicGame )
  934. {
  935. switch ( g_pGameTypes->GetCurrentGameMode() )
  936. {
  937. case CS_GameMode::GunGame_Progressive:
  938. boardSetIndex = 2; // ONLINE_GG_PROG
  939. break;
  940. case CS_GameMode::GunGame_Bomb:
  941. boardSetIndex = 3; // ONLINE_GG_BOMB
  942. break;
  943. default: // Unsupported game type
  944. Warning( "Leaderboard Write Error: Unknown CurrentGameMode value: %d!\n", g_pGameTypes->GetCurrentGameMode() );
  945. break;
  946. }
  947. }
  948. }
  949. break;
  950. default:
  951. {
  952. Warning( "Leaderboard Write Error: Unknown CurrentGameType value: %d!\n", g_pGameTypes->GetCurrentGameType() );
  953. }
  954. break;
  955. }
  956. // Sanity check the board type we selected:
  957. if ( boardSetIndex < 0 || boardSetIndex >= kNumLeaderboardIDs )
  958. {
  959. Warning( "Leaderboard Write Error: Current Game type/mode does not have a valid Leaderboard associated with it!\n" );
  960. Warning( " Game Setup: type = %d, mode = %d, isMultiplayer? %d, isPublic? %d [ expected boardSetIdx = %d ]\n",
  961. g_pGameTypes->GetCurrentGameType(),
  962. g_pGameTypes->GetCurrentGameMode(),
  963. bMultiplayerGame, bPublicGame, boardSetIndex );
  964. return;
  965. }
  966. // Construct keyvalues that set the values we want to write to the leaderboard.
  967. KeyValues *pLeaderboardInfo = new KeyValues( "leaderboardinfo" );
  968. KeyValues::AutoDelete autoDelete( pLeaderboardInfo );
  969. CEG_PROTECT_MEMBER_FUNCTION( CCSClientGameStats_WriteLeaderboardStats );
  970. //
  971. // Write out: Contribution Score
  972. char csBoardName[256] = {0};
  973. // Write to the appropriate board based on input type for all online boards
  974. InputDevice_t inputDevice = g_pInputSystem->GetCurrentInputDevice();
  975. // If we somehow don't have a device set yet, assume we're using the default device for the platform
  976. if ( inputDevice == INPUT_DEVICE_NONE )
  977. {
  978. inputDevice = PlatformInputDevice::GetDefaultInputDeviceForPlatform();
  979. }
  980. const char* pDeviceName = PlatformInputDevice::GetInputDeviceNameInternal( inputDevice );
  981. if ( pDeviceName == NULL )
  982. {
  983. Warning( "Leaderboard Write Error: Invalid input device (InputType_t = %d)- cannot write to ELO leaderboard!\n", inputDevice );
  984. }
  985. else
  986. {
  987. V_snprintf( csBoardName, ARRAYSIZE(csBoardName), "%s_%s", g_LeaderboardIDMap[boardSetIndex].csName, pDeviceName );
  988. }
  989. KeyValues *pkv = NULL;
  990. if ( csBoardName[0] != 0 )
  991. {
  992. pkv = pLeaderboardInfo->FindKey( csBoardName, true );
  993. if ( pkv )
  994. {
  995. pkv->SetInt( "average_contribution", 0 );
  996. pkv->SetInt( "mvp_awards", m_roundStats[userSlot][CSSTAT_MVPS] );
  997. pkv->SetInt( "rounds_played", m_roundStats[userSlot][CSSTAT_ROUNDS_PLAYED] );
  998. pkv->SetInt( "kills", m_roundStats[userSlot][CSSTAT_KILLS] );
  999. pkv->SetInt( "deaths", m_roundStats[userSlot][CSSTAT_DEATHS] );
  1000. pkv->SetInt( "damage", m_roundStats[userSlot][CSSTAT_DAMAGE] );
  1001. pkv->SetInt( "total_contribution", m_roundStats[userSlot][CSSTAT_CONTRIBUTION_SCORE] );
  1002. }
  1003. }
  1004. //
  1005. // Write out: Kill / Death Ratio
  1006. pkv = pLeaderboardInfo->FindKey( g_LeaderboardIDMap[boardSetIndex].kdName, true );
  1007. if ( pkv )
  1008. {
  1009. pkv->SetInt( "kd_ratio", 0 );
  1010. pkv->SetInt( "kills", m_roundStats[userSlot][CSSTAT_KILLS] );
  1011. pkv->SetInt( "shots_fired", m_roundStats[userSlot][CSSTAT_SHOTS_FIRED] );
  1012. pkv->SetInt( "head_shots", m_roundStats[userSlot][CSSTAT_KILLS_HEADSHOT] );
  1013. pkv->SetInt( "deaths", m_roundStats[userSlot][CSSTAT_DEATHS] );
  1014. pkv->SetInt( "shots_hit", m_roundStats[userSlot][CSSTAT_SHOTS_HIT] );
  1015. pkv->SetInt( "rounds_played", m_roundStats[userSlot][CSSTAT_ROUNDS_PLAYED] );
  1016. }
  1017. //
  1018. // Write out: Wins
  1019. pkv = pLeaderboardInfo->FindKey( g_LeaderboardIDMap[boardSetIndex].winsName, true );
  1020. if ( pkv )
  1021. {
  1022. int winsAsCT = bIsCT ? m_roundStats[userSlot][CSSTAT_CT_ROUNDS_WON] : 0;
  1023. int winsAsT = !bIsCT ? m_roundStats[userSlot][CSSTAT_T_ROUNDS_WON] : 0;
  1024. int totalWins = winsAsCT + winsAsT;
  1025. int totalPlayed = m_roundStats[userSlot][CSSTAT_ROUNDS_PLAYED];
  1026. int totalLosses = clamp( totalPlayed - totalWins, 0, totalPlayed );
  1027. int lossesAsCT = bIsCT ? (totalPlayed - m_roundStats[userSlot][CSSTAT_CT_ROUNDS_WON]) : 0;
  1028. int lossesAsT = !bIsCT ? (totalPlayed - m_roundStats[userSlot][CSSTAT_T_ROUNDS_WON]) : 0;
  1029. pkv->SetInt( "wins_ratio", 0 );
  1030. pkv->SetInt( "total_wins", totalWins );
  1031. pkv->SetInt( "total_losses", totalLosses );
  1032. pkv->SetInt( "win_as_ct", winsAsCT );
  1033. pkv->SetInt( "win_as_t", winsAsT );
  1034. pkv->SetInt( "loss_as_ct", lossesAsCT );
  1035. pkv->SetInt( "loss_as_t", lossesAsT );
  1036. }
  1037. //
  1038. // Write out: Stars
  1039. pkv = pLeaderboardInfo->FindKey( g_LeaderboardIDMap[boardSetIndex].starsName, true );
  1040. if ( pkv )
  1041. {
  1042. // Number of detonations is the number of "completed objectives" if you're on the Terrorist team
  1043. int totalDetonations = 0;
  1044. if ( !bIsCT )
  1045. {
  1046. totalDetonations = m_roundStats[userSlot][CSSTAT_OBJECTIVES_COMPLETED];
  1047. }
  1048. pkv->SetInt( "numstars", m_roundStats[userSlot][CSSTAT_MVPS] );
  1049. pkv->SetInt( "bombs_planted", m_roundStats[userSlot][CSSTAT_NUM_BOMBS_PLANTED] );
  1050. pkv->SetInt( "bombs_detonated", totalDetonations );
  1051. pkv->SetInt( "bombs_defused", m_roundStats[userSlot][CSSTAT_NUM_BOMBS_DEFUSED] );
  1052. pkv->SetInt( "hostages_rescued", m_roundStats[userSlot][CSSTAT_NUM_HOSTAGES_RESCUED] );
  1053. }
  1054. //
  1055. // Write out: Games played
  1056. pkv = pLeaderboardInfo->FindKey( g_LeaderboardIDMap[boardSetIndex].gpName, true );
  1057. if ( pkv )
  1058. {
  1059. // Run through all medals to determine how many have been unlocked
  1060. CUtlMap<int, CBaseAchievement *> &achievements = g_AchievementMgrCS.GetAchievements( userID );
  1061. DWORD nMedalCount = 0;
  1062. for ( int i=achievements.FirstInorder(); i!=achievements.InvalidIndex(); i=achievements.NextInorder(i) )
  1063. {
  1064. if ( achievements[i]->IsAchieved() )
  1065. ++nMedalCount;
  1066. }
  1067. // $TODO: This credits the entire round to having played as CT or T - is there any info about actual playtime?
  1068. int playTimeTotal = m_roundStats[userSlot][CSSTAT_PLAYTIME];
  1069. int ctTime = bIsCT ? playTimeTotal : 0;
  1070. int tTime = !bIsCT ? playTimeTotal : 0;
  1071. pkv->SetInt( "num_rounds", m_roundStats[userSlot][CSSTAT_ROUNDS_PLAYED] );
  1072. pkv->SetInt( "time_played", playTimeTotal );
  1073. pkv->SetInt( "time_played_ct", ctTime );
  1074. pkv->SetInt( "time_played_t", tTime );
  1075. pkv->SetInt( "total_medals", nMedalCount );
  1076. }
  1077. DevMsg( "Updating leaderboards with:\n" );
  1078. KeyValuesDumpAsDevMsg( pLeaderboardInfo, 1 );
  1079. pProfile->UpdateLeaderboardData( pLeaderboardInfo );
  1080. }
  1081. #endif // !_X360
  1082. }
  1083. #ifdef _X360
  1084. CAsyncLeaderboardWriteThread::CAsyncLeaderboardWriteThread()
  1085. {
  1086. m_hThread = NULL;
  1087. m_hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
  1088. }
  1089. CAsyncLeaderboardWriteThread::~CAsyncLeaderboardWriteThread()
  1090. {
  1091. if ( m_hThread )
  1092. ReleaseThreadHandle( m_hThread );
  1093. if ( m_hEvent )
  1094. CloseHandle( m_hEvent );
  1095. }
  1096. CAsyncLeaderboardWriteThread::LeaderboardWriteData_t* CAsyncLeaderboardWriteThread::CreateLeaderboardWriteData( void )
  1097. {
  1098. LeaderboardWriteData_t* pData = new LeaderboardWriteData_t;
  1099. ZeroMemory( pData, sizeof(LeaderboardWriteData_t) );
  1100. return pData;
  1101. }
  1102. void CAsyncLeaderboardWriteThread::QueueData( LeaderboardWriteData_t *pData )
  1103. {
  1104. if ( !pData )
  1105. return;
  1106. AUTO_LOCK( m_mutex );
  1107. m_queue.AddToTail( pData );
  1108. if ( !m_hThread )
  1109. {
  1110. m_hThread = CreateSimpleThread( CallbackThreadProc, this );
  1111. }
  1112. // Signal the event to let the thread know that some data is waiting
  1113. SetEvent( m_hEvent );
  1114. }
  1115. void CAsyncLeaderboardWriteThread::ThreadProc( void )
  1116. {
  1117. for ( ; ; )
  1118. {
  1119. // Wait until our event is signaled that says we have data waiting
  1120. if ( WaitForSingleObject( m_hEvent, INFINITE ) == WAIT_OBJECT_0 )
  1121. {
  1122. // Reset our event
  1123. ResetEvent( m_hEvent );
  1124. while ( m_queue.Count() > 0 )
  1125. {
  1126. // Grab an item from the queue
  1127. LeaderboardWriteData_t *pData = NULL;
  1128. {
  1129. AUTO_LOCK( m_mutex );
  1130. if ( m_queue.Count() )
  1131. {
  1132. pData = m_queue[0];
  1133. m_queue.Remove( 0 );
  1134. }
  1135. }
  1136. // [smessick] Check to see if the player is signed into LIVE
  1137. int userID = pData->userID;
  1138. bool isSignedInToLIVE = ( XUserGetSigninState( userID ) == eXUserSigninState_SignedInToLive );
  1139. // [smessick] Don't attempt the write to the leaderboards if the player is not signed into Xbox LIVE.
  1140. //ReleaseAssert( pData != NULL );
  1141. if ( !isSignedInToLIVE )
  1142. {
  1143. Warning( "[CAsyncLeaderboardWriteThread] Not signed into LIVE. Removing queued data.\n" );
  1144. delete pData;
  1145. continue;
  1146. }
  1147. bool writeSuccess = false;
  1148. if ( xboxsystem )
  1149. {
  1150. int kills = pData->propertiesContribScore[3].value.nData; // PROPERTY_CSS_LB_CS_TOTAL_KILLS
  1151. int deaths = pData->propertiesContribScore[4].value.nData; // PROPERTY_CSS_LB_CS_TOTAL_DEATHS
  1152. int contribScore = pData->propertiesContribScore[7].value.nData; // PROPERTY_CSS_LB_CS_TOTAL_CONTRIB_SCORE
  1153. int roundsPlayed = pData->propertiesContribScore[2].value.nData; // PROPERTY_CSS_LB_CS_TOTAL_ROUNDS_PLAYED
  1154. int gamesWon = pData->propertiesWins[1].value.nData; // PROPERTY_CSS_LB_WINS_WINS
  1155. // Before we can write some values to leaderboard (contrib score/round, average k/d, etc) we must
  1156. // read from them so we can retrieve the existing data to ensure that our formulas
  1157. // that determine rank are based on the appropriate values.
  1158. int result = 0;
  1159. // Construct the stat specs for the data we're interested in
  1160. const int kNumSpecReads = 2;
  1161. XUSER_STATS_SPEC statsSpec[kNumSpecReads];
  1162. ZeroMemory( statsSpec, kNumSpecReads * sizeof(statsSpec) );
  1163. statsSpec[0].dwViewId = pData->viewProperties[0].dwViewId; // Contrib score board
  1164. statsSpec[0].dwNumColumnIds = 4;
  1165. statsSpec[0].rgwColumnIds[0] = STATS_COLUMN_CS_ONLINE_CASUAL_TOTAL_KILLS;
  1166. statsSpec[0].rgwColumnIds[1] = STATS_COLUMN_CS_ONLINE_CASUAL_TOTAL_DEATHS;
  1167. statsSpec[0].rgwColumnIds[2] = STATS_COLUMN_CS_ONLINE_CASUAL_TOTAL_CONTRIB_SCORE;
  1168. statsSpec[0].rgwColumnIds[3] = STATS_COLUMN_CS_ONLINE_CASUAL_TOTAL_ROUNDS_PLAYED;
  1169. statsSpec[1].dwViewId = pData->viewProperties[2].dwViewId; // Wins board
  1170. statsSpec[1].dwNumColumnIds = 1;
  1171. statsSpec[1].rgwColumnIds[0] = STATS_COLUMN_WINS_ONLINE_CASUAL_WINS_TOTAL;
  1172. XUSER_STATS_READ_RESULTS *pResultsBuffer = 0;
  1173. result = xboxsystem->EnumerateStatsByXuid( pData->xuid, 1, kNumSpecReads, statsSpec, (void**)(&pResultsBuffer), false );
  1174. if ( result == ERROR_SUCCESS )
  1175. {
  1176. // Make sure all queried views are included in our result:
  1177. if ( pResultsBuffer->dwNumViews == kNumSpecReads )
  1178. {
  1179. // Get the data we're interested in: This will fail gracefully if this is our first leaderboard-write for the current user
  1180. // from the Contrib score board:
  1181. if ( pResultsBuffer->pViews[0].dwNumRows == 1 &&
  1182. pResultsBuffer->pViews[0].pRows[0].dwNumColumns == 4 )
  1183. {
  1184. if ( pResultsBuffer->pViews[0].pRows[0].pColumns[0].wColumnId == STATS_COLUMN_CS_ONLINE_CASUAL_TOTAL_KILLS )
  1185. {
  1186. kills += pResultsBuffer->pViews[0].pRows[0].pColumns[0].Value.nData;
  1187. }
  1188. if ( pResultsBuffer->pViews[0].pRows[0].pColumns[1].wColumnId == STATS_COLUMN_CS_ONLINE_CASUAL_TOTAL_DEATHS )
  1189. {
  1190. deaths += pResultsBuffer->pViews[0].pRows[0].pColumns[1].Value.nData;
  1191. }
  1192. if ( pResultsBuffer->pViews[0].pRows[0].pColumns[2].wColumnId == STATS_COLUMN_CS_ONLINE_CASUAL_TOTAL_CONTRIB_SCORE )
  1193. {
  1194. contribScore += pResultsBuffer->pViews[0].pRows[0].pColumns[2].Value.nData;
  1195. }
  1196. if ( pResultsBuffer->pViews[0].pRows[0].pColumns[3].wColumnId == STATS_COLUMN_CS_ONLINE_CASUAL_TOTAL_ROUNDS_PLAYED )
  1197. {
  1198. roundsPlayed += pResultsBuffer->pViews[0].pRows[0].pColumns[3].Value.nData;
  1199. }
  1200. }
  1201. // from the Wins board:
  1202. if ( pResultsBuffer->pViews[1].dwNumRows == 1 &&
  1203. pResultsBuffer->pViews[1].pRows[0].dwNumColumns == 1 )
  1204. {
  1205. if ( pResultsBuffer->pViews[1].pRows[0].pColumns[0].wColumnId == STATS_COLUMN_WINS_ONLINE_CASUAL_WINS_TOTAL )
  1206. {
  1207. gamesWon += pResultsBuffer->pViews[1].pRows[0].pColumns[0].Value.nData;
  1208. }
  1209. }
  1210. }
  1211. }
  1212. delete [] pResultsBuffer;
  1213. // Calculate the player's new rank
  1214. float fAverageContribScore = 0.0f;
  1215. float fGamesPlayedRatio = 0.0f;
  1216. float fKillDeathRatio = 0.0f;
  1217. float fWinRatio = 0.0f;
  1218. fGamesPlayedRatio = clamp( roundsPlayed, 0.0f, 20.0f ) / 20.0f;
  1219. if ( deaths > 0 )
  1220. {
  1221. fKillDeathRatio = ( (float)kills / (float)deaths ) * fGamesPlayedRatio;
  1222. // printf( "Calculating k/d ratio: kills=%d, deaths=%d, gameRatio=%f\n", kills, deaths, fGamesPlayedRatio );
  1223. }
  1224. else
  1225. {
  1226. fKillDeathRatio = (float)kills * fGamesPlayedRatio;
  1227. // printf( "Calculating k/d ratio with NO deaths: kills=%d, gameRatio=%f\n", kills, fGamesPlayedRatio );
  1228. }
  1229. if ( roundsPlayed > 0 )
  1230. {
  1231. fWinRatio = ( (float)gamesWon / (float)roundsPlayed ) * fGamesPlayedRatio;
  1232. fAverageContribScore = ( (float)contribScore / (float)roundsPlayed );
  1233. // printf( "Calculating avg contrib score: contribScore=%d, rounds=%d\n", contribScore, roundsPlayed );
  1234. // printf( "Calculating win ratio: wins=%d, rounds=%d, gameRatio=%f\n", gamesWon, roundsPlayed, fGamesPlayedRatio );
  1235. }
  1236. // Update our write data with the adjusted rank information
  1237. pData->propertiesContribScore[0].value.i64Data = fAverageContribScore * 10000000; // PROPERTY_CSS_LB_CS_AVERAGE_CONTRIB_SCORE (or PROPERTY_CSS_LB_CS_ELO_RATING, for an offline-mode board)
  1238. //printf( "**** Writing out average contrib score: %f as %lld\n", fAverageContribScore, pData->propertiesContribScore[0].value.i64Data );
  1239. pData->propertiesKillDeath[0].value.i64Data = fKillDeathRatio * 10000000; // PROPERTY_CSS_LB_KD_KD_FORMULA
  1240. // printf( "**** Writing out k/d ratio score: %f as %lld\n", fKillDeathRatio, pData->propertiesKillDeath[0].value.i64Data );
  1241. pData->propertiesWins[0].value.i64Data = fWinRatio * 10000000; // PROPERTY_CSS_LB_WINS_WIN_FORMULA
  1242. // printf( "**** Writing out win ratio score: %f as %lld\n", fWinRatio, pData->propertiesWins[0].value.i64Data );
  1243. // Create a fake session to write the data
  1244. DWORD userIndexes[XUSER_MAX_COUNT];
  1245. BOOL privateSlots[XUSER_MAX_COUNT] = { TRUE, TRUE, TRUE, TRUE };
  1246. XSESSION_INFO sessionInfo;
  1247. ULONGLONG sessionNonce;
  1248. HANDLE hSession = NULL;
  1249. const int numValidUserIndexes = 1;
  1250. userIndexes[0] = userID;
  1251. XUserSetContext( userIndexes[0], X_CONTEXT_GAME_TYPE, X_CONTEXT_GAME_TYPE_STANDARD);
  1252. DWORD dw = XSessionCreate(XSESSION_CREATE_USES_STATS, userIndexes[0], 0, numValidUserIndexes, &sessionNonce, &sessionInfo, NULL, &hSession);
  1253. if ( dw == ERROR_SUCCESS )
  1254. {
  1255. dw = XSessionJoinLocal(hSession, numValidUserIndexes, userIndexes, privateSlots, NULL);
  1256. if ( dw == ERROR_SUCCESS )
  1257. {
  1258. dw = XSessionStart(hSession, 0, NULL);
  1259. if ( dw == ERROR_SUCCESS )
  1260. {
  1261. // Perform the actual write to the XBox Live service
  1262. dw = xboxsystem->WriteStats( hSession, pData->xuid, NUM_VIEW_PROPERTIES, &pData->viewProperties, false );
  1263. if ( dw == ERROR_SUCCESS )
  1264. {
  1265. writeSuccess = true;
  1266. }
  1267. XSessionEnd(hSession, NULL);
  1268. }
  1269. }
  1270. XSessionDelete(hSession, NULL);
  1271. }
  1272. }
  1273. // [smessick] Log a warning if the write failed.
  1274. if ( !writeSuccess )
  1275. {
  1276. Warning( "[CAsyncLeaderboardWriteThread] Failed to write leaderboard data. Ignoring data.\n" );
  1277. }
  1278. // Delete our allocated object
  1279. delete pData;
  1280. }
  1281. }
  1282. }
  1283. }
  1284. #endif // _X360
  1285. bool CCSClientGameStats::SyncCSMatchmakingDataToTitleData( int iController, CSSyncStatValueDirection_t eOp )
  1286. {
  1287. // Get the local player.
  1288. IPlayerLocal *pPlayerLocal = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( iController );
  1289. if ( !pPlayerLocal )
  1290. return false;
  1291. #if defined ( _X360 )
  1292. TitleDataFieldsDescription_t const *pFields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
  1293. if ( !ValidateTitleBlockVersion( pFields, pPlayerLocal, eOp, 1 ) )
  1294. return false;
  1295. MatchmakingData *pMMData = pPlayerLocal->GetPlayerMatchmakingData();
  1296. if ( !pMMData )
  1297. {
  1298. return false;
  1299. }
  1300. #define MATCHMAKINGDATA_FIELD( mmDataField ) \
  1301. Q_snprintf( fieldName, 255, "MMDATA.usr.%s%d", #mmDataField, mmDataType ); \
  1302. if ( TitleDataFieldsDescription_t const *pField = TitleDataFieldsDescriptionFindByString( pFields, fieldName ) ) \
  1303. { \
  1304. if ( pField->m_eDataType != TitleDataFieldsDescription_t::DT_uint16 ) \
  1305. { \
  1306. Warning( "%s is expected to be defined as DT_uint16\n", fieldName ); \
  1307. continue; \
  1308. } \
  1309. \
  1310. if ( eOp == CSSTAT_READ_STAT ) \
  1311. pMMData->mmDataField[ mmDataType ][ MMDATA_SCOPE_LIFETIME ] = TitleDataFieldsDescriptionGetValue<uint16>( pField, pPlayerLocal ); \
  1312. else \
  1313. TitleDataFieldsDescriptionSetValue<uint16>( pField, pPlayerLocal, pMMData->mmDataField[ mmDataType ][ MMDATA_SCOPE_LIFETIME ] ); \
  1314. } \
  1315. else \
  1316. { \
  1317. Warning( "Could not find TitleDataField for %s%d\n", #mmDataField, mmDataType ); \
  1318. }
  1319. char fieldName[ 256 ] = { 0 };
  1320. for ( int mmDataType = 0; mmDataType < MMDATA_TYPE_COUNT; ++mmDataType )
  1321. {
  1322. MATCHMAKINGDATA_FIELD( mContribution );
  1323. MATCHMAKINGDATA_FIELD( mMVPs );
  1324. MATCHMAKINGDATA_FIELD( mKills );
  1325. MATCHMAKINGDATA_FIELD( mDeaths );
  1326. MATCHMAKINGDATA_FIELD( mHeadShots );
  1327. MATCHMAKINGDATA_FIELD( mDamage );
  1328. MATCHMAKINGDATA_FIELD( mShotsFired );
  1329. MATCHMAKINGDATA_FIELD( mShotsHit );
  1330. MATCHMAKINGDATA_FIELD( mDominations );
  1331. MATCHMAKINGDATA_FIELD( mRoundsPlayed );
  1332. }
  1333. #undef MATCHMAKINGDATA_FIELD
  1334. #endif // _X360
  1335. return true;
  1336. }
  1337. bool CCSClientGameStats::SyncCSRankingDataToTitleData( int iController, CSSyncStatValueDirection_t eOp )
  1338. {
  1339. // Get the local player.
  1340. IPlayerLocal *pPlayerLocal = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( iController );
  1341. if ( !pPlayerLocal )
  1342. return false;
  1343. #if defined( _GAMECONSOLE )
  1344. TitleDataFieldsDescription_t const *pFields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
  1345. #if defined( _X360 )
  1346. if ( !ValidateTitleBlockVersion( pFields, pPlayerLocal, eOp, 3 ) )
  1347. return false;
  1348. #endif
  1349. // Get Player's Local Ranking Data
  1350. IPlayerRankingDataStore *pRankingData = pPlayerLocal->GetPlayerRankingData();
  1351. Assert( pRankingData );
  1352. char fieldName[ 64 ] = { 0 };
  1353. // Iterate through the ELO data by history, game mode, controller, online mode
  1354. // Player Rankings by mode, controller, w/ optional history
  1355. for ( int m = 0; m < ELOTitleData::NUM_GAME_MODES_ELO_RANKED; m++ )
  1356. {
  1357. int numControllers = PlatformInputDevice::GetInputDeviceCountforPlatform();
  1358. for ( int c = 1; c <= numControllers; c++ )
  1359. {
  1360. V_snprintf( fieldName, sizeof(fieldName), TITLE_DATA_PREFIX "ELO.MODE%d.CTR%d", m, c );
  1361. if ( TitleDataFieldsDescription_t const *pField = TitleDataFieldsDescriptionFindByString( pFields, fieldName ) )
  1362. {
  1363. InputDevice_t controller = PlatformInputDevice::GetInputDeviceTypefromPlatformOrdinal( c );
  1364. if ( pField->m_eDataType != TitleDataFieldsDescription_t::DT_ELO )
  1365. {
  1366. ELOWarning( "ELO: %s is expected to be defined as DT_ELO\n", fieldName );
  1367. continue;
  1368. }
  1369. if ( eOp == CSSTAT_READ_STAT )
  1370. {
  1371. PlayerELORank_t ELORank = TitleDataFieldsDescriptionGetValue<PlayerELORank_t>( pField, pPlayerLocal );
  1372. ELOMsg( "ELO: TitleData ELO Read (%d, %d) = %d\n", m, (int) controller, ELORank );
  1373. pRankingData->InitELORank( m, controller, ELORank );
  1374. }
  1375. else
  1376. {
  1377. PlayerELORank_t ELORank = pRankingData->ReadELORank( m, controller );
  1378. ELOMsg( "ELO: TitleDataELO Write (%d, %d ) = %d\n", m, (int) controller, ELORank );
  1379. TitleDataFieldsDescriptionSetValue<PlayerELORank_t>( pField, pPlayerLocal, ELORank );
  1380. }
  1381. }
  1382. else
  1383. {
  1384. Warning( "Could not find TitleDataField for %s\n", fieldName );
  1385. }
  1386. }
  1387. // Load/save the elo bracket info for game modes.
  1388. CFmtStr bracketInfo( TITLE_DATA_PREFIX"ELO.MODE%d.BRACKETINFO", m );
  1389. if ( TitleDataFieldsDescription_t const *pField = TitleDataFieldsDescriptionFindByString( pFields, bracketInfo.Access() ) )
  1390. {
  1391. if ( eOp == CSSTAT_READ_STAT )
  1392. {
  1393. uint16 data = TitleDataFieldsDescriptionGetValue<uint16>( pField, pPlayerLocal );
  1394. PlayerELOBracketInfo_t tmp;
  1395. V_memcpy( &tmp, &data, sizeof( uint16 ) );
  1396. g_PlayerRankManager.Console_SetEloBracket( (ELOGameType_t) m, tmp );
  1397. ELOMsg( "ELO: TitleData ELO Bracket Read (%d) = display: %d prev: %d count %d\n", m,
  1398. tmp.m_DisplayBracket, tmp.m_PreviousBracket, tmp.m_NumGamesInBracket );
  1399. }
  1400. else
  1401. {
  1402. uint16 data = 0;
  1403. PlayerELOBracketInfo_t bracketInfo;
  1404. if ( g_PlayerRankManager.Console_GetEloBracket( (ELOGameType_t) m, &bracketInfo ) >= 0 )
  1405. {
  1406. V_memcpy( &data, &bracketInfo, sizeof( data ) );
  1407. ELOMsg( "ELO: TitleData ELO Bracket Write (%d) = display: %d prev: %d count %d\n", m,
  1408. bracketInfo.m_DisplayBracket, bracketInfo.m_PreviousBracket, bracketInfo.m_NumGamesInBracket );
  1409. }
  1410. else
  1411. {
  1412. ELOMsg( "ELO: TitleData ELO Bracket Write (%d) = No bracket info for game mode. Writing 0.", m );
  1413. }
  1414. TitleDataFieldsDescriptionSetValue<uint16>( pField, pPlayerLocal, data );
  1415. }
  1416. }
  1417. else
  1418. {
  1419. Warning( "Could not find TitleDataField for %s\n", bracketInfo.Access() );
  1420. }
  1421. }
  1422. #endif // _GAMECONSOLE
  1423. return true;
  1424. }
  1425. // Increment the current round matchmaking data by the data in the given stats structure.
  1426. // This method may be called multiple times per round so we have to deal with rounds played
  1427. // differently. Each call to the method is the delta from the previous call.
  1428. void CCSClientGameStats::IncrementMatchmakingData( const StatsCollection_t &stats )
  1429. {
  1430. #if defined ( _X360 )
  1431. ACTIVE_SPLITSCREEN_PLAYER_GUARD( GET_ACTIVE_SPLITSCREEN_SLOT() );
  1432. // Get the active local player.
  1433. IPlayerLocal *pPlayerLocal = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( XBX_GetActiveUserId() );
  1434. if ( !pPlayerLocal )
  1435. return;
  1436. // Get the current mode of play
  1437. if ( CSGameRules() )
  1438. {
  1439. // Determine if we're playing gungame progress or not, because we use different matchmaking stats for that game type.
  1440. MatchmakingDataType mmDataType = CSGameRules()->IsPlayingGunGameProgressive() ? MMDATA_TYPE_GGPROGRESSIVE : MMDATA_TYPE_GENERAL;
  1441. // Get the matchmaking data for the player.
  1442. MatchmakingData *pMMData = pPlayerLocal->GetPlayerMatchmakingData();
  1443. // Increment each of the entries by the stats collection.
  1444. pMMData->mContribution[mmDataType][MMDATA_SCOPE_ROUND] += stats[CSSTAT_CONTRIBUTION_SCORE];
  1445. pMMData->mMVPs[mmDataType][MMDATA_SCOPE_ROUND] += stats[CSSTAT_MVPS];
  1446. pMMData->mKills[mmDataType][MMDATA_SCOPE_ROUND] += stats[CSSTAT_KILLS];
  1447. pMMData->mDeaths[mmDataType][MMDATA_SCOPE_ROUND] += stats[CSSTAT_DEATHS];
  1448. pMMData->mHeadShots[mmDataType][MMDATA_SCOPE_ROUND] += stats[CSSTAT_KILLS_HEADSHOT];
  1449. pMMData->mDamage[mmDataType][MMDATA_SCOPE_ROUND] += stats[CSSTAT_DAMAGE];
  1450. pMMData->mShotsFired[mmDataType][MMDATA_SCOPE_ROUND] += stats[CSSTAT_SHOTS_FIRED];
  1451. pMMData->mShotsHit[mmDataType][MMDATA_SCOPE_ROUND] += stats[CSSTAT_SHOTS_HIT];
  1452. pMMData->mDominations[mmDataType][MMDATA_SCOPE_ROUND] += stats[CSSTAT_DOMINATIONS];
  1453. }
  1454. #endif
  1455. }
  1456. // Get the matchmaking data for current primary user and compute the new rolling average based
  1457. // on the data accumulated so far.
  1458. void CCSClientGameStats::UpdateMatchmakingData( void )
  1459. {
  1460. #if defined ( _X360 )
  1461. ACTIVE_SPLITSCREEN_PLAYER_GUARD( GET_ACTIVE_SPLITSCREEN_SLOT() );
  1462. // Get the active local player.
  1463. IPlayerLocal *pPlayerLocal = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( XBX_GetActiveUserId() );
  1464. if ( !pPlayerLocal )
  1465. return;
  1466. // Get the current mode of play
  1467. if ( CSGameRules() )
  1468. {
  1469. // Determine if we're playing gungame progress or not, because we use different matchmaking stats for that game type.
  1470. MatchmakingDataType mmDataType = CSGameRules()->IsPlayingGunGameProgressive() ? MMDATA_TYPE_GGPROGRESSIVE : MMDATA_TYPE_GENERAL;
  1471. // Update the player's rolling averages for their matchmaking data.
  1472. pPlayerLocal->UpdatePlayerMatchmakingData( mmDataType );
  1473. // Reset the per round matchmaking data.
  1474. pPlayerLocal->ResetPlayerMatchmakingData( MMDATA_SCOPE_ROUND );
  1475. }
  1476. #endif // _X360
  1477. }
  1478. // Reset the matchmaking data for the current primary user for the given scope.
  1479. void CCSClientGameStats::ResetMatchmakingData( MatchmakingDataScope mmDataScope )
  1480. {
  1481. #if defined ( _X360 )
  1482. ACTIVE_SPLITSCREEN_PLAYER_GUARD( GET_ACTIVE_SPLITSCREEN_SLOT() );
  1483. // Get the active local player.
  1484. IPlayerLocal *pPlayerLocal = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( XBX_GetActiveUserId() );
  1485. if ( !pPlayerLocal )
  1486. return;
  1487. pPlayerLocal->ResetPlayerMatchmakingData( mmDataScope );
  1488. #endif // _X360
  1489. }
  1490. // OGS data and functions
  1491. #if !defined( _GAMECONSOLE )
  1492. // WARNING: must be in sync with the CSClientCsgoGameEventType_t in .h file
  1493. char const *g_CSClientCsgoGameEventTypeNames[] = {
  1494. "Undefined",
  1495. "Spray",
  1496. "ConnectionProblem",
  1497. "ConnectionLoss",
  1498. "ConnectionChoke",
  1499. };
  1500. // WARNING: must be in sync with the CSClientCsgoGameEventType_t in .h file
  1501. void CCSClientGameStats::AddClientCSGOGameEvent( CSClientCsgoGameEventType_t eEvent, Vector const &pos, QAngle const &ang, uint64 ullData /* = 0ull */, char const *szMapName /* = NULL */, int16 nRound /* = CSCLIENTCSGOGAMEEVENTTYPE_AUTODETECT_INT16 */, int16 nRoundSecondsElapsed /* = CSCLIENTCSGOGAMEEVENTTYPE_AUTODETECT_INT16 */ )
  1502. {
  1503. CsgoGameEvent_t &cge = m_arrClientCsgoGameEvents[ m_arrClientCsgoGameEvents.AddToTail() ];
  1504. cge.m_eEvent = eEvent;
  1505. cge.m_pos = pos;
  1506. cge.m_ang = ang;
  1507. cge.m_ullData = ullData;
  1508. cge.m_symMap = CUtlSymbol( ( szMapName && *szMapName ) ? szMapName : engine->GetLevelNameShort() );
  1509. cge.m_nRound = nRound;
  1510. if ( nRound == CSCLIENTCSGOGAMEEVENTTYPE_AUTODETECT_INT16 )
  1511. {
  1512. if ( !CSGameRules() || CSGameRules()->IsWarmupPeriod() )
  1513. {
  1514. cge.m_nRound = 0;
  1515. }
  1516. else
  1517. {
  1518. cge.m_nRound = CSGameRules()->GetTotalRoundsPlayed();
  1519. if ( !CSGameRules()->IsRoundOver() )
  1520. ++ cge.m_nRound;
  1521. }
  1522. }
  1523. cge.m_numRoundSeconds = nRoundSecondsElapsed;
  1524. if ( nRoundSecondsElapsed == CSCLIENTCSGOGAMEEVENTTYPE_AUTODETECT_INT16 )
  1525. {
  1526. cge.m_numRoundSeconds = gpGlobals->curtime - CSGameRules()->GetRoundStartTime();
  1527. }
  1528. switch ( eEvent )
  1529. {
  1530. case k_CSClientCsgoGameEventType_SprayApplication:
  1531. cge.m_bRequireMoreReliableUpload = true;
  1532. break;
  1533. default:
  1534. cge.m_bRequireMoreReliableUpload = false;
  1535. break;
  1536. }
  1537. }
  1538. ConVar cl_debug_round_stat_submission( "cl_debug_round_stat_submission", "0", FCVAR_DEVELOPMENTONLY );
  1539. CCSClientGameStats::StatContainerList_t* CCSClientGameStats::s_StatLists = new CCSClientGameStats::StatContainerList_t();
  1540. void CCSClientGameStats::UploadRoundStats()
  1541. {
  1542. // Upload all client game events, and remove the ones that we don't need to reupload
  1543. FOR_EACH_VEC( m_arrClientCsgoGameEvents, i )
  1544. {
  1545. CsgoGameEvent_t const &cge = m_arrClientCsgoGameEvents[i];
  1546. char const *szEvent = g_CSClientCsgoGameEventTypeNames[0];
  1547. Assert( cge.m_eEvent > 0 && cge.m_eEvent < Q_ARRAYSIZE( g_CSClientCsgoGameEventTypeNames ) );
  1548. if ( ( cge.m_eEvent > 0 ) && ( cge.m_eEvent < Q_ARRAYSIZE( g_CSClientCsgoGameEventTypeNames ) ) )
  1549. szEvent = g_CSClientCsgoGameEventTypeNames[cge.m_eEvent];
  1550. if ( GetSteamWorksGameStatsClient().AddCsgoGameEventStat( cge.m_symMap.String(), szEvent, cge.m_pos, cge.m_ang, cge.m_ullData, cge.m_nRound, cge.m_numRoundSeconds )
  1551. || !cge.m_bRequireMoreReliableUpload )
  1552. m_arrClientCsgoGameEvents.Remove( i -- );
  1553. }
  1554. C_CSPlayer *pPlayer = ToCSPlayer( C_BasePlayer::GetLocalPlayer() );
  1555. if ( cl_debug_round_stat_submission.GetBool() )
  1556. {
  1557. Msg( "Attempting to submit round stats... ");
  1558. }
  1559. // Need to have played more than 10 seconds. If you haven't, then that means it's a nop round when first joining a server and having it restart
  1560. // due to having players on it. Also need to ensure that rounds played is greater than 0 since we'll be subtracting 1 to make it 0 based.
  1561. bool bIsValidTimedMatch = ( m_roundStats[0][CSSTAT_PLAYTIME] > 10 && m_matchStats[0][CSSTAT_ROUNDS_PLAYED] > 0 );
  1562. bool bIsValidArmsRaceMatch = CSGameRules()->IsPlayingGunGameProgressive() && ( m_RoundEndReason == CTs_Win || m_RoundEndReason == Terrorists_Win );
  1563. if ( pPlayer && ( bIsValidTimedMatch || bIsValidArmsRaceMatch ) )
  1564. {
  1565. SRoundData roundData( &m_roundStats[0] );
  1566. if ( cl_debug_round_stat_submission.GetBool() )
  1567. {
  1568. Msg( "Client session ID %llu Server Session ID %llu \n", GetSteamWorksGameStatsClient().GetSessionID(), GetSteamWorksGameStatsClient().GetServerSessionID() );
  1569. }
  1570. // Use servers count of rounds this match, not rounds played by this player.
  1571. //pRoundData->nRound = m_matchStats[0][CSSTAT_ROUNDS_PLAYED] - 1;
  1572. roundData.nRound = CSGameRules( )->GetTotalRoundsPlayed( );
  1573. if ( cl_debug_round_stat_submission.GetBool() )
  1574. {
  1575. Msg( "Submitting session id %llu round %d\n", GetSteamWorksGameStatsClient().GetSessionID(), roundData.nRound );
  1576. }
  1577. static int sLastRoundSubmitted = -1;
  1578. static uint64 sLastSessionIDSubmitted = 0;
  1579. if ( sLastRoundSubmitted >= 0 && sLastSessionIDSubmitted != 0 )
  1580. {
  1581. // HACK: We've got so many primary key violations we're effecting OGS perf.
  1582. // We currently think there are community servers running mods/rule changes
  1583. // that are responsible for much of this so we can't fix all of it. This
  1584. // horrible hack will throw out problem submits.
  1585. if ( roundData.nRound <= sLastRoundSubmitted && sLastSessionIDSubmitted == GetSteamWorksGameStatsClient( ).GetSessionID( ) )
  1586. {
  1587. Warning( "OGS PK VIOLATION: Dropping round data for round %d session %llu because we've already submitted it.\n", roundData.nRound, GetSteamWorksGameStatsClient().GetSessionID() );
  1588. m_roundStats[0].Reset();
  1589. return;
  1590. }
  1591. }
  1592. roundData.nReason = m_RoundEndReason;
  1593. // HACK: Adding to the 16th bit of pRoundData->nRoundTime
  1594. // This lets us keep track of whether a player has:
  1595. // attempted to rescue a hostage.
  1596. roundData.nRoundTime |= ((uint32)m_bObjectiveAttempted) << 16;
  1597. // EXPERIMENTAL COLUMN IN OGS
  1598. //
  1599. // This column is general-purpose, intended to collect interesting stats on a round-by-round granularity
  1600. // RoundData was selected because coarse player attributes (e.g., their current server's tick rate, certain convars)
  1601. // seem unlikely to change at a faster rate. In many cases these values will be repeated across rounds, so most
  1602. // SQL aggregations involving the column will be AVG.
  1603. //
  1604. // We expect that the specific attributes stored below will change, possibly frequently. In some cases the attributes will
  1605. // be stale. The primary value of this column is to exist as a rapid option for sampling new player stats that are unlikely
  1606. // to be relevant in the distant future.
  1607. //
  1608. // An obvious problem with this implementation is loss of history, so any changes to the experimental column should be
  1609. // listed here, with a timeline. List previously-recorded attributes in little endian order, with #bits
  1610. //
  1611. // Experiment 5:
  1612. //
  1613. // Primary Weapon Def Index;
  1614. // Primary Weapon Ammo Count at death;
  1615. // Secondary Weapon Def Index;
  1616. // Secondary Weapon Ammo Count at death;
  1617. //
  1618. // Master Music Volume
  1619. // Main Menu Volume
  1620. // Round Start Volume
  1621. // Round End Volume
  1622. // Map Objective Volume
  1623. // Ten Second Warning Volume
  1624. // Death Cam Volume
  1625. // // ConVarRef required
  1626. static ConVarRef snd_musicvolume( "snd_musicvolume" );
  1627. static ConVarRef snd_menumusic_volume( "snd_menumusic_volume" );
  1628. static ConVarRef snd_roundstart_volume( "snd_roundstart_volume" );
  1629. static ConVarRef snd_roundend_volume( "snd_roundend_volume" );
  1630. static ConVarRef snd_mapobjective_volume( "snd_mapobjective_volume" );
  1631. static ConVarRef snd_tensecondwarning_volume( "snd_tensecondwarning_volume" );
  1632. static ConVarRef snd_deathcamera_volume( "snd_deathcamera_volume" );
  1633. static ConVarRef voice_scale("voice_scale");
  1634. uint8 *pData = ( uint8* )&roundData.llExperimental;
  1635. // Ammo count at death OGS data
  1636. //
  1637. *( pData ) = ( uint8 )pPlayer->m_roundEndAmmoCount.nPrimaryWeaponDefIndex;
  1638. *( ++pData ) = ( uint8 )pPlayer->m_roundEndAmmoCount.nPrimaryWeaponAmmoCount;
  1639. *( ++pData ) = ( uint8 )pPlayer->m_roundEndAmmoCount.nSecondaryWeaponDefIndex;
  1640. *( ++pData ) = ( uint8 )pPlayer->m_roundEndAmmoCount.nSecondaryWeaponAmmoCount;
  1641. //
  1642. // end of ammo count at death OGS data
  1643. // This code allowed us to measure discrepency between client and server bullet hits.
  1644. // It became obsolete when we started using a separate seed for client and server
  1645. // to eliminate 'rage' hacks.
  1646. //
  1647. *( ++pData ) = ( uint8 )pPlayer->m_ui8ClientServerHitDifference;
  1648. // Experiment 6:
  1649. // Replay utilization
  1650. // EE_REPLAY_OFFERED = 1,
  1651. // EE_REPLAY_REQUESTED = 2,
  1652. // EE_REPLAY_STARTED = 4,
  1653. // EE_REPLAY_CANCELLED = 8,
  1654. // EE_REPLAY_AUTOMATIC = 16
  1655. *( ++pData ) = ( uint8 )g_HltvReplaySystem.GetExperimentalEvents();
  1656. // float tickrate = 1.0 / gpGlobals->interval_per_tick;
  1657. // *( ++pData ) = ( uint8 )tickrate;
  1658. // // Sound and Music
  1659. // uint8 nMusicVolumeAsPct = (uint8)( 100.0f * snd_musicvolume.GetFloat() );
  1660. // uint8 nMenuMusicVolumeAsPct = (uint8)( 100.0f * snd_menumusic_volume.GetFloat() );
  1661. // uint8 nRoundStartVolumeAsPct = (uint8)( 100.0f * snd_roundstart_volume.GetFloat() );
  1662. // uint8 nRoundEndVolumeAsPct = (uint8)( 100.0f * snd_roundend_volume.GetFloat() );
  1663. // uint8 nMapObjectiveVolumeAsPct = (uint8)( 100.0f * snd_mapobjective_volume.GetFloat() );
  1664. // uint8 nTenSecondWarningVolumeAsPct = (uint8)( 100.0f * snd_tensecondwarning_volume.GetFloat() );
  1665. // uint8 nDeathCameraVolumeAsPct = (uint8)( 100.0f * snd_deathcamera_volume.GetFloat() );
  1666. //
  1667. // // Pack Reasonable Volume Duplets to fit 64 bits - set each to 0-10 range, pack into left and right 4 bits.
  1668. // uint8 nMusicAndMenuMusicVolume = (uint8)( ( nMusicVolumeAsPct / 10 ) | ( nMenuMusicVolumeAsPct / 10 ) << 4 );
  1669. // uint8 nRoundStartAndEndVolume = (uint8)( ( nRoundStartVolumeAsPct / 10 ) | ( nRoundEndVolumeAsPct / 10 ) << 4 );
  1670. // uint8 nMapObjectiveAndWarning = (uint8)( ( nMapObjectiveVolumeAsPct / 10 ) | ( nTenSecondWarningVolumeAsPct / 10 ) << 4 );
  1671. //
  1672. // *( ++pData ) = (uint8)nMusicAndMenuMusicVolume;
  1673. // *( ++pData ) = (uint8)nRoundStartAndEndVolume;
  1674. // *( ++pData ) = (uint8)nMapObjectiveAndWarning;
  1675. // *( ++pData ) = (uint8)nDeathCameraVolumeAsPct;
  1676. // END EXPERIMENTAL
  1677. // Our current money + what we spent is what we started with at the beginning of round
  1678. roundData.nStartingMoney = pPlayer->m_iStartAccount & 0x0000FFFF;
  1679. //NOTHER TEMP HACK: Store round start player net worth in the top 16 bits of starting money
  1680. roundData.nStartingMoney |= ((uint32)pPlayer->GetRoundStartEquipmentValue( )) << 16;
  1681. roundData.nTeamID = pPlayer->m_iTeamNum;
  1682. if ( cl_debug_round_stat_submission.GetBool() )
  1683. Msg( "Setting team num %d", roundData.nTeamID );
  1684. if( CSGameRules()->IsPlayingGunGameProgressive() )
  1685. {
  1686. roundData.nRoundScore = m_roundStats[ 0 ][ CSSTAT_GG_PROGRESSIVE_CONTRIBUTION_SCORE ];
  1687. }
  1688. else
  1689. {
  1690. roundData.nRoundScore = m_roundStats[ 0 ][ CSSTAT_CONTRIBUTION_SCORE ];
  1691. }
  1692. // Send off all OGS stats at level shutdown
  1693. KeyValues *pKV = new KeyValues( "basedata" );
  1694. if ( !pKV )
  1695. return;
  1696. char szMapNameBuffer[MAX_PATH];
  1697. V_strcpy_safe( szMapNameBuffer, engine->GetLevelName() );
  1698. V_FixSlashes( szMapNameBuffer, '/' ); // use consistent slashes so we don't get double entries for different platforms
  1699. V_StripExtension(szMapNameBuffer, szMapNameBuffer, sizeof( szMapNameBuffer ) );
  1700. int nLen = V_strlen( "maps/" );
  1701. if ( StringHasPrefix( szMapNameBuffer, "maps/" ) && *( szMapNameBuffer + nLen ) )
  1702. {
  1703. // skip maps dir
  1704. pKV->SetString( "MapID", szMapNameBuffer + nLen );
  1705. }
  1706. else
  1707. {
  1708. pKV->SetString( "MapID", szMapNameBuffer );
  1709. }
  1710. SubmitStat( &roundData );
  1711. // Perform the actual submission
  1712. SubmitGameStats( pKV );
  1713. sLastRoundSubmitted = CSGameRules()->GetTotalRoundsPlayed();
  1714. sLastSessionIDSubmitted = GetSteamWorksGameStatsClient().GetSessionID();
  1715. pKV->deleteThis();
  1716. m_RoundEndReason = Invalid_Round_End_Reason;
  1717. m_bObjectiveAttempted = false;
  1718. }
  1719. else if ( cl_debug_round_stat_submission.GetBool() )
  1720. {
  1721. Msg( "Skipping -- Client thinks round time is %d and num matches is %d\n", m_roundStats[0][CSSTAT_PLAYTIME], m_matchStats[0][CSSTAT_ROUNDS_PLAYED] );
  1722. }
  1723. m_roundStats[0].Reset();
  1724. }
  1725. #endif