diff --git a/engine/sys_getmodes.cpp b/engine/sys_getmodes.cpp index 43dce4d0..ddc26601 100644 --- a/engine/sys_getmodes.cpp +++ b/engine/sys_getmodes.cpp @@ -820,7 +820,7 @@ void CVideoMode_Common::SetupStartupGraphic() // loading.vtf buf.Clear(); // added this Clear() because we saw cases where LoadVTF was not emptying the buf fully in the above section const char* loading = "materials/console/startup_loading.vtf"; - if ( IsSteamDeck() ) + if ( IsGamepadUI() ) loading = "materials/gamepadui/game_logo.vtf"; m_pLoadingTexture = LoadVTF( buf, loading ); if ( !m_pLoadingTexture ) @@ -887,7 +887,7 @@ void CVideoMode_Common::DrawStartupGraphic() IMaterial *pMaterial = g_pMaterialSystem->CreateMaterial( "__background", pVMTKeyValues ); const char* loading = "console/startup_loading.vtf"; - if ( IsSteamDeck() ) + if ( IsGamepadUI() ) loading = "gamepadui/game_logo.vtf"; pVMTKeyValues = new KeyValues( "UnlitGeneric" ); @@ -896,7 +896,7 @@ void CVideoMode_Common::DrawStartupGraphic() pVMTKeyValues->SetInt( "$ignorez", 1 ); pVMTKeyValues->SetInt( "$nofog", 1 ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); - pVMTKeyValues->SetInt( "$nocull", 1 ); + pVMTKeyValues->SetInt( "$nocull", 1 ); IMaterial *pLoadingMaterial = g_pMaterialSystem->CreateMaterial( "__loading", pVMTKeyValues ); int w = GetModeStereoWidth(); @@ -929,7 +929,7 @@ void CVideoMode_Common::DrawStartupGraphic() slide = 0; DrawScreenSpaceRectangle( pMaterial, 0, 0+slide, w, h-50, 0, 0, tw-1, th-1, tw, th, NULL,1,1,depth ); - if ( !IsSteamDeck() ) + if ( !IsGamepadUI() ) DrawScreenSpaceRectangle( pLoadingMaterial, w-lw, h-lh+slide/2, lw, lh, 0, 0, lw-1, lh-1, lw, lh, NULL,1,1,depth-0.1 ); else // TODO: Steam Deck @@ -978,11 +978,15 @@ void CVideoMode_Common::DrawStartupGraphic() pRenderContext->ClearColor3ub( 0, 0, 0 ); pRenderContext->ClearBuffers( true, true, true ); DrawScreenSpaceRectangle( pMaterial, 0, 0, w, h, 0, 0, tw-1, th-1, tw, th, NULL,1,1,depth ); - DrawScreenSpaceRectangle( pLoadingMaterial, w-lw, h-lh, lw, lh, 0, 0, lw-1, lh-1, lw, lh, NULL,1,1,depth ); + if(!IsGamepadUI()) + DrawScreenSpaceRectangle( pLoadingMaterial, w-lw, h-lh, lw, lh, 0, 0, lw-1, lh-1, lw, lh, NULL,1,1,depth ); + g_pMaterialSystem->SwapBuffers(); } } + + #ifdef DX_TO_GL_ABSTRACTION g_pMaterialSystem->DoStartupShaderPreloading(); #endif diff --git a/engine/vgui_baseui_interface.cpp b/engine/vgui_baseui_interface.cpp index 9fa8c899..8252ad17 100644 --- a/engine/vgui_baseui_interface.cpp +++ b/engine/vgui_baseui_interface.cpp @@ -613,7 +613,7 @@ void CEngineVGui::Init() return; } - if ( IsX360() || IsSteamDeck() ) + if ( IsX360() || IsGamepadUI() ) { CCommand ccommand; if ( CL_ShouldLoadBackgroundLevel( ccommand ) ) @@ -1273,12 +1273,18 @@ void CEngineVGui::OnLevelLoadingStarted() } } - if ( IsX360() || IsSteamDeck() ) + if ( IsX360() || !IsGamepadUI() ) { // TCR requirement, always!!! m_bShowProgressDialog = true; } + // i dont want gamepadui menu while loading + //if (IsGamepadUI()) + //{ + // m_bShowProgressDialog = false; + //} + // we've starting loading a level/connecting to a server staticGameUIFuncs->OnLevelLoadingStarted( m_bShowProgressDialog ); diff --git a/game/client/cdll_client_int.cpp b/game/client/cdll_client_int.cpp index ab16bd7c..b691ecaf 100644 --- a/game/client/cdll_client_int.cpp +++ b/game/client/cdll_client_int.cpp @@ -131,6 +131,11 @@ #include "haptics/haptic_utils.h" #include "haptics/haptic_msgs.h" +#ifdef GAMEPADUI +#include "../gamepadui/igamepadui.h" +ConVar cl_gamepadui_mainmenu_draw("cl_gamepadui_mainmenu_draw", "0", FCVAR_DEVELOPMENTONLY); +#endif // GAMEPADUI + #if defined( TF_CLIENT_DLL ) #include "abuse_report.h" #endif @@ -217,6 +222,10 @@ IEngineClientReplay *g_pEngineClientReplay = NULL; IReplaySystem *g_pReplay = NULL; #endif +#if defined(GAMEPADUI) +IGamepadUI* g_pGamepadUI = nullptr; +#endif // GAMEPADUI + IHaptics* haptics = NULL;// NVNT haptics system interface singleton //============================================================================= @@ -1156,6 +1165,54 @@ void CHLClient::PostInit() } } #endif + +#ifndef PORTAL +#if defined(GAMEPADUI) + if (IsGamepadUI()) + { + CSysModule* pGamepadUIModule = g_pFullFileSystem->LoadModule("gamepadui", "GAMEBIN", false); + if (pGamepadUIModule != nullptr) + { + GamepadUI_Log("Loaded gamepadui module.\n"); + + CreateInterfaceFn gamepaduiFactory = Sys_GetFactory(pGamepadUIModule); + if (gamepaduiFactory != nullptr) + { + g_pGamepadUI = (IGamepadUI*)gamepaduiFactory(GAMEPADUI_INTERFACE_VERSION, NULL); + if (g_pGamepadUI != nullptr) + { + GamepadUI_Log("Initializing IGamepadUI interface...\n"); + + factorylist_t factories; + FactoryList_Retrieve(factories); + g_pGamepadUI->Initialize(factories.appSystemFactory); + +#ifdef STEAM_INPUT + g_pSteamInput->SetGamepadUI(true); + g_pGamepadUI->SetSteamInput(g_pSteamInput); +#endif + } + else + { + GamepadUI_Log("Unable to pull IGamepadUI interface.\n"); + } + } + else + { + GamepadUI_Log("Unable to get gamepadui factory.\n"); + } + } + else + { + GamepadUI_Log("Unable to load gamepadui module\n"); + } +} +#endif // GAMEPADUI +#else + if (IsGamepadUI()) + GamepadUI_Log("This version of GamepadUI doesnt work with portal 1. Idk why."); +#endif // !PORTAL + } //----------------------------------------------------------------------------- @@ -1196,6 +1253,11 @@ void CHLClient::Shutdown( void ) UncacheAllMaterials(); IGameSystem::ShutdownAllSystems(); + +#if defined(GAMEPADUI) + if (g_pGamepadUI != nullptr) + g_pGamepadUI->Shutdown(); +#endif // GAMEPADUI gHUD.Shutdown(); VGui_Shutdown(); @@ -1243,6 +1305,11 @@ int CHLClient::HudVidInit( void ) GetClientVoiceMgr()->VidInit(); +#if defined(GAMEPADUI) + if (g_pGamepadUI != nullptr) + g_pGamepadUI->VidInit(); +#endif // GAMEPADUI + return 1; } @@ -1293,6 +1360,11 @@ void CHLClient::HudUpdate( bool bActive ) g_pSixenseInput->SixenseFrame( 0, NULL ); } #endif + +#if defined(GAMEPADUI) + if (g_pGamepadUI != nullptr) + g_pGamepadUI->OnUpdate(frametime); +#endif // GAMEPADUI } //----------------------------------------------------------------------------- @@ -1301,6 +1373,7 @@ void CHLClient::HudUpdate( bool bActive ) void CHLClient::HudReset( void ) { gHUD.VidInit(); + PhysicsReset(); } @@ -1640,6 +1713,11 @@ void CHLClient::LevelInitPreEntity( char const* pMapName ) CReplayRagdollRecorder::Instance().Init(); } #endif + +#if defined(GAMEPADUI) + if (g_pGamepadUI != nullptr) + g_pGamepadUI->OnLevelInitializePreEntity(); +#endif // GAMEPADUI } @@ -1651,6 +1729,11 @@ void CHLClient::LevelInitPostEntity( ) IGameSystem::LevelInitPostEntityAllSystems(); C_PhysPropClientside::RecreateAll(); internalCenterPrint->Clear(); + +#if defined(GAMEPADUI) + if (g_pGamepadUI != nullptr) + g_pGamepadUI->OnLevelInitializePostEntity(); +#endif // GAMEPADUI } //----------------------------------------------------------------------------- @@ -1717,6 +1800,11 @@ void CHLClient::LevelShutdown( void ) StopAllRumbleEffects(); +#if defined(GAMEPADUI) + if (g_pGamepadUI != nullptr) + g_pGamepadUI->OnLevelShutdown(); +#endif // GAMEPADUI + gHUD.LevelShutdown(); internalCenterPrint->Clear(); diff --git a/game/client/client_base.vpc b/game/client/client_base.vpc index 90e16cb8..1c49c8a9 100644 --- a/game/client/client_base.vpc +++ b/game/client/client_base.vpc @@ -55,6 +55,8 @@ $Configuration $PreprocessorDefinitions "$BASE;ENABLE_CHROMEHTMLWINDOW;fopen=dont_use_fopen" [$WIN32] $PreprocessorDefinitions "$BASE;ENABLE_CHROMEHTMLWINDOW;" [$OSXALL] $PreprocessorDefinitions "$BASE;ENABLE_CHROMEHTMLWINDOW;USE_WEBM_FOR_REPLAY;" [$LINUXALL] + $PreprocessorDefinitions "$BASE;GAMEPADUI_DLL;" + $PreprocessorDefinitions "$BASE;GAMEPADUI;" $PreprocessorDefinitions "$BASE;CURL_STATICLIB" [$WIN32 && $BUILD_REPLAY] $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" $Create/UsePCHThroughFile "cbase.h" diff --git a/game/client/hl1/hl1_clientmode.cpp b/game/client/hl1/hl1_clientmode.cpp index 55076221..b5878606 100644 --- a/game/client/hl1/hl1_clientmode.cpp +++ b/game/client/hl1/hl1_clientmode.cpp @@ -13,7 +13,11 @@ // default FOV for HL1 ConVar default_fov( "default_fov", "90", FCVAR_CHEAT ); -ConVar fov_desired( "fov_desired", "90", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, 75.0, true, 110.0 ); +#ifdef GAMEPADUI +ConVar fov_desired("fov_desired", "75", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, 75.0, true, 120.0); +#else +ConVar fov_desired("fov_desired", "75", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, 75.0, true, 110.0); +#endif // GAMEPADUI // The current client mode. Always ClientModeNormal in HL. IClientMode *g_pClientMode = NULL; diff --git a/game/client/hl2/clientmode_hlnormal.cpp b/game/client/hl2/clientmode_hlnormal.cpp index ad3989c3..36c636a4 100644 --- a/game/client/hl2/clientmode_hlnormal.cpp +++ b/game/client/hl2/clientmode_hlnormal.cpp @@ -19,7 +19,11 @@ extern bool g_bRollingCredits; +#ifdef GAMEPADUI +ConVar fov_desired("fov_desired", "75", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, 75.0, true, 120.0); +#else ConVar fov_desired( "fov_desired", "75", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, 75.0, true, 110.0 ); +#endif // GAMEPADUI //----------------------------------------------------------------------------- // Globals diff --git a/game/client/hud_crosshair.cpp b/game/client/hud_crosshair.cpp index 6dd5e2f6..1cb8048a 100644 --- a/game/client/hud_crosshair.cpp +++ b/game/client/hud_crosshair.cpp @@ -259,7 +259,7 @@ void CHudCrosshair::Paint( void ) } int iScreenDiv = 1600; - if ( IsSteamDeck() ) + if ( IsGamepadUI() ) iScreenDiv = 1440; float flPlayerScale; diff --git a/game/client/portal/clientmode_portal.cpp b/game/client/portal/clientmode_portal.cpp index e7cdc525..64450e8a 100644 --- a/game/client/portal/clientmode_portal.cpp +++ b/game/client/portal/clientmode_portal.cpp @@ -20,7 +20,11 @@ // default FOV for HL2 ConVar default_fov( "default_fov", "75", FCVAR_CHEAT ); -ConVar fov_desired( "fov_desired", "75", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, 75.0, true, 110.0 ); +#ifdef GAMEPADUI +ConVar fov_desired("fov_desired", "75", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, 75.0, true, 120.0); +#else +ConVar fov_desired("fov_desired", "75", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, 75.0, true, 110.0); +#endif // GAMEPADUI // The current client mode. Always ClientModeNormal in HL. IClientMode *g_pClientMode = NULL; diff --git a/game/gamepadui/gamepadui_achievements.cpp b/game/gamepadui/gamepadui_achievements.cpp new file mode 100644 index 00000000..ca7b50b1 --- /dev/null +++ b/game/gamepadui/gamepadui_achievements.cpp @@ -0,0 +1,324 @@ + +#include "gamepadui_button.h" +#include "gamepadui_frame.h" +#include "gamepadui_scroll.h" +#include "gamepadui_interface.h" +#include "gamepadui_image.h" + +#include "ienginevgui.h" +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" +#include "fmtstr.h" + +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/ImagePanel.h" +#include "vgui_controls/ScrollBar.h" + +#include "KeyValues.h" +#include "filesystem.h" + +#include "tier0/memdbgon.h" + +class GamepadUIAchievement; + +class GamepadUIAchievementsPanel : public GamepadUIFrame +{ + DECLARE_CLASS_SIMPLE( GamepadUIAchievementsPanel, GamepadUIFrame ); + +public: + GamepadUIAchievementsPanel( vgui::Panel *pParent, const char* pPanelName ); + + void UpdateGradients() OVERRIDE; + + void OnThink() OVERRIDE; + void OnCommand( char const* pCommand ) OVERRIDE; + + MESSAGE_FUNC_HANDLE( OnGamepadUIButtonNavigatedTo, "OnGamepadUIButtonNavigatedTo", button ); + + void LayoutAchievementPanels(); + + void OnMouseWheeled( int nDelta ) OVERRIDE; + +private: + CUtlVector< GamepadUIAchievement* > m_pAchievementPanels; + + GamepadUIScrollState m_ScrollState; + + int m_nTotalAchievements = 0; + int m_nUnlockedAchievements = 0; + + GAMEPADUI_PANEL_PROPERTY( float, m_AchievementsFade, "Achievements.Fade", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_AchievementsOffsetX, "Achievements.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_AchievementsOffsetY, "Achievements.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flAchievementsSpacing, "Achievements.Spacing", "0", SchemeValueTypes::ProportionalFloat ); +}; + +class GamepadUIAchievement : public GamepadUIButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUIAchievement, GamepadUIButton ); + + GamepadUIAchievement( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char* pSchemeFile, const char* pCommand, const char* pText, const char* pDescription, const char *pChapterImage ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_Image( pChapterImage ) + { + } + + GamepadUIAchievement( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char* pSchemeFile, const char* pCommand, const wchar* pText, const wchar* pDescription, const char *pChapterImage ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_Image( pChapterImage ) + { + } + + void ApplySchemeSettings( vgui::IScheme* pScheme ) OVERRIDE + { + BaseClass::ApplySchemeSettings( pScheme ); + + m_hProgressFont = pScheme->GetFont( "Button.Progress.Font", true ); + } + + void Paint() OVERRIDE + { + int x, y, w, t; + GetBounds( x, y, w, t ); + + PaintButton(); + + vgui::surface()->DrawSetColor( m_colProgressColor ); + vgui::surface()->DrawFilledRect( 0, GetDrawHeight() - m_flProgressHeight, m_flWidth * m_flProgress, GetDrawHeight() ); + + if ( m_nGoal > 1 && m_flProgress > 0.0f ) + { + vgui::surface()->DrawSetColor( m_colUnprogressColor ); + vgui::surface()->DrawFilledRect( m_flWidth * m_flProgress, GetDrawHeight() - m_flProgressHeight, m_flWidth, GetDrawHeight() ); + } + + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawSetTexture( m_Image ); + int nImageSize = m_flHeight - m_flIconInset * 2; + vgui::surface()->DrawTexturedRect( m_flIconInset, m_flIconInset, m_flIconInset + nImageSize, m_flIconInset + nImageSize ); + vgui::surface()->DrawSetTexture( 0 ); + + PaintText(); + + if ( m_nGoal > 1 ) + { + wchar_t wcsProgress[32]; + int nLength = V_swprintf_safe( wcsProgress, L"%d/%d", m_nCount, m_nGoal ); + vgui::surface()->DrawSetTextColor( m_colTextColor ); + vgui::surface()->DrawSetTextFont( m_hProgressFont ); + + int32 textSizeX = 0, textSizeY = 0; + vgui::surface()->GetTextSize( m_hProgressFont, wcsProgress, textSizeX, textSizeY ); + int textPosX = m_flWidth - textSizeX - m_flProgressOffsetX; + int textPosY = GetDrawHeight() / 2 - textSizeY / 2; + + vgui::surface()->DrawSetTextPos( textPosX, textPosY ); + vgui::surface()->DrawPrintText( wcsProgress, nLength ); + } + } + + void SetProgress( bool bAchieved, int nCount, int nGoal ) + { + m_nCount = nCount; + m_nGoal = nGoal; + + if (bAchieved) + { + m_flProgress = 1.0f; + m_nCount = m_nGoal; + } + else + m_flProgress = min( float( nCount ) / float( nGoal ), 1.0f ); + } + +private: + GamepadUIImage m_Image; + + float m_flProgress = 0.0f; + int m_nCount = 0; + int m_nGoal = 0; + + vgui::HFont m_hProgressFont = vgui::INVALID_FONT; + + GAMEPADUI_PANEL_PROPERTY( Color, m_colProgressColor, "Button.Background.Progress", "255 0 0 255", SchemeValueTypes::Color ); + GAMEPADUI_PANEL_PROPERTY( Color, m_colUnprogressColor, "Button.Background.Unprogress", "255 0 0 255", SchemeValueTypes::Color ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flProgressHeight, "Button.Progress.Height", "1", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flIconInset, "Button.Icon.Inset", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flProgressOffsetX, "Button.Progress.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); +}; + +GamepadUIAchievementsPanel::GamepadUIAchievementsPanel( vgui::Panel *pParent, const char* pPanelName ) : BaseClass( pParent, pPanelName ) +{ + vgui::HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_DEFAULT_PANEL_SCHEME, "SchemePanel" ); + SetScheme( hScheme ); + + GetFrameTitle() = GamepadUIString( "#GameUI_Achievements_Title" ); + SetFooterButtons( FooterButtons::Back ); + + Activate(); + + GamepadUI::GetInstance().GetAchievementMgr()->EnsureGlobalStateLoaded(); + int nAllAchievements = GamepadUI::GetInstance().GetAchievementMgr()->GetAchievementCount(); + for ( int i = 0; i < nAllAchievements; i++ ) + { + IAchievement *pCurAchievement = GamepadUI::GetInstance().GetAchievementMgr()->GetAchievementByIndex( i ); + if ( !pCurAchievement ) + continue; + + if ( pCurAchievement->IsAchieved() ) + m_nUnlockedAchievements++; + + // Don't show hidden achievements if not achieved. + if ( pCurAchievement->ShouldHideUntilAchieved() && !pCurAchievement->IsAchieved() ) + continue; + + char szIconName[MAX_PATH]; + V_sprintf_safe( szIconName, "gamepadui/achievements/%s%s.vmt", pCurAchievement->GetName(), pCurAchievement->IsAchieved() ? "" : "_bw" ); + + char szMaterialName[MAX_PATH]; + V_sprintf_safe( szMaterialName, "materials/%s", szIconName ); + if ( !g_pFullFileSystem->FileExists( szMaterialName ) ) + V_sprintf_safe( szIconName, "vgui/achievements/%s%s.vmt", pCurAchievement->GetName(), pCurAchievement->IsAchieved() ? "" : "_bw" ); + + V_sprintf_safe( szMaterialName, "materials/%s", szIconName ); + if ( !g_pFullFileSystem->FileExists( szMaterialName ) ) + V_strcpy_safe( szIconName, "vgui/hud/icon_locked.vmt" ); + + auto pAchievementPanel = new GamepadUIAchievement( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemeachievement.res", + "", + ACHIEVEMENT_LOCALIZED_NAME( pCurAchievement ), + ACHIEVEMENT_LOCALIZED_DESC( pCurAchievement ), + szIconName ); + pAchievementPanel->SetProgress( pCurAchievement->IsAchieved(), pCurAchievement->GetCount(), pCurAchievement->GetGoal() ); + pAchievementPanel->SetPriority( m_nTotalAchievements ); + m_pAchievementPanels.AddToTail( pAchievementPanel ); + + m_nTotalAchievements++; + } + + if ( m_pAchievementPanels.Count() ) + m_pAchievementPanels[0]->NavigateTo(); + + for ( int i = 1; i < m_pAchievementPanels.Count(); i++ ) + { + m_pAchievementPanels[i]->SetNavUp( m_pAchievementPanels[i - 1] ); + m_pAchievementPanels[i - 1]->SetNavDown( m_pAchievementPanels[i] ); + } + + UpdateGradients(); +} + +void GamepadUIAchievementsPanel::UpdateGradients() +{ + const float flTime = GamepadUI::GetInstance().GetTime(); + GamepadUI::GetInstance().GetGradientHelper()->ResetTargets( flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Up, { 1.0f, 1.0f }, flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Down, { 1.0f, 0.5f }, flTime ); +} + +void GamepadUIAchievementsPanel::OnThink() +{ + BaseClass::OnThink(); + + LayoutAchievementPanels(); +} + +void GamepadUIAchievementsPanel::OnGamepadUIButtonNavigatedTo( vgui::VPANEL button ) +{ + GamepadUIButton *pButton = dynamic_cast< GamepadUIButton * >( vgui::ipanel()->GetPanel( button, GetModuleName() ) ); + if ( pButton ) + { + if ( pButton->GetAlpha() != 255 ) + { + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + int nX, nY; + pButton->GetPos( nX, nY ); + + int nTargetY = pButton->GetPriority() * (pButton->m_flHeightAnimationValue[ButtonStates::Out] + m_flAchievementsSpacing); + + if ( nY < nParentH / 2 ) + { + nTargetY += nParentH - m_AchievementsOffsetY; + // Add a bit of spacing to make this more visually appealing :) + nTargetY -= m_flAchievementsSpacing; + } + else + { + nTargetY += pButton->GetMaxHeight(); + // Add a bit of spacing to make this more visually appealing :) + nTargetY += (pButton->GetMaxHeight() / 2) + 2 * m_flAchievementsSpacing; + } + + + m_ScrollState.SetScrollTarget( nTargetY - ( nParentH - m_AchievementsOffsetY ), GamepadUI::GetInstance().GetTime() ); + } + } +} + +void GamepadUIAchievementsPanel::LayoutAchievementPanels() +{ + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + float flScrollClamp = 0.0f; + for ( int i = 0; i < m_pAchievementPanels.Count(); i++ ) + { + int size = ( m_pAchievementPanels[i]->GetTall() + m_flAchievementsSpacing ); + + if ( i < m_pAchievementPanels.Count() - 2 ) + flScrollClamp += size; + } + + m_ScrollState.UpdateScrollBounds( 0.0f, flScrollClamp ); + + int previousSizes = 0; + for ( int i = 0; i < m_pAchievementPanels.Count(); i++ ) + { + int tall = m_pAchievementPanels[i]->GetTall(); + int size = ( tall + m_flAchievementsSpacing ); + + int y = m_AchievementsOffsetY + previousSizes - m_ScrollState.GetScrollProgress(); + int fade = 255; + if ( y < m_AchievementsOffsetY ) + fade = ( 1.0f - clamp( -( y - m_AchievementsOffsetY ) / m_AchievementsFade, 0.0f, 1.0f ) ) * 255.0f; + if ( y > nParentH - m_AchievementsFade ) + fade = ( 1.0f - clamp(( y - ( nParentH - m_AchievementsFade - size ) ) / m_AchievementsFade, 0.0f, 1.0f ) ) * 255.0f; + if ( m_pAchievementPanels[i]->HasFocus() && fade != 0 ) + fade = 255; + m_pAchievementPanels[i]->SetAlpha( fade ); + m_pAchievementPanels[i]->SetPos( m_AchievementsOffsetX, y ); + m_pAchievementPanels[i]->SetVisible( true ); + previousSizes += size; + } + + m_ScrollState.UpdateScrolling( 2.0f, GamepadUI::GetInstance().GetTime() ); +} + +void GamepadUIAchievementsPanel::OnCommand( char const* pCommand ) +{ + if ( !V_strcmp( pCommand, "action_back" ) ) + { + Close(); + } + else + { + BaseClass::OnCommand( pCommand ); + } +} + +void GamepadUIAchievementsPanel::OnMouseWheeled( int nDelta ) +{ + m_ScrollState.OnMouseWheeled( nDelta * 160.0f, GamepadUI::GetInstance().GetTime() ); +} + +CON_COMMAND( gamepadui_openachievementsdialog, "" ) +{ + new GamepadUIAchievementsPanel( GamepadUI::GetInstance().GetBasePanel(), "AchievementsPanel" ); +} diff --git a/game/gamepadui/gamepadui_base.vpc b/game/gamepadui/gamepadui_base.vpc new file mode 100644 index 00000000..efbecd48 --- /dev/null +++ b/game/gamepadui/gamepadui_base.vpc @@ -0,0 +1,137 @@ +//----------------------------------------------------------------------------- +// GAMEPADUI_BASE.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro OUTBINDIR "$SRCDIR\..\game\$GAMENAME\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration "Debug" +{ + $General + { + $OutputDirectory ".\Debug_$GAMENAME" [$WINDOWS] + $IntermediateDirectory ".\Debug_$GAMENAME" [$WINDOWS] + } +} + +$Configuration "Release" +{ + $General + { + $OutputDirectory ".\Release_$GAMENAME" [$WINDOWS] + $IntermediateDirectory ".\Release_$GAMENAME" [$WINDOWS] + } +} + +$Configuration +{ + $General + { + $OutputDirectory ".\$GAMENAME" [$OSXALL] + } + + $Compiler + { + $AdditionalIncludeDirectories "$BASE;.\;$SRCDIR\vgui2\include;$SRCDIR\vgui2\controls;$SRCDIR\game\gamepadui;..\..\public" + $PreprocessorDefinitions "$BASE;GAMEPADUI_DLL;VERSION_SAFE_STEAM_API_INTERFACES;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead" + } + + $Linker + { + $SystemLibraries "iconv" [$OSXALL] + $SystemFrameworks "Carbon" [$OSXALL] + $SystemLibraries "rt" [$LINUXALL] + $IgnoreImportLibrary "TRUE" + $AdditionalDependencies "$BASE winmm.lib" [$WINDOWS] + $AdditionalDependencies "$BASE wsock32.lib Ws2_32.lib" [$BUILD_REPLAY] + } +} + +$Project +{ + $Folder "Header Files" + { + $Folder "Public" + { + $File "igamepadui.h" + } + + $File "gamepadui_basepanel.h" + $File "gamepadui_button.h" + $File "gamepadui_frame.h" + $File "gamepadui_genericconfirmation.h" + $File "gamepadui_genericframes.h" + $File "gamepadui_gradient_helper.h" + $File "gamepadui_image.h" + $File "gamepadui_interface.h" + $File "gamepadui_mainmenu.h" + $File "gamepadui_panel.h" + $File "gamepadui_scroll.h" + $File "gamepadui_string.h" + $File "gamepadui_scrollbar.h" + $File "gamepadui_util.h" + } + + $Folder "Source Files" + { + $Folder "Common" + { + $File "$SRCDIR\public\vgui_controls\vgui_controls.cpp" + $File "$SRCDIR\common\language.cpp" + } + + $File "gamepadui_achievements.cpp" + $File "gamepadui_basepanel.cpp" + $File "gamepadui_button.cpp" + $File "gamepadui_frame.cpp" + $File "gamepadui_genericconfirmation.cpp" + $File "gamepadui_genericframes.cpp" + $File "gamepadui_interface.cpp" + $File "gamepadui_mainmenu.cpp" + $File "gamepadui_newgame.cpp" + $File "gamepadui_savegame.cpp" + $File "gamepadui_options.cpp" + $File "gamepadui_scrollbar.cpp" + $File "gamepadui_util.cpp" + } + + $Folder "Link Libraries" + { + $Lib bitmap + $Lib choreoobjects + $Lib mathlib + $Lib matsys_controls + $Lib tier1 + $Lib tier2 + $Lib tier3 + $Lib vgui_controls + $Lib vtf + + $Lib expanded_steam [$STEAM_INPUT] + $ImpLib steam_api [$STEAM_INPUT] + $Lib "$LIBCOMMON\libpng" [$STEAM_INPUT] + + // these are only necessary if HL2_RETAIL is enabled. + $ImpLib steam_api [$HL2_RETAIl] + $Lib "$LIBCOMMON/libjpeg" [$HL2_RETAIl] + $Lib libpng [$HL2_RETAIl] + + $Lib $LIBCOMMON/libcrypto [$POSIX] + + $ImpLib "$LIBCOMMON\curl" [$OSXALL] + + $Lib "$LIBCOMMON\libcurl" [$WIN32] + $Lib "libz" [$WINDOWS] + + $ImpLib "SDL2" [$SDL] + + $Libexternal libz [$LINUXALL] + $Libexternal "$LIBCOMMON/libcurl" [$LINUXALL] + $Libexternal "$LIBCOMMON/libcurlssl" [$LINUXALL] + $Libexternal "$LIBCOMMON/libssl" [$LINUXALL] + } + +} diff --git a/game/gamepadui/gamepadui_basepanel.cpp b/game/gamepadui/gamepadui_basepanel.cpp new file mode 100644 index 00000000..3f0fb8d9 --- /dev/null +++ b/game/gamepadui/gamepadui_basepanel.cpp @@ -0,0 +1,243 @@ +#include "gamepadui_basepanel.h" +#include "gamepadui_mainmenu.h" + +#ifdef _WIN32 +#ifdef INVALID_HANDLE_VALUE +#undef INVALID_HANDLE_VALUE +#endif +#include +#else +#include +#endif + +#include "icommandline.h" +#include "filesystem.h" +#include "gamepadui_interface.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar gamepadui_background_music_duck( "gamepadui_background_music_duck", "0.35", FCVAR_ARCHIVE ); +ConVar gamepadui_sizing_panel_width( "gamepadui_sizing_panel_width", "1280", FCVAR_ARCHIVE ); +ConVar gamepadui_sizing_panel_height( "gamepadui_sizing_panel_height", "800", FCVAR_ARCHIVE ); + +GamepadUIBasePanel::GamepadUIBasePanel( vgui::VPANEL parent ) : BaseClass( NULL, "GamepadUIBasePanel" ) +{ + SetParent( parent ); + MakePopup( false ); + + m_nBackgroundMusicGUID = 0; + m_bBackgroundMusicEnabled = !CommandLine()->FindParm( "-nostartupsound" ); + + m_pSizingPanel = new GamepadUISizingPanel( this ); + + m_pMainMenu = new GamepadUIMainMenu( this ); + OnMenuStateChanged(); +} + +void GamepadUIBasePanel::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // Josh: Need to use GetVParent because this is across + // a DLL boundary. + int nVParentW, nVParentH; + vgui::ipanel()->GetSize( GetVParent(), nVParentW, nVParentH ); + SetBounds( 0, 0, nVParentW, nVParentH ); + + // Josh: + // Force the main menu to invalidate itself. + // There is a weird ordering bug in VGUI we need to workaround. + m_pMainMenu->InvalidateLayout( false, true ); + + m_pSizingPanel->InvalidateLayout( false, true ); +} + +GamepadUISizingPanel *GamepadUIBasePanel::GetSizingPanel() const +{ + return m_pSizingPanel; +} + +GamepadUIMainMenu* GamepadUIBasePanel::GetMainMenuPanel() const +{ + return m_pMainMenu; +} + +GamepadUIFrame *GamepadUIBasePanel::GetCurrentFrame() const +{ + return m_pCurrentFrame; +} + +void GamepadUIBasePanel::SetCurrentFrame( GamepadUIFrame *pFrame ) +{ + if (pFrame != NULL && m_pCurrentFrame != NULL) + { + // If there's already a frame, close it + m_pCurrentFrame->Close(); + } + + m_pCurrentFrame = pFrame; +} + + +void GamepadUIBasePanel::OnMenuStateChanged() +{ + if ( m_bBackgroundMusicEnabled && GamepadUI::GetInstance().IsGamepadUIVisible() ) + { + if ( !IsBackgroundMusicPlaying() ) + ActivateBackgroundEffects(); + } + else + ReleaseBackgroundMusic(); + + if (m_pCurrentFrame && m_pCurrentFrame != m_pMainMenu) + { + m_pCurrentFrame->Close(); + m_pCurrentFrame = NULL; + } +} + +void GamepadUIBasePanel::ActivateBackgroundEffects() +{ + StartBackgroundMusic( 1.0f ); +} + +bool GamepadUIBasePanel::IsBackgroundMusicPlaying() +{ + if ( !m_nBackgroundMusicGUID ) + return false; + + return GamepadUI::GetInstance().GetEngineSound()->IsSoundStillPlaying( m_nBackgroundMusicGUID ); +} + +bool GamepadUIBasePanel::StartBackgroundMusic( float flVolume ) +{ + if ( IsBackgroundMusicPlaying() ) + return true; + + /* mostly from GameUI */ + char path[ 512 ]; + Q_snprintf( path, sizeof( path ), "sound/ui/gamestartup*.mp3" ); + Q_FixSlashes( path ); + CUtlVector fileNames; + FileFindHandle_t fh; + + char const *fn = g_pFullFileSystem->FindFirstEx( path, "MOD", &fh ); + if ( fn ) + { + do + { + char ext[ 10 ]; + Q_ExtractFileExtension( fn, ext, sizeof( ext ) ); + + if ( !Q_stricmp( ext, "mp3" ) ) + { + char temp[ 512 ]; + { + Q_snprintf( temp, sizeof( temp ), "ui/%s", fn ); + } + + char *found = new char[ strlen( temp ) + 1 ]; + Q_strncpy( found, temp, strlen( temp ) + 1 ); + + Q_FixSlashes( found ); + fileNames.AddToTail( found ); + } + + fn = g_pFullFileSystem->FindNext( fh ); + + } while ( fn ); + + g_pFullFileSystem->FindClose( fh ); + } + + if ( !fileNames.Count() ) + return false; + +#ifdef WIN32 + SYSTEMTIME SystemTime; + GetSystemTime( &SystemTime ); + int index = SystemTime.wMilliseconds % fileNames.Count(); +#else + struct timeval tm; + gettimeofday( &tm, NULL ); + int index = tm.tv_usec/1000 % fileNames.Count(); +#endif + + const char* pSoundFile = NULL; + + if ( fileNames.IsValidIndex(index) && fileNames[index] ) + pSoundFile = fileNames[ index ]; + + if ( !pSoundFile ) + return false; + + // check and see if we have a background map loaded. + // if not, this code path won't properly play the music. + const bool bInGame = GamepadUI::GetInstance().GetEngineClient()->IsLevelMainMenuBackground(); + if ( bInGame ) + { + // mixes too loud against soft ui sounds + GamepadUI::GetInstance().GetEngineSound()->EmitAmbientSound( pSoundFile, gamepadui_background_music_duck.GetFloat() * flVolume ); + m_nBackgroundMusicGUID = GamepadUI::GetInstance().GetEngineSound()->GetGuidForLastSoundEmitted(); + } + else + { + // old way, failsafe in case we don't have a background level. + char found[ 512 ]; + Q_snprintf( found, sizeof( found ), "play *#%s\n", pSoundFile ); + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( found ); + } + + fileNames.PurgeAndDeleteElements(); + + return m_nBackgroundMusicGUID != 0; +} + +void GamepadUIBasePanel::ReleaseBackgroundMusic() +{ + if ( !m_nBackgroundMusicGUID ) + return; + + // need to stop the sound now, do not queue the stop + // we must release the 2-5MB held by this resource + GamepadUI::GetInstance().GetEngineSound()->StopSoundByGuid( m_nBackgroundMusicGUID ); + m_nBackgroundMusicGUID = 0; +} + +GamepadUISizingPanel::GamepadUISizingPanel( vgui::Panel *pParent ) : BaseClass( pParent, "GamepadUISizingPanel" ) +{ + SetVisible( false ); +} + +void GamepadUISizingPanel::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + int w = GetParent()->GetWide(); + int h = GetParent()->GetTall(); + + float flX, flY; + GamepadUI::GetInstance().GetScreenRatio( flX, flY ); + + float targetW = gamepadui_sizing_panel_width.GetFloat() * flX; + float targetH = gamepadui_sizing_panel_height.GetFloat() * flY; + + w -= targetW; + h -= targetH; + if (w <= 0 || h <= 0) + { + GamepadUI_Log( "Setting sizing panel bounds to 0, 0, %i, %i (proportional)\n", GetParent()->GetWide(), GetParent()->GetTall() ); + SetBounds( 0, 0, GetParent()->GetWide(), GetParent()->GetTall() ); + + m_flScaleX = m_flScaleY = 1.0f; + } + else + { + GamepadUI_Log( "Setting sizing panel bounds to %i, %i, %i, %i\n", w/2, h/2, (int)targetW, (int)targetH ); + SetBounds( w/2, h/2, targetW, targetH ); + + m_flScaleX = ((float)GetParent()->GetWide()) / targetW; + m_flScaleY = ((float)GetParent()->GetTall()) / targetH; + } +} diff --git a/game/gamepadui/gamepadui_basepanel.h b/game/gamepadui/gamepadui_basepanel.h new file mode 100644 index 00000000..5394ccb2 --- /dev/null +++ b/game/gamepadui/gamepadui_basepanel.h @@ -0,0 +1,60 @@ +#ifndef GAMEPADUI_BASEPANEL_H +#define GAMEPADUI_BASEPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gamepadui_interface.h" + +class GamepadUIMainMenu; +class GamepadUIFrame; + +class GamepadUIBasePanel : public vgui::Panel +{ + DECLARE_CLASS_SIMPLE( GamepadUIBasePanel, vgui::Panel ); +public: + GamepadUIBasePanel( vgui::VPANEL parent ); + + void ApplySchemeSettings( vgui::IScheme* pScheme ) OVERRIDE; + + GamepadUISizingPanel *GetSizingPanel() const; + GamepadUIMainMenu *GetMainMenuPanel() const; + + GamepadUIFrame *GetCurrentFrame() const; + void SetCurrentFrame( GamepadUIFrame *pFrame ); + + void OnMenuStateChanged(); + + void ActivateBackgroundEffects(); + bool IsBackgroundMusicPlaying(); + bool StartBackgroundMusic( float flVolume ); + void ReleaseBackgroundMusic(); + +private: + GamepadUISizingPanel *m_pSizingPanel = NULL; + GamepadUIMainMenu *m_pMainMenu = NULL; + + GamepadUIFrame *m_pCurrentFrame = NULL; + + int m_nBackgroundMusicGUID; + bool m_bBackgroundMusicEnabled; + +}; + +class GamepadUISizingPanel : public vgui::Panel +{ + DECLARE_CLASS_SIMPLE( GamepadUISizingPanel, vgui::Panel ); +public: + GamepadUISizingPanel( vgui::Panel *pParent ); + + void ApplySchemeSettings( vgui::IScheme* pScheme ) OVERRIDE; + + void GetScale( float &flX, float &flY ) const { flX = m_flScaleX; flY = m_flScaleY; } + +private: + + float m_flScaleX; + float m_flScaleY; +}; + +#endif // GAMEPADUI_BASEPANEL_H diff --git a/game/gamepadui/gamepadui_bonusmaps.cpp b/game/gamepadui/gamepadui_bonusmaps.cpp new file mode 100644 index 00000000..15e788cb --- /dev/null +++ b/game/gamepadui/gamepadui_bonusmaps.cpp @@ -0,0 +1,1375 @@ +#include "gamepadui_interface.h" +#include "gamepadui_image.h" +#include "gamepadui_util.h" +#include "gamepadui_frame.h" +#include "gamepadui_scrollbar.h" +#include "gamepadui_genericconfirmation.h" + +#include "ienginevgui.h" +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" + +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/ImagePanel.h" +#include "vgui_controls/ScrollBar.h" + +#include "KeyValues.h" +#include "filesystem.h" + +#include "tier0/memdbgon.h" + + +#if defined ( GAMEPADUI_ENABLE_BONUSMAPS ) // SDK2013 lacks the necessary files to compile this. (Madi) + +#define SDK_2013_HACKS MAPBASE + +#if SDK_2013_HACKS +struct ChallengeDescription_t +{ + int iBest; + int iGold; + int iSilver; + int iBronze; + char szName[64]; + char szComment[64]; + int iType; +}; + +struct BonusMapDescription_t +{ + char szMapName[128]; + char szMapFileName[128]; + char szImageName[128]; + char szComment[256]; + char szFileName[128]; + + bool bIsFolder; + bool bComplete; + bool bLocked; + + CUtlVector m_Challenges; + + BonusMapDescription_t *m_pParent; +}; +#endif + +class GamepadUIBonusButton; + +#define GAMEPADUI_BONUS_SCHEME GAMEPADUI_RESOURCE_FOLDER "schemebonusbutton.res" + +#define NUM_CHALLENGES 5 + +ConVar gamepadui_selected_challenge("gamepadui_selected_challenge", "0", FCVAR_ARCHIVE); + +const char g_pszMedalNames[4][8] = +{ + "none", + "bronze", + "silver", + "gold" +}; + +void GetChallengeMedals( const ChallengeDescription_t *pChallengeDescription, int &iBest, int &iEarnedMedal, int &iNext, int &iNextMedal ) +{ + iBest = pChallengeDescription->iBest; + + if ( iBest == -1 ) + iEarnedMedal = 0; + else if ( iBest <= pChallengeDescription->iGold ) + iEarnedMedal = 3; + else if ( iBest <= pChallengeDescription->iSilver ) + iEarnedMedal = 2; + else if ( iBest <= pChallengeDescription->iBronze ) + iEarnedMedal = 1; + else + iEarnedMedal = 0; + + iNext = -1; + + switch ( iEarnedMedal ) + { + case 0: + iNext = pChallengeDescription->iBronze; + iNextMedal = 1; + break; + case 1: + iNext = pChallengeDescription->iSilver; + iNextMedal = 2; + break; + case 2: + iNext = pChallengeDescription->iGold; + iNextMedal = 3; + break; + case 3: + iNext = -1; + iNextMedal = -1; + break; + } +} + +void CycleSelectedChallenge() +{ + int nChallenge = Clamp( gamepadui_selected_challenge.GetInt(), 0, NUM_CHALLENGES-1 ); + nChallenge = ( nChallenge + 1 ) % NUM_CHALLENGES; + gamepadui_selected_challenge.SetValue( nChallenge ); +} + +class GamepadUIBonusMapsPanel : public GamepadUIFrame +{ + DECLARE_CLASS_SIMPLE( GamepadUIBonusMapsPanel, GamepadUIFrame ); + +public: + GamepadUIBonusMapsPanel( vgui::Panel *pParent, const char* pPanelName ); + + void UpdateGradients(); + + void OnThink() OVERRIDE; + void OnCommand( char const* pCommand ) OVERRIDE; + + MESSAGE_FUNC_HANDLE( OnGamepadUIButtonNavigatedTo, "OnGamepadUIButtonNavigatedTo", button ); + + void BuildMapsList(); + void LayoutBonusButtons(); + void Paint() OVERRIDE; + void ApplySchemeSettings(vgui::IScheme* pScheme) OVERRIDE; + + void OnMouseWheeled( int delta ) OVERRIDE; + +#if SDK_2013_HACKS + void ScanBonusMaps(); + void ScanBonusMapSubDir( const char *pszDir, BonusMapDescription_t *pParent ); + void LoadBonusMapDir( const char *pszDir, BonusMapDescription_t *pParent = NULL, BonusMapDescription_t **ppFolder = NULL ); + void LoadBonusMap( const char *pszMapList, BonusMapDescription_t *pParent = NULL ); + void ClearBonusMapsList(); + int BonusCount(); + BonusMapDescription_t *GetBonusData( int iMapIndex ); + bool IsValidIndex( int iMapIndex ); + + BonusMapDescription_t *GetCurrentFolder(); + void BackFolder(); + void EnterFolder( BonusMapDescription_t *pFolder ); + + void SetCurrentChallengeObjectives( int iBronze, int iSilver, int iGold ); + void SetCurrentChallengeNames( const char *pszFileName, const char *pszMapName, const char *pszChallengeName ); + +private: + + CUtlVector< BonusMapDescription_t > m_BonusFolderHierarchy; +#endif + +private: + CUtlVector< GamepadUIBonusButton* > m_pBonusButtons; + CUtlVector< BonusMapDescription_t > m_Bonuses; + + int m_nBonusRowSize = 3; + + GamepadUIScrollState m_ScrollState; + + GamepadUIScrollBar *m_pScrollBar; + + GAMEPADUI_PANEL_PROPERTY( float, m_BonusOffsetX, "Bonus.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_BonusOffsetY, "Bonus.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_BonusSpacing, "Bonus.Spacing", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flBonusFade, "Bonus.Fade", "80", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flFooterMedalSize, "FooterMedal.Current.Size", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flFooterMedalNextSize, "FooterMedal.Next.Size", "0", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flBonusProgressOffsetX, "Bonus.Progress.OffsetX", "384", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flBonusProgressOffsetY, "Bonus.Progress.OffsetY", "56", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flBonusProgressWidth, "Bonus.Progress.Width", "256", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flBonusProgressHeight, "Bonus.Progress.Height", "32", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( Color, m_colProgressBackgroundColor, "Bonus.Progress.Background", "32 32 32 128", SchemeValueTypes::Color ); + GAMEPADUI_PANEL_PROPERTY( Color, m_colProgressFillColor, "Bonus.Progress.Fill", "255 192 0 255", SchemeValueTypes::Color ); + + vgui::HFont m_hDescFont = vgui::INVALID_FONT; + vgui::HFont m_hGoalFont = vgui::INVALID_FONT; + + GamepadUIImage m_CachedMedals[2]; + char m_szCachedMedalNames[256][2]; + + bool m_bHasChallenges; + float m_flCompletionPerc; +}; + +class GamepadUIBonusButton : public GamepadUIButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUIBonusButton, GamepadUIButton ); + + GamepadUIBonusButton( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char* pSchemeFile, const char* pCommand, const char* pText, const char* pDescription, const char *pBonusImage ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_Image( pBonusImage ) + , m_LockIcon( "gamepadui/lockylock" ) + { + bCompleted[0] = false; + bCompleted[1] = false; + bCompleted[2] = false; + } + + GamepadUIBonusButton( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char* pSchemeFile, const char* pCommand, const wchar* pText, const wchar* pDescription, const char *pBonusImage ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_Image( pBonusImage ) + , m_LockIcon( "gamepadui/lockylock" ) + { + bCompleted[0] = false; + bCompleted[1] = false; + bCompleted[2] = false; + } + + ~GamepadUIBonusButton() + { + if ( s_pLastBonusButton == this ) + s_pLastBonusButton = NULL; + } + + ButtonState GetCurrentButtonState() OVERRIDE + { + if ( s_pLastBonusButton == this ) + return ButtonState::Over; + return BaseClass::GetCurrentButtonState(); + } + + void Paint() OVERRIDE + { + int x, y, w, t; + GetBounds( x, y, w, t ); + + PaintButton(); + + int iAlpha = GetAlpha(); + vgui::surface()->DrawSetColor( Color( iAlpha, iAlpha, iAlpha, iAlpha ) ); + vgui::surface()->DrawSetTexture( m_Image ); + int imgH = ( w * 9 ) / 16; + //vgui::surface()->DrawTexturedRect( 0, 0, w, ); + float offset = m_flTextOffsetYAnimationValue[ButtonStates::Out] - m_flTextOffsetY; + + if (m_bTGA) + vgui::surface()->DrawTexturedRect( 0, 0, w, ( imgH - offset ) ); + else + vgui::surface()->DrawTexturedSubRect( 0, 0, w, w * 100 / 180, 0.0f, 0.0f, 1.0f * ( 180.0f / 256.0f ), ( ( imgH - offset ) / imgH ) * 100.0f / 128.0f ); + + vgui::surface()->DrawSetTexture( 0 ); + if ( !IsEnabled() ) + { + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 16 ) ); + vgui::surface()->DrawFilledRect( 0, 0, w, w * 100 / 180 ); + + vgui::surface()->DrawSetColor( m_colLock ); + vgui::surface()->DrawSetTexture( m_LockIcon ); + int nX = m_flWidth / 2 - m_flLockSize / 2; + int nY = imgH / 2 - m_flLockSize / 2; + vgui::surface()->DrawTexturedRect( nX, nY, nX + m_flLockSize, nY + m_flLockSize ); + } + + vgui::surface()->DrawSetColor( m_colProgressColor ); + vgui::surface()->DrawFilledRect( 0, (w * 100 / 180) - m_flProgressHeight, w * ( IsComplete() ? 1.0f : 0.0f), (w * 100 / 180)); + + PaintText(); + + int nChallenge = Clamp( gamepadui_selected_challenge.GetInt(), 0, NUM_CHALLENGES-1 ); + if ( m_Medals[ nChallenge ].IsValid() ) + { + vgui::surface()->DrawSetColor( Color( iAlpha, iAlpha, iAlpha, iAlpha ) ); + vgui::surface()->DrawSetTexture( m_Medals[ nChallenge ] ); + vgui::surface()->DrawTexturedRect( m_flMedalOffsetX, m_flMedalOffsetY, m_flMedalOffsetX + m_flMedalSize, m_flMedalOffsetY + m_flMedalSize ); + vgui::surface()->DrawSetTexture( 0 ); + } + } + + void ApplySchemeSettings( vgui::IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + float flX, flY; + if (GamepadUI::GetInstance().GetScreenRatio( flX, flY )) + { + if (flX != 1.0f) + { + // For now, undo the scaling from base class + m_flWidth /= flX; + for (int i = 0; i < ButtonStates::Count; i++) + m_flWidthAnimationValue[i] /= flX; + } + + SetSize( m_flWidth, m_flHeight + m_flExtraHeight ); + DoAnimations( true ); + } + + // Use a smaller font if necessary + int nTextSizeX, nTextSizeY; + vgui::surface()->GetTextSize( m_hTextFont, m_strButtonText.String(), nTextSizeX, nTextSizeY ); + if ( nTextSizeX >= m_flWidth ) + { + m_hTextFont = pScheme->GetFont( "Button.Text.Font.Small", true ); + m_hTextFontOver = pScheme->GetFont( "Button.Text.Font.Small.Over", true ); + if (m_hTextFontOver == vgui::INVALID_FONT ) + m_hTextFontOver = m_hTextFont; + } + } + + void NavigateTo() OVERRIDE + { + BaseClass::NavigateTo(); + s_pLastBonusButton = this; + } + + const BonusMapDescription_t& GetBonusMapDescription() const + { + return m_BonusMapDesc; + } + + bool IsComplete() const + { + if ( m_BonusMapDesc.bIsFolder ) + return false; + int nChallenge = Clamp( gamepadui_selected_challenge.GetInt(), 0, NUM_CHALLENGES-1 ); + return m_BonusMapDesc.bComplete || bCompleted[ nChallenge ]; + } + + void SetBonusMapDescription( BonusMapDescription_t* pDesc ) + { + for ( auto& medal : m_Medals ) + medal.Cleanup(); + m_BonusMapDesc = *pDesc; + +#if SDK_2013_HACKS + if ( m_BonusMapDesc.m_Challenges.Count() == 0 ) +#else + if ( !m_BonusMapDesc.m_pChallenges ) +#endif + return; + + int nNumChallenges = 0; +#if SDK_2013_HACKS + for ( ChallengeDescription_t& challenge : m_BonusMapDesc.m_Challenges ) +#else + for ( ChallengeDescription_t& challenge : *m_BonusMapDesc.m_pChallenges ) +#endif + { + int iChallengeNum = challenge.iType != -1 ? challenge.iType : nNumChallenges; + int iBest, iEarnedMedal, iNext, iNextMedal; + GetChallengeMedals( &challenge, iBest, iEarnedMedal, iNext, iNextMedal ); + + bCompleted[ iChallengeNum ] = iNextMedal == -1; + + char szBuff[256]; + if (iChallengeNum < 10) + Q_snprintf(szBuff, 256, "vgui/medals/medal_0%i_%s", iChallengeNum, g_pszMedalNames[iEarnedMedal]); + else + Q_snprintf(szBuff, 256, "vgui/medals/medal_%i_%s", iChallengeNum, g_pszMedalNames[iEarnedMedal]); + m_Medals[ iChallengeNum ].SetImage( szBuff ); + + nNumChallenges++; + } + } + + void SetTGAImage( const char *pszImage ) + { + m_Image.SetTGAImage( pszImage ); + m_bTGA = true; + } + + static GamepadUIBonusButton* GetLastBonusButton() { return s_pLastBonusButton; } + +private: + GamepadUIImage m_Image; + GamepadUIImage m_Medals[NUM_CHALLENGES]; + GamepadUIImage m_LockIcon; + + bool bCompleted[NUM_CHALLENGES]; + + bool m_bTGA = false; + + static GamepadUIBonusButton *s_pLastBonusButton; + + BonusMapDescription_t m_BonusMapDesc = {}; + + GAMEPADUI_PANEL_PROPERTY( Color, m_colProgressColor, "Button.Background.Progress", "255 0 0 255", SchemeValueTypes::Color ); + GAMEPADUI_PANEL_PROPERTY( float, m_flProgressHeight, "Button.Progress.Height", "1", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flMedalSize, "Button.Medal.Size", "64", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flMedalOffsetX, "Button.Medal.OffsetX", "8", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flMedalOffsetY, "Button.Medal.OffsetY", "8", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flLockSize, "Button.Lock.Size", "80", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( Color, m_colLock, "Button.Lock", "20 20 20 255", SchemeValueTypes::Color ); +}; + +GamepadUIBonusButton* GamepadUIBonusButton::s_pLastBonusButton = NULL; + +GamepadUIBonusMapsPanel::GamepadUIBonusMapsPanel( vgui::Panel *pParent, const char* PanelName ) : BaseClass( pParent, PanelName ) +{ + vgui::HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_DEFAULT_PANEL_SCHEME, "SchemePanel" ); + SetScheme( hScheme ); + + FooterButtonMask buttons = FooterButtons::Back | FooterButtons::Select; + SetFooterButtons( buttons, FooterButtons::Select ); + + BuildMapsList(); + Activate(); + + UpdateGradients(); + + m_pScrollBar = new GamepadUIScrollBar( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemescrollbar.res", + NULL, false ); +} + +void GamepadUIBonusMapsPanel::UpdateGradients() +{ + const float flTime = GamepadUI::GetInstance().GetTime(); + GamepadUI::GetInstance().GetGradientHelper()->ResetTargets( flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Up, { 1.0f, 1.0f }, flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Down, { 1.0f, 0.5f }, flTime ); +} + +void GamepadUIBonusMapsPanel::OnThink() +{ + BaseClass::OnThink(); + + LayoutBonusButtons(); +} + +void GamepadUIBonusMapsPanel::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + m_hGoalFont = pScheme->GetFont( "Goal.Font", true ); + m_hDescFont = pScheme->GetFont( "BonusDesc.Font", true ); + + float flX, flY; + if (GamepadUI::GetInstance().GetScreenRatio( flX, flY )) + { + m_BonusOffsetX *= flX; + } + + int nX, nY; + GamepadUI::GetInstance().GetSizingPanelOffset( nX, nY ); + if (nX > 0) + { + GamepadUI::GetInstance().GetSizingPanelScale( flX, flY ); + flX *= 0.5f; + + m_BonusOffsetX += ((float)nX) * flX; + m_flBonusFade += ((float)nX) * flX; + } +} + +void GamepadUIBonusMapsPanel::OnGamepadUIButtonNavigatedTo( vgui::VPANEL button ) +{ + // TODO: Scroll +#if 1 + GamepadUIBonusButton *pButton = dynamic_cast< GamepadUIBonusButton * >( vgui::ipanel()->GetPanel( button, GetModuleName() ) ); + if ( pButton ) + { + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + int nX, nY; + pButton->GetPos( nX, nY ); + if ( nY + m_flFooterButtonsOffsetY + m_nFooterButtonHeight + pButton->GetTall() > nParentH || nY < m_BonusOffsetY ) + { + int nRow = ( m_pBonusButtons.Find( pButton ) / m_nBonusRowSize ); + if ( nY >= m_BonusOffsetY ) + nRow--; + + float flScrollProgress = (pButton->m_flHeight + m_BonusSpacing) * ((float)nRow); + m_ScrollState.SetScrollTarget( flScrollProgress, GamepadUI::GetInstance().GetTime() ); + } + } +#endif +} + +#define MAX_LISTED_BONUS_MAPS 128 + +void GamepadUIBonusMapsPanel::BuildMapsList() +{ + m_pBonusButtons.PurgeAndDeleteElements(); + +#if SDK_2013_HACKS + ClearBonusMapsList(); + ScanBonusMaps(); +#else + GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->ClearBonusMapsList(); + GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->ScanBonusMaps(); +#endif + +#if !SDK_2013_HACKS + char szDisplayPath[_MAX_PATH]; + Q_snprintf( szDisplayPath, _MAX_PATH, "%s/", GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->GetPath() ); +#endif + + //bool bIsRoot = !Q_strcmp( GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->GetPath(), "." ); + //if ( bIsRoot ) + GetFrameTitle() = GamepadUIString( "#GameUI_BonusMaps" ); + //else + // GetFrameTitle() = GamepadUIString( szDisplayPath ); + + m_bHasChallenges = false; + +#if SDK_2013_HACKS + + // add to the list + for ( int iMapIndex = 0; iMapIndex < m_Bonuses.Count() && iMapIndex < MAX_LISTED_BONUS_MAPS; ++iMapIndex) + { + BonusMapDescription_t *pDesc = &m_Bonuses[iMapIndex]; + + char szImage[MAX_PATH]; + bool bTGA = false; + if (pDesc->szImageName[0]) + { + if (pDesc->szImageName[0] == '.' && GetCurrentFolder()) + V_snprintf( szImage, sizeof( szImage ), "%s%c%s", GetCurrentFolder()->szFileName, CORRECT_PATH_SEPARATOR, pDesc->szImageName ); + else + V_strncpy( szImage, pDesc->szImageName, sizeof( szImage ) ); + + if (!g_pFullFileSystem->FileExists( szImage )) + V_snprintf( szImage, sizeof( szImage ), "vgui/%s", pDesc->szImageName ); + + const char *pszExt = V_GetFileExtension( szImage ); + if (pszExt && V_strncmp( pszExt, "tga", 4 ) == 0) + bTGA = true; + } + else + { + // Look for a nearby image + if (pDesc->bIsFolder) + { + Q_snprintf( szImage, sizeof( szImage ), "%s%cfoldericon", pDesc->szFileName, CORRECT_PATH_SEPARATOR ); + } + else + { + if (GetCurrentFolder() != NULL) + { + Q_snprintf( szImage, sizeof( szImage ), "%s%c%s", GetCurrentFolder()->szFileName, CORRECT_PATH_SEPARATOR, pDesc->szFileName ); + } + else + { + V_strncpy( szImage, pDesc->szFileName, sizeof( szImage ) ); + } + } + + V_SetExtension( szImage, ".tga", sizeof( szImage ) ); + + if (!g_pFullFileSystem->FileExists( szImage )) + { + // Use default + V_strncpy( szImage, "vgui/bonusmaps/icon_bonus_map_default", sizeof( szImage ) ); + } + else + bTGA = true; + } + + if ( pDesc->m_Challenges.Count() > 0 ) + m_bHasChallenges = true; + + GamepadUIBonusButton *pChapterButton = new GamepadUIBonusButton( + this, this, + GAMEPADUI_BONUS_SCHEME, "action_map", + pDesc->szMapName, NULL, /*pDesc->szComment*/ szImage); + pChapterButton->SetPriority( iMapIndex ); + pChapterButton->SetEnabled( !pDesc->bLocked ); + pChapterButton->SetForwardToParent( true ); + pChapterButton->SetBonusMapDescription( pDesc ); + + // Make bonus maps render under everything + pChapterButton->SetZPos( 1 ); + + if (bTGA) + pChapterButton->SetTGAImage( szImage ); + + m_pBonusButtons.AddToTail( pChapterButton ); + } +#else + int iMapCount = GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->BonusCount(); + + // add to the list + for ( int iMapIndex = 0; iMapIndex < iMapCount && iMapIndex < MAX_LISTED_BONUS_MAPS; ++iMapIndex ) + { + BonusMapDescription_t *pDesc = GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->GetBonusData( iMapIndex ); + + char szImage[MAX_PATH]; + V_snprintf( szImage, sizeof( szImage ), "vgui/%s", pDesc->szImageName ); + + if ( pDesc->m_pChallenges ) + m_bHasChallenges = true; + + GamepadUIBonusButton *pChapterButton = new GamepadUIBonusButton( + this, this, + GAMEPADUI_BONUS_SCHEME, "action_map", + pDesc->szMapName, NULL, /*pDesc->szComment*/ szImage); + pChapterButton->SetPriority( iMapIndex ); + pChapterButton->SetEnabled( !pDesc->bLocked ); + pChapterButton->SetForwardToParent( true ); + pChapterButton->SetBonusMapDescription( pDesc ); + + m_pBonusButtons.AddToTail( pChapterButton ); + m_Bonuses.AddToTail( *pDesc ); + } +#endif + + if ( m_pBonusButtons.Count() > 0 ) + m_pBonusButtons[0]->NavigateTo(); + + for ( int i = 1; i < m_pBonusButtons.Count(); i++) + { + m_pBonusButtons[i]->SetNavLeft(m_pBonusButtons[i - 1]); + m_pBonusButtons[i - 1]->SetNavRight(m_pBonusButtons[i] ); + } + + FooterButtonMask buttons = FooterButtons::Back | FooterButtons::Select; + + if (m_bHasChallenges) + { + buttons |= FooterButtons::Challenge; + + // Get completion percentage + int nNumChallenges = 0; + int nNumComplete = 0; + for ( int i = 0; i < m_pBonusButtons.Count(); i++ ) + { + int nChallengeCount = m_pBonusButtons[i]->GetBonusMapDescription().m_Challenges.Count(); + if (nChallengeCount <= 0) + continue; + + nNumChallenges += (nChallengeCount*3); + + if (m_pBonusButtons[i]->GetBonusMapDescription().bComplete) + { + nNumComplete += (nChallengeCount*3); + } + else + { + const CUtlVector &desc = m_pBonusButtons[i]->GetBonusMapDescription().m_Challenges; + for (int c = 0; c < nChallengeCount; c++) + { + if (desc[c].iBest != -1) + { + if (desc[c].iBest <= desc[c].iGold) + nNumComplete += 3; + else if (desc[c].iBest <= desc[c].iSilver) + nNumComplete += 2; + else if (desc[c].iBest <= desc[c].iBronze) + nNumComplete += 1; + } + } + } + } + + m_flCompletionPerc = ((float)nNumComplete) / ((float)nNumChallenges); + } + + SetFooterButtons( buttons, FooterButtons::Select ); +} + +void GamepadUIBonusMapsPanel::LayoutBonusButtons() +{ + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + float flScrollClamp = ((float)m_pBonusButtons[0]->GetTall() + m_BonusSpacing) * Max(1.0f, ceilf(((float)m_pBonusButtons.Count() - m_nBonusRowSize*2) / ((float)m_nBonusRowSize)) ); + + m_ScrollState.UpdateScrollBounds( 0.0f, flScrollClamp ); + + if (m_pBonusButtons.Count() > 0) + { + m_pScrollBar->InitScrollBar( &m_ScrollState, nParentW - m_BonusOffsetX - m_BonusSpacing, m_BonusOffsetY ); + m_pScrollBar->UpdateScrollBounds( 0.0f, flScrollClamp, + ((m_pBonusButtons[0]->GetTall() + m_BonusSpacing) * m_nBonusRowSize*2), nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight - m_BonusOffsetY ); + } + + int x = m_BonusOffsetX; + int y = m_BonusOffsetY - m_ScrollState.GetScrollProgress(); + CUtlVector< CUtlVector< GamepadUIBonusButton* > > pButtonRows; + int j = 0; + for ( int i = 0; i < m_pBonusButtons.Count(); i++ ) + { + int size = ( m_pBonusButtons[0]->GetWide() + m_BonusSpacing ); + + if ( x + size > nParentW - m_BonusOffsetX ) + { + j = 0; + x = m_BonusOffsetX; + y += m_pBonusButtons[0]->GetTall() + m_BonusSpacing; + } + + int fade = 255; + if ( y < m_BonusOffsetY ) + fade = RemapValClamped( m_BonusOffsetY - y, m_flBonusFade, 0, 0, 255 ); + else if ( y > ( nParentH - (int)m_flFooterButtonsOffsetY - m_nFooterButtonHeight - (int)m_flBonusFade ) ) + fade = RemapValClamped( y - ( nParentH - (int)m_flFooterButtonsOffsetY - m_nFooterButtonHeight - (int)m_flBonusFade ), 0, m_flBonusFade, 255, 0 ); + if ( ( m_pBonusButtons[i]->HasFocus() && m_pBonusButtons[i]->IsEnabled() ) && fade != 0 ) + fade = 255; + + m_pBonusButtons[i]->SetAlpha( fade ); + + m_pBonusButtons[i]->SetPos( x, y ); + m_pBonusButtons[i]->SetVisible( true ); + j++; + + x += size + m_BonusSpacing; + while (pButtonRows.Size() <= j) + pButtonRows.AddToTail(); + pButtonRows[j].AddToTail( m_pBonusButtons[i] ); + } + + for ( int i = 0; i < pButtonRows.Count(); i++ ) + { + for (int j = 1; j < pButtonRows[i].Count(); j++) + { + pButtonRows[i][j]->SetNavUp(pButtonRows[i][j - 1]); + pButtonRows[i][j - 1]->SetNavDown(pButtonRows[i][j]); + } + } + + if (pButtonRows.Count() > 1) + m_nBonusRowSize = pButtonRows.Count()-1; + else + m_nBonusRowSize = 1; + + m_ScrollState.UpdateScrolling( 2.0f, GamepadUI::GetInstance().GetTime() ); +} + +void GamepadUIBonusMapsPanel::OnCommand( char const* pCommand ) +{ + if ( !V_strcmp( pCommand, "action_back" ) ) + { +#if SDK_2013_HACKS + bool bIsRoot = GetCurrentFolder() == NULL; +#else + bool bIsRoot = !Q_strcmp( GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->GetPath(), "." ); +#endif + if ( bIsRoot ) + Close(); + else + { +#if SDK_2013_HACKS + BackFolder(); +#else + GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->BackPath(); +#endif + + BuildMapsList(); + } + } + else if ( !V_strcmp( pCommand, "action_challenges" ) ) + { + CycleSelectedChallenge(); + } + else if ( !V_strcmp( pCommand, "action_map" ) ) + { + GamepadUIBonusButton* pButton = GamepadUIBonusButton::GetLastBonusButton(); + if ( pButton ) + { + int mapIndex = pButton->GetPriority(); +#if SDK_2013_HACKS + if ( IsValidIndex( mapIndex ) ) +#else + if ( GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->IsValidIndex( mapIndex ) ) +#endif + { +#if SDK_2013_HACKS + BonusMapDescription_t *pBonusMap = GetBonusData( mapIndex ); +#else + BonusMapDescription_t *pBonusMap = GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->GetBonusData( mapIndex ); +#endif + + // Don't do anything with locked items + if ( pBonusMap->bLocked ) + return; + +#if SDK_2013_HACKS + const char *shortName = pBonusMap->szFileName; +#else + const char *shortName = pBonusMap->szShortName; +#endif + if ( shortName && shortName[ 0 ] ) + { + if ( pBonusMap->bIsFolder ) + { +#if SDK_2013_HACKS + EnterFolder( pBonusMap ); +#else + GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->AppendPath( shortName ); +#endif + + BuildMapsList(); + } + else + { + // Load the game, return to top and switch to engine + char sz[ 256 ]; + + // Set the challenge mode if one is selected + int iChallenge = 0; + + GamepadUIBonusButton* pButton = GamepadUIBonusButton::GetLastBonusButton(); + if ( pButton ) + { + const BonusMapDescription_t& desc = pButton->GetBonusMapDescription(); +#if SDK_2013_HACKS + if ( desc.m_Challenges.Count() > 0 ) +#else + if ( desc.m_pChallenges ) +#endif + { + iChallenge = Clamp( gamepadui_selected_challenge.GetInt(), 0, NUM_CHALLENGES-1 ) + 1; + } + } + + // Set commentary + ConVarRef commentary( "commentary" ); + commentary.SetValue( 0 ); + + ConVarRef sv_cheats( "sv_cheats" ); + sv_cheats.SetValue( 0 ); + + if ( iChallenge > 0 ) + { + Q_snprintf( sz, sizeof( sz ), "sv_bonus_challenge %i\n", iChallenge ); + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted(sz); + +#if SDK_2013_HACKS + ChallengeDescription_t *pChallengeDescription = &pBonusMap->m_Challenges[iChallenge - 1]; +#else + ChallengeDescription_t *pChallengeDescription = &((*pBonusMap->m_pChallenges)[ iChallenge - 1 ]); +#endif + + // Set up medal goals +#if SDK_2013_HACKS + SetCurrentChallengeObjectives( pChallengeDescription->iBronze, pChallengeDescription->iSilver, pChallengeDescription->iGold ); + SetCurrentChallengeNames( pBonusMap->szFileName, pBonusMap->szMapName, pChallengeDescription->szName ); +#else + GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->SetCurrentChallengeObjectives( pChallengeDescription->iBronze, pChallengeDescription->iSilver, pChallengeDescription->iGold ); + GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->SetCurrentChallengeNames( pBonusMap->szFileName, pBonusMap->szMapName, pChallengeDescription->szName ); +#endif + } + + if ( pBonusMap->szMapFileName[ 0 ] != '.' ) + { + Q_snprintf( sz, sizeof( sz ), "progress_enable\nmap %s\n", pBonusMap->szMapFileName ); + } + else + { +#if SDK_2013_HACKS + const char *pchSubDir = Q_strnchr( pBonusMap->szFileName, '/', Q_strlen( pBonusMap->szFileName ) ); +#else + const char *pchSubDir = Q_strnchr( GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->GetPath(), '/', Q_strlen( GamepadUI::GetInstance().GetGameUI()->GetBonusMapsDatabase()->GetPath() ) ); +#endif + + if ( pchSubDir ) + { + pchSubDir = Q_strnchr( pchSubDir + 1, '/', Q_strlen( pchSubDir ) ); + + if ( pchSubDir ) + { + ++pchSubDir; + const char *pchMapFileName = pBonusMap->szMapFileName + 2; + Q_snprintf( sz, sizeof( sz ), "progress_enable\nmap %s/%s\n", pchSubDir, pchMapFileName ); + } + } + } + + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( sz ); + + OnClose(); + } + } + } + } + } + else + { + BaseClass::OnCommand( pCommand ); + } +} + +void GamepadUIBonusMapsPanel::Paint() +{ + BaseClass::Paint(); + + GamepadUIBonusButton* pButton = GamepadUIBonusButton::GetLastBonusButton(); + if ( !pButton ) + return; + + const BonusMapDescription_t& desc = pButton->GetBonusMapDescription(); + + // Draw description + if (desc.szComment[0]) + { + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + float flX = m_flFooterButtonsOffsetX + m_nFooterButtonWidth + m_flFooterButtonsSpacing; + float flY = nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight; + + int nMaxWidth = 0; + + if (desc.m_Challenges.Count() > 0) + { + flY += m_flFooterMedalSize + m_flFooterButtonsSpacing; + + /* + flX += m_flFooterMedalSize + (m_flFooterButtonsSpacing*4) + m_flFooterDescMedalSpace; + +#ifdef STEAM_INPUT + const bool bController = GamepadUI::GetInstance().GetSteamInput()->IsEnabled(); +#elif defined(HL2_RETAIL) // Steam input and Steam Controller are not supported in SDK2013 (Madi) + const bool bController = g_pInputSystem->IsSteamControllerActive(); +#else + const bool bController = (g_pInputSystem->GetJoystickCount() >= 1); +#endif + if (bController) + { + // Need extra room for the extra button + nMaxWidth -= m_nFooterButtonWidth + m_flFooterButtonsSpacing; + } + */ + } + + vgui::surface()->DrawSetTextColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawSetTextFont( m_hDescFont ); + vgui::surface()->DrawSetTextPos( flX, flY ); + + nMaxWidth += nParentW - flX - (m_flFooterButtonsOffsetX + m_nFooterButtonWidth + m_flFooterButtonsSpacing); + + GamepadUIString strComment( desc.szComment ); + DrawPrintWrappedText( m_hDescFont, flX, flY, strComment.String(), strComment.Length(), nMaxWidth, true); + } + +#if SDK_2013_HACKS + if (desc.m_Challenges.Count() == 0) +#else + if ( !desc.m_pChallenges ) +#endif + return; + + // Draw progress bar + if (m_bHasChallenges) + { + float w = m_flBonusProgressOffsetX + m_flBonusProgressWidth; + float h = m_flBonusProgressOffsetY + m_flBonusProgressHeight; + vgui::surface()->DrawSetColor( m_colProgressBackgroundColor ); + vgui::surface()->DrawFilledRect( m_flBonusProgressOffsetX, m_flBonusProgressOffsetY, w, h ); + + Color colFill = m_colProgressFillColor; + for (int i = 0; i < 3; i++) + colFill[i] *= m_flCompletionPerc; + + vgui::surface()->DrawSetColor( colFill ); + vgui::surface()->DrawFilledRect( m_flBonusProgressOffsetX, m_flBonusProgressOffsetY, m_flBonusProgressOffsetX + (m_flBonusProgressWidth * m_flCompletionPerc), h ); + + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawOutlinedRect( m_flBonusProgressOffsetX, m_flBonusProgressOffsetY, w, h ); + + // Text + wchar_t szWideBuff[64]; + V_snwprintf( szWideBuff, sizeof( szWideBuff ), L"%i%% %ls", (int)(100.0f * m_flCompletionPerc), g_pVGuiLocalize->Find( "#GameUI_BonusMapsCompletion" ) ); + + int nTextW, nTextH; + vgui::surface()->GetTextSize( m_hGoalFont, szWideBuff, nTextW, nTextH ); + + vgui::surface()->DrawSetTextColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawSetTextFont( m_hGoalFont ); + vgui::surface()->DrawSetTextPos( m_flBonusProgressOffsetX + (m_flBonusProgressWidth/2) - (nTextW/2), m_flBonusProgressOffsetY + (m_flBonusProgressHeight/2) - (nTextH/2) ); + vgui::surface()->DrawPrintText( szWideBuff, V_wcslen( szWideBuff ) ); + } + + int nCurrentChallenge = Clamp(gamepadui_selected_challenge.GetInt(), 0, NUM_CHALLENGES-1); + + int nNumChallenges = 0; +#if SDK_2013_HACKS + for (const ChallengeDescription_t& challenge : desc.m_Challenges) +#else + for (ChallengeDescription_t& challenge : *desc.m_pChallenges) +#endif + { + int iChallengeNum = challenge.iType != -1 ? challenge.iType : nNumChallenges; + + if ( iChallengeNum == nCurrentChallenge ) + { + int iBest, iEarnedMedal, iNext, iNextMedal; + GetChallengeMedals( &challenge, iBest, iEarnedMedal, iNext, iNextMedal ); + + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + char szBuff[256]; + if ( iEarnedMedal != -1 ) + { + if (iChallengeNum < 10) + Q_snprintf(szBuff, 256, "vgui/medals/medal_0%i_%s", iChallengeNum, g_pszMedalNames[iEarnedMedal]); + else + Q_snprintf(szBuff, 256, "vgui/medals/medal_%i_%s", iChallengeNum, g_pszMedalNames[iEarnedMedal]); + if ( V_strcmp( m_szCachedMedalNames[0], szBuff)) + { + m_CachedMedals[ 0 ].SetImage( szBuff ); + V_strcpy( m_szCachedMedalNames[0], szBuff); + } + } + else + { + m_CachedMedals[0].Cleanup(); + V_strcpy(m_szCachedMedalNames[0], ""); + } + + if ( iNextMedal != -1 ) + { + if (iChallengeNum < 10) + Q_snprintf(szBuff, 256, "vgui/medals/medal_0%i_%s", iChallengeNum, g_pszMedalNames[iNextMedal]); + else + Q_snprintf(szBuff, 256, "vgui/medals/medal_%i_%s", iChallengeNum, g_pszMedalNames[iNextMedal]); + if ( V_strcmp( m_szCachedMedalNames[1], szBuff)) + { + m_CachedMedals[ 1 ].SetImage( szBuff ); + V_strcpy( m_szCachedMedalNames[1], szBuff); + } + } + else + { + m_CachedMedals[1].Cleanup(); + V_strcpy(m_szCachedMedalNames[1], ""); + } + + float flX = m_flFooterButtonsOffsetX + m_nFooterButtonWidth + m_flFooterButtonsSpacing; + float flY = nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight; + + // Josh: I should clean this later... + int iBestWide = 0; + int iBestTall = 0; + if (iBest != -1) + { + char szBuff[256]; + wchar_t szWideBuff[256]; + wchar_t szWideBuff2[256]; + +#ifdef MAPBASE + // Negative ints are used for hacks related to upwards goals + if (iBest < 0) + iBest = -iBest; +#endif + + Q_snprintf(szBuff, sizeof(szBuff), "%i", iBest); + g_pVGuiLocalize->ConvertANSIToUnicode(szBuff, szWideBuff2, sizeof(szWideBuff2)); + g_pVGuiLocalize->ConstructString(szWideBuff, sizeof(szWideBuff), g_pVGuiLocalize->Find("#GameUI_BonusMapsBest"), 1, szWideBuff2); + + if ( m_CachedMedals[0].IsValid() ) + { + vgui::surface()->DrawSetTexture( m_CachedMedals[ 0 ] ); + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawTexturedRect( flX, flY, flX + m_flFooterMedalSize, flY + m_flFooterMedalSize ); + } + + vgui::surface()->DrawSetTextColor(Color(255, 255, 255, 255)); + vgui::surface()->DrawSetTextFont( m_hGoalFont ); + vgui::surface()->DrawSetTextPos( flX + m_flFooterMedalSize + m_flFooterButtonsSpacing, flY ); + vgui::surface()->GetTextSize( m_hGoalFont, szWideBuff, iBestWide, iBestTall); + vgui::surface()->DrawPrintText( szWideBuff, V_wcslen(szWideBuff) ); + } + + int iNextWide = 0; + int iNextTall = 0; + if (iNext != -1) + { + char szBuff[256]; + wchar_t szWideBuff[256]; + wchar_t szWideBuff2[256]; + +#ifdef MAPBASE + // Negative ints are used for hacks related to upwards goals + if (iNext < 0) + iNext = -iNext; +#endif + + Q_snprintf(szBuff, sizeof(szBuff), "%i", iNext); + g_pVGuiLocalize->ConvertANSIToUnicode(szBuff, szWideBuff2, sizeof(szWideBuff2)); + g_pVGuiLocalize->ConstructString(szWideBuff, sizeof(szWideBuff), g_pVGuiLocalize->Find("#GameUI_BonusMapsGoal"), 1, szWideBuff2); + + vgui::surface()->DrawSetTextColor(Color(255, 255, 255, 255)); + vgui::surface()->DrawSetTextFont( m_hGoalFont ); + vgui::surface()->DrawSetTextPos( flX + m_flFooterMedalSize + m_flFooterButtonsSpacing, flY + iBestTall ); + vgui::surface()->GetTextSize( m_hGoalFont, szWideBuff, iNextWide, iNextTall); + vgui::surface()->DrawPrintText( szWideBuff, V_wcslen(szWideBuff) ); + + if ( m_CachedMedals[1].IsValid() ) + { + vgui::surface()->DrawSetTexture( m_CachedMedals[ 1 ] ); + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + float flX2 = flX + m_flFooterMedalSize + Max(iNextWide, iBestWide) + 2 * m_flFooterButtonsSpacing; + float flY2 = flY + iBestTall; + vgui::surface()->DrawTexturedRect( flX2, flY2, flX2 + m_flFooterMedalNextSize, flY2 + m_flFooterMedalNextSize); + } + } + + return; + } + + nNumChallenges++; + } +} + +void GamepadUIBonusMapsPanel::OnMouseWheeled( int nDelta ) +{ + m_ScrollState.OnMouseWheeled( nDelta * m_BonusSpacing * 20.0f, GamepadUI::GetInstance().GetTime() ); +} + +#if SDK_2013_HACKS +void GamepadUIBonusMapsPanel::ScanBonusMaps() +{ + if (GetCurrentFolder() == NULL) + { + // Load bonus maps from the bonus maps manifest + KeyValues *pBonusMapsManifest = new KeyValues( "BonusMapsManifest" ); + if (pBonusMapsManifest->LoadFromFile( g_pFullFileSystem, "scripts/bonus_maps_manifest.txt" )) + { + for (KeyValues *pSubKey = pBonusMapsManifest->GetFirstSubKey(); pSubKey != NULL; pSubKey = pSubKey->GetNextKey()) + { + if (Q_stricmp( pSubKey->GetName(), "dir" ) == 0) + { + LoadBonusMapDir( pSubKey->GetString() ); + } + else if (Q_stricmp( pSubKey->GetName(), "search" ) == 0) + { + char szSearch[MAX_PATH]; + Q_snprintf( szSearch, sizeof( szSearch ), "%s/*", pSubKey->GetString() ); + + FileFindHandle_t dirHandle; + const char *pszFileName = g_pFullFileSystem->FindFirst( szSearch, &dirHandle ); + while (pszFileName) + { + if (g_pFullFileSystem->FindIsDirectory( dirHandle )) + { + char szBonusDir[MAX_PATH]; + Q_snprintf( szBonusDir, sizeof( szBonusDir ), "%s/%s", pSubKey->GetString(), pszFileName ); + LoadBonusMapDir( szBonusDir ); + } + else if (Q_stricmp( Q_GetFileExtension( pszFileName ), "bns" ) == 0) + { + char szBonusDir[MAX_PATH]; + Q_snprintf( szBonusDir, sizeof( szBonusDir ), "%s/%s", pSubKey->GetString(), pszFileName ); + LoadBonusMap( szBonusDir ); + } + + pszFileName = g_pFullFileSystem->FindNext( dirHandle ); + } + g_pFullFileSystem->FindClose( dirHandle ); + } + } + } + pBonusMapsManifest->deleteThis(); + } + else + { + // Just look in the folder's directory + ScanBonusMapSubDir( GetCurrentFolder()->szFileName, GetCurrentFolder() ); + } + + // Load save data and modify each entry accordingly + KeyValues *pBonusSaveData = new KeyValues( "BonusSaveData" ); + if (pBonusSaveData->LoadFromFile( g_pFullFileSystem, "save/bonus_maps_data.bmd" )) + { + KeyValues *pBonusFiles = pBonusSaveData->FindKey( "bonusfiles" ); + if (pBonusFiles) + { + for (KeyValues *pSubKey = pBonusFiles->GetFirstSubKey(); pSubKey != NULL; pSubKey = pSubKey->GetNextKey()) + { + // Look for each map and modify data based on what's saved + for (KeyValues *pMap = pSubKey->GetFirstSubKey(); pMap != NULL; pMap = pMap->GetNextKey()) + { + for ( int iMapIndex = 0; iMapIndex < m_Bonuses.Count() && iMapIndex < MAX_LISTED_BONUS_MAPS; ++iMapIndex) + { + BonusMapDescription_t *pDesc = &m_Bonuses[iMapIndex]; + if (Q_strcmp( pDesc->szMapName, pMap->GetName() ) != 0) + continue; + + pDesc->bLocked = pMap->GetBool( "lock" ); + pDesc->bComplete = pMap->GetBool( "complete" ); + + for (int iChallenge = 0; iChallenge < pDesc->m_Challenges.Count(); iChallenge++) + { + pDesc->m_Challenges[iChallenge].iBest = pMap->GetInt( pDesc->m_Challenges[iChallenge].szName, -1 ); + } + + break; + } + } + } + } + } + pBonusSaveData->deleteThis(); +} + +void GamepadUIBonusMapsPanel::ScanBonusMapSubDir( const char *pszDir, BonusMapDescription_t *pParent ) +{ + char szSearch[MAX_PATH]; + Q_snprintf( szSearch, sizeof( szSearch ), "%s/*", pszDir ); + + FileFindHandle_t dirHandle; + const char *pszFileName = g_pFullFileSystem->FindFirst( szSearch, &dirHandle ); + while (pszFileName) + { + if (g_pFullFileSystem->FindIsDirectory( dirHandle )) + { + if (pszFileName[0] != '.') + { + char szBonusDir[MAX_PATH]; + Q_snprintf( szBonusDir, sizeof( szBonusDir ), "%s/%s", pszDir, pszFileName ); + + //BonusMapDescription_t *pFolder = NULL; + LoadBonusMapDir( szBonusDir, pParent/*, &pFolder*/ ); + //ScanBonusMapSubDir( szBonusDir, pFolder ); + } + } + else if (Q_strcmp( Q_GetFileExtension( pszFileName ), "bns" ) == 0 && !Q_strstr(pszFileName, "folderinfo")) + { + char szBonusDir[MAX_PATH]; + Q_snprintf( szBonusDir, sizeof( szBonusDir ), "%s/%s", pszDir, pszFileName ); + LoadBonusMap( szBonusDir, pParent ); + } + + pszFileName = g_pFullFileSystem->FindNext( dirHandle ); + } + g_pFullFileSystem->FindClose( dirHandle ); +} + +void GamepadUIBonusMapsPanel::LoadBonusMapDir( const char *pszDir, BonusMapDescription_t *pParent, BonusMapDescription_t **ppFolder ) +{ + char szFolderName[MAX_PATH]; + Q_FileBase( pszDir, szFolderName, sizeof( szFolderName ) ); + + char szFolderInfo[MAX_PATH]; + //char szMapList[MAX_PATH]; + Q_snprintf( szFolderInfo, sizeof( szFolderInfo ), "%s/folderinfo.bns", pszDir ); + //Q_snprintf( szMapList, sizeof( szMapList ), "%s/%s.bns", pszDir, szFolderName ); + + KeyValues *pFolderInfoKV = new KeyValues( "FolderInfo" ); + if (!pFolderInfoKV->LoadFromFile( g_pFullFileSystem, szFolderInfo )) + { + pFolderInfoKV->deleteThis(); + return; + } + + int iFolder = m_Bonuses.AddToTail(); + m_Bonuses[iFolder].bIsFolder = true; + m_Bonuses[iFolder].bComplete = false; + m_Bonuses[iFolder].bLocked = pFolderInfoKV->GetBool( "lock" ); + Q_strncpy( m_Bonuses[iFolder].szMapName, pFolderInfoKV->GetName(), sizeof( m_Bonuses[iFolder].szMapName ) ); + Q_strncpy( m_Bonuses[iFolder].szImageName, pFolderInfoKV->GetString( "image" ), sizeof( m_Bonuses[iFolder].szImageName ) ); + Q_strncpy( m_Bonuses[iFolder].szComment, pFolderInfoKV->GetString( "comment" ), sizeof( m_Bonuses[iFolder].szComment ) ); + Q_strncpy( m_Bonuses[iFolder].szFileName, pszDir, sizeof( m_Bonuses[iFolder].szFileName ) ); + + m_Bonuses[iFolder].m_pParent = pParent; + + pFolderInfoKV->deleteThis(); + + //LoadBonusMap( szMapList, &m_Bonuses[iFolder] ); + + if (ppFolder) + { + *ppFolder = &m_Bonuses[iFolder]; + } +} + +void GamepadUIBonusMapsPanel::LoadBonusMap( const char *pszMapList, BonusMapDescription_t *pParent ) +{ + KeyValues *pMapListKV = new KeyValues( "MapList" ); + if (!pMapListKV->LoadFromFile( g_pFullFileSystem, pszMapList )) + { + pMapListKV->deleteThis(); + return; + } + + for (KeyValues *pSubKey = pMapListKV; pSubKey != NULL; pSubKey = pSubKey->GetNextKey()) + { + int i = m_Bonuses.AddToTail(); + + m_Bonuses[i].bIsFolder = false; + m_Bonuses[i].bComplete = false; + m_Bonuses[i].bLocked = pSubKey->GetBool( "lock" ); + Q_strncpy( m_Bonuses[i].szMapName, pSubKey->GetName(), sizeof( m_Bonuses[i].szMapName ) ); + Q_strncpy( m_Bonuses[i].szMapFileName, pSubKey->GetString( "map" ), sizeof( m_Bonuses[i].szMapFileName ) ); + Q_strncpy( m_Bonuses[i].szImageName, pSubKey->GetString( "image" ), sizeof( m_Bonuses[i].szImageName ) ); + Q_strncpy( m_Bonuses[i].szComment, pSubKey->GetString( "comment" ), sizeof( m_Bonuses[i].szComment ) ); + Q_strncpy( m_Bonuses[i].szFileName, pszMapList, sizeof( m_Bonuses[i].szFileName ) ); + + m_Bonuses[i].m_pParent = pParent; + + if (KeyValues *pChallenges = pSubKey->FindKey( "challenges" )) + { + for (KeyValues *pChallenge = pChallenges->GetFirstSubKey(); pChallenge != NULL; pChallenge = pChallenge->GetNextKey()) + { + int i2 = m_Bonuses[i].m_Challenges.AddToTail(); + + Q_strncpy( m_Bonuses[i].m_Challenges[i2].szName, pChallenge->GetName(), sizeof( m_Bonuses[i].m_Challenges[i2].szName ) ); + Q_strncpy( m_Bonuses[i].m_Challenges[i2].szComment, pChallenge->GetString("comment"), sizeof(m_Bonuses[i].m_Challenges[i2].szComment)); + m_Bonuses[i].m_Challenges[i2].iType = pChallenge->GetInt("type"); + m_Bonuses[i].m_Challenges[i2].iBronze = pChallenge->GetInt("bronze"); + m_Bonuses[i].m_Challenges[i2].iSilver = pChallenge->GetInt("silver"); + m_Bonuses[i].m_Challenges[i2].iGold = pChallenge->GetInt("gold"); + } + } + } + pMapListKV->deleteThis(); +} + +void GamepadUIBonusMapsPanel::ClearBonusMapsList() +{ + m_Bonuses.Purge(); + m_ScrollState.SetScrollTarget( 0, 0 ); +} + +inline int GamepadUIBonusMapsPanel::BonusCount() +{ + return m_Bonuses.Count(); +} + +inline BonusMapDescription_t *GamepadUIBonusMapsPanel::GetBonusData( int iMapIndex ) +{ + return &m_Bonuses[iMapIndex]; +} + +inline bool GamepadUIBonusMapsPanel::IsValidIndex( int iMapIndex ) +{ + return m_Bonuses.IsValidIndex( iMapIndex ); +} + +inline BonusMapDescription_t *GamepadUIBonusMapsPanel::GetCurrentFolder() +{ + if (m_BonusFolderHierarchy.Count() > 0) + return &m_BonusFolderHierarchy[0]; + return NULL; +} + +inline void GamepadUIBonusMapsPanel::BackFolder() +{ + if (m_BonusFolderHierarchy.Count() > 0) + m_BonusFolderHierarchy.Remove( 0 ); +} + +inline void GamepadUIBonusMapsPanel::EnterFolder( BonusMapDescription_t *pFolder ) +{ + int i = m_BonusFolderHierarchy.AddToHead(); + memcpy( &m_BonusFolderHierarchy[i], pFolder, sizeof( m_BonusFolderHierarchy[i] ) ); + if (m_BonusFolderHierarchy.Count() > 1) + m_BonusFolderHierarchy[i].m_pParent = &m_BonusFolderHierarchy.Element( i + 1 ); +} + +void GamepadUIBonusMapsPanel::SetCurrentChallengeObjectives( int iBronze, int iSilver, int iGold ) +{ + GamepadUI::GetInstance().SetCurrentChallengeObjectives( iBronze, iSilver, iGold ); +} + +void GamepadUIBonusMapsPanel::SetCurrentChallengeNames( const char *pszFileName, const char *pszMapName, const char *pszChallengeName ) +{ + GamepadUI::GetInstance().SetCurrentChallengeNames( pszFileName, pszMapName, pszChallengeName ); +} +#endif + +CON_COMMAND( gamepadui_openbonusmapsdialog, "" ) +{ + new GamepadUIBonusMapsPanel( GamepadUI::GetInstance().GetBasePanel(), "" ); +} + +#endif // GAMEPADUI_ENABLE_BONUSMAPS diff --git a/game/gamepadui/gamepadui_button.cpp b/game/gamepadui/gamepadui_button.cpp new file mode 100644 index 00000000..84e88da7 --- /dev/null +++ b/game/gamepadui/gamepadui_button.cpp @@ -0,0 +1,419 @@ +#include "gamepadui_button.h" +#include "gamepadui_interface.h" +#include "gamepadui_util.h" + +#include "vgui/IVGui.h" +#include "vgui/ISurface.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define DEFAULT_BTN_ARMED_SOUND "ui/buttonrollover.wav" +#define DEFAULT_BTN_RELEASED_SOUND "ui/buttonclickrelease.wav" + +ConVar gamepadui_center_footer_buttons( "gamepadui_center_footer_buttons", "1", FCVAR_NONE, "Centers footer buttons when not using gamepad" ); + +GamepadUIButton::GamepadUIButton( vgui::Panel *pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char *pCommand, const char *pText, const char *pDescription ) + : BaseClass( pParent, "", "", pActionSignalTarget, pCommand ) + , m_strButtonText( pText ) + , m_strButtonDescription( pDescription ) +{ + SetScheme( vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), pSchemeFile, "SchemePanel" ) ); +} + +GamepadUIButton::GamepadUIButton( vgui::Panel *pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char *pCommand, const wchar_t *pText, const wchar_t *pDescription ) + : BaseClass( pParent, "", "", pActionSignalTarget, pCommand ) + , m_strButtonText( pText ) + , m_strButtonDescription( pDescription ) +{ + SetScheme( vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), pSchemeFile, "SchemePanel" ) ); +} + +void GamepadUIButton::ApplySchemeSettings(vgui::IScheme* pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + const char *pButtonSound = pScheme->GetResourceString( "Button.Sound.Armed" ); + if (pButtonSound && *pButtonSound) + SetArmedSound( pButtonSound ); + else + SetArmedSound( DEFAULT_BTN_ARMED_SOUND ); + + pButtonSound = pScheme->GetResourceString( "Button.Sound.Released" ); + if (pButtonSound && *pButtonSound) + SetReleasedSound( pButtonSound ); + else + SetReleasedSound( DEFAULT_BTN_RELEASED_SOUND ); + + pButtonSound = pScheme->GetResourceString( "Button.Sound.Depressed" ); + if (pButtonSound && *pButtonSound) + SetDepressedSound( pButtonSound ); + + SetPaintBorderEnabled( false ); + SetPaintBackgroundEnabled( false ); + SetConsoleStylePanel( true ); + + UpdateSchemeProperties( this, pScheme ); + + m_hTextFont = pScheme->GetFont( "Button.Text.Font", true ); + m_hTextFontOver = pScheme->GetFont( "Button.Text.Font.Over", true ); + if (m_hTextFontOver == vgui::INVALID_FONT ) + m_hTextFontOver = m_hTextFont; + m_hDescriptionFont = pScheme->GetFont( "Button.Description.Font", true ); + + m_ePreviousState = ButtonStates::Out; + + m_flCachedExtraHeight = 0.0f; + if (!m_strButtonDescription.IsEmpty()) + { + if ( m_bDescriptionWrap ) + m_flCachedExtraHeight = DrawPrintWrappedText(m_hDescriptionFont, 0, 0, m_strButtonDescription.String(), m_strButtonDescription.Length(), m_flWidthAnimationValue[ButtonStates::Over] - 2 * (m_flDescriptionOffsetX + m_flTextOffsetX), false); + else + m_flCachedExtraHeight = 0.0f; + } + + float flX, flY; + if (GamepadUI::GetInstance().GetScreenRatio( flX, flY )) + { + if (flX != 1.0f) + { + m_flWidth *= flX; + for (int i = 0; i < ButtonStates::Count; i++) + m_flWidthAnimationValue[i] *= flX; + } + if (flY != 1.0f) + { + m_flHeight *= flY; + for (int i = 0; i < ButtonStates::Count; i++) + m_flHeightAnimationValue[i] *= flY; + } + } + + SetSize( m_flWidth, m_flHeight + m_flExtraHeight ); + DoAnimations( true ); +} + +void GamepadUIButton::RunAnimations( ButtonState state ) +{ + GAMEPADUI_RUN_ANIMATION_COMMAND( m_flWidth, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_flHeight, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_flTextOffsetX, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_flTextOffsetY, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_flDescriptionOffsetX, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_flDescriptionOffsetY, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colBackgroundColor, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colTextColor, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colDescriptionColor, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_flGlyphFade, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_bDescriptionHide, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_flTextLeftBorder, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colLeftBorder, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_flTextBottomBorder, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colBottomBorder, vgui::AnimationController::INTERPOLATOR_LINEAR ); +} + +void GamepadUIButton::DoAnimations( bool bForce ) +{ + ButtonState state = this->GetCurrentButtonState(); + if (m_ePreviousState != state || bForce) + { + this->RunAnimations( state ); + m_ePreviousState = state; + } + + SetSize(m_flWidth, m_flHeight + m_flExtraHeight); +} + +void GamepadUIButton::OnThink() +{ + BaseClass::OnThink(); + DoAnimations(); +} + +void GamepadUIButton::PaintButton() +{ + vgui::surface()->DrawSetColor(m_colBackgroundColor); + vgui::surface()->DrawFilledRect(0, 0, m_flWidth, m_flHeight + m_flExtraHeight); + + PaintBorders(); +} + +void GamepadUIButton::PaintBorders() +{ + if ( m_flTextLeftBorder ) + { + vgui::surface()->DrawSetColor(m_colLeftBorder); + vgui::surface()->DrawFilledRect(0, 0, m_flTextLeftBorder, m_flHeight + m_flExtraHeight); + } + + if ( m_flTextBottomBorder ) + { + vgui::surface()->DrawSetColor( m_colBottomBorder ); + vgui::surface()->DrawFilledRect( 0, m_flHeight + m_flExtraHeight - m_flTextBottomBorder, m_flWidth, m_flHeight + m_flExtraHeight ); + } +} + +int GamepadUIButton::PaintText() +{ + ButtonState state = this->GetCurrentButtonState(); + int nTextPosX = 0, nTextPosY = 0; + int nTextSizeX = 0, nTextSizeY = 0; + + if (!m_strButtonText.IsEmpty()) + { + vgui::surface()->DrawSetTextFont(state == ButtonStates::Out ? m_hTextFont : m_hTextFontOver); + vgui::surface()->GetTextSize(state == ButtonStates::Out ? m_hTextFont : m_hTextFontOver, m_strButtonText.String(), nTextSizeX, nTextSizeY); + + if (m_CenterX) + nTextPosX = m_flWidth / 2 - nTextSizeX / 2 + m_flTextOffsetX; + nTextPosX += m_flTextOffsetX; + nTextPosY = m_flHeight / 2 - nTextSizeY / 2 + m_flTextOffsetY; + } + +#if defined(HL2_RETAIL) || defined(STEAM_INPUT) + +#if defined(HL2_RETAIL) // Steam input and Steam Controller are not supported in SDK2013 (Madi) + if ( g_pInputSystem->IsSteamControllerActive() ) +#else + if (GamepadUI::GetInstance().GetSteamInput()->IsEnabled() && GamepadUI::GetInstance().GetSteamInput()->UseGlyphs()) +#endif + { + const int nGlyphSize = m_flHeight * 0.80f; + if ( m_glyph.SetupGlyph( nGlyphSize, FooterButtons::GetButtonActionHandleString( m_eFooterButton ) ) ) + { + int nGlyphPosX = m_flTextOffsetX; + if (m_CenterX) + { + nGlyphPosX = nTextPosX - m_flHeight / 2; + nTextPosX += m_flHeight / 2; + } + else + { + nTextPosX += nGlyphSize + (m_flTextOffsetX / 2); + } + int nGlyphPosY = m_flHeight / 2 - nGlyphSize / 2; + + int nAlpha = 255 * (1.0f - m_flGlyphFade); + + m_glyph.PaintGlyph( nGlyphPosX, nGlyphPosY, nGlyphSize, nAlpha ); + } + } + else +#endif // HL2_RETAIL + if (GetFooterButton() != FooterButtons::None && gamepadui_center_footer_buttons.GetBool() && !m_CenterX) + { + nTextPosX = m_flWidth / 2 - nTextSizeX / 2; + } + + if (!m_strButtonText.IsEmpty()) + { + vgui::surface()->DrawSetTextColor(m_colTextColor); + vgui::surface()->DrawSetTextFont(state == ButtonStates::Out ? m_hTextFont : m_hTextFontOver); + vgui::surface()->DrawSetTextPos(nTextPosX, nTextPosY); + vgui::surface()->DrawPrintText(m_strButtonText.String(), m_strButtonText.Length()); + } + + float flCurTime = GamepadUI::GetInstance().GetTime(); + const float flHeightTransTime = m_flHeightAnimationDuration; + float flNewExtraHeight = 0.0f; + if (!m_strButtonDescription.IsEmpty() && !m_bDescriptionHide) + { + vgui::surface()->DrawSetTextColor(m_colDescriptionColor); + vgui::surface()->DrawSetTextFont(m_hDescriptionFont); + if ( m_bDescriptionWrap ) + { + flNewExtraHeight = DrawPrintWrappedText(m_hDescriptionFont, nTextPosX + m_flDescriptionOffsetX, nTextPosY + nTextSizeY + m_flDescriptionOffsetY, m_strButtonDescription.String(), m_strButtonDescription.Length(), m_flWidth - 2 * (m_flDescriptionOffsetX + m_flTextOffsetX), true); + } + else + { + flNewExtraHeight = 0; + vgui::surface()->DrawSetTextPos( nTextPosX + m_flDescriptionOffsetX, nTextPosY + nTextSizeY + m_flDescriptionOffsetY ); + vgui::surface()->DrawPrintText( m_strButtonDescription.String(), m_strButtonDescription.Length() ); + } + } + + if ( m_flTargetExtraHeight != flNewExtraHeight) + { + m_flExtraHeightTime = flCurTime; + m_flLastExtraHeight = m_flExtraHeight; + m_flTargetExtraHeight = flNewExtraHeight; + } + + m_flExtraHeight = Lerp( Clamp( ( flCurTime - m_flExtraHeightTime ) / flHeightTransTime, 0.0f, 1.0f ), m_flLastExtraHeight, m_flTargetExtraHeight ); + + return nTextSizeX; +} + +void GamepadUIButton::Paint() +{ + BaseClass::Paint(); + + PaintButton(); + PaintText(); + + m_bNavigateTo = false; +} + + +void GamepadUIButton::OnKeyCodePressed( vgui::KeyCode code ) +{ + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + switch ( buttonCode ) + { +#ifdef HL2_RETAIL // Steam input and Steam Controller are not supported in SDK2013 (Madi) + case STEAMCONTROLLER_A: +#endif + + case KEY_XBUTTON_A: + case KEY_ENTER: + if ( IsEnabled() ) + { + ForceDepressed( true ); + m_bControllerPressed = true; + } + BaseClass::OnKeyCodePressed( code ); + // Forward back up to parents for our buttons. + if ( m_bForwardToParent ) + vgui::Panel::OnKeyCodePressed( code ); + break; + default: + if ( m_bControllerPressed ) + { + ForceDepressed( false ); + m_bControllerPressed = false; + } + BaseClass::OnKeyCodePressed( code ); + break; + } +} + +void GamepadUIButton::OnKeyCodeReleased( vgui::KeyCode code ) +{ + ButtonCode_t buttonCode = GetBaseButtonCode(code); + switch ( buttonCode ) + { +#ifdef HL2_RETAIL // Steam input and Steam Controller are not supported in SDK2013 (Madi) + case STEAMCONTROLLER_A: +#endif + + case KEY_XBUTTON_A: + case KEY_ENTER: + if ( IsEnabled() && IsDepressed() && m_bControllerPressed ) + { + ForceDepressed( false ); + DoClick(); + m_bControllerPressed = false; + } + // Forward back up to parents for our buttons. + if ( m_bForwardToParent ) + vgui::Panel::OnKeyCodeReleased( code ); + break; + default: + BaseClass::OnKeyCodeReleased( code ); + break; + } +} + +void GamepadUIButton::SetFooterButton( FooterButton button ) +{ + m_eFooterButton = button; + m_glyph.Cleanup(); +} + +FooterButton GamepadUIButton::GetFooterButton() const +{ + return m_eFooterButton; +} + +void GamepadUIButton::SetForwardToParent( bool bForwardToParent ) +{ + m_bForwardToParent = bForwardToParent; +} + +bool GamepadUIButton::GetForwardToParent() const +{ + return m_bForwardToParent; +} + +bool GamepadUIButton::IsFooterButton() const +{ + return m_eFooterButton != FooterButtons::None; +} + +ButtonState GamepadUIButton::GetCurrentButtonState() +{ + if ( IsDepressed() ) + return ButtonStates::Pressed; + else if ( HasFocus() && IsEnabled() ) + return ButtonStates::Over; + else if ( IsArmed() || m_bNavigateTo ) + { + if ( IsArmed() ) + m_bNavigateTo = false; + + return ButtonStates::Over; + } + else + return ButtonStates::Out; +} + +void GamepadUIButton::NavigateTo() +{ + BaseClass::NavigateTo(); + + if ( IsFooterButton() ) + return; + + if ( GetVParent() ) + { + KeyValues* msg = new KeyValues( "OnGamepadUIButtonNavigatedTo" ); + msg->SetInt( "button", ToHandle() ); + + vgui::ivgui()->PostMessage( GetVParent(), msg, GetVPanel() ); + } + + m_bNavigateTo = true; + RequestFocus( 0 ); +} + + +void GamepadUIButton::NavigateFrom() +{ + BaseClass::NavigateFrom(); + + m_bNavigateTo = false; +} + +void GamepadUIButton::OnCursorEntered() +{ +#ifdef STEAM_INPUT + if ( GamepadUI::GetInstance().GetSteamInput()->IsEnabled() || !IsEnabled() ) +#elif defined(HL2_RETAIL) // Steam input and Steam Controller are not supported in SDK2013 (Madi) + if ( g_pInputSystem->IsSteamControllerActive() || !IsEnabled() ) +#else + if ( !IsEnabled() ) +#endif + return; + + BaseClass::OnCursorEntered(); + + if ( IsFooterButton() || !m_bMouseNavigate ) + return; + + if ( GetParent() ) + GetParent()->NavigateToChild( this ); + else + NavigateTo(); +} + +void GamepadUIButton::FireActionSignal() +{ + BaseClass::FireActionSignal(); + + //PostMessageToAllSiblingsOfType< GamepadUIButton >( new KeyValues( "OnSiblingGamepadUIButtonOpened" ) ); +} + +void GamepadUIButton::OnSiblingGamepadUIButtonOpened() +{ + m_bNavigateTo = false; +} diff --git a/game/gamepadui/gamepadui_button.h b/game/gamepadui/gamepadui_button.h new file mode 100644 index 00000000..dceb8cca --- /dev/null +++ b/game/gamepadui/gamepadui_button.h @@ -0,0 +1,220 @@ +#ifndef GAMEPADUI_BUTTON_H +#define GAMEPADUI_BUTTON_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gamepadui_panel.h" +#include "gamepadui_string.h" +#include "gamepadui_glyph.h" +#include "vgui_controls/Button.h" + +namespace ButtonStates +{ + enum ButtonState + { + Out, + Over, + Pressed, + + Count, + }; +} +using ButtonState = ButtonStates::ButtonState; + +namespace FooterButtons +{ + enum FooterButton + { + None = 0, + Back = ( 1 << 0 ), + Cancel = ( 1 << 1 ), + LeftSelect = ( 1 << 2 ), + + // This button and any before are to the left. + LeftMask = ( LeftSelect | Back | Cancel ), + // Any buttons after here are to the right. + DeclineMask = ( Back | Cancel ), + + Select = ( 1 << 3 ), + Apply = ( 1 << 4 ), + Okay = ( 1 << 5 ), + Commentary = ( 1 << 6 ), + BonusMaps = ( 1 << 7 ), + Challenge = ( 1 << 8 ), + UseDefaults = ( 1 << 9 ), + Delete = ( 1 << 10 ), + + // Buttons that are 'confirmatory' + ConfirmMask = ( LeftSelect | Select | Okay ), + }; + static const int MaxFooterButtons = 11; + + inline const char* GetButtonName( FooterButton button ) + { + switch ( button ) + { + case Back: return "#GameUI_Back"; + case Cancel: return "#GameUI_Cancel"; + case LeftSelect: + case Select: return "#GameUI_Select"; + case Apply: return "#GameUI_Apply"; + case Okay: return "#GameUI_Ok"; + case Commentary: return "#GameUI_Commentary"; + case BonusMaps: return "#Deck_BonusMaps"; + case Challenge: return "#Deck_Challenges"; + case UseDefaults: return "#GameUI_UseDefaults"; + case Delete: return "#GameUI_Delete"; + } + return "Unknown"; + } + + inline const char* GetButtonAction( FooterButton button ) + { + switch ( button ) + { + case Back: return "action_back"; + case Cancel: return "action_cancel"; + case LeftSelect: + case Select: return "action_select"; + case Apply: return "action_apply"; + case Okay: return "action_okay"; + case Commentary: return "action_commentary"; + case BonusMaps: return "action_bonus_maps"; + case Challenge: return "action_challenges"; + case UseDefaults: return "action_usedefaults"; + case Delete: return "action_delete"; + } + return ""; + } + + inline const char* GetButtonActionHandleString( FooterButton button ) + { + switch ( button ) + { + case Back: return "menu_cancel"; + case Cancel: return "menu_cancel"; + case LeftSelect: + case Select: return "menu_select"; + case Apply: return "menu_y"; + case Okay: return "menu_select"; + case Commentary: return "menu_y"; + case BonusMaps: return "menu_x"; + case Challenge: return "menu_y"; + case UseDefaults: return "menu_x"; + case Delete: return "menu_x"; + } + return ""; + } + + inline FooterButton GetButtonByIdx( int i ) + { + return static_cast< FooterButton >( 1 << i ); + } + + using FooterButtonMask = unsigned int; +} +using FooterButton = FooterButtons::FooterButton; +using FooterButtonMask = FooterButtons::FooterButtonMask; + +class GamepadUIButton : public vgui::Button, public SchemeValueMap +{ + DECLARE_CLASS_SIMPLE( GamepadUIButton, vgui::Button ); +public: + GamepadUIButton( vgui::Panel *pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char *pCommand, const char *pText, const char *pDescription ); + GamepadUIButton( vgui::Panel *pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char *pCommand, const wchar_t *pText, const wchar_t *pDescription ); + + void ApplySchemeSettings( vgui::IScheme* pScheme ) OVERRIDE; + void OnThink() OVERRIDE; + void Paint() OVERRIDE; + void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE; + void OnKeyCodeReleased( vgui::KeyCode code ) OVERRIDE; + void NavigateTo() OVERRIDE; + void NavigateFrom() OVERRIDE; + void OnCursorEntered() OVERRIDE; + void FireActionSignal() OVERRIDE; + + MESSAGE_FUNC( OnSiblingGamepadUIButtonOpened, "OnSiblingGamepadUIButtonOpened" ); + + virtual void RunAnimations( ButtonState state ); + void DoAnimations( bool bForce = false ); + void PaintButton(); + void PaintBorders(); + int PaintText(); + + void SetPriority( int nPriority ) { m_nPriority = nPriority; } + int GetPriority() const { return m_nPriority; } + + void SetFooterButton( FooterButton button ); + FooterButton GetFooterButton() const; + + void SetForwardToParent( bool bForwardToParent ); + bool GetForwardToParent() const; + + GamepadUIString& GetButtonText() { return m_strButtonText; } + const GamepadUIString& GetButtonText() const { return m_strButtonText; } + GamepadUIString& GetButtonDescription() { return m_strButtonDescription; } + const GamepadUIString& GetButtonDescription() const { return m_strButtonDescription; } + + virtual ButtonState GetCurrentButtonState(); + + bool IsFooterButton() const; + + float GetDrawHeight() const { return m_flHeight + m_flExtraHeight; } + float GetMaxHeight() const { return m_flHeightAnimationValue[ ButtonStates::Over ] + m_flCachedExtraHeight; } + + void SetMouseNavigate( bool bMouseNavigate ) { m_bMouseNavigate = bMouseNavigate; } + +protected: + + ButtonState m_ePreviousState = ButtonStates::Out; + + bool m_bCursorOver = false; + bool m_bControllerPressed = false; + bool m_bNavigateTo = false; + bool m_bForwardToParent = false; + bool m_bMouseNavigate = true; + + float m_flExtraHeight = 0; + float m_flCachedExtraHeight = 0; + float m_flTargetExtraHeight = 0; + float m_flLastExtraHeight = 0; + float m_flExtraHeightTime = 0; + + FooterButton m_eFooterButton = FooterButtons::None; + + GamepadUIString m_strButtonText; + GamepadUIString m_strButtonDescription; + GamepadUIGlyph m_glyph; + +public: + + int m_nPriority = 0; + + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( float, m_flWidth, "Button.Width", "392", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( float, m_flHeight, "Button.Height", "40", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( float, m_flTextOffsetX, "Button.Text.OffsetX", "10", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( float, m_flTextOffsetY, "Button.Text.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( float, m_flDescriptionOffsetX, "Button.Description.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( float, m_flDescriptionOffsetY, "Button.Description.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colBackgroundColor, "Button.Background", "0 0 0 0", SchemeValueTypes::Color ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colTextColor, "Button.Text", "255 255 255 255", SchemeValueTypes::Color ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colDescriptionColor, "Button.Description", "255 255 255 255", SchemeValueTypes::Color ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( float, m_flGlyphFade, "Button.Glyphs.Fade", "0", SchemeValueTypes::Float ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( bool, m_bDescriptionHide, "Button.Description.Hide", "0", SchemeValueTypes::Bool ); + + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( float, m_flTextLeftBorder, "Button.Text.LeftBorder", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colLeftBorder, "Button.Background.LeftBorder", "0 0 0 0", SchemeValueTypes::Color ); + + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( float, m_flTextBottomBorder, "Button.Text.BottomBorder", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colBottomBorder, "Button.Background.BottomBorder", "0 0 0 0", SchemeValueTypes::Color ); + + GAMEPADUI_PANEL_PROPERTY( bool, m_CenterX, "Button.Text.CenterX", "0", SchemeValueTypes::Bool ); + GAMEPADUI_PANEL_PROPERTY( bool, m_bDescriptionWrap, "Button.Description.Wrap", "1", SchemeValueTypes::Bool ); + + vgui::HFont m_hTextFont = vgui::INVALID_FONT; + vgui::HFont m_hTextFontOver = vgui::INVALID_FONT; + vgui::HFont m_hDescriptionFont = vgui::INVALID_FONT; +}; + +#endif // GAMEPADUI_BUTTON_H diff --git a/game/gamepadui/gamepadui_episodic.vpc b/game/gamepadui/gamepadui_episodic.vpc new file mode 100644 index 00000000..266b6a20 --- /dev/null +++ b/game/gamepadui/gamepadui_episodic.vpc @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// GAMEPADUI_EPISODIC.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro GAMENAME "mod_episodic" // Stock SDK2013, Modders should replace this line with their mod folder name if needed. +$Macro OUTBINNAME "gamepadui" + +$Include "$SRCDIR\game\gamepadui\gamepadui_base.vpc" + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;GAMEPADUI_GAME_HL2;GAMEPADUI_GAME_EPISODIC" + } +} + +$Project "GamepadUI (Episodic)" +{ +} diff --git a/game/gamepadui/gamepadui_frame.cpp b/game/gamepadui/gamepadui_frame.cpp new file mode 100644 index 00000000..dc49942c --- /dev/null +++ b/game/gamepadui/gamepadui_frame.cpp @@ -0,0 +1,414 @@ +// 🐸 +#include "gamepadui_frame.h" +#include "gamepadui_button.h" +#include "gamepadui_interface.h" +#include "gamepadui_basepanel.h" + +#include "inputsystem/iinputsystem.h" +#include "vgui/ISurface.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +GamepadUIFrame::GamepadUIFrame( vgui::Panel *pParent, const char *pszPanelName, bool bShowTaskbarIcon, bool bPopup ) + : BaseClass( pParent, pszPanelName, bShowTaskbarIcon, bPopup ) +{ + SetConsoleStylePanel( true ); + // bodge to disable the frames title image and display our own + // (the frames title has an invalid zpos and does not draw over the garnish) + Frame::SetTitle( "", false ); + + if (pParent && pParent == GamepadUI::GetInstance().GetBasePanel()) + { + GamepadUIBasePanel *pPanel = static_cast(pParent); + pPanel->SetCurrentFrame( this ); + } + + memset( &m_pFooterButtons, 0, sizeof( m_pFooterButtons ) ); +} + +void GamepadUIFrame::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // Josh: Big load of common default state for us + MakeReadyForUse(); + SetMoveable( false ); + SetCloseButtonVisible( false ); + SetMinimizeButtonVisible( false ); + SetMaximizeButtonVisible( false ); + SetMenuButtonResponsive( false ); + SetMinimizeToSysTrayButtonVisible( false ); + SetSizeable( false ); + SetDeleteSelfOnClose( true ); + SetPaintBackgroundEnabled( false ); + SetPaintBorderEnabled( false ); + SetTitleBarVisible( false ); + + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + SetBounds( 0, 0, nParentW, nParentH ); + UpdateSchemeProperties( this, pScheme ); + + m_hTitleFont = pScheme->GetFont( "Title.Font", true ); + m_hGenericFont = pScheme->GetFont( "Generic.Text.Font", true ); +} + +void GamepadUIFrame::OnThink() +{ + BaseClass::OnThink(); + + LayoutFooterButtons(); +} + +void GamepadUIFrame::Paint() +{ + BaseClass::Paint(); + + PaintBackgroundGradients(); + PaintTitle(); +} + +void GamepadUIFrame::UpdateGradients() +{ +} + +void GamepadUIFrame::PaintBackgroundGradients() +{ + vgui::surface()->DrawSetColor( Color( 0, 0, 0, 255 ) ); + + GradientHelper* pGradients = GamepadUI::GetInstance().GetGradientHelper(); + const float flTime = GamepadUI::GetInstance().GetTime(); + GradientInfo gradients[ GradientSides::Count ] = + { + pGradients->GetGradient( GradientSides::Left, flTime ), + pGradients->GetGradient( GradientSides::Right, flTime ), + pGradients->GetGradient( GradientSides::Up, flTime ), + pGradients->GetGradient( GradientSides::Down, flTime ), + }; + + if ( gradients[ GradientSides::Left ].flExtent && + gradients[ GradientSides::Left ].flAmount ) + { + vgui::surface()->DrawFilledRectFade( + 0, + 0, + GetWide() * gradients[ GradientSides::Left ].flExtent, + GetTall(), + 255.0f * gradients[ GradientSides::Left ].flAmount, 0, + true); + } + + if ( gradients[ GradientSides::Right ].flExtent && + gradients[ GradientSides::Right ].flAmount ) + { + vgui::surface()->DrawFilledRectFade( + 0, + 0, + GetWide() * gradients[ GradientSides::Right ].flExtent, + GetTall(), + 0, 255.0f * gradients[ GradientSides::Right ].flAmount, + true); + } + + if ( gradients[ GradientSides::Down ].flExtent && + gradients[ GradientSides::Down ].flAmount ) + { + vgui::surface()->DrawFilledRectFade( + 0, + 0, + GetWide(), + GetTall() * gradients[ GradientSides::Down ].flExtent, + 255.0f * gradients[ GradientSides::Down ].flAmount, 0, + false); + } + + if ( gradients[ GradientSides::Up ].flExtent && + gradients[ GradientSides::Up ].flAmount ) + { + vgui::surface()->DrawFilledRectFade( + 0, + 0, + GetWide(), + GetTall() * gradients[ GradientSides::Up ].flExtent, + 0, 255.0f * gradients[ GradientSides::Up ].flAmount, + false); + } +} + +void GamepadUIFrame::PaintTitle() +{ + if ( m_strFrameTitle.IsEmpty() ) + return; + + vgui::surface()->DrawSetTextColor( m_colTitleColor ); + vgui::surface()->DrawSetTextFont( m_hTitleFont ); + vgui::surface()->DrawSetTextPos( m_flTitleOffsetX, m_flTitleOffsetY ); + vgui::surface()->DrawPrintText( m_strFrameTitle.String(), m_strFrameTitle.Length() ); +} + +void GamepadUIFrame::SetFooterButtons( FooterButtonMask mask, FooterButtonMask controllerOnlyMask ) +{ + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( m_pFooterButtons[i] ) + { + delete m_pFooterButtons[i]; + m_pFooterButtons[i] = NULL; + } + + FooterButton button = FooterButtons::GetButtonByIdx( i ); + if ( mask & button ) + { + m_pFooterButtons[i] = new GamepadUIButton( + this, this, + GAMEPADUI_DEFAULT_PANEL_SCHEME, FooterButtons::GetButtonAction( button ), + FooterButtons::GetButtonName( button ), "" ); + m_pFooterButtons[i]->SetFooterButton( button ); + // Make footer buttons render over everything. + m_pFooterButtons[i]->SetZPos( 100 ); + } + } + + m_ControllerOnlyFooterMask = controllerOnlyMask; +} + +void GamepadUIFrame::LayoutFooterButtons() +{ + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + int nLeftOffset = 0, nRightOffset = 0; + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( !m_pFooterButtons[i] || !m_pFooterButtons[i]->IsVisible() ) + continue; + + m_nFooterButtonWidth = m_pFooterButtons[i]->GetWide(); + m_nFooterButtonHeight = m_pFooterButtons[i]->GetTall(); + FooterButton button = FooterButtons::GetButtonByIdx( i ); + if ( m_bFooterButtonsStack ) + { + if ( button & FooterButtons::LeftMask ) + { + m_pFooterButtons[i]->SetPos( m_flFooterButtonsOffsetX, nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight - nLeftOffset ); + nLeftOffset += m_flFooterButtonsSpacing + m_pFooterButtons[i]->GetTall(); + } + else + { + m_pFooterButtons[i]->SetPos( nParentW - m_pFooterButtons[i]->GetWide() - m_flFooterButtonsOffsetX, nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight - nRightOffset ); + nRightOffset += m_flFooterButtonsSpacing + m_pFooterButtons[i]->GetTall(); + } + } + else + { + if ( button & FooterButtons::LeftMask ) + { + m_pFooterButtons[i]->SetPos( m_flFooterButtonsOffsetX + nLeftOffset, nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight ); + nLeftOffset += m_flFooterButtonsSpacing + m_pFooterButtons[i]->GetWide(); + } + else + { + m_pFooterButtons[i]->SetPos( nParentW - m_pFooterButtons[i]->GetWide() - nRightOffset - m_flFooterButtonsOffsetX, nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight ); + nRightOffset += m_flFooterButtonsSpacing + m_pFooterButtons[i]->GetWide(); + } + } + +#ifdef STEAM_INPUT + const bool bController = GamepadUI::GetInstance().GetSteamInput()->IsEnabled(); +#elif defined(HL2_RETAIL) // Steam input and Steam Controller are not supported in SDK2013 (Madi) + const bool bController = g_pInputSystem->IsSteamControllerActive(); +#else + const bool bController = ( g_pInputSystem->GetJoystickCount() >= 1 ); +#endif + + const bool bVisible = bController || !( m_ControllerOnlyFooterMask & button ); + m_pFooterButtons[i]->SetVisible( bVisible ); + } +} + +void GamepadUIFrame::OnClose() +{ + BaseClass::OnClose(); + + if (GetParent() && GetParent() == GamepadUI::GetInstance().GetBasePanel()) + { + GamepadUIBasePanel *pPanel = static_cast(GetParent()); + pPanel->SetCurrentFrame( NULL ); + } + + GamepadUIFrame *pFrame = dynamic_cast( GetParent() ); + if ( pFrame ) + pFrame->UpdateGradients(); + else + GamepadUI::GetInstance().ResetToMainMenuGradients(); +} + +void GamepadUIFrame::OnKeyCodePressed( vgui::KeyCode code ) +{ + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + switch (buttonCode) + { +#ifdef HL2_RETAIL // Steam input and Steam Controller are not supported in SDK2013 (Madi) + case STEAMCONTROLLER_A: +#endif + + case KEY_XBUTTON_A: + case KEY_ENTER: + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( FooterButtons::GetButtonByIdx(i) & FooterButtons::ConfirmMask ) + { + if ( m_pFooterButtons[i] ) + m_pFooterButtons[i]->ForceDepressed( true ); + } + } + break; + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_Y: +#endif + + case KEY_XBUTTON_Y: + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( FooterButtons::GetButtonByIdx(i) & ( FooterButtons::Apply | FooterButtons::Commentary | FooterButtons::Challenge ) ) + { + if ( m_pFooterButtons[i] ) + m_pFooterButtons[i]->ForceDepressed( true ); + } + } + break; + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_X: +#endif + + case KEY_XBUTTON_X: + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( FooterButtons::GetButtonByIdx(i) & ( FooterButtons::BonusMaps | FooterButtons::UseDefaults | FooterButtons::Delete ) ) + { + if ( m_pFooterButtons[i] ) + m_pFooterButtons[i]->ForceDepressed( true ); + } + } + break; + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_B: +#endif + + case KEY_XBUTTON_B: + case KEY_ESCAPE: + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( FooterButtons::GetButtonByIdx(i) & FooterButtons::DeclineMask ) + { + if ( m_pFooterButtons[i] ) + m_pFooterButtons[i]->ForceDepressed( true ); + } + } + break; + default: + BaseClass::OnKeyCodePressed( code ); + break; + } +} +void GamepadUIFrame::OnKeyCodeReleased( vgui::KeyCode code ) +{ + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + switch (buttonCode) + { + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_A: +#endif + + case KEY_XBUTTON_A: + case KEY_ENTER: + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( FooterButtons::GetButtonByIdx(i) & FooterButtons::ConfirmMask ) + { + if ( m_pFooterButtons[i] ) + { + if ( m_pFooterButtons[i]->IsDepressed() ) + { + m_pFooterButtons[i]->ForceDepressed( false ); + m_pFooterButtons[i]->DoClick(); + } + } + } + } + break; + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_Y: +#endif + + case KEY_XBUTTON_Y: + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( FooterButtons::GetButtonByIdx(i) & ( FooterButtons::Apply | FooterButtons::Commentary | FooterButtons::Challenge ) ) + { + if ( m_pFooterButtons[i] ) + { + if ( m_pFooterButtons[i]->IsDepressed() ) + { + m_pFooterButtons[i]->ForceDepressed( false ); + m_pFooterButtons[i]->DoClick(); + } + } + } + } + break; + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_X: +#endif + + case KEY_XBUTTON_X: + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( FooterButtons::GetButtonByIdx(i) & ( FooterButtons::BonusMaps | FooterButtons::UseDefaults | FooterButtons::Delete ) ) + { + if ( m_pFooterButtons[i] ) + { + if ( m_pFooterButtons[i]->IsDepressed() ) + { + m_pFooterButtons[i]->ForceDepressed( false ); + m_pFooterButtons[i]->DoClick(); + } + } + } + } + break; + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_B: +#endif + + case KEY_XBUTTON_B: + case KEY_ESCAPE: + for ( int i = 0; i < FooterButtons::MaxFooterButtons; i++ ) + { + if ( FooterButtons::GetButtonByIdx(i) & FooterButtons::DeclineMask ) + { + if ( m_pFooterButtons[i] ) + { + if ( m_pFooterButtons[i]->IsDepressed() ) + { + m_pFooterButtons[i]->ForceDepressed( false ); + m_pFooterButtons[i]->DoClick(); + } + } + } + } + break; + default: + BaseClass::OnKeyCodeReleased( code ); + break; + } +} + diff --git a/game/gamepadui/gamepadui_frame.h b/game/gamepadui/gamepadui_frame.h new file mode 100644 index 00000000..a87871c5 --- /dev/null +++ b/game/gamepadui/gamepadui_frame.h @@ -0,0 +1,55 @@ +#ifndef GAMEPADUI_FRAME_H +#define GAMEPADUI_FRAME_H +#ifdef _WIN32 +#pragma once +#endif +#include "gamepadui_panel.h" +#include "gamepadui_button.h" +#include "gamepadui_string.h" + +#include "vgui_controls/Frame.h" + +class GamepadUIFrame : public vgui::Frame, public SchemeValueMap +{ + DECLARE_CLASS_SIMPLE( GamepadUIFrame, vgui::Frame ); +public: + GamepadUIFrame( vgui::Panel *pParent, const char *pszPanelName, bool bShowTaskbarIcon = true, bool bPopup = true ); + void ApplySchemeSettings( vgui::IScheme* pScheme ) OVERRIDE; + void OnThink() OVERRIDE; + void Paint() OVERRIDE; + void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE; + void OnKeyCodeReleased( vgui::KeyCode code ) OVERRIDE; + + GamepadUIString& GetFrameTitle() { return m_strFrameTitle; } + const GamepadUIString& GetFrameTitle() const { return m_strFrameTitle; } + + void PaintTitle(); + void SetFooterButtons( FooterButtonMask mask, FooterButtonMask controllerOnlyMask = 0 ); + void OnClose(); + + virtual void UpdateGradients(); +protected: + void LayoutFooterButtons(); + void PaintBackgroundGradients(); + + GamepadUIString m_strFrameTitle; + GamepadUIButton *m_pFooterButtons[ FooterButtons::MaxFooterButtons ]; + FooterButtonMask m_ControllerOnlyFooterMask = 0; + + GAMEPADUI_PANEL_PROPERTY( Color, m_colTitleColor, "Title", "255 255 255 255", SchemeValueTypes::Color ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flTitleOffsetX, "Title.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flTitleOffsetY, "Title.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flFooterButtonsOffsetX, "FooterButtons.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flFooterButtonsOffsetY, "FooterButtons.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flFooterButtonsSpacing, "FooterButtons.Spacing", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( bool, m_bFooterButtonsStack, "FooterButtons.StackVertically", "0", SchemeValueTypes::Bool ); + + vgui::HFont m_hTitleFont = vgui::INVALID_FONT; + vgui::HFont m_hGenericFont = vgui::INVALID_FONT; + int m_nFooterButtonWidth = 0; + int m_nFooterButtonHeight = 0; +}; + +#endif // GAMEPADUI_FRAME_H diff --git a/game/gamepadui/gamepadui_genericconfirmation.cpp b/game/gamepadui/gamepadui_genericconfirmation.cpp new file mode 100644 index 00000000..4f28c7bf --- /dev/null +++ b/game/gamepadui/gamepadui_genericconfirmation.cpp @@ -0,0 +1,184 @@ +#include "gamepadui_interface.h" +#include "gamepadui_genericconfirmation.h" +#include "gamepadui_util.h" + +#include "ienginevgui.h" +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" + +#include "tier0/memdbgon.h" + +ConVar gamepadui_center_confirmation_panels( "gamepadui_center_confirmation_panels", "1", FCVAR_NONE, "Centers confirmation panels" ); + +GamepadUIGenericConfirmationPanel::GamepadUIGenericConfirmationPanel( vgui::Panel *pParent, const char* pPanelName, const char* pTitle, const char* pText, std::function pCommand, bool bSmallFont, bool bShowCancel ) + : BaseClass( pParent, pPanelName ) + , m_pCommand( std::move( pCommand ) ) + , m_pszGenericConfirmationFontName( bSmallFont ? "Generic.Text.Font" : "GenericConfirmation.Font" ) +{ + vgui::HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_DEFAULT_PANEL_SCHEME, "SchemePanel" ); + SetScheme( hScheme ); + + GetFrameTitle() = GamepadUIString( pTitle ); + m_strText = GamepadUIString( pText ); + FooterButtonMask buttons = FooterButtons::Okay; + if ( bShowCancel ) + buttons |= FooterButtons::Cancel; + SetFooterButtons( buttons ); + + Activate(); + + UpdateGradients(); +} + +GamepadUIGenericConfirmationPanel::GamepadUIGenericConfirmationPanel( vgui::Panel *pParent, const char* pPanelName, const wchar_t* pTitle, const wchar_t* pText, std::function pCommand, bool bSmallFont, bool bShowCancel ) + : BaseClass( pParent, pPanelName ) + , m_pCommand( std::move( pCommand ) ) + , m_pszGenericConfirmationFontName( bSmallFont ? "Generic.Text.Font" : "GenericConfirmation.Font" ) +{ + vgui::HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_DEFAULT_PANEL_SCHEME, "SchemePanel" ); + SetScheme( hScheme ); + + GetFrameTitle() = GamepadUIString( pTitle ); + m_strText = GamepadUIString( pText ); + FooterButtonMask buttons = FooterButtons::Okay; + if ( bShowCancel ) + buttons |= FooterButtons::Cancel; + SetFooterButtons( buttons ); + + Activate(); + + UpdateGradients(); +} + +void GamepadUIGenericConfirmationPanel::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_hGenericConfirmationFont = pScheme->GetFont( m_pszGenericConfirmationFontName, true ); + + if ( !m_strText.IsEmpty() && gamepadui_center_confirmation_panels.GetBool() ) + { + int nTall = DrawPrintWrappedText( m_hGenericConfirmationFont, m_flGenericConfirmationOffsetX, m_flGenericConfirmationOffsetY, m_strText.String(), m_strText.Length(), GetWide() - 2 * m_flGenericConfirmationOffsetX, false ); + + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + int nYOffset = (nParentH / 2 - nTall / 2) - (m_flTitleOffsetY * 0.5f); + + m_flFooterButtonsOffsetY = (nYOffset - m_flTitleOffsetY); + m_flFooterButtonsOffsetX *= 3; + + m_flTitleOffsetY = nYOffset - (m_flGenericConfirmationOffsetY - m_flTitleOffsetY); + m_flGenericConfirmationOffsetY = nYOffset; + } +} + +void GamepadUIGenericConfirmationPanel::PaintText() +{ + if ( m_strText.IsEmpty() ) + return; + + vgui::surface()->DrawSetTextColor( m_colGenericConfirmationColor ); + vgui::surface()->DrawSetTextFont( m_hGenericConfirmationFont ); + vgui::surface()->DrawSetTextPos( m_flGenericConfirmationOffsetX, m_flGenericConfirmationOffsetY ); + DrawPrintWrappedText( m_hGenericConfirmationFont, m_flGenericConfirmationOffsetX, m_flGenericConfirmationOffsetY, m_strText.String(), m_strText.Length(), GetWide() - 2 * m_flGenericConfirmationOffsetX, true ); +} + +void GamepadUIGenericConfirmationPanel::UpdateGradients() +{ + const float flTime = GamepadUI::GetInstance().GetTime(); + GamepadUI::GetInstance().GetGradientHelper()->ResetTargets( flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Up, { 1.0f, 1.0f }, flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Left, { 1.0f, 0.6667f }, flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Down, { 1.0f, 1.0f }, flTime ); +} + +void GamepadUIGenericConfirmationPanel::Paint() +{ + BaseClass::Paint(); + + PaintText(); + // Workaround focus shifting to main menu and getting the wrong gradients + // causing us to not over paint everything leading to weird left over bits of text + UpdateGradients(); +} + +void GamepadUIGenericConfirmationPanel::OnCommand( const char* pCommand ) +{ + if ( !V_strcmp( pCommand, "action_cancel" ) ) + { + Close(); + } + else if ( !V_strcmp( pCommand, "action_okay" ) ) + { + m_pCommand(); + Close(); + } +} + +CON_COMMAND( gamepadui_openquitgamedialog, "" ) +{ + new GamepadUIGenericConfirmationPanel( GamepadUI::GetInstance().GetBasePanel(), "QuitConfirmationPanel", "#GameUI_QuitConfirmationTitle", "#GameUI_QuitConfirmationText", + []() + { + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( "quit" ); + } ); +} + +CON_COMMAND( gamepadui_opengenerictextdialog, "Opens a generic text dialog.\nFormat: gamepadui_opengenerictextdialog <text> <small font (0 or 1)>" ) +{ + if (args.ArgC() < 4) + { + Msg("Format: gamepadui_opengenerictextdialog <title> <text> <small font (0 or 1)>\n"); + return; + } + + vgui::Panel *pParent = GamepadUI::GetInstance().GetCurrentFrame(); + if (!pParent) + pParent = GamepadUI::GetInstance().GetBasePanel(); + + new GamepadUIGenericConfirmationPanel( pParent, "GenericConfirmationPanel", args.Arg(1), args.Arg(2), + [](){}, args.Arg(3)[0] != '0', false ); +} + +CON_COMMAND( gamepadui_opengenericconfirmdialog, "Opens a generic confirmation dialog which executes a command.\nFormat: gamepadui_opengenericconfirmdialog <title> <text> <small font (0 or 1)> <command>\n<command> supports quotation marks as double apostrophes." ) +{ + if (args.ArgC() < 5) + { + Msg("Format: gamepadui_opengenericconfirmdialog <title> <text> <small font (0 or 1)> <command>\n"); + return; + } + + vgui::Panel *pParent = GamepadUI::GetInstance().GetCurrentFrame(); + if (!pParent) + pParent = GamepadUI::GetInstance().GetBasePanel(); + + // To get the command, we just use the remaining string after the small font parameter + // This method is fairly dirty and relies a bit on guesswork, but it allows spaces and quotes to be used + // without having to worry about how the initial dialog command handles it + const char *pCmd = args.GetCommandString(); + char *pSmallFont = V_strstr( pCmd, args.Arg( 3 )[0] != '0' ? " 1 " : " 0 " ); + if (!pSmallFont) + { + // Look for quotes instead + pSmallFont = V_strstr( pCmd, args.Arg( 3 )[0] != '0' ? " \"1\" " : " \"0\" " ); + if (pSmallFont) + pCmd += (pSmallFont - pCmd) + 5; + else + { + // Give up and use the 4th argument + pCmd = args.Arg( 4 ); + } + } + else + { + pCmd += (pSmallFont - pCmd) + 3; + } + + new GamepadUIGenericConfirmationPanel( pParent, "GenericConfirmationPanel", args.Arg(1), args.Arg(2), + [pCmd]() + { + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( pCmd ); + }, args.Arg(3)[0] != '0', true ); +} diff --git a/game/gamepadui/gamepadui_genericconfirmation.h b/game/gamepadui/gamepadui_genericconfirmation.h new file mode 100644 index 00000000..26750df2 --- /dev/null +++ b/game/gamepadui/gamepadui_genericconfirmation.h @@ -0,0 +1,42 @@ +#ifndef GAMEPADUI_GENERICCONFIRMATION_H +#define GAMEPADUI_GENERICCONFIRMATION_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gamepadui_frame.h" +#include "tier0/valve_minmax_off.h" +#include <functional> +#include "tier0/valve_minmax_on.h" + +class GamepadUIGenericConfirmationPanel : public GamepadUIFrame +{ + DECLARE_CLASS_SIMPLE( GamepadUIGenericConfirmationPanel, GamepadUIFrame ); + +public: + GamepadUIGenericConfirmationPanel( vgui::Panel *pParent, const char* pPanelName, const char *pTitle, const char *pText, std::function<void()> pCommand, bool bSmallFont = false, bool bShowCancel = true ); + GamepadUIGenericConfirmationPanel( vgui::Panel *pParent, const char* pPanelName, const wchar_t *pTitle, const wchar_t *pText, std::function<void()> pCommand, bool bSmallFont = false, bool bShowCancel = true ); + + void Paint() OVERRIDE; + void OnCommand( const char *pCommand ) OVERRIDE; + void ApplySchemeSettings( vgui::IScheme* pScheme ) OVERRIDE; + void UpdateGradients() OVERRIDE; + + +private: + void PaintText(); + + GamepadUIString m_strText; + + std::function<void()> m_pCommand; + + GAMEPADUI_PANEL_PROPERTY( Color, m_colGenericConfirmationColor, "GenericConfirmation", "255 255 255 255", SchemeValueTypes::Color ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flGenericConfirmationOffsetX, "GenericConfirmation.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flGenericConfirmationOffsetY, "GenericConfirmation.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + + vgui::HFont m_hGenericConfirmationFont; + const char *m_pszGenericConfirmationFontName; +}; + +#endif diff --git a/game/gamepadui/gamepadui_genericframes.cpp b/game/gamepadui/gamepadui_genericframes.cpp new file mode 100644 index 00000000..e1ba0ff6 --- /dev/null +++ b/game/gamepadui/gamepadui_genericframes.cpp @@ -0,0 +1,187 @@ +#include "gamepadui_interface.h" +#include "gamepadui_genericframes.h" +#include "gamepadui_util.h" + +#include "ienginevgui.h" +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" + +#include "tier0/memdbgon.h" + +GamepadUIGenericBasePanel::GamepadUIGenericBasePanel( vgui::Panel *pParent, const char* pPanelName, const char* pTitle, std::function<void()> pCommand, bool bShowCancel ) + : BaseClass( pParent, pPanelName ) + , m_pCommand( std::move( pCommand ) ) +{ + vgui::HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_DEFAULT_PANEL_SCHEME, "SchemePanel" ); + SetScheme( hScheme ); + + GetFrameTitle() = GamepadUIString( pTitle ); + FooterButtonMask buttons = FooterButtons::Okay; + if ( bShowCancel ) + buttons |= FooterButtons::Cancel; + SetFooterButtons( buttons ); + + Activate(); + + UpdateGradients(); +} + +GamepadUIGenericBasePanel::GamepadUIGenericBasePanel( vgui::Panel *pParent, const char* pPanelName, const wchar_t* pTitle, std::function<void()> pCommand, bool bShowCancel ) + : BaseClass( pParent, pPanelName ) + , m_pCommand( std::move( pCommand ) ) +{ + vgui::HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_DEFAULT_PANEL_SCHEME, "SchemePanel" ); + SetScheme( hScheme ); + + GetFrameTitle() = GamepadUIString( pTitle ); + FooterButtonMask buttons = FooterButtons::Okay; + if ( bShowCancel ) + buttons |= FooterButtons::Cancel; + SetFooterButtons( buttons ); + + Activate(); + + UpdateGradients(); +} + +void GamepadUIGenericBasePanel::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( CenterPanel() ) + { + int nWide, nTall; + ContentSize( nWide, nTall ); + + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + int nYOffset = (nParentH / 2 - nTall / 2) - (m_flTitleOffsetY * 0.5f); + + m_flFooterButtonsOffsetY = (nYOffset - m_flTitleOffsetY); + m_flFooterButtonsOffsetX *= 3; + + m_flTitleOffsetY = nYOffset - (m_flContentOffsetY - m_flTitleOffsetY); + m_flContentOffsetY = nYOffset; + } +} + +void GamepadUIGenericBasePanel::UpdateGradients() +{ + const float flTime = GamepadUI::GetInstance().GetTime(); + GamepadUI::GetInstance().GetGradientHelper()->ResetTargets( flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Up, { 1.0f, 1.0f }, flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Left, { 1.0f, 0.6667f }, flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Down, { 1.0f, 1.0f }, flTime ); +} + +void GamepadUIGenericBasePanel::Paint() +{ + BaseClass::Paint(); + + PaintContent(); + // Workaround focus shifting to main menu and getting the wrong gradients + // causing us to not over paint everything leading to weird left over bits of text + UpdateGradients(); +} + +void GamepadUIGenericBasePanel::OnCommand( const char* pCommand ) +{ + if ( !V_strcmp( pCommand, "action_cancel" ) ) + { + Close(); + } + else if ( !V_strcmp( pCommand, "action_okay" ) ) + { + m_pCommand(); + Close(); + } +} + +//----------------------------------------------------------------------------- + +GamepadUIGenericImagePanel::GamepadUIGenericImagePanel( vgui::Panel *pParent, const char* pPanelName, const char* pTitle, const char *pImage, float flImageWide, float flImageTall, std::function<void()> pCommand, bool bShowCancel ) + : BaseClass( pParent, pPanelName, pTitle, pCommand, bShowCancel ) +{ + const char *pszExt = V_GetFileExtension( pImage ); + if (pszExt && V_strncmp( pszExt, "tga", 4 ) == 0) + { + m_Image.SetTGAImage( pImage ); + } + else + { + m_Image.SetImage( pImage ); + } + + m_flBaseImageWide = flImageWide; + m_flBaseImageTall = flImageTall; +} + +GamepadUIGenericImagePanel::GamepadUIGenericImagePanel( vgui::Panel *pParent, const char* pPanelName, const wchar_t* pTitle, const char *pImage, float flImageWide, float flImageTall, std::function<void()> pCommand, bool bShowCancel ) + : BaseClass( pParent, pPanelName, pTitle, pCommand, bShowCancel ) +{ + const char *pszExt = V_GetFileExtension( pImage ); + if (pszExt && V_strncmp( pszExt, "tga", 4 ) == 0) + { + m_Image.SetTGAImage( pImage ); + } + else + { + m_Image.SetImage( pImage ); + } + + m_flBaseImageWide = flImageWide; + m_flBaseImageTall = flImageTall; +} + +void GamepadUIGenericImagePanel::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_flImageWide = float( vgui::scheme()->GetProportionalScaledValueEx( GetScheme(), int( m_flBaseImageWide ) ) ); + m_flImageTall = float( vgui::scheme()->GetProportionalScaledValueEx( GetScheme(), int( m_flBaseImageTall ) ) ); + + // For now, always center the image independent of the frame + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + m_flContentOffsetX = (nParentW / 2 - m_flImageWide / 2); + m_flContentOffsetY = (nParentH / 2 - m_flImageTall / 2); +} + +void GamepadUIGenericImagePanel::PaintContent() +{ + vgui::surface()->DrawSetTexture( m_Image ); + vgui::surface()->DrawSetColor( m_colContentColor ); + vgui::surface()->DrawTexturedRect( m_flContentOffsetX, m_flContentOffsetY, m_flContentOffsetX + m_flImageWide, m_flContentOffsetY + m_flImageTall ); + vgui::surface()->DrawSetTexture( 0 ); +} + +CON_COMMAND( gamepadui_opengenericimagedialog, "Opens a generic image dialog.\nFormat: gamepadui_opengenericimagedialog <title> <image> <width> <height> <optional: command>" ) +{ + if (args.ArgC() < 5) + { + Msg("Format: gamepadui_opengenericimagedialog <title> <image> <width> <height> <optional: command>\n"); + return; + } + + // Optional command + const char *pCmd = NULL; + if (args.ArgC() > 5) + pCmd = args.Arg( 5 ); + + // TODO: Parent to current frame + new GamepadUIGenericImagePanel( GamepadUI::GetInstance().GetBasePanel(), "GenericPanel", args.Arg(1), args.Arg(2), atof(args.Arg(3)), atof(args.Arg(4)), + [pCmd]() + { + if (pCmd) + { + // Replace '' with quotes + char szCmd[512]; + V_StrSubst( pCmd, "''", "\"", szCmd, sizeof(szCmd) ); + + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( szCmd ); + } + }, false ); +} diff --git a/game/gamepadui/gamepadui_genericframes.h b/game/gamepadui/gamepadui_genericframes.h new file mode 100644 index 00000000..0a8bbeb4 --- /dev/null +++ b/game/gamepadui/gamepadui_genericframes.h @@ -0,0 +1,62 @@ +#ifndef GAMEPADUI_GENERICFRAME_H +#define GAMEPADUI_GENERICFRAME_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gamepadui_frame.h" +#include "gamepadui_image.h" +#include "tier0/valve_minmax_off.h" +#include <functional> +#include "tier0/valve_minmax_on.h" + +class GamepadUIGenericBasePanel : public GamepadUIFrame +{ + DECLARE_CLASS_SIMPLE( GamepadUIGenericBasePanel, GamepadUIFrame ); + +public: + GamepadUIGenericBasePanel( vgui::Panel *pParent, const char *pPanelName, const char *pTitle, std::function<void()> pCommand, bool bShowCancel = true ); + GamepadUIGenericBasePanel( vgui::Panel *pParent, const char *pPanelName, const wchar_t *pTitle, std::function<void()> pCommand, bool bShowCancel = true ); + + void Paint() OVERRIDE; + void OnCommand( const char *pCommand ) OVERRIDE; + void ApplySchemeSettings( vgui::IScheme* pScheme ) OVERRIDE; + void UpdateGradients() OVERRIDE; + + virtual void PaintContent() {} + + virtual void ContentSize( int &nWide, int &nTall ) {} + virtual bool CenterPanel() { return false; } + +protected: + + GAMEPADUI_PANEL_PROPERTY( Color, m_colContentColor, "Content", "255 255 255 255", SchemeValueTypes::Color ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flContentOffsetX, "Content.OffsetX", "64", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flContentOffsetY, "Content.OffsetY", "110", SchemeValueTypes::ProportionalFloat ); + +private: + std::function<void()> m_pCommand; +}; + +class GamepadUIGenericImagePanel : public GamepadUIGenericBasePanel +{ + DECLARE_CLASS_SIMPLE( GamepadUIGenericImagePanel, GamepadUIGenericBasePanel ); + +public: + GamepadUIGenericImagePanel( vgui::Panel *pParent, const char* pPanelName, const char *pTitle, const char *pImage, float flImageWide, float flImageTall, std::function<void()> pCommand, bool bShowCancel = false ); + GamepadUIGenericImagePanel( vgui::Panel *pParent, const char* pPanelName, const wchar_t *pTitle, const char *pImage, float flImageWide, float flImageTall, std::function<void()> pCommand, bool bShowCancel = false ); + + void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + void PaintContent() OVERRIDE; + +private: + GamepadUIImage m_Image; + + float m_flBaseImageWide; + float m_flBaseImageTall; + float m_flImageWide; // Proportionally scaled + float m_flImageTall; // Proportionally scaled +}; + +#endif diff --git a/game/gamepadui/gamepadui_glyph.h b/game/gamepadui/gamepadui_glyph.h new file mode 100644 index 00000000..5c7fe6ad --- /dev/null +++ b/game/gamepadui/gamepadui_glyph.h @@ -0,0 +1,236 @@ +#ifndef GAMEPADUI_GLYPH_H +#define GAMEPADUI_GLYPH_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gamepadui_interface.h" +#include "gamepadui_util.h" + +#ifdef STEAM_INPUT +#include "img_png_loader.h" +#elif defined(HL2_RETAIL) // Steam input and Steam Controller are not supported in SDK2013 (Madi) +#include "steam/hl2/isteaminput.h" +#include "imageutils.h" +#endif // HL2_RETAIL + +#include "inputsystem/iinputsystem.h" +#include "bitmap/bitmap.h" + +class GamepadUIGlyph +{ +public: + GamepadUIGlyph() + { + m_nOriginTextures[0] = -1; + m_nOriginTextures[1] = -1; + } + + ~GamepadUIGlyph() + { + Cleanup(); + } + + bool SetupGlyph( int nSize, const char *pszAction, bool bBaseLight = false ) + { +#ifdef STEAM_INPUT + + if (m_pszActionOrigin && !V_strcmp(pszAction, m_pszActionOrigin)) + return IsValid(); + + Cleanup(); + + m_pszActionOrigin = pszAction; + + int iRealSize = nSize; + + int kStyles[2] = + { + //bBaseLight ? ESteamInputGlyphStyle_Light : ESteamInputGlyphStyle_Knockout, + //ESteamInputGlyphStyle_Dark, + bBaseLight ? 1 : 0, + 2, + }; + + for ( int i = 0; i < 2; i++ ) + { + CUtlVector <const char *> szStringList; + GamepadUI::GetInstance().GetSteamInput()->GetGlyphPNGsForCommand( szStringList, pszAction, iRealSize, kStyles[i] ); + if (szStringList.Count() == 0) + { + Cleanup(); + return false; + } + + CUtlMemory< byte > image; + int w, h; + if ( !PNGtoRGBA( g_pFullFileSystem, szStringList[0], image, w, h ) ) + { + Cleanup(); + return false; + } + + DevMsg( "Loaded png %dx%d\n", w, h ); + + m_nOriginTextures[i] = vgui::surface()->CreateNewTextureID(true); + if ( m_nOriginTextures[i] <= 0 ) + { + Cleanup(); + return false; + } + g_pMatSystemSurface->DrawSetTextureRGBA( m_nOriginTextures[i], image.Base(), w, h, true, false ); + } +#elif defined(HL2_RETAIL) // Steam input and Steam Controller are not supported in SDK2013 (Madi) + uint64 nSteamInputHandles[STEAM_INPUT_MAX_COUNT]; + if ( !GamepadUI::GetInstance().GetSteamAPIContext() || !GamepadUI::GetInstance().GetSteamAPIContext()->SteamInput() ) + return false; + + GamepadUI::GetInstance().GetSteamAPIContext()->SteamInput()->GetConnectedControllers( nSteamInputHandles ); + + uint64 nController = g_pInputSystem->GetActiveSteamInputHandle(); + if ( !nController ) + nController = nSteamInputHandles[0]; + + if ( !nController ) + return false; + + InputActionSetHandle_t hActionSet = GamepadUI::GetInstance().GetSteamAPIContext()->SteamInput()->GetCurrentActionSet( nController ); + InputDigitalActionHandle_t hDigitalAction = GamepadUI::GetInstance().GetSteamAPIContext()->SteamInput()->GetDigitalActionHandle( pszAction ); + if ( !hDigitalAction ) + { + Cleanup(); + return false; + } + + EInputActionOrigin eOrigins[STEAM_INPUT_MAX_ORIGINS] = {}; + int nOriginCount = GamepadUI::GetInstance().GetSteamAPIContext()->SteamInput()->GetDigitalActionOrigins( nController, hActionSet, hDigitalAction, eOrigins ); + EInputActionOrigin eOrigin = eOrigins[0]; + if ( !nOriginCount || eOrigin == k_EInputActionOrigin_None ) + { + Cleanup(); + return false; + } + + if ( m_eActionOrigin == eOrigin ) + return IsValid(); + + Cleanup(); + + m_eActionOrigin = eOrigin; + + if ( !IsPowerOfTwo( nSize ) ) + nSize = NextPowerOfTwo( nSize ); + + int nGlyphSize = 256; + ESteamInputGlyphSize eGlyphSize = k_ESteamInputGlyphSize_Large; + if (nSize <= 32) + { + eGlyphSize = k_ESteamInputGlyphSize_Small; + nGlyphSize = 32; + } + else if (nSize <= 128) + { + eGlyphSize = k_ESteamInputGlyphSize_Medium; + nGlyphSize = 128; + } + else + { + eGlyphSize = k_ESteamInputGlyphSize_Large; + nGlyphSize = 256; + } + + ESteamInputGlyphStyle kStyles[2] = + { + bBaseLight ? ESteamInputGlyphStyle_Light : ESteamInputGlyphStyle_Knockout, + ESteamInputGlyphStyle_Dark, + }; + + for ( int i = 0; i < 2; i++ ) + { + const char* pszGlyph = GamepadUI::GetInstance().GetSteamAPIContext()->SteamInput()->GetGlyphPNGForActionOrigin( eOrigin, eGlyphSize, kStyles[i] ); + if (!pszGlyph) + { + Cleanup(); + return false; + } + + Bitmap_t bitmap; + ConversionErrorType error = ImgUtl_LoadBitmap( pszGlyph, bitmap ); + if ( error != CE_SUCCESS ) + { + Cleanup(); + return false; + } + + ImgUtl_ResizeBitmap( bitmap, nSize, nSize, &bitmap ); + + m_nOriginTextures[i] = vgui::surface()->CreateNewTextureID(true); + if ( m_nOriginTextures[i] <= 0 ) + { + Cleanup(); + return false; + } + g_pMatSystemSurface->DrawSetTextureRGBAEx2( m_nOriginTextures[i], bitmap.GetBits(), bitmap.Width(), bitmap.Height(), ImageFormat::IMAGE_FORMAT_RGBA8888, true, false ); + } +#else + return false; + +#endif // HL2_RETAIL + + return true; + } + + void PaintGlyph( int nX, int nY, int nSize, int nBaseAlpha ) + { + int nPressedAlpha = 255 - nBaseAlpha; + + if ( nBaseAlpha ) + { + vgui::surface()->DrawSetColor( Color( 255, 255, 255, nBaseAlpha ) ); + vgui::surface()->DrawSetTexture( m_nOriginTextures[0] ); + vgui::surface()->DrawTexturedRect( nX, nY, nX + nSize, nY + nSize ); + } + + if ( nPressedAlpha ) + { + vgui::surface()->DrawSetColor( Color( 255, 255, 255, nPressedAlpha ) ); + vgui::surface()->DrawSetTexture( m_nOriginTextures[1] ); + vgui::surface()->DrawTexturedRect( nX, nY, nX + nSize, nY + nSize ); + } + + vgui::surface()->DrawSetTexture(0); + } + + bool IsValid() + { + return m_nOriginTextures[0] > 0 && m_nOriginTextures[1] > 0; + } + + void Cleanup() + { + for ( int i = 0; i < 2; i++ ) + { + if ( m_nOriginTextures[ i ] > 0 ) + vgui::surface()->DestroyTextureID( m_nOriginTextures[ i ] ); + m_nOriginTextures[ i ] = -1; + } + +#ifdef STEAM_INPUT + m_pszActionOrigin = NULL; +#elif defined(HL2_RETAIL) + m_eActionOrigin = k_EInputActionOrigin_None; +#endif // HL2_RETAIL + } + +private: + +#ifdef STEAM_INPUT + const char *m_pszActionOrigin = NULL; +#elif defined(HL2_RETAIL) + EInputActionOrigin m_eActionOrigin = k_EInputActionOrigin_None; +#endif // HL2_RETAIL + + int m_nOriginTextures[2]; +}; + +#endif diff --git a/game/gamepadui/gamepadui_gradient_helper.h b/game/gamepadui/gamepadui_gradient_helper.h new file mode 100644 index 00000000..6dd147c6 --- /dev/null +++ b/game/gamepadui/gamepadui_gradient_helper.h @@ -0,0 +1,77 @@ +#ifndef GAMEPADUI_GRADIENT_HELPER_H +#define GAMEPADUI_GRADIENT_HELPER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "igamepadui.h" +#include "mathlib/mathlib.h" + +namespace GradientSides +{ + enum GradientSide + { + Left, + Right, + Up, + Down, + + Count + }; +} +using GradientSide = GradientSides::GradientSide; + +struct GradientInfo +{ + float flAmount; + float flExtent; +}; + +class GradientHelper +{ +public: + GradientHelper() + { + memset( &m_LastGradientChange, 0, sizeof( m_LastGradientChange ) ); + memset( &m_LastGradientInfo, 0, sizeof( m_LastGradientInfo ) ); + memset( &m_TargetGradientInfos, 0, sizeof( m_TargetGradientInfos ) ); + } + + void ResetTargets( float flTime ) + { + for ( int i = 0; i < GradientSides::Count; i++ ) + SetTargetGradient( GradientSide(i), GradientInfo{}, flTime ); + } + + void SetTargetGradient( GradientSide side, GradientInfo info, float flTime ) + { + m_LastGradientInfo[side] = GetGradient( side, flTime ); + m_TargetGradientInfos[side] = info; + m_LastGradientChange[side] = flTime; + } + + GradientInfo GetGradient( GradientSide side, float flTime ) + { + const GradientInfo& target = m_TargetGradientInfos[side]; + const GradientInfo& last = m_LastGradientInfo[side]; + + float flFadeTime = SimpleSpline( + RemapValClamped( flTime, m_LastGradientChange[side], m_LastGradientChange[side] + 0.5f, 0.0f, 1.0f )); + + return GradientInfo + { + Lerp( flFadeTime, last.flAmount, target.flAmount ), + last.flAmount && target.flAmount && last.flExtent > target.flExtent + ? Lerp( flFadeTime, last.flExtent, target.flExtent ) + : target.flExtent + }; + } + +private: + float m_LastGradientChange [ GradientSides::Count ]; + GradientInfo m_LastGradientInfo [ GradientSides::Count ]; + GradientInfo m_TargetGradientInfos[ GradientSides::Count ]; +}; + + +#endif // GAMEPADUI_GRADIENT_HELPER_H diff --git a/game/gamepadui/gamepadui_hl2.vpc b/game/gamepadui/gamepadui_hl2.vpc new file mode 100644 index 00000000..752e2947 --- /dev/null +++ b/game/gamepadui/gamepadui_hl2.vpc @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// GAMEPADUI_HL2.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro GAMENAME "mod_hl2" // Stock SDK2013, Modders should replace this line with their mod folder name if needed. +$Macro OUTBINNAME "gamepadui" + +$Include "$SRCDIR\game\gamepadui\gamepadui_base.vpc" + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;GAMEPADUI_GAME_HL2" + } +} + +$Project "GamepadUI (HL2)" +{ +} diff --git a/game/gamepadui/gamepadui_image.h b/game/gamepadui/gamepadui_image.h new file mode 100644 index 00000000..eb527f51 --- /dev/null +++ b/game/gamepadui/gamepadui_image.h @@ -0,0 +1,67 @@ +#ifndef GAMEPADUI_IMAGE_H +#define GAMEPADUI_IMAGE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui_controls/Panel.h" +#include "bitmap/tgaloader.h" + +class GamepadUIImage +{ +public: + GamepadUIImage() + { + } + GamepadUIImage( const char* pName ) + { + SetImage( pName ); + } + ~GamepadUIImage() + { + Cleanup(); + } + void Cleanup() + { + if ( IsValid() ) + vgui::surface()->DestroyTextureID( m_nId ); + + m_nId = -1; + } + void SetImage( const char* pName ) + { + Cleanup(); + + m_nId = vgui::surface()->CreateNewTextureID(); + vgui::surface()->DrawSetTextureFile( m_nId, pName, true, false ); + } + void SetTGAImage( const char* pName ) + { + Cleanup(); + + CUtlMemory< unsigned char > tga; + int nWidth, nHeight; + if ( !TGALoader::LoadRGBA8888( pName, tga, nWidth, nHeight ) ) + return; + + m_nId = vgui::surface()->CreateNewTextureID( true ); + +#ifdef HL2_RETAIL // this crashes SDK2013 in the save/load menu (Madi) + g_pMatSystemSurface->DrawSetTextureRGBAEx2( m_nId, tga.Base(), nWidth, nHeight, IMAGE_FORMAT_RGBA8888, true ); +#else + g_pMatSystemSurface->DrawSetTextureRGBAEx( m_nId, tga.Base(), nWidth, nHeight, IMAGE_FORMAT_RGBA8888 ); +#endif + } + bool IsValid() + { + return m_nId > 0; + } + operator int() + { + return m_nId; + } +private: + int m_nId = -1; +}; + +#endif // GAMEPADUI_IMAGE_H diff --git a/game/gamepadui/gamepadui_interface.cpp b/game/gamepadui/gamepadui_interface.cpp new file mode 100644 index 00000000..d34309e2 --- /dev/null +++ b/game/gamepadui/gamepadui_interface.cpp @@ -0,0 +1,279 @@ +#include "gamepadui_interface.h" +#include "gamepadui_basepanel.h" +#include "gamepadui_mainmenu.h" + +#include "vgui/ILocalize.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static CDllDemandLoader s_GameUI( "GameUI" ); + +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( GamepadUI, IGamepadUI, GAMEPADUI_INTERFACE_VERSION, GamepadUI::GetInstance() ); + +GamepadUI *GamepadUI::s_pGamepadUI = NULL; + +GamepadUI& GamepadUI::GetInstance() +{ + if ( !s_pGamepadUI ) + s_pGamepadUI = new GamepadUI; + + return *s_pGamepadUI; +} + +void GamepadUI::Initialize( CreateInterfaceFn factory ) +{ + ConnectTier1Libraries( &factory, 1 ); + ConnectTier2Libraries( &factory, 1 ); + ConVar_Register( FCVAR_CLIENTDLL ); + ConnectTier3Libraries( &factory, 1 ); + + m_pEngineClient = (IVEngineClient*) factory( VENGINE_CLIENT_INTERFACE_VERSION, NULL ); + m_pEngineSound = (IEngineSound*) factory( IENGINESOUND_CLIENT_INTERFACE_VERSION, NULL ); + m_pEngineVGui = (IEngineVGui*) factory( VENGINE_VGUI_VERSION, NULL ); + m_pGameUIFuncs = (IGameUIFuncs*) factory( VENGINE_GAMEUIFUNCS_VERSION, NULL ); + m_pMaterialSystem = (IMaterialSystem*) factory( MATERIAL_SYSTEM_INTERFACE_VERSION, NULL ); + m_pMaterialSystemSurface = (IMatSystemSurface*) factory( MAT_SYSTEM_SURFACE_INTERFACE_VERSION, NULL ); + m_pRenderView = (IVRenderView*) factory( VENGINE_RENDERVIEW_INTERFACE_VERSION, NULL ); + m_pSoundEmitterSystemBase = (ISoundEmitterSystemBase*)factory( SOUNDEMITTERSYSTEM_INTERFACE_VERSION, NULL ); + + CreateInterfaceFn gameuiFactory = s_GameUI.GetFactory(); + if ( gameuiFactory ) + m_pGameUI = (IGameUI*) gameuiFactory( GAMEUI_INTERFACE_VERSION, NULL ); + + m_pAchievementMgr = (IAchievementMgr*) m_pEngineClient->GetAchievementMgr(); + + bool bFailed = !m_pEngineClient || + !m_pEngineSound || + !m_pEngineVGui || + !m_pGameUIFuncs || + !m_pMaterialSystem || + !m_pMaterialSystemSurface || + !m_pRenderView || + !m_pSoundEmitterSystemBase || + !m_pGameUI || + !m_pAchievementMgr; + if ( bFailed ) + { + GamepadUI_Log( "GamepadUI::Initialize() failed to get necessary interfaces.\n" ); + return; + } + + g_pVGuiLocalize->AddFile( "resource/gameui_%language%.txt", "GAME", true ); + g_pVGuiLocalize->AddFile( "resource/deck_%language%.txt", "GAME", true ); + +#if defined(HL2_RETAIL) || defined(STEAM_INPUT) + SteamAPI_InitSafe(); + SteamAPI_SetTryCatchCallbacks( false ); + m_SteamAPIContext.Init(); +#endif // HL2_RETAIL + + m_pBasePanel = new GamepadUIBasePanel( GetRootVPanel() ); + if ( !m_pBasePanel ) + { + GamepadUI_Log( "Failed to create BasePanel.\n" ); + return; + } + + GamepadUI_Log( "Overriding menu.\n" ); + + m_pGameUI->SetMainMenuOverride( GetBaseVPanel() ); + + m_pAnimationController = new vgui::AnimationController( m_pBasePanel ); + m_pAnimationController->SetProportional( false ); + + GetMainMenu()->Activate(); +} + +void GamepadUI::Shutdown() +{ + if ( m_pGameUI ) + m_pGameUI->SetMainMenuOverride( NULL ); + + if ( m_pBasePanel ) + m_pBasePanel->DeletePanel(); + +#if defined(HL2_RETAIL) || defined(STEAM_INPUT) + m_SteamAPIContext.Clear(); +#endif + + ConVar_Unregister(); + DisconnectTier3Libraries(); + DisconnectTier2Libraries(); + DisconnectTier1Libraries(); +} + + +void GamepadUI::OnUpdate( float flFrametime ) +{ + if ( m_pAnimationController ) + m_pAnimationController->UpdateAnimations( GetTime() ); +} + +void GamepadUI::OnLevelInitializePreEntity() +{ +} + +void GamepadUI::OnLevelInitializePostEntity() +{ + m_pBasePanel->OnMenuStateChanged(); + GetMainMenu()->OnMenuStateChanged(); +} + +void GamepadUI::OnLevelShutdown() +{ + if ( m_pAnimationController ) + { + m_pAnimationController->UpdateAnimations( GetTime() ); + m_pAnimationController->RunAllAnimationsToCompletion(); + } + + m_pBasePanel->OnMenuStateChanged(); + GetMainMenu()->OnMenuStateChanged(); +} + +void GamepadUI::VidInit() +{ + int w, h; + vgui::surface()->GetScreenSize( w, h ); + + Assert( w != 0 && h != 0 ); + + // Scale elements proportional to the aspect ratio's distance from 16:10 + const float flDefaultInvAspect = 0.625f; + float flInvAspectRatio = ((float)h) / ((float)w); + + if (flInvAspectRatio != flDefaultInvAspect) + { + m_flScreenXRatio = 1.0f - (flInvAspectRatio - flDefaultInvAspect); + } + else + { + m_flScreenXRatio = 1.0f; + } + + m_flScreenYRatio = 1.0f; + + m_pBasePanel->InvalidateLayout( false, true ); +} + + +bool GamepadUI::IsInLevel() const +{ + const char *pLevelName = m_pEngineClient->GetLevelName(); + return pLevelName && *pLevelName && !m_pEngineClient->IsLevelMainMenuBackground(); +} + +bool GamepadUI::IsInBackgroundLevel() const +{ + const char *pLevelName = m_pEngineClient->GetLevelName(); + return pLevelName && *pLevelName && m_pEngineClient->IsLevelMainMenuBackground(); +} + +bool GamepadUI::IsInMultiplayer() const +{ + return IsInLevel() && m_pEngineClient->GetMaxClients() > 1; +} + +bool GamepadUI::IsGamepadUIVisible() const +{ + return !IsInLevel() || IsInBackgroundLevel(); +} + + +void GamepadUI::ResetToMainMenuGradients() +{ + GetMainMenu()->UpdateGradients(); +} + +vgui::VPANEL GamepadUI::GetRootVPanel() const +{ + return m_pEngineVGui->GetPanel( PANEL_GAMEUIDLL ); +} + +vgui::Panel *GamepadUI::GetBasePanel() const +{ + return m_pBasePanel; +} + +vgui::VPANEL GamepadUI::GetBaseVPanel() const +{ + return m_pBasePanel ? m_pBasePanel->GetVPanel() : 0; +} + +vgui::Panel *GamepadUI::GetSizingPanel() const +{ + return m_pBasePanel ? m_pBasePanel->GetSizingPanel() : NULL; +} + +vgui::VPANEL GamepadUI::GetSizingVPanel() const +{ + return GetSizingPanel() ? GetSizingPanel()->GetVPanel() : 0; +} + +vgui::Panel *GamepadUI::GetMainMenuPanel() const +{ + return m_pBasePanel ? m_pBasePanel->GetMainMenuPanel() : NULL; +} + +vgui::VPANEL GamepadUI::GetMainMenuVPanel() const +{ + return GetMainMenuPanel() ? GetMainMenuPanel()->GetVPanel() : 0; +} + +GamepadUIMainMenu* GamepadUI::GetMainMenu() const +{ + return static_cast<GamepadUIMainMenu*>( GetMainMenuPanel() ); +} + +void GamepadUI::GetSizingPanelScale( float &flX, float &flY ) const +{ + vgui::Panel *pPanel = GetSizingPanel(); + if (!pPanel) + return; + static_cast<GamepadUISizingPanel*>(pPanel)->GetScale( flX, flY ); +} + +void GamepadUI::GetSizingPanelOffset( int &nX, int &nY ) const +{ + vgui::Panel *pPanel = GetSizingPanel(); + if (!pPanel) + return; + pPanel->GetPos( nX, nY ); +} + +GamepadUIFrame *GamepadUI::GetCurrentFrame() const +{ + return m_pBasePanel->GetCurrentFrame(); +} + +vgui::VPANEL GamepadUI::GetCurrentFrameVPanel() const +{ + return m_pBasePanel->GetCurrentFrame()->GetVPanel(); +} + +#ifdef MAPBASE +void GamepadUI::BonusMapChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName ) +{ + Q_strncpy( pchFileName, m_szChallengeFileName, sizeof( m_szChallengeFileName ) ); + Q_strncpy( pchMapName, m_szChallengeMapName, sizeof( m_szChallengeMapName ) ); + Q_strncpy( pchChallengeName, m_szChallengeName, sizeof( m_szChallengeName ) ); +} + +void GamepadUI::BonusMapChallengeObjectives( int &iBronze, int &iSilver, int &iGold ) +{ + iBronze = m_iBronze; iSilver = m_iSilver; iGold = m_iGold; +} + +void GamepadUI::SetCurrentChallengeObjectives( int iBronze, int iSilver, int iGold ) +{ + m_iBronze = iBronze; m_iSilver = iSilver; m_iGold = iGold; +} + +void GamepadUI::SetCurrentChallengeNames( const char *pszFileName, const char *pszMapName, const char *pszChallengeName ) +{ + Q_strncpy( m_szChallengeFileName, pszFileName, sizeof( m_szChallengeFileName ) ); + Q_strncpy( m_szChallengeMapName, pszMapName, sizeof( m_szChallengeMapName ) ); + Q_strncpy( m_szChallengeName, pszChallengeName, sizeof( m_szChallengeName ) ); +} +#endif diff --git a/game/gamepadui/gamepadui_interface.h b/game/gamepadui/gamepadui_interface.h new file mode 100644 index 00000000..19f977df --- /dev/null +++ b/game/gamepadui/gamepadui_interface.h @@ -0,0 +1,152 @@ +#ifndef GAMEPADUI_INTERFACE_H +#define GAMEPADUI_INTERFACE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "igamepadui.h" + +#include "cdll_int.h" +#include "engine/IEngineSound.h" +#include "gamepadui_gradient_helper.h" +#include "GameUI/IGameUI.h" +#include "iachievementmgr.h" +#include "ienginevgui.h" +#include "ivrenderview.h" +#include "materialsystem/imaterialsystem.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "tier1/interface.h" +#include "tier1/strtools.h" +#include "vgui_controls/AnimationController.h" +#include "vgui_controls/Panel.h" +#include "vgui/VGUI.h" +#include "VGuiMatSurface/IMatSystemSurface.h" +#include "view_shared.h" +#include "IGameUIFuncs.h" +#include "steam/steam_api.h" +#ifdef STEAM_INPUT +#include "expanded_steam/isteaminput.h" +#endif + +class GamepadUIBasePanel; +class GamepadUIMainMenu; + +#define GAMEPADUI_RESOURCE_FOLDER "gamepadui" CORRECT_PATH_SEPARATOR_S + +class GamepadUIBasePanel; +class GamepadUISizingPanel; +class GamepadUIFrame; + +class GamepadUI : public IGamepadUI +{ +public: + static GamepadUI& GetInstance(); + + void Initialize( CreateInterfaceFn factory ) OVERRIDE; + void Shutdown() OVERRIDE; + + void OnUpdate( float flFrametime ) OVERRIDE; + void OnLevelInitializePreEntity() OVERRIDE; + void OnLevelInitializePostEntity() OVERRIDE; + void OnLevelShutdown() OVERRIDE; + + void VidInit() OVERRIDE; + + bool IsInLevel() const; + bool IsInBackgroundLevel() const; + bool IsInMultiplayer() const; + + bool IsGamepadUIVisible() const; + + vgui::VPANEL GetRootVPanel() const; + vgui::Panel *GetBasePanel() const; + vgui::VPANEL GetBaseVPanel() const; + vgui::Panel *GetSizingPanel() const; + vgui::VPANEL GetSizingVPanel() const; + vgui::Panel *GetMainMenuPanel() const; + vgui::VPANEL GetMainMenuVPanel() const; + + IAchievementMgr *GetAchievementMgr() const { return m_pAchievementMgr; } + IEngineSound *GetEngineSound() const { return m_pEngineSound; } + IEngineVGui *GetEngineVGui() const { return m_pEngineVGui; } + IGameUI *GetGameUI() const { return m_pGameUI; } + IGameUIFuncs *GetGameUIFuncs() const { return m_pGameUIFuncs; } + IMaterialSystem *GetMaterialSystem() const { return m_pMaterialSystem; } + IMatSystemSurface *GetMaterialSystemSurface() const { return m_pMaterialSystemSurface; } + ISoundEmitterSystemBase *GetSoundEmitterSystemBase() const { return m_pSoundEmitterSystemBase; } + IVEngineClient *GetEngineClient() const { return m_pEngineClient; } + IVRenderView *GetRenderView() const { return m_pRenderView; } +#ifdef STEAM_INPUT + ISource2013SteamInput *GetSteamInput() const { return m_pSteamInput; } +#endif + + vgui::AnimationController *GetAnimationController() const { return m_pAnimationController; } + float GetTime() const { return Plat_FloatTime(); } + GradientHelper *GetGradientHelper() { return &m_GradientHelper; } + + void ResetToMainMenuGradients(); + + CSteamAPIContext* GetSteamAPIContext() { return &m_SteamAPIContext; } + + bool GetScreenRatio( float &flX, float &flY ) const { flX = m_flScreenXRatio; flY = m_flScreenYRatio; return (flX != 1.0f || flY != 1.0f); } + + void GetSizingPanelScale( float &flX, float &flY ) const; + void GetSizingPanelOffset( int &nX, int &nY ) const; + + GamepadUIFrame *GetCurrentFrame() const; + vgui::VPANEL GetCurrentFrameVPanel() const; + +#ifdef MAPBASE + void BonusMapChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName ) OVERRIDE; + void BonusMapChallengeObjectives( int &iBronze, int &iSilver, int &iGold ) OVERRIDE; + + void SetCurrentChallengeObjectives( int iBronze, int iSilver, int iGold ); + void SetCurrentChallengeNames( const char *pszFileName, const char *pszMapName, const char *pszChallengeName ); +#endif + +#ifdef STEAM_INPUT + // TODO: Replace with proper singleton interface in the future + void SetSteamInput( ISource2013SteamInput *pSteamInput ) override { m_pSteamInput = pSteamInput; } +#endif + +private: + + IEngineSound *m_pEngineSound = NULL; + IEngineVGui *m_pEngineVGui = NULL; + IGameUIFuncs *m_pGameUIFuncs = NULL; + IMaterialSystem *m_pMaterialSystem = NULL; + IMatSystemSurface *m_pMaterialSystemSurface = NULL; + ISoundEmitterSystemBase *m_pSoundEmitterSystemBase = NULL; + IVEngineClient *m_pEngineClient = NULL; + IVRenderView *m_pRenderView = NULL; + + IGameUI *m_pGameUI = NULL; + IAchievementMgr *m_pAchievementMgr = NULL; + +#ifdef STEAM_INPUT + ISource2013SteamInput *m_pSteamInput = NULL; +#endif + + vgui::AnimationController *m_pAnimationController = NULL; + GamepadUIBasePanel *m_pBasePanel = NULL; + + GradientHelper m_GradientHelper; + CSteamAPIContext m_SteamAPIContext; + + GamepadUIMainMenu* GetMainMenu() const; + + float m_flScreenXRatio = 1.0f; + float m_flScreenYRatio = 1.0f; + +#ifdef MAPBASE + char m_szChallengeFileName[MAX_PATH]; + char m_szChallengeMapName[48]; + char m_szChallengeName[48]; + + int m_iBronze, m_iSilver, m_iGold; +#endif + + static GamepadUI *s_pGamepadUI; +}; + +#endif // GAMEPADUI_INTERFACE_H diff --git a/game/gamepadui/gamepadui_mainmenu.cpp b/game/gamepadui/gamepadui_mainmenu.cpp new file mode 100644 index 00000000..1ad75a65 --- /dev/null +++ b/game/gamepadui/gamepadui_mainmenu.cpp @@ -0,0 +1,347 @@ +#include "gamepadui_interface.h" +#include "gamepadui_basepanel.h" +#include "gamepadui_mainmenu.h" + +#include "vgui/ISurface.h" +#include "vgui/ILocalize.h" +#include "vgui/IVGui.h" + +#include "KeyValues.h" +#include "filesystem.h" + +#include "tier0/memdbgon.h" + +#define GAMEPADUI_MAINMENU_SCHEME GAMEPADUI_RESOURCE_FOLDER "schememainmenu.res" +#define GAMEPADUI_MAINMENU_FILE GAMEPADUI_RESOURCE_FOLDER "mainmenu.res" + +#ifdef GAMEPADUI_GAME_EZ2 +ConVar gamepadui_show_ez2_version( "gamepadui_show_ez2_version", "1", FCVAR_NONE, "Show E:Z2 version in menu" ); +ConVar gamepadui_show_old_ui_button( "gamepadui_show_old_ui_button", "1", FCVAR_NONE, "Show button explaining how to switch to the old UI (Changes may not take effect until changing level)" ); +#endif + +GamepadUIMainMenu::GamepadUIMainMenu( vgui::Panel* pParent ) + : BaseClass( pParent, "MainMenu" ) +{ + vgui::HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_MAINMENU_SCHEME, "SchemeMainMenu" ); + SetScheme( hScheme ); + + KeyValues* pModData = new KeyValues( "ModData" ); + if ( pModData ) + { + if ( pModData->LoadFromFile( g_pFullFileSystem, "gameinfo.txt" ) ) + { + m_LogoText[ 0 ] = pModData->GetString( "gamepadui_title", pModData->GetString( "title" ) ); + m_LogoText[ 1 ] = pModData->GetString( "gamepadui_title2", pModData->GetString( "title2" ) ); + } + pModData->deleteThis(); + } + + LoadMenuButtons(); + + SetFooterButtons( FooterButtons::Select, FooterButtons::Select ); +} + +void GamepadUIMainMenu::UpdateGradients() +{ + const float flTime = GamepadUI::GetInstance().GetTime(); + GamepadUI::GetInstance().GetGradientHelper()->ResetTargets( flTime ); +#ifdef GAMEPADUI_GAME_EZ2 + // E:Z2 reduces the gradient so that the background map can be more easily seen + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Left, { 1.0f, GamepadUI::GetInstance().IsInBackgroundLevel() ? 0.333f : 0.666f }, flTime ); +#else + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Left, { 1.0f, 0.666f }, flTime ); +#endif + + // In case a controller is added mid-game + SetFooterButtons( FooterButtons::Select, FooterButtons::Select ); +} + +void GamepadUIMainMenu::LoadMenuButtons() +{ + KeyValues* pDataFile = new KeyValues( "MainMenuScript" ); + if ( pDataFile ) + { + if ( pDataFile->LoadFromFile( g_pFullFileSystem, GAMEPADUI_MAINMENU_FILE ) ) + { + for ( KeyValues* pData = pDataFile->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) + { + GamepadUIButton* pButton = new GamepadUIButton( + this, this, + GAMEPADUI_MAINMENU_SCHEME, + pData->GetString( "command" ), + pData->GetString( "text", "Sample Text" ), + pData->GetString( "description", "" ) ); + pButton->SetName( pData->GetName() ); + pButton->SetPriority( V_atoi( pData->GetString( "priority", "0" ) ) ); + pButton->SetVisible( true ); + + const char* pFamily = pData->GetString( "family", "all" ); + if ( !V_strcmp( pFamily, "ingame" ) || !V_strcmp( pFamily, "all" ) ) + m_Buttons[ GamepadUIMenuStates::InGame ].AddToTail( pButton ); + if ( !V_strcmp( pFamily, "mainmenu" ) || !V_strcmp( pFamily, "all" ) ) + m_Buttons[ GamepadUIMenuStates::MainMenu ].AddToTail( pButton ); + } + } + + pDataFile->deleteThis(); + } + +#ifdef GAMEPADUI_GAME_EZ2 + { + m_pSwitchToOldUIButton = new GamepadUIButton( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schememainmenu_olduibutton.res", + "cmd gamepadui_opengenerictextdialog #GameUI_SwitchToOldUI_Title #GameUI_SwitchToOldUI_Info 1", + "#GameUI_GameMenu_SwitchToOldUI", "" ); + m_pSwitchToOldUIButton->SetPriority( 0 ); + m_pSwitchToOldUIButton->SetVisible( true ); + } +#endif + + UpdateButtonVisibility(); +} + +void GamepadUIMainMenu::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + SetBounds( 0, 0, nParentW, nParentH ); + + const char *pImage = pScheme->GetResourceString( "Logo.Image" ); + if ( pImage && *pImage ) + m_LogoImage.SetImage( pImage ); + m_hLogoFont = pScheme->GetFont( "Logo.Font", true ); + +#ifdef GAMEPADUI_GAME_EZ2 + m_hVersionFont = pScheme->GetFont( "Version.Font", true ); + + ConVarRef ez2_version( "ez2_version" ); + m_strEZ2Version = ez2_version.GetString(); +#endif +} + +void GamepadUIMainMenu::LayoutMainMenu() +{ + int nY = GetCurrentButtonOffset(); + CUtlVector<GamepadUIButton*>& currentButtons = GetCurrentButtons(); + for ( GamepadUIButton *pButton : currentButtons ) + { + nY += pButton->GetTall(); + pButton->SetPos( m_flButtonsOffsetX, GetTall() - nY ); + nY += m_flButtonSpacing; + } + +#ifdef GAMEPADUI_GAME_EZ2 + if ( m_pSwitchToOldUIButton && m_pSwitchToOldUIButton->IsVisible() ) + { + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + m_pSwitchToOldUIButton->SetPos( m_flOldUIButtonOffsetX, nParentH - m_pSwitchToOldUIButton->m_flHeight - m_flOldUIButtonOffsetY ); + } +#endif +} + +void GamepadUIMainMenu::PaintLogo() +{ + vgui::surface()->DrawSetTextColor( m_colLogoColor ); + vgui::surface()->DrawSetTextFont( m_hLogoFont ); + + int nMaxLogosW = 0, nTotalLogosH = 0; + int nLogoW[ 2 ], nLogoH[ 2 ]; + for ( int i = 0; i < 2; i++ ) + { + nLogoW[ i ] = 0; + nLogoH[ i ] = 0; + if ( !m_LogoText[ i ].IsEmpty() ) + vgui::surface()->GetTextSize( m_hLogoFont, m_LogoText[ i ].String(), nLogoW[ i ], nLogoH[ i ] ); + nMaxLogosW = Max( nLogoW[ i ], nMaxLogosW ); + nTotalLogosH += nLogoH[ i ]; + } + + int nLogoY = GetTall() - ( GetCurrentLogoOffset() + nTotalLogosH ); + + if ( m_LogoImage.IsValid() ) + { + int nY1 = nLogoY; + int nY2 = nY1 + nLogoH[ 0 ]; + int nX1 = m_flLogoOffsetX; + int nX2 = nX1 + ( nLogoH[ 0 ] * 3 ); + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawSetTexture( m_LogoImage ); + vgui::surface()->DrawTexturedRect( nX1, nY1, nX2, nY2 ); + vgui::surface()->DrawSetTexture( 0 ); + } + else + { + for ( int i = 1; i >= 0; i-- ) + { + vgui::surface()->DrawSetTextPos( m_flLogoOffsetX, nLogoY ); + vgui::surface()->DrawPrintText( m_LogoText[ i ].String(), m_LogoText[ i ].Length() ); + + nLogoY -= nLogoH[ i ]; + } + } + +#ifdef GAMEPADUI_GAME_EZ2 + if (gamepadui_show_ez2_version.GetBool() && !m_strEZ2Version.IsEmpty()) + { + int nVersionW, nVersionH; + vgui::surface()->GetTextSize( m_hVersionFont, m_strEZ2Version.String(), nVersionW, nVersionH ); + + vgui::surface()->DrawSetTextColor( m_colVersionColor ); + vgui::surface()->DrawSetTextFont( m_hVersionFont ); + vgui::surface()->DrawSetTextPos( m_flLogoOffsetX + m_flVersionOffsetX + nLogoW[0], nLogoY + (nLogoH[0] * 2) - nVersionH); + vgui::surface()->DrawPrintText( m_strEZ2Version.String(), m_strEZ2Version.Length() ); + } +#endif +} + +void GamepadUIMainMenu::OnThink() +{ + BaseClass::OnThink(); + + LayoutMainMenu(); +} + +void GamepadUIMainMenu::Paint() +{ + BaseClass::Paint(); + + PaintLogo(); +} + +void GamepadUIMainMenu::OnCommand( char const* pCommand ) +{ + if ( StringHasPrefixCaseSensitive( pCommand, "cmd " ) ) + { + const char* pszClientCmd = &pCommand[ 4 ]; + if ( *pszClientCmd ) + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( pszClientCmd ); + + // This is a hack to reset bonus challenges in the event that the player disconnected before the map loaded. + // We have no known way of detecting that event and differentiating between a bonus level and non-bonus level being loaded, + // so for now, we just reset this when the player presses any menu button, as that indicates they are in the menu and no longer loading a bonus level + // (note that this does not cover loading a map through other means, like through the console) + ConVarRef sv_bonus_challenge( "sv_bonus_challenge" ); + if (sv_bonus_challenge.GetInt() != 0) + { + GamepadUI_Log( "Resetting sv_bonus_challenge\n" ); + sv_bonus_challenge.SetValue( 0 ); + } + } + else + { + BaseClass::OnCommand( pCommand ); + } +} + +void GamepadUIMainMenu::OnSetFocus() +{ + BaseClass::OnSetFocus(); + OnMenuStateChanged(); +} + +void GamepadUIMainMenu::OnMenuStateChanged() +{ + UpdateGradients(); + UpdateButtonVisibility(); +} + +void GamepadUIMainMenu::UpdateButtonVisibility() +{ + for ( CUtlVector<GamepadUIButton*>& buttons : m_Buttons ) + { + for ( GamepadUIButton* pButton : buttons ) + { + pButton->NavigateFrom(); + pButton->SetVisible( false ); + } + } + + CUtlVector<GamepadUIButton*>& currentButtons = GetCurrentButtons(); + currentButtons.Sort( []( GamepadUIButton* const* a, GamepadUIButton* const* b ) -> int + { + return ( ( *a )->GetPriority() > ( *b )->GetPriority() ); + }); + + for ( int i = 1; i < currentButtons.Count(); i++ ) + { + currentButtons[i]->SetNavDown( currentButtons[i - 1] ); + currentButtons[i - 1]->SetNavUp( currentButtons[i] ); + } + + for ( GamepadUIButton* pButton : currentButtons ) + pButton->SetVisible( true ); + + if ( !currentButtons.IsEmpty() ) + currentButtons[ currentButtons.Count() - 1 ]->NavigateTo(); + +#ifdef GAMEPADUI_GAME_EZ2 + if ( m_pSwitchToOldUIButton ) + { + if ( (!GamepadUI::GetInstance().GetSteamInput() || !GamepadUI::GetInstance().GetSteamInput()->IsSteamRunningOnSteamDeck()) && gamepadui_show_old_ui_button.GetBool() ) + { + m_pSwitchToOldUIButton->SetVisible( true ); + + if (!currentButtons.IsEmpty()) + { + currentButtons[ 0 ]->SetNavDown( m_pSwitchToOldUIButton ); + m_pSwitchToOldUIButton->SetNavUp( currentButtons[0] ); + } + } + else + { + m_pSwitchToOldUIButton->SetVisible( false ); + } + } +#endif +} + +void GamepadUIMainMenu::OnKeyCodeReleased( vgui::KeyCode code ) +{ + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + switch (buttonCode) + { +#ifdef HL2_RETAIL // Steam input and Steam Controller are not supported in SDK2013 (Madi) + case STEAMCONTROLLER_B: +#endif + + case KEY_XBUTTON_B: + if ( GamepadUI::GetInstance().IsInLevel() ) + { + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( "gamemenucommand resumegame" ); + // I tried it and didn't like it. + // Oh well. + //vgui::surface()->PlaySound( "UI/buttonclickrelease.wav" ); + } + break; + default: + BaseClass::OnKeyCodeReleased( code ); + break; + } +} + +GamepadUIMenuState GamepadUIMainMenu::GetCurrentMenuState() const +{ + if ( GamepadUI::GetInstance().IsInLevel() ) + return GamepadUIMenuStates::InGame; + return GamepadUIMenuStates::MainMenu; +} + +CUtlVector<GamepadUIButton*>& GamepadUIMainMenu::GetCurrentButtons() +{ + return m_Buttons[ GetCurrentMenuState() ]; +} + +float GamepadUIMainMenu::GetCurrentButtonOffset() +{ + return GetCurrentMenuState() == GamepadUIMenuStates::InGame ? m_flButtonsOffsetYInGame : m_flButtonsOffsetYMenu; +} + +float GamepadUIMainMenu::GetCurrentLogoOffset() +{ + return GetCurrentMenuState() == GamepadUIMenuStates::InGame ? m_flLogoOffsetYInGame : m_flLogoOffsetYMenu; +} diff --git a/game/gamepadui/gamepadui_mainmenu.h b/game/gamepadui/gamepadui_mainmenu.h new file mode 100644 index 00000000..6127c975 --- /dev/null +++ b/game/gamepadui/gamepadui_mainmenu.h @@ -0,0 +1,88 @@ +#ifndef GAMEPADUI_MAINMENU_H +#define GAMEPADUI_MAINMENU_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gamepadui_frame.h" +#include "gamepadui_button.h" +#include "gamepadui_image.h" + +namespace GamepadUIMenuStates +{ + enum GamepadUIMenuState + { + InGame, + MainMenu, + + Count + }; +} +using GamepadUIMenuState = GamepadUIMenuStates::GamepadUIMenuState; + +class GamepadUIMainMenu : public GamepadUIFrame +{ + DECLARE_CLASS_SIMPLE( GamepadUIMainMenu, GamepadUIFrame ); + +public: + GamepadUIMainMenu( vgui::Panel* pParent ); + + void ApplySchemeSettings( vgui::IScheme* pScheme ) OVERRIDE; + void OnCommand( char const* pCommand ) OVERRIDE; + void OnSetFocus() OVERRIDE; + void UpdateGradients() OVERRIDE; + + void OnThink() OVERRIDE; + void Paint() OVERRIDE; + + void LoadMenuButtons(); + void LayoutMainMenu(); + void PaintLogo(); + void OnMenuStateChanged(); + + void OnKeyCodeReleased( vgui::KeyCode code ); + +private: + + void UpdateButtonVisibility(); + GamepadUIMenuState GetCurrentMenuState() const; + CUtlVector<GamepadUIButton*>& GetCurrentButtons(); + float GetCurrentButtonOffset(); + float GetCurrentLogoOffset(); + + CUtlVector<GamepadUIButton*> m_Buttons[ GamepadUIMenuStates::Count ]; + +#ifdef GAMEPADUI_GAME_EZ2 + GamepadUIButton *m_pSwitchToOldUIButton; + + GAMEPADUI_PANEL_PROPERTY( float, m_flOldUIButtonOffsetX, "OldUIButton.OffsetX", "48", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flOldUIButtonOffsetY, "OldUIButton.OffsetY", "32", SchemeValueTypes::ProportionalFloat ); +#endif + + GamepadUIString m_LogoText[ 2 ]; + GamepadUIImage m_LogoImage; + + GAMEPADUI_PANEL_PROPERTY( float, m_flButtonSpacing, "Buttons.Space", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flButtonsOffsetX, "Buttons.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flButtonsOffsetYMenu, "Buttons.OffsetY.MainMenu", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flButtonsOffsetYInGame, "Buttons.OffsetY.InGame", "0", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flLogoOffsetX, "Logo.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flLogoOffsetYMenu, "Logo.OffsetY.MainMenu", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flLogoOffsetYInGame, "Logo.OffsetY.InGame", "0", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( Color, m_colLogoColor, "Logo", "255 255 255 255", SchemeValueTypes::Color ); + + vgui::HFont m_hLogoFont; + +#ifdef GAMEPADUI_GAME_EZ2 + GAMEPADUI_PANEL_PROPERTY( Color, m_colVersionColor, "Version", "255 128 0 255", SchemeValueTypes::Color ); + GAMEPADUI_PANEL_PROPERTY( float, m_flVersionOffsetX, "Version.OffsetX", "16", SchemeValueTypes::ProportionalFloat ); + + vgui::HFont m_hVersionFont; + + GamepadUIString m_strEZ2Version; +#endif +}; + +#endif diff --git a/game/gamepadui/gamepadui_newgame.cpp b/game/gamepadui/gamepadui_newgame.cpp new file mode 100644 index 00000000..75ff677b --- /dev/null +++ b/game/gamepadui/gamepadui_newgame.cpp @@ -0,0 +1,547 @@ +#include "gamepadui_interface.h" +#include "gamepadui_image.h" +#include "gamepadui_util.h" +#include "gamepadui_frame.h" +#include "gamepadui_scrollbar.h" +#include "gamepadui_genericconfirmation.h" + +#include "ienginevgui.h" +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" + +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/ImagePanel.h" +#include "vgui_controls/ScrollBar.h" + +#include "KeyValues.h" +#include "filesystem.h" + +#include "icommandline.h" + +#include "tier0/memdbgon.h" + +class GamepadUIChapterButton; +struct chapter_t; + +#define GAMEPADUI_CHAPTER_SCHEME GAMEPADUI_RESOURCE_FOLDER "schemechapterbutton.res" + +ConVar gamepadui_newgame_commentary_toggle( "gamepadui_newgame_commentary_toggle", "1", FCVAR_NONE, "Makes the commentary button toggle commentary mode instead of going straight into the game" ); + +// Modders should override this if necessary. (Madi) +// TODO - merge these into scheme config? +bool GameHasCommentary() +{ +#ifdef GAMEPADUI_GAME_EZ2 + // NOTE: Not all builds have commentary yet, so check for a file in the first map first + static bool bHasCommentary = g_pFullFileSystem->FileExists( "maps/ez2_c0_1_commentary.txt" ); + return bHasCommentary; +#else + const char *pszGameDir = CommandLine()->ParmValue( "-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) ); + return !V_strcmp( pszGameDir, "episodic" ) || + !V_strcmp( pszGameDir, "ep2" ) || + !V_strcmp( pszGameDir, "portal" ) || + !V_strcmp( pszGameDir, "lostcoast" ); +#endif +} + +bool GameHasBonusMaps() +{ + const char *pszGameDir = CommandLine()->ParmValue( "-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) ); + return !V_strcmp( pszGameDir, "portal" ); +} + +class GamepadUINewGamePanel : public GamepadUIFrame +{ + DECLARE_CLASS_SIMPLE( GamepadUINewGamePanel, GamepadUIFrame ); + +public: + GamepadUINewGamePanel( vgui::Panel *pParent, const char* pPanelName ); + + void UpdateGradients(); + + void OnThink() OVERRIDE; + void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + void OnCommand( char const* pCommand ) OVERRIDE; + + MESSAGE_FUNC_HANDLE( OnGamepadUIButtonNavigatedTo, "OnGamepadUIButtonNavigatedTo", button ); + + void LayoutChapterButtons(); + + void OnMouseWheeled( int delta ) OVERRIDE; + + void StartGame( int nChapter ); + + bool InCommentaryMode() const { return m_bCommentaryMode; } + + GamepadUIImage *GetCommentaryThumb( float &flSize, float &flOffsetX, float &flOffsetY ) + { + flSize = m_flCommentaryThumbSize; flOffsetX = m_flCommentaryThumbOffsetX; flOffsetY = m_flCommentaryThumbOffsetY; + return &m_CommentaryThumb; + } + +private: + CUtlVector< GamepadUIChapterButton* > m_pChapterButtons; + CUtlVector< chapter_t > m_Chapters; + + GamepadUIScrollState m_ScrollState; + + GamepadUIScrollBar *m_pScrollBar; + + GAMEPADUI_PANEL_PROPERTY( float, m_ChapterOffsetX, "Chapters.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_ChapterOffsetY, "Chapters.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_ChapterSpacing, "Chapters.Spacing", "0", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flCommentaryThumbSize, "Chapters.CommentaryThumb.Size", "64", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flCommentaryThumbOffsetX, "Chapters.CommentaryThumb.OffsetX", "8", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flCommentaryThumbOffsetY, "Chapters.CommentaryThumb.OffsetY", "8", SchemeValueTypes::ProportionalFloat ); + + bool m_bCommentaryMode = false; + GamepadUIImage m_CommentaryThumb; +}; + +class GamepadUIChapterButton : public GamepadUIButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUIChapterButton, GamepadUIButton ); + + GamepadUIChapterButton( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char* pSchemeFile, const char* pCommand, const char* pText, const char* pDescription, const char *pChapterImage ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_Image( pChapterImage ) + { + } + + GamepadUIChapterButton( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char* pSchemeFile, const char* pCommand, const wchar* pText, const wchar* pDescription, const char *pChapterImage ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_Image( pChapterImage ) + { + } + + ~GamepadUIChapterButton() + { + if ( s_pLastNewGameButton == this ) + s_pLastNewGameButton = NULL; + } + + ButtonState GetCurrentButtonState() OVERRIDE + { + if ( s_pLastNewGameButton == this ) + return ButtonState::Over; + return BaseClass::GetCurrentButtonState(); + } + + void Paint() OVERRIDE + { + int x, y, w, t; + GetBounds( x, y, w, t ); + + PaintButton(); + + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawSetTexture( m_Image ); + int imgH = ( w * 9 ) / 16; + //vgui::surface()->DrawTexturedRect( 0, 0, w, ); + float offset = m_flTextOffsetYAnimationValue[ButtonStates::Out] - m_flTextOffsetY; + vgui::surface()->DrawTexturedSubRect( 0, 0, w, imgH - offset, 0.0f, 0.0f, 1.0f, ( imgH - offset ) / imgH ); + vgui::surface()->DrawSetTexture( 0 ); + if ( !IsEnabled() ) + { + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 16 ) ); + vgui::surface()->DrawFilledRect( 0, 0, w, imgH - offset ); + } + + if ( GetParent() && gamepadui_newgame_commentary_toggle.GetBool() ) + { + GamepadUINewGamePanel *pPanel = static_cast<GamepadUINewGamePanel*>( GetParent() ); + if (pPanel && pPanel->InCommentaryMode()) + { + float flSize, flOffsetX, flOffsetY; + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawSetTexture( *pPanel->GetCommentaryThumb( flSize, flOffsetX, flOffsetY ) ); + vgui::surface()->DrawTexturedRect( flOffsetX, flOffsetY, flOffsetX + flSize, flOffsetY + flSize ); + vgui::surface()->DrawSetTexture( 0 ); + } + } + + PaintText(); + } + + void ApplySchemeSettings( vgui::IScheme* pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + float flX, flY; + if (GamepadUI::GetInstance().GetScreenRatio( flX, flY )) + { + if (flX != 1.0f) + { + m_flHeight *= flX; + for (int i = 0; i < ButtonStates::Count; i++) + m_flHeightAnimationValue[i] *= flX; + + // Also change the text offset + m_flTextOffsetY *= flX; + for (int i = 0; i < ButtonStates::Count; i++) + m_flTextOffsetYAnimationValue[i] *= flX; + } + + SetSize( m_flWidth, m_flHeight + m_flExtraHeight ); + DoAnimations( true ); + } + } + + void NavigateTo() OVERRIDE + { + BaseClass::NavigateTo(); + s_pLastNewGameButton = this; + } + + static GamepadUIChapterButton* GetLastNewGameButton() { return s_pLastNewGameButton; } + +private: + GamepadUIImage m_Image; + + static GamepadUIChapterButton *s_pLastNewGameButton; +}; + +GamepadUIChapterButton* GamepadUIChapterButton::s_pLastNewGameButton = NULL; + +/* From GameUI: */ +// TODO: Modders may need to override this. (Madi) +static const int MAX_CHAPTERS = 32; + +struct chapter_t +{ + char filename[32]; +}; + +static int __cdecl ChapterSortFunc( const void *elem1, const void *elem2 ) +{ + chapter_t *c1 = ( chapter_t * )elem1; + chapter_t *c2 = ( chapter_t * )elem2; + + // compare chapter number first + static int chapterlen = strlen( "chapter" ); + if ( atoi( c1->filename + chapterlen ) > atoi( c2->filename + chapterlen ) ) + return 1; + else if ( atoi( c1->filename + chapterlen ) < atoi( c2->filename + chapterlen ) ) + return -1; + + // compare length second ( longer string show up later in the list, eg. chapter9 before chapter9a ) + if ( strlen( c1->filename ) > strlen( c2->filename ) ) + return 1; + else if ( strlen( c1->filename ) < strlen( c2->filename ) ) + return -1; + + // compare strings third + return strcmp( c1->filename, c2->filename ); +} + +static int FindChapters( chapter_t *pChapters ) +{ + int chapterIndex = 0; + char szFullFileName[MAX_PATH]; + + FileFindHandle_t findHandle = FILESYSTEM_INVALID_FIND_HANDLE; + const char *fileName = "cfg/chapter*.cfg"; + fileName = g_pFullFileSystem->FindFirst( fileName, &findHandle ); + while ( fileName && chapterIndex < MAX_CHAPTERS ) + { + if ( fileName[0] ) + { + // Only load chapter configs from the current mod's cfg dir + // or else chapters appear that we don't want! + Q_snprintf( szFullFileName, sizeof( szFullFileName ), "cfg/%s", fileName ); + FileHandle_t f = g_pFullFileSystem->Open( szFullFileName, "rb", "MOD" ); + if ( f ) + { + // don't load chapter files that are empty, used in the demo + if ( g_pFullFileSystem->Size( f ) > 0 ) + { + Q_strncpy( pChapters[chapterIndex].filename, fileName, sizeof( pChapters[chapterIndex].filename ) ); + ++chapterIndex; + } + g_pFullFileSystem->Close( f ); + } + } + fileName = g_pFullFileSystem->FindNext( findHandle ); + } + + qsort( pChapters, chapterIndex, sizeof( chapter_t ), &ChapterSortFunc ); + return chapterIndex; +} +/* End from GameUI */ + +static int GetUnlockedChapters() +{ + ConVarRef var( "sv_unlockedchapters" ); + + return var.IsValid() ? MAX( var.GetInt(), 1 ) : 1; +} + +GamepadUINewGamePanel::GamepadUINewGamePanel( vgui::Panel *pParent, const char* PanelName ) : BaseClass( pParent, PanelName ) +{ + vgui::HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_DEFAULT_PANEL_SCHEME, "SchemePanel" ); + SetScheme( hScheme ); + + GetFrameTitle() = GamepadUIString( "#GameUI_NewGame" ); + FooterButtonMask buttons = FooterButtons::Back | FooterButtons::Select; + if ( GameHasCommentary() ) + buttons |= FooterButtons::Commentary; + if ( GameHasBonusMaps() ) + buttons |= FooterButtons::BonusMaps; + SetFooterButtons( buttons, FooterButtons::Select ); + + Activate(); + + chapter_t chapters[MAX_CHAPTERS]; + int nChapterCount = FindChapters( chapters ); + + for ( int i = 0; i < nChapterCount; i++ ) + { + const char *fileName = chapters[i].filename; + char chapterID[32] = { 0 }; + sscanf( fileName, "chapter%s", chapterID ); + // strip the extension + char *ext = V_stristr( chapterID, ".cfg" ); + if ( ext ) + { + *ext = 0; + } + const char* pGameDir = COM_GetModDirectory(); + + char chapterName[64]; + Q_snprintf( chapterName, sizeof( chapterName ), "#%s_Chapter%s_Title", pGameDir, chapterID ); + + char command[32]; + Q_snprintf( command, sizeof( command ), "chapter %d", i ); + + wchar_t text[32]; + wchar_t num[32]; + wchar_t* chapter = g_pVGuiLocalize->Find( "#GameUI_Chapter" ); + g_pVGuiLocalize->ConvertANSIToUnicode( chapterID, num, sizeof( num ) ); + _snwprintf( text, ARRAYSIZE( text ), L"%ls %ls", chapter ? chapter : L"CHAPTER", num ); + + GamepadUIString strChapterName( chapterName ); + + char chapterImage[64]; + Q_snprintf( chapterImage, sizeof( chapterImage ), "gamepadui/chapter%s.vmt", chapterID ); + GamepadUIChapterButton *pChapterButton = new GamepadUIChapterButton( + this, this, + GAMEPADUI_CHAPTER_SCHEME, command, + strChapterName.String(), text, chapterImage); + pChapterButton->SetEnabled( i < GetUnlockedChapters() ); + pChapterButton->SetPriority( i ); + pChapterButton->SetForwardToParent( true ); + + m_pChapterButtons.AddToTail( pChapterButton ); + m_Chapters.AddToTail( chapters[i] ); + } + + if ( m_pChapterButtons.Count() > 0 ) + m_pChapterButtons[0]->NavigateTo(); + + for ( int i = 1; i < m_pChapterButtons.Count(); i++ ) + { + m_pChapterButtons[i]->SetNavLeft( m_pChapterButtons[i - 1] ); + m_pChapterButtons[i - 1]->SetNavRight( m_pChapterButtons[i] ); + } + + m_CommentaryThumb.SetImage( "vgui/hud/icon_commentary" ); + + UpdateGradients(); + + m_pScrollBar = new GamepadUIScrollBar( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemescrollbar.res", + NULL, true ); +} + +void GamepadUINewGamePanel::UpdateGradients() +{ + const float flTime = GamepadUI::GetInstance().GetTime(); + GamepadUI::GetInstance().GetGradientHelper()->ResetTargets( flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Up, { 1.0f, 1.0f }, flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Down, { 1.0f, 0.5f }, flTime ); +} + +void GamepadUINewGamePanel::OnThink() +{ + BaseClass::OnThink(); + + LayoutChapterButtons(); +} + +void GamepadUINewGamePanel::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + float flX, flY; + if (GamepadUI::GetInstance().GetScreenRatio( flX, flY )) + { + m_ChapterOffsetX *= (flX*flX); + m_ChapterOffsetX *= (flY*flY); + } + + if (m_pChapterButtons.Count() > 0) + { + m_pScrollBar->InitScrollBar( &m_ScrollState, m_ChapterOffsetX, m_ChapterOffsetY + m_pChapterButtons[0]->GetTall() + m_ChapterSpacing ); + } +} + +void GamepadUINewGamePanel::OnGamepadUIButtonNavigatedTo( vgui::VPANEL button ) +{ + GamepadUIButton *pButton = dynamic_cast< GamepadUIButton * >( vgui::ipanel()->GetPanel( button, GetModuleName() ) ); + if ( pButton ) + { + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + int nX, nY; + pButton->GetPos( nX, nY ); + if ( nX + pButton->m_flWidth > nParentW || nX < 0 ) + { + int nTargetX = pButton->GetPriority() * (pButton->m_flWidth + m_ChapterSpacing); + + if ( nX < nParentW / 2 ) + { + nTargetX += nParentW - m_ChapterOffsetX; + // Add a bit of spacing to make this more visually appealing :) + nTargetX -= m_ChapterSpacing; + } + else + { + nTargetX += pButton->m_flWidth; + // Add a bit of spacing to make this more visually appealing :) + nTargetX += (pButton->m_flWidth / 2) + m_ChapterSpacing; + } + + + m_ScrollState.SetScrollTarget( nTargetX - ( nParentW - m_ChapterOffsetX ), GamepadUI::GetInstance().GetTime() ); + } + } +} + +void GamepadUINewGamePanel::LayoutChapterButtons() +{ + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + float flScrollClamp = m_ChapterOffsetX; + for ( int i = 0; i < m_pChapterButtons.Count(); i++ ) + { + int nSize = ( m_pChapterButtons[0]->GetWide() + m_ChapterSpacing ); + + if ( i < m_pChapterButtons.Count() - 2 ) + flScrollClamp += nSize; + } + + m_ScrollState.UpdateScrollBounds( 0.0f, flScrollClamp ); + + if (m_pChapterButtons.Count() > 0) + { + m_pScrollBar->UpdateScrollBounds( 0.0f, flScrollClamp, + ( m_pChapterButtons[0]->GetWide() + m_ChapterSpacing ) * 2.0f, nParentW - (m_ChapterOffsetX*2.0f) ); + } + + for ( int i = 0; i < m_pChapterButtons.Count(); i++ ) + { + int size = ( m_pChapterButtons[0]->GetWide() + m_ChapterSpacing ); + + m_pChapterButtons[i]->SetPos( m_ChapterOffsetX + i * size - m_ScrollState.GetScrollProgress(), m_ChapterOffsetY ); + m_pChapterButtons[i]->SetVisible( true ); + } + + m_ScrollState.UpdateScrolling( 2.0f, GamepadUI::GetInstance().GetTime() ); +} + +void GamepadUINewGamePanel::OnCommand( char const* pCommand ) +{ + if ( !V_strcmp( pCommand, "action_back" ) ) + { + Close(); + } + else if ( !V_strcmp( pCommand, "action_commentary" ) ) + { + GamepadUIChapterButton *pPanel = GamepadUIChapterButton::GetLastNewGameButton(); + if ( pPanel ) + { + if ( gamepadui_newgame_commentary_toggle.GetBool() ) + { + m_bCommentaryMode = !m_bCommentaryMode; + } + else + { + m_bCommentaryMode = true; + pPanel->DoClick(); + } + } + } + else if ( !V_strcmp( pCommand, "action_bonus_maps" ) ) + { + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( "gamepadui_openbonusmapsdialog\n" ); + } + else if ( StringHasPrefixCaseSensitive( pCommand, "chapter " ) ) + { + const char* pszEngineCommand = pCommand + 8; + if ( *pszEngineCommand ) + StartGame( atoi( pszEngineCommand ) ); + + Close(); + } + else + { + BaseClass::OnCommand( pCommand ); + } +} + +void GamepadUINewGamePanel::OnMouseWheeled( int nDelta ) +{ + m_ScrollState.OnMouseWheeled( nDelta * m_ChapterSpacing * 20.0f, GamepadUI::GetInstance().GetTime() ); +} + +struct MapCommand_t +{ + char szCommand[512]; +}; + +void GamepadUINewGamePanel::StartGame( int nChapter ) +{ + MapCommand_t cmd; + cmd.szCommand[0] = 0; + Q_snprintf( cmd.szCommand, sizeof( cmd.szCommand ), "disconnect\ndeathmatch 0\nprogress_enable\nexec %s\n", m_Chapters[nChapter].filename ); + + // Set commentary + ConVarRef commentary( "commentary" ); + commentary.SetValue( m_bCommentaryMode ); + + ConVarRef sv_cheats( "sv_cheats" ); + sv_cheats.SetValue( m_bCommentaryMode ); + + // If commentary is on, we go to the explanation dialog ( but not for teaser trailers ) + if ( m_bCommentaryMode )//&& !m_ChapterPanels[m_iSelectedChapter]->IsTeaserChapter() ) + { + // Check our current state and disconnect us from any multiplayer server we're connected to. + // This fixes an exploit where players would click "start" on the commentary dialog to enable + // sv_cheats on the client ( via the code above ) and then hit <esc> to get out of the explanation dialog. + if ( GamepadUI::GetInstance().IsInMultiplayer() ) + { + GamepadUI::GetInstance().GetEngineClient()->ExecuteClientCmd( "disconnect" ); + } + + new GamepadUIGenericConfirmationPanel( GamepadUI::GetInstance().GetBasePanel(), "SaveOverwriteConfirmationPanel", "#GameUI_LoadCommentary", "#GAMEUI_Commentary_Console_Explanation", + [cmd]() + { + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( cmd.szCommand ); + }, true); + } + else + { + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( cmd.szCommand ); + } +} + +CON_COMMAND( gamepadui_opennewgamedialog, "" ) +{ + new GamepadUINewGamePanel( GamepadUI::GetInstance().GetBasePanel(), "" ); +} diff --git a/game/gamepadui/gamepadui_options.cpp b/game/gamepadui/gamepadui_options.cpp new file mode 100644 index 00000000..5ff3e562 --- /dev/null +++ b/game/gamepadui/gamepadui_options.cpp @@ -0,0 +1,2586 @@ +#include "gamepadui_interface.h" +#include "gamepadui_button.h" +#include "gamepadui_frame.h" +#include "gamepadui_scrollbar.h" +#include "gamepadui_image.h" +#include "gamepadui_genericconfirmation.h" + +#include "ienginevgui.h" +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" +#include "vgui/IInput.h" + +#include "vgui_controls/ComboBox.h" + +#include "KeyValues.h" +#include "filesystem.h" +#include "utlbuffer.h" +#include "inputsystem/iinputsystem.h" +#include "materialsystem/materialsystem_config.h" + +#if defined( USE_SDL ) +#include "SDL.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +const int MAX_OPTIONS_TABS = 8; + +#define GAMEPADUI_OPTIONS_FILE GAMEPADUI_RESOURCE_FOLDER "options.res" + +class GamepadUIOptionButton; +class GamepadUIWheelyWheel; +void OnResolutionsNeedUpdate( IConVar *var, const char *pOldValue, float flOldValue ); + +extern CUtlSymbolTable g_ButtonSoundNames; + +ConVar _gamepadui_water_detail( "_gamepadui_water_detail", "0" ); +ConVar _gamepadui_shadow_detail( "_gamepadui_shadow_detail", "0" ); +ConVar _gamepadui_antialiasing( "_gamepadui_antialiasing", "0" ); +ConVar _gamepadui_aspectratio( "_gamepadui_aspectratio", "0", FCVAR_NONE, "", OnResolutionsNeedUpdate ); +ConVar _gamepadui_displaymode( "_gamepadui_displaymode", "0", FCVAR_NONE, "", OnResolutionsNeedUpdate ); +ConVar _gamepadui_resolution( "_gamepadui_resolution", "0" ); +ConVar _gamepadui_sound_quality( "_gamepadui_sound_quality", "0" ); +ConVar _gamepadui_closecaptions( "_gamepadui_closecaptions", "0" ); +#ifdef HL2_RETAIL +ConVar _gamepadui_hudaspect( "_gamepadui_hudaspect", "0" ); +#endif +ConVar _gamepadui_skill( "_gamepadui_skill", "0" ); + +struct GamepadUITab +{ + GamepadUIButton *pTabButton; + CUtlVector< GamepadUIOptionButton* > pButtons; + GamepadUIScrollState ScrollState; + bool bAlternating; + bool bHorizontal; + CUtlVector<CUtlSymbol> KeysToUnbind; +}; + +class GamepadUIOptionsPanel : public GamepadUIFrame +{ + DECLARE_CLASS_SIMPLE( GamepadUIOptionsPanel, GamepadUIFrame ); + +public: + GamepadUIOptionsPanel( vgui::Panel *pParent, const char* pPanelName ); + ~GamepadUIOptionsPanel(); + + void OnThink() OVERRIDE; + void Paint() OVERRIDE; + void LayoutCurrentTab(); + void OnCommand( char const* pCommand ) OVERRIDE; + + void UpdateGradients() OVERRIDE; + + void LoadOptionTabs( const char* pszOptionsFile ); + + void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + + void SetOptionDescription( GamepadUIString *pStr ) { m_strOptionDescription = pStr; } + void SetOptionImage( GamepadUIImage *pImg ) { m_pOptionImage = pImg; } + + void SetActiveTab( int nTab ); + int GetActiveTab(); + void OnMouseWheeled( int delta ) OVERRIDE; + + void UpdateResolutions(); + void ClearBindings(); + void FillInBindings(); + void ApplyKeyBindings(); + + void OnKeyBound( const char *pKey ); + + void OnKeyCodePressed( vgui::KeyCode code ); + + MESSAGE_FUNC_HANDLE( OnGamepadUIButtonNavigatedTo, "OnGamepadUIButtonNavigatedTo", button ); + + static GamepadUIOptionsPanel *GetInstance() + { + return s_pOptionsPanel; + } + + GamepadUIWheelyWheel *GetResolutionButton() + { + return m_pResolutionButton; + } + +private: + + GAMEPADUI_PANEL_PROPERTY( float, m_flTabsOffsetX, "Tabs.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flTabsOffsetY, "Tabs.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flOptionsFade, "Options.Fade", "80", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flScrollBarOffsetX, "Options.Scrollbar.OffsetX", "716", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flScrollBarOffsetY, "Options.Scrollbar.OffsetY", "128", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flScrollBarHeight, "Options.Scrollbar.Height", "256", SchemeValueTypes::ProportionalFloat ); + + GamepadUITab m_Tabs[ MAX_OPTIONS_TABS ]; + int m_nTabCount = 0; + + GamepadUIWheelyWheel* m_pResolutionButton = NULL; + + GamepadUIGlyph m_leftGlyph; + GamepadUIGlyph m_rightGlyph; + + GamepadUIString *m_strOptionDescription = NULL; + vgui::HFont m_hDescFont = vgui::INVALID_FONT; + + GamepadUIImage *m_pOptionImage = NULL; + + GamepadUIScrollBar *m_pScrollBar; + + static GamepadUIOptionsPanel *s_pOptionsPanel; +}; + +GamepadUIOptionsPanel* GamepadUIOptionsPanel::s_pOptionsPanel = NULL; + +ConVar gamepadui_last_options_tab( "gamepadui_last_options_tab", "0", FCVAR_ARCHIVE ); + +class GamepadUIOptionButton : public GamepadUIButton +{ + DECLARE_CLASS_SIMPLE( GamepadUIOptionButton, GamepadUIButton ); +public: + GamepadUIOptionButton( vgui::Panel *pParent, vgui::Panel *pActionSignalTarget, const char *pSchemeFile, const char *pCommand, const char *pText, const char *pDescription ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + { + } + + GamepadUIOptionButton( vgui::Panel *pParent, vgui::Panel *pActionSignalTarget, const char *pSchemeFile, const char *pCommand, const wchar_t *pText, const wchar_t *pDescription ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + { + } + + virtual bool ShowDescriptionAtFooter() { return true; } + + void SetArmed( bool state ) OVERRIDE + { + BaseClass::SetArmed( state ); + + if (state && ShowDescriptionAtFooter()) + { + Assert( GamepadUIOptionsPanel::GetInstance() != NULL ); + GamepadUIOptionsPanel::GetInstance()->SetOptionDescription( &m_strButtonDescription ); + m_bDescriptionHide = true; + + if (m_OptionImage.IsValid()) + { + GamepadUIOptionsPanel::GetInstance()->SetOptionImage( &m_OptionImage ); + } + else + { + GamepadUIOptionsPanel::GetInstance()->SetOptionImage( NULL ); + } + } + } + + void SetHorizontal( bool bHorz ) + { + m_bHorizontal = bHorz; + } + + bool IsHorizontal() + { + return m_bHorizontal; + } + + inline void SetOptionImage( const char *pName ) + { + if (pName == NULL) + { + m_OptionImage.Cleanup(); + return; + } + + m_OptionImage.SetImage( pName ); + } + + inline GamepadUIImage &GetOptionImage() + { + return m_OptionImage; + } + +private: + bool m_bHorizontal = false; + + GamepadUIImage m_OptionImage; +}; + +class GamepadUIHeaderButton : public GamepadUIOptionButton +{ + DECLARE_CLASS_SIMPLE( GamepadUIHeaderButton, GamepadUIOptionButton ); +public: + GamepadUIHeaderButton( vgui::Panel *pParent, vgui::Panel *pActionSignalTarget, const char *pSchemeFile, const char *pCommand, const char *pText, const char *pDescription ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + { + } + + GamepadUIHeaderButton( vgui::Panel *pParent, vgui::Panel *pActionSignalTarget, const char *pSchemeFile, const char *pCommand, const wchar_t *pText, const wchar_t *pDescription ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + { + } + + void NavigateTo() + { + switch (m_LastNavDirection) + { + case ND_UP: NavigateUp(); break; + case ND_DOWN: NavigateDown(); break; + case ND_LEFT: NavigateLeft(); break; + case ND_RIGHT: NavigateRight(); break; + case ND_BACK: NavigateBack(); break; + } + } + + void ApplySchemeSettings( vgui::IScheme* pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + if (m_bCenter) + { + m_CenterX = true; + } + } + + void SetCentered( bool bHorz ) + { + m_bCenter = bHorz; + } + + bool IsCentered() + { + return m_bCenter; + } + +private: + bool m_bCenter = false; +}; + +class GamepadUICheckButton : public GamepadUIOptionButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUICheckButton, GamepadUIOptionButton ); + + GamepadUICheckButton( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char* pCommand, const char *pText, const char *pDescription ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + { + } + + virtual void ApplySchemeSettings( vgui::IScheme* pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + m_hIconFont = pScheme->GetFont( "Button.Check.Font", true ); + } + + virtual void Paint() + { + BaseClass::Paint(); + + vgui::surface()->DrawSetTextColor( m_colTextColor ); + vgui::surface()->DrawSetTextFont( m_hIconFont ); + vgui::surface()->DrawSetTextPos( m_flCheckOffsetX, m_flHeight / 2 - m_flCheckHeight / 2 ); + vgui::surface()->DrawPrintText( L"j", 1 ); + vgui::surface()->DrawSetTextPos( m_flCheckOffsetX, m_flHeight / 2 - m_flCheckHeight / 2 ); + vgui::surface()->DrawPrintText( L"k", 1 ); + } + +private: + + GAMEPADUI_PANEL_PROPERTY( float, m_flCheckOffsetX, "Button.Check.OffsetX", "10", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flCheckHeight, "Button.Check.Height", "18", SchemeValueTypes::ProportionalFloat ); + + vgui::HFont m_hIconFont = vgui::INVALID_FONT; +}; + +struct GamepadUIOption +{ + GamepadUIString strOptionText; + int nValue = 0; + + union + { + struct + { + int nWidth; + int nHeight; + }; + void *pData; + } userdata; +}; + +class GamepadUIKeyButton : public GamepadUIOptionButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUIKeyButton, GamepadUIOptionButton ); + + GamepadUIKeyButton( const char *pszBinding, vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char* pCommand, const char *pText, const char *pDescription ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_szBinding( pszBinding ) + { + } + + void Paint() OVERRIDE + { + BaseClass::Paint(); + + if ( m_bBeingBound ) + { + vgui::surface()->DrawSetColor( m_colBinding ); + vgui::surface()->DrawFilledRect( m_flWidth - m_flTextOffsetX - m_flBindingWidth, m_flHeight / 2 - m_flBindingHeight / 2, m_flWidth - m_flTextOffsetX, m_flHeight / 2 + m_flBindingHeight / 2 ); + } + else + { + ButtonState state = GetCurrentButtonState(); + + wchar_t wszBuffer[ 128 ]; + V_UTF8ToUnicode( m_szKey.String(), wszBuffer, sizeof( wszBuffer ) ); + V_wcsupr( wszBuffer ); + + int nTextSizeX, nTextSizeY; + vgui::surface()->DrawSetTextFont(state == ButtonStates::Out ? m_hTextFont : m_hTextFontOver); + vgui::surface()->GetTextSize(state == ButtonStates::Out ? m_hTextFont : m_hTextFontOver, wszBuffer, nTextSizeX, nTextSizeY); + + vgui::surface()->DrawSetTextPos( m_flWidth - m_flTextOffsetX - nTextSizeX, m_flHeight / 2 - nTextSizeY / 2 ); + vgui::surface()->DrawPrintText( wszBuffer, V_wcslen( wszBuffer ) ); + } + } + + void ApplySchemeSettings(vgui::IScheme* pScheme) + { + BaseClass::ApplySchemeSettings(pScheme); + + // Move the depressed sound to play after key capture + // (This would be more fitting for the release sound, but this class reuses the slider res file, which doesn't normally use a release sound) + m_sCaptureSoundName = m_sDepressedSoundName; + m_sDepressedSoundName = UTL_INVAL_SYMBOL; + } + + ButtonState GetCurrentButtonState() OVERRIDE + { + if ( m_bBeingBound ) + return ButtonStates::Pressed; + if ( s_bBeingBound ) + return ButtonStates::Out; + return BaseClass::GetCurrentButtonState(); + } + + void OnMouseDoublePressed(vgui::MouseCode code) OVERRIDE + { + if ( !s_bBeingBound ) + StartCapture(); + } + + void OnKeyCodePressed( vgui::KeyCode code ) + { + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + switch ( buttonCode ) + { + case KEY_ENTER: + if ( !s_bBeingBound ) + StartCapture(); + break; + case KEY_DELETE: + if ( !s_bBeingBound ) + ClearKey(); + break; + default: + BaseClass::OnKeyCodePressed( code ); + break; + } + } + + void OnThink() OVERRIDE + { + BaseClass::OnThink(); + + if ( m_bBeingBound ) + { + ButtonCode_t code = BUTTON_CODE_INVALID; + if ( GamepadUI::GetInstance().GetEngineClient()->CheckDoneKeyTrapping( code ) ) + { + OnKeyBound( code ); + } + } + } + + void StartCapture() + { + m_bBeingBound = true; + s_bBeingBound = true; + g_pInputSystem->SetNovintPure( true ); + GamepadUI::GetInstance().GetEngineClient()->StartKeyTrapMode(); + vgui::surface()->SetCursor( vgui::dc_none ); + SetCursor( vgui::dc_none ); + vgui::input()->GetCursorPos( m_iMouseX, m_iMouseY ); + } + + void EndCapture() + { + m_bBeingBound = false; + s_bBeingBound = false; + vgui::input()->SetMouseCapture( NULL ); + RequestFocus(); + g_pInputSystem->SetNovintPure( false ); + vgui::surface()->SetCursor( vgui::dc_arrow ); + SetCursor( vgui::dc_arrow ); + vgui::input()->SetCursorPos ( m_iMouseX, m_iMouseY ); + } + + void OnKeyBound( ButtonCode_t code ) + { + EndCapture(); + + const char *pKey = g_pInputSystem->ButtonCodeToString( code ); + GamepadUIOptionsPanel *pOptions = GamepadUIOptionsPanel::GetInstance(); + if ( pOptions ) + pOptions->OnKeyBound( pKey ); + m_szKey = pKey; + + vgui::surface()->PlaySound( g_ButtonSoundNames.String( m_sCaptureSoundName ) ); + } + + const char *GetKeyBinding() { return m_szBinding.String(); } + + const char *GetKey() { return m_szKey.String(); } + void SetKey( const char *pKey ) { m_szKey = pKey; } + + void ClearKey() { m_szKey = ""; } + +protected: + CUtlString m_szBinding; + CUtlString m_szKey; + + bool m_bBeingBound = false; + static bool s_bBeingBound; + int m_iMouseX, m_iMouseY; + + CUtlSymbol m_sCaptureSoundName = UTL_INVAL_SYMBOL; + + GAMEPADUI_PANEL_PROPERTY( float, m_flBindingWidth, "Button.Binding.Width", "160", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flBindingHeight, "Button.Binding.Height", "11", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( Color, m_colBinding, "Button.Binding", "0 0 0 255", SchemeValueTypes::Color ); +}; + +bool GamepadUIKeyButton::s_bBeingBound = false; + +class GamepadUIConvarButton : public GamepadUIOptionButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUIConvarButton, GamepadUIOptionButton ); + + GamepadUIConvarButton( const char *pszCvar, const char* pszCvarDepends, bool bInstantApply, vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char* pCommand, const char *pText, const char *pDescription ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_cvar( pszCvar ) + , m_szDependentCVar( pszCvarDepends ) + , m_bInstantApply( bInstantApply ) + { + } + + virtual void UpdateConVar() = 0; + virtual bool IsDirty() = 0; + virtual void SetToDefault() = 0; + virtual bool IsConVarEnabled() const { return true; } + + const char *GetDependentCVar() const + { + return m_szDependentCVar.String(); + } + + const char *GetConVarName() const + { + return m_cvar.GetName(); + } + +protected: + ConVarRef m_cvar; + CUtlString m_szDependentCVar; + bool m_bInstantApply = false; +}; + +class GamepadUIWheelyWheel : public GamepadUIConvarButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUIWheelyWheel, GamepadUIConvarButton ); + + GamepadUIWheelyWheel( const char *pszCvar, const char *pszCvarDepends, bool bInstantApply, bool bSignOnly, vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char* pCommand, const char *pText, const char *pDescription ) + : BaseClass( pszCvar, pszCvarDepends, bInstantApply, pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_bSignOnly( bSignOnly ) + { + } + + void OnKeyCodePressed( vgui::KeyCode code ) + { + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + switch ( buttonCode ) + { + case KEY_LEFT: + case KEY_XBUTTON_LEFT: + +#ifdef HL2_RETAIL // Steam input and Steam Controller are not supported in SDK2013 (Madi) + case STEAMCONTROLLER_DPAD_LEFT: +#endif + vgui::surface()->PlaySound( g_ButtonSoundNames.String( m_sDepressedSoundName ) ); + DecrementValue(); + break; + + case KEY_RIGHT: + case KEY_XBUTTON_RIGHT: + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_DPAD_RIGHT: +#endif + vgui::surface()->PlaySound( g_ButtonSoundNames.String( m_sDepressedSoundName ) ); + IncrementValue(); + break; + + default: + BaseClass::OnKeyCodePressed( code ); + break; + } + } + + void OnMousePressed( vgui::MouseCode code ) + { + GamepadUIString& strOption = m_Options[ m_nSelectedItem ].strOptionText; + + int x, y; + GetPos( x, y ); + + int nTextW, nTextH; + vgui::surface()->GetTextSize( m_hTextFont, strOption.String(), nTextW, nTextH ); + + int nScrollerSize = vgui::surface()->GetCharacterWidth( m_hTextFont, L'<' ) + vgui::surface()->GetCharacterWidth( m_hTextFont, L' ' ); + int nTextLen = (nTextW + 2 * nScrollerSize); + int nTextStart = x + m_flWidth - m_flTextOffsetX - nTextLen; + int nTextHalfwayPoint = x + m_flWidth - m_flTextOffsetX - (nTextLen / 2); + + // Give some room to roughly press the scroller's left side + nTextStart -= m_flClickPadding; + + // Change value based on what side the player clicked on + int mx, my; + g_pVGuiInput->GetCursorPos( mx, my ); + if (mx > nTextHalfwayPoint) + { + // Right side + IncrementValue(); + } + else if (mx > nTextStart) + { + // Left side + DecrementValue(); + } + else + return; // Don't play sound + + BaseClass::OnMousePressed( code ); + } + + void IncrementValue() + { + if (m_DangerousOptions.Count()) + { + int nNewItem = 0; + if ( m_Options.Count() ) + nNewItem = ( m_nSelectedItem + 1) % m_Options.Count(); + + int nIndex = m_DangerousOptions.Find( nNewItem ); + if (nIndex != m_DangerousOptions.InvalidIndex()) + { + ShowWarning( nIndex, true ); + return; + } + } + + IncrementValueInner(); + } + + void DecrementValue() + { + if (m_DangerousOptions.Count()) + { + int nNewItem = m_nSelectedItem - 1; + if ( nNewItem < 0 ) + nNewItem = Max( 0, m_Options.Count() - 1); + + int nIndex = m_DangerousOptions.Find( nNewItem ); + if (nIndex != m_DangerousOptions.InvalidIndex()) + { + ShowWarning( nIndex, false ); + return; + } + } + + DecrementValueInner(); + } + + void IncrementValueInner() + { + if ( m_Options.Count() ) + m_nSelectedItem = ( m_nSelectedItem + 1 ) % m_Options.Count(); + if ( m_bInstantApply ) + UpdateConVar(); + } + + void DecrementValueInner() + { + if ( --m_nSelectedItem < 0 ) + m_nSelectedItem = Max( 0, m_Options.Count() - 1 ); + if ( m_bInstantApply ) + UpdateConVar(); + } + + void ShowWarning( int nIndex, bool bIncrement ) + { + new GamepadUIGenericConfirmationPanel( GamepadUIOptionsPanel::GetInstance(), "OptionWarning", GamepadUIString( "#GameUI_Confirm" ).String(), m_DangerousOptionsText[nIndex].String(), + [this, bIncrement]() { + bIncrement ? this->IncrementValueInner() : this->DecrementValueInner(); + }, true, true ); + } + + void AddDangerousOption( int option, const char *pszText ) + { + m_DangerousOptions.AddToTail( option ); + m_DangerousOptionsText.AddToTail( GamepadUIString( pszText ) ); + } + + void UpdateConVar() OVERRIDE + { + if ( IsDirty() ) + { + if ( m_bSignOnly ) + m_cvar.SetValue( abs( m_cvar.GetFloat() ) * m_Options[m_nSelectedItem].nValue ); + else + m_cvar.SetValue( m_Options[ m_nSelectedItem ].nValue ); + } + } + + + bool IsDirty() OVERRIDE + { + return m_cvar.IsValid() && GetCvarValue() != m_Options[m_nSelectedItem].nValue; + } + + virtual void Paint() + { + BaseClass::Paint(); + + if ( m_nSelectedItem >= m_Options.Count() ) + return; + + GamepadUIString& strOption = m_Options[ m_nSelectedItem ].strOptionText; + ButtonState state = GetCurrentButtonState(); + + int nTextW, nTextH; + vgui::surface()->GetTextSize( m_hTextFont, strOption.String(), nTextW, nTextH ); + + int nScrollerSize = vgui::surface()->GetCharacterWidth( m_hTextFont, L'<' ) + vgui::surface()->GetCharacterWidth( m_hTextFont, L' ' ); + + int nTextY = m_flHeight / 2 - nTextH / 2 + m_flTextOffsetY; + + vgui::surface()->DrawSetTextFont( m_hTextFont ); + if ( state != ButtonStates::Out ) + { + vgui::surface()->DrawSetTextPos( m_flWidth - m_flTextOffsetX - nTextW - 2 * nScrollerSize, nTextY ); + vgui::surface()->DrawPrintText( L"< ", 2 ); + } + vgui::surface()->DrawSetTextPos( m_flWidth - m_flTextOffsetX - nTextW - nScrollerSize, nTextY ); + vgui::surface()->DrawPrintText( strOption.String(), strOption.Length() ); + if ( state != ButtonStates::Out ) + { + vgui::surface()->DrawSetTextPos( m_flWidth - m_flTextOffsetX - nScrollerSize, nTextY ); + vgui::surface()->DrawPrintText( L" >", 2 ); + } + } + + void ClearOptions() + { + m_Options.RemoveAll(); + } + + void AddOptionItem( GamepadUIOption option ) + { + m_Options.AddToTail( option ); + } + + int GetOptionCount() + { + return m_Options.Count(); + } + + int GetCvarValue() + { + if ( m_bSignOnly ) + return (int)Sign( m_cvar.GetFloat() ); + else + return m_cvar.GetInt(); + } + + void SetToDefault() OVERRIDE + { + if ( m_cvar.IsValid() ) + { + const int nCurrentValue = GetCvarValue(); + for ( int i = 0; i < m_Options.Count(); i++) + { + if ( m_Options[ i ].nValue == nCurrentValue ) + m_nSelectedItem = i; + } + } + } + + GamepadUIOption *GetOption( int nIndex ) + { + if ( nIndex < 0 || nIndex >= m_Options.Count() ) + return NULL; + return &m_Options[ nIndex ]; + } + + bool IsConVarEnabled() const OVERRIDE + { + return !!m_Options[m_nSelectedItem].nValue; + } + +private: + + bool m_bSignOnly = false; + int m_nSelectedItem = 0; + CUtlVector< GamepadUIOption > m_Options; + + GAMEPADUI_PANEL_PROPERTY( float, m_flClickPadding, "Button.Wheel.ClickPadding", "24", SchemeValueTypes::ProportionalFloat ); + + CUtlVector< int > m_DangerousOptions; + CUtlVector< GamepadUIString > m_DangerousOptionsText; + +}; + +class GamepadUISlideySlide : public GamepadUIConvarButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUISlideySlide, GamepadUIConvarButton ); + + GamepadUISlideySlide( const char *pszCvar, const char* pszCvarDepends, bool bInstantApply, float flMin, float flMax, float flStep, int nTextPrecision, vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char* pCommand, const char *pText, const char *pDescription ) + : BaseClass( pszCvar, pszCvarDepends, bInstantApply, pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_flMin( flMin ) + , m_flMax( flMax ) + , m_flStep( flStep ) + , nTextPrecision( nTextPrecision ) + { + SetUseCaptureMouse( true ); + } + + void OnKeyCodePressed( vgui::KeyCode code ) + { + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + switch ( buttonCode ) + { + case KEY_LEFT: + case KEY_XBUTTON_LEFT: + +#ifdef HL2_RETAIL // Steam input and Steam Controller are not supported in SDK2013 (Madi) + case STEAMCONTROLLER_DPAD_LEFT: +#endif + { + float flValue = Clamp( m_flValue - (m_bFineAdjust ? m_flMouseStep : m_flStep), m_flMin, m_flMax ); + if (flValue != m_flValue) + { + if (m_sSliderSoundName != UTL_INVAL_SYMBOL) + vgui::surface()->PlaySound( g_ButtonSoundNames.String( m_sSliderSoundName ) ); + m_flValue = flValue; + if ( m_bInstantApply ) + UpdateConVar(); + } + } + break; + + case KEY_RIGHT: + case KEY_XBUTTON_RIGHT: + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_DPAD_RIGHT: +#endif + { + float flValue = Clamp( m_flValue + (m_bFineAdjust ? m_flMouseStep : m_flStep), m_flMin, m_flMax ); + if (flValue != m_flValue) + { + if (m_sSliderSoundName != UTL_INVAL_SYMBOL) + vgui::surface()->PlaySound( g_ButtonSoundNames.String( m_sSliderSoundName ) ); + m_flValue = flValue; + if ( m_bInstantApply ) + UpdateConVar(); + } + } + break; + + case KEY_RSHIFT: + case KEY_LSHIFT: + { + m_bFineAdjust = true; + } + break; + + default: + BaseClass::OnKeyCodePressed( code ); + break; + } + } + + void OnKeyCodeReleased( vgui::KeyCode code ) + { + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + switch ( buttonCode ) + { + case KEY_RSHIFT: + case KEY_LSHIFT: + { + m_bFineAdjust = false; + } + break; + + default: + BaseClass::OnKeyCodeReleased( code ); + break; + } + } + + void OnMousePressed( vgui::MouseCode code ) + { + int x, y; + GetPos( x, y ); + + int mx, my; + g_pVGuiInput->GetCursorPos( mx, my ); + + int iSliderEnd = x + m_flWidth - m_flTextOffsetX; + int iSliderStart = iSliderEnd - m_flSliderWidth; + + // Allow some wiggle room + if (mx > iSliderStart-4 && mx < iSliderEnd+4) + { + // Start influencing the slider value + BaseClass::OnMousePressed( code ); + } + } + + void SetMouseStep( float flStep ) + { + m_flMouseStep = flStep; + } + + void OnThink() + { + if (IsSelected()) + { + int x, y; + GetPos( x, y ); + + int mx, my; + g_pVGuiInput->GetCursorPos( mx, my ); + + int iSliderEnd = x + m_flWidth - m_flTextOffsetX; + int iSliderStart = iSliderEnd - m_flSliderWidth; + + // Set the slider value to whichever step is closest to the cursor + float flProgress = RemapValClamped( mx, iSliderStart, iSliderEnd, m_flMin, m_flMax ); + + float flRemainder = fmodf( flProgress, m_flMouseStep ); + flProgress -= flRemainder; + + if ((flRemainder / m_flMouseStep) > 0.5f) + flProgress += m_flMouseStep; + + if (flProgress != m_flValue) + { + if (m_sSliderSoundName != UTL_INVAL_SYMBOL && m_flLastSliderSoundTime < GamepadUI::GetInstance().GetTime()) + { + vgui::surface()->PlaySound( g_ButtonSoundNames.String( m_sSliderSoundName ) ); + m_flLastSliderSoundTime = GamepadUI::GetInstance().GetTime() + 0.04f; // Arbitrary sound cooldown + } + m_flValue = flProgress; + if ( m_bInstantApply ) + UpdateConVar(); + } + } + + BaseClass::OnThink(); + } + + void ApplySchemeSettings(vgui::IScheme* pScheme) + { + BaseClass::ApplySchemeSettings(pScheme); + + const char *pSliderSound = pScheme->GetResourceString( "Slider.Sound.Adjust" ); + if (pSliderSound && *pSliderSound) + m_sSliderSoundName = g_ButtonSoundNames.AddString( pSliderSound ); + } + + void UpdateConVar() OVERRIDE + { + if ( IsDirty() ) + m_cvar.SetValue( m_flValue ); + } + + bool IsDirty() OVERRIDE + { + return m_cvar.IsValid() && m_cvar.GetFloat() != m_flValue; + } + + float GetMultiplier() const + { + return ( m_flValue - m_flMin ) / ( m_flMax - m_flMin ); + } + + virtual void Paint() + { + BaseClass::Paint(); + + if ( nTextPrecision >= 0 ) + { + wchar_t szValue[ 256 ]; + V_snwprintf( szValue, sizeof( szValue ), L"%.*f", nTextPrecision, m_flValue ); + + int w, h; + vgui::surface()->GetTextSize( m_hTextFont, szValue, w, h ); + vgui::surface()->DrawSetTextPos( m_flWidth - 2 * m_flTextOffsetX - m_flSliderWidth - w, m_flHeight / 2 - h / 2 ); + vgui::surface()->DrawPrintText( szValue, V_wcslen( szValue ) ); + } + + vgui::surface()->DrawSetColor( m_colSliderBacking ); + vgui::surface()->DrawFilledRect( m_flWidth - m_flTextOffsetX - m_flSliderWidth, m_flHeight / 2 - m_flSliderHeight / 2, m_flWidth - m_flTextOffsetX, m_flHeight / 2 + m_flSliderHeight / 2 ); + + float flFill = m_flSliderWidth * ( 1.0f - GetMultiplier() ); + + vgui::surface()->DrawSetColor( m_colSliderFill ); + vgui::surface()->DrawFilledRect( m_flWidth - m_flTextOffsetX - m_flSliderWidth, m_flHeight / 2 - m_flSliderHeight / 2, m_flWidth - m_flTextOffsetX - flFill, m_flHeight / 2 + m_flSliderHeight / 2 ); + } + + void SetToDefault() OVERRIDE + { + if ( m_cvar.IsValid() ) + m_flValue = m_cvar.GetFloat(); + } + + void RunAnimations( ButtonState state ) OVERRIDE + { + BaseClass::RunAnimations( state ); + + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colSliderBacking, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colSliderFill, vgui::AnimationController::INTERPOLATOR_LINEAR ); + } + +private: + + float m_flValue = 0.0f; + + float m_flMin = 0.0f; + float m_flMax = 1.0f; + float m_flStep = 0.1f; + + int nTextPrecision = -1; + + CUtlSymbol m_sSliderSoundName = UTL_INVAL_SYMBOL; + float m_flLastSliderSoundTime = 0.0f; + + float m_flMouseStep = 0.1f; + bool m_bFineAdjust = false; + + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colSliderBacking, "Slider.Backing", "255 255 255 22", SchemeValueTypes::Color ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colSliderFill, "Slider.Fill", "255 255 255 255", SchemeValueTypes::Color ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flSliderWidth, "Slider.Width", "160", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flSliderHeight, "Slider.Height", "11", SchemeValueTypes::ProportionalFloat ); + +}; + +class GamepadUISkillySkill : public GamepadUIOptionButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUISkillySkill, GamepadUIOptionButton ); + + GamepadUISkillySkill( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char* pSchemeFile, const char* pCommand, const char* pText, const char* pDescription, const char *pImage, int nSkill ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_Image( pImage ) + , m_nSkill( nSkill ) + { + } + + GamepadUISkillySkill( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char* pSchemeFile, const char* pCommand, const wchar* pText, const wchar* pDescription, const char *pImage, int nSkill ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pText, pDescription ) + , m_Image( pImage ) + , m_nSkill( nSkill ) + { + } + + void RunAnimations( ButtonState state ) OVERRIDE + { + BaseClass::RunAnimations( state ); + + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colTitleBackground, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colDescriptionBackground, vgui::AnimationController::INTERPOLATOR_LINEAR ); + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colImage, vgui::AnimationController::INTERPOLATOR_LINEAR ); + } + + ButtonState GetCurrentButtonState() OVERRIDE + { + if ( _gamepadui_skill.GetInt() == m_nSkill ) + return ButtonState::Pressed; + + return BaseClass::GetCurrentButtonState(); + } + + void NavigateTo() OVERRIDE + { + BaseClass::NavigateTo(); + + _gamepadui_skill.SetValue( m_nSkill ); + } + + void DoClick() OVERRIDE + { + BaseClass::DoClick(); + + _gamepadui_skill.SetValue( m_nSkill ); + } + + void Paint() OVERRIDE + { + PaintButton(); + + int nTextSizeX, nTextSizeY; + vgui::surface()->GetTextSize(GetCurrentButtonState() == ButtonStates::Out ? m_hTextFont : m_hTextFontOver, m_strButtonText.String(), nTextSizeX, nTextSizeY); + int y2 = m_flTextOffsetY + m_flHeight / 2 + nTextSizeY / 2; + + vgui::surface()->DrawSetColor(m_colTitleBackground); + vgui::surface()->DrawFilledRect(0, m_flTextOffsetY + m_flHeight / 2 - nTextSizeY / 2, nTextSizeX + m_flTextOffsetX * 2, y2); + + vgui::surface()->DrawSetColor(m_colDescriptionBackground); + vgui::surface()->DrawFilledRect(0, y2, m_flWidth, m_flHeight + m_flExtraHeight); + + vgui::surface()->DrawSetColor( m_colImage ); + vgui::surface()->DrawSetTexture( m_Image ); + vgui::surface()->DrawTexturedRect( m_flWidth / 2 - m_flImageWidth / 2, 0, m_flWidth / 2 + m_flImageWidth / 2, m_flImageHeight ); + vgui::surface()->DrawSetTexture( 0 ); + + PaintText(); + + PaintBorders(); + } + + virtual bool ShowDescriptionAtFooter() { return false; } + +private: + GamepadUIImage m_Image; + + int m_nSkill = 0; + + GAMEPADUI_PANEL_PROPERTY( float, m_flImageWidth, "Button.Image.Width", "40", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flImageHeight, "Button.Image.Height", "40", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colImage, "Button.Image", "255 255 255 255", SchemeValueTypes::Color ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colTitleBackground, "Button.Title.Background", "0 0 0 0", SchemeValueTypes::Color ); + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colDescriptionBackground, "Button.Description.Background", "0 0 0 0", SchemeValueTypes::Color ); +}; + +struct AAMode_t +{ + GamepadUIString strName; + int m_nNumSamples; + int m_nQualityLevel; +}; + +int g_nNumAAModes = 0; +AAMode_t g_AAModes[16]; + +void InitAAModes() +{ + static bool s_bAAModesInitialized = false; + if ( s_bAAModesInitialized ) + return; + s_bAAModesInitialized = true; + + g_nNumAAModes = 0; + + g_AAModes[g_nNumAAModes].strName = "#GameUI_None"; + g_AAModes[g_nNumAAModes].m_nNumSamples = 1; + g_AAModes[g_nNumAAModes].m_nQualityLevel = 0; + g_nNumAAModes++; + + if ( materials->SupportsMSAAMode(2) ) + { + g_AAModes[g_nNumAAModes].strName = "#GameUI_2X"; + g_AAModes[g_nNumAAModes].m_nNumSamples = 2; + g_AAModes[g_nNumAAModes].m_nQualityLevel = 0; + g_nNumAAModes++; + } + + if ( materials->SupportsMSAAMode(4) ) + { + g_AAModes[g_nNumAAModes].strName = "#GameUI_4X"; + g_AAModes[g_nNumAAModes].m_nNumSamples = 4; + g_AAModes[g_nNumAAModes].m_nQualityLevel = 0; + g_nNumAAModes++; + } + + if ( materials->SupportsMSAAMode(6) ) + { + g_AAModes[g_nNumAAModes].strName = "#GameUI_6X"; + g_AAModes[g_nNumAAModes].m_nNumSamples = 6; + g_AAModes[g_nNumAAModes].m_nQualityLevel = 0; + g_nNumAAModes++; + } + + if ( materials->SupportsCSAAMode(4, 2) ) // nVidia CSAA "8x" + { + g_AAModes[g_nNumAAModes].strName = "#GameUI_8X_CSAA"; + g_AAModes[g_nNumAAModes].m_nNumSamples = 4; + g_AAModes[g_nNumAAModes].m_nQualityLevel = 2; + g_nNumAAModes++; + } + + if ( materials->SupportsCSAAMode(4, 4) ) // nVidia CSAA "16x" + { + g_AAModes[g_nNumAAModes].strName = "#GameUI_16X_CSAA"; + g_AAModes[g_nNumAAModes].m_nNumSamples = 4; + g_AAModes[g_nNumAAModes].m_nQualityLevel = 4; + g_nNumAAModes++; + } + + if ( materials->SupportsMSAAMode(8) ) + { + g_AAModes[g_nNumAAModes].strName = "#GameUI_8X"; + g_AAModes[g_nNumAAModes].m_nNumSamples = 8; + g_AAModes[g_nNumAAModes].m_nQualityLevel = 0; + g_nNumAAModes++; + } + + if ( materials->SupportsCSAAMode(8, 2) ) // nVidia CSAA "16xQ" + { + g_AAModes[g_nNumAAModes].strName = "#GameUI_16XQ_CSAA"; + g_AAModes[g_nNumAAModes].m_nNumSamples = 8; + g_AAModes[g_nNumAAModes].m_nQualityLevel = 2; + g_nNumAAModes++; + } +} + +struct RatioToAspectMode_t +{ + int anamorphic; + float aspectRatio; +}; +RatioToAspectMode_t g_RatioToAspectModes[] = +{ + { 0, 4.0f / 3.0f }, + { 1, 16.0f / 9.0f }, + { 2, 16.0f / 10.0f }, + { 2, 1.0f }, +}; + +int GetScreenAspectMode( int width, int height ) +{ + float aspectRatio = (float)width / (float)height; + + // just find the closest ratio + float closestAspectRatioDist = 99999.0f; + int closestAnamorphic = 0; + for (int i = 0; i < ARRAYSIZE(g_RatioToAspectModes); i++) + { + float dist = fabs( g_RatioToAspectModes[i].aspectRatio - aspectRatio ); + if (dist < closestAspectRatioDist) + { + closestAspectRatioDist = dist; + closestAnamorphic = g_RatioToAspectModes[i].anamorphic; + } + } + + return closestAnamorphic; +} + +void OnResolutionsNeedUpdate( IConVar *var, const char *pOldValue, float flOldValue ) +{ + GamepadUIOptionsPanel* pOptionsPanel = GamepadUIOptionsPanel::GetInstance(); + if ( pOptionsPanel ) + pOptionsPanel->UpdateResolutions(); +} + +static int GetSDLDisplayIndex() +{ +#if defined( USE_SDL ) + static ConVarRef sdl_displayindex( "sdl_displayindex" ); + + Assert( sdl_displayindex.IsValid() ); + return sdl_displayindex.IsValid() ? sdl_displayindex.GetInt() : 0; +#else + return 0; +#endif +} + +static int GetSDLDisplayIndexFullscreen() +{ +#if defined( USE_SDL ) + static ConVarRef sdl_displayindex_fullscreen( "sdl_displayindex_fullscreen" ); + + Assert( sdl_displayindex_fullscreen.IsValid() ); + return sdl_displayindex_fullscreen.IsValid() ? sdl_displayindex_fullscreen.GetInt() : -1; +#else + return -1; +#endif +} + +int GetCurrentWaterDetail() +{ + ConVarRef r_waterforceexpensive( "r_waterforceexpensive" ); + ConVarRef r_waterforcereflectentities( "r_waterforcereflectentities" ); + + if ( r_waterforcereflectentities.GetBool() ) + return 2; + + if ( r_waterforceexpensive.GetBool() ) + return 1; + + return 0; +} + +int GetCurrentShadowDetail() +{ + ConVarRef r_shadowrendertotexture( "r_shadowrendertotexture" ); + ConVarRef r_flashlightdepthtexture( "r_flashlightdepthtexture" ); + + if ( r_flashlightdepthtexture.GetBool() ) + return 2; + + if ( r_shadowrendertotexture.GetBool() ) + return 1; + + return 0; +} + +int GetCurrentAntialiasing() +{ + ConVarRef mat_antialias( "mat_antialias" ); + ConVarRef mat_aaquality( "mat_aaquality" ); + int nAASamples = mat_antialias.GetInt(); + int nAAQuality = mat_aaquality.GetInt(); + + // Run through the AA Modes supported by the device + for ( int nAAMode = 0; nAAMode < g_nNumAAModes; nAAMode++ ) + { + // If we found the mode that matches what we're looking for, return the index + if ( ( g_AAModes[nAAMode].m_nNumSamples == nAASamples) && ( g_AAModes[nAAMode].m_nQualityLevel == nAAQuality) ) + { + return nAAMode; + } + } + + return 0; // Didn't find what we're looking for, so no AA +} + +int GetCurrentAspectRatio() +{ + const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard(); + return GetScreenAspectMode( config.m_VideoMode.m_Width, config.m_VideoMode.m_Height ); +} + +int GetCurrentDisplayMode() +{ + const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard(); + if ( config.Windowed() ) + { +#ifdef HL2_RETAIL // SDK2013 does not natively support borderless windowed (Madi) + if ( config.NoWindowBorder() ) + return 1; +#endif + + return 0; + } + +#ifdef HL2_RETAIL + return 2 + GetSDLDisplayIndex(); +#else + // TODO FIXME: SDK2013 uses the inverse here...1 is "windowed" and 0 is "fullscreen" in materialsystem. + // but our values mean the opposite in gamepadUI code. (Madi) + return 1 + GetSDLDisplayIndex(); +#endif +} + + +int GetCurrentSoundQuality() +{ + ConVarRef snd_pitchquality( "snd_pitchquality" ); + ConVarRef dsp_slow_cpu( "dsp_slow_cpu" ); + + if ( snd_pitchquality.GetInt() ) + return 2; + + if ( !dsp_slow_cpu.GetBool() ) + return 1; + + return 0; +} + +int GetCurrentCloseCaptions() +{ + ConVarRef closecaption( "closecaption" ); + ConVarRef cc_subtitles( "cc_subtitles" ); + + if ( closecaption.GetBool() ) + return cc_subtitles.GetBool() ? 1 : 2; + return 0; +} + +#ifdef HL2_RETAIL +int GetCurrentHudAspectRatio() +{ + ConVarRef hud_aspect( "hud_aspect" ); + float flHudAspect = hud_aspect.GetFloat(); + if ( flHudAspect > 0.0f ) + { + if ( flHudAspect < 1.4f ) + return 1; + else if (flHudAspect < 1.7f) + return 3; + else + return 2; + } + else + return 0; +} +#endif + +int GetCurrentSkill() +{ + ConVarRef skill( "skill" ); + return skill.GetInt(); +} + +void FlushPendingAntialiasing() +{ + ConVarRef mat_antialias( "mat_antialias" ); + ConVarRef mat_aaquality( "mat_aaquality" ); + + int nAAMode = Clamp( _gamepadui_antialiasing.GetInt(), 0, Max( 0, g_nNumAAModes - 1 ) ); + if ( mat_antialias.GetInt() != g_AAModes[ nAAMode ].m_nNumSamples ) + mat_antialias.SetValue( g_AAModes[ nAAMode ].m_nNumSamples ); + + if ( mat_aaquality.GetInt() != g_AAModes[ nAAMode ].m_nQualityLevel ) + mat_aaquality.SetValue( g_AAModes[ nAAMode ].m_nQualityLevel ); +} + +void FlushPendingWaterDetail() +{ + ConVarRef r_waterforceexpensive( "r_waterforceexpensive" ); + ConVarRef r_waterforcereflectentities( "r_waterforcereflectentities" ); + + bool bForceExpensive = _gamepadui_water_detail.GetInt() > 0; + bool bForceReflect = _gamepadui_water_detail.GetInt() > 1; + + if ( r_waterforceexpensive.GetBool() != bForceExpensive ) + r_waterforceexpensive.SetValue( bForceExpensive ); + + if ( r_waterforcereflectentities.GetBool() != bForceReflect ) + r_waterforcereflectentities.SetValue(bForceReflect); +} + +void FlushPendingShadowDetail() +{ + ConVarRef r_shadowrendertotexture( "r_shadowrendertotexture" ); + ConVarRef r_flashlightdepthtexture( "r_flashlightdepthtexture" ); + + bool bShadowRenderToTexture = _gamepadui_shadow_detail.GetInt() > 0; + bool bFlashlightDepthTexture = _gamepadui_shadow_detail.GetInt() > 1; + + if ( r_shadowrendertotexture.GetBool() != bShadowRenderToTexture ) + r_shadowrendertotexture.SetValue( bShadowRenderToTexture ); + + if ( r_flashlightdepthtexture.GetBool() != bFlashlightDepthTexture ) + r_flashlightdepthtexture.SetValue( bFlashlightDepthTexture ); +} + +void FlushPendingResolution() +{ + GamepadUIOptionsPanel *pOptions = GamepadUIOptionsPanel::GetInstance(); + if ( !pOptions ) + return; + + GamepadUIWheelyWheel *pResButton = pOptions->GetResolutionButton(); + if ( !pResButton ) + return; + + GamepadUIOption *pOption = pResButton->GetOption( _gamepadui_resolution.GetInt() ); + if ( !pOption ) + return; + + const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard(); + bool bDirty = false; + + if ( GetCurrentDisplayMode() != _gamepadui_displaymode.GetInt() ) + bDirty = true; + + if ( pOption->userdata.nWidth != config.m_VideoMode.m_Width || pOption->userdata.nHeight != config.m_VideoMode.m_Height ) + bDirty = true; + + if ( !bDirty ) + return; + + const int nWidth = pOption->userdata.nWidth; + const int nHeight = pOption->userdata.nHeight; +#ifdef HL2_RETAIL + const int nWindowed = _gamepadui_displaymode.GetInt() < 2 ? 1 : 0; + const int nBorderless = _gamepadui_displaymode.GetInt() == 1 ? 1 : 0; +#else + // TODO FIXME: SDK2013 uses the inverse here...1 is "windowed" and 0 is "fullscreen" in materialsystem. + // but our values mean the opposite in gamepadUI code. (Madi) + const int nWindowed = _gamepadui_displaymode.GetInt() == 1 ? 0 /* fullscreen*/ : 1 /* windowed */; +#endif + + char szCmd[ 256 ]; + +#ifdef HL2_RETAIL + Q_snprintf( szCmd, sizeof( szCmd ), "mat_setvideomode %i %i %i %i\n", nWidth, nHeight, nWindowed, nBorderless ); +#else + Q_snprintf( szCmd, sizeof( szCmd ), "mat_setvideomode %i %i %i\n", nWidth, nHeight, nWindowed ); +#endif + + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( szCmd ); +} + +void FlushPendingSoundQuality() +{ + ConVarRef snd_pitchquality( "snd_pitchquality" ); + ConVarRef dsp_slow_cpu( "dsp_slow_cpu" ); + ConVarRef dsp_enhance_stereo( "dsp_enhance_stereo" ); + ConVarRef snd_surround_speakers( "snd_surround_speakers" ); + + int nSoundQuality = _gamepadui_sound_quality.GetInt(); + switch ( nSoundQuality ) + { + default: + case 2: + // Headphones at high quality get enhanced stereo turned on + dsp_enhance_stereo.SetValue( snd_surround_speakers.GetInt() == 0 ); + snd_pitchquality.SetValue( true ); + dsp_slow_cpu.SetValue( false ); + break; + case 1: + snd_pitchquality.SetValue( false ); + dsp_slow_cpu.SetValue( false ); + break; + case 0: + snd_pitchquality.SetValue( false ); + dsp_slow_cpu.SetValue( true ); + break; + } +} + +void FlushPendingCloseCaptions() +{ + ConVarRef closecaption( "closecaption" ); + ConVarRef cc_subtitles( "cc_subtitles" ); + + int nCloseCaptions = _gamepadui_closecaptions.GetInt(); + bool bCloseCaptionConvarValue = false; + switch ( nCloseCaptions ) + { + default: + case 2: + cc_subtitles.SetValue( false ); + bCloseCaptionConvarValue = true; + break; + case 1: + cc_subtitles.SetValue(true); + bCloseCaptionConvarValue = true; + break; + case 0: + cc_subtitles.SetValue(false); + bCloseCaptionConvarValue = false; + break; + } + + // Stuff the close caption change to the console so that it can be + // sent to the server (FCVAR_USERINFO) so that you don't have to restart + // the level for the change to take effect. + char szCmd[ 64 ]; + Q_snprintf( szCmd, sizeof( szCmd ), "closecaption %i\n", bCloseCaptionConvarValue ? 1 : 0 ); + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( szCmd ); +} + +#ifdef HL2_RETAIL +void FlushPendingHudAspectRatio() +{ + ConVarRef hud_aspect( "hud_aspect" ); + + int nHudAspect = _gamepadui_hudaspect.GetInt(); + switch ( nHudAspect ) + { + default: + case 0: + hud_aspect.SetValue( 0.0f ); + break; + case 1: + hud_aspect.SetValue( 4.0f / 3.0f ); + break; + case 2: + hud_aspect.SetValue( 16.0f / 9.0f ); + break; + case 3: + hud_aspect.SetValue( 16.0f / 10.0f ); + break; + } +} +#endif + +void FlushPendingSkill() +{ + ConVarRef skill( "skill" ); + skill.SetValue( _gamepadui_skill.GetInt() ); +} + +void UpdateHelperConvars() +{ + _gamepadui_water_detail.SetValue( GetCurrentWaterDetail() ); + _gamepadui_shadow_detail.SetValue( GetCurrentShadowDetail() ); + _gamepadui_antialiasing.SetValue( GetCurrentAntialiasing() ); + _gamepadui_aspectratio.SetValue( GetCurrentAspectRatio() ); + _gamepadui_displaymode.SetValue( GetCurrentDisplayMode() ); + _gamepadui_sound_quality.SetValue( GetCurrentSoundQuality() ); + _gamepadui_closecaptions.SetValue( GetCurrentCloseCaptions() ); +#ifdef HL2_RETAIL + _gamepadui_hudaspect.SetValue( GetCurrentHudAspectRatio() ); +#endif + _gamepadui_skill.SetValue( GetCurrentSkill() ); +} + +void FlushHelperConVars() +{ + FlushPendingWaterDetail(); + FlushPendingShadowDetail(); + FlushPendingAntialiasing(); + FlushPendingResolution(); + FlushPendingSoundQuality(); + FlushPendingCloseCaptions(); +#ifdef HL2_RETAIL + FlushPendingHudAspectRatio(); +#endif + FlushPendingSkill(); +} + +GamepadUIOptionsPanel::GamepadUIOptionsPanel( vgui::Panel* pParent, const char* pPanelName ) + : BaseClass( pParent, pPanelName ) +{ + s_pOptionsPanel = this; + + vgui::HScheme Scheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_DEFAULT_PANEL_SCHEME, "SchemePanel" ); + SetScheme( Scheme ); + + GetFrameTitle() = GamepadUIString( "#GameUI_Options" ); + SetFooterButtons( FooterButtons::Apply | FooterButtons::Back ); + + Activate(); + + InitAAModes(); + UpdateHelperConvars(); + + LoadOptionTabs( GAMEPADUI_OPTIONS_FILE ); + FillInBindings(); + + SetActiveTab( GetActiveTab() ); + + UpdateGradients(); + + m_pScrollBar = new GamepadUIScrollBar( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemescrollbar.res", + NULL, false ); +} + +GamepadUIOptionsPanel::~GamepadUIOptionsPanel() +{ + if (s_pOptionsPanel == this) + { + s_pOptionsPanel = NULL; + } +} + +void GamepadUIOptionsPanel::OnThink() +{ + BaseClass::OnThink(); + + LayoutCurrentTab(); +} + +void GamepadUIOptionsPanel::Paint() +{ + BaseClass::Paint(); + + if ( !m_nTabCount ) + return; + + const int nLastTabX = m_flTabsOffsetX + m_nTabCount * m_Tabs[0].pTabButton->GetWide(); + + const int nTabSize = m_Tabs[0].pTabButton->GetTall(); + const int nGlyphSize = nTabSize * 0.90f; + const int nGlyphOffsetX = nGlyphSize / 4.0f; + const int nGlyphOffsetY = nTabSize - nGlyphSize; + + if ( m_leftGlyph.SetupGlyph( nGlyphSize, "menu_lb", true ) ) + m_leftGlyph.PaintGlyph( m_flTabsOffsetX - nGlyphSize - nGlyphOffsetX, m_flTabsOffsetY + nGlyphOffsetY / 2, nGlyphSize, 255 ); + + if ( m_rightGlyph.SetupGlyph( nGlyphSize, "menu_rb", true ) ) + m_rightGlyph.PaintGlyph( nLastTabX + nGlyphOffsetX, m_flTabsOffsetY + nGlyphOffsetY / 2, nGlyphSize, 255 ); + + // Draw description/image + if (m_strOptionDescription != NULL || m_pOptionImage != NULL) + { + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + float flX = m_flFooterButtonsOffsetX + m_nFooterButtonWidth + m_flFooterButtonsSpacing; + float flY = nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight; + + int nMaxWidth = nParentW - flX - (m_flFooterButtonsOffsetX + m_nFooterButtonWidth + m_flFooterButtonsSpacing); + + if (m_pOptionImage != NULL) + { + int wide, tall; + vgui::surface()->DrawGetTextureSize( *m_pOptionImage, wide, tall ); + + // TODO: More defined/controllable dimensions? + wide = (wide/tall) * m_nFooterButtonHeight * 3; + tall = m_nFooterButtonHeight * 3; + nMaxWidth -= wide; + + vgui::surface()->DrawSetTexture( *m_pOptionImage ); + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawTexturedRect( flX + nMaxWidth, flY, flX + nMaxWidth + wide, flY + tall ); + + // Minor spacing + nMaxWidth -= 4; + } + + if (m_strOptionDescription != NULL) + { + vgui::surface()->DrawSetTextColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawSetTextFont( m_hDescFont ); + vgui::surface()->DrawSetTextPos( flX, flY ); + + DrawPrintWrappedText( m_hDescFont, flX, flY, m_strOptionDescription->String(), m_strOptionDescription->Length(), nMaxWidth, true ); + } + } +} + +void GamepadUIOptionsPanel::UpdateGradients() +{ + const float flTime = GamepadUI::GetInstance().GetTime(); + GamepadUI::GetInstance().GetGradientHelper()->ResetTargets( flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Up, { 1.0f, 1.0f }, flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Down, { 1.0f, 0.5f }, flTime ); +} + +void GamepadUIOptionsPanel::LayoutCurrentTab() +{ + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + int x = m_flTabsOffsetX; + int y = 0; + for ( int i = 0; i < m_nTabCount; i++ ) + { + GamepadUITab& tab = m_Tabs[ i ]; + tab.pTabButton->SetPos( x, m_flTabsOffsetY ); + tab.pTabButton->SetVisible( true ); + x += tab.pTabButton->GetWide(); + y = m_flTabsOffsetY + tab.pTabButton->GetTall(); + + for ( GamepadUIButton *pButton : tab.pButtons ) + pButton->SetVisible( false ); + } + + int nActiveTab = GetActiveTab(); + + int i = 0; + int previousxSizes = 0; + int previousySizes = 0; + int previousxHeight = 0; + int buttonWide = 0; + for ( GamepadUIOptionButton *pButton : m_Tabs[ nActiveTab ].pButtons ) + { + int fade = 255; + + int buttonY = y; + int buttonX = m_flTabsOffsetX; + if ( pButton->IsHorizontal() ) + { + buttonX += previousxSizes; + } + else if (previousxHeight > 0) + { + // We just ended a row, append its height + previousySizes += previousxHeight; + previousxHeight = 0; + previousxSizes = 0; + } + + { + buttonY = y + previousySizes - m_Tabs[nActiveTab].ScrollState.GetScrollProgress(); + if ( buttonY < y ) + fade = RemapValClamped( y - buttonY, abs(m_flOptionsFade - pButton->GetTall()), 0, 0, 255 ); + else if ( buttonY > ( nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight - m_flOptionsFade ) ) + fade = RemapValClamped( ( nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight ) - ( buttonY + pButton->GetTall() ), 0, m_flOptionsFade, 0, 255 ); + if ( ( pButton->HasFocus() && pButton->IsEnabled() ) && fade != 0 ) + fade = 255; + } + + GamepadUIConvarButton* pCvarButton = dynamic_cast<GamepadUIConvarButton*>(pButton); + bool bHasDependencies = true; + if ( pCvarButton ) + { + const char *pszDependentCVar = pCvarButton->GetDependentCVar(); + if ( pszDependentCVar && *pszDependentCVar ) + { + bHasDependencies = false; + + bool bFound = false; + + for ( GamepadUIButton *pOtherButton : m_Tabs[ nActiveTab ].pButtons ) + { + GamepadUIConvarButton* pOtherCvarButton = dynamic_cast<GamepadUIConvarButton*>(pOtherButton); + if ( pOtherCvarButton ) + { + const char* pszConVarName = pOtherCvarButton->GetConVarName(); + if ( pszConVarName && *pszConVarName && !V_strcmp( pszDependentCVar, pszConVarName ) ) + { + bHasDependencies = pOtherCvarButton->IsConVarEnabled(); + bFound = true; + break; + } + } + } + + if ( !bFound ) + { + ConVarRef cvar(pszDependentCVar); + bHasDependencies = cvar.GetBool(); + } + } + } + + pButton->SetAlpha(fade); + pButton->SetVisible(bHasDependencies); + pButton->SetPos( buttonX, buttonY ); + + if ( pButton->IsEnabled() && pButton->IsVisible() && fade && m_Tabs[nActiveTab].bAlternating) + { + buttonWide = pButton->GetWide(); + if ( i % 2 ) + vgui::surface()->DrawSetColor( Color( 0, 0, 0, ( 20 * Min( 255, fade + 127 ) ) / 255 ) ); + else + vgui::surface()->DrawSetColor( Color( fade, fade, fade, fade > 64 ? 1 : 0 ) ); + + vgui::surface()->DrawFilledRect( buttonX, buttonY, buttonX + buttonWide, buttonY + pButton->GetTall() ); + } + + if ( pButton->IsHorizontal() ) + { + // Set previousxHeight to the tallest button + if (pButton->GetTall() > previousxHeight) + previousxHeight = pButton->GetTall(); + previousxSizes += pButton->GetWide(); + } + else + { + previousySizes += pButton->GetTall(); + } + i++; + } + + int yMax = 0; + { + if (previousySizes > m_flScrollBarHeight) + yMax = previousySizes - m_flScrollBarHeight; + m_Tabs[ nActiveTab ].ScrollState.UpdateScrollBounds( 0.0f, yMax ); + + m_pScrollBar->InitScrollBar( &m_Tabs[nActiveTab].ScrollState, + m_flScrollBarOffsetX, m_flScrollBarOffsetY ); + + m_pScrollBar->UpdateScrollBounds( 0.0f, yMax, + m_flScrollBarHeight, m_flScrollBarHeight ); + + m_Tabs[ nActiveTab ].ScrollState.UpdateScrollBounds( 0.0f, yMax ); + } + + m_Tabs[nActiveTab].ScrollState.UpdateScrolling( 2.0f, GamepadUI::GetInstance().GetTime() ); +} + +void GamepadUIOptionsPanel::OnMouseWheeled( int delta ) +{ + m_Tabs[ GetActiveTab() ].ScrollState.OnMouseWheeled( delta * 100.0f, GamepadUI::GetInstance().GetTime() ); +} + +CON_COMMAND( _gamepadui_resetkeys, "" ) +{ + GamepadUIOptionsPanel::GetInstance()->ClearBindings(); + GamepadUIOptionsPanel::GetInstance()->FillInBindings(); +} + +void GamepadUIOptionsPanel::OnCommand( char const* pCommand ) +{ + if ( !V_strcmp( pCommand, "action_back" ) ) + { + Close(); + } + else if ( !V_strcmp( pCommand, "action_apply" ) ) + { + for ( int i = 0; i < m_Tabs[ GetActiveTab() ].pButtons.Count(); i++ ) + { + GamepadUIConvarButton *pConVarButton = dynamic_cast< GamepadUIConvarButton* >( m_Tabs[ GetActiveTab() ].pButtons[ i ] ); + if ( pConVarButton ) + pConVarButton->UpdateConVar(); + } + + FlushHelperConVars(); + ApplyKeyBindings(); + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( "exec userconfig.cfg\nhost_writeconfig\nmat_savechanges\n" ); + } + else if ( !V_strcmp( pCommand, "action_usedefaults" ) ) + { + new GamepadUIGenericConfirmationPanel( GamepadUIOptionsPanel::GetInstance(), "UseDefaultsConfirm", GamepadUIString( "#GameUI_KeyboardSettings" ).String(), GamepadUIString("#GameUI_KeyboardSettingsText").String(), + [](){ + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( "exec config_default.cfg\n_gamepadui_resetkeys\n" ); + }, false, true); + } + else if ( StringHasPrefixCaseSensitive( pCommand, "tab " ) ) + { + const char *pszTab = &pCommand[4]; + if ( *pszTab ) + SetActiveTab( atoi( pszTab ) ); + } + else if ( !V_strcmp( pCommand, "open_steaminput" ) ) + { +#ifdef STEAM_INPUT + if (GamepadUI::GetInstance().GetSteamInput()->IsEnabled()) + { + GamepadUI::GetInstance().GetSteamInput()->ShowBindingPanel( GamepadUI::GetInstance().GetSteamInput()->GetActiveController() ); + } +#elif defined(HL2_RETAIL) // Steam input and Steam Controller are not supported in SDK2013 (Madi) + uint64_t nController = g_pInputSystem->GetActiveSteamInputHandle(); + if ( !nController ) + { + InputHandle_t nControllers[ STEAM_INPUT_MAX_COUNT ]; + GamepadUI::GetInstance().GetSteamAPIContext()->SteamInput()->GetConnectedControllers( nControllers ); + nController = nControllers[0]; + } + if ( nController ) + GamepadUI::GetInstance().GetSteamAPIContext()->SteamInput()->ShowBindingPanel( nController ); +#endif // HL2_RETAIL + } + else if ( !V_strcmp( pCommand, "open_techcredits" ) ) + { + GamepadUIString title = "#GameUI_ThirdPartyTechCredits"; + GamepadUIString bink = "#GameUI_Bink"; + GamepadUIString miles = "#GameUI_Miles_Audio"; + GamepadUIString voice = "#GameUI_Miles_Voice"; + wchar_t wszBuf[4096]; +#ifdef WIN32 + V_snwprintf( wszBuf, 4096, L"%s\n\n%s\n\n%s", bink.String(), miles.String(), voice.String() ); +#else + V_snwprintf( wszBuf, 4096, L"%S\n\n%S\n\n%S", bink.String(), miles.String(), voice.String() ); +#endif + new GamepadUIGenericConfirmationPanel( GamepadUIOptionsPanel::GetInstance(), "TechCredits", title.String(), wszBuf, + [](){}, true, false); + } + else if ( StringHasPrefixCaseSensitive( pCommand, "cmd " ) ) + { + const char* pszClientCmd = &pCommand[ 4 ]; + if ( *pszClientCmd ) + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( pszClientCmd ); + } + else + { + BaseClass::OnCommand( pCommand ); + } +} + +int GamepadUIOptionsPanel::GetActiveTab() +{ + int nUserTab = gamepadui_last_options_tab.GetInt(); + + int nActiveTab = Clamp( nUserTab, 0, Max( 0, m_nTabCount - 1 ) ); + if ( nUserTab != nActiveTab ) + gamepadui_last_options_tab.SetValue( nActiveTab ); + return nActiveTab; +} + +const char *UTIL_Parse( const char *data, char *token, int sizeofToken ) +{ + data = GamepadUI::GetInstance().GetEngineClient()->ParseFile( data, token, sizeofToken ); + return data; +} + +static void GetResolutionName( vmode_t *mode, char *sz, int sizeofsz, int desktopWidth, int desktopHeight ) +{ + Q_snprintf( sz, sizeofsz, "%i x %i%s", mode->width, mode->height, + ( mode->width == desktopWidth ) && ( mode->height == desktopHeight ) ? " (native)": "" ); +} + +static void GetCurrentAndDesktopSize( int ¤tWidth, int ¤tHeight, int &desktopWidth, int &desktopHeight ) +{ + const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard(); + + currentWidth = config.m_VideoMode.m_Width; + currentHeight = config.m_VideoMode.m_Height; + + // Windowed is the last item in the combobox. + GamepadUI::GetInstance().GetGameUIFuncs()->GetDesktopResolution( desktopWidth, desktopHeight ); + +#if defined( USE_SDL ) + bool bWindowed = _gamepadui_displaymode.GetInt() < 2; + + int nNewDisplayIndex = _gamepadui_displaymode.GetInt() - 2; + if ( !bWindowed ) + { + + SDL_Rect rect; + if ( !SDL_GetDisplayBounds( nNewDisplayIndex, &rect ) ) + { + desktopWidth = rect.w; + desktopHeight = rect.h; + } + } + + bool bNewFullscreenDisplay = ( !bWindowed && ( GetSDLDisplayIndexFullscreen() != nNewDisplayIndex ) ); + if ( bNewFullscreenDisplay ) + { + currentWidth = desktopWidth; + currentHeight = desktopHeight; + } +#endif +} + +void GamepadUIOptionsPanel::UpdateResolutions() +{ + if ( !m_pResolutionButton ) + return; + + m_pResolutionButton->ClearOptions(); + + const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard(); + + int currentWidth, currentHeight, desktopWidth, desktopHeight; + GetCurrentAndDesktopSize( currentWidth, currentHeight, desktopWidth, desktopHeight ); + + // get full video mode list + vmode_t *plist = NULL; + int count = 0; + GamepadUI::GetInstance().GetGameUIFuncs()->GetVideoModes(&plist, &count); + + int nFilteredModeCount = 0; + int nSelectedDefaultMode = -1; + for ( int i = 0; i < count; i++, plist++ ) + { + char sz[ 256 ]; + GetResolutionName( plist, sz, sizeof( sz ), desktopWidth, desktopHeight ); + + int iAspectMode = GetScreenAspectMode( plist->width, plist->height ); + + if ( iAspectMode == _gamepadui_aspectratio.GetInt() ) + { + if ( ( plist->width == currentWidth && plist->height == currentHeight ) || + ( nSelectedDefaultMode == -1 && plist->width == config.m_VideoMode.m_Width && plist->height == config.m_VideoMode.m_Height ) ) + { + nSelectedDefaultMode = nFilteredModeCount; + } + + GamepadUIOption option; + option.nValue = nFilteredModeCount++; + option.strOptionText.SetRawUTF8( sz ); + option.userdata.nWidth = plist->width; + option.userdata.nHeight = plist->height; + m_pResolutionButton->AddOptionItem( option ); + } + } + + if ( nSelectedDefaultMode == -1 ) + nSelectedDefaultMode = Max( m_pResolutionButton->GetOptionCount() - 1, 0 ); + + _gamepadui_resolution.SetValue( nSelectedDefaultMode ); + m_pResolutionButton->SetToDefault(); +} + +void GamepadUIOptionsPanel::ClearBindings() +{ + for ( int i = 0; i < m_nTabCount; i++ ) + { + for ( GamepadUIButton* pButton : m_Tabs[i].pButtons ) + { + GamepadUIKeyButton* pKeyButton = dynamic_cast<GamepadUIKeyButton*>(pButton); + if ( pKeyButton ) + pKeyButton->ClearKey(); + } + } +} + +// Mainly from GameUI +void GamepadUIOptionsPanel::FillInBindings() +{ + for ( int i = 0; i < m_nTabCount; i++ ) + m_Tabs[ i ].KeysToUnbind.RemoveAll(); + + bool bJoystick = false; + ConVarRef var( "joystick" ); + if ( var.IsValid() ) + { + bJoystick = var.GetBool(); + } + + // NVNT see if we have a falcon connected. + bool bFalcon = false; + ConVarRef falconVar("hap_HasDevice"); + if ( var.IsValid() ) + { + bFalcon = var.GetBool(); + } + + // Fill in bindings + for ( int i = 0; i < BUTTON_CODE_LAST; i++ ) + { + // Look up binding + const char *binding = GamepadUI::GetInstance().GetGameUIFuncs()->GetBindingForButtonCode((ButtonCode_t)i); + if ( !binding ) + continue; + + // See if there is an item for this one? + GamepadUIKeyButton *pButton = NULL; + int nTab = 0; + for ( int j = 0; j < m_nTabCount; j++ ) + { + for ( GamepadUIButton *pFindButton : m_Tabs[ j ].pButtons ) + { + GamepadUIKeyButton *pFindKeyButton = dynamic_cast< GamepadUIKeyButton* >( pFindButton ); + if ( pFindKeyButton && !V_strcmp( pFindKeyButton->GetKeyBinding(), binding ) ) + { + pButton = pFindKeyButton; + nTab = j; + break; + } + } + } + + if ( pButton ) + { + const char *pKeyName = g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ); + const char *pCurrentKey = pButton->GetKey(); + if ( *pCurrentKey ) + { + ButtonCode_t currentBC = (ButtonCode_t)GamepadUI::GetInstance().GetGameUIFuncs()->GetButtonCodeForBind( pCurrentKey ); + + bool bShouldOverride = bJoystick && IsJoystickCode((ButtonCode_t)i) && !IsJoystickCode(currentBC); + if( !bShouldOverride && bFalcon && IsNovintCode((ButtonCode_t)i) && !IsNovintCode(currentBC) ) + bShouldOverride = true; + + if ( !bShouldOverride ) + continue; + + m_Tabs[ nTab ].KeysToUnbind.FindAndRemove( pCurrentKey ); + } + pButton->SetKey( pKeyName ); + m_Tabs[ nTab ].KeysToUnbind.AddToTail( pKeyName ); + } + } +} + +void GamepadUIOptionsPanel::ApplyKeyBindings() +{ + const int nTab = GetActiveTab(); + for ( int i = 0; i < m_Tabs[ nTab ].KeysToUnbind.Count(); i++ ) + { + char buff[256]; + Q_snprintf( buff, sizeof(buff), "unbind \"%s\"\n", m_Tabs[ nTab].KeysToUnbind[ i ].String() ); + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( buff ); + } + m_Tabs[ nTab ].KeysToUnbind.RemoveAll(); + + for ( GamepadUIButton *pButton : m_Tabs[ nTab ].pButtons ) + { + GamepadUIKeyButton *pKeyButton = dynamic_cast< GamepadUIKeyButton* >( pButton ); + if ( !pKeyButton ) + continue; + + const char *pKey = pKeyButton->GetKey(); + if ( !pKey ) + continue; + + char buff[256]; + Q_snprintf( buff, sizeof(buff), "bind \"%s\" \"%s\"\n", pKey, pKeyButton->GetKeyBinding() ); + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( buff ); + } +} + +void GamepadUIOptionsPanel::OnKeyBound( const char *pKey ) +{ + for ( int i = 0; i < m_nTabCount; i++ ) + { + for ( GamepadUIButton* pFindButton : m_Tabs[i].pButtons ) + { + GamepadUIKeyButton* pFindKeyButton = dynamic_cast<GamepadUIKeyButton*>(pFindButton); + if ( pFindKeyButton && !V_strcmp( pFindKeyButton->GetKey(), pKey ) ) + pFindKeyButton->ClearKey(); + } + } +} + +void GamepadUIOptionsPanel::OnKeyCodePressed( vgui::KeyCode code ) +{ + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + + + switch ( buttonCode ) + { +#ifdef HL2_RETAIL // Steam input and Steam Controller are not supported in SDK2013 (Madi) + case STEAMCONTROLLER_LEFT_BUMPER: +#else + case KEY_XBUTTON_LEFT_SHOULDER: +#endif + SetActiveTab( GetActiveTab() - 1 ); + break; + +#ifdef HL2_RETAIL + case STEAMCONTROLLER_RIGHT_BUMPER: +#else + case KEY_XBUTTON_RIGHT_SHOULDER: +#endif + SetActiveTab( GetActiveTab() + 1 ); + break; + default: + return BaseClass::OnKeyCodePressed( code ); + } +} + +void GamepadUIOptionsPanel::OnGamepadUIButtonNavigatedTo( vgui::VPANEL button ) +{ + GamepadUIButton *pButton = dynamic_cast< GamepadUIButton * >( vgui::ipanel()->GetPanel( button, GetModuleName() ) ); + if ( pButton ) + { + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + const float flTabButtonHeight = m_Tabs[ GetActiveTab() ].pTabButton->m_flHeight; + + float flScrollRegion = nParentH - (m_flTabsOffsetY + m_flFooterButtonsOffsetY + m_nFooterButtonHeight + flTabButtonHeight); + int nX, nY; + pButton->GetPos( nX, nY ); + if ( nY + m_flFooterButtonsOffsetY + m_nFooterButtonHeight + pButton->GetTall() > nParentH || nY < m_flTabsOffsetY + flTabButtonHeight ) + { + int nTargetY = 0; + int nThisButton = -1; + int nHeader = -1; + int nHorzHeight = 0; + for ( int i = 0; i < m_Tabs[ GetActiveTab() ].pButtons.Count(); i++ ) + { + if ( m_Tabs[ GetActiveTab() ].pButtons[i] == pButton) + { + nThisButton = i; + break; + } + + if (m_Tabs[GetActiveTab()].pButtons[i]->IsHorizontal()) + { + nHorzHeight += m_Tabs[GetActiveTab()].pButtons[i]->m_flHeight; + continue; + } + else if (nHorzHeight != 0) + { + nTargetY += nHorzHeight; + nHorzHeight = 0; + } + + if (!m_Tabs[GetActiveTab()].pButtons[i]->IsEnabled()) + nHeader = i; + else + nHeader = -1; + + nTargetY += m_Tabs[ GetActiveTab() ].pButtons[i]->m_flHeight; + } + + // This button isn't part of the current tab, so don't scroll + if (nThisButton == -1) + return; + + // If this button has a section header above it and we're going up, scroll to it + if ( nHeader != -1 && nY < nParentH / 2 ) + nTargetY -= m_Tabs[ GetActiveTab() ].pButtons[ nHeader ]->m_flHeight; + + if ( nY < nParentH / 2 ) + { + nTargetY -= (pButton->m_flHeightAnimationValue[ButtonStates::Over] / 2); + } + else + { + nTargetY -= flScrollRegion; + nTargetY += pButton->m_flHeight; + nTargetY += (pButton->m_flHeightAnimationValue[ButtonStates::Over] / 2); + } + + m_Tabs[ GetActiveTab() ].ScrollState.SetScrollTarget( nTargetY, GamepadUI::GetInstance().GetTime()); + } + } +} + +void GamepadUIOptionsPanel::SetActiveTab( int nTab ) +{ + nTab = Clamp( nTab, 0, Max( m_nTabCount - 1, 0 ) ); + + gamepadui_last_options_tab.SetValue( nTab ); + int nActiveTab = GetActiveTab(); + for ( int i = 0; i < m_nTabCount; i++ ) + m_Tabs[ i ].pTabButton->ForceDepressed( i == nActiveTab ); + + FooterButtonMask buttons = FooterButtons::Apply | FooterButtons::Back; + if ( V_strncmp( m_Tabs[nActiveTab].pTabButton->GetName(), "Keyboard", 8 ) == 0 ) + buttons |= FooterButtons::UseDefaults; + SetFooterButtons( buttons ); + + for ( GamepadUIButton *pButton : m_Tabs[ nActiveTab ].pButtons ) + { + if ( pButton->GetCurrentButtonState() == ButtonState::Pressed ) + { + pButton->NavigateTo(); + return; + } + } + + for ( GamepadUIButton *pButton : m_Tabs[ nActiveTab ].pButtons ) + { + if ( pButton->IsEnabled() ) + { + pButton->NavigateTo(); + return; + } + } +} + +void GamepadUIOptionsPanel::LoadOptionTabs( const char *pszOptionsFile ) +{ + KeyValues* pDataFile = new KeyValues( "Options" ); + if ( pDataFile->LoadFromFile( g_pFullFileSystem, pszOptionsFile ) ) + { + for ( KeyValues* pTabData = pDataFile->GetFirstSubKey(); pTabData != NULL; pTabData = pTabData->GetNextKey() ) + { + { + char buttonCmd[64]; + V_snprintf( buttonCmd, sizeof( buttonCmd ), "tab %d", m_nTabCount ); + + auto button = new GamepadUIButton( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemetab.res", + buttonCmd, + pTabData->GetString( "title" ), "" ); + button->SetZPos( 50 ); + //button->SetFooterButton( true ); + if ( m_nTabCount == gamepadui_last_options_tab.GetInt() ) + button->ForceDepressed( true ); + m_Tabs[ m_nTabCount ].pTabButton = button; + } + + m_Tabs[ m_nTabCount ].pTabButton->SetName( pTabData->GetName() ); + m_Tabs[ m_nTabCount ].bAlternating = pTabData->GetBool( "alternating" ); + m_Tabs[ m_nTabCount ].bHorizontal = pTabData->GetBool( "horizontal" ); + + if ( !V_strcmp( pTabData->GetString( "items_from" ), "keyboard" ) ) + { + char szBinding[256]; + char szDescription[256]; + + // Load the default keys list + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + if ( !g_pFullFileSystem->ReadFile( "scripts/kb_act.lst", NULL, buf ) ) + return; + + const char *data = ( const char* )buf.Base(); + + char token[512]; + while ( 1 ) + { + data = UTIL_Parse( data, token, sizeof( token ) ); + // Done. + if ( strlen( token ) <= 0 ) + break; + + Q_strncpy( szBinding, token, sizeof( szBinding ) ); + + data = UTIL_Parse( data, token, sizeof( token ) ); + if ( strlen( token ) <= 0 ) + { + break; + } + + Q_strncpy( szDescription, token, sizeof( szDescription ) ); + + // Skip '======' rows + if ( szDescription[ 0 ] != '=' ) + { + // Flag as special header row if binding is "blank" + if ( !stricmp( szBinding, "blank" ) ) + { + // add header item + auto button = new GamepadUIHeaderButton( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemeoptions_sectiontitle.res", + "button_pressed", + szDescription, "" ); + //button->SetFooterButton( true ); + button->SetEnabled( false ); + m_Tabs[ m_nTabCount ].pButtons.AddToTail( button ); + } + else + { + // Add to list + auto button = new GamepadUIKeyButton( + szBinding, this, this, + GAMEPADUI_RESOURCE_FOLDER "schemeoptions_wheelywheel.res", + "button_pressed", + szDescription, "" ); + m_Tabs[ m_nTabCount ].pButtons.AddToTail( button ); + } + } + } + + FillInBindings(); + } + + KeyValues* pTabItems = pTabData->FindKey( "items" ); + if ( pTabItems ) + { + for ( KeyValues* pItemData = pTabItems->GetFirstSubKey(); pItemData != NULL; pItemData = pItemData->GetNextKey() ) + { + const char *pItemType = pItemData->GetString( "type", "droppydown" ); + if ( !V_strcmp( pItemType, "checkybox" ) ) + { + auto button = new GamepadUICheckButton( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemeoptions_checkybox.res", + "button_pressed", + pItemData->GetString( "text", "" ), pItemData->GetString( "description", "" ) ); + m_Tabs[ m_nTabCount ].pButtons.AddToTail( button ); + } + else if ( !V_strcmp( pItemType, "skillyskill" ) ) + { + auto button = new GamepadUISkillySkill( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemeoptions_skillyskill.res", + "button_pressed", + pItemData->GetString( "text", "" ), pItemData->GetString( "description", "" ), + pItemData->GetString( "image", "" ), V_atoi( pItemData->GetString( "skill", "" ) ) ); + button->SetMouseNavigate( false ); + button->SetHorizontal( pItemData->GetBool( "horizontal", m_Tabs[ m_nTabCount ].bHorizontal ) ); + m_Tabs[ m_nTabCount ].pButtons.AddToTail( button ); + } + else if ( !V_strcmp( pItemType, "slideyslide" ) ) + { + const char *pszCvar = pItemData->GetString( "convar" ); + const char *pszCvarDepends = pItemData->GetString( "depends_on" ); + bool bInstantApply = pItemData->GetBool( "instantapply" ); + float flMin = pItemData->GetFloat( "min", 0.0f ); + float flMax = pItemData->GetFloat( "max", 1.0f ); + float flStep = pItemData->GetFloat( "step", 0.1f ); + int nTextPrecision = pItemData->GetInt( "textprecision", -1 ); + auto button = new GamepadUISlideySlide( + pszCvar, pszCvarDepends, bInstantApply, flMin, flMax, flStep, nTextPrecision, + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemeoptions_slideyslide.res", + "button_pressed", + pItemData->GetString( "text", "" ), pItemData->GetString( "description", "" ) ); + button->SetToDefault(); + button->SetMouseStep( pItemData->GetFloat( "mouse_step", flStep ) ); + button->SetOptionImage( pItemData->GetString( "image", NULL ) ); + m_Tabs[ m_nTabCount ].pButtons.AddToTail( button ); + } + else if ( !V_strcmp( pItemType, "headeryheader" ) ) + { + // add header item + auto button = new GamepadUIHeaderButton( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemeoptions_sectiontitle.res", + "button_pressed", + pItemData->GetString( "text", "" ), pItemData->GetString( "description", "" ) ); + //button->SetFooterButton( true ); + button->SetEnabled( false ); + button->SetCentered( pItemData->GetBool( "center" ) ); + m_Tabs[ m_nTabCount ].pButtons.AddToTail( button ); + } + else if ( !V_strcmp( pItemType, "wheelywheel" ) ) + { + const char *pszCvar = pItemData->GetString( "convar" ); + const char *pszCvarDepends = pItemData->GetString( "depends_on" ); + bool bInstantApply = pItemData->GetBool( "instantapply" ); + bool bSignOnly = pItemData->GetBool( "signonly" ); + auto button = new GamepadUIWheelyWheel( + pszCvar, pszCvarDepends, bInstantApply, bSignOnly, + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemeoptions_wheelywheel.res", + "button_pressed", + pItemData->GetString( "text", "" ), pItemData->GetString( "description", "" ) ); + + const char *pszOptionsFrom = pItemData->GetString( "options_from" ); + KeyValues *pOptions = pItemData->FindKey( "options" ); + if ( pOptions ) + { + for ( KeyValues* pOptionData = pOptions->GetFirstSubKey(); pOptionData != NULL; pOptionData = pOptionData->GetNextKey() ) + { + GamepadUIOption option; + option.nValue = V_atoi( pOptionData->GetName() ); + option.strOptionText = GamepadUIString( pOptionData->GetString() ); + button->AddOptionItem( option ); + } + } + else if ( pszOptionsFrom && *pszOptionsFrom ) + { + if ( !V_strcmp( pszOptionsFrom, "antialiasing" ) ) + { + for ( int i = 0; i < g_nNumAAModes; i++ ) + { + GamepadUIOption option; + option.nValue = i; + option.strOptionText = g_AAModes[ i ].strName; + button->AddOptionItem( option ); + } + } + else if ( !V_strcmp( pszOptionsFrom, "displaymode" ) ) + { + int i = 0; + + GamepadUIOption option; + option.nValue = i++; + option.strOptionText = "#GameUI_Windowed"; + button->AddOptionItem( option ); + +#ifdef HL2_RETAIL // SDK2013 does not support borderless windowed (Madi) + wchar_t wszNoBorderText[ 256 ]; + V_swprintf_safe( wszNoBorderText, L"%ls (No Border)", option.strOptionText.String() ); + option.nValue = i++; + option.strOptionText = wszNoBorderText; + button->AddOptionItem( option ); +#endif // HL2_RETAIL + +#if defined( USE_SDL ) && defined( DX_TO_GL_ABSTRACTION ) + GamepadUIString strFullscreen = "#GameUI_Fullscreen"; + int numVideoDisplays = SDL_GetNumVideoDisplays(); + + if ( numVideoDisplays <= 1 ) + { + option.nValue = i++; + option.strOptionText = strFullscreen; + button->AddOptionItem( option ); + } + else + { + for ( int display = 0; display < numVideoDisplays; display++ ) + { + wchar_t wszItemText[ 256 ]; + V_swprintf_safe( wszItemText, L"%ls (%d)", strFullscreen.String(), display); + + option.nValue = i++; + option.strOptionText = wszItemText; + button->AddOptionItem( option ); + } + + } +#else + option.nValue = i++; + option.strOptionText = "#GameUI_Fullscreen"; + button->AddOptionItem( option ); +#endif + } + else if ( !V_strcmp( pszOptionsFrom, "resolutions" ) ) + { + m_pResolutionButton = button; + + UpdateResolutions(); + } + } + button->SetToDefault(); + button->SetOptionImage( pItemData->GetString( "image", NULL ) ); + + // Values which require confirmation before changing + KeyValues *pConfirm = pItemData->FindKey( "confirm" ); + if (pConfirm) + { + for ( KeyValues* pConfirmData = pConfirm->GetFirstSubKey(); pConfirmData != NULL; pConfirmData = pConfirmData->GetNextKey() ) + { + button->AddDangerousOption( V_atoi( pConfirmData->GetName() ), pConfirmData->GetString() ); + } + } + + m_Tabs[ m_nTabCount ].pButtons.AddToTail( button ); + } + else if ( !V_strcmp( pItemType, "button" ) ) + { + auto button = new GamepadUIOptionButton( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemeoptions_wheelywheel.res", + pItemData->GetString( "command", "button_pressed" ), + pItemData->GetString( "text", "" ), pItemData->GetString( "description", "" ) ); + m_Tabs[ m_nTabCount ].pButtons.AddToTail( button ); + } + } + } + + CUtlVector< GamepadUIOptionButton* >& pButtons = m_Tabs[ m_nTabCount ].pButtons; + for ( int i = 1; i < pButtons.Count(); i++ ) + { + int iPrev = i - 1; + + if ( pButtons[i]->IsHorizontal() ) + { + pButtons[ i ]->SetNavLeft( pButtons[ iPrev ] ); + pButtons[ iPrev ]->SetNavRight( pButtons[ i ] ); + } + else if ( pButtons[ iPrev ]->IsHorizontal() ) + { + pButtons[ i ]->SetNavUp( pButtons[ iPrev ] ); + + // Make sure all previous horizontal buttons go down to this + int i2 = i-1; + for (; i2 >= 1 && pButtons[i2]->IsHorizontal(); i2-- ) + { + pButtons[ i2 ]->SetNavDown( pButtons[ i ] ); + } + } + else + { + pButtons[ i ]->SetNavUp( pButtons[ iPrev ] ); + pButtons[ iPrev ]->SetNavDown( pButtons[ i ] ); + } + } + + m_nTabCount++; + } + } +} + +void GamepadUIOptionsPanel::ApplySchemeSettings( vgui::IScheme* pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_hDescFont = pScheme->GetFont( "Button.Description.Font", true ); + + float flX, flY; + if (GamepadUI::GetInstance().GetScreenRatio( flX, flY )) + { + m_flTabsOffsetX *= (flX * flX); + m_flScrollBarOffsetX *= (flX); + } + + int nX, nY; + GamepadUI::GetInstance().GetSizingPanelOffset( nX, nY ); + if (nX > 0) + { + GamepadUI::GetInstance().GetSizingPanelScale( flX, flY ); + m_flTabsOffsetX += ((float)nX) * flX * 0.5f; + m_flScrollBarOffsetX += ((float)nX) * flX * 0.1f; + } +} + +CON_COMMAND( gamepadui_openoptionsdialog, "" ) +{ + new GamepadUIOptionsPanel( GamepadUI::GetInstance().GetBasePanel(), "" ); +} diff --git a/game/gamepadui/gamepadui_panel.h b/game/gamepadui/gamepadui_panel.h new file mode 100644 index 00000000..5beaf23a --- /dev/null +++ b/game/gamepadui/gamepadui_panel.h @@ -0,0 +1,191 @@ +#ifndef GAMEPADUI_PANEL_H +#define GAMEPADUI_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gamepadui_interface.h" +#include "vgui_controls/Panel.h" + +// There are a lot of really sucky macros in here +// solely because VGUI base class stuff completely +// falls apart when you use it with template classes. +// - Josh + +#define GAMEPADUI_CONCAT_( x, y ) x ## y +#define GAMEPADUI_CONCAT( x, y ) GAMEPADUI_CONCAT_( x, y ) + +#define GAMEPADUI_STRINGIFY_( x ) #x +#define GAMEPADUI_STRINGIFY( x ) GAMEPADUI_STRINGIFY_( x ) + +#define GAMEPADUI_DEFAULT_PANEL_SCHEME GAMEPADUI_RESOURCE_FOLDER "schemepanel.res" + +namespace SchemeValueTypes +{ + enum SchemeValueType + { + Float, + Bool, + ProportionalFloat, + Color + }; +} +using SchemeValueType = SchemeValueTypes::SchemeValueType; + +class SchemeValueMap +{ +public: + struct SchemeValue + { + const char* pszName; + const char *pszScriptName; + SchemeValueType Type; + const char *pszDefaultValue; + PANELLOOKUPFUNC pfnFunc; + }; + + void AddValueToSchemeMap( const char *pszName, const char *pszScriptName, SchemeValueType Type, char const *pszDefaultValue, PANELLOOKUPFUNC pfnFunc) + { + m_Values.AddToTail( SchemeValue{ pszName, pszScriptName, Type, pszDefaultValue, pfnFunc } ); + } + + void UpdateSchemeProperties( vgui::Panel* pPanel, vgui::IScheme* pScheme ) + { + for ( SchemeValue& value : m_Values ) + { + const char *pszResourceStr = pScheme->GetResourceString( value.pszScriptName ); + switch ( value.Type ) + { + case SchemeValueTypes::ProportionalFloat: + case SchemeValueTypes::Float: + { + float flValue = 0.0f; + if ( pszResourceStr && *pszResourceStr) + flValue = atof( pszResourceStr ); + else if ( value.pszDefaultValue && *value.pszDefaultValue ) + flValue = atof( value.pszDefaultValue ); + if ( value.Type == SchemeValueTypes::ProportionalFloat ) + flValue = float( vgui::scheme()->GetProportionalScaledValueEx( pPanel->GetScheme(), int( flValue ) ) ); + + *static_cast< float* >( value.pfnFunc( pPanel ) ) = flValue; + break; + } + case SchemeValueTypes::Bool: + { + int iVal = 0; + if ( pszResourceStr && *pszResourceStr ) + iVal = atoi( pszResourceStr ); + else if ( value.pszDefaultValue && *value.pszDefaultValue ) + iVal = atoi( value.pszDefaultValue ); + *static_cast<bool*>( value.pfnFunc( pPanel ) ) = !!iVal; + break; + + } + case SchemeValueType::Color: + { + Color cVal = Color( 0, 0, 0, 0 ); + if ( value.pszDefaultValue && *value.pszDefaultValue ) + { + float r = 0.0f, g = 0.0f, b = 0.0f, a = 0.0f; + sscanf( value.pszDefaultValue, "%f %f %f %f", &r, &g, &b, &a ); + cVal[ 0 ] = (unsigned char) r; + cVal[ 1 ] = (unsigned char) g; + cVal[ 2 ] = (unsigned char) b; + cVal[ 3 ] = (unsigned char) a; + } + + cVal = pScheme->GetColor( value.pszScriptName, cVal ); + *static_cast<Color*>( value.pfnFunc( pPanel ) ) = cVal; + break; + } + } + } + } + +private: + CUtlVector< SchemeValue > m_Values; +}; + + +// Josh: Multi-line macro +#define GAMEPADUI_RUN_ANIMATION_COMMAND( name, interpolator ) \ + if ( GamepadUI::GetInstance().GetAnimationController() ) \ + GamepadUI::GetInstance().GetAnimationController()->RunAnimationCommand( this, #name , name##AnimationValue[state], 0.0f, name##AnimationDuration, interpolator ) + + +// Josh: Multi-line macro +#define GAMEPADUI_PANEL_PROPERTY( type, name, scriptname, defaultvalue, typealias ) \ + class PanelAnimationVar_##name; \ + friend class PanelAnimationVar_##name; \ + static void *GetVar_##name( vgui::Panel *panel ) \ + { \ + return &(( ThisClass *)panel)->name; \ + } \ + class PanelAnimationVar_##name \ + { \ + public: \ + static void InitVar( SchemeValueMap *pMap ) \ + { \ + pMap->AddValueToSchemeMap( #name, scriptname, typealias, defaultvalue, ThisClass::GetVar_##name ); \ + } \ + PanelAnimationVar_##name( SchemeValueMap *pMap ) \ + { \ + PanelAnimationVar_##name::InitVar( pMap ); \ + } \ + }; \ + PanelAnimationVar_##name m_##name##_register = { this }; \ + type name; + + +// Josh: Multi-line macro +#define GAMEPADUI_BUTTON_ANIMATED_PROPERTY( type, name, scriptname, defaultvalue, typealias ) \ + class PanelAnimationVar_##name; \ + friend class PanelAnimationVar_##name; \ + static void *GetVar_##name( vgui::Panel *panel ) \ + { \ + return &(( ThisClass *)panel)->name; \ + } \ + static void *GetVar_##name##_Out( vgui::Panel *panel ) \ + { \ + return &(( ThisClass *)panel)->name##AnimationValue[ButtonStates::Out]; \ + } \ + static void *GetVar_##name##_Over( vgui::Panel *panel ) \ + { \ + return &(( ThisClass *)panel)->name##AnimationValue[ButtonStates::Over]; \ + } \ + static void *GetVar_##name##_Pressed( vgui::Panel *panel ) \ + { \ + return &(( ThisClass *)panel)->name##AnimationValue[ButtonStates::Pressed]; \ + } \ + static void *GetVar_##name##_AnimationDuration( vgui::Panel *panel ) \ + { \ + return &(( ThisClass *)panel)->name##AnimationDuration; \ + } \ + class PanelAnimationVar_##name \ + { \ + public: \ + static void InitVar( SchemeValueMap *pMap ) \ + { \ + pMap->AddValueToSchemeMap( #name, scriptname ".Out", typealias, defaultvalue, ThisClass::GetVar_##name ); \ + pMap->AddValueToSchemeMap( #name, scriptname ".Out", typealias, defaultvalue, ThisClass::GetVar_##name##_Out ); \ + pMap->AddValueToSchemeMap( #name, scriptname ".Over", typealias, defaultvalue, ThisClass::GetVar_##name##_Over ); \ + pMap->AddValueToSchemeMap( #name, scriptname ".Pressed", typealias, defaultvalue, ThisClass::GetVar_##name##_Pressed ); \ + pMap->AddValueToSchemeMap( #name, scriptname ".Animation.Duration", SchemeValueTypes::Float, "0.2", ThisClass::GetVar_##name##_AnimationDuration ); \ + static bool bAdded = false; \ + if ( !bAdded ) \ + { \ + bAdded = true; \ + AddToAnimationMap( #name, #type, #name, defaultvalue, false, ThisClass::GetVar_##name ); \ + } \ + } \ + PanelAnimationVar_##name( SchemeValueMap *pMap ) \ + { \ + PanelAnimationVar_##name::InitVar( pMap ); \ + } \ + }; \ + PanelAnimationVar_##name m_##name##_register = { this }; \ + type name; \ + type name##AnimationValue[ButtonStates::Count]; \ + float name##AnimationDuration; + +#endif // GAMEPADUI_PANEL_H diff --git a/game/gamepadui/gamepadui_portal.vpc b/game/gamepadui/gamepadui_portal.vpc new file mode 100644 index 00000000..3143d421 --- /dev/null +++ b/game/gamepadui/gamepadui_portal.vpc @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// GAMEPADUI_PORTAL.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro GAMENAME "portal" +$Macro OUTBINNAME "gamepadui" + +$Include "$SRCDIR\game\gamepadui\gamepadui_base.vpc" + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;GAMEPADUI_GAME_PORTAL" + } +} + +$Project "GamepadUI (Portal)" +{ + $Folder "Source Files" + { + $File "gamepadui_bonusmaps.cpp" + } +} diff --git a/game/gamepadui/gamepadui_savegame.cpp b/game/gamepadui/gamepadui_savegame.cpp new file mode 100644 index 00000000..32ba505a --- /dev/null +++ b/game/gamepadui/gamepadui_savegame.cpp @@ -0,0 +1,1235 @@ +#include "gamepadui_interface.h" +#include "gamepadui_image.h" +#include "gamepadui_util.h" +#include "gamepadui_genericconfirmation.h" +#include "gamepadui_scrollbar.h" + +#include "ienginevgui.h" +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" +#include "GameUI/IGameUI.h" + +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/ImagePanel.h" +#include "vgui_controls/ScrollBar.h" +#include "savegame_version.h" + +#include "KeyValues.h" +#include "filesystem.h" + +#include "tier0/memdbgon.h" + +ConVar gamepadui_savegame_use_delete_mode( "gamepadui_savegame_use_delete_mode", "1", FCVAR_NONE, "Causes the save game panel to use a \"delete mode\" when not using a controller, showing X buttons next to each save game" ); +#ifdef GAMEPADUI_GAME_EZ2 +ConVar gamepadui_savegame_wilson_thumb( "gamepadui_savegame_wilson_thumb", "1", FCVAR_NONE, "Shows a Wilson icon on save games that have Wilson" ); +#endif + +class GamepadUISaveButton; +struct SaveGameDescription_t; + +class GamepadUISaveGamePanel : public GamepadUIFrame +{ + DECLARE_CLASS_SIMPLE( GamepadUISaveGamePanel, GamepadUIFrame ); + +public: + GamepadUISaveGamePanel( vgui::Panel *pParent, const char* pPanelName, bool bIsSave ); + ~GamepadUISaveGamePanel(); + + void UpdateGradients(); + + void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + void OnThink() OVERRIDE; + void Paint() OVERRIDE; + void OnCommand( char const* pCommand ) OVERRIDE; + void OnMouseWheeled( int nDelta ) OVERRIDE; + + bool InDeleteMode() { return m_pDeletePanels.Count() > 0; } + +#ifdef GAMEPADUI_GAME_EZ2 + int IsSaveSuspect( const char *pszEZ2Version, const char *pszMapName, int nMapVersion, const char **ppszIncompatibleVersion, const char **ppszLastCompatibleBranch ); + + GamepadUIImage &GetWilsonThumb( float &flSize, float &flOffsetX, float &flOffsetY ) + { + flSize = m_flThumbSize; flOffsetX = m_flThumbOffsetX; flOffsetY = m_flThumbOffsetY; + return m_WilsonThumb; + } +#endif + + MESSAGE_FUNC_HANDLE( OnGamepadUIButtonNavigatedTo, "OnGamepadUIButtonNavigatedTo", button ); + +private: + void ScanSavedGames(); + void LayoutSaveButtons(); + bool ParseSaveData( char const* pFileName, char const* pShortName, SaveGameDescription_t& save ); + static int SaveReadNameAndComment( FileHandle_t f, OUT_Z_CAP( nameSize ) char* name, int nameSize, OUT_Z_CAP( commentSize ) char* comment, int commentSize ); +#ifdef GAMEPADUI_GAME_EZ2 + static int SaveReadCustomMetadata( const char *pSaveName, char *ez2version, int ez2versionSize, char *platform, int platformSize, int &nMapVersion, bool &bDeck, bool &bWilson ); + + void LoadVersionHistory(); + void UnloadVersionHistory(); +#endif + void FindSaveSlot( OUT_Z_CAP( bufsize ) char* buffer, int bufsize ); + void DeleteSaveGame( const char* pFileName ); + + void LoadGame( const SaveGameDescription_t* pSave ); + void SaveGame( const SaveGameDescription_t *pSave ); + + GamepadUIString m_strNoSaveString; + + CUtlVector<GamepadUISaveButton*> m_pSavePanels; + CUtlVector<SaveGameDescription_t> m_Saves; + + CUtlVector<GamepadUIButton*> m_pDeletePanels; + + GamepadUIScrollState m_ScrollState; + + GamepadUIScrollBar *m_pScrollBar = NULL; + + bool m_bIsSave; + +#ifdef GAMEPADUI_GAME_EZ2 + KeyValues *m_pVersionHistory; + + GamepadUIImage m_WilsonThumb; +#endif + + GAMEPADUI_PANEL_PROPERTY( float, m_flSavesFade, "Saves.Fade", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flSavesOffsetX, "Saves.OffsetX", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flSavesOffsetY, "Saves.OffsetY", "0", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flSavesSpacing, "Saves.Spacing", "0", SchemeValueTypes::ProportionalFloat ); + + GAMEPADUI_PANEL_PROPERTY( float, m_flThumbSize, "Saves.Thumb.Size", "16", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flThumbOffsetX, "Saves.Thumb.OffsetX", "4", SchemeValueTypes::ProportionalFloat ); + GAMEPADUI_PANEL_PROPERTY( float, m_flThumbOffsetY, "Saves.Thumb.OffsetY", "4", SchemeValueTypes::ProportionalFloat ); +}; + +/* From GameUI */ +struct SaveGameDescription_t; +#define SAVEGAME_MAPNAME_LEN 32 +#define SAVEGAME_COMMENT_LEN 80 +#define SAVEGAME_ELAPSED_LEN 32 + +#define TGA_IMAGE_PANEL_WIDTH 180 +#define TGA_IMAGE_PANEL_HEIGHT 100 +#define MAX_LISTED_SAVE_GAMES 128 +#define NEW_SAVE_GAME_TIMESTAMP 0xFFFFFFFF + +struct SaveGameDescription_t +{ + char szShortName[64]; + char szFileName[128]; + char szMapName[SAVEGAME_MAPNAME_LEN]; + char szComment[SAVEGAME_COMMENT_LEN]; + char szType[64]; + char szElapsedTime[SAVEGAME_ELAPSED_LEN]; + char szFileTime[32]; + unsigned int iTimestamp; + unsigned int iSize; +#ifdef GAMEPADUI_GAME_EZ2 + char szEZ2Version[8]; + char szPlatform[16]; + int nMapVersion; + bool bDeck; + bool bWilson; +#endif +}; +/* End from GameUI */ + +#ifdef GAMEPADUI_GAME_EZ2 +enum +{ + SaveSuspectLevel_None, + SaveSuspectLevel_Mismatch, // Save is from a different version + SaveSuspectLevel_Incompatible, // Save is from an explicitly incompatible version +}; +#endif + +class GamepadUISaveButton : public GamepadUIButton +{ +public: + DECLARE_CLASS_SIMPLE( GamepadUISaveButton, GamepadUIButton ); + + GamepadUISaveButton( vgui::Panel* pParent, vgui::Panel* pActionSignalTarget, const char *pSchemeFile, const char* pCommand, const SaveGameDescription_t *pSaveGame ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, pCommand, pSaveGame->szComment, pSaveGame->szFileTime ) + , m_Image() + , m_pSaveGame( pSaveGame ) + { + char tga[_MAX_PATH]; + Q_strncpy( tga, pSaveGame->szFileName, sizeof( tga ) ); + char *ext = strstr( tga, ".sav" ); + if ( ext ) + strcpy( ext, ".tga" ); + + char tga2[_MAX_PATH]; + Q_snprintf( tga2, sizeof( tga2 ), "//MOD/%s", tga ); + + if ( g_pFullFileSystem->FileExists( tga2 ) ) + { + m_Image.SetTGAImage( tga2 ); + m_bUseTGAImage = true; + } + else + { + m_Image.SetImage( "gamepadui/save_game.vmt" ); + } + +#ifdef GAMEPADUI_GAME_EZ2 + m_strEZ2Version = GamepadUIString( pSaveGame->szEZ2Version ); +#endif + } + +#ifdef GAMEPADUI_GAME_EZ2 + void RunAnimations( ButtonState state ) + { + BaseClass::RunAnimations( state ); + + GAMEPADUI_RUN_ANIMATION_COMMAND( m_colVersion, vgui::AnimationController::INTERPOLATOR_LINEAR ); + } + + void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE + { + BaseClass::ApplySchemeSettings( pScheme ); + + char szVersion[8]; + V_UnicodeToUTF8( m_strEZ2Version.String(), szVersion, sizeof( szVersion ) ); + m_iSaveSuspectLevel = static_cast<GamepadUISaveGamePanel *>(GetParent())->IsSaveSuspect( szVersion, m_pSaveGame->szMapName, m_pSaveGame->nMapVersion, &m_pIncompatibleVersion, &m_pLastCompatibleBranch ); + + if ( m_iSaveSuspectLevel != SaveSuspectLevel_None ) + { + switch (m_iSaveSuspectLevel) + { + case SaveSuspectLevel_Mismatch: + m_colVersionAnimationValue[ButtonStates::Out] = m_colVersionMismatch; + break; + + case SaveSuspectLevel_Incompatible: + { + // For now, correspond suspect colors to over and out states + m_colBackgroundColorAnimationValue[ButtonStates::Over] = m_colVersionIncompatible; + m_colTextColorAnimationValue[ButtonStates::Out] = m_colVersionIncompatible; + m_colDescriptionColorAnimationValue[ButtonStates::Out] = m_colVersionIncompatible; + m_colVersionAnimationValue[ButtonStates::Out] = m_colVersionIncompatible; + break; + } + } + + DoAnimations( true ); + } + } +#endif + + void Paint() OVERRIDE + { + int x, y, w, t; + GetBounds( x, y, w, t ); + + PaintButton(); + + // Save game icons are 180x100 + int imgW = (t * 180) / 100; + + if ( m_Image.IsValid() ) + { + vgui::surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + vgui::surface()->DrawSetTexture( m_Image ); + int imgH = t; + // Half pixel offset to avoid leaking into pink + black + if ( m_bUseTGAImage ) + { + const float flHalfPixelX = ( 0.5f / 180.0f ); + const float flHalfPixelY = ( 0.5f / 100.0f ); + vgui::surface()->DrawTexturedSubRect( 0, 0, imgW, imgH, 0.0f, 0.0f, 1.0f - flHalfPixelX, 1.0f - flHalfPixelY ); + } + else + { + vgui::surface()->DrawTexturedRect( 0, 0, imgW, imgH ); + } + vgui::surface()->DrawSetTexture( 0 ); + } + else + { + vgui::surface()->DrawSetColor( Color( 0, 0, 0, 255 ) ); + imgW = ( t * 180 ) / 100; + int imgH = t; + vgui::surface()->DrawFilledRect( 0, 0, imgW, imgH ); + } + + PaintText(); + +#ifdef GAMEPADUI_GAME_EZ2 + if ( m_pSaveGame->iTimestamp != NEW_SAVE_GAME_TIMESTAMP ) + { + const wchar_t *pwszEZ2Version = m_strEZ2Version.String(); + int nEZ2VerLen = m_strEZ2Version.Length(); + if (m_strEZ2Version.IsEmpty()) + { + // Display a question mark for unknown versions + pwszEZ2Version = L"?"; + nEZ2VerLen = 1; + } + + int nTextW, nTextH; + vgui::surface()->GetTextSize( m_hDescriptionFont, pwszEZ2Version, nTextW, nTextH ); + + int nTextX = m_flWidth - m_flTextOffsetX - nTextW + imgW; + int nTextY = m_flHeight + m_flTextOffsetY - nTextH; + + vgui::surface()->DrawSetTextFont( m_hTextFont ); + vgui::surface()->DrawSetTextPos( nTextX, nTextY ); + vgui::surface()->DrawSetTextColor( m_colVersion ); + vgui::surface()->DrawPrintText( pwszEZ2Version, nEZ2VerLen ); + } + + if ( m_pSaveGame->bWilson && gamepadui_savegame_wilson_thumb.GetBool() ) + { + float flSize, flOffsetX, flOffsetY; + vgui::surface()->DrawSetColor( m_colDescriptionColor ); + vgui::surface()->DrawSetTexture( static_cast<GamepadUISaveGamePanel*>(GetParent())->GetWilsonThumb( flSize, flOffsetX, flOffsetY ) ); + //vgui::surface()->DrawTexturedSubRect( m_flWidth - flOffsetX - flSize, flOffsetY, m_flWidth - flOffsetX, flOffsetY + flSize, 0.28125f, 0.1875f, 0.703125f, 0.703125f ); + vgui::surface()->DrawTexturedSubRect( flOffsetX, flOffsetY, flOffsetX + flSize, flOffsetY + flSize, 0.28125f, 0.1875f, 0.703125f, 0.703125f ); + vgui::surface()->DrawSetTexture( 0 ); + } +#endif + } + + const SaveGameDescription_t* GetSaveGame() const + { + return m_pSaveGame; + } + +#ifdef GAMEPADUI_GAME_EZ2 + int GetSaveSuspectLevel() const + { + return m_iSaveSuspectLevel; + } + + const char *GetIncompatibleVersion() const + { + return m_pIncompatibleVersion; + } + + const char *GetLastCompatibleBranch() const + { + return m_pLastCompatibleBranch; + } +#endif + +private: + bool m_bUseTGAImage = false; + GamepadUIImage m_Image; + const SaveGameDescription_t *m_pSaveGame; +#ifdef GAMEPADUI_GAME_EZ2 + GamepadUIString m_strEZ2Version; + + int m_iSaveSuspectLevel; + const char *m_pIncompatibleVersion = NULL; + const char *m_pLastCompatibleBranch = NULL; + + GAMEPADUI_BUTTON_ANIMATED_PROPERTY( Color, m_colVersion, "Button.Version", "255 255 255 255", SchemeValueTypes::Color ); + GAMEPADUI_PANEL_PROPERTY( Color, m_colVersionMismatch, "Button.Version.Mismatch", "255 224 0 255", SchemeValueTypes::Color ); + GAMEPADUI_PANEL_PROPERTY( Color, m_colVersionIncompatible, "Button.Version.Incompatible", "255 128 0 255", SchemeValueTypes::Color ); +#endif +}; + +GamepadUISaveGamePanel::GamepadUISaveGamePanel( vgui::Panel* pParent, const char* pPanelName, bool bIsSave ) + : BaseClass( pParent, pPanelName ) + , m_bIsSave( bIsSave ) +{ + vgui::HScheme Scheme = vgui::scheme()->LoadSchemeFromFileEx( GamepadUI::GetInstance().GetSizingVPanel(), GAMEPADUI_DEFAULT_PANEL_SCHEME, "SchemePanel" ); + SetScheme( Scheme ); + + GetFrameTitle() = GamepadUIString(m_bIsSave ? "#GameUI_SaveGame" : "#GameUI_LoadGame"); + + Activate(); + +#ifdef GAMEPADUI_GAME_EZ2 + m_WilsonThumb.SetImage( "vgui/icons/icon_wilson" ); + + LoadVersionHistory(); +#endif + ScanSavedGames(); + + if ( m_pSavePanels.Count() ) + m_pSavePanels[0]->NavigateTo(); + + UpdateGradients(); +} + +GamepadUISaveGamePanel::~GamepadUISaveGamePanel() +{ +#ifdef GAMEPADUI_GAME_EZ2 + UnloadVersionHistory(); +#endif +} + +void GamepadUISaveGamePanel::UpdateGradients() +{ + const float flTime = GamepadUI::GetInstance().GetTime(); + GamepadUI::GetInstance().GetGradientHelper()->ResetTargets( flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Up, { 1.0f, 1.0f }, flTime ); + GamepadUI::GetInstance().GetGradientHelper()->SetTargetGradient( GradientSide::Down, { 1.0f, 1.0f }, flTime ); +} + +void GamepadUISaveGamePanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_bFooterButtonsStack = true; + + float flX, flY; + if (GamepadUI::GetInstance().GetScreenRatio( flX, flY )) + { + m_flSavesOffsetX *= (flX); + } + + int nX, nY; + GamepadUI::GetInstance().GetSizingPanelOffset( nX, nY ); + if (nX > 0) + { + GamepadUI::GetInstance().GetSizingPanelScale( flX, flY ); + flX *= 0.4f; + + m_flSavesOffsetX += ((float)nX) * flX; + m_flSavesFade += ((float)nX) * flX; + } + + if ( m_pScrollBar ) + { + m_pScrollBar->InitScrollBar( &m_ScrollState, m_flSavesOffsetX + m_pSavePanels[0]->GetWide() + m_flSavesSpacing, m_flSavesOffsetY ); + } +} + +void GamepadUISaveGamePanel::OnThink() +{ + BaseClass::OnThink(); + + LayoutSaveButtons(); +} + +void GamepadUISaveGamePanel::Paint() +{ + BaseClass::Paint(); + + if ( !m_strNoSaveString.IsEmpty() ) + { + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + vgui::surface()->DrawSetTextFont( m_hGenericFont ); + vgui::surface()->DrawSetTextColor( Color( 255, 255, 255, 255 ) ); + int nTextW, nTextH; + vgui::surface()->GetTextSize( m_hGenericFont, m_strNoSaveString.String(), nTextW, nTextH ); + vgui::surface()->DrawSetTextPos( nParentW / 2 - nTextW / 2, m_flSavesOffsetY + nTextH ); + vgui::surface()->DrawPrintText( m_strNoSaveString.String(), m_strNoSaveString.Length() ); + } +} + +/* Mostly from GameUI */ +void GamepadUISaveGamePanel::ScanSavedGames() +{ + m_Saves.Purge(); + m_pSavePanels.PurgeAndDeleteElements(); + m_pDeletePanels.PurgeAndDeleteElements(); + + if ( m_bIsSave ) + m_Saves.AddToTail( SaveGameDescription_t{ "NewSavedGame", "", "", "#GameUI_NewSaveGame", "", "", "Current", NEW_SAVE_GAME_TIMESTAMP } ); + + // populate list box with all saved games on record: + char szDirectory[_MAX_PATH]; + Q_snprintf( szDirectory, sizeof( szDirectory ), "save/*.sav" ); + + // iterate the saved files + FileFindHandle_t handle; + const char* pFileName = g_pFullFileSystem->FindFirst( szDirectory, &handle ); + while ( pFileName ) + { + if ( !Q_strnicmp( pFileName, "HLSave", strlen( "HLSave" ) ) ) + { + pFileName = g_pFullFileSystem->FindNext( handle ); + continue; + } + + char szFileName[_MAX_PATH]; + Q_snprintf( szFileName, sizeof( szFileName ), "save/%s", pFileName ); + + // Only load save games from the current mod's save dir + if ( !g_pFullFileSystem->FileExists( szFileName, "MOD" ) ) + { + pFileName = g_pFullFileSystem->FindNext( handle ); + continue; + } + + SaveGameDescription_t save; + if ( ParseSaveData( szFileName, pFileName, save ) ) + { + m_Saves.AddToTail( save ); + } + + pFileName = g_pFullFileSystem->FindNext( handle ); + } + + g_pFullFileSystem->FindClose( handle ); + + // sort the save list + qsort( m_Saves.Base(), m_Saves.Count(), sizeof( SaveGameDescription_t ), []( const void* lhs, const void* rhs ) { + const SaveGameDescription_t *s1 = ( const SaveGameDescription_t * )lhs; + const SaveGameDescription_t *s2 = ( const SaveGameDescription_t * )rhs; + + if ( s1->iTimestamp < s2->iTimestamp ) + return 1; + else if ( s1->iTimestamp > s2->iTimestamp ) + return -1; + + // timestamps are equal, so just sort by filename + return strcmp( s1->szFileName, s2->szFileName ); + } ); + + // add to the list + for ( int saveIndex = 0; saveIndex < m_Saves.Count() && saveIndex < MAX_LISTED_SAVE_GAMES; saveIndex++ ) + { + GamepadUISaveButton *button = new GamepadUISaveButton( this, this, GAMEPADUI_RESOURCE_FOLDER "schemesavebutton.res", "load_save", &m_Saves[saveIndex] ); + button->SetPriority( saveIndex ); + button->SetForwardToParent( true ); + m_pSavePanels.AddToTail( button ); + } + + // display a message if there are no save games + if ( !m_Saves.Count() ) + { + m_strNoSaveString = GamepadUIString( "#GameUI_NoSaveGamesToDisplay" ); + SetFooterButtons( FooterButtons::Back ); + } + else + { + if ( m_bIsSave ) + { + SetFooterButtons( FooterButtons::Back | FooterButtons::Select, FooterButtons::Select ); + } + else + { + SetFooterButtons( FooterButtons::Back | FooterButtons::Delete | FooterButtons::Select, FooterButtons::Select ); + } + } + + SetControlEnabled( "loadsave", false ); + SetControlEnabled( "delete", false ); + + if ( m_pSavePanels.Count() ) + { + if ( !m_pScrollBar ) + { + m_pScrollBar = new GamepadUIScrollBar( + this, this, + GAMEPADUI_RESOURCE_FOLDER "schemescrollbar.res", + NULL, false ); + + m_pScrollBar->SetNavLeft( m_pSavePanels[0] ); + } + } + else if ( m_pScrollBar ) + { + m_pScrollBar->MarkForDeletion(); + m_pScrollBar = NULL; + } + + for ( int i = 1; i < m_pSavePanels.Count(); i++ ) + { + m_pSavePanels[i]->SetNavUp( m_pSavePanels[i - 1] ); + m_pSavePanels[i - 1]->SetNavDown( m_pSavePanels[i] ); + } +} + +bool GamepadUISaveGamePanel::ParseSaveData( char const* pFileName, char const* pShortName, SaveGameDescription_t& save ) +{ + char szMapName[SAVEGAME_MAPNAME_LEN]; + char szComment[SAVEGAME_COMMENT_LEN]; + char szElapsedTime[SAVEGAME_ELAPSED_LEN]; + + if ( !pFileName || !pShortName ) + return false; + + Q_strncpy( save.szShortName, pShortName, sizeof( save.szShortName ) ); + Q_strncpy( save.szFileName, pFileName, sizeof( save.szFileName ) ); + + FileHandle_t fh = g_pFullFileSystem->Open( pFileName, "rb", "MOD" ); + if ( fh == FILESYSTEM_INVALID_HANDLE ) + return false; + + int readok = SaveReadNameAndComment( fh, szMapName, ARRAYSIZE( szMapName ), szComment, ARRAYSIZE( szComment ) ); + g_pFullFileSystem->Close( fh ); + + if ( !readok ) + { + return false; + } + +#ifdef GAMEPADUI_GAME_EZ2 + char szEZ2Version[8]; + char szPlatform[16]; + int nMapVersion = 0; + bool bDeck = false; + bool bWilson = false; + SaveReadCustomMetadata( pFileName, szEZ2Version, sizeof(szEZ2Version), szPlatform, sizeof(szPlatform), nMapVersion, bDeck, bWilson ); +#endif + + Q_strncpy( save.szMapName, szMapName, sizeof( save.szMapName ) ); + + // Elapsed time is the last 6 characters in comment. ( mmm:ss ) + int i; + i = strlen( szComment ); + Q_strncpy( szElapsedTime, "??", sizeof( szElapsedTime ) ); + if ( i >= 6 ) + { + Q_strncpy( szElapsedTime, ( char* )&szComment[i - 6], 7 ); + szElapsedTime[6] = '\0'; + + // parse out + int minutes = atoi( szElapsedTime ); + int seconds = atoi( szElapsedTime + 4 ); + + // reformat + if ( minutes ) + { + Q_snprintf( szElapsedTime, sizeof( szElapsedTime ), "%d %s %d seconds", minutes, minutes > 1 ? "minutes" : "minute", seconds ); + } + else + { + Q_snprintf( szElapsedTime, sizeof( szElapsedTime ), "%d seconds", seconds ); + } + + // Chop elapsed out of comment. + int n; + + n = i - 6; + szComment[n] = '\0'; + + n--; + + // Strip back the spaces at the end. + while ( ( n >= 1 ) && + szComment[n] && + szComment[n] == ' ' ) + { + szComment[n--] = '\0'; + } + } + + // calculate the file name to print + const char* pszType = ""; + if ( strstr( pFileName, "quick" ) ) + { + pszType = "#GameUI_QuickSave"; + } + else if ( strstr( pFileName, "autosave" ) ) + { + pszType = "#GameUI_AutoSave"; + } + + Q_strncpy( save.szType, pszType, sizeof( save.szType ) ); + Q_strncpy( save.szComment, szComment, sizeof( save.szComment ) ); + Q_strncpy( save.szElapsedTime, szElapsedTime, sizeof( save.szElapsedTime ) ); + + // Now get file time stamp. + long fileTime = g_pFullFileSystem->GetFileTime( pFileName ); + char szFileTime[32]; + g_pFullFileSystem->FileTimeToString( szFileTime, sizeof( szFileTime ), fileTime ); + char* newline = strstr( szFileTime, "\n" ); + if ( newline ) + { + *newline = 0; + } + Q_strncpy( save.szFileTime, szFileTime, sizeof( save.szFileTime ) ); + save.iTimestamp = fileTime; +#ifdef GAMEPADUI_GAME_EZ2 + Q_strncpy( save.szEZ2Version, szEZ2Version, sizeof( save.szEZ2Version ) ); + Q_strncpy( save.szPlatform, szPlatform, sizeof( save.szPlatform ) ); + save.nMapVersion = nMapVersion; + save.bDeck = bDeck; + save.bWilson = bWilson; +#endif + return true; +} + +int GamepadUISaveGamePanel::SaveReadNameAndComment( FileHandle_t f, OUT_Z_CAP( nameSize ) char* name, int nameSize, OUT_Z_CAP( commentSize ) char* comment, int commentSize ) +{ + int i, tag, size, tokenSize, tokenCount; + char* pSaveData, * pFieldName, ** pTokenList; + + name[0] = '\0'; + comment[0] = '\0'; + + g_pFullFileSystem->Read( &tag, sizeof( int ), f ); + if ( tag != MAKEID( 'J', 'S', 'A', 'V' ) ) + { + return 0; + } + + g_pFullFileSystem->Read( &tag, sizeof( int ), f ); + if ( tag != SAVEGAME_VERSION ) // Enforce version for now + { + return 0; + } + + g_pFullFileSystem->Read( &size, sizeof( int ), f ); + + g_pFullFileSystem->Read( &tokenCount, sizeof( int ), f ); // These two ints are the token list + g_pFullFileSystem->Read( &tokenSize, sizeof( int ), f ); + size += tokenSize; + + // Sanity Check. + if ( tokenCount < 0 || tokenCount > 1024 * 1024 * 32 ) + { + return 0; + } + + if ( tokenSize < 0 || tokenSize > 1024 * 1024 * 32 ) + { + return 0; + } + + pSaveData = ( char* )new char[size]; + g_pFullFileSystem->Read( pSaveData, size, f ); + + int nNumberOfFields; + + char* pData; + int nFieldSize; + + pData = pSaveData; + + // Allocate a table for the strings, and parse the table + if ( tokenSize > 0 ) + { + pTokenList = new char* [tokenCount]; + + // Make sure the token strings pointed to by the pToken hashtable. + for ( i = 0; i < tokenCount; i++ ) + { + pTokenList[i] = *pData ? pData : NULL; // Point to each string in the pToken table + while ( *pData++ ); // Find next token ( after next null ) + } + } + else + pTokenList = NULL; + + // short, short ( size, index of field name ) + nFieldSize = *( short* )pData; + pData += sizeof( short ); + pFieldName = pTokenList[*( short* )pData]; + + if ( stricmp( pFieldName, "GameHeader" ) ) + { + delete[] pSaveData; + return 0; + }; + + // int ( fieldcount ) + pData += sizeof( short ); + nNumberOfFields = *( int* )pData; + pData += nFieldSize; + + // Each field is a short ( size ), short ( index of name ), binary string of "size" bytes ( data ) + for ( i = 0; i < nNumberOfFields; i++ ) + { + // Data order is: + // Size + // szName + // Actual Data + + nFieldSize = *( short* )pData; + pData += sizeof( short ); + + pFieldName = pTokenList[*( short* )pData]; + pData += sizeof( short ); + + if ( !stricmp( pFieldName, "comment" ) ) + { + int copySize = MAX( commentSize, nFieldSize ); + Q_strncpy( comment, pData, copySize ); + } + else if ( !stricmp( pFieldName, "mapName" ) ) + { + int copySize = MAX( nameSize, nFieldSize ); + Q_strncpy( name, pData, copySize ); + }; + + // Move to Start of next field. + pData += nFieldSize; + }; + + // Delete the string table we allocated + delete[] pTokenList; + delete[] pSaveData; + + if ( strlen( name ) > 0 && strlen( comment ) > 0 ) + return 1; + + return 0; +} + +#ifdef GAMEPADUI_GAME_EZ2 +int GamepadUISaveGamePanel::SaveReadCustomMetadata( const char *pSaveName, char *ez2version, int ez2versionSize, char *platform, int platformSize, int &nMapVersion, bool &bDeck, bool &bWilson ) +{ + char name[MAX_PATH]; + Q_strncpy( name, pSaveName, sizeof( name ) ); + Q_SetExtension( name, ".txt", sizeof( name ) ); + + KeyValues *pCustomSaveMetadata = new KeyValues( "CustomSaveMetadata" ); + if (pCustomSaveMetadata->LoadFromFile( g_pFullFileSystem, name, "MOD" )) + { + Q_strncpy( ez2version, pCustomSaveMetadata->GetString( "ez2_version" ), ez2versionSize ); + Q_strncpy( platform, pCustomSaveMetadata->GetString( "platform" ), platformSize ); + nMapVersion = pCustomSaveMetadata->GetInt( "mapversion" ); + bDeck = pCustomSaveMetadata->GetBool( "is_deck" ); + bWilson = pCustomSaveMetadata->GetBool( "wilson" ); + + pCustomSaveMetadata->deleteThis(); + return 1; + } + + pCustomSaveMetadata->deleteThis(); + return 0; +} + +// 0 = equal, negative = version 1 greater, positive = version 2 greater +// +// Actual return number is based on which version place is greater/less +// For example: +// - '1' would mean version 1 is a major version greater than version 2 +// - '-2' would mean version 2 is a minor version greater than version 1 +static int CompareVersions( const char *pszVersion1, const char *pszVersion2 ) +{ + if (!(pszVersion1 || *pszVersion1)) + return 1; + if (!(pszVersion2 || *pszVersion2)) + return -1; + + // If the first character isn't a number, it's not a valid version + if (pszVersion1[0] < '0' || pszVersion1[0] > '9') + return 1; + if (pszVersion2[0] < '0' || pszVersion2[0] > '9') + return -1; + + CUtlStringList szVersionNums1; + V_SplitString( pszVersion1, ".", szVersionNums1 ); + + CUtlStringList szVersionNums2; + V_SplitString( pszVersion2, ".", szVersionNums2 ); + + Assert( szVersionNums1.Count() >= szVersionNums2.Count() ); + + int nReturn = 0; + for (int i = 0; i < szVersionNums1.Count(); i++) + { + int nV1 = atoi( szVersionNums1[i] ); + int nV2 = atoi( szVersionNums2[i] ); + if (nV1 > nV2) + { + nReturn = -(i+1); + break; + } + else if (nV1 < nV2) + { + nReturn = i+1; + break; + } + } + + szVersionNums1.PurgeAndDeleteElements(); + szVersionNums2.PurgeAndDeleteElements(); + + return nReturn; +} + +void GamepadUISaveGamePanel::LoadVersionHistory() +{ + m_pVersionHistory = new KeyValues( "VersionHistory" ); + m_pVersionHistory->LoadFromFile( g_pFullFileSystem, "scripts/ez2_version_history.txt", "MOD" ); +} + +void GamepadUISaveGamePanel::UnloadVersionHistory() +{ + m_pVersionHistory->deleteThis(); +} + +int GamepadUISaveGamePanel::IsSaveSuspect( const char *pszEZ2Version, const char *pszMapName, int nMapVersion, const char **ppszIncompatibleVersion, const char **ppszLastCompatibleBranch ) +{ + if (!(pszEZ2Version && *pszEZ2Version)) + { + // Placeholder version for updates which predate this system + pszEZ2Version = "0.0.0"; + *ppszLastCompatibleBranch = "release-1.5"; + } + + if (m_pVersionHistory) + { + bool bNewerVersionExists = false; + KeyValues *pVersionKey = m_pVersionHistory->GetFirstSubKey(); + while (pVersionKey) + { + int iVerCompare = CompareVersions( pVersionKey->GetName(), pszEZ2Version ); + if (iVerCompare <= -1) + { + // Newer version of E:Z2 than the save game's + + // Each major/minor version + bNewerVersionExists = (iVerCompare >= -2); + + // Check if this version breaks all previous versions + if (pVersionKey->GetBool( "universal" )) + { + *ppszIncompatibleVersion = pVersionKey->GetName(); + return SaveSuspectLevel_Incompatible; + } + + // Check for incompatible maps + KeyValues *pMaps = pVersionKey->FindKey( "maps" ); + if (pMaps) + { + KeyValues *pMapKey = pMaps->FindKey( pszMapName ); + if (pMapKey) + { + *ppszIncompatibleVersion = pVersionKey->GetName(); + return SaveSuspectLevel_Incompatible; + } + } + } + else if (iVerCompare == 0) + { + // Matching version of E:Z2 + *ppszLastCompatibleBranch = pVersionKey->GetString( "branch", *ppszLastCompatibleBranch ); + } + + pVersionKey = pVersionKey->GetNextKey(); + } + + if (bNewerVersionExists) + return SaveSuspectLevel_Mismatch; + } + + return SaveSuspectLevel_None; +} +#endif + +void GamepadUISaveGamePanel::FindSaveSlot( OUT_Z_CAP( bufsize ) char* buffer, int bufsize ) +{ + buffer[0] = 0; + char szFileName[_MAX_PATH]; + for ( int i = 0; i < 1000; i++ ) + { + Q_snprintf( szFileName, sizeof( szFileName ), "save/half-life-%03i.sav", i ); + + FileHandle_t fp = g_pFullFileSystem->Open( szFileName, "rb" ); + if ( !fp ) + { + // clean up name + Q_strncpy( buffer, szFileName + 5, bufsize ); + char* ext = strstr( buffer, ".sav" ); + if ( ext ) + { + *ext = 0; + } + return; + } + g_pFullFileSystem->Close( fp ); + } + + AssertMsg( false, "Could not generate new save game file" ); +} + +void GamepadUISaveGamePanel::DeleteSaveGame( const char* pFileName ) +{ + if ( !pFileName || !pFileName[0] ) + return; + + // delete the save game file + g_pFullFileSystem->RemoveFile( pFileName, "MOD" ); + + // delete the associated tga + char tga[_MAX_PATH]; + Q_strncpy( tga, pFileName, sizeof( tga ) ); + char* ext = strstr( tga, ".sav" ); + if ( ext ) + { + strcpy( ext, ".tga" ); + } + g_pFullFileSystem->RemoveFile( tga, "MOD" ); + +#ifdef GAMEPADUI_GAME_EZ2 + // delete the associated txt + Q_SetExtension( tga, ".txt", sizeof( tga ) ); + g_pFullFileSystem->RemoveFile( tga, "MOD" ); +#endif +} +/* End Mostly from GameUI */ + +void GamepadUISaveGamePanel::OnGamepadUIButtonNavigatedTo( vgui::VPANEL button ) +{ + GamepadUIButton *pButton = dynamic_cast< GamepadUIButton * >( vgui::ipanel()->GetPanel( button, GetModuleName() ) ); + if ( pButton ) + { + if ( pButton->GetAlpha() != 255 ) + { + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + int nX, nY; + pButton->GetPos( nX, nY ); + + int nTargetY = pButton->GetPriority() * ( pButton->m_flHeightAnimationValue[ButtonStates::Out] + m_flSavesSpacing ); + + if ( nY < nParentH / 2 ) + { + nTargetY += nParentH - m_flSavesOffsetY; + // Add a bit of spacing to make this more visually appealing :) + nTargetY -= m_flSavesSpacing; + } + else + { + nTargetY += pButton->m_flHeightAnimationValue[ButtonStates::Over]; + // Add a bit of spacing to make this more visually appealing :) + nTargetY += (pButton->m_flHeightAnimationValue[ButtonStates::Over] / 2) + m_flSavesSpacing; + } + + + m_ScrollState.SetScrollTarget( nTargetY - ( nParentH - m_flSavesOffsetY), GamepadUI::GetInstance().GetTime() ); + } + } +} + +void GamepadUISaveGamePanel::LayoutSaveButtons() +{ + int nParentW, nParentH; + GetParent()->GetSize( nParentW, nParentH ); + + float scrollClamp = 0.0f; + for ( int i = 0; i < ( int )m_pSavePanels.Count(); i++ ) + { + int size = ( m_pSavePanels[i]->GetTall() + m_flSavesSpacing ); + + if ( i < ( ( int )m_pSavePanels.Count() ) - 3 ) + scrollClamp += size; + } + + m_ScrollState.UpdateScrollBounds( 0.0f, scrollClamp ); + + if (m_pSavePanels.Count() > 0) + { + m_pScrollBar->UpdateScrollBounds( 0.0f, scrollClamp, + ((m_pSavePanels[0]->GetTall() + m_flSavesSpacing) * 3), nParentH - m_flFooterButtonsOffsetY - m_nFooterButtonHeight - m_flSavesOffsetY ); + } + + int previousSizes = 0; + for ( int i = 0; i < ( int )m_pSavePanels.Count(); i++ ) + { + int tall = m_pSavePanels[i]->GetTall(); + int size = ( tall + m_flSavesSpacing ); + + int y = m_flSavesOffsetY + previousSizes - m_ScrollState.GetScrollProgress(); + int fade = 255; + if ( y < m_flSavesOffsetY ) + fade = ( 1.0f - clamp( -( y - m_flSavesOffsetY ) / m_flSavesFade, 0.0f, 1.0f ) ) * 255.0f; + if ( y > nParentH - m_flSavesFade ) + fade = ( 1.0f - clamp( ( y - ( nParentH - m_flSavesFade - size ) ) / m_flSavesFade, 0.0f, 1.0f ) ) * 255.0f; + if ( m_pSavePanels[i]->HasFocus() && fade != 0 ) + fade = 255; + m_pSavePanels[i]->SetAlpha( fade ); + m_pSavePanels[i]->SetPos( m_flSavesOffsetX, y ); + m_pSavePanels[i]->SetVisible( true ); + previousSizes += size; + + if (m_pDeletePanels.Count() > i) + { + m_pDeletePanels[i]->SetPos( m_flSavesOffsetX - m_pDeletePanels[i]->GetWide() - m_flSavesSpacing, y ); + m_pDeletePanels[i]->SetAlpha( fade ); + m_pDeletePanels[i]->SetVisible( true ); + } + } + + m_ScrollState.UpdateScrolling( 2.0f, GamepadUI::GetInstance().GetTime() ); +} + +void GamepadUISaveGamePanel::LoadGame( const SaveGameDescription_t* pSave ) +{ + const char* shortName = pSave->szShortName; + if ( shortName && shortName[0] ) + { + // Load the game, return to top and switch to engine + char sz[256]; + Q_snprintf( sz, sizeof( sz ), "progress_enable\nload %s\n", shortName ); + + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( sz ); + + // Close this dialog + Close(); + } +} + +void GamepadUISaveGamePanel::SaveGame( const SaveGameDescription_t* pSave ) +{ + // delete any existing save + DeleteSaveGame( pSave->szFileName ); + + // save to a new name + char saveName[128]; + FindSaveSlot( saveName, sizeof( saveName ) ); + if ( saveName && saveName[0] ) + { + // Load the game, return to top and switch to engine + char sz[256]; + Q_snprintf( sz, sizeof( sz ), "save %s\n", saveName ); + + GamepadUI::GetInstance().GetEngineClient()->ClientCmd_Unrestricted( sz ); + Close(); + GamepadUI::GetInstance().GetGameUI()->SendMainMenuCommand( "resumegame" ); + } +} + +void GamepadUISaveGamePanel::OnCommand( char const* pCommand ) +{ + if ( !V_strcmp( pCommand, "action_back" ) ) + { + Close(); + } + else if ( !V_strcmp( pCommand, "action_delete_mode_button" ) ) + { + for ( auto& panel : m_pDeletePanels ) + { + if ( panel->HasFocus() ) + { + new GamepadUIGenericConfirmationPanel( this, "SaveDeleteConfirmationPanel", "#GameUI_ConfirmDeleteSaveGame_Title", "#GameUI_ConfirmDeleteSaveGame_Info", + [this, panel]() + { + DeleteSaveGame( panel->GetName() ); + ScanSavedGames(); + } ); + break; + } + } + } + else if ( !V_strcmp( pCommand, "action_delete" ) ) + { +#ifdef STEAM_INPUT + const bool bController = GamepadUI::GetInstance().GetSteamInput()->IsEnabled(); +#elif defined(HL2_RETAIL) // Steam input and Steam Controller are not supported in SDK2013 (Madi) + const bool bController = g_pInputSystem->IsSteamControllerActive(); +#else + const bool bController = ( g_pInputSystem->GetJoystickCount() >= 1 ); +#endif + if (InDeleteMode()) + { + m_pDeletePanels.PurgeAndDeleteElements(); + } + else if (!bController && gamepadui_savegame_use_delete_mode.GetBool()) + { + // Add delete panels + for (int i = 0; i < m_pSavePanels.Count(); i++) + { + GamepadUIButton *button = new GamepadUIButton( this, this, GAMEPADUI_RESOURCE_FOLDER "schemedeletesavebutton.res", "action_delete_mode_button", "X", ""); + button->SetName( m_pSavePanels[i]->GetSaveGame()->szFileName ); + button->SetPriority( m_pSavePanels[i]->GetPriority() ); + button->SetForwardToParent( true ); + m_pDeletePanels.AddToTail( button ); + } + } + else + { + // Delete directly if using a controller + for ( auto& panel : m_pSavePanels ) + { + if ( panel->HasFocus() ) + { + new GamepadUIGenericConfirmationPanel( this, "SaveDeleteConfirmationPanel", "#GameUI_ConfirmDeleteSaveGame_Title", "#GameUI_ConfirmDeleteSaveGame_Info", + [this, panel]() + { + int i = m_pSavePanels.Find( panel ); + DeleteSaveGame( panel->GetSaveGame()->szFileName ); + ScanSavedGames(); + + if ( i > 0 && m_pSavePanels.Count() >= i ) + m_pSavePanels[i-1]->NavigateTo(); + else if ( m_pSavePanels.Count() ) + m_pSavePanels[0]->NavigateTo(); + } ); + break; + } + } + } + } + else if ( !V_strcmp( pCommand, "load_save" ) ) + { + for ( auto& panel : m_pSavePanels ) + { + if ( panel->HasFocus() ) + { + auto* pSave = panel->GetSaveGame(); + if ( m_bIsSave ) + { + if ( panel->GetSaveGame()->iTimestamp != NEW_SAVE_GAME_TIMESTAMP ) + { + new GamepadUIGenericConfirmationPanel( this, "SaveOverwriteConfirmationPanel", "#GameUI_ConfirmOverwriteSaveGame_Title", "#GameUI_ConfirmOverwriteSaveGame_Info", + [this, pSave]() + { + SaveGame( pSave ); + } ); + } + else + SaveGame( pSave ); + } + else + { +#ifdef GAMEPADUI_GAME_EZ2 + if (panel->GetSaveSuspectLevel() != SaveSuspectLevel_None) + { + wchar_t wszLastCompatibleBranch[ 16 ]; + V_UTF8ToUnicode( panel->GetLastCompatibleBranch(), wszLastCompatibleBranch, sizeof( wszLastCompatibleBranch ) ); + + const char *pszFrameTitle = NULL; + wchar_t buf[ 512 ]; + switch (panel->GetSaveSuspectLevel()) + { + case SaveSuspectLevel_Mismatch: + g_pVGuiLocalize->ConstructString( buf, sizeof( buf ), g_pVGuiLocalize->Find( "#GameUI_DifferentVersion_Info" ), 1, wszLastCompatibleBranch ); + pszFrameTitle = "#GameUI_DifferentVersion_Title"; + break; + case SaveSuspectLevel_Incompatible: + g_pVGuiLocalize->ConstructString( buf, sizeof( buf ), g_pVGuiLocalize->Find( "#GameUI_IncompatibleVersion_Info" ), 1, wszLastCompatibleBranch ); + pszFrameTitle = "#GameUI_IncompatibleVersion_Title"; + break; + } + + new GamepadUIGenericConfirmationPanel( this, "IncompatibleVersionConfirmationPanel", g_pVGuiLocalize->Find( pszFrameTitle ), buf, + [this, pSave]() + { + LoadGame( pSave ); + }, true ); + } + else +#endif + LoadGame( pSave ); + } + break; + } + } + } + else + { + BaseClass::OnCommand( pCommand ); + } +} + +void GamepadUISaveGamePanel::OnMouseWheeled( int delta ) +{ + m_ScrollState.OnMouseWheeled( delta * 200.0f, GamepadUI::GetInstance().GetTime() ); +} + +CON_COMMAND( gamepadui_opensavegamedialog, "" ) +{ + new GamepadUISaveGamePanel( GamepadUI::GetInstance().GetBasePanel(), "", true ); +} + +CON_COMMAND( gamepadui_openloadgamedialog, "" ) +{ + new GamepadUISaveGamePanel( GamepadUI::GetInstance().GetBasePanel(), "", false ); +} diff --git a/game/gamepadui/gamepadui_scroll.h b/game/gamepadui/gamepadui_scroll.h new file mode 100644 index 00000000..9de7100b --- /dev/null +++ b/game/gamepadui/gamepadui_scroll.h @@ -0,0 +1,55 @@ +#ifndef GAMEPADUI_SCROLL_H +#define GAMEPADUI_SCROLL_H +#ifdef _WIN32 +#pragma once +#endif + +class GamepadUIScrollState +{ +public: + GamepadUIScrollState() + { + } + + void UpdateScrollBounds( float flMin, float flMax ) + { + m_flScrollTarget = clamp( m_flScrollTarget, flMin, flMax ); + } + + void UpdateScrolling( float flScrollSpeed, float flTime ) + { + float flLerpV = Min( ( flTime - m_flScrollLastScrolledTime ) * flScrollSpeed, 1.0f ); + flLerpV *= 2.0f - flLerpV; + m_flScrollProgress = Lerp( flLerpV, m_flScrollLastScrolledValue, m_flScrollTarget ); + } + + float GetScrollProgress() + { + return m_flScrollProgress; + } + + float GetScrollTarget() const + { + return m_flScrollTarget; + } + + void SetScrollTarget( float flScrollTarget, float flTime ) + { + m_flScrollTarget = flScrollTarget; + m_flScrollLastScrolledValue = m_flScrollProgress; + m_flScrollLastScrolledTime = flTime; + } + + void OnMouseWheeled( int nDelta, float flTime ) + { + SetScrollTarget( GetScrollTarget() - nDelta, flTime ); + } + +private: + float m_flScrollTarget = 0.0f; + float m_flScrollProgress = 0.0f; + float m_flScrollLastScrolledValue = 0.0f; + float m_flScrollLastScrolledTime = 0.0f; +}; + +#endif // GAMEPADUI_SCROLL_H diff --git a/game/gamepadui/gamepadui_scrollbar.cpp b/game/gamepadui/gamepadui_scrollbar.cpp new file mode 100644 index 00000000..2dc9e445 --- /dev/null +++ b/game/gamepadui/gamepadui_scrollbar.cpp @@ -0,0 +1,160 @@ +#include "gamepadui_scrollbar.h" +#include "vgui/IInput.h" + +void GamepadUIScrollBar::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + GetPos( m_nStartX, m_nStartY ); + + if (m_bHorizontal) + { + m_flScrollSize = m_flWidth; + } + else + { + m_flScrollSize = m_flHeight; + } +} + +void GamepadUIScrollBar::OnThink() +{ + BaseClass::OnThink(); + + if (!m_pScrollState) + return; + + if (m_nMouseOffset != -1) + { + int nMouseX, nMouseY; + vgui::input()->GetCursorPos( nMouseX, nMouseY ); + + if (m_bHorizontal) + { + float flRatio = ((float)((nMouseX - m_nStartX) - m_nMouseOffset)) / m_flScrollSize; + + m_pScrollState->SetScrollTarget( flRatio * m_flMax, GamepadUI::GetInstance().GetTime() ); + } + else + { + float flRatio = ((float)((nMouseY - m_nStartY) - m_nMouseOffset)) / m_flScrollSize; + + m_pScrollState->SetScrollTarget( flRatio * m_flMax, GamepadUI::GetInstance().GetTime() ); + } + } + else if (m_flKeyDir != 0) + { + m_pScrollState->SetScrollTarget( m_pScrollState->GetScrollProgress() + m_flKeyDir, GamepadUI::GetInstance().GetTime() ); + } + + if (m_flMax > 0.0f) + { + if (m_bHorizontal) + { + int nX = m_nStartX + (m_flScrollSize * ((m_pScrollState->GetScrollProgress() - m_flMin) / m_flMax)); + SetPos( nX, m_nStartY ); + } + else + { + int nY = m_nStartY + (m_flScrollSize * ((m_pScrollState->GetScrollProgress() - m_flMin) / m_flMax)); + SetPos( m_nStartX, nY ); + } + } +} + +void GamepadUIScrollBar::InitScrollBar( GamepadUIScrollState *pScrollState, int nX, int nY ) +{ + m_pScrollState = pScrollState; + + m_nStartX = nX; + m_nStartY = nY; +} + +void GamepadUIScrollBar::UpdateScrollBounds( float flMin, float flMax, float flRegionSize, float flScrollSize ) +{ + if (flMin == 0.0f && flMax == 0.0f) + { + SetVisible( false ); + m_pScrollState = NULL; + return; + } + + SetVisible( true ); + + m_flMin = flMin; + m_flMax = flMax; + m_flRegionSize = flRegionSize; + + if (m_bHorizontal) + { + m_flWidth = flScrollSize * (m_flRegionSize / (m_flMax + m_flRegionSize)); + for (int i = 0; i < ButtonStates::Count; i++) + m_flWidthAnimationValue[i] = m_flWidth; + + m_flScrollSize = flScrollSize - m_flWidth; + } + else + { + m_flHeight = flScrollSize * (m_flRegionSize / (m_flMax + m_flRegionSize)); + for (int i = 0; i < ButtonStates::Count; i++) + m_flHeightAnimationValue[i] = m_flHeight; + + m_flScrollSize = flScrollSize - m_flHeight; + } +} + +void GamepadUIScrollBar::OnMousePressed( vgui::MouseCode code ) +{ + BaseClass::OnMousePressed( code ); + + int nX, nY; + GetPos( nX, nY ); + + int nMouseX, nMouseY; + vgui::input()->GetCursorPos( nMouseX, nMouseY ); + + m_nMouseOffset = (m_bHorizontal ? nMouseX - nX : nMouseY - nY); +} + +void GamepadUIScrollBar::OnMouseReleased( vgui::MouseCode code ) +{ + BaseClass::OnMouseReleased( code ); + + m_nMouseOffset = -1; +} + +void GamepadUIScrollBar::OnKeyCodePressed( vgui::KeyCode code ) +{ + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + + switch ( buttonCode ) + { + case KEY_UP: + case KEY_XBUTTON_UP: + m_flKeyDir = -m_flScrollSpeed; + break; + case KEY_DOWN: + case KEY_XBUTTON_DOWN: + m_flKeyDir = m_flScrollSpeed; + break; + default: + return BaseClass::OnKeyCodePressed( code ); + } +} + +void GamepadUIScrollBar::OnKeyCodeReleased( vgui::KeyCode code ) +{ + ButtonCode_t buttonCode = GetBaseButtonCode( code ); + + switch ( buttonCode ) + { + case KEY_UP: + case KEY_XBUTTON_UP: + case KEY_DOWN: + case KEY_XBUTTON_DOWN: + m_flKeyDir = 0; + break; + default: + return BaseClass::OnKeyCodeReleased( code ); + } +} diff --git a/game/gamepadui/gamepadui_scrollbar.h b/game/gamepadui/gamepadui_scrollbar.h new file mode 100644 index 00000000..918b11db --- /dev/null +++ b/game/gamepadui/gamepadui_scrollbar.h @@ -0,0 +1,53 @@ +#ifndef GAMEPADUI_SCROLLBAR_H +#define GAMEPADUI_SCROLLBAR_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gamepadui_button.h" +#include "gamepadui_scroll.h" + +class GamepadUIScrollBar : public GamepadUIButton +{ + DECLARE_CLASS_SIMPLE( GamepadUIScrollBar, GamepadUIButton ); +public: + GamepadUIScrollBar( vgui::Panel *pParent, vgui::Panel *pActionSignalTarget, const char *pSchemeFile, GamepadUIScrollState *pScrollState, bool bHorizontal ) + : BaseClass( pParent, pActionSignalTarget, pSchemeFile, "", "", "" ) + { + m_pScrollState = pScrollState; + m_bHorizontal = bHorizontal; + + SetVisible( false ); + } + + void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + void OnThink() OVERRIDE; + + void InitScrollBar( GamepadUIScrollState *pScrollState, int nX, int nY ); + void UpdateScrollBounds( float flMin, float flMax, float flRegionSize, float flScrollSize ); + + void OnMousePressed( vgui::MouseCode code ) OVERRIDE; + void OnMouseReleased( vgui::MouseCode code ) OVERRIDE; + + void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE; + void OnKeyCodeReleased( vgui::KeyCode code ) OVERRIDE; + +private: + int m_nStartX = 0; + int m_nStartY = 0; + + float m_flScrollSize = 0.0f; + float m_flRegionSize = 0.0f; + float m_flMin = 0; + float m_flMax = 0; + GamepadUIScrollState *m_pScrollState = NULL; + + int m_nMouseOffset = -1; + int m_flKeyDir = 0; + + bool m_bHorizontal = false; + + GAMEPADUI_PANEL_PROPERTY( float, m_flScrollSpeed, "ScrollBar.Speed", "64", SchemeValueTypes::ProportionalFloat ); +}; + +#endif // GAMEPADUI_SCROLLBAR_H diff --git a/game/gamepadui/gamepadui_string.h b/game/gamepadui/gamepadui_string.h new file mode 100644 index 00000000..edd522a8 --- /dev/null +++ b/game/gamepadui/gamepadui_string.h @@ -0,0 +1,114 @@ +#ifndef GAMEPADUI_STRING_H +#define GAMEPADUI_STRING_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui/ILocalize.h" +#include "tier1/utlvector.h" + +extern vgui::ILocalize *g_pVGuiLocalize; + +class GamepadUIString +{ +public: + GamepadUIString() + { + } + + GamepadUIString( const char *pszText ) + { + SetText( pszText ); + } + + GamepadUIString( const wchar_t *pszText ) + { + SetText( pszText ); + } + + GamepadUIString( const wchar_t *pszText, int nLength ) + { + SetText( pszText, nLength ); + } + + const wchar_t *String() const + { + if ( m_ManagedText.Count() ) + return m_ManagedText.Base(); + + return L""; + } + + int Length() const + { + if ( m_ManagedText.Count() ) + return m_ManagedText.Count() - 1; + + return 0; + } + + bool IsEmpty() const + { + return Length() == 0; + } + + void SetText( const char *pszText ) + { + m_ManagedText.Purge(); + + if ( !pszText || !*pszText ) + return; + + const wchar_t *pszFoundText = g_pVGuiLocalize->Find( pszText ); + if ( !pszFoundText ) + { + SetRawUTF8( pszText ); + } + else + { + int nChars = V_wcslen( pszFoundText ); + SetText( pszFoundText, nChars ); + } + } + + void SetText( const wchar_t *pszText, int nLength ) + { + m_ManagedText.Purge(); + + if ( !pszText || !nLength ) + return; + + m_ManagedText.EnsureCapacity( nLength + 1 ); + for ( int i = 0; i < nLength; i++ ) + m_ManagedText.AddToTail( pszText[ i ] ); + m_ManagedText.AddToTail( L'\0' ); + } + + void SetText( const wchar_t *pszText ) + { + if ( !pszText ) + SetText( NULL, 0 ); + else + SetText( pszText, V_wcslen( pszText ) ); + } + + void SetRawUTF8( const char* pszText ) + { + m_ManagedText.Purge(); + + if ( !pszText || !*pszText ) + return; + + wchar_t szUnicode[ 4096 ]; + memset( szUnicode, 0, sizeof( wchar_t ) * 4096 ); + + V_UTF8ToUnicode( pszText, szUnicode, sizeof( szUnicode ) ); + int nChars = V_strlen(pszText); + if ( nChars > 1 ) + SetText( szUnicode, nChars - 1 ); + } +private: + CCopyableUtlVector< wchar_t > m_ManagedText; +}; + +#endif // GAMEPADUI_STRING_H diff --git a/game/gamepadui/gamepadui_util.cpp b/game/gamepadui/gamepadui_util.cpp new file mode 100644 index 00000000..f352b9b1 --- /dev/null +++ b/game/gamepadui/gamepadui_util.cpp @@ -0,0 +1,106 @@ +#include "gamepadui_util.h" +#include "gamepadui_interface.h" + +#include "tier0/icommandline.h" +#include "tier1/strtools.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Josh: Unused, but referenced by imageutils.cpp +// SDK2013: not necessary here (Madi) +#ifdef HL2_RETAIL +class IVEngineClient* engine = NULL; +#endif + +// Josh: Copied verbatim from basically every other module +// we have on this planet. +const char *COM_GetModDirectory() +{ + static char szModDir[ MAX_PATH ] = {}; + if ( V_strlen( szModDir ) == 0 ) + { + const char *pszGameDir = CommandLine()->ParmValue("-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) ); + V_strncpy( szModDir, pszGameDir, sizeof(szModDir) ); + if ( strchr( szModDir, '/' ) || strchr( szModDir, '\\' ) ) + { + V_StripLastDir( szModDir, sizeof(szModDir) ); + int nDirLen = V_strlen( szModDir ); + V_strncpy( szModDir, pszGameDir + nDirLen, sizeof(szModDir) - nDirLen ); + } + } + + return szModDir; +} + +int DrawPrintWrappedText(vgui::HFont font, int pX, int pY, const wchar_t* pszText, int nLength, int nMaxWidth, bool bDraw) +{ + float x = 0.0f; + int extraY = 0; + const int nFontTall = vgui::surface()->GetFontTall(font); + const wchar_t* wszStrStart = pszText; + const wchar_t* wszLastSpace = NULL; + const wchar_t* wszEnd = pszText + nLength; + + for (const wchar_t* wsz = pszText; *wsz; wsz++) + { + wchar_t ch = *wsz; + + if (ch == L' ' || ch == L'\n') + wszLastSpace = wsz; + +#if USE_GETKERNEDCHARWIDTH + wchar_t chBefore = 0; + wchar_t chAfter = 0; + if (wsz > pszText) + chBefore = wsz[-1]; + chAfter = wsz[1]; + float flWide = 0.0f, flabcA = 0.0f; + vgui::surface()->GetKernedCharWidth(font, ch, chBefore, chAfter, flWide, flabcA); + if (ch == L' ') + x = ceil(x); + + surface()->DrawSetTextPos(x + px, y + py); + surface()->DrawUnicodeChar(ch); + x += floor(flWide + 0.6); +#else + x += vgui::surface()->GetCharacterWidth(font, ch); +#endif + + if (x >= nMaxWidth || ch == L'\n') + { + const wchar_t* wszNewStart = wszLastSpace ? wszLastSpace : wsz; + if ( bDraw ) + { + vgui::surface()->DrawSetTextPos(pX, pY); + vgui::surface()->DrawPrintText(wszStrStart, (int)(intp)(wszNewStart - wszStrStart)); + } + wszStrStart = wszNewStart + 1; + wsz = wszStrStart; + if ( ch == L'\n' ) + wsz--; + x = 0; + pY += nFontTall; + extraY += nFontTall; + } + } + + if (wszStrStart != wszEnd && bDraw) + { + vgui::surface()->DrawSetTextPos(pX, pY); + vgui::surface()->DrawPrintText(wszStrStart, (int)(intp)(wszEnd - wszStrStart)); + } + + return extraY; +} + +int NextPowerOfTwo( int v ) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return ++v; +} diff --git a/game/gamepadui/gamepadui_util.h b/game/gamepadui/gamepadui_util.h new file mode 100644 index 00000000..13cb803c --- /dev/null +++ b/game/gamepadui/gamepadui_util.h @@ -0,0 +1,15 @@ +#ifndef GAMEPADUI_UTIL_H +#define GAMEPADUI_UTIL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "vgui/ISurface.h" + +const char *COM_GetModDirectory(); + +int DrawPrintWrappedText(vgui::HFont font, int pX, int pY, const wchar_t* pszText, int nLength, int nMaxWidth, bool bDraw); + +int NextPowerOfTwo( int v ); + +#endif // GAMEPADUI_UTIL_H diff --git a/game/gamepadui/igamepadui.h b/game/gamepadui/igamepadui.h new file mode 100644 index 00000000..1505d528 --- /dev/null +++ b/game/gamepadui/igamepadui.h @@ -0,0 +1,47 @@ +#ifndef IGAMEPADUI_H +#define IGAMEPADUI_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/interface.h" +#include "vgui/VGUI.h" +#include "mathlib/vector.h" +#include "ivrenderview.h" + +class ISource2013SteamInput; + +abstract_class IGamepadUI : public IBaseInterface +{ +public: + virtual void Initialize( CreateInterfaceFn factory ) = 0; + virtual void Shutdown() = 0; + + virtual void OnUpdate( float flFrametime ) = 0; + virtual void OnLevelInitializePreEntity() = 0; + virtual void OnLevelInitializePostEntity() = 0; + virtual void OnLevelShutdown() = 0; + + virtual void VidInit() = 0; + +#ifdef STEAM_INPUT + // TODO: Replace with proper singleton interface in the future + virtual void SetSteamInput( ISource2013SteamInput *pSteamInput ) = 0; +#endif + +#ifdef MAPBASE + virtual void BonusMapChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName ) = 0; + virtual void BonusMapChallengeObjectives( int &iBronze, int &iSilver, int &iGold ) = 0; +#endif +}; + +#define GAMEPADUI_INTERFACE_VERSION "GamepadUI001" + +// Lil easter egg :-) +#ifdef GAMEPADUI_GAME_PORTAL +#define GamepadUI_Log(...) ConColorMsg( Color( 61, 189, 237, 255 ), "[GamepadUI] " __VA_ARGS__ ) +#else +#define GamepadUI_Log(...) ConColorMsg( Color( 255, 134, 44, 255 ), "[GamepadUI] " __VA_ARGS__ ) +#endif + +#endif // IGAMEPADUI_H diff --git a/game/gamepadui/wscript b/game/gamepadui/wscript new file mode 100644 index 00000000..82b8dc01 --- /dev/null +++ b/game/gamepadui/wscript @@ -0,0 +1,92 @@ +#! /usr/bin/env python +# encoding: utf-8 + +from waflib import Utils +import os +import vpc_parser + +top = '.' +PROJECT_NAME = 'gamepadui' + +def options(opt): + return + +games = { + 'hl2': ['gamepadui_base.vpc', 'gamepadui_hl2.vpc'], + 'hl2mp': ['gamepadui_base.vpc', 'gamepadui_hl2.vpc'], + 'hl1': ['gamepadui_base.vpc', 'gamepadui_hl2.vpc'], + 'episodic': ['gamepadui_base.vpc', 'gamepadui_episodic.vpc'], + 'portal': ['gamepadui_base.vpc', 'gamepadui_portal.vpc'], + 'hl1mp': ['gamepadui_base.vpc', 'gamepadui_hl2.vpc'], + 'cstrike': ['gamepadui_base.vpc', 'gamepadui_hl2.vpc'], + 'dod': ['gamepadui_base.vpc', 'gamepadui_hl2.vpc'] +} + +def configure(conf): + game = conf.options.GAMES + conf.env.GAMES = game + + conf.env.append_unique('DEFINES', ['DISABLE_STEAM=1']) + + if game not in games.keys(): + conf.fatal("Couldn't find game: ", game) + +def build(bld): + game = vpc_parser.parse_vpcs( bld.env, games[bld.env.GAMES], '../..' ) + + includes = [ + '.', + 'game_controls', + '../../common', + '../../public', + '../../public/tier0', + '../../public/tier1', + '../../vgui2/vgui_controls', + '../shared', + '../game/gamepadui' + ] + + libs = [ + 'tier0', + 'particles', + 'dmxloader', + 'vgui_controls', + 'matsys_controls', + 'tier1', + 'tier2', + 'tier3', + 'mathlib', + 'vstdlib', + 'choreoobjects', + 'steam_api', + 'bitmap', + 'vtf', + 'RT', + 'ZLIB' + ] + + install_path = bld.env.PREFIX + if bld.env.DEST_OS != 'android': + install_path += '/'+bld.env.GAMES+'/bin' + + #source = [ 'in_touch.cpp' ] + if bld.env.DEST_OS == 'win32': + libs += ['USER32'] + + source = game["sources"] + ['../../public/tier0/memoverride.cpp'] + includes += game["includes"] + defines = game["defines"] + + bld.shlib( + source = source, + target = PROJECT_NAME, + name = PROJECT_NAME, + features = 'c cxx', + includes = includes, + defines = defines, + use = libs, + install_path = install_path, + subsystem = bld.env.MSVC_SUBSYSTEM, + idx = bld.get_taskgen_count() + ) + diff --git a/game/shared/point_bonusmaps_accessor.cpp b/game/shared/point_bonusmaps_accessor.cpp index 56a650fd..c261ea29 100644 --- a/game/shared/point_bonusmaps_accessor.cpp +++ b/game/shared/point_bonusmaps_accessor.cpp @@ -11,6 +11,13 @@ #include "GameUI/IGameUI.h" #include "fmtstr.h" #include "igameevents.h" +#ifdef MAPBASE +#include "filesystem.h" +#include "saverestore.h" +#endif +#ifdef GAMEPADUI +#include "../gamepadui/igamepadui.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -18,6 +25,9 @@ // See interface.h/.cpp for specifics: basically this ensures that we actually Sys_UnloadModule the dll and that we don't call Sys_LoadModule // over and over again. static CDllDemandLoader g_GameUI( "GameUI" ); +#if defined(GAMEPADUI) && defined(CLIENT_DLL) +extern IGamepadUI *g_pGamepadUI; +#endif #ifndef CLIENT_DLL @@ -95,6 +105,14 @@ void CPointBonusMapsAccessor::InputSave( inputdata_t& inputdata ) #endif +#ifdef MAPBASE +void CustomBMSystem_UpdateChallenges( const char *pchFileName, const char *pchMapName, const char *pchChallengeName, int iBest ); + +bool CustomBMSystem_OverridingInterface(); +void CustomBMSystem_BonusMapChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName ); +void CustomBMSystem_BonusMapChallengeObjectives( int &iBronze, int &iSilver, int &iGold ); +#endif + void BonusMapChallengeUpdate( const char *pchFileName, const char *pchMapName, const char *pchChallengeName, int iBest ) { CreateInterfaceFn gameUIFactory = g_GameUI.GetFactory(); @@ -116,12 +134,25 @@ void BonusMapChallengeUpdate( const char *pchFileName, const char *pchMapName, c event->SetInt( "numgold", piNumMedals[ 2 ] ); gameeventmanager->FireEvent( event ); } + +#ifdef MAPBASE + CustomBMSystem_UpdateChallenges( pchFileName, pchMapName, pchChallengeName, iBest ); +#endif } } } void BonusMapChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName ) { +#ifdef MAPBASE + // This accounts for cases where challenges are "skipped", which causes the data from GameUI to get desynced + if (CustomBMSystem_OverridingInterface()) + { + CustomBMSystem_BonusMapChallengeNames( pchFileName, pchMapName, pchChallengeName ); + return; + } +#endif + CreateInterfaceFn gameUIFactory = g_GameUI.GetFactory(); if ( gameUIFactory ) { @@ -135,6 +166,15 @@ void BonusMapChallengeNames( char *pchFileName, char *pchMapName, char *pchChall void BonusMapChallengeObjectives( int &iBronze, int &iSilver, int &iGold ) { +#ifdef MAPBASE + // This accounts for cases where challenges are "skipped", which causes the data from GameUI to get desynced + if (CustomBMSystem_OverridingInterface()) + { + CustomBMSystem_BonusMapChallengeObjectives( iBronze, iSilver, iGold ); + return; + } +#endif + CreateInterfaceFn gameUIFactory = g_GameUI.GetFactory(); if ( gameUIFactory ) { @@ -145,3 +185,823 @@ void BonusMapChallengeObjectives( int &iBronze, int &iSilver, int &iGold ) } } } + +#ifdef MAPBASE +#ifdef CLIENT_DLL +// This is so that the client can access the bonus challenge before it's moved to the player without any complex networking. +ConVar sv_bonus_challenge( "sv_bonus_challenge", "0", FCVAR_REPLICATED | FCVAR_HIDDEN ); +#else +extern ConVar sv_bonus_challenge; +#endif + +//----------------------------------------------------------------------------- +// Purpose: A wrapper-like system which tries to make up for the bonus maps framework being hardcoded into GameUI. +// At the moment, this system mostly involves restoring bonus challenges and allowing them to be used in mods. +// +// This aims to resolve or work around the following specific issues: +// +// - A limitation where challenge types cannot go beyond the number of image boxes defined in the .res file. +// - Not a complete workaround. They still have to occupy a valid image box, but different types can be used for one image. +// - A bug where "skipped" challenge types in a level will cause a desync between the GameUI and the player/cvar's value. +// - A bug where challenges with negative scores do not count as completed. +// - Negative scores themselves are a workaround for allowing challenges which require the "most of" something rather than the "least of" something, +// as the bonus maps framework normally only recognizes the latter. +// - May not appear immediately since this fix can be overwritten by GameUI under some circumstances. +// - A bug/limitation where challenge information didn't save/restore after restarting the game. +// +// The system also offers the following enhancements: +// +// - Two new game events for monitoring challenge progress for maps and challenge types in particular. They're useful +// if you want to have achievements (or at least progress monitoring) for specific maps or challenge types, as the +// existing events only covered all challenges in the game. +// - VScript functions for accessing bonus map and challenge values. +// +//----------------------------------------------------------------------------- +class CCustomBonusMapSystem : public CAutoGameSystem +{ +public: + DECLARE_DATADESC(); + + CCustomBonusMapSystem() : CAutoGameSystem( "CCustomBonusMapSystem" ) + { + m_pKV_CurrentBonusData = NULL; + m_pKV_SaveData = NULL; + m_bUpdatedChallenges = false; + } + + virtual bool Init() + { + return true; + } + + virtual void Shutdown() + { + RefreshSaveAdjustments(); + } + + virtual void LevelInitPreEntity() + { + CreateInterfaceFn gameUIFactory = g_GameUI.GetFactory(); + if ( gameUIFactory ) + { + m_pGameUI = (IGameUI *) gameUIFactory(GAMEUI_INTERFACE_VERSION, NULL ); + } + +#if defined(GAMEPADUI) && defined(CLIENT_DLL) + if (g_pGamepadUI && sv_bonus_challenge.GetInt() != 0) + { + g_pGamepadUI->BonusMapChallengeNames( m_szChallengeFileName, m_szChallengeMapName, m_szChallengeName ); + g_pGamepadUI->BonusMapChallengeObjectives( m_iBronze, m_iSilver, m_iGold ); + LoadBonusDataKV( m_szChallengeFileName ); + m_bOverridingInterface = true; + + //VerifyChallengeValues( sv_bonus_challenge.GetInt() ); + + // Get them onto the server + engine->ClientCmd_Unrestricted( VarArgs( "_set_sv_bonus %i \"%s\" \"%s\" \"%s\" %i %i %i\n", + sv_bonus_challenge.GetInt(), m_szChallengeFileName, m_szChallengeMapName, m_szChallengeName, + m_iBronze, m_iSilver, m_iGold ) ); + } + else +#endif + // Get the GameUI's values to override its interface (covers cases where GameUI's challenge information has desynced) + if (m_pGameUI) + { + m_pGameUI->BonusMapChallengeNames( m_szChallengeFileName, m_szChallengeMapName, m_szChallengeName ); + m_pGameUI->BonusMapChallengeObjectives( m_iBronze, m_iSilver, m_iGold ); + LoadBonusDataKV( m_szChallengeFileName ); + +//#ifndef CLIENT_DLL + if (sv_bonus_challenge.GetInt() != 0) + { + VerifyChallengeValues( sv_bonus_challenge.GetInt() ); + } +//#endif + } + } + + virtual void LevelInitPostEntity() + { + } + + virtual void LevelShutdownPreEntity() + { + } + + virtual void LevelShutdownPostEntity() + { + // Destroy KV + if (m_pKV_CurrentBonusData) + { + m_pKV_CurrentBonusData->deleteThis(); + m_pKV_CurrentBonusData = NULL; + } + + if (m_pKV_SaveData) + { + m_pKV_SaveData->deleteThis(); + m_pKV_SaveData = NULL; + } + + m_bUpdatedChallenges = false; + m_bOverridingInterface = false; + + RefreshSaveAdjustments(); + } + + virtual void OnRestore() + { + // No longer necessary due to datadesc and save/restore handler + /* +#ifdef CLIENT_DLL + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); +#else + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); +#endif + if (pPlayer && pPlayer->GetBonusChallenge() != 0) + { + if (m_pGameUI) + { + m_pGameUI->BonusMapChallengeNames( m_szChallengeFileName, m_szChallengeMapName, m_szChallengeName ); + m_pGameUI->BonusMapChallengeObjectives( m_iBronze, m_iSilver, m_iGold ); + LoadBonusDataKV( m_szChallengeFileName ); + + VerifyChallengeValues( pPlayer->GetBonusChallenge() ); + } + } + */ + } + + //------------------------------------------------------------------------------------ + + void LoadBonusDataKV( const char *pchFileName ) + { + // Destroy KV if we have any + if (m_pKV_CurrentBonusData) + { + m_pKV_CurrentBonusData->deleteThis(); + m_pKV_CurrentBonusData = NULL; + } + + // Reload the database + m_pKV_CurrentBonusData = new KeyValues( "CurrentBonusMapData" ); + m_pKV_CurrentBonusData->LoadFromFile( g_pFullFileSystem, pchFileName, NULL ); + } + + void LoadSaveDataKV( bool bRefreshData = false ) + { + if (bRefreshData) + { + // Destroy KV if we have any + if (m_pKV_SaveData) + { + m_pKV_SaveData->deleteThis(); + m_pKV_SaveData = NULL; + } + + // Save the database + m_pGameUI->BonusMapDatabaseSave(); + } + else if (m_pKV_SaveData) + { + return; + } + + // Reload the database + m_pKV_SaveData = new KeyValues( "SavedBonusMapData" ); + m_pKV_SaveData->LoadFromFile( g_pFullFileSystem, "save/bonus_maps_data.bmd", "MOD" ); + } + + //------------------------------------------------------------------------------------ + + void UpdateChallenges( const char *pchFileName, const char *pchMapName, const char *pchChallengeName, int iBest ) + { + if ( !m_pGameUI || m_bUpdatedChallenges ) + return; + + LoadSaveDataKV( true ); + + //---------------------------------------- + + Msg( "Updating challenges\n" ); + + KeyValues *pBonusFiles = m_pKV_SaveData->FindKey( "bonusfiles" ); + if (!pBonusFiles) + { + Warning( "Can't find bonus files\n" ); + return; + } + + // Can't use FindKey for file paths + KeyValues *pFile = NULL; + for (KeyValues *key = pBonusFiles->GetFirstSubKey(); key != NULL; key = key->GetNextKey()) + { + if ( !Q_stricmp( key->GetName(), pchFileName ) ) + { + pFile = key; + break; + } + } + + // Identify when all bonus challenges on this map are complete + if (pFile) + { + // Since we have saved data, get a list of this map's challenges + LoadBonusDataKV( pchFileName ); + + CUtlVector<int> iAllScores; + + for (KeyValues *pBDMap = m_pKV_CurrentBonusData; pBDMap != NULL; pBDMap = pBDMap->GetNextKey()) + { + KeyValues *pChallenges = pBDMap->FindKey( "challenges" ); + if (pChallenges) + { + KeyValues *pSaveMap = pFile->FindKey( pBDMap->GetName() ); + if (!pSaveMap) + continue; + + KeyValues *pThisChallenge = pChallenges->FindKey( pchChallengeName ); + if (pThisChallenge) + { + KeyValues *pSavedChallenge = pSaveMap->FindKey( pchChallengeName ); + if (!pSavedChallenge) + { + iAllScores.AddToTail( 0 ); + continue; + } + + // Add to progress vector + if (pSavedChallenge->GetInt() < pThisChallenge->GetInt( "gold" )) + iAllScores.AddToTail( 3 ); + else if (pSavedChallenge->GetInt() < pThisChallenge->GetInt( "silver" )) + iAllScores.AddToTail( 2 ); + else if (pSavedChallenge->GetInt() < pThisChallenge->GetInt( "bronze" )) + iAllScores.AddToTail( 1 ); + else + iAllScores.AddToTail( 0 ); + } + + // Only do the rest if it's the map we just completed + if (!Q_stricmp( pBDMap->GetName(), pchMapName )) + { + CUtlDict<int> iScores; + + // Compare all challenges to the saved data + for (KeyValues *challenge = pChallenges->GetFirstSubKey(); challenge != NULL; challenge = challenge->GetNextKey()) + { + KeyValues *pSavedChallenge = pSaveMap->FindKey( challenge->GetName() ); + if (!pSavedChallenge) + { + iScores.Insert( challenge->GetName(), 0 ); + continue; + } + + // Add to progress vector + if (pSavedChallenge->GetInt() < challenge->GetInt("gold")) + iScores.Insert( challenge->GetName(), 3 ); + else if (pSavedChallenge->GetInt() < challenge->GetInt("silver")) + iScores.Insert( challenge->GetName(), 2 ); + else if (pSavedChallenge->GetInt() < challenge->GetInt("bronze")) + iScores.Insert( challenge->GetName(), 1 ); + else + iScores.Insert( challenge->GetName(), 0 ); + } + + int iNumMedals[3] = { 0, 0, 0 }; + for (unsigned int i = 0; i < iScores.Count(); i++) + { + switch (iScores[i]) + { + // These are supposed to fall through into each other + case 3: iNumMedals[2]++; // Gold + case 2: iNumMedals[1]++; // Silver + case 1: iNumMedals[0]++; // Bronze + } + } + + // Fire an expanded map update event with more map-specific values + // + // "challenge_map_update" + // { + // "challenge" "short" + // "challengescore" "short" + // "challengebest" "short" + // "numbronze" "short" + // "numsilver" "short" + // "numgold" "short" + // } + // + IGameEvent *event = gameeventmanager->CreateEvent( "challenge_map_update", true ); + if ( event ) + { + + Msg("CHALLENGE MAP UPDATE:\n Map: %s, Challenge: %s\nChallenge Score: %i\n Ratio Bronze: %f, Ratio Silver: %f, Ratio Gold: %f\n", + pchMapName, pchChallengeName, iScores.Find( pchChallengeName ), + (float)iNumMedals[0] / (float)iScores.Count(), (float)iNumMedals[1] / (float)iScores.Count(), (float)iNumMedals[2] / (float)iScores.Count() ); + +#ifdef CLIENT_DLL + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); +#else + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); +#endif + + event->SetInt( "challenge", pPlayer ? pPlayer->GetBonusChallenge() : -1 ); + event->SetInt( "challengescore", iBest ); + event->SetInt( "challengebest", iScores.Find( pchChallengeName ) ); + event->SetInt( "numbronze", iNumMedals[0] ); + event->SetInt( "numsilver", iNumMedals[1] ); + event->SetInt( "numgold", iNumMedals[2] ); +#ifdef CLIENT_DLL + gameeventmanager->FireEventClientSide( event ); +#else + gameeventmanager->FireEvent( event ); +#endif + } + } + } + + + //if (pSaveMap->FindKey( "complete", false )) + } + + // Fire an event for the challenge itself + // + // "challenge_update" + // { + // "challenge" "short" + // "numbronze" "short" + // "numsilver" "short" + // "numgold" "short" + // } + // + IGameEvent *event = gameeventmanager->CreateEvent( "challenge_update", true ); + if ( event ) + { + int iNumMedals[3] = { 0, 0, 0 }; + for (int i = 0; i < iAllScores.Count(); i++) + { + switch (iAllScores[i]) + { + // These are supposed to fall through into each other + case 3: iNumMedals[2]++; // Gold + case 2: iNumMedals[1]++; // Silver + case 1: iNumMedals[0]++; // Bronze + } + } + + Msg("CHALLENGE UPDATE:\n Ratio Bronze: %f, Ratio Silver: %f, Ratio Gold: %f\n", + (float)iNumMedals[0] / (float)iAllScores.Count(), (float)iNumMedals[1] / (float)iAllScores.Count(), (float)iNumMedals[2] / (float)iAllScores.Count() ); + +#ifdef CLIENT_DLL + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); +#else + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); +#endif + + event->SetInt( "challenge", pPlayer ? pPlayer->GetBonusChallenge() : -1 ); + event->SetInt( "numbronze", iNumMedals[0] ); + event->SetInt( "numsilver", iNumMedals[1] ); + event->SetInt( "numgold", iNumMedals[2] ); +#ifdef CLIENT_DLL + gameeventmanager->FireEventClientSide( event ); +#else + gameeventmanager->FireEvent( event ); +#endif + } + } + + m_bUpdatedChallenges = true; + } + + void RefreshSaveAdjustments() + { + if ( !m_pGameUI ) + return; + + LoadSaveDataKV( true ); + + //---------------------------------------- + + KeyValues *pBonusFiles = m_pKV_SaveData->FindKey( "bonusfiles" ); + if (!pBonusFiles) + { + Warning( "Can't find bonus files\n" ); + return; + } + + bool bChanged = true; + + // Go through all maps we have save data for + for (KeyValues *pFile = pBonusFiles->GetFirstSubKey(); pFile != NULL; pFile = pFile->GetNextKey()) + { + // Get a list of this map's challenges + LoadBonusDataKV( pFile->GetName() ); + + CUtlVector<int> iAllScores; + + for (KeyValues *pBDMap = m_pKV_CurrentBonusData; pBDMap != NULL; pBDMap = pBDMap->GetNextKey()) + { + KeyValues *pChallenges = pBDMap->FindKey( "challenges" ); + if (pChallenges) + { + KeyValues *pSaveMap = pFile->FindKey( pBDMap->GetName() ); + if (!pSaveMap) + continue; + + CUtlDict<int> iScores; + + // Compare all challenges to the saved data + for (KeyValues *challenge = pChallenges->GetFirstSubKey(); challenge != NULL; challenge = challenge->GetNextKey()) + { + KeyValues *pSavedChallenge = pSaveMap->FindKey( challenge->GetName() ); + if (!pSavedChallenge) + { + iScores.Insert( challenge->GetName(), 0 ); + continue; + } + + // Add to progress vector + if (pSavedChallenge->GetInt() < challenge->GetInt("gold")) + iScores.Insert( challenge->GetName(), 3 ); + else if (pSavedChallenge->GetInt() < challenge->GetInt("silver")) + iScores.Insert( challenge->GetName(), 2 ); + else if (pSavedChallenge->GetInt() < challenge->GetInt("bronze")) + iScores.Insert( challenge->GetName(), 1 ); + else + iScores.Insert( challenge->GetName(), 0 ); + } + + int iNumMedals[3] = { 0, 0, 0 }; + for (unsigned int i = 0; i < iScores.Count(); i++) + { + switch (iScores[i]) + { + // These are supposed to fall through into each other + case 3: iNumMedals[2]++; // Gold + case 2: iNumMedals[1]++; // Silver + case 1: iNumMedals[0]++; // Bronze + } + } + + // Is the map completed? + if (iNumMedals[2] >= (int)iScores.Count()) + { + // Make sure the completion key is there. If it's not, there was a problem with the database code + // (This typically happens when the scores are negative) + if (pSaveMap && pSaveMap->FindKey( "complete" ) == NULL) + { + // Create the completion key and save + pSaveMap->SetBool( "complete", true ); + bChanged = true; + } + } + } + } + } + + if (bChanged) + m_pKV_SaveData->SaveToFile( g_pFullFileSystem, "save/bonus_maps_data.bmd", "MOD" ); + } + + //------------------------------------------------------------------------------------ + + bool OverridingInterface() { return m_bOverridingInterface; } + + void VerifyChallengeValues( int iTrueChallenge, bool bOverride = false ) + { + // Use sv_bonus_challenge to find this map and challenge in the file + for (KeyValues *pBDMap = m_pKV_CurrentBonusData; pBDMap != NULL; pBDMap = pBDMap->GetNextKey()) + { + if (Q_stricmp( pBDMap->GetName(), m_szChallengeMapName ) != 0) + continue; + + KeyValues *pChallenges = pBDMap->FindKey( "challenges" ); + if (pChallenges) + { + KeyValues *pGameUIChallenge = pChallenges->FindKey( m_szChallengeName ); + KeyValues *pConVarChallenge = NULL; + + for (KeyValues *challenge = pChallenges->GetFirstSubKey(); challenge != NULL; challenge = challenge->GetNextKey()) + { + if (challenge->GetInt( "type", -1 )+1 == iTrueChallenge) + { + pConVarChallenge = challenge; + break; + } + } + + // Begin overriding GameUI + m_bOverridingInterface = true; + + if ((pGameUIChallenge != pConVarChallenge || bOverride) && pConVarChallenge) + { + // This indicates there was a desync within GameUI + + //Msg( "****************** OVERRIDING GAME UI BONUS MAP INFO ******************\n%i -> %i --- %s -> %s\n******************************************************\n", + // pGameUIChallenge->GetInt( "type" ), iTrueChallenge, + // m_szChallengeName, pConVarChallenge->GetName() ); + + Msg( "Overriding GameUI bonus map info!!! (%i -> %i) (%s -> %s)\n", pGameUIChallenge->GetInt( "type" ), iTrueChallenge, m_szChallengeName, pConVarChallenge->GetName() ); + + Q_strncpy( m_szChallengeName, pConVarChallenge->GetName(), sizeof( m_szChallengeName ) ); + + m_iBronze = pConVarChallenge->GetInt( "bronze", 0 ); + m_iSilver = pConVarChallenge->GetInt( "silver", 0 ); + m_iGold = pConVarChallenge->GetInt( "gold", 0 ); + } + } + + break; + } + } + + void SetCustomBonusMapChallengeNames( const char *pchFileName, const char *pchMapName, const char *pchChallengeName ) + { + Q_strncpy( m_szChallengeFileName, pchFileName, sizeof( m_szChallengeFileName ) ); + Q_strncpy( m_szChallengeMapName, pchMapName, sizeof( m_szChallengeMapName ) ); + Q_strncpy( m_szChallengeName, pchChallengeName, sizeof( m_szChallengeName ) ); + m_bOverridingInterface = true; + } + + void CustomBonusMapChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName ) + { + Q_strncpy( pchFileName, m_szChallengeFileName, sizeof( m_szChallengeFileName ) ); + Q_strncpy( pchMapName, m_szChallengeMapName, sizeof( m_szChallengeMapName ) ); + Q_strncpy( pchChallengeName, m_szChallengeName, sizeof( m_szChallengeName ) ); + } + + void SetCustomBonusMapChallengeObjectives( int iBronze, int iSilver, int iGold ) + { + m_iBronze = iBronze; m_iSilver = iSilver; m_iGold = iGold; + m_bOverridingInterface = true; + } + + void CustomBonusMapChallengeObjectives( int &iBronze, int &iSilver, int &iGold ) + { + iBronze = m_iBronze; iSilver = m_iSilver; iGold = m_iGold; + } + +#ifdef GAME_DLL + // Allows a challenge to turn into another type in-game, allowing multiple challenges of the same "type" and + // getting around issues connected to too many challenge types in the dialog + void ResolveCustomBonusChallenge( CBasePlayer *pPlayer ) + { + for (KeyValues *pBDMap = m_pKV_CurrentBonusData; pBDMap != NULL; pBDMap = pBDMap->GetNextKey()) + { + //Msg( "Bonus map data has map %s\n", pBDMap->GetName() ); + + // Only do the rest if it's the map we just completed + if (!Q_stricmp( pBDMap->GetName(), m_szChallengeMapName )) + { + KeyValues *pChallenges = pBDMap->FindKey( "challenges" ); + if (pChallenges) + { + KeyValues *pThisChallenge = pChallenges->FindKey( m_szChallengeName ); + if (pThisChallenge) + { + // Get the hacky "type_game" + int iTypeGame = pThisChallenge->GetInt( "type_game", 0 ); + if (iTypeGame != 0) + pPlayer->SetBonusChallenge( iTypeGame ); + } + } + } + } + } +#endif + + //------------------------------------------------------------------------------------ + +#ifdef MAPBASE_VSCRIPT + virtual void RegisterVScript() + { + g_pScriptVM->RegisterInstance( this, "BonusMaps" ); + } +#endif + + //------------------------------------------------------------------------------------ + + const char *GetChallengeFileName() + { + return m_szChallengeFileName; + } + + const char *GetChallengeMapName() + { + return m_szChallengeMapName; + } + + const char *GetChallengeName() + { + return m_szChallengeName; + } + + int GetBronze() + { + return m_iBronze; + } + + int GetSilver() + { + return m_iSilver; + } + + int GetGold() + { + return m_iGold; + } + + //------------------------------------------------------------------------------------ + + inline bool BonusChallengesActive() + { +#ifdef CLIENT_DLL + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); +#else + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); +#endif + if (pPlayer && pPlayer->GetBonusChallenge() != 0) + { + return true; + } + else + { + return sv_bonus_challenge.GetInt() != 0; + } + } + +private: + + bool m_bUpdatedChallenges; + + //------------------------------------------- + + bool m_bOverridingInterface; + + char m_szChallengeFileName[MAX_PATH]; + char m_szChallengeMapName[48]; + char m_szChallengeName[48]; + + int m_iBronze, m_iSilver, m_iGold; + + //------------------------------------------- + + KeyValues *m_pKV_CurrentBonusData; + KeyValues *m_pKV_SaveData; + + IGameUI *m_pGameUI; +#ifdef GAMEPADUI + IGamepadUI *m_pGamepadUI; +#endif +}; + +CCustomBonusMapSystem g_CustomBonusMapSystem; + +BEGIN_DATADESC_NO_BASE( CCustomBonusMapSystem ) + DEFINE_FIELD( m_bOverridingInterface, FIELD_BOOLEAN ), + + DEFINE_AUTO_ARRAY( m_szChallengeFileName, FIELD_CHARACTER ), + DEFINE_AUTO_ARRAY( m_szChallengeMapName, FIELD_CHARACTER ), + DEFINE_AUTO_ARRAY( m_szChallengeName, FIELD_CHARACTER ), + + DEFINE_FIELD( m_iBronze, FIELD_INTEGER ), + DEFINE_FIELD( m_iSilver, FIELD_INTEGER ), + DEFINE_FIELD( m_iGold, FIELD_INTEGER ), +END_DATADESC() + +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CCustomBonusMapSystem, SCRIPT_SINGLETON "A custom bonus map tracking system." ) + DEFINE_SCRIPTFUNC( OverridingInterface, "Returns true if the custom bonus map system is overriding the GameUI interface." ) + + DEFINE_SCRIPTFUNC( GetChallengeFileName, "Gets the .bns file this bonus map originates from." ) + DEFINE_SCRIPTFUNC( GetChallengeMapName, "Gets the name of this bonus map from the .bns file. This is the literal name of the bonus map as it appears in the menu, which may be a user-friendly title and/or a localization token." ) + DEFINE_SCRIPTFUNC( GetChallengeName, "If a challenge is active, this gets its name. This is the literal name of the challenge as it appears in the menu, which may be a user-friendly title and/or a localization token." ) + + DEFINE_SCRIPTFUNC( GetBronze, "If a challenge is active, this gets its Bronze goal value." ) + DEFINE_SCRIPTFUNC( GetSilver, "If a challenge is active, this gets its Silver goal value." ) + DEFINE_SCRIPTFUNC( GetGold, "If a challenge is active, this gets its Gold goal value." ) +END_SCRIPTDESC(); +#endif + +//------------------------------------------------------------------------------------ + +inline void CustomBMSystem_UpdateChallenges( const char *pchFileName, const char *pchMapName, const char *pchChallengeName, int iBest ) +{ + g_CustomBonusMapSystem.UpdateChallenges( pchFileName, pchMapName, pchChallengeName, iBest ); +} + +inline bool CustomBMSystem_OverridingInterface() +{ + return g_CustomBonusMapSystem.OverridingInterface(); +} + +inline void CustomBMSystem_BonusMapChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName ) +{ + g_CustomBonusMapSystem.CustomBonusMapChallengeNames( pchFileName, pchMapName, pchChallengeName ); +} + +void CustomBMSystem_BonusMapChallengeObjectives( int &iBronze, int &iSilver, int &iGold ) +{ + g_CustomBonusMapSystem.CustomBonusMapChallengeObjectives( iBronze, iSilver, iGold ); +} + +#ifdef GAME_DLL +void ResolveCustomBonusChallenge( CBasePlayer *pPlayer ) +{ + g_CustomBonusMapSystem.ResolveCustomBonusChallenge( pPlayer ); +} + +//------------------------------------------------------------------------------------ + +void CC_SetSVBonus( const CCommand &args ) +{ + sv_bonus_challenge.SetValue( args.Arg( 1 ) ); + + // m_szChallengeFileName, m_szChallengeMapName, m_szChallengeName + g_CustomBonusMapSystem.SetCustomBonusMapChallengeNames( args.Arg( 2 ), args.Arg( 3 ), args.Arg( 4 ) ); + g_CustomBonusMapSystem.SetCustomBonusMapChallengeObjectives( atoi( args.Arg( 5 ) ), atoi( args.Arg( 6 ) ), atoi( args.Arg( 7 ) ) ); + g_CustomBonusMapSystem.LoadBonusDataKV( args.Arg( 2 ) ); + + Msg( "Challenge: %s, file name: %s, map name: %s, challenge name: %s\n", args.Arg( 1 ), args.Arg( 2 ), args.Arg( 3 ), args.Arg( 4 ) ); + + //g_CustomBonusMapSystem.VerifyChallengeValues( sv_bonus_challenge.GetInt(), true ); +} +static ConCommand _set_sv_bonus("_set_sv_bonus", CC_SetSVBonus, "", FCVAR_HIDDEN); + +#endif + +//----------------------------------------------------------------------------- +// Custom Bonus System Save/Restore +//----------------------------------------------------------------------------- +static short CUSTOM_BONUS_SAVE_RESTORE_VERSION = 1; + +class CCustomBonusSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler +{ +public: + const char *GetBlockName() + { + return "CustomBonusMapSystem"; + } + + //--------------------------------- + + void Save( ISave *pSave ) + { + // We only use this to save challenge info at the moment + bool bDoSave = g_CustomBonusMapSystem.BonusChallengesActive(); + + pSave->WriteBool( &bDoSave ); + if ( bDoSave ) + { + pSave->WriteAll( &g_CustomBonusMapSystem, g_CustomBonusMapSystem.GetDataDescMap() ); + } + } + + //--------------------------------- + + void WriteSaveHeaders( ISave *pSave ) + { + pSave->WriteShort( &CUSTOM_BONUS_SAVE_RESTORE_VERSION ); + } + + //--------------------------------- + + void ReadRestoreHeaders( IRestore *pRestore ) + { + // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. + short version; + pRestore->ReadShort( &version ); + m_fDoLoad = ( version == CUSTOM_BONUS_SAVE_RESTORE_VERSION ); + } + + //--------------------------------- + + void Restore( IRestore *pRestore, bool createPlayers ) + { + if ( m_fDoLoad ) + { + bool bDoRestore = false; + + pRestore->ReadBool( &bDoRestore ); + if ( bDoRestore ) + { + pRestore->ReadAll( &g_CustomBonusMapSystem, g_CustomBonusMapSystem.GetDataDescMap() ); + } + } + } + +private: + bool m_fDoLoad; +}; + +//----------------------------------------------------------------------------- + +CCustomBonusSaveRestoreBlockHandler g_CustomBonusSaveRestoreBlockHandler; + +//------------------------------------- + +ISaveRestoreBlockHandler *GetCustomBonusSaveRestoreBlockHandler() +{ + return &g_CustomBonusSaveRestoreBlockHandler; +} +#endif diff --git a/gameui/BasePanel.cpp b/gameui/BasePanel.cpp index b9f0bb07..9bc1dcde 100644 --- a/gameui/BasePanel.cpp +++ b/gameui/BasePanel.cpp @@ -87,6 +87,9 @@ using namespace vgui; #include "tier1/utlstring.h" #include "steam/steam_api.h" +#include "materialsystem/imaterial.h" +#include "tier2/renderutils.h" + #ifdef ANDROID #include <SDL_misc.h> #endif @@ -732,10 +735,10 @@ public: MESSAGE_FUNC_HANDLE( OnCursorEnteredMenuItem, "CursorEnteredMenuItem", menuItem); + vgui::VPANEL m_hMainMenuOverridePanel; private: CFooterPanel *m_pConsoleFooter; vgui::CKeyRepeatHandler m_KeyRepeat; - vgui::VPANEL m_hMainMenuOverridePanel; }; //----------------------------------------------------------------------------- @@ -811,6 +814,8 @@ CBasePanel::CBasePanel() : Panel(NULL, "BaseGameUIPanel") m_iBackgroundImageID = -1; m_iProductImageID = -1; m_iLoadingImageID = -1; + m_iLoadingSpinnerImageID = -1; + m_fLoadingSpinnerFrame = 0; if ( GameUI().IsConsoleUI() ) { @@ -833,8 +838,11 @@ CBasePanel::CBasePanel() : Panel(NULL, "BaseGameUIPanel") #endif } - m_pGameMenuButtons.AddToTail( CreateMenuButton( this, "GameMenuButton", ModInfo().GetGameTitle() ) ); - m_pGameMenuButtons.AddToTail( CreateMenuButton( this, "GameMenuButton2", ModInfo().GetGameTitle2() ) ); + if (!IsGamepadUI()) + { + m_pGameMenuButtons.AddToTail(CreateMenuButton(this, "GameMenuButton", ModInfo().GetGameTitle())); + m_pGameMenuButtons.AddToTail(CreateMenuButton(this, "GameMenuButton2", ModInfo().GetGameTitle2())); + } #ifdef CS_BETA if ( !ModInfo().NoCrosshair() ) // hack to not show the BETA for HL2 or HL1Port { @@ -979,6 +987,12 @@ CBasePanel::~CBasePanel() vgui::surface()->DestroyTextureID( m_iLoadingImageID ); m_iLoadingImageID = -1; } + + if (m_iLoadingSpinnerImageID != -1 ) + { + vgui::surface()->DestroyTextureID(m_iLoadingSpinnerImageID); + m_iLoadingSpinnerImageID = -1; + } } } @@ -1301,7 +1315,14 @@ void CBasePanel::SetBackgroundRenderState(EBackgroundState state) // fade background into main menu m_bRenderingBackgroundTransition = true; m_flTransitionStartTime = frametime; - m_flTransitionEndTime = frametime + 3.0f; + if (IsGamepadUI()) + { + m_flTransitionEndTime = frametime + 1.0f; + } + else + { + m_flTransitionEndTime = frametime + 3.0f; + } } } else if ( state == BACKGROUND_LOADING ) @@ -1362,6 +1383,12 @@ void CBasePanel::OnLevelLoadingStarted() { m_bLevelLoading = true; + ConVarRef("cl_gamepadui_mainmenu_draw").SetValue(false); + + //Msg("%d\n", GameUI().IsLoading()); + //GameUI().SetLoadingState(m_bLevelLoading); + //Msg("%d\n", GameUI().IsLoading()); + m_pGameMenu->ShowFooter( false ); if ( m_hMatchmakingBasePanel.Get() ) @@ -1384,6 +1411,8 @@ void CBasePanel::OnLevelLoadingFinished() { m_bLevelLoading = false; + ConVarRef("cl_gamepadui_mainmenu_draw").SetValue(true); + if ( m_hMatchmakingBasePanel.Get() ) { m_hMatchmakingBasePanel->OnCommand( "LevelLoadingFinished" ); @@ -1475,7 +1504,29 @@ void CBasePanel::DrawBackgroundImage() surface()->DrawSetTexture(m_iLoadingImageID); int twide, ttall; surface()->DrawGetTextureSize(m_iLoadingImageID, twide, ttall); - surface()->DrawTexturedRect(wide - twide, tall - ttall, wide, tall); + if (IsGamepadUI()) + { + surface()->DrawTexturedRect(wide - ((twide / 512.f) * twide) - 30, 30, wide - 30, (ttall / 512.f) * ttall + 30); + + static unsigned int nFrameCache = 0; + surface()->DrawGetTextureSize(m_iLoadingSpinnerImageID, twide, ttall); //now use twide and ttall for spinner + IScheme* pScheme = vgui::scheme()->GetIScheme(vgui::scheme()->GetScheme("Scheme")); + surface()->DrawSetColor(pScheme->GetColor("SteamDeckSpinner", { 201, 100, 0, alpha })); + surface()->DrawSetTextureFrame(m_iLoadingSpinnerImageID, ((int)m_fLoadingSpinnerFrame) % surface()->GetTextureNumFrames(m_iLoadingSpinnerImageID), &nFrameCache); + surface()->DrawSetTexture(m_iLoadingSpinnerImageID); + + surface()->DrawTexturedRect(wide - ((twide / 512.f)* twide) - 30, 30, wide - 30, (ttall / 512.f) * ttall + 30); + + static float SpinnerTimeDelta = 0; + static float SpinnerTime = 0; + SpinnerTimeDelta = engine->Time() - SpinnerTime; + m_fLoadingSpinnerFrame += SpinnerTimeDelta * 100; + SpinnerTime = engine->Time(); + } + else + { + surface()->DrawTexturedRect(wide - twide, tall - ttall, wide, tall); + } } // update the menu alpha @@ -1802,6 +1853,7 @@ void CBasePanel::PerformLayout() //----------------------------------------------------------------------------- void CBasePanel::ApplySchemeSettings(IScheme *pScheme) { + int i; BaseClass::ApplySchemeSettings(pScheme); @@ -1864,7 +1916,7 @@ void CBasePanel::ApplySchemeSettings(IScheme *pScheme) SetBgColor(Color(0, 0, 0, 0)); m_BackdropColor = pScheme->GetColor("mainmenu.backdrop", Color(0, 0, 0, 128)); - + char filename[MAX_PATH]; if ( IsX360() ) { @@ -1902,6 +1954,7 @@ void CBasePanel::ApplySchemeSettings(IScheme *pScheme) { m_iBackgroundImageID = surface()->CreateNewTextureID(); } + surface()->DrawSetTextureFile( m_iBackgroundImageID, filename, false, false ); if ( IsX360() ) @@ -1915,19 +1968,40 @@ void CBasePanel::ApplySchemeSettings(IScheme *pScheme) } surface()->DrawSetTextureFile( m_iProductImageID, filename, false, false ); } - + + if ( IsPC() ) { // load the loading icon if ( m_iLoadingImageID == -1 ) { - const char* loading = "console/startup_loading"; - if ( IsSteamDeck() ) - loading = "gamepadui/game_logo"; - m_iLoadingImageID = surface()->CreateNewTextureID(); - surface()->DrawSetTextureFile( m_iLoadingImageID, loading, false, false ); + if (IsGamepadUI()) + { + const char* loading = "gamepadui/game_logo.vtf"; + m_iLoadingImageID = surface()->CreateNewTextureID(); + surface()->DrawSetTextureFile(m_iLoadingImageID, loading, true, false); + + } + else + { + const char* loading = "console/startup_loading"; + m_iLoadingImageID = surface()->CreateNewTextureID(); + surface()->DrawSetTextureFile(m_iLoadingImageID, loading, false, false); + } + + } + + if (IsGamepadUI()) + { + if (m_iLoadingSpinnerImageID == -1) + { + const char* loadingCircle = "gamepadui/spinner"; + m_iLoadingSpinnerImageID = surface()->CreateNewTextureID(); + surface()->DrawSetTextureFile(m_iLoadingSpinnerImageID, loadingCircle, true, false); + } } } + } //----------------------------------------------------------------------------- diff --git a/gameui/BasePanel.h b/gameui/BasePanel.h index bb1e9b42..c22b90c9 100644 --- a/gameui/BasePanel.h +++ b/gameui/BasePanel.h @@ -399,6 +399,7 @@ private: int m_iBackgroundImageID; int m_iRenderTargetImageID; int m_iLoadingImageID; + int m_iLoadingSpinnerImageID; float m_fLoadingSpinnerFrame; int m_iProductImageID; bool m_bLevelLoading; bool m_bEverActivated; diff --git a/public/tier1/KeyValues.h b/public/tier1/KeyValues.h index 3a6af814..e2114e52 100644 --- a/public/tier1/KeyValues.h +++ b/public/tier1/KeyValues.h @@ -428,7 +428,7 @@ inline bool KeyValues::IsEmpty( int keySymbol ) return dat ? dat->IsEmpty( ) : true; } -bool IsSteamDeck(); +bool IsGamepadUI(); bool EvaluateConditional( const char *str ); diff --git a/tier1/KeyValues.cpp b/tier1/KeyValues.cpp index 75664e43..8dd52cdd 100644 --- a/tier1/KeyValues.cpp +++ b/tier1/KeyValues.cpp @@ -2179,32 +2179,28 @@ void KeyValues::RecursiveMergeKeyValues( KeyValues *baseKV ) } } -static int s_nSteamDeckCached = -1; - -bool IsSteamDeck() +//Gamepadui +bool IsGamepadUI() { - if (s_nSteamDeckCached == -1) { - if ( CommandLine()->CheckParm( "-nogamepadui" ) != 0 ) - { - s_nSteamDeckCached = 0; - } - else - { - if ( CommandLine()->CheckParm( "-gamepadui" ) != 0 ) - { - s_nSteamDeckCached = 1; - } - else - { - char *deck = getenv("SteamDeck"); - if ( deck == 0 || *deck == 0 ) - s_nSteamDeckCached = 0; - else - s_nSteamDeckCached = atoi(deck) != 0; - } - } - } - return s_nSteamDeckCached; + //we dont want to use shader editor AND gamepadui at the same time + if (CommandLine()->FindParm("-shaderedit")) + return false; + + //we dont want tools AND gamepadui at the same time + if (CommandLine()->FindParm("-tools")) + return false; + + if (CommandLine()->FindParm("-nogamepadui")) + return false; + + if (CommandLine()->FindParm("-gamepadui")) + return true; + + const char* pszSteamDeckEnv = getenv("SteamDeck"); + if (pszSteamDeckEnv && *pszSteamDeckEnv) + return atoi(pszSteamDeckEnv) != 0; + + return false; } //----------------------------------------------------------------------------- @@ -2224,7 +2220,7 @@ bool EvaluateConditional( const char *str ) bNot = true; if ( Q_stristr( str, "$DECK" ) ) - return IsSteamDeck() ^ bNot; + return IsGamepadUI() ^ bNot; if ( Q_stristr( str, "$X360" ) ) return IsX360() ^ bNot; diff --git a/wscript b/wscript index 16f2263b..362aba3e 100644 --- a/wscript +++ b/wscript @@ -94,6 +94,7 @@ projects={ 'utils/vtex', 'unicode', 'video', + 'game/gamepadui', ], 'tests': [ 'appframework',