diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/tf/vgui/tf_classmenu.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/client/tf/vgui/tf_classmenu.cpp')
| -rw-r--r-- | game/client/tf/vgui/tf_classmenu.cpp | 1703 |
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 ); + } + } +} + + |