diff options
Diffstat (limited to 'game/client/cstrike/cs_client_gamestats.cpp')
| -rw-r--r-- | game/client/cstrike/cs_client_gamestats.cpp | 789 |
1 files changed, 789 insertions, 0 deletions
diff --git a/game/client/cstrike/cs_client_gamestats.cpp b/game/client/cstrike/cs_client_gamestats.cpp new file mode 100644 index 0000000..8a8ea54 --- /dev/null +++ b/game/client/cstrike/cs_client_gamestats.cpp @@ -0,0 +1,789 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//------------------------------------------------------------- +// File: cs_client_gamestats.cpp +// Desc: Manages client side stat storage, accumulation, and access +// Author: Peter Freese <[email protected]> +// 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<CAchievementMgr *>( 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<StatsCollection_t&>(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<StatsCollection_t&>(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]; + } +} |