|
|
//========= 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 ); } } }
|