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.

1506 lines
50 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "cbase.h"
  8. #include <vgui_controls/Label.h>
  9. #include <vgui_controls/Button.h>
  10. #include <vgui_controls/ComboBox.h>
  11. #include <vgui_controls/ImagePanel.h>
  12. #include <vgui_controls/RichText.h>
  13. #include <vgui_controls/Frame.h>
  14. #include <vgui_controls/QueryBox.h>
  15. #include <vgui/IScheme.h>
  16. #include <vgui/ILocalize.h>
  17. #include <vgui/ISurface.h>
  18. #include "ienginevgui.h"
  19. #include <game/client/iviewport.h>
  20. #include "tf_tips.h"
  21. #include "tf_mapinfo.h"
  22. #include "vgui_avatarimage.h"
  23. #include "VGuiMatSurface/IMatSystemSurface.h"
  24. #include "tf_statsummary.h"
  25. #include <convar.h>
  26. #include "fmtstr.h"
  27. #include "tf_gamerules.h"
  28. #include "tf_gc_client.h"
  29. using namespace vgui;
  30. #if defined( REPLAY_ENABLED )
  31. extern bool g_bIsReplayRewinding;
  32. #else
  33. bool g_bIsReplayRewinding = false;
  34. #endif
  35. const char *g_pszTipsClassImages[] =
  36. {
  37. "", // TF_CLASS_UNDEFINED = 0,
  38. "class_portraits/scout", // TF_CLASS_SCOUT,
  39. "class_portraits/sniper",// TF_CLASS_SNIPER,
  40. "class_portraits/soldier", // TF_CLASS_SOLDIER,
  41. "class_portraits/demoman", // TF_CLASS_DEMOMAN,
  42. "class_portraits/medic", // TF_CLASS_MEDIC,
  43. "class_portraits/heavy", // TF_CLASS_HEAVYWEAPONS,
  44. "class_portraits/pyro", // TF_CLASS_PYRO,
  45. "class_portraits/spy", // TF_CLASS_SPY,
  46. "class_portraits/engineer", // TF_CLASS_ENGINEER,
  47. };
  48. ClassDetails_t g_PerClassStatDetails[15] =
  49. {
  50. { TFSTAT_POINTSSCORED, ALL_CLASSES, "#TF_ClassRecord_MostPoints", "#TF_ClassRecord_Alt_MostPoints" },
  51. { TFSTAT_KILLS, ALL_CLASSES, "#TF_ClassRecord_MostKills", "#TF_ClassRecord_Alt_MostKills" },
  52. { TFSTAT_KILLASSISTS, ALL_CLASSES, "#TF_ClassRecord_MostAssists", "#TF_ClassRecord_Alt_MostAssists" },
  53. { TFSTAT_CAPTURES, ALL_CLASSES, "#TF_ClassRecord_MostCaptures", "#TF_ClassRecord_Alt_MostCaptures" },
  54. { TFSTAT_DEFENSES, ALL_CLASSES, "#TF_ClassRecord_MostDefenses", "#TF_ClassRecord_Alt_MostDefenses" },
  55. { TFSTAT_DAMAGE, ALL_CLASSES, "#TF_ClassRecord_MostDamage", "#TF_ClassRecord_Alt_MostDamage" },
  56. { TFSTAT_BUILDINGSDESTROYED, ALL_CLASSES, "#TF_ClassRecord_MostDestruction", "#TF_ClassRecord_Alt_MostDestruction" },
  57. { TFSTAT_DOMINATIONS, ALL_CLASSES, "#TF_ClassRecord_MostDominations", "#TF_ClassRecord_Alt_MostDominations" },
  58. { TFSTAT_PLAYTIME, ALL_CLASSES, "#TF_ClassRecord_LongestLife", "#TF_ClassRecord_Alt_LongestLife" },
  59. { TFSTAT_HEALING, MAKESTATFLAG(TF_CLASS_MEDIC) | MAKESTATFLAG(TF_CLASS_ENGINEER) | MAKESTATFLAG(TF_CLASS_HEAVYWEAPONS), "#TF_ClassRecord_MostHealing", "#TF_ClassRecord_Alt_MostHealing" },
  60. { TFSTAT_INVULNS, MAKESTATFLAG(TF_CLASS_MEDIC), "#TF_ClassRecord_MostInvulns", "#TF_ClassRecord_Alt_MostInvulns" },
  61. { TFSTAT_MAXSENTRYKILLS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostSentryKills", "#TF_ClassRecord_Alt_MostSentryKills" },
  62. { TFSTAT_TELEPORTS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostTeleports", "#TF_ClassRecord_Alt_MostTeleports" },
  63. { TFSTAT_HEADSHOTS, MAKESTATFLAG(TF_CLASS_SNIPER) | MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostHeadshots", "#TF_ClassRecord_Alt_MostHeadshots" },
  64. { TFSTAT_BACKSTABS, MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostBackstabs", "#TF_ClassRecord_Alt_MostBackstabs" },
  65. };
  66. ClassDetails_t g_PerClassMVMStatDetails[12] =
  67. {
  68. { TFSTAT_POINTSSCORED, ALL_CLASSES, "#TF_ClassRecord_MostPoints", "#TF_ClassRecord_Alt_MostPoints" },
  69. { TFSTAT_KILLS, ALL_CLASSES, "#TF_ClassRecord_MostKills", "#TF_ClassRecord_Alt_MostKills" },
  70. { TFSTAT_KILLASSISTS, ALL_CLASSES, "#TF_ClassRecord_MostAssists", "#TF_ClassRecord_Alt_MostAssists" },
  71. { TFSTAT_DEFENSES, ALL_CLASSES, "#TF_ClassRecord_MostDefenses", "#TF_ClassRecord_Alt_MostDefenses" },
  72. { TFSTAT_DAMAGE, ALL_CLASSES, "#TF_ClassRecord_MostDamage", "#TF_ClassRecord_Alt_MostDamage" },
  73. { TFSTAT_PLAYTIME, ALL_CLASSES, "#TF_ClassRecord_LongestLife", "#TF_ClassRecord_Alt_LongestLife" },
  74. { TFSTAT_HEALING, MAKESTATFLAG(TF_CLASS_MEDIC) | MAKESTATFLAG(TF_CLASS_ENGINEER) | MAKESTATFLAG(TF_CLASS_HEAVYWEAPONS), "#TF_ClassRecord_MostHealing", "#TF_ClassRecord_Alt_MostHealing" },
  75. { TFSTAT_INVULNS, MAKESTATFLAG(TF_CLASS_MEDIC), "#TF_ClassRecord_MostInvulns", "#TF_ClassRecord_Alt_MostInvulns" },
  76. { TFSTAT_MAXSENTRYKILLS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostSentryKills", "#TF_ClassRecord_Alt_MostSentryKills" },
  77. { TFSTAT_TELEPORTS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostTeleports", "#TF_ClassRecord_Alt_MostTeleports" },
  78. { TFSTAT_HEADSHOTS, MAKESTATFLAG(TF_CLASS_SNIPER) | MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostHeadshots", "#TF_ClassRecord_Alt_MostHeadshots" },
  79. { TFSTAT_BACKSTABS, MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostBackstabs", "#TF_ClassRecord_Alt_MostBackstabs" },
  80. };
  81. CTFStatsSummaryPanel *g_pTFStatsSummaryPanel = NULL;
  82. CUtlVector<CTFStatsSummaryPanel *> g_vecStatPanels;
  83. //-----------------------------------------------------------------------------
  84. // Purpose:
  85. //-----------------------------------------------------------------------------
  86. void UpdateStatSummaryPanels( CUtlVector<ClassStats_t> &vecClassStats )
  87. {
  88. for ( int i = 0; i < g_vecStatPanels.Count(); i++ )
  89. {
  90. g_vecStatPanels[i]->SetStats( vecClassStats );
  91. }
  92. }
  93. //-----------------------------------------------------------------------------
  94. // Purpose: Returns the global stats summary panel
  95. //-----------------------------------------------------------------------------
  96. CTFStatsSummaryPanel *GStatsSummaryPanel()
  97. {
  98. if ( NULL == g_pTFStatsSummaryPanel )
  99. {
  100. g_pTFStatsSummaryPanel = new CTFStatsSummaryPanel();
  101. }
  102. return g_pTFStatsSummaryPanel;
  103. }
  104. //-----------------------------------------------------------------------------
  105. // Purpose: Destroys the global stats summary panel
  106. //-----------------------------------------------------------------------------
  107. void DestroyStatsSummaryPanel()
  108. {
  109. if ( NULL != g_pTFStatsSummaryPanel )
  110. {
  111. g_pTFStatsSummaryPanel->MarkForDeletion();
  112. g_pTFStatsSummaryPanel = NULL;
  113. }
  114. }
  115. //-----------------------------------------------------------------------------
  116. // Purpose: Constructor
  117. //-----------------------------------------------------------------------------
  118. CTFStatsSummaryPanel::CTFStatsSummaryPanel()
  119. : BaseClass( NULL, "TFStatsSummary", vgui::scheme()->LoadSchemeFromFile( "Resource/ClientScheme.res", "ClientScheme" ) )
  120. , m_bShowingLeaderboard( false )
  121. , m_bLoadingCommunityMap( false )
  122. {
  123. Init();
  124. }
  125. //-----------------------------------------------------------------------------
  126. // Purpose: Constructor
  127. //-----------------------------------------------------------------------------
  128. void CTFStatsSummaryPanel::Init( void )
  129. {
  130. m_bControlsLoaded = false;
  131. m_bInteractive = false;
  132. m_bEmbedded = false;
  133. m_xStartLHBar = 0;
  134. m_xStartRHBar = 0;
  135. m_iBarHeight = 1;
  136. m_iBarMaxWidth = 1;
  137. m_pPlayerData = new vgui::EditablePanel( this, "statdata" );
  138. m_pInteractiveHeaders = new vgui::EditablePanel( m_pPlayerData, "InteractiveHeaders" );
  139. m_pNonInteractiveHeaders = new vgui::EditablePanel( m_pPlayerData, "NonInteractiveHeaders" );
  140. m_pBarChartComboBoxA = new vgui::ComboBox( m_pInteractiveHeaders, "BarChartComboA", 10, false );
  141. m_pBarChartComboBoxB = new vgui::ComboBox( m_pInteractiveHeaders, "BarChartComboB", 10, false );
  142. m_pClassComboBox = new vgui::ComboBox( m_pInteractiveHeaders, "ClassCombo", 10, false );
  143. m_pTipImage = new CTFImagePanel( this, "TipImage" );
  144. m_pTipText = new vgui::Label( this, "TipText", "" );
  145. m_pMapInfoPanel = NULL;
  146. m_pMainBackground = NULL;
  147. m_pLeaderboardTitle = NULL;
  148. m_pContributedPanel = NULL;
  149. #ifdef _X360
  150. m_pFooter = new CTFFooter( this, "Footer" );
  151. m_bShowBackButton = false;
  152. #else
  153. m_pNextTipButton = new vgui::Button( this, "NextTipButton", "" );
  154. m_pResetStatsButton = new vgui::Button( this, "ResetStatsButton", "" );
  155. m_pCloseButton = new vgui::Button( this, "CloseButton", "" );
  156. #endif
  157. m_pBarChartComboBoxA->AddActionSignalTarget( this );
  158. m_pBarChartComboBoxB->AddActionSignalTarget( this );
  159. m_pClassComboBox->AddActionSignalTarget( this );
  160. ListenForGameEvent( "server_spawn" );
  161. Reset();
  162. g_vecStatPanels.AddToTail( this );
  163. }
  164. //-----------------------------------------------------------------------------
  165. // Purpose: Constructor
  166. //-----------------------------------------------------------------------------
  167. CTFStatsSummaryPanel::CTFStatsSummaryPanel( vgui::Panel *parent ) : BaseClass( parent, "TFStatsSummary",
  168. vgui::scheme()->LoadSchemeFromFile( "Resource/ClientScheme.res", "ClientScheme" ) )
  169. {
  170. Init();
  171. }
  172. //-----------------------------------------------------------------------------
  173. // Purpose:
  174. //-----------------------------------------------------------------------------
  175. CTFStatsSummaryPanel::~CTFStatsSummaryPanel()
  176. {
  177. g_vecStatPanels.FindAndRemove( this );
  178. }
  179. //-----------------------------------------------------------------------------
  180. // Purpose: Shows this dialog as a modal dialog
  181. //-----------------------------------------------------------------------------
  182. void CTFStatsSummaryPanel::ShowModal()
  183. {
  184. #ifdef _X360
  185. m_bInteractive = false;
  186. m_bShowBackButton = true;
  187. #else
  188. // we are in interactive mode, enable controls
  189. m_bInteractive = true;
  190. #endif
  191. SetParent( enginevgui->GetPanel( PANEL_GAMEUIDLL ) );
  192. UpdateDialog();
  193. SetVisible( true );
  194. MoveToFront();
  195. }
  196. //-----------------------------------------------------------------------------
  197. // Purpose:
  198. //-----------------------------------------------------------------------------
  199. void CTFStatsSummaryPanel::SetupForEmbedded( void )
  200. {
  201. m_bInteractive = true;
  202. m_bEmbedded = true;
  203. UpdateDialog();
  204. InvalidateLayout( true, true );
  205. }
  206. //-----------------------------------------------------------------------------
  207. // Purpose:
  208. //-----------------------------------------------------------------------------
  209. void CTFStatsSummaryPanel::PerformLayout()
  210. {
  211. BaseClass::PerformLayout();
  212. #ifndef _X360
  213. if ( m_pTipImage && m_pTipText )
  214. {
  215. int iX,iY;
  216. m_pTipImage->GetPos(iX,iY);
  217. int iTX, iTY;
  218. m_pTipText->GetPos(iTX, iTY);
  219. m_pTipText->SetPos( iX + m_pTipImage->GetWide() + XRES(8), iTY );
  220. }
  221. if ( m_pNextTipButton )
  222. {
  223. m_pNextTipButton->SizeToContents();
  224. }
  225. if ( m_pResetStatsButton )
  226. {
  227. m_pResetStatsButton->SizeToContents();
  228. }
  229. #endif
  230. }
  231. //-----------------------------------------------------------------------------
  232. // Purpose:
  233. //-----------------------------------------------------------------------------
  234. void CTFStatsSummaryPanel::OnThink()
  235. {
  236. BaseClass::OnThink();
  237. if ( m_bShowingLeaderboard )
  238. {
  239. UpdateLeaderboard();
  240. }
  241. }
  242. //-----------------------------------------------------------------------------
  243. // Purpose: Command handler
  244. //-----------------------------------------------------------------------------
  245. void CTFStatsSummaryPanel::OnCommand( const char *command )
  246. {
  247. if ( 0 == Q_stricmp( command, "vguicancel" ) )
  248. {
  249. m_bInteractive = false;
  250. UpdateDialog();
  251. SetVisible( false );
  252. SetParent( (VPANEL) NULL );
  253. #ifdef _X360
  254. SetDefaultSelections();
  255. m_bShowBackButton = true;
  256. #endif
  257. }
  258. #ifndef _X360
  259. else if ( 0 == Q_stricmp( command, "resetstatsbutton" ) )
  260. {
  261. QueryBox *qb = new QueryBox( "#GameUI_Confirm", "#TF_ConfirmResetStats" );
  262. if (qb != NULL)
  263. {
  264. qb->SetOKCommand(new KeyValues("DoResetStats") );
  265. qb->AddActionSignalTarget(this);
  266. qb->MoveToFront();
  267. qb->DoModal();
  268. }
  269. }
  270. #endif
  271. else if ( 0 == Q_stricmp( command, "nexttip" ) )
  272. {
  273. UpdateTip();
  274. }
  275. BaseClass::OnCommand( command );
  276. }
  277. //-----------------------------------------------------------------------------
  278. // Purpose: Resets the dialog
  279. //-----------------------------------------------------------------------------
  280. void CTFStatsSummaryPanel::Reset()
  281. {
  282. m_aClassStats.RemoveAll();
  283. SetDefaultSelections();
  284. }
  285. //-----------------------------------------------------------------------------
  286. // Purpose: Sets all user-controllable dialog settings to default values
  287. //-----------------------------------------------------------------------------
  288. void CTFStatsSummaryPanel::SetDefaultSelections()
  289. {
  290. m_iSelectedClass = TF_CLASS_UNDEFINED;
  291. m_statBarGraph[0] = TFSTAT_POINTSSCORED;
  292. m_displayBarGraph[0]= SHOW_MAX;
  293. m_statBarGraph[1] = TFSTAT_PLAYTIME;
  294. m_displayBarGraph[1] = SHOW_TOTAL;
  295. m_pBarChartComboBoxA->ActivateItemByRow( 0 );
  296. m_pBarChartComboBoxB->ActivateItemByRow( 10 );
  297. }
  298. //-----------------------------------------------------------------------------
  299. // Purpose: Set the background image based on the current mode
  300. //-----------------------------------------------------------------------------
  301. void CTFStatsSummaryPanel::UpdateMainBackground( void )
  302. {
  303. if ( IsPC() )
  304. {
  305. m_pMainBackground = dynamic_cast<ImagePanel *>( FindChildByName( "MainBackground" ) );
  306. if ( m_pMainBackground )
  307. {
  308. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() );
  309. // determine if we're in widescreen or not and select the appropriate image
  310. int screenWide, screenTall;
  311. surface()->GetScreenSize( screenWide, screenTall );
  312. float aspectRatio = (float)screenWide/(float)screenTall;
  313. bool bIsWidescreen = aspectRatio >= 1.5999f;
  314. if ( g_bIsReplayRewinding )
  315. {
  316. m_pMainBackground->SetImage( bIsWidescreen ? "../console/rewind_background_widescreen" : "../console/rewind_background" );
  317. }
  318. else if ( engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() )
  319. {
  320. m_pMainBackground->SetImage( bIsWidescreen ? "../console/replay_loading_widescreen" : "../console/replay_loading" );
  321. }
  322. else if ( pMatchDesc && pMatchDesc->GetMapLoadBackgroundOverride( bIsWidescreen ) ) // Use match override if we have one
  323. {
  324. m_pMainBackground->SetImage( pMatchDesc->GetMapLoadBackgroundOverride( bIsWidescreen ) );
  325. }
  326. else
  327. {
  328. m_pMainBackground->SetImage( bIsWidescreen ? "../console/background01_widescreen" : "../console/background01" );
  329. }
  330. }
  331. }
  332. }
  333. //-----------------------------------------------------------------------------
  334. // Purpose: Applies scheme settings
  335. //-----------------------------------------------------------------------------
  336. void CTFStatsSummaryPanel::ApplySchemeSettings(vgui::IScheme *pScheme)
  337. {
  338. BaseClass::ApplySchemeSettings( pScheme );
  339. SetProportional( true );
  340. if ( m_bEmbedded )
  341. {
  342. LoadControlSettings( "Resource/UI/StatSummary_Embedded.res" );
  343. }
  344. else
  345. {
  346. LoadControlSettings( "Resource/UI/StatSummary.res" );
  347. }
  348. m_bControlsLoaded = true;
  349. // set the background image
  350. UpdateMainBackground();
  351. m_pMapInfoPanel = dynamic_cast< EditablePanel *>( FindChildByName( "MapInfo" ) );
  352. m_vecLeaderboardEntries.RemoveAll();
  353. if ( m_pMapInfoPanel )
  354. {
  355. for ( int i = 0; i < 10; ++ i )
  356. {
  357. vgui::EditablePanel *pEntryUI = new vgui::EditablePanel( m_pMapInfoPanel, "LeaderboardEntry" );
  358. pEntryUI->ApplySchemeSettings( pScheme );
  359. pEntryUI->LoadControlSettings( "Resource/UI/LeaderboardEntry.res" );
  360. m_vecLeaderboardEntries.AddToTail( pEntryUI );
  361. }
  362. }
  363. // get the dimensions and position of a left-hand bar and a right-hand bar so we can do bar sizing later
  364. Panel *pLHBar = m_pPlayerData->FindChildByName( "ClassBar1A" );
  365. Panel *pRHBar = m_pPlayerData->FindChildByName( "ClassBar1B" );
  366. if ( pLHBar && pRHBar )
  367. {
  368. int y;
  369. pLHBar->GetBounds( m_xStartLHBar, y, m_iBarMaxWidth, m_iBarHeight );
  370. pRHBar->GetBounds( m_xStartRHBar, y, m_iBarMaxWidth, m_iBarHeight );
  371. }
  372. // fill the combo box selections appropriately
  373. InitBarChartComboBox( m_pBarChartComboBoxA );
  374. InitBarChartComboBox( m_pBarChartComboBoxB );
  375. // fill the class names in the class combo box
  376. HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ScoreboardSmall", true );
  377. m_pClassComboBox->SetFont( hFont );
  378. m_pClassComboBox->RemoveAll();
  379. KeyValues *pKeyValues = new KeyValues( "data" );
  380. pKeyValues->SetInt( "class", TF_CLASS_UNDEFINED );
  381. m_pClassComboBox->AddItem( "#StatSummary_Label_AsAnyClass", pKeyValues );
  382. for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ )
  383. {
  384. if ( iClass == TF_CLASS_CIVILIAN )
  385. continue;
  386. pKeyValues = new KeyValues( "data" );
  387. pKeyValues->SetInt( "class", iClass );
  388. m_pClassComboBox->AddItem( g_aPlayerClassNames[iClass], pKeyValues );
  389. }
  390. m_pClassComboBox->ActivateItemByRow( 0 );
  391. if ( m_pMapInfoPanel )
  392. {
  393. m_pContributedPanel = dynamic_cast< vgui::EditablePanel* >( m_pMapInfoPanel->FindChildByName( "ContributedLabel" ) );
  394. }
  395. SetDefaultSelections();
  396. UpdateDialog();
  397. if ( !m_bEmbedded )
  398. {
  399. SetVisible( false );
  400. }
  401. }
  402. //-----------------------------------------------------------------------------
  403. // Purpose:
  404. //-----------------------------------------------------------------------------
  405. void CTFStatsSummaryPanel::OnKeyCodePressed( KeyCode code )
  406. {
  407. if ( IsX360() )
  408. {
  409. if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
  410. {
  411. OnCommand( "nexttip" ) ;
  412. }
  413. else if ( code == KEY_XBUTTON_B || code == STEAMCONTROLLER_B )
  414. {
  415. OnCommand( "vguicancel" );
  416. }
  417. }
  418. }
  419. //-----------------------------------------------------------------------------
  420. // Purpose: Sets stats to use
  421. //-----------------------------------------------------------------------------
  422. void CTFStatsSummaryPanel::SetStats( CUtlVector<ClassStats_t> &vecClassStats )
  423. {
  424. m_aClassStats = vecClassStats;
  425. if ( m_bControlsLoaded )
  426. {
  427. UpdateDialog();
  428. }
  429. }
  430. //-----------------------------------------------------------------------------
  431. // Purpose: Updates the dialog
  432. //-----------------------------------------------------------------------------
  433. void CTFStatsSummaryPanel::ClearMapLabel()
  434. {
  435. SetDialogVariable( "maplabel", "" );
  436. SetDialogVariable( "maptype", "" );
  437. vgui::Label *pLabel = dynamic_cast<Label *>( FindChildByName( "OnYourWayLabel" ) );
  438. if ( pLabel && pLabel->IsVisible() )
  439. {
  440. pLabel->SetVisible( false );
  441. }
  442. pLabel = dynamic_cast<Label *>( FindChildByName( "MapType" ) );
  443. if ( pLabel && pLabel->IsVisible() )
  444. {
  445. pLabel->SetVisible( false );
  446. }
  447. if ( m_pContributedPanel )
  448. {
  449. m_pContributedPanel->SetVisible( false );
  450. }
  451. }
  452. //-----------------------------------------------------------------------------
  453. // Purpose:
  454. //-----------------------------------------------------------------------------
  455. void CTFStatsSummaryPanel::ShowMapInfo( bool bShowMapInfo, bool bIsMVM /*= false*/, bool bBackgroundOverride /*= false*/ )
  456. {
  457. if ( m_pMainBackground )
  458. {
  459. m_pMainBackground->SetVisible( !bShowMapInfo );
  460. }
  461. m_pPlayerData->SetVisible( bIsMVM || !bShowMapInfo );
  462. m_pNextTipButton->SetVisible( m_bInteractive && !bShowMapInfo );
  463. m_pResetStatsButton->SetVisible( m_bInteractive && !bShowMapInfo );
  464. if ( m_pMapInfoPanel )
  465. {
  466. m_pMapInfoPanel->SetVisible( bShowMapInfo );
  467. vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" );
  468. if ( pInfoBG )
  469. {
  470. pInfoBG->SetVisible( bShowMapInfo && !bIsMVM && !bBackgroundOverride );
  471. }
  472. }
  473. }
  474. //-----------------------------------------------------------------------------
  475. // Purpose:
  476. //-----------------------------------------------------------------------------
  477. void CTFStatsSummaryPanel::OnMapLoad( const char *pMapName )
  478. {
  479. if ( g_bIsReplayRewinding || engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() )
  480. return;
  481. bool bWidescreenBackground = false;
  482. bool bIsMVM = ( pMapName && !Q_strncmp( pMapName, "mvm_", 4 ) );
  483. const char *pszBackgroundOverride = NULL;
  484. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() );
  485. if ( pMatchDesc )
  486. {
  487. int screenWide, screenTall;
  488. surface()->GetScreenSize( screenWide, screenTall );
  489. float aspectRatio = (float)screenWide/(float)screenTall;
  490. bool bWideScreen = aspectRatio >= 1.5999f;
  491. // Check if there's a widescreen override
  492. if( bWideScreen )
  493. {
  494. pszBackgroundOverride = pMatchDesc->GetMapLoadBackgroundOverride( true );
  495. if ( pszBackgroundOverride )
  496. {
  497. // Success! We're done
  498. bWidescreenBackground = true;
  499. }
  500. }
  501. if ( !bWideScreen && !pszBackgroundOverride )
  502. {
  503. pszBackgroundOverride = pMatchDesc->GetMapLoadBackgroundOverride( false );
  504. }
  505. }
  506. else if ( bIsMVM )
  507. {
  508. // this will preserve the current behavior for non-matchmaking servers
  509. pszBackgroundOverride = "mvm_background_map";
  510. }
  511. bool bIsCommunityMap = false;
  512. const char *pAuthors = NULL;
  513. const MapDef_t *pMapInfo = GetItemSchema()->GetMasterMapDefByName( pMapName );
  514. if ( pMapInfo )
  515. {
  516. bIsCommunityMap = pMapInfo->IsCommunityMap();
  517. pAuthors = pMapInfo->pszAuthorsLocKey;
  518. }
  519. ShowMapInfo( true, bIsMVM, ( pszBackgroundOverride != NULL ) );
  520. m_xStartLeaderboard = 0;
  521. m_yStartLeaderboard = 0;
  522. // If we're loading a background map, don't display anything
  523. // HACK: Client doesn't get gpGlobals->eLoadType, so just do string compare for now.
  524. if ( Q_stristr( pMapName, "background") )
  525. {
  526. ClearMapLabel();
  527. }
  528. else
  529. {
  530. // set the map name in the UI
  531. wchar_t wzMapName[255]=L"";
  532. g_pVGuiLocalize->ConvertANSIToUnicode( GetMapDisplayName( pMapName ), wzMapName, sizeof( wzMapName ) );
  533. SetDialogVariable( "maplabel", wzMapName );
  534. SetDialogVariable( "maptype", g_pVGuiLocalize->Find( GetMapType( pMapName ) ) );
  535. vgui::Label *pLabel = dynamic_cast<Label *>( FindChildByName( "OnYourWayLabel" ) );
  536. if ( pLabel && !pLabel->IsVisible() )
  537. {
  538. pLabel->SetVisible( true );
  539. }
  540. pLabel = dynamic_cast<Label *>( FindChildByName( "MapType" ) );
  541. if ( pLabel && !pLabel->IsVisible() )
  542. {
  543. pLabel->SetVisible( true );
  544. }
  545. ImagePanel *pMapImage = m_pMapInfoPanel ? dynamic_cast< ImagePanel *>( m_pMapInfoPanel->FindChildByName( "MapImage" ) ) : NULL;
  546. if ( pMapImage )
  547. {
  548. // load the map image (if it exists for the current map)
  549. char szMapImage[ MAX_PATH ];
  550. Q_snprintf( szMapImage, sizeof( szMapImage ), "VGUI/maps/menu_photos_%s", pMapName );
  551. Q_strlower( szMapImage );
  552. IMaterial *pMapMaterial = materials->FindMaterial( szMapImage, TEXTURE_GROUP_VGUI, false );
  553. if ( pMapMaterial && !IsErrorMaterial( pMapMaterial ) && !pszBackgroundOverride )
  554. {
  555. // take off the vgui/ at the beginning when we set the image
  556. Q_snprintf( szMapImage, sizeof( szMapImage ), "maps/menu_photos_%s", pMapName );
  557. Q_strlower( szMapImage );
  558. pMapImage->SetImage( szMapImage );
  559. pMapImage->SetVisible( true );
  560. }
  561. else
  562. {
  563. pMapImage->SetVisible( false );
  564. }
  565. }
  566. ImagePanel *pBackgroundImage = m_pMapInfoPanel ? dynamic_cast< ImagePanel *>( m_pMapInfoPanel->FindChildByName( "Background" ) ) : NULL;
  567. if ( pBackgroundImage )
  568. {
  569. const char* pszBackgroundImage = pszBackgroundOverride ? pszBackgroundOverride : "stamp_background_map";
  570. pBackgroundImage->SetImage( pszBackgroundImage );
  571. // Resize to accomodate the background image coming in
  572. if ( bWidescreenBackground )
  573. {
  574. pBackgroundImage->SetWide( GetWide() );
  575. }
  576. else
  577. {
  578. pBackgroundImage->SetWide( GetTall() * ( 4.f / 3.f ) );
  579. }
  580. }
  581. if ( bIsMVM )
  582. {
  583. UpdateClassDetails( true );
  584. m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" );
  585. m_pMapInfoPanel->SetDialogVariable( "title", "" );
  586. m_pMapInfoPanel->SetDialogVariable( "authors", "" );
  587. FOR_EACH_VEC( m_vecLeaderboardEntries, i )
  588. {
  589. EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] );
  590. if ( pContainer )
  591. {
  592. pContainer->SetVisible( false );
  593. }
  594. }
  595. }
  596. else
  597. {
  598. m_pLeaderboardTitle = NULL;
  599. // add authors
  600. if ( m_pMapInfoPanel )
  601. {
  602. if ( bIsCommunityMap )
  603. {
  604. m_pMapInfoPanel->SetDialogVariable( "title", g_pVGuiLocalize->Find( "#TF_MapAuthors_Community_Title" ) );
  605. m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" );
  606. m_pMapInfoPanel->SetDialogVariable( "authors", g_pVGuiLocalize->Find( pAuthors ) );
  607. m_pLeaderboardTitle = m_pMapInfoPanel->FindChildByName( "MapLeaderboardTitle" );
  608. }
  609. else
  610. {
  611. m_pMapInfoPanel->SetDialogVariable( "title", g_pVGuiLocalize->Find( "#TF_DuelLeaderboard_Title" ) );
  612. m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" );
  613. m_pMapInfoPanel->SetDialogVariable( "authors", "" );
  614. m_pLeaderboardTitle = m_pMapInfoPanel->FindChildByName( "Title" );
  615. }
  616. }
  617. if ( m_pLeaderboardTitle )
  618. {
  619. m_pLeaderboardTitle->GetPos( m_xStartLeaderboard, m_yStartLeaderboard );
  620. m_yStartLeaderboard += m_pLeaderboardTitle->GetTall();
  621. }
  622. // request leaderboard data
  623. m_bShowingLeaderboard = true;
  624. if ( bIsCommunityMap )
  625. {
  626. MapInfo_RefreshLeaderboard( pMapName );
  627. }
  628. else
  629. {
  630. Leaderboards_Refresh();
  631. }
  632. m_bLoadingCommunityMap = bIsCommunityMap;
  633. if ( m_pContributedPanel && steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamFriends() )
  634. {
  635. int iDonationAmount = MapInfo_GetDonationAmount( steamapicontext->SteamUser()->GetSteamID().GetAccountID(), pMapName );
  636. m_pContributedPanel->SetVisible( iDonationAmount != 0 );
  637. if ( iDonationAmount != 0 )
  638. {
  639. m_pContributedPanel->SetDialogVariable( "playername", steamapicontext->SteamFriends()->GetPersonaName() );
  640. }
  641. }
  642. UpdateLeaderboard();
  643. }
  644. }
  645. }
  646. void CTFStatsSummaryPanel::UpdateLeaderboard()
  647. {
  648. if ( m_pMapInfoPanel == NULL || steamapicontext == NULL || steamapicontext->SteamUserStats() == NULL || steamapicontext->SteamUser() == NULL )
  649. return;
  650. const int kMaxVisible_Supporters = 5;
  651. const int kIdeallyNumVisible_Supporters = 3;
  652. const int kMaxVisible_DuelWins = 10;
  653. const int kIdeallyNumVisible_DuelWins = 5;
  654. // retrieve scores
  655. CUtlVector< LeaderboardEntry_t* > scores;
  656. bool bVisible = true;
  657. int iNumLeaderboardEntries = 0;
  658. if ( m_bLoadingCommunityMap )
  659. {
  660. bVisible = MapInfo_GetLeaderboardInfo( engine->GetLevelName(), scores, iNumLeaderboardEntries, kIdeallyNumVisible_Supporters );
  661. wchar_t wzNumEntriesString[256];
  662. _snwprintf( wzNumEntriesString, ARRAYSIZE( wzNumEntriesString ), L"%i", iNumLeaderboardEntries );
  663. wchar_t wzTitle[256];
  664. g_pVGuiLocalize->ConstructString_safe( wzTitle, g_pVGuiLocalize->Find( "#TF_MapDonators_Title" ), 1, wzNumEntriesString );
  665. m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", wzTitle );
  666. }
  667. else
  668. {
  669. bVisible = Leaderboards_GetDuelWins( scores, false );
  670. if ( bVisible && scores.Count() < kIdeallyNumVisible_DuelWins )
  671. {
  672. bVisible = Leaderboards_GetDuelWins( scores, true ) && scores.Count() > 0;
  673. }
  674. // show old stats
  675. m_pPlayerData->SetVisible( bVisible == false );
  676. if ( m_pMapInfoPanel )
  677. {
  678. vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" );
  679. if ( pInfoBG )
  680. {
  681. pInfoBG->SetVisible( bVisible );
  682. }
  683. }
  684. }
  685. const int kMaxVisible = m_bLoadingCommunityMap ? kMaxVisible_Supporters : kMaxVisible_DuelWins;
  686. // try to show local player in relation to the people in the list
  687. if ( bVisible && scores.Count() > 0 && steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUserStats() )
  688. {
  689. int iLocalPlayerIdx = -1;
  690. CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID();
  691. FOR_EACH_VEC( scores, i )
  692. {
  693. const LeaderboardEntry_t *leaderboardEntry = scores[i];
  694. if ( leaderboardEntry->m_steamIDUser == localSteamID )
  695. {
  696. iLocalPlayerIdx = i;
  697. break;
  698. }
  699. }
  700. // local player is in the list, but is outside the visible range
  701. // so we want to move them to the last spot
  702. // and move the closest person above them as well
  703. if ( iLocalPlayerIdx >= kMaxVisible )
  704. {
  705. LeaderboardEntry_t *entryLocalPlayer = scores[iLocalPlayerIdx];
  706. LeaderboardEntry_t *closestPlayer = scores[iLocalPlayerIdx - 1];
  707. scores[kMaxVisible - 1] = entryLocalPlayer;
  708. scores[kMaxVisible - 2] = closestPlayer;
  709. }
  710. }
  711. // set avatars and names
  712. int x = m_xStartLeaderboard;
  713. int y = m_yStartLeaderboard;
  714. FOR_EACH_VEC( m_vecLeaderboardEntries, i )
  715. {
  716. EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] );
  717. if ( pContainer )
  718. {
  719. bool bIsEntryVisible = bVisible && i < scores.Count() && i < kMaxVisible;
  720. pContainer->SetVisible( bIsEntryVisible );
  721. pContainer->SetPos( x, y );
  722. y += pContainer->GetTall();
  723. if ( bIsEntryVisible )
  724. {
  725. const LeaderboardEntry_t *leaderboardEntry = scores[i];
  726. const CSteamID &steamID = leaderboardEntry->m_steamIDUser;
  727. pContainer->SetDialogVariable( "username", CFmtStr( "%d. %s - %d", leaderboardEntry->m_nGlobalRank, InventoryManager()->PersonaName_Get( steamID.GetAccountID() ), leaderboardEntry->m_nScore ) );
  728. CAvatarImagePanel *pAvatar = dynamic_cast< CAvatarImagePanel* >( pContainer->FindChildByName( "AvatarImage" ) );
  729. if ( pAvatar )
  730. {
  731. pAvatar->SetShouldDrawFriendIcon( false );
  732. pAvatar->SetPlayer( steamID, k_EAvatarSize32x32 );
  733. }
  734. }
  735. }
  736. }
  737. if ( m_pLeaderboardTitle )
  738. {
  739. bool bShowTitle = bVisible && scores.Count() > 0;
  740. if ( m_pLeaderboardTitle->IsVisible() != bShowTitle )
  741. {
  742. m_pLeaderboardTitle->SetVisible( bShowTitle );
  743. }
  744. }
  745. m_bShowingLeaderboard = bVisible;
  746. }
  747. //-----------------------------------------------------------------------------
  748. // Purpose: Updates the dialog
  749. //-----------------------------------------------------------------------------
  750. void CTFStatsSummaryPanel::UpdateDialog()
  751. {
  752. UpdateMainBackground();
  753. if ( g_bIsReplayRewinding || engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() )
  754. {
  755. // hide all of the various panels for the other loadscreen modes
  756. if ( IsPC() )
  757. {
  758. ClearMapLabel();
  759. m_pPlayerData->SetVisible( false );
  760. m_pNextTipButton->SetVisible( false );
  761. m_pResetStatsButton->SetVisible( false );
  762. m_pInteractiveHeaders->SetVisible( false );
  763. m_pNonInteractiveHeaders->SetVisible( false );
  764. m_pTipText->SetVisible( false );
  765. m_pTipImage->SetVisible( false );
  766. if ( m_pMapInfoPanel )
  767. {
  768. m_pMapInfoPanel->SetVisible( false );
  769. vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" );
  770. if ( pInfoBG )
  771. {
  772. pInfoBG->SetVisible( false );
  773. }
  774. }
  775. }
  776. return;
  777. }
  778. RandomSeed( Plat_MSTime() );
  779. m_iTotalSpawns = 0;
  780. // if we don't have stats for any class, add empty stat entries for them
  781. for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ )
  782. {
  783. if ( iClass == TF_CLASS_CIVILIAN )
  784. continue; // Ignore the civilian.
  785. int j;
  786. for ( j = 0; j < m_aClassStats.Count(); j++ )
  787. {
  788. if ( m_aClassStats[j].iPlayerClass == iClass )
  789. {
  790. m_iTotalSpawns += m_aClassStats[j].iNumberOfRounds;
  791. break;
  792. }
  793. }
  794. if ( j == m_aClassStats.Count() )
  795. {
  796. ClassStats_t stats;
  797. stats.iPlayerClass = iClass;
  798. m_aClassStats.AddToTail( stats );
  799. }
  800. }
  801. ClearMapLabel();
  802. #ifdef _X360
  803. if ( m_pFooter )
  804. {
  805. m_pFooter->ShowButtonLabel( "nexttip", m_bShowBackButton );
  806. m_pFooter->ShowButtonLabel( "back", m_bShowBackButton );
  807. }
  808. #endif
  809. // fill out bar charts
  810. UpdateBarCharts();
  811. // fill out class details
  812. UpdateClassDetails();
  813. // update the tip
  814. UpdateTip();
  815. // show or hide controls depending on if we're interactive or not
  816. UpdateControls();
  817. }
  818. //-----------------------------------------------------------------------------
  819. // Purpose: Updates bar charts
  820. //-----------------------------------------------------------------------------
  821. void CTFStatsSummaryPanel::UpdateBarCharts()
  822. {
  823. // sort the class stats by the selected stat for right-hand bar chart
  824. m_aClassStats.Sort( &CTFStatsSummaryPanel::CompareClassStats );
  825. // loop for left & right hand charts
  826. for ( int iChart = 0; iChart < 2; iChart++ )
  827. {
  828. float flMax = 0;
  829. for ( int i = 0; i < m_aClassStats.Count(); i++ )
  830. {
  831. // get max value of stat being charted so we know how to scale the graph
  832. float flVal = GetDisplayValue( m_aClassStats[i], m_statBarGraph[iChart], m_displayBarGraph[iChart] );
  833. flMax = MAX( flVal, flMax );
  834. }
  835. // draw the bar chart value for each player class
  836. // TODO: Fix up after the civilian becomes playable.
  837. int iChartBar = 0;
  838. for ( int i = 0; i < m_aClassStats.Count(); i++ )
  839. {
  840. int iClass = m_aClassStats[i].iPlayerClass;
  841. if ( iClass == TF_CLASS_CIVILIAN )
  842. {
  843. continue;
  844. }
  845. if ( 0 == iChart )
  846. {
  847. // if this is the first chart, set the class label for each class
  848. m_pPlayerData->SetDialogVariable( CFmtStr( "class%d", iChartBar+1 ), g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) );
  849. }
  850. // draw the bar for this class
  851. DisplayBarValue( iChart, iChartBar++, m_aClassStats[i], m_statBarGraph[iChart], m_displayBarGraph[iChart], flMax );
  852. }
  853. }
  854. }
  855. //-----------------------------------------------------------------------------
  856. // Purpose: Updates class details
  857. //-----------------------------------------------------------------------------
  858. void CTFStatsSummaryPanel::UpdateClassDetails( bool bIsMVM )
  859. {
  860. vgui::Label *pTitle = assert_cast< vgui::Label* >( FindChildByName( "RecordsLabel1", true ) );
  861. if ( pTitle )
  862. {
  863. pTitle->SetText( bIsMVM ? "#StatSummary_Label_BestMVMMoments" : "#StatSummary_Label_BestMoments" );
  864. }
  865. const wchar_t *wzWithClassFmt = g_pVGuiLocalize->Find( "#StatSummary_ScoreAsClassFmt" );
  866. const wchar_t *wzWithoutClassFmt = L"%s1";
  867. ClassDetails_t *pStatDetails = ( bIsMVM ? g_PerClassMVMStatDetails : g_PerClassStatDetails );
  868. int nArraySize = ( bIsMVM ? ARRAYSIZE( g_PerClassMVMStatDetails ) : ARRAYSIZE( g_PerClassStatDetails ) );
  869. // display the record for each stat
  870. int iRow = 0;
  871. for ( int i = 0; i < nArraySize; i++ )
  872. {
  873. TFStatType_t statType = pStatDetails[i].statType;
  874. int iClass = TF_CLASS_UNDEFINED;
  875. int iMaxVal = 0;
  876. // if there is a selected class, and if this stat should not be shown for this class, skip this stat
  877. if ( m_iSelectedClass != TF_CLASS_UNDEFINED && ( 0 == ( pStatDetails[i].iFlagsClass & MAKESTATFLAG( m_iSelectedClass ) ) ) )
  878. continue;
  879. if ( m_iSelectedClass == TF_CLASS_UNDEFINED )
  880. {
  881. // if showing best from any class, look through all player classes to determine the max value of this stat
  882. for ( int j = 0; j < m_aClassStats.Count(); j++ )
  883. {
  884. RoundStats_t *pRoundStats = &( bIsMVM ? m_aClassStats[j].maxMVM : m_aClassStats[j].max );
  885. if ( pRoundStats->m_iStat[statType] > iMaxVal )
  886. {
  887. // remember max value and class that has max value
  888. iMaxVal = pRoundStats->m_iStat[statType];
  889. iClass = m_aClassStats[j].iPlayerClass;
  890. }
  891. }
  892. }
  893. else
  894. {
  895. // show best from selected class
  896. iClass = m_iSelectedClass;
  897. for ( int j = 0; j < m_aClassStats.Count(); j++ )
  898. {
  899. if ( m_aClassStats[j].iPlayerClass == iClass )
  900. {
  901. RoundStats_t *pRoundStats = &( bIsMVM ? m_aClassStats[j].maxMVM : m_aClassStats[j].max );
  902. iMaxVal = pRoundStats->m_iStat[statType];
  903. break;
  904. }
  905. }
  906. }
  907. wchar_t wzStatNum[32];
  908. wchar_t wzStatVal[128];
  909. if ( TFSTAT_PLAYTIME == statType )
  910. {
  911. // playtime gets displayed as a time string
  912. g_pVGuiLocalize->ConvertANSIToUnicode( FormatSeconds( iMaxVal ), wzStatNum, sizeof( wzStatNum ) );
  913. }
  914. else
  915. {
  916. // all other stats are just shown as a #
  917. swprintf_s( wzStatNum, ARRAYSIZE( wzStatNum ), L"%d", iMaxVal );
  918. }
  919. if ( TF_CLASS_UNDEFINED == m_iSelectedClass && iMaxVal > 0 )
  920. {
  921. // if we are doing a cross-class view (no single selected class) and the max value is non-zero, show "# (as <class>)"
  922. wchar_t *wzLocalizedClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] );
  923. g_pVGuiLocalize->ConstructString_safe( wzStatVal, wzWithClassFmt, 2, wzStatNum, wzLocalizedClassName );
  924. }
  925. else
  926. {
  927. // just show the value
  928. g_pVGuiLocalize->ConstructString_safe( wzStatVal, wzWithoutClassFmt, 1, wzStatNum );
  929. }
  930. // set the label
  931. m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dlabel", iRow+1 ), g_pVGuiLocalize->Find( pStatDetails[i].szResourceName ) );
  932. // set the value
  933. m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dvalue", iRow+1 ), wzStatVal );
  934. iRow++;
  935. }
  936. // if there are any leftover rows for the selected class, fill out the remaining rows with blank labels and values
  937. for ( ; iRow < 15; iRow ++ )
  938. {
  939. m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dlabel", iRow+1 ), "" );
  940. m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dvalue", iRow+1 ), "" );
  941. }
  942. }
  943. //-----------------------------------------------------------------------------
  944. // Purpose: Updates the tip
  945. //-----------------------------------------------------------------------------
  946. void CTFStatsSummaryPanel::UpdateTip()
  947. {
  948. int iTipClass = TF_CLASS_UNDEFINED;
  949. SetDialogVariable( "tiptext", g_TFTips.GetRandomTip( iTipClass ) );
  950. if ( m_pTipImage )
  951. {
  952. if ( iTipClass > TF_CLASS_UNDEFINED && iTipClass <= TF_CLASS_ENGINEER )
  953. {
  954. m_pTipImage->SetVisible( true );
  955. m_pTipImage->SetImage( g_pszTipsClassImages[iTipClass] );
  956. }
  957. else
  958. {
  959. m_pTipImage->SetVisible( false );
  960. }
  961. }
  962. }
  963. //-----------------------------------------------------------------------------
  964. // Purpose: Shows or hides controls
  965. //-----------------------------------------------------------------------------
  966. void CTFStatsSummaryPanel::UpdateControls()
  967. {
  968. // show or hide controls depending on what mode we're in
  969. #ifndef _X360
  970. bool bShowPlayerData = ( m_bInteractive || m_iTotalSpawns > 0 );
  971. #else
  972. bool bShowPlayerData = ( m_bInteractive || m_bShowBackButton || m_iTotalSpawns > 0 );
  973. #endif
  974. m_pPlayerData->SetVisible( bShowPlayerData );
  975. m_pInteractiveHeaders->SetVisible( m_bInteractive );
  976. m_pNonInteractiveHeaders->SetVisible( !m_bInteractive );
  977. m_pTipText->SetVisible( bShowPlayerData );
  978. m_pTipImage->SetVisible( bShowPlayerData );
  979. if ( !IsX360() )
  980. {
  981. if ( !m_bInteractive )
  982. {
  983. char szTemp[128];
  984. // update our non-interactive headers to match the current combo box selections
  985. Label *pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "BarChartLabelA" ) );
  986. if ( pLabel && m_pBarChartComboBoxA )
  987. {
  988. m_pBarChartComboBoxA->GetItemText( m_pBarChartComboBoxA->GetActiveItem(), szTemp, sizeof( szTemp ) );
  989. pLabel->SetText( szTemp );
  990. }
  991. pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "BarChartLabelB" ) );
  992. if ( pLabel && m_pBarChartComboBoxB )
  993. {
  994. m_pBarChartComboBoxB->GetItemText( m_pBarChartComboBoxB->GetActiveItem(), szTemp, sizeof( szTemp ) );
  995. pLabel->SetText( szTemp );
  996. }
  997. pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "OverallRecordLabel" ) );
  998. if ( pLabel && m_pClassComboBox )
  999. {
  1000. m_pClassComboBox->GetItemText( m_pClassComboBox->GetActiveItem(), szTemp, sizeof( szTemp ) );
  1001. pLabel->SetText( szTemp );
  1002. }
  1003. }
  1004. }
  1005. #ifndef _X360
  1006. m_pNextTipButton->SetVisible( m_bInteractive );
  1007. m_pResetStatsButton->SetVisible( m_bInteractive );
  1008. m_pCloseButton->SetVisible( m_bInteractive && !m_bEmbedded );
  1009. #endif
  1010. }
  1011. //-----------------------------------------------------------------------------
  1012. // Purpose: Initializes a bar chart combo box
  1013. //-----------------------------------------------------------------------------
  1014. void CTFStatsSummaryPanel::InitBarChartComboBox( ComboBox *pComboBox )
  1015. {
  1016. struct BarChartComboInit_t
  1017. {
  1018. TFStatType_t statType;
  1019. StatDisplay_t statDisplay;
  1020. const char *szName;
  1021. };
  1022. BarChartComboInit_t initData[] =
  1023. {
  1024. { TFSTAT_POINTSSCORED, SHOW_MAX, "#StatSummary_StatTitle_MostPoints" },
  1025. { TFSTAT_POINTSSCORED, SHOW_AVG, "#StatSummary_StatTitle_AvgPoints" },
  1026. { TFSTAT_KILLS, SHOW_MAX, "#StatSummary_StatTitle_MostKills" },
  1027. { TFSTAT_KILLS, SHOW_AVG, "#StatSummary_StatTitle_AvgKills" },
  1028. { TFSTAT_CAPTURES, SHOW_MAX, "#StatSummary_StatTitle_MostCaptures" },
  1029. { TFSTAT_CAPTURES, SHOW_AVG, "#StatSummary_StatTitle_AvgCaptures" },
  1030. { TFSTAT_KILLASSISTS, SHOW_MAX, "#StatSummary_StatTitle_MostAssists" },
  1031. { TFSTAT_KILLASSISTS, SHOW_AVG, "#StatSummary_StatTitle_AvgAssists" },
  1032. { TFSTAT_DAMAGE, SHOW_MAX, "#StatSummary_StatTitle_MostDamage" },
  1033. { TFSTAT_DAMAGE, SHOW_AVG, "#StatSummary_StatTitle_AvgDamage" },
  1034. { TFSTAT_PLAYTIME, SHOW_TOTAL, "#StatSummary_StatTitle_TotalPlaytime" },
  1035. { TFSTAT_PLAYTIME, SHOW_MAX, "#StatSummary_StatTitle_LongestLife" },
  1036. };
  1037. // set the font
  1038. HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ScoreboardVerySmall", true );
  1039. pComboBox->SetFont( hFont );
  1040. pComboBox->RemoveAll();
  1041. // add all the options to the combo box
  1042. for ( int i=0; i < ARRAYSIZE( initData ); i++ )
  1043. {
  1044. KeyValues *pKeyValues = new KeyValues( "data" );
  1045. pKeyValues->SetInt( "stattype", initData[i].statType );
  1046. pKeyValues->SetInt( "statdisplay", initData[i].statDisplay );
  1047. pComboBox->AddItem( g_pVGuiLocalize->Find( initData[i].szName ), pKeyValues );
  1048. }
  1049. pComboBox->SetNumberOfEditLines( ARRAYSIZE( initData ) );
  1050. }
  1051. //-----------------------------------------------------------------------------
  1052. // Purpose: Helper function that sets the specified dialog variable to
  1053. // "<value> (as <localized class name>)"
  1054. //-----------------------------------------------------------------------------
  1055. void CTFStatsSummaryPanel::SetValueAsClass( const char *pDialogVariable, int iValue, int iPlayerClass )
  1056. {
  1057. if ( iValue > 0 )
  1058. {
  1059. wchar_t *wzScoreAsClassFmt = g_pVGuiLocalize->Find( "#StatSummary_ScoreAsClassFmt" );
  1060. wchar_t *wzLocalizedClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[iPlayerClass] );
  1061. wchar_t wzVal[16];
  1062. wchar_t wzMsg[128];
  1063. swprintf( wzVal, ARRAYSIZE( wzVal ), L"%d", iValue );
  1064. g_pVGuiLocalize->ConstructString_safe( wzMsg, wzScoreAsClassFmt, 2, wzVal, wzLocalizedClassName );
  1065. m_pPlayerData->SetDialogVariable( pDialogVariable, wzMsg );
  1066. }
  1067. else
  1068. {
  1069. m_pPlayerData->SetDialogVariable( pDialogVariable, "0" );
  1070. }
  1071. }
  1072. //-----------------------------------------------------------------------------
  1073. // Purpose: Sets the specified bar chart item to the specified value, in range 0->1
  1074. //-----------------------------------------------------------------------------
  1075. void CTFStatsSummaryPanel::DisplayBarValue( int iChart, int iBar, ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay, float flMaxValue )
  1076. {
  1077. const char *szControlSuffix = ( 0 == iChart ? "A" : "B" );
  1078. Panel *pBar = m_pPlayerData->FindChildByName( CFmtStr( "ClassBar%d%s", iBar+1, szControlSuffix ) );
  1079. Label *pLabel = dynamic_cast<Label*>( m_pPlayerData->FindChildByName( CFmtStr( "classbarlabel%d%s", iBar+1, szControlSuffix ) ) );
  1080. if ( !pBar || !pLabel )
  1081. return;
  1082. // get the stat value
  1083. float flValue = GetDisplayValue( stats, statType, statDisplay );
  1084. // calculate the bar size to draw, in the range of 0.0->1.0
  1085. float flBarRange = SafeCalcFraction( flValue, flMaxValue );
  1086. // calculate the # of pixels of bar width to draw
  1087. int iBarWidth = MAX( (int) ( flBarRange * (float) m_iBarMaxWidth ), 1 );
  1088. // Get the text label to draw for this bar. For values of 0, draw nothing, to minimize clutter
  1089. const char *szLabel = ( flValue > 0 ? RenderValue( flValue, statType, statDisplay ) : "" );
  1090. // draw the label outside the bar if there's room
  1091. bool bLabelOutsideBar = true;
  1092. const int iLabelSpacing = 4;
  1093. HFont hFont = pLabel->GetFont();
  1094. int iLabelWidth = UTIL_ComputeStringWidth( hFont, szLabel );
  1095. if ( iBarWidth + iLabelWidth + iLabelSpacing > m_iBarMaxWidth )
  1096. {
  1097. // if there's not room outside the bar for the label, draw it inside the bar
  1098. bLabelOutsideBar = false;
  1099. }
  1100. int xBar,yBar,xLabel,yLabel;
  1101. pBar->GetPos( xBar,yBar );
  1102. pLabel->GetPos( xLabel,yLabel );
  1103. m_pPlayerData->SetDialogVariable( CFmtStr( "classbarlabel%d%s", iBar+1, szControlSuffix ), szLabel );
  1104. if ( 1 == iChart )
  1105. {
  1106. // drawing code for RH bar chart
  1107. xBar = m_xStartRHBar;
  1108. pBar->SetBounds( xBar, yBar, iBarWidth, m_iBarHeight );
  1109. if ( bLabelOutsideBar )
  1110. {
  1111. pLabel->SetPos( xBar + iBarWidth + iLabelSpacing, yLabel );
  1112. }
  1113. else
  1114. {
  1115. pLabel->SetPos( xBar + iBarWidth - ( iLabelWidth + iLabelSpacing ), yLabel );
  1116. }
  1117. }
  1118. else
  1119. {
  1120. // drawing code for LH bar chart
  1121. xBar = m_xStartLHBar + m_iBarMaxWidth - iBarWidth;
  1122. pBar->SetBounds( xBar, yBar, iBarWidth, m_iBarHeight );
  1123. if ( bLabelOutsideBar )
  1124. {
  1125. pLabel->SetPos( xBar - ( iLabelWidth + iLabelSpacing ), yLabel );
  1126. }
  1127. else
  1128. {
  1129. pLabel->SetPos( xBar + iLabelSpacing, yLabel );
  1130. }
  1131. }
  1132. }
  1133. //-----------------------------------------------------------------------------
  1134. // Purpose: Calculates a fraction and guards from divide by 0. (Returns 0 if
  1135. // denominator is 0.)
  1136. //-----------------------------------------------------------------------------
  1137. float CTFStatsSummaryPanel::SafeCalcFraction( float flNumerator, float flDemoninator )
  1138. {
  1139. if ( 0 == flDemoninator )
  1140. return 0;
  1141. return flNumerator / flDemoninator;
  1142. }
  1143. //-----------------------------------------------------------------------------
  1144. // Purpose: Formats # of seconds into a string
  1145. //-----------------------------------------------------------------------------
  1146. const char *FormatSeconds( int seconds )
  1147. {
  1148. static char string[64];
  1149. int hours = 0;
  1150. int minutes = seconds / 60;
  1151. if ( minutes > 0 )
  1152. {
  1153. seconds -= (minutes * 60);
  1154. hours = minutes / 60;
  1155. if ( hours > 0 )
  1156. {
  1157. minutes -= (hours * 60);
  1158. }
  1159. }
  1160. if ( hours > 0 )
  1161. {
  1162. Q_snprintf( string, sizeof(string), "%2i:%02i:%02i", hours, minutes, seconds );
  1163. }
  1164. else
  1165. {
  1166. Q_snprintf( string, sizeof(string), "%02i:%02i", minutes, seconds );
  1167. }
  1168. return string;
  1169. }
  1170. //-----------------------------------------------------------------------------
  1171. // Purpose: Static sort function that sorts in descending order by play time
  1172. //-----------------------------------------------------------------------------
  1173. int __cdecl CTFStatsSummaryPanel::CompareClassStats( const ClassStats_t *pStats0, const ClassStats_t *pStats1 )
  1174. {
  1175. // sort stats first by right-hand bar graph
  1176. TFStatType_t statTypePrimary = GStatsSummaryPanel()->m_statBarGraph[1];
  1177. StatDisplay_t statDisplayPrimary = GStatsSummaryPanel()->m_displayBarGraph[1];
  1178. // then by left-hand bar graph
  1179. TFStatType_t statTypeSecondary = GStatsSummaryPanel()->m_statBarGraph[0];
  1180. StatDisplay_t statDisplaySecondary = GStatsSummaryPanel()->m_displayBarGraph[0];
  1181. float flValPrimary0 = GetDisplayValue( (ClassStats_t &) *pStats0, statTypePrimary, statDisplayPrimary );
  1182. float flValPrimary1 = GetDisplayValue( (ClassStats_t &) *pStats1, statTypePrimary, statDisplayPrimary );
  1183. float flValSecondary0 = GetDisplayValue( (ClassStats_t &) *pStats0, statTypeSecondary, statDisplaySecondary );
  1184. float flValSecondary1 = GetDisplayValue( (ClassStats_t &) *pStats1, statTypeSecondary, statDisplaySecondary );
  1185. // sort in descending order by primary stat value
  1186. if ( flValPrimary1 > flValPrimary0 )
  1187. return 1;
  1188. if ( flValPrimary1 < flValPrimary0 )
  1189. return -1;
  1190. // if primary stat values are equal, sort in descending order by secondary stat value
  1191. if ( flValSecondary1 > flValSecondary0 )
  1192. return 1;
  1193. if ( flValSecondary1 < flValSecondary0 )
  1194. return -1;
  1195. // if primary & secondary stats are equal, sort by class for consistent sort order
  1196. return ( pStats1->iPlayerClass - pStats0->iPlayerClass );
  1197. }
  1198. //-----------------------------------------------------------------------------
  1199. // Purpose: Called when text changes in combo box
  1200. //-----------------------------------------------------------------------------
  1201. void CTFStatsSummaryPanel::OnTextChanged( KeyValues *data )
  1202. {
  1203. Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
  1204. vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel );
  1205. if ( m_pBarChartComboBoxA == pComboBox || m_pBarChartComboBoxB == pComboBox )
  1206. {
  1207. // a bar chart combo box changed, update the bar charts
  1208. KeyValues *pUserDataA = m_pBarChartComboBoxA->GetActiveItemUserData();
  1209. KeyValues *pUserDataB = m_pBarChartComboBoxB->GetActiveItemUserData();
  1210. if ( !pUserDataA || !pUserDataB )
  1211. return;
  1212. m_statBarGraph[0] = (TFStatType_t) pUserDataA->GetInt( "stattype" );
  1213. m_displayBarGraph[0] = (StatDisplay_t) pUserDataA->GetInt( "statdisplay" );
  1214. m_statBarGraph[1] = (TFStatType_t) pUserDataB->GetInt( "stattype" );
  1215. m_displayBarGraph[1] = (StatDisplay_t) pUserDataB->GetInt( "statdisplay" );
  1216. UpdateBarCharts();
  1217. }
  1218. else if ( m_pClassComboBox == pComboBox )
  1219. {
  1220. // the class selection combo box changed, update class details
  1221. KeyValues *pUserData = m_pClassComboBox->GetActiveItemUserData();
  1222. if ( !pUserData )
  1223. return;
  1224. m_iSelectedClass = pUserData->GetInt( "class", TF_CLASS_UNDEFINED );
  1225. UpdateClassDetails();
  1226. }
  1227. }
  1228. //-----------------------------------------------------------------------------
  1229. // Purpose: Command target handler called from reset stats confirmation query box
  1230. //-----------------------------------------------------------------------------
  1231. void CTFStatsSummaryPanel::DoResetStats()
  1232. {
  1233. #ifndef _X360
  1234. // reset the stats
  1235. engine->ClientCmd( "resetplayerstats" );
  1236. #endif
  1237. }
  1238. //-----------------------------------------------------------------------------
  1239. // Purpose: Returns the stat value for specified display type
  1240. //-----------------------------------------------------------------------------
  1241. float CTFStatsSummaryPanel::GetDisplayValue( ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay )
  1242. {
  1243. switch ( statDisplay )
  1244. {
  1245. case SHOW_MAX:
  1246. return stats.max.m_iStat[statType];
  1247. break;
  1248. case SHOW_TOTAL:
  1249. return stats.accumulated.m_iStat[statType];
  1250. break;
  1251. case SHOW_AVG:
  1252. return SafeCalcFraction( stats.accumulated.m_iStat[statType], stats.iNumberOfRounds );
  1253. break;
  1254. default:
  1255. AssertOnce( false );
  1256. return 0;
  1257. }
  1258. }
  1259. //-----------------------------------------------------------------------------
  1260. // Purpose: Gets the text representation of this value
  1261. //-----------------------------------------------------------------------------
  1262. const char *CTFStatsSummaryPanel::RenderValue( float flValue, TFStatType_t statType, StatDisplay_t statDisplay )
  1263. {
  1264. static char szValue[64];
  1265. if ( TFSTAT_PLAYTIME == statType )
  1266. {
  1267. // the playtime stat is shown in seconds
  1268. return FormatSeconds( (int) flValue );
  1269. }
  1270. else if ( SHOW_AVG == statDisplay )
  1271. {
  1272. // if it's an average, render as a float w/2 decimal places
  1273. Q_snprintf( szValue, ARRAYSIZE( szValue ), "%.2f", flValue );
  1274. }
  1275. else
  1276. {
  1277. // otherwise, render as an integer
  1278. Q_snprintf( szValue, ARRAYSIZE( szValue ), "%d", (int) flValue );
  1279. }
  1280. return szValue;
  1281. }
  1282. //-----------------------------------------------------------------------------
  1283. // Purpose: Event handler
  1284. //-----------------------------------------------------------------------------
  1285. void CTFStatsSummaryPanel::FireGameEvent( IGameEvent *event )
  1286. {
  1287. const char *pEventName = event->GetName();
  1288. // when we are changing levels and
  1289. if ( 0 == Q_strcmp( pEventName, "server_spawn" ) )
  1290. {
  1291. if ( !m_bInteractive )
  1292. {
  1293. const char *pMapName = event->GetString( "mapname" );
  1294. if ( pMapName )
  1295. {
  1296. OnMapLoad( pMapName );
  1297. }
  1298. }
  1299. }
  1300. }
  1301. //-----------------------------------------------------------------------------
  1302. // Purpose: Called when we are activated during level load
  1303. //-----------------------------------------------------------------------------
  1304. void CTFStatsSummaryPanel::OnActivate()
  1305. {
  1306. ClearMapLabel();
  1307. m_bShowingLeaderboard = false;
  1308. m_bLoadingCommunityMap = false;
  1309. ShowMapInfo( false );
  1310. #ifdef _X360
  1311. m_bShowBackButton = false;
  1312. #endif
  1313. UpdateDialog();
  1314. }
  1315. //-----------------------------------------------------------------------------
  1316. // Purpose: Called when we are deactivated at end of level load
  1317. //-----------------------------------------------------------------------------
  1318. void CTFStatsSummaryPanel::OnDeactivate()
  1319. {
  1320. ClearMapLabel();
  1321. }
  1322. CON_COMMAND( showstatsdlg, "Shows the player stats dialog" )
  1323. {
  1324. #ifdef _DEBUG
  1325. GStatsSummaryPanel()->InvalidateLayout( false, true );
  1326. #endif
  1327. GStatsSummaryPanel()->ShowModal();
  1328. #ifdef _DEBUG
  1329. GStatsSummaryPanel()->OnMapLoad( "cp_coldfront" );
  1330. #endif
  1331. }