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.

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