//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "BasePanel.h" #include "SaveGameDialog.h" #include "winlite.h" // FILETIME #include "vgui/ILocalize.h" #include "vgui/ISurface.h" #include "vgui/ISystem.h" #include "vgui/IVGui.h" #include "vgui_controls/AnimationController.h" #include "vgui_controls/ImagePanel.h" #include "filesystem.h" #include "KeyValues.h" #include "ModInfo.h" #include "EngineInterface.h" #include "GameUI_Interface.h" #include "vstdlib/random.h" #include "SaveGameBrowserDialog.h" extern const char *COM_GetModDirectory( void ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CGameSavePanel::CGameSavePanel( CSaveGameBrowserDialog *parent, SaveGameDescription_t *pSaveDesc, bool bCommandPanel ) : BaseClass( parent, "SaveGamePanel" ) { // Store our save description internally for reference later by our parent m_SaveInfo = (*pSaveDesc); m_bNewSavePanel = bCommandPanel; // Setup our main graphical elements m_pLevelPicBorder = SETUP_PANEL( new ImagePanel( this, "LevelPicBorder" ) ); m_pLevelPic = SETUP_PANEL( new ImagePanel( this, "LevelPic" ) ); // Setup our various labels m_pChapterTitle = new Label( this, "ChapterLabel", m_SaveInfo.szComment ); m_pTime = new Label( this, "TimeLabel", m_SaveInfo.szFileTime ); m_pElapsedTime = new Label( this, "ElapsedLabel", m_SaveInfo.szElapsedTime ); m_pType = new Label( this, "TypeLabel", m_SaveInfo.szType ); // Make sure we have a chapter description char *pchChapterName = Q_stristr( m_SaveInfo.szComment, "chapter" ); if ( pchChapterName ) { char szChapterImage[ 256 ]; Q_snprintf( szChapterImage, sizeof(szChapterImage), "chapters/%s", Q_strlower( pchChapterName ) ); char *ext = Q_strrchr( szChapterImage, '_' ); if ( ext ) { *ext = '\0'; } m_pLevelPic->SetImage( szChapterImage ); } else { m_pLevelPic->SetImage( "ui_logo" ); } // Setup our basic settings KeyValues *pKeys = NULL; if ( GameUI().IsConsoleUI() ) { pKeys = BasePanel()->GetConsoleControlSettings()->FindKey( "SaveGamePanel.res" ); } LoadControlSettings( "Resource/SaveGamePanel.res", NULL, pKeys ); int px, py; m_pLevelPicBorder->GetPos( px, py ); SetSize( m_pLevelPicBorder->GetWide(), py + m_pLevelPicBorder->GetTall() + ( m_pType->GetTall() + 16 ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CGameSavePanel::~CGameSavePanel( void ) { if ( m_pLevelPicBorder ) delete m_pLevelPicBorder; if ( m_pLevelPic ) delete m_pLevelPic; if ( m_pChapterTitle ) delete m_pChapterTitle; if ( m_pTime ) delete m_pTime; if ( m_pElapsedTime ) delete m_pElapsedTime; if ( m_pType ) delete m_pType; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGameSavePanel::ApplySchemeSettings( IScheme *pScheme ) { m_TextColor = pScheme->GetColor( "NewGame.TextColor", Color(255, 255, 255, 255) ); m_FillColor = pScheme->GetColor( "NewGame.FillColor", Color(255, 255, 255, 255) ); m_DisabledColor = pScheme->GetColor( "NewGame.DisabledColor", Color(255, 255, 255, 4) ); m_SelectedColor = pScheme->GetColor( "NewGame.SelectionColor", Color(255, 255, 255, 255) ); // Turn various labels off if we're the "stubbed" panel if ( m_bNewSavePanel ) { m_pTime->SetVisible( false ); m_pElapsedTime->SetVisible( false ); m_pType->SetVisible( false ); } // Setup our initial state m_pChapterTitle->SetFgColor( m_TextColor ); m_pTime->SetFgColor( m_TextColor ); m_pElapsedTime->SetFgColor( m_TextColor ); m_pLevelPic->SetFillColor( Color( 0, 0, 0, 255 ) ); m_pLevelPicBorder->SetFillColor( Color( 0, 0, 0, 255 ) ); if ( m_bNewSavePanel ) { float flScaleAmount = m_pLevelPic->GetScaleAmount(); if ( flScaleAmount <= 0.0f ) flScaleAmount = 1.0f; // TBD: Draw the game logo here! int picWide = 64.0f * flScaleAmount; int picTall = 64.0f * flScaleAmount; int borderWide = m_pLevelPicBorder->GetWide(); int borderTall = m_pLevelPicBorder->GetTall(); int borderX, borderY; m_pLevelPicBorder->GetPos( borderX, borderY ); m_pLevelPic->SetPos( borderX + ( ( borderWide - picWide ) / 2 ), borderY + ( ( borderTall - picTall ) / 2 ) ); m_pLevelPic->SetFillColor( Color( 0, 0, 0, 0 ) ); } BaseClass::ApplySchemeSettings( pScheme ); } //----------------------------------------------------------------------------- // Purpose: Overwrite the level description // Input : *pDesc - Description to use //----------------------------------------------------------------------------- void CGameSavePanel::SetDescription( SaveGameDescription_t *pDesc ) { // Store our save description internally for reference later by our parent m_SaveInfo = (*pDesc); // Setup our main graphical elements m_pChapterTitle->SetText( m_SaveInfo.szComment ); m_pTime->SetText( m_SaveInfo.szFileTime ); m_pElapsedTime->SetText( m_SaveInfo.szElapsedTime ); m_pType->SetText( m_SaveInfo.szType ); // Make sure we have a chapter description char *pchChapterName = Q_stristr( m_SaveInfo.szComment, "chapter" ); if ( pchChapterName ) { char szChapterImage[ 256 ]; Q_snprintf( szChapterImage, sizeof(szChapterImage), "chapters/%s", Q_strlower( pchChapterName ) ); char *ext = Q_strrchr( szChapterImage, '_' ); if ( ext ) { *ext = '\0'; } m_pLevelPic->SetImage( szChapterImage ); } } // // // // // //----------------------------------------------------------------------------- // Purpose: new game chapter selection //----------------------------------------------------------------------------- CSaveGameBrowserDialog::CSaveGameBrowserDialog( vgui::Panel *parent ) : BaseClass( parent, "SaveGameDialog" ), m_bFilterAutosaves( false ), m_iSelectedSave( -1 ), m_bScrolling( false ), m_ScrollCt( 0 ), m_ScrollSpeed( 0.0f ), m_ButtonPressed( SCROLL_NONE ), m_ScrollDirection( SCROLL_NONE ), m_nDeletedPanel( INVALID_INDEX ), m_nAddedPanel( INVALID_INDEX ), m_nUsedStorageSpace( 0 ), m_bControlDisabled( false ) { // Setup basic attributes SetDeleteSelfOnClose( true ); SetSizeable( false ); // Create the backer that highlights the currently selected save m_pCenterBg = SETUP_PANEL( new Panel( this, "CenterBG" ) ); m_pCenterBg->SetPaintBackgroundType( 2 ); m_pCenterBg->SetVisible( true ); // Create our button footer m_pFooter = new CFooterPanel( parent, "SaveGameFooter" ); // Load our res files from the keyvalue we're holding KeyValues *pKeys = NULL; if ( GameUI().IsConsoleUI() ) { pKeys = BasePanel()->GetConsoleControlSettings()->FindKey( "SaveGameDialog.res" ); } LoadControlSettings( "Resource/SaveGameDialog.res", NULL, pKeys ); } //----------------------------------------------------------------------------- // Destructor //----------------------------------------------------------------------------- CSaveGameBrowserDialog::~CSaveGameBrowserDialog( void ) { // Release all elements m_SavePanels.PurgeAndDeleteElements(); // Kill the footer if ( m_pFooter ) { delete m_pFooter; m_pFooter = NULL; } if ( m_pCenterBg ) { delete m_pCenterBg; m_pCenterBg = NULL; } } //----------------------------------------------------------------------------- // Purpose: Show the "No save games to display" indication label and hide all browsing UI //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::ShowNoSaveGameUI( void ) { // Show the "no save games" text vgui::Label *pNoSavesLabel = dynamic_cast(FindChildByName( "NoSavesLabel" )); if ( pNoSavesLabel ) { if ( m_bSaveGameIsCorrupt ) { pNoSavesLabel->SetText("#GameUI_SaveGame_CorruptFile"); } else { pNoSavesLabel->SetText("#GameUI_NoSaveGamesToDisplay"); } pNoSavesLabel->SetVisible( true ); } if ( m_pCenterBg ) m_pCenterBg->SetVisible( false ); vgui::Panel *pLeftArrow = FindChildByName( "LeftArrow" ); if ( pLeftArrow ) pLeftArrow->SetVisible( false ); vgui::Panel *pRightArrow = FindChildByName( "RightArrow" ); if ( pRightArrow ) pRightArrow->SetVisible( false ); } //----------------------------------------------------------------------------- // Purpose: Hide all "No save games" UI //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::HideNoSaveGameUI( void ) { // Show the "no save games" text vgui::Panel *pNoSavesLabel = FindChildByName( "NoSavesLabel" ); if ( pNoSavesLabel ) pNoSavesLabel->SetVisible( false ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::LayoutPanels( void ) { // Setup our panels depending on the mode we're in if ( HasActivePanels() ) { // Hide any indicators about no save games HideNoSaveGameUI(); // Layout panel positions relative to the dialog center. int panelWidth = m_SavePanels[0]->GetWide() + 16; int dialogWidth = GetWide(); m_PanelXPos[2] = ( dialogWidth - panelWidth ) / 2 + 8; m_PanelXPos[1] = m_PanelXPos[2] - panelWidth; m_PanelXPos[0] = m_PanelXPos[1]; m_PanelXPos[3] = m_PanelXPos[2] + panelWidth; m_PanelXPos[4] = m_PanelXPos[3]; m_PanelAlpha[0] = 0; m_PanelAlpha[1] = 64; m_PanelAlpha[2] = 255; m_PanelAlpha[3] = 64; m_PanelAlpha[4] = 0; int panelHeight; m_SavePanels[0]->GetSize( panelWidth, panelHeight ); m_pCenterBg->SetVisible( true ); m_pCenterBg->SetWide( panelWidth + 16 ); m_pCenterBg->SetPos( m_PanelXPos[2] - 8, m_PanelYPos[2] - (panelHeight - m_nCenterBgTallDefault) + 8 ); m_pCenterBg->SetBgColor( Color( 190, 115, 0, 255 ) ); } else { // Hide anything to do with browsing the saves ShowNoSaveGameUI(); } // Do internal cleanup to make sure we present a correct state to the user UpdateMenuComponents( SCROLL_NONE ); UpdateFooterOptions(); } //----------------------------------------------------------------------------- // Purpose: Do a fancy slide-out when we're first displayed //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::AnimateDialogStart( void ) { const float flAnimInTime = 0.5f; const float flOffset = 0.1f; for ( int i = 0; i < NUM_SLOTS; i++ ) { if ( m_PanelIndex[i] == INVALID_INDEX ) continue; // Start us at the "opening" position CGameSavePanel *panel = m_SavePanels[ m_PanelIndex[i] ]; if ( panel ) { panel->SetPos( m_PanelXPos[0], m_PanelYPos[0] ); panel->SetAlpha( m_PanelAlpha[0] ); panel->SetVisible( true ); panel->SetEnabled( true ); panel->SetZPos( NUM_SLOTS - i ); } // Now make them slide out where they're going GetAnimationController()->RunAnimationCommand( panel, "xpos", m_PanelXPos[i], 0, flAnimInTime + (flOffset*i), vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); GetAnimationController()->RunAnimationCommand( panel, "ypos", m_PanelYPos[i], 0, flAnimInTime + (flOffset*i), vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); // Panel alpha GetAnimationController()->RunAnimationCommand( panel, "alpha", m_PanelAlpha[i], 0, flAnimInTime + (flOffset*i), vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); } // Move and fade the back label m_pCenterBg->SetAlpha( 0 ); int nX, nY; m_pCenterBg->GetPos( nX, nY ); m_pCenterBg->SetPos( nX-m_pCenterBg->GetWide(), nY ); GetAnimationController()->RunAnimationCommand( m_pCenterBg, "xpos", nX, 0, flAnimInTime + (flOffset*2), vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); GetAnimationController()->RunAnimationCommand( m_pCenterBg, "alpha", 255, 0, (flAnimInTime+ (flOffset*2))*2.0f, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); CGameSavePanel *selectedPanel = GetActivePanel(); if ( selectedPanel && selectedPanel->IsAutoSaveType() ) { m_pCenterBg->SetTall( m_nCenterBgTallDefault + 20 ); } else { m_pCenterBg->SetTall( m_nCenterBgTallDefault ); } } //----------------------------------------------------------------------------- // Purpose: Do our initial layout //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::Activate( void ) { // Start scanning for saved games ScanSavedGames( m_bFilterAutosaves ); // Finish our layout depending on what the result of the scan was LayoutPanels(); // Animate the opening animation AnimateDialogStart(); BaseClass::Activate(); } //----------------------------------------------------------------------------- // Purpose: Apply special properties of the menu //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::ApplySettings( KeyValues *inResourceData ) { BaseClass::ApplySettings( inResourceData ); int ypos = inResourceData->GetInt( "chapterypos", 20 ); for ( int i = 0; i < NUM_SLOTS; ++i ) { m_PanelYPos[i] = ypos; } m_nCenterBgTallDefault = inResourceData->GetInt( "centerbgtall", 0 ); m_pCenterBg->SetTall( m_nCenterBgTallDefault ); m_ScrollSpeedSlow = inResourceData->GetFloat( "scrollslow", 0.0f ); m_ScrollSpeedFast = inResourceData->GetFloat( "scrollfast", 0.0f ); SetFastScroll( false ); } //----------------------------------------------------------------------------- // Purpose: Apply scheme settings //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); UpdateMenuComponents( SCROLL_NONE ); } //----------------------------------------------------------------------------- // Purpose: sets the correct properties for visible components //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::UpdateMenuComponents( EScrollDirection dir ) { // This is called prior to any scrolling, so we need to look ahead to the post-scroll state int centerIdx = SLOT_CENTER; // Scroll given our direction of travel if ( dir == SCROLL_LEFT ) { ++centerIdx; } else if ( dir == SCROLL_RIGHT ) { --centerIdx; } int leftIdx = centerIdx - 1; int rightIdx = centerIdx + 1; // Update the state of the side arrows depending on whether or not we can scroll that direction vgui::Panel *leftArrow = this->FindChildByName( "LeftArrow" ); vgui::Panel *rightArrow = this->FindChildByName( "RightArrow" ); if ( leftArrow ) { leftArrow->SetVisible( true ); if ( m_PanelIndex[leftIdx] != INVALID_INDEX ) { leftArrow->SetFgColor( Color( 255, 255, 255, 255 ) ); } else { leftArrow->SetFgColor( Color( 128, 128, 128, 64 ) ); } } if ( rightArrow ) { rightArrow->SetVisible( true ); if ( m_PanelIndex[rightIdx] != INVALID_INDEX ) { rightArrow->SetFgColor( Color( 255, 255, 255, 255 ) ); } else { rightArrow->SetFgColor( Color( 128, 128, 128, 64 ) ); } } } //----------------------------------------------------------------------------- // Purpose: sets a chapter as selected //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::SetSelectedSaveIndex( int index ) { m_iSelectedSave = index; // If we have no panels, there's nothing to update if ( HasActivePanels() == false ) return; // Setup panels to the left of the selected panel int currIdx = index; for ( int i = SLOT_CENTER; i >= 0 && currIdx >= 0; --i ) { m_PanelIndex[i] = currIdx; --currIdx; InitPanelIndexForDisplay( i ); } // Setup panels to the right of the selected panel currIdx = index + 1; for ( int i = SLOT_CENTER + 1; i < NUM_SLOTS && currIdx < m_SavePanels.Count(); ++i ) { m_PanelIndex[i] = currIdx; ++currIdx; InitPanelIndexForDisplay( i ); } UpdateMenuComponents( SCROLL_NONE ); } //----------------------------------------------------------------------------- // Purpose: Remove the currently selected animation from the list with proper animations //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::RemoveActivePanel( void ) { // Kill the current panel m_nDeletedPanel = m_PanelIndex[SLOT_CENTER]; // Start our current panel fading CGameSavePanel *pPanel = m_SavePanels[ m_nDeletedPanel ]; GetAnimationController()->RunAnimationCommand( pPanel, "alpha", 0, 0, m_ScrollSpeedFast, vgui::AnimationController::INTERPOLATOR_ACCEL ); GetAnimationController()->RunAnimationCommand( m_pCenterBg, "alpha", 0, 0, m_ScrollSpeedFast, vgui::AnimationController::INTERPOLATOR_ACCEL ); PostMessage( this, new KeyValues( "FinishDelete" ), m_ScrollSpeed ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::CloseAfterSave( void ) { OnCommand( "CloseAndSelectResume" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::FinishInsert( void ) { CGameSavePanel *panel = m_SavePanels[ m_nAddedPanel ]; const float flScrollSpeed = 0.75f; // Run the actual movement GetAnimationController()->RunAnimationCommand( panel, "xpos", m_PanelXPos[SLOT_RIGHT], 0, flScrollSpeed, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); GetAnimationController()->RunAnimationCommand( panel, "ypos", m_PanelYPos[SLOT_RIGHT], 0, flScrollSpeed, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); // Panel alpha GetAnimationController()->RunAnimationCommand( panel, "alpha", 255, 0, flScrollSpeed, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); PostMessage( this, new KeyValues( "CloseAfterSave" ), flScrollSpeed*2.0f ); } //----------------------------------------------------------------------------- // Purpose: Insert a new panel at the desired location //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::AnimateInsertNewPanel( const SaveGameDescription_t *pDesc ) { // This is the panel that's going to move CGameSavePanel *pNewPanel = SETUP_PANEL( new CGameSavePanel( this, (SaveGameDescription_t *) pDesc ) ); pNewPanel->SetVisible( false ); // Tack this onto the list m_nAddedPanel = m_SavePanels.InsertAfter( 0, pNewPanel ); // Set it up but turn it off immediately pNewPanel->SetPos( m_PanelXPos[SLOT_CENTER], m_PanelYPos[SLOT_CENTER] ); pNewPanel->SetVisible( true ); pNewPanel->SetEnabled( true ); pNewPanel->SetZPos( 0 ); pNewPanel->SetAlpha( 0.0f ); // Increment our indices to reflect the change for ( int i = 0; i < NUM_SLOTS; i++ ) { if ( m_PanelIndex[i] == INVALID_INDEX ) continue; if ( m_PanelIndex[i] > 0 ) { m_PanelIndex[i]++; } } // Fade the right panel away if ( IsValidPanel( m_PanelIndex[ SLOT_RIGHT ] ) ) { CGameSavePanel *panel = m_SavePanels[ m_PanelIndex[ SLOT_RIGHT ] ]; // Run the actual movement GetAnimationController()->RunAnimationCommand( panel, "xpos", m_PanelXPos[SLOT_OFFRIGHT], 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); GetAnimationController()->RunAnimationCommand( panel, "ypos", m_PanelYPos[SLOT_OFFRIGHT], 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); // Panel alpha GetAnimationController()->RunAnimationCommand( panel, "alpha", m_PanelAlpha[SLOT_OFFRIGHT], 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); PostMessage( this, new KeyValues( "FinishInsert" ), m_ScrollSpeed ); } else { PostMessage( this, new KeyValues( "FinishInsert" ), 0.1f ); } } //----------------------------------------------------------------------------- // Purpose: Pop in the new description //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::FinishOverwriteFadeDown( void ) { const float flFadeInTime = 0.25f; // Fade the right panel away CGameSavePanel *pActivePanel = GetActivePanel(); if ( pActivePanel ) { pActivePanel->SetDescription( &m_NewSaveGameDesc ); // Panel alpha GetAnimationController()->RunAnimationCommand( pActivePanel, "alpha", m_PanelAlpha[SLOT_CENTER], 0, flFadeInTime, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); } GetAnimationController()->RunAnimationCommand( m_pCenterBg, "alpha", 255, 0, flFadeInTime, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); PostMessage( this, new KeyValues( "CloseAfterSave" ), flFadeInTime + 0.1f ); } //----------------------------------------------------------------------------- // Purpose: Animate an overwrite event by fading out the old panel and bringing it back with a new description // Input : *pNewDesc - The new description to display //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::AnimateOverwriteActivePanel( const SaveGameDescription_t *pNewDesc ) { // Save a copy of this description m_NewSaveGameDesc = (*pNewDesc); // Fade the right panel away CGameSavePanel *pActivePanel = GetActivePanel(); if ( pActivePanel ) { // Panel alpha GetAnimationController()->RunAnimationCommand( pActivePanel, "alpha", 0, 0, 0.5f, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); } GetAnimationController()->RunAnimationCommand( m_pCenterBg, "alpha", 0, 0, 0.5f, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); PostMessage( this, new KeyValues( "FinishOverwriteFadeDown" ), 0.75f ); } //----------------------------------------------------------------------------- // Purpose: Called before a panel scroll starts. //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::PreScroll( EScrollDirection dir ) { int hideIdx = INVALID_INDEX; if ( m_nDeletedPanel != INVALID_INDEX ) { hideIdx = m_nDeletedPanel; } else if ( dir == SCROLL_LEFT ) { hideIdx = m_PanelIndex[SLOT_LEFT]; } else if ( dir == SCROLL_RIGHT ) { hideIdx = m_PanelIndex[SLOT_RIGHT]; } if ( hideIdx != INVALID_INDEX ) { // Push back the panel that's about to be hidden // so the next panel scrolls over the top of it. m_SavePanels[hideIdx]->SetZPos( 0 ); } } //----------------------------------------------------------------------------- // Purpose: Called after a panel scroll finishes. //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::PostScroll( EScrollDirection dir ) { // FIXME: Nothing to do here... } //----------------------------------------------------------------------------- // Purpose: Initiates a panel scroll and starts the animation. //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::ScrollSelectionPanels( EScrollDirection dir ) { // Only initiate a scroll if panels aren't currently scrolling if ( !m_bScrolling ) { // Handle any pre-scroll setup PreScroll( dir ); if ( dir == SCROLL_LEFT) { m_ScrollCt += SCROLL_LEFT; } else if ( dir == SCROLL_RIGHT && m_PanelIndex[SLOT_CENTER] != 0 ) { m_ScrollCt += SCROLL_RIGHT; } m_bScrolling = true; AnimateSelectionPanels(); // Update the arrow colors, help text, and buttons. Doing it here looks better than having // the components change after the entire scroll animation has finished. UpdateMenuComponents( m_ScrollDirection ); } } //----------------------------------------------------------------------------- // Purpose: Do all slide animation work here // Input : nPanelIndex - Panel we're currently operating on // nNextPanelIndex - Panel we're going to be moving over //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::PerformSlideAction( int nPanelIndex, int nNextPanelIndex ) { CGameSavePanel *panel = m_SavePanels[ m_PanelIndex[ nPanelIndex ] ]; // Run the actual movement GetAnimationController()->RunAnimationCommand( panel, "xpos", m_PanelXPos[nNextPanelIndex], 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); GetAnimationController()->RunAnimationCommand( panel, "ypos", m_PanelYPos[nNextPanelIndex], 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); // Panel alpha GetAnimationController()->RunAnimationCommand( panel, "alpha", m_PanelAlpha[nNextPanelIndex], 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); } //----------------------------------------------------------------------------- // Purpose: Initiates the scripted scroll and fade effects of all five slotted panels //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::AnimateSelectionPanels( void ) { int idxOffset = 0; int startIdx = SLOT_LEFT; int endIdx = SLOT_RIGHT; // Don't scroll outside the bounds of the panel list if ( m_ScrollCt >= SCROLL_LEFT && m_PanelIndex[SLOT_CENTER] < m_SavePanels.Count() - 1 ) { if ( m_nDeletedPanel != INVALID_INDEX ) { startIdx = SLOT_RIGHT; } idxOffset = -1; endIdx = SLOT_OFFRIGHT; m_ScrollDirection = SCROLL_LEFT; } else if ( m_ScrollCt <= SCROLL_RIGHT && m_PanelIndex[SLOT_CENTER] > 0 ) { idxOffset = 1; startIdx = SLOT_OFFLEFT; m_ScrollDirection = SCROLL_RIGHT; } if ( 0 == idxOffset ) { // Kill the scroll, it's outside the bounds m_ScrollCt = 0; m_bScrolling = false; m_ScrollDirection = SCROLL_NONE; vgui::surface()->PlaySound( "player/suit_denydevice.wav" ); return; } // Should never happen if ( startIdx > endIdx ) return; for ( int i = startIdx; i <= endIdx; ++i ) { // Don't animate the special panel, just skip it if ( m_PanelIndex[i] == m_nDeletedPanel ) continue; int nNextIdx = i+idxOffset; if ( m_PanelIndex[i] != INVALID_INDEX ) { PerformSlideAction( i, nNextIdx ); } } vgui::surface()->PlaySound( "UI/buttonclick.wav" ); // Animate the center background panel GetAnimationController()->RunAnimationCommand( m_pCenterBg, "alpha", 0, 0, m_ScrollSpeed * 0.25f, vgui::AnimationController::INTERPOLATOR_SIMPLESPLINE ); // Scrolling up through chapters, offset is negative m_iSelectedSave -= idxOffset; UpdateFooterOptions(); PostMessage( this, new KeyValues( "FinishScroll" ), m_ScrollSpeed ); } //----------------------------------------------------------------------------- // Purpose: After a scroll, each panel slot holds the index of a panel that has // scrolled to an adjacent slot. This function updates each slot so // it holds the index of the panel that is actually in that slot's position. //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::ShiftPanelIndices( int offset ) { // Shift all the elements over one slot, then calculate what the last slot's index should be. int lastSlot = NUM_SLOTS - 1; // Handle the deletion case if ( m_nDeletedPanel != INVALID_INDEX ) { // Scroll panels in from the right Q_memmove( &m_PanelIndex[SLOT_CENTER], &m_PanelIndex[SLOT_RIGHT], 2* sizeof( m_PanelIndex[SLOT_CENTER] ) ); if ( m_PanelIndex[lastSlot] != INVALID_INDEX ) { int num = m_PanelIndex[ lastSlot ] + 1; if ( IsValidPanel( num ) ) { m_PanelIndex[lastSlot] = num; InitPanelIndexForDisplay( lastSlot ); } else { m_PanelIndex[lastSlot] = INVALID_INDEX; } } } else if ( offset > 0 ) { // Hide the panel that's dropping out of the slots if ( IsValidPanel( m_PanelIndex[0] ) ) { m_SavePanels[ m_PanelIndex[0] ]->SetVisible( false ); } // Scrolled panels to the right, so shift the indices one slot to the left Q_memmove( &m_PanelIndex[0], &m_PanelIndex[1], lastSlot * sizeof( m_PanelIndex[0] ) ); if ( m_PanelIndex[lastSlot] != INVALID_INDEX ) { int num = m_PanelIndex[ lastSlot ] + 1; if ( IsValidPanel( num ) ) { m_PanelIndex[lastSlot] = num; InitPanelIndexForDisplay( lastSlot ); } else { m_PanelIndex[lastSlot] = INVALID_INDEX; } } } else { // Hide the panel that's dropping out of the slots if ( IsValidPanel( m_PanelIndex[lastSlot] ) ) { m_SavePanels[ m_PanelIndex[lastSlot] ]->SetVisible( false ); } // Scrolled panels to the left, so shift the indices one slot to the right Q_memmove( &m_PanelIndex[1], &m_PanelIndex[0], lastSlot * sizeof( m_PanelIndex[0] ) ); if ( m_PanelIndex[0] != INVALID_INDEX ) { int num = m_PanelIndex[0] - 1; if ( IsValidPanel( num ) ) { m_PanelIndex[0] = num; InitPanelIndexForDisplay( 0 ); } else { m_PanelIndex[0] = INVALID_INDEX; } } } } //----------------------------------------------------------------------------- // Purpose: Validates an index into the selection panels vector //----------------------------------------------------------------------------- bool CSaveGameBrowserDialog::IsValidPanel( const int idx ) { if ( idx < 0 || idx >= m_SavePanels.Count() ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: Sets up a panel's properties before it is displayed //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::InitPanelIndexForDisplay( const int idx ) { CGameSavePanel *panel = m_SavePanels[ m_PanelIndex[idx] ]; if ( panel ) { panel->SetPos( m_PanelXPos[idx], m_PanelYPos[idx] ); panel->SetAlpha( m_PanelAlpha[idx] ); panel->SetVisible( true ); panel->SetEnabled( true ); if ( m_PanelAlpha[idx] ) { panel->SetZPos( NUM_SLOTS ); } } } //----------------------------------------------------------------------------- // Purpose: Sets which scroll speed should be used //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::SetFastScroll( bool fast ) { m_ScrollSpeed = fast ? m_ScrollSpeedFast : m_ScrollSpeedSlow; } //----------------------------------------------------------------------------- // Purpose: Checks if a button is being held down, and speeds up the scroll //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::ContinueScrolling( void ) { if ( !GameUI().IsConsoleUI() ) { if ( m_PanelIndex[SLOT_CENTER-1] % 3 ) { ScrollSelectionPanels( m_ScrollDirection ); } return; } if ( m_ButtonPressed == m_ScrollDirection ) { SetFastScroll( true ); ScrollSelectionPanels( m_ScrollDirection ); } else if ( m_ButtonPressed != SCROLL_NONE ) { // The other direction has been pressed - start a slow scroll SetFastScroll( false ); ScrollSelectionPanels( (EScrollDirection)m_ButtonPressed ); } else { SetFastScroll( false ); } } //----------------------------------------------------------------------------- // Purpose: Fade animation has finished, now slide or be done //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::FinishDelete( void ) { // Catch the case where all saves are now gone! if ( m_SavePanels.Count() == 1 ) { m_nDeletedPanel = INVALID_INDEX; m_SavePanels.PurgeAndDeleteElements(); for ( int i = 0; i < NUM_SLOTS; i++ ) { m_PanelIndex[i] = INVALID_INDEX; } LayoutPanels(); return; } EScrollDirection nDirection = ( IsValidPanel( m_nDeletedPanel + 1 ) ) ? SCROLL_LEFT : SCROLL_RIGHT; ScrollSelectionPanels( nDirection ); } //----------------------------------------------------------------------------- // Purpose: Called when a scroll distance of one slot has been completed //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::FinishScroll( void ) { // Fade the center bg panel back in GetAnimationController()->RunAnimationCommand( m_pCenterBg, "alpha", 255, 0, m_ScrollSpeed * 0.25f, vgui::AnimationController::INTERPOLATOR_LINEAR ); ShiftPanelIndices( m_ScrollDirection ); m_bScrolling = false; m_ScrollCt = 0; // End of scroll step PostScroll( m_ScrollDirection ); if ( m_nDeletedPanel != INVALID_INDEX ) { // Find where we're going next int newSave = m_nDeletedPanel; if ( m_SavePanels.IsValidIndex( m_nDeletedPanel + 1 ) == false ) { newSave = m_nDeletedPanel - 1; } // Remove it from our list CGameSavePanel *pPanel = m_SavePanels[ m_nDeletedPanel ]; m_SavePanels.Remove( m_nDeletedPanel ); delete pPanel; // Decrement all the indices to reflect the change for ( int i = 0; i < NUM_SLOTS; i++ ) { if ( m_PanelIndex[i] > m_nDeletedPanel ) m_PanelIndex[i]--; } // Clear the spot and be done with it SetSelectedSaveIndex( newSave ); m_nDeletedPanel = INVALID_INDEX; UpdateMenuComponents( SCROLL_NONE ); } // Size the "autosave" blade if need-be CGameSavePanel *selectedPanel = GetActivePanel(); if ( selectedPanel && selectedPanel->IsAutoSaveType() ) { m_pCenterBg->SetTall( m_nCenterBgTallDefault + 20 ); } else { m_pCenterBg->SetTall( m_nCenterBgTallDefault ); } // Continue scrolling if necessary ContinueScrolling(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::OnClose( void ) { SetControlDisabled( true ); m_KeyRepeat.Reset(); BasePanel()->RunCloseAnimation( "CloseNewGameDialog_OpenMainMenu" ); BaseClass::OnClose(); } //----------------------------------------------------------------------------- // Purpose: Our save games have changed, so layout our panel again //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::RefreshSaveGames( void ) { // Close any pending messages BasePanel()->CloseMessageDialog( DIALOG_STACK_IDX_WARNING ); // Don't leave us in a locked state SetControlDisabled( false ); // Re-scan the saved games ScanSavedGames( m_bFilterAutosaves ); // Re-layout the panels LayoutPanels(); // Run our animation again AnimateDialogStart(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::PerformSelectedAction( void ) { // By default, do nothing m_KeyRepeat.Reset(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::PerformDeletion( void ) { // By default, do nothing m_KeyRepeat.Reset(); } //----------------------------------------------------------------------------- // Purpose: Release our key repeater //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::OnKeyCodeReleased( vgui::KeyCode code ) { m_KeyRepeat.KeyUp( code ); BaseClass::OnKeyCodeReleased( code ); } //----------------------------------------------------------------------------- // Purpose: Update our keypress repeater //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::OnThink( void ) { vgui::KeyCode code = m_KeyRepeat.KeyRepeated(); if ( code ) { OnKeyCodePressed( code ); } BaseClass::OnThink(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::OnKeyCodePressed( vgui::KeyCode code ) { // If the console has UI up, then ignore it if ( BasePanel()->IsWaitingForConsoleUI() ) return; // Inhibit key activity during transitions if ( GetAlpha() != 255 || m_bControlDisabled ) return; m_KeyRepeat.KeyDown( code ); switch( code ) { case KEY_XBUTTON_A: case STEAMCONTROLLER_A: PerformSelectedAction(); break; case KEY_XBUTTON_B: case STEAMCONTROLLER_B: OnClose(); break; case KEY_XBUTTON_X: case STEAMCONTROLLER_X: PerformDeletion(); break; case KEY_XBUTTON_Y: case STEAMCONTROLLER_Y: BasePanel()->OnChangeStorageDevice(); break; // Move the selection up and down case KEY_XSTICK1_LEFT: case KEY_XBUTTON_LEFT: case STEAMCONTROLLER_DPAD_LEFT: ScrollSelectionPanels( SCROLL_RIGHT ); break; case KEY_XSTICK1_RIGHT: case KEY_XBUTTON_RIGHT: case STEAMCONTROLLER_DPAD_RIGHT: ScrollSelectionPanels( SCROLL_LEFT ); break; default: break; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::PaintBackground( void ) { int wide, tall; GetSize( wide, tall ); Color col = GetBgColor(); DrawBox( 0, 0, wide, tall, col, 1.0f ); int y = 32; // draw an inset Color darkColor; darkColor.SetColor( 0.70f * (float)col.r(), 0.70f * (float)col.g(), 0.70f * (float)col.b(), col.a() ); vgui::surface()->DrawSetColor( darkColor ); vgui::surface()->DrawFilledRect( 8, y, wide - 8, tall - 8 ); } //----------------------------------------------------------------------------- // Purpose: Parses the save game info out of the .sav file header //----------------------------------------------------------------------------- bool CSaveGameBrowserDialog::ParseSaveData( char const *pszFileName, char const *pszShortName, SaveGameDescription_t *save ) { char szMapName[SAVEGAME_MAPNAME_LEN]; char szComment[SAVEGAME_COMMENT_LEN]; char szElapsedTime[SAVEGAME_ELAPSED_LEN]; if ( !pszFileName || !pszShortName ) return false; Q_strncpy( save->szShortName, pszShortName, sizeof(save->szShortName) ); Q_strncpy( save->szFileName, pszFileName, sizeof(save->szFileName) ); FileHandle_t fh = g_pFullFileSystem->Open( pszFileName, "rb", "MOD" ); if (fh == FILESYSTEM_INVALID_HANDLE) return false; save->iSize = g_pFullFileSystem->Size( fh ); int readok = SaveReadNameAndComment( fh, szMapName, sizeof(szMapName), szComment, sizeof(szComment) ); g_pFullFileSystem->Close(fh); if ( !readok ) { return false; } 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); int hours = minutes / 60; minutes %= 60; wchar_t wzHours[6]; wchar_t wzMins[4]; wchar_t wzSecs[4]; _snwprintf( wzHours, ARRAYSIZE(wzHours), L"%d", hours ); _snwprintf( wzMins, ARRAYSIZE(wzMins), L"%d", minutes ); _snwprintf( wzSecs, ARRAYSIZE(wzSecs), L"%d", seconds ); wchar_t buf[20]; // reformat if ( hours ) { g_pVGuiLocalize->ConstructString( buf, sizeof( buf ), g_pVGuiLocalize->Find( "#GameUI_LoadDialog_Hr_Min" ), 2, wzHours, wzMins ); } else if ( minutes ) { g_pVGuiLocalize->ConstructString( buf, sizeof( buf ), g_pVGuiLocalize->Find( "#GameUI_LoadDialog_Min_Sec" ), 2, wzMins, wzSecs ); } else { g_pVGuiLocalize->ConstructString( buf, sizeof( buf ), g_pVGuiLocalize->Find( "#GameUI_LoadDialog_Sec" ), 1, wzSecs ); } g_pVGuiLocalize->ConvertUnicodeToANSI( buf, szElapsedTime, sizeof(szElapsedTime) ); // Chop elapsed out of comment. char *pChop = Q_stristr( szComment, " " ); if ( pChop != NULL ) { (*pChop) = '\0'; } } // calculate the file name to print const char *pszType = ""; if (strstr(pszFileName, "quick")) { pszType = "#GameUI_QuickSave"; } else if (strstr(pszFileName, "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(pszFileName); 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; return true; } //----------------------------------------------------------------------------- // Purpose: Update our footer options depending on what we've selected //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::UpdateFooterOptions( void ) { // Do nothing } //----------------------------------------------------------------------------- // Purpose: Sort our games by time //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::SortSaveGames( SaveGameDescription_t *pSaves, unsigned int nNumSaves ) { qsort( pSaves, nNumSaves, sizeof(SaveGameDescription_t), &CBaseSaveGameDialog::SaveGameSortFunc ); } //----------------------------------------------------------------------------- // Purpose: builds save game list from directory //----------------------------------------------------------------------------- void CSaveGameBrowserDialog::ScanSavedGames( bool bIgnoreAutosave ) { // Start with a clean slate m_nUsedStorageSpace = 0; m_bSaveGameIsCorrupt = false; // Clear all known panels I'm holding now m_SavePanels.PurgeAndDeleteElements(); // Reset all indices for ( int i = 0; i < NUM_SLOTS; ++i ) { m_PanelIndex[i] = INVALID_INDEX; } // Clear our list CUtlVector saveGames; // Get the search path char szDirectory[_MAX_PATH]; if ( IsX360() ) Q_snprintf( szDirectory, sizeof( szDirectory ), "%s:/*", COM_GetModDirectory() ); else Q_snprintf( szDirectory, sizeof( szDirectory ), "save/*" ); Q_DefaultExtension( szDirectory, IsX360() ? ".360.sav" : ".sav", sizeof( szDirectory ) ); Q_FixSlashes( szDirectory ); // 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]; if ( IsX360() ) Q_snprintf(szFileName, sizeof( szFileName ), "%s:/%s", COM_GetModDirectory(), pFileName ); else Q_snprintf(szFileName, sizeof( szFileName ), "save/%s", pFileName); Q_FixSlashes( szFileName ); // 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 ) ) { // Add on this file's size to the count m_nUsedStorageSpace += save.iSize; // Always ignore autosave dangerous (they're not considered safe until committed) if ( Q_stristr( save.szShortName, "dangerous" ) ) { pFileName = g_pFullFileSystem->FindNext( handle ); continue; } // If we're ignoring autosaves, skip it here if ( bIgnoreAutosave ) { if ( !Q_stricmp( save.szType, "#GameUI_Autosave" ) ) { pFileName = g_pFullFileSystem->FindNext( handle ); continue; } } saveGames.AddToTail( save ); } pFileName = g_pFullFileSystem->FindNext( handle ); } g_pFullFileSystem->FindClose( handle ); // Sort the save list SortSaveGames( saveGames.Base(), saveGames.Count() ); // Now add them in order for ( int i = 0; i < saveGames.Count(); i++ ) { CGameSavePanel *savePanel = SETUP_PANEL( new CGameSavePanel( this, &saveGames[i] ) ); savePanel->SetVisible( false ); m_SavePanels.AddToTail( savePanel ); } // Notify derived classes that save games are done being scanned OnDoneScanningSaveGames(); // Always start with the first panel (as we're sorted in a specific order) SetSelectedSaveIndex( 0 ); } //----------------------------------------------------------------------------- // Purpose: Return the currently selected panel //----------------------------------------------------------------------------- CGameSavePanel *CSaveGameBrowserDialog::GetActivePanel( void ) { if ( IsValidPanel( m_iSelectedSave ) == false ) return NULL; return m_SavePanels[ m_iSelectedSave ]; }