You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
386 lines
15 KiB
386 lines
15 KiB
|
|
//========= 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;
|
|
|