Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

552 lines
16 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "BaseSaveGameDialog.h"
  7. #include "FileSystem.h"
  8. #include "savegame_version.h"
  9. #include "vgui_controls/PanelListPanel.h"
  10. #include "vgui_controls/Label.h"
  11. #include "vgui_controls/ImagePanel.h"
  12. #include "vgui_controls/Button.h"
  13. #include "vgui_controls/tgaimagepanel.h"
  14. #include "tier1/utlbuffer.h"
  15. #include "tier2/resourceprecacher.h"
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include "FileSystem.h"
  19. #include "MouseMessageForwardingPanel.h"
  20. // memdbgon must be the last include file in a .cpp file!!!
  21. #include "tier0/memdbgon.h"
  22. using namespace vgui;
  23. #define TGA_IMAGE_PANEL_WIDTH 180
  24. #define TGA_IMAGE_PANEL_HEIGHT 100
  25. #define MAX_LISTED_SAVE_GAMES 128
  26. // Dead, basemod ui trumps...
  27. #if 0
  28. PRECACHE_REGISTER_BEGIN( GLOBAL, BaseSaveGameDialog )
  29. PRECACHE( MATERIAL, "vgui/resource/autosave.vmt" )
  30. PRECACHE_REGISTER_END()
  31. #endif
  32. //-----------------------------------------------------------------------------
  33. // Purpose: Describes the layout of a same game pic
  34. //-----------------------------------------------------------------------------
  35. class CSaveGamePanel : public vgui::EditablePanel
  36. {
  37. DECLARE_CLASS_SIMPLE( CSaveGamePanel, vgui::EditablePanel );
  38. public:
  39. CSaveGamePanel( PanelListPanel *parent, const char *name, int saveGameListItemID ) : BaseClass( parent, name )
  40. {
  41. m_iSaveGameListItemID = saveGameListItemID;
  42. m_pParent = parent;
  43. m_pSaveGameImage = new CTGAImagePanel( this, "SaveGameImage" );
  44. m_pAutoSaveImage = new ImagePanel( this, "AutoSaveImage" );
  45. m_pSaveGameScreenshotBackground = new ImagePanel( this, "SaveGameScreenshotBackground" );
  46. m_pChapterLabel = new Label( this, "ChapterLabel", "" );
  47. m_pTypeLabel = new Label( this, "TypeLabel", "" );
  48. m_pElapsedTimeLabel = new Label( this, "ElapsedTimeLabel", "" );
  49. m_pFileTimeLabel = new Label( this, "FileTimeLabel", "" );
  50. CMouseMessageForwardingPanel *panel = new CMouseMessageForwardingPanel(this, NULL);
  51. panel->SetZPos(2);
  52. SetSize( 200, 140 );
  53. LoadControlSettings( "resource/SaveGamePanel.res" );
  54. m_FillColor = m_pSaveGameScreenshotBackground->GetFillColor();
  55. }
  56. void SetSaveGameInfo( SaveGameDescription_t &save )
  57. {
  58. // set the bitmap to display
  59. char tga[_MAX_PATH];
  60. Q_strncpy( tga, save.szFileName, sizeof(tga) );
  61. char *ext = strstr( tga, ".sav" );
  62. if ( ext )
  63. {
  64. strcpy( ext, ".tga" );
  65. }
  66. // If a TGA file exists then it is a user created savegame
  67. if ( g_pFullFileSystem->FileExists( tga ) )
  68. {
  69. m_pSaveGameImage->SetTGAFilename( tga );
  70. }
  71. // If there is no TGA then it is either an autosave or the user TGA file has been deleted
  72. else
  73. {
  74. m_pSaveGameImage->SetVisible( false );
  75. m_pAutoSaveImage->SetVisible( true );
  76. m_pAutoSaveImage->SetImage( "resource\\autosave" );
  77. }
  78. // set the title text
  79. m_pChapterLabel->SetText( save.szComment );
  80. // type
  81. SetControlString( "TypeLabel", save.szType );
  82. SetControlString( "ElapsedTimeLabel", save.szElapsedTime );
  83. SetControlString( "FileTimeLabel", save.szFileTime );
  84. }
  85. MESSAGE_FUNC_INT( OnPanelSelected, "PanelSelected", state )
  86. {
  87. if ( state )
  88. {
  89. // set the text color to be orange, and the pic border to be orange
  90. m_pSaveGameScreenshotBackground->SetFillColor( m_SelectedColor );
  91. m_pChapterLabel->SetFgColor( m_SelectedColor );
  92. m_pTypeLabel->SetFgColor( m_SelectedColor );
  93. m_pElapsedTimeLabel->SetFgColor( m_SelectedColor );
  94. m_pFileTimeLabel->SetFgColor( m_SelectedColor );
  95. }
  96. else
  97. {
  98. m_pSaveGameScreenshotBackground->SetFillColor( m_FillColor );
  99. m_pChapterLabel->SetFgColor( m_TextColor );
  100. m_pTypeLabel->SetFgColor( m_TextColor );
  101. m_pElapsedTimeLabel->SetFgColor( m_TextColor );
  102. m_pFileTimeLabel->SetFgColor( m_TextColor );
  103. }
  104. PostMessage( m_pParent->GetVParent(), new KeyValues("PanelSelected") );
  105. }
  106. virtual void OnMousePressed( vgui::MouseCode code )
  107. {
  108. m_pParent->SetSelectedPanel( this );
  109. }
  110. virtual void ApplySchemeSettings( IScheme *pScheme )
  111. {
  112. m_TextColor = pScheme->GetColor( "NewGame.TextColor", Color(255, 255, 255, 255) );
  113. m_SelectedColor = pScheme->GetColor( "NewGame.SelectionColor", Color(255, 255, 255, 255) );
  114. BaseClass::ApplySchemeSettings( pScheme );
  115. }
  116. virtual void OnMouseDoublePressed( vgui::MouseCode code )
  117. {
  118. // call the panel
  119. OnMousePressed( code );
  120. PostMessage( m_pParent->GetParent(), new KeyValues("Command", "command", "loadsave") );
  121. }
  122. int GetSaveGameListItemID()
  123. {
  124. return m_iSaveGameListItemID;
  125. }
  126. private:
  127. vgui::PanelListPanel *m_pParent;
  128. vgui::Label *m_pChapterLabel;
  129. CTGAImagePanel *m_pSaveGameImage;
  130. ImagePanel *m_pAutoSaveImage;
  131. // things to change color when the selection changes
  132. ImagePanel *m_pSaveGameScreenshotBackground;
  133. Label *m_pTypeLabel;
  134. Label *m_pElapsedTimeLabel;
  135. Label *m_pFileTimeLabel;
  136. Color m_TextColor, m_FillColor, m_SelectedColor;
  137. int m_iSaveGameListItemID;
  138. };
  139. //-----------------------------------------------------------------------------
  140. // Purpose: Constructor
  141. //-----------------------------------------------------------------------------
  142. CBaseSaveGameDialog::CBaseSaveGameDialog( vgui::Panel *parent, const char *name ) : BaseClass( parent, name )
  143. {
  144. CreateSavedGamesList();
  145. ScanSavedGames();
  146. new vgui::Button( this, "loadsave", "" );
  147. SetControlEnabled( "loadsave", false );
  148. }
  149. //-----------------------------------------------------------------------------
  150. // Purpose: Creates the load game display list
  151. //-----------------------------------------------------------------------------
  152. void CBaseSaveGameDialog::CreateSavedGamesList()
  153. {
  154. m_pGameList = new vgui::PanelListPanel( this, "listpanel_loadgame" );
  155. m_pGameList->SetFirstColumnWidth( 0 );
  156. }
  157. //-----------------------------------------------------------------------------
  158. // Purpose: returns the save file name of the selected item
  159. //-----------------------------------------------------------------------------
  160. int CBaseSaveGameDialog::GetSelectedItemSaveIndex()
  161. {
  162. CSaveGamePanel *panel = dynamic_cast<CSaveGamePanel *>(m_pGameList->GetSelectedPanel());
  163. if ( panel )
  164. {
  165. // find the panel in the list
  166. for ( int i = 0; i < m_SaveGames.Count(); i++ )
  167. {
  168. if ( i == panel->GetSaveGameListItemID() )
  169. {
  170. return i;
  171. }
  172. }
  173. }
  174. return m_SaveGames.InvalidIndex();
  175. }
  176. //-----------------------------------------------------------------------------
  177. // Purpose: builds save game list from directory
  178. //-----------------------------------------------------------------------------
  179. void CBaseSaveGameDialog::ScanSavedGames()
  180. {
  181. // populate list box with all saved games on record:
  182. char szDirectory[_MAX_PATH];
  183. Q_snprintf( szDirectory, sizeof( szDirectory ), "save/*.sav" );
  184. // clear the current list
  185. m_pGameList->DeleteAllItems();
  186. m_SaveGames.RemoveAll();
  187. // iterate the saved files
  188. FileFindHandle_t handle;
  189. const char *pFileName = g_pFullFileSystem->FindFirst( szDirectory, &handle );
  190. while (pFileName)
  191. {
  192. if ( !Q_strnicmp(pFileName, "HLSave", strlen( "HLSave" ) ) )
  193. {
  194. pFileName = g_pFullFileSystem->FindNext( handle );
  195. continue;
  196. }
  197. char szFileName[_MAX_PATH];
  198. Q_snprintf(szFileName, sizeof( szFileName ), "save/%s", pFileName);
  199. // Only load save games from the current mod's save dir
  200. if( !g_pFullFileSystem->FileExists( szFileName, "MOD" ) )
  201. {
  202. pFileName = g_pFullFileSystem->FindNext( handle );
  203. continue;
  204. }
  205. SaveGameDescription_t save;
  206. if ( ParseSaveData( szFileName, pFileName, save ) )
  207. {
  208. m_SaveGames.AddToTail( save );
  209. }
  210. pFileName = g_pFullFileSystem->FindNext( handle );
  211. }
  212. g_pFullFileSystem->FindClose( handle );
  213. // notify derived classes that save games are being scanned (so they can insert their own)
  214. OnScanningSaveGames();
  215. // sort the save list
  216. qsort( m_SaveGames.Base(), m_SaveGames.Count(), sizeof(SaveGameDescription_t), &SaveGameSortFunc );
  217. // add to the list
  218. for ( int saveIndex = 0; saveIndex < m_SaveGames.Count() && saveIndex < MAX_LISTED_SAVE_GAMES; saveIndex++ )
  219. {
  220. // add the item to the panel
  221. AddSaveGameItemToList( saveIndex );
  222. }
  223. // display a message if there are no save games
  224. if ( !m_SaveGames.Count() )
  225. {
  226. vgui::Label *pNoSavesLabel = SETUP_PANEL(new Label(m_pGameList, "NoSavesLabel", "#GameUI_NoSaveGamesToDisplay"));
  227. pNoSavesLabel->SetTextColorState(vgui::Label::CS_DULL);
  228. m_pGameList->AddItem( NULL, pNoSavesLabel );
  229. }
  230. SetControlEnabled( "loadsave", false );
  231. SetControlEnabled( "delete", false );
  232. }
  233. //-----------------------------------------------------------------------------
  234. // Purpose: Adds an item to the list
  235. //-----------------------------------------------------------------------------
  236. void CBaseSaveGameDialog::AddSaveGameItemToList( int saveIndex )
  237. {
  238. // create the new panel and add to the list
  239. CSaveGamePanel *saveGamePanel = new CSaveGamePanel( m_pGameList, "SaveGamePanel", saveIndex );
  240. saveGamePanel->SetSaveGameInfo( m_SaveGames[saveIndex] );
  241. m_pGameList->AddItem( NULL, saveGamePanel );
  242. }
  243. //-----------------------------------------------------------------------------
  244. // Purpose: Parses the save game info out of the .sav file header
  245. //-----------------------------------------------------------------------------
  246. bool CBaseSaveGameDialog::ParseSaveData( char const *pszFileName, char const *pszShortName, SaveGameDescription_t &save )
  247. {
  248. char szMapName[SAVEGAME_MAPNAME_LEN];
  249. char szComment[SAVEGAME_COMMENT_LEN];
  250. char szElapsedTime[SAVEGAME_ELAPSED_LEN];
  251. if ( !pszFileName || !pszShortName )
  252. return false;
  253. Q_strncpy( save.szShortName, pszShortName, sizeof(save.szShortName) );
  254. Q_strncpy( save.szFileName, pszFileName, sizeof(save.szFileName) );
  255. FileHandle_t fh = g_pFullFileSystem->Open( pszFileName, "rb", "MOD" );
  256. if (fh == FILESYSTEM_INVALID_HANDLE)
  257. return false;
  258. int readok = SaveReadNameAndComment( fh, szMapName, szComment );
  259. g_pFullFileSystem->Close(fh);
  260. if ( !readok )
  261. {
  262. return false;
  263. }
  264. Q_strncpy( save.szMapName, szMapName, sizeof(save.szMapName) );
  265. // Elapsed time is the last 6 characters in comment. (mmm:ss)
  266. int i;
  267. i = strlen( szComment );
  268. Q_strncpy( szElapsedTime, "??", sizeof( szElapsedTime ) );
  269. if (i >= 6)
  270. {
  271. Q_strncpy( szElapsedTime, (char *)&szComment[i - 6], 7 );
  272. szElapsedTime[6] = '\0';
  273. // parse out
  274. int minutes = atoi( szElapsedTime );
  275. int seconds = atoi( szElapsedTime + 4);
  276. // reformat
  277. if ( minutes )
  278. {
  279. Q_snprintf( szElapsedTime, sizeof(szElapsedTime), "%d %s %d seconds", minutes, minutes > 1 ? "minutes" : "minute", seconds );
  280. }
  281. else
  282. {
  283. Q_snprintf( szElapsedTime, sizeof(szElapsedTime), "%d seconds", seconds );
  284. }
  285. // Chop elapsed out of comment.
  286. int n;
  287. n = i - 6;
  288. szComment[n] = '\0';
  289. n--;
  290. // Strip back the spaces at the end.
  291. while ((n >= 1) &&
  292. szComment[n] &&
  293. szComment[n] == ' ')
  294. {
  295. szComment[n--] = '\0';
  296. }
  297. }
  298. // calculate the file name to print
  299. const char *pszType = "";
  300. if (strstr(pszFileName, "quick"))
  301. {
  302. pszType = "#GameUI_QuickSave";
  303. }
  304. else if (strstr(pszFileName, "autosave"))
  305. {
  306. pszType = "#GameUI_AutoSave";
  307. }
  308. Q_strncpy( save.szType, pszType, sizeof(save.szType) );
  309. Q_strncpy( save.szComment, szComment, sizeof(save.szComment) );
  310. Q_strncpy( save.szElapsedTime, szElapsedTime, sizeof(save.szElapsedTime) );
  311. // Now get file time stamp.
  312. long fileTime = g_pFullFileSystem->GetFileTime(pszFileName);
  313. char szFileTime[32];
  314. g_pFullFileSystem->FileTimeToString(szFileTime, sizeof(szFileTime), fileTime);
  315. char *newline = strstr(szFileTime, "\n");
  316. if (newline)
  317. {
  318. *newline = 0;
  319. }
  320. Q_strncpy( save.szFileTime, szFileTime, sizeof(save.szFileTime) );
  321. save.iTimestamp = fileTime;
  322. return true;
  323. }
  324. //-----------------------------------------------------------------------------
  325. // Purpose: timestamp sort function for savegames
  326. //-----------------------------------------------------------------------------
  327. int CBaseSaveGameDialog::SaveGameSortFunc( const void *lhs, const void *rhs )
  328. {
  329. const SaveGameDescription_t *s1 = (const SaveGameDescription_t *)lhs;
  330. const SaveGameDescription_t *s2 = (const SaveGameDescription_t *)rhs;
  331. if (s1->iTimestamp < s2->iTimestamp)
  332. return 1;
  333. else if (s1->iTimestamp > s2->iTimestamp)
  334. return -1;
  335. // timestamps are equal, so just sort by filename
  336. return strcmp(s1->szFileName, s2->szFileName);
  337. }
  338. #define MAKEID(d,c,b,a) ( ((int)(a) << 24) | ((int)(b) << 16) | ((int)(c) << 8) | ((int)(d)) )
  339. int SaveReadNameAndComment( FileHandle_t f, char *name, char *comment )
  340. {
  341. int i, tag, size, tokenSize, tokenCount;
  342. char *pSaveData, *pFieldName, **pTokenList;
  343. g_pFullFileSystem->Read( &tag, sizeof(int), f );
  344. if ( tag != MAKEID('J','S','A','V') )
  345. {
  346. return 0;
  347. }
  348. g_pFullFileSystem->Read( &tag, sizeof(int), f );
  349. if ( tag != SAVEGAME_VERSION ) // Enforce version for now
  350. {
  351. return 0;
  352. }
  353. name[0] = '\0';
  354. comment[0] = '\0';
  355. g_pFullFileSystem->Read( &size, sizeof(int), f );
  356. g_pFullFileSystem->Read( &tokenCount, sizeof(int), f ); // These two ints are the token list
  357. g_pFullFileSystem->Read( &tokenSize, sizeof(int), f );
  358. size += tokenSize;
  359. // Sanity Check.
  360. if ( tokenCount < 0 || tokenCount > 1024*1024*32 )
  361. {
  362. return 0;
  363. }
  364. if ( tokenSize < 0 || tokenSize > 1024*1024*32 )
  365. {
  366. return 0;
  367. }
  368. pSaveData = (char *)new char[size];
  369. g_pFullFileSystem->Read(pSaveData, size, f);
  370. int nNumberOfFields;
  371. char *pData;
  372. int nFieldSize;
  373. pData = pSaveData;
  374. // Allocate a table for the strings, and parse the table
  375. if ( tokenSize > 0 )
  376. {
  377. pTokenList = new char *[tokenCount];
  378. // Make sure the token strings pointed to by the pToken hashtable.
  379. for( i=0; i<tokenCount; i++ )
  380. {
  381. pTokenList[i] = *pData ? pData : NULL; // Point to each string in the pToken table
  382. while( *pData++ ); // Find next token (after next null)
  383. }
  384. }
  385. else
  386. pTokenList = NULL;
  387. // short, short (size, index of field name)
  388. nFieldSize = *(short *)pData;
  389. pData += sizeof(short);
  390. pFieldName = pTokenList[ *(short *)pData ];
  391. if (stricmp(pFieldName, "GameHeader"))
  392. {
  393. delete[] pSaveData;
  394. return 0;
  395. };
  396. // int (fieldcount)
  397. pData += sizeof(short);
  398. nNumberOfFields = *(int*)pData;
  399. pData += nFieldSize;
  400. // Each field is a short (size), short (index of name), binary string of "size" bytes (data)
  401. for (i = 0; i < nNumberOfFields; i++)
  402. {
  403. // Data order is:
  404. // Size
  405. // szName
  406. // Actual Data
  407. nFieldSize = *(short *)pData;
  408. pData += sizeof(short);
  409. pFieldName = pTokenList[ *(short *)pData ];
  410. pData += sizeof(short);
  411. if (!stricmp(pFieldName, "comment"))
  412. {
  413. Q_strncpy(comment, pData, nFieldSize);
  414. }
  415. else if (!stricmp(pFieldName, "mapName"))
  416. {
  417. Q_strncpy(name, pData, nFieldSize);
  418. };
  419. // Move to Start of next field.
  420. pData += nFieldSize;
  421. };
  422. // Delete the string table we allocated
  423. delete[] pTokenList;
  424. delete[] pSaveData;
  425. if (strlen(name) > 0 && strlen(comment) > 0)
  426. return 1;
  427. return 0;
  428. }
  429. //-----------------------------------------------------------------------------
  430. // Purpose: deletes an existing save game
  431. //-----------------------------------------------------------------------------
  432. void CBaseSaveGameDialog::DeleteSaveGame( const char *fileName )
  433. {
  434. if ( !fileName || !fileName[0] )
  435. return;
  436. // delete the save game file
  437. g_pFullFileSystem->RemoveFile( fileName, "MOD" );
  438. // delete the associated tga
  439. char tga[_MAX_PATH];
  440. Q_strncpy( tga, fileName, sizeof(tga) );
  441. char *ext = strstr( tga, ".sav" );
  442. if ( ext )
  443. {
  444. strcpy( ext, ".tga" );
  445. }
  446. g_pFullFileSystem->RemoveFile( tga, "MOD" );
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Purpose: One item has been selected
  450. //-----------------------------------------------------------------------------
  451. void CBaseSaveGameDialog::OnPanelSelected()
  452. {
  453. SetControlEnabled( "loadsave", true );
  454. SetControlEnabled( "delete", true );
  455. }