summaryrefslogtreecommitdiff
path: root/game/client/dod/dod_playerstats.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/dod/dod_playerstats.cpp')
-rw-r--r--game/client/dod/dod_playerstats.cpp344
1 files changed, 344 insertions, 0 deletions
diff --git a/game/client/dod/dod_playerstats.cpp b/game/client/dod/dod_playerstats.cpp
new file mode 100644
index 0000000..24db375
--- /dev/null
+++ b/game/client/dod/dod_playerstats.cpp
@@ -0,0 +1,344 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "dod_playerstats.h"
+#include "hud_macros.h"
+
+const char *g_pszShortTeamNames[2] =
+{
+ "US",
+ "Ger"
+};
+
+const char *g_pszClassNames[NUM_DOD_PLAYERCLASSES] =
+{
+ "Rifleman",
+ "Assault",
+ "Support",
+ "Sniper",
+ "MG",
+ "Rocket"
+};
+
+const char *g_pszStatNames[DODSTAT_MAX] =
+{
+ "iPlayTime",
+ "iRoundsWon",
+ "iRoundsLost",
+ "iKills",
+ "iDeaths",
+ "iCaptures",
+ "iBlocks",
+ "iBombsPlanted",
+ "iBombsDefused",
+ "iDominations",
+ "iRevenges",
+ "iShotsHit",
+ "iShotsFired",
+ "iHeadshots"
+};
+
+int iValidWeaponStatBitMask = ( (1<<DODSTAT_KILLS ) | (1<<DODSTAT_SHOTS_HIT) | (1<<DODSTAT_SHOTS_FIRED) | (1<<DODSTAT_HEADSHOTS) );
+int iValidPlayerStatBitMask = 0xFFFF & ~( (1<<DODSTAT_SHOTS_HIT) | (1<<DODSTAT_SHOTS_FIRED) | (1<<DODSTAT_HEADSHOTS) );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &msg -
+//-----------------------------------------------------------------------------
+void __MsgFunc_DODPlayerStatsUpdate( bf_read &msg )
+{
+ g_DODPlayerStats.MsgFunc_DODPlayerStatsUpdate( msg );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CDODPlayerStats::CDODPlayerStats()
+{
+ m_flTimeNextForceUpload = 0;
+}
+
+//-----------------------------------------------------------------------------
+// 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 CDODPlayerStats::PostInit()
+{
+ SetNextForceUploadTime();
+
+ ListenForGameEvent( "player_stats_updated" );
+ ListenForGameEvent( "user_data_downloaded" );
+
+ HOOK_MESSAGE( DODPlayerStatsUpdate );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: called at level shutdown
+//-----------------------------------------------------------------------------
+void CDODPlayerStats::LevelShutdownPreEntity()
+{
+ // upload user stats to Steam on every map change or when we quit
+ UploadStats();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: called when the stats have changed in-game
+//-----------------------------------------------------------------------------
+void CDODPlayerStats::FireGameEvent( IGameEvent *event )
+{
+ const char *pEventName = event->GetName();
+
+ if ( FStrEq( pEventName, "user_data_downloaded" ) )
+ {
+ // Store our stat data
+
+ Assert( steamapicontext->SteamUserStats() );
+ if ( !steamapicontext->SteamUserStats() )
+ return;
+
+ CGameID gameID( engine->GetAppID() );
+
+ // reset everything
+ Q_memset( &m_PlayerStats, 0, sizeof(m_PlayerStats) );
+ Q_memset( &m_WeaponStats, 0, sizeof(m_WeaponStats) );
+
+ // read playerclass stats
+ for ( int iTeam=0;iTeam<2;iTeam++ )
+ {
+ for ( int iClass=0;iClass<NUM_DOD_PLAYERCLASSES;iClass++ )
+ {
+ for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
+ {
+ if ( iValidPlayerStatBitMask & (1<<iStat) )
+ {
+ char szStatName[256];
+ int iData;
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.%s.%s", g_pszShortTeamNames[iTeam], g_pszClassNames[iClass], g_pszStatNames[iStat] );
+
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ // use Steam's value
+ m_PlayerStats[iTeam][iClass].m_iStat[iStat] = iData;
+ }
+ }
+ }
+ }
+ }
+
+ // read weapon stats
+ for ( int iWeapon=WEAPON_NONE+1;iWeapon<WEAPON_MAX;iWeapon++ )
+ {
+ for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
+ {
+ if ( iValidWeaponStatBitMask & (1<<iStat) )
+ {
+ char szStatName[256];
+ int iData;
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.%s", s_WeaponAliasInfo[iWeapon], g_pszStatNames[iStat] );
+
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ // use Steam's value
+ m_WeaponStats[iWeapon].m_iStat[iStat] = iData;
+ }
+ }
+ }
+ }
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
+ if ( event )
+ {
+ event->SetBool( "forceupload", false );
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+}
+
+void CDODPlayerStats::MsgFunc_DODPlayerStatsUpdate( bf_read &msg )
+{
+ dod_stat_accumulator_t playerStats;
+ Q_memset( &playerStats, 0, sizeof(playerStats) );
+
+ // get the fixed-size information
+ int iClass = msg.ReadByte();
+ int iTeam = msg.ReadByte();
+
+ int iPlayerStatBits = msg.ReadLong();
+
+ Assert( iClass >= 0 && iClass <= 5 );
+ if ( iClass < 0 || iClass > 5 )
+ return;
+
+ // the bitfield indicates which stats are contained in the message. Set the stats appropriately.
+ int iStat = DODSTAT_FIRST;
+ while ( iPlayerStatBits > 0 )
+ {
+ if ( iPlayerStatBits & 1 )
+ {
+ playerStats.m_iStat[iStat] = msg.ReadLong();
+ }
+ iPlayerStatBits >>= 1;
+ iStat++;
+ }
+
+ int iNumWeapons = msg.ReadByte();
+ int i;
+
+ CUtlVector<weapon_stat_t> vecWeaponStats;
+
+ for ( i=0;i<iNumWeapons;i++ )
+ {
+ weapon_stat_t weaponStat;
+ Q_memset( &weaponStat, 0, sizeof(weaponStat) );
+
+ weaponStat.iWeaponID = msg.ReadByte();
+
+ vecWeaponStats.AddToTail( weaponStat );
+ }
+
+ for ( i=0;i<vecWeaponStats.Count();i++ )
+ {
+ int iWeaponStatMask = msg.ReadLong();
+
+ int iStat = DODSTAT_FIRST;
+ while ( iWeaponStatMask > 0 )
+ {
+ if ( iWeaponStatMask & 1 )
+ {
+ vecWeaponStats[i].stats.m_iStat[iStat] = msg.ReadLong();
+ }
+ iWeaponStatMask >>= 1;
+ iStat++;
+ }
+ }
+
+ // sanity check: the message should contain exactly the # of bytes we expect based on the bit field
+ Assert( !msg.IsOverflowed() );
+ Assert( 0 == msg.GetNumBytesLeft() );
+ // if byte count isn't correct, bail out and don't use this data, rather than risk polluting player stats with garbage
+ if ( msg.IsOverflowed() || ( 0 != msg.GetNumBytesLeft() ) )
+ return;
+
+ UpdateStats( iClass, iTeam, &playerStats, &vecWeaponStats );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Uploads stats for current Steam user to Steam
+//-----------------------------------------------------------------------------
+void CDODPlayerStats::UploadStats()
+{
+ // only upload if Steam is running
+ if ( !steamapicontext->SteamUserStats() )
+ return;
+
+ CGameID gameID( engine->GetAppID() );
+ char szStatName[256];
+
+ // write playerclass stats
+ for ( int iTeam=0;iTeam<2;iTeam++ )
+ {
+ for ( int iClass=0;iClass<NUM_DOD_PLAYERCLASSES;iClass++ )
+ {
+ for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
+ {
+ if ( iValidPlayerStatBitMask & (1<<iStat) )
+ {
+ if ( m_PlayerStats[iTeam][iClass].m_bDirty[iStat] )
+ {
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.%s.%s", g_pszShortTeamNames[iTeam], g_pszClassNames[iClass], g_pszStatNames[iStat] );
+
+ steamapicontext->SteamUserStats()->SetStat( szStatName, m_PlayerStats[iTeam][iClass].m_iStat[iStat] );
+ }
+ }
+ }
+ }
+ }
+
+ // write weapon stats
+ for ( int iWeapon=WEAPON_NONE+1;iWeapon<WEAPON_MAX;iWeapon++ )
+ {
+ for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
+ {
+ if ( m_WeaponStats[iWeapon].m_bDirty[iStat] )
+ {
+ if ( iValidWeaponStatBitMask & (1<<iStat) )
+ {
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.%s", s_WeaponAliasInfo[iWeapon], g_pszStatNames[iStat] );
+
+ steamapicontext->SteamUserStats()->SetStat( szStatName, m_WeaponStats[iWeapon].m_iStat[iStat] );
+ }
+ }
+ }
+ }
+
+ SetNextForceUploadTime();
+}
+
+void CDODPlayerStats::UpdateStats( int iPlayerClass, int iTeam, dod_stat_accumulator_t *playerStats, CUtlVector<weapon_stat_t> *vecWeaponStats )
+{
+ Assert( iPlayerClass >= 0 && iPlayerClass < NUM_DOD_PLAYERCLASSES );
+ if ( iPlayerClass < 0 || iPlayerClass >= NUM_DOD_PLAYERCLASSES )
+ return;
+
+ // translate the team index into [0,1]
+ int iTeamIndex = ( iTeam == TEAM_ALLIES ) ? 0 : 1;
+
+ // upload this class' data
+
+ // add this stat update to our stored stats
+ for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
+ {
+ if ( iValidPlayerStatBitMask & (1<<iStat) )
+ {
+ if ( playerStats->m_iStat[iStat] > 0 )
+ {
+ m_PlayerStats[iTeamIndex][iPlayerClass].m_iStat[iStat] += playerStats->m_iStat[iStat];
+ m_PlayerStats[iTeamIndex][iPlayerClass].m_bDirty[iStat] = true;
+ }
+ }
+ }
+
+ int iWeaponStatCount = vecWeaponStats->Count();
+
+ for ( int i=0;i<iWeaponStatCount;i++ )
+ {
+ int iWeaponID = vecWeaponStats->Element(i).iWeaponID;
+
+ for ( int iStat=0;iStat<DODSTAT_MAX;iStat++ )
+ {
+ if ( iValidWeaponStatBitMask & (1<<iStat) )
+ {
+ int iValue = vecWeaponStats->Element(i).stats.m_iStat[iStat];
+ if ( iValue > 0 )
+ {
+ m_WeaponStats[iWeaponID].m_iStat[iStat] += iValue;
+ m_WeaponStats[iWeaponID].m_bDirty[iStat] = true;
+ }
+ }
+ }
+ }
+
+ // if we haven't uploaded stats in a long time, upload them
+ if ( ( gpGlobals->curtime >= m_flTimeNextForceUpload ) )
+ {
+ UploadStats();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sets the next time to force a stats upload at
+//-----------------------------------------------------------------------------
+void CDODPlayerStats::SetNextForceUploadTime()
+{
+ // pick a time a while from now (an hour +/- 15 mins) to upload stats if we haven't gotten a map change by then
+ m_flTimeNextForceUpload = gpGlobals->curtime + ( 60 * RandomInt( 45, 75 ) );
+}
+
+CDODPlayerStats g_DODPlayerStats; \ No newline at end of file