//========= Copyright Valve Corporation, All rights reserved. ============// //------------------------------------------------------------- // File: cs_client_gamestats.cpp // Desc: Manages client side stat storage, accumulation, and access // Author: Peter Freese // Date: 2009/09/11 // Copyright: © 2009 Hidden Path Entertainment // // Keywords: //------------------------------------------------------------- #include "cbase.h" #include "cs_client_gamestats.h" #include "achievementmgr.h" #include "engine/imatchmaking.h" #include "ipresence.h" #include "usermessages.h" #include "c_cs_player.h" #include "achievements_cs.h" #include "vgui/ILocalize.h" #include "c_team.h" #include "../shared/steamworks_gamestats.h" CCSClientGameStats g_CSClientGameStats; void MsgFunc_PlayerStatsUpdate( bf_read &msg ) { g_CSClientGameStats.MsgFunc_PlayerStatsUpdate(msg); } void MsgFunc_MatchStatsUpdate( bf_read &msg ) { g_CSClientGameStats.MsgFunc_MatchStatsUpdate(msg); } CCSClientGameStats::StatContainerList_t* CCSClientGameStats::s_StatLists = new CCSClientGameStats::StatContainerList_t(); //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CCSClientGameStats::CCSClientGameStats() { m_bSteamStatsDownload = false; } //----------------------------------------------------------------------------- // Purpose: called at init time after all systems are init'd. We have to // do this in PostInit because the Steam app ID is not available earlier //----------------------------------------------------------------------------- void CCSClientGameStats::PostInit() { // listen for events ListenForGameEvent( "player_stats_updated" ); ListenForGameEvent( "user_data_downloaded" ); ListenForGameEvent( "round_end" ); ListenForGameEvent( "round_start" ); usermessages->HookMessage( "PlayerStatsUpdate", ::MsgFunc_PlayerStatsUpdate ); usermessages->HookMessage( "MatchStatsUpdate", ::MsgFunc_MatchStatsUpdate ); GetSteamWorksSGameStatsUploader().StartSession(); m_RoundEndReason = Invalid_Round_End_Reason; } //----------------------------------------------------------------------------- // Purpose: called at level shutdown //----------------------------------------------------------------------------- void CCSClientGameStats::LevelShutdownPreEntity() { // This is a good opportunity to update our last match stats UpdateLastMatchStats(); // upload user stats to Steam on every map change UpdateSteamStats(); } //----------------------------------------------------------------------------- // Purpose: called at app shutdown //----------------------------------------------------------------------------- void CCSClientGameStats::Shutdown() { GetSteamWorksSGameStatsUploader().EndSession(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCSClientGameStats::LevelShutdownPreClearSteamAPIContext( void ) { UploadRoundData(); } //----------------------------------------------------------------------------- // Purpose: called when the stats have changed in-game //----------------------------------------------------------------------------- void CCSClientGameStats::FireGameEvent( IGameEvent *event ) { const char *pEventName = event->GetName(); if ( 0 == Q_strcmp( pEventName, "player_stats_updated" ) ) { UpdateSteamStats(); } else if ( 0 == Q_strcmp( pEventName, "user_data_downloaded" ) ) { RetrieveSteamStats(); } else if ( Q_strcmp( pEventName, "round_end" ) == 0 ) { // Store off the reason the round ended. Used with the OGS data. m_RoundEndReason = event->GetInt( "reason", Invalid_Round_End_Reason ); // update player count for last match stats int iCurrentPlayerCount = 0; if ( GetGlobalTeam(TEAM_CT) != NULL ) iCurrentPlayerCount += GetGlobalTeam(TEAM_CT)->Get_Number_Players(); if ( GetGlobalTeam(TEAM_TERRORIST) != NULL ) iCurrentPlayerCount += GetGlobalTeam(TEAM_TERRORIST)->Get_Number_Players(); m_matchMaxPlayerCount = MAX(m_matchMaxPlayerCount, iCurrentPlayerCount); } // The user stats for a round get sent piece meal, so we'll wait until a new round starts // before sending the previous round stats. else if ( Q_strcmp( pEventName, "round_start" ) == 0 && m_roundStats[CSSTAT_PLAYTIME] > 0 ) { SRoundData *pRoundStatData = new SRoundData( &m_roundStats); C_CSPlayer *pPlayer = ToCSPlayer( C_BasePlayer::GetLocalPlayer() ); if ( pPlayer ) { // Our current money + what we spent is what we started with at the beginning of round pRoundStatData->nStartingMoney = pPlayer->GetAccount() + m_roundStats[CSSTAT_MONEY_SPENT]; } m_RoundStatData.AddToTail( pRoundStatData ); UploadRoundData(); m_roundStats.Reset(); } } void CCSClientGameStats::RetrieveSteamStats() { Assert( steamapicontext->SteamUserStats() ); if ( !steamapicontext->SteamUserStats() ) return; // we shouldn't be downloading stats more than once Assert(m_bSteamStatsDownload == false); if (m_bSteamStatsDownload) return; int nStatFailCount = 0; for ( int i = 0; i < CSSTAT_MAX; ++i ) { if ( CSStatProperty_Table[i].szSteamName == NULL ) continue; int iData; if ( steamapicontext->SteamUserStats()->GetStat( CSStatProperty_Table[i].szSteamName, &iData ) ) { m_lifetimeStats[CSStatProperty_Table[i].statId] = iData; } else { ++nStatFailCount; } } if ( nStatFailCount > 0 ) { Msg("RetrieveSteamStats: failed to get %i stats\n", nStatFailCount); return; } IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" ); if ( event ) { gameeventmanager->FireEventClientSide( event ); } m_bSteamStatsDownload = true; } //----------------------------------------------------------------------------- // Purpose: Uploads stats for current Steam user to Steam //----------------------------------------------------------------------------- void CCSClientGameStats::UpdateSteamStats() { // only upload if Steam is running if ( !steamapicontext->SteamUserStats() ) return; CAchievementMgr *pAchievementMgr = dynamic_cast( engine->GetAchievementMgr() ); Assert(pAchievementMgr != NULL); if (!pAchievementMgr) return; // don't upload any stats if we haven't successfully download stats yet if ( !m_bSteamStatsDownload ) { // try to periodically download stats from Steam if we haven't gotten them yet static float fLastStatsRetrieveTime = 0.0f; const float kRetrieveInterval = 30.0f; if ( gpGlobals->curtime > fLastStatsRetrieveTime + kRetrieveInterval ) { pAchievementMgr->DownloadUserData(); fLastStatsRetrieveTime = gpGlobals->curtime; } return; } for ( int i = 0; i < CSSTAT_MAX; ++i ) { if ( CSStatProperty_Table[i].szSteamName == NULL ) continue; // set the stats locally in Steam client steamapicontext->SteamUserStats()->SetStat( CSStatProperty_Table[i].szSteamName, m_lifetimeStats[CSStatProperty_Table[i].statId]); } // let the achievement manager know the stats have changed pAchievementMgr->SetDirty(true); } CON_COMMAND_F( stats_reset, "Resets all player stats", FCVAR_CLIENTDLL ) { g_CSClientGameStats.ResetAllStats(); } CON_COMMAND_F( stats_dump, "Dumps all player stats", FCVAR_DEVELOPMENTONLY ) { Msg( "Accumulated stats on Steam\n"); const StatsCollection_t& accumulatedStats = g_CSClientGameStats.GetLifetimeStats(); for ( int i = 0; i < CSSTAT_MAX; ++i ) { if ( CSStatProperty_Table[i].szSteamName == NULL ) continue; Msg( "%42s: %i\n", CSStatProperty_Table[i].szSteamName, accumulatedStats[CSStatProperty_Table[i].statId]); } } #if defined(_DEBUG) CON_COMMAND_F( stats_preload, "Load stats with data ripe for getting achievmenets", FCVAR_DEVELOPMENTONLY ) { struct DataSet { CSStatType_t statId; int value; }; DataSet statData[] = { { CSSTAT_KILLS, 9999}, { CSSTAT_ROUNDS_WON, 4999}, { CSSTAT_PISTOLROUNDS_WON, 249}, { CSSTAT_MONEY_EARNED, 49999999}, { CSSTAT_DAMAGE, 999999}, { CSSTAT_KILLS_DEAGLE, 199}, { CSSTAT_KILLS_USP, 199}, { CSSTAT_KILLS_GLOCK, 199}, { CSSTAT_KILLS_P228, 199}, { CSSTAT_KILLS_ELITE, 99}, { CSSTAT_KILLS_FIVESEVEN, 99}, { CSSTAT_KILLS_AWP, 999}, { CSSTAT_KILLS_AK47, 999}, { CSSTAT_KILLS_M4A1, 999}, { CSSTAT_KILLS_AUG, 499}, { CSSTAT_KILLS_SG552, 499}, { CSSTAT_KILLS_SG550, 499}, { CSSTAT_KILLS_GALIL, 499}, { CSSTAT_KILLS_FAMAS, 499}, { CSSTAT_KILLS_SCOUT, 999}, { CSSTAT_KILLS_G3SG1, 499}, { CSSTAT_KILLS_P90, 999}, { CSSTAT_KILLS_MP5NAVY, 999}, { CSSTAT_KILLS_TMP, 499}, { CSSTAT_KILLS_MAC10, 499}, { CSSTAT_KILLS_UMP45, 999}, { CSSTAT_KILLS_M3, 199}, { CSSTAT_KILLS_XM1014, 199}, { CSSTAT_KILLS_M249, 499}, { CSSTAT_KILLS_KNIFE, 99}, { CSSTAT_KILLS_HEGRENADE, 499}, { CSSTAT_KILLS_HEADSHOT, 249}, { CSSTAT_KILLS_ENEMY_WEAPON, 99}, { CSSTAT_KILLS_ENEMY_BLINDED, 24}, { CSSTAT_NUM_BOMBS_DEFUSED, 99}, { CSSTAT_NUM_BOMBS_PLANTED, 99}, { CSSTAT_NUM_HOSTAGES_RESCUED, 499}, { CSSTAT_KILLS_KNIFE_FIGHT, 99}, { CSSTAT_DECAL_SPRAYS, 99}, { CSSTAT_NIGHTVISION_DAMAGE, 4999}, { CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, 99}, { CSSTAT_MAP_WINS_CS_ASSAULT, 99}, { CSSTAT_MAP_WINS_CS_COMPOUND, 99}, { CSSTAT_MAP_WINS_CS_HAVANA, 99}, { CSSTAT_MAP_WINS_CS_ITALY, 99}, { CSSTAT_MAP_WINS_CS_MILITIA, 99}, { CSSTAT_MAP_WINS_CS_OFFICE, 99}, { CSSTAT_MAP_WINS_DE_AZTEC, 99}, { CSSTAT_MAP_WINS_DE_CBBLE, 99}, { CSSTAT_MAP_WINS_DE_CHATEAU, 99}, { CSSTAT_MAP_WINS_DE_DUST2, 99}, { CSSTAT_MAP_WINS_DE_DUST, 99}, { CSSTAT_MAP_WINS_DE_INFERNO, 99}, { CSSTAT_MAP_WINS_DE_NUKE, 99}, { CSSTAT_MAP_WINS_DE_PIRANESI, 99}, { CSSTAT_MAP_WINS_DE_PORT, 99}, { CSSTAT_MAP_WINS_DE_PRODIGY, 99}, { CSSTAT_MAP_WINS_DE_TIDES, 99}, { CSSTAT_MAP_WINS_DE_TRAIN, 99}, { CSSTAT_WEAPONS_DONATED, 99}, { CSSTAT_DOMINATIONS, 9}, { CSSTAT_DOMINATION_OVERKILLS, 99}, { CSSTAT_REVENGES, 19}, }; StatsCollection_t& lifetimeStats = const_cast(g_CSClientGameStats.GetLifetimeStats()); for ( int i = 0; i < ARRAYSIZE(statData); ++i ) { CSStatType_t statId = statData[i].statId; lifetimeStats[statId] = statData[i].value; } IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" ); if ( event ) { gameeventmanager->FireEventClientSide( event ); } } #endif #if defined(_DEBUG) CON_COMMAND_F( stats_corrupt, "Load stats with corrupt values", FCVAR_DEVELOPMENTONLY ) { struct DataSet { CSStatType_t statId; int value; }; DataSet badData[] = { { CSSTAT_SHOTS_HIT, 0x40000089 }, { CSSTAT_SHOTS_FIRED, 0x400002BE }, { CSSTAT_KILLS, 0x40000021 }, { CSSTAT_DEATHS, 0x00000056 }, { CSSTAT_DAMAGE, 0x00000FE3 }, { CSSTAT_NUM_BOMBS_PLANTED, 0x00000004 }, { CSSTAT_NUM_BOMBS_DEFUSED, 0x00000000 }, { CSSTAT_PLAYTIME, 0x40000F46 }, { CSSTAT_ROUNDS_WON, 0x40000028 }, { CSSTAT_ROUNDS_PLAYED, 0x40001019 }, { CSSTAT_PISTOLROUNDS_WON, 0x00000001 }, { CSSTAT_MONEY_EARNED, 0x00021E94 }, { CSSTAT_KILLS_DEAGLE, 0x00000009 }, { CSSTAT_KILLS_USP, 0x00000000 }, { CSSTAT_KILLS_GLOCK, 0x00000002 }, { CSSTAT_KILLS_P228, 0x00000000 }, { CSSTAT_KILLS_ELITE, 0x00000000 }, { CSSTAT_KILLS_FIVESEVEN, 0x00000000 }, { CSSTAT_KILLS_AWP, 0x00000000 }, { CSSTAT_KILLS_AK47, 0x00000001 }, { CSSTAT_KILLS_M4A1, 0x00000000 }, { CSSTAT_KILLS_AUG, 0x00000000 }, { CSSTAT_KILLS_SG552, 0x00000000 }, { CSSTAT_KILLS_SG550, 0x00000000 }, { CSSTAT_KILLS_GALIL, 0x00000000 }, { CSSTAT_KILLS_FAMAS, 0x00000001 }, { CSSTAT_KILLS_SCOUT, 0x00000000 }, { CSSTAT_KILLS_G3SG1, 0x00000000 }, { CSSTAT_KILLS_P90, 0x00000001 }, { CSSTAT_KILLS_MP5NAVY, 0x00000000 }, { CSSTAT_KILLS_TMP, 0x00000002 }, { CSSTAT_KILLS_MAC10, 0x00000000 }, { CSSTAT_KILLS_UMP45, 0x00000001 }, { CSSTAT_KILLS_M3, 0x00000000 }, { CSSTAT_KILLS_XM1014, 0x0000000A }, { CSSTAT_KILLS_M249, 0x00000000 }, { CSSTAT_KILLS_KNIFE, 0x00000000 }, { CSSTAT_KILLS_HEGRENADE, 0x00000000 }, { CSSTAT_SHOTS_DEAGLE, 0x0000004C }, { CSSTAT_SHOTS_USP, 0x00000001 }, { CSSTAT_SHOTS_GLOCK, 0x00000017 }, { CSSTAT_SHOTS_P228, 0x00000000 }, { CSSTAT_SHOTS_ELITE, 0x00000000 }, { CSSTAT_SHOTS_FIVESEVEN, 0x00000000 }, { CSSTAT_SHOTS_AWP, 0x00000000 }, { CSSTAT_SHOTS_AK47, 0x0000000E }, { CSSTAT_SHOTS_M4A1, 0x00000000 }, { CSSTAT_SHOTS_AUG, 0x00000000 }, { CSSTAT_SHOTS_SG552, 0x00000000 }, { CSSTAT_SHOTS_SG550, 0x00000008 }, { CSSTAT_SHOTS_GALIL, 0x00000000 }, { CSSTAT_SHOTS_FAMAS, 0x00000010 }, { CSSTAT_SHOTS_SCOUT, 0x00000000 }, { CSSTAT_SHOTS_G3SG1, 0x00000000 }, { CSSTAT_SHOTS_P90, 0x0000007F }, { CSSTAT_SHOTS_MP5NAVY, 0x00000000 }, { CSSTAT_SHOTS_TMP, 0x00000010 }, { CSSTAT_SHOTS_MAC10, 0x00000000 }, { CSSTAT_SHOTS_UMP45, 0x00000015 }, { CSSTAT_SHOTS_M3, 0x00000009 }, { CSSTAT_SHOTS_XM1014, 0x0000024C }, { CSSTAT_SHOTS_M249, 0x00000000 }, { CSSTAT_HITS_DEAGLE, 0x00000019 }, { CSSTAT_HITS_USP, 0x00000000 }, { CSSTAT_HITS_GLOCK, 0x0000000A }, { CSSTAT_HITS_P228, 0x00000000 }, { CSSTAT_HITS_ELITE, 0x00000000 }, { CSSTAT_HITS_FIVESEVEN, 0x00000000 }, { CSSTAT_HITS_AWP, 0x00000000 }, { CSSTAT_HITS_AK47, 0x00000003 }, { CSSTAT_HITS_M4A1, 0x00000000 }, { CSSTAT_HITS_AUG, 0x00000000 }, { CSSTAT_HITS_SG552, 0x00000000 }, { CSSTAT_HITS_SG550, 0x00000001 }, { CSSTAT_HITS_GALIL, 0x00000000 }, { CSSTAT_HITS_FAMAS, 0x00000007 }, { CSSTAT_HITS_SCOUT, 0x00000000 }, { CSSTAT_HITS_G3SG1, 0x00000000 }, { CSSTAT_HITS_P90, 0x0000000D }, { CSSTAT_HITS_MP5NAVY, 0x00000000 }, { CSSTAT_HITS_TMP, 0x00000006 }, { CSSTAT_HITS_MAC10, 0x00000000 }, { CSSTAT_HITS_UMP45, 0x00000006 }, { CSSTAT_HITS_M3, 0x00000000 }, { CSSTAT_HITS_XM1014, 0x0000004C }, { CSSTAT_HITS_M249, 0x00000000 }, { CSSTAT_KILLS_HEADSHOT, 0x00000013 }, { CSSTAT_KILLS_ENEMY_BLINDED, 0x00000002 }, { CSSTAT_KILLS_ENEMY_WEAPON, 0x00000002 }, { CSSTAT_KILLS_KNIFE_FIGHT, 0x00000000 }, { CSSTAT_DECAL_SPRAYS, 0x00000000 }, { CSSTAT_NIGHTVISION_DAMAGE, 0x00000000 }, { CSSTAT_NUM_HOSTAGES_RESCUED, 0x00000000 }, { CSSTAT_NUM_BROKEN_WINDOWS, 0x00000000 }, { CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, 0x00000000 }, { CSSTAT_WEAPONS_DONATED, 0x00000000 }, { CSSTAT_DOMINATIONS, 0x00000001 }, { CSSTAT_DOMINATION_OVERKILLS, 0x00000000 }, { CSSTAT_REVENGES, 0x00000000 }, { CSSTAT_MVPS, 0x00000005 }, { CSSTAT_MAP_WINS_CS_ASSAULT, 0x00000000 }, { CSSTAT_MAP_WINS_CS_COMPOUND, 0x00000000 }, { CSSTAT_MAP_WINS_CS_HAVANA, 0x00000000 }, { CSSTAT_MAP_WINS_CS_ITALY, 0x40000002 }, { CSSTAT_MAP_WINS_CS_MILITIA, 0x00000000 }, { CSSTAT_MAP_WINS_CS_OFFICE, 0x00000000 }, { CSSTAT_MAP_WINS_DE_AZTEC, 0x0000000A }, { CSSTAT_MAP_WINS_DE_CBBLE, 0x40000000 }, { CSSTAT_MAP_WINS_DE_CHATEAU, 0x00000000 }, { CSSTAT_MAP_WINS_DE_DUST2, 0x0000000B }, { CSSTAT_MAP_WINS_DE_DUST, 0x00000000 }, { CSSTAT_MAP_WINS_DE_INFERNO, 0x00000000 }, { CSSTAT_MAP_WINS_DE_NUKE, 0x00000000 }, { CSSTAT_MAP_WINS_DE_PIRANESI, 0x00000000 }, { CSSTAT_MAP_WINS_DE_PORT, 0x00000000 }, { CSSTAT_MAP_WINS_DE_PRODIGY, 0x00000000 }, { CSSTAT_MAP_WINS_DE_TIDES, 0x00000000 }, { CSSTAT_MAP_WINS_DE_TRAIN, 0x00000000 }, { CSSTAT_MAP_ROUNDS_CS_ASSAULT, 0x00000000 }, { CSSTAT_MAP_ROUNDS_CS_COMPOUND, 0x00000000 }, { CSSTAT_MAP_ROUNDS_CS_HAVANA, 0x00000000 }, { CSSTAT_MAP_ROUNDS_CS_ITALY, 0x00000000 }, { CSSTAT_MAP_ROUNDS_CS_MILITIA, 0x00000000 }, { CSSTAT_MAP_ROUNDS_CS_OFFICE, 0x00000003 }, { CSSTAT_MAP_ROUNDS_DE_AZTEC, 0x00000019 }, { CSSTAT_MAP_ROUNDS_DE_CBBLE, 0x00000000 }, { CSSTAT_MAP_ROUNDS_DE_CHATEAU, 0x00000000 }, { CSSTAT_MAP_ROUNDS_DE_DUST2, 0x00000014 }, { CSSTAT_MAP_ROUNDS_DE_DUST, 0x00000000 }, { CSSTAT_MAP_ROUNDS_DE_INFERNO, 0x00000000 }, { CSSTAT_MAP_ROUNDS_DE_NUKE, 0x00000000 }, { CSSTAT_MAP_ROUNDS_DE_PIRANESI, 0x00000000 }, { CSSTAT_MAP_ROUNDS_DE_PORT, 0x00000000 }, { CSSTAT_MAP_ROUNDS_DE_PRODIGY, 0x00000000 }, { CSSTAT_MAP_ROUNDS_DE_TIDES, 0x00000000 }, { CSSTAT_MAP_ROUNDS_DE_TRAIN, 0x00000000 }, { CSSTAT_LASTMATCH_T_ROUNDS_WON, 0x00000000 }, { CSSTAT_LASTMATCH_CT_ROUNDS_WON, 0x00000000 }, { CSSTAT_LASTMATCH_ROUNDS_WON, 0x40000000 }, { CSSTAT_LASTMATCH_KILLS, 0x00000000 }, { CSSTAT_LASTMATCH_DEATHS, 0x00000000 }, { CSSTAT_LASTMATCH_MVPS, 0x00000000 }, { CSSTAT_LASTMATCH_DAMAGE, 0x00000000 }, { CSSTAT_LASTMATCH_MONEYSPENT, 0x00000000 }, { CSSTAT_LASTMATCH_DOMINATIONS, 0x00000000 }, { CSSTAT_LASTMATCH_REVENGES, 0x00000000 }, { CSSTAT_LASTMATCH_MAX_PLAYERS, 0x0000001B }, { CSSTAT_LASTMATCH_FAVWEAPON_ID, 0x00000000 }, { CSSTAT_LASTMATCH_FAVWEAPON_SHOTS, 0x00000000 }, { CSSTAT_LASTMATCH_FAVWEAPON_HITS, 0x00000000 }, { CSSTAT_LASTMATCH_FAVWEAPON_KILLS, 0x00000000 }, }; StatsCollection_t& lifetimeStats = const_cast(g_CSClientGameStats.GetLifetimeStats()); for ( int i = 0; i < ARRAYSIZE(badData); ++i ) { CSStatType_t statId = badData[i].statId; lifetimeStats[statId] = badData[i].value; } IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" ); if ( event ) { gameeventmanager->FireEventClientSide( event ); } } #endif int CCSClientGameStats::GetStatCount() { return CSSTAT_MAX; } PlayerStatData_t CCSClientGameStats::GetStatByIndex( int index ) { PlayerStatData_t statData; statData.iStatId = CSStatProperty_Table[index].statId; statData.iStatValue = m_lifetimeStats[statData.iStatId]; // we can make this more efficient by caching the localized names statData.pStatDisplayName = g_pVGuiLocalize->Find( CSStatProperty_Table[index].szLocalizationToken ); return statData; } PlayerStatData_t CCSClientGameStats::GetStatById( int id ) { Assert(id >= 0 && id < CSSTAT_MAX); if ( id >= 0 && id < CSSTAT_MAX) { return GetStatByIndex(id); } else { PlayerStatData_t dummy; dummy.pStatDisplayName = NULL; dummy.iStatId = CSSTAT_UNDEFINED; dummy.iStatValue = 0; return dummy; } } void CCSClientGameStats::UpdateStats( const StatsCollection_t &stats ) { C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer(); if ( !pPlayer ) return; // don't count stats if cheats on, commentary mode, etc if ( !g_AchievementMgrCS.CheckAchievementsEnabled() ) return; // Update the accumulated stats m_lifetimeStats.Aggregate(stats); m_matchStats.Aggregate(stats); m_roundStats.Aggregate(stats); IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" ); if ( event ) { gameeventmanager->FireEventClientSide( event ); } } void CCSClientGameStats::ResetAllStats( void ) { m_lifetimeStats.Reset(); m_matchStats.Reset(); m_roundStats.Reset(); // reset the stats on Steam if (steamapicontext && steamapicontext->SteamUserStats()) { steamapicontext->SteamUserStats()->ResetAllStats(false); } IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" ); if ( event ) { gameeventmanager->FireEventClientSide( event ); } } void CCSClientGameStats::MsgFunc_MatchStatsUpdate( bf_read &msg ) { int firstStat = msg.ReadShort(); for (int iStat = firstStat; iStat < CSSTAT_MAX && msg.GetNumBytesLeft() > 0; iStat++ ) { m_directTStatAverages.m_fStat[iStat] = msg.ReadFloat(); m_directCTStatAverages.m_fStat[iStat] = msg.ReadFloat(); m_directPlayerStatAverages.m_fStat[iStat] = msg.ReadFloat(); } // sanity check: the message should contain exactly the # of bytes we expect based on the bit field Assert( !msg.IsOverflowed() ); Assert( 0 == msg.GetNumBytesLeft() ); } void CCSClientGameStats::MsgFunc_PlayerStatsUpdate( bf_read &msg ) { // Note: if any check fails while decoding this message, bail out and disregard this data to avoid // potentially polluting player stats StatsCollection_t deltaStats; CRC32_t crc; CRC32_Init( &crc ); const uint32 key = 0x82DA9F4C; // this key should match the key in cs_gamestats.cpp CRC32_ProcessBuffer( &crc, &key, sizeof(key)); const byte version = 0x01; CRC32_ProcessBuffer( &crc, &version, sizeof(version)); if (msg.ReadByte() != version) { Warning("PlayerStatsUpdate message: ignoring unsupported version\n"); return; } byte iStatsToRead = msg.ReadByte(); CRC32_ProcessBuffer( &crc, &iStatsToRead, sizeof(iStatsToRead)); for ( int i = 0; i < iStatsToRead; ++i) { byte iStat = msg.ReadByte(); CRC32_ProcessBuffer( &crc, &iStat, sizeof(iStat)); if (iStat >= CSSTAT_MAX) { Warning("PlayerStatsUpdate: invalid statId encountered; ignoring stats update\n"); return; } short delta = msg.ReadShort(); deltaStats[iStat] = delta; CRC32_ProcessBuffer( &crc, &delta, sizeof(delta)); } CRC32_Final( &crc ); CRC32_t readCRC = msg.ReadLong(); if (readCRC != crc || msg.IsOverflowed() || ( 0 != msg.GetNumBytesLeft() ) ) { Warning("PlayerStatsUpdate message from server is corrupt; ignoring\n"); return; } // do one additional pass for out of band values for ( int iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat ) { if (deltaStats[iStat] < 0 || deltaStats[iStat] >= 0x4000) { Warning("PlayerStatsUpdate message from server has out of band values; ignoring\n"); return; } } // everything looks okay at this point; add these stats for the player's round, match, and lifetime stats UpdateStats(deltaStats); } void CCSClientGameStats::UploadRoundData() { // If there's nothing to send, don't bother if ( !m_RoundStatData.Count() ) return; // Temporary ConVar to disable stats if ( sv_noroundstats.GetBool() ) { m_RoundStatData.PurgeAndDeleteElements(); return; } // Send off all OGS stats at level shutdown KeyValues *pKV = new KeyValues( "basedata" ); if ( !pKV ) return; pKV->SetString( "MapID", MapName() ); // Add all the vector based stats for ( int k=0 ; k < m_RoundStatData.Count() ; ++k ) { m_RoundStatData[ k ] ->nRoundEndReason = m_RoundEndReason; SubmitStat( m_RoundStatData[ k ] ); } // Perform the actual submission SubmitGameStats( pKV ); // Clear out the per map stats m_RoundStatData.Purge(); pKV->deleteThis(); // Reset the last round's ending status. m_RoundEndReason = Invalid_Round_End_Reason; } void CCSClientGameStats::ResetMatchStats() { m_roundStats.Reset(); m_matchStats.Reset(); m_matchMaxPlayerCount = 0; } void CCSClientGameStats::UpdateLastMatchStats() { // only update that last match if we actually have valid data if ( m_matchStats[CSSTAT_ROUNDS_PLAYED] == 0 ) return; // check to see if the player materially participate; they could have been spectating or joined just in time for the ending. int s = 0; s += m_matchStats[CSSTAT_ROUNDS_WON]; s += m_matchStats[CSSTAT_KILLS]; s += m_matchStats[CSSTAT_DEATHS]; s += m_matchStats[CSSTAT_MVPS]; s += m_matchStats[CSSTAT_DAMAGE]; s += m_matchStats[CSSTAT_MONEY_SPENT]; if ( s == 0 ) return; m_lifetimeStats[CSSTAT_LASTMATCH_T_ROUNDS_WON] = m_matchStats[CSSTAT_T_ROUNDS_WON]; m_lifetimeStats[CSSTAT_LASTMATCH_CT_ROUNDS_WON] = m_matchStats[CSSTAT_CT_ROUNDS_WON]; m_lifetimeStats[CSSTAT_LASTMATCH_ROUNDS_WON] = m_matchStats[CSSTAT_ROUNDS_WON]; m_lifetimeStats[CSSTAT_LASTMATCH_KILLS] = m_matchStats[CSSTAT_KILLS]; m_lifetimeStats[CSSTAT_LASTMATCH_DEATHS] = m_matchStats[CSSTAT_DEATHS]; m_lifetimeStats[CSSTAT_LASTMATCH_MVPS] = m_matchStats[CSSTAT_MVPS]; m_lifetimeStats[CSSTAT_LASTMATCH_DAMAGE] = m_matchStats[CSSTAT_DAMAGE]; m_lifetimeStats[CSSTAT_LASTMATCH_MONEYSPENT] = m_matchStats[CSSTAT_MONEY_SPENT]; m_lifetimeStats[CSSTAT_LASTMATCH_DOMINATIONS] = m_matchStats[CSSTAT_DOMINATIONS]; m_lifetimeStats[CSSTAT_LASTMATCH_REVENGES] = m_matchStats[CSSTAT_REVENGES]; m_lifetimeStats[CSSTAT_LASTMATCH_MAX_PLAYERS] = m_matchMaxPlayerCount; CalculateMatchFavoriteWeapons(); } //----------------------------------------------------------------------------- // Purpose: Calculate and store the match favorite weapon for each player as only deltaStats for that weapon are stored on Steam //----------------------------------------------------------------------------- void CCSClientGameStats::CalculateMatchFavoriteWeapons() { int maxKills = 0, maxKillId = -1; for( int j = CSSTAT_KILLS_DEAGLE; j <= CSSTAT_KILLS_M249; ++j ) { if ( m_matchStats[j] > maxKills ) { maxKills = m_matchStats[j]; maxKillId = j; } } if ( maxKillId == -1 ) { m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_ID] = WEAPON_NONE; m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_SHOTS] = 0; m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_HITS] = 0; m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_KILLS] = 0; } else { int statTableID = -1; for (int j = 0; WeaponName_StatId_Table[j].killStatId != CSSTAT_UNDEFINED; ++j) { if ( WeaponName_StatId_Table[j].killStatId == maxKillId ) { statTableID = j; break; } } Assert( statTableID != -1 ); m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_ID] = WeaponName_StatId_Table[statTableID].weaponId; m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_SHOTS] = m_matchStats[WeaponName_StatId_Table[statTableID].shotStatId]; m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_HITS] = m_matchStats[WeaponName_StatId_Table[statTableID].hitStatId]; m_lifetimeStats[CSSTAT_LASTMATCH_FAVWEAPON_KILLS] = m_matchStats[WeaponName_StatId_Table[statTableID].killStatId]; } }