790 lines
25 KiB
C++
790 lines
25 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//-------------------------------------------------------------
|
|
// File: cs_client_gamestats.cpp
|
|
// Desc: Manages client side stat storage, accumulation, and access
|
|
// Author: Peter Freese <peter@hiddenpath.com>
|
|
// 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];
|
|
}
|
|
}
|