summaryrefslogtreecommitdiff
path: root/game/client/tf/vgui/tf_classmenu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/tf/vgui/tf_classmenu.cpp')
-rw-r--r--game/client/tf/vgui/tf_classmenu.cpp1703
1 files changed, 1703 insertions, 0 deletions
diff --git a/game/client/tf/vgui/tf_classmenu.cpp b/game/client/tf/vgui/tf_classmenu.cpp
new file mode 100644
index 0000000..c6ba14c
--- /dev/null
+++ b/game/client/tf/vgui/tf_classmenu.cpp
@@ -0,0 +1,1703 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_classmenu.h"
+#include "IGameUIFuncs.h" // for key bindings
+#include "tf_hud_notification_panel.h"
+#include "character_info_panel.h"
+#include "playerspawncache.h"
+#include "iclientmode.h"
+#include "econ_gcmessages.h"
+#include "gc_clientsystem.h"
+#include "vgui/IInput.h"
+#include <vgui_controls/PanelListPanel.h>
+#include <vgui_controls/ScrollBarSlider.h>
+#include "tf_gamerules.h"
+#include "engine/IEngineSound.h"
+#include "inputsystem/iinputsystem.h"
+
+#if defined( REPLAY_ENABLED )
+#include "replay/iclientreplaycontext.h"
+#include "replay/ireplaysystem.h"
+#include "replay/ienginereplay.h"
+#include "replay/shared_defs.h"
+#endif
+
+extern IGameUIFuncs *gameuifuncs; // for key binding details
+
+using namespace vgui;
+
+ConVar _cl_classmenuopen( "_cl_classmenuopen", "0", 0, "internal cvar used to tell server when class menu is open" );
+
+#if defined( REPLAY_ENABLED )
+ConVar replay_replaywelcomedlgcount( "replay_replaywelcomedlgcount", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "The number of times the replay help dialog has displayed." );
+#endif
+
+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." );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFClassTipsItemPanel::CTFClassTipsItemPanel( Panel *parent, const char *name, int iListItemID ) : BaseClass( parent, name )
+{
+ m_pTipIcon = new vgui::ImagePanel( this, "TipIcon" );
+ m_pTipLabel = new CExLabel( this, "TipLabel", "" );
+
+// m_pTipIcon = dynamic_cast<vgui::ImagePanel*>( FindChildByName( "TipIcon" ) );
+// m_pTipLabel = dynamic_cast<CExLabel*>( FindChildByName( "TipLabel" ) );
+}
+
+CTFClassTipsItemPanel::~CTFClassTipsItemPanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassTipsItemPanel::SetClassTip( const wchar_t *pwszText, const char *pszIcon )
+{
+ // Set tf_english string and .res icon path
+ if ( m_pTipLabel && m_pTipIcon )
+ {
+ if ( pszIcon )
+ {
+ m_pTipIcon->SetImage( pszIcon );
+ }
+ else
+ {
+ m_pTipIcon->SetVisible( false );
+// int iX, iY = 0;
+// m_pTipLabel->GetPos( iX, iY );
+// int nIconWidth = m_pTipIcon->GetWide();
+// m_pTipLabel->SetPos( ( iX - nIconWidth ), iY );
+ }
+ m_pTipLabel->SetText( pwszText );
+ }
+
+ InvalidateLayout( true, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassTipsItemPanel::ApplySchemeSettings( IScheme* pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/ClassTipsItem.res" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFClassTipsListPanel : public PanelListPanel
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFClassTipsListPanel, PanelListPanel );
+
+public:
+ CTFClassTipsListPanel( Panel *parent, const char *panelName )
+ : PanelListPanel( parent, panelName )
+ {
+ m_pScrollBar = GetScrollbar();
+
+ m_pUpArrow = new CExImageButton( this, "UpArrow", "" );
+ if ( m_pUpArrow )
+ {
+ m_pUpArrow->AddActionSignalTarget( m_pScrollBar );
+ m_pUpArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 0));
+ m_pUpArrow->GetImage()->SetShouldScaleImage( true );
+ m_pUpArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
+ m_pUpArrow->SetAlpha( 255 );
+ m_pUpArrow->SetPaintBackgroundEnabled( false );
+ m_pUpArrow->SetVisible( false );
+ m_pUpArrow->PassMouseTicksTo( m_pScrollBar );
+ m_pUpArrow->SetImageDefault( "chalkboard_scroll_up" );
+ }
+
+ m_pDownArrow = new CExImageButton( this, "DownArrow", "" );
+ if ( m_pDownArrow )
+ {
+ m_pDownArrow->AddActionSignalTarget( m_pScrollBar );
+ m_pDownArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 1));
+ m_pDownArrow->GetImage()->SetShouldScaleImage( true );
+ m_pDownArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
+ m_pDownArrow->SetAlpha( 255 );
+ m_pDownArrow->SetPaintBackgroundEnabled( false );
+ m_pDownArrow->SetVisible( false );
+ m_pDownArrow->PassMouseTicksTo( m_pScrollBar );
+ m_pDownArrow->SetImageDefault( "chalkboard_scroll_down" );
+ }
+
+ if ( m_pScrollBar )
+ {
+ m_pScrollBar->SetOverriddenButtons( m_pUpArrow, m_pDownArrow );
+ m_pScrollBar->SetVisible( false );
+ }
+
+ m_pLine = new vgui::ImagePanel( this, "Line" );
+ m_pBox = new vgui::ImagePanel( this, "Box" );
+ if ( m_pLine )
+ {
+ m_pLine->SetImage( "chalkboard_scroll_line" );
+ m_pLine->SetShouldScaleImage( true );
+ }
+
+ if ( m_pBox )
+ {
+ m_pBox->SetImage( "chalkboard_scroll_box" );
+ m_pBox->SetShouldScaleImage( true );
+ }
+
+ SetScrollBarImagesVisible( false );
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ ~CTFClassTipsListPanel()
+ {
+ ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+
+private:
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ int GetListHeight( void )
+ {
+ int nHeight = 0;
+
+ for ( int i = FirstItem(); i < GetItemCount(); i++ )
+ {
+ vgui::Panel *pClassTipsItemPanel = GetItemPanel( i );
+ if ( pClassTipsItemPanel )
+ {
+ nHeight += pClassTipsItemPanel->GetTall();
+ }
+ }
+
+ return nHeight;
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ void SetScrollBarImagesVisible( bool visible )
+ {
+ if ( m_pDownArrow && m_pDownArrow->IsVisible() != visible )
+ {
+ m_pDownArrow->SetVisible( visible );
+ m_pDownArrow->SetEnabled( visible );
+ }
+
+ if ( m_pUpArrow && m_pUpArrow->IsVisible() != visible )
+ {
+ m_pUpArrow->SetVisible( visible );
+ m_pUpArrow->SetEnabled( visible );
+ }
+
+ if ( m_pLine && m_pLine->IsVisible() != visible )
+ {
+ m_pLine->SetVisible( visible );
+ }
+
+ if ( m_pBox && m_pBox->IsVisible() != visible )
+ {
+ m_pBox->SetVisible( visible );
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ void OnTick()
+ {
+ if ( !IsVisible() )
+ return;
+
+ if ( m_pDownArrow && m_pUpArrow && m_pLine && m_pBox )
+ {
+ if ( m_pScrollBar && m_pScrollBar->IsVisible() && GetListHeight() > GetTall() )
+ {
+ SetScrollBarImagesVisible ( true );
+
+ // set the alpha on the up arrow
+ int nMin, nMax;
+ m_pScrollBar->GetRange( nMin, nMax );
+ int nScrollPos = m_pScrollBar->GetValue();
+ int nRangeWindow = m_pScrollBar->GetRangeWindow();
+ int nBottom = nMax - nRangeWindow;
+ if ( nBottom < 0 )
+ {
+ nBottom = 0;
+ }
+
+ int nAlpha = ( nScrollPos - nMin <= 0 ) ? 90 : 255;
+ m_pUpArrow->SetAlpha( nAlpha );
+
+ // set the alpha on the down arrow
+ nAlpha = ( nScrollPos >= nBottom ) ? 90 : 255;
+ m_pDownArrow->SetAlpha( nAlpha );
+
+ ScrollBarSlider *pSlider = m_pScrollBar->GetSlider();
+ if ( pSlider && pSlider->GetRangeWindow() > 0 )
+ {
+ int x, y, w, t, min, max;
+ m_pLine->GetBounds( x, y, w, t );
+ pSlider->GetNobPos( min, max );
+ m_pBox->SetBounds( x, y + min, w, ( max - min ) );
+ }
+ }
+ else
+ {
+ // turn off our images
+ SetScrollBarImagesVisible ( false );
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( m_pScrollBar )
+ {
+ m_pScrollBar->SetZPos( 500 );
+ m_pUpArrow->SetZPos( 501 );
+ m_pDownArrow->SetZPos( 501 );
+
+ // turn off painting the vertical scrollbar
+ m_pScrollBar->SetPaintBackgroundEnabled( false );
+ m_pScrollBar->SetPaintBorderEnabled( false );
+ m_pScrollBar->SetPaintEnabled( false );
+ m_pScrollBar->SetScrollbarButtonsVisible( false );
+ m_pScrollBar->GetButton(0)->SetMouseInputEnabled( false );
+ m_pScrollBar->GetButton(1)->SetMouseInputEnabled( false );
+
+ if ( m_pScrollBar->IsVisible() )
+ {
+ int nMin, nMax;
+ m_pScrollBar->GetRange( nMin, nMax );
+ m_pScrollBar->SetValue( nMin );
+
+ int nScrollbarWide = m_pScrollBar->GetWide();
+
+ int wide, tall;
+ GetSize( wide, tall );
+
+ if ( m_pUpArrow )
+ {
+ m_pUpArrow->SetBounds( wide - nScrollbarWide, 0, nScrollbarWide, nScrollbarWide );
+ m_pUpArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide );
+ }
+
+ if ( m_pLine )
+ {
+ m_pLine->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, tall - ( 2 * nScrollbarWide ) );
+ }
+
+ if ( m_pBox )
+ {
+ m_pBox->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, nScrollbarWide );
+ }
+
+ if ( m_pDownArrow )
+ {
+ m_pDownArrow->SetBounds( wide - nScrollbarWide, tall - nScrollbarWide, nScrollbarWide, nScrollbarWide );
+ m_pDownArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide );
+ }
+
+ SetScrollBarImagesVisible( false );
+ }
+ }
+ }
+
+ vgui::ScrollBar *m_pScrollBar;
+ CExImageButton *m_pUpArrow;
+ CExImageButton *m_pDownArrow;
+ vgui::ImagePanel *m_pLine;
+ vgui::ImagePanel *m_pBox;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFClassTipsPanel : public EditablePanel
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFClassTipsPanel, EditablePanel );
+
+public:
+ CTFClassTipsPanel( Panel *parent, const char *panelName )
+ : EditablePanel( parent, panelName )
+ {
+ m_pClassTipsListPanel = new CTFClassTipsListPanel( this, "ClassTipsListPanel" );
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ ~CTFClassTipsPanel()
+ {
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ void SetClass( int iClass )
+ {
+ const char *pPathID = IsX360() ? "MOD" : "GAME";
+ m_fmtResFilename.sprintf( "classes/%s.res", g_aRawPlayerClassNames[ iClass ] );
+ if ( !g_pFullFileSystem->FileExists( m_fmtResFilename.Access(), pPathID ) &&
+ g_pFullFileSystem->FileExists( "classes/default.res", pPathID ) )
+ {
+ m_fmtResFilename.sprintf( "classes/default.res" );
+ }
+
+ if ( m_pClassTipsListPanel )
+ {
+ m_pClassTipsListPanel->DeleteAllItems();
+
+ int nScrollToItem = 0;
+
+ // Get tip count
+ const wchar_t *wzTipCount = g_pVGuiLocalize->Find( CFmtStr( "ClassTips_%d_Count", iClass ) );
+ int nTipCount = wzTipCount ? _wtoi( wzTipCount ) : 0;
+ for ( int iTip = 1; iTip < nTipCount+1; ++iTip )
+ {
+ const wchar_t *pwszText = g_pVGuiLocalize->Find( CFmtStr( "#ClassTips_%d_%d", iClass, iTip ) );
+ const wchar_t *pwszTextMvM = g_pVGuiLocalize->Find( CFmtStr( "#ClassTips_%d_%d_MvM", iClass, iTip ) );
+ wchar_t *pwszIcon = g_pVGuiLocalize->Find( CFmtStr( "ClassTips_%d_%d_Icon", iClass, iTip ) );
+ char szIcon[MAX_PATH];
+
+ szIcon[0] = 0;
+ if ( pwszIcon )
+ {
+ g_pVGuiLocalize->ConvertUnicodeToANSI( pwszIcon, szIcon, sizeof( szIcon ) );
+ }
+
+ // Don't load MvM tips outside the mode
+ if ( pwszTextMvM )
+ {
+ if ( !TFGameRules()->IsMannVsMachineMode() )
+ continue;
+
+ // If we're MvM mode, remember first MvM tip
+ if ( !nScrollToItem )
+ nScrollToItem = iTip;
+ }
+
+ // Create a TipsItemPanel for each tip
+ if ( pwszText || pwszTextMvM )
+ {
+ CTFClassTipsItemPanel *pClassTipsItemPanel = new CTFClassTipsItemPanel( this, "ClassTipsItemPanel", iTip );
+ if ( pwszText )
+ {
+ pClassTipsItemPanel->SetClassTip( pwszText, szIcon );
+ }
+ else if ( pwszTextMvM )
+ {
+ pClassTipsItemPanel->SetClassTip( pwszTextMvM, szIcon );
+ }
+
+ m_pClassTipsListPanel->AddItem( NULL, pClassTipsItemPanel );
+ }
+ }
+
+ if ( m_pClassTipsListPanel->GetItemCount() > 0 )
+ {
+ m_pClassTipsListPanel->SetFirstColumnWidth( 0 );
+ m_pClassTipsListPanel->ScrollToItem( nScrollToItem );
+ }
+ }
+
+ InvalidateLayout( true, true );
+ }
+
+private:
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/ClassTipsList.res" );
+ }
+
+ CTFClassTipsListPanel *m_pClassTipsListPanel;
+
+ CFmtStr m_fmtResFilename;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFClassMenu::CTFClassMenu( IViewPort *pViewPort )
+: CClassMenu( pViewPort )
+{
+ MakePopup();
+
+ m_mouseoverButtons.RemoveAll();
+
+ m_iClassMenuKey = BUTTON_CODE_INVALID;
+ m_iCurrentClassIndex = TF_CLASS_HEAVYWEAPONS;
+
+#ifdef _X360
+ m_pFooter = new CTFFooter( this, "Footer" );
+#endif
+
+ m_pTFPlayerModelPanel = NULL;
+ m_pSelectAClassLabel = NULL;
+
+ m_pClassTipsPanel = new CTFClassTipsPanel( this, "ClassTipsPanel" );
+
+ Q_memset( m_pClassButtons, 0, sizeof( m_pClassButtons ) );
+
+#ifndef _X360
+ char tempName[MAX_PATH];
+ for ( int i = 0 ; i < CLASS_COUNT_IMAGES ; ++i )
+ {
+ Q_snprintf( tempName, sizeof( tempName ), "countImage%d", i );
+ m_ClassCountImages[i] = new CTFImagePanel( this, tempName );
+ }
+
+ m_pCountLabel = NULL;
+
+ m_pLocalPlayerImage = new CTFImagePanel( this, "localPlayerImage" );
+ m_pLocalPlayerBG = new CTFImagePanel( this, "localPlayerBG" );
+ m_iLocalPlayerClass = TEAM_UNASSIGNED;
+
+ m_pClassButtons[TF_CLASS_SCOUT] = new CExImageButton( this, "scout", "", this );
+ m_pClassButtons[TF_CLASS_SOLDIER] = new CExImageButton( this, "soldier", "", this );
+ m_pClassButtons[TF_CLASS_PYRO] = new CExImageButton( this, "pyro", "", this );
+ m_pClassButtons[TF_CLASS_DEMOMAN] = new CExImageButton( this, "demoman", "", this );
+ m_pClassButtons[TF_CLASS_MEDIC] = new CExImageButton( this, "medic", "", this );
+ m_pClassButtons[TF_CLASS_HEAVYWEAPONS] = new CExImageButton( this, "heavyweapons", "", this );
+ m_pClassButtons[TF_CLASS_SNIPER] = new CExImageButton( this, "sniper", "", this );
+ m_pClassButtons[TF_CLASS_ENGINEER] = new CExImageButton( this, "engineer", "", this );
+ m_pClassButtons[TF_CLASS_SPY] = new CExImageButton( this, "spy", "", this );
+ m_pClassButtons[TF_CLASS_RANDOM] = new CExImageButton( this, "random", "", this );
+#endif
+
+ m_pEditLoadoutButton = NULL;
+ m_nBaseMusicGuid = -1;
+
+ ListenForGameEvent( "localplayer_changeteam" );
+ ListenForGameEvent( "show_match_summary" );
+
+ Q_memset( m_pMvmUpgradeImages, 0, sizeof( m_pMvmUpgradeImages ) );
+ m_pMvmUpgradeImages[TF_CLASS_SCOUT] = new vgui::ImagePanel( this, "MvMUpgradeImageScout" );
+ m_pMvmUpgradeImages[TF_CLASS_SOLDIER] = new vgui::ImagePanel( this, "MvMUpgradeImageSolider" );
+ m_pMvmUpgradeImages[TF_CLASS_PYRO] = new vgui::ImagePanel( this, "MvMUpgradeImagePyro" );
+ m_pMvmUpgradeImages[TF_CLASS_DEMOMAN] = new vgui::ImagePanel( this, "MvMUpgradeImageDemoman" );
+ m_pMvmUpgradeImages[TF_CLASS_MEDIC] = new vgui::ImagePanel( this, "MvMUpgradeImageMedic" );
+ m_pMvmUpgradeImages[TF_CLASS_HEAVYWEAPONS] = new vgui::ImagePanel( this, "MvMUpgradeImageHeavy" );
+ m_pMvmUpgradeImages[TF_CLASS_SNIPER] = new vgui::ImagePanel( this, "MvMUpgradeImageSniper" );
+ m_pMvmUpgradeImages[TF_CLASS_ENGINEER] = new vgui::ImagePanel( this, "MvMUpgradeImageEngineer" );
+ m_pMvmUpgradeImages[TF_CLASS_SPY] = new vgui::ImagePanel( this, "MvMUpgradeImageSpy" );
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
+ {
+ m_pClassHintIcons[i] = nullptr;
+ }
+
+ // Load the .res file
+ if ( ::input->IsSteamControllerActive() )
+ {
+ LoadControlSettings( "Resource/UI/ClassSelection_SC.res" );
+ m_pCancelHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "CancelHintIcon" ) );
+ m_pEditLoadoutHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "EditLoadoutHintIcon" ) );
+
+ m_pClassHintIcons[TF_CLASS_SCOUT] = dynamic_cast< CSCHintIcon* >( FindChildByName( "ScoutHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_SOLDIER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SoldierHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_PYRO] = dynamic_cast< CSCHintIcon* >( FindChildByName( "PyroHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_DEMOMAN] = dynamic_cast< CSCHintIcon* >( FindChildByName( "DemomanHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_HEAVYWEAPONS] = dynamic_cast< CSCHintIcon* >( FindChildByName( "HeavyHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_MEDIC] = dynamic_cast< CSCHintIcon* >( FindChildByName( "MedicHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_SPY] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SpyHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_ENGINEER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "EngineerHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_SNIPER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SniperHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_RANDOM] = dynamic_cast< CSCHintIcon* >( FindChildByName( "RandomHintIcon" ) );
+
+ for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
+ {
+ if ( m_pClassHintIcons[i] )
+ {
+ m_pClassHintIcons[i]->SetVisible( false );
+ }
+ }
+
+ SetMouseInputEnabled( false );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/ClassSelection.res" );
+ m_pCancelHintIcon = m_pEditLoadoutHintIcon = nullptr;
+ SetMouseInputEnabled( true );
+ }
+
+ m_pTFPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("TFPlayerModel") );
+ m_pSelectAClassLabel = dynamic_cast<CExLabel*>( FindChildByName( "ClassMenuSelect" ) );
+ m_pEditLoadoutButton = dynamic_cast<CExButton*>( FindChildByName( "EditLoadoutButton" ) );
+
+ const char *pTeamExtension = GetTeamNumber() == TF_TEAM_BLUE ? "blu" : "red";
+ for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i )
+ {
+ if ( !m_pClassButtons[i] )
+ continue;
+
+ if ( !IsValidTFPlayerClass( i ) && i != TF_CLASS_RANDOM )
+ continue;
+
+ m_pClassButtons[i]->SetImageSelected( CFmtStr( "class_sel_sm_%s_%s", g_aRawPlayerClassNamesShort[i], pTeamExtension ).Access() );
+ if( i != TF_CLASS_RANDOM )
+ {
+ m_pClassButtons[i]->SetArmedSound("misc/null.wav");
+ }
+ }
+}
+
+void CTFClassMenu::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+#ifndef _X360
+ m_pCountLabel = dynamic_cast< CExLabel * >( FindChildByName( "CountLabel" ) );
+
+ if ( m_pCountLabel )
+ {
+ m_pCountLabel->SizeToContents();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFClassMenu::GetCurrentPlayerClass()
+{
+ int iClass = TF_CLASS_HEAVYWEAPONS;
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() != TF_CLASS_UNDEFINED )
+ {
+ iClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex();
+ }
+
+ return iClass;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExImageButton *CTFClassMenu::GetCurrentClassButton()
+{
+ const int iClass = GetCurrentPlayerClass();
+ m_iCurrentClassIndex = iRemapIndexToClass[ iClass ];
+ return m_pClassButtons[iClass];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::ShowPanel( bool bShow )
+{
+ if ( bShow )
+ {
+ // Hide the other class menu
+ if ( gViewPortInterface )
+ {
+ gViewPortInterface->ShowPanel( GetTeamNumber() == TF_TEAM_BLUE ? PANEL_CLASS_RED : PANEL_CLASS_BLUE, false );
+ }
+
+ // can't change class if you're on the losing team during the "bonus time" after a team has won the round
+ if ( ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN &&
+ C_TFPlayer::GetLocalTFPlayer() &&
+ C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TFGameRules()->GetWinningTeam()
+ && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_SPECTATOR
+ && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_UNASSIGNED
+ && GetSpectatorMode() == OBS_MODE_NONE ) ||
+ TFGameRules()->State_Get() == GR_STATE_GAME_OVER ||
+ ( TFGameRules()->IsInTraining() && C_TFPlayer::GetLocalTFPlayer() &&
+ ( C_TFPlayer::GetLocalTFPlayer()->GetPlayerClass() == NULL || C_TFPlayer::GetLocalTFPlayer()->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) ) )
+ {
+ SetVisible( false );
+
+ CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel );
+ if ( pNotifyPanel )
+ {
+ if ( C_TFPlayer::GetLocalTFPlayer() )
+ {
+ pNotifyPanel->SetupNotifyCustom( "#TF_CantChangeClassNow", "ico_notify_flag_moving", C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() );
+ }
+ }
+
+ return;
+ }
+
+ engine->CheckPoint( "ClassMenu" );
+
+ // Force us to reload our scheme, in case Steam Controller stuff has changed.
+ InvalidateLayout( true, true );
+
+ Activate();
+
+ m_iClassMenuKey = gameuifuncs->GetButtonCodeForBind( "changeclass" );
+ m_iScoreBoardKey = gameuifuncs->GetButtonCodeForBind( "showscores" );
+
+ SelectClass( GetCurrentPlayerClass() );
+ }
+ else
+ {
+ SetVisible( false );
+ if ( m_pTFPlayerModelPanel )
+ {
+ m_pTFPlayerModelPanel->ClearCarriedItems();
+ }
+ }
+}
+
+const char *g_pszLegacyClassSelectVCDWeapons[TF_LAST_NORMAL_CLASS] =
+{
+ "", // TF_CLASS_UNDEFINED = 0,
+ "", // TF_CLASS_SCOUT, // weapons handled individually
+ "", // TF_CLASS_SNIPER, // weapons handled individually
+ "", // TF_CLASS_SOLDIER, // weapons handled individually
+ "tf_weapon_grenadelauncher", // TF_CLASS_DEMOMAN,
+ "tf_weapon_medigun", // TF_CLASS_MEDIC,
+ "tf_weapon_minigun", // TF_CLASS_HEAVYWEAPONS,
+ "tf_weapon_flamethrower", // TF_CLASS_PYRO,
+ "", // TF_CLASS_SPY, // weapons handled individually
+ "tf_weapon_wrench", // TF_CLASS_ENGINEER,
+};
+
+int g_iLegacyClassSelectWeaponSlots[TF_LAST_NORMAL_CLASS] =
+{
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_UNDEFINED = 0,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_SCOUT, // TF_FIRST_NORMAL_CLASS
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_SNIPER,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_SOLDIER,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_DEMOMAN,
+ LOADOUT_POSITION_SECONDARY, // TF_CLASS_MEDIC,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_HEAVYWEAPONS,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_PYRO,
+ LOADOUT_POSITION_MELEE, // TF_CLASS_SPY,
+ LOADOUT_POSITION_MELEE, // TF_CLASS_ENGINEER,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::UpdateButtonSelectionStates( int iClass )
+{
+ // Set the correct button as selected, all other buttons as not selected
+ for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i )
+ {
+ if ( !m_pClassButtons[ i ] )
+ continue;
+
+ m_pClassButtons[ i ]->SetSelected( i == iClass );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::SelectClass( int iClass )
+{
+ if ( !engine->IsInGame() )
+ return;
+
+ if ( !m_pTFPlayerModelPanel )
+ return;
+
+ if ( !m_pClassTipsPanel )
+ return;
+
+ if ( !m_pEditLoadoutButton )
+ return;
+
+ // Update select hint icon for Steam Controller
+ for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
+ {
+ if ( m_pClassHintIcons[i] )
+ {
+ m_pClassHintIcons[i]->SetVisible( i == iClass );
+ }
+ }
+
+ // Were we random? If so, we'll force our class to refresh later to prevent the
+ // model panel thinking the class hasn't changed.
+ const bool bClassWasRandom = m_iCurrentClassIndex == TF_CLASS_RANDOM;
+
+ // Cache current player class
+ m_iCurrentClassIndex = iClass;
+
+ UpdateButtonSelectionStates( iClass );
+
+ bool bRandomClass = iClass == TF_CLASS_RANDOM;
+ if ( !IsValidTFPlayerClass( iClass ) && !bRandomClass )
+ {
+ m_pTFPlayerModelPanel->SetVisible( false );
+ m_pTFPlayerModelPanel->ClearCarriedItems();
+ return;
+ }
+
+ m_pTFPlayerModelPanel->SetVisible( true );
+ m_pTFPlayerModelPanel->ClearCarriedItems();
+
+ if ( bRandomClass )
+ {
+ m_pEditLoadoutButton->SetVisible( false );
+ if ( m_pEditLoadoutHintIcon )
+ {
+ m_pEditLoadoutHintIcon->SetVisible( false );
+ }
+
+ MDLHandle_t hModel = mdlcache->FindMDL( "models/class_menu/random_class_icon.mdl" );
+ m_pTFPlayerModelPanel->SetMDL( hModel );
+ m_pTFPlayerModelPanel->SetSequence( ACT_IDLE );
+ m_pTFPlayerModelPanel->InvalidateLayout( true, true ); // Updates position
+ m_pTFPlayerModelPanel->SetSkin( GetTeamNumber() == TF_TEAM_RED ? 0 : 1 );
+ mdlcache->Release( hModel ); // counterbalance addref from within FindMDL
+ }
+ else
+ {
+ bool bIsRobot = false;
+ // Check for Robot
+ static CSchemaAttributeDefHandle pAttrDef_PlayerRobot( "appear as mvm robot" );
+ for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i );
+ if ( !pItemData )
+ continue;
+ if ( FindAttribute( pItemData, pAttrDef_PlayerRobot ) )
+ {
+ bIsRobot = true;
+ break;
+ }
+ }
+
+ /*if ( pLocalPlayer )
+ {
+ int iRobot = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iRobot, appear_as_mvm_robot );
+ bIsRobot = iRobot ? true : false;
+ }*/
+ m_pTFPlayerModelPanel->SetToPlayerClass( iClass, bIsRobot, bClassWasRandom );
+
+ m_pEditLoadoutButton->SetVisible( true );
+ if ( m_pEditLoadoutHintIcon )
+ {
+ m_pEditLoadoutHintIcon->SetVisible( true );
+ }
+
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ int iTeam = GetTeamNumber();
+ m_pTFPlayerModelPanel->SetTeam( iTeam );
+ }
+
+ LoadItems();
+ }
+
+ m_pClassTipsPanel->SetClass( iClass );
+
+ enginesound->StopSoundByGuid( m_nBaseMusicGuid );
+ CBroadcastRecipientFilter filter;
+ char nClassMusicStr[64];
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ sprintf( nClassMusicStr, "music.mvm_class_menu_0%i", iClass );
+ }
+ else
+ {
+ sprintf( nClassMusicStr, "music.class_menu_0%i", iClass );
+ }
+ CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, nClassMusicStr );
+ m_nBaseMusicGuid = enginesound->GetGuidForLastSoundEmitted();
+
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::LoadItems()
+{
+ const int iClass = m_pTFPlayerModelPanel->GetPlayerClass();
+
+ m_pTFPlayerModelPanel->ClearCarriedItems();
+
+ static CSchemaAttributeDefHandle pAttrDef_DisableFancyLoadoutAnim( "disable fancy class select anim" );
+ bool bCanUseFancyClassSelectAnimation = true;
+
+ static CSchemaAttributeDefHandle pAttrDef_ClassSelectOverrideVCD( "class select override vcd" );
+ CAttribute_String attrClassSelectOverrideVCD;
+
+ const char *pszVCD = "class_select";
+
+ for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i );
+ if ( pItemData && pItemData->IsValid() )
+ {
+ m_pTFPlayerModelPanel->AddCarriedItem( pItemData );
+
+ // Certain items have different shapes and would interfere with our class select animations.
+ bCanUseFancyClassSelectAnimation = bCanUseFancyClassSelectAnimation
+ && !pItemData->FindAttribute( pAttrDef_DisableFancyLoadoutAnim );
+
+ // Some items want to override the class select VCD
+ if ( pItemData->FindAttribute( pAttrDef_ClassSelectOverrideVCD, &attrClassSelectOverrideVCD ) )
+ {
+ const char *pszClassSelectOverrideVCD = attrClassSelectOverrideVCD.value().c_str();
+ if ( pszClassSelectOverrideVCD && *pszClassSelectOverrideVCD )
+ {
+ pszVCD = pszClassSelectOverrideVCD;
+ }
+ }
+ }
+ }
+
+ m_pTFPlayerModelPanel->PlayVCD( bCanUseFancyClassSelectAnimation ? pszVCD : NULL, g_pszLegacyClassSelectVCDWeapons[iClass] );
+ m_pTFPlayerModelPanel->HoldItemInSlot( g_iLegacyClassSelectWeaponSlots[iClass] );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnKeyCodePressed( KeyCode code )
+{
+ m_KeyRepeat.KeyDown( code );
+
+ if ( code > KEY_0 && code <= KEY_9 )
+ {
+ const int iButton = code - KEY_0;
+ const int iClass = iRemapIndexToClass[ iButton ];
+ SelectClass( iClass );
+ Go();
+ }
+ else if ( code > KEY_PAD_0 && code <= KEY_PAD_9 )
+ {
+ const int iButton = code - KEY_PAD_0;
+ const int iClass = iRemapIndexToClass[ iButton ];
+ SelectClass( iClass );
+ Go();
+ }
+ else if( code == KEY_ENTER || code == KEY_SPACE || code == KEY_XBUTTON_A || code == KEY_XBUTTON_RTRIGGER || code == STEAMCONTROLLER_A )
+ {
+ Go();
+ }
+ else if ( ( m_iClassMenuKey != BUTTON_CODE_INVALID && m_iClassMenuKey == code ) ||
+ code == KEY_XBUTTON_BACK ||
+ code == KEY_XBUTTON_B ||
+ code == STEAMCONTROLLER_B ||
+ code == KEY_0 ||
+ code == KEY_PAD_0 )
+ {
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pLocalPlayer && ( pLocalPlayer->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) )
+ {
+ ShowPanel( false );
+ }
+ }
+ else if( code == KEY_XBUTTON_RIGHT || code == KEY_XSTICK1_RIGHT || code == STEAMCONTROLLER_DPAD_RIGHT )
+ {
+ int loopCheck = 0;
+ int nCurrentClass = GetRemappedMenuIndexForClass( m_iCurrentClassIndex );
+
+ do
+ {
+ loopCheck++;
+ nCurrentClass++;
+ nCurrentClass = ( nCurrentClass % TF_CLASS_MENU_BUTTONS );
+ } while( ( m_pClassButtons[ iRemapIndexToClass[nCurrentClass] ] == NULL ) && ( loopCheck < TF_CLASS_MENU_BUTTONS ) );
+
+ SelectClass( iRemapIndexToClass[ nCurrentClass ] );
+ }
+ else if ( code == STEAMCONTROLLER_Y )
+ {
+ OnCommand( "openloadout" );
+ }
+ else if( code == KEY_XBUTTON_LEFT || code == KEY_XSTICK1_LEFT || code == STEAMCONTROLLER_DPAD_LEFT )
+ {
+ int loopCheck = 0;
+ int nCurrentClass = GetRemappedMenuIndexForClass( m_iCurrentClassIndex );
+
+ do
+ {
+ loopCheck++;
+ nCurrentClass--;
+ if( nCurrentClass <= 0 )
+ {
+ nCurrentClass = GetRemappedMenuIndexForClass( TF_CLASS_RANDOM );
+ }
+ } while( ( m_pClassButtons[ iRemapIndexToClass[nCurrentClass] ] == NULL ) && ( loopCheck < TF_CLASS_MENU_BUTTONS ) );
+
+ SelectClass( iRemapIndexToClass[ nCurrentClass ] );
+ }
+ else if( code == KEY_XBUTTON_UP || code == KEY_XSTICK1_UP || code == STEAMCONTROLLER_DPAD_UP )
+ {
+ // Scroll class info text up
+ if ( g_lastPanel )
+ {
+ CExRichText *pRichText = dynamic_cast< CExRichText * >( g_lastPanel->FindChildByName( "classInfo" ) );
+
+ if ( pRichText )
+ {
+ PostMessage( pRichText, new KeyValues("MoveScrollBarDirect", "delta", 1) );
+ }
+ }
+ }
+ else if( code == KEY_XBUTTON_DOWN || code == KEY_XSTICK1_DOWN || code == STEAMCONTROLLER_DPAD_DOWN )
+ {
+ // Scroll class info text up
+ if ( g_lastPanel )
+ {
+ CExRichText *pRichText = dynamic_cast< CExRichText * >( g_lastPanel->FindChildByName( "classInfo" ) );
+
+ if ( pRichText )
+ {
+ PostMessage( pRichText, new KeyValues("MoveScrollBarDirect", "delta", -1) );
+ }
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnKeyCodeReleased( vgui::KeyCode code )
+{
+ m_KeyRepeat.KeyUp( code );
+
+ BaseClass::OnKeyCodeReleased( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnThink()
+{
+ vgui::KeyCode code = m_KeyRepeat.KeyRepeated();
+ if ( code )
+ {
+ OnKeyCodePressed( code );
+ }
+
+ // Get mouse cursor position
+ int aCursorPos[2];
+ vgui::input()->GetCursorPos( aCursorPos[0], aCursorPos[1] );
+
+ // Go through all buttons - if the mouse is within one, select that class
+ for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i )
+ {
+ if ( !m_pClassButtons[ i ] )
+ continue;
+
+ if ( m_iCurrentClassIndex != i && m_pClassButtons[ i ]->IsWithin( aCursorPos[0], aCursorPos[1] ) )
+ {
+ SelectClass( i );
+ }
+ }
+
+ //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this.
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
+ }
+
+ BaseClass::OnThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::SetCancelButtonVisible( bool bVisible )
+{
+ SetVisibleButton( "CancelButton", bVisible );
+ if ( m_pCancelHintIcon )
+ {
+ m_pCancelHintIcon->SetVisible( bVisible );
+ }
+
+ if ( m_pSelectAClassLabel )
+ {
+ m_pSelectAClassLabel->SetVisible( !bVisible );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::Update()
+{
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ // Force them to pick a class if they haven't picked one yet.
+ if ( ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() != TF_CLASS_UNDEFINED ) )
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "cancel", true );
+ }
+#else
+ SetCancelButtonVisible( true );
+
+ if ( TFGameRules() && TFGameRules()->IsInHighlanderMode() )
+ {
+ SetVisibleButton( "ResetButton", true );
+ }
+ else
+ {
+ SetVisibleButton( "ResetButton", false );
+ }
+#endif
+ }
+ else
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "cancel", false );
+ }
+#else
+ SetCancelButtonVisible( false );
+ SetVisibleButton( "ResetButton", false );
+#endif
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Panel *CTFClassMenu::CreateControlByName( const char *controlName )
+{
+ if ( !Q_stricmp( "CIconPanel", controlName ) )
+ {
+ return new CIconPanel( this, "icon_panel" );
+ }
+ else
+ {
+ return BaseClass::CreateControlByName( controlName );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Catch the mouseover event and set the active class
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnShowPage( vgui::Panel *panel, const char *pagename )
+{
+ for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
+ {
+ if (m_pClassButtons[i] == panel )
+ {
+// SelectClass( i );
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Draw nothing
+//-----------------------------------------------------------------------------
+void CTFClassMenu::PaintBackground( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Do things that should be done often, eg number of players in the
+// selected class
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnTick( void )
+{
+ //When a player changes teams, their class and team values don't get here
+ //necessarily before the command to update the class menu. This leads to the cancel button
+ //being visible and people cancelling before they have a class. check for class == TF_CLASS_UNDEFINED and if so
+ //hide the cancel button
+
+ if ( !IsVisible() )
+ return;
+
+#ifndef _X360
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ // Force them to pick a class if they haven't picked one yet.
+ if ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() == TF_CLASS_UNDEFINED )
+ {
+ SetCancelButtonVisible( false );
+ SetVisibleButton( "ResetButton", false );
+ }
+
+ UpdateClassCounts();
+
+#endif
+
+ BaseClass::OnTick();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnClose()
+{
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ // Clear the HIDEHUD_HEALTH bit we hackily added. Turns out prediction
+ // was restoring these bits every frame. Unfortunately, prediction
+ // is off for karts which means the spell hud item would disappear if you
+ // brought up this menu and returned.
+ pLocalPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_HEALTH;
+ }
+
+ ShowPanel( false );
+
+ BaseClass::OnClose();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::SetVisible( bool state )
+{
+ BaseClass::SetVisible( state );
+
+ m_KeyRepeat.Reset();
+
+ if ( state )
+ {
+ engine->ServerCmd( "menuopen" ); // to the server
+ engine->ClientCmd( "_cl_classmenuopen 1" ); // for other panels
+ CBroadcastRecipientFilter filter;
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.mvm_class_menu" );
+ }
+ else
+ {
+ CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.class_menu" );
+ }
+
+ CheckMvMUpgrades();
+ }
+ else
+ {
+ engine->ServerCmd( "menuclosed" );
+ engine->ClientCmd( "_cl_classmenuopen 0" );
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ CBaseEntity::StopSound( SOUND_FROM_UI_PANEL, "music.mvm_class_menu" );
+ }
+ else
+ {
+ CBaseEntity::StopSound( SOUND_FROM_UI_PANEL, "music.class_menu" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::Go()
+{
+ const int iClass = m_iCurrentClassIndex;
+ if ( iClass == TF_CLASS_UNDEFINED )
+ return;
+
+ // Check class limits
+ if ( TFGameRules() && !TFGameRules()->CanPlayerChooseClass( C_TFPlayer::GetLocalTFPlayer(), iClass ) )
+ return;
+
+#if defined( REPLAY_ENABLED )
+ // Display replay recording message if appropriate
+ int &nDisplayedConnectedRecording = CPlayerSpawnCache::Instance().m_Data.m_nDisplayedConnectedRecording;
+ if ( g_pReplay->IsReplayEnabled() &&
+ !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
+ !nDisplayedConnectedRecording &&
+ replay_replaywelcomedlgcount.GetInt() <= MAX_TIMES_TO_SHOW_REPLAY_WELCOME_DLG )
+ {
+ wchar_t wText[256];
+ wchar wKeyBind[80];
+ char szText[256];
+
+ const char *pSaveReplayKey = engine->Key_LookupBinding( "save_replay" );
+ if ( !pSaveReplayKey )
+ {
+ pSaveReplayKey = "< not bound >";
+ }
+ g_pVGuiLocalize->ConvertANSIToUnicode( pSaveReplayKey, wKeyBind, sizeof( wKeyBind ) );
+ g_pVGuiLocalize->ConstructString_safe( wText, g_pVGuiLocalize->Find( "#Replay_ConnectRecording" ), 1, wKeyBind );
+ g_pVGuiLocalize->ConvertUnicodeToANSI( wText, szText, sizeof( szText ) );
+
+ extern ConVar replay_msgduration_connectrecording;
+ g_pClientMode->DisplayReplayMessage( szText, replay_msgduration_connectrecording.GetFloat(), false, NULL, true );
+
+ // Don't execute this clause next time the player spawns, unless the cache has been cleared
+ ++nDisplayedConnectedRecording;
+
+ // Increment (archives)
+ replay_replaywelcomedlgcount.SetValue( replay_replaywelcomedlgcount.GetInt() + 1 );
+ }
+#endif
+
+ // This will complete any pending replay, commit if necessary, and clear - this way when the player respawns
+ // we will start with a fresh replay for the new life.
+ g_pClientReplayContext->OnPlayerClassChanged();
+
+ // Change class
+ BaseClass::OnCommand( CFmtStr( "joinclass %s", g_aRawPlayerClassNames[ iClass ] ).Access() );
+
+ CBroadcastRecipientFilter filter;
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.mvm_class_select" );
+ }
+ else
+ {
+
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnCommand( const char *command )
+{
+ if ( !V_strnicmp( command, "select", 6 ) )
+ {
+ const char *pClass = command + 6;
+ const int iClass = atoi( pClass );
+
+ // Avoid restarting the animation if the user selected on the same class
+ if ( iClass != m_iCurrentClassIndex )
+ {
+ SelectClass( iClass );
+ }
+ else
+ {
+ // Ensure selection states, in case the user clicks a button and drags away
+ UpdateButtonSelectionStates( iClass );
+ }
+
+ Go();
+ }
+ else if ( !V_strnicmp( command, "resetclass", 10 ) )
+ {
+ if ( TFGameRules() && !TFGameRules()->IsInHighlanderMode() )
+ return;
+
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+ else if ( !V_strnicmp( command, "openloadout", 11 ) )
+ {
+ // Let this panel know when you've closed, so we can reload items
+ EconUI()->AddPanelCloseListener( this );
+
+ // Make the back button close, rather than go back to the econ root panel
+ EconUI()->SetClosePanel( -m_iCurrentClassIndex );
+
+ // Set team number, so the model's color will match
+ EconUI()->SetDefaultTeam( GetTeamNumber() );
+
+ // Go directly to the loadout for the selected class
+ EconUI()->OpenEconUI( -m_iCurrentClassIndex );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Console command to select a class
+//-----------------------------------------------------------------------------
+void CTFClassMenu::Join_Class( const CCommand &args )
+{
+ if ( args.ArgC() > 1 )
+ {
+ char cmd[256];
+ Q_snprintf( cmd, sizeof( cmd ), "joinclass %s", args.Arg( 1 ) );
+ OnCommand( cmd );
+ ShowPanel( false );
+ }
+}
+
+static const char *g_sDialogVariables[] = {
+ "",
+ "numScout",
+ "numSoldier",
+ "numPyro",
+
+ "numDemoman",
+ "numHeavy",
+ "numEngineer",
+
+ "numMedic",
+ "numSniper",
+ "numSpy",
+ "",
+};
+
+static const char *g_sClassImagesBlue[] = {
+ "",
+ "class_sel_sm_scout_blu",
+ "class_sel_sm_soldier_blu",
+ "class_sel_sm_pyro_blu",
+
+ "class_sel_sm_demo_blu",
+ "class_sel_sm_heavy_blu",
+ "class_sel_sm_engineer_blu",
+
+ "class_sel_sm_medic_blu",
+ "class_sel_sm_sniper_blu",
+ "class_sel_sm_spy_blu",
+
+ "class_sel_sm_scout_blu",
+};
+
+static const char *g_sClassImagesRed[] = {
+ "",
+ "class_sel_sm_scout_red",
+ "class_sel_sm_soldier_red",
+ "class_sel_sm_pyro_red",
+
+ "class_sel_sm_demo_red",
+ "class_sel_sm_heavy_red",
+ "class_sel_sm_engineer_red",
+
+ "class_sel_sm_medic_red",
+ "class_sel_sm_sniper_red",
+ "class_sel_sm_spy_red",
+
+ "class_sel_sm_scout_red",
+};
+
+int g_ClassDefinesRemap[] = {
+ 0,
+ TF_CLASS_SCOUT,
+ TF_CLASS_SOLDIER,
+ TF_CLASS_PYRO,
+
+ TF_CLASS_DEMOMAN,
+ TF_CLASS_HEAVYWEAPONS,
+ TF_CLASS_ENGINEER,
+
+ TF_CLASS_MEDIC,
+ TF_CLASS_SNIPER,
+ TF_CLASS_SPY,
+ TF_CLASS_CIVILIAN,
+};
+
+void CTFClassMenu::UpdateNumClassLabels( int iTeam )
+{
+#ifndef _X360
+ int nTotalCount = 0;
+
+ // count how many of each class there are
+ if ( !g_TF_PR )
+ return;
+
+ if ( iTeam < FIRST_GAME_TEAM || iTeam >= TF_TEAM_COUNT ) // invalid team number
+ return;
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ bool bSpectator = pLocalPlayer && pLocalPlayer->GetTeamNumber() == TEAM_SPECTATOR;
+
+ int iLocalPlayerClass = TF_CLASS_UNDEFINED;
+ if ( pLocalPlayer )
+ {
+ iLocalPlayerClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex();
+ }
+
+ if ( iLocalPlayerClass == TF_CLASS_UNDEFINED )
+ {
+ m_iLocalPlayerClass = iLocalPlayerClass;
+
+ if ( m_pLocalPlayerImage && m_pLocalPlayerImage->IsVisible() )
+ {
+ m_pLocalPlayerImage->SetVisible( false );
+ }
+
+ if ( m_pLocalPlayerBG && m_pLocalPlayerBG->IsVisible() )
+ {
+ m_pLocalPlayerBG->SetVisible( false );
+ }
+ }
+
+ for( int i = TF_FIRST_NORMAL_CLASS ; i <= TF_LAST_NORMAL_CLASS ; i++ )
+ {
+ if ( bSpectator == true )
+ {
+ SetDialogVariable( g_sDialogVariables[i], "" );
+ continue;
+ }
+
+ int classCount = g_TF_PR->GetCountForPlayerClass( iTeam, g_ClassDefinesRemap[i], false );
+ int iClassLimit = TFGameRules()->GetClassLimit( g_ClassDefinesRemap[i] );
+
+ if ( iClassLimit != NO_CLASS_LIMIT )
+ {
+ if ( classCount >= iClassLimit )
+ {
+ if ( classCount > 0 )
+ {
+ wchar_t wTemp[32];
+ wchar_t wzCount[10];
+ _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", classCount );
+ g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("TF_ClassLimitHit"), 1, wzCount );
+ SetDialogVariable( g_sDialogVariables[i], wTemp );
+ }
+ else
+ {
+ SetDialogVariable( g_sDialogVariables[i], g_pVGuiLocalize->Find("TF_ClassLimitHit_None") );
+ }
+ }
+ else
+ {
+ wchar_t wTemp[32];
+ wchar_t wzCount[10];
+ _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", classCount );
+ wchar_t wzMax[10];
+ _snwprintf( wzMax, ARRAYSIZE( wzMax ), L"%d", iClassLimit );
+ g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("TF_ClassLimitUnder"), 2, wzCount, wzMax );
+ SetDialogVariable( g_sDialogVariables[i], wTemp );
+ }
+ }
+ else if ( classCount > 0 )
+ {
+ SetDialogVariable( g_sDialogVariables[i], classCount );
+ }
+ else
+ {
+ SetDialogVariable( g_sDialogVariables[i], "" );
+ }
+
+ if ( g_ClassDefinesRemap[i] == iLocalPlayerClass )
+ {
+ // take 1 off the count for the images since the local player has their own image already
+ if ( classCount > 0 )
+ {
+ classCount--;
+ }
+
+ if ( m_pLocalPlayerImage )
+ {
+ if ( !m_pLocalPlayerImage->IsVisible() )
+ {
+ m_pLocalPlayerImage->SetVisible( true );
+ }
+
+ if ( m_iLocalPlayerClass != iLocalPlayerClass )
+ {
+ m_iLocalPlayerClass = iLocalPlayerClass;
+ m_pLocalPlayerImage->SetImage( iTeam == TF_TEAM_BLUE ? g_sClassImagesBlue[i] : g_sClassImagesRed[i] );
+ }
+ }
+
+ if ( m_pLocalPlayerBG && !m_pLocalPlayerBG->IsVisible() )
+ {
+ m_pLocalPlayerBG->SetVisible( true );
+ }
+ }
+
+ if ( nTotalCount < CLASS_COUNT_IMAGES )
+ {
+ for ( int j = 0 ; j < classCount ; ++j )
+ {
+ CTFImagePanel *pImage = m_ClassCountImages[nTotalCount];
+ if ( pImage )
+ {
+ pImage->SetVisible( true );
+ pImage->SetImage( iTeam == TF_TEAM_BLUE ? g_sClassImagesBlue[i] : g_sClassImagesRed[i] );
+ }
+
+ nTotalCount++;
+ if ( nTotalCount >= CLASS_COUNT_IMAGES )
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if ( nTotalCount == 0 )
+ {
+ // no classes for our team yet
+ if ( m_pCountLabel && m_pCountLabel->IsVisible() )
+ {
+ m_pCountLabel->SetVisible( false );
+ }
+ }
+ else
+ {
+ if ( m_pCountLabel && !m_pCountLabel->IsVisible() )
+ {
+ m_pCountLabel->SetVisible( true );
+ }
+ }
+
+ // turn off any unused images
+ while ( nTotalCount < CLASS_COUNT_IMAGES )
+ {
+ CTFImagePanel *pImage = m_ClassCountImages[nTotalCount];
+ if ( pImage )
+ {
+ pImage->SetVisible( false );
+ }
+
+ nTotalCount++;
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::FireGameEvent( IGameEvent *event )
+{
+ const char *pszEventName = event->GetName();
+
+ // when we are changing levels
+ if ( FStrEq( pszEventName, "localplayer_changeteam" ) )
+ {
+ if ( IsVisible() )
+ {
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ int iTeam = pLocalPlayer->GetTeamNumber();
+ if ( iTeam != GetTeamNumber() )
+ {
+ ShowPanel( false );
+
+ if ( iTeam == TF_TEAM_BLUE )
+ {
+ gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true );
+ }
+ else
+ {
+ gViewPortInterface->ShowPanel( PANEL_CLASS_RED, true );
+ }
+ }
+ }
+ }
+ }
+ else if ( FStrEq( pszEventName, "show_match_summary" ) )
+ {
+ if ( IsVisible() )
+ {
+ ShowPanel( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnEconUIClosed()
+{
+ // Reload items on model panel, in case anything's changed
+ LoadItems();
+}
+
+int g_nNumUpgradeIconsForLastHint = 0;
+
+//-----------------------------------------------------------------------------
+void CTFClassMenu::CheckMvMUpgrades()
+{
+ // Set MvM Icons invisible
+ for ( int icons = 0; icons < ARRAYSIZE( m_pMvmUpgradeImages ); ++icons )
+ {
+ if ( m_pMvmUpgradeImages[icons] == NULL )
+ continue;
+ m_pMvmUpgradeImages[icons]->SetVisible( false );
+ }
+
+ if ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() )
+ return;
+
+ CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance();
+ if ( !pStats )
+ return;
+
+ CUtlVector< CUpgradeInfo > *upgrades = pStats->GetLocalPlayerUpgrades();
+
+ int nShowUpgradingHint = -1;
+ int nNumUpgradeIconsForHint = 0;
+
+ for ( int i = 0; i < upgrades->Count(); ++i )
+ {
+ vgui::Panel *pUpgradeImage = m_pMvmUpgradeImages[upgrades->Element(i).m_iPlayerClass];
+
+ if ( !pUpgradeImage )
+ continue;
+
+ if ( !pUpgradeImage->IsVisible() )
+ {
+ pUpgradeImage->SetVisible( true );
+ nNumUpgradeIconsForHint++;
+
+ // Only show the hint if we've shown it 3 or less times ever
+ if ( nShowUpgradingHint == -1 && tf_mvm_classupgradehelpcount.GetInt() < 3 )
+ {
+ int nY;
+ pUpgradeImage->GetPos( nShowUpgradingHint, nY );
+ nShowUpgradingHint += pUpgradeImage->GetWide() / 2;
+ }
+ }
+ }
+
+ // Only show the hint if there are more upgrade icon than the last time we openned the menu
+ if ( nShowUpgradingHint != -1 && g_nNumUpgradeIconsForLastHint < nNumUpgradeIconsForHint )
+ {
+ CExplanationPopup *pPopup = dynamic_cast< CExplanationPopup* >( FindChildByName("StartExplanation") );
+ if ( pPopup )
+ {
+ pPopup->SetCalloutInParentsX( nShowUpgradingHint );
+ pPopup->Popup();
+
+ g_nNumUpgradeIconsForLastHint = nNumUpgradeIconsForHint;
+ tf_mvm_classupgradehelpcount.SetValue( tf_mvm_classupgradehelpcount.GetInt() + 1 );
+ }
+ }
+}
+
+