You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1721 lines
56 KiB
1721 lines
56 KiB
//========= 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 ); |
|
} |
|
} |
|
}
|
|
|