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

3592 lines
115 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "tf_gamerules.h"
  8. #include "tf_gamestats.h"
  9. #include "tf_obj_sentrygun.h"
  10. #include "tf_obj_dispenser.h"
  11. #include "tf_obj_sapper.h"
  12. #include "tf_team.h"
  13. #include "usermessages.h"
  14. #include "player_resource.h"
  15. #include "team.h"
  16. #include "achievementmgr.h"
  17. #include "hl2orange.spa.h"
  18. #include "tf_weapon_medigun.h"
  19. #include "NextBot/NextBotManager.h"
  20. #include "team_control_point_master.h"
  21. #include "steamworks_gamestats.h"
  22. #include "vote_controller.h"
  23. #include "tf_mann_vs_machine_stats.h"
  24. #include "tf_passtime_logic.h"
  25. #include "filesystem.h" // for temp passtime local stats logging
  26. #include "passtime_convars.h"
  27. #include "tf_matchmaking_shared.h"
  28. #include "gc_clientsystem.h"
  29. #include "tf_gcmessages.h"
  30. #include "rtime.h"
  31. #include "team_train_watcher.h"
  32. #include "tf_gc_api.h"
  33. extern ConVar tf_mm_trusted;
  34. // Must run with -gamestats to be able to turn on/off stats with ConVar below.
  35. static ConVar tf_stats_nogameplaycheck( "tf_stats_nogameplaycheck", "0", FCVAR_NONE , "Disable normal check for valid gameplay, send stats regardless." );
  36. //static ConVar tf_stats_track( "tf_stats_track", "1", FCVAR_NONE, "Turn on//off tf stats tracking." );
  37. //static ConVar tf_stats_verbose( "tf_stats_verbose", "0", FCVAR_NONE, "Turn on//off verbose logging of stats." );
  38. CTFGameStats CTF_GameStats;
  39. const char *g_aClassNames[] =
  40. {
  41. "TF_CLASS_UNDEFINED",
  42. "TF_CLASS_SCOUT",
  43. "TF_CLASS_SNIPER",
  44. "TF_CLASS_SOLDIER",
  45. "TF_CLASS_DEMOMAN",
  46. "TF_CLASS_MEDIC",
  47. "TF_CLASS_HEAVYWEAPONS",
  48. "TF_CLASS_PYRO",
  49. "TF_CLASS_SPY",
  50. "TF_CLASS_ENGINEER",
  51. "TF_CLASS_CIVILIAN",
  52. };
  53. //-----------------------------------------------------------------------------
  54. // Purpose: Constructor
  55. // Input : -
  56. //-----------------------------------------------------------------------------
  57. CTFGameStats::CTFGameStats()
  58. {
  59. gamestats = this;
  60. Clear();
  61. SetDefLessFunc( m_MapsPlaytime );
  62. }
  63. //-----------------------------------------------------------------------------
  64. // Purpose: Destructor
  65. // Input : -
  66. //-----------------------------------------------------------------------------
  67. CTFGameStats::~CTFGameStats()
  68. {
  69. Clear();
  70. }
  71. //-----------------------------------------------------------------------------
  72. // Purpose: Clear out game stats
  73. // Input : -
  74. //-----------------------------------------------------------------------------
  75. void CTFGameStats::Clear( void )
  76. {
  77. m_bRoundActive = false;
  78. m_iRoundsPlayed = 0;
  79. m_iEvents = 0;
  80. m_iKillCount = 0;
  81. m_iPlayerUpdates = 0;
  82. m_reportedStats.Clear();
  83. Q_memset( m_aPlayerStats, 0, sizeof( m_aPlayerStats ) );
  84. m_rdStats.Clear();
  85. m_passtimeStats.Clear();
  86. m_iLoadoutChangesCount = 1;
  87. }
  88. //-----------------------------------------------------------------------------
  89. // Purpose: Adds our data to the gamestats data that gets uploaded.
  90. // Returns true if we added data, false if we didn't
  91. //-----------------------------------------------------------------------------
  92. bool CTFGameStats::AddDataForSend( KeyValues *pKV, StatSendType_t sendType )
  93. {
  94. // we only have data to send at level shutdown
  95. if ( sendType != STATSEND_LEVELSHUTDOWN )
  96. return false;
  97. // do we have anything to report?
  98. if ( !m_reportedStats.m_bValidData )
  99. return false;
  100. // Save data per map.
  101. CUtlDict<TF_Gamestats_LevelStats_t, unsigned short> &dictMapStats = m_reportedStats.m_dictMapStats;
  102. Assert( dictMapStats.Count() <= 1 );
  103. for ( int iMap = dictMapStats.First(); iMap != dictMapStats.InvalidIndex(); iMap = dictMapStats.Next( iMap ) )
  104. {
  105. // Get the current map.
  106. TF_Gamestats_LevelStats_t *pCurrentMap = &dictMapStats[iMap];
  107. Assert( pCurrentMap );
  108. KeyValues *pKVData = new KeyValues( "tf_mapdata" );
  109. pKVData->SetInt( "RoundsPlayed", pCurrentMap->m_Header.m_iRoundsPlayed );
  110. pKVData->SetInt( "TotalTime", pCurrentMap->m_Header.m_iTotalTime );
  111. pKVData->SetInt( "BlueWins", pCurrentMap->m_Header.m_iBlueWins );
  112. pKVData->SetInt( "RedWins", pCurrentMap->m_Header.m_iRedWins );
  113. pKVData->SetInt( "Stalemates", pCurrentMap->m_Header.m_iStalemates );
  114. pKVData->SetInt( "BlueSuddenDeathWins", pCurrentMap->m_Header.m_iBlueSuddenDeathWins );
  115. pKVData->SetInt( "RedSuddenDeathWins", pCurrentMap->m_Header.m_iRedSuddenDeathWins );
  116. for ( int i = 0; i <= MAX_CONTROL_POINTS; i++ )
  117. {
  118. if ( pCurrentMap->m_Header.m_iLastCapChangedInRound[i] )
  119. {
  120. pKVData->SetInt( UTIL_VarArgs("RoundsEndingOnCP%d",i), pCurrentMap->m_Header.m_iLastCapChangedInRound[i] );
  121. }
  122. }
  123. pKV->AddSubKey( pKVData );
  124. // save class stats
  125. for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass ++ )
  126. {
  127. TF_Gamestats_ClassStats_t &classStats = pCurrentMap->m_aClassStats[iClass];
  128. if ( classStats.iTotalTime > 0 )
  129. {
  130. pKVData = new KeyValues( "tf_classdata" );
  131. pKVData->SetInt( "Class", iClass );
  132. pKVData->SetInt( "Spawns", classStats.iSpawns );
  133. pKVData->SetInt( "TotalTime", classStats.iTotalTime );
  134. pKVData->SetInt( "Score", classStats.iScore );
  135. pKVData->SetInt( "Kills", classStats.iKills );
  136. pKVData->SetInt( "Deaths", classStats.iDeaths );
  137. pKVData->SetInt( "Assists", classStats.iAssists );
  138. pKVData->SetInt( "Captures", classStats.iCaptures );
  139. pKVData->SetInt( "ClassChanges", classStats.iClassChanges );
  140. pKV->AddSubKey( pKVData );
  141. }
  142. }
  143. // save weapon stats
  144. for ( int iWeapon = TF_WEAPON_NONE+1; iWeapon < TF_WEAPON_COUNT; iWeapon++ )
  145. {
  146. TF_Gamestats_WeaponStats_t &weaponStats = pCurrentMap->m_aWeaponStats[iWeapon];
  147. if ( weaponStats.iShotsFired > 0 )
  148. {
  149. pKVData = new KeyValues( "tf_weapondata" );
  150. pKVData->SetInt( "WeaponID", iWeapon );
  151. pKVData->SetInt( "ShotsFired", weaponStats.iShotsFired );
  152. pKVData->SetInt( "ShotsFiredCrit", weaponStats.iCritShotsFired );
  153. pKVData->SetInt( "ShotsHit", weaponStats.iHits );
  154. pKVData->SetInt( "DamageTotal", weaponStats.iTotalDamage );
  155. pKVData->SetInt( "HitsWithKnownDistance", weaponStats.iHitsWithKnownDistance );
  156. pKVData->SetInt( "DistanceTotal", weaponStats.iTotalDistance );
  157. pKV->AddSubKey( pKVData );
  158. }
  159. }
  160. //// save deaths
  161. //for ( int i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); i++ )
  162. //{
  163. // TF_Gamestats_LevelStats_t::PlayerDeathsLump_t &playerDeath = pCurrentMap->m_aPlayerDeaths[i];
  164. // pKVData = new KeyValues( "tf_deaths" );
  165. // pKVData->SetInt( "DeathIndex", i );
  166. // pKVData->SetInt( "X", playerDeath.nPosition[0] );
  167. // pKVData->SetInt( "Y", playerDeath.nPosition[1] );
  168. // pKVData->SetInt( "Z", playerDeath.nPosition[2] );
  169. // pKV->AddSubKey( pKVData );
  170. //}
  171. }
  172. // clear stats since we've now reported these
  173. m_reportedStats.Clear();
  174. return true;
  175. }
  176. extern CBaseGameStats_Driver CBGSDriver;
  177. //-----------------------------------------------------------------------------
  178. // Purpose:
  179. //-----------------------------------------------------------------------------
  180. bool CTFGameStats::Init( void )
  181. {
  182. ListenForGameEvent( "teamplay_round_start" );
  183. ListenForGameEvent( "tf_game_over" );
  184. ListenForGameEvent( "teamplay_game_over" );
  185. // CTF Gameplay Events
  186. ListenForGameEvent( "teamplay_flag_event" );
  187. // CP Gameplay Events
  188. //ListenForGameEvent( "teamplay_point_startcapture" );
  189. ListenForGameEvent( "teamplay_point_captured" );
  190. //ListenForGameEvent( "teamplay_capture_blocked" );
  191. // Player Event
  192. ListenForGameEvent( "player_disconnect" );
  193. return true;
  194. }
  195. //-----------------------------------------------------------------------------
  196. // Purpose:
  197. //-----------------------------------------------------------------------------
  198. void StripNewlineFromString( char *string )
  199. {
  200. int nStrlength = strlen( string ) - 1;
  201. if ( nStrlength >= 0 )
  202. {
  203. if ( string[nStrlength] == '\n' || string[nStrlength] == '\r' )
  204. string[nStrlength] = 0;
  205. }
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose:
  209. //-----------------------------------------------------------------------------
  210. void CTFGameStats::Event_LevelInit( void )
  211. {
  212. ClearCurrentGameData();
  213. // Get the host ip and port.
  214. int nIPAddr = 0;
  215. short nPort = 0;
  216. ConVar *hostip = cvar->FindVar( "hostip" );
  217. if ( hostip )
  218. {
  219. nIPAddr = hostip->GetInt();
  220. }
  221. ConVar *hostport = cvar->FindVar( "hostip" );
  222. if ( hostport )
  223. {
  224. nPort = hostport->GetInt();
  225. }
  226. m_reportedStats.m_pCurrentGame->Init( STRING( gpGlobals->mapname ), gpGlobals->mapversion, nIPAddr, nPort, gpGlobals->curtime );
  227. m_reportedStats.m_bValidData = false;
  228. TF_Gamestats_LevelStats_t *map = m_reportedStats.FindOrAddMapStats( STRING( gpGlobals->mapname ) );
  229. map->Init( STRING( gpGlobals->mapname ), gpGlobals->mapversion, nIPAddr, nPort, gpGlobals->curtime );
  230. m_currentRoundRed.m_iNumRounds = 0;
  231. m_currentMap.Init( STRING( gpGlobals->mapname ), gpGlobals->mapversion, nIPAddr, nPort, gpGlobals->curtime );
  232. if ( !g_pStringTableServerMapCycle )
  233. return;
  234. // Parse the server's mapcycle for playtime tracking (used in voting)
  235. if ( !m_MapsPlaytime.Count() )
  236. {
  237. int index = g_pStringTableServerMapCycle->FindStringIndex( "ServerMapCycle" );
  238. if ( index != ::INVALID_STRING_INDEX )
  239. {
  240. int nLength = 0;
  241. const char *pszMapCycle = (const char *)g_pStringTableServerMapCycle->GetStringUserData( index, &nLength );
  242. if ( pszMapCycle && pszMapCycle[0] )
  243. {
  244. if ( pszMapCycle && nLength )
  245. {
  246. CUtlVector< char * > vecMapCycle;
  247. V_SplitString( pszMapCycle, "\n", vecMapCycle );
  248. if ( vecMapCycle.Count() )
  249. {
  250. for ( int index = 0; index < vecMapCycle.Count(); index++ )
  251. {
  252. // Initialize the list with random playtimes - to vary the first vote options
  253. int nSeed = RandomInt( 1, 300 );
  254. StripNewlineFromString( vecMapCycle[index] );
  255. // Canonicalize map name and ensure we know of it
  256. char szMapName[ 64 ] = { 0 };
  257. V_strncpy( szMapName, vecMapCycle[index], sizeof( szMapName ) );
  258. IVEngineServer::eFindMapResult eResult = engine->FindMap( szMapName, sizeof( szMapName ) );
  259. switch ( eResult )
  260. {
  261. case IVEngineServer::eFindMap_Found:
  262. case IVEngineServer::eFindMap_NonCanonical:
  263. case IVEngineServer::eFindMap_FuzzyMatch:
  264. m_MapsPlaytime.Insert( CUtlConstString( szMapName ), nSeed );
  265. break;
  266. case IVEngineServer::eFindMap_NotFound:
  267. // Don't know the canonical map name for stats here :-/
  268. case IVEngineServer::eFindMap_PossiblyAvailable:
  269. default:
  270. break;
  271. }
  272. }
  273. }
  274. vecMapCycle.PurgeAndDeleteElements();
  275. }
  276. }
  277. }
  278. }
  279. }
  280. //-----------------------------------------------------------------------------
  281. // Purpose: Opens a new server session when a level is started.
  282. //-----------------------------------------------------------------------------
  283. void CTFGameStats::LevelInitPreEntity()
  284. {
  285. // Start the server session.
  286. GetSteamWorksSGameStatsUploader().StartSession();
  287. }
  288. //-----------------------------------------------------------------------------
  289. // Purpose: Closes the server session when the level is shutdown.
  290. //-----------------------------------------------------------------------------
  291. void CTFGameStats::LevelShutdownPreClearSteamAPIContext()
  292. {
  293. // Write server specific end session rows.
  294. SW_WriteHostsRow();
  295. // End the server session.
  296. GetSteamWorksSGameStatsUploader().EndSession();
  297. }
  298. //-----------------------------------------------------------------------------
  299. // Purpose:
  300. //-----------------------------------------------------------------------------
  301. void CTFGameStats::Event_LevelShutdown( float flElapsed )
  302. {
  303. if ( m_reportedStats.m_pCurrentGame )
  304. {
  305. flElapsed = gpGlobals->curtime - m_reportedStats.m_pCurrentGame->m_flRoundStartTime;
  306. m_reportedStats.m_pCurrentGame->m_Header.m_iTotalTime += (int) flElapsed;
  307. }
  308. // Store data for the vote system (for issues that present choices based on stats)
  309. AccumulateVoteData();
  310. // add current game data in to data for this level
  311. AccumulateGameData();
  312. if ( m_bRoundActive )
  313. {
  314. m_bRoundActive = false;
  315. m_iRoundsPlayed++;
  316. SW_GameStats_WriteRound( -1, false, m_bServerShutdown ? RE_SERVER_SHUTDOWN : RE_SERVER_MAP_CHANGE );
  317. for ( int iPlayerIndex=1; iPlayerIndex<=MAX_PLAYERS; iPlayerIndex++ )
  318. {
  319. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  320. if ( pPlayer )
  321. {
  322. SW_GameStats_WritePlayer( pPlayer );
  323. }
  324. }
  325. }
  326. SW_GameStats_WriteMap();
  327. if ( m_bServerShutdown )
  328. {
  329. StopListeningForAllEvents();
  330. }
  331. m_bServerShutdown = false;
  332. m_bRoundActive = false;
  333. m_iRoundsPlayed = 0;
  334. }
  335. //-----------------------------------------------------------------------------
  336. // Purpose: Resets all stats for this player
  337. //-----------------------------------------------------------------------------
  338. void CTFGameStats::ResetPlayerStats( CTFPlayer *pPlayer )
  339. {
  340. PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
  341. // reset the stats on this player
  342. stats.Reset();
  343. // reset the matrix of who killed whom with respect to this player
  344. ResetKillHistory( pPlayer );
  345. }
  346. //-----------------------------------------------------------------------------
  347. // Purpose: Resets the kill history for this player
  348. //-----------------------------------------------------------------------------
  349. void CTFGameStats::ResetKillHistory( CTFPlayer *pPlayer )
  350. {
  351. int iPlayerIndex = pPlayer->entindex();
  352. // for every other player, set all all the kills with respect to this player to 0
  353. for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
  354. {
  355. PlayerStats_t &statsOther = m_aPlayerStats[i];
  356. statsOther.statsKills.iNumKilled[iPlayerIndex] = 0;
  357. statsOther.statsKills.iNumKilledBy[iPlayerIndex] = 0;
  358. statsOther.statsKills.iNumKilledByUnanswered[iPlayerIndex] = 0;
  359. }
  360. }
  361. //-----------------------------------------------------------------------------
  362. // Purpose: Resets per-round stats for all players
  363. //-----------------------------------------------------------------------------
  364. void CTFGameStats::ResetRoundStats()
  365. {
  366. for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
  367. {
  368. m_aPlayerStats[i].statsCurrentRound.Reset();
  369. }
  370. m_currentRoundRed.Reset();
  371. m_currentRoundBlue.Reset();
  372. IGameEvent *event = gameeventmanager->CreateEvent( "stats_resetround" );
  373. if ( event )
  374. {
  375. gameeventmanager->FireEvent( event );
  376. }
  377. }
  378. //-----------------------------------------------------------------------------
  379. // Purpose: Increments specified stat for specified player by specified amount
  380. //-----------------------------------------------------------------------------
  381. void CTFGameStats::IncrementStat( CTFPlayer *pPlayer, TFStatType_t statType, int iValue )
  382. {
  383. if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() && TFGameRules()->State_Get() != GR_STATE_RND_RUNNING )
  384. return;
  385. PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
  386. stats.statsCurrentLife.m_iStat[statType] += iValue;
  387. stats.statsCurrentRound.m_iStat[statType] += iValue;
  388. stats.mapStatsCurrentLife.m_iStat[statType] += iValue;
  389. stats.mapStatsCurrentRound.m_iStat[statType] += iValue;
  390. stats.statsAccumulated.m_iStat[statType] += iValue;
  391. stats.mapStatsAccumulated.m_iStat[statType] += iValue;
  392. }
  393. //-----------------------------------------------------------------------------
  394. // Purpose:
  395. //-----------------------------------------------------------------------------
  396. void CTFGameStats::SendStatsToPlayer( CTFPlayer *pPlayer, bool bIsAlive )
  397. {
  398. PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
  399. // set the play time for the round
  400. stats.statsCurrentLife.m_iStat[TFSTAT_PLAYTIME] = (int) gpGlobals->curtime - pPlayer->GetSpawnTime();
  401. stats.statsCurrentLife.m_iStat[TFSTAT_POINTSSCORED] = TFGameRules()->CalcPlayerScore( &stats.statsCurrentLife, pPlayer );
  402. stats.statsCurrentLife.m_iStat[TFSTAT_MAXSENTRYKILLS] = pPlayer->GetMaxSentryKills();
  403. stats.mapStatsCurrentLife.m_iStat[TFMAPSTAT_PLAYTIME] = (int) gpGlobals->curtime - pPlayer->GetSpawnTime();
  404. // make a bit field of all the stats we want to send (all with non-zero values)
  405. int iStat;
  406. int iSendBits = 0;
  407. for ( iStat = TFSTAT_FIRST; iStat <= TFSTAT_LAST; iStat++ )
  408. {
  409. if ( stats.statsCurrentLife.m_iStat[iStat] > 0 )
  410. {
  411. iSendBits |= ( 1 << ( iStat - TFSTAT_FIRST ) );
  412. }
  413. }
  414. iStat = TFSTAT_FIRST;
  415. CSingleUserRecipientFilter filter( pPlayer );
  416. filter.MakeReliable();
  417. UserMessageBegin( filter, "PlayerStatsUpdate" );
  418. WRITE_BYTE( pPlayer->GetPlayerClass()->GetClassIndex() ); // write the class
  419. WRITE_BYTE( bIsAlive ); // write if the player is currently alive
  420. WRITE_LONG( iSendBits ); // write the bit mask of which stats follow in the message
  421. // write all the non-zero stats according to the bit mask
  422. while ( iSendBits > 0 )
  423. {
  424. if ( iSendBits & 1 )
  425. {
  426. WRITE_LONG( stats.statsCurrentLife.m_iStat[iStat] );
  427. }
  428. iSendBits >>= 1;
  429. iStat ++;
  430. }
  431. MessageEnd();
  432. for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ )
  433. {
  434. const MapDef_t *pMapDef = GetItemSchema()->GetMasterMapDefByIndex( i );
  435. if ( V_strcmp( pMapDef->pszMapName, gpGlobals->mapname.ToCStr() ) == 0 )
  436. {
  437. iStat = 0;
  438. iSendBits = 0;
  439. for ( iStat = TFMAPSTAT_FIRST; iStat <= TFMAPSTAT_LAST; iStat++ )
  440. {
  441. if ( stats.mapStatsCurrentLife.m_iStat[iStat] > 0 )
  442. {
  443. iSendBits |= ( 1 << ( iStat - TFMAPSTAT_FIRST ) );
  444. }
  445. }
  446. iStat = TFMAPSTAT_FIRST;
  447. UserMessageBegin( filter, "MapStatsUpdate" );
  448. WRITE_UBITLONG( pMapDef->GetStatsIdentifier(), 32 ); // write the mapid
  449. WRITE_LONG( iSendBits ); // write the bit mask of which stats follow in the message
  450. // write all the non-zero stats according to the bit mask
  451. while ( iSendBits > 0 )
  452. {
  453. if ( iSendBits & 1 )
  454. {
  455. WRITE_LONG( stats.mapStatsCurrentLife.m_iStat[iStat] );
  456. }
  457. iSendBits >>= 1;
  458. iStat ++;
  459. }
  460. MessageEnd();
  461. break;
  462. }
  463. }
  464. AccumulateAndResetPerLifeStats( pPlayer );
  465. }
  466. //-----------------------------------------------------------------------------
  467. // Purpose:
  468. //-----------------------------------------------------------------------------
  469. void CTFGameStats::AccumulateAndResetPerLifeStats( CTFPlayer *pPlayer )
  470. {
  471. int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
  472. PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
  473. // add score from previous life and reset current life stats
  474. int iScore = TFGameRules()->CalcPlayerScore( &stats.statsCurrentLife, pPlayer );
  475. if ( m_reportedStats.m_pCurrentGame != NULL )
  476. {
  477. m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iScore += iScore;
  478. }
  479. stats.statsCurrentRound.m_iStat[TFSTAT_POINTSSCORED] += iScore;
  480. stats.statsAccumulated.m_iStat[TFSTAT_POINTSSCORED] += iScore;
  481. stats.statsCurrentLife.Reset();
  482. }
  483. //-----------------------------------------------------------------------------
  484. // Purpose:
  485. //-----------------------------------------------------------------------------
  486. void CTFGameStats::Event_PlayerConnected( CBasePlayer *pPlayer )
  487. {
  488. ResetPlayerStats( ToTFPlayer( pPlayer ) );
  489. PlayerStats_t &stats = CTF_GameStats.m_aPlayerStats[pPlayer->entindex()];
  490. stats.iConnectTime = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  491. }
  492. //-----------------------------------------------------------------------------
  493. // Purpose:
  494. //-----------------------------------------------------------------------------
  495. void CTFGameStats::Event_PlayerDisconnectedTF( CTFPlayer *pTFPlayer )
  496. {
  497. if ( !pTFPlayer )
  498. return;
  499. PlayerStats_t &stats = CTF_GameStats.m_aPlayerStats[pTFPlayer->entindex()];
  500. stats.iDisconnectTime = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  501. SW_GameStats_WritePlayer( pTFPlayer, true );
  502. ResetPlayerStats( pTFPlayer );
  503. if ( pTFPlayer->IsAlive() )
  504. {
  505. int iClass = pTFPlayer->GetPlayerClass()->GetClassIndex();
  506. if ( m_reportedStats.m_pCurrentGame != NULL )
  507. {
  508. m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iTotalTime += (int) ( gpGlobals->curtime - pTFPlayer->GetSpawnTime() );
  509. }
  510. }
  511. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pTFPlayer->GetTeamNumber() );
  512. if ( round )
  513. {
  514. round->m_Summary.iTeamQuit++;
  515. }
  516. }
  517. //-----------------------------------------------------------------------------
  518. // Purpose:
  519. //-----------------------------------------------------------------------------
  520. void CTFGameStats::Event_PlayerChangedClass( CTFPlayer *pPlayer, int iOldClass, int iNewClass )
  521. {
  522. if ( iNewClass >= TF_FIRST_NORMAL_CLASS && iNewClass <= TF_LAST_NORMAL_CLASS )
  523. {
  524. if ( m_reportedStats.m_pCurrentGame )
  525. {
  526. m_reportedStats.m_pCurrentGame->m_aClassStats[iNewClass].iClassChanges += 1;
  527. }
  528. IncrementStat( pPlayer, TFSTAT_CLASSCHANGES, 1 );
  529. // Record this in steamworks stats also...
  530. SW_ClassChange( pPlayer, iOldClass, iNewClass );
  531. }
  532. }
  533. //-----------------------------------------------------------------------------
  534. // Purpose:
  535. //-----------------------------------------------------------------------------
  536. void CTFGameStats::Event_PlayerSpawned( CTFPlayer *pPlayer )
  537. {
  538. // if player is spawning as a member of valid team, increase the spawn count for his class
  539. int iTeam = pPlayer->GetTeamNumber();
  540. int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
  541. if ( TEAM_UNASSIGNED != iTeam && TEAM_SPECTATOR != iTeam )
  542. {
  543. if ( m_reportedStats.m_pCurrentGame != NULL )
  544. {
  545. m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iSpawns++;
  546. }
  547. }
  548. TF_Gamestats_LevelStats_t *map = m_reportedStats.m_pCurrentGame;
  549. if ( !map )
  550. return;
  551. // calculate peak player count on each team
  552. for ( iTeam = FIRST_GAME_TEAM; iTeam < TF_TEAM_COUNT; iTeam++ )
  553. {
  554. int iPlayerCount = GetGlobalTeam( iTeam )->GetNumPlayers();
  555. if ( iPlayerCount > map->m_iPeakPlayerCount[iTeam] )
  556. {
  557. map->m_iPeakPlayerCount[iTeam] = iPlayerCount;
  558. }
  559. }
  560. }
  561. //-----------------------------------------------------------------------------
  562. // Purpose:
  563. //-----------------------------------------------------------------------------
  564. void CTFGameStats::Event_PlayerForceRespawn( CTFPlayer *pPlayer )
  565. {
  566. if ( pPlayer->IsAlive() && !TFGameRules()->PrevRoundWasWaitingForPlayers() )
  567. {
  568. // send stats to player
  569. SendStatsToPlayer( pPlayer, true );
  570. // if player is alive before respawn, add time from this life to class stat
  571. int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
  572. if ( m_reportedStats.m_pCurrentGame != NULL )
  573. {
  574. m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iTotalTime += (int) ( gpGlobals->curtime - pPlayer->GetSpawnTime() );
  575. }
  576. }
  577. AccumulateAndResetPerLifeStats( pPlayer );
  578. }
  579. //-----------------------------------------------------------------------------
  580. // Purpose:
  581. //-----------------------------------------------------------------------------
  582. void CTFGameStats::Event_PlayerLeachedHealth( CTFPlayer *pPlayer, bool bDispenserHeal, float amount )
  583. {
  584. // make sure value is sane
  585. Assert( amount >= 0 );
  586. Assert( amount < 1000 );
  587. if ( !bDispenserHeal )
  588. {
  589. // If this was a heal by enemy medic and the first such heal that the server is aware of for this player,
  590. // send an achievement event to client. On the client, it will award achievement if player doesn't have it yet
  591. PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
  592. if ( 0 == stats.statsAccumulated.m_iStat[TFSTAT_HEALTHLEACHED] )
  593. {
  594. pPlayer->AwardAchievement( ACHIEVEMENT_TF_GET_HEALED_BYENEMY );
  595. }
  596. }
  597. IncrementStat( pPlayer, TFSTAT_HEALTHLEACHED, (int) amount );
  598. }
  599. //-----------------------------------------------------------------------------
  600. // Purpose:
  601. //-----------------------------------------------------------------------------
  602. TF_Gamestats_RoundStats_t* CTFGameStats::GetRoundStatsForTeam( int iTeamNumber )
  603. {
  604. if ( iTeamNumber == TF_TEAM_BLUE )
  605. return &m_currentRoundBlue;
  606. else if ( iTeamNumber == TF_TEAM_RED )
  607. return &m_currentRoundRed;
  608. else
  609. return NULL;
  610. }
  611. //-----------------------------------------------------------------------------
  612. // Purpose:
  613. //-----------------------------------------------------------------------------
  614. void CTFGameStats::Event_PlayerHealedOther( CTFPlayer *pPlayer, float amount )
  615. {
  616. // make sure value is sane
  617. int iAmount = (int) amount;
  618. Assert( iAmount >= 0 );
  619. Assert( iAmount <= 1000 );
  620. if ( iAmount < 0 || iAmount > 1000 )
  621. {
  622. DevMsg( "CTFGameStats: bogus healing value of %d reported, ignoring\n", iAmount );
  623. return;
  624. }
  625. IncrementStat( pPlayer, TFSTAT_HEALING, (int) amount );
  626. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pPlayer->GetTeamNumber() );
  627. if ( round )
  628. {
  629. round->m_Summary.iHealingDone += amount;
  630. }
  631. }
  632. //-----------------------------------------------------------------------------
  633. // Purpose: How much health effects like mad milk generate - awarded to the provider
  634. //-----------------------------------------------------------------------------
  635. void CTFGameStats::Event_PlayerHealedOtherAssist( CTFPlayer *pPlayer, float amount )
  636. {
  637. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  638. if ( pPlayer && pMatch )
  639. {
  640. // Anti-farming in official matchmaking modes
  641. if ( gpGlobals->curtime - pPlayer->GetLastDamageReceivedTime() > 90.f ||
  642. gpGlobals->curtime - pPlayer->GetLastEntityDamagedTime() > 90.f )
  643. {
  644. return;
  645. }
  646. }
  647. Assert ( pPlayer );
  648. // make sure value is sane
  649. int iAmount = (int) amount;
  650. Assert( iAmount >= 0 );
  651. Assert( iAmount <= 1000 );
  652. if ( iAmount < 0 || iAmount > 1000 )
  653. {
  654. DevMsg( "CTFGameStats: bogus healing value of %d reported, ignoring\n", iAmount );
  655. return;
  656. }
  657. IncrementStat( pPlayer, TFSTAT_HEALING_ASSIST, (int) amount );
  658. }
  659. //-----------------------------------------------------------------------------
  660. // Purpose: Raw damage blocked due to effects like invuln, projectile shields, etc
  661. //-----------------------------------------------------------------------------
  662. void CTFGameStats::Event_PlayerBlockedDamage( CTFPlayer *pPlayer, int nAmount )
  663. {
  664. Assert( pPlayer && nAmount > 0 && nAmount < 3000 );
  665. if ( nAmount < 0 || nAmount > 3000 )
  666. {
  667. DevMsg( "CTFGameStats: bogus blocked damage value of %d reported, ignoring\n", nAmount );
  668. return;
  669. }
  670. IncrementStat( pPlayer, TFSTAT_DAMAGE_BLOCKED, nAmount );
  671. }
  672. //-----------------------------------------------------------------------------
  673. // Purpose:
  674. //-----------------------------------------------------------------------------
  675. void CTFGameStats::Event_AssistKill( CTFPlayer *pAttacker, CBaseEntity *pVictim )
  676. {
  677. // increment player's stat
  678. IncrementStat( pAttacker, TFSTAT_KILLASSISTS, 1 );
  679. // increment reported class stats
  680. int iClass = pAttacker->GetPlayerClass()->GetClassIndex();
  681. if ( m_reportedStats.m_pCurrentGame != NULL )
  682. {
  683. m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iAssists++;
  684. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pAttacker->GetTeamNumber() );
  685. if ( round )
  686. {
  687. round->m_Summary.iAssists++;
  688. }
  689. }
  690. if ( pVictim->IsPlayer() )
  691. {
  692. // keep track of how many times every player kills every other player
  693. CTFPlayer *pPlayerVictim = ToTFPlayer( pVictim );
  694. TrackKillStats( pAttacker, pPlayerVictim );
  695. }
  696. }
  697. //-----------------------------------------------------------------------------
  698. // Purpose:
  699. //-----------------------------------------------------------------------------
  700. void CTFGameStats::Event_PlayerInvulnerable( CTFPlayer *pPlayer )
  701. {
  702. IncrementStat( pPlayer, TFSTAT_INVULNS, 1 );
  703. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pPlayer->GetTeamNumber() );
  704. if ( round )
  705. {
  706. round->m_Summary.iInvulns++;
  707. }
  708. }
  709. //-----------------------------------------------------------------------------
  710. // Purpose:
  711. //-----------------------------------------------------------------------------
  712. void CTFGameStats::Event_PlayerCreatedBuilding( CTFPlayer *pPlayer, CBaseObject *pBuilding )
  713. {
  714. // sappers are buildings from the code's point of view but not from the player's, don't count them
  715. CObjectSapper *pSapper = dynamic_cast<CObjectSapper *>( pBuilding );
  716. if ( pSapper )
  717. return;
  718. IncrementStat( pPlayer, TFSTAT_BUILDINGSBUILT, 1 );
  719. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pPlayer->GetTeamNumber() );
  720. if ( round )
  721. {
  722. round->m_Summary.iBuildingsBuilt++;
  723. }
  724. }
  725. //-----------------------------------------------------------------------------
  726. // Purpose:
  727. //-----------------------------------------------------------------------------
  728. void CTFGameStats::Event_PlayerDestroyedBuilding( CTFPlayer *pPlayer, CBaseObject *pBuilding )
  729. {
  730. // sappers are buildings from the code's point of view but not from the player's, don't count them
  731. CObjectSapper *pSapper = dynamic_cast<CObjectSapper *>( pBuilding );
  732. if ( pSapper )
  733. return;
  734. IncrementStat( pPlayer, TFSTAT_BUILDINGSDESTROYED, 1 );
  735. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pPlayer->GetTeamNumber() );
  736. if ( round )
  737. {
  738. round->m_Summary.iBuildingsDestroyed++;
  739. }
  740. }
  741. //-----------------------------------------------------------------------------
  742. // Purpose:
  743. //-----------------------------------------------------------------------------
  744. void CTFGameStats::Event_AssistDestroyBuilding( CTFPlayer *pPlayer, CBaseObject *pBuilding )
  745. {
  746. // sappers are buildings from the code's point of view but not from the player's, don't count them
  747. CObjectSapper *pSapper = dynamic_cast<CObjectSapper *>( pBuilding );
  748. if ( pSapper )
  749. return;
  750. IncrementStat( pPlayer, TFSTAT_KILLASSISTS, 1 );
  751. }
  752. //-----------------------------------------------------------------------------
  753. // Purpose:
  754. //-----------------------------------------------------------------------------
  755. void CTFGameStats::Event_Headshot( CTFPlayer *pKiller, bool bBowShot )
  756. {
  757. IncrementStat( pKiller, TFSTAT_HEADSHOTS, 1 );
  758. if ( bBowShot ) // Extra points for a bow shot.
  759. {
  760. IncrementStat( pKiller, TFSTAT_BONUS_POINTS, 5 ); // Extra point.
  761. }
  762. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pKiller->GetTeamNumber() );
  763. if ( round )
  764. {
  765. round->m_Summary.iHeadshots++;
  766. }
  767. }
  768. //-----------------------------------------------------------------------------
  769. // Purpose:
  770. //-----------------------------------------------------------------------------
  771. void CTFGameStats::Event_Backstab( CTFPlayer *pKiller )
  772. {
  773. IncrementStat( pKiller, TFSTAT_BACKSTABS, 1 );
  774. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pKiller->GetTeamNumber() );
  775. if ( round )
  776. {
  777. round->m_Summary.iBackstabs++;
  778. }
  779. }
  780. //-----------------------------------------------------------------------------
  781. // Purpose:
  782. //-----------------------------------------------------------------------------
  783. void CTFGameStats::Event_PlayerStunBall( CTFPlayer *pAttacker, bool bSpecial )
  784. {
  785. if ( bSpecial )
  786. {
  787. IncrementStat( pAttacker, TFSTAT_BONUS_POINTS, 20 );
  788. }
  789. else
  790. {
  791. IncrementStat( pAttacker, TFSTAT_BONUS_POINTS, 10 );
  792. }
  793. }
  794. //-----------------------------------------------------------------------------
  795. // Purpose:
  796. //-----------------------------------------------------------------------------
  797. void CTFGameStats::Event_PlayerAwardBonusPoints( CTFPlayer *pPlayer, CBaseEntity *pSource, int nCount )
  798. {
  799. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  800. if ( pPlayer && pMatch )
  801. {
  802. // Anti-farming in official matchmaking modes
  803. if ( gpGlobals->curtime - pPlayer->GetLastDamageReceivedTime() > 90.f ||
  804. gpGlobals->curtime - pPlayer->GetLastEntityDamagedTime() > 90.f )
  805. {
  806. return;
  807. }
  808. }
  809. IncrementStat( pPlayer, TFSTAT_BONUS_POINTS, nCount );
  810. // This event ends up drawing a combattext number
  811. if ( pSource )
  812. {
  813. if ( nCount >= 10 )
  814. {
  815. CSingleUserRecipientFilter filter( pPlayer );
  816. filter.MakeReliable();
  817. UserMessageBegin( filter, "PlayerBonusPoints" );
  818. WRITE_BYTE( nCount );
  819. WRITE_BYTE( pPlayer->entindex() );
  820. WRITE_BYTE( pSource->entindex() );
  821. MessageEnd();
  822. }
  823. }
  824. }
  825. //-----------------------------------------------------------------------------
  826. // Purpose:
  827. //-----------------------------------------------------------------------------
  828. void CTFGameStats::Event_PlayerHealthkitPickup( CTFPlayer *pTFPlayer )
  829. {
  830. IncrementStat( pTFPlayer, TFSTAT_HEALTHKITS, 1 );
  831. }
  832. //-----------------------------------------------------------------------------
  833. // Purpose:
  834. //-----------------------------------------------------------------------------
  835. void CTFGameStats::Event_PlayerAmmokitPickup( CTFPlayer *pTFPlayer )
  836. {
  837. IncrementStat( pTFPlayer, TFSTAT_AMMOKITS, 1 );
  838. }
  839. //-----------------------------------------------------------------------------
  840. // Purpose:
  841. //-----------------------------------------------------------------------------
  842. void CTFGameStats::Event_PlayerUsedTeleport( CTFPlayer *pTeleportOwner, CTFPlayer *pTeleportingPlayer )
  843. {
  844. // We don't count the builder's teleports
  845. if ( pTeleportOwner && pTeleportOwner != pTeleportingPlayer )
  846. {
  847. IncrementStat( pTeleportOwner, TFSTAT_TELEPORTS, 1 );
  848. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pTeleportOwner->GetTeamNumber() );
  849. if ( round )
  850. {
  851. round->m_Summary.iTeleports++;
  852. }
  853. }
  854. }
  855. //-----------------------------------------------------------------------------
  856. // Purpose:
  857. //-----------------------------------------------------------------------------
  858. void CTFGameStats::Event_PlayerFiredWeapon( CTFPlayer *pPlayer, bool bCritical )
  859. {
  860. // If normal gameplay state, track weapon stats.
  861. if ( TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
  862. {
  863. CTFWeaponBase *pTFWeapon = pPlayer->GetActiveTFWeapon();
  864. if ( pTFWeapon )
  865. {
  866. // record shots fired in reported per-weapon stats
  867. int iWeaponID = pTFWeapon->GetWeaponID();
  868. if ( m_reportedStats.m_pCurrentGame != NULL )
  869. {
  870. TF_Gamestats_WeaponStats_t *pWeaponStats = &m_reportedStats.m_pCurrentGame->m_aWeaponStats[iWeaponID];
  871. pWeaponStats->iShotsFired++;
  872. if ( bCritical )
  873. {
  874. pWeaponStats->iCritShotsFired++;
  875. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pPlayer->GetTeamNumber() );
  876. if ( round )
  877. {
  878. round->m_Summary.iCrits++;
  879. }
  880. IncrementStat( pPlayer, TFSTAT_CRITS, 1 );
  881. }
  882. }
  883. // need a better place to do this
  884. pPlayer->OnMyWeaponFired( pTFWeapon );
  885. // inform the bots
  886. TheNextBots().OnWeaponFired( pPlayer, pTFWeapon );
  887. }
  888. }
  889. IncrementStat( pPlayer, TFSTAT_SHOTS_FIRED, 1 );
  890. }
  891. //-----------------------------------------------------------------------------
  892. // Purpose:
  893. //-----------------------------------------------------------------------------
  894. void CTFGameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info, int iDamageTaken )
  895. {
  896. // defensive guard against insanely huge damage values that apparently get into the stats system once in a while -- ignore insane values
  897. const int INSANE_PLAYER_DAMAGE = TFGameRules()->IsMannVsMachineMode() ? 5000 : 1500;
  898. Assert( iDamageTaken >= 0 );
  899. if ( ( iDamageTaken < 0 ) || ( iDamageTaken > INSANE_PLAYER_DAMAGE ) )
  900. return;
  901. CObjectSentrygun *pSentry = NULL;
  902. CTFPlayer *pTarget = ToTFPlayer( pBasePlayer );
  903. CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() );
  904. if ( !pAttacker )
  905. {
  906. pSentry = dynamic_cast< CObjectSentrygun * >( info.GetAttacker() );
  907. if ( !pSentry )
  908. return;
  909. pAttacker = pSentry->GetOwner();
  910. }
  911. // don't count damage to yourself
  912. if ( pTarget == pAttacker )
  913. return;
  914. if ( pAttacker )
  915. {
  916. IncrementStat( pAttacker, TFSTAT_DAMAGE, iDamageTaken );
  917. if ( info.GetDamageType() & (DMG_BURN | DMG_IGNITE) )
  918. {
  919. IncrementStat( pAttacker, TFSTAT_FIREDAMAGE, iDamageTaken );
  920. }
  921. if ( info.GetDamageType() & DMG_BLAST )
  922. {
  923. IncrementStat( pAttacker, TFSTAT_BLASTDAMAGE, iDamageTaken );
  924. }
  925. // Ranged stats
  926. if ( !( info.GetDamageType() & DMG_MELEE ) )
  927. {
  928. IncrementStat( pAttacker, TFSTAT_DAMAGE_RANGED, iDamageTaken );
  929. if ( info.GetDamageType() & DMG_CRITICAL )
  930. {
  931. if ( pAttacker->m_Shared.IsCritBoosted() )
  932. {
  933. IncrementStat( pAttacker, TFSTAT_DAMAGE_RANGED_CRIT_BOOSTED, iDamageTaken );
  934. }
  935. else
  936. {
  937. IncrementStat( pAttacker, TFSTAT_DAMAGE_RANGED_CRIT_RANDOM, iDamageTaken );
  938. }
  939. }
  940. }
  941. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pAttacker->GetTeamNumber() );
  942. if ( round )
  943. {
  944. round->m_Summary.iDamageDone += iDamageTaken;
  945. }
  946. //Report MvM damage to bots
  947. if ( TFGameRules()->IsMannVsMachineMode() )
  948. {
  949. CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance();
  950. if ( pStats )
  951. {
  952. if ( pTarget && pTarget->IsBot() )
  953. {
  954. if ( pTarget->IsMiniBoss() )
  955. {
  956. pStats->PlayerEvent_DealtDamageToGiants( pAttacker, iDamageTaken );
  957. }
  958. else
  959. {
  960. pStats->PlayerEvent_DealtDamageToBots( pAttacker, iDamageTaken );
  961. }
  962. }
  963. }
  964. }
  965. }
  966. if ( pTarget )
  967. {
  968. IncrementStat( pTarget, TFSTAT_DAMAGETAKEN, iDamageTaken );
  969. }
  970. TF_Gamestats_LevelStats_t::PlayerDamageLump_t damage;
  971. Vector killerOrg(0, 0, 0);
  972. // set the location where the target was hit
  973. const Vector &org = pTarget->GetAbsOrigin();
  974. damage.nTargetPosition[ 0 ] = static_cast<int>( org.x );
  975. damage.nTargetPosition[ 1 ] = static_cast<int>( org.y );
  976. damage.nTargetPosition[ 2 ] = static_cast<int>( org.z );
  977. // set the class of the attacker
  978. CBaseEntity *pInflictor = info.GetInflictor();
  979. CBasePlayer *pScorer = TFGameRules()->GetDeathScorer( pAttacker, pInflictor, pTarget );
  980. if ( !pSentry )
  981. {
  982. pSentry = dynamic_cast< CObjectSentrygun * >( pInflictor );
  983. }
  984. if ( pSentry != NULL )
  985. {
  986. killerOrg = pSentry->GetAbsOrigin();
  987. damage.iAttackClass = TF_CLASS_ENGINEER;
  988. damage.iWeapon = ( info.GetDamageType() & DMG_BLAST ) ? TF_WEAPON_SENTRY_ROCKET : TF_WEAPON_SENTRY_BULLET;
  989. }
  990. else if ( dynamic_cast<CObjectDispenser *>( pInflictor ) )
  991. {
  992. damage.iAttackClass = TF_CLASS_ENGINEER;
  993. damage.iWeapon = TF_WEAPON_DISPENSER;
  994. }
  995. else
  996. {
  997. CTFPlayer *pTFAttacker = ToTFPlayer( pScorer );
  998. if ( pTFAttacker )
  999. {
  1000. CTFPlayerClass *pAttackerClass = pTFAttacker->GetPlayerClass();
  1001. damage.iAttackClass = ( !pAttackerClass ) ? TF_CLASS_UNDEFINED : pAttackerClass->GetClassIndex();
  1002. killerOrg = pTFAttacker->GetAbsOrigin();
  1003. }
  1004. else
  1005. {
  1006. damage.iAttackClass = TF_CLASS_UNDEFINED;
  1007. killerOrg = org;
  1008. }
  1009. // find the weapon the killer used
  1010. damage.iWeapon = GetWeaponFromDamage( info );
  1011. }
  1012. // If normal gameplay state, track weapon stats.
  1013. if ( ( TFGameRules()->State_Get() == GR_STATE_RND_RUNNING ) && ( damage.iWeapon != TF_WEAPON_NONE ) )
  1014. {
  1015. // record hits & damage in reported per-weapon stats
  1016. if ( m_reportedStats.m_pCurrentGame != NULL )
  1017. {
  1018. TF_Gamestats_WeaponStats_t *pWeaponStats = &m_reportedStats.m_pCurrentGame->m_aWeaponStats[damage.iWeapon];
  1019. pWeaponStats->iHits++;
  1020. pWeaponStats->iTotalDamage += iDamageTaken;
  1021. // Try and figure out where the damage is coming from
  1022. Vector vecDamageOrigin = info.GetReportedPosition();
  1023. // If we didn't get an origin to use, try using the attacker's origin
  1024. if ( vecDamageOrigin == vec3_origin )
  1025. {
  1026. if ( pSentry )
  1027. {
  1028. vecDamageOrigin = pSentry->GetAbsOrigin();
  1029. }
  1030. else
  1031. {
  1032. vecDamageOrigin = killerOrg;
  1033. }
  1034. }
  1035. if ( vecDamageOrigin != vec3_origin )
  1036. {
  1037. pWeaponStats->iHitsWithKnownDistance++;
  1038. int iDistance = (int) vecDamageOrigin.DistTo( pBasePlayer->GetAbsOrigin() );
  1039. // Msg( "Damage distance: %d\n", iDistance );
  1040. pWeaponStats->iTotalDistance += iDistance;
  1041. }
  1042. }
  1043. }
  1044. Assert( damage.iAttackClass != TF_CLASS_UNDEFINED );
  1045. // record the time the damage occurred
  1046. damage.fTime = gpGlobals->curtime;
  1047. // store the attacker's position
  1048. damage.nAttackerPosition[ 0 ] = static_cast<int>( killerOrg.x );
  1049. damage.nAttackerPosition[ 1 ] = static_cast<int>( killerOrg.y );
  1050. damage.nAttackerPosition[ 2 ] = static_cast<int>( killerOrg.z );
  1051. // set the class of the target
  1052. CTFPlayer *pTFPlayer = ToTFPlayer( pTarget );
  1053. CTFPlayerClass *pTargetClass = ( pTFPlayer ) ? pTFPlayer->GetPlayerClass() : NULL;
  1054. damage.iTargetClass = ( !pTargetClass ) ? TF_CLASS_UNDEFINED : pTargetClass->GetClassIndex();
  1055. Assert( damage.iTargetClass != TF_CLASS_UNDEFINED );
  1056. // record the damage done
  1057. damage.iDamage = info.GetDamage();
  1058. // record if it was a crit
  1059. damage.iCrit = ( ( info.GetDamageType() & DMG_CRITICAL ) != 0 );
  1060. // record if it was a kill
  1061. damage.iKill = ( pTarget->GetHealth() <= 0 );
  1062. // add it to the list of damages
  1063. if ( m_reportedStats.m_pCurrentGame != NULL )
  1064. {
  1065. //m_reportedStats.m_pCurrentGame->m_aPlayerDamage.AddToTail( damage );
  1066. m_reportedStats.m_pCurrentGame->m_bIsRealServer = true;
  1067. }
  1068. }
  1069. //-----------------------------------------------------------------------------
  1070. // Purpose: How much damage effects like jarate add - awarded to the provider
  1071. //-----------------------------------------------------------------------------
  1072. void CTFGameStats::Event_PlayerDamageAssist( CBasePlayer *pProvider, int iBonusDamage )
  1073. {
  1074. Assert( pProvider );
  1075. const int INSANE_PLAYER_DAMAGE = 5000;
  1076. Assert( iBonusDamage >= 0 );
  1077. Assert( iBonusDamage <= INSANE_PLAYER_DAMAGE );
  1078. if ( iBonusDamage < 0 || iBonusDamage > INSANE_PLAYER_DAMAGE )
  1079. return;
  1080. CTFPlayer *pTFProvider = ToTFPlayer( pProvider );
  1081. if ( pTFProvider )
  1082. {
  1083. IncrementStat( pTFProvider, TFSTAT_DAMAGE_ASSIST, iBonusDamage );
  1084. }
  1085. }
  1086. //-----------------------------------------------------------------------------
  1087. // Purpose: Damage done to all boss types
  1088. //-----------------------------------------------------------------------------
  1089. void CTFGameStats::Event_BossDamage( CBasePlayer *pAttacker, int iDamage )
  1090. {
  1091. const int INSANE_DAMAGE = 5000;
  1092. Assert( iDamage >= 0 );
  1093. Assert( iDamage <= INSANE_DAMAGE );
  1094. if ( iDamage < 0 || iDamage > INSANE_DAMAGE )
  1095. return;
  1096. CTFPlayer *pTFAttacker = ToTFPlayer( pAttacker );
  1097. if ( pTFAttacker )
  1098. {
  1099. IncrementStat( pTFAttacker, TFSTAT_DAMAGE_BOSS, iDamage );
  1100. }
  1101. }
  1102. //-----------------------------------------------------------------------------
  1103. // Purpose:
  1104. //-----------------------------------------------------------------------------
  1105. void CTFGameStats::Event_PlayerSuicide( CBasePlayer *pPlayer )
  1106. {
  1107. CTFPlayer *pTFPlayer = dynamic_cast<CTFPlayer*>( pPlayer );
  1108. if ( pTFPlayer )
  1109. {
  1110. IncrementStat( pTFPlayer, TFSTAT_SUICIDES, 1 );
  1111. }
  1112. }
  1113. //-----------------------------------------------------------------------------
  1114. // Purpose:
  1115. //-----------------------------------------------------------------------------
  1116. void CTFGameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
  1117. {
  1118. // This also gets called when the victim is a building. That gets tracked separately as building destruction, don't count it here
  1119. if ( !pVictim->IsPlayer() )
  1120. return;
  1121. CTFPlayer *pPlayerAttacker = static_cast< CTFPlayer * >( pAttacker );
  1122. IncrementStat( pPlayerAttacker, TFSTAT_KILLS, 1 );
  1123. // keep track of how many times every player kills every other player
  1124. CTFPlayer *pPlayerVictim = ToTFPlayer( pVictim );
  1125. TrackKillStats( pAttacker, pPlayerVictim );
  1126. int iClass = pPlayerAttacker->GetPlayerClass()->GetClassIndex();
  1127. if ( m_reportedStats.m_pCurrentGame != NULL )
  1128. {
  1129. m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iKills++;
  1130. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pAttacker->GetTeamNumber() );
  1131. if ( round )
  1132. {
  1133. round->m_Summary.iKills++;
  1134. }
  1135. }
  1136. // Throwable Kill tracking
  1137. if ( info.GetDamageCustom() == TF_DMG_CUSTOM_THROWABLE )
  1138. {
  1139. CTFPlayer *pTFAttacker = ToTFPlayer( pAttacker );
  1140. if ( pTFAttacker )
  1141. {
  1142. Event_PlayerThrowableKill( pTFAttacker );
  1143. }
  1144. }
  1145. // Scouts get additional points for killing medics that were actively healing.
  1146. if ( (iClass == TF_CLASS_SCOUT) && pPlayerVictim && (pPlayerVictim->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC) )
  1147. {
  1148. // Determine if the medic was using their (now holstered) heal gun.
  1149. CWeaponMedigun *pMedigun = (CWeaponMedigun *) pPlayerVictim->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN );
  1150. if ( pMedigun && pMedigun->m_bWasHealingBeforeDeath )
  1151. {
  1152. IncrementStat( pPlayerAttacker, TFSTAT_BONUS_POINTS, 10 );
  1153. }
  1154. }
  1155. // Players get points for killing a Rune carrier
  1156. if ( pPlayerVictim->m_Shared.IsCarryingRune() )
  1157. {
  1158. IncrementStat( pPlayerAttacker, TFSTAT_KILLS_RUNECARRIER, 1 );
  1159. }
  1160. }
  1161. void CTFGameStats::Event_KillDetail( CTFPlayer* pKiller, CTFPlayer* pVictim, CTFPlayer* pAssister, IGameEvent* event /*player_death*/, const CTakeDamageInfo &info )
  1162. {
  1163. SW_GameStats_WriteKill( pKiller, pVictim, pAssister, event, info );
  1164. }
  1165. //-----------------------------------------------------------------------------
  1166. // Purpose:
  1167. //-----------------------------------------------------------------------------
  1168. void CTFGameStats::Event_RoundStart()
  1169. {
  1170. m_iGameEndReason = 0;
  1171. m_currentRoundRed.Reset();
  1172. m_currentRoundBlue.Reset();
  1173. m_bRoundActive = true;
  1174. m_iKillCount = 0;
  1175. m_iPlayerUpdates = 0;
  1176. m_currentRoundRed.m_iRoundStartTime = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch();
  1177. m_rdStats.Clear();
  1178. m_passtimeStats.Clear();
  1179. }
  1180. //-----------------------------------------------------------------------------
  1181. // Purpose:
  1182. //-----------------------------------------------------------------------------
  1183. void CTFGameStats::Event_RoundEnd( int iWinningTeam, bool bFullRound, float flRoundTime, bool bWasSuddenDeathWin )
  1184. {
  1185. TF_Gamestats_LevelStats_t *map = m_reportedStats.m_pCurrentGame;
  1186. Assert( map );
  1187. if ( !map )
  1188. return;
  1189. m_reportedStats.m_pCurrentGame->m_Header.m_iTotalTime += (int) flRoundTime;
  1190. m_reportedStats.m_pCurrentGame->m_flRoundStartTime = gpGlobals->curtime;
  1191. // if ( !bFullRound )
  1192. // return;
  1193. map->m_Header.m_iRoundsPlayed++;
  1194. switch ( iWinningTeam )
  1195. {
  1196. case TF_TEAM_RED:
  1197. map->m_Header.m_iRedWins++;
  1198. if ( bWasSuddenDeathWin )
  1199. {
  1200. map->m_Header.m_iRedSuddenDeathWins++;
  1201. }
  1202. break;
  1203. case TF_TEAM_BLUE:
  1204. map->m_Header.m_iBlueWins++;
  1205. if ( bWasSuddenDeathWin )
  1206. {
  1207. map->m_Header.m_iBlueSuddenDeathWins++;
  1208. }
  1209. break;
  1210. case TEAM_UNASSIGNED:
  1211. map->m_Header.m_iStalemates++;
  1212. break;
  1213. default:
  1214. Assert( false );
  1215. break;
  1216. }
  1217. // Determine which control point was last captured
  1218. if ( TFGameRules() && ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP || TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT || TFGameRules()->GetGameType() == TF_GAMETYPE_ARENA ) )
  1219. {
  1220. int iLastCP = TFGameRules()->GetLastCapPointChanged();
  1221. if ( iLastCP >= 0 && iLastCP <= MAX_CONTROL_POINTS )
  1222. {
  1223. map->m_Header.m_iLastCapChangedInRound[iLastCP]++;
  1224. }
  1225. }
  1226. // add current game data in to data for this level
  1227. AccumulateGameData();
  1228. m_bRoundActive = false;
  1229. m_iRoundsPlayed++;
  1230. SW_GameStats_WriteRound( iWinningTeam, bFullRound, RE_ROUND_END );
  1231. for ( int iPlayerIndex=1; iPlayerIndex<=MAX_PLAYERS; iPlayerIndex++ )
  1232. {
  1233. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  1234. if ( pPlayer )
  1235. {
  1236. SW_GameStats_WritePlayer( pPlayer );
  1237. }
  1238. }
  1239. SW_PasstimeRoundEnded();
  1240. }
  1241. //-----------------------------------------------------------------------------
  1242. // Purpose:
  1243. //-----------------------------------------------------------------------------
  1244. void CTFGameStats::Event_GameEnd()
  1245. {
  1246. // Send a stats update to all players who are still alive. (Dead one have already
  1247. // received theirs when they died.)
  1248. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  1249. {
  1250. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  1251. if ( pPlayer && pPlayer->IsConnected() && pPlayer->IsAlive() )
  1252. {
  1253. SendStatsToPlayer( pPlayer, true );
  1254. }
  1255. }
  1256. }
  1257. //-----------------------------------------------------------------------------
  1258. // Purpose:
  1259. //-----------------------------------------------------------------------------
  1260. void CTFGameStats::Event_PlayerCapturedPoint( CTFPlayer *pPlayer )
  1261. {
  1262. // increment player stats
  1263. IncrementStat( pPlayer, TFSTAT_CAPTURES, 1 );
  1264. // increment reported stats
  1265. int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
  1266. if ( m_reportedStats.m_pCurrentGame != NULL )
  1267. {
  1268. m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iCaptures++;
  1269. }
  1270. }
  1271. //-----------------------------------------------------------------------------
  1272. // Purpose:
  1273. //-----------------------------------------------------------------------------
  1274. void CTFGameStats::Event_PlayerReturnedFlag( CTFPlayer *pPlayer )
  1275. {
  1276. // increment player stats
  1277. IncrementStat( pPlayer, TFSTAT_FLAGRETURNS, 1 );
  1278. }
  1279. //-----------------------------------------------------------------------------
  1280. // Purpose:
  1281. //-----------------------------------------------------------------------------
  1282. void CTFGameStats::Event_PlayerScoresEscortPoints( CTFPlayer *pPlayer, int iPoints )
  1283. {
  1284. // increment player stats
  1285. IncrementStat( pPlayer, TFSTAT_CAPTURES, iPoints );
  1286. SW_GameEvent( pPlayer, "escort_scored", iPoints );
  1287. // increment reported stats
  1288. //int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
  1289. //if ( m_reportedStats.m_pCurrentGame != NULL )
  1290. //{
  1291. // m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iCaptures++;
  1292. //}
  1293. }
  1294. //-----------------------------------------------------------------------------
  1295. // Purpose:
  1296. //-----------------------------------------------------------------------------
  1297. void CTFGameStats::Event_PlayerDefendedPoint( CTFPlayer *pPlayer )
  1298. {
  1299. IncrementStat( pPlayer, TFSTAT_DEFENSES, 1 );
  1300. ConVarRef tf_gamemode_cp( "tf_gamemode_cp" );
  1301. ConVarRef tf_gamemode_payload( "tf_gamemode_payload" );
  1302. if ( tf_gamemode_cp.GetInt() == 1 )
  1303. {
  1304. SW_GameEvent( pPlayer, "point_blocked", 1 );
  1305. }
  1306. else if ( tf_gamemode_payload.GetInt() == 1 )
  1307. {
  1308. SW_GameEvent( pPlayer, "escort_blocked", 1 );
  1309. }
  1310. }
  1311. //-----------------------------------------------------------------------------
  1312. // Purpose:
  1313. //-----------------------------------------------------------------------------
  1314. void CTFGameStats::Event_PlayerDominatedOther( CTFPlayer *pAttacker )
  1315. {
  1316. IncrementStat( pAttacker, TFSTAT_DOMINATIONS, 1 );
  1317. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pAttacker->GetTeamNumber() );
  1318. if ( round )
  1319. {
  1320. round->m_Summary.iDominations++;
  1321. }
  1322. }
  1323. //-----------------------------------------------------------------------------
  1324. // Purpose:
  1325. //-----------------------------------------------------------------------------
  1326. void CTFGameStats::Event_PlayerRevenge( CTFPlayer *pAttacker )
  1327. {
  1328. IncrementStat( pAttacker, TFSTAT_REVENGE, 1 );
  1329. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pAttacker->GetTeamNumber() );
  1330. if ( round )
  1331. {
  1332. round->m_Summary.iRevenges++;
  1333. }
  1334. }
  1335. //-----------------------------------------------------------------------------
  1336. // Purpose:
  1337. //-----------------------------------------------------------------------------
  1338. void CTFGameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
  1339. {
  1340. Assert( pPlayer );
  1341. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  1342. IncrementStat( pTFPlayer, TFSTAT_DEATHS, 1 );
  1343. SendStatsToPlayer( pTFPlayer, false );
  1344. // TF_Gamestats_LevelStats_t::PlayerDeathsLump_t death;
  1345. Vector killerOrg;
  1346. // set the location where the target died
  1347. const Vector &org = pPlayer->GetAbsOrigin();
  1348. // death.nPosition[ 0 ] = static_cast<int>( org.x );
  1349. // death.nPosition[ 1 ] = static_cast<int>( org.y );
  1350. // death.nPosition[ 2 ] = static_cast<int>( org.z );
  1351. // set the class of the attacker
  1352. CBaseEntity *pInflictor = info.GetInflictor();
  1353. CBaseEntity *pKiller = info.GetAttacker();
  1354. CTFPlayer *pScorer = ToTFPlayer( TFGameRules()->GetDeathScorer( pKiller, pInflictor, pPlayer ) );
  1355. if ( pInflictor && pInflictor->IsBaseObject() && dynamic_cast< CObjectSentrygun * >( pInflictor ) != NULL )
  1356. {
  1357. killerOrg = pInflictor->GetAbsOrigin();
  1358. }
  1359. else
  1360. {
  1361. if ( pScorer )
  1362. {
  1363. // CTFPlayerClass *pAttackerClass = pScorer->GetPlayerClass();
  1364. // death.iAttackClass = ( !pAttackerClass ) ? TF_CLASS_UNDEFINED : pAttackerClass->GetClassIndex();
  1365. killerOrg = pScorer->GetAbsOrigin();
  1366. }
  1367. else
  1368. {
  1369. // death.iAttackClass = TF_CLASS_UNDEFINED;
  1370. killerOrg = org;
  1371. }
  1372. }
  1373. // set the class of the target
  1374. // CTFPlayerClass *pTargetClass = ( pTFPlayer ) ? pTFPlayer->GetPlayerClass() : NULL;
  1375. // death.iTargetClass = ( !pTargetClass ) ? TF_CLASS_UNDEFINED : pTargetClass->GetClassIndex();
  1376. // find the weapon the killer used
  1377. // death.iWeapon = GetWeaponFromDamage( info );
  1378. // calculate the distance to the killer
  1379. // death.iDistance = static_cast<unsigned short>( ( killerOrg - org ).Length() );
  1380. // add it to the list of deaths
  1381. TF_Gamestats_LevelStats_t *map = m_reportedStats.m_pCurrentGame;
  1382. if ( map )
  1383. {
  1384. //const int MAX_REPORTED_DEATH_COORDS = 2000; // limit list of death coords reported so it doesn't grow unbounded.
  1385. //if ( map->m_aPlayerDeaths.Count() < MAX_REPORTED_DEATH_COORDS )
  1386. //{
  1387. // map->m_aPlayerDeaths.AddToTail( death );
  1388. //}
  1389. int iClass = ToTFPlayer( pPlayer )->GetPlayerClass()->GetClassIndex();
  1390. if ( m_reportedStats.m_pCurrentGame != NULL )
  1391. {
  1392. m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iDeaths++;
  1393. m_reportedStats.m_pCurrentGame->m_aClassStats[iClass].iTotalTime += (int) ( gpGlobals->curtime - pTFPlayer->GetSpawnTime() );
  1394. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pPlayer->GetTeamNumber() );
  1395. if ( round )
  1396. {
  1397. round->m_Summary.iDeaths++;
  1398. }
  1399. if ( pKiller != NULL && pKiller == pPlayer )
  1400. {
  1401. TF_Gamestats_RoundStats_t* round = GetRoundStatsForTeam( pKiller->GetTeamNumber() );
  1402. if ( round )
  1403. {
  1404. round->m_Summary.iSuicides++;
  1405. }
  1406. }
  1407. }
  1408. }
  1409. }
  1410. //-----------------------------------------------------------------------------
  1411. // Purpose: Event handler
  1412. //-----------------------------------------------------------------------------
  1413. void CTFGameStats::FireGameEvent( IGameEvent *event )
  1414. {
  1415. const char *pEventName = event->GetName();
  1416. if ( Q_strcmp( "tf_game_over", pEventName ) == 0 )
  1417. {
  1418. StoreGameEndReason( event->GetString( "reason" ) );
  1419. Event_GameEnd();
  1420. }
  1421. else if ( Q_strcmp( "teamplay_game_over", pEventName ) == 0 )
  1422. {
  1423. StoreGameEndReason( event->GetString( "reason" ) );
  1424. Event_GameEnd();
  1425. }
  1426. else if ( Q_strcmp( "teamplay_round_start", pEventName ) == 0 )
  1427. {
  1428. Event_RoundStart();
  1429. }
  1430. else if ( Q_strcmp( "teamplay_flag_event", pEventName ) == 0 )
  1431. {
  1432. SW_FlagEvent( event->GetInt( "player" ), event->GetInt( "carrier"), event->GetInt( "eventtype") );
  1433. }
  1434. else if ( Q_strcmp( "teamplay_point_startcapture", pEventName ) == 0 )
  1435. {
  1436. // Not sure this is necessary to track, since it's only sent when a cap is freshly started.
  1437. // Should probably see what the community needs are or what our needs are after we release the initial set of stats.
  1438. /*
  1439. const char *cappers = event->GetString( "cappers" );
  1440. for ( int i = 0; i < Q_strlen( cappers ); i++ )
  1441. {
  1442. SW_CapEvent( event->GetInt( "cp" ), cappers[i], "point_start_capture", 0 );
  1443. }
  1444. */
  1445. }
  1446. else if ( Q_strcmp( "teamplay_point_captured", pEventName ) == 0 )
  1447. {
  1448. const char *cappers = event->GetString( "cappers" );
  1449. for ( int i = 0; i < Q_strlen( cappers ); i++ )
  1450. {
  1451. SW_CapEvent( event->GetInt( "cp" ), cappers[i], "point_captured", 1 );
  1452. }
  1453. }
  1454. else if ( Q_strcmp( "player_disconnect", pEventName ) == 0 )
  1455. {
  1456. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByUserId( event->GetInt("userid") ) );
  1457. if ( pPlayer )
  1458. {
  1459. CTF_GameStats.Event_PlayerLoadoutChanged( pPlayer, true );
  1460. }
  1461. }
  1462. }
  1463. //-----------------------------------------------------------------------------
  1464. // Purpose: Adds data from current game into accumulated data for this level.
  1465. //-----------------------------------------------------------------------------
  1466. void CTFGameStats::StoreGameEndReason( const char* reason )
  1467. {
  1468. if ( Q_strcmp( reason, "Reached Time Limit" ) == 0 )
  1469. {
  1470. m_iGameEndReason = RE_TIME_LIMIT;
  1471. }
  1472. else if ( Q_strcmp( reason, "Reached Win Limit" ) == 0 )
  1473. {
  1474. m_iGameEndReason = RE_WIN_LIMIT;
  1475. }
  1476. else if ( Q_strcmp( reason, "Reached Win Difference Limit" ) == 0 )
  1477. {
  1478. m_iGameEndReason = RE_WIN_DIFF_LIMIT;
  1479. }
  1480. else if ( Q_strcmp( reason, "Reached Round Limit" ) == 0 )
  1481. {
  1482. m_iGameEndReason = RE_ROUND_LIMIT;
  1483. }
  1484. else if ( Q_strcmp( reason, "NextLevel CVAR" ) == 0 )
  1485. {
  1486. m_iGameEndReason = RE_NEXT_LEVEL_CVAR;
  1487. }
  1488. }
  1489. //-----------------------------------------------------------------------------
  1490. // Purpose: Adds data from current game into accumulated data for this level.
  1491. //-----------------------------------------------------------------------------
  1492. void CTFGameStats::AccumulateGameData()
  1493. {
  1494. // find or add a bucket for this level
  1495. TF_Gamestats_LevelStats_t *map = m_reportedStats.FindOrAddMapStats( STRING( gpGlobals->mapname ) );
  1496. // get current game data
  1497. TF_Gamestats_LevelStats_t *game = m_reportedStats.m_pCurrentGame;
  1498. if ( !map || !game )
  1499. return;
  1500. if ( IsRealGameplay( game ) )
  1501. {
  1502. // if this looks like real game play, add it to stats
  1503. map->Accumulate( game );
  1504. m_reportedStats.m_bValidData = true;
  1505. }
  1506. m_currentMap.Accumulate( game ); // Steamworks stats always accumulate.
  1507. ClearCurrentGameData();
  1508. }
  1509. //-----------------------------------------------------------------------------
  1510. // Purpose:
  1511. //-----------------------------------------------------------------------------
  1512. void CTFGameStats::AccumulateVoteData( void )
  1513. {
  1514. if ( !g_voteController )
  1515. return;
  1516. if ( !g_pStringTableServerMapCycle )
  1517. return;
  1518. // Find the current map and update playtime
  1519. int iIndex = m_MapsPlaytime.Find( CUtlConstString( STRING( gpGlobals->mapname ) ) );
  1520. if ( iIndex != m_MapsPlaytime.InvalidIndex() )
  1521. {
  1522. TF_Gamestats_LevelStats_t *CurrentGame = m_reportedStats.m_pCurrentGame;
  1523. //Msg( "%s -- old: %i ", STRING( gpGlobals->mapname ), m_MapsPlaytime[iIndex] );
  1524. m_MapsPlaytime[iIndex] += CurrentGame->m_Header.m_iTotalTime;
  1525. //Msg( "new: %i\n", STRING( gpGlobals->mapname ), m_MapsPlaytime[iIndex] );
  1526. }
  1527. }
  1528. struct MapNameAndPlaytime_t
  1529. {
  1530. const char* szName;
  1531. int nTime;
  1532. };
  1533. // Returns negative if elem2 > elem1, positive if elem2 < elem1, and zero if elem 1 == elem2
  1534. static int __cdecl SortMapPlaytime( const void *elem1, const void *elem2 )
  1535. {
  1536. int time1 = static_cast< const MapNameAndPlaytime_t * >( elem1 )->nTime;
  1537. int time2 = static_cast< const MapNameAndPlaytime_t * >( elem2 )->nTime;
  1538. if ( time2 < time1 )
  1539. return -1;
  1540. if ( time2 > time1 )
  1541. return 1;
  1542. return 0;
  1543. }
  1544. //-----------------------------------------------------------------------------
  1545. // Purpose: Method used by the vote system to retrieve various data, like map playtime
  1546. //-----------------------------------------------------------------------------
  1547. bool CTFGameStats::GetVoteData( const char *szIssueName, int nNumOptions, CUtlVector <const char*> &vecNames )
  1548. {
  1549. // Feeds lowest playtime maps to the vote system to present as options
  1550. if ( Q_strcmp( szIssueName, "NextLevel" ) == 0 )
  1551. {
  1552. // This can only happen if we don't get any maps from the mapcycle file
  1553. if ( !m_MapsPlaytime.Count() )
  1554. return false;
  1555. vecNames.EnsureCapacity( MIN( nNumOptions, (int) m_MapsPlaytime.Count() ) );
  1556. // What's the next map in the mapcycle? Place that first in the output
  1557. m_szNextMap[0] = '\0';
  1558. CMultiplayRules *pRules = dynamic_cast< CMultiplayRules * >( GameRules() );
  1559. if ( pRules )
  1560. {
  1561. pRules->GetNextLevelName( m_szNextMap, sizeof( m_szNextMap ) );
  1562. if ( m_szNextMap[0] )
  1563. {
  1564. vecNames.AddToTail( m_szNextMap );
  1565. }
  1566. }
  1567. CUtlVector< MapNameAndPlaytime_t > vecMapsAndPlaytime;
  1568. vecMapsAndPlaytime.EnsureCapacity( m_MapsPlaytime.Count() );
  1569. // Feed the maps into a vector for sorting
  1570. FOR_EACH_MAP_FAST( m_MapsPlaytime, iIndex )
  1571. {
  1572. const char *szItemName = m_MapsPlaytime.Key( iIndex ).Get();
  1573. int nItemTime = m_MapsPlaytime.Element( iIndex );
  1574. // Exclude the next map (already added) and the current map (omitted)
  1575. if ( Q_strcmp( szItemName, m_szNextMap ) != 0 &&
  1576. Q_strcmp( szItemName, STRING( gpGlobals->mapname ) ) != 0 )
  1577. {
  1578. int iVec = vecMapsAndPlaytime.AddToTail();
  1579. vecMapsAndPlaytime[ iVec ].szName = szItemName;
  1580. vecMapsAndPlaytime[ iVec ].nTime = nItemTime;
  1581. }
  1582. }
  1583. qsort( vecMapsAndPlaytime.Base(), vecMapsAndPlaytime.Count(), sizeof( MapNameAndPlaytime_t ), SortMapPlaytime );
  1584. // Copy sorted maps to output until we have got enough options
  1585. FOR_EACH_VEC( vecMapsAndPlaytime, iVec )
  1586. {
  1587. if ( vecNames.Count() >= nNumOptions )
  1588. break;
  1589. vecNames.AddToTail( vecMapsAndPlaytime[ iVec ].szName );
  1590. }
  1591. return true;
  1592. }
  1593. return false;
  1594. }
  1595. //-----------------------------------------------------------------------------
  1596. // Purpose: Clears data for current game
  1597. //-----------------------------------------------------------------------------
  1598. void CTFGameStats::ClearCurrentGameData()
  1599. {
  1600. if ( m_reportedStats.m_pCurrentGame )
  1601. {
  1602. delete m_reportedStats.m_pCurrentGame;
  1603. }
  1604. m_reportedStats.m_pCurrentGame = new TF_Gamestats_LevelStats_t;
  1605. }
  1606. //-----------------------------------------------------------------------------
  1607. // Purpose: Updates the stats of who has killed whom
  1608. //-----------------------------------------------------------------------------
  1609. void CTFGameStats::TrackKillStats( CBasePlayer *pAttacker, CBasePlayer *pVictim )
  1610. {
  1611. int iPlayerIndexAttacker = pAttacker->entindex();
  1612. int iPlayerIndexVictim = pVictim->entindex();
  1613. PlayerStats_t &statsAttacker = m_aPlayerStats[iPlayerIndexAttacker];
  1614. PlayerStats_t &statsVictim = m_aPlayerStats[iPlayerIndexVictim];
  1615. statsVictim.statsKills.iNumKilledBy[iPlayerIndexAttacker]++;
  1616. statsVictim.statsKills.iNumKilledByUnanswered[iPlayerIndexAttacker]++;
  1617. statsAttacker.statsKills.iNumKilled[iPlayerIndexVictim]++;
  1618. statsAttacker.statsKills.iNumKilledByUnanswered[iPlayerIndexVictim] = 0;
  1619. }
  1620. struct PlayerStats_t *CTFGameStats::FindPlayerStats( CBasePlayer *pPlayer )
  1621. {
  1622. if ( !pPlayer )
  1623. return NULL;
  1624. return &m_aPlayerStats[pPlayer->entindex()];
  1625. }
  1626. bool CTFGameStats::IsRealGameplay( TF_Gamestats_LevelStats_t *game )
  1627. {
  1628. // Sanity-check that this looks like real game play -- must have minimum # of players on both teams,
  1629. // minimum time and some damage to players must have occurred
  1630. if ( tf_stats_nogameplaycheck.GetInt() )
  1631. return true;
  1632. bool bIsRealGameplay = (
  1633. ( game->m_iPeakPlayerCount[TF_TEAM_RED] >= TFGameRules()->GetStatsMinimumPlayers() ) &&
  1634. ( game->m_iPeakPlayerCount[TF_TEAM_BLUE] >= TFGameRules()->GetStatsMinimumPlayers() ) &&
  1635. ( game->m_Header.m_iTotalTime >= TFGameRules()->GetStatsMinimumPlayedTime() ) && ( game->m_bIsRealServer )
  1636. );
  1637. return bIsRealGameplay;
  1638. }
  1639. //-----------------------------------------------------------------------------
  1640. // Purpose: //Deprecated
  1641. //-----------------------------------------------------------------------------
  1642. //static void CC_ListDeaths( const CCommand &args )
  1643. //{
  1644. // if ( !UTIL_IsCommandIssuedByServerAdmin() )
  1645. // return;
  1646. //
  1647. // Msg( "Command Deprecated");
  1648. //
  1649. // //TF_Gamestats_LevelStats_t *map = CTF_GameStats.m_reportedStats.m_pCurrentGame;
  1650. // //if ( !map )
  1651. // // return;
  1652. //
  1653. // //for( int i = 0; i < map->m_aPlayerDeaths.Count(); i++ )
  1654. // //{
  1655. // // Msg( "%s killed %s with %s at (%d,%d,%d), distance %d\n",
  1656. // // g_aClassNames[ map->m_aPlayerDeaths[ i ].iAttackClass ],
  1657. // // g_aClassNames[ map->m_aPlayerDeaths[ i ].iTargetClass ],
  1658. // // WeaponIdToAlias( map->m_aPlayerDeaths[ i ].iWeapon ),
  1659. // // map->m_aPlayerDeaths[ i ].nPosition[ 0 ],
  1660. // // map->m_aPlayerDeaths[ i ].nPosition[ 1 ],
  1661. // // map->m_aPlayerDeaths[ i ].nPosition[ 2 ],
  1662. // // map->m_aPlayerDeaths[ i ].iDistance );
  1663. // //}
  1664. //
  1665. // //Msg( "\n---------------------------------\n\n" );
  1666. //
  1667. // //for( int i = 0; i < map->m_aPlayerDamage.Count(); i++ )
  1668. // //{
  1669. // // Msg( "%.2f : %s at (%d,%d,%d) caused %d damage to %s with %s at (%d,%d,%d)%s%s\n",
  1670. // // map->m_aPlayerDamage[ i ].fTime,
  1671. // // g_aClassNames[ map->m_aPlayerDamage[ i ].iAttackClass ],
  1672. // // map->m_aPlayerDamage[ i ].nAttackerPosition[ 0 ],
  1673. // // map->m_aPlayerDamage[ i ].nAttackerPosition[ 1 ],
  1674. // // map->m_aPlayerDamage[ i ].nAttackerPosition[ 2 ],
  1675. // // map->m_aPlayerDamage[ i ].iDamage,
  1676. // // g_aClassNames[ map->m_aPlayerDamage[ i ].iTargetClass ],
  1677. // // WeaponIdToAlias( map->m_aPlayerDamage[ i ].iWeapon ),
  1678. // // map->m_aPlayerDamage[ i ].nTargetPosition[ 0 ],
  1679. // // map->m_aPlayerDamage[ i ].nTargetPosition[ 1 ],
  1680. // // map->m_aPlayerDamage[ i ].nTargetPosition[ 2 ],
  1681. // // map->m_aPlayerDamage[ i ].iCrit ? ", CRIT!" : "",
  1682. // // map->m_aPlayerDamage[ i ].iKill ? ", KILL" : "" );
  1683. // //}
  1684. //
  1685. // //Msg( "\n---------------------------------\n\n" );
  1686. // //Msg( "listed %d deaths\n", map->m_aPlayerDeaths.Count() );
  1687. // //Msg( "listed %d damages\n\n", map->m_aPlayerDamage.Count() );
  1688. //}
  1689. //
  1690. //static ConCommand listDeaths("listdeaths", CC_ListDeaths, "lists player deaths", FCVAR_DEVELOPMENTONLY );
  1691. CON_COMMAND_F( tf_dumpplayerstats, "Dumps current player stats", FCVAR_DEVELOPMENTONLY )
  1692. {
  1693. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  1694. return;
  1695. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  1696. {
  1697. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  1698. if ( pPlayer && pPlayer->IsConnected() )
  1699. {
  1700. PlayerStats_t &stats = CTF_GameStats.m_aPlayerStats[pPlayer->entindex()];
  1701. Msg( "%s:\n", pPlayer->GetPlayerName() );
  1702. for ( int iStat = TFSTAT_FIRST; iStat <= TFSTAT_LAST; iStat++ )
  1703. {
  1704. Msg( " Stat %d = %d (round), %d (map)\n", iStat, stats.statsCurrentRound.m_iStat[iStat], stats.statsAccumulated.m_iStat[iStat] );
  1705. }
  1706. }
  1707. }
  1708. }
  1709. //-----------------------------------------------------------------------------
  1710. // Purpose: New Steamworks Database Map Data
  1711. //-----------------------------------------------------------------------------
  1712. void CTFGameStats::SW_GameStats_WriteMap()
  1713. {
  1714. #if !defined(NO_STEAM)
  1715. KeyValues* pKVData = new KeyValues( "TF2ServerMaps" );
  1716. pKVData->SetInt( "MapIndex", CBGSDriver.m_iNumLevels );
  1717. pKVData->SetInt( "StartTime", m_currentMap.m_iMapStartTime );
  1718. pKVData->SetInt( "EndTime", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  1719. pKVData->SetString( "MapID", m_currentMap.m_Header.m_szMapName );
  1720. const char* pszGameTypeID = GetGameTypeID();
  1721. if ( pszGameTypeID )
  1722. {
  1723. pKVData->SetString( "GameTypeID", pszGameTypeID );
  1724. }
  1725. pKVData->SetInt( "RoundsPlayed", m_iRoundsPlayed );
  1726. if ( m_currentMap.m_Header.m_iBlueWins > 0 )
  1727. {
  1728. pKVData->SetInt( "BlueWins", m_currentMap.m_Header.m_iBlueWins );
  1729. }
  1730. if ( m_currentMap.m_Header.m_iRedWins > 0 )
  1731. {
  1732. pKVData->SetInt( "RedWins", m_currentMap.m_Header.m_iRedWins );
  1733. }
  1734. int iRedScore = GetGlobalTFTeam( TF_TEAM_RED )->GetScore();
  1735. if ( iRedScore )
  1736. {
  1737. pKVData->SetInt( "RedScore", iRedScore );
  1738. }
  1739. int iBlueScore = GetGlobalTFTeam( TF_TEAM_BLUE )->GetScore();
  1740. if ( iBlueScore )
  1741. {
  1742. pKVData->SetInt( "BlueScore", iBlueScore );
  1743. }
  1744. if ( m_currentMap.m_Header.m_iStalemates > 0 )
  1745. {
  1746. pKVData->SetInt( "Stalemates", m_currentMap.m_Header.m_iStalemates );
  1747. }
  1748. if ( m_currentMap.m_Header.m_iBlueSuddenDeathWins > 0 )
  1749. {
  1750. pKVData->SetInt( "BlueSuddenDeathWins", m_currentMap.m_Header.m_iBlueSuddenDeathWins );
  1751. }
  1752. if ( m_currentMap.m_Header.m_iRedSuddenDeathWins > 0 )
  1753. {
  1754. pKVData->SetInt( "RedSuddenDeathWins", m_currentMap.m_Header.m_iRedSuddenDeathWins );
  1755. }
  1756. if ( m_bServerShutdown )
  1757. {
  1758. m_iGameEndReason = RE_SERVER_SHUTDOWN;
  1759. }
  1760. else if ( !m_iGameEndReason )
  1761. {
  1762. m_iGameEndReason = RE_SERVER_MAP_CHANGE;
  1763. }
  1764. int iReason = clamp( m_iGameEndReason, 0, MAX_ROUND_END_REASON-1 );
  1765. pKVData->SetString( "EndReason", g_aRoundEndReasons[iReason] );
  1766. pKVData->SetInt( "MapVersion", m_currentMap.m_Header.m_nMapRevision );
  1767. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  1768. #endif
  1769. }
  1770. //-----------------------------------------------------------------------------
  1771. // Purpose: New Steamworks Database Round Data
  1772. //-----------------------------------------------------------------------------
  1773. void CTFGameStats::SW_GameStats_WriteRound( int iWinningTeam, bool bFullRound, int iEndReason )
  1774. {
  1775. #if !defined(NO_STEAM)
  1776. // Flush data gathered so far...
  1777. GetSteamWorksSGameStatsUploader().FlushStats();
  1778. // Round info.
  1779. KeyValues* pKVData = new KeyValues( "TF2ServerRounds" );
  1780. pKVData->SetInt( "MapIndex", 0 );
  1781. pKVData->SetInt( "RoundIndex", m_iRoundsPlayed );
  1782. const char* pszGameTypeID = GetGameTypeID();
  1783. if ( pszGameTypeID )
  1784. {
  1785. pKVData->SetString( "GameTypeID", pszGameTypeID );
  1786. }
  1787. pKVData->SetInt( "EndTime", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  1788. pKVData->SetInt( "StartTime", m_currentRoundRed.m_iRoundStartTime );
  1789. pKVData->SetString( "EndReason", ClampedArrayElement( g_aRoundEndReasons, iEndReason ) );
  1790. iWinningTeam = clamp( iWinningTeam, 0, TF_TEAM_COUNT - 1 );
  1791. pKVData->SetString( "WinningTeam", ClampedArrayElement( g_aTeamNames, iWinningTeam ) );
  1792. if ( bFullRound )
  1793. {
  1794. pKVData->SetInt( "FullRound", bFullRound );
  1795. }
  1796. int nRoundsRemaining = 0;
  1797. if ( ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT ) && TFGameRules()->HasMultipleTrains() )
  1798. {
  1799. if ( g_hControlPointMasters.Count() && g_hControlPointMasters[0] && g_hControlPointMasters[0]->PlayingMiniRounds() )
  1800. {
  1801. nRoundsRemaining = g_hControlPointMasters[0]->NumPlayableControlPointRounds();
  1802. }
  1803. }
  1804. else
  1805. {
  1806. if ( g_hControlPointMasters.Count() && g_hControlPointMasters[0] )
  1807. {
  1808. nRoundsRemaining = g_hControlPointMasters[0]->NumPlayableControlPointRounds();
  1809. }
  1810. }
  1811. if ( nRoundsRemaining )
  1812. {
  1813. pKVData->SetInt( "RoundsRemaining", nRoundsRemaining );
  1814. }
  1815. int iRedScore = GetGlobalTFTeam( TF_TEAM_RED )->GetScore();
  1816. if ( iRedScore )
  1817. {
  1818. pKVData->SetInt( "RedScore", iRedScore );
  1819. }
  1820. int iBlueScore = GetGlobalTFTeam( TF_TEAM_BLUE )->GetScore();
  1821. if ( iBlueScore )
  1822. {
  1823. pKVData->SetInt( "BlueScore", iBlueScore );
  1824. }
  1825. int iRedFlags = GetGlobalTFTeam( TF_TEAM_RED )->GetFlagCaptures();
  1826. if ( iRedFlags )
  1827. {
  1828. pKVData->SetInt( "RedFlagCaps", iRedFlags );
  1829. }
  1830. int iBlueFlags = GetGlobalTFTeam( TF_TEAM_BLUE )->GetFlagCaptures();
  1831. if ( iBlueFlags )
  1832. {
  1833. pKVData->SetInt( "BlueFlagCaps", iBlueFlags );
  1834. }
  1835. for ( int iTeam = FIRST_GAME_TEAM; iTeam < TF_TEAM_COUNT; iTeam++ )
  1836. {
  1837. int iPlayerCount = GetGlobalTeam( iTeam )->GetNumPlayers();
  1838. if ( iPlayerCount == 0 )
  1839. continue;
  1840. switch ( iTeam )
  1841. {
  1842. case TF_TEAM_BLUE:
  1843. pKVData->SetInt( "BluePlayerCount", iPlayerCount );
  1844. break;
  1845. case TF_TEAM_RED:
  1846. pKVData->SetInt( "RedPlayerCount", iPlayerCount );
  1847. break;
  1848. }
  1849. }
  1850. bool bStalemate = iWinningTeam == TEAM_UNASSIGNED;
  1851. if ( bStalemate )
  1852. {
  1853. pKVData->SetInt( "Stalemate", bStalemate );
  1854. }
  1855. if ( m_currentRoundRed.m_Summary.iTeamQuit > 0 )
  1856. {
  1857. pKVData->SetInt( "RedTeamQuit", m_currentRoundRed.m_Summary.iTeamQuit );
  1858. }
  1859. if ( m_currentRoundBlue.m_Summary.iTeamQuit > 0 )
  1860. {
  1861. pKVData->SetInt( "BlueTeamQuit", m_currentRoundBlue.m_Summary.iTeamQuit );
  1862. }
  1863. if ( m_currentRoundRed.m_Summary.iKills > 0 )
  1864. {
  1865. pKVData->SetInt( "RedKills", m_currentRoundRed.m_Summary.iKills );
  1866. }
  1867. if ( m_currentRoundBlue.m_Summary.iKills > 0 )
  1868. {
  1869. pKVData->SetInt( "BlueKills", m_currentRoundBlue.m_Summary.iKills );
  1870. }
  1871. if ( m_currentRoundRed.m_Summary.iDeaths > 0 )
  1872. {
  1873. pKVData->SetInt( "RedDeaths", m_currentRoundRed.m_Summary.iDeaths );
  1874. }
  1875. if ( m_currentRoundBlue.m_Summary.iDeaths > 0 )
  1876. {
  1877. pKVData->SetInt( "BlueDeaths", m_currentRoundBlue.m_Summary.iDeaths );
  1878. }
  1879. if ( m_currentRoundRed.m_Summary.iSuicides > 0 )
  1880. {
  1881. pKVData->SetInt( "RedSuicides", m_currentRoundRed.m_Summary.iSuicides );
  1882. }
  1883. if ( m_currentRoundBlue.m_Summary.iSuicides > 0 )
  1884. {
  1885. pKVData->SetInt( "BlueSuicides", m_currentRoundBlue.m_Summary.iSuicides );
  1886. }
  1887. if ( m_currentRoundRed.m_Summary.iAssists > 0 )
  1888. {
  1889. pKVData->SetInt( "RedAssists", m_currentRoundRed.m_Summary.iAssists );
  1890. }
  1891. if ( m_currentRoundBlue.m_Summary.iAssists > 0 )
  1892. {
  1893. pKVData->SetInt( "BlueAssists", m_currentRoundBlue.m_Summary.iAssists );
  1894. }
  1895. if ( m_currentRoundRed.m_Summary.iBuildingsBuilt > 0 )
  1896. {
  1897. pKVData->SetInt( "RedBuildingsBuilt", m_currentRoundRed.m_Summary.iBuildingsBuilt );
  1898. }
  1899. if ( m_currentRoundBlue.m_Summary.iBuildingsBuilt > 0 )
  1900. {
  1901. pKVData->SetInt( "BlueBuildingsBuilt", m_currentRoundBlue.m_Summary.iBuildingsBuilt );
  1902. }
  1903. if ( m_currentRoundRed.m_Summary.iBuildingsDestroyed > 0 )
  1904. {
  1905. pKVData->SetInt( "RedBuildingsDestroyed", m_currentRoundRed.m_Summary.iBuildingsDestroyed );
  1906. }
  1907. if ( m_currentRoundBlue.m_Summary.iBuildingsDestroyed > 0 )
  1908. {
  1909. pKVData->SetInt( "BlueBuildingsDestroyed", m_currentRoundBlue.m_Summary.iBuildingsDestroyed );
  1910. }
  1911. if ( m_currentRoundRed.m_Summary.iHeadshots > 0 )
  1912. {
  1913. pKVData->SetInt( "RedHeadshots", m_currentRoundRed.m_Summary.iHeadshots );
  1914. }
  1915. if ( m_currentRoundBlue.m_Summary.iHeadshots > 0 )
  1916. {
  1917. pKVData->SetInt( "BlueHeadshots", m_currentRoundBlue.m_Summary.iHeadshots );
  1918. }
  1919. if ( m_currentRoundRed.m_Summary.iDominations > 0 )
  1920. {
  1921. pKVData->SetInt( "RedDominations", m_currentRoundRed.m_Summary.iDominations );
  1922. }
  1923. if ( m_currentRoundBlue.m_Summary.iDominations > 0 )
  1924. {
  1925. pKVData->SetInt( "BlueDominations", m_currentRoundBlue.m_Summary.iDominations );
  1926. }
  1927. if ( m_currentRoundRed.m_Summary.iRevenges > 0 )
  1928. {
  1929. pKVData->SetInt( "RedRevenges", m_currentRoundRed.m_Summary.iRevenges );
  1930. }
  1931. if ( m_currentRoundBlue.m_Summary.iRevenges > 0 )
  1932. {
  1933. pKVData->SetInt( "BlueRevenges", m_currentRoundBlue.m_Summary.iRevenges );
  1934. }
  1935. if ( m_currentRoundRed.m_Summary.iInvulns > 0 )
  1936. {
  1937. pKVData->SetInt( "RedInvulns", m_currentRoundRed.m_Summary.iInvulns );
  1938. }
  1939. if ( m_currentRoundBlue.m_Summary.iInvulns > 0 )
  1940. {
  1941. pKVData->SetInt( "BlueInvulns", m_currentRoundBlue.m_Summary.iInvulns );
  1942. }
  1943. if ( m_currentRoundRed.m_Summary.iTeleports > 0 )
  1944. {
  1945. pKVData->SetInt( "RedTeleports", m_currentRoundRed.m_Summary.iTeleports );
  1946. }
  1947. if ( m_currentRoundBlue.m_Summary.iTeleports > 0 )
  1948. {
  1949. pKVData->SetInt( "BlueTeleports", m_currentRoundBlue.m_Summary.iTeleports );
  1950. }
  1951. if ( m_currentRoundRed.m_Summary.iDamageDone > 0 )
  1952. {
  1953. pKVData->SetInt( "RedDamageDone", m_currentRoundRed.m_Summary.iDamageDone );
  1954. }
  1955. if ( m_currentRoundBlue.m_Summary.iDamageDone > 0 )
  1956. {
  1957. pKVData->SetInt( "BlueDamageDone", m_currentRoundBlue.m_Summary.iDamageDone );
  1958. }
  1959. if ( m_currentRoundRed.m_Summary.iHealingDone > 0 )
  1960. {
  1961. pKVData->SetInt( "RedHealingDone", m_currentRoundRed.m_Summary.iHealingDone );
  1962. }
  1963. if ( m_currentRoundBlue.m_Summary.iHealingDone > 0 )
  1964. {
  1965. pKVData->SetInt( "BlueHealingDone", m_currentRoundBlue.m_Summary.iHealingDone );
  1966. }
  1967. if ( m_currentRoundRed.m_Summary.iCrits > 0 )
  1968. {
  1969. pKVData->SetInt( "RedCrits", m_currentRoundRed.m_Summary.iCrits );
  1970. }
  1971. if ( m_currentRoundBlue.m_Summary.iCrits > 0 )
  1972. {
  1973. pKVData->SetInt( "BlueCrits", m_currentRoundBlue.m_Summary.iCrits );
  1974. }
  1975. if ( m_currentRoundRed.m_Summary.iBackstabs > 0 )
  1976. {
  1977. pKVData->SetInt( "RedBackstabs", m_currentRoundRed.m_Summary.iBackstabs );
  1978. }
  1979. if ( m_currentRoundBlue.m_Summary.iBackstabs > 0 )
  1980. {
  1981. pKVData->SetInt( "BlueBackstabs", m_currentRoundBlue.m_Summary.iBackstabs );
  1982. }
  1983. const char *pszReg = GameCoordinator_GetRegistrationString();
  1984. bool bOfficial = pszReg && V_strstr( pszReg, "'Gordon'" ) && tf_mm_trusted.GetBool();
  1985. pKVData->SetInt( "IsTrustedServer", bOfficial );
  1986. //INT_FIELD( nIsOfficial, IsTrustedServer, int8 )
  1987. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  1988. #endif
  1989. }
  1990. //-----------------------------------------------------------------------------
  1991. // Purpose: New Steamworks Database Player Data
  1992. // Player reports are sent every round and when a player disconnects.
  1993. //-----------------------------------------------------------------------------
  1994. void CTFGameStats::SW_GameStats_WritePlayer( CTFPlayer *pPlayer, bool bDisconnected )
  1995. {
  1996. // Everytime we write out a player we also want to write out their loadout stats
  1997. Event_PlayerLoadoutChanged( pPlayer, true );
  1998. #if !defined(NO_STEAM)
  1999. if ( !pPlayer )
  2000. return;
  2001. if ( pPlayer->IsBot() )
  2002. return;
  2003. CSteamID steamIDForPlayer;
  2004. if ( !pPlayer->GetSteamID( &steamIDForPlayer ) )
  2005. return;
  2006. PlayerStats_t &stats = CTF_GameStats.m_aPlayerStats[pPlayer->entindex()];
  2007. // Player info.
  2008. KeyValues* pKVData = new KeyValues( "TF2ServerPlayers" );
  2009. pKVData->SetInt( "MapIndex", 0 );
  2010. int iRoundIndex = m_iRoundsPlayed;
  2011. if ( bDisconnected && m_bRoundActive )
  2012. iRoundIndex++; // Increment so we report the current round if the player disconnected before the round was over.
  2013. pKVData->SetInt( "RoundIndex", iRoundIndex );
  2014. pKVData->SetInt( "PlayerUpdates", ++m_iPlayerUpdates );
  2015. pKVData->SetUint64( "AccountIDPlayer", steamIDForPlayer.ConvertToUint64() );
  2016. pKVData->SetInt( "ConnectTime", stats.iConnectTime );
  2017. pKVData->SetInt( "DisconnectTime", MAX( stats.iDisconnectTime, 0 ) );
  2018. if ( stats.statsCurrentRound.m_iStat[TFSTAT_KILLS] > 0 )
  2019. {
  2020. pKVData->SetInt( "Kills", stats.statsCurrentRound.m_iStat[TFSTAT_KILLS] );
  2021. }
  2022. if ( stats.statsCurrentRound.m_iStat[TFSTAT_DEATHS] > 0 )
  2023. {
  2024. pKVData->SetInt( "Deaths", stats.statsCurrentRound.m_iStat[TFSTAT_DEATHS] );
  2025. }
  2026. if ( stats.statsCurrentRound.m_iStat[TFSTAT_SUICIDES] > 0 )
  2027. {
  2028. pKVData->SetInt( "Suicides", stats.statsCurrentRound.m_iStat[TFSTAT_SUICIDES] );
  2029. }
  2030. if ( stats.statsCurrentRound.m_iStat[TFSTAT_KILLASSISTS] > 0 )
  2031. {
  2032. pKVData->SetInt( "Assists", stats.statsCurrentRound.m_iStat[TFSTAT_KILLASSISTS] );
  2033. }
  2034. if ( stats.statsCurrentRound.m_iStat[TFSTAT_BUILDINGSBUILT] > 0 )
  2035. {
  2036. pKVData->SetInt( "BuildingsBuilt", stats.statsCurrentRound.m_iStat[TFSTAT_BUILDINGSBUILT] );
  2037. }
  2038. if ( stats.statsCurrentRound.m_iStat[TFSTAT_BUILDINGSDESTROYED] > 0 )
  2039. {
  2040. pKVData->SetInt( "BuildingsDestroyed", stats.statsCurrentRound.m_iStat[TFSTAT_BUILDINGSDESTROYED] );
  2041. }
  2042. if ( stats.statsCurrentRound.m_iStat[TFSTAT_HEADSHOTS] > 0 )
  2043. {
  2044. pKVData->SetInt( "Headshots", stats.statsCurrentRound.m_iStat[TFSTAT_HEADSHOTS] );
  2045. }
  2046. if ( stats.statsCurrentRound.m_iStat[TFSTAT_DOMINATIONS] > 0 )
  2047. {
  2048. pKVData->SetInt( "Dominations", stats.statsCurrentRound.m_iStat[TFSTAT_DOMINATIONS] );
  2049. }
  2050. if ( stats.statsCurrentRound.m_iStat[TFSTAT_REVENGE] > 0 )
  2051. {
  2052. pKVData->SetInt( "Revenges", stats.statsCurrentRound.m_iStat[TFSTAT_REVENGE] );
  2053. }
  2054. if ( stats.statsCurrentRound.m_iStat[TFSTAT_DAMAGE] > 0 )
  2055. {
  2056. pKVData->SetInt( "DamageDone", stats.statsCurrentRound.m_iStat[TFSTAT_DAMAGE] );
  2057. }
  2058. if ( stats.statsCurrentRound.m_iStat[TFSTAT_DAMAGETAKEN] > 0 )
  2059. {
  2060. pKVData->SetInt( "DamageTaken", stats.statsCurrentRound.m_iStat[TFSTAT_DAMAGETAKEN] );
  2061. }
  2062. if ( stats.statsCurrentRound.m_iStat[TFSTAT_HEALING] > 0 )
  2063. {
  2064. pKVData->SetInt( "HealingDone", stats.statsCurrentRound.m_iStat[TFSTAT_HEALING] );
  2065. }
  2066. if ( stats.statsCurrentRound.m_iStat[TFSTAT_HEALTHKITS] > 0 )
  2067. {
  2068. pKVData->SetInt( "HealthKits", stats.statsCurrentRound.m_iStat[TFSTAT_HEALTHKITS] );
  2069. }
  2070. if ( stats.statsCurrentRound.m_iStat[TFSTAT_AMMOKITS] > 0 )
  2071. {
  2072. pKVData->SetInt( "AmmoKits", stats.statsCurrentRound.m_iStat[TFSTAT_AMMOKITS] );
  2073. }
  2074. if ( stats.statsCurrentRound.m_iStat[TFSTAT_CLASSCHANGES] > 0 )
  2075. {
  2076. pKVData->SetInt( "ClassChanges", stats.statsCurrentRound.m_iStat[TFSTAT_CLASSCHANGES] );
  2077. }
  2078. if ( stats.statsCurrentRound.m_iStat[TFSTAT_INVULNS] > 0 )
  2079. {
  2080. pKVData->SetInt( "Invulns", stats.statsCurrentRound.m_iStat[TFSTAT_INVULNS] );
  2081. }
  2082. if ( stats.statsCurrentRound.m_iStat[TFSTAT_CRITS] > 0 )
  2083. {
  2084. pKVData->SetInt( "Crits", stats.statsCurrentRound.m_iStat[TFSTAT_CRITS] );
  2085. }
  2086. if ( stats.statsCurrentRound.m_iStat[TFSTAT_BACKSTABS] > 0 )
  2087. {
  2088. pKVData->SetInt( "Backstabs", stats.statsCurrentRound.m_iStat[TFSTAT_BACKSTABS] );
  2089. }
  2090. if ( stats.statsCurrentRound.m_iStat[TFSTAT_TELEPORTS] > 0 )
  2091. {
  2092. pKVData->SetInt( "Teleports", stats.statsCurrentRound.m_iStat[TFSTAT_TELEPORTS] );
  2093. }
  2094. if ( stats.statsCurrentRound.m_iStat[TFSTAT_SHOTS_HIT] > 0 )
  2095. {
  2096. pKVData->SetInt( "ShotsHit", stats.statsCurrentRound.m_iStat[TFSTAT_SHOTS_HIT] );
  2097. }
  2098. if ( stats.statsCurrentRound.m_iStat[TFSTAT_SHOTS_FIRED] > 0 )
  2099. {
  2100. pKVData->SetInt( "ShotsFired", stats.statsCurrentRound.m_iStat[TFSTAT_SHOTS_FIRED] );
  2101. }
  2102. if ( stats.statsCurrentRound.m_iStat[TFSTAT_POINTSSCORED] > 0 )
  2103. {
  2104. pKVData->SetInt( "PointsScored", stats.statsCurrentRound.m_iStat[TFSTAT_POINTSSCORED] );
  2105. }
  2106. if ( stats.statsCurrentRound.m_iStat[TFSTAT_CAPTURES] > 0 )
  2107. {
  2108. pKVData->SetInt( "Captures", stats.statsCurrentRound.m_iStat[TFSTAT_CAPTURES] );
  2109. }
  2110. if ( stats.statsCurrentRound.m_iStat[TFSTAT_DEFENSES] > 0 )
  2111. {
  2112. pKVData->SetInt( "Defenses", stats.statsCurrentRound.m_iStat[TFSTAT_DEFENSES] );
  2113. }
  2114. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  2115. #endif
  2116. }
  2117. //-----------------------------------------------------------------------------
  2118. // Purpose: New Steamworks Database Kill Data
  2119. //-----------------------------------------------------------------------------
  2120. void CTFGameStats::SW_GameStats_WriteKill( CTFPlayer* pKiller, CTFPlayer* pVictim, CTFPlayer* pAssister, IGameEvent* event /*player_death*/, const CTakeDamageInfo &info )
  2121. {
  2122. if ( !pKiller || !pVictim )
  2123. return; // Recorded kills must have a killer and a victim.
  2124. if ( pKiller->IsBot() && pVictim->IsBot() )
  2125. {
  2126. if ( !pAssister || pAssister->IsBot() )
  2127. return; // Don't record kills that only involve bots.
  2128. }
  2129. // 08/26/2010 - For now, don't record kills involving bots at all.
  2130. // This is to work around an issue with inserting duplicate keys.
  2131. if ( pKiller->IsBot() || pVictim->IsBot() )
  2132. return;
  2133. if ( pAssister && pAssister->IsBot() )
  2134. return;
  2135. if ( TFGameRules()->State_Get() != GR_STATE_RND_RUNNING )
  2136. return; // Only record kills during an active round.
  2137. if ( !m_bRoundActive )
  2138. return;
  2139. // Kills info.
  2140. KeyValues* pKVData = new KeyValues( "TF2ServerKills" );
  2141. pKVData->SetInt( "MapIndex", 0 );
  2142. pKVData->SetInt( "RoundIndex", m_iRoundsPlayed+1 );
  2143. CSteamID steamIDForPlayer;
  2144. pVictim->GetSteamID( &steamIDForPlayer );
  2145. if ( !pVictim->IsBot() )
  2146. {
  2147. pKVData->SetUint64( "AccountIDVictim", steamIDForPlayer.ConvertToUint64() );
  2148. }
  2149. else
  2150. {
  2151. pKVData->SetUint64( "AccountIDVictim", 1 );
  2152. }
  2153. pKiller->GetSteamID( &steamIDForPlayer );
  2154. if ( !pKiller->IsBot() )
  2155. {
  2156. pKVData->SetUint64( "AccountIDKiller", steamIDForPlayer.ConvertToUint64() );
  2157. }
  2158. else
  2159. {
  2160. pKVData->SetUint64( "AccountIDKiller", 1 );
  2161. }
  2162. if ( pAssister && !pAssister->IsBot() )
  2163. {
  2164. pAssister->GetSteamID( &steamIDForPlayer );
  2165. pKVData->SetUint64( "AccountIDAssister", steamIDForPlayer.ConvertToUint64() );
  2166. }
  2167. else
  2168. {
  2169. if ( !pAssister )
  2170. {
  2171. pKVData->SetUint64( "AccountIDAssister", 0 );
  2172. }
  2173. else if ( pAssister->IsBot() )
  2174. {
  2175. pKVData->SetUint64( "AccountIDAssister", 1 );
  2176. }
  2177. }
  2178. pKVData->SetInt( "KillCount", ++m_iKillCount );
  2179. pKVData->SetInt( "KillTime", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  2180. // Participant Details
  2181. const Vector& victimPos = pVictim->GetAbsOrigin();
  2182. pKVData->SetString( "VictimClass", ClampedArrayElement( g_aPlayerClassNames_NonLocalized, pVictim->GetPlayerClass()->GetClassIndex() ) );
  2183. pKVData->SetFloat( "VictimLocationX", victimPos.x );
  2184. pKVData->SetFloat( "VictimLocationY", victimPos.y );
  2185. pKVData->SetFloat( "VictimLocationZ", victimPos.z );
  2186. const Vector& killerPos = pKiller->GetAbsOrigin();
  2187. pKVData->SetString( "KillerClass", ClampedArrayElement( g_aPlayerClassNames_NonLocalized, pKiller->GetPlayerClass()->GetClassIndex() ) );
  2188. pKVData->SetFloat( "KillerLocationX", killerPos.x );
  2189. pKVData->SetFloat( "KillerLocationY", killerPos.y );
  2190. pKVData->SetFloat( "KillerLocationZ", killerPos.z );
  2191. if ( pAssister )
  2192. {
  2193. const Vector& assisterPos = pAssister->GetAbsOrigin();
  2194. pKVData->SetString( "AssisterClass", ClampedArrayElement( g_aPlayerClassNames_NonLocalized, pAssister->GetPlayerClass()->GetClassIndex() ) );
  2195. pKVData->SetFloat( "AssisterLocationX", assisterPos.x );
  2196. pKVData->SetFloat( "AssisterLocationY", assisterPos.y );
  2197. pKVData->SetFloat( "AssisterLocationZ", assisterPos.z );
  2198. }
  2199. int damageBits = event->GetInt( "damagebits" );
  2200. const char* log_name = event->GetString( "weapon_logclassname" );
  2201. pKVData->SetString( "WeaponLogNameID", log_name );
  2202. pKVData->SetString( "WeaponID", ClampedArrayElement( g_aWeaponNames, event->GetInt( "weaponid" ) ) );
  2203. ETFDmgCustom iCustomKill = (ETFDmgCustom)event->GetInt( "customkill" );
  2204. if ( iCustomKill > 0 )
  2205. {
  2206. pKVData->SetString( "CustomDamageInfo", GetCustomDamageName( iCustomKill ) );
  2207. }
  2208. int16 damage = clamp( RoundFloatToInt( info.GetDamage() ), 32767, -32767 );
  2209. pKVData->SetInt( "Damage", damage );
  2210. pKVData->SetFloat( "Distance", victimPos.DistTo( killerPos ) );
  2211. Vector damagePos = info.GetDamagePosition();
  2212. pKVData->SetFloat( "DamageSourceX", damagePos.x );
  2213. pKVData->SetFloat( "DamageSourceY", damagePos.y );
  2214. pKVData->SetFloat( "DamageSourceZ", damagePos.z );
  2215. bool bTest = ( damageBits & DMG_CRITICAL ) > 0;
  2216. if ( bTest )
  2217. {
  2218. pKVData->SetInt( "IsCrit", bTest );
  2219. }
  2220. // Break down the death flags to make this data easier to handle.
  2221. int deathFlags = event->GetInt( "death_flags" );
  2222. bTest = ( deathFlags & TF_DEATH_DOMINATION ) > 0;
  2223. if ( bTest )
  2224. {
  2225. pKVData->SetInt( "IsDomination", bTest );
  2226. }
  2227. bTest = ( deathFlags & TF_DEATH_REVENGE ) > 0;
  2228. if ( bTest )
  2229. {
  2230. pKVData->SetInt( "IsRevenge", bTest );
  2231. }
  2232. bTest = ( deathFlags & TF_DEATH_ASSISTER_DOMINATION ) > 0;
  2233. if ( bTest )
  2234. {
  2235. pKVData->SetInt( "IsAssisterDomination", bTest );
  2236. }
  2237. bTest = ( deathFlags & TF_DEATH_ASSISTER_REVENGE ) > 0;
  2238. if ( bTest )
  2239. {
  2240. pKVData->SetInt( "IsAssisterRevenge", bTest );
  2241. }
  2242. bTest = ( deathFlags & TF_DEATH_FIRST_BLOOD ) > 0;
  2243. if ( bTest )
  2244. {
  2245. pKVData->SetInt( "IsFirstBlood", bTest );
  2246. }
  2247. bTest = ( deathFlags & TF_DEATH_FEIGN_DEATH ) > 0;
  2248. if ( bTest )
  2249. {
  2250. pKVData->SetInt( "IsFeignDeath", bTest );
  2251. }
  2252. bTest = ( deathFlags & TF_DEATH_INTERRUPTED ) > 0;
  2253. if ( bTest )
  2254. {
  2255. pKVData->SetInt( "IsInterrupted", bTest );
  2256. }
  2257. bTest = ( deathFlags & TF_DEATH_GIBBED ) > 0;
  2258. if ( bTest )
  2259. {
  2260. pKVData->SetInt( "IsGibbed", bTest );
  2261. }
  2262. bTest = ( pKiller == pVictim );
  2263. if ( bTest )
  2264. {
  2265. pKVData->SetInt( "IsSuicide", bTest );
  2266. }
  2267. // Break down the interesting condition flags.
  2268. int stunFlags = event->GetInt( "stun_flags" );
  2269. bTest = ( pVictim->m_Shared.InCond( TF_COND_STUNNED ) &&
  2270. ( (stunFlags & TF_STUN_LOSER_STATE) || (stunFlags & TF_STUN_CONTROLS) ) );
  2271. if ( bTest )
  2272. {
  2273. pKVData->SetInt( "IsVictimBallStunned", bTest );
  2274. }
  2275. bTest = pVictim->m_Shared.InCond( TF_COND_URINE );
  2276. if ( bTest )
  2277. {
  2278. pKVData->SetInt( "IsVictimJarated", bTest );
  2279. }
  2280. bTest = pVictim->m_Shared.InCond( TF_COND_BLEEDING );
  2281. if ( bTest )
  2282. {
  2283. pKVData->SetInt( "IsVictimBleeding", bTest );
  2284. }
  2285. bTest = pVictim->m_Shared.InCond( TF_COND_BURNING );
  2286. if ( bTest )
  2287. {
  2288. pKVData->SetInt( "IsVictimBurning", bTest );
  2289. }
  2290. bTest = pVictim->m_Shared.InCond( TF_COND_DISGUISED );
  2291. if ( bTest )
  2292. {
  2293. pKVData->SetInt( "IsVictimDisguised", bTest );
  2294. }
  2295. bTest = pVictim->m_Shared.InCond( TF_COND_STEALTHED );
  2296. if ( bTest )
  2297. {
  2298. pKVData->SetInt( "IsVictimStealthed", bTest );
  2299. }
  2300. bTest = pVictim->m_Shared.InCond( TF_COND_ZOOMED );
  2301. if ( bTest )
  2302. {
  2303. pKVData->SetInt( "IsVictimZoomed", bTest );
  2304. }
  2305. bTest = pKiller->m_Shared.InCond( TF_COND_CRITBOOSTED );
  2306. if ( bTest )
  2307. {
  2308. pKVData->SetInt( "IsKillerCritBoosted", bTest );
  2309. }
  2310. bTest = pKiller->m_Shared.InCond( TF_COND_CRITBOOSTED_RAGE_BUFF );
  2311. if ( bTest )
  2312. {
  2313. pKVData->SetInt( "IsKillerRageCritBoosted", bTest );
  2314. }
  2315. bTest = pKiller->m_Shared.InCond( TF_COND_OFFENSEBUFF );
  2316. if ( bTest )
  2317. {
  2318. pKVData->SetInt( "IsKillerSoldierBuffed", bTest );
  2319. }
  2320. bTest = pKiller->m_Shared.InCond( TF_COND_DEFENSEBUFF );
  2321. if ( bTest )
  2322. {
  2323. pKVData->SetInt( "IsKillerSoldierDefenseBuffed", bTest );
  2324. }
  2325. bTest = pKiller->m_Shared.InCond( TF_COND_REGENONDAMAGEBUFF );
  2326. if ( bTest )
  2327. {
  2328. pKVData->SetInt( "IsKillerSoldierRegenBuffed", bTest );
  2329. }
  2330. bTest = pKiller->m_Shared.InCond( TF_COND_SHIELD_CHARGE );
  2331. if ( bTest )
  2332. {
  2333. pKVData->SetInt( "IsKillerShieldCharging", bTest );
  2334. }
  2335. bTest = pKiller->m_Shared.InCond( TF_COND_DEMO_BUFF );
  2336. if ( bTest )
  2337. {
  2338. pKVData->SetInt( "IsKillerEyelanderBuffed", bTest );
  2339. }
  2340. bTest = pKiller->m_Shared.InCond( TF_COND_ZOOMED );
  2341. if ( bTest )
  2342. {
  2343. pKVData->SetInt( "IsKillerZoomed", bTest );
  2344. }
  2345. bTest = pKiller->m_Shared.IsInvulnerable();
  2346. if ( bTest )
  2347. {
  2348. pKVData->SetInt( "IsKillerInvulnerable", bTest );
  2349. }
  2350. int16 victim_health = clamp( pVictim->GetHealthBefore(), 32767, -32767 );
  2351. pKVData->SetInt( "VictimHealth", victim_health );
  2352. int16 killer_health = clamp( pKiller->GetHealth(), 32767, -32767 );
  2353. pKVData->SetInt( "KillerHealth", killer_health );
  2354. if ( pAssister )
  2355. {
  2356. int16 assister_health = clamp( pAssister->GetHealth(), 32767, -32767 );
  2357. pKVData->SetInt( "AssisterHealth", assister_health );
  2358. }
  2359. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  2360. }
  2361. //-----------------------------------------------------------------------------
  2362. // Purpose: Records player team activity during a round.
  2363. //-----------------------------------------------------------------------------
  2364. void CTFGameStats::Event_TeamChange( CTFPlayer* pPlayer, int oldTeam, int newTeam )
  2365. {
  2366. if ( pPlayer->IsBot() )
  2367. return;
  2368. CSteamID steamIDForPlayer;
  2369. if ( !pPlayer->GetSteamID( &steamIDForPlayer ) )
  2370. return;
  2371. if ( oldTeam == newTeam )
  2372. return;
  2373. // if ( oldTeam == 0 || newTeam == 0 )
  2374. // return;
  2375. #if !defined(NO_STEAM)
  2376. KeyValues* pKVData = new KeyValues( "TF2ServerTeamChanges" );
  2377. pKVData->SetInt( "MapIndex", 0 );
  2378. pKVData->SetInt( "RoundIndex", m_iRoundsPlayed+1 );
  2379. // pKVData->SetInt( "TimeSubmitted", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  2380. pKVData->SetString( "OldTeam", ClampedArrayElement( g_aTeamNames, oldTeam ) );
  2381. pKVData->SetString( "NewTeam", ClampedArrayElement( g_aTeamNames, newTeam ) );
  2382. pKVData->SetInt( "ChangeCount", pPlayer->GetTeamChangeCount() );
  2383. pKVData->SetUint64( "AccountIDPlayer", steamIDForPlayer.ConvertToUint64() );
  2384. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  2385. #endif
  2386. }
  2387. //-----------------------------------------------------------------------------
  2388. // Purpose: Records players touching currency packs - primarily MvM, but future modes will likely use
  2389. //-----------------------------------------------------------------------------
  2390. void CTFGameStats::Event_PlayerCollectedCurrency( CBasePlayer *pPlayer, int nAmount )
  2391. {
  2392. Assert( pPlayer );
  2393. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  2394. if ( pTFPlayer )
  2395. {
  2396. IncrementStat( pTFPlayer, TFSTAT_CURRENCY_COLLECTED, nAmount );
  2397. }
  2398. }
  2399. //-----------------------------------------------------------------------------
  2400. // Purpose: Records the item set a player is using and for how long (until class, map, server or loadout change)
  2401. //-----------------------------------------------------------------------------
  2402. void CTFGameStats::Event_PlayerLoadoutChanged( CTFPlayer *pPlayer, bool bForceReport )
  2403. {
  2404. // Steam needs to be updated to take in the new table. So we disable the table first
  2405. #if !defined(NO_STEAM)
  2406. if ( !pPlayer )
  2407. return;
  2408. if ( pPlayer->IsBot() )
  2409. return;
  2410. CSteamID steamIDForPlayer;
  2411. if ( !pPlayer->GetSteamID( &steamIDForPlayer ) )
  2412. return;
  2413. PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
  2414. // Not enough time reported, reset
  2415. int iSecondsUsed = (int)( gpGlobals->curtime - stats.loadoutStats.flStartTime );
  2416. bool bActuallyChanged = false;
  2417. bool bIsInit = false;
  2418. int iPrevClass = stats.loadoutStats.iClass;
  2419. bActuallyChanged |= pPlayer->GetPlayerClass()->GetClassIndex() != iPrevClass;
  2420. // if this is the first time through, class is invalid and we dont want to report anything
  2421. // Table updated, using v2
  2422. KeyValues* pKVData = new KeyValues( "TF2ServerPlayerLoadoutv2" );
  2423. int iSlotCount = LOADOUT_POSITION_MISC2 + 1;
  2424. for ( int iSlot = 0; iSlot < iSlotCount; ++iSlot )
  2425. {
  2426. int iDefIndex = stats.loadoutStats.iLoadoutItemDefIndices[ iSlot ];
  2427. bIsInit |= iDefIndex != INVALID_ITEM_DEF_INDEX;
  2428. pKVData->SetInt( CFmtStr("SlotDef%d", iSlot), iDefIndex );
  2429. pKVData->SetInt( CFmtStr("SlotQuality%d", iSlot), stats.loadoutStats.iLoadoutItemQualities[ iSlot ] );
  2430. pKVData->SetInt( CFmtStr("SlotStyle%d", iSlot), stats.loadoutStats.iLoadoutItemStyles[ iSlot ] );
  2431. // Check to see if the item actually changed
  2432. item_definition_index_t iItemDef = INVALID_ITEM_DEF_INDEX;
  2433. CEconItemView *pItem = pPlayer->GetLoadoutItem( pPlayer->GetPlayerClass()->GetClassIndex(), iSlot );
  2434. if ( pItem )
  2435. {
  2436. iItemDef = pItem->GetItemDefIndex();
  2437. }
  2438. // Check if there was actually a change
  2439. bActuallyChanged |= stats.loadoutStats.iLoadoutItemDefIndices[ iSlot ] != iItemDef;
  2440. // Set the new items
  2441. int iItemQuality = pItem ? pItem->GetItemQuality() : AE_UNDEFINED;
  2442. style_index_t iItemStyle = pItem ? pItem->GetStyle() : 0;
  2443. stats.loadoutStats.SetItemDef( iSlot, iItemDef, iItemQuality, iItemStyle );
  2444. }
  2445. pKVData->SetInt( "ID", ++m_iLoadoutChangesCount );
  2446. pKVData->SetInt( "SecondsEquipped", iSecondsUsed );
  2447. pKVData->SetInt( "Class", iPrevClass );
  2448. pKVData->SetInt( "AccountIDPlayer", (int)steamIDForPlayer.ConvertToUint64() ); // OGS does not actually support uints
  2449. if ( iPrevClass < TF_FIRST_NORMAL_CLASS || iPrevClass >= TF_LAST_NORMAL_CLASS )
  2450. {
  2451. bIsInit = false;
  2452. }
  2453. // pKVData->SetInt( "TimeSubmitted", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  2454. // Ignore forced respawn calls that trigger this but don't actually change your loadout
  2455. // do not report if time used is less then 5 minutes
  2456. if ( ( bActuallyChanged || bForceReport ) )
  2457. {
  2458. if ( stats.loadoutStats.flStartTime > 0 && iSecondsUsed > 300 && bIsInit )
  2459. {
  2460. // IsCompetitive
  2461. bool bIsCompetitive = TFGameRules() ? TFGameRules()->IsCompetitiveMode() : false;
  2462. // IsTrusted
  2463. const char *pszReg = GameCoordinator_GetRegistrationString();
  2464. bool bOfficial = pszReg && V_strstr( pszReg, "'Gordon'" ) && tf_mm_trusted.GetBool();
  2465. pKVData->SetInt( "IsTrustedServer", bOfficial );
  2466. pKVData->SetInt( "IsCompetitive", bIsCompetitive );
  2467. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  2468. }
  2469. stats.loadoutStats.Set( pPlayer->GetPlayerClass()->GetClassIndex() );
  2470. }
  2471. #endif
  2472. }
  2473. //-----------------------------------------------------------------------------
  2474. // Purpose:
  2475. //-----------------------------------------------------------------------------
  2476. void CTFGameStats::Event_PlayerRevived( CTFPlayer *pPlayer )
  2477. {
  2478. IncrementStat( pPlayer, TFSTAT_REVIVED, 1 );
  2479. }
  2480. //-----------------------------------------------------------------------------
  2481. // Purpose:
  2482. //-----------------------------------------------------------------------------
  2483. void CTFGameStats::Event_PlayerThrowableHit( CTFPlayer *pAttacker )
  2484. {
  2485. IncrementStat( pAttacker, TFSTAT_THROWABLEHIT, 1 );
  2486. }
  2487. //-----------------------------------------------------------------------------
  2488. // Purpose:
  2489. //-----------------------------------------------------------------------------
  2490. void CTFGameStats::Event_PlayerThrowableKill( CTFPlayer *pAttacker )
  2491. {
  2492. IncrementStat( pAttacker, TFSTAT_THROWABLEKILL, 1 );
  2493. }
  2494. //-----------------------------------------------------------------------------
  2495. // Purpose: Track only their highest - not cumulative
  2496. //-----------------------------------------------------------------------------
  2497. void CTFGameStats::Event_PlayerEarnedKillStreak( CTFPlayer *pAttacker )
  2498. {
  2499. if ( !pAttacker )
  2500. return;
  2501. PlayerStats_t &stats = m_aPlayerStats[pAttacker->entindex()];
  2502. int nCount = pAttacker->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills );
  2503. int nMax = stats.statsCurrentRound.m_iStat[TFSTAT_KILLSTREAK_MAX];
  2504. if ( nCount > nMax )
  2505. {
  2506. stats.statsCurrentRound.m_iStat[TFSTAT_KILLSTREAK_MAX] = nCount;
  2507. }
  2508. }
  2509. //-----------------------------------------------------------------------------
  2510. // Purpose: Halloween!
  2511. //-----------------------------------------------------------------------------
  2512. void CTFGameStats::Event_HalloweenBossEvent( uint8 unBossType, uint16 unBossLevel, uint8 unEventType, uint8 unPlayersInvolved, float fElapsedTime )
  2513. {
  2514. //if ( !GCClientSystem() )
  2515. // return;
  2516. //static uint8 unEventCounter = 0;
  2517. //GCSDK::CProtoBufMsg<CMsgHalloween_ServerBossEvent> msg( k_EMsgGC_Halloween_ServerBossEvent );
  2518. //msg.Body().set_event_counter( unEventCounter++ );
  2519. //msg.Body().set_timestamp( CRTime::RTime32TimeCur() );
  2520. //msg.Body().set_boss_type( unBossType );
  2521. //msg.Body().set_boss_level( unBossLevel );
  2522. //msg.Body().set_event_type( unEventType );
  2523. //msg.Body().set_players_involved( unPlayersInvolved );
  2524. //msg.Body().set_elapsed_time( fElapsedTime );
  2525. //GCClientSystem()->BSendMessage( msg );
  2526. }
  2527. //-----------------------------------------------------------------------------
  2528. // Purpose: Records player class activity during a round.
  2529. //-----------------------------------------------------------------------------
  2530. void CTFGameStats::SW_ClassChange( CTFPlayer* pPlayer, int oldClass, int newClass )
  2531. {
  2532. if ( pPlayer->IsBot() )
  2533. return;
  2534. CSteamID steamIDForPlayer;
  2535. if ( !pPlayer->GetSteamID( &steamIDForPlayer ) )
  2536. return;
  2537. if ( oldClass == newClass )
  2538. return;
  2539. // if ( oldClass == 0 || newClass == 0 )
  2540. // return;
  2541. #if !defined(NO_STEAM)
  2542. KeyValues* pKVData = new KeyValues( "TF2ServerClassChanges" );
  2543. // pKVData->SetInt( "MapIndex", CBGSDriver.m_iNumLevels+1 );
  2544. pKVData->SetInt( "RoundIndex", m_iRoundsPlayed+1 );
  2545. // pKVData->SetInt( "TimeSubmitted", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  2546. oldClass = clamp( oldClass, 0, TF_CLASS_COUNT-1 );
  2547. pKVData->SetString( "OldClass", g_aPlayerClassNames_NonLocalized[oldClass] );
  2548. newClass = clamp( newClass, 0, TF_CLASS_COUNT-1 );
  2549. pKVData->SetString( "NewClass", g_aPlayerClassNames_NonLocalized[newClass] );
  2550. pKVData->SetInt( "ChangeCount", pPlayer->GetClassChangeCount() );
  2551. pKVData->SetUint64( "AccountIDPlayer", steamIDForPlayer.ConvertToUint64() );
  2552. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  2553. #endif
  2554. }
  2555. //-----------------------------------------------------------------------------
  2556. // Purpose: Records player scoring activity during a round.
  2557. //-----------------------------------------------------------------------------
  2558. void CTFGameStats::SW_GameEvent( CTFPlayer* pPlayer, const char* pszEventID, int iPoints )
  2559. {
  2560. if ( pPlayer && pPlayer->IsBot() )
  2561. return;
  2562. #if !defined(NO_STEAM)
  2563. KeyValues* pKVData = new KeyValues( "TF2ServerGameEvents" );
  2564. pKVData->SetInt( "RoundIndex", m_iRoundsPlayed+1 );
  2565. // pKVData->SetInt( "TimeSubmitted", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  2566. pKVData->SetInt( "ChangeCount", ++m_iEvents );
  2567. pKVData->SetString( "EventID", pszEventID );
  2568. if ( iPoints )
  2569. {
  2570. pKVData->SetInt( "Points", iPoints );
  2571. }
  2572. if ( pPlayer )
  2573. {
  2574. pKVData->SetString( "Team", ClampedArrayElement( g_aTeamNames, pPlayer->GetTeamNumber() ) );
  2575. pKVData->SetFloat( "LocationX", pPlayer->GetAbsOrigin().x );
  2576. pKVData->SetFloat( "LocationY", pPlayer->GetAbsOrigin().y );
  2577. pKVData->SetFloat( "LocationZ", pPlayer->GetAbsOrigin().z );
  2578. CSteamID steamIDForPlayer;
  2579. if ( pPlayer->GetSteamID( &steamIDForPlayer ) )
  2580. {
  2581. pKVData->SetUint64( "AccountIDPlayer", steamIDForPlayer.ConvertToUint64() );
  2582. }
  2583. else
  2584. {
  2585. pKVData->SetUint64( "AccountIDPlayer", 0 );
  2586. }
  2587. }
  2588. else
  2589. {
  2590. pKVData->SetString( "Team", g_aTeamNames[0] );
  2591. pKVData->SetUint64( "AccountIDPlayer", 0 );
  2592. }
  2593. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  2594. #endif
  2595. }
  2596. //-----------------------------------------------------------------------------
  2597. // Purpose: Records flag activity during a match.
  2598. //-----------------------------------------------------------------------------
  2599. void CTFGameStats::SW_FlagEvent( int iPlayer, int iCarrier, int iEventType )
  2600. {
  2601. CTFPlayer* pPlayer = ToTFPlayer( UTIL_PlayerByIndex(iPlayer) );
  2602. int iPoints = 0;
  2603. const char* pszEventID = NULL;
  2604. switch ( iEventType )
  2605. {
  2606. case TF_FLAGEVENT_PICKUP:
  2607. pszEventID = "flag_pickup";
  2608. break;
  2609. case TF_FLAGEVENT_CAPTURE:
  2610. pszEventID = "flag_captured";
  2611. iPoints = 1;
  2612. break;
  2613. case TF_FLAGEVENT_DEFEND:
  2614. pszEventID = "flag_defended";
  2615. iPoints = 1;
  2616. break;
  2617. case TF_FLAGEVENT_DROPPED:
  2618. pszEventID = "flag_dropped";
  2619. break;
  2620. case TF_FLAGEVENT_RETURNED:
  2621. pszEventID = "flag_returned";
  2622. break;
  2623. }
  2624. SW_GameEvent( pPlayer, pszEventID, iPoints );
  2625. }
  2626. //-----------------------------------------------------------------------------
  2627. // Purpose: Records control point activity during a match.
  2628. //-----------------------------------------------------------------------------
  2629. void CTFGameStats::SW_CapEvent( int iPoint, int iPlayer, const char* pszEventID, int iPoints )
  2630. {
  2631. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex(iPlayer) );
  2632. if ( !pPlayer )
  2633. return;
  2634. SW_GameEvent( pPlayer, pszEventID, iPoints );
  2635. }
  2636. //-----------------------------------------------------------------------------
  2637. // Purpose: Uploads the hosts row, which has general information about the game server.
  2638. //-----------------------------------------------------------------------------
  2639. void CTFGameStats::SW_WriteHostsRow()
  2640. {
  2641. #if !defined(NO_STEAM)
  2642. // Gather info we'll be sending...
  2643. int maxClients = gpGlobals->maxClients;
  2644. bool isDedicated = engine->IsDedicatedServer();
  2645. bool isVACSecure = SteamGameServer_BSecure();
  2646. bool cheatsWereOn = TFGameRules() && TFGameRules()->HaveCheatsBeenEnabledDuringLevel();
  2647. bool isPassword = GetSteamWorksSGameStatsUploader().IsPassworded();
  2648. KeyValues* pKVData = new KeyValues( "TF2ServerHosts" );
  2649. // Server Browse Info
  2650. pKVData->SetInt( "ServerIP", GetSteamWorksSGameStatsUploader().GetServerIP() );
  2651. pKVData->SetString( "ServerName", GetSteamWorksSGameStatsUploader().GetHostName() );
  2652. // Server Config
  2653. pKVData->SetInt( "PlayerSlots", maxClients );
  2654. if ( isDedicated )
  2655. {
  2656. pKVData->SetInt( "IsDedicated", isDedicated );
  2657. }
  2658. if ( isPassword )
  2659. {
  2660. pKVData->SetInt( "IsPassworded", isPassword );
  2661. }
  2662. if ( isVACSecure )
  2663. {
  2664. pKVData->SetInt( "IsVACSecure", isVACSecure );
  2665. }
  2666. if ( cheatsWereOn )
  2667. {
  2668. pKVData->SetInt( "IsCheats", cheatsWereOn );
  2669. }
  2670. ConVarRef mp_timelimit( "mp_timelimit" );
  2671. if ( mp_timelimit.GetInt() )
  2672. {
  2673. pKVData->SetInt( "TimeLimit", mp_timelimit.GetInt() );
  2674. }
  2675. ConVarRef tf_flag_caps_per_round( "tf_flag_caps_per_round" );
  2676. if ( tf_flag_caps_per_round.GetInt() )
  2677. {
  2678. pKVData->SetInt( "FlagCapsPerRound", tf_flag_caps_per_round.GetInt() );
  2679. }
  2680. ConVarRef mp_maxrounds( "mp_maxrounds" );
  2681. if ( mp_maxrounds.GetInt() )
  2682. {
  2683. pKVData->SetInt( "MaxRounds", mp_maxrounds.GetInt() );
  2684. }
  2685. ConVarRef mp_winlimit( "mp_winlimit" );
  2686. if ( mp_winlimit.GetInt() )
  2687. {
  2688. pKVData->SetInt( "WinLimit", mp_winlimit.GetInt() );
  2689. }
  2690. ConVarRef mp_disable_respawn_times( "mp_disable_respawn_times" );
  2691. if ( mp_disable_respawn_times.GetInt() )
  2692. {
  2693. pKVData->SetInt( "DisableRespawnTimes", mp_disable_respawn_times.GetInt() );
  2694. }
  2695. ConVarRef mp_stalemate_meleeonly( "mp_stalemate_meleeonly" );
  2696. if ( mp_stalemate_meleeonly.GetInt() )
  2697. {
  2698. pKVData->SetInt( "StalemateMeleeOnly", mp_stalemate_meleeonly.GetInt() );
  2699. }
  2700. ConVarRef mp_forceautoteam( "mp_forceautoteam" );
  2701. if ( mp_forceautoteam.GetInt() )
  2702. {
  2703. pKVData->SetInt( "ForceAutoTeam", mp_forceautoteam.GetInt() );
  2704. }
  2705. // Server Activity Info
  2706. RTime32 starttime = GetSteamWorksSGameStatsUploader().GetStartTime();
  2707. if ( starttime )
  2708. {
  2709. pKVData->SetInt( "ServerStartTime", starttime );
  2710. }
  2711. RTime32 endtime = GetSteamWorksSGameStatsUploader().GetEndTime();
  2712. if ( endtime )
  2713. {
  2714. pKVData->SetInt( "ServerEndTime", endtime );
  2715. }
  2716. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  2717. #endif
  2718. }
  2719. //-----------------------------------------------------------------------------
  2720. #undef min
  2721. #undef max
  2722. struct PasstimeHistogramStats
  2723. {
  2724. double min, max, mean, median, mode, stdev;
  2725. PasstimeHistogramStats() : min(0), max(0), mean(0), median(0), mode(0), stdev(0) {}
  2726. };
  2727. static int qsort_ascending_uint16( const void *a, const void *b )
  2728. {
  2729. return *((uint16*)b) - *((uint16*)a);
  2730. }
  2731. template<int TMaxSamples>
  2732. static PasstimeHistogramStats Passtime_SampleStats( const uint16 (&samples)[TMaxSamples], uint16 iSampleCount )
  2733. {
  2734. PasstimeHistogramStats result;
  2735. if ( (iSampleCount <= 1) || (iSampleCount > TMaxSamples) )
  2736. {
  2737. return result;
  2738. }
  2739. // mode is useless, so don't bother
  2740. // sort for median
  2741. qsort( (void*) &samples[0], iSampleCount, sizeof(samples[0]), &qsort_ascending_uint16 );
  2742. //
  2743. // Sum, Min, Max
  2744. //
  2745. double sum = 0;
  2746. result.min = DBL_MAX;
  2747. result.max = DBL_MIN;
  2748. for ( uint32 i = 0; i < iSampleCount; ++i )
  2749. {
  2750. float s = samples[i];
  2751. sum += s;
  2752. result.min = MIN( s, result.min );
  2753. result.max = MAX( s, result.max );
  2754. }
  2755. //
  2756. // Mean
  2757. //
  2758. result.mean = (double)sum / (double)iSampleCount;
  2759. //
  2760. // Median
  2761. //
  2762. result.median = samples[ iSampleCount / 2 ]; // close enough
  2763. //
  2764. // Stdev
  2765. //
  2766. for ( uint32 i = 0; i < iSampleCount; ++i )
  2767. {
  2768. double s = samples[i];
  2769. result.stdev += (s - result.mean) * (s - result.mean);
  2770. }
  2771. result.stdev = sqrt( result.stdev / ((double)(iSampleCount - 1)) );
  2772. return result;
  2773. }
  2774. template<int TBinCount>
  2775. static PasstimeHistogramStats Passtime_HistogramStats( const uint32 (&hist)[TBinCount], uint32 iHistSum, uint32 iSampleCount )
  2776. {
  2777. PasstimeHistogramStats result;
  2778. if ( iSampleCount <= 1 )
  2779. {
  2780. return result;
  2781. }
  2782. //
  2783. // Mean
  2784. //
  2785. result.mean = (float)iHistSum / (float)iSampleCount;
  2786. //
  2787. // Min
  2788. //
  2789. for ( uint32 i = 0; i < 256; ++i )
  2790. {
  2791. if ( hist[i] != 0 )
  2792. {
  2793. result.min = i;
  2794. break;
  2795. }
  2796. }
  2797. //
  2798. // Max
  2799. //
  2800. for ( int32 i = 255; i >= 0; --i )
  2801. {
  2802. if ( hist[i] != 0 )
  2803. {
  2804. result.max = i;
  2805. break;
  2806. }
  2807. }
  2808. //
  2809. // Median
  2810. //
  2811. int iMedSample = iSampleCount / 2;
  2812. int iMedian;
  2813. for ( iMedian = 0; iMedian < 256; ++iMedian )
  2814. {
  2815. if ( hist[iMedian] != 0 )
  2816. break;
  2817. }
  2818. while( (iMedSample > 0) && (iMedian < 256) )
  2819. {
  2820. iMedSample -= hist[iMedian];
  2821. ++iMedian;
  2822. }
  2823. result.median = iMedian - 1;
  2824. //
  2825. // Mode, stdev
  2826. //
  2827. uint32 iLargestCount = 0;
  2828. result.mode = -1; // wat
  2829. for ( uint32 i = 0; i < 256; ++i )
  2830. {
  2831. uint32 iSampleCount = hist[i];
  2832. for ( uint32 j = 0; j < iSampleCount; ++j )
  2833. {
  2834. // this feels dumb
  2835. result.stdev += (i - result.mean) * (i - result.mean);
  2836. }
  2837. if ( iSampleCount > iLargestCount )
  2838. {
  2839. iLargestCount = iSampleCount;
  2840. result.mode = i;
  2841. }
  2842. }
  2843. result.stdev = sqrt( result.stdev / ((double)iSampleCount - 1) );
  2844. return result;
  2845. }
  2846. void CTFGameStats::SW_PasstimeRoundEnded()
  2847. {
  2848. #if !defined(NO_STEAM)
  2849. if ( !TFGameRules() || !g_pPasstimeLogic )
  2850. {
  2851. return;
  2852. }
  2853. // Flush data gathered so far...
  2854. GetSteamWorksSGameStatsUploader().FlushStats();
  2855. KeyValues *pKVData = new KeyValues( "TF2ServerPasstimeRoundEndedv2" );
  2856. pKVData->SetString( "MapID", m_currentMap.m_Header.m_szMapName ); // Reference table
  2857. pKVData->SetInt( "RoundIndex", m_iRoundsPlayed );
  2858. pKVData->SetInt( "TotalPassesStarted", m_passtimeStats.summary.nTotalPassesStarted );
  2859. pKVData->SetInt( "TotalPassesFailed", m_passtimeStats.summary.nTotalPassesFailed );
  2860. pKVData->SetInt( "TotalPassesShotDown", m_passtimeStats.summary.nTotalPassesShotDown );
  2861. pKVData->SetInt( "TotalPassesCompleted", m_passtimeStats.summary.nTotalPassesCompleted );
  2862. pKVData->SetInt( "TotalPassesCompletedNearGoal", m_passtimeStats.summary.nTotalPassesCompletedNearGoal );
  2863. pKVData->SetInt( "TotalPassesIntercepted", m_passtimeStats.summary.nTotalPassesIntercepted );
  2864. pKVData->SetInt( "TotalPassesInterceptedNearGoal", m_passtimeStats.summary.nTotalPassesInterceptedNearGoal );
  2865. pKVData->SetInt( "TotalPassRequests", m_passtimeStats.summary.nTotalPassRequests );
  2866. pKVData->SetInt( "TotalTosses", m_passtimeStats.summary.nTotalTosses );
  2867. pKVData->SetInt( "TotalTossesCompleted", m_passtimeStats.summary.nTotalTossesCompleted );
  2868. pKVData->SetInt( "TotalTossesIntercepted", m_passtimeStats.summary.nTotalTossesIntercepted );
  2869. pKVData->SetInt( "TotalTossesInterceptedNearGoal", m_passtimeStats.summary.nTotalTossesInterceptedNearGoal );
  2870. pKVData->SetInt( "TotalSteals", m_passtimeStats.summary.nTotalSteals );
  2871. pKVData->SetInt( "TotalStealsNearGoal", m_passtimeStats.summary.nTotalStealsNearGoal );
  2872. pKVData->SetInt( "TotalBallSpawnShots", m_passtimeStats.summary.nTotalBallSpawnShots );
  2873. pKVData->SetInt( "TotalScores", m_passtimeStats.summary.nTotalScores );
  2874. pKVData->SetInt( "TotalRecoveries", m_passtimeStats.summary.nTotalRecoveries );
  2875. pKVData->SetInt( "TotalCarrySec", m_passtimeStats.summary.nTotalCarrySec );
  2876. pKVData->SetInt( "TotalWinningTeamBallCarrySec", m_passtimeStats.summary.nTotalWinningTeamBallCarrySec );
  2877. pKVData->SetInt( "TotalLosingTeamBallCarrySec", m_passtimeStats.summary.nTotalLosingTeamBallCarrySec );
  2878. pKVData->SetInt( "TotalThrowCancels", m_passtimeStats.summary.nTotalThrowCancels );
  2879. pKVData->SetInt( "TotalSpeedBoosts", m_passtimeStats.summary.nTotalSpeedBoosts );
  2880. pKVData->SetInt( "TotalJumpPads", m_passtimeStats.summary.nTotalJumpPads );
  2881. pKVData->SetInt( "TotalCarrierSpeedBoosts", m_passtimeStats.summary.nTotalCarrierSpeedBoosts );
  2882. pKVData->SetInt( "TotalCarrierJumpPads", m_passtimeStats.summary.nTotalCarrierJumpPads );
  2883. pKVData->SetInt( "BallNeutralSec", m_passtimeStats.summary.nBallNeutralSec );
  2884. pKVData->SetInt( "GoalType", m_passtimeStats.summary.nGoalType );
  2885. pKVData->SetInt( "RoundEndReason", m_passtimeStats.summary.nRoundEndReason );
  2886. pKVData->SetInt( "RoundRemainingSec", m_passtimeStats.summary.nRoundRemainingSec );
  2887. pKVData->SetInt( "RoundMaxSec", m_passtimeStats.summary.nRoundMaxSec );
  2888. pKVData->SetInt( "RoundElapsedSec", m_passtimeStats.summary.nRoundMaxSec - m_passtimeStats.summary.nRoundRemainingSec );
  2889. pKVData->SetInt( "PlayersBlueMax", m_passtimeStats.summary.nPlayersBlueMax );
  2890. pKVData->SetInt( "PlayersRedMax", m_passtimeStats.summary.nPlayersRedMax );
  2891. pKVData->SetInt( "ScoreRed", m_passtimeStats.summary.nScoreRed );
  2892. pKVData->SetInt( "ScoreBlue", m_passtimeStats.summary.nScoreBlue );
  2893. pKVData->SetBool( "Stalemate", m_passtimeStats.summary.bStalemate );
  2894. pKVData->SetBool( "SuddenDeath", m_passtimeStats.summary.bSuddenDeath );
  2895. pKVData->SetBool( "MeleeOnlySuddenDeath", m_passtimeStats.summary.bMeleeOnlySuddenDeath );
  2896. auto ballFracStats = Passtime_HistogramStats( m_passtimeStats.summary.arrBallFracHist,
  2897. m_passtimeStats.summary.nBallFracHistSum, m_passtimeStats.summary.nBallFracSampleCount );
  2898. pKVData->SetInt( "BallFracHistMin", (int)round( ballFracStats.min ) );
  2899. pKVData->SetInt( "BallFracHistMax", (int)round( ballFracStats.max ) );
  2900. pKVData->SetInt( "BallFracHistMean", (int)round( ballFracStats.mean ) );
  2901. pKVData->SetInt( "BallFracHistMedian", (int)round( ballFracStats.median ) );
  2902. pKVData->SetInt( "BallFracHistMode", (int)round( ballFracStats.mode ) );
  2903. pKVData->SetInt( "BallFracHistStdev", (int)round( ballFracStats.stdev ) );
  2904. pKVData->SetInt( "BallFracHistSampleCount", (int)m_passtimeStats.summary.nBallFracSampleCount ); // for approx global average
  2905. auto passTravelDistStats = Passtime_SampleStats(
  2906. m_passtimeStats.summary.arrPassTravelDistSamples, m_passtimeStats.summary.nPassTravelDistSampleCount );
  2907. pKVData->SetInt( "PassTravelDistMin", (int)round( passTravelDistStats.min ) );
  2908. pKVData->SetInt( "PassTravelDistMax", (int)round( passTravelDistStats.max ) );
  2909. pKVData->SetInt( "PassTravelDistMean", (int)round( passTravelDistStats.mean ) );
  2910. pKVData->SetInt( "PassTravelDistMedian", (int)round( passTravelDistStats.median ) );
  2911. //pKVData->SetInt( "PassTravelDistMode", (int) round( passTravelDistStats.mode ) ); meaningless
  2912. pKVData->SetInt( "PassTravelDistStdev", (int)round( passTravelDistStats.stdev ) );
  2913. pKVData->SetInt( "PassTravelDistSampleCount", (int)m_passtimeStats.summary.nPassTravelDistSampleCount ); // for approx global average
  2914. // have to flatten class stats because stats system can't handle nested tables
  2915. {
  2916. char aClassKey[32] = { 0, };
  2917. for ( int nClass = TF_FIRST_NORMAL_CLASS; nClass <= TF_LAST_NORMAL_CLASS; ++nClass )
  2918. {
  2919. V_sprintf_safe( aClassKey, "TotalScores_%s", g_aRawPlayerClassNamesShort[nClass] );
  2920. pKVData->SetInt( aClassKey, m_passtimeStats.classes[nClass].nTotalScores );
  2921. V_sprintf_safe( aClassKey, "TotalCarrySec_%s", g_aRawPlayerClassNamesShort[nClass] );
  2922. pKVData->SetInt( aClassKey, m_passtimeStats.classes[nClass].nTotalCarrySec );
  2923. }
  2924. }
  2925. const char *pszReg = GameCoordinator_GetRegistrationString();
  2926. bool bOfficial = pszReg && V_strstr( pszReg, "'Gordon'" ) && tf_mm_trusted.GetBool();
  2927. pKVData->SetInt( "IsTrustedServer", bOfficial );
  2928. if ( tf_passtime_save_stats.GetBool() )
  2929. {
  2930. // do this before AddStatsForUpload because it might actually just delete pKVData
  2931. // i need to copy it because i need to add some keys and i don't want there to be any possibility
  2932. // of tainting the kv that's sent to the stats server
  2933. auto pKVCopy = pKVData->MakeCopy();
  2934. auto iNow = CRTime::RTime32TimeCur();
  2935. char filename[128];
  2936. V_sprintf_safe(filename, "passtime_stats_%u.txt", iNow);
  2937. // add keys to simulate what the stats server usually adds to the database automatically
  2938. pKVData->SetInt( "SessionID", 0 );
  2939. pKVData->SetInt( "TimeReported", iNow );
  2940. pKVData->SaveToFile( g_pFullFileSystem, filename );
  2941. pKVCopy->deleteThis();
  2942. }
  2943. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  2944. #endif
  2945. }
  2946. //-----------------------------------------------------------------------------
  2947. void CTFGameStats::Event_PowerUpModeDeath( CTFPlayer *pKiller, CTFPlayer *pVictim )
  2948. {
  2949. #if !defined(NO_STEAM)
  2950. //START_TABLE( k_ESchemaCatalogOGS, TF2PowerUpModeKillsv2, TABLE_PROP_NORMAL )
  2951. // INT_FIELD( llSessionID, SessionID, uint64 ) // Reporting server
  2952. // INT_FIELD( nAccountID, AccountID, int32 ) // Player
  2953. // INT_FIELD( nID, ID, int32 ) // ID
  2954. // INT_FIELD( bIsTrustedServer, IsTrustedServer, bool )
  2955. // INT_FIELD( nKillerClass, KillerClass, int16 )
  2956. // INT_FIELD( nKillerRune, KillerRune, int16 )
  2957. // INT_FIELD( nKillerKillstreak, KillerKillstreak, int16 )
  2958. // INT_FIELD( nKillerPrimary, KillerPrimary, int32 )
  2959. // INT_FIELD( nKillerSecondary, KillerSecondary, int32 )
  2960. // INT_FIELD( nKillerMelee, KillerMelee, int32 )
  2961. // INT_FIELD( nVictimClass, VictimClass, int16 )
  2962. // INT_FIELD( nVictimRune, VictimRune, int16 )
  2963. // INT_FIELD( nVictimKillstreak, VictimKillstreak, int16 )
  2964. // INT_FIELD( RTime32UpdateTime, TimeSubmitted, RTime32 )
  2965. // PRIMARY_KEYS_CLUSTERED( 80, nAccountID, RTime32UpdateTime, llSessionID )
  2966. // WIPE_TABLE_BETWEEN_TESTS( k_EWipePolicyWipeForAllTests )
  2967. // PARTITION_INTERVAL( k_EPartitionIntervalDaily )
  2968. // OWNING_APPLICATION( 440 )
  2969. // END_TABLE
  2970. if ( !TFGameRules() || !TFGameRules()->IsPowerupMode() )
  2971. return;
  2972. if ( !pKiller || !pVictim )
  2973. return;
  2974. if ( pKiller->IsBot() || pVictim->IsBot() )
  2975. return;
  2976. CSteamID killerID;
  2977. pKiller->GetSteamID( &killerID );
  2978. if ( !killerID.IsValid() || !killerID.BIndividualAccount() )
  2979. return;
  2980. const char *pszReg = GameCoordinator_GetRegistrationString();
  2981. bool bOfficial = pszReg && V_strstr( pszReg, "'Gordon'" ) && tf_mm_trusted.GetBool();
  2982. KeyValues* pKVData = new KeyValues( "TF2PowerUpModeKillsv2" );
  2983. pKVData->SetInt( "AccountID", (int)killerID.GetAccountID() );
  2984. pKVData->SetInt( "ID", (int)m_iEvents++ );
  2985. pKVData->SetInt( "IsTrustedServer", bOfficial );
  2986. pKVData->SetInt( "KillerClass", pKiller->GetPlayerClass()->GetClassIndex() );
  2987. pKVData->SetInt( "KillerRune", GetConditionFromRuneType( pKiller->m_Shared.GetCarryingRuneType() ) );
  2988. pKVData->SetInt( "KillerKillstreak", pKiller->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_KillsAll ) );
  2989. CEconItemView *pItem = pKiller->GetLoadoutItem( pKiller->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_PRIMARY );
  2990. item_definition_index_t iItemDef = pItem ? pItem->GetItemDefIndex() : 0;
  2991. pKVData->SetInt( "KillerPrimary", iItemDef );
  2992. pItem = pKiller->GetLoadoutItem( pKiller->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_SECONDARY );
  2993. iItemDef = pItem ? pItem->GetItemDefIndex() : 0;
  2994. pKVData->SetInt( "KillerSecondary", iItemDef );
  2995. pItem = pKiller->GetLoadoutItem( pKiller->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_MELEE );
  2996. iItemDef = pItem ? pItem->GetItemDefIndex() : 0;
  2997. pKVData->SetInt( "KillerMelee", iItemDef );
  2998. pKVData->SetInt( "VictimClass", pVictim->GetPlayerClass()->GetClassIndex() );
  2999. pKVData->SetInt( "VictimRune", GetConditionFromRuneType( pVictim->m_Shared.GetCarryingRuneType() ) );
  3000. pKVData->SetInt( "VictimKillstreak", pVictim->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_KillsAll ) );
  3001. //pKVData->SetInt( "TimeSubmitted", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  3002. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  3003. #endif // !NO_STEAM
  3004. }
  3005. //-----------------------------------------------------------------------------
  3006. void CTFGameStats::Event_PowerUpRuneDuration( CTFPlayer *pPlayer, int iDuration, int nRune )
  3007. {
  3008. #if !defined(NO_STEAM)
  3009. //-----------------------------------------------------------------------------
  3010. // OGS: TF2 PowerUp Mode - Power Up duration
  3011. //-----------------------------------------------------------------------------
  3012. //START_TABLE( k_ESchemaCatalogOGS, TF2PowerUpModeRuneDuration, TABLE_PROP_NORMAL )
  3013. // INT_FIELD( llSessionID, SessionID, uint64 ) // Reporting server
  3014. // INT_FIELD( nAccountID, AccountID, uint32 ) // Player
  3015. // INT_FIELD( nID, ID, int32 ) // ID
  3016. // INT_FIELD( bIsTrustedServer, IsTrustedServer, bool )
  3017. // INT_FIELD( nPlayerClass, PlayerClass, int16 )
  3018. // INT_FIELD( nPlayerRune, PlayerRune, int16 )
  3019. // INT_FIELD( nPlayerKillstreak, PlayerKillstreak, int16 )
  3020. // INT_FIELD( nRuneDuration, RuneDuration, int32 )
  3021. // INT_FIELD( nPlayerPrimary, PlayerPrimary, int32 )
  3022. // INT_FIELD( nPlayerSecondary, PlayerSecondary, int32 )
  3023. // INT_FIELD( nPlayerMelee, PlayerMelee, int32 )
  3024. // INT_FIELD( RTime32UpdateTime, TimeSubmitted, RTime32 )
  3025. // PRIMARY_KEYS_CLUSTERED( 80, nAccountID, nID, RTime32UpdateTime, llSessionID )
  3026. // WIPE_TABLE_BETWEEN_TESTS( k_EWipePolicyWipeForAllTests )
  3027. // PARTITION_INTERVAL( k_EPartitionIntervalDaily )
  3028. // OWNING_APPLICATION( 440 )
  3029. // END_TABLE
  3030. if ( !TFGameRules() || !TFGameRules()->IsPowerupMode() )
  3031. return;
  3032. if ( !pPlayer || pPlayer->IsBot() )
  3033. return;
  3034. CSteamID playerID;
  3035. pPlayer->GetSteamID( &playerID );
  3036. if ( !playerID.IsValid() || !playerID.BIndividualAccount() )
  3037. return;
  3038. const char *pszReg = GameCoordinator_GetRegistrationString();
  3039. bool bOfficial = pszReg && V_strstr( pszReg, "'Gordon'" ) && tf_mm_trusted.GetBool();
  3040. KeyValues* pKVData = new KeyValues( "TF2PowerUpModeRuneDuration" );
  3041. pKVData->SetInt( "AccountID", (int)playerID.GetAccountID() );
  3042. pKVData->SetInt( "ID", (int)m_iEvents++ );
  3043. pKVData->SetInt( "IsTrustedServer", bOfficial );
  3044. pKVData->SetInt( "PlayerClass", pPlayer->GetPlayerClass()->GetClassIndex() );
  3045. pKVData->SetInt( "PlayerRune", nRune );
  3046. pKVData->SetInt( "PlayerKillstreak", pPlayer->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_KillsAll ) );
  3047. pKVData->SetInt( "RuneDuration", iDuration );
  3048. CEconItemView *pItem = pPlayer->GetLoadoutItem( pPlayer->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_PRIMARY );
  3049. item_definition_index_t iItemDef = pItem ? pItem->GetItemDefIndex() : 0;
  3050. pKVData->SetInt( "PlayerPrimary", iItemDef );
  3051. pItem = pPlayer->GetLoadoutItem( pPlayer->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_SECONDARY );
  3052. iItemDef = pItem ? pItem->GetItemDefIndex() : 0;
  3053. pKVData->SetInt( "PlayerSecondary", iItemDef );
  3054. pItem = pPlayer->GetLoadoutItem( pPlayer->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_MELEE );
  3055. iItemDef = pItem ? pItem->GetItemDefIndex() : 0;
  3056. pKVData->SetInt( "PlayerMelee", iItemDef );
  3057. //pKVData->SetInt( "TimeSubmitted", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
  3058. GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  3059. #endif // !NO_STEAM
  3060. }