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.

1703 lines
50 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tf_classmenu.h"
  9. #include "IGameUIFuncs.h" // for key bindings
  10. #include "tf_hud_notification_panel.h"
  11. #include "character_info_panel.h"
  12. #include "playerspawncache.h"
  13. #include "iclientmode.h"
  14. #include "econ_gcmessages.h"
  15. #include "gc_clientsystem.h"
  16. #include "vgui/IInput.h"
  17. #include <vgui_controls/PanelListPanel.h>
  18. #include <vgui_controls/ScrollBarSlider.h>
  19. #include "tf_gamerules.h"
  20. #include "engine/IEngineSound.h"
  21. #include "inputsystem/iinputsystem.h"
  22. #if defined( REPLAY_ENABLED )
  23. #include "replay/iclientreplaycontext.h"
  24. #include "replay/ireplaysystem.h"
  25. #include "replay/ienginereplay.h"
  26. #include "replay/shared_defs.h"
  27. #endif
  28. extern IGameUIFuncs *gameuifuncs; // for key binding details
  29. using namespace vgui;
  30. ConVar _cl_classmenuopen( "_cl_classmenuopen", "0", 0, "internal cvar used to tell server when class menu is open" );
  31. #if defined( REPLAY_ENABLED )
  32. ConVar replay_replaywelcomedlgcount( "replay_replaywelcomedlgcount", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "The number of times the replay help dialog has displayed." );
  33. #endif
  34. ConVar tf_mvm_classupgradehelpcount( "tf_mvm_classupgradehelpcount", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "The number of times the player upgrade help dialog has displayed." );
  35. //-----------------------------------------------------------------------------
  36. // Purpose:
  37. //-----------------------------------------------------------------------------
  38. CTFClassTipsItemPanel::CTFClassTipsItemPanel( Panel *parent, const char *name, int iListItemID ) : BaseClass( parent, name )
  39. {
  40. m_pTipIcon = new vgui::ImagePanel( this, "TipIcon" );
  41. m_pTipLabel = new CExLabel( this, "TipLabel", "" );
  42. // m_pTipIcon = dynamic_cast<vgui::ImagePanel*>( FindChildByName( "TipIcon" ) );
  43. // m_pTipLabel = dynamic_cast<CExLabel*>( FindChildByName( "TipLabel" ) );
  44. }
  45. CTFClassTipsItemPanel::~CTFClassTipsItemPanel()
  46. {
  47. }
  48. //-----------------------------------------------------------------------------
  49. // Purpose:
  50. //-----------------------------------------------------------------------------
  51. void CTFClassTipsItemPanel::SetClassTip( const wchar_t *pwszText, const char *pszIcon )
  52. {
  53. // Set tf_english string and .res icon path
  54. if ( m_pTipLabel && m_pTipIcon )
  55. {
  56. if ( pszIcon )
  57. {
  58. m_pTipIcon->SetImage( pszIcon );
  59. }
  60. else
  61. {
  62. m_pTipIcon->SetVisible( false );
  63. // int iX, iY = 0;
  64. // m_pTipLabel->GetPos( iX, iY );
  65. // int nIconWidth = m_pTipIcon->GetWide();
  66. // m_pTipLabel->SetPos( ( iX - nIconWidth ), iY );
  67. }
  68. m_pTipLabel->SetText( pwszText );
  69. }
  70. InvalidateLayout( true, true );
  71. }
  72. //-----------------------------------------------------------------------------
  73. // Purpose:
  74. //-----------------------------------------------------------------------------
  75. void CTFClassTipsItemPanel::ApplySchemeSettings( IScheme* pScheme )
  76. {
  77. BaseClass::ApplySchemeSettings( pScheme );
  78. LoadControlSettings( "Resource/UI/ClassTipsItem.res" );
  79. }
  80. //-----------------------------------------------------------------------------
  81. // Purpose:
  82. //-----------------------------------------------------------------------------
  83. class CTFClassTipsListPanel : public PanelListPanel
  84. {
  85. private:
  86. DECLARE_CLASS_SIMPLE( CTFClassTipsListPanel, PanelListPanel );
  87. public:
  88. CTFClassTipsListPanel( Panel *parent, const char *panelName )
  89. : PanelListPanel( parent, panelName )
  90. {
  91. m_pScrollBar = GetScrollbar();
  92. m_pUpArrow = new CExImageButton( this, "UpArrow", "" );
  93. if ( m_pUpArrow )
  94. {
  95. m_pUpArrow->AddActionSignalTarget( m_pScrollBar );
  96. m_pUpArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 0));
  97. m_pUpArrow->GetImage()->SetShouldScaleImage( true );
  98. m_pUpArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
  99. m_pUpArrow->SetAlpha( 255 );
  100. m_pUpArrow->SetPaintBackgroundEnabled( false );
  101. m_pUpArrow->SetVisible( false );
  102. m_pUpArrow->PassMouseTicksTo( m_pScrollBar );
  103. m_pUpArrow->SetImageDefault( "chalkboard_scroll_up" );
  104. }
  105. m_pDownArrow = new CExImageButton( this, "DownArrow", "" );
  106. if ( m_pDownArrow )
  107. {
  108. m_pDownArrow->AddActionSignalTarget( m_pScrollBar );
  109. m_pDownArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 1));
  110. m_pDownArrow->GetImage()->SetShouldScaleImage( true );
  111. m_pDownArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
  112. m_pDownArrow->SetAlpha( 255 );
  113. m_pDownArrow->SetPaintBackgroundEnabled( false );
  114. m_pDownArrow->SetVisible( false );
  115. m_pDownArrow->PassMouseTicksTo( m_pScrollBar );
  116. m_pDownArrow->SetImageDefault( "chalkboard_scroll_down" );
  117. }
  118. if ( m_pScrollBar )
  119. {
  120. m_pScrollBar->SetOverriddenButtons( m_pUpArrow, m_pDownArrow );
  121. m_pScrollBar->SetVisible( false );
  122. }
  123. m_pLine = new vgui::ImagePanel( this, "Line" );
  124. m_pBox = new vgui::ImagePanel( this, "Box" );
  125. if ( m_pLine )
  126. {
  127. m_pLine->SetImage( "chalkboard_scroll_line" );
  128. m_pLine->SetShouldScaleImage( true );
  129. }
  130. if ( m_pBox )
  131. {
  132. m_pBox->SetImage( "chalkboard_scroll_box" );
  133. m_pBox->SetShouldScaleImage( true );
  134. }
  135. SetScrollBarImagesVisible( false );
  136. vgui::ivgui()->AddTickSignal( GetVPanel() );
  137. }
  138. //-----------------------------------------------------------------------------
  139. // Purpose:
  140. //-----------------------------------------------------------------------------
  141. ~CTFClassTipsListPanel()
  142. {
  143. ivgui()->RemoveTickSignal( GetVPanel() );
  144. }
  145. private:
  146. //-----------------------------------------------------------------------------
  147. // Purpose:
  148. //-----------------------------------------------------------------------------
  149. int GetListHeight( void )
  150. {
  151. int nHeight = 0;
  152. for ( int i = FirstItem(); i < GetItemCount(); i++ )
  153. {
  154. vgui::Panel *pClassTipsItemPanel = GetItemPanel( i );
  155. if ( pClassTipsItemPanel )
  156. {
  157. nHeight += pClassTipsItemPanel->GetTall();
  158. }
  159. }
  160. return nHeight;
  161. }
  162. //-----------------------------------------------------------------------------
  163. // Purpose:
  164. //-----------------------------------------------------------------------------
  165. void SetScrollBarImagesVisible( bool visible )
  166. {
  167. if ( m_pDownArrow && m_pDownArrow->IsVisible() != visible )
  168. {
  169. m_pDownArrow->SetVisible( visible );
  170. m_pDownArrow->SetEnabled( visible );
  171. }
  172. if ( m_pUpArrow && m_pUpArrow->IsVisible() != visible )
  173. {
  174. m_pUpArrow->SetVisible( visible );
  175. m_pUpArrow->SetEnabled( visible );
  176. }
  177. if ( m_pLine && m_pLine->IsVisible() != visible )
  178. {
  179. m_pLine->SetVisible( visible );
  180. }
  181. if ( m_pBox && m_pBox->IsVisible() != visible )
  182. {
  183. m_pBox->SetVisible( visible );
  184. }
  185. }
  186. //-----------------------------------------------------------------------------
  187. // Purpose:
  188. //-----------------------------------------------------------------------------
  189. void OnTick()
  190. {
  191. if ( !IsVisible() )
  192. return;
  193. if ( m_pDownArrow && m_pUpArrow && m_pLine && m_pBox )
  194. {
  195. if ( m_pScrollBar && m_pScrollBar->IsVisible() && GetListHeight() > GetTall() )
  196. {
  197. SetScrollBarImagesVisible ( true );
  198. // set the alpha on the up arrow
  199. int nMin, nMax;
  200. m_pScrollBar->GetRange( nMin, nMax );
  201. int nScrollPos = m_pScrollBar->GetValue();
  202. int nRangeWindow = m_pScrollBar->GetRangeWindow();
  203. int nBottom = nMax - nRangeWindow;
  204. if ( nBottom < 0 )
  205. {
  206. nBottom = 0;
  207. }
  208. int nAlpha = ( nScrollPos - nMin <= 0 ) ? 90 : 255;
  209. m_pUpArrow->SetAlpha( nAlpha );
  210. // set the alpha on the down arrow
  211. nAlpha = ( nScrollPos >= nBottom ) ? 90 : 255;
  212. m_pDownArrow->SetAlpha( nAlpha );
  213. ScrollBarSlider *pSlider = m_pScrollBar->GetSlider();
  214. if ( pSlider && pSlider->GetRangeWindow() > 0 )
  215. {
  216. int x, y, w, t, min, max;
  217. m_pLine->GetBounds( x, y, w, t );
  218. pSlider->GetNobPos( min, max );
  219. m_pBox->SetBounds( x, y + min, w, ( max - min ) );
  220. }
  221. }
  222. else
  223. {
  224. // turn off our images
  225. SetScrollBarImagesVisible ( false );
  226. }
  227. }
  228. }
  229. //-----------------------------------------------------------------------------
  230. // Purpose:
  231. //-----------------------------------------------------------------------------
  232. void ApplySchemeSettings( IScheme *pScheme )
  233. {
  234. BaseClass::ApplySchemeSettings( pScheme );
  235. if ( m_pScrollBar )
  236. {
  237. m_pScrollBar->SetZPos( 500 );
  238. m_pUpArrow->SetZPos( 501 );
  239. m_pDownArrow->SetZPos( 501 );
  240. // turn off painting the vertical scrollbar
  241. m_pScrollBar->SetPaintBackgroundEnabled( false );
  242. m_pScrollBar->SetPaintBorderEnabled( false );
  243. m_pScrollBar->SetPaintEnabled( false );
  244. m_pScrollBar->SetScrollbarButtonsVisible( false );
  245. m_pScrollBar->GetButton(0)->SetMouseInputEnabled( false );
  246. m_pScrollBar->GetButton(1)->SetMouseInputEnabled( false );
  247. if ( m_pScrollBar->IsVisible() )
  248. {
  249. int nMin, nMax;
  250. m_pScrollBar->GetRange( nMin, nMax );
  251. m_pScrollBar->SetValue( nMin );
  252. int nScrollbarWide = m_pScrollBar->GetWide();
  253. int wide, tall;
  254. GetSize( wide, tall );
  255. if ( m_pUpArrow )
  256. {
  257. m_pUpArrow->SetBounds( wide - nScrollbarWide, 0, nScrollbarWide, nScrollbarWide );
  258. m_pUpArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide );
  259. }
  260. if ( m_pLine )
  261. {
  262. m_pLine->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, tall - ( 2 * nScrollbarWide ) );
  263. }
  264. if ( m_pBox )
  265. {
  266. m_pBox->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, nScrollbarWide );
  267. }
  268. if ( m_pDownArrow )
  269. {
  270. m_pDownArrow->SetBounds( wide - nScrollbarWide, tall - nScrollbarWide, nScrollbarWide, nScrollbarWide );
  271. m_pDownArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide );
  272. }
  273. SetScrollBarImagesVisible( false );
  274. }
  275. }
  276. }
  277. vgui::ScrollBar *m_pScrollBar;
  278. CExImageButton *m_pUpArrow;
  279. CExImageButton *m_pDownArrow;
  280. vgui::ImagePanel *m_pLine;
  281. vgui::ImagePanel *m_pBox;
  282. };
  283. //-----------------------------------------------------------------------------
  284. // Purpose:
  285. //-----------------------------------------------------------------------------
  286. class CTFClassTipsPanel : public EditablePanel
  287. {
  288. private:
  289. DECLARE_CLASS_SIMPLE( CTFClassTipsPanel, EditablePanel );
  290. public:
  291. CTFClassTipsPanel( Panel *parent, const char *panelName )
  292. : EditablePanel( parent, panelName )
  293. {
  294. m_pClassTipsListPanel = new CTFClassTipsListPanel( this, "ClassTipsListPanel" );
  295. }
  296. //-----------------------------------------------------------------------------
  297. // Purpose:
  298. //-----------------------------------------------------------------------------
  299. ~CTFClassTipsPanel()
  300. {
  301. }
  302. //-----------------------------------------------------------------------------
  303. // Purpose:
  304. //-----------------------------------------------------------------------------
  305. void SetClass( int iClass )
  306. {
  307. const char *pPathID = IsX360() ? "MOD" : "GAME";
  308. m_fmtResFilename.sprintf( "classes/%s.res", g_aRawPlayerClassNames[ iClass ] );
  309. if ( !g_pFullFileSystem->FileExists( m_fmtResFilename.Access(), pPathID ) &&
  310. g_pFullFileSystem->FileExists( "classes/default.res", pPathID ) )
  311. {
  312. m_fmtResFilename.sprintf( "classes/default.res" );
  313. }
  314. if ( m_pClassTipsListPanel )
  315. {
  316. m_pClassTipsListPanel->DeleteAllItems();
  317. int nScrollToItem = 0;
  318. // Get tip count
  319. const wchar_t *wzTipCount = g_pVGuiLocalize->Find( CFmtStr( "ClassTips_%d_Count", iClass ) );
  320. int nTipCount = wzTipCount ? _wtoi( wzTipCount ) : 0;
  321. for ( int iTip = 1; iTip < nTipCount+1; ++iTip )
  322. {
  323. const wchar_t *pwszText = g_pVGuiLocalize->Find( CFmtStr( "#ClassTips_%d_%d", iClass, iTip ) );
  324. const wchar_t *pwszTextMvM = g_pVGuiLocalize->Find( CFmtStr( "#ClassTips_%d_%d_MvM", iClass, iTip ) );
  325. wchar_t *pwszIcon = g_pVGuiLocalize->Find( CFmtStr( "ClassTips_%d_%d_Icon", iClass, iTip ) );
  326. char szIcon[MAX_PATH];
  327. szIcon[0] = 0;
  328. if ( pwszIcon )
  329. {
  330. g_pVGuiLocalize->ConvertUnicodeToANSI( pwszIcon, szIcon, sizeof( szIcon ) );
  331. }
  332. // Don't load MvM tips outside the mode
  333. if ( pwszTextMvM )
  334. {
  335. if ( !TFGameRules()->IsMannVsMachineMode() )
  336. continue;
  337. // If we're MvM mode, remember first MvM tip
  338. if ( !nScrollToItem )
  339. nScrollToItem = iTip;
  340. }
  341. // Create a TipsItemPanel for each tip
  342. if ( pwszText || pwszTextMvM )
  343. {
  344. CTFClassTipsItemPanel *pClassTipsItemPanel = new CTFClassTipsItemPanel( this, "ClassTipsItemPanel", iTip );
  345. if ( pwszText )
  346. {
  347. pClassTipsItemPanel->SetClassTip( pwszText, szIcon );
  348. }
  349. else if ( pwszTextMvM )
  350. {
  351. pClassTipsItemPanel->SetClassTip( pwszTextMvM, szIcon );
  352. }
  353. m_pClassTipsListPanel->AddItem( NULL, pClassTipsItemPanel );
  354. }
  355. }
  356. if ( m_pClassTipsListPanel->GetItemCount() > 0 )
  357. {
  358. m_pClassTipsListPanel->SetFirstColumnWidth( 0 );
  359. m_pClassTipsListPanel->ScrollToItem( nScrollToItem );
  360. }
  361. }
  362. InvalidateLayout( true, true );
  363. }
  364. private:
  365. //-----------------------------------------------------------------------------
  366. // Purpose:
  367. //-----------------------------------------------------------------------------
  368. virtual void ApplySchemeSettings( IScheme *pScheme )
  369. {
  370. BaseClass::ApplySchemeSettings( pScheme );
  371. LoadControlSettings( "Resource/UI/ClassTipsList.res" );
  372. }
  373. CTFClassTipsListPanel *m_pClassTipsListPanel;
  374. CFmtStr m_fmtResFilename;
  375. };
  376. //-----------------------------------------------------------------------------
  377. // Purpose:
  378. //-----------------------------------------------------------------------------
  379. CTFClassMenu::CTFClassMenu( IViewPort *pViewPort )
  380. : CClassMenu( pViewPort )
  381. {
  382. MakePopup();
  383. m_mouseoverButtons.RemoveAll();
  384. m_iClassMenuKey = BUTTON_CODE_INVALID;
  385. m_iCurrentClassIndex = TF_CLASS_HEAVYWEAPONS;
  386. #ifdef _X360
  387. m_pFooter = new CTFFooter( this, "Footer" );
  388. #endif
  389. m_pTFPlayerModelPanel = NULL;
  390. m_pSelectAClassLabel = NULL;
  391. m_pClassTipsPanel = new CTFClassTipsPanel( this, "ClassTipsPanel" );
  392. Q_memset( m_pClassButtons, 0, sizeof( m_pClassButtons ) );
  393. #ifndef _X360
  394. char tempName[MAX_PATH];
  395. for ( int i = 0 ; i < CLASS_COUNT_IMAGES ; ++i )
  396. {
  397. Q_snprintf( tempName, sizeof( tempName ), "countImage%d", i );
  398. m_ClassCountImages[i] = new CTFImagePanel( this, tempName );
  399. }
  400. m_pCountLabel = NULL;
  401. m_pLocalPlayerImage = new CTFImagePanel( this, "localPlayerImage" );
  402. m_pLocalPlayerBG = new CTFImagePanel( this, "localPlayerBG" );
  403. m_iLocalPlayerClass = TEAM_UNASSIGNED;
  404. m_pClassButtons[TF_CLASS_SCOUT] = new CExImageButton( this, "scout", "", this );
  405. m_pClassButtons[TF_CLASS_SOLDIER] = new CExImageButton( this, "soldier", "", this );
  406. m_pClassButtons[TF_CLASS_PYRO] = new CExImageButton( this, "pyro", "", this );
  407. m_pClassButtons[TF_CLASS_DEMOMAN] = new CExImageButton( this, "demoman", "", this );
  408. m_pClassButtons[TF_CLASS_MEDIC] = new CExImageButton( this, "medic", "", this );
  409. m_pClassButtons[TF_CLASS_HEAVYWEAPONS] = new CExImageButton( this, "heavyweapons", "", this );
  410. m_pClassButtons[TF_CLASS_SNIPER] = new CExImageButton( this, "sniper", "", this );
  411. m_pClassButtons[TF_CLASS_ENGINEER] = new CExImageButton( this, "engineer", "", this );
  412. m_pClassButtons[TF_CLASS_SPY] = new CExImageButton( this, "spy", "", this );
  413. m_pClassButtons[TF_CLASS_RANDOM] = new CExImageButton( this, "random", "", this );
  414. #endif
  415. m_pEditLoadoutButton = NULL;
  416. m_nBaseMusicGuid = -1;
  417. ListenForGameEvent( "localplayer_changeteam" );
  418. ListenForGameEvent( "show_match_summary" );
  419. Q_memset( m_pMvmUpgradeImages, 0, sizeof( m_pMvmUpgradeImages ) );
  420. m_pMvmUpgradeImages[TF_CLASS_SCOUT] = new vgui::ImagePanel( this, "MvMUpgradeImageScout" );
  421. m_pMvmUpgradeImages[TF_CLASS_SOLDIER] = new vgui::ImagePanel( this, "MvMUpgradeImageSolider" );
  422. m_pMvmUpgradeImages[TF_CLASS_PYRO] = new vgui::ImagePanel( this, "MvMUpgradeImagePyro" );
  423. m_pMvmUpgradeImages[TF_CLASS_DEMOMAN] = new vgui::ImagePanel( this, "MvMUpgradeImageDemoman" );
  424. m_pMvmUpgradeImages[TF_CLASS_MEDIC] = new vgui::ImagePanel( this, "MvMUpgradeImageMedic" );
  425. m_pMvmUpgradeImages[TF_CLASS_HEAVYWEAPONS] = new vgui::ImagePanel( this, "MvMUpgradeImageHeavy" );
  426. m_pMvmUpgradeImages[TF_CLASS_SNIPER] = new vgui::ImagePanel( this, "MvMUpgradeImageSniper" );
  427. m_pMvmUpgradeImages[TF_CLASS_ENGINEER] = new vgui::ImagePanel( this, "MvMUpgradeImageEngineer" );
  428. m_pMvmUpgradeImages[TF_CLASS_SPY] = new vgui::ImagePanel( this, "MvMUpgradeImageSpy" );
  429. vgui::ivgui()->AddTickSignal( GetVPanel() );
  430. }
  431. //-----------------------------------------------------------------------------
  432. // Purpose:
  433. //-----------------------------------------------------------------------------
  434. void CTFClassMenu::ApplySchemeSettings( IScheme *pScheme )
  435. {
  436. BaseClass::ApplySchemeSettings( pScheme );
  437. for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
  438. {
  439. m_pClassHintIcons[i] = nullptr;
  440. }
  441. // Load the .res file
  442. if ( ::input->IsSteamControllerActive() )
  443. {
  444. LoadControlSettings( "Resource/UI/ClassSelection_SC.res" );
  445. m_pCancelHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "CancelHintIcon" ) );
  446. m_pEditLoadoutHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "EditLoadoutHintIcon" ) );
  447. m_pClassHintIcons[TF_CLASS_SCOUT] = dynamic_cast< CSCHintIcon* >( FindChildByName( "ScoutHintIcon" ) );
  448. m_pClassHintIcons[TF_CLASS_SOLDIER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SoldierHintIcon" ) );
  449. m_pClassHintIcons[TF_CLASS_PYRO] = dynamic_cast< CSCHintIcon* >( FindChildByName( "PyroHintIcon" ) );
  450. m_pClassHintIcons[TF_CLASS_DEMOMAN] = dynamic_cast< CSCHintIcon* >( FindChildByName( "DemomanHintIcon" ) );
  451. m_pClassHintIcons[TF_CLASS_HEAVYWEAPONS] = dynamic_cast< CSCHintIcon* >( FindChildByName( "HeavyHintIcon" ) );
  452. m_pClassHintIcons[TF_CLASS_MEDIC] = dynamic_cast< CSCHintIcon* >( FindChildByName( "MedicHintIcon" ) );
  453. m_pClassHintIcons[TF_CLASS_SPY] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SpyHintIcon" ) );
  454. m_pClassHintIcons[TF_CLASS_ENGINEER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "EngineerHintIcon" ) );
  455. m_pClassHintIcons[TF_CLASS_SNIPER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SniperHintIcon" ) );
  456. m_pClassHintIcons[TF_CLASS_RANDOM] = dynamic_cast< CSCHintIcon* >( FindChildByName( "RandomHintIcon" ) );
  457. for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
  458. {
  459. if ( m_pClassHintIcons[i] )
  460. {
  461. m_pClassHintIcons[i]->SetVisible( false );
  462. }
  463. }
  464. SetMouseInputEnabled( false );
  465. }
  466. else
  467. {
  468. LoadControlSettings( "Resource/UI/ClassSelection.res" );
  469. m_pCancelHintIcon = m_pEditLoadoutHintIcon = nullptr;
  470. SetMouseInputEnabled( true );
  471. }
  472. m_pTFPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("TFPlayerModel") );
  473. m_pSelectAClassLabel = dynamic_cast<CExLabel*>( FindChildByName( "ClassMenuSelect" ) );
  474. m_pEditLoadoutButton = dynamic_cast<CExButton*>( FindChildByName( "EditLoadoutButton" ) );
  475. const char *pTeamExtension = GetTeamNumber() == TF_TEAM_BLUE ? "blu" : "red";
  476. for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i )
  477. {
  478. if ( !m_pClassButtons[i] )
  479. continue;
  480. if ( !IsValidTFPlayerClass( i ) && i != TF_CLASS_RANDOM )
  481. continue;
  482. m_pClassButtons[i]->SetImageSelected( CFmtStr( "class_sel_sm_%s_%s", g_aRawPlayerClassNamesShort[i], pTeamExtension ).Access() );
  483. if( i != TF_CLASS_RANDOM )
  484. {
  485. m_pClassButtons[i]->SetArmedSound("misc/null.wav");
  486. }
  487. }
  488. }
  489. void CTFClassMenu::PerformLayout()
  490. {
  491. BaseClass::PerformLayout();
  492. #ifndef _X360
  493. m_pCountLabel = dynamic_cast< CExLabel * >( FindChildByName( "CountLabel" ) );
  494. if ( m_pCountLabel )
  495. {
  496. m_pCountLabel->SizeToContents();
  497. }
  498. #endif
  499. }
  500. //-----------------------------------------------------------------------------
  501. // Purpose:
  502. //-----------------------------------------------------------------------------
  503. int CTFClassMenu::GetCurrentPlayerClass()
  504. {
  505. int iClass = TF_CLASS_HEAVYWEAPONS;
  506. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  507. if ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() != TF_CLASS_UNDEFINED )
  508. {
  509. iClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex();
  510. }
  511. return iClass;
  512. }
  513. //-----------------------------------------------------------------------------
  514. // Purpose:
  515. //-----------------------------------------------------------------------------
  516. CExImageButton *CTFClassMenu::GetCurrentClassButton()
  517. {
  518. const int iClass = GetCurrentPlayerClass();
  519. m_iCurrentClassIndex = iRemapIndexToClass[ iClass ];
  520. return m_pClassButtons[iClass];
  521. }
  522. //-----------------------------------------------------------------------------
  523. // Purpose:
  524. //-----------------------------------------------------------------------------
  525. void CTFClassMenu::ShowPanel( bool bShow )
  526. {
  527. if ( bShow )
  528. {
  529. // Hide the other class menu
  530. if ( gViewPortInterface )
  531. {
  532. gViewPortInterface->ShowPanel( GetTeamNumber() == TF_TEAM_BLUE ? PANEL_CLASS_RED : PANEL_CLASS_BLUE, false );
  533. }
  534. // can't change class if you're on the losing team during the "bonus time" after a team has won the round
  535. if ( ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN &&
  536. C_TFPlayer::GetLocalTFPlayer() &&
  537. C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TFGameRules()->GetWinningTeam()
  538. && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_SPECTATOR
  539. && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_UNASSIGNED
  540. && GetSpectatorMode() == OBS_MODE_NONE ) ||
  541. TFGameRules()->State_Get() == GR_STATE_GAME_OVER ||
  542. ( TFGameRules()->IsInTraining() && C_TFPlayer::GetLocalTFPlayer() &&
  543. ( C_TFPlayer::GetLocalTFPlayer()->GetPlayerClass() == NULL || C_TFPlayer::GetLocalTFPlayer()->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) ) )
  544. {
  545. SetVisible( false );
  546. CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel );
  547. if ( pNotifyPanel )
  548. {
  549. if ( C_TFPlayer::GetLocalTFPlayer() )
  550. {
  551. pNotifyPanel->SetupNotifyCustom( "#TF_CantChangeClassNow", "ico_notify_flag_moving", C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() );
  552. }
  553. }
  554. return;
  555. }
  556. engine->CheckPoint( "ClassMenu" );
  557. // Force us to reload our scheme, in case Steam Controller stuff has changed.
  558. InvalidateLayout( true, true );
  559. Activate();
  560. m_iClassMenuKey = gameuifuncs->GetButtonCodeForBind( "changeclass" );
  561. m_iScoreBoardKey = gameuifuncs->GetButtonCodeForBind( "showscores" );
  562. SelectClass( GetCurrentPlayerClass() );
  563. }
  564. else
  565. {
  566. SetVisible( false );
  567. if ( m_pTFPlayerModelPanel )
  568. {
  569. m_pTFPlayerModelPanel->ClearCarriedItems();
  570. }
  571. }
  572. }
  573. const char *g_pszLegacyClassSelectVCDWeapons[TF_LAST_NORMAL_CLASS] =
  574. {
  575. "", // TF_CLASS_UNDEFINED = 0,
  576. "", // TF_CLASS_SCOUT, // weapons handled individually
  577. "", // TF_CLASS_SNIPER, // weapons handled individually
  578. "", // TF_CLASS_SOLDIER, // weapons handled individually
  579. "tf_weapon_grenadelauncher", // TF_CLASS_DEMOMAN,
  580. "tf_weapon_medigun", // TF_CLASS_MEDIC,
  581. "tf_weapon_minigun", // TF_CLASS_HEAVYWEAPONS,
  582. "tf_weapon_flamethrower", // TF_CLASS_PYRO,
  583. "", // TF_CLASS_SPY, // weapons handled individually
  584. "tf_weapon_wrench", // TF_CLASS_ENGINEER,
  585. };
  586. int g_iLegacyClassSelectWeaponSlots[TF_LAST_NORMAL_CLASS] =
  587. {
  588. LOADOUT_POSITION_PRIMARY, // TF_CLASS_UNDEFINED = 0,
  589. LOADOUT_POSITION_PRIMARY, // TF_CLASS_SCOUT, // TF_FIRST_NORMAL_CLASS
  590. LOADOUT_POSITION_PRIMARY, // TF_CLASS_SNIPER,
  591. LOADOUT_POSITION_PRIMARY, // TF_CLASS_SOLDIER,
  592. LOADOUT_POSITION_PRIMARY, // TF_CLASS_DEMOMAN,
  593. LOADOUT_POSITION_SECONDARY, // TF_CLASS_MEDIC,
  594. LOADOUT_POSITION_PRIMARY, // TF_CLASS_HEAVYWEAPONS,
  595. LOADOUT_POSITION_PRIMARY, // TF_CLASS_PYRO,
  596. LOADOUT_POSITION_MELEE, // TF_CLASS_SPY,
  597. LOADOUT_POSITION_MELEE, // TF_CLASS_ENGINEER,
  598. };
  599. //-----------------------------------------------------------------------------
  600. // Purpose:
  601. //-----------------------------------------------------------------------------
  602. void CTFClassMenu::UpdateButtonSelectionStates( int iClass )
  603. {
  604. // Set the correct button as selected, all other buttons as not selected
  605. for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i )
  606. {
  607. if ( !m_pClassButtons[ i ] )
  608. continue;
  609. m_pClassButtons[ i ]->SetSelected( i == iClass );
  610. }
  611. }
  612. //-----------------------------------------------------------------------------
  613. // Purpose:
  614. //-----------------------------------------------------------------------------
  615. void CTFClassMenu::SelectClass( int iClass )
  616. {
  617. if ( !engine->IsInGame() )
  618. return;
  619. if ( !m_pTFPlayerModelPanel )
  620. return;
  621. if ( !m_pClassTipsPanel )
  622. return;
  623. if ( !m_pEditLoadoutButton )
  624. return;
  625. // Update select hint icon for Steam Controller
  626. for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
  627. {
  628. if ( m_pClassHintIcons[i] )
  629. {
  630. m_pClassHintIcons[i]->SetVisible( i == iClass );
  631. }
  632. }
  633. // Were we random? If so, we'll force our class to refresh later to prevent the
  634. // model panel thinking the class hasn't changed.
  635. const bool bClassWasRandom = m_iCurrentClassIndex == TF_CLASS_RANDOM;
  636. // Cache current player class
  637. m_iCurrentClassIndex = iClass;
  638. UpdateButtonSelectionStates( iClass );
  639. bool bRandomClass = iClass == TF_CLASS_RANDOM;
  640. if ( !IsValidTFPlayerClass( iClass ) && !bRandomClass )
  641. {
  642. m_pTFPlayerModelPanel->SetVisible( false );
  643. m_pTFPlayerModelPanel->ClearCarriedItems();
  644. return;
  645. }
  646. m_pTFPlayerModelPanel->SetVisible( true );
  647. m_pTFPlayerModelPanel->ClearCarriedItems();
  648. if ( bRandomClass )
  649. {
  650. m_pEditLoadoutButton->SetVisible( false );
  651. if ( m_pEditLoadoutHintIcon )
  652. {
  653. m_pEditLoadoutHintIcon->SetVisible( false );
  654. }
  655. MDLHandle_t hModel = mdlcache->FindMDL( "models/class_menu/random_class_icon.mdl" );
  656. m_pTFPlayerModelPanel->SetMDL( hModel );
  657. m_pTFPlayerModelPanel->SetSequence( ACT_IDLE );
  658. m_pTFPlayerModelPanel->InvalidateLayout( true, true ); // Updates position
  659. m_pTFPlayerModelPanel->SetSkin( GetTeamNumber() == TF_TEAM_RED ? 0 : 1 );
  660. mdlcache->Release( hModel ); // counterbalance addref from within FindMDL
  661. }
  662. else
  663. {
  664. bool bIsRobot = false;
  665. // Check for Robot
  666. static CSchemaAttributeDefHandle pAttrDef_PlayerRobot( "appear as mvm robot" );
  667. for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ )
  668. {
  669. CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i );
  670. if ( !pItemData )
  671. continue;
  672. if ( FindAttribute( pItemData, pAttrDef_PlayerRobot ) )
  673. {
  674. bIsRobot = true;
  675. break;
  676. }
  677. }
  678. /*if ( pLocalPlayer )
  679. {
  680. int iRobot = 0;
  681. CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iRobot, appear_as_mvm_robot );
  682. bIsRobot = iRobot ? true : false;
  683. }*/
  684. m_pTFPlayerModelPanel->SetToPlayerClass( iClass, bIsRobot, bClassWasRandom );
  685. m_pEditLoadoutButton->SetVisible( true );
  686. if ( m_pEditLoadoutHintIcon )
  687. {
  688. m_pEditLoadoutHintIcon->SetVisible( true );
  689. }
  690. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  691. if ( pLocalPlayer )
  692. {
  693. int iTeam = GetTeamNumber();
  694. m_pTFPlayerModelPanel->SetTeam( iTeam );
  695. }
  696. LoadItems();
  697. }
  698. m_pClassTipsPanel->SetClass( iClass );
  699. enginesound->StopSoundByGuid( m_nBaseMusicGuid );
  700. CBroadcastRecipientFilter filter;
  701. char nClassMusicStr[64];
  702. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  703. {
  704. sprintf( nClassMusicStr, "music.mvm_class_menu_0%i", iClass );
  705. }
  706. else
  707. {
  708. sprintf( nClassMusicStr, "music.class_menu_0%i", iClass );
  709. }
  710. CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, nClassMusicStr );
  711. m_nBaseMusicGuid = enginesound->GetGuidForLastSoundEmitted();
  712. }
  713. //-----------------------------------------------------------------------------
  714. // Purpose:
  715. //-----------------------------------------------------------------------------
  716. void CTFClassMenu::LoadItems()
  717. {
  718. const int iClass = m_pTFPlayerModelPanel->GetPlayerClass();
  719. m_pTFPlayerModelPanel->ClearCarriedItems();
  720. static CSchemaAttributeDefHandle pAttrDef_DisableFancyLoadoutAnim( "disable fancy class select anim" );
  721. bool bCanUseFancyClassSelectAnimation = true;
  722. static CSchemaAttributeDefHandle pAttrDef_ClassSelectOverrideVCD( "class select override vcd" );
  723. CAttribute_String attrClassSelectOverrideVCD;
  724. const char *pszVCD = "class_select";
  725. for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ )
  726. {
  727. CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i );
  728. if ( pItemData && pItemData->IsValid() )
  729. {
  730. m_pTFPlayerModelPanel->AddCarriedItem( pItemData );
  731. // Certain items have different shapes and would interfere with our class select animations.
  732. bCanUseFancyClassSelectAnimation = bCanUseFancyClassSelectAnimation
  733. && !pItemData->FindAttribute( pAttrDef_DisableFancyLoadoutAnim );
  734. // Some items want to override the class select VCD
  735. if ( pItemData->FindAttribute( pAttrDef_ClassSelectOverrideVCD, &attrClassSelectOverrideVCD ) )
  736. {
  737. const char *pszClassSelectOverrideVCD = attrClassSelectOverrideVCD.value().c_str();
  738. if ( pszClassSelectOverrideVCD && *pszClassSelectOverrideVCD )
  739. {
  740. pszVCD = pszClassSelectOverrideVCD;
  741. }
  742. }
  743. }
  744. }
  745. m_pTFPlayerModelPanel->PlayVCD( bCanUseFancyClassSelectAnimation ? pszVCD : NULL, g_pszLegacyClassSelectVCDWeapons[iClass] );
  746. m_pTFPlayerModelPanel->HoldItemInSlot( g_iLegacyClassSelectWeaponSlots[iClass] );
  747. }
  748. //-----------------------------------------------------------------------------
  749. // Purpose:
  750. //-----------------------------------------------------------------------------
  751. void CTFClassMenu::OnKeyCodePressed( KeyCode code )
  752. {
  753. m_KeyRepeat.KeyDown( code );
  754. if ( code > KEY_0 && code <= KEY_9 )
  755. {
  756. const int iButton = code - KEY_0;
  757. const int iClass = iRemapIndexToClass[ iButton ];
  758. SelectClass( iClass );
  759. Go();
  760. }
  761. else if ( code > KEY_PAD_0 && code <= KEY_PAD_9 )
  762. {
  763. const int iButton = code - KEY_PAD_0;
  764. const int iClass = iRemapIndexToClass[ iButton ];
  765. SelectClass( iClass );
  766. Go();
  767. }
  768. else if( code == KEY_ENTER || code == KEY_SPACE || code == KEY_XBUTTON_A || code == KEY_XBUTTON_RTRIGGER || code == STEAMCONTROLLER_A )
  769. {
  770. Go();
  771. }
  772. else if ( ( m_iClassMenuKey != BUTTON_CODE_INVALID && m_iClassMenuKey == code ) ||
  773. code == KEY_XBUTTON_BACK ||
  774. code == KEY_XBUTTON_B ||
  775. code == STEAMCONTROLLER_B ||
  776. code == KEY_0 ||
  777. code == KEY_PAD_0 )
  778. {
  779. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  780. if ( pLocalPlayer && ( pLocalPlayer->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) )
  781. {
  782. ShowPanel( false );
  783. }
  784. }
  785. else if( code == KEY_XBUTTON_RIGHT || code == KEY_XSTICK1_RIGHT || code == STEAMCONTROLLER_DPAD_RIGHT )
  786. {
  787. int loopCheck = 0;
  788. int nCurrentClass = GetRemappedMenuIndexForClass( m_iCurrentClassIndex );
  789. do
  790. {
  791. loopCheck++;
  792. nCurrentClass++;
  793. nCurrentClass = ( nCurrentClass % TF_CLASS_MENU_BUTTONS );
  794. } while( ( m_pClassButtons[ iRemapIndexToClass[nCurrentClass] ] == NULL ) && ( loopCheck < TF_CLASS_MENU_BUTTONS ) );
  795. SelectClass( iRemapIndexToClass[ nCurrentClass ] );
  796. }
  797. else if ( code == STEAMCONTROLLER_Y )
  798. {
  799. OnCommand( "openloadout" );
  800. }
  801. else if( code == KEY_XBUTTON_LEFT || code == KEY_XSTICK1_LEFT || code == STEAMCONTROLLER_DPAD_LEFT )
  802. {
  803. int loopCheck = 0;
  804. int nCurrentClass = GetRemappedMenuIndexForClass( m_iCurrentClassIndex );
  805. do
  806. {
  807. loopCheck++;
  808. nCurrentClass--;
  809. if( nCurrentClass <= 0 )
  810. {
  811. nCurrentClass = GetRemappedMenuIndexForClass( TF_CLASS_RANDOM );
  812. }
  813. } while( ( m_pClassButtons[ iRemapIndexToClass[nCurrentClass] ] == NULL ) && ( loopCheck < TF_CLASS_MENU_BUTTONS ) );
  814. SelectClass( iRemapIndexToClass[ nCurrentClass ] );
  815. }
  816. else if( code == KEY_XBUTTON_UP || code == KEY_XSTICK1_UP || code == STEAMCONTROLLER_DPAD_UP )
  817. {
  818. // Scroll class info text up
  819. if ( g_lastPanel )
  820. {
  821. CExRichText *pRichText = dynamic_cast< CExRichText * >( g_lastPanel->FindChildByName( "classInfo" ) );
  822. if ( pRichText )
  823. {
  824. PostMessage( pRichText, new KeyValues("MoveScrollBarDirect", "delta", 1) );
  825. }
  826. }
  827. }
  828. else if( code == KEY_XBUTTON_DOWN || code == KEY_XSTICK1_DOWN || code == STEAMCONTROLLER_DPAD_DOWN )
  829. {
  830. // Scroll class info text up
  831. if ( g_lastPanel )
  832. {
  833. CExRichText *pRichText = dynamic_cast< CExRichText * >( g_lastPanel->FindChildByName( "classInfo" ) );
  834. if ( pRichText )
  835. {
  836. PostMessage( pRichText, new KeyValues("MoveScrollBarDirect", "delta", -1) );
  837. }
  838. }
  839. }
  840. else
  841. {
  842. BaseClass::OnKeyCodePressed( code );
  843. }
  844. }
  845. //-----------------------------------------------------------------------------
  846. // Purpose:
  847. //-----------------------------------------------------------------------------
  848. void CTFClassMenu::OnKeyCodeReleased( vgui::KeyCode code )
  849. {
  850. m_KeyRepeat.KeyUp( code );
  851. BaseClass::OnKeyCodeReleased( code );
  852. }
  853. //-----------------------------------------------------------------------------
  854. // Purpose:
  855. //-----------------------------------------------------------------------------
  856. void CTFClassMenu::OnThink()
  857. {
  858. vgui::KeyCode code = m_KeyRepeat.KeyRepeated();
  859. if ( code )
  860. {
  861. OnKeyCodePressed( code );
  862. }
  863. // Get mouse cursor position
  864. int aCursorPos[2];
  865. vgui::input()->GetCursorPos( aCursorPos[0], aCursorPos[1] );
  866. // Go through all buttons - if the mouse is within one, select that class
  867. for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i )
  868. {
  869. if ( !m_pClassButtons[ i ] )
  870. continue;
  871. if ( m_iCurrentClassIndex != i && m_pClassButtons[ i ]->IsWithin( aCursorPos[0], aCursorPos[1] ) )
  872. {
  873. SelectClass( i );
  874. }
  875. }
  876. //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this.
  877. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  878. if ( pLocalPlayer )
  879. {
  880. pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
  881. }
  882. BaseClass::OnThink();
  883. }
  884. //-----------------------------------------------------------------------------
  885. // Purpose:
  886. //-----------------------------------------------------------------------------
  887. void CTFClassMenu::SetCancelButtonVisible( bool bVisible )
  888. {
  889. SetVisibleButton( "CancelButton", bVisible );
  890. if ( m_pCancelHintIcon )
  891. {
  892. m_pCancelHintIcon->SetVisible( bVisible );
  893. }
  894. if ( m_pSelectAClassLabel )
  895. {
  896. m_pSelectAClassLabel->SetVisible( !bVisible );
  897. }
  898. }
  899. //-----------------------------------------------------------------------------
  900. // Purpose:
  901. //-----------------------------------------------------------------------------
  902. void CTFClassMenu::Update()
  903. {
  904. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  905. // Force them to pick a class if they haven't picked one yet.
  906. if ( ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() != TF_CLASS_UNDEFINED ) )
  907. {
  908. #ifdef _X360
  909. if ( m_pFooter )
  910. {
  911. m_pFooter->ShowButtonLabel( "cancel", true );
  912. }
  913. #else
  914. SetCancelButtonVisible( true );
  915. if ( TFGameRules() && TFGameRules()->IsInHighlanderMode() )
  916. {
  917. SetVisibleButton( "ResetButton", true );
  918. }
  919. else
  920. {
  921. SetVisibleButton( "ResetButton", false );
  922. }
  923. #endif
  924. }
  925. else
  926. {
  927. #ifdef _X360
  928. if ( m_pFooter )
  929. {
  930. m_pFooter->ShowButtonLabel( "cancel", false );
  931. }
  932. #else
  933. SetCancelButtonVisible( false );
  934. SetVisibleButton( "ResetButton", false );
  935. #endif
  936. }
  937. }
  938. //-----------------------------------------------------------------------------
  939. // Purpose:
  940. //-----------------------------------------------------------------------------
  941. Panel *CTFClassMenu::CreateControlByName( const char *controlName )
  942. {
  943. if ( !Q_stricmp( "CIconPanel", controlName ) )
  944. {
  945. return new CIconPanel( this, "icon_panel" );
  946. }
  947. else
  948. {
  949. return BaseClass::CreateControlByName( controlName );
  950. }
  951. }
  952. //-----------------------------------------------------------------------------
  953. // Catch the mouseover event and set the active class
  954. //-----------------------------------------------------------------------------
  955. void CTFClassMenu::OnShowPage( vgui::Panel *panel, const char *pagename )
  956. {
  957. for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
  958. {
  959. if (m_pClassButtons[i] == panel )
  960. {
  961. // SelectClass( i );
  962. break;
  963. }
  964. }
  965. }
  966. //-----------------------------------------------------------------------------
  967. // Draw nothing
  968. //-----------------------------------------------------------------------------
  969. void CTFClassMenu::PaintBackground( void )
  970. {
  971. }
  972. //-----------------------------------------------------------------------------
  973. // Do things that should be done often, eg number of players in the
  974. // selected class
  975. //-----------------------------------------------------------------------------
  976. void CTFClassMenu::OnTick( void )
  977. {
  978. //When a player changes teams, their class and team values don't get here
  979. //necessarily before the command to update the class menu. This leads to the cancel button
  980. //being visible and people cancelling before they have a class. check for class == TF_CLASS_UNDEFINED and if so
  981. //hide the cancel button
  982. if ( !IsVisible() )
  983. return;
  984. #ifndef _X360
  985. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  986. // Force them to pick a class if they haven't picked one yet.
  987. if ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() == TF_CLASS_UNDEFINED )
  988. {
  989. SetCancelButtonVisible( false );
  990. SetVisibleButton( "ResetButton", false );
  991. }
  992. UpdateClassCounts();
  993. #endif
  994. BaseClass::OnTick();
  995. }
  996. //-----------------------------------------------------------------------------
  997. // Purpose:
  998. //-----------------------------------------------------------------------------
  999. void CTFClassMenu::OnClose()
  1000. {
  1001. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  1002. if ( pLocalPlayer )
  1003. {
  1004. // Clear the HIDEHUD_HEALTH bit we hackily added. Turns out prediction
  1005. // was restoring these bits every frame. Unfortunately, prediction
  1006. // is off for karts which means the spell hud item would disappear if you
  1007. // brought up this menu and returned.
  1008. pLocalPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_HEALTH;
  1009. }
  1010. ShowPanel( false );
  1011. BaseClass::OnClose();
  1012. }
  1013. //-----------------------------------------------------------------------------
  1014. // Purpose:
  1015. //-----------------------------------------------------------------------------
  1016. void CTFClassMenu::SetVisible( bool state )
  1017. {
  1018. BaseClass::SetVisible( state );
  1019. m_KeyRepeat.Reset();
  1020. if ( state )
  1021. {
  1022. engine->ServerCmd( "menuopen" ); // to the server
  1023. engine->ClientCmd( "_cl_classmenuopen 1" ); // for other panels
  1024. CBroadcastRecipientFilter filter;
  1025. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  1026. {
  1027. CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.mvm_class_menu" );
  1028. }
  1029. else
  1030. {
  1031. CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.class_menu" );
  1032. }
  1033. CheckMvMUpgrades();
  1034. }
  1035. else
  1036. {
  1037. engine->ServerCmd( "menuclosed" );
  1038. engine->ClientCmd( "_cl_classmenuopen 0" );
  1039. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  1040. {
  1041. CBaseEntity::StopSound( SOUND_FROM_UI_PANEL, "music.mvm_class_menu" );
  1042. }
  1043. else
  1044. {
  1045. CBaseEntity::StopSound( SOUND_FROM_UI_PANEL, "music.class_menu" );
  1046. }
  1047. }
  1048. }
  1049. //-----------------------------------------------------------------------------
  1050. // Purpose:
  1051. //-----------------------------------------------------------------------------
  1052. void CTFClassMenu::Go()
  1053. {
  1054. const int iClass = m_iCurrentClassIndex;
  1055. if ( iClass == TF_CLASS_UNDEFINED )
  1056. return;
  1057. // Check class limits
  1058. if ( TFGameRules() && !TFGameRules()->CanPlayerChooseClass( C_TFPlayer::GetLocalTFPlayer(), iClass ) )
  1059. return;
  1060. #if defined( REPLAY_ENABLED )
  1061. // Display replay recording message if appropriate
  1062. int &nDisplayedConnectedRecording = CPlayerSpawnCache::Instance().m_Data.m_nDisplayedConnectedRecording;
  1063. if ( g_pReplay->IsReplayEnabled() &&
  1064. !g_pEngineClientReplay->IsPlayingReplayDemo() && // FIXME: We shouldn't need this here but for some reason the engine thinks a replay is recording during demo playback, even though replay_recording has a FCVAR_DONTRECORD flag
  1065. !nDisplayedConnectedRecording &&
  1066. replay_replaywelcomedlgcount.GetInt() <= MAX_TIMES_TO_SHOW_REPLAY_WELCOME_DLG )
  1067. {
  1068. wchar_t wText[256];
  1069. wchar wKeyBind[80];
  1070. char szText[256];
  1071. const char *pSaveReplayKey = engine->Key_LookupBinding( "save_replay" );
  1072. if ( !pSaveReplayKey )
  1073. {
  1074. pSaveReplayKey = "< not bound >";
  1075. }
  1076. g_pVGuiLocalize->ConvertANSIToUnicode( pSaveReplayKey, wKeyBind, sizeof( wKeyBind ) );
  1077. g_pVGuiLocalize->ConstructString_safe( wText, g_pVGuiLocalize->Find( "#Replay_ConnectRecording" ), 1, wKeyBind );
  1078. g_pVGuiLocalize->ConvertUnicodeToANSI( wText, szText, sizeof( szText ) );
  1079. extern ConVar replay_msgduration_connectrecording;
  1080. g_pClientMode->DisplayReplayMessage( szText, replay_msgduration_connectrecording.GetFloat(), false, NULL, true );
  1081. // Don't execute this clause next time the player spawns, unless the cache has been cleared
  1082. ++nDisplayedConnectedRecording;
  1083. // Increment (archives)
  1084. replay_replaywelcomedlgcount.SetValue( replay_replaywelcomedlgcount.GetInt() + 1 );
  1085. }
  1086. #endif
  1087. // This will complete any pending replay, commit if necessary, and clear - this way when the player respawns
  1088. // we will start with a fresh replay for the new life.
  1089. g_pClientReplayContext->OnPlayerClassChanged();
  1090. // Change class
  1091. BaseClass::OnCommand( CFmtStr( "joinclass %s", g_aRawPlayerClassNames[ iClass ] ).Access() );
  1092. CBroadcastRecipientFilter filter;
  1093. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  1094. {
  1095. CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.mvm_class_select" );
  1096. }
  1097. else
  1098. {
  1099. }
  1100. }
  1101. //-----------------------------------------------------------------------------
  1102. // Purpose:
  1103. //-----------------------------------------------------------------------------
  1104. void CTFClassMenu::OnCommand( const char *command )
  1105. {
  1106. if ( !V_strnicmp( command, "select", 6 ) )
  1107. {
  1108. const char *pClass = command + 6;
  1109. const int iClass = atoi( pClass );
  1110. // Avoid restarting the animation if the user selected on the same class
  1111. if ( iClass != m_iCurrentClassIndex )
  1112. {
  1113. SelectClass( iClass );
  1114. }
  1115. else
  1116. {
  1117. // Ensure selection states, in case the user clicks a button and drags away
  1118. UpdateButtonSelectionStates( iClass );
  1119. }
  1120. Go();
  1121. }
  1122. else if ( !V_strnicmp( command, "resetclass", 10 ) )
  1123. {
  1124. if ( TFGameRules() && !TFGameRules()->IsInHighlanderMode() )
  1125. return;
  1126. engine->ClientCmd( const_cast<char *>( command ) );
  1127. }
  1128. else if ( !V_strnicmp( command, "openloadout", 11 ) )
  1129. {
  1130. // Let this panel know when you've closed, so we can reload items
  1131. EconUI()->AddPanelCloseListener( this );
  1132. // Make the back button close, rather than go back to the econ root panel
  1133. EconUI()->SetClosePanel( -m_iCurrentClassIndex );
  1134. // Set team number, so the model's color will match
  1135. EconUI()->SetDefaultTeam( GetTeamNumber() );
  1136. // Go directly to the loadout for the selected class
  1137. EconUI()->OpenEconUI( -m_iCurrentClassIndex );
  1138. }
  1139. else
  1140. {
  1141. BaseClass::OnCommand( command );
  1142. }
  1143. }
  1144. //-----------------------------------------------------------------------------
  1145. // Purpose: Console command to select a class
  1146. //-----------------------------------------------------------------------------
  1147. void CTFClassMenu::Join_Class( const CCommand &args )
  1148. {
  1149. if ( args.ArgC() > 1 )
  1150. {
  1151. char cmd[256];
  1152. Q_snprintf( cmd, sizeof( cmd ), "joinclass %s", args.Arg( 1 ) );
  1153. OnCommand( cmd );
  1154. ShowPanel( false );
  1155. }
  1156. }
  1157. static const char *g_sDialogVariables[] = {
  1158. "",
  1159. "numScout",
  1160. "numSoldier",
  1161. "numPyro",
  1162. "numDemoman",
  1163. "numHeavy",
  1164. "numEngineer",
  1165. "numMedic",
  1166. "numSniper",
  1167. "numSpy",
  1168. "",
  1169. };
  1170. static const char *g_sClassImagesBlue[] = {
  1171. "",
  1172. "class_sel_sm_scout_blu",
  1173. "class_sel_sm_soldier_blu",
  1174. "class_sel_sm_pyro_blu",
  1175. "class_sel_sm_demo_blu",
  1176. "class_sel_sm_heavy_blu",
  1177. "class_sel_sm_engineer_blu",
  1178. "class_sel_sm_medic_blu",
  1179. "class_sel_sm_sniper_blu",
  1180. "class_sel_sm_spy_blu",
  1181. "class_sel_sm_scout_blu",
  1182. };
  1183. static const char *g_sClassImagesRed[] = {
  1184. "",
  1185. "class_sel_sm_scout_red",
  1186. "class_sel_sm_soldier_red",
  1187. "class_sel_sm_pyro_red",
  1188. "class_sel_sm_demo_red",
  1189. "class_sel_sm_heavy_red",
  1190. "class_sel_sm_engineer_red",
  1191. "class_sel_sm_medic_red",
  1192. "class_sel_sm_sniper_red",
  1193. "class_sel_sm_spy_red",
  1194. "class_sel_sm_scout_red",
  1195. };
  1196. int g_ClassDefinesRemap[] = {
  1197. 0,
  1198. TF_CLASS_SCOUT,
  1199. TF_CLASS_SOLDIER,
  1200. TF_CLASS_PYRO,
  1201. TF_CLASS_DEMOMAN,
  1202. TF_CLASS_HEAVYWEAPONS,
  1203. TF_CLASS_ENGINEER,
  1204. TF_CLASS_MEDIC,
  1205. TF_CLASS_SNIPER,
  1206. TF_CLASS_SPY,
  1207. TF_CLASS_CIVILIAN,
  1208. };
  1209. void CTFClassMenu::UpdateNumClassLabels( int iTeam )
  1210. {
  1211. #ifndef _X360
  1212. int nTotalCount = 0;
  1213. // count how many of each class there are
  1214. if ( !g_TF_PR )
  1215. return;
  1216. if ( iTeam < FIRST_GAME_TEAM || iTeam >= TF_TEAM_COUNT ) // invalid team number
  1217. return;
  1218. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1219. bool bSpectator = pLocalPlayer && pLocalPlayer->GetTeamNumber() == TEAM_SPECTATOR;
  1220. int iLocalPlayerClass = TF_CLASS_UNDEFINED;
  1221. if ( pLocalPlayer )
  1222. {
  1223. iLocalPlayerClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex();
  1224. }
  1225. if ( iLocalPlayerClass == TF_CLASS_UNDEFINED )
  1226. {
  1227. m_iLocalPlayerClass = iLocalPlayerClass;
  1228. if ( m_pLocalPlayerImage && m_pLocalPlayerImage->IsVisible() )
  1229. {
  1230. m_pLocalPlayerImage->SetVisible( false );
  1231. }
  1232. if ( m_pLocalPlayerBG && m_pLocalPlayerBG->IsVisible() )
  1233. {
  1234. m_pLocalPlayerBG->SetVisible( false );
  1235. }
  1236. }
  1237. for( int i = TF_FIRST_NORMAL_CLASS ; i <= TF_LAST_NORMAL_CLASS ; i++ )
  1238. {
  1239. if ( bSpectator == true )
  1240. {
  1241. SetDialogVariable( g_sDialogVariables[i], "" );
  1242. continue;
  1243. }
  1244. int classCount = g_TF_PR->GetCountForPlayerClass( iTeam, g_ClassDefinesRemap[i], false );
  1245. int iClassLimit = TFGameRules()->GetClassLimit( g_ClassDefinesRemap[i] );
  1246. if ( iClassLimit != NO_CLASS_LIMIT )
  1247. {
  1248. if ( classCount >= iClassLimit )
  1249. {
  1250. if ( classCount > 0 )
  1251. {
  1252. wchar_t wTemp[32];
  1253. wchar_t wzCount[10];
  1254. _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", classCount );
  1255. g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("TF_ClassLimitHit"), 1, wzCount );
  1256. SetDialogVariable( g_sDialogVariables[i], wTemp );
  1257. }
  1258. else
  1259. {
  1260. SetDialogVariable( g_sDialogVariables[i], g_pVGuiLocalize->Find("TF_ClassLimitHit_None") );
  1261. }
  1262. }
  1263. else
  1264. {
  1265. wchar_t wTemp[32];
  1266. wchar_t wzCount[10];
  1267. _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", classCount );
  1268. wchar_t wzMax[10];
  1269. _snwprintf( wzMax, ARRAYSIZE( wzMax ), L"%d", iClassLimit );
  1270. g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("TF_ClassLimitUnder"), 2, wzCount, wzMax );
  1271. SetDialogVariable( g_sDialogVariables[i], wTemp );
  1272. }
  1273. }
  1274. else if ( classCount > 0 )
  1275. {
  1276. SetDialogVariable( g_sDialogVariables[i], classCount );
  1277. }
  1278. else
  1279. {
  1280. SetDialogVariable( g_sDialogVariables[i], "" );
  1281. }
  1282. if ( g_ClassDefinesRemap[i] == iLocalPlayerClass )
  1283. {
  1284. // take 1 off the count for the images since the local player has their own image already
  1285. if ( classCount > 0 )
  1286. {
  1287. classCount--;
  1288. }
  1289. if ( m_pLocalPlayerImage )
  1290. {
  1291. if ( !m_pLocalPlayerImage->IsVisible() )
  1292. {
  1293. m_pLocalPlayerImage->SetVisible( true );
  1294. }
  1295. if ( m_iLocalPlayerClass != iLocalPlayerClass )
  1296. {
  1297. m_iLocalPlayerClass = iLocalPlayerClass;
  1298. m_pLocalPlayerImage->SetImage( iTeam == TF_TEAM_BLUE ? g_sClassImagesBlue[i] : g_sClassImagesRed[i] );
  1299. }
  1300. }
  1301. if ( m_pLocalPlayerBG && !m_pLocalPlayerBG->IsVisible() )
  1302. {
  1303. m_pLocalPlayerBG->SetVisible( true );
  1304. }
  1305. }
  1306. if ( nTotalCount < CLASS_COUNT_IMAGES )
  1307. {
  1308. for ( int j = 0 ; j < classCount ; ++j )
  1309. {
  1310. CTFImagePanel *pImage = m_ClassCountImages[nTotalCount];
  1311. if ( pImage )
  1312. {
  1313. pImage->SetVisible( true );
  1314. pImage->SetImage( iTeam == TF_TEAM_BLUE ? g_sClassImagesBlue[i] : g_sClassImagesRed[i] );
  1315. }
  1316. nTotalCount++;
  1317. if ( nTotalCount >= CLASS_COUNT_IMAGES )
  1318. {
  1319. break;
  1320. }
  1321. }
  1322. }
  1323. }
  1324. if ( nTotalCount == 0 )
  1325. {
  1326. // no classes for our team yet
  1327. if ( m_pCountLabel && m_pCountLabel->IsVisible() )
  1328. {
  1329. m_pCountLabel->SetVisible( false );
  1330. }
  1331. }
  1332. else
  1333. {
  1334. if ( m_pCountLabel && !m_pCountLabel->IsVisible() )
  1335. {
  1336. m_pCountLabel->SetVisible( true );
  1337. }
  1338. }
  1339. // turn off any unused images
  1340. while ( nTotalCount < CLASS_COUNT_IMAGES )
  1341. {
  1342. CTFImagePanel *pImage = m_ClassCountImages[nTotalCount];
  1343. if ( pImage )
  1344. {
  1345. pImage->SetVisible( false );
  1346. }
  1347. nTotalCount++;
  1348. }
  1349. #endif
  1350. }
  1351. //-----------------------------------------------------------------------------
  1352. // Purpose:
  1353. //-----------------------------------------------------------------------------
  1354. void CTFClassMenu::FireGameEvent( IGameEvent *event )
  1355. {
  1356. const char *pszEventName = event->GetName();
  1357. // when we are changing levels
  1358. if ( FStrEq( pszEventName, "localplayer_changeteam" ) )
  1359. {
  1360. if ( IsVisible() )
  1361. {
  1362. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  1363. if ( pLocalPlayer )
  1364. {
  1365. int iTeam = pLocalPlayer->GetTeamNumber();
  1366. if ( iTeam != GetTeamNumber() )
  1367. {
  1368. ShowPanel( false );
  1369. if ( iTeam == TF_TEAM_BLUE )
  1370. {
  1371. gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true );
  1372. }
  1373. else
  1374. {
  1375. gViewPortInterface->ShowPanel( PANEL_CLASS_RED, true );
  1376. }
  1377. }
  1378. }
  1379. }
  1380. }
  1381. else if ( FStrEq( pszEventName, "show_match_summary" ) )
  1382. {
  1383. if ( IsVisible() )
  1384. {
  1385. ShowPanel( false );
  1386. }
  1387. }
  1388. }
  1389. //-----------------------------------------------------------------------------
  1390. // Purpose:
  1391. //-----------------------------------------------------------------------------
  1392. void CTFClassMenu::OnEconUIClosed()
  1393. {
  1394. // Reload items on model panel, in case anything's changed
  1395. LoadItems();
  1396. }
  1397. int g_nNumUpgradeIconsForLastHint = 0;
  1398. //-----------------------------------------------------------------------------
  1399. void CTFClassMenu::CheckMvMUpgrades()
  1400. {
  1401. // Set MvM Icons invisible
  1402. for ( int icons = 0; icons < ARRAYSIZE( m_pMvmUpgradeImages ); ++icons )
  1403. {
  1404. if ( m_pMvmUpgradeImages[icons] == NULL )
  1405. continue;
  1406. m_pMvmUpgradeImages[icons]->SetVisible( false );
  1407. }
  1408. if ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() )
  1409. return;
  1410. CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance();
  1411. if ( !pStats )
  1412. return;
  1413. CUtlVector< CUpgradeInfo > *upgrades = pStats->GetLocalPlayerUpgrades();
  1414. int nShowUpgradingHint = -1;
  1415. int nNumUpgradeIconsForHint = 0;
  1416. for ( int i = 0; i < upgrades->Count(); ++i )
  1417. {
  1418. vgui::Panel *pUpgradeImage = m_pMvmUpgradeImages[upgrades->Element(i).m_iPlayerClass];
  1419. if ( !pUpgradeImage )
  1420. continue;
  1421. if ( !pUpgradeImage->IsVisible() )
  1422. {
  1423. pUpgradeImage->SetVisible( true );
  1424. nNumUpgradeIconsForHint++;
  1425. // Only show the hint if we've shown it 3 or less times ever
  1426. if ( nShowUpgradingHint == -1 && tf_mvm_classupgradehelpcount.GetInt() < 3 )
  1427. {
  1428. int nY;
  1429. pUpgradeImage->GetPos( nShowUpgradingHint, nY );
  1430. nShowUpgradingHint += pUpgradeImage->GetWide() / 2;
  1431. }
  1432. }
  1433. }
  1434. // Only show the hint if there are more upgrade icon than the last time we openned the menu
  1435. if ( nShowUpgradingHint != -1 && g_nNumUpgradeIconsForLastHint < nNumUpgradeIconsForHint )
  1436. {
  1437. CExplanationPopup *pPopup = dynamic_cast< CExplanationPopup* >( FindChildByName("StartExplanation") );
  1438. if ( pPopup )
  1439. {
  1440. pPopup->SetCalloutInParentsX( nShowUpgradingHint );
  1441. pPopup->Popup();
  1442. g_nNumUpgradeIconsForLastHint = nNumUpgradeIconsForHint;
  1443. tf_mvm_classupgradehelpcount.SetValue( tf_mvm_classupgradehelpcount.GetInt() + 1 );
  1444. }
  1445. }
  1446. }