diff options
Diffstat (limited to 'game/client/tf/vgui/tf_training_ui.cpp')
| -rw-r--r-- | game/client/tf/vgui/tf_training_ui.cpp | 2205 |
1 files changed, 2205 insertions, 0 deletions
diff --git a/game/client/tf/vgui/tf_training_ui.cpp b/game/client/tf/vgui/tf_training_ui.cpp new file mode 100644 index 0000000..808c423 --- /dev/null +++ b/game/client/tf/vgui/tf_training_ui.cpp @@ -0,0 +1,2205 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include <filesystem.h> +#include "ienginevgui.h" +#include "tf_gcmessages.h" +#include "tf_mouseforwardingpanel.h" +#include "gc_clientsystem.h" +#include "c_tf_gamestats.h" +#include "tf_hud_mainmenuoverride.h" +#include "tf_gamerules.h" +#include "econ/confirm_dialog.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//------------------------------------------------------------------------------------------------------ + +#define TRAINING_DIALOG_NAME "TrainingDialog" +#define TRAINING_PROGRESS_FILE "trainingprogress.txt" + +//------------------------------------------------------------------------------------------------------ + +#ifdef _DEBUG +#define PRINT_KEY_VALUES( kv_ ) { CUtlBuffer buf(0, 0, CUtlBuffer::TEXT_BUFFER); kv_->RecursiveSaveToFile( buf, 0 ); ConMsg( "---\n%s\n---\n", buf.String() ); } +#else +#define PRINT_KEY_VALUES( kv_ ) { } +#endif + +//------------------------------------------------------------------------------------------------------ + +enum GameMode_t // Supported game modes for offline practice +{ + MODE_INVALID = -1, + + MODE_CP, + MODE_KOTH, + MODE_PL, + + NUM_GAME_MODES +}; + +static const char *gs_pGameModeTokens[ NUM_GAME_MODES ] = { + "#Gametype_CP", + "#Gametype_Koth", + "#Gametype_Escort", +}; + +//------------------------------------------------------------------------------------------------------ + +ConVar cl_training_completed_with_classes( "cl_training_completed_with_classes", "0", FCVAR_ARCHIVE, "Bitfield representing what classes have been used to complete training." ); + +bool Training_TrainingProgressFileExists() +{ + const char *pFilename = TRAINING_PROGRESS_FILE; + return g_pFullFileSystem->FileExists( pFilename, NULL ); +} + +//------------------------------------------------------------------------------------------------------ + +static int Training_GetClassProgress( int iClass ) // Returns a percent, in the range [0,100] +{ + Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS ); + + const int nDefaultResult = iClass == TF_CLASS_SOLDIER ? 0 : -1; + + KeyValuesAD pTrainingProgressData( "TrainingProgress" ); + if ( !pTrainingProgressData ) + { + Warning( "Failed to save training progress!\n" ); + AssertMsg( 0, "Failed to save training progress!\n" ); + return nDefaultResult; + } + + const char *pFilename = TRAINING_PROGRESS_FILE; + if ( !pTrainingProgressData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ) ) + return nDefaultResult; + + const char *pClassName = g_aPlayerClassNames_NonLocalized[ iClass ]; + KeyValues *pClassSubKey = pTrainingProgressData->FindKey( pClassName ); + if ( !pClassSubKey ) + return nDefaultResult; + + return pClassSubKey->GetInt( "progress", nDefaultResult ); +} + +static void Training_GetProgress( int pClass[TF_CLASS_COUNT] ) // Returns a percent, in the range [0,100] +{ + KeyValuesAD pTrainingProgressData( "TrainingProgress" ); + const char *pFilename = TRAINING_PROGRESS_FILE; + + bool bLoadedFileOk = pTrainingProgressData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ); + + for ( int i = 0; i < TF_CLASS_COUNT; ++i ) + { + const int iClass = i; + const int nDefaultResult = ( bLoadedFileOk && iClass == TF_CLASS_SOLDIER ) ? 0 : -1; + + const char *pClassName = g_aPlayerClassNames_NonLocalized[ iClass ]; + KeyValues *pClassSubKey = pTrainingProgressData->FindKey( pClassName ); + if ( !pClassSubKey ) + { + pClass[ i ] = nDefaultResult; + continue; + } + + pClass[ i ] = pClassSubKey->GetInt( "progress", nDefaultResult ); + } +} + +KeyValues *Training_LoadProgressFile() +{ + KeyValues *pTrainingProgressData = new KeyValues( "TrainingProgress" ); + if ( !pTrainingProgressData ) + { + Warning( "Failed to save training progress!\n" ); + AssertMsg( 0, "Failed to save training progress!\n" ); + return NULL; + } + + // Attempt to load any existing progress from disk + const char *pFilename = TRAINING_PROGRESS_FILE; + if ( !pTrainingProgressData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ) ) + { + // File didn't exist - create from defaults + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + KeyValues *pClassSubKey = new KeyValues( g_aPlayerClassNames_NonLocalized[ i ] ); + if ( !pClassSubKey ) + continue; + + pClassSubKey->SetInt( "progress", -1 ); // -1 means they haven't beat anything + pTrainingProgressData->AddSubKey( pClassSubKey ); + } + } + + return pTrainingProgressData; +} + +void Training_SaveProgress( KeyValues *pTrainingProgressData ) +{ + const char *pFilename = TRAINING_PROGRESS_FILE; + if ( !pTrainingProgressData->SaveToFile( g_pFullFileSystem, pFilename, "MOD" ) ) + { + Warning( "Failed to save progress!\n" ); + AssertMsg( 0, "Failed to save progress!" ); + } +} + +KeyValues *Training_FindClassData( KeyValues *pTrainingProgressData, int iClass ) +{ + const char *pClassName = g_aPlayerClassNames_NonLocalized[ iClass ]; + return pTrainingProgressData->FindKey( pClassName ); +} + +void Training_SaveProgress( int pProgress[ TF_CLASS_COUNT ] ) +{ + KeyValues *pTrainingProgressData = Training_LoadProgressFile(); + if ( !pTrainingProgressData ) + return; + + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + KeyValues *pClassSubKey = Training_FindClassData( pTrainingProgressData, i ); + if ( !pClassSubKey ) + { + AssertMsg( 0, "All classes should have been created on load if they didn't exist - this should not happen!" ); + continue; + } + + Assert( pProgress[ i ] >= -1 ); + pClassSubKey->SetInt( "progress", pProgress[ i ] ); + } + + Training_SaveProgress( pTrainingProgressData ); +} + +void Training_MarkClassComplete( int iClass, int iStage ) +{ + Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS ); + Assert( iStage >= 0 ); + + KeyValues *pTrainingProgressData = Training_LoadProgressFile(); + if ( !pTrainingProgressData ) + return; + + // Find the data for the corresponding class + KeyValues *pClassSubKey = Training_FindClassData( pTrainingProgressData, iClass ); + if ( pClassSubKey ) + { + pClassSubKey->SetInt( "progress", iStage ); + } + else + { + Warning( "Failed to load data for class %s!\n", g_aPlayerClassNames_NonLocalized[ iClass ] ); + } + + // Unlock next class if necessary + const int iLastTrainingClass = TF_CLASS_ENGINEER; + if ( iClass != iLastTrainingClass ) + { + const int aNextClasses[ TF_CLASS_COUNT ] = { + -1, // TF_CLASS_UNDEFINED + -1, // TF_CLASS_SCOUT + -1, // TF_CLASS_SNIPER + TF_CLASS_DEMOMAN, // TF_CLASS_SOLDIER + TF_CLASS_SPY, // TF_CLASS_DEMOMAN + -1, // TF_CLASS_MEDIC + -1, // TF_CLASS_HEAVYWEAPONS + -1, // TF_CLASS_PYRO + TF_CLASS_ENGINEER, // TF_CLASS_SPY + -1, // TF_CLASS_ENGINEER + }; + const int aUnlockRequirements[ TF_CLASS_COUNT ] = { + -1, // TF_CLASS_UNDEFINED + -1, // TF_CLASS_SCOUT + -1, // TF_CLASS_SNIPER + 2, // TF_CLASS_SOLDIER - must beat 2 stages to complete soldier training + 1, // TF_CLASS_DEMOMAN + -1, // TF_CLASS_MEDIC + -1, // TF_CLASS_HEAVYWEAPONS + -1, // TF_CLASS_PYRO + 1, // TF_CLASS_SPY + -1, // TF_CLASS_ENGINEER + }; + const int iNextClass = aNextClasses[ iClass ]; + const bool bCurrentClassCompleted = iStage >= aUnlockRequirements[ iClass ]; + if ( iNextClass >= TF_FIRST_NORMAL_CLASS && bCurrentClassCompleted ) + { + // Find the data for the given class and unlock it + KeyValues *pNextClassData = pTrainingProgressData->FindKey( g_aPlayerClassNames_NonLocalized[ iNextClass ] ); + if ( pNextClassData ) + { + pNextClassData->SetInt( "progress", 0 ); + } + else + { + AssertMsg( 0, "This class data should have been filled out above" ); + } + } + } + + // Attempt to save + Training_SaveProgress( pTrainingProgressData ); + + // Free + pTrainingProgressData->deleteThis(); +} + +static ConVar training_map_video( "training_map_video", "", 0, "Video to show for training" ); + +void CL_Training_LevelShutdown() +{ + training_map_video.Revert(); +} + +int Training_GetNumCoursesForClass( int iClass ) +{ + static int s_aClassCourses[ TF_CLASS_COUNT ] = { + 0, // TF_CLASS_UNDEFINED + 0, // TF_CLASS_SCOUT + 0, // TF_CLASS_SNIPER + 2, // TF_CLASS_SOLDIER + 1, // TF_CLASS_DEMOMAN + 0, // TF_CLASS_MEDIC + 0, // TF_CLASS_HEAVYWEAPONS + 0, // TF_CLASS_PYRO + 1, // TF_CLASS_SPY + 1, // TF_CLASS_ENGINEER + }; + + AssertMsg( iClass >= 0 && iClass < TF_CLASS_COUNT, "Training_GetNumCoursesForClass(): Class out of range!" ); + return s_aClassCourses[ iClass ]; +} + +int Training_GetNumCourses() +{ + static bool s_bComputed = false; + static int s_nTotal = 0; + + if ( !s_bComputed ) + { + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + s_nTotal += Training_GetNumCoursesForClass( i ); + } + s_bComputed = true; + } + + AssertMsg( s_nTotal == 5, "Number of total courses is incorrect - should be soldier (2) + demo (1) + spy (1) + engy (1)" ); + + return s_nTotal; +} + +int Training_GetProgressCount() +{ + int aProgress[ TF_CLASS_COUNT ]; + Training_GetProgress( aProgress ); + + int nTotalProgress = 0; + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + int nClassProgress = Training_GetClassProgress( i ); + if ( nClassProgress > 0 ) + { + nTotalProgress += nClassProgress; + } + } + + return nTotalProgress; +} + +bool Training_IsComplete() +{ + return Training_GetProgressCount() == Training_GetNumCourses(); +} + +void Training_Init() +{ + // If the progress file already exists, early out as we only do conversation from the old system to the new here. + if ( Training_TrainingProgressFileExists() ) + return; + + int aProgress[ TF_CLASS_COUNT ]; + + int fProgressOld = cl_training_completed_with_classes.GetInt(); + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + if ( ( fProgressOld & ( 1 << i ) ) != 0 ) + { + aProgress[ i ] = 1; + } + else + { + aProgress[ i ] = -1; + } + } + + const int TRAINING_CLASS_ATTACK_DEFEND = 15; + + // Add an explicit check for attack/defend + if ( ( fProgressOld & ( 1 << TRAINING_CLASS_ATTACK_DEFEND ) ) != 0 ) + { + aProgress[ TF_CLASS_SOLDIER ] = 2; + } + + // Soldier should always be at least 0 + aProgress[ TF_CLASS_SOLDIER ] = MAX( aProgress[ TF_CLASS_SOLDIER ], 0 ); + + // Save a file with the given progress settings + Training_SaveProgress( aProgress ); +} + +//------------------------------------------------------------------------------------------------------ + +Panel *FindAncestorByName( Panel *pChild, const char *pName ) +{ + if ( !pChild ) + return NULL; + + Panel *pCurrent = pChild->GetParent(); + while ( pCurrent ) + { + if ( FStrEq( pCurrent->GetName(), pName ) ) + return pCurrent; + + pCurrent = pCurrent->GetParent(); + } + + return NULL; +} + +CExButton *SetupButtonActionSignalTarget( Panel *pParent, const char *pButtonName, const char *pCommand = NULL ) +{ + CExButton *pButton = dynamic_cast< CExButton * >( pParent->FindChildByName( pButtonName ) ); + EditablePanel *pTrainingDialog = static_cast< EditablePanel * >( FindAncestorByName( pParent, TRAINING_DIALOG_NAME ) ); Assert( pTrainingDialog ); + + if ( pButton && pTrainingDialog ) + { + if ( pCommand ) + { + pButton->SetCommand( pCommand ); + } + + pButton->AddActionSignalTarget( pTrainingDialog ); + } + + return pButton; +} + +//------------------------------------------------------------------------------------------------------ + +// +// Sets dialog title/subtitle and sets up cancel/back buttons +// +class CTrainingBasePanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTrainingBasePanel, EditablePanel ); +public: + CTrainingBasePanel( Panel *pParent, const char *pName ) + : EditablePanel( pParent, pName ), + m_pPrevPagePanel( NULL ) + { + } + + virtual void ApplySettings( KeyValues *pInResourceData ) + { + BaseClass::ApplySettings( pInResourceData ); + + m_strTitleToken = pInResourceData->GetString( "TrainingTitle", NULL ); + m_strSubTitleToken = pInResourceData->GetString( "TrainingSubTitle", NULL ); + } + + inline bool FindCharInWideString( const wchar_t *pStr, wchar_t c ) + { + if ( !pStr ) + return false; + + const int nLen = V_wcslen( pStr ); + for ( int i = 0; i < nLen; ++i ) + { + if ( pStr[ i ] == c ) + return true; + } + + return false; + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + EditablePanel *pTrainingDialog = static_cast< EditablePanel * >( FindAncestorByName( this, TRAINING_DIALOG_NAME ) ); Assert( pTrainingDialog ); + if ( pTrainingDialog ) + { + const wchar_t *pTitleString = g_pVGuiLocalize->Find( m_strTitleToken.Get() ); + if ( FindCharInWideString( pTitleString, L'%' ) ) + { + KeyValues *pTitleFormatData = GetTitleFormatData(); AssertMsg( pTitleFormatData, "Should get valid data here." ); + if ( pTitleFormatData ) + { + wchar_t wszTitle[ 1024 ]; + g_pVGuiLocalize->ConstructString_safe( wszTitle, m_strTitleToken.Get(), pTitleFormatData ); + pTitleFormatData->deleteThis(); + + pTrainingDialog->SetDialogVariable( "title", wszTitle ); + } + } + else + { + pTrainingDialog->SetDialogVariable( "title", g_pVGuiLocalize->Find( m_strTitleToken.Get() ) ); + } + + pTrainingDialog->SetDialogVariable( "subtitle", g_pVGuiLocalize->Find( m_strSubTitleToken ) ); + } + } + + virtual void OnCommand( const char *pCommand ) + { + if ( FStrEq( pCommand, "goprev" ) ) + { + GoPrev(); + } + else if ( FStrEq( pCommand, "gonext" ) ) + { + GoNext(); + } + else + { + BaseClass::OnCommand( pCommand ); + } + } + + virtual void OnKeyCodePressed( KeyCode nCode ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( nCode ); + + if ( nCode == KEY_SPACE || nCode == KEY_ENTER || nCode == KEY_XBUTTON_A || nCode == STEAMCONTROLLER_A ) + { + Go(); + } + else if ( nButtonCode == KEY_XBUTTON_LEFT || + nButtonCode == KEY_XSTICK1_LEFT || + nButtonCode == KEY_XSTICK2_LEFT || + nButtonCode == STEAMCONTROLLER_DPAD_LEFT || + nButtonCode == KEY_LEFT ) + { + GoPrev(); + } + else if ( nButtonCode == KEY_XBUTTON_RIGHT || + nButtonCode == KEY_XSTICK1_RIGHT || + nButtonCode == KEY_XSTICK2_RIGHT || + nButtonCode == STEAMCONTROLLER_DPAD_RIGHT || + nButtonCode == KEY_RIGHT ) + { + GoNext(); + } + else + { + BaseClass::OnKeyCodePressed( nCode ); + } + } + + void Go() + { + EditablePanel *pTrainingDialog = static_cast< EditablePanel * >( FindAncestorByName( this, TRAINING_DIALOG_NAME ) ); Assert( pTrainingDialog ); + if ( pTrainingDialog ) + { + const char *pGoCommand = GetGoCommand(); + if ( pGoCommand ) + { + pTrainingDialog->OnCommand( pGoCommand ); + } + } + } + + virtual void GoPrev() + { + } + + virtual void GoNext() + { + } + + virtual void OnBackPressed() + { + } + + virtual KeyValues *GetTitleFormatData() const + { + return NULL; + } + + virtual const char *GetGoCommand() const + { + return NULL; + } + + void SetPrevPage( CTrainingBasePanel *pPanel ) + { + m_pPrevPagePanel = pPanel; + } + + CTrainingBasePanel *GetPrevPage() + { + return m_pPrevPagePanel; + } + + virtual bool IsFirstPage() const + { + return false; + } + + virtual bool ShouldShowGradient() const + { + return false; + } + +protected: + CUtlString m_strTitleToken; + CUtlString m_strSubTitleToken; + CTrainingBasePanel *m_pPrevPagePanel; +}; + +//------------------------------------------------------------------------------------------------------ + +class CTrainingBaseCarouselPanel : public CTrainingBasePanel +{ + DECLARE_CLASS_SIMPLE( CTrainingBaseCarouselPanel, CTrainingBasePanel ); +public: + CTrainingBaseCarouselPanel( Panel *pParent, const char *pName ) + : CTrainingBasePanel( pParent, pName ), + m_iPage( 0 ) + { + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + CFmtStr fmtCurPageLabelText( "%i/%i", m_iPage + 1, GetNumPages() ); + SetDialogVariable( "curpage", fmtCurPageLabelText.Access() ); + + const int nNumPages = GetNumPages(); + + // Set visibility on buttons and current page based on the number of pages. + CExLabel *pCurPageLabel = dynamic_cast< CExLabel * >( FindChildByName( "CurPageLabel" ) ); + if ( pCurPageLabel ) + { + pCurPageLabel->SetVisible( nNumPages > 1 ); + } + + const char *pNavButtonNames[2] = { "PrevButton", "NextButton" }; + for ( int i = 0; i < 2; ++i ) + { + CExButton *pCurButton = dynamic_cast< CExButton * >( FindChildByName( pNavButtonNames[ i ] ) ); + if ( !pCurButton ) + continue; + pCurButton->SetVisible( nNumPages > 1 ); + } + } + + virtual void OnCommand( const char *pCommand ) + { + if ( FStrEq( pCommand, "goprev" ) ) + { + GoPrev(); + } + else if ( FStrEq( pCommand, "gonext" ) ) + { + GoNext(); + } + else + { + BaseClass::OnCommand( pCommand ); + } + } + + virtual void OnKeyCodePressed( KeyCode nCode ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( nCode ); + + if ( nButtonCode == KEY_XBUTTON_LEFT || + nButtonCode == KEY_XSTICK1_LEFT || + nButtonCode == KEY_XSTICK2_LEFT || + nButtonCode == KEY_LEFT ) + { + GoPrev(); + } + else if ( nButtonCode == KEY_XBUTTON_RIGHT || + nButtonCode == KEY_XSTICK1_RIGHT || + nButtonCode == KEY_XSTICK2_RIGHT || + nButtonCode == KEY_RIGHT ) + { + GoNext(); + } + else + { + BaseClass::OnKeyCodePressed( nCode ); + } + } + + void GoPrev() + { + --m_iPage; + + if ( m_iPage < 0 ) + { + m_iPage += GetNumPages(); + } + + InvalidateLayout( false, true ); + } + + void GoNext() + { + m_iPage = ( m_iPage + 1 ) % GetNumPages(); + + InvalidateLayout( false, true ); + } + + virtual int GetNumPages() const = 0; + +protected: + int m_iPage; +}; + +//------------------------------------------------------------------------------------------------------ + +class CModePanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CModePanel, EditablePanel ); +public: + CModePanel( Panel *pParent, const char *pName ) + : EditablePanel( pParent, pName ) + { + HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" ); + SetScheme( hScheme ); + SetProportional( true ); + } + + ~CModePanel() + { + } + + virtual void ApplySettings( KeyValues *pInResourceData ) + { + BaseClass::ApplySettings( pInResourceData ); + + m_strModeNameToken = pInResourceData->GetString( "modename", NULL ); + m_strDescriptionToken = pInResourceData->GetString( "description", NULL ); + m_strImageToken = pInResourceData->GetString( "image", NULL ); + m_strStartCommand = pInResourceData->GetString( "startcommand", NULL ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/modeselection/modepanel.res" ); + + EditablePanel *pContainer = static_cast< EditablePanel * >( FindChildByName( "ModeInfoContainer" ) ); + if ( pContainer ) + { + pContainer->SetDialogVariable( "modename", g_pVGuiLocalize->Find( m_strModeNameToken.Get() ) ); + pContainer->SetDialogVariable( "description", g_pVGuiLocalize->Find( m_strDescriptionToken.Get() ) ); + + EditablePanel *pImageFrame = static_cast< EditablePanel * >( pContainer->FindChildByName( "ImageFrame" ) ); + if ( pImageFrame ) + { + ImagePanel *pImage = dynamic_cast< ImagePanel * >( pContainer->FindChildByName( "Image" ) ); + if ( pImage ) + { + pImage->SetImage( m_strImageToken ); + pImage->SetParent( pImageFrame ); + } + } + } + + SetupButtonActionSignalTarget( this, "StartButton", m_strStartCommand.Get() ); + } + + virtual void PerformLayout( void ) + { + BaseClass::PerformLayout(); + + GetParent()->NavigateTo(); + } + +private: + CUtlString m_strModeNameToken; + CUtlString m_strDescriptionToken; + CUtlString m_strImageToken; + CUtlString m_strStartCommand; +}; + +DECLARE_BUILD_FACTORY( CModePanel ); + +//------------------------------------------------------------------------------------------------------ + +class CModeSelectionPanel : public CTrainingBasePanel +{ + DECLARE_CLASS_SIMPLE( CModeSelectionPanel, CTrainingBasePanel ); +public: + CModeSelectionPanel( Panel *pParent, const char *pName ) + : CTrainingBasePanel( pParent, pName ) + { + SetProportional( true ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/modeselection/modeselection.res" ); + } + + virtual bool IsFirstPage() const + { + return true; + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + Panel *pPanel = FindChildByName( "BasicTrainingPanel" ); + if ( pPanel ) + { + pPanel->SetNavToRelay( "StartButton" ); + pPanel->SetNavRight( "<<OfflinePracticePanel" ); + pPanel->InvalidateLayout(); + } + + pPanel = FindChildByName( "OfflinePracticePanel" ); + if ( pPanel ) + { + pPanel->SetNavToRelay( "StartButton" ); + pPanel->SetNavLeft( "<<BasicTrainingPanel" ); + pPanel->InvalidateLayout(); + } + + SetNavToRelay( "BasicTrainingPanel" ); + } +}; + +DECLARE_BUILD_FACTORY( CModeSelectionPanel ); + +//------------------------------------------------------------------------------------------------------ + +class CBasicTraining_ClassPanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CBasicTraining_ClassPanel, EditablePanel ); +public: + CBasicTraining_ClassPanel( Panel *pParent, const char *pName ) + : EditablePanel( pParent, pName ), + m_pImagePanel( NULL ), + m_pSelectButton( NULL ) + { + SetProportional( true ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/basictraining/classpanel.res" ); + + m_pImagePanel = dynamic_cast< ImagePanel * >( FindChildByName( "Image" ) ); Assert( m_pImagePanel ); + m_pSelectButton = SetupButtonActionSignalTarget( this, "SelectButton" ); Assert( m_pSelectButton ); + + if ( m_pSelectButton ) + { + m_pSelectButton->SetDefaultBorder( pScheme->GetBorder( m_pSelectButton->IsEnabled() ? "MainMenuButtonDefault" : "MainMenuButtonDisabled" ) ); + } + + m_pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_pImagePanel && m_pSelectButton ) + { + const int nMargin = XRES( 10 ); + const int nButtonStartY = YRES( 215 ); + + m_pImagePanel->SetBounds( nMargin, YRES( 20 ), GetWide() - 2 * nMargin, nButtonStartY - YRES( 40 ) ); + + int aButtonBounds[4] = { + nMargin, nButtonStartY, GetWide() - nMargin * 2, (int)YRES( 25 ) + }; + + m_pSelectButton->SetBounds( aButtonBounds[0], aButtonBounds[1], aButtonBounds[2], aButtonBounds[3] ); + + CExLabel *pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) ); + if ( pProgressLabel ) + { + int aPos[2]; + pProgressLabel->GetPos( aPos[0], aPos[1] ); + pProgressLabel->SetPos( aButtonBounds[0], aPos[1] ); + pProgressLabel->SetWide( aButtonBounds[2] ); + } + } + + GetParent()->NavigateTo(); + } + + void SetClassData( int iClass, int nProgress, const char *pImageBase ) + { + const bool bLocked = nProgress < 0; + + if ( m_pImagePanel ) + { + CFmtStr fmtImagePath( "%s_%s", pImageBase, bLocked ? "off" : "on" ); + m_pImagePanel->SetImage( fmtImagePath.Access() ); + } + + if ( m_pSelectButton ) + { + m_pSelectButton->SetEnabled( !bLocked ); + } + + if ( m_pProgressLabel ) + { + const int nPercent = (int)( 100.0f * nProgress / Training_GetNumCoursesForClass( iClass ) ); + wchar_t wszLocalized[256]; + if ( nPercent > 0 ) + { + if ( nPercent < 100 ) + { + wchar_t wszNum[16] = L""; + V_snwprintf( wszNum, ARRAYSIZE( wszNum ), L"%i", nPercent ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TR_Progress" ), 1, wszNum ); + } + else + { + V_wcsncpy( wszLocalized, g_pVGuiLocalize->Find( "#TR_ProgressDone" ), sizeof( wszLocalized ) ); + } + + m_pProgressLabel->SetText( wszLocalized ); + m_pProgressLabel->SetVisible( true ); + } + else + { + m_pProgressLabel->SetVisible( false ); + } + } + + InvalidateLayout( true, false ); + } + + void SetSelectCommand( const char *pCommand ) + { + if ( m_pSelectButton ) + { + m_pSelectButton->SetCommand( pCommand ); + } + } + +private: + ImagePanel *m_pImagePanel; + CExButton *m_pSelectButton; + CExLabel *m_pProgressLabel; +}; + +DECLARE_BUILD_FACTORY( CBasicTraining_ClassPanel ); + +//------------------------------------------------------------------------------------------------------ + +enum Consts_t +{ + NUM_CLASS_PANELS = 4, +}; + +const char *g_pClassPanelNames[ NUM_CLASS_PANELS ] = +{ + "SoldierPanel", + "DemoPanel", + "SpyPanel", + "EngineerPanel" +}; + +class CBasicTraining_ClassSelectionPanel : public CTrainingBasePanel +{ + DECLARE_CLASS_SIMPLE( CBasicTraining_ClassSelectionPanel, CTrainingBasePanel ); +public: + CBasicTraining_ClassSelectionPanel( Panel *pParent, const char *pName ) + : CTrainingBasePanel( pParent, pName ) + { + SetProportional( true ); + + for ( int i = 0; i < NUM_CLASS_PANELS; ++i ) + { + m_PanelInfos[ i ].m_pPanel = new CBasicTraining_ClassPanel( this, g_pClassPanelNames[ i ] ); + } + } + + virtual void ApplySettings( KeyValues *pInResourceData ) + { + BaseClass::ApplySettings( pInResourceData ); + + for ( int i = 0; i < NUM_CLASS_PANELS; ++i ) + { + CFmtStr fmtToken( "Class%iToken", i ); + m_PanelInfos[ i ].m_strSelectButtonToken = pInResourceData->GetString( fmtToken.Access(), NULL ); + + CFmtStr fmtImage( "Class%iImage", i ); + m_PanelInfos[ i ].m_strClassImage = pInResourceData->GetString( fmtImage.Access(), NULL ); + + CFmtStr fmtCommand( "Class%iCommand", i ); + m_PanelInfos[ i ].m_strCommand = pInResourceData->GetString( fmtCommand.Access(), NULL ); + } + + PRINT_KEY_VALUES( pInResourceData ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/basictraining/classselection.res" ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + const int nWidth = GetWide(); + const int nClassPanelW = nWidth / NUM_CLASS_PANELS; + const int nClassPanelH = YRES( 260 ); + + const int aTrainingClasses[ NUM_CLASS_PANELS ] = { + TF_CLASS_SOLDIER, TF_CLASS_DEMOMAN, TF_CLASS_SPY, TF_CLASS_ENGINEER + }; + + + for ( int i = 0; i < NUM_CLASS_PANELS; ++i ) + { + CBasicTraining_ClassPanel *pCurClassPanel = m_PanelInfos[ i ].m_pPanel; + + pCurClassPanel->SetBounds( + i * nClassPanelW, + 0, + nClassPanelW, + nClassPanelH + ); + + pCurClassPanel->SetDialogVariable( "selectbuttontext", g_pVGuiLocalize->Find( m_PanelInfos[ i ].m_strSelectButtonToken.Get() ) ); + + const int nProgress = Training_GetClassProgress( aTrainingClasses[ i ] ); + pCurClassPanel->SetClassData( aTrainingClasses[ i ], nProgress, m_PanelInfos[ i ].m_strClassImage.Get() ); + + pCurClassPanel->SetSelectCommand( m_PanelInfos[ i ].m_strCommand.Get() ); + + pCurClassPanel->SetNavToRelay( "SelectButton" ); + + char szName[ 64 ]; + if ( i > 0 ) + { + Panel *pPrevPanel = m_PanelInfos[ i - 1 ].m_pPanel; + if ( pPrevPanel ) + { + V_snprintf( szName, sizeof( szName ), "<%s", pPrevPanel->GetName() ); + pCurClassPanel->SetNavLeft( szName ); + + V_snprintf( szName, sizeof( szName ), "<%s", pCurClassPanel->GetName() ); + pPrevPanel->SetNavRight( szName ); + } + } + + pCurClassPanel->InvalidateLayout(); + } + + SetNavToRelay( g_pClassPanelNames[ 0 ] ); + } + + virtual bool ShouldShowGradient() const + { + return true; + } + +private: + struct ClassPanelInfo_t + { + CBasicTraining_ClassPanel *m_pPanel; + CUtlString m_strSelectButtonToken; + CUtlString m_strClassImage; + CUtlString m_strCommand; + } + m_PanelInfos[ NUM_CLASS_PANELS ]; +}; + +DECLARE_BUILD_FACTORY( CBasicTraining_ClassSelectionPanel ); + +//------------------------------------------------------------------------------------------------------ + +class CBasicTraining_ClassDetailsPanel : public CTrainingBasePanel +{ + DECLARE_CLASS_SIMPLE( CBasicTraining_ClassDetailsPanel, CTrainingBasePanel ); +public: + CBasicTraining_ClassDetailsPanel( Panel *pParent, const char *pName ) + : CTrainingBasePanel( pParent, pName ), + m_iClass( TF_CLASS_UNDEFINED ), + m_pStartTrainingButton( NULL ) + { + SetProportional( true ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/basictraining/classdetails.res" ); + + EditablePanel *pOverlayPanel = dynamic_cast< EditablePanel * >( FindChildByName( "OverlayPanel" ) ); + if ( pOverlayPanel && m_iClass >= TF_FIRST_NORMAL_CLASS && m_iClass < TF_LAST_NORMAL_CLASS ) + { + pOverlayPanel->SetDialogVariable( "classname", g_pVGuiLocalize->Find( g_aPlayerClassNames[ m_iClass ] ) ); + + CFmtStr fmtDescToken( "TR_ClassInfo_%s", m_szClassName ); + pOverlayPanel->SetDialogVariable( "description", g_pVGuiLocalize->Find( fmtDescToken.Access() ) ); + + for ( int i = 0; i < 3; ++i ) + { + CFmtStr fmtWeaponImageName( "WeaponImage%i", i ); + ImagePanel *pCurImage = dynamic_cast< ImagePanel * >( pOverlayPanel->FindChildByName( fmtWeaponImageName.Access() ) ); + + if ( pCurImage ) + { + CFmtStr fmtWeaponImagePath; + GetWeaponPath( m_iClass, i, fmtWeaponImagePath ); + pCurImage->SetImage( fmtWeaponImagePath.Access() ); + } + } + } + + ImagePanel *pClassImage = dynamic_cast< ImagePanel * >( FindChildByName( "ClassImage" ) ); + if ( pClassImage ) + { + CFmtStr fmtImageName( "training/class_%s_on", m_szClassName ); + pClassImage->SetImage( fmtImageName.Access() ); + } + + ImagePanel *pClassIconImage = dynamic_cast< ImagePanel * >( FindChildByName( "ClassIconImage" ) ); + if ( pClassIconImage ) + { + CFmtStr fmtImageName( "training/class_icon_%s", m_szClassName ); + pClassIconImage->SetImage( fmtImageName.Access() ); + } + + m_pStartTrainingButton = SetupButtonActionSignalTarget( this, "StartTrainingButton" ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + SetNavToRelay( "StartTrainingButton" ); + NavigateTo(); + } + + void GetWeaponPath( int iClass, int iWeapon, CFmtStr &fmtOut ) // iWeapon is in [0,2] + { + static const char *s_pWeaponNames[ TF_CLASS_COUNT ][ 3 ] = { + { NULL, NULL, NULL }, // TF_CLASS_UNDEFINED + { NULL, NULL, NULL }, // TF_CLASS_SCOUT + { NULL, NULL, NULL }, // TF_CLASS_SNIPER + { "rocketlauncher", "shotgun", "shovel" }, // TF_CLASS_SOLDIER + { "grenadelauncher", "stickybomb_launcher", "bottle" }, // TF_CLASS_DEMOMAN, + { NULL, NULL, NULL }, // TF_CLASS_MEDIC + { NULL, NULL, NULL }, // TF_CLASS_HEAVYWEAPONS + { NULL, NULL, NULL }, // TF_CLASS_PYRO + { "revolver", "c_spy_watch", "knife", }, // TF_CLASS_SPY, + { "shotgun", "pistol", "wrench" }, // TF_CLASS_ENGINEER, + }; + + Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_CLASS_COUNT ); + Assert( iWeapon >= 0 && iWeapon < 3 ); + + if ( iClass == TF_CLASS_SPY && iWeapon == 1 ) + { + fmtOut.sprintf( "../backpack/weapons/c_models/c_spy_watch/parts/c_spy_watch" ); + } + else + { + fmtOut.sprintf( "../backpack/weapons/w_models/w_%s", s_pWeaponNames[ iClass ][ iWeapon ] ); + } + } + + virtual bool ShouldShowGradient() const + { + return true; + } + + virtual const char *GetGoCommand() const + { + if ( !m_pStartTrainingButton ) + return NULL; + + KeyValues *pCommand = m_pStartTrainingButton->GetCommand(); + if ( !pCommand ) + return NULL; + + return pCommand->GetString( "command", NULL ); + } + + void SetClass( const char *pClassName ) + { + V_strcpy_safe( m_szClassName, pClassName ); + + // Setup class details panel + if ( FStrEq( pClassName, "soldier" ) ) + { + m_iClass = TF_CLASS_SOLDIER; + } + else if ( FStrEq( pClassName, "demoman" ) ) + { + m_iClass = TF_CLASS_DEMOMAN; + } + else if ( FStrEq( pClassName, "spy" ) ) + { + m_iClass = TF_CLASS_SPY; + } + else if ( FStrEq( pClassName, "engineer" ) ) + { + m_iClass = TF_CLASS_ENGINEER; + } + else + { + AssertMsg( 0, "Bad class name." ); + } + } + +private: + char m_szClassName[16]; + int m_iClass; + CExButton *m_pStartTrainingButton; +}; + +DECLARE_BUILD_FACTORY( CBasicTraining_ClassDetailsPanel ); + +//------------------------------------------------------------------------------------------------------ + +class COfflinePractice_ModeSelectionPanel : public CTrainingBaseCarouselPanel +{ + DECLARE_CLASS_SIMPLE( COfflinePractice_ModeSelectionPanel, CTrainingBaseCarouselPanel ); +public: + COfflinePractice_ModeSelectionPanel( Panel *pParent, const char *pName ) + : CTrainingBaseCarouselPanel( pParent, pName ), + m_pGameModeImagePanel( NULL ) + { + SetProportional( true ); + } + + virtual void ApplySettings( KeyValues *pInResourceData ) + { + BaseClass::ApplySettings( pInResourceData ); + + for ( int i = 0; i < NUM_PRACTICE_MODES; ++i ) + { + CFmtStr fmtModeToken( "Mode%iToken", i ); + m_ModeInfos[ i ].m_strModeToken = pInResourceData->GetString( fmtModeToken.Access(), NULL ); + + CFmtStr fmtDescToken( "Desc%iToken", i ); + m_ModeInfos[ i ].m_strDescToken = pInResourceData->GetString( fmtDescToken.Access(), NULL ); + + CFmtStr fmtImagePath( "Image%iPath", i ); + m_ModeInfos[ i ].m_strImage = pInResourceData->GetString( fmtImagePath.Access(), NULL ); + + CFmtStr fmtModeId( "Mode%iId", i ); + m_ModeInfos[ i ].m_nId = ( GameMode_t )pInResourceData->GetInt( fmtModeId.Access(), MODE_INVALID ); Assert( m_ModeInfos[ i ].m_nId != MODE_INVALID ); + } + + PRINT_KEY_VALUES( pInResourceData ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/ui/training/offlinepractice/practicemodeselection.res" ); + + m_pGameModeImagePanel = dynamic_cast< ImagePanel * >( FindChildByName( "GameModeImagePanel" ) ); + if ( m_pGameModeImagePanel ) + { + Assert( m_iPage >= 0 && m_iPage < NUM_PRACTICE_MODES ); + m_pGameModeImagePanel->SetImage( m_ModeInfos[ m_iPage ].m_strImage.Get() ); + } + + SetupButtonActionSignalTarget( this, "SelectCurrentGameModeButton" ); + + SetDialogVariable( "description", g_pVGuiLocalize->Find( m_ModeInfos[ m_iPage ].m_strDescToken.Get() ) ); + SetDialogVariable( "gamemode", g_pVGuiLocalize->Find( m_ModeInfos[ m_iPage ].m_strModeToken.Get() ) ); + + CFmtStr fmtCurPageLabelText( "%i/%i", m_iPage + 1, NUM_PRACTICE_MODES ); + SetDialogVariable( "curpage", fmtCurPageLabelText.Access() ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_pGameModeImagePanel ) + { + // Use .res file ypos + int aPos[2]; + m_pGameModeImagePanel->GetPos( aPos[0], aPos[1] ); + + // Center + m_pGameModeImagePanel->SetPos( ( GetWide() - m_pGameModeImagePanel->GetWide() ) / 2, aPos[1] ); + } + + SetNavToRelay( "SelectCurrentGameModeButton" ); + NavigateTo(); + } + + virtual int GetNumPages() const + { + return NUM_PRACTICE_MODES; + } + + GameMode_t GetMode() const + { + return m_ModeInfos[ m_iPage ].m_nId; + } + +private: + enum Consts_t + { + NUM_PRACTICE_MODES = 3, + }; + + struct PracticeModeInfo_t + { + CUtlString m_strModeToken; + CUtlString m_strDescToken; + CUtlString m_strImage; + GameMode_t m_nId; + } + m_ModeInfos[ NUM_PRACTICE_MODES ]; + + ImagePanel *m_pGameModeImagePanel; +}; + +DECLARE_BUILD_FACTORY( COfflinePractice_ModeSelectionPanel ); + + +const char *g_pDifficultyModes[ 4 ] = { "Easy", "Normal", "Hard", "Expert" }; + +//------------------------------------------------------------------------------------------------------ + +class COfflinePractice_MapSelectionPanel : public CTrainingBaseCarouselPanel +{ + DECLARE_CLASS_SIMPLE( COfflinePractice_MapSelectionPanel, CTrainingBaseCarouselPanel ); + + struct MapInfo_t + { + CUtlString m_strDisplayName; + CUtlString m_strName; + int m_aPlayerRange[2]; + }; + +public: + COfflinePractice_MapSelectionPanel( Panel *pParent, const char *pName ) + : CTrainingBaseCarouselPanel( pParent, pName ), + m_pMapImagePanel( NULL ), + m_pDefaultsData( NULL ), + m_pDifficultyComboBox( NULL ), + m_pSavedData( NULL ), + m_iGameMode( MODE_INVALID ) + { + SetProportional( true ); + LoadMapData(); + } + + ~COfflinePractice_MapSelectionPanel() + { + for ( int i = 0; i < NUM_GAME_MODES; ++i ) + { + m_vecMapData[i].PurgeAndDeleteElements(); + } + + if ( m_pDefaultsData ) + { + m_pDefaultsData->deleteThis(); + } + } + + void SetGameMode( int iGameMode ) + { + m_iGameMode = iGameMode; + m_iPage = 0; + InvalidateLayout( false, true ); + } + + const MapInfo_t *GetSelectedMapInfo() const + { + return m_iGameMode < 0 ? NULL : m_vecMapData[ m_iGameMode ][ m_iPage ]; + } + + int GetMaxPlayers() const + { + return GetSelectedMapInfo()->m_aPlayerRange[1]; + } + + const char *GetMapName() const + { + return GetSelectedMapInfo()->m_strName.Get(); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + LoadControlSettings( "resource/ui/training/offlinepractice/mapselection.res" ); + + BaseClass::ApplySchemeSettings( pScheme ); + + const MapInfo_t *pCurMapInfo = GetSelectedMapInfo(); + if ( !pCurMapInfo ) + return; + + m_pMapImagePanel = dynamic_cast< ImagePanel * >( FindChildByName( "MapImagePanel" ) ); + if ( m_pMapImagePanel ) + { + Assert( m_iPage >= 0 && m_iPage < GetMapCount() ); + + CFmtStr fmtMapImageBasePath( "training/screenshots/%s.vmt", pCurMapInfo->m_strName.Get() ); + m_pMapImagePanel->SetImage( fmtMapImageBasePath.Access() ); + } + + // Send the 'select' button's command to the actual dialog + SetupButtonActionSignalTarget( this, "SelectCurrentMapButton" ); + + // update recommended number of players + CExLabel *pSuggestedPlayerCountLabel = dynamic_cast< CExLabel * >( FindChildByName( "SuggestedPlayerCountLabel" ) ); + if ( pSuggestedPlayerCountLabel ) + { + wchar_t wszLocalized[256]; + wchar_t wszNum1[16]=L""; + wchar_t wszNum2[16]=L""; + V_snwprintf( wszNum1, ARRAYSIZE( wszNum1 ), L"%i", pCurMapInfo->m_aPlayerRange[0] ); + V_snwprintf( wszNum2, ARRAYSIZE( wszNum2 ), L"%i", pCurMapInfo->m_aPlayerRange[1] ); + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_OfflinePractice_NumPlayers" ), 2, wszNum1, wszNum2 ); + pSuggestedPlayerCountLabel->SetText( wszLocalized ); + } + + m_pDifficultyComboBox = dynamic_cast< ComboBox * >( FindChildByName( "DifficultyComboBox" ) ); + if ( m_pDifficultyComboBox ) + { + for ( int i = 0; i < ARRAYSIZE( g_pDifficultyModes ); ++i ) + { + m_pDifficultyComboBox->AddItem( g_pDifficultyModes[i], NULL ); + } + } + + TextEntry *pNumPlayersTextEntry = dynamic_cast< TextEntry * >( FindChildByName( "NumPlayersTextEntry" ) ); + if ( pNumPlayersTextEntry ) + { + pNumPlayersTextEntry->SetBorder( pScheme->GetBorder( "ComboBoxBorder" ) ); + } + + SetupButtonActionSignalTarget( this, "StartOfflinePracticeButton" ); + + SetDialogVariable( "mapname", pCurMapInfo->m_strDisplayName.Get() ); + + UpdateControlsFromSavedData( m_pDifficultyComboBox, pNumPlayersTextEntry ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_pMapImagePanel ) + { + // Use .res file ypos + int aPos[2]; + m_pMapImagePanel->GetPos( aPos[0], aPos[1] ); + + // Center + m_pMapImagePanel->SetPos( ( GetWide() - m_pMapImagePanel->GetWide() ) / 2, aPos[1] ); + } + + SetNavToRelay( "StartOfflinePracticeButton" ); + NavigateTo(); + } + + virtual void OnKeyCodePressed( KeyCode nCode ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( nCode ); + + if ( nButtonCode == KEY_XBUTTON_X ) + { + if ( m_pDifficultyComboBox ) + { + m_pDifficultyComboBox->SilentActivateItemByRow( ( m_pDifficultyComboBox->GetActiveItem() + 1 ) % ARRAYSIZE( g_pDifficultyModes ) ); + } + } + else if ( nButtonCode == KEY_XBUTTON_UP || + nButtonCode == KEY_XSTICK1_UP || + nButtonCode == KEY_XSTICK2_UP || + nButtonCode == KEY_UP ) + { + SetControlInt( "NumPlayersTextEntry", clamp( GetControlInt( "NumPlayersTextEntry", 0 ) + 1, 1, 31 ) ); + } + else if ( nButtonCode == KEY_XBUTTON_DOWN || + nButtonCode == KEY_XSTICK1_DOWN || + nButtonCode == KEY_XSTICK2_DOWN || + nButtonCode == KEY_RIGHT ) + { + SetControlInt( "NumPlayersTextEntry", clamp( GetControlInt( "NumPlayersTextEntry", 0 ) - 1, 1, 31 ) ); + } + else + { + BaseClass::OnKeyCodePressed( nCode ); + } + } + + virtual int GetNumPages() const + { + return GetMapCount(); + } + + int GetMapCount() const + { + return m_vecMapData[ m_iGameMode ].Count(); + } + + void GetControlValues( int *pOutNumPlayers, int *pOutDiff, CUtlString *pOutMap = NULL ) + { + const MapInfo_t *pSelectedMapInfo = GetSelectedMapInfo(); + if ( !pSelectedMapInfo ) + return; + + *pOutNumPlayers = clamp( GetControlInt( "NumPlayersTextEntry", 0 ), 1, 31 ); + + *pOutDiff = clamp( GetBotDifficulty(), 0, 3 ); + + if ( pOutMap ) + { + *pOutMap = pSelectedMapInfo->m_strName; + } + } + + bool DoSetup() + { + // @note Tom Bui: if you add any other convars that get set, please revert them + // in CTFBotManager::RevertOfflinePracticeConvars() + + const MapInfo_t *pSelectedMapInfo = GetSelectedMapInfo(); + if ( !pSelectedMapInfo ) + return false; + + int nQuota = 1; + int iDifficulty = 0; + GetControlValues( &nQuota, &iDifficulty ); + + // the player count in the dialog includes the human player, so decrease the bot count by one + ConVarRef tf_bot_quota( "tf_bot_quota" ); + tf_bot_quota.SetValue( nQuota - 1 ); + + ConVarRef tf_bot_quota_mode( "tf_bot_quota_mode" ); + tf_bot_quota_mode.SetValue( "normal" ); + + ConVarRef tf_bot_auto_vacate( "tf_bot_auto_vacate" ); + tf_bot_auto_vacate.SetValue( 0 ); + + ConVarRef tf_bot_difficulty( "tf_bot_difficulty" ); + tf_bot_difficulty.SetValue( iDifficulty ); + + ConVarRef tf_bot_offline_practice( "tf_bot_offline_practice" ); + tf_bot_offline_practice.SetValue( 1 ); + + tf_training_client_message.SetValue( TRAINING_CLIENT_MESSAGE_WATCHING_INTRO_MOVIE ); + + SaveSettings(); + + return true; + } + +private: + virtual KeyValues *GetTitleFormatData() const + { + KeyValues *pResult = new KeyValues( "data" ); + if ( pResult ) + { + const char *pGameModeToken = ( m_iGameMode >= 0 && m_iGameMode < NUM_GAME_MODES ) ? gs_pGameModeTokens[ m_iGameMode ] : ""; + pResult->SetWString( "gametype", g_pVGuiLocalize->Find( pGameModeToken ) ); + } + return pResult; + } + + virtual void OnBackPressed() + { + SaveSettings(); + } + + void SaveSettings() + { + // Save settings + if ( m_pSavedData ) + { + int nNumPlayers = 1; + int iDifficulty = 0; + CUtlString strMap; + GetControlValues( &nNumPlayers, &iDifficulty, &strMap ); + + m_pSavedData->SetInt( "tf_bot_quota", nNumPlayers ); + m_pSavedData->SetInt( "tf_bot_difficulty", iDifficulty ); + m_pSavedData->SetString( "map", strMap.Get() ); + } + + if ( !m_pSavedData->SaveToFile( g_pFullFileSystem, "OfflinePracticeConfig.vdf", "MOD" ) ) + { + Warning( "Failed to write save data to OfflinePracticeConfig.vdf!\n" ); + } + } + + int GetBotDifficulty() const + { + if ( m_pDifficultyComboBox ) + { + return m_pDifficultyComboBox->GetActiveItem(); + } + + AssertMsg( 0, "Shouldn't get here." ); + return 0; + } + + void UpdateControlsFromSavedData( ComboBox *pDifficultyComboBox, TextEntry *pNumPlayersTextEntry ) + { + if ( !pDifficultyComboBox ) + return; + + if ( !pNumPlayersTextEntry ) + return; + + int iDifficulty = -1; + int nQuota = 0; + const char *defaultMap = ""; + + if ( m_pSavedData ) + { + m_pSavedData->deleteThis(); + m_pSavedData = NULL; + } + + m_pSavedData = new KeyValues( "OfflinePracticeConfig" ); + + // load the config data + if ( m_pSavedData ) + { + // this is game-specific data, so it should live in GAME, not CONFIG + if ( m_pSavedData->LoadFromFile( g_pFullFileSystem, "OfflinePracticeConfig.vdf", "MOD" ) ) + { + iDifficulty = m_pSavedData->GetInt( "tf_bot_difficulty", -1 ); + nQuota = m_pSavedData->GetInt( "tf_bot_quota", 0 ); + defaultMap = m_pSavedData->GetString( "map", "" ); + } + } + + if ( m_pDefaultsData ) + { + const int nMaxPlayers = m_pDefaultsData->GetInt( "max_players" ); + + if ( FStrEq( defaultMap, "" ) ) + { + defaultMap = m_pDefaultsData->GetString( "map", "" ); + } + + if ( nQuota == 0 ) + { + nQuota = m_pDefaultsData->GetInt( "suggested_players", nMaxPlayers ); + } + + if ( iDifficulty == -1 ) + { + const char *pDifficultyString = m_pDefaultsData->GetString( "difficulty" ); + if ( pDifficultyString ) + { + static const char* difficulties [] = { "easy", "normal", "hard", "expert" }; + for ( int i = 0, n = ARRAYSIZE(difficulties); i < n; ++i ) + { + if ( Q_strcmp( difficulties[i], pDifficultyString ) == 0) + { + iDifficulty = i; + break; + } + } + } + } + } + + // Set values in controls + m_pDifficultyComboBox->SilentActivateItemByRow( iDifficulty ); + + CFmtStr fmtNumPlayers( "%i", nQuota ); + pNumPlayersTextEntry->SetText( fmtNumPlayers.Access() ); + } + + void LoadMapData() + { + m_pOfflinePracticeData = new KeyValues( "offline_practice.res" ); + const char *pFilename = "resource/offline_practice.res"; + if ( !m_pOfflinePracticeData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ) ) + { + Warning( "Could not load %s!\n", pFilename ); + return; + } + + // Save defaults + KeyValues *pDefaultsData = m_pOfflinePracticeData->FindKey( "defaults" ); + if ( pDefaultsData ) + { + m_pDefaultsData = pDefaultsData->MakeCopy(); + } + + KeyValues *pMapData = m_pOfflinePracticeData->FindKey( "maps" ); + if ( pMapData ) + { + FOR_EACH_TRUE_SUBKEY( pMapData, pCurMap ) + { + MapInfo_t *pMapInfo = new MapInfo_t; + + pMapInfo->m_strName = pCurMap->GetName(); + pMapInfo->m_strDisplayName = pCurMap->GetString( "name" ); + pMapInfo->m_aPlayerRange[0] = pCurMap->GetInt( "min_players" ); + pMapInfo->m_aPlayerRange[1] = pCurMap->GetInt( "max_players" ); + + // Figure out which bucket to add to + const GameMode_t iGameMode = GetGameModeFromMapName( pMapInfo->m_strName.Get() ); + if ( iGameMode != MODE_INVALID ) + { + AddMapInfo( pMapInfo, iGameMode ); + } + } + } + + pMapData->deleteThis(); + } + + GameMode_t GetGameModeFromMapName( const char *pMapName ) + { + if ( !V_strnicmp( pMapName, "cp", 2 ) ) + { + return MODE_CP; + } + else if ( !V_strnicmp( pMapName, "koth", 4 ) ) + { + return MODE_KOTH; + } + else if ( !V_strnicmp( pMapName, "pl", 2 ) ) + { + return MODE_PL; + } + + AssertMsg( 0, "Should never get here!" ); + + return MODE_INVALID; + } + + void AddMapInfo( MapInfo_t *pMapInfo, GameMode_t iGameMode ) + { + m_vecMapData[ iGameMode ].AddToTail( pMapInfo ); + } + + int m_iGameMode; + KeyValues *m_pSavedData; + KeyValues *m_pDefaultsData; + ImagePanel *m_pMapImagePanel; + ComboBox *m_pDifficultyComboBox; + KeyValues *m_pOfflinePracticeData; + CUtlVector< MapInfo_t * > m_vecMapData[ NUM_GAME_MODES ]; +}; + +DECLARE_BUILD_FACTORY( COfflinePractice_MapSelectionPanel ); + +//------------------------------------------------------------------------------------------------------ + +class CTrainingDialog : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTrainingDialog, EditablePanel ); +public: + CTrainingDialog( Panel *parent ) + : EditablePanel( parent, TRAINING_DIALOG_NAME ), + m_pBackButton( NULL ), + m_pCancelButton( NULL ), + m_pGradientBgPanel( NULL ), + m_pModeSelectionPanel( NULL ), + m_pCurrentPagePanel( NULL ), + m_pBasicTraining_ClassSelectionPanel( NULL ), + m_pBasicTraining_ClassDetailsPanel( NULL ), + m_pOfflinePractice_ModeSelectionPanel( NULL ), + m_pOfflinePractice_MapSelectionPanel( NULL ), + m_pTrainingData( NULL ), + m_bStartTraining( false ), + m_bContinue( false ) + { + HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" ); + SetScheme(scheme); + SetProportional( true ); + m_pContainer = new EditablePanel( this, "Container" ); + + // load configuration + const char *filename = "resource/training.res"; + m_pTrainingData = new KeyValues( "training.res" ); + Assert( m_pTrainingData ); + if ( !m_pTrainingData->LoadFromFile( g_pFullFileSystem, filename, "MOD" ) ) + { + Warning( "Unable to load '%s'\n", filename ); + AssertMsg( 0, "Couldn't load training data!" ); + } + + C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_open", "training" ); + } + + virtual ~CTrainingDialog() + { + C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_close", "training" ); + + ivgui()->RemoveTickSignal( GetVPanel() ); + } + + virtual void SetDialogVariable( const char *pVarName, const char *pValue ) + { + m_pContainer->SetDialogVariable( pVarName, pValue ); + } + + virtual void SetDialogVariable( const char *pVarName, const wchar_t *pValue ) + { + m_pContainer->SetDialogVariable( pVarName, pValue ); + } + + virtual void SetDialogVariable( const char *pVarName, int nValue ) + { + m_pContainer->SetDialogVariable( pVarName, nValue ); + } + + virtual void SetDialogVariable( const char *pVarName, float flValue ) + { + m_pContainer->SetDialogVariable( pVarName, flValue ); + } + + void SetupButton( const char *pPanelName, CExButton **ppOut = NULL ) + { + Panel *pPanel = m_pContainer->FindChildByName( pPanelName ); + if ( pPanel ) + { + pPanel->AddActionSignalTarget( this ); + } + + if ( ppOut ) + { + *ppOut = static_cast< CExButton * >( pPanel ); + } + } + + virtual void Show() + { + SetVisible( true ); + MakePopup(); + MoveToFront(); + SetKeyBoardInputEnabled( true ); + SetMouseInputEnabled( true ); + TFModalStack()->PushModal( this ); + } + + virtual void OnThink() + { + BaseClass::OnThink(); + } + + virtual void OnCommand( const char *pCommand ) + { + C_CTFGameStats::ImmediateWriteInterfaceEvent( "on_command(training)", pCommand ); + + if ( FStrEq( pCommand, "prevpage" ) ) + { + ShowPrevPage(); + } + else if ( FStrEq( pCommand, "cancel" ) ) + { + Close(); + } + else if ( FStrEq( pCommand, "basictrainingselected" ) ) + { + BasicTraining_ShowClassSelection(); + } + else if ( FStrEq( pCommand, "offlinepracticeselected" ) ) + { + OfflinePractice_ShowPracticeMode(); + } + else if ( FStrEq( pCommand, "startbasictraining" ) ) + { + BasicTraining_Start(); + } + else if ( !V_strnicmp( pCommand, "basictraining_classselection_", 29 ) ) + { + BasicTraining_ShowClassDetailsPage( pCommand + 29 ); + } + else if ( FStrEq( pCommand, "selectcurrentgamemode" ) ) + { + OfflinePractice_ShowMapSelection(); + } + else if ( FStrEq( pCommand, "startofflinepractice" ) ) + { + OfflinePractice_Start(); + } + else + { + BaseClass::OnCommand( pCommand ); + } + } + + void SetCurrentPage( CTrainingBasePanel *pPanel, bool bGoingBack = false ) + { + AssertMsg( pPanel, "Setting current page to NULL!" ); + + pPanel->SetVisible( true ); + + if ( !bGoingBack ) + { + pPanel->SetPrevPage( m_pCurrentPagePanel ); + } + + m_pCurrentPagePanel = pPanel; + m_pCurrentPagePanel->InvalidateLayout( false, true ); + + InvalidateLayout( true, false ); + } + + void ShowPrevPage() + { + CTrainingBasePanel *pPrevPagePanel = m_pCurrentPagePanel->GetPrevPage(); + if ( pPrevPagePanel ) + { + if ( m_pCurrentPagePanel == pPrevPagePanel ) + return; + + m_pCurrentPagePanel->SetVisible( false ); + m_pCurrentPagePanel->OnBackPressed(); + SetCurrentPage( pPrevPagePanel, true ); + } + else + { + OnCommand( "cancel" ); + } + } + + void HideCurrentPage() + { + m_pCurrentPagePanel->SetVisible( false ); + } + + void BasicTraining_ShowClassSelection() + { + if ( m_pCurrentPagePanel == m_pBasicTraining_ClassSelectionPanel ) + return; + + HideCurrentPage(); + SetCurrentPage( m_pBasicTraining_ClassSelectionPanel ); + } + + int GetClassFromData( KeyValues *pClassData ) + { + const int iClass = pClassData->GetInt( "class", TF_CLASS_SOLDIER ); + if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS ) + { + return TF_CLASS_SOLDIER; + } + return iClass; + } + + static void ConfirmDialogCallback( bool bConfirmed, void *pContext ) + { + CTrainingDialog *pDialog = ( CTrainingDialog * )pContext; + if ( pDialog ) + { + pDialog->m_bContinue = bConfirmed; + pDialog->m_bStartTraining = true; + } + } + + void ConfirmContinue() + { + ShowConfirmDialog( "#TR_ContinueTitle", "#TR_ContinueMsg", "#TR_Continue", "#TR_StartOver", &ConfirmDialogCallback, NULL, this ); + } + + void BasicTraining_Start() + { + if ( !m_pTrainingData ) + return; + + KeyValues *pData = m_pTrainingData->FindKey( m_strBasicTrainingClassName.Get() ); + if ( !pData ) + return; + + // Override for soldier - if target practice is complete, start from + const int iClass = GetClassFromData( pData ); + int nProgress = Training_GetClassProgress( iClass ); + if ( iClass == TF_CLASS_SOLDIER && nProgress >= 1 ) + { + ConfirmContinue(); + return; + } + + m_bStartTraining = true; + m_bContinue = false; + } + + virtual void Think() + { + if ( !m_bStartTraining ) + return; + + KeyValues *pData = m_pTrainingData->FindKey( m_strBasicTrainingClassName.Get() ); + if ( !pData ) + return; + + // Override map if user has selected to continue + const char *pMapName = pData->GetString( "map", NULL ); + if ( m_bContinue ) + { + pMapName = "tr_dustbowl"; + } + + if ( pMapName ) + { + const int iClass = GetClassFromData( pData ); + + ConVarRef training_class( "training_class" ); + training_class.SetValue( iClass ); + + const char* pMapVideo = pData->GetString( "video", "" ); + training_map_video.SetValue( pMapVideo ); + + // create the command to execute + CFmtStr fmtMapCommand( "disconnect\nwait\nwait\n\nprogress_enable\nmap %s\n", pMapName ); + + // exec + engine->ClientCmd_Unrestricted( fmtMapCommand.Access() ); + } + + Close(); + } + + void BasicTraining_ShowClassDetailsPage( const char *pClassName ) + { + if ( m_pCurrentPagePanel == m_pBasicTraining_ClassDetailsPanel ) + return; + + HideCurrentPage(); + + m_pBasicTraining_ClassDetailsPanel->SetClass( pClassName ); + m_pBasicTraining_ClassDetailsPanel->InvalidateLayout( true, true ); + + SetCurrentPage( m_pBasicTraining_ClassDetailsPanel ); + + // Cache class + m_strBasicTrainingClassName = pClassName; + } + + void OfflinePractice_ShowPracticeMode() + { + if ( m_pCurrentPagePanel == m_pOfflinePractice_ModeSelectionPanel ) + return; + + HideCurrentPage(); + SetCurrentPage( m_pOfflinePractice_ModeSelectionPanel ); + } + + void OfflinePractice_ShowMapSelection() + { + if ( m_pCurrentPagePanel == m_pOfflinePractice_MapSelectionPanel ) + return; + + // Pass on the game mode to the map selection panel so it will only show corresponding maps + const GameMode_t nGameMode = m_pOfflinePractice_ModeSelectionPanel->GetMode(); + m_pOfflinePractice_MapSelectionPanel->SetGameMode( nGameMode ); + + HideCurrentPage(); + SetCurrentPage( m_pOfflinePractice_MapSelectionPanel ); + } + + void OfflinePractice_Start() + { + // reset server enforced cvars + g_pCVar->RevertFlaggedConVars( FCVAR_REPLICATED ); + + // Cheats were disabled; revert all cheat cvars to their default values. + // This must be done heading into multiplayer games because people can play + // demos etc and set cheat cvars with sv_cheats 0. + g_pCVar->RevertFlaggedConVars( FCVAR_CHEAT ); + + DevMsg( "FCVAR_CHEAT cvars reverted to defaults.\n" ); + + if ( m_pOfflinePractice_MapSelectionPanel->DoSetup() ) + { + // create the command to execute + CFmtStr1024 fmtMapCommand( + "disconnect\nwait\nwait\nmaxplayers %i\n\nprogress_enable\nmap %s\n", + m_pOfflinePractice_MapSelectionPanel->GetMaxPlayers(), + m_pOfflinePractice_MapSelectionPanel->GetMapName() + ); + + // exec + engine->ClientCmd_Unrestricted( fmtMapCommand.Access() ); + } + + Close(); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/ui/training/main.res" ); + + SetupButton( "CancelButton", &m_pCancelButton ); + SetupButton( "BackButton", &m_pBackButton ); + + m_pModeSelectionPanel = dynamic_cast< CModeSelectionPanel * >( m_pContainer->FindChildByName( "ModeSelectionPanel" ) ); Assert( m_pModeSelectionPanel ); + m_pBasicTraining_ClassSelectionPanel = dynamic_cast< CBasicTraining_ClassSelectionPanel * >( m_pContainer->FindChildByName( "BasicTraining_ClassSelectionPanel" ) ); Assert( m_pBasicTraining_ClassSelectionPanel ); + m_pBasicTraining_ClassDetailsPanel = dynamic_cast< CBasicTraining_ClassDetailsPanel * >( m_pContainer->FindChildByName( "BasicTraining_ClassDetailsPanel" ) ); Assert( m_pBasicTraining_ClassDetailsPanel ); + m_pOfflinePractice_ModeSelectionPanel = dynamic_cast< COfflinePractice_ModeSelectionPanel * >( m_pContainer->FindChildByName( "OfflinePractice_ModeSelectionPanel" ) ); Assert( m_pOfflinePractice_ModeSelectionPanel ); + m_pOfflinePractice_MapSelectionPanel = dynamic_cast< COfflinePractice_MapSelectionPanel * >( m_pContainer->FindChildByName( "OfflinePractice_MapSelectionPanel" ) ); Assert( m_pOfflinePractice_MapSelectionPanel ); + m_pGradientBgPanel = dynamic_cast< ImagePanel * >( m_pContainer->FindChildByName( "GradientBgPanel" ) ); + + m_pCurrentPagePanel = m_pModeSelectionPanel; + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_pCurrentPagePanel ) + { + m_pCurrentPagePanel->SetVisible( true ); + + const bool bFirstPage = m_pCurrentPagePanel->IsFirstPage(); + if ( m_pBackButton && m_pCancelButton ) + { + m_pBackButton->SetVisible( !bFirstPage ); + + int w = m_pContainer->GetWide(); + const int nBuffer = XRES( 5 ); + + int cbx, cby; + m_pCancelButton->GetPos( cbx, cby ); + + if ( bFirstPage ) + { + m_pCancelButton->SetPos( ( w - m_pCancelButton->GetWide() ) / 2, cby ); + } + else + { + m_pBackButton->SetPos( w/2 - m_pBackButton->GetWide() - nBuffer, cby ); + m_pCancelButton->SetPos( w/2 + nBuffer, cby ); + } + + if ( m_pGradientBgPanel ) + { + m_pGradientBgPanel->SetVisible( m_pCurrentPagePanel->ShouldShowGradient() ); + } + } + } + } + + virtual void OnKeyCodePressed( KeyCode code ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + if ( code == KEY_ESCAPE ) + { + OnCommand( "cancel" ); + } + else if ( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B ) + { + OnCommand( "prevpage" ); + } + else if ( code == KEY_ENTER || code == KEY_SPACE || nButtonCode == KEY_XBUTTON_A || nButtonCode == STEAMCONTROLLER_A ) + { + if ( m_pCurrentPagePanel ) + { + m_pCurrentPagePanel->Go(); + } + } + else + { + BaseClass::OnKeyCodePressed( code ); + } + } + +protected: + void Close() + { + SetVisible( false ); + TFModalStack()->PopModal( this ); + MarkForDeletion(); + } + +private: + EditablePanel *m_pContainer; + CModeSelectionPanel *m_pModeSelectionPanel; + CBasicTraining_ClassSelectionPanel *m_pBasicTraining_ClassSelectionPanel; + CBasicTraining_ClassDetailsPanel *m_pBasicTraining_ClassDetailsPanel; + COfflinePractice_ModeSelectionPanel *m_pOfflinePractice_ModeSelectionPanel; + COfflinePractice_MapSelectionPanel *m_pOfflinePractice_MapSelectionPanel; + CTrainingBasePanel *m_pCurrentPagePanel; + CTrainingBasePanel *m_pPrevPagePanel; + CExButton *m_pCancelButton; + CExButton *m_pBackButton; + ImagePanel *m_pGradientBgPanel; + KeyValues *m_pTrainingData; + CUtlString m_strBasicTrainingClassName; + bool m_bStartTraining; + bool m_bContinue; +}; + +static DHANDLE<CTrainingDialog> g_pTrainingDialog; + +//------------------------------------------------------------------------------------------------------ + +void CL_ShowTrainingDialog( const CCommand &args ) +{ + if ( g_pTrainingDialog.Get() == NULL ) + { + IViewPortPanel *pMMOverride = ( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) ); + g_pTrainingDialog = new CTrainingDialog( (CHudMainMenuOverride*)pMMOverride ); + g_pTrainingDialog->InvalidateLayout( true, true ); + } + g_pTrainingDialog->Show(); +} + +//------------------------------------------------------------------------------------------------------ + +CON_COMMAND( cl_training_class_unlock_all, "Unlock all training" ) +{ + for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) + { + Training_MarkClassComplete( i, 100 ); + } +} + +//------------------------------------------------------------------------------------------------------ + +#ifdef _DEBUG +CON_COMMAND( training_set, 0 ) +{ + if ( args.ArgC() != 3 ) + { + Warning( "Not enough arguments\n" ); + return; + } + + Training_MarkClassComplete( atoi( args[1] ), atoi( args[2] ) ); +} +#endif + +//------------------------------------------------------------------------------------------------------ + +static ConCommand training_showdlg( "training_showdlg", &CL_ShowTrainingDialog, "Displays the training dialog." );
\ No newline at end of file |