summaryrefslogtreecommitdiff
path: root/gameui/BaseSaveGameDialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gameui/BaseSaveGameDialog.cpp')
-rw-r--r--gameui/BaseSaveGameDialog.cpp668
1 files changed, 668 insertions, 0 deletions
diff --git a/gameui/BaseSaveGameDialog.cpp b/gameui/BaseSaveGameDialog.cpp
new file mode 100644
index 0000000..99f7fc1
--- /dev/null
+++ b/gameui/BaseSaveGameDialog.cpp
@@ -0,0 +1,668 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "BaseSaveGameDialog.h"
+#include "filesystem.h"
+#include "savegame_version.h"
+#include "vgui_controls/PanelListPanel.h"
+#include "vgui_controls/Label.h"
+#include "vgui_controls/ImagePanel.h"
+#include "vgui_controls/Button.h"
+#include "tier1/utlbuffer.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "filesystem.h"
+
+#include "MouseMessageForwardingPanel.h"
+#include "TGAImagePanel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+using namespace vgui;
+
+#define TGA_IMAGE_PANEL_WIDTH 180
+#define TGA_IMAGE_PANEL_HEIGHT 100
+
+#define MAX_LISTED_SAVE_GAMES 128
+
+//-----------------------------------------------------------------------------
+// Purpose: Describes the layout of a same game pic
+//-----------------------------------------------------------------------------
+class CSaveGamePanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CSaveGamePanel, vgui::EditablePanel );
+public:
+ CSaveGamePanel( PanelListPanel *parent, const char *name, int saveGameListItemID ) : BaseClass( parent, name )
+ {
+ m_iSaveGameListItemID = saveGameListItemID;
+ m_pParent = parent;
+ m_pSaveGameImage = new CTGAImagePanel( this, "SaveGameImage" );
+ m_pAutoSaveImage = new ImagePanel( this, "AutoSaveImage" );
+ m_pSaveGameScreenshotBackground = new ImagePanel( this, "SaveGameScreenshotBackground" );
+ m_pChapterLabel = new Label( this, "ChapterLabel", "" );
+ m_pTypeLabel = new Label( this, "TypeLabel", "" );
+ m_pElapsedTimeLabel = new Label( this, "ElapsedTimeLabel", "" );
+ m_pFileTimeLabel = new Label( this, "FileTimeLabel", "" );
+
+ CMouseMessageForwardingPanel *panel = new CMouseMessageForwardingPanel(this, NULL);
+ panel->SetZPos(2);
+
+ SetSize( 200, 140 );
+
+ LoadControlSettings( "resource/SaveGamePanel.res" );
+
+ m_FillColor = m_pSaveGameScreenshotBackground->GetFillColor();
+ }
+
+ void SetSaveGameInfo( SaveGameDescription_t &save )
+ {
+ // set the bitmap to display
+ char tga[_MAX_PATH];
+ Q_strncpy( tga, save.szFileName, sizeof(tga) );
+ char *ext = strstr( tga, ".sav" );
+ if ( ext )
+ {
+ strcpy( ext, ".tga" );
+ }
+
+ // If a TGA file exists then it is a user created savegame
+ if ( g_pFullFileSystem->FileExists( tga ) )
+ {
+ m_pSaveGameImage->SetTGA( tga );
+ }
+ // If there is no TGA then it is either an autosave or the user TGA file has been deleted
+ else
+ {
+ m_pSaveGameImage->SetVisible( false );
+ m_pAutoSaveImage->SetVisible( true );
+ m_pAutoSaveImage->SetImage( "resource\\autosave" );
+ }
+
+ // set the title text
+ m_pChapterLabel->SetText( save.szComment );
+
+ // type
+ SetControlString( "TypeLabel", save.szType );
+ SetControlString( "ElapsedTimeLabel", save.szElapsedTime );
+ SetControlString( "FileTimeLabel", save.szFileTime );
+ }
+
+ MESSAGE_FUNC_INT( OnPanelSelected, "PanelSelected", state )
+ {
+ if ( state )
+ {
+ // set the text color to be orange, and the pic border to be orange
+ m_pSaveGameScreenshotBackground->SetFillColor( m_SelectedColor );
+ m_pChapterLabel->SetFgColor( m_SelectedColor );
+ m_pTypeLabel->SetFgColor( m_SelectedColor );
+ m_pElapsedTimeLabel->SetFgColor( m_SelectedColor );
+ m_pFileTimeLabel->SetFgColor( m_SelectedColor );
+ }
+ else
+ {
+ m_pSaveGameScreenshotBackground->SetFillColor( m_FillColor );
+ m_pChapterLabel->SetFgColor( m_TextColor );
+ m_pTypeLabel->SetFgColor( m_TextColor );
+ m_pElapsedTimeLabel->SetFgColor( m_TextColor );
+ m_pFileTimeLabel->SetFgColor( m_TextColor );
+ }
+
+ PostMessage( m_pParent->GetVParent(), new KeyValues("PanelSelected") );
+ }
+
+ virtual void OnMousePressed( vgui::MouseCode code )
+ {
+ m_pParent->SetSelectedPanel( this );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ m_TextColor = pScheme->GetColor( "NewGame.TextColor", Color(255, 255, 255, 255) );
+ m_SelectedColor = pScheme->GetColor( "NewGame.SelectionColor", Color(255, 255, 255, 255) );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void OnMouseDoublePressed( vgui::MouseCode code )
+ {
+ // call the panel
+ OnMousePressed( code );
+ PostMessage( m_pParent->GetParent(), new KeyValues("Command", "command", "loadsave") );
+ }
+
+ int GetSaveGameListItemID()
+ {
+ return m_iSaveGameListItemID;
+ }
+
+private:
+ vgui::PanelListPanel *m_pParent;
+ vgui::Label *m_pChapterLabel;
+ CTGAImagePanel *m_pSaveGameImage;
+ ImagePanel *m_pAutoSaveImage;
+
+ // things to change color when the selection changes
+ ImagePanel *m_pSaveGameScreenshotBackground;
+ Label *m_pTypeLabel;
+ Label *m_pElapsedTimeLabel;
+ Label *m_pFileTimeLabel;
+ Color m_TextColor, m_FillColor, m_SelectedColor;
+
+ int m_iSaveGameListItemID;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CBaseSaveGameDialog::CBaseSaveGameDialog( vgui::Panel *parent, const char *name ) : BaseClass( parent, name )
+{
+ CreateSavedGamesList();
+ ScanSavedGames();
+
+ m_pLoadButton = new vgui::Button( this, "loadsave", "" );
+ SetControlEnabled( "loadsave", false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates the load game display list
+//-----------------------------------------------------------------------------
+void CBaseSaveGameDialog::CreateSavedGamesList()
+{
+ m_pGameList = new vgui::PanelListPanel( this, "listpanel_loadgame" );
+ m_pGameList->SetFirstColumnWidth( 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns the save file name of the selected item
+//-----------------------------------------------------------------------------
+int CBaseSaveGameDialog::GetSelectedItemSaveIndex()
+{
+ CSaveGamePanel *panel = dynamic_cast<CSaveGamePanel *>(m_pGameList->GetSelectedPanel());
+ if ( panel )
+ {
+ // find the panel in the list
+ for ( int i = 0; i < m_SaveGames.Count(); i++ )
+ {
+ if ( i == panel->GetSaveGameListItemID() )
+ {
+ return i;
+ }
+ }
+ }
+ return m_SaveGames.InvalidIndex();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: builds save game list from directory
+//-----------------------------------------------------------------------------
+void CBaseSaveGameDialog::ScanSavedGames()
+{
+ // populate list box with all saved games on record:
+ char szDirectory[_MAX_PATH];
+ Q_snprintf( szDirectory, sizeof( szDirectory ), "save/*.sav" );
+
+ // clear the current list
+ m_pGameList->DeleteAllItems();
+ m_SaveGames.RemoveAll();
+
+ // 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_SaveGames.AddToTail( save );
+ }
+
+ pFileName = g_pFullFileSystem->FindNext( handle );
+ }
+
+ g_pFullFileSystem->FindClose( handle );
+
+ // notify derived classes that save games are being scanned (so they can insert their own)
+ OnScanningSaveGames();
+
+ // sort the save list
+ qsort( m_SaveGames.Base(), m_SaveGames.Count(), sizeof(SaveGameDescription_t), &SaveGameSortFunc );
+
+ // add to the list
+ for ( int saveIndex = 0; saveIndex < m_SaveGames.Count() && saveIndex < MAX_LISTED_SAVE_GAMES; saveIndex++ )
+ {
+ // add the item to the panel
+ AddSaveGameItemToList( saveIndex );
+ }
+
+ // display a message if there are no save games
+ if ( !m_SaveGames.Count() )
+ {
+ vgui::Label *pNoSavesLabel = SETUP_PANEL(new Label(m_pGameList, "NoSavesLabel", "#GameUI_NoSaveGamesToDisplay"));
+ pNoSavesLabel->SetTextColorState(vgui::Label::CS_DULL);
+ m_pGameList->AddItem( NULL, pNoSavesLabel );
+ }
+
+ SetControlEnabled( "loadsave", false );
+ SetControlEnabled( "delete", false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds an item to the list
+//-----------------------------------------------------------------------------
+void CBaseSaveGameDialog::AddSaveGameItemToList( int saveIndex )
+{
+ // create the new panel and add to the list
+ CSaveGamePanel *saveGamePanel = new CSaveGamePanel( m_pGameList, "SaveGamePanel", saveIndex );
+ saveGamePanel->SetSaveGameInfo( m_SaveGames[saveIndex] );
+ m_pGameList->AddItem( NULL, saveGamePanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Parses the save game info out of the .sav file header
+//-----------------------------------------------------------------------------
+bool CBaseSaveGameDialog::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;
+
+ int readok = SaveReadNameAndComment( fh, szMapName, ARRAYSIZE(szMapName), szComment, ARRAYSIZE(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);
+
+ // 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(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;
+}
+
+void CBaseSaveGameDialog::OnKeyCodeTyped( vgui::KeyCode code )
+{
+ if ( code == KEY_ESCAPE )
+ {
+ OnCommand( "Close" );
+ return;
+ }
+
+ BaseClass::OnKeyCodeTyped( code );
+}
+
+void CBaseSaveGameDialog::OnKeyCodePressed( vgui::KeyCode code )
+{
+ if ( code == KEY_XBUTTON_B )
+ {
+ OnCommand( "Close" );
+ return;
+ }
+ else if ( code == KEY_XSTICK1_DOWN ||
+ code == KEY_XSTICK2_DOWN ||
+ code == KEY_XBUTTON_DOWN ||
+ code == KEY_DOWN )
+ {
+ if ( m_pGameList->GetItemCount() )
+ {
+ Panel *pSelectedPanel = m_pGameList->GetSelectedPanel();
+ if ( !pSelectedPanel )
+ {
+ m_pGameList->SetSelectedPanel( m_pGameList->GetItemPanel( m_pGameList->FirstItem() ) );
+ m_pGameList->ScrollToItem( m_pGameList->FirstItem() );
+ return;
+ }
+ else
+ {
+ int nNextPanelID = m_pGameList->FirstItem();
+ while ( nNextPanelID != m_pGameList->InvalidItemID() )
+ {
+ if ( m_pGameList->GetItemPanel( nNextPanelID ) == pSelectedPanel )
+ {
+ nNextPanelID = m_pGameList->NextItem( nNextPanelID );
+ if ( nNextPanelID != m_pGameList->InvalidItemID() )
+ {
+ m_pGameList->SetSelectedPanel( m_pGameList->GetItemPanel( nNextPanelID ) );
+ m_pGameList->ScrollToItem( nNextPanelID );
+ return;
+ }
+
+ break;
+ }
+
+ nNextPanelID = m_pGameList->NextItem( nNextPanelID );
+ }
+ }
+ }
+ }
+ else if ( code == KEY_XSTICK1_UP ||
+ code == KEY_XSTICK2_UP ||
+ code == KEY_XBUTTON_UP ||
+ code == KEY_UP )
+ {
+ if ( m_pGameList->GetItemCount() )
+ {
+ Panel *pSelectedPanel = m_pGameList->GetSelectedPanel();
+ if ( !pSelectedPanel )
+ {
+ m_pGameList->SetSelectedPanel( m_pGameList->GetItemPanel( m_pGameList->FirstItem() ) );
+ m_pGameList->ScrollToItem( m_pGameList->FirstItem() );
+ return;
+ }
+ else
+ {
+ int nNextPanelID = m_pGameList->FirstItem();
+ if ( m_pGameList->GetItemPanel( nNextPanelID ) != pSelectedPanel )
+ {
+ while ( nNextPanelID != m_pGameList->InvalidItemID() )
+ {
+ int nOldPanelID = nNextPanelID;
+ nNextPanelID = m_pGameList->NextItem( nNextPanelID );
+
+ if ( nNextPanelID != m_pGameList->InvalidItemID() )
+ {
+ if ( m_pGameList->GetItemPanel( nNextPanelID ) == pSelectedPanel )
+ {
+ m_pGameList->SetSelectedPanel( m_pGameList->GetItemPanel( nOldPanelID ) );
+ m_pGameList->ScrollToItem( nOldPanelID );
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else if ( code == KEY_ENTER || code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
+ {
+ Panel *pSelectedPanel = m_pGameList->GetSelectedPanel();
+ if ( pSelectedPanel )
+ {
+ if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
+ {
+ ConVarRef var( "joystick" );
+ if ( var.IsValid() && !var.GetBool() )
+ {
+ var.SetValue( true );
+ }
+
+ ConVarRef var2( "hud_fastswitch" );
+ if ( var2.IsValid() && var2.GetInt() != 2 )
+ {
+ var2.SetValue( 2 );
+ }
+ }
+
+ m_pLoadButton->DoClick();
+ return;
+ }
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: timestamp sort function for savegames
+//-----------------------------------------------------------------------------
+int CBaseSaveGameDialog::SaveGameSortFunc( 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);
+}
+
+#define MAKEID(d,c,b,a) ( ((int)(a) << 24) | ((int)(b) << 16) | ((int)(c) << 8) | ((int)(d)) )
+
+int 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;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: deletes an existing save game
+//-----------------------------------------------------------------------------
+void CBaseSaveGameDialog::DeleteSaveGame( const char *fileName )
+{
+ if ( !fileName || !fileName[0] )
+ return;
+
+ // delete the save game file
+ g_pFullFileSystem->RemoveFile( fileName, "MOD" );
+
+ // delete the associated tga
+ char tga[_MAX_PATH];
+ Q_strncpy( tga, fileName, sizeof(tga) );
+ char *ext = strstr( tga, ".sav" );
+ if ( ext )
+ {
+ strcpy( ext, ".tga" );
+ }
+ g_pFullFileSystem->RemoveFile( tga, "MOD" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: One item has been selected
+//-----------------------------------------------------------------------------
+void CBaseSaveGameDialog::OnPanelSelected()
+{
+ SetControlEnabled( "loadsave", true );
+ SetControlEnabled( "delete", true );
+}
+
+