Team Fortress 2 Source Code as on 22/4/2020
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.

1188 lines
39 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #ifdef WIN32
  9. #include "winerror.h"
  10. #endif
  11. #include "tf_hud_statpanel.h"
  12. #include "tf_hud_winpanel.h"
  13. #include <vgui/IVGui.h>
  14. #include "vgui_controls/AnimationController.h"
  15. #include "iclientmode.h"
  16. #include "c_tf_playerresource.h"
  17. #include <vgui_controls/Label.h>
  18. #include <vgui/ILocalize.h>
  19. #include <vgui/ISurface.h>
  20. #include "tf/c_tf_player.h"
  21. #include "tf/c_tf_team.h"
  22. #include "tf/tf_steamstats.h"
  23. #include "filesystem.h"
  24. #include "dmxloader/dmxloader.h"
  25. #include "fmtstr.h"
  26. #include "tf_statsummary.h"
  27. #include "usermessages.h"
  28. #include "hud_macros.h"
  29. #include "ixboxsystem.h"
  30. #include "achievementmgr.h"
  31. #include "tf_hud_freezepanel.h"
  32. #include "tf_gamerules.h"
  33. #include "tf_mapinfo.h"
  34. #include <vgui_controls/MessageBox.h>
  35. // memdbgon must be the last include file in a .cpp file!!!
  36. #include "tier0/memdbgon.h"
  37. DECLARE_HUDELEMENT_DEPTH( CTFStatPanel, 1 );
  38. DECLARE_HUD_MESSAGE( CTFStatPanel, PlayerStatsUpdate );
  39. DECLARE_HUD_MESSAGE( CTFStatPanel, MapStatsUpdate );
  40. BEGIN_DMXELEMENT_UNPACK( RoundStats_t )
  41. DMXELEMENT_UNPACK_FIELD( "iNumShotsHit", "0", int, m_iStat[TFSTAT_SHOTS_HIT] )
  42. DMXELEMENT_UNPACK_FIELD( "iNumShotsFired", "0", int, m_iStat[TFSTAT_SHOTS_FIRED] )
  43. DMXELEMENT_UNPACK_FIELD( "iNumberOfKills", "0", int, m_iStat[TFSTAT_KILLS] )
  44. DMXELEMENT_UNPACK_FIELD( "iNumDeaths", "0", int, m_iStat[TFSTAT_DEATHS] )
  45. DMXELEMENT_UNPACK_FIELD( "iDamageDealt", "0", int, m_iStat[TFSTAT_DAMAGE] )
  46. DMXELEMENT_UNPACK_FIELD( "iPlayTime", "0", int, m_iStat[TFSTAT_PLAYTIME] )
  47. DMXELEMENT_UNPACK_FIELD( "iPointCaptures", "0", int, m_iStat[TFSTAT_CAPTURES] )
  48. DMXELEMENT_UNPACK_FIELD( "iPointDefenses", "0", int, m_iStat[TFSTAT_DEFENSES] )
  49. DMXELEMENT_UNPACK_FIELD( "iDominations", "0", int, m_iStat[TFSTAT_DOMINATIONS] )
  50. DMXELEMENT_UNPACK_FIELD( "iRevenge", "0", int, m_iStat[TFSTAT_REVENGE] )
  51. DMXELEMENT_UNPACK_FIELD( "iPointsScored", "0", int, m_iStat[TFSTAT_POINTSSCORED] )
  52. DMXELEMENT_UNPACK_FIELD( "iBuildingsDestroyed", "0", int, m_iStat[TFSTAT_BUILDINGSDESTROYED] )
  53. DMXELEMENT_UNPACK_FIELD( "iHeadshots", "0", int, m_iStat[TFSTAT_HEADSHOTS] )
  54. DMXELEMENT_UNPACK_FIELD( "iHealthPointsHealed", "0", int, m_iStat[TFSTAT_HEALING] )
  55. DMXELEMENT_UNPACK_FIELD( "iNumInvulnerable", "0", int, m_iStat[TFSTAT_INVULNS] )
  56. DMXELEMENT_UNPACK_FIELD( "iKillAssists", "0", int, m_iStat[TFSTAT_KILLASSISTS] )
  57. DMXELEMENT_UNPACK_FIELD( "iBackstabs", "0", int, m_iStat[TFSTAT_BACKSTABS] )
  58. DMXELEMENT_UNPACK_FIELD( "iHealthPointsLeached", "0", int, m_iStat[TFSTAT_HEALTHLEACHED] )
  59. DMXELEMENT_UNPACK_FIELD( "iBuildingsBuilt", "0", int, m_iStat[TFSTAT_BUILDINGSBUILT] )
  60. DMXELEMENT_UNPACK_FIELD( "iSentryKills", "0", int, m_iStat[TFSTAT_MAXSENTRYKILLS] )
  61. DMXELEMENT_UNPACK_FIELD( "iNumTeleports", "0", int, m_iStat[TFSTAT_TELEPORTS] )
  62. DMXELEMENT_UNPACK_FIELD( "iFireDamage", "0", int, m_iStat[TFSTAT_FIREDAMAGE] )
  63. DMXELEMENT_UNPACK_FIELD( "iBonusPoints", "0", int, m_iStat[TFSTAT_BONUS_POINTS] )
  64. DMXELEMENT_UNPACK_FIELD( "iBlastDamage", "0", int, m_iStat[TFSTAT_BLASTDAMAGE] )
  65. END_DMXELEMENT_UNPACK( RoundStats_t, s_RoundStatsUnpack )
  66. BEGIN_DMXELEMENT_UNPACK( RoundMapStats_t )
  67. DMXELEMENT_UNPACK_FIELD( "iPlayTime", "0", int, m_iStat[TFMAPSTAT_PLAYTIME] )
  68. END_DMXELEMENT_UNPACK( RoundMapStats_t, s_RoundMapStatsUnpack )
  69. BEGIN_DMXELEMENT_UNPACK( ClassStats_t )
  70. DMXELEMENT_UNPACK_FIELD( "iPlayerClass", "0", int, iPlayerClass )
  71. DMXELEMENT_UNPACK_FIELD( "iNumberOfRounds", "0", int, iNumberOfRounds )
  72. // RoundStats_t accumulated;
  73. // RoundStats_t max;
  74. // RoundStats_t currentRound;
  75. // RoundStats_t accumulatedMVM;
  76. // RoundStats_t maxMVM;
  77. END_DMXELEMENT_UNPACK( ClassStats_t, s_ClassStatsUnpack )
  78. BEGIN_DMXELEMENT_UNPACK( MapStats_t )
  79. DMXELEMENT_UNPACK_FIELD( "iMapID", "0", map_identifier_t, iMapID )
  80. DMXELEMENT_UNPACK_FIELD( "iNumberOfRounds", "0", int, iNumberOfRounds )
  81. // RoundStats_t accumulated;
  82. // RoundStats_t currentRound;
  83. END_DMXELEMENT_UNPACK( MapStats_t, s_MapStatsUnpack )
  84. // priority order of stats to display record for; earlier position in list is highest
  85. TFStatType_t g_statPriority[] = { TFSTAT_HEADSHOTS, TFSTAT_BACKSTABS, TFSTAT_MAXSENTRYKILLS, TFSTAT_HEALING, TFSTAT_KILLS, TFSTAT_KILLASSISTS,
  86. TFSTAT_DAMAGE, TFSTAT_DOMINATIONS, TFSTAT_INVULNS, TFSTAT_BUILDINGSDESTROYED, TFSTAT_CAPTURES, TFSTAT_DEFENSES, TFSTAT_REVENGE, TFSTAT_TELEPORTS, TFSTAT_BUILDINGSBUILT,
  87. TFSTAT_HEALTHLEACHED, TFSTAT_POINTSSCORED, TFSTAT_PLAYTIME, TFSTAT_BONUS_POINTS };
  88. // stat types that we don't display records for, kept in this list just so we can assert all stats appear in one list or the other
  89. TFStatType_t g_statUnused[] = { TFSTAT_DEATHS, TFSTAT_UNDEFINED, TFSTAT_SHOTS_FIRED, TFSTAT_SHOTS_HIT, TFSTAT_FIREDAMAGE, TFSTAT_BLASTDAMAGE,
  90. TFSTAT_DAMAGETAKEN, TFSTAT_HEALTHKITS, TFSTAT_AMMOKITS, TFSTAT_CLASSCHANGES, TFSTAT_CRITS, TFSTAT_SUICIDES, TFSTAT_CURRENCY_COLLECTED, TFSTAT_DAMAGE_ASSIST, TFSTAT_HEALING_ASSIST,
  91. TFSTAT_DAMAGE_BOSS, TFSTAT_DAMAGE_BLOCKED, TFSTAT_DAMAGE_RANGED, TFSTAT_DAMAGE_RANGED_CRIT_RANDOM, TFSTAT_DAMAGE_RANGED_CRIT_BOOSTED, TFSTAT_REVIVED, TFSTAT_THROWABLEHIT, TFSTAT_THROWABLEKILL, TFSTAT_KILLS_RUNECARRIER, TFSTAT_FLAGRETURNS,
  92. TFSTAT_KILLSTREAK_MAX };
  93. // priority order of stats to display record for; earlier position in list is highest
  94. TFMapStatType_t g_mapStatPriority[] = { TFMAPSTAT_PLAYTIME };
  95. // stat types that we don't display records for, kept in this list just so we can assert all stats appear in one list or the other
  96. TFMapStatType_t g_mapStatUnused[] = { TFMAPSTAT_UNDEFINED };
  97. // localization keys for stat panel text, must be in same order as TFStatType_t
  98. const char *g_szLocalizedRecordText[] =
  99. {
  100. "",
  101. "[shots hit]",
  102. "[shots fired]",
  103. "#StatPanel_Kills",
  104. "[deaths]",
  105. "#StatPanel_DamageDealt",
  106. "#StatPanel_Captures",
  107. "#StatPanel_Defenses",
  108. "#StatPanel_Dominations",
  109. "#StatPanel_Revenge",
  110. "#StatPanel_PointsScored",
  111. "#StatPanel_BuildingsDestroyed",
  112. "#StatPanel_Headshots",
  113. "#StatPanel_PlayTime",
  114. "#StatPanel_Healing",
  115. "#StatPanel_Invulnerable",
  116. "#StatPanel_KillAssists",
  117. "#StatPanel_Backstabs",
  118. "#StatPanel_HealthLeached",
  119. "#StatPanel_BuildingsBuilt",
  120. "#StatPanel_SentryKills",
  121. "#StatPanel_Teleports",
  122. "#StatPanel_BonusPoints"
  123. };
  124. const char *g_szLocalizedMVMRecordText[] =
  125. {
  126. "",
  127. "[shots hit]",
  128. "[shots fired]",
  129. "#StatPanel_MVM_Kills",
  130. "[deaths]",
  131. "#StatPanel_MVM_DamageDealt",
  132. "#StatPanel_MVM_Captures",
  133. "#StatPanel_MVM_Defenses",
  134. "#StatPanel_MVM_Dominations",
  135. "#StatPanel_MVM_Revenge",
  136. "#StatPanel_MVM_PointsScored",
  137. "#StatPanel_MVM_BuildingsDestroyed",
  138. "#StatPanel_MVM_Headshots",
  139. "#StatPanel_MVM_PlayTime",
  140. "#StatPanel_MVM_Healing",
  141. "#StatPanel_MVM_Invulnerable",
  142. "#StatPanel_MVM_KillAssists",
  143. "#StatPanel_MVM_Backstabs",
  144. "#StatPanel_MVM_HealthLeached",
  145. "#StatPanel_MVM_BuildingsBuilt",
  146. "#StatPanel_MVM_SentryKills",
  147. "#StatPanel_MVM_Teleports",
  148. "#StatPanel_MVM_BonusPoints"
  149. };
  150. static CTFStatPanel *statPanel = NULL;
  151. extern CAchievementMgr g_AchievementMgrTF;
  152. //-----------------------------------------------------------------------------
  153. // Purpose: Returns the static stats panel
  154. //-----------------------------------------------------------------------------
  155. CTFStatPanel *GetStatPanel()
  156. {
  157. return statPanel;
  158. }
  159. //-----------------------------------------------------------------------------
  160. // Purpose: Constructor
  161. //-----------------------------------------------------------------------------
  162. CTFStatPanel::CTFStatPanel( const char *pElementName )
  163. : EditablePanel( NULL, "StatPanel" ), CHudElement( pElementName )
  164. {
  165. // Assert that all defined stats are in our prioritized list or explicitly unused
  166. Assert( ARRAYSIZE( g_statPriority ) + ARRAYSIZE( g_statUnused ) == TFSTAT_TOTAL );
  167. Assert( ARRAYSIZE( g_mapStatPriority ) + ARRAYSIZE( g_mapStatUnused ) == TFMAPSTAT_TOTAL );
  168. ResetDisplayedStat();
  169. m_bStatsChanged = false;
  170. m_bLocalFileTrusted = false;
  171. m_flTimeLastSpawn = 0;
  172. vgui::Panel *pParent = g_pClientMode->GetViewport();
  173. SetParent( pParent );
  174. m_bShouldBeVisible = false;
  175. SetScheme( "ClientScheme" );
  176. statPanel = this;
  177. m_pClassImage = new CTFClassImage( this, "StatPanelClassImage" );
  178. // Read stats from disk. (Definitive stat store for X360; for PC, whatever we get from Steam is authoritative.)
  179. ReadStats();
  180. RegisterForRenderGroup( "mid" );
  181. }
  182. //-----------------------------------------------------------------------------
  183. // Purpose: Destructor
  184. //-----------------------------------------------------------------------------
  185. CTFStatPanel::~CTFStatPanel()
  186. {
  187. if ( statPanel == this )
  188. statPanel = NULL;
  189. }
  190. //-----------------------------------------------------------------------------
  191. // Purpose: called when level is shutting down
  192. //-----------------------------------------------------------------------------
  193. void CTFStatPanel::LevelShutdown()
  194. {
  195. // write out stats if they've changed
  196. WriteStats();
  197. UpdateStatSummaryPanel();
  198. }
  199. //-----------------------------------------------------------------------------
  200. // Purpose:
  201. //-----------------------------------------------------------------------------
  202. void CTFStatPanel::ApplySettings( KeyValues *inResourceData )
  203. {
  204. BaseClass::ApplySettings( inResourceData );
  205. }
  206. //-----------------------------------------------------------------------------
  207. // Purpose:
  208. //-----------------------------------------------------------------------------
  209. void CTFStatPanel::Reset()
  210. {
  211. if ( gpGlobals->curtime > m_flTimeHide )
  212. {
  213. Hide();
  214. }
  215. }
  216. //-----------------------------------------------------------------------------
  217. // Purpose: Resets which stat is being displayed
  218. //-----------------------------------------------------------------------------
  219. void CTFStatPanel::ResetDisplayedStat()
  220. {
  221. m_iCurStatValue = 0;
  222. m_iCurStatTeam = TEAM_UNASSIGNED;
  223. m_statType = TFSTAT_UNDEFINED;
  224. m_recordBreakType = RECORDBREAK_NONE;
  225. m_iCurStatClass = TF_CLASS_UNDEFINED;
  226. m_bDisplayAfterSpawn = false;
  227. m_flTimeHide = 0;
  228. }
  229. //-----------------------------------------------------------------------------
  230. // Purpose:
  231. //-----------------------------------------------------------------------------
  232. void CTFStatPanel::Init()
  233. {
  234. // listen for events
  235. HOOK_HUD_MESSAGE( CTFStatPanel, PlayerStatsUpdate );
  236. HOOK_HUD_MESSAGE( CTFStatPanel, MapStatsUpdate );
  237. ListenForGameEvent( "player_spawn" );
  238. Hide();
  239. CHudElement::Init();
  240. }
  241. //-----------------------------------------------------------------------------
  242. // Purpose:
  243. //-----------------------------------------------------------------------------
  244. void CTFStatPanel::UpdateStats( int iClass, const RoundStats_t &stats, bool bAlive )
  245. {
  246. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  247. if ( !pPlayer )
  248. return;
  249. // don't count stats if cheats on, commentary mode, etc
  250. if ( !g_AchievementMgrTF.CheckAchievementsEnabled() )
  251. return;
  252. ClassStats_t &classStats = GetClassStats( iClass );
  253. bool bMVM = TFGameRules() && TFGameRules()->IsMannVsMachineMode();
  254. if ( bMVM )
  255. {
  256. classStats.AccumulateMVMRound( stats );
  257. }
  258. else
  259. {
  260. classStats.AccumulateRound( stats );
  261. }
  262. // sentry kills is a max value rather than a count, meaningless to accumulate
  263. classStats.accumulated.m_iStat[TFSTAT_MAXSENTRYKILLS] = 0;
  264. classStats.accumulatedMVM.m_iStat[TFSTAT_MAXSENTRYKILLS] = 0;
  265. ResetDisplayedStat();
  266. m_iCurStatClass = iClass;
  267. // run through all stats we keep records for, update the max value, and if a record is set,
  268. // remember the highest priority record
  269. for ( int i= ARRAYSIZE( g_statPriority )-1; i >= 0; i-- )
  270. {
  271. TFStatType_t statType = g_statPriority[i];
  272. if ( statType == TFSTAT_BONUS_POINTS )
  273. continue;
  274. int iCur = stats.m_iStat[statType];
  275. int iMax = ( bMVM ? classStats.maxMVM.m_iStat[statType] : classStats.max.m_iStat[statType] );
  276. if ( iCur > iMax )
  277. {
  278. // Record was set, remember what stat set a record.
  279. if ( bMVM )
  280. {
  281. classStats.maxMVM.m_iStat[statType] = iCur;
  282. }
  283. else
  284. {
  285. classStats.max.m_iStat[statType] = iCur;
  286. }
  287. m_iCurStatValue = iCur;
  288. m_statType = statType;
  289. m_recordBreakType = RECORDBREAK_BEST;
  290. }
  291. else if ( ( iCur > 0 ) && ( m_recordBreakType <= RECORDBREAK_TIE ) && ( iCur == iMax ) )
  292. {
  293. // if we haven't broken a record and we tied this one, display it
  294. m_iCurStatValue = iCur;
  295. m_statType = statType;
  296. m_recordBreakType = RECORDBREAK_TIE;
  297. }
  298. else if ( ( iCur > 0 ) && ( m_recordBreakType <= RECORDBREAK_CLOSE ) && ( iCur >= (int) ( (float) iMax * 0.8f ) ) )
  299. {
  300. // if we haven't broken a record or tied a record but we came close to this one, display it
  301. m_iCurStatValue = iCur;
  302. m_statType = statType;
  303. m_recordBreakType = RECORDBREAK_CLOSE;
  304. }
  305. }
  306. m_bStatsChanged = true;
  307. if ( m_statType > TFSTAT_UNDEFINED )
  308. {
  309. m_iCurStatTeam = pPlayer->GetTeamNumber();
  310. if ( !bAlive || ( gpGlobals->curtime - m_flTimeLastSpawn < 3.0 ) )
  311. {
  312. // show the panel now if dead or very recently spawned
  313. vgui::ivgui()->AddTickSignal( GetVPanel(), 1000 );
  314. extern ConVar hud_freezecamhide;
  315. if ( !hud_freezecamhide.GetBool() && !IsTakingAFreezecamScreenshot() )
  316. {
  317. ShowStatPanel( m_iCurStatClass, m_iCurStatTeam, m_iCurStatValue, m_statType, m_recordBreakType, bAlive );
  318. m_flTimeHide = gpGlobals->curtime + 20.0f;
  319. }
  320. }
  321. else
  322. {
  323. // otherwise wait until next spawn to show panel
  324. m_bDisplayAfterSpawn = true;
  325. }
  326. }
  327. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  328. if ( event )
  329. {
  330. event->SetBool( "forceupload", false );
  331. gameeventmanager->FireEventClientSide( event );
  332. }
  333. UpdateStatSummaryPanel();
  334. }
  335. void CTFStatPanel::UpdateMapStats( map_identifier_t iMapID, const RoundMapStats_t &stats )
  336. {
  337. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  338. if ( !pPlayer )
  339. return;
  340. // It's ok to count map stats while cheating and such
  341. //if ( !g_AchievementMgrTF.CheckAchievementsEnabled() )
  342. // return;
  343. MapStats_t &mapStats = GetMapStats( iMapID );
  344. mapStats.AccumulateRound( stats );
  345. ResetDisplayedStat();
  346. m_iCurStatClass = iMapID;
  347. m_bStatsChanged = true;
  348. IGameEvent * event = gameeventmanager->CreateEvent( "player_stats_updated" );
  349. if ( event )
  350. {
  351. event->SetBool( "forceupload", false );
  352. gameeventmanager->FireEventClientSide( event );
  353. }
  354. UpdateStatSummaryPanel();
  355. }
  356. //-----------------------------------------------------------------------------
  357. // Purpose:
  358. //-----------------------------------------------------------------------------
  359. void CTFStatPanel::TestStatPanel( TFStatType_t statType, RecordBreakType_t recordType )
  360. {
  361. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  362. if ( !pPlayer )
  363. {
  364. Msg( "Please load a map first.\n" );
  365. return;
  366. }
  367. bool bMVM = TFGameRules() && TFGameRules()->IsMannVsMachineMode();
  368. m_iCurStatClass = pPlayer->GetPlayerClass()->GetClassIndex();
  369. ClassStats_t &classStats = GetClassStats( m_iCurStatClass );
  370. m_iCurStatValue = bMVM ? classStats.maxMVM.m_iStat[statType] : classStats.max.m_iStat[statType];
  371. m_iCurStatTeam = pPlayer->GetTeamNumber();
  372. ShowStatPanel( m_iCurStatClass, m_iCurStatTeam, m_iCurStatValue, statType, recordType, false );
  373. }
  374. //-----------------------------------------------------------------------------
  375. // Purpose: Writes stat file. Used as primary storage for X360. For PC,
  376. // Steam is authoritative but we write stat file for debugging (although
  377. // we never read it).
  378. //-----------------------------------------------------------------------------
  379. void CTFStatPanel::WriteStats( void )
  380. {
  381. if ( !m_bStatsChanged )
  382. return;
  383. MEM_ALLOC_CREDIT();
  384. DECLARE_DMX_CONTEXT();
  385. CDmxElement *pPlayerStats = CreateDmxElement( "PlayerStats" );
  386. CDmxElementModifyScope modify( pPlayerStats );
  387. // get Steam ID. If not logged into Steam, use 0
  388. int iSteamID = 0;
  389. if ( steamapicontext->SteamUser() )
  390. {
  391. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  392. iSteamID = steamID.GetAccountID();
  393. }
  394. // Calc CRC of all data to make the local data file somewhat tamper-resistant
  395. int iCRC = CalcCRC( iSteamID );
  396. pPlayerStats->SetValue( "iVersion", static_cast<int>( PLAYERSTATS_FILE_VERSION ) );
  397. pPlayerStats->SetValue( "SteamID", iSteamID );
  398. pPlayerStats->SetValue( "iTimestamp", iCRC ); // store the CRC with a non-obvious name
  399. CDmxAttribute *pClassStatsList = pPlayerStats->AddAttribute( "aClassStats" );
  400. CUtlVector< CDmxElement* >& classStats = pClassStatsList->GetArrayForEdit<CDmxElement*>();
  401. modify.Release();
  402. for( int i = 0; i < m_aClassStats.Count(); i++ )
  403. {
  404. const ClassStats_t &stat = m_aClassStats[ i ];
  405. // strip out any garbage class data
  406. if ( ( stat.iPlayerClass > (TF_LAST_NORMAL_CLASS-1) ) || ( stat.iPlayerClass < TF_FIRST_NORMAL_CLASS ) )
  407. continue;
  408. CDmxElement *pClass = CreateDmxElement( "ClassStats_t" );
  409. classStats.AddToTail( pClass );
  410. CDmxElementModifyScope modifyClass( pClass );
  411. pClass->SetValue( "comment: classname", g_aPlayerClassNames_NonLocalized[ stat.iPlayerClass ] );
  412. pClass->AddAttributesFromStructure( &stat, s_ClassStatsUnpack );
  413. CDmxElement *pAccumulated = CreateDmxElement( "RoundStats_t" );
  414. pAccumulated->AddAttributesFromStructure( &stat.accumulated, s_RoundStatsUnpack );
  415. pClass->SetValue( "accumulated", pAccumulated );
  416. CDmxElement *pMax = CreateDmxElement( "RoundStats_t" );
  417. pMax->AddAttributesFromStructure( &stat.max, s_RoundStatsUnpack );
  418. pClass->SetValue( "max", pMax );
  419. CDmxElement *pAccumulatedMVM = CreateDmxElement( "RoundStats_t" );
  420. pAccumulatedMVM->AddAttributesFromStructure( &stat.accumulatedMVM, s_RoundStatsUnpack );
  421. pClass->SetValue( "accumulatedmvm", pAccumulatedMVM );
  422. CDmxElement *pMaxMVM = CreateDmxElement( "RoundStats_t" );
  423. pMaxMVM->AddAttributesFromStructure( &stat.maxMVM, s_RoundStatsUnpack );
  424. pClass->SetValue( "maxmvm", pMaxMVM );
  425. }
  426. CDmxAttribute *pMapStatsList = pPlayerStats->AddAttribute( "aMapStats" );
  427. CUtlVector< CDmxElement* >& mapStats = pMapStatsList->GetArrayForEdit<CDmxElement*>();
  428. for( int i = 0; i < m_aMapStats.Count(); i++ )
  429. {
  430. const MapStats_t &stat = m_aMapStats[ i ];
  431. // strip out any garbage map data
  432. if ( !IsValidMapID( stat.iMapID ) )
  433. continue;
  434. CDmxElement *pMap = CreateDmxElement( "MapStats_t" );
  435. mapStats.AddToTail( pMap );
  436. CDmxElementModifyScope modifyClass( pMap );
  437. pMap->SetValue( "comment: mapname", GetMapNameFromID( stat.iMapID ) );
  438. pMap->AddAttributesFromStructure( &stat, s_MapStatsUnpack );
  439. CDmxElement *pAccumulated = CreateDmxElement( "RoundMapStats_t" );
  440. pAccumulated->AddAttributesFromStructure( &stat.accumulated, s_RoundMapStatsUnpack );
  441. pMap->SetValue( "accumulated", pAccumulated );
  442. }
  443. if ( IsX360() )
  444. {
  445. #ifdef _X360
  446. if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
  447. return;
  448. #endif
  449. }
  450. char szFilename[_MAX_PATH];
  451. if ( IsX360() )
  452. Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/tf2_playerstats.dmx" );
  453. else
  454. Q_snprintf( szFilename, sizeof( szFilename ), "tf2_playerstats.dmx" );
  455. {
  456. MEM_ALLOC_CREDIT();
  457. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  458. if ( SerializeDMX( buf, pPlayerStats, szFilename ) )
  459. {
  460. filesystem->WriteFile( szFilename, "MOD", buf );
  461. }
  462. }
  463. CleanupDMX( pPlayerStats );
  464. if ( IsX360() )
  465. {
  466. xboxsystem->FinishContainerWrites();
  467. }
  468. m_bStatsChanged = false;
  469. }
  470. //-----------------------------------------------------------------------------
  471. // Purpose:
  472. //-----------------------------------------------------------------------------
  473. bool CTFStatPanel::ReadStats( void )
  474. {
  475. CDmxElement *pPlayerStats;
  476. DECLARE_DMX_CONTEXT();
  477. if ( IsX360() )
  478. {
  479. #ifdef _X360
  480. if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
  481. return false;
  482. #endif
  483. }
  484. char szFilename[_MAX_PATH];
  485. if ( IsX360() )
  486. {
  487. Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/tf2_playerstats.dmx" );
  488. }
  489. else
  490. {
  491. Q_snprintf( szFilename, sizeof( szFilename ), "tf2_playerstats.dmx" );
  492. }
  493. MEM_ALLOC_CREDIT();
  494. bool bOk = UnserializeDMX( szFilename, "MOD", true, &pPlayerStats );
  495. if ( !bOk )
  496. return false;
  497. int iVersion = pPlayerStats->GetValue< int >( "iVersion" );
  498. if ( iVersion > PLAYERSTATS_FILE_VERSION )
  499. {
  500. // file is beyond our comprehension
  501. return false;
  502. }
  503. int iSteamID = pPlayerStats->GetValue<int>( "SteamID" );
  504. int iCRCFile = pPlayerStats->GetValue<int>( "iTimestamp" );
  505. const CUtlVector< CDmxElement* > &aClassStatsList = pPlayerStats->GetArray< CDmxElement * >( "aClassStats" );
  506. int iCount = aClassStatsList.Count();
  507. m_aClassStats.SetCount( iCount );
  508. for( int i = 0; i < m_aClassStats.Count(); i++ )
  509. {
  510. CDmxElement *pClass = aClassStatsList[ i ];
  511. ClassStats_t &stat = m_aClassStats[ i ];
  512. pClass->UnpackIntoStructure( &stat, sizeof( stat ), s_ClassStatsUnpack );
  513. CDmxElement *pAccumulated = pClass->GetValue< CDmxElement * >( "accumulated" );
  514. if ( pAccumulated )
  515. {
  516. pAccumulated->UnpackIntoStructure( &stat.accumulated, sizeof( stat.accumulated ), s_RoundStatsUnpack );
  517. }
  518. CDmxElement *pMax = pClass->GetValue< CDmxElement * >( "max" );
  519. if ( pMax )
  520. {
  521. pMax->UnpackIntoStructure( &stat.max, sizeof( stat.max ), s_RoundStatsUnpack );
  522. }
  523. CDmxElement *pAccumulatedMVM = pClass->GetValue< CDmxElement * >( "accumulatedMVM" );
  524. if ( pAccumulatedMVM )
  525. {
  526. pAccumulatedMVM->UnpackIntoStructure( &stat.accumulatedMVM, sizeof( stat.accumulatedMVM ), s_RoundStatsUnpack );
  527. }
  528. CDmxElement *pMaxMVM = pClass->GetValue< CDmxElement * >( "maxMVM" );
  529. if ( pMaxMVM )
  530. {
  531. pMaxMVM->UnpackIntoStructure( &stat.maxMVM, sizeof( stat.maxMVM ), s_RoundStatsUnpack );
  532. }
  533. }
  534. const CUtlVector< CDmxElement* > &aMapStatsList = pPlayerStats->GetArray< CDmxElement * >( "aMapStats" );
  535. iCount = aMapStatsList.Count();
  536. m_aMapStats.SetCount( iCount );
  537. for( int i = 0; i < aMapStatsList.Count(); i++ )
  538. {
  539. CDmxElement *pMap = aMapStatsList[ i ];
  540. MapStats_t &stat = m_aMapStats[ i ];
  541. pMap->UnpackIntoStructure( &stat, sizeof( stat ), s_MapStatsUnpack );
  542. CDmxElement *pAccumulated = pMap->GetValue< CDmxElement * >( "accumulated" );
  543. if ( pAccumulated )
  544. {
  545. pAccumulated->UnpackIntoStructure( &stat.accumulated, sizeof( stat.accumulated ), s_RoundMapStatsUnpack );
  546. }
  547. }
  548. CleanupDMX( pPlayerStats );
  549. UpdateStatSummaryPanel();
  550. // check file CRC and steam ID to see if we think this file has not been tampered with
  551. int iCRC = CalcCRC( iSteamID );
  552. // does file CRC match CRC generated from file data, and is there a Steam ID in the file
  553. if ( ( iCRC == iCRCFile ) && ( iSteamID > 0 ) && steamapicontext->SteamUser() )
  554. {
  555. // does the file Steam ID match current Steam ID (so you can't hand around files)
  556. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  557. if ( steamID.GetAccountID() == (uint32) iSteamID )
  558. {
  559. m_bLocalFileTrusted = true;
  560. }
  561. }
  562. m_bStatsChanged = false;
  563. return true;
  564. }
  565. //-----------------------------------------------------------------------------
  566. // Purpose: Calcs CRC of all stat data
  567. //-----------------------------------------------------------------------------
  568. int CTFStatPanel::CalcCRC( int iSteamID )
  569. {
  570. CRC32_t crc;
  571. CRC32_Init( &crc );
  572. // make a CRC of stat data
  573. CRC32_ProcessBuffer( &crc, &iSteamID, sizeof( iSteamID ) );
  574. for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ )
  575. {
  576. // add each class' data to the CRC
  577. ClassStats_t &classStats = GetClassStats( iClass );
  578. CRC32_ProcessBuffer( &crc, &classStats, sizeof( classStats ) );
  579. // since the class data structure is highly guessable from the file, add one other thing to make the CRC hard to hack w/o code disassembly
  580. int iObfuscate = iClass * iClass;
  581. CRC32_ProcessBuffer( &crc, &iObfuscate, sizeof( iObfuscate ) );
  582. }
  583. CRC32_Final( &crc );
  584. return (int) ( crc & 0x7FFFFFFF );
  585. }
  586. //-----------------------------------------------------------------------------
  587. // Purpose:
  588. //-----------------------------------------------------------------------------
  589. void CTFStatPanel::ShowStatPanel( int iClass, int iTeam, int iCurStatValue, TFStatType_t statType, RecordBreakType_t recordBreakType,
  590. bool bAlive )
  591. {
  592. bool bMVM = TFGameRules() && TFGameRules()->IsMannVsMachineMode();
  593. // If this is MvM mode and we're looking at the round end, dont show the stats
  594. // panel because the PVEWinPanel will be up
  595. if( bMVM && TFGameRules()->State_Get() == GR_STATE_TEAM_WIN )
  596. {
  597. return;
  598. }
  599. ClassStats_t &classStats = GetClassStats( iClass );
  600. vgui::Label *pLabel = dynamic_cast<Label *>( FindChildByName( "summaryLabel" ) );
  601. if ( !pLabel )
  602. return;
  603. const char *pRecordTextSuffix[RECORDBREAK_MAX] = { "", "close", "tie", "best" };
  604. const char *pLocalizedTitle = bAlive ? "#StatPanel_Title_Alive" : "#StatPanel_Title_Dead";
  605. SetDialogVariable( "title", g_pVGuiLocalize->Find( pLocalizedTitle ) );
  606. SetDialogVariable( "stattextlarge", "" );
  607. SetDialogVariable( "stattextsmall", "" );
  608. if ( recordBreakType == RECORDBREAK_CLOSE )
  609. {
  610. // if we are displaying that the player got close to a record, show current & best values
  611. char szCur[32],szBest[32];
  612. wchar_t wzCur[32],wzBest[32];
  613. GetStatValueAsString( iCurStatValue, statType, szCur, ARRAYSIZE( szCur ) );
  614. GetStatValueAsString( bMVM ? classStats.maxMVM.m_iStat[statType] : classStats.max.m_iStat[statType], statType, szBest, ARRAYSIZE( szBest ) );
  615. g_pVGuiLocalize->ConvertANSIToUnicode( szCur, wzCur, sizeof( wzCur ) );
  616. g_pVGuiLocalize->ConvertANSIToUnicode( szBest, wzBest, sizeof( wzBest ) );
  617. wchar_t *wzFormat = g_pVGuiLocalize->Find( "#StatPanel_Format_Close" );
  618. wchar_t wzText[256];
  619. g_pVGuiLocalize->ConstructString_safe( wzText, wzFormat, 2, wzCur, wzBest );
  620. SetDialogVariable( "stattextsmall", wzText );
  621. }
  622. else
  623. {
  624. // player broke or tied a record, just show current value
  625. char szValue[32];
  626. GetStatValueAsString( iCurStatValue, statType, szValue, ARRAYSIZE( szValue ) );
  627. SetDialogVariable( "stattextlarge", szValue );
  628. }
  629. SetDialogVariable( "statdesc", g_pVGuiLocalize->Find( CFmtStr( "%s_%s", bMVM ? g_szLocalizedMVMRecordText[statType] : g_szLocalizedRecordText[statType],
  630. pRecordTextSuffix[recordBreakType] ) ) );
  631. // Set the class name. We can't use a dialog variable because it's a string that's already
  632. // been set using a dialog variable, and apparently we don't support nested dialog variables.
  633. wchar_t szOriginalSummary[ 256 ];
  634. wchar_t szSummary[ 256 ];
  635. // This is the field that "statdesc" completed for us
  636. pLabel->GetText( szOriginalSummary, sizeof( szOriginalSummary ) );
  637. const wchar_t *pszPlayerClass = L"undefined";
  638. if ( ( iClass >= TF_FIRST_NORMAL_CLASS ) && ( iClass <= TF_LAST_NORMAL_CLASS ) )
  639. {
  640. pszPlayerClass = g_pVGuiLocalize->Find( g_aPlayerClassNames[ iClass ] );
  641. }
  642. g_pVGuiLocalize->ConstructString_safe( szSummary, szOriginalSummary, 1, pszPlayerClass );
  643. pLabel->SetText( szSummary );
  644. if ( m_pClassImage )
  645. {
  646. m_pClassImage->SetClass( iTeam, iClass, 0 );
  647. }
  648. Show();
  649. }
  650. //-----------------------------------------------------------------------------
  651. // Purpose:
  652. //-----------------------------------------------------------------------------
  653. void CTFStatPanel::FireGameEvent( IGameEvent * event )
  654. {
  655. const char *pEventName = event->GetName();
  656. if ( Q_strcmp( "player_spawn", pEventName ) == 0 )
  657. {
  658. int iUserID = event->GetInt( "userid" );
  659. if ( !C_TFPlayer::GetLocalTFPlayer() || ( C_TFPlayer::GetLocalTFPlayer()->GetUserID() != iUserID ) )
  660. return;
  661. if ( m_bDisplayAfterSpawn )
  662. {
  663. // if we have a panel to display after spawn, show it now
  664. vgui::ivgui()->AddTickSignal( GetVPanel(), 1000 );
  665. ShowStatPanel( m_iCurStatClass, m_iCurStatTeam, m_iCurStatValue, m_statType, m_recordBreakType, true );
  666. m_flTimeHide = gpGlobals->curtime + 12.0f;
  667. m_bDisplayAfterSpawn = false;
  668. }
  669. else
  670. {
  671. // hide panel if we're currently showing it
  672. Hide();
  673. }
  674. m_flTimeLastSpawn = gpGlobals->curtime;
  675. }
  676. }
  677. //-----------------------------------------------------------------------------
  678. // Purpose:
  679. //-----------------------------------------------------------------------------
  680. void CTFStatPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
  681. {
  682. BaseClass::ApplySchemeSettings( pScheme );
  683. LoadControlSettings( "resource/UI/StatPanel_Base.res" );
  684. vgui::Panel *pStatBox = FindChildByName("StatBox");
  685. if ( pStatBox )
  686. {
  687. // Dirty hack: Make the statbox update now, and then change its bgColor.
  688. // When it then gets ApplySchemeSetting called shortly after this, it doesn't
  689. // reapply the scheme because its dirty-scheme flag has been removed.
  690. pStatBox->ApplySchemeSettings( pScheme );
  691. pStatBox->SetBgColor( GetSchemeColor("TransparentLightBlack", pScheme) );
  692. }
  693. }
  694. //-----------------------------------------------------------------------------
  695. // Purpose:
  696. //-----------------------------------------------------------------------------
  697. void CTFStatPanel::OnTick()
  698. {
  699. // see if it's time to hide the panel
  700. if ( m_flTimeHide > 0 && gpGlobals->curtime > m_flTimeHide )
  701. {
  702. Hide();
  703. }
  704. }
  705. //-----------------------------------------------------------------------------
  706. // Purpose:
  707. //-----------------------------------------------------------------------------
  708. void CTFStatPanel::Show()
  709. {
  710. m_bShouldBeVisible = true;
  711. HideLowerPriorityHudElementsInGroup( "mid" );
  712. }
  713. //-----------------------------------------------------------------------------
  714. // Purpose:
  715. //-----------------------------------------------------------------------------
  716. void CTFStatPanel::Hide()
  717. {
  718. m_bShouldBeVisible = false;
  719. if ( m_flTimeHide > 0 )
  720. {
  721. m_flTimeHide = 0;
  722. vgui::ivgui()->RemoveTickSignal( GetVPanel() );
  723. }
  724. UnhideLowerPriorityHudElementsInGroup( "mid" );
  725. }
  726. //-----------------------------------------------------------------------------
  727. // Purpose:
  728. //-----------------------------------------------------------------------------
  729. bool CTFStatPanel::ShouldDraw( void )
  730. {
  731. if ( !m_bShouldBeVisible )
  732. return false;
  733. if ( IsTakingAFreezecamScreenshot() )
  734. return false;
  735. if ( TFGameRules() && TFGameRules()->IsInArenaMode() == true )
  736. {
  737. m_flTimeHide = gpGlobals->curtime;
  738. return false;
  739. }
  740. return CHudElement::ShouldDraw();
  741. }
  742. //-----------------------------------------------------------------------------
  743. // Purpose:
  744. //-----------------------------------------------------------------------------
  745. void CTFStatPanel::ClearStatsInMemory( void )
  746. {
  747. m_aClassStats.RemoveAll();
  748. m_aMapStats.RemoveAll();
  749. m_bStatsChanged = true;
  750. ResetDisplayedStat();
  751. UpdateStatSummaryPanel();
  752. }
  753. //-----------------------------------------------------------------------------
  754. // Purpose:
  755. //-----------------------------------------------------------------------------
  756. void CTFStatPanel::ResetStats( void )
  757. {
  758. // Only allow this action if the player is connected to steam and the achievement manager exists.
  759. // Otherwise, our stat panel stats, stat file, and local steam context stats will be out of sync because UploadStats will fail.
  760. CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() );
  761. if ( !steamapicontext->SteamUserStats() || !pAchievementMgr )
  762. {
  763. MessageBox *msg = new MessageBox( "#TF_SteamRequired", "#TF_SteamRequiredResetStats" );
  764. if ( msg != NULL )
  765. {
  766. msg->AddActionSignalTarget(this);
  767. msg->MoveToFront();
  768. msg->DoModal();
  769. }
  770. return;
  771. }
  772. // The following operations are order dependent:
  773. // Nukes the panel's version of the stats. Note that after this is called the local steam stats are out of sync.
  774. ClearStatsInMemory();
  775. // WriteStats will write out our stat file (not really used on the PC, authoritative on the XBox 360).
  776. WriteStats();
  777. // At this point local and remote steam stats are out of sync.
  778. // Nuke the players stats on steam to seal the deal.
  779. steamapicontext->SteamUserStats()->ResetAllStats( false );
  780. // UploadStats will pull the players' class stats from CTFStatPanel and stomp the steam stats in memory.
  781. g_TFSteamStats.UploadStats();
  782. }
  783. //-----------------------------------------------------------------------------
  784. // Purpose: returns class stat struct for specified class
  785. //-----------------------------------------------------------------------------
  786. ClassStats_t &CTFStatPanel::GetClassStats( int iClass )
  787. {
  788. Assert( statPanel );
  789. Assert( iClass >= TF_FIRST_NORMAL_CLASS );
  790. Assert( iClass <= TF_LAST_NORMAL_CLASS );
  791. int i;
  792. for( i = 0; i < statPanel->m_aClassStats.Count(); i++ )
  793. {
  794. if ( statPanel->m_aClassStats[i].iPlayerClass == iClass )
  795. return statPanel->m_aClassStats[i];
  796. }
  797. ClassStats_t stats;
  798. stats.iPlayerClass = iClass;
  799. statPanel->m_aClassStats.AddToTail( stats );
  800. return statPanel->m_aClassStats[statPanel->m_aClassStats.Count()-1];
  801. }
  802. MapStats_t &CTFStatPanel::GetMapStats( map_identifier_t iMapID )
  803. {
  804. Assert( statPanel );
  805. Assert( IsValidMapID( iMapID ) );
  806. int i;
  807. for( i = 0; i < statPanel->m_aMapStats.Count(); i++ )
  808. {
  809. if ( statPanel->m_aMapStats[i].iMapID == iMapID )
  810. return statPanel->m_aMapStats[i];
  811. }
  812. MapStats_t stats;
  813. stats.iMapID = iMapID;
  814. statPanel->m_aMapStats.AddToTail( stats );
  815. return statPanel->m_aMapStats[statPanel->m_aMapStats.Count()-1];
  816. }
  817. bool CTFStatPanel::IsValidMapID( map_identifier_t iMapID )
  818. {
  819. for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ )
  820. {
  821. const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByIndex( i );
  822. if ( iMapID == pMap->GetStatsIdentifier() )
  823. {
  824. return true;
  825. }
  826. }
  827. return false;
  828. }
  829. const char* CTFStatPanel::GetMapNameFromID( map_identifier_t iMapID )
  830. {
  831. for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ )
  832. {
  833. const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByIndex( i );
  834. if ( iMapID == pMap->GetStatsIdentifier() )
  835. {
  836. return pMap->pszMapName;
  837. }
  838. }
  839. return "";
  840. }
  841. //-----------------------------------------------------------------------------
  842. // Purpose: Updates the stat summary panel w/current stats
  843. //-----------------------------------------------------------------------------
  844. void CTFStatPanel::UpdateStatSummaryPanel()
  845. {
  846. UpdateStatSummaryPanels( m_aClassStats );
  847. }
  848. //-----------------------------------------------------------------------------
  849. // Purpose: Return the total time this player has played the game, in hours.
  850. //-----------------------------------------------------------------------------
  851. float CTFStatPanel::GetTotalHoursPlayed( void )
  852. {
  853. float totalTimePlayed = 0;
  854. for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
  855. {
  856. totalTimePlayed += GetClassStats( iClass ).accumulated.m_iStat[ TFSTAT_PLAYTIME ] + GetClassStats( iClass ).accumulatedMVM.m_iStat[ TFSTAT_PLAYTIME ];
  857. }
  858. return totalTimePlayed / ( 60.0f * 60.0f );
  859. }
  860. //-----------------------------------------------------------------------------
  861. // Purpose: Renders stat value as string
  862. //-----------------------------------------------------------------------------
  863. void CTFStatPanel::GetStatValueAsString( int iValue, TFStatType_t statType, char *value, int valuelen )
  864. {
  865. if ( TFSTAT_PLAYTIME == statType )
  866. {
  867. // Format time as a time string
  868. Q_strncpy( value, FormatSeconds( iValue ), valuelen );
  869. }
  870. else
  871. {
  872. // all other stats are just displayed as #'s
  873. Q_snprintf( value, valuelen, "%d", iValue );
  874. }
  875. }
  876. //-----------------------------------------------------------------------------
  877. // Purpose: Called when we get a stat update for the local player
  878. //-----------------------------------------------------------------------------
  879. void CTFStatPanel::MsgFunc_PlayerStatsUpdate( bf_read &msg )
  880. {
  881. RoundStats_t stats;
  882. // get the fixed-size information
  883. int iClass = msg.ReadByte();
  884. bool bAlive = msg.ReadByte();
  885. int iSendBits = msg.ReadLong();
  886. Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS );
  887. if ( iClass < TF_FIRST_NORMAL_CLASS || iClass > TF_LAST_NORMAL_CLASS )
  888. return;
  889. // the bitfield indicates which stats are contained in the message. Set the stats appropriately.
  890. int iStat = TFSTAT_FIRST;
  891. while ( iSendBits > 0 && iStat <= TFSTAT_LAST )
  892. {
  893. if ( iSendBits & 1 )
  894. {
  895. stats.m_iStat[iStat] = msg.ReadLong();
  896. }
  897. iSendBits >>= 1;
  898. iStat++;
  899. }
  900. // sanity check: the message should contain exactly the # of bytes we expect based on the bit field
  901. Assert( !msg.IsOverflowed() );
  902. Assert( 0 == msg.GetNumBytesLeft() );
  903. // if byte count isn't correct, bail out and don't use this data, rather than risk polluting player stats with garbage
  904. if ( msg.IsOverflowed() || ( 0 != msg.GetNumBytesLeft() ) )
  905. return;
  906. UpdateStats( iClass, stats, bAlive );
  907. }
  908. void CTFStatPanel::MsgFunc_MapStatsUpdate( bf_read &msg )
  909. {
  910. RoundMapStats_t stats;
  911. // get the fixed-size information
  912. map_identifier_t iMapID = msg.ReadUBitLong( 32 );
  913. int iSendBits = msg.ReadLong();
  914. if ( !IsValidMapID( iMapID ) )
  915. return;
  916. // the bitfield indicates which stats are contained in the message. Set the stats appropriately.
  917. int iStat = TFMAPSTAT_FIRST;
  918. while ( iSendBits > 0 && iStat <= TFMAPSTAT_LAST )
  919. {
  920. if ( iSendBits & 1 )
  921. {
  922. stats.m_iStat[iStat] = msg.ReadLong();
  923. }
  924. iSendBits >>= 1;
  925. iStat++;
  926. }
  927. // sanity check: the message should contain exactly the # of bytes we expect based on the bit field
  928. Assert( !msg.IsOverflowed() );
  929. Assert( 0 == msg.GetNumBytesLeft() );
  930. // if byte count isn't correct, bail out and don't use this data, rather than risk polluting player stats with garbage
  931. if ( msg.IsOverflowed() || ( 0 != msg.GetNumBytesLeft() ) )
  932. return;
  933. UpdateMapStats( iMapID, stats );
  934. }
  935. /**********************************************************************************/
  936. void TestStatPanel( const CCommand &args )
  937. {
  938. int iPanelType;
  939. if( args.ArgC() < 2 )
  940. {
  941. ConMsg( "Usage: teststatpanel < panel type > < optional record type >\n" );
  942. ConMsg( "Usable panel types are %d to %d. Record types are %d to %d\n", TFSTAT_FIRST, TFSTAT_LAST, RECORDBREAK_NONE+1, RECORDBREAK_MAX-1 );
  943. return;
  944. }
  945. if ( statPanel )
  946. {
  947. iPanelType = atoi( args.Arg( 1 ) );
  948. int iRecordType = RECORDBREAK_BEST;
  949. if ( args.ArgC() >= 3 )
  950. {
  951. iRecordType = atoi( args.Arg( 2 ) );
  952. }
  953. if ( ( iPanelType <= TFSTAT_UNDEFINED ) || ( iPanelType >= TFSTAT_TOTAL ) || (iRecordType <= RECORDBREAK_NONE) || (iRecordType >= RECORDBREAK_MAX) )
  954. {
  955. ConMsg( "Usage: teststatpanel < panel type > < optional record type >\n" );
  956. ConMsg( "Usable panel types are %d to %d. Record types are %d to %d\n", TFSTAT_FIRST, TFSTAT_LAST, RECORDBREAK_NONE+1, RECORDBREAK_MAX-1 );
  957. return;
  958. }
  959. statPanel->TestStatPanel( (TFStatType_t) iPanelType, (RecordBreakType_t)iRecordType );
  960. }
  961. }
  962. //-----------------------------------------------------------------------------
  963. // Purpose:
  964. //-----------------------------------------------------------------------------
  965. void HideStatPanel()
  966. {
  967. if ( statPanel )
  968. {
  969. statPanel->Hide();
  970. }
  971. }
  972. //-----------------------------------------------------------------------------
  973. // Purpose:
  974. //-----------------------------------------------------------------------------
  975. void ResetPlayerStats()
  976. {
  977. if ( statPanel )
  978. {
  979. statPanel->ResetStats();
  980. }
  981. }
  982. //-----------------------------------------------------------------------------
  983. // Purpose:
  984. //-----------------------------------------------------------------------------
  985. void RefreshPlayerStats()
  986. {
  987. if ( statPanel )
  988. {
  989. if ( !statPanel->ReadStats() )
  990. {
  991. // Read failed, need to clear everything
  992. statPanel->ClearStatsInMemory();
  993. }
  994. }
  995. }
  996. ConCommand teststatpanel( "teststatpanel", TestStatPanel, "", FCVAR_DEVELOPMENTONLY );
  997. ConCommand hidestatpanel( "hidestatpanel", HideStatPanel, "", FCVAR_DEVELOPMENTONLY );
  998. ConCommand refreshplayerstats( "refreshplayerstats", RefreshPlayerStats, "", FCVAR_DEVELOPMENTONLY );
  999. ConCommand resetplayerstats( "resetplayerstats", ResetPlayerStats, "", FCVAR_CLIENTCMD_CAN_EXECUTE );