summaryrefslogtreecommitdiff
path: root/game/server/cstrike/cs_gamestats.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/cstrike/cs_gamestats.cpp')
-rw-r--r--game/server/cstrike/cs_gamestats.cpp1721
1 files changed, 1721 insertions, 0 deletions
diff --git a/game/server/cstrike/cs_gamestats.cpp b/game/server/cstrike/cs_gamestats.cpp
new file mode 100644
index 0000000..afb02a0
--- /dev/null
+++ b/game/server/cstrike/cs_gamestats.cpp
@@ -0,0 +1,1721 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: CS game stats
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Some tricky business here - we don't want to include the precompiled header for the statreader
+// and trying to #ifdef it out does funky things like ignoring the #endif. Define our header file
+// separately and include it based on the switch
+
+#include "cbase.h"
+
+#include <tier0/platform.h>
+#include "cs_gamerules.h"
+#include "cs_gamestats.h"
+#include "weapon_csbase.h"
+#include "props.h"
+#include "cs_achievement_constants.h"
+#include "../../shared/cstrike/weapon_c4.h"
+
+#include <time.h>
+#include "filesystem.h"
+#include "bot_util.h"
+#include "cdll_int.h"
+#include "steamworks_gamestats.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+float g_flGameStatsUpdateTime = 0.0f;
+short g_iTerroristVictories[CS_NUM_LEVELS];
+short g_iCounterTVictories[CS_NUM_LEVELS];
+short g_iWeaponPurchases[WEAPON_MAX];
+
+short g_iAutoBuyPurchases = 0;
+short g_iReBuyPurchases = 0;
+short g_iAutoBuyM4A1Purchases = 0;
+short g_iAutoBuyAK47Purchases = 0;
+short g_iAutoBuyFamasPurchases = 0;
+short g_iAutoBuyGalilPurchases = 0;
+short g_iAutoBuyVestHelmPurchases = 0;
+short g_iAutoBuyVestPurchases = 0;
+
+struct PropModelStats_t
+{
+ const char* szPropModelName;
+ CSStatType_t statType;
+} PropModelStatsTableInit[] =
+{
+ { "models/props/cs_office/computer_caseb.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/computer_monitor.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/phone.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/projector.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/TV_plasma.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/computer_keyboard.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/radio.mdl", CSSTAT_PROPSBROKEN_OFFICERADIO },
+ { "models/props/cs_office/trash_can.mdl", CSSTAT_PROPSBROKEN_OFFICEJUNK },
+ { "models/props/cs_office/file_box.mdl", CSSTAT_PROPSBROKEN_OFFICEJUNK },
+ { "models/props_junk/watermelon01.mdl", CSSTAT_PROPSBROKEN_ITALY_MELON },
+ // models/props/de_inferno/claypot01.mdl
+ // models/props/de_inferno/claypot02.mdl
+ // models/props/de_dust/grainbasket01c.mdl
+ // models/props_junk/wood_crate001a.mdl
+ // models/props/cs_office/file_box_p1.mdl
+};
+
+
+struct ServerStats_t
+{
+ int achievementId;
+ int statId;
+ int roundRequirement;
+ int matchRequirement;
+ const char* mapFilter;
+
+ bool IsMet(int roundStat, int matchStat)
+ {
+ return roundStat >= roundRequirement && matchStat >= matchRequirement;
+ }
+} ServerStatBasedAchievements[] =
+{
+ { CSBreakWindows, CSSTAT_NUM_BROKEN_WINDOWS, AchievementConsts::BreakWindowsInOfficeRound_Windows, 0, "cs_office" },
+ { CSBreakProps, CSSTAT_PROPSBROKEN_ALL, AchievementConsts::BreakPropsInRound_Props, 0, NULL },
+ { CSUnstoppableForce, CSSTAT_KILLS, AchievementConsts::UnstoppableForce_Kills, 0, NULL },
+ { CSHeadshotsInRound, CSSTAT_KILLS_HEADSHOT, AchievementConsts::HeadshotsInRound_Kills, 0, NULL },
+ { CSDominationOverkillsMatch, CSSTAT_DOMINATION_OVERKILLS, 0, 10, NULL },
+};
+
+//=============================================================================
+// HPE_BEGIN:
+// [Forrest] Allow nemesis/revenge to be turned off for a server
+//=============================================================================
+static void SvNoNemesisChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
+{
+ ConVarRef var( pConVar );
+ if ( var.IsValid() && var.GetBool() )
+ {
+ // Clear all nemesis relationships.
+ for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
+ {
+ CCSPlayer *pTemp = ToCSPlayer( UTIL_PlayerByIndex( i ) );
+ if ( pTemp )
+ {
+ pTemp->RemoveNemesisRelationships();
+ }
+ }
+ }
+}
+
+ConVar sv_nonemesis( "sv_nonemesis", "0", 0, "Disable nemesis and revenge.", SvNoNemesisChangeCallback );
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+int GetCSLevelIndex( const char *pLevelName )
+{
+ for ( int i = 0; MapName_StatId_Table[i].statWinsId != CSSTAT_UNDEFINED; i ++ )
+ {
+ if ( Q_strcmp( pLevelName, MapName_StatId_Table[i].szMapName ) == 0 )
+ return i;
+ }
+
+ return -1;
+}
+
+ConVar sv_debugroundstats( "sv_debugroundstats", "0", 0, "A temporary variable that will print extra information about stats upload which may be useful in debugging any problems." );
+
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] Addition of CCS_GameStats class
+//=============================================================================
+
+CCSGameStats CCS_GameStats;
+CCSGameStats::StatContainerList_t* CCSGameStats::s_StatLists = new CCSGameStats::StatContainerList_t();
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+// Input : -
+//-----------------------------------------------------------------------------
+CCSGameStats::CCSGameStats()
+{
+ gamestats = this;
+ Clear();
+ m_fDisseminationTimerLow = m_fDisseminationTimerHigh = 0.0f;
+
+ // create table for mapping prop models to stats
+ for ( int i = 0; i < ARRAYSIZE(PropModelStatsTableInit); ++i)
+ {
+ m_PropStatTable.Insert(PropModelStatsTableInit[i].szPropModelName, PropModelStatsTableInit[i].statType);
+ }
+
+ m_numberOfRoundsForDirectAverages = 0;
+ m_numberOfTerroristEntriesForDirectAverages = 0;
+ m_numberOfCounterTerroristEntriesForDirectAverages = 0;
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+// Input : -
+//-----------------------------------------------------------------------------
+CCSGameStats::~CCSGameStats()
+{
+ Clear();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Clear out game stats
+// Input : -
+//-----------------------------------------------------------------------------
+void CCSGameStats::Clear( void )
+{
+ m_PlayerSnipedPosition.Purge();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CCSGameStats::Init( void )
+{
+ ListenForGameEvent( "round_start" );
+ ListenForGameEvent( "round_end" );
+ ListenForGameEvent( "break_prop" );
+ ListenForGameEvent( "player_decal");
+ ListenForGameEvent( "hegrenade_detonate");
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::PostInit( void )
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::LevelShutdownPreClearSteamAPIContext( void )
+{
+ // If we have any unsent round stats we better send them now or we'll lose them
+ UploadRoundStats();
+
+ GetSteamWorksSGameStatsUploader().EndSession();
+}
+
+extern double g_rowCommitTime;
+extern double g_rowWriteTime;
+
+void CCSGameStats::UploadRoundStats( void )
+{
+ CFastTimer totalTimer, purchasesAndDeathsStatTimer, weaponsStatBuildTimer, weaponsStatSubmitTimer, submitTimer, cleanupTimer;
+
+ // Prevent uploading empty data
+ if ( !m_bInRound )
+ return;
+
+ if ( sv_noroundstats.GetBool() )
+ {
+ if ( sv_debugroundstats.GetBool() )
+ {
+ Msg( "sv_noroundstats is enabled. Not uploading round stats.\n" );
+ }
+
+ m_MarketPurchases.PurgeAndDeleteElements();
+ m_WeaponData.PurgeAndDeleteElements();
+ m_DeathData.PurgeAndDeleteElements();
+ return;
+ }
+
+ m_bInRound = false;
+
+ KeyValues *pKV = new KeyValues( "basedata" );
+ if ( !pKV )
+ return;
+
+ totalTimer.Start();
+ purchasesAndDeathsStatTimer.Start();
+
+ const char *pzMapName = gpGlobals->mapname.ToCStr();
+ pKV->SetString( "MapID", pzMapName );
+
+ for ( int k=0 ; k < m_MarketPurchases.Count() ; ++k )
+ SubmitStat( m_MarketPurchases[ k ] );
+
+ for ( int k=0 ; k < m_DeathData.Count() ; ++k )
+ SubmitStat( m_DeathData[ k ] );
+
+ purchasesAndDeathsStatTimer.End();
+ weaponsStatBuildTimer.Start();
+
+ // Now add the weapon stats that HPE collected
+ for (int iWeapon = 0; iWeapon < WEAPON_MAX; ++iWeapon)
+ {
+ CCSWeaponInfo* pInfo = GetWeaponInfo( (CSWeaponID)iWeapon );
+ if ( !pInfo )
+ continue;
+
+ const char* pWeaponName = pInfo->szClassName;
+ if ( !pWeaponName || !pWeaponName[0] || ( m_weaponStats[iWeapon][0].shots == 0 && m_weaponStats[iWeapon][1].shots == 0 ) )
+ continue;
+
+
+ m_WeaponData.AddToTail( new SCSSWeaponData( pWeaponName, m_weaponStats[iWeapon][0] ) );
+
+ // Now add the bot data if we're collecting detailled data
+ if ( GetSteamWorksSGameStatsUploader().IsCollectingDetails() )
+ {
+ char pWeaponNameModified[64];
+ V_snprintf( pWeaponNameModified, ARRAYSIZE(pWeaponNameModified), "%s_bot", pWeaponName );
+ m_WeaponData.AddToTail( new SCSSWeaponData( pWeaponNameModified, m_weaponStats[iWeapon][1] ) );
+ }
+ }
+
+ weaponsStatBuildTimer.End();
+ weaponsStatSubmitTimer.Start();
+
+ for ( int k=0 ; k < m_WeaponData.Count() ; ++k )
+ SubmitStat( m_WeaponData[ k ] );
+
+ weaponsStatSubmitTimer.End();
+
+ // Perform the actual submission
+ submitTimer.Start();
+ SubmitGameStats( pKV );
+ submitTimer.End();
+
+ int iPurchases, iDeathData, iWeaponData;
+ int listCount = s_StatLists->Count();
+ iPurchases = m_MarketPurchases.Count();
+ iDeathData = m_DeathData.Count();
+ iWeaponData = m_WeaponData.Count();
+
+ // Clear out the per round stats
+ cleanupTimer.Start();
+ m_MarketPurchases.Purge();
+ m_WeaponData.Purge();
+ m_DeathData.Purge();
+ pKV->deleteThis();
+ cleanupTimer.End();
+
+ totalTimer.End();
+
+ if ( sv_debugroundstats.GetBool() )
+ {
+ Msg( "**** ROUND STAT DEBUG ****\n" );
+ Msg( "UploadRoundStats completed. %.3f msec. Breakdown:\n a: %.3f msec\n b: %.3f msec\n c: %.3f msec\n d: %.3f msec\n e: %.3f msec\n objects: %d %d %d %d\n commit: %.3fms\n write: %.3fms.\n\n",
+ totalTimer.GetDuration().GetMillisecondsF(),
+ purchasesAndDeathsStatTimer.GetDuration().GetMillisecondsF(),
+ weaponsStatBuildTimer.GetDuration().GetMillisecondsF(),
+ weaponsStatSubmitTimer.GetDuration().GetMillisecondsF(),
+ submitTimer.GetDuration().GetMillisecondsF(),
+ cleanupTimer.GetDuration().GetMillisecondsF(),
+ iPurchases, iDeathData, iWeaponData, listCount,
+ g_rowCommitTime, g_rowWriteTime);
+ }
+}
+
+#ifdef _DEBUG
+CON_COMMAND ( teststats, "Test command" )
+{
+ CFastTimer totalTimer;
+ double uploadTime = 0.0f;
+ g_rowCommitTime = 0.0f;
+ g_rowWriteTime = 0.0f;
+
+
+ for( int i = 0; i < 1000; i++ )
+ {
+ KeyValues *pKV = new KeyValues( "basedata" );
+ if ( !pKV )
+ return;
+
+ pKV->SetName( "foobartest" );
+ pKV->SetUint64( "test1", 1234 );
+ pKV->SetUint64( "test2", 1234 );
+ pKV->SetUint64( "test3", 1234 );
+ pKV->SetUint64( "test4", 1234 );
+ pKV->SetString( "test5", "TEST1234567890TEST1234567890TEST!");
+ /*pKV->SetString( "test6", "TEST1234567890TEST1234567890TEST!");
+ pKV->SetString( "test7", "TEST1234567890TEST1234567890TEST!");
+ pKV->SetString( "test8", "TEST1234567890TEST1234567890TEST!");
+ pKV->SetString( "test9", "TEST1234567890TEST1234567890TEST!");*/
+
+ totalTimer.Start();
+ GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKV, args.ArgC() == 1 );
+ totalTimer.End();
+
+ uploadTime += totalTimer.GetDuration().GetMillisecondsF();
+ }
+
+ Msg( "teststats took %.3f msec commit: %.3fms write: %.3fms.\n", uploadTime, g_rowCommitTime, g_rowWriteTime );
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::SubmitGameStats( KeyValues *pKV )
+{
+ int listCount = s_StatLists->Count();
+ for( int i=0; i < listCount; ++i )
+ {
+ // Create a master key value that has stats everybody should share (map name, session ID, etc)
+ (*s_StatLists)[i]->SendData(pKV);
+ (*s_StatLists)[i]->Clear();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_ShotFired( CBasePlayer *pPlayer, CBaseCombatWeapon* pWeapon )
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+
+ CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(pWeapon);
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] adding tracking for weapon used fun fact
+ //=============================================================================
+
+ if ( pCSPlayer )
+ {
+ // [dwenger] Update the player's tracking of which weapon type they fired
+ pCSPlayer->PlayerUsedFirearm( pWeapon );
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ IncrementStat( pCSPlayer, CSSTAT_SHOTS_FIRED, 1 );
+ // Increment the individual weapon
+ if( pCSWeapon )
+ {
+ CSWeaponID weaponId = pCSWeapon->GetWeaponID();
+ for (int i = 0; WeaponName_StatId_Table[i].shotStatId != CSSTAT_UNDEFINED; ++i)
+ {
+ if ( WeaponName_StatId_Table[i].weaponId == weaponId )
+ {
+ IncrementStat( pCSPlayer, WeaponName_StatId_Table[i].shotStatId, 1 );
+ break;
+ }
+ }
+
+ int iType = pCSPlayer->IsBot();
+ ++m_weaponStats[weaponId][iType].shots;
+ }
+ }
+}
+
+void CCSGameStats::Event_ShotHit( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+
+ IncrementStat( pCSPlayer, CSSTAT_SHOTS_HIT, 1 );
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ if ( pInflictor )
+ {
+ if ( pInflictor == pPlayer )
+ {
+ if ( pPlayer->GetActiveWeapon() )
+ {
+ CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(pPlayer->GetActiveWeapon());
+ if (pCSWeapon)
+ {
+ CSWeaponID weaponId = pCSWeapon->GetWeaponID();
+ for (int i = 0; WeaponName_StatId_Table[i].shotStatId != CSSTAT_UNDEFINED; ++i)
+ {
+ if ( WeaponName_StatId_Table[i].weaponId == weaponId )
+ {
+ IncrementStat( pCSPlayer, WeaponName_StatId_Table[i].hitStatId, 1 );
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+void CCSGameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+
+ IncrementStat( pCSPlayer, CSSTAT_DEATHS, 1 );
+}
+
+void CCSGameStats::Event_PlayerSprayedDecal( CCSPlayer* pPlayer )
+{
+ IncrementStat( pPlayer, CSSTAT_DECAL_SPRAYS, 1 );
+}
+
+void CCSGameStats::Event_PlayerKilled_PreWeaponDrop( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+ CCSPlayer *pAttacker = ToCSPlayer( info.GetAttacker() );
+ bool victimZoomed = ( pCSPlayer->GetFOV() != pCSPlayer->GetDefaultFOV() );
+
+ if (victimZoomed)
+ {
+ IncrementStat(pAttacker, CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, 1);
+ }
+
+
+ //Check for knife fight
+ if (pAttacker && pCSPlayer && pAttacker == info.GetInflictor() && pAttacker->GetTeamNumber() != pCSPlayer->GetTeamNumber())
+ {
+ CWeaponCSBase* attackerWeapon = pAttacker->GetActiveCSWeapon();
+ CWeaponCSBase* victimWeapon = pCSPlayer->GetActiveCSWeapon();
+
+ if (attackerWeapon && victimWeapon)
+ {
+ CSWeaponID attackerWeaponID = attackerWeapon->GetWeaponID();
+ CSWeaponID victimWeaponID = victimWeapon->GetWeaponID();
+
+ if (attackerWeaponID == WEAPON_KNIFE && victimWeaponID == WEAPON_KNIFE)
+ {
+ IncrementStat(pAttacker, CSSTAT_KILLS_KNIFE_FIGHT, 1);
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_BombPlanted( CCSPlayer* pPlayer)
+{
+ IncrementStat( pPlayer, CSSTAT_NUM_BOMBS_PLANTED, 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_BombDefused( CCSPlayer* pPlayer)
+{
+
+ IncrementStat( pPlayer, CSSTAT_NUM_BOMBS_DEFUSED, 1 );
+ IncrementStat( pPlayer, CSSTAT_OBJECTIVES_COMPLETED, 1 );
+ if( pPlayer && pPlayer->HasDefuser() )
+ {
+ IncrementStat( pPlayer, CSSTAT_BOMBS_DEFUSED_WITHKIT, 1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Increment terrorist team stat
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_BombExploded( CCSPlayer* pPlayer )
+{
+ IncrementStat( pPlayer, CSSTAT_OBJECTIVES_COMPLETED, 1 );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_HostageRescued( CCSPlayer* pPlayer)
+{
+ IncrementStat( pPlayer, CSSTAT_NUM_HOSTAGES_RESCUED, 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Increment counter-terrorist team stat
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_AllHostagesRescued()
+{
+ IncrementTeamStat( TEAM_CT, CSSTAT_OBJECTIVES_COMPLETED, 1 );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_WindowShattered( CBasePlayer *pPlayer)
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+
+ IncrementStat( pCSPlayer, CSSTAT_NUM_BROKEN_WINDOWS, 1 );
+}
+
+
+void CCSGameStats::Event_BreakProp( CCSPlayer* pPlayer, CBreakableProp *pProp )
+{
+ if (!pPlayer)
+ return;
+
+ DevMsg("Player %s broke a %s (%i)\n", pPlayer->GetPlayerName(), pProp->GetModelName().ToCStr(), pProp->entindex());
+
+ int iIndex = m_PropStatTable.Find(pProp->GetModelName().ToCStr());
+ if (m_PropStatTable.IsValidIndex(iIndex))
+ {
+ IncrementStat(pPlayer, m_PropStatTable[iIndex], 1);
+ }
+ IncrementStat(pPlayer, CSSTAT_PROPSBROKEN_ALL, 1);
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::UpdatePlayerRoundStats(int winner)
+{
+ int mapIndex = GetCSLevelIndex(gpGlobals->mapname.ToCStr());
+ CSStatType_t mapStatWinIndex = CSSTAT_UNDEFINED, mapStatRoundIndex = CSSTAT_UNDEFINED;
+
+ if ( mapIndex != -1 )
+ {
+ mapStatWinIndex = MapName_StatId_Table[mapIndex].statWinsId;
+ mapStatRoundIndex = MapName_StatId_Table[mapIndex].statRoundsId;
+ }
+
+ // increment the team specific stats
+ IncrementTeamStat( winner, CSSTAT_ROUNDS_WON, 1 );
+ if ( mapStatWinIndex != CSSTAT_UNDEFINED )
+ {
+ IncrementTeamStat( winner, mapStatWinIndex, 1 );
+ }
+ if ( CSGameRules()->IsPistolRound() )
+ {
+ IncrementTeamStat( winner, CSSTAT_PISTOLROUNDS_WON, 1 );
+ }
+ IncrementTeamStat( TEAM_TERRORIST, CSSTAT_ROUNDS_PLAYED, 1 );
+ IncrementTeamStat( TEAM_CT, CSSTAT_ROUNDS_PLAYED, 1 );
+ IncrementTeamStat( TEAM_TERRORIST, mapStatRoundIndex, 1 );
+ IncrementTeamStat( TEAM_CT, mapStatRoundIndex, 1 );
+
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( pPlayer && pPlayer->IsConnected() )
+ {
+ if ( winner == TEAM_CT )
+ {
+ IncrementStat( pPlayer, CSSTAT_CT_ROUNDS_WON, 1, true );
+ }
+ else if ( winner == TEAM_TERRORIST )
+ {
+ IncrementStat( pPlayer, CSSTAT_T_ROUNDS_WON, 1, true );
+ }
+
+ if ( winner == TEAM_CT || winner == TEAM_TERRORIST )
+ {
+ // Increment the win stats if this player is on the winning team
+ if ( pPlayer->GetTeamNumber() == winner )
+ {
+ IncrementStat( pPlayer, CSSTAT_ROUNDS_WON, 1, true );
+
+ if ( CSGameRules()->IsPistolRound() )
+ {
+ IncrementStat( pPlayer, CSSTAT_PISTOLROUNDS_WON, 1, true );
+ }
+
+ if ( mapStatWinIndex != CSSTAT_UNDEFINED )
+ {
+ IncrementStat( pPlayer, mapStatWinIndex, 1, true );
+ }
+ }
+
+ IncrementStat( pPlayer, CSSTAT_ROUNDS_PLAYED, 1 );
+ if ( mapStatWinIndex != CSSTAT_UNDEFINED )
+ {
+ IncrementStat( pPlayer, mapStatRoundIndex, 1, true );
+ }
+
+ // set the play time for the round
+ IncrementStat( pPlayer, CSSTAT_PLAYTIME, (int)CSGameRules()->GetRoundElapsedTime(), true );
+ }
+ }
+ }
+
+ // send a stats update to all players
+ for ( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( pPlayer && pPlayer->IsConnected())
+ {
+ SendStatsToPlayer(pPlayer, CSSTAT_PRIORITY_ENDROUND);
+ }
+ }
+}
+
+void CCSGameStats::SendRollingStatsAveragesToAllPlayers()
+{
+ //The most stats we can send at once is the max message size minus the header, divided by the total size of each stat.
+ const int maxStatsPerMessage = (MAX_USER_MSG_DATA - sizeof(CSStatType_t)) / (3 * sizeof(float));
+
+ const int numMessagesNeeded = ((CSSTAT_MAX - CSSTAT_FIRST) / maxStatsPerMessage) + 1;
+
+ for (int batchIndex = 0 ; batchIndex < numMessagesNeeded ; ++batchIndex)
+ {
+ int firstStatInThisBatch = (batchIndex * maxStatsPerMessage) + CSSTAT_FIRST;
+ int lastMessageInThisBatch = firstStatInThisBatch + maxStatsPerMessage - 1;
+
+ CRecipientFilter filter;
+ filter.AddAllPlayers();
+ UserMessageBegin( filter, "MatchStatsUpdate" );
+
+ WRITE_SHORT(firstStatInThisBatch);
+
+ for ( int iStat = firstStatInThisBatch; iStat < CSSTAT_MAX && iStat <= lastMessageInThisBatch; ++iStat )
+ {
+ WRITE_FLOAT(m_rollingTStatAverages.m_fStat[iStat]);
+ WRITE_FLOAT(m_rollingCTStatAverages.m_fStat[iStat]);
+ WRITE_FLOAT(m_rollingPlayerStatAverages.m_fStat[iStat]);
+ }
+
+ MessageEnd();
+ }
+}
+
+
+void CCSGameStats::SendDirectStatsAveragesToAllPlayers()
+{
+ //The most stats we can send at once is the max message size minus the header, divided by the total size of each stat.
+ const int maxStatsPerMessage = (MAX_USER_MSG_DATA - sizeof(CSStatType_t)) / (3 * sizeof(float));
+
+ const int numMessagesNeeded = ((CSSTAT_MAX - CSSTAT_FIRST) / maxStatsPerMessage) + 1;
+
+ for (int batchIndex = 0 ; batchIndex < numMessagesNeeded ; ++batchIndex)
+ {
+ int firstStatInThisBatch = (batchIndex * maxStatsPerMessage) + CSSTAT_FIRST;
+ int lastMessageInThisBatch = firstStatInThisBatch + maxStatsPerMessage - 1;
+
+ CRecipientFilter filter;
+ filter.AddAllPlayers();
+ UserMessageBegin( filter, "MatchStatsUpdate" );
+
+ WRITE_SHORT(firstStatInThisBatch);
+
+ for ( int iStat = firstStatInThisBatch; iStat < CSSTAT_MAX && iStat <= lastMessageInThisBatch; ++iStat )
+ {
+ WRITE_FLOAT(m_directTStatAverages.m_fStat[iStat]);
+ WRITE_FLOAT(m_directCTStatAverages.m_fStat[iStat]);
+ WRITE_FLOAT(m_directPlayerStatAverages.m_fStat[iStat]);
+ }
+
+ MessageEnd();
+ }
+}
+
+void CCSGameStats::ComputeRollingStatAverages()
+{
+ int numPlayers = 0;
+ int numCTs = 0;
+ int numTs = 0;
+
+ RoundStatsRollingAverage_t currentRoundTStatsAverage;
+ RoundStatsRollingAverage_t currentRoundCTStatsAverage;
+ RoundStatsRollingAverage_t currentRoundPlayerStatsAverage;
+
+ currentRoundTStatsAverage.Reset();
+ currentRoundCTStatsAverage.Reset();
+ currentRoundPlayerStatsAverage.Reset();
+
+ //for ( int iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
+ {
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( pPlayer && pPlayer->IsConnected())
+ {
+ StatsCollection_t &roundStats = m_aPlayerStats[pPlayer->entindex()].statsCurrentRound;
+
+ int teamNumber = pPlayer->GetTeamNumber();
+ if (teamNumber == TEAM_CT)
+ {
+ numCTs++;
+ numPlayers++;
+ currentRoundCTStatsAverage += roundStats;
+ currentRoundPlayerStatsAverage += roundStats;
+ }
+ else if (teamNumber == TEAM_TERRORIST)
+ {
+ numTs++;
+ numPlayers++;
+ currentRoundTStatsAverage += roundStats;
+ currentRoundPlayerStatsAverage += roundStats;
+ }
+ }
+ }
+
+ if (numTs > 0)
+ {
+ currentRoundTStatsAverage /= numTs;
+ }
+
+ if (numCTs > 0)
+ {
+ currentRoundCTStatsAverage /= numCTs;
+ }
+
+ if (numPlayers > 0)
+ {
+ currentRoundPlayerStatsAverage /= numPlayers;
+ }
+
+ m_rollingTStatAverages.RollDataSetIntoAverage(currentRoundTStatsAverage);
+ m_rollingCTStatAverages.RollDataSetIntoAverage(currentRoundCTStatsAverage);
+ m_rollingPlayerStatAverages.RollDataSetIntoAverage(currentRoundPlayerStatsAverage);
+ }
+}
+
+
+void CCSGameStats::ComputeDirectStatAverages()
+{
+ m_numberOfRoundsForDirectAverages++;
+
+ m_directCTStatAverages.Reset();
+ m_directTStatAverages.Reset();
+ m_directPlayerStatAverages.Reset();
+
+
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( pPlayer && pPlayer->IsConnected())
+ {
+ StatsCollection_t &matchStats = m_aPlayerStats[pPlayer->entindex()].statsCurrentMatch;
+
+ int teamNumber = pPlayer->GetTeamNumber();
+ if (teamNumber == TEAM_CT)
+ {
+ m_numberOfCounterTerroristEntriesForDirectAverages++;
+ m_directCTStatAverages += matchStats;
+ m_directPlayerStatAverages += matchStats;
+ }
+ else if (teamNumber == TEAM_TERRORIST)
+ {
+ m_numberOfTerroristEntriesForDirectAverages++;
+ m_directTStatAverages += matchStats;
+ m_directPlayerStatAverages += matchStats;
+ }
+ }
+ }
+
+ if (m_numberOfTerroristEntriesForDirectAverages > 0)
+ {
+ m_directTStatAverages /= m_numberOfTerroristEntriesForDirectAverages;
+ m_directTStatAverages *= m_numberOfRoundsForDirectAverages;
+ }
+
+ if (m_numberOfCounterTerroristEntriesForDirectAverages > 0)
+ {
+ m_directCTStatAverages /= m_numberOfCounterTerroristEntriesForDirectAverages;
+ m_directCTStatAverages *= m_numberOfRoundsForDirectAverages;
+ }
+
+ int numPlayers = m_numberOfCounterTerroristEntriesForDirectAverages + m_numberOfTerroristEntriesForDirectAverages;
+
+ if (numPlayers > 0)
+ {
+ m_directPlayerStatAverages /= numPlayers;
+ m_directPlayerStatAverages *= m_numberOfRoundsForDirectAverages;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Log accumulated weapon usage and performance data
+//-----------------------------------------------------------------------------
+void CCSGameStats::DumpMatchWeaponMetrics()
+{
+ //// generate a filename
+ //time_t t = time( NULL );
+ //struct tm *now = localtime( &t );
+ //if ( !now )
+ // return;
+
+ //int year = now->tm_year + 1900;
+ //int month = now->tm_mon + 1;
+ //int day = now->tm_mday;
+ //int hour = now->tm_hour;
+ //int minute = now->tm_min;
+ //int second = now->tm_sec;
+
+ //char filename[ 128 ];
+ //Q_snprintf( filename, sizeof(filename), "wm_%4d%02d%02d_%02d%02d%02d_%s.csv",
+ // year, month, day, hour, minute, second, gpGlobals->mapname.ToCStr());
+
+ //FileHandle_t hLogFile = filesystem->Open( filename, "wt" );
+
+ //if ( hLogFile == FILESYSTEM_INVALID_HANDLE )
+ // return;
+
+ //filesystem->FPrintf(hLogFile, "%s\n", "WeaponId, Mode, Cost, Bullets, CycleTime, TotalShots, TotalHits, TotalDamage, TotalKills");
+
+ //for (int iWeapon = 0; iWeapon < WEAPON_MAX; ++iWeapon)
+ //{
+ // CCSWeaponInfo* pInfo = GetWeaponInfo( (CSWeaponID)iWeapon );
+ // if ( !pInfo )
+ // continue;
+
+ // const char* pWeaponName = pInfo->szClassName;
+ // if ( !pWeaponName )
+ // continue;
+
+ // if ( V_strncmp(pWeaponName, "weapon_", 7) == 0 )
+ // pWeaponName += 7;
+
+ // for ( int iMode = 0; iMode < WeaponMode_MAX; ++iMode)
+ // {
+ // filesystem->FPrintf(hLogFile, "%s, %d, %d, %d, %f, %d, %d, %d, %d\n",
+ // pWeaponName,
+ // iMode,
+ // pInfo->GetWeaponPrice(),
+ // pInfo->m_iBullets,
+ // pInfo->m_flCycleTime,
+ // m_weaponStats[iWeapon][iMode].shots,
+ // m_weaponStats[iWeapon][iMode].hits,
+ // m_weaponStats[iWeapon][iMode].damage,
+ // m_weaponStats[iWeapon][iMode].kills);
+ // }
+ //}
+
+ //filesystem->FPrintf(hLogFile, "\n");
+ //filesystem->FPrintf(hLogFile, "weapon_accuracy_model, %d\n", weapon_accuracy_model.GetInt());
+ //filesystem->FPrintf(hLogFile, "bot_difficulty, %d\n", cv_bot_difficulty.GetInt());
+
+ //g_pFullFileSystem->Close(hLogFile);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_PlayerConnected( CBasePlayer *pPlayer )
+{
+ ResetPlayerStats( ToCSPlayer( pPlayer ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_PlayerDisconnected( CBasePlayer *pPlayer )
+{
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+ if ( !pCSPlayer )
+ return;
+
+ ResetPlayerStats( pCSPlayer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
+{
+ // This also gets called when the victim is a building. That gets tracked separately as building destruction, don't count it here
+ if ( !pVictim->IsPlayer() )
+ return;
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ CCSPlayer *pPlayerAttacker = ToCSPlayer( pAttacker );
+ CCSPlayer *pPlayerVictim = ToCSPlayer( pVictim );
+
+ // keep track of how many times every player kills every other player
+ TrackKillStats( pPlayerAttacker, pPlayerVictim );
+
+ // Skip rest of stat reporting for friendly fire
+ if ( pPlayerAttacker->GetTeam() == pVictim->GetTeam() )
+ return;
+
+ CSWeaponID weaponId = WEAPON_NONE;
+
+ if ( pInflictor )
+ {
+ if ( pInflictor == pAttacker )
+ {
+ if ( pAttacker->GetActiveWeapon() )
+ {
+ CBaseCombatWeapon* weapon = pAttacker->GetActiveWeapon();
+ if (weapon)
+ {
+ CWeaponCSBase* pCSWeapon = static_cast<CWeaponCSBase*>(weapon);
+
+ weaponId = pCSWeapon->GetWeaponID();
+
+ CCSWeaponInfo *info = GetWeaponInfo(weaponId);
+
+ if (info && info->m_iTeam != TEAM_UNASSIGNED && pAttacker->GetTeamNumber() != info->m_iTeam )
+ {
+ IncrementStat(pPlayerAttacker, CSSTAT_KILLS_ENEMY_WEAPON, 1);
+ }
+ }
+ }
+ }
+ else
+ {
+ //In the case of grenades, the inflictor is the spawned grenade entity.
+ if ( V_strcmp(pInflictor->m_iClassname.ToCStr(), "hegrenade_projectile") == 0 )
+ weaponId = WEAPON_HEGRENADE;
+ }
+ }
+
+ // update weapon stats
+ ++m_weaponStats[weaponId][pPlayerAttacker->IsBot()].kills;
+
+ for (int i = 0; WeaponName_StatId_Table[i].killStatId != CSSTAT_UNDEFINED; ++i)
+ {
+ if ( WeaponName_StatId_Table[i].weaponId == weaponId )
+ {
+ IncrementStat( pPlayerAttacker, WeaponName_StatId_Table[i].killStatId, 1 );
+ break;
+ }
+ }
+
+ if (pPlayerVictim && pPlayerVictim->IsBlind())
+ {
+ IncrementStat( pPlayerAttacker, CSSTAT_KILLS_ENEMY_BLINDED, 1 );
+ }
+
+ if (pPlayerVictim && pPlayerAttacker && pPlayerAttacker->IsBlindForAchievement())
+ {
+ IncrementStat( pPlayerAttacker, CSSTAT_KILLS_WHILE_BLINDED, 1 );
+ }
+
+ // [sbodenbender] check for deaths near planted bomb for funfact
+ if (pPlayerVictim && pPlayerAttacker && pPlayerAttacker->GetTeamNumber() == TEAM_TERRORIST && CSGameRules()->m_bBombPlanted)
+ {
+ float bombCheckDistSq = AchievementConsts::KillEnemyNearBomb_MaxDistance * AchievementConsts::KillEnemyNearBomb_MaxDistance;
+ for ( int i=0; i < g_PlantedC4s.Count(); i++ )
+ {
+ CPlantedC4 *pC4 = g_PlantedC4s[i];
+
+ if ( pC4->IsBombActive() )
+ {
+ Vector bombPos = pC4->GetAbsOrigin();
+ Vector victimToBomb = pPlayerVictim->GetAbsOrigin() - bombPos;
+ Vector attackerToBomb = pPlayerAttacker->GetAbsOrigin() - bombPos;
+ if (victimToBomb.LengthSqr() < bombCheckDistSq || attackerToBomb.LengthSqr() < bombCheckDistSq)
+ {
+ IncrementStat(pPlayerAttacker, CSSTAT_KILLS_WHILE_DEFENDING_BOMB, 1);
+ break; // you only get credit for one kill even if you happen to be by more than one bomb
+ }
+ }
+ }
+ }
+
+ //Increment stat if this is a headshot.
+ if (info.GetDamageType() & DMG_HEADSHOT)
+ {
+ IncrementStat( pPlayerAttacker, CSSTAT_KILLS_HEADSHOT, 1 );
+ }
+
+ IncrementStat( pPlayerAttacker, CSSTAT_KILLS, 1 );
+
+ // we don't have a simple way (yet) to check if the victim actually just achieved The Unstoppable Force, so we
+ // award this achievement simply if they've met the requirements and would have received it.
+ PlayerStats_t &victimStats = m_aPlayerStats[pVictim->entindex()];
+ if (victimStats.statsCurrentRound[CSSTAT_KILLS] >= AchievementConsts::UnstoppableForce_Kills)
+ {
+ pPlayerAttacker->AwardAchievement(CSImmovableObject);
+ }
+
+ CCSGameRules::TeamPlayerCounts playerCounts[TEAM_MAXCOUNT];
+
+ CSGameRules()->GetPlayerCounts(playerCounts);
+ int iAttackerTeamNumber = pPlayerAttacker->GetTeamNumber() ;
+ if (playerCounts[iAttackerTeamNumber].totalAlivePlayers == 1 && playerCounts[iAttackerTeamNumber].killedPlayers >= 2)
+ {
+ IncrementStat(pPlayerAttacker, CSSTAT_KILLS_WHILE_LAST_PLAYER_ALIVE, 1);
+ }
+
+ //if they were damaged by more than one person that must mean that someone else did damage before the killer finished them off.
+ if (pPlayerVictim->GetNumEnemyDamagers() > 1)
+ {
+ IncrementStat(pPlayerAttacker, CSSTAT_KILLS_ENEMY_WOUNDED, 1);
+ }
+
+ // Let's check for the "Happy Camper" achievement where we snipe two players while standing in the same spot.
+ if ( pPlayerAttacker && !pPlayerAttacker->IsBot() && weaponId != WEAPON_NONE )
+ {
+ // Were we using a sniper rifle?
+ bool bUsingSniper = ( weaponId == WEAPON_AWP ||
+ weaponId == WEAPON_SCOUT ||
+ weaponId == WEAPON_SG550 ||
+ weaponId == WEAPON_G3SG1 );
+
+ // If we're zoomed in
+ if ( bUsingSniper && pPlayerAttacker->GetFOV() != pPlayerAttacker->GetDefaultFOV() )
+ {
+ // Get our position
+ Vector position = pPlayerAttacker->GetAbsOrigin();
+ int userid = pPlayerAttacker->GetUserID();
+
+ bool bFoundPlayerEntry = false;
+ FOR_EACH_LL( m_PlayerSnipedPosition, iElement )
+ {
+ sHappyCamperSnipePosition *pSnipePosition = &m_PlayerSnipedPosition[iElement];
+
+ // We've found this player's entry. Next, check to see if they meet the achievement criteria
+ if ( userid == pSnipePosition->m_iUserID )
+ {
+ bFoundPlayerEntry = true;
+ Vector posDif = position - pSnipePosition->m_vPos;
+
+ // Give a small epsilon to account for floating point anomalies
+ if ( posDif.IsLengthLessThan( .01f) )
+ {
+ pPlayerAttacker->AwardAchievement( CSSnipeTwoFromSameSpot );
+ }
+
+ // Update their position
+ pSnipePosition->m_vPos = position;
+ break;
+ }
+ }
+
+ // No entry so add one
+ if ( !bFoundPlayerEntry )
+ {
+ m_PlayerSnipedPosition.AddToTail( sHappyCamperSnipePosition( userid, position ) );
+ }
+ }
+ }
+}
+
+
+void CCSGameStats::CalculateOverkill(CCSPlayer* pAttacker, CCSPlayer* pVictim)
+{
+ //Count domination overkills - Do this before determining domination
+ if (pAttacker->GetTeam() != pVictim->GetTeam())
+ {
+ if (pAttacker->IsPlayerDominated(pVictim->entindex()))
+ {
+ IncrementStat( pAttacker, CSSTAT_DOMINATION_OVERKILLS, 1 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Steamworks Gamestats death tracking
+//-----------------------------------------------------------------------------
+void CCSGameStats::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info )
+{
+ if ( !pVictim )
+ return;
+
+ m_DeathData.AddToTail( new SCSSDeathData( pVictim, info ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stats event for giving damage to player
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info )
+{
+ CCSPlayer *pAttacker = ToCSPlayer( info.GetAttacker() );
+ if ( pAttacker && pAttacker->GetTeam() != pBasePlayer->GetTeam() )
+ {
+ CSWeaponMode weaponMode = Primary_Mode;
+ IncrementStat( pAttacker, CSSTAT_DAMAGE, info.GetDamage() );
+
+ if (pAttacker->m_bNightVisionOn)
+ {
+ IncrementStat( pAttacker, CSSTAT_NIGHTVISION_DAMAGE, info.GetDamage() );
+ }
+
+ const char *pWeaponName = NULL;
+
+ if ( info.GetInflictor() == info.GetAttacker() )
+ {
+ if ( pAttacker->GetActiveWeapon() )
+ {
+ CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(pAttacker->GetActiveWeapon());
+ if (pCSWeapon)
+ {
+ pWeaponName = pCSWeapon->GetClassname();
+ weaponMode = pCSWeapon->m_weaponMode;
+ }
+ }
+ }
+ // Need to determine the weapon name
+ else
+ {
+ pWeaponName = info.GetInflictor()->GetClassname();
+ }
+
+ // Unknown weapon?!?
+ if ( !pWeaponName )
+ return;
+
+ // Now update the damage this weapon has done
+ CSWeaponID weaponId = AliasToWeaponID( GetTranslatedWeaponAlias( pWeaponName ) );
+ for (int i = 0; WeaponName_StatId_Table[i].shotStatId != CSSTAT_UNDEFINED; ++i)
+ {
+ if ( weaponId == WeaponName_StatId_Table[i].weaponId )
+ {
+ int weaponId = WeaponName_StatId_Table[i].weaponId;
+ int iType = pAttacker->IsBot();
+ ++m_weaponStats[weaponId][iType].hits;
+ m_weaponStats[weaponId][iType].damage += info.GetDamage();
+ break;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stats event for giving money to player
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_MoneyEarned( CCSPlayer* pPlayer, int moneyEarned)
+{
+ if ( pPlayer && moneyEarned > 0)
+ {
+ IncrementStat(pPlayer, CSSTAT_MONEY_EARNED, moneyEarned);
+ }
+}
+
+void CCSGameStats::Event_MoneySpent( CCSPlayer* pPlayer, int moneySpent, const char *pItemName )
+{
+ if ( pPlayer && moneySpent > 0)
+ {
+ IncrementStat(pPlayer, CSSTAT_MONEY_SPENT, moneySpent);
+ if ( pItemName && !pPlayer->IsBot() )
+ {
+ CSteamID steamIDForBuyer;
+ pPlayer->GetSteamID( &steamIDForBuyer );
+ m_MarketPurchases.AddToTail( new SMarketPurchases( steamIDForBuyer.ConvertToUint64(), moneySpent, pItemName ) );
+ }
+ }
+}
+
+void CCSGameStats::Event_PlayerDonatedWeapon (CCSPlayer* pPlayer)
+{
+ if (pPlayer)
+ {
+ IncrementStat(pPlayer, CSSTAT_WEAPONS_DONATED, 1);
+ }
+}
+
+void CCSGameStats::Event_MVPEarned( CCSPlayer* pPlayer )
+{
+ if (pPlayer)
+ {
+ IncrementStat(pPlayer, CSSTAT_MVPS, 1);
+ }
+}
+
+void CCSGameStats::Event_KnifeUse( CCSPlayer* pPlayer, bool bStab, int iDamage )
+{
+ if (pPlayer)
+ {
+ int weaponId = WEAPON_KNIFE;
+ int iType = pPlayer->IsBot();
+
+ IncrementStat( pPlayer, CSSTAT_SHOTS_KNIFE, 1 );
+ ++m_weaponStats[weaponId][iType].shots;
+ if ( iDamage )
+ {
+ IncrementStat( pPlayer, CSSTAT_HITS_KNIFE, 1 );
+ ++m_weaponStats[weaponId][iType].hits;
+
+ IncrementStat( pPlayer, CSSTAT_DAMAGE_KNIFE, iDamage );
+ m_weaponStats[weaponId][iType].damage += iDamage;
+ }
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose: Event handler
+//-----------------------------------------------------------------------------
+void CCSGameStats::FireGameEvent( IGameEvent *event )
+{
+ const char *pEventName = event->GetName();
+
+ if ( V_strcmp(pEventName, "round_start") == 0 )
+ {
+ m_PlayerSnipedPosition.Purge();
+ m_bInRound = true;
+ }
+ else if ( V_strcmp(pEventName, "round_end") == 0 )
+ {
+ const int reason = event->GetInt( "reason" );
+
+ if( reason == Game_Commencing )
+ {
+ ResetPlayerClassMatchStats();
+ }
+ else
+ {
+ UpdatePlayerRoundStats(event->GetInt("winner"));
+ ComputeDirectStatAverages();
+ SendDirectStatsAveragesToAllPlayers();
+
+ UploadRoundStats();
+ ResetWeaponStats();
+ }
+ return;
+ }
+ else if ( V_strcmp(pEventName, "break_prop") == 0 )
+ {
+ int userid = event->GetInt("userid", 0);
+ int entindex = event->GetInt("entindex", 0);
+ CBreakableProp* pProp = static_cast<CBreakableProp*>(CBaseEntity::Instance(entindex));
+ Event_BreakProp(ToCSPlayer(UTIL_PlayerByUserId(userid)), pProp);
+ }
+ else if ( V_strcmp(pEventName, "player_decal") == 0 )
+ {
+ int userid = event->GetInt("userid", 0);
+ Event_PlayerSprayedDecal(ToCSPlayer(UTIL_PlayerByUserId(userid)));
+ }
+ else if ( V_strcmp(pEventName, "hegrenade_detonate") == 0 )
+ {
+ int userid = event->GetInt("userid", 0);
+ IncrementStat( ToCSPlayer(UTIL_PlayerByUserId(userid)), CSSTAT_SHOTS_HEGRENADE, 1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return stats for the given player
+//-----------------------------------------------------------------------------
+const PlayerStats_t& CCSGameStats::FindPlayerStats( CBasePlayer *pPlayer ) const
+{
+ return m_aPlayerStats[pPlayer->entindex()];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return stats for the given team
+//-----------------------------------------------------------------------------
+const StatsCollection_t& CCSGameStats::GetTeamStats( int iTeamIndex ) const
+{
+ int arrayIndex = iTeamIndex - FIRST_GAME_TEAM;
+ Assert( arrayIndex >= 0 && arrayIndex < TEAM_MAXCOUNT - FIRST_GAME_TEAM );
+ return m_aTeamStats[arrayIndex];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets the stats for each team
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetAllTeamStats()
+{
+ for ( int i = 0; i < ARRAYSIZE(m_aTeamStats); ++i )
+ {
+ m_aTeamStats[i].Reset();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets all stats (including round, match, accumulated and rolling averages
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetAllStats()
+{
+ for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
+ {
+ m_aPlayerStats[i].statsDelta.Reset();
+ m_aPlayerStats[i].statsCurrentRound.Reset();
+ m_aPlayerStats[i].statsCurrentMatch.Reset();
+ m_rollingCTStatAverages.Reset();
+ m_rollingTStatAverages.Reset();
+ m_rollingPlayerStatAverages.Reset();
+ m_numberOfRoundsForDirectAverages = 0;
+ m_numberOfTerroristEntriesForDirectAverages = 0;
+ m_numberOfCounterTerroristEntriesForDirectAverages = 0;
+ }
+}
+
+
+void CCSGameStats::ResetWeaponStats()
+{
+ V_memset(m_weaponStats, 0, sizeof(m_weaponStats));
+}
+
+void CCSGameStats::IncrementTeamStat( int iTeamIndex, int iStatIndex, int iAmount )
+{
+ int arrayIndex = iTeamIndex - TEAM_TERRORIST;
+ Assert( iStatIndex >= 0 && iStatIndex < CSSTAT_MAX );
+ if( arrayIndex >= 0 && arrayIndex < TEAM_MAXCOUNT - TEAM_TERRORIST )
+ {
+ m_aTeamStats[arrayIndex][iStatIndex] += iAmount;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets all stats for this player
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetPlayerStats( CBasePlayer* pPlayer )
+{
+ PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
+ // reset the stats on this player
+ stats.Reset();
+ // reset the matrix of who killed whom with respect to this player
+ ResetKillHistory( pPlayer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets the kill history for this player
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetKillHistory( CBasePlayer* pPlayer )
+{
+ int iPlayerIndex = pPlayer->entindex();
+
+ PlayerStats_t& statsPlayer = m_aPlayerStats[iPlayerIndex];
+
+ // for every other player, set all all the kills with respect to this player to 0
+ for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
+ {
+ //reset their record of us.
+ PlayerStats_t &statsOther = m_aPlayerStats[i];
+ statsOther.statsKills.iNumKilled[iPlayerIndex] = 0;
+ statsOther.statsKills.iNumKilledBy[iPlayerIndex] = 0;
+ statsOther.statsKills.iNumKilledByUnanswered[iPlayerIndex] = 0;
+
+ //reset our record of them
+ statsPlayer.statsKills.iNumKilled[i] = 0;
+ statsPlayer.statsKills.iNumKilledBy[i] = 0;
+ statsPlayer.statsKills.iNumKilledByUnanswered[i] = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets per-round stats for all players
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetRoundStats()
+{
+ for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
+ {
+ m_aPlayerStats[i].statsCurrentRound.Reset();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Increments specified stat for specified player by specified amount
+//-----------------------------------------------------------------------------
+void CCSGameStats::IncrementStat( CCSPlayer* pPlayer, CSStatType_t statId, int iDelta, bool bPlayerOnly /* = false */ )
+{
+ if ( pPlayer )
+ {
+ PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
+ stats.statsDelta[statId] += iDelta;
+ stats.statsCurrentRound[statId] += iDelta;
+ stats.statsCurrentMatch[statId] += iDelta;
+
+ // increment team stat
+ int teamIndex = pPlayer->GetTeamNumber() - FIRST_GAME_TEAM;
+ if ( !bPlayerOnly && teamIndex >= 0 && teamIndex < ARRAYSIZE(m_aTeamStats) )
+ {
+ m_aTeamStats[teamIndex][statId] += iDelta;
+ }
+
+ for (int i = 0; i < ARRAYSIZE(ServerStatBasedAchievements); ++i)
+ {
+ if (ServerStatBasedAchievements[i].statId == statId)
+ {
+ // skip this if there is a map filter and it doesn't match
+ if (ServerStatBasedAchievements[i].mapFilter != NULL && V_strcmp(gpGlobals->mapname.ToCStr(), ServerStatBasedAchievements[i].mapFilter) != 0)
+ continue;
+
+ bool bWasMet = ServerStatBasedAchievements[i].IsMet(stats.statsCurrentRound[statId] - iDelta, stats.statsCurrentMatch[statId] - iDelta);
+ bool bIsMet = ServerStatBasedAchievements[i].IsMet(stats.statsCurrentRound[statId], stats.statsCurrentMatch[statId]);
+ if (!bWasMet && bIsMet)
+ {
+ pPlayer->AwardAchievement(ServerStatBasedAchievements[i].achievementId);
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the specified stat for specified player to the specified amount
+//-----------------------------------------------------------------------------
+void CCSGameStats::SetStat( CCSPlayer *pPlayer, CSStatType_t statId, int iValue )
+{
+ if (pPlayer)
+ {
+ int oldRoundValue, oldMatchValue;
+ PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
+
+ oldRoundValue = stats.statsCurrentRound[statId];
+ oldMatchValue = stats.statsCurrentMatch[statId];
+
+ stats.statsDelta[statId] = iValue;
+ stats.statsCurrentRound[statId] = iValue;
+ stats.statsCurrentMatch[statId] = iValue;
+
+ for (int i = 0; i < ARRAYSIZE(ServerStatBasedAchievements); ++i)
+ {
+ if (ServerStatBasedAchievements[i].statId == statId)
+ {
+ // skip this if there is a map filter and it doesn't match
+ if (ServerStatBasedAchievements[i].mapFilter != NULL && V_strcmp(gpGlobals->mapname.ToCStr(), ServerStatBasedAchievements[i].mapFilter) != 0)
+ continue;
+
+ bool bWasMet = ServerStatBasedAchievements[i].IsMet(oldRoundValue, oldMatchValue);
+ bool bIsMet = ServerStatBasedAchievements[i].IsMet(stats.statsCurrentRound[statId], stats.statsCurrentMatch[statId]);
+ if (!bWasMet && bIsMet)
+ {
+ pPlayer->AwardAchievement(ServerStatBasedAchievements[i].achievementId);
+ }
+ }
+ }
+ }
+}
+
+void CCSGameStats::SendStatsToPlayer( CCSPlayer * pPlayer, int iMinStatPriority )
+{
+ ASSERT(CSSTAT_MAX < 255); // if we add more than 255 stats, we'll need to update this protocol
+ if ( pPlayer && pPlayer->IsConnected())
+ {
+ StatsCollection_t &deltaStats = m_aPlayerStats[pPlayer->entindex()].statsDelta;
+
+ // check to see if we have any stats to actually send
+ byte iStatsToSend = 0;
+ for ( int iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
+ {
+ ASSERT(CSStatProperty_Table[iStat].statId == iStat);
+ if ( CSStatProperty_Table[iStat].statId != iStat )
+ {
+ Warning( "CSStatProperty_Table[iStat].statId != iStat, (%d)", CSStatProperty_Table[iStat].statId );
+ }
+ int iPriority = CSStatProperty_Table[iStat].flags & CSSTAT_PRIORITY_MASK;
+ if (deltaStats[iStat] != 0 && iPriority >= iMinStatPriority)
+ {
+ ++iStatsToSend;
+ }
+ }
+
+ // nothing changed - bail out
+ if ( !iStatsToSend )
+ return;
+
+ CSingleUserRecipientFilter filter( pPlayer );
+ filter.MakeReliable();
+ UserMessageBegin( filter, "PlayerStatsUpdate" );
+
+ CRC32_t crc;
+ CRC32_Init( &crc );
+
+ // begin the CRC with a trivially hidden key value to discourage packet modification
+ const uint32 key = 0x82DA9F4C; // this key should match the key in cs_client_gamestats.cpp
+ CRC32_ProcessBuffer( &crc, &key, sizeof(key));
+
+ // if we make any change to the ordering of the stats or this message format, update this value
+ const byte version = 0x01;
+ CRC32_ProcessBuffer( &crc, &version, sizeof(version));
+ WRITE_BYTE(version);
+
+ CRC32_ProcessBuffer( &crc, &iStatsToSend, sizeof(iStatsToSend));
+ WRITE_BYTE(iStatsToSend);
+
+ for ( byte iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
+ {
+ int iPriority = CSStatProperty_Table[iStat].flags & CSSTAT_PRIORITY_MASK;
+ if (deltaStats[iStat] != 0 && iPriority >= iMinStatPriority)
+ {
+ CRC32_ProcessBuffer( &crc, &iStat, sizeof(iStat));
+ WRITE_BYTE(iStat);
+ Assert(deltaStats[iStat] <= 0x7FFF && deltaStats[iStat] > 0); // make sure we aren't truncating bits
+ short delta = deltaStats[iStat];
+ CRC32_ProcessBuffer( &crc, &delta, sizeof(delta));
+ WRITE_SHORT( deltaStats[iStat]);
+ deltaStats[iStat] = 0;
+ --iStatsToSend;
+ }
+ }
+
+ Assert(iStatsToSend == 0);
+
+ CRC32_Final( &crc );
+ WRITE_LONG(crc);
+
+ MessageEnd();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sends intermittent stats updates for stats that need to be updated during a round and/or life
+//-----------------------------------------------------------------------------
+void CCSGameStats::PreClientUpdate()
+{
+ int iMinStatPriority = -1;
+ m_fDisseminationTimerHigh += gpGlobals->frametime;
+ m_fDisseminationTimerLow += gpGlobals->frametime;
+
+ if ( m_fDisseminationTimerHigh > cDisseminationTimeHigh)
+ {
+ iMinStatPriority = CSSTAT_PRIORITY_HIGH;
+ m_fDisseminationTimerHigh = 0.0f;
+
+ if ( m_fDisseminationTimerLow > cDisseminationTimeLow)
+ {
+ iMinStatPriority = CSSTAT_PRIORITY_LOW;
+ m_fDisseminationTimerLow = 0.0f;
+ }
+ }
+ else
+ return;
+
+ //The proper time has elapsed, now send the update to every player
+ for ( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex(iPlayerIndex) );
+ SendStatsToPlayer(pPlayer, iMinStatPriority);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the stats of who has killed whom
+//-----------------------------------------------------------------------------
+void CCSGameStats::TrackKillStats( CCSPlayer *pAttacker, CCSPlayer *pVictim )
+{
+ int iPlayerIndexAttacker = pAttacker->entindex();
+ int iPlayerIndexVictim = pVictim->entindex();
+
+ PlayerStats_t &statsAttacker = m_aPlayerStats[iPlayerIndexAttacker];
+ PlayerStats_t &statsVictim = m_aPlayerStats[iPlayerIndexVictim];
+
+ statsVictim.statsKills.iNumKilledBy[iPlayerIndexAttacker]++;
+ statsVictim.statsKills.iNumKilledByUnanswered[iPlayerIndexAttacker]++;
+ statsAttacker.statsKills.iNumKilled[iPlayerIndexVictim]++;
+ statsAttacker.statsKills.iNumKilledByUnanswered[iPlayerIndexVictim] = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines if attacker and victim have gotten domination or revenge
+//-----------------------------------------------------------------------------
+void CCSGameStats::CalcDominationAndRevenge( CCSPlayer *pAttacker, CCSPlayer *pVictim, int *piDeathFlags )
+{
+ //=============================================================================
+ // HPE_BEGIN:
+ // [Forrest] Allow nemesis/revenge to be turned off for a server
+ //=============================================================================
+ if ( sv_nonemesis.GetBool() )
+ {
+ return;
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ //If there is no attacker, there is no domination or revenge
+ if( !pAttacker || !pVictim )
+ {
+ return;
+ }
+ if (pAttacker->GetTeam() == pVictim->GetTeam())
+ {
+ return;
+ }
+ int iPlayerIndexVictim = pVictim->entindex();
+ PlayerStats_t &statsVictim = m_aPlayerStats[iPlayerIndexVictim];
+ // calculate # of unanswered kills between killer & victim
+ // This is plus 1 as this function gets called before the stat is updated. That is done so that the domination
+ // and revenge will be calculated prior to the death message being sent to the clients
+ int attackerEntityIndex = pAttacker->entindex();
+ int iKillsUnanswered = statsVictim.statsKills.iNumKilledByUnanswered[attackerEntityIndex] + 1;
+
+ if ( CS_KILLS_FOR_DOMINATION == iKillsUnanswered )
+ {
+ // this is the Nth unanswered kill between killer and victim, killer is now dominating victim
+ *piDeathFlags |= ( CS_DEATH_DOMINATION );
+ }
+ else if ( pVictim->IsPlayerDominated( pAttacker->entindex() ) )
+ {
+ // the killer killed someone who was dominating him, gains revenge
+ *piDeathFlags |= ( CS_DEATH_REVENGE );
+ }
+
+ //Check the overkill on 1 player achievement
+ if (iKillsUnanswered == CS_KILLS_FOR_DOMINATION + AchievementConsts::ExtendedDomination_AdditionalKills)
+ {
+ pAttacker->AwardAchievement(CSExtendedDomination);
+ }
+
+ if (iKillsUnanswered == CS_KILLS_FOR_DOMINATION)
+ {
+ //this is the Nth unanswered kill between killer and victim, killer is now dominating victim
+ //set victim to be dominated by killer
+ pAttacker->SetPlayerDominated( pVictim, true );
+
+ //Check concurrent dominations achievement
+ int numConcurrentDominations = 0;
+ for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
+ {
+ CCSPlayer *pPlayer= ToCSPlayer( UTIL_PlayerByIndex( i ) );
+ if (pPlayer && pAttacker->IsPlayerDominated(pPlayer->entindex()))
+ {
+ numConcurrentDominations++;
+ }
+ }
+ if (numConcurrentDominations >= AchievementConsts::ConcurrentDominations_MinDominations)
+ {
+ pAttacker->AwardAchievement(CSConcurrentDominations);
+ }
+
+
+ // record stats
+ Event_PlayerDominatedOther( pAttacker, pVictim );
+ }
+ else if ( pVictim->IsPlayerDominated( pAttacker->entindex() ) )
+ {
+ // the killer killed someone who was dominating him, gains revenge
+ // set victim to no longer be dominating the killer
+
+ pVictim->SetPlayerDominated( pAttacker, false );
+ // record stats
+ Event_PlayerRevenge( pAttacker );
+ }
+}
+
+void CCSGameStats::Event_PlayerDominatedOther( CCSPlayer *pAttacker, CCSPlayer* pVictim )
+{
+ IncrementStat( pAttacker, CSSTAT_DOMINATIONS, 1 );
+}
+
+void CCSGameStats::Event_PlayerRevenge( CCSPlayer *pAttacker )
+{
+ IncrementStat( pAttacker, CSSTAT_REVENGES, 1 );
+}
+
+void CCSGameStats::Event_PlayerAvengedTeammate( CCSPlayer* pAttacker, CCSPlayer* pAvengedPlayer )
+{
+ if (pAttacker && pAvengedPlayer)
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_avenged_teammate" );
+
+ if ( event )
+ {
+ event->SetInt( "avenger_id", pAttacker->GetUserID() );
+ event->SetInt( "avenged_player_id", pAvengedPlayer->GetUserID() );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+}
+
+void CCSGameStats::Event_LevelInit()
+{
+ ResetAllTeamStats();
+ ResetWeaponStats();
+ CBaseGameStats::Event_LevelInit();
+ GetSteamWorksSGameStatsUploader().StartSession();
+}
+
+void CCSGameStats::Event_LevelShutdown( float fElapsed )
+{
+ DumpMatchWeaponMetrics();
+ CBaseGameStats::Event_LevelShutdown(fElapsed);
+}
+
+// Reset any per match info that resides in the player class
+void CCSGameStats::ResetPlayerClassMatchStats()
+{
+ for ( int i = 1; i <= MAX_PLAYERS; i++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
+
+ if ( pPlayer )
+ {
+ pPlayer->SetNumMVPs( 0 );
+ }
+ }
+}