source-engine/game/gamepadui/gamepadui_newgame.cpp
2024-04-13 19:58:55 +03:00

548 lines
19 KiB
C++

#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(), "" );
}