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.

2014 lines
64 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: CS game stats
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. // Some tricky business here - we don't want to include the precompiled header for the statreader
  8. // and trying to #ifdef it out does funky things like ignoring the #endif. Define our header file
  9. // separately and include it based on the switch
  10. #include "cbase.h"
  11. #include <tier0/platform.h>
  12. #include "cs_gamerules.h"
  13. #include "cs_gamestats.h"
  14. #include "weapon_csbase.h"
  15. #include "props.h"
  16. #include "cs_achievement_constants.h"
  17. #include "weapon_c4.h"
  18. #include "cs_bot.h"
  19. #include <time.h>
  20. #include "filesystem.h"
  21. #include "bot_util.h"
  22. // needed for recording grenade detonation rows
  23. #include "basecsgrenade_projectile.h"
  24. #include "flashbang_projectile.h"
  25. #include "hegrenade_projectile.h"
  26. #include "Effects/inferno.h"
  27. #if !defined( _GAMECONSOLE )
  28. #include "cdll_int.h"
  29. #endif
  30. // NOTE: This has to be the last file included!
  31. #include "tier0/memdbgon.h"
  32. //#define DEBUG_STAT_TRANSMISSION
  33. extern ConVar game_mode;
  34. extern ConVar game_type;
  35. extern ConVar mp_roundtime;
  36. float g_flGameStatsUpdateTime = 0.0f;
  37. short g_iTerroristVictories[CS_NUM_LEVELS];
  38. short g_iCounterTVictories[CS_NUM_LEVELS];
  39. short g_iWeaponPurchases[WEAPON_MAX];
  40. short g_iAutoBuyPurchases = 0;
  41. short g_iReBuyPurchases = 0;
  42. short g_iAutoBuyM4A1Purchases = 0;
  43. short g_iAutoBuyAK47Purchases = 0;
  44. short g_iAutoBuyFamasPurchases = 0;
  45. short g_iAutoBuyGalilPurchases = 0;
  46. short g_iAutoBuyGalilARPurchases = 0;
  47. short g_iAutoBuyVestHelmPurchases = 0;
  48. short g_iAutoBuyVestPurchases = 0;
  49. struct
  50. {
  51. char* szPropModelName;
  52. CSStatType_t statType;
  53. } PropModelStatsTableInit[] =
  54. {
  55. { "models/props/cs_office/computer_caseb.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
  56. { "models/props/cs_office/computer_monitor.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
  57. { "models/props/cs_office/phone.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
  58. { "models/props/cs_office/projector.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
  59. { "models/props/cs_office/TV_plasma.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
  60. { "models/props/cs_office/computer_keyboard.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
  61. { "models/props/cs_office/radio.mdl", CSSTAT_PROPSBROKEN_OFFICERADIO },
  62. { "models/props/cs_office/trash_can.mdl", CSSTAT_PROPSBROKEN_OFFICEJUNK },
  63. { "models/props/cs_office/file_box.mdl", CSSTAT_PROPSBROKEN_OFFICEJUNK },
  64. { "models/props_junk/watermelon01.mdl", CSSTAT_PROPSBROKEN_ITALY_MELON },
  65. // models/props/de_inferno/claypot01.mdl
  66. // models/props/de_inferno/claypot02.mdl
  67. // models/props/de_dust/grainbasket01c.mdl
  68. // models/props_junk/wood_crate001a.mdl
  69. // models/props/cs_office/file_box_p1.mdl
  70. };
  71. struct
  72. {
  73. int achievementId;
  74. int statId;
  75. int roundRequirement;
  76. int matchRequirement;
  77. char* mapFilter;
  78. bool disallowGunGameProgressive;
  79. bool IsMet(int roundStat, int matchStat)
  80. {
  81. return roundStat >= roundRequirement && matchStat >= matchRequirement;
  82. }
  83. } ServerStatBasedAchievements[] =
  84. {
  85. { CSBreakWindows, CSSTAT_NUM_BROKEN_WINDOWS, AchievementConsts::BreakWindowsInOfficeRound_Windows, 0, "cs_office", false },
  86. //{ CSBreakProps, CSSTAT_PROPSBROKEN_ALL, AchievementConsts::BreakPropsInRound_Props, 0, NULL },
  87. { CSUnstoppableForce, CSSTAT_KILLS, AchievementConsts::UnstoppableForce_Kills, 0, NULL, true },
  88. { CSHeadshotsInRound, CSSTAT_KILLS_HEADSHOT, AchievementConsts::HeadshotsInRound_Kills, 0, NULL, true },
  89. { CSDominationOverkillsMatch, CSSTAT_DOMINATION_OVERKILLS, 0, 10, NULL, false },
  90. };
  91. // The struct below should be updated (along with the CSBombEventName enum table) whenever we write data for a new bomb-related event.
  92. struct BombEventNameInfo
  93. {
  94. CSBombEventName eventID;
  95. const char * name;
  96. };
  97. BombEventNameInfo s_BombEventNameInfo[]=
  98. {
  99. { BOMB_EVENT_NAME_PLANTED, "bomb_planted" },
  100. { BOMB_EVENT_NAME_DEFUSED, "bomb_defused" },
  101. };
  102. CSBombEventName BombEventNameFromString( const char* pEventName )
  103. {
  104. for ( int i = 0; i < ARRAYSIZE( s_BombEventNameInfo ); ++i )
  105. {
  106. if ( !V_stricmp( s_BombEventNameInfo[i].name, pEventName ) )
  107. {
  108. return s_BombEventNameInfo[i].eventID;
  109. }
  110. }
  111. return BOMB_EVENT_NAME_NONE;
  112. }
  113. #if !defined( NO_STEAM )
  114. uint32 GetPlayerID( CCSPlayer *pPlayer )
  115. {
  116. // Steam account id's for bots based on difficulty
  117. static int32 s_botIDs[ NUM_DIFFICULTY_LEVELS ] =
  118. {
  119. 551,
  120. 552,
  121. 553,
  122. 554,
  123. };
  124. // if we're controlling a bot, return the bot ID
  125. if( pPlayer->IsControllingBot() )
  126. {
  127. CCSPlayer* controlledPlayer = pPlayer->GetControlledBot();
  128. AssertMsg( controlledPlayer != pPlayer, "Player should never match controlled player: this will cause an infinite loop" );
  129. if( controlledPlayer )
  130. {
  131. return GetPlayerID( controlledPlayer );
  132. }
  133. }
  134. if ( pPlayer->IsBot() )
  135. {
  136. CCSBot* pBot = dynamic_cast< CCSBot* >( pPlayer );
  137. if ( pBot )
  138. {
  139. const BotProfile* pProfile = pBot->GetProfile();
  140. if ( pProfile )
  141. {
  142. for ( int i = NUM_DIFFICULTY_LEVELS - 1; i >= BOT_EASY; --i )
  143. {
  144. if ( pProfile->IsDifficulty( ( BotDifficultyType ) i ) )
  145. {
  146. return s_botIDs[ i ];
  147. }
  148. }
  149. }
  150. }
  151. }
  152. return pPlayer->GetSteamIDAsUInt64();
  153. }
  154. #endif // !NO_STEAM
  155. // [Forrest] Allow nemesis/revenge to be turned off for a server
  156. static void SvNoNemesisChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
  157. {
  158. ConVarRef var( pConVar );
  159. if ( var.IsValid() && var.GetBool() )
  160. {
  161. // Clear all nemesis relationships.
  162. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  163. {
  164. CCSPlayer *pTemp = ToCSPlayer( UTIL_PlayerByIndex( i ) );
  165. if ( pTemp )
  166. {
  167. pTemp->RemoveNemesisRelationships();
  168. }
  169. }
  170. }
  171. }
  172. ConVar sv_nonemesis( "sv_nonemesis", "0", 0, "Disable nemesis and revenge.", SvNoNemesisChangeCallback );
  173. ConVar sv_dumpmatchweaponmetrics( "sv_dumpmatchweaponmetrics", "0", FCVAR_DEVELOPMENTONLY, "Turn on the exporting of weapon metrics at the end of a level.");
  174. ConVar sv_debugroundstats( "sv_debugroundstats", "0", FCVAR_DEVELOPMENTONLY );
  175. int GetCSLevelIndex( const char *pLevelName )
  176. {
  177. for ( int i = 0; MapName_StatId_Table[i].statWinsId != CSSTAT_UNDEFINED; i ++ )
  178. {
  179. if ( Q_strcmp( pLevelName, MapName_StatId_Table[i].szMapName ) == 0 )
  180. return i;
  181. }
  182. return -1;
  183. }
  184. CCSGameStats CCS_GameStats;
  185. #if !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
  186. CCSGameStats::StatContainerList_t* CCSGameStats::s_StatLists = new CCSGameStats::StatContainerList_t();
  187. #endif
  188. //-----------------------------------------------------------------------------
  189. // Purpose: Constructor
  190. // Input : -
  191. //-----------------------------------------------------------------------------
  192. CCSGameStats::CCSGameStats()
  193. {
  194. gamestats = this;
  195. Clear();
  196. m_fDisseminationTimerLow = m_fDisseminationTimerHigh = 0.0f;
  197. // create table for mapping prop models to stats
  198. for ( int i = 0; i < ARRAYSIZE(PropModelStatsTableInit); ++i)
  199. {
  200. m_PropStatTable.Insert(PropModelStatsTableInit[i].szPropModelName, PropModelStatsTableInit[i].statType);
  201. }
  202. m_numberOfRoundsForDirectAverages = 0;
  203. m_numberOfTerroristEntriesForDirectAverages = 0;
  204. m_numberOfCounterTerroristEntriesForDirectAverages = 0;
  205. }
  206. //-----------------------------------------------------------------------------
  207. // Purpose: Destructor
  208. // Input : -
  209. //-----------------------------------------------------------------------------
  210. CCSGameStats::~CCSGameStats()
  211. {
  212. Clear();
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Purpose: Clear out game stats
  216. // Input : -
  217. //-----------------------------------------------------------------------------
  218. void CCSGameStats::Clear( void )
  219. {
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose:
  223. //-----------------------------------------------------------------------------
  224. bool CCSGameStats::Init( void )
  225. {
  226. ListenForGameEvent( "round_end" );
  227. ListenForGameEvent( "round_officially_ended" );
  228. ListenForGameEvent( "break_prop" );
  229. ListenForGameEvent( "player_decal" );
  230. ListenForGameEvent( "begin_new_match" );
  231. ListenForGameEvent( "bomb_planted" );
  232. ListenForGameEvent( "bomb_defused" );
  233. return true;
  234. }
  235. //-----------------------------------------------------------------------------
  236. // Purpose:
  237. //-----------------------------------------------------------------------------
  238. void CCSGameStats::Event_ShotFired( CBasePlayer *pPlayer, CBaseCombatWeapon* pWeapon )
  239. {
  240. if ( CSGameRules()->IsRoundOver() )
  241. return;
  242. Assert( pPlayer );
  243. CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
  244. // [dwenger] adding tracking for weapon used fun fact
  245. if ( pCSPlayer && pWeapon )
  246. {
  247. // [dwenger] Update the player's tracking of which weapon type they fired
  248. pCSPlayer->PlayerUsedFirearm( pWeapon );
  249. if( !pWeapon->HasAnyAmmo() )
  250. pCSPlayer->PlayerEmptiedAmmoForFirearm( pWeapon );
  251. IncrementStat( pCSPlayer, CSSTAT_SHOTS_FIRED, 1 );
  252. CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(pWeapon);
  253. // Increment the individual weapon
  254. if( pCSWeapon )
  255. {
  256. CSWeaponID weaponId = pCSWeapon->GetCSWeaponID();
  257. //if ( weaponId == WEAPON_HEGRENADE )
  258. // int here = 0;
  259. // OGS tracking
  260. // Check to see if this bullet is from a weapon that fires multiple bullets with a single shot.
  261. #if !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
  262. uint8 iSubBullet = 0;
  263. SWeaponShotData *lastShotData = m_WeaponShotData.Count() ? m_WeaponShotData.Tail() : NULL;
  264. // If the previous weapon shot data has the save bulletid, then we check the sub bullet id and increment one from that
  265. if ( lastShotData && lastShotData->m_uiBulletID == CCSPlayer::GetBulletGroup() )
  266. {
  267. iSubBullet = lastShotData->m_uiSubBulletID + 1;
  268. }
  269. m_WeaponShotData.AddToTail( new SWeaponShotData( pCSPlayer, pCSWeapon, iSubBullet, CSGameRules()->m_iTotalRoundsPlayed, (int)pCSWeapon->m_flRecoilIndex ) );
  270. #endif
  271. for (int i = 0; WeaponName_StatId_Table[i].weaponId != WEAPON_NONE; ++i)
  272. {
  273. if ( WeaponName_StatId_Table[i].weaponId == weaponId && WeaponName_StatId_Table[i].shotStatId != CSSTAT_UNDEFINED )
  274. {
  275. IncrementStat( pCSPlayer, WeaponName_StatId_Table[i].shotStatId, 1 );
  276. break;
  277. }
  278. }
  279. CSWeaponMode weaponMode = pCSWeapon->m_weaponMode;
  280. ++m_weaponStats[weaponId][weaponMode].shots;
  281. }
  282. }
  283. }
  284. void CCSGameStats::Event_ShotHit( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
  285. {
  286. Assert( pPlayer );
  287. CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
  288. if ( info.GetDamagedOtherPlayers() <= 0 )
  289. {
  290. // [dkorus] a 'hit' is a only counted for the first character hit. Otherwise we can end up with an artificially high hit count and >100% accuracy
  291. IncrementStat( pCSPlayer, CSSTAT_SHOTS_HIT, 1 );
  292. }
  293. CBaseEntity *pInflictor = info.GetInflictor();
  294. if ( pInflictor )
  295. {
  296. if ( pInflictor == pPlayer )
  297. {
  298. if ( pPlayer->GetActiveWeapon() )
  299. {
  300. CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(pPlayer->GetActiveWeapon());
  301. if (pCSWeapon)
  302. {
  303. CSWeaponID weaponId = pCSWeapon->GetCSWeaponID();
  304. for (int i = 0; WeaponName_StatId_Table[i].weaponId != WEAPON_NONE; ++i)
  305. {
  306. if ( WeaponName_StatId_Table[i].weaponId == weaponId && WeaponName_StatId_Table[i].shotStatId != CSSTAT_UNDEFINED )
  307. {
  308. IncrementStat( pCSPlayer, WeaponName_StatId_Table[i].hitStatId, 1 );
  309. IncrementStat( pCSPlayer, WeaponName_StatId_Table[i].damageStatId, info.GetDamage() );
  310. break;
  311. }
  312. }
  313. CSWeaponMode weaponMode = pCSWeapon->m_weaponMode;
  314. ++m_weaponStats[weaponId][weaponMode].hits;
  315. m_weaponStats[weaponId][weaponMode].damage += info.GetDamage();
  316. }
  317. }
  318. }
  319. }
  320. }
  321. void CCSGameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
  322. {
  323. Assert( pPlayer );
  324. CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
  325. IncrementStat( pCSPlayer, CSSTAT_DEATHS, 1 );
  326. }
  327. void CCSGameStats::Event_PlayerSprayedDecal( CCSPlayer* pPlayer )
  328. {
  329. IncrementStat( pPlayer, CSSTAT_DECAL_SPRAYS, 1 );
  330. }
  331. void CCSGameStats::Event_PlayerKilled_PreWeaponDrop( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
  332. {
  333. Assert( pPlayer );
  334. CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
  335. CCSPlayer *pAttacker = ToCSPlayer( info.GetAttacker() );
  336. bool victimZoomed = ( pCSPlayer->GetFOV() != pCSPlayer->GetDefaultFOV() );
  337. if (victimZoomed)
  338. {
  339. IncrementStat(pAttacker, CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, 1);
  340. }
  341. //Check for knife fight achievements
  342. if (pAttacker && pCSPlayer && pAttacker == info.GetInflictor() && pAttacker->GetTeamNumber() != pCSPlayer->GetTeamNumber())
  343. {
  344. CWeaponCSBase* attackerWeapon = pAttacker->GetActiveCSWeapon();
  345. CWeaponCSBase* victimWeapon = pCSPlayer->GetActiveCSWeapon();
  346. CSWeaponID victimWeaponID = ( ( victimWeapon ) ? victimWeapon->GetCSWeaponID() : WEAPON_NONE );
  347. if (attackerWeapon && victimWeapon)
  348. {
  349. CSWeaponID attackerWeaponID = attackerWeapon->GetCSWeaponID();
  350. if (attackerWeaponID == WEAPON_KNIFE && victimWeaponID == WEAPON_KNIFE)
  351. {
  352. IncrementStat(pAttacker, CSSTAT_KILLS_KNIFE_FIGHT, 1);
  353. }
  354. if( CSGameRules( )->IsPlayingGunGame() )
  355. {
  356. int nWeapon = CSGameRules()->GetCurrentGunGameWeapon( pAttacker->m_iGunGameProgressiveWeaponIndex, pAttacker->GetTeamNumber() );
  357. if ( nWeapon == WEAPON_KNIFE &&
  358. CSGameRules( )->IsPlayingGunGameTRBomb() )
  359. {
  360. // just got a knife kill in a TR game
  361. if ( pCSPlayer->PlacedBombThisRound() )
  362. pAttacker->SetKnifeLevelKilledBombPlacer();
  363. }
  364. nWeapon = CSGameRules()->GetCurrentGunGameWeapon( pCSPlayer->m_iGunGameProgressiveWeaponIndex, pCSPlayer->GetTeamNumber() );
  365. if ( nWeapon == WEAPON_KNIFE || nWeapon == WEAPON_KNIFE_GG )
  366. {
  367. pAttacker->AwardAchievement( CSGunGameKillKnifer );
  368. if ( attackerWeaponID == WEAPON_KNIFE || attackerWeaponID == WEAPON_KNIFE_GG )
  369. {
  370. pAttacker->AwardAchievement( CSGunGameKnifeKillKnifer );
  371. }
  372. if ( attackerWeapon->IsKindOf( WEAPONTYPE_SUBMACHINEGUN ) )
  373. {
  374. pAttacker->AwardAchievement( CSGunGameSMGKillKnifer );
  375. }
  376. }
  377. }
  378. }
  379. }
  380. }
  381. //-----------------------------------------------------------------------------
  382. // Purpose:
  383. //-----------------------------------------------------------------------------
  384. void CCSGameStats::Event_BombPlanted( CCSPlayer* pPlayer)
  385. {
  386. IncrementStat( pPlayer, CSSTAT_NUM_BOMBS_PLANTED, 1 );
  387. if( CSGameRules( )->IsPlayingGunGameTRBomb() )
  388. {
  389. IncrementStat( pPlayer, CSSTAT_TR_NUM_BOMBS_PLANTED, 1 );
  390. }
  391. }
  392. //-----------------------------------------------------------------------------
  393. // Purpose:
  394. //-----------------------------------------------------------------------------
  395. void CCSGameStats::Event_BombDefused( CCSPlayer* pPlayer)
  396. {
  397. IncrementStat( pPlayer, CSSTAT_NUM_BOMBS_DEFUSED, 1 );
  398. IncrementStat( pPlayer, CSSTAT_OBJECTIVES_COMPLETED, 1 );
  399. if( pPlayer && pPlayer->HasDefuser() )
  400. {
  401. IncrementStat( pPlayer, CSSTAT_BOMBS_DEFUSED_WITHKIT, 1 );
  402. }
  403. if( CSGameRules( )->IsPlayingGunGameTRBomb() )
  404. {
  405. IncrementStat( pPlayer, CSSTAT_TR_NUM_BOMBS_DEFUSED, 1 );
  406. }
  407. }
  408. //-----------------------------------------------------------------------------
  409. // Purpose: Increment terrorist team stat
  410. //-----------------------------------------------------------------------------
  411. void CCSGameStats::Event_BombExploded( CCSPlayer* pPlayer )
  412. {
  413. IncrementStat( pPlayer, CSSTAT_OBJECTIVES_COMPLETED, 1 );
  414. }
  415. //-----------------------------------------------------------------------------
  416. // Purpose:
  417. //-----------------------------------------------------------------------------
  418. void CCSGameStats::Event_HostageRescued( CCSPlayer* pPlayer)
  419. {
  420. IncrementStat( pPlayer, CSSTAT_NUM_HOSTAGES_RESCUED, 1 );
  421. }
  422. //-----------------------------------------------------------------------------
  423. // Purpose: Increment counter-terrorist team stat
  424. //-----------------------------------------------------------------------------
  425. void CCSGameStats::Event_AllHostagesRescued()
  426. {
  427. IncrementTeamStat( TEAM_CT, CSSTAT_OBJECTIVES_COMPLETED, 1 );
  428. }
  429. //-----------------------------------------------------------------------------
  430. // Purpose:
  431. //-----------------------------------------------------------------------------
  432. void CCSGameStats::Event_WindowShattered( CBasePlayer *pPlayer)
  433. {
  434. Assert( pPlayer );
  435. CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
  436. IncrementStat( pCSPlayer, CSSTAT_NUM_BROKEN_WINDOWS, 1 );
  437. }
  438. void CCSGameStats::Event_BreakProp( CCSPlayer* pPlayer, CBreakableProp *pProp )
  439. {
  440. if (!pPlayer)
  441. return;
  442. DevMsg("Player %s broke a %s (%i)\n", pPlayer->GetPlayerName(), pProp->GetModelName().ToCStr(), pProp->entindex());
  443. int iIndex = m_PropStatTable.Find(pProp->GetModelName().ToCStr());
  444. if (m_PropStatTable.IsValidIndex(iIndex))
  445. {
  446. IncrementStat(pPlayer, m_PropStatTable[iIndex], 1);
  447. }
  448. IncrementStat(pPlayer, CSSTAT_PROPSBROKEN_ALL, 1);
  449. }
  450. //-----------------------------------------------------------------------------
  451. // Purpose:
  452. //-----------------------------------------------------------------------------
  453. void CCSGameStats::UpdatePlayerRoundStats(int winner)
  454. {
  455. int mapIndex = GetCSLevelIndex(gpGlobals->mapname.ToCStr());
  456. CSStatType_t mapStatWinIndex = CSSTAT_UNDEFINED, mapStatRoundIndex = CSSTAT_UNDEFINED;
  457. if ( mapIndex != -1 )
  458. {
  459. mapStatWinIndex = MapName_StatId_Table[mapIndex].statWinsId;
  460. mapStatRoundIndex = MapName_StatId_Table[mapIndex].statRoundsId;
  461. }
  462. // increment the team specific stats
  463. IncrementTeamStat( winner, CSSTAT_ROUNDS_WON, 1 );
  464. if( CSGameRules( )->IsPlayingGunGame() )
  465. {
  466. IncrementTeamStat( winner, CSTAT_GUNGAME_ROUNDS_WON, 1 );
  467. IncrementTeamStat( TEAM_TERRORIST, CSTAT_GUNGAME_ROUNDS_PLAYED, 1 );
  468. IncrementTeamStat( TEAM_CT, CSTAT_GUNGAME_ROUNDS_PLAYED, 1 );
  469. }
  470. if ( mapStatWinIndex != CSSTAT_UNDEFINED )
  471. {
  472. IncrementTeamStat( winner, mapStatWinIndex, 1 );
  473. }
  474. if ( CSGameRules()->IsPistolRound() )
  475. {
  476. IncrementTeamStat( winner, CSSTAT_PISTOLROUNDS_WON, 1 );
  477. }
  478. IncrementTeamStat( TEAM_TERRORIST, CSSTAT_ROUNDS_PLAYED, 1 );
  479. IncrementTeamStat( TEAM_CT, CSSTAT_ROUNDS_PLAYED, 1 );
  480. if( mapStatRoundIndex != CSSTAT_UNDEFINED )
  481. {
  482. IncrementTeamStat( TEAM_TERRORIST, mapStatRoundIndex, 1 );
  483. IncrementTeamStat( TEAM_CT, mapStatRoundIndex, 1 );
  484. }
  485. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  486. {
  487. CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  488. if ( pPlayer && pPlayer->IsConnected() )
  489. {
  490. IncrementStat( pPlayer, CSSTAT_ROUNDS_PLAYED, 1, false, true );
  491. if ( CSGameRules()->IsPlayingGunGame() )
  492. {
  493. IncrementStat( pPlayer, CSTAT_GUNGAME_ROUNDS_PLAYED, 1, false, true );
  494. }
  495. if( CSGameRules()->IsPlayingGunGameProgressive() )
  496. {
  497. IncrementStat( pPlayer,CSSTAT_GG_PROGRESSIVE_CONTRIBUTION_SCORE, MAX( pPlayer->GetRoundContributionScore(), 0.0f), false, true );
  498. }
  499. else
  500. {
  501. IncrementStat( pPlayer,CSSTAT_CONTRIBUTION_SCORE, MAX( pPlayer->GetRoundContributionScore(), 0.0f), false, true );
  502. }
  503. pPlayer->ClearRoundContributionScore();
  504. pPlayer->ClearRoundProximityScore();
  505. if ( winner == TEAM_CT )
  506. {
  507. IncrementStat( pPlayer, CSSTAT_CT_ROUNDS_WON, 1, true, true );
  508. }
  509. else if ( winner == TEAM_TERRORIST )
  510. {
  511. IncrementStat( pPlayer, CSSTAT_T_ROUNDS_WON, 1, true, true );
  512. }
  513. if ( winner == TEAM_CT || winner == TEAM_TERRORIST )
  514. {
  515. // Increment the win stats if this player is on the winning team
  516. if ( pPlayer->GetTeamNumber() == winner )
  517. {
  518. IncrementStat( pPlayer, CSSTAT_ROUNDS_WON, 1, true, true );
  519. if( CSGameRules( )->IsPlayingGunGame() )
  520. {
  521. IncrementStat( pPlayer, CSTAT_GUNGAME_ROUNDS_WON, 1, true, true );
  522. }
  523. if ( CSGameRules()->IsPistolRound() )
  524. {
  525. IncrementStat( pPlayer, CSSTAT_PISTOLROUNDS_WON, 1, true, true );
  526. }
  527. if ( mapStatWinIndex != CSSTAT_UNDEFINED )
  528. {
  529. IncrementStat( pPlayer, mapStatWinIndex, 1, true, true );
  530. }
  531. }
  532. if ( mapStatWinIndex != CSSTAT_UNDEFINED )
  533. {
  534. IncrementStat( pPlayer, mapStatRoundIndex, 1, true, true );
  535. }
  536. // set the play time for the round
  537. IncrementStat( pPlayer, CSSTAT_PLAYTIME, (int)CSGameRules()->GetRoundElapsedTime(), true, true );
  538. }
  539. }
  540. }
  541. // send a stats update to all players
  542. for ( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  543. {
  544. CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  545. if ( pPlayer && pPlayer->IsConnected())
  546. {
  547. SendStatsToPlayer(pPlayer, CSSTAT_PRIORITY_ENDROUND);
  548. }
  549. }
  550. }
  551. //-----------------------------------------------------------------------------
  552. // Purpose: Log accumulated weapon usage and performance data
  553. //-----------------------------------------------------------------------------
  554. void CCSGameStats::DumpMatchWeaponMetrics()
  555. {
  556. // generate a filename
  557. time_t t = time( NULL );
  558. struct tm *now = localtime( &t );
  559. if ( !now )
  560. return;
  561. int year = now->tm_year + 1900;
  562. int month = now->tm_mon + 1;
  563. int day = now->tm_mday;
  564. int hour = now->tm_hour;
  565. int minute = now->tm_min;
  566. int second = now->tm_sec;
  567. char filename[ 128 ];
  568. Q_snprintf( filename, sizeof(filename), "wm_%4d%02d%02d_%02d%02d%02d_%s.csv",
  569. year, month, day, hour, minute, second, gpGlobals->mapname.ToCStr());
  570. FileHandle_t hLogFile = filesystem->Open( filename, "wt" );
  571. if ( hLogFile == FILESYSTEM_INVALID_HANDLE )
  572. return;
  573. filesystem->FPrintf(hLogFile, "%s\n", "WeaponId, Mode, Cost, Bullets, CycleTime, TotalShots, TotalHits, TotalDamage, TotalKills");
  574. for (int iWeapon = 0; iWeapon < WEAPON_MAX; ++iWeapon)
  575. {
  576. const CCSWeaponInfo* pInfo = GetWeaponInfo( (CSWeaponID)iWeapon );
  577. if ( !pInfo )
  578. continue;
  579. const char* pWeaponName = pInfo->szClassName;
  580. if ( !pWeaponName )
  581. continue;
  582. if ( IsWeaponClassname( pWeaponName ) )
  583. {
  584. pWeaponName += WEAPON_CLASSNAME_PREFIX_LENGTH;
  585. }
  586. for ( int iMode = 0; iMode < WeaponMode_MAX; ++iMode)
  587. {
  588. filesystem->FPrintf(hLogFile, "%s, %d, %d, %d, %f, %d, %d, %d, %d\n",
  589. pWeaponName,
  590. iMode,
  591. pInfo->GetWeaponPrice(),
  592. pInfo->GetBullets(),
  593. pInfo->GetCycleTime(),
  594. m_weaponStats[iWeapon][iMode].shots,
  595. m_weaponStats[iWeapon][iMode].hits,
  596. m_weaponStats[iWeapon][iMode].damage,
  597. m_weaponStats[iWeapon][iMode].kills);
  598. }
  599. }
  600. filesystem->FPrintf(hLogFile, "\n");
  601. filesystem->FPrintf(hLogFile, "bot_difficulty, %d\n", cv_bot_difficulty.GetInt());
  602. g_pFullFileSystem->Close(hLogFile);
  603. }
  604. //-----------------------------------------------------------------------------
  605. // Purpose:
  606. //-----------------------------------------------------------------------------
  607. void CCSGameStats::Event_PlayerConnected( CBasePlayer *pPlayer )
  608. {
  609. ResetPlayerStats( ToCSPlayer( pPlayer ) );
  610. }
  611. //-----------------------------------------------------------------------------
  612. // Purpose:
  613. //-----------------------------------------------------------------------------
  614. void CCSGameStats::Event_PlayerDisconnected( CBasePlayer *pPlayer )
  615. {
  616. CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
  617. if ( !pCSPlayer )
  618. return;
  619. ResetPlayerStats( pCSPlayer );
  620. }
  621. //-----------------------------------------------------------------------------
  622. // Purpose:
  623. //-----------------------------------------------------------------------------
  624. void CCSGameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
  625. {
  626. // This also gets called when the victim is a building. That gets tracked separately as building destruction, don't count it here
  627. if ( !pVictim->IsPlayer() )
  628. return;
  629. CCSPlayer *pPlayerAttacker = ToCSPlayer( pAttacker );
  630. CCSPlayer *pPlayerVictim = ToCSPlayer( pVictim );
  631. // keep track of how many times every player kills every other player
  632. TrackKillStats( pPlayerAttacker, pPlayerVictim );
  633. // Skip rest of stat reporting for friendly fire
  634. if ( pPlayerAttacker->GetTeam() == pVictim->GetTeam() )
  635. return;
  636. CWeaponCSBase* pCSWeapon = dynamic_cast<CWeaponCSBase*>( info.GetWeapon() );
  637. // CSN-8452 Gungame modes were counting towards kills with enemy weapons.
  638. if ( CSGameRules()->IsPlayingClassic() )
  639. {
  640. if ( pCSWeapon )
  641. {
  642. if ( pCSWeapon->WasOwnedByTeam( pVictim->GetTeamNumber() ) )
  643. {
  644. // Stat is incremented if kill is made with a weapon that was once owned by the team that the killed player is on
  645. IncrementStat(pPlayerAttacker, CSSTAT_KILLS_ENEMY_WEAPON, 1);
  646. }
  647. }
  648. }
  649. CSWeaponMode weaponMode = Primary_Mode;
  650. if ( pCSWeapon )
  651. {
  652. weaponMode = pCSWeapon->m_weaponMode;
  653. }
  654. // CSN-8983 - Grenades aren't the player's weapon upon killing. Get the weapon info from the damage.
  655. CSWeaponID weaponId = WEAPON_NONE;
  656. const CCSWeaponInfo* pWeaponInfoFronDamage = CCSPlayer::GetWeaponInfoFromDamageInfo(info);
  657. if ( pWeaponInfoFronDamage )
  658. {
  659. weaponId = pWeaponInfoFronDamage->m_weaponId;
  660. }
  661. // update weapon stats
  662. ++m_weaponStats[weaponId][weaponMode].kills;
  663. for (int i = 0; WeaponName_StatId_Table[i].killStatId != CSSTAT_UNDEFINED; ++i)
  664. {
  665. if ( WeaponName_StatId_Table[i].weaponId == weaponId )
  666. {
  667. IncrementStat( pPlayerAttacker, WeaponName_StatId_Table[i].killStatId, 1 );
  668. break;
  669. }
  670. }
  671. if (pPlayerVictim && pPlayerVictim->IsBlind())
  672. {
  673. IncrementStat( pPlayerAttacker, CSSTAT_KILLS_ENEMY_BLINDED, 1 );
  674. }
  675. if (pPlayerVictim && pPlayerAttacker && pPlayerAttacker->IsBlindForAchievement())
  676. {
  677. IncrementStat( pPlayerAttacker, CSSTAT_KILLS_WHILE_BLINDED, 1 );
  678. }
  679. // [sbodenbender] check for deaths near planted bomb for funfact
  680. if (pPlayerVictim && pPlayerAttacker && pPlayerAttacker->GetTeamNumber() == TEAM_TERRORIST && CSGameRules()->m_bBombPlanted)
  681. {
  682. if ( pPlayerAttacker->IsCloseToActiveBomb() || pPlayerVictim->IsCloseToActiveBomb() )
  683. {
  684. IncrementStat(pPlayerAttacker, CSSTAT_KILLS_WHILE_DEFENDING_BOMB, 1);
  685. }
  686. }
  687. //Increment stat if this is a headshot.
  688. if (info.GetDamageType() & DMG_HEADSHOT)
  689. {
  690. IncrementStat( pPlayerAttacker, CSSTAT_KILLS_HEADSHOT, 1 );
  691. }
  692. IncrementStat( pPlayerAttacker, CSSTAT_KILLS, 1 );
  693. // we don't have a simple way (yet) to check if the victim actually just achieved The Unstoppable Force, so we
  694. // award this achievement simply if they've met the requirements and would have received it.
  695. PlayerStats_t &victimStats = m_aPlayerStats[pVictim->entindex()];
  696. if (victimStats.statsCurrentRound[CSSTAT_KILLS] >= AchievementConsts::ImmovableObject_Kills && !CSGameRules()->IsPlayingGunGameProgressive() )
  697. {
  698. pPlayerAttacker->AwardAchievement(CSImmovableObject);
  699. }
  700. CCSGameRules::TeamPlayerCounts playerCounts[TEAM_MAXCOUNT];
  701. CSGameRules()->GetPlayerCounts(playerCounts);
  702. int iAttackerTeamNumber = pPlayerAttacker->GetTeamNumber() ;
  703. if (playerCounts[iAttackerTeamNumber].totalAlivePlayers == 1 && playerCounts[iAttackerTeamNumber].killedPlayers >= 2)
  704. {
  705. IncrementStat(pPlayerAttacker, CSSTAT_KILLS_WHILE_LAST_PLAYER_ALIVE, 1);
  706. }
  707. //if they were damaged by more than one person that must mean that someone else did damage before the killer finished them off.
  708. if (pPlayerVictim->GetNumEnemyDamagers() > 1)
  709. {
  710. IncrementStat(pPlayerAttacker, CSSTAT_KILLS_ENEMY_WOUNDED, 1);
  711. }
  712. // set the number of consecutive kills this scorer has on the victim:
  713. int nConsecutiveKills = pPlayerAttacker ? MAX( FindPlayerStats( pPlayerVictim ).statsKills.iNumKilledByUnanswered[pPlayerAttacker->entindex()], 1 ) : 0;
  714. pPlayerVictim->SetLastConcurrentKilled( MIN( nConsecutiveKills, 8 ) );
  715. // check to see if a player killed another with a StatTrak weapon
  716. if ( CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>( info.GetWeapon() ) )
  717. {
  718. // Does the player own this item?
  719. CSteamID HolderSteamID;
  720. pAttacker->GetSteamID( &HolderSteamID );
  721. if ( CEconItemView *pItemView = pWeapon->GetEconItemView() )
  722. {
  723. // Get the supported killeater types on this weapon
  724. CUtlSortVector<uint32> killEaterTypes;
  725. pItemView->GetKillEaterTypes( killEaterTypes );
  726. if ( killEaterTypes.Count() > 0 )
  727. {
  728. if ( pItemView->GetAccountID() == HolderSteamID.GetAccountID() )
  729. {
  730. // Here we could differentiate on StatTrak types (normal, headshot, etc.):
  731. IncrementStat( pPlayerAttacker, CSSTAT_KILLS_WITH_STATTRAK_WEAPON, 1 );
  732. }
  733. }
  734. }
  735. }
  736. }
  737. void CCSGameStats::CalculateOverkill(CCSPlayer* pAttacker, CCSPlayer* pVictim)
  738. {
  739. //Count domination overkills - Do this before determining domination
  740. if (pAttacker->GetTeam() != pVictim->GetTeam())
  741. {
  742. if (pAttacker->IsPlayerDominated(pVictim->entindex()))
  743. {
  744. IncrementStat( pAttacker, CSSTAT_DOMINATION_OVERKILLS, 1 );
  745. }
  746. }
  747. }
  748. //-----------------------------------------------------------------------------
  749. // Purpose: Stats event for giving damage to player
  750. //-----------------------------------------------------------------------------
  751. void CCSGameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info )
  752. {
  753. CCSPlayer *pAttacker = ToCSPlayer( info.GetAttacker() );
  754. if ( pAttacker && pAttacker->GetTeam() != pBasePlayer->GetTeam() )
  755. {
  756. IncrementStat( pAttacker, CSSTAT_DAMAGE, info.GetDamage() );
  757. }
  758. // OGS stats
  759. // See if this is a bullet from a weapon that fires multiple (shotgun)
  760. #if !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
  761. if ( info.GetBulletID() != 0 )
  762. {
  763. uint8 iSubBullet = 0;
  764. SWeaponHitData *lastHitData = m_WeaponHitData.Count() ? m_WeaponHitData.Tail() : NULL;
  765. // If the previous weapon shot data has the save bulletid, then we check the sub bullet id and increment one from that
  766. if ( lastHitData && lastHitData->m_uiBulletID == CCSPlayer::GetBulletGroup() )
  767. {
  768. iSubBullet = lastHitData->m_uiSubBulletID + 1;
  769. }
  770. m_WeaponHitData.AddToTail( new SWeaponHitData( ToCSPlayer( pBasePlayer ), info, iSubBullet, CSGameRules()->m_iTotalRoundsPlayed, info.GetRecoilIndex() ) );
  771. }
  772. #endif
  773. }
  774. //-----------------------------------------------------------------------------
  775. // Purpose: Stats event for giving money to player
  776. //-----------------------------------------------------------------------------
  777. void CCSGameStats::Event_MoneyEarned( CCSPlayer* pPlayer, int moneyEarned)
  778. {
  779. if ( pPlayer && moneyEarned > 0)
  780. {
  781. IncrementStat(pPlayer, CSSTAT_MONEY_EARNED, moneyEarned);
  782. }
  783. }
  784. void CCSGameStats::Event_MoneySpent( CCSPlayer* pPlayer, int moneySpent, const char *pItemName )
  785. {
  786. if ( pPlayer && moneySpent > 0)
  787. {
  788. IncrementStat(pPlayer, CSSTAT_MONEY_SPENT, moneySpent);
  789. #if !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
  790. if ( pItemName && !pPlayer->IsBot() )
  791. {
  792. CSteamID steamIDForBuyer;
  793. pPlayer->GetSteamID( &steamIDForBuyer );
  794. m_MarketPurchases.AddToTail( new SMarketPurchases( steamIDForBuyer.ConvertToUint64(), moneySpent, pItemName, CSGameRules()->m_iTotalRoundsPlayed ) );
  795. }
  796. #endif
  797. }
  798. }
  799. void CCSGameStats::Event_PlayerDonatedWeapon (CCSPlayer* pPlayer)
  800. {
  801. if (pPlayer)
  802. {
  803. IncrementStat(pPlayer, CSSTAT_WEAPONS_DONATED, 1);
  804. }
  805. }
  806. void CCSGameStats::Event_MVPEarned( CCSPlayer* pPlayer )
  807. {
  808. if (pPlayer)
  809. {
  810. IncrementStat(pPlayer, CSSTAT_MVPS, 1);
  811. }
  812. }
  813. //-----------------------------------------------------------------------------
  814. // Purpose: Event handler
  815. //-----------------------------------------------------------------------------
  816. void CCSGameStats::FireGameEvent( IGameEvent *event )
  817. {
  818. const char *pEventName = event->GetName();
  819. if ( V_strcmp(pEventName, "round_end") == 0 )
  820. {
  821. const int reason = event->GetInt( "reason" );
  822. if( reason == Game_Commencing )
  823. {
  824. ResetPlayerClassMatchStats();
  825. }
  826. else
  827. {
  828. UpdatePlayerRoundStats(event->GetInt("winner"));
  829. }
  830. }
  831. else if ( V_strcmp( pEventName, "round_officially_ended" ) == 0 )
  832. {
  833. #if !defined( _GAMECONSOLE )
  834. // Upload round stats here to avoid end-of-round visual hitch
  835. UploadRoundStats();
  836. #endif
  837. }
  838. else if ( V_strcmp(pEventName, "break_prop") == 0 )
  839. {
  840. int userid = event->GetInt("userid", 0);
  841. int entindex = event->GetInt("entindex", 0);
  842. CBreakableProp* pProp = static_cast<CBreakableProp*>(CBaseEntity::Instance(entindex));
  843. Event_BreakProp(ToCSPlayer(UTIL_PlayerByUserId(userid)), pProp);
  844. }
  845. else if ( V_strcmp(pEventName, "player_decal") == 0 )
  846. {
  847. int userid = event->GetInt("userid", 0);
  848. Event_PlayerSprayedDecal(ToCSPlayer(UTIL_PlayerByUserId(userid)));
  849. }
  850. else if ( V_strcmp(pEventName, "begin_new_match") == 0 )
  851. {
  852. CreateNewGameStatsSession();
  853. }
  854. else if ( V_strcmp(pEventName, "bomb_planted") == 0 || V_strcmp(pEventName, "bomb_defused") == 0 )
  855. {
  856. //Generate a special weapon hit entry for each alive human player
  857. CPlantedC4* pPlantedC4 = g_PlantedC4s[0];
  858. uint8 unBombsite = event->GetInt("site",0); //Get Bombsite
  859. CSBombEventName nBombEventName = BombEventNameFromString(pEventName);
  860. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  861. {
  862. if (CCSPlayer *pCSPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) ) )
  863. {
  864. if ( pCSPlayer->IsConnected() && pCSPlayer->IsAlive() && !pCSPlayer->IsBot() )
  865. {
  866. CCSPlayer::StartNewBulletGroup();
  867. SWeaponHitData *pHitData = new SWeaponHitData;
  868. if ( pHitData->InitAsBombEvent( pCSPlayer, pPlantedC4, CCSPlayer::GetBulletGroup(), unBombsite, nBombEventName ) )
  869. {
  870. CCS_GameStats.RecordWeaponHit( pHitData ); // submission deletes the struct.
  871. }
  872. else
  873. {
  874. delete pHitData;
  875. }
  876. }
  877. }
  878. }
  879. }
  880. }
  881. //-----------------------------------------------------------------------------
  882. // Purpose: Return stats for the given player
  883. //-----------------------------------------------------------------------------
  884. const PlayerStats_t& CCSGameStats::FindPlayerStats( CBasePlayer *pPlayer ) const
  885. {
  886. return m_aPlayerStats[pPlayer->entindex()];
  887. }
  888. //-----------------------------------------------------------------------------
  889. // Purpose: Return stats for the given team
  890. //-----------------------------------------------------------------------------
  891. const StatsCollection_t& CCSGameStats::GetTeamStats( int iTeamIndex ) const
  892. {
  893. int arrayIndex = iTeamIndex - FIRST_GAME_TEAM;
  894. Assert( arrayIndex >= 0 && arrayIndex < TEAM_MAXCOUNT - FIRST_GAME_TEAM );
  895. return m_aTeamStats[arrayIndex];
  896. }
  897. //-----------------------------------------------------------------------------
  898. // Purpose: Resets the stats for each team
  899. //-----------------------------------------------------------------------------
  900. void CCSGameStats::ResetAllTeamStats()
  901. {
  902. for ( int i = 0; i < ARRAYSIZE(m_aTeamStats); ++i )
  903. {
  904. m_aTeamStats[i].Reset();
  905. }
  906. }
  907. //-----------------------------------------------------------------------------
  908. // Purpose: Resets all stats (including round, match, accumulated and rolling averages
  909. //-----------------------------------------------------------------------------
  910. void CCSGameStats::ResetAllStats()
  911. {
  912. for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
  913. {
  914. m_aPlayerStats[i].statsDelta.Reset();
  915. m_aPlayerStats[i].statsCurrentRound.Reset();
  916. m_aPlayerStats[i].statsCurrentMatch.Reset();
  917. m_aPlayerStats[i].statsKills.Reset();
  918. m_numberOfRoundsForDirectAverages = 0;
  919. m_numberOfTerroristEntriesForDirectAverages = 0;
  920. m_numberOfCounterTerroristEntriesForDirectAverages = 0;
  921. }
  922. ClearOGSRoundStats();
  923. }
  924. void CCSGameStats::ResetWeaponStats()
  925. {
  926. V_memset(m_weaponStats, 0, sizeof(m_weaponStats));
  927. }
  928. void CCSGameStats::IncrementTeamStat( int iTeamIndex, int iStatIndex, int iAmount )
  929. {
  930. int arrayIndex = iTeamIndex - TEAM_TERRORIST;
  931. Assert( iStatIndex >= 0 && iStatIndex < CSSTAT_MAX );
  932. if( arrayIndex >= 0 && arrayIndex < TEAM_MAXCOUNT - TEAM_TERRORIST )
  933. {
  934. m_aTeamStats[arrayIndex][iStatIndex] += iAmount;
  935. }
  936. }
  937. //-----------------------------------------------------------------------------
  938. // Purpose: Resets all stats for this player
  939. //-----------------------------------------------------------------------------
  940. void CCSGameStats::ResetPlayerStats( CBasePlayer* pPlayer )
  941. {
  942. PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
  943. // reset the stats on this player
  944. stats.Reset();
  945. // reset the matrix of who killed whom with respect to this player
  946. ResetKillHistory( pPlayer );
  947. }
  948. //-----------------------------------------------------------------------------
  949. // Purpose: Resets the kill history for this player
  950. //-----------------------------------------------------------------------------
  951. void CCSGameStats::ResetKillHistory( CBasePlayer* pPlayer )
  952. {
  953. int iPlayerIndex = pPlayer->entindex();
  954. PlayerStats_t& statsPlayer = m_aPlayerStats[iPlayerIndex];
  955. // for every other player, set all all the kills with respect to this player to 0
  956. for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
  957. {
  958. //reset their record of us.
  959. PlayerStats_t &statsOther = m_aPlayerStats[i];
  960. statsOther.statsKills.iNumKilled[iPlayerIndex] = 0;
  961. statsOther.statsKills.iNumKilledBy[iPlayerIndex] = 0;
  962. statsOther.statsKills.iNumKilledByUnanswered[iPlayerIndex] = 0;
  963. //reset our record of them
  964. statsPlayer.statsKills.iNumKilled[i] = 0;
  965. statsPlayer.statsKills.iNumKilledBy[i] = 0;
  966. statsPlayer.statsKills.iNumKilledByUnanswered[i] = 0;
  967. }
  968. }
  969. //-----------------------------------------------------------------------------
  970. // Purpose: Resets per-round stats for all players
  971. //-----------------------------------------------------------------------------
  972. void CCSGameStats::ResetRoundStats()
  973. {
  974. for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
  975. {
  976. m_aPlayerStats[i].statsCurrentRound.Reset();
  977. }
  978. ClearOGSRoundStats();
  979. }
  980. //-----------------------------------------------------------------------------
  981. // Purpose: Reset round stats
  982. //-----------------------------------------------------------------------------
  983. void CCSGameStats::ClearOGSRoundStats()
  984. {
  985. #if !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
  986. m_WeaponHitData.PurgeAndDeleteElements();
  987. m_WeaponMissData.PurgeAndDeleteElements();
  988. m_WeaponShotData.PurgeAndDeleteElements();
  989. m_MarketPurchases.PurgeAndDeleteElements();
  990. #endif
  991. }
  992. //-----------------------------------------------------------------------------
  993. // Purpose: Increments specified stat for specified player by specified amount
  994. //-----------------------------------------------------------------------------
  995. void CCSGameStats::IncrementStat( CCSPlayer* pPlayer, CSStatType_t statId, int iDelta, bool bPlayerOnly /* = false */, bool bIncludeBotController /* = false */ )
  996. {
  997. // note: don't track stats for players after they switch teams
  998. if ( pPlayer && !pPlayer->m_bTeamChanged)
  999. {
  1000. // if we're controlling a bot, credit the BOT with our stats
  1001. if( pPlayer->IsControllingBot() )
  1002. {
  1003. CCSPlayer* controlledPlayer = pPlayer->GetControlledBot();
  1004. AssertMsg( controlledPlayer != pPlayer, "Player should never match controlled player: this will cause an infinite loop" );
  1005. if( controlledPlayer )
  1006. {
  1007. IncrementStat( controlledPlayer, statId,iDelta,bPlayerOnly );
  1008. }
  1009. if ( !bIncludeBotController )
  1010. return;
  1011. }
  1012. PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
  1013. stats.statsDelta[statId] += iDelta;
  1014. stats.statsCurrentRound[statId] += iDelta;
  1015. stats.statsCurrentMatch[statId] += iDelta;
  1016. // increment team stat
  1017. int teamIndex = pPlayer->GetTeamNumber() - FIRST_GAME_TEAM;
  1018. if ( !bPlayerOnly && teamIndex >= 0 && teamIndex < ARRAYSIZE(m_aTeamStats) )
  1019. {
  1020. m_aTeamStats[teamIndex][statId] += iDelta;
  1021. }
  1022. for (int i = 0; i < ARRAYSIZE(ServerStatBasedAchievements); ++i)
  1023. {
  1024. if (ServerStatBasedAchievements[i].statId == statId)
  1025. {
  1026. // skip this if there is a map filter and it doesn't match
  1027. if (ServerStatBasedAchievements[i].mapFilter != NULL && V_strcmp(gpGlobals->mapname.ToCStr(), ServerStatBasedAchievements[i].mapFilter) != 0)
  1028. continue;
  1029. if ( CSGameRules()->IsPlayingGunGameProgressive() && ServerStatBasedAchievements[i].disallowGunGameProgressive )
  1030. continue;
  1031. bool bWasMet = ServerStatBasedAchievements[i].IsMet(stats.statsCurrentRound[statId] - iDelta, stats.statsCurrentMatch[statId] - iDelta);
  1032. bool bIsMet = ServerStatBasedAchievements[i].IsMet(stats.statsCurrentRound[statId], stats.statsCurrentMatch[statId]);
  1033. if (!bWasMet && bIsMet)
  1034. {
  1035. pPlayer->AwardAchievement(ServerStatBasedAchievements[i].achievementId);
  1036. }
  1037. }
  1038. }
  1039. }
  1040. }
  1041. //-----------------------------------------------------------------------------
  1042. // Purpose: Sets the specified stat for specified player to the specified amount
  1043. //-----------------------------------------------------------------------------
  1044. void CCSGameStats::SetStat( CCSPlayer *pPlayer, CSStatType_t statId, int iValue )
  1045. {
  1046. if (pPlayer)
  1047. {
  1048. int oldRoundValue, oldMatchValue;
  1049. PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
  1050. oldRoundValue = stats.statsCurrentRound[statId];
  1051. oldMatchValue = stats.statsCurrentMatch[statId];
  1052. stats.statsDelta[statId] = iValue;
  1053. stats.statsCurrentRound[statId] = iValue;
  1054. stats.statsCurrentMatch[statId] = iValue;
  1055. for (int i = 0; i < ARRAYSIZE(ServerStatBasedAchievements); ++i)
  1056. {
  1057. if (ServerStatBasedAchievements[i].statId == statId)
  1058. {
  1059. // skip this if there is a map filter and it doesn't match
  1060. if (ServerStatBasedAchievements[i].mapFilter != NULL && V_strcmp(gpGlobals->mapname.ToCStr(), ServerStatBasedAchievements[i].mapFilter) != 0)
  1061. continue;
  1062. bool bWasMet = ServerStatBasedAchievements[i].IsMet(oldRoundValue, oldMatchValue);
  1063. bool bIsMet = ServerStatBasedAchievements[i].IsMet(stats.statsCurrentRound[statId], stats.statsCurrentMatch[statId]);
  1064. if (!bWasMet && bIsMet)
  1065. {
  1066. pPlayer->AwardAchievement(ServerStatBasedAchievements[i].achievementId);
  1067. }
  1068. }
  1069. }
  1070. }
  1071. }
  1072. void CRC32Helper_ProcessInt16( CRC32_t &crc, int16 n )
  1073. {
  1074. int16 plat_n = LittleShort( n );
  1075. CRC32_ProcessBuffer( &crc, &plat_n, sizeof(plat_n) );
  1076. }
  1077. void CRC32Helper_ProcessInt32( CRC32_t &crc, int32 n )
  1078. {
  1079. int32 plat_n = LittleDWord( n );
  1080. CRC32_ProcessBuffer( &crc, &plat_n, sizeof(plat_n) );
  1081. }
  1082. void CRC32Helper_ProcessUInt32( CRC32_t &crc, uint32 n )
  1083. {
  1084. uint32 plat_n = LittleDWord( n );
  1085. CRC32_ProcessBuffer( &crc, &plat_n, sizeof(plat_n) );
  1086. }
  1087. void CCSGameStats::SendStatsToPlayer( CCSPlayer * pPlayer, int iMinStatPriority )
  1088. {
  1089. ASSERT(CSSTAT_MAX < 0xFFFF); // if we add more than 2^16 stats, we'll need to update this protocol
  1090. if ( pPlayer && pPlayer->IsConnected())
  1091. {
  1092. StatsCollection_t &deltaStats = m_aPlayerStats[pPlayer->entindex()].statsDelta;
  1093. // check to see if we have any stats to actually send
  1094. short iStatsToSend = 0;
  1095. for ( int iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
  1096. {
  1097. int iPriority = CSStatProperty_Table[iStat].flags & CSSTAT_PRIORITY_MASK;
  1098. if (deltaStats[iStat] != 0 && iPriority >= iMinStatPriority)
  1099. {
  1100. #if defined ( DEBUG_STAT_TRANSMISSION )
  1101. Msg( "Sending Stat '%s' to client. Value: %d\n", CSStatProperty_Table[iStat].szSteamName, deltaStats[iStat] );
  1102. #endif
  1103. ++iStatsToSend;
  1104. }
  1105. }
  1106. // nothing changed - bail out
  1107. if ( !iStatsToSend )
  1108. return;
  1109. CSingleUserRecipientFilter filter( pPlayer );
  1110. filter.MakeReliable();
  1111. CCSUsrMsg_PlayerStatsUpdate msg;
  1112. CRC32_t crc;
  1113. CRC32_Init( &crc );
  1114. // begin the CRC with a trivially hidden key value to discourage packet modification
  1115. const uint32 key = 0x82DA9F4C; // this key should match the key in cs_client_gamestats.cpp
  1116. CRC32Helper_ProcessUInt32( crc, key );
  1117. // if we make any change to the ordering of the stats or this message format, update this value
  1118. const byte version = 0x03;
  1119. CRC32_ProcessBuffer( &crc, &version, sizeof(version));
  1120. msg.set_version(version);
  1121. CRC32Helper_ProcessInt16( crc, iStatsToSend );
  1122. for ( short iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
  1123. {
  1124. int iPriority = CSStatProperty_Table[iStat].flags & CSSTAT_PRIORITY_MASK;
  1125. if (deltaStats[iStat] != 0 && iPriority >= iMinStatPriority)
  1126. {
  1127. CCSUsrMsg_PlayerStatsUpdate::Stat *pStat = msg.add_stats();
  1128. CRC32Helper_ProcessInt16( crc, iStat );
  1129. pStat->set_idx(iStat);
  1130. Assert(deltaStats[iStat] <= 0x7FFF && deltaStats[iStat] > 0); // make sure we aren't truncating bits
  1131. short delta = deltaStats[iStat];
  1132. CRC32Helper_ProcessInt16( crc, delta );
  1133. pStat->set_delta( deltaStats[iStat]);
  1134. deltaStats[iStat] = 0;
  1135. --iStatsToSend;
  1136. }
  1137. }
  1138. Assert(iStatsToSend == 0);
  1139. int userID = pPlayer->GetUserID();
  1140. msg.set_user_id( userID );
  1141. CRC32Helper_ProcessInt32( crc, userID );
  1142. CRC32_Final( &crc );
  1143. msg.set_crc(crc);
  1144. SendUserMessage( filter, CS_UM_PlayerStatsUpdate, msg );
  1145. }
  1146. }
  1147. //-----------------------------------------------------------------------------
  1148. // Purpose: Sends intermittent stats updates for stats that need to be updated during a round and/or life
  1149. //-----------------------------------------------------------------------------
  1150. void CCSGameStats::PreClientUpdate()
  1151. {
  1152. int iMinStatPriority = -1;
  1153. m_fDisseminationTimerHigh += gpGlobals->frametime;
  1154. m_fDisseminationTimerLow += gpGlobals->frametime;
  1155. if ( m_fDisseminationTimerHigh > cDisseminationTimeHigh)
  1156. {
  1157. iMinStatPriority = CSSTAT_PRIORITY_HIGH;
  1158. m_fDisseminationTimerHigh = 0.0f;
  1159. if ( m_fDisseminationTimerLow > cDisseminationTimeLow)
  1160. {
  1161. iMinStatPriority = CSSTAT_PRIORITY_LOW;
  1162. m_fDisseminationTimerLow = 0.0f;
  1163. }
  1164. }
  1165. else
  1166. return;
  1167. //The proper time has elapsed, now send the update to every player
  1168. for ( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  1169. {
  1170. CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex(iPlayerIndex) );
  1171. SendStatsToPlayer(pPlayer, iMinStatPriority);
  1172. }
  1173. }
  1174. //-----------------------------------------------------------------------------
  1175. // Purpose: Updates the stats of who has killed whom
  1176. //-----------------------------------------------------------------------------
  1177. void CCSGameStats::TrackKillStats( CCSPlayer *pAttacker, CCSPlayer *pVictim )
  1178. {
  1179. int iPlayerIndexAttacker = pAttacker->entindex();
  1180. int iPlayerIndexVictim = pVictim->entindex();
  1181. PlayerStats_t &statsAttacker = m_aPlayerStats[iPlayerIndexAttacker];
  1182. PlayerStats_t &statsVictim = m_aPlayerStats[iPlayerIndexVictim];
  1183. if( !pVictim->IsControllingBot() )
  1184. {
  1185. statsVictim.statsKills.iNumKilledBy[iPlayerIndexAttacker]++;
  1186. statsVictim.statsKills.iNumKilledByUnanswered[iPlayerIndexAttacker]++;
  1187. }
  1188. if( !pAttacker->IsControllingBot() )
  1189. {
  1190. statsAttacker.statsKills.iNumKilled[iPlayerIndexVictim]++;
  1191. statsAttacker.statsKills.iNumKilledByUnanswered[iPlayerIndexVictim] = 0;
  1192. }
  1193. }
  1194. //-----------------------------------------------------------------------------
  1195. // Purpose: Determines if attacker and victim have gotten domination or revenge
  1196. //-----------------------------------------------------------------------------
  1197. void CCSGameStats::CalcDominationAndRevenge( CCSPlayer *pAttacker, CCSPlayer *pVictim, int *piDeathFlags )
  1198. {
  1199. // [Forrest] Allow nemesis/revenge to be turned off for a server
  1200. if ( sv_nonemesis.GetBool() )
  1201. {
  1202. return;
  1203. }
  1204. // if we aren't playing gungame, we dont do domination or revenge
  1205. if ( !CSGameRules()->IsPlayingGunGame() )
  1206. return;
  1207. //If there is no attacker, there is no domination or revenge
  1208. if( !pAttacker || !pVictim )
  1209. {
  1210. return;
  1211. }
  1212. if (pAttacker->GetTeam() == pVictim->GetTeam())
  1213. {
  1214. return;
  1215. }
  1216. int iPlayerIndexVictim = pVictim->entindex();
  1217. PlayerStats_t &statsVictim = m_aPlayerStats[iPlayerIndexVictim];
  1218. // calculate # of unanswered kills between killer & victim
  1219. // This is plus 1 as this function gets called before the stat is updated. That is done so that the domination
  1220. // and revenge will be calculated prior to the death message being sent to the clients
  1221. int attackerEntityIndex = pAttacker->entindex();
  1222. int iKillsUnanswered = statsVictim.statsKills.iNumKilledByUnanswered[attackerEntityIndex] + 1;
  1223. if ( CS_KILLS_FOR_DOMINATION == iKillsUnanswered )
  1224. {
  1225. // this is the Nth unanswered kill between killer and victim, killer is now dominating victim
  1226. *piDeathFlags |= ( CS_DEATH_DOMINATION );
  1227. }
  1228. else if ( pVictim->IsPlayerDominated( pAttacker->entindex() ) && !pAttacker->IsControllingBot() )
  1229. {
  1230. // the killer killed someone who was dominating him, gains revenge
  1231. *piDeathFlags |= ( CS_DEATH_REVENGE );
  1232. }
  1233. //Check the overkill on 1 player achievement
  1234. if (!pAttacker->IsControllingBot() && iKillsUnanswered == CS_KILLS_FOR_DOMINATION + AchievementConsts::ExtendedDomination_AdditionalKills)
  1235. {
  1236. pAttacker->AwardAchievement(CSExtendedDomination);
  1237. }
  1238. if (!pAttacker->IsControllingBot() && iKillsUnanswered == CS_KILLS_FOR_DOMINATION)
  1239. {
  1240. //this is the Nth unanswered kill between killer and victim, killer is now dominating victim
  1241. //set victim to be dominated by killer
  1242. pAttacker->SetPlayerDominated( pVictim, true );
  1243. //Check concurrent dominations achievement
  1244. int numConcurrentDominations = 0;
  1245. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  1246. {
  1247. CCSPlayer *pPlayer= ToCSPlayer( UTIL_PlayerByIndex( i ) );
  1248. if (pPlayer && pAttacker->IsPlayerDominated(pPlayer->entindex()))
  1249. {
  1250. numConcurrentDominations++;
  1251. }
  1252. }
  1253. if (numConcurrentDominations >= AchievementConsts::ConcurrentDominations_MinDominations)
  1254. {
  1255. pAttacker->AwardAchievement(CSConcurrentDominations);
  1256. }
  1257. // record stats
  1258. Event_PlayerDominatedOther( pAttacker, pVictim );
  1259. }
  1260. else if ( pVictim->IsPlayerDominated( pAttacker->entindex() ) && !pAttacker->IsControllingBot() )
  1261. {
  1262. // the killer killed someone who was dominating him, gains revenge
  1263. // set victim to no longer be dominating the killer
  1264. pVictim->SetPlayerDominated( pAttacker, false );
  1265. // record stats
  1266. Event_PlayerRevenge( pAttacker );
  1267. }
  1268. }
  1269. void CCSGameStats::Event_PlayerDominatedOther( CCSPlayer *pAttacker, CCSPlayer* pVictim )
  1270. {
  1271. IncrementStat( pAttacker, CSSTAT_DOMINATIONS, 1 );
  1272. }
  1273. void CCSGameStats::Event_PlayerRevenge( CCSPlayer *pAttacker )
  1274. {
  1275. IncrementStat( pAttacker, CSSTAT_REVENGES, 1 );
  1276. }
  1277. void CCSGameStats::Event_PlayerAvengedTeammate( CCSPlayer* pAttacker, CCSPlayer* pAvengedPlayer )
  1278. {
  1279. if (pAttacker && pAvengedPlayer)
  1280. {
  1281. IGameEvent *event = gameeventmanager->CreateEvent( "player_avenged_teammate" );
  1282. if ( event )
  1283. {
  1284. event->SetInt( "avenger_id", pAttacker->GetUserID() );
  1285. event->SetInt( "avenged_player_id", pAvengedPlayer->GetUserID() );
  1286. gameeventmanager->FireEvent( event );
  1287. }
  1288. }
  1289. }
  1290. void CCSGameStats::Event_LevelInit()
  1291. {
  1292. ResetAllTeamStats();
  1293. ResetWeaponStats();
  1294. CBaseGameStats::Event_LevelInit();
  1295. }
  1296. void CCSGameStats::Event_LevelShutdown( float fElapsed )
  1297. {
  1298. if (sv_dumpmatchweaponmetrics.GetBool())
  1299. {
  1300. DumpMatchWeaponMetrics();
  1301. }
  1302. CBaseGameStats::Event_LevelShutdown(fElapsed);
  1303. #if !defined( _GAMECONSOLE )
  1304. GetSteamWorksGameStatsServer().EndSession();
  1305. #endif
  1306. }
  1307. // Reset any per match info that resides in the player class
  1308. void CCSGameStats::ResetPlayerClassMatchStats()
  1309. {
  1310. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  1311. {
  1312. CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
  1313. if ( pPlayer )
  1314. {
  1315. pPlayer->SetNumMVPs( 0 );
  1316. }
  1317. }
  1318. }
  1319. #if !defined( _GAMECONSOLE )
  1320. extern double g_rowCommitTime;
  1321. extern double g_rowWriteTime;
  1322. //-----------------------------------------------------------------------------
  1323. // Purpose: Submits all round specific data to the OGS system
  1324. //-----------------------------------------------------------------------------
  1325. void CCSGameStats::UploadRoundStats( void )
  1326. {
  1327. #if !defined( NO_STEAM )
  1328. // If we don't have any data to send, we can early out now;
  1329. // Purpose: Linux servers hang if they submit too many bullets at once and they restart. That's bad!
  1330. // Only report rounds that are likely to yield valuable data and not cause issues:
  1331. // Competitive Rounds (including tournaments on other servers)
  1332. // Valve's Casual Rounds (to capture Operation Payback and other special events).
  1333. bool bIsCompetitiveRound = ( game_mode.GetInt() == 1 && game_type.GetInt() == 0 && CSGameRules() && CSGameRules()->GetRoundLength() < 300 );
  1334. bool bIsValveCasualRound = ( game_mode.GetInt() == 0 && game_type.GetInt() == 0 && IsValveDedicated() ); //Adding IsValveDedicated
  1335. static char const * s_pchTournamentServer = CommandLine()->ParmValue( "-tournament", ( char const * ) NULL );
  1336. static bool s_bSubmittingStats = ( RandomFloat() < 0.1 ) || ( IsValveDedicated() && s_pchTournamentServer ); // Valve tournament major servers do not throttle
  1337. bool bIsValidMatch = bIsCompetitiveRound || bIsValveCasualRound;
  1338. if ( !bIsValidMatch || !s_bSubmittingStats )
  1339. {
  1340. CCSPlayer::ResetBulletGroup();
  1341. ClearOGSRoundStats();
  1342. return;
  1343. }
  1344. KeyValues *pKV = new KeyValues( "basedata" );
  1345. if ( !pKV )
  1346. return;
  1347. CFastTimer totalTimer, weaponHitTimer, weaponMissTimer, marketPurchaseTimer, submitTimer, cleanupTimer;
  1348. g_rowCommitTime = 0.0f;
  1349. g_rowWriteTime = 0.0f;
  1350. totalTimer.Start();
  1351. const char *pzMapName = gpGlobals->mapname.ToCStr();
  1352. pKV->SetString( "MapID", pzMapName );
  1353. // Calculate the shot misses by searching for any entries that are in the shot fired list but not in the damage given list
  1354. for ( int i = 0 ; i < m_WeaponShotData.Count() ; ++i )
  1355. {
  1356. bool found = false;
  1357. for ( int k=0 ; k < m_WeaponHitData.Count() ; ++k )
  1358. {
  1359. // This shot was a hit so we can move on and search for the next miss
  1360. if ( m_WeaponShotData[i]->m_uiBulletID == m_WeaponHitData[k]->m_uiBulletID && m_WeaponShotData[i]->m_uiSubBulletID == m_WeaponHitData[k]->m_uiSubBulletID )
  1361. {
  1362. found = true;
  1363. break;
  1364. }
  1365. }
  1366. // This shot was a miss so add it to Missed container
  1367. if ( !found )
  1368. {
  1369. m_WeaponMissData.AddToTail( new SWeaponMissData( m_WeaponShotData[i] ) );
  1370. }
  1371. }
  1372. weaponHitTimer.Start();
  1373. uint32 iNumHits = m_WeaponHitData.Count();
  1374. for ( int j=0 ; j < m_WeaponHitData.Count() ; ++j )
  1375. {
  1376. m_WeaponHitData[ j ]->CompactBulletID();
  1377. SubmitStat( m_WeaponHitData[ j ] );
  1378. }
  1379. weaponHitTimer.End();
  1380. weaponMissTimer.Start();
  1381. uint32 iNumMisses = m_WeaponMissData.Count();
  1382. for ( int k=0 ; k < m_WeaponMissData.Count() ; ++k )
  1383. {
  1384. m_WeaponMissData[ k ]->CompactBulletID();
  1385. SubmitStat( m_WeaponMissData[ k ] );
  1386. }
  1387. weaponMissTimer.End();
  1388. marketPurchaseTimer.Start();
  1389. uint32 iNumPurchases = m_MarketPurchases.Count();
  1390. for ( int k=0 ; k < m_MarketPurchases.Count() ; ++k )
  1391. SubmitStat( m_MarketPurchases[ k ] );
  1392. marketPurchaseTimer.End();
  1393. submitTimer.Start();
  1394. // Perform the actual submission
  1395. SubmitGameStats( pKV );
  1396. submitTimer.End();
  1397. cleanupTimer.Start();
  1398. // Clear out the per round stats
  1399. ClearOGSRoundStats();
  1400. pKV->deleteThis();
  1401. cleanupTimer.End();
  1402. totalTimer.End();
  1403. if ( sv_debugroundstats.GetBool() )
  1404. {
  1405. Msg( "**** ROUND STAT DEBUG ****\n" );
  1406. Msg( "UploadRoundStats completed. %.3f msec. Breakdown:\n hit: %.3f msec\n miss: %.3f msec\n market: %.3f msec\n submit: %.3f msec\n cleanup: %.3f msec\n counts: %d %d %d \n commit: %.3fms\n write: %.3fms.\n\n",
  1407. totalTimer.GetDuration().GetMillisecondsF(),
  1408. weaponHitTimer.GetDuration().GetMillisecondsF(),
  1409. weaponMissTimer.GetDuration().GetMillisecondsF(),
  1410. marketPurchaseTimer.GetDuration().GetMillisecondsF(),
  1411. submitTimer.GetDuration().GetMillisecondsF(),
  1412. cleanupTimer.GetDuration().GetMillisecondsF(),
  1413. iNumHits, iNumMisses, iNumPurchases, g_rowCommitTime, g_rowWriteTime );
  1414. }
  1415. // Reset the bullet ID.
  1416. CCSPlayer::ResetBulletGroup();
  1417. #endif // !NO_STEAM
  1418. }
  1419. #if 0
  1420. CON_COMMAND ( teststats, "Test command" )
  1421. {
  1422. CFastTimer totalTimer;
  1423. double uploadTime = 0.0f;
  1424. g_rowCommitTime = 0.0f;
  1425. g_rowWriteTime = 0.0f;
  1426. for( int i = 0; i < 1000; i++ )
  1427. {
  1428. KeyValues *pKV = new KeyValues( "basedata" );
  1429. if ( !pKV )
  1430. return;
  1431. pKV->SetName( "foobartest" );
  1432. pKV->SetUint64( "test1", 1234 );
  1433. pKV->SetUint64( "test2", 1234 );
  1434. pKV->SetUint64( "test3", 1234 );
  1435. pKV->SetUint64( "test4", 1234 );
  1436. pKV->SetString( "test5", "TEST1234567890TEST1234567890TEST!");
  1437. totalTimer.Start();
  1438. GetSteamWorksGameStatsServer().AddStatsForUpload( pKV, args.ArgC() == 1 );
  1439. totalTimer.End();
  1440. uploadTime += totalTimer.GetDuration().GetMillisecondsF();
  1441. }
  1442. Msg( "teststats took %.3f msec commit: %.3fms write: %.3fms.\n", uploadTime, g_rowCommitTime, g_rowWriteTime );
  1443. }
  1444. #endif
  1445. //-----------------------------------------------------------------------------
  1446. // Purpose:
  1447. //-----------------------------------------------------------------------------
  1448. void CCSGameStats::SubmitGameStats( KeyValues *pKV )
  1449. {
  1450. #if !defined( NO_STEAM )
  1451. int listCount = s_StatLists->Count();
  1452. for( int i=0; i < listCount; ++i )
  1453. {
  1454. // Create a master key value that has stats everybody should share (map name, session ID, etc)
  1455. (*s_StatLists)[i]->SendData(pKV);
  1456. (*s_StatLists)[i]->Clear();
  1457. }
  1458. #endif // !NO_STEAM
  1459. }
  1460. //-----------------------------------------------------------------------------
  1461. // Purpose:
  1462. //-----------------------------------------------------------------------------
  1463. CCSGameStats::StatContainerList_t* CCSGameStats::GetStatContainerList( void )
  1464. {
  1465. #if !defined( NO_STEAM )
  1466. return s_StatLists;
  1467. #else
  1468. return NULL;
  1469. #endif
  1470. }
  1471. //-----------------------------------------------------------------------------
  1472. // Purpose:
  1473. //-----------------------------------------------------------------------------
  1474. bool CCSGameStats::AnyOGSDataToSubmit( void )
  1475. {
  1476. #if !defined( NO_STEAM )
  1477. return m_WeaponShotData.Count() > 0 || m_MarketPurchases.Count() > 0;
  1478. #else
  1479. return false;
  1480. #endif
  1481. }
  1482. void CCSGameStats::CreateNewGameStatsSession( void )
  1483. {
  1484. GetSteamWorksGameStatsServer().EndSession();
  1485. GetSteamWorksGameStatsServer().StartSession();
  1486. }
  1487. void CCSGameStats::RecordWeaponHit( SWeaponHitData* pHitData )
  1488. {
  1489. m_WeaponHitData.AddToTail( pHitData );
  1490. }
  1491. float UTIL_GetEffectiveRange( CCSPlayer* pPlayer )
  1492. {
  1493. if ( !pPlayer )
  1494. return 0.0f;
  1495. CWeaponCSBase *weapon = dynamic_cast< CWeaponCSBase * >( pPlayer->GetActiveWeapon() );
  1496. if ( !weapon )
  1497. return 0.0f;
  1498. Vector vecDirShooting, vecRight, vecUp;
  1499. AngleVectors( pPlayer->GetFinalAimAngle(), &vecDirShooting, &vecRight, &vecUp );
  1500. float fInaccuracy = weapon->GetInaccuracy();
  1501. float fSpread = weapon->GetSpread();
  1502. float fFinalInaccuracy = fInaccuracy + fSpread;
  1503. Vector vecInaccFinal = vecDirShooting + fFinalInaccuracy * vecRight + fFinalInaccuracy * vecUp;
  1504. VectorNormalize( vecInaccFinal );
  1505. float flDotInaccFinal = DotProduct( vecDirShooting.Normalized(), vecInaccFinal.Normalized() );
  1506. float flAngleInaccFinal = flDotInaccFinal < 0.0f ? -acos( flDotInaccFinal ) : acos( flDotInaccFinal );
  1507. //Msg( "Inaccuracy : %.2f deg.\n", RAD2DEG( flAngleInaccFinal ) );
  1508. return ( 0.5 * 12 ) / tanf( 0.5 * flAngleInaccFinal ); // 12 inch dinner plate
  1509. }
  1510. SWeaponHitData::SWeaponHitData( CCSPlayer *pCSTarget, const CTakeDamageInfo &info, uint8 subBullet, uint8 round, uint8 iRecoilIndex )
  1511. {
  1512. Clear();
  1513. CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(info.GetWeapon());
  1514. // If we don't have a valid pCSWeapon then the weapon is most likely a radius type weapon
  1515. uint8 shotInaccuracy = 0;
  1516. {
  1517. CBaseEntity* pInflictor = info.GetInflictor();
  1518. if ( CBaseCSGrenadeProjectile* pGrenade = dynamic_cast< CBaseCSGrenadeProjectile* >( pInflictor ) )
  1519. {
  1520. //Some form of grenade, not Molotov.
  1521. if ( CCSPlayer *pPlayer = ToCSPlayer( pGrenade->GetThrower() ) )
  1522. {
  1523. for ( int i = LOADOUT_POSITION_GRENADE0; i <= LOADOUT_POSITION_GRENADE5; ++i )
  1524. {
  1525. if ( !pGrenade->m_pWeaponInfo )
  1526. continue;
  1527. if ( !pPlayer->Inventory() )
  1528. continue;
  1529. CEconItemView *pGrenadeItemView = pPlayer->Inventory()->GetItemInLoadout( pPlayer->GetTeamNumber(), i ); //LOADOUT_POSITION_GRENADE0 );
  1530. if ( !pGrenadeItemView || !pGrenadeItemView->GetItemDefinition() )
  1531. continue;
  1532. //if ( V_strcmp( pGrenadeItemView->GetItemDefinition()->GetItemClass(), pGrenadeItemView->m_pWeaponInfo->szClassname ) == 0 )
  1533. if ( V_strcmp( pGrenadeItemView->GetItemDefinition()->GetItemClass(), pGrenade->m_pWeaponInfo->szClassName ) == 0 )
  1534. {
  1535. m_ui8WeaponID = (uint8)pGrenadeItemView->GetItemIndex();
  1536. }
  1537. }
  1538. }
  1539. }
  1540. else if ( CInferno* pInferno = dynamic_cast< CInferno* >( pInflictor ) )
  1541. {
  1542. //Molotov's FIRE damage, not the projectile itself.
  1543. CCSPlayer *pPlayer = ToCSPlayer( info.GetAttacker() );
  1544. if ( pPlayer && pPlayer->Inventory() )
  1545. {
  1546. for ( int i = LOADOUT_POSITION_GRENADE0; i <= LOADOUT_POSITION_GRENADE5; ++i )
  1547. {
  1548. CEconItemView *pInfernoItemView = pPlayer->Inventory()->GetItemInLoadout( pPlayer->GetTeamNumber(), i );
  1549. if ( !pInfernoItemView || !pInfernoItemView->GetItemDefinition() || !pInferno->GetSourceWeaponInfo() )
  1550. continue;
  1551. if ( V_strcmp( pInfernoItemView->GetItemDefinition()->GetItemClass(), pInferno->GetSourceWeaponInfo()->szClassName) == 0 )
  1552. {
  1553. m_ui8WeaponID = (uint8)pInfernoItemView->GetItemIndex();
  1554. }
  1555. }
  1556. }
  1557. }
  1558. else if ( CPlantedC4* pPlantedC4 = dynamic_cast< CPlantedC4* > ( pInflictor ) )
  1559. {
  1560. //C4 explosion damage
  1561. if ( CCSPlayer *pPlayer = pPlantedC4->GetPlanter() )
  1562. {
  1563. if ( CEconItemView *pPlantedC4ItemView = pPlayer->Inventory()->GetItemInLoadout( pPlayer->GetTeamNumber(), LOADOUT_POSITION_C4 ) )
  1564. {
  1565. m_ui8WeaponID = (uint8)pPlantedC4ItemView->GetItemIndex();
  1566. }
  1567. }
  1568. }
  1569. }
  1570. CCSPlayer *pCSAttacker = ToCSPlayer( info.GetAttacker() );
  1571. // If there isn't a valid attacker, then try using the weapon's owner entity
  1572. if ( !pCSAttacker && pCSWeapon )
  1573. {
  1574. pCSAttacker = ToCSPlayer( pCSWeapon->GetOwnerEntity() );
  1575. }
  1576. if ( pCSAttacker )
  1577. {
  1578. // If we haven't been able to classify the weapon yet, then assume it's the player's current active weapon.
  1579. if ( m_ui8WeaponID == WEAPON_NONE && pCSAttacker->GetActiveWeapon() )
  1580. {
  1581. CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(pCSAttacker->GetActiveWeapon());
  1582. m_ui8WeaponID = /*pCSWeapon ? (uint8)pCSWeapon->GetEconItemView()->GetItemIndex() :*/ WEAPON_NONE;
  1583. }
  1584. m_vAttackerPos = pCSAttacker->GetAbsOrigin();
  1585. m_ui64AttackerID = GetPlayerID( pCSAttacker );
  1586. m_uAttackerMovement = (uint8)pCSAttacker->GetAbsVelocity().Length2D() << 8
  1587. | (FBitSet( pCSAttacker->GetFlags(), FL_ONGROUND ) ? 0 : 1) << 16 //Not on ground?
  1588. | (FBitSet( pCSAttacker->GetFlags(), FL_DUCKING ) ? 1 : 0) << 17
  1589. | shotInaccuracy;
  1590. }
  1591. if ( pCSTarget )
  1592. {
  1593. m_vTargetPos = pCSTarget->GetAbsOrigin();
  1594. m_uiDamage = info.GetDamage();
  1595. m_ui8Health = pCSTarget->GetHealth();
  1596. // Where on the target's body was hit?
  1597. // HITGROUP_GENERIC, HITGROUP_HEAD, HITGROUP_CHEST, HITGROUP_STOMACH, HITGROUP_LEFTARM, HITGROUP_RIGHTARM, HITGROUP_LEFTLEG, HITGROUP_RIGHTLEG, HITGROUP_GEAR
  1598. m_HitRegion = pCSTarget->m_LastHitGroup;
  1599. m_ui64TargertID = GetPlayerID( pCSTarget );
  1600. m_uAttackerMovement = m_uAttackerMovement | (uint8)pCSTarget->GetAbsVelocity().Length2D() << 24;
  1601. }
  1602. m_uiBulletID = info.GetBulletID();
  1603. m_uiSubBulletID = subBullet;
  1604. m_uiRecoilIndex = iRecoilIndex;
  1605. m_RoundID = round;
  1606. }
  1607. bool SWeaponHitData::InitAsGrenadeDetonation( CBaseCSGrenadeProjectile *pGrenade, uint32 unBulletGroup )
  1608. {
  1609. // Weaponinfo gets set in different places for different grenades...
  1610. // all grenades getting detonated should have a weaponinfo set by then, but it's hard to know that just reading
  1611. // the code. Putting a warning/safety guard here just in case.
  1612. if ( !pGrenade || !pGrenade->m_pWeaponInfo )
  1613. {
  1614. Warning( "Failing to submit row for a grenade detonation: Grenade has no weapon info!\n" );
  1615. return false;
  1616. }
  1617. m_ui8WeaponID = pGrenade->m_pWeaponInfo->m_weaponId;
  1618. CCSPlayer *pCSAttacker = ToCSPlayer( pGrenade->GetThrower() );
  1619. Assert( pGrenade->GetThrower() == pGrenade->GetOriginalThrower() ); // Appears these are always the same-- If this fires, investigate which should be recorded.
  1620. if ( pCSAttacker )
  1621. {
  1622. m_vAttackerPos = pCSAttacker->GetAbsOrigin();
  1623. m_ui64AttackerID = GetPlayerID( pCSAttacker );
  1624. }
  1625. // target is always null for grenade detonation rows, origin is the place we landed
  1626. m_ui64TargertID = 0;
  1627. m_vTargetPos = pGrenade->GetAbsOrigin();
  1628. m_uiBulletID = unBulletGroup;
  1629. // overriding to store info about what the grenaded did upon detonation
  1630. m_HitRegion = pGrenade->m_unOGSExtraFlags;
  1631. m_RoundID = CSGameRules()->m_iTotalRoundsPlayed;
  1632. // for flashes, using these fields to smuggle counts of players effected
  1633. if ( CFlashbangProjectile *pFlash = dynamic_cast<CFlashbangProjectile*>(pGrenade) )
  1634. {
  1635. m_uiDamage = pFlash->m_numOpponentsHit;
  1636. m_ui8Health = pFlash->m_numTeammatesHit;
  1637. }
  1638. else
  1639. {
  1640. m_uiDamage = 0;
  1641. m_ui8Health = 0;
  1642. }
  1643. // todo: possible places to smuggle info
  1644. m_uiSubBulletID = 0;
  1645. m_uiRecoilIndex = 0;
  1646. return true;
  1647. }
  1648. bool SWeaponHitData::InitAsBombEvent( CCSPlayer *pCSPlayer, CPlantedC4 *pPlantedC4, uint32 unBulletGroup, uint8 unBombsite, CSBombEventName nBombEventID )
  1649. {
  1650. // If for any reason we cannot get a pointer to the currently-planted C4 or current player, skip data collection
  1651. if ( !pCSPlayer || !pPlantedC4 )
  1652. {
  1653. Warning( "Failing to submit row for bomb plant: Player or C4 missing!\n" );
  1654. return false;
  1655. }
  1656. //If bomb has been planted and is not defused, set TargetID to Planter
  1657. //If bomb has been planted and has been defused, set TargetID to Defuser
  1658. //Store plant state in m_uiDamage
  1659. m_uiDamage = nBombEventID;
  1660. if ( CCSPlayer *pPlanter = pPlantedC4->GetPlanter() )
  1661. {
  1662. //Get data from planted C4: WeaponID
  1663. if ( CEconItemView *pPlantedC4ItemView = pPlanter->Inventory()->GetItemInLoadout( pPlanter->GetTeamNumber(), LOADOUT_POSITION_C4 ) )
  1664. {
  1665. m_ui8WeaponID = (uint8)pPlantedC4ItemView->GetItemIndex();
  1666. }
  1667. if ( nBombEventID == BOMB_EVENT_NAME_PLANTED )
  1668. {
  1669. m_ui64TargertID = GetPlayerID( pPlanter );
  1670. m_uiDamage = 1;
  1671. }
  1672. if ( nBombEventID == BOMB_EVENT_NAME_DEFUSED )
  1673. {
  1674. if ( CCSPlayer *pDefuser = pPlantedC4->GetDefuser() )
  1675. {
  1676. m_ui64TargertID = GetPlayerID( pDefuser );
  1677. }
  1678. }
  1679. }
  1680. m_vTargetPos = pPlantedC4->GetAbsOrigin(); //Record Bomb Location
  1681. // Attacker position, in this case, is just the location of the current alive player
  1682. if ( pCSPlayer )
  1683. {
  1684. m_vAttackerPos = pCSPlayer->GetAbsOrigin();
  1685. m_ui64AttackerID = GetPlayerID( pCSPlayer );
  1686. }
  1687. m_uiBulletID = unBulletGroup;
  1688. // overriding to store info about the bombsite involved.
  1689. // Shifting storage of unBombsite from m_HitRegion, because we have no guarantee that the value will fall within a tinyint field.
  1690. m_uAttackerMovement = unBombsite;
  1691. m_RoundID = CSGameRules()->m_iTotalRoundsPlayed;
  1692. // fields remaining to store info about the plant/defuse event
  1693. // m_HitRegion = 0;
  1694. // m_ui8Health = 0;
  1695. // m_uiSubBulletID = 0;
  1696. // m_uiRecoilIndex = 0;
  1697. return true;
  1698. }
  1699. #endif // !_GAMECONSOLE