summaryrefslogtreecommitdiff
path: root/game/client/tf/tf_steamstats.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/tf/tf_steamstats.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/client/tf/tf_steamstats.cpp')
-rw-r--r--game/client/tf/tf_steamstats.cpp386
1 files changed, 386 insertions, 0 deletions
diff --git a/game/client/tf/tf_steamstats.cpp b/game/client/tf/tf_steamstats.cpp
new file mode 100644
index 0000000..4fd8d74
--- /dev/null
+++ b/game/client/tf/tf_steamstats.cpp
@@ -0,0 +1,386 @@
+
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_steamstats.h"
+#include "tf_hud_statpanel.h"
+#include "achievementmgr.h"
+#include "engine/imatchmaking.h"
+#include "ipresence.h"
+#include "../game/shared/tf/tf_shareddefs.h"
+#include "../game/shared/tf/tf_gamestats_shared.h"
+
+struct StatMap_t
+{
+ const char *pszName;
+ int iStat;
+ int iLiveStat;
+};
+
+// subset of stats which we store in Steam
+StatMap_t g_SteamStats[] = {
+ { "iNumberOfKills", TFSTAT_KILLS, PROPERTY_KILLS, },
+ { "iDamageDealt", TFSTAT_DAMAGE, PROPERTY_DAMAGE_DEALT, },
+ { "iPlayTime", TFSTAT_PLAYTIME, PROPERTY_PLAY_TIME, },
+ { "iPointCaptures", TFSTAT_CAPTURES, PROPERTY_POINT_CAPTURES, },
+ { "iPointDefenses", TFSTAT_DEFENSES, PROPERTY_POINT_DEFENSES, },
+ { "iDominations", TFSTAT_DOMINATIONS, PROPERTY_DOMINATIONS, },
+ { "iRevenge", TFSTAT_REVENGE, PROPERTY_REVENGE, },
+ { "iPointsScored", TFSTAT_POINTSSCORED, PROPERTY_POINTS_SCORED, },
+ { "iBuildingsDestroyed", TFSTAT_BUILDINGSDESTROYED, PROPERTY_BUILDINGS_DESTROYED, },
+ { "iNumInvulnerable", TFSTAT_INVULNS, PROPERTY_INVULNS, },
+ { "iKillAssists", TFSTAT_KILLASSISTS, PROPERTY_KILL_ASSISTS, },
+};
+
+// class specific stats
+StatMap_t g_SteamStats_Pyro[] = {
+ { "iFireDamage", TFSTAT_FIREDAMAGE, -1, }, // Added post-XBox, isn't saved in Live
+ { NULL, 0, 0, },
+};
+
+StatMap_t g_SteamStats_Demoman[] = {
+ { "iBlastDamage", TFSTAT_BLASTDAMAGE, -1, }, // Added post-XBox, isn't saved in Live
+ { NULL, 0, 0, },
+};
+
+StatMap_t g_SteamStats_Engineer[] = {
+ { "iBuildingsBuilt", TFSTAT_BUILDINGSBUILT, PROPERTY_BUILDINGS_BUILT, },
+ { "iSentryKills", TFSTAT_MAXSENTRYKILLS, PROPERTY_SENTRY_KILLS, },
+ { "iNumTeleports", TFSTAT_TELEPORTS, PROPERTY_TELEPORTS, },
+ { NULL, 0, 0, },
+};
+
+StatMap_t g_SteamStats_Medic[] = {
+ { "iHealthPointsHealed", TFSTAT_HEALING, PROPERTY_HEALTH_POINTS_HEALED, },
+ { NULL, 0, 0, },
+};
+
+StatMap_t g_SteamStats_Sniper[] = {
+ { "iHeadshots", TFSTAT_HEADSHOTS, PROPERTY_HEADSHOTS, },
+ { NULL, 0, 0, },
+};
+
+StatMap_t g_SteamStats_Spy[] = {
+ { "iHeadshots", TFSTAT_HEADSHOTS, PROPERTY_HEADSHOTS, },
+ { "iBackstabs", TFSTAT_BACKSTABS, PROPERTY_BACKSTABS, },
+ { "iHealthPointsLeached", TFSTAT_HEALTHLEACHED, PROPERTY_HEALTH_POINTS_LEACHED, },
+ { NULL, 0, 0, },
+};
+
+StatMap_t* g_SteamStats_Class[] = {
+ NULL, // Undefined
+ NULL, // Scout
+ g_SteamStats_Sniper, // Sniper
+ NULL, // Soldier
+ g_SteamStats_Demoman, // Demoman
+ g_SteamStats_Medic, // Medic
+ NULL, // Heavy
+ g_SteamStats_Pyro, // Pyro
+ g_SteamStats_Spy, // Spy
+ g_SteamStats_Engineer, // Engineer
+};
+
+// subset of map stats which we store in Steam
+StatMap_t g_SteamMapStats[] = {
+ { "iPlayTime", TFMAPSTAT_PLAYTIME, PROPERTY_PLAY_TIME, },
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFSteamStats::CTFSteamStats()
+{
+ 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 CTFSteamStats::PostInit()
+{
+ SetNextForceUploadTime();
+ ListenForGameEvent( "player_stats_updated" );
+ ListenForGameEvent( "user_data_downloaded" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: called at level shutdown
+//-----------------------------------------------------------------------------
+void CTFSteamStats::LevelShutdownPreEntity()
+{
+ // upload user stats to Steam on every map change
+ UploadStats();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: called when the stats have changed in-game
+//-----------------------------------------------------------------------------
+void CTFSteamStats::FireGameEvent( IGameEvent *event )
+{
+ const char *pEventName = event->GetName();
+ if ( 0 == Q_strcmp( pEventName, "player_stats_updated" ) )
+ {
+ bool bForceUpload = event->GetBool( "forceupload" );
+
+ // if we haven't uploaded stats in a long time, upload them
+ if ( ( gpGlobals->curtime >= m_flTimeNextForceUpload ) || bForceUpload )
+ {
+ UploadStats();
+ }
+ }
+ else if ( 0 == Q_strcmp( pEventName, "user_data_downloaded" ) )
+ {
+ Assert( steamapicontext->SteamUserStats() );
+ if ( !steamapicontext->SteamUserStats() )
+ return;
+ CTFStatPanel *pStatPanel = GET_HUDELEMENT( CTFStatPanel );
+ Assert( pStatPanel );
+
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ // Grab generic stats:
+ ClassStats_t &classStats = CTFStatPanel::GetClassStats( iClass );
+ for ( int iStat = 0; iStat < ARRAYSIZE( g_SteamStats ); iStat++ )
+ {
+ char szStatName[256];
+ int iData;
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.accum.%s", g_aPlayerClassNames_NonLocalized[iClass], g_SteamStats[iStat].pszName );
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ classStats.accumulated.m_iStat[g_SteamStats[iStat].iStat] = iData;
+ }
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.max.%s", g_aPlayerClassNames_NonLocalized[iClass], g_SteamStats[iStat].pszName );
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ classStats.max.m_iStat[g_SteamStats[iStat].iStat] = iData;
+ }
+
+ // MVM Stats
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.mvm.accum.%s", g_aPlayerClassNames_NonLocalized[iClass], g_SteamStats[iStat].pszName );
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ classStats.accumulatedMVM.m_iStat[g_SteamStats[iStat].iStat] = iData;
+ }
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.mvm.max.%s", g_aPlayerClassNames_NonLocalized[iClass], g_SteamStats[iStat].pszName );
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ classStats.maxMVM.m_iStat[g_SteamStats[iStat].iStat] = iData;
+ }
+ }
+
+ // Grab class specific stats:
+ StatMap_t* pClassStatMap = g_SteamStats_Class[iClass];
+ if ( pClassStatMap )
+ {
+ int iStat = 0;
+ do
+ {
+ char szStatName[256];
+ int iData;
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.accum.%s", g_aPlayerClassNames_NonLocalized[iClass], pClassStatMap[iStat].pszName );
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ classStats.accumulated.m_iStat[pClassStatMap[iStat].iStat] = iData;
+ }
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.max.%s", g_aPlayerClassNames_NonLocalized[iClass], pClassStatMap[iStat].pszName );
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ classStats.max.m_iStat[pClassStatMap[iStat].iStat] = iData;
+ }
+
+ // MVM Stats
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.mvm.accum.%s", g_aPlayerClassNames_NonLocalized[iClass], pClassStatMap[iStat].pszName );
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ classStats.accumulatedMVM.m_iStat[pClassStatMap[iStat].iStat] = iData;
+ }
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.mvm.max.%s", g_aPlayerClassNames_NonLocalized[iClass], pClassStatMap[iStat].pszName );
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ classStats.maxMVM.m_iStat[pClassStatMap[iStat].iStat] = iData;
+ }
+ iStat++;
+ }
+ while ( pClassStatMap[iStat].pszName );
+ }
+ }
+
+ for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ )
+ {
+ const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByIndex( i );
+
+ // Grab generic stats:
+ MapStats_t &mapStats = CTFStatPanel::GetMapStats( pMap->GetStatsIdentifier() );
+ for ( int iStat = 0; iStat < ARRAYSIZE( g_SteamMapStats ); iStat++ )
+ {
+ char szStatName[256];
+ int iData;
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.accum.%s", pMap->pszMapName, g_SteamMapStats[iStat].pszName );
+ if ( steamapicontext->SteamUserStats()->GetStat( szStatName, &iData ) )
+ {
+ mapStats.accumulated.m_iStat[g_SteamMapStats[iStat].iStat] = iData;
+ }
+ }
+ }
+
+ IGameEvent * pEvent = gameeventmanager->CreateEvent( "player_stats_updated" );
+ if ( pEvent )
+ {
+ pEvent->SetBool( "forceupload", false );
+ gameeventmanager->FireEventClientSide( pEvent );
+ }
+
+ pStatPanel->SetStatsChanged( true );
+ pStatPanel->UpdateStatSummaryPanel();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Uploads stats for current Steam user to Steam
+//-----------------------------------------------------------------------------
+void CTFSteamStats::UploadStats()
+{
+ if ( IsX360() )
+ {
+ ReportLiveStats();
+ return;
+ }
+
+ // Only upload if Steam is running & the achievement manager exists.
+ if ( !steamapicontext->SteamUserStats() )
+ return;
+
+ CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() );
+ if ( !pAchievementMgr )
+ return;
+
+ // Stomp local steam context stats with those in the stat panel.
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ // Set generic stats:
+ ClassStats_t &classStats = CTFStatPanel::GetClassStats( iClass );
+ for ( int iStat = 0; iStat < ARRAYSIZE( g_SteamStats ); iStat++ )
+ {
+ char szStatName[256];
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.accum.%s", g_aPlayerClassNames_NonLocalized[iClass], g_SteamStats[iStat].pszName );
+ steamapicontext->SteamUserStats()->SetStat( szStatName, classStats.accumulated.m_iStat[g_SteamStats[iStat].iStat] );
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.max.%s", g_aPlayerClassNames_NonLocalized[iClass], g_SteamStats[iStat].pszName );
+ steamapicontext->SteamUserStats()->SetStat( szStatName, classStats.max.m_iStat[g_SteamStats[iStat].iStat] );
+
+ // MVM Stats
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.mvm.accum.%s", g_aPlayerClassNames_NonLocalized[iClass], g_SteamStats[iStat].pszName );
+ steamapicontext->SteamUserStats()->SetStat( szStatName, classStats.accumulatedMVM.m_iStat[g_SteamStats[iStat].iStat] );
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.mvm.max.%s", g_aPlayerClassNames_NonLocalized[iClass], g_SteamStats[iStat].pszName );
+ steamapicontext->SteamUserStats()->SetStat( szStatName, classStats.maxMVM.m_iStat[g_SteamStats[iStat].iStat] );
+ }
+
+ // Set class specific stats:
+ StatMap_t* pClassStatMap = g_SteamStats_Class[iClass];
+ if ( pClassStatMap )
+ {
+ int iStat = 0;
+ do
+ {
+ char szStatName[256];
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.accum.%s", g_aPlayerClassNames_NonLocalized[iClass], pClassStatMap[iStat].pszName );
+ steamapicontext->SteamUserStats()->SetStat( szStatName, classStats.accumulated.m_iStat[pClassStatMap[iStat].iStat] );
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.max.%s", g_aPlayerClassNames_NonLocalized[iClass], pClassStatMap[iStat].pszName );
+ steamapicontext->SteamUserStats()->SetStat( szStatName, classStats.max.m_iStat[pClassStatMap[iStat].iStat] );
+
+ // MVM Stats
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.mvm.accum.%s", g_aPlayerClassNames_NonLocalized[iClass], pClassStatMap[iStat].pszName );
+ steamapicontext->SteamUserStats()->SetStat( szStatName, classStats.accumulatedMVM.m_iStat[pClassStatMap[iStat].iStat] );
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.mvm.max.%s", g_aPlayerClassNames_NonLocalized[iClass], pClassStatMap[iStat].pszName );
+ steamapicontext->SteamUserStats()->SetStat( szStatName, classStats.maxMVM.m_iStat[pClassStatMap[iStat].iStat] );
+
+ iStat++;
+ }
+ while ( pClassStatMap[iStat].pszName );
+ }
+ }
+
+ // Stomp local steam context stats with those in the stat panel.
+ for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ )
+ {
+ const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByIndex( i );
+
+ // Set generic stats:
+ MapStats_t &mapStats = CTFStatPanel::GetMapStats( pMap->GetStatsIdentifier() );
+ for ( int iStat = 0; iStat < ARRAYSIZE( g_SteamMapStats ); iStat++ )
+ {
+ char szStatName[256];
+
+ Q_snprintf( szStatName, ARRAYSIZE( szStatName ), "%s.accum.%s", pMap->pszMapName, g_SteamMapStats[iStat].pszName );
+ steamapicontext->SteamUserStats()->SetStat( szStatName, mapStats.accumulated.m_iStat[g_SteamMapStats[iStat].iStat] );
+ }
+ }
+
+ // Send our local steam context stats to the server.
+ pAchievementMgr->UploadUserData();
+ SetNextForceUploadTime();
+
+ // Now everything should be sync'd up (stat panel, local steam context, remote steam depot).
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Accumulate player stats and send them to matchmaking for reporting to Live
+//-----------------------------------------------------------------------------
+void CTFSteamStats::ReportLiveStats()
+{
+ int statsTotals[ARRAYSIZE( g_SteamStats )];
+ Q_memset( &statsTotals, 0, sizeof( statsTotals ) );
+
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ ClassStats_t &classStats = CTFStatPanel::GetClassStats( iClass );
+ for ( int iStat = 0; iStat < ARRAYSIZE( g_SteamStats ); iStat++ )
+ {
+ statsTotals[iStat] = MAX( statsTotals[iStat], classStats.max.m_iStat[g_SteamStats[iStat].iStat] );
+ }
+ }
+
+ // send the stats to matchmaking
+ for ( int i = 0; i < ARRAYSIZE( g_SteamStats ); ++i )
+ {
+ // Points scored is looked up by the stats reporting function
+ if ( g_SteamStats[i].iLiveStat == PROPERTY_POINTS_SCORED )
+ continue;
+
+ // If we hit this assert, we've added a new stat that Live won't know how to store
+ Assert( g_SteamStats[i].iLiveStat != -1 );
+
+ if ( g_SteamStats[i].iLiveStat != -1 )
+ {
+ presence->SetStat( g_SteamStats[i].iLiveStat, statsTotals[i], XUSER_DATA_TYPE_INT32 );
+ }
+ }
+
+ presence->UploadStats();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sets the next time to force a stats upload at
+//-----------------------------------------------------------------------------
+void CTFSteamStats::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 ) );
+}
+
+CTFSteamStats g_TFSteamStats;