2020-04-22 18:56:21 +02:00
//========= 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"
2024-09-06 14:43:27 +02:00
# include "cs_weapon_parse.h"
2020-04-22 18:56:21 +02:00
# 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 | |
2024-09-06 14:43:27 +02:00
weaponId = = WEAPON_G3SG1 | |
weaponId = = WEAPON_M82A1 ) ;
2020-04-22 18:56:21 +02:00
// 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 ) ;
}
}
}