diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/tf/tf_steamstats.cpp | |
| download | archived-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.cpp | 386 |
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; |