GameState.cpp 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // FILE: GameState.cpp ////////////////////////////////////////////////////////////////////////////
  24. // Author: Colin Day, September 2002
  25. // Desc: Game state singleton from which to load and save the game state
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h"
  29. #include "Common/File.h"
  30. #include "Common/FileSystem.h"
  31. #include "Common/GameEngine.h"
  32. #include "Common/GameState.h"
  33. #include "Common/GameStateMap.h"
  34. #include "Common/LatchRestore.h"
  35. #include "Common/MapObject.h"
  36. #include "Common/PlayerList.h"
  37. #include "Common/RandomValue.h"
  38. #include "Common/Radar.h"
  39. #include "Common/Team.h"
  40. #include "Common/WellKnownKeys.h"
  41. #include "Common/XferLoad.h"
  42. #include "Common/XferSave.h"
  43. #include "GameClient/CampaignManager.h"
  44. #include "GameClient/GadgetListBox.h"
  45. #include "GameClient/GameClient.h"
  46. #include "GameClient/GameText.h"
  47. #include "GameClient/MapUtil.h"
  48. #include "GameClient/MessageBox.h"
  49. #include "GameClient/InGameUI.h"
  50. #include "GameClient/ParticleSys.h"
  51. #include "GameClient/TerrainVisual.h"
  52. #include "GameLogic/GameLogic.h"
  53. #include "GameLogic/GhostObject.h"
  54. #include "GameLogic/PartitionManager.h"
  55. #include "GameLogic/ScriptEngine.h"
  56. #include "GameLogic/SidesList.h"
  57. #include "GameLogic/TerrainLogic.h"
  58. #ifdef _INTERNAL
  59. // for occasional debugging...
  60. //#pragma optimize("", off)
  61. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  62. #endif
  63. // PUBLIC DATA ////////////////////////////////////////////////////////////////////////////////////
  64. GameState *TheGameState = NULL;
  65. // PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
  66. static const Char *SAVE_FILE_EOF = "SG_EOF";
  67. static const Char *SAVE_GAME_EXTENSION = ".sav";
  68. static const Char *ZERO_NAME_ONLY = "00000000";
  69. static const Int MAX_SAVE_FILE_NUMBER = 99999999;
  70. ///////////////////////////////////////////////////////////////////////////////////////////////////
  71. #define GAME_STATE_BLOCK_STRING "CHUNK_GameState" // block of save game data with game info data
  72. #define CAMPAIGN_BLOCK_STRING "CHUNK_Campaign" // block of game data that has campaign info
  73. // ------------------------------------------------------------------------------------------------
  74. // ------------------------------------------------------------------------------------------------
  75. SaveGameInfo::SaveGameInfo( void )
  76. {
  77. date.day = 0;
  78. date.dayOfWeek = 0;
  79. date.hour = 0;
  80. date.milliseconds = 0;
  81. date.minute = 0;
  82. date.month = 0;
  83. date.second = 0;
  84. date.year = 0;
  85. missionNumber = 0;
  86. saveFileType = SAVE_FILE_TYPE_NORMAL;
  87. } // end SaveGameInfo
  88. // ------------------------------------------------------------------------------------------------
  89. // ------------------------------------------------------------------------------------------------
  90. SaveGameInfo::~SaveGameInfo( void )
  91. {
  92. } // end ~SaveGameInfo
  93. // ------------------------------------------------------------------------------------------------
  94. /** Is this date newer than the other one passed in? */
  95. // ------------------------------------------------------------------------------------------------
  96. Bool SaveDate::isNewerThan( SaveDate *other )
  97. {
  98. // year
  99. if( year > other->year )
  100. return TRUE;
  101. else if( year < other->year )
  102. return FALSE;
  103. else
  104. {
  105. // month
  106. if( month > other->month )
  107. return TRUE;
  108. else if( month < other->month )
  109. return FALSE;
  110. else
  111. {
  112. // day
  113. if( day > other->day )
  114. return TRUE;
  115. else if( day < other->day )
  116. return FALSE;
  117. else
  118. {
  119. // hour
  120. if( hour > other->hour )
  121. return TRUE;
  122. else if( hour < other->hour )
  123. return FALSE;
  124. else
  125. {
  126. // minute
  127. if( minute > other->minute )
  128. return TRUE;
  129. else if( minute < other->minute )
  130. return FALSE;
  131. else
  132. {
  133. // second
  134. if( second > other->second )
  135. return TRUE;
  136. else if( second < other->second )
  137. return FALSE;
  138. else
  139. {
  140. // millisecond
  141. if( milliseconds > other->milliseconds )
  142. return TRUE;
  143. else
  144. return FALSE;
  145. } // end else
  146. } // end else
  147. } // end else
  148. } // end else
  149. } // end else
  150. } // end else
  151. } // end isNewerThan
  152. // ------------------------------------------------------------------------------------------------
  153. /** Find a snapshot block info that matches the token passed in */
  154. // ------------------------------------------------------------------------------------------------
  155. GameState::SnapshotBlock *GameState::findBlockInfoByToken( AsciiString token, SnapshotType which )
  156. {
  157. // sanity
  158. if( token.isEmpty() )
  159. return NULL;
  160. // search for match our list
  161. SnapshotBlock *blockInfo;
  162. SnapshotBlockListIterator it;
  163. for( it = m_snapshotBlockList[which].begin(); it != m_snapshotBlockList[which].end(); ++it )
  164. {
  165. // get info
  166. blockInfo = &(*it);
  167. // check for match
  168. if( blockInfo->blockName == token )
  169. return blockInfo;
  170. } // end for
  171. // not found
  172. return NULL;
  173. } // end findLexiconEntryByToken
  174. ///////////////////////////////////////////////////////////////////////////////////////////////////
  175. ///////////////////////////////////////////////////////////////////////////////////////////////////
  176. ///////////////////////////////////////////////////////////////////////////////////////////////////
  177. UnicodeString getUnicodeDateBuffer(SYSTEMTIME timeVal)
  178. {
  179. // setup date buffer for local region date format
  180. #define DATE_BUFFER_SIZE 256
  181. OSVERSIONINFO osvi;
  182. osvi.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
  183. UnicodeString displayDateBuffer;
  184. if (GetVersionEx(&osvi))
  185. { //check if we're running Win9x variant since they may need different characters
  186. if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
  187. {
  188. char dateBuffer[ DATE_BUFFER_SIZE ];
  189. GetDateFormat( LOCALE_SYSTEM_DEFAULT,
  190. DATE_SHORTDATE,
  191. &timeVal,
  192. NULL,
  193. dateBuffer, sizeof(dateBuffer) );
  194. displayDateBuffer.translate(dateBuffer);
  195. return displayDateBuffer;
  196. }
  197. }
  198. wchar_t dateBuffer[ DATE_BUFFER_SIZE ];
  199. GetDateFormatW( LOCALE_SYSTEM_DEFAULT,
  200. DATE_SHORTDATE,
  201. &timeVal,
  202. NULL,
  203. dateBuffer, sizeof(dateBuffer) );
  204. displayDateBuffer.set(dateBuffer);
  205. return displayDateBuffer;
  206. //displayDateBuffer.format( L"%ls", dateBuffer );
  207. }
  208. UnicodeString getUnicodeTimeBuffer(SYSTEMTIME timeVal)
  209. {
  210. // setup time buffer for local region time format
  211. UnicodeString displayTimeBuffer;
  212. OSVERSIONINFO osvi;
  213. osvi.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
  214. if (GetVersionEx(&osvi))
  215. { //check if we're running Win9x variant since they may need different characters
  216. if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
  217. {
  218. char timeBuffer[ DATE_BUFFER_SIZE ];
  219. GetTimeFormat( LOCALE_SYSTEM_DEFAULT,
  220. TIME_NOSECONDS|TIME_FORCE24HOURFORMAT|TIME_NOTIMEMARKER,
  221. &timeVal,
  222. NULL,
  223. timeBuffer, sizeof(timeBuffer) );
  224. displayTimeBuffer.translate(timeBuffer);
  225. return displayTimeBuffer;
  226. }
  227. }
  228. // setup time buffer for local region time format
  229. #define TIME_BUFFER_SIZE 256
  230. wchar_t timeBuffer[ TIME_BUFFER_SIZE ];
  231. GetTimeFormatW( LOCALE_SYSTEM_DEFAULT,
  232. TIME_NOSECONDS,
  233. &timeVal,
  234. NULL,
  235. timeBuffer,
  236. sizeof(timeBuffer) );
  237. displayTimeBuffer.set(timeBuffer);
  238. return displayTimeBuffer;
  239. }
  240. // ------------------------------------------------------------------------------------------------
  241. // ------------------------------------------------------------------------------------------------
  242. GameState::GameState( void )
  243. {
  244. m_availableGames = NULL;
  245. m_isInLoadGame = FALSE;
  246. } // end GameState
  247. // ------------------------------------------------------------------------------------------------
  248. // ------------------------------------------------------------------------------------------------
  249. GameState::~GameState( void )
  250. {
  251. // clear our snapshot block list
  252. for (Int i=0; i<SNAPSHOT_MAX; ++i)
  253. m_snapshotBlockList[i].clear();
  254. // make certain that the post process list is clean
  255. m_snapshotPostProcessList.clear();
  256. // clear any available game
  257. clearAvailableGames();
  258. } // end ~GameState
  259. // ------------------------------------------------------------------------------------------------
  260. /** Init the game state subsystem */
  261. // ------------------------------------------------------------------------------------------------
  262. void GameState::init( void )
  263. {
  264. // add all the snapshot objects to our list of data blocks for save game files
  265. addSnapshotBlock( GAME_STATE_BLOCK_STRING, TheGameState, SNAPSHOT_SAVELOAD );
  266. addSnapshotBlock( CAMPAIGN_BLOCK_STRING, TheCampaignManager, SNAPSHOT_SAVELOAD );
  267. addSnapshotBlock( "CHUNK_GameStateMap", TheGameStateMap, SNAPSHOT_SAVELOAD );
  268. addSnapshotBlock( "CHUNK_TerrainLogic", TheTerrainLogic, SNAPSHOT_SAVELOAD );
  269. addSnapshotBlock( "CHUNK_TeamFactory", TheTeamFactory, SNAPSHOT_SAVELOAD );
  270. addSnapshotBlock( "CHUNK_Players", ThePlayerList, SNAPSHOT_SAVELOAD );
  271. addSnapshotBlock( "CHUNK_GameLogic", TheGameLogic, SNAPSHOT_SAVELOAD );
  272. addSnapshotBlock( "CHUNK_Radar", TheRadar, SNAPSHOT_SAVELOAD );
  273. addSnapshotBlock( "CHUNK_ScriptEngine", TheScriptEngine, SNAPSHOT_SAVELOAD );
  274. addSnapshotBlock( "CHUNK_SidesList", TheSidesList, SNAPSHOT_SAVELOAD );
  275. addSnapshotBlock( "CHUNK_TacticalView", TheTacticalView, SNAPSHOT_SAVELOAD );
  276. addSnapshotBlock( "CHUNK_GameClient", TheGameClient, SNAPSHOT_SAVELOAD );
  277. addSnapshotBlock( "CHUNK_InGameUI", TheInGameUI, SNAPSHOT_SAVELOAD );
  278. addSnapshotBlock( "CHUNK_Partition", ThePartitionManager, SNAPSHOT_SAVELOAD );
  279. addSnapshotBlock( "CHUNK_ParticleSystem", TheParticleSystemManager, SNAPSHOT_SAVELOAD );
  280. addSnapshotBlock( "CHUNK_TerrainVisual", TheTerrainVisual, SNAPSHOT_SAVELOAD );
  281. addSnapshotBlock( "CHUNK_GhostObject", TheGhostObjectManager, SNAPSHOT_SAVELOAD );
  282. // add all the snapshot objects to our list of data blocks for deep CRCs of logic
  283. addSnapshotBlock( "CHUNK_TeamFactory", TheTeamFactory, SNAPSHOT_DEEPCRC_LOGICONLY );
  284. addSnapshotBlock( "CHUNK_Players", ThePlayerList, SNAPSHOT_DEEPCRC_LOGICONLY );
  285. addSnapshotBlock( "CHUNK_GameLogic", TheGameLogic, SNAPSHOT_DEEPCRC_LOGICONLY );
  286. addSnapshotBlock( "CHUNK_ScriptEngine", TheScriptEngine, SNAPSHOT_DEEPCRC_LOGICONLY );
  287. addSnapshotBlock( "CHUNK_SidesList", TheSidesList, SNAPSHOT_DEEPCRC_LOGICONLY );
  288. addSnapshotBlock( "CHUNK_Partition", ThePartitionManager, SNAPSHOT_DEEPCRC_LOGICONLY );
  289. m_isInLoadGame = FALSE;
  290. } // end init
  291. // ------------------------------------------------------------------------------------------------
  292. /** Reset */
  293. // ------------------------------------------------------------------------------------------------a
  294. void GameState::reset( void )
  295. {
  296. // clear the post process snapshot list
  297. m_snapshotPostProcessList.clear();
  298. // clear any available game
  299. clearAvailableGames();
  300. m_isInLoadGame = FALSE;
  301. } // end reset
  302. // ------------------------------------------------------------------------------------------------
  303. /** Clear any available games entries */
  304. // ------------------------------------------------------------------------------------------------
  305. void GameState::clearAvailableGames( void )
  306. {
  307. AvailableGameInfo *gameInfo;
  308. while( m_availableGames )
  309. {
  310. gameInfo = m_availableGames->next;
  311. delete m_availableGames;
  312. m_availableGames = gameInfo;
  313. } // end while
  314. } // end clearAvailableGames
  315. // ------------------------------------------------------------------------------------------------
  316. /** Add a snapshot and block name pair to the systems used to load and save */
  317. // ------------------------------------------------------------------------------------------------
  318. void GameState::addSnapshotBlock( AsciiString blockName, Snapshot *snapshot, SnapshotType which )
  319. {
  320. // sanity
  321. if( blockName.isEmpty() || snapshot == NULL )
  322. {
  323. DEBUG_CRASH(( "addSnapshotBlock: Invalid parameters\n" ));
  324. return;
  325. } // end if
  326. // add to the list
  327. SnapshotBlock blockInfo;
  328. blockInfo.snapshot = snapshot;
  329. blockInfo.blockName = blockName;
  330. m_snapshotBlockList[which].push_back( blockInfo );
  331. } // end addSnapshotBlock
  332. // ------------------------------------------------------------------------------------------------
  333. /** Given the filename of a save file, find the highest filename number */
  334. // ------------------------------------------------------------------------------------------------
  335. static void findHighFileNumber( AsciiString filename, void *userData )
  336. {
  337. // sanity
  338. if( filename.isEmpty() )
  339. return;
  340. // sanity check for ".sav" at the end of the filename
  341. if( filename.endsWithNoCase( SAVE_GAME_EXTENSION ) == FALSE )
  342. return;
  343. // strip off the extension at the end of the filename
  344. AsciiString nameOnly = filename;
  345. for( Int count = 0; count < strlen( SAVE_GAME_EXTENSION ); count++ )
  346. nameOnly.removeLastChar();
  347. // convert filename (which is only numbers) to a number
  348. Int fileNumber = atoi( nameOnly.str() );
  349. //
  350. // atoi will return zero if the string could not be converted, if the filename is
  351. // not literally "00000000.sav" then that means the conversion could not be done so
  352. // we should reject this filename from further processing
  353. //
  354. if( fileNumber == 0 && nameOnly.compare( ZERO_NAME_ONLY ) != 0 )
  355. return;
  356. // compare against the file number we're keeping tally of in the user data parameter
  357. Int *highFileNumber = (Int *)userData;
  358. if( fileNumber >= *highFileNumber )
  359. *highFileNumber = fileNumber;
  360. } // end findHighFileNumber
  361. // ------------------------------------------------------------------------------------------------
  362. /** Given the save files on disk, find the "next" filename to use when saving a game */
  363. // ------------------------------------------------------------------------------------------------
  364. AsciiString GameState::findNextSaveFilename( UnicodeString desc )
  365. {
  366. // works, but needs approval from mgmt (srj)
  367. // GS activating for patch
  368. #define COCKBEER_DONT_USE_NORMAL_FILE_NAMES_THANKS
  369. #ifdef USE_NORMAL_FILE_NAMES_THANKS
  370. Int i;
  371. AsciiString adesc;
  372. for (i = 0; i < desc.getLength(); ++i)
  373. {
  374. char c = (char)desc.getCharAt(i);
  375. if (isalnum(c))
  376. adesc.concat(c);
  377. else
  378. adesc.concat('_');
  379. }
  380. for (i = 1; i <= 9999; ++i)
  381. {
  382. AsciiString leaf;
  383. leaf.format("%s_%04d%s", adesc.str(), i, SAVE_GAME_EXTENSION);
  384. AsciiString path = getFilePathInSaveDirectory(leaf);
  385. if( _access( path.str(), 0 ) == -1 )
  386. return leaf; // note that this returns the leaf, not the full path
  387. }
  388. #else
  389. //
  390. // This method has code to support two modes of finding the next filename, one of
  391. // them starts with a filename of 00000000.sav and counts up the number looking for
  392. // a file that doesn't exist and therefore a filename we can use (LOWEST_NUMBER).
  393. // The other method iterates all save files and returns a filename that is one larger
  394. // than the largest filename number encountered (HIGHEST_NUMBER)
  395. //
  396. enum FindNextFileType { LOWEST_NUMBER = 0, HIGHEST_NUMBER = 1 };
  397. FindNextFileType searchType = LOWEST_NUMBER;
  398. if( searchType == HIGHEST_NUMBER )
  399. {
  400. // iterate all the save files in the directory and find the highest file number
  401. Int highFileNumber = -1;
  402. iterateSaveFiles( findHighFileNumber, &highFileNumber );
  403. // check for a filename that is too big (this is unlikely but theoretically possible)
  404. if( highFileNumber + 1 > MAX_SAVE_FILE_NUMBER )
  405. return AsciiString::TheEmptyString;
  406. // construct filename with a number higher than the highest one we found
  407. AsciiString filename;
  408. filename.format( "%08d%s", highFileNumber + 1, SAVE_GAME_EXTENSION );
  409. return filename;
  410. } // end if
  411. else if( searchType == LOWEST_NUMBER )
  412. {
  413. AsciiString filename;
  414. AsciiString fullPath;
  415. Int i = 0;
  416. while( TRUE )
  417. {
  418. // construct filename (########.sav)
  419. filename.format( "%08d%s", i, SAVE_GAME_EXTENSION );
  420. // construct full path to file given the filename
  421. fullPath = getFilePathInSaveDirectory(filename);
  422. // if file does not exist we're all good
  423. if( _access( fullPath.str(), 0 ) == -1 )
  424. return filename;
  425. // test the text filename
  426. i++;
  427. // check for at the max limit (this is highly unlikely but possible)
  428. if( i > MAX_SAVE_FILE_NUMBER )
  429. return AsciiString::TheEmptyString;
  430. } // end while
  431. } // end else if
  432. else
  433. {
  434. DEBUG_CRASH(( "GameState::findNextSaveFilename - Unknown file search type '%d'\n", searchType ));
  435. return AsciiString::TheEmptyString;
  436. } // end else
  437. #endif
  438. // no appropriate filename could be found, return the empty string
  439. return AsciiString::TheEmptyString;
  440. } // end findNextSaveFilename
  441. // ------------------------------------------------------------------------------------------------
  442. /** Save the current state of the engine in a save file
  443. * NOTE: filename is a *filename only* */
  444. // ------------------------------------------------------------------------------------------------
  445. SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
  446. SaveFileType saveType, SnapshotType which )
  447. {
  448. // if there is no filename, this is a new file being created, find an appropriate filename
  449. if( filename.isEmpty() )
  450. filename = findNextSaveFilename( desc );
  451. if( filename.isEmpty() )
  452. {
  453. DEBUG_CRASH(( "GameState::saveGame - Unable to find valid filename for save game\n" ));
  454. return SC_NO_FILE_AVAILABLE;
  455. } // end if
  456. // make absolutely sure the save directory exists
  457. CreateDirectory( getSaveDirectory().str(), NULL );
  458. // construct path to file
  459. AsciiString filepath = getFilePathInSaveDirectory(filename);
  460. // save description as current description in the game state
  461. m_gameInfo.description = desc;
  462. // open the save file
  463. XferSave xferSave;
  464. try {
  465. xferSave.open( filepath );
  466. } catch(...) {
  467. // print error message to the user
  468. TheInGameUI->message( "GUI:Error" );
  469. DEBUG_LOG(( "Error opening file '%s'\n", filepath.str() ));
  470. return SC_ERROR;
  471. }
  472. // save our save file type
  473. SaveGameInfo *gameInfo = getSaveGameInfo();
  474. gameInfo->saveFileType = saveType;
  475. // save our mission map name if applicable
  476. if( saveType == SAVE_FILE_TYPE_MISSION )
  477. gameInfo->missionMapName = TheCampaignManager->getCurrentMap();
  478. else
  479. gameInfo->missionMapName.clear();
  480. // set the pristine map to the current campaign map
  481. // this is now done during startNewGame()
  482. // gameInfo->pristineMapName = TheCampaignManager->getCurrentMap();
  483. // write the save file
  484. try
  485. {
  486. // save file
  487. xferSaveData( &xferSave, which );
  488. } // end try
  489. catch( ... )
  490. {
  491. UnicodeString ufilepath;
  492. ufilepath.translate(filepath);
  493. UnicodeString msg;
  494. msg.format( TheGameText->fetch("GUI:ErrorSavingGame"), ufilepath.str() );
  495. MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, NULL);
  496. // close the file and get out of here
  497. xferSave.close();
  498. return SC_ERROR;
  499. } // end catch
  500. // close the file
  501. xferSave.close();
  502. // print message to the user for game successfully saved
  503. UnicodeString msg = TheGameText->fetch( "GUI:GameSaveComplete" );
  504. TheInGameUI->message( msg );
  505. return SC_OK;
  506. } // end saveGame
  507. // ------------------------------------------------------------------------------------------------
  508. /** A mission save */
  509. // ------------------------------------------------------------------------------------------------
  510. SaveCode GameState::missionSave( void )
  511. {
  512. // get campaign
  513. Campaign *campaign = TheCampaignManager->getCurrentCampaign();
  514. // get mission #
  515. Int missionNumber = TheCampaignManager->getCurrentMissionNumber() + 1;
  516. // format a string for the mission save description
  517. UnicodeString format = TheGameText->fetch( "GUI:MissionSave" );
  518. UnicodeString desc;
  519. desc.format( format, TheGameText->fetch( campaign->m_campaignNameLabel ).str(), missionNumber );
  520. // do an automatic mission save
  521. return TheGameState->saveGame( AsciiString(""), desc, SAVE_FILE_TYPE_MISSION );
  522. } // end missionSave
  523. // ------------------------------------------------------------------------------------------------
  524. /** Load the save game pointed to by filename */
  525. // ------------------------------------------------------------------------------------------------
  526. SaveCode GameState::loadGame( AvailableGameInfo gameInfo )
  527. {
  528. // sanity check for file
  529. if( doesSaveGameExist( gameInfo.filename ) == FALSE )
  530. return SC_FILE_NOT_FOUND;
  531. // clear game data just like loading from the debug map load screen for mission saves
  532. if( gameInfo.saveGameInfo.saveFileType == SAVE_FILE_TYPE_MISSION )
  533. {
  534. if (TheGameLogic->isInGame())
  535. TheGameLogic->clearGameData( FALSE );
  536. } // end if
  537. //
  538. // clear the save directory of any temporary "scratch pad" maps that were extracted
  539. // from any previously loaded save game files
  540. //
  541. TheGameStateMap->clearScratchPadMaps();
  542. // construct path to file
  543. AsciiString filepath = getFilePathInSaveDirectory(gameInfo.filename);
  544. // open the save file
  545. XferLoad xferLoad;
  546. xferLoad.open( filepath );
  547. // clear out the game engine
  548. TheGameEngine->reset();
  549. // lock creation of new ghost objects
  550. TheGhostObjectManager->saveLockGhostObjects( TRUE );
  551. LatchRestore<Bool> inLoadGame(m_isInLoadGame, TRUE);
  552. // load the save data
  553. Bool error = FALSE;
  554. try
  555. {
  556. // load file
  557. xferSaveData( &xferLoad, SNAPSHOT_SAVELOAD );
  558. } // end try
  559. catch( ... )
  560. {
  561. error = TRUE;
  562. } // end catch
  563. // close the file
  564. xferLoad.close();
  565. // un-savelock the ghost objects
  566. TheGhostObjectManager->saveLockGhostObjects( FALSE );
  567. try
  568. {
  569. // do the post-process from a save game load
  570. gameStatePostProcessLoad();
  571. }
  572. catch (...)
  573. {
  574. error = TRUE;
  575. }
  576. // check for error
  577. if( error == TRUE )
  578. {
  579. // clear it out, again
  580. if (TheGameLogic->isInGame())
  581. TheGameLogic->clearGameData( FALSE );
  582. TheGameEngine->reset();
  583. // print error message to the user
  584. UnicodeString ufilepath;
  585. ufilepath.translate(filepath);
  586. UnicodeString msg;
  587. msg.format( TheGameText->fetch("GUI:ErrorLoadingGame"), ufilepath.str() );
  588. MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, NULL);
  589. return SC_INVALID_DATA; // you can't use a naked "throw" outside of a catch statement!
  590. } // end if
  591. //
  592. // when loading a mission save, we want to do as much normal loading stuff as we
  593. // can cause we don't have any real save game data to load other than the
  594. // game state map info and campaign manager stuff
  595. //
  596. if( getSaveGameInfo()->saveFileType == SAVE_FILE_TYPE_MISSION )
  597. {
  598. InitRandom(0);
  599. TheWritableGlobalData->m_pendingFile = getSaveGameInfo()->missionMapName;
  600. GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME );
  601. msg->appendIntegerArgument(GAME_SINGLE_PLAYER);
  602. msg->appendIntegerArgument(TheCampaignManager->getGameDifficulty());
  603. msg->appendIntegerArgument(TheCampaignManager->getRankPoints());
  604. // remove the mission save data, we've got all we need and have started the load
  605. SaveGameInfo *gameInfo = getSaveGameInfo();
  606. gameInfo->saveFileType = SAVE_FILE_TYPE_NORMAL;
  607. gameInfo->missionMapName.clear();
  608. } // end if
  609. return SC_OK;
  610. } // end loadGame
  611. //-------------------------------------------------------------------------------------------------
  612. AsciiString GameState::getSaveDirectory() const
  613. {
  614. AsciiString tmp = TheGlobalData->getPath_UserData();
  615. tmp.concat("Save\\");
  616. return tmp;
  617. }
  618. //-------------------------------------------------------------------------------------------------
  619. AsciiString GameState::getFilePathInSaveDirectory(const AsciiString& leaf) const
  620. {
  621. AsciiString tmp = getSaveDirectory();
  622. tmp.concat(leaf);
  623. return tmp;
  624. }
  625. //-------------------------------------------------------------------------------------------------
  626. Bool GameState::isInSaveDirectory(const AsciiString& path) const
  627. {
  628. return path.startsWithNoCase(getSaveDirectory());
  629. }
  630. // ------------------------------------------------------------------------------------------------
  631. AsciiString GameState::getMapLeafName(const AsciiString& in) const
  632. {
  633. char* p = strrchr(in.str(), '\\');
  634. if (p)
  635. {
  636. //
  637. // p points to the last '\' (if found), however, if a '\' was found there better
  638. // be another character beyond it, otherwise the map filename would actually
  639. // be a *directory* Just move to the first character beyond it so we are looking
  640. // at the name only
  641. //
  642. ++p;
  643. DEBUG_ASSERTCRASH( p != NULL && *p != 0, ("GameState::xfer - Illegal map name encountered\n") );
  644. return p;
  645. }
  646. else
  647. {
  648. return in;
  649. }
  650. }
  651. // ------------------------------------------------------------------------------------------------
  652. static const char* findLastBackslashInRangeInclusive(const char* start, const char* end)
  653. {
  654. while (end >= start)
  655. {
  656. if (*end == '\\')
  657. return end;
  658. --end;
  659. }
  660. return NULL;
  661. }
  662. // ------------------------------------------------------------------------------------------------
  663. static AsciiString getMapLeafAndDirName(const AsciiString& in)
  664. {
  665. const char* start = in.str();
  666. const char* end = in.str() + in.getLength() - 1;
  667. const char* p = findLastBackslashInRangeInclusive(start, end);
  668. if (p)
  669. {
  670. const char* p2 = findLastBackslashInRangeInclusive(start, p-1);
  671. if (p2)
  672. {
  673. // we have something like:
  674. // maps\foo\foo.map
  675. // c:\mydocs\c&cdata\maps\foo\foo.map
  676. return p2 + 1;
  677. }
  678. else
  679. {
  680. // we have something like:
  681. // save\foo.map
  682. return in;
  683. }
  684. }
  685. else
  686. {
  687. DEBUG_CRASH(("Illegal map-dir-name... should have at least one backslash"));
  688. return in;
  689. }
  690. }
  691. // ------------------------------------------------------------------------------------------------
  692. static AsciiString removeExtension(const AsciiString& in)
  693. {
  694. char buf[1024];
  695. strcpy(buf, in.str());
  696. char* p = strrchr(buf, '.');
  697. if (p)
  698. {
  699. *p = 0;
  700. }
  701. return AsciiString(buf);
  702. }
  703. // ------------------------------------------------------------------------------------------------
  704. const char* PORTABLE_SAVE = "Save\\";
  705. const char* PORTABLE_MAPS = "Maps\\";
  706. const char* PORTABLE_USER_MAPS = "UserData\\Maps\\";
  707. // ------------------------------------------------------------------------------------------------
  708. AsciiString GameState::realMapPathToPortableMapPath(const AsciiString& in) const
  709. {
  710. AsciiString prefix;
  711. if (in.startsWithNoCase(getSaveDirectory()))
  712. {
  713. prefix = PORTABLE_SAVE;
  714. prefix.concat(getMapLeafName(in));
  715. }
  716. else if (in.startsWithNoCase(TheMapCache->getMapDir()))
  717. {
  718. prefix = PORTABLE_MAPS;
  719. prefix.concat(getMapLeafAndDirName(in));
  720. }
  721. else if (in.startsWithNoCase(TheMapCache->getUserMapDir()))
  722. {
  723. prefix = PORTABLE_USER_MAPS;
  724. prefix.concat(getMapLeafAndDirName(in));
  725. }
  726. else
  727. {
  728. DEBUG_CRASH(("Map file was not found in any of the expected directories; this is impossible"));
  729. //throw INI_INVALID_DATA;
  730. // uncaught exceptions crash us. better to just use a bad path.
  731. prefix = in;
  732. }
  733. prefix.toLower();
  734. return prefix;
  735. }
  736. // ------------------------------------------------------------------------------------------------
  737. AsciiString GameState::portableMapPathToRealMapPath(const AsciiString& in) const
  738. {
  739. AsciiString prefix;
  740. if (in.startsWithNoCase(PORTABLE_SAVE))
  741. {
  742. // the save dir ends with "\\"
  743. prefix = getSaveDirectory();
  744. prefix.concat(getMapLeafName(in));
  745. }
  746. else if (in.startsWithNoCase(PORTABLE_MAPS))
  747. {
  748. // the map dir DOES NOT end with "\\", must add it
  749. prefix = TheMapCache->getMapDir();
  750. prefix.concat("\\");
  751. prefix.concat(getMapLeafAndDirName(in));
  752. }
  753. else if (in.startsWithNoCase(PORTABLE_USER_MAPS))
  754. {
  755. // the map dir DOES NOT end with "\\", must add it
  756. prefix = TheMapCache->getUserMapDir();
  757. prefix.concat("\\");
  758. prefix.concat(getMapLeafAndDirName(in));
  759. }
  760. else
  761. {
  762. DEBUG_CRASH(("Map file was not found in any of the expected directories; this is impossible"));
  763. //throw INI_INVALID_DATA;
  764. // uncaught exceptions crash us. better to just use a bad path.
  765. prefix = in;
  766. }
  767. prefix.toLower();
  768. return prefix;
  769. }
  770. // ------------------------------------------------------------------------------------------------
  771. /** Does the save game file exist */
  772. // ------------------------------------------------------------------------------------------------
  773. Bool GameState::doesSaveGameExist( AsciiString filename )
  774. {
  775. // construct full path to file
  776. AsciiString filepath = getFilePathInSaveDirectory(filename);
  777. // open file
  778. XferLoad xfer;
  779. try
  780. {
  781. // try to open it
  782. xfer.open( filepath );
  783. } // end try
  784. catch( ... )
  785. {
  786. // unable to open file, it must not be here
  787. return FALSE;
  788. } // end catch
  789. // close the file, we don't want to to anything with it right now
  790. xfer.close();
  791. return TRUE;
  792. } // doesSaveGameExist
  793. // ------------------------------------------------------------------------------------------------
  794. /** Get save game info from the filename specified */
  795. // ------------------------------------------------------------------------------------------------
  796. void GameState::getSaveGameInfoFromFile( AsciiString filename, SaveGameInfo *saveGameInfo )
  797. {
  798. AsciiString token;
  799. Int blockSize;
  800. Bool done = FALSE;
  801. SnapshotBlock *blockInfo;
  802. // sanity
  803. if( filename.isEmpty() == TRUE || saveGameInfo == NULL )
  804. {
  805. DEBUG_CRASH(( "GameState::getSaveGameInfoFromFile - Illegal parameters\n" ));
  806. return;
  807. } // end if
  808. // open file for partial loading
  809. XferLoad xferLoad;
  810. xferLoad.open( filename );
  811. //
  812. // disable post processing cause we're not really doing a load of game data that
  813. // needs post processing and we don't want to keep track of any snapshots we loaded
  814. //
  815. xferLoad.setOptions( XO_NO_POST_PROCESSING );
  816. // read all data blocks in the file
  817. while( done == FALSE )
  818. {
  819. // read next token
  820. xferLoad.xferAsciiString( &token );
  821. // check for end of file token
  822. if( token.compareNoCase( SAVE_FILE_EOF ) == 0 )
  823. {
  824. // we should never get here, if we did, we didn't find block of data we needed
  825. DEBUG_CRASH(( "GameState::getSaveGameInfoFromFile - Game info not found in file '%s'\n", filename.str() ));
  826. done = TRUE;
  827. } // end if
  828. else
  829. {
  830. // find matching token in the save file lexicon
  831. blockInfo = findBlockInfoByToken( token, SNAPSHOT_SAVELOAD );
  832. if( blockInfo == NULL )
  833. throw SC_UNKNOWN_BLOCK;
  834. // read the data size of this block
  835. blockSize = xferLoad.beginBlock();
  836. // is this the block of game info data
  837. if( stricmp( token.str(), GAME_STATE_BLOCK_STRING ) == 0 )
  838. {
  839. GameState tempGameState;
  840. // parse this data
  841. try
  842. {
  843. // load data
  844. xferLoad.xferSnapshot( &tempGameState );
  845. } // end try
  846. catch( ... )
  847. {
  848. DEBUG_CRASH(( "GameState::getSaveGameInfoFromFile - Error loading block '%s' in file '%s'\n",
  849. blockInfo->blockName.str(), filename.str() ));
  850. throw;
  851. } // end catch
  852. // data was found, copy game state info over
  853. *saveGameInfo = *tempGameState.getSaveGameInfo();
  854. // we're all done with this file now
  855. done = TRUE;
  856. } // end if
  857. else
  858. {
  859. // not a block we care about, just skip it
  860. xferLoad.skip( blockSize );
  861. // end of block
  862. xferLoad.endBlock();
  863. } // end else
  864. } // end else, valid data block token
  865. } // end while, not done
  866. // close the file
  867. xferLoad.close();
  868. } // end getSaveGameInfoFromFile
  869. // ------------------------------------------------------------------------------------------------
  870. /** Create game info and add to available list */
  871. // ------------------------------------------------------------------------------------------------
  872. static void addGameToAvailableList( AsciiString filename, void *userData )
  873. {
  874. AvailableGameInfo **listHead = (AvailableGameInfo **)userData;
  875. // sanity
  876. DEBUG_ASSERTCRASH( listHead != NULL, ("addGameToAvailableList - Illegal parameters\n") );
  877. DEBUG_ASSERTCRASH( filename.isEmpty() == FALSE, ("addGameToAvailableList - Illegal filename\n") );
  878. try {
  879. // get header info from this listbox
  880. SaveGameInfo saveGameInfo;
  881. TheGameState->getSaveGameInfoFromFile( filename, &saveGameInfo );
  882. // allocate new info
  883. AvailableGameInfo *newInfo = new AvailableGameInfo;
  884. // assign data
  885. newInfo->prev = NULL;
  886. newInfo->next = NULL;
  887. newInfo->saveGameInfo = saveGameInfo;
  888. newInfo->filename = filename;
  889. // attach to list
  890. if( *listHead == NULL )
  891. *listHead = newInfo;
  892. else
  893. {
  894. AvailableGameInfo *curr, *prev;
  895. // insert this info so that the most recent games are always at the top of this list
  896. for( curr = *listHead; curr != NULL; curr = curr->next )
  897. {
  898. // save current as previous
  899. prev = curr;
  900. // check to see if curr is older than the new info, if so, put new info just ahead of curr
  901. if( newInfo->saveGameInfo.date.isNewerThan( &curr->saveGameInfo.date ) )
  902. {
  903. if( curr->prev )
  904. curr->prev->next = newInfo;
  905. else
  906. *listHead = newInfo;
  907. newInfo->prev = curr->prev;
  908. curr->prev = newInfo;
  909. newInfo->next = curr;
  910. break;
  911. } // end if
  912. } // end for
  913. // if not inserted, put at end
  914. if( curr == NULL )
  915. {
  916. prev->next = newInfo;
  917. newInfo->prev = prev;
  918. } // end if
  919. } // end else
  920. } catch(...) {
  921. // Do nothing - just return.
  922. }
  923. } // end addGameToAvailableList
  924. // ------------------------------------------------------------------------------------------------
  925. /** Populate the listbox passed in with a list of the save games present on the hard drive */
  926. // ------------------------------------------------------------------------------------------------
  927. void GameState::populateSaveGameListbox( GameWindow *listbox, SaveLoadLayoutType layoutType )
  928. {
  929. Int index;
  930. // sanity
  931. if( listbox == NULL )
  932. return;
  933. // first clear all entries in the listbox
  934. GadgetListBoxReset( listbox );
  935. // setup the first entry of the listbox to be a new game when saving is allowed
  936. if( layoutType != SLLT_LOAD_ONLY )
  937. {
  938. UnicodeString newGameText = TheGameText->fetch( "GUI:NewSaveGame" );
  939. Color newGameColor = GameMakeColor( 200, 200, 255, 255 );
  940. index = GadgetListBoxAddEntryText( listbox, newGameText, newGameColor, -1 );
  941. GadgetListBoxSetItemData( listbox, NULL, index );
  942. } // end if
  943. // clear the available games
  944. clearAvailableGames();
  945. // iterate all the save files in the directory and populate the listbox
  946. iterateSaveFiles( addGameToAvailableList, &m_availableGames );
  947. // add all games found to the list box
  948. AvailableGameInfo *info;
  949. SaveGameInfo *saveGameInfo;
  950. SYSTEMTIME systemTime;
  951. UnsignedInt count = 0;
  952. for( info = m_availableGames; info; info = info->next, count++ )
  953. {
  954. // get save game info
  955. saveGameInfo = &info->saveGameInfo;
  956. // setup a system time structure given the data we saved in the file
  957. systemTime.wYear = saveGameInfo->date.year;
  958. systemTime.wMonth = saveGameInfo->date.month;
  959. systemTime.wDayOfWeek = saveGameInfo->date.dayOfWeek;
  960. systemTime.wDay = saveGameInfo->date.day;
  961. systemTime.wHour = saveGameInfo->date.hour;
  962. systemTime.wMinute = saveGameInfo->date.minute;
  963. systemTime.wSecond = saveGameInfo->date.second;
  964. systemTime.wMilliseconds = saveGameInfo->date.milliseconds;
  965. // setup date buffer for local region date format
  966. UnicodeString displayDateBuffer = getUnicodeDateBuffer(systemTime);
  967. // setup time buffer for local region time format
  968. UnicodeString displayTimeBuffer = getUnicodeTimeBuffer(systemTime);
  969. // description string
  970. UnicodeString displayLabel = saveGameInfo->description;
  971. if( displayLabel.isEmpty() == TRUE )
  972. {
  973. Bool exists = FALSE;
  974. displayLabel = TheGameText->fetch( saveGameInfo->mapLabel, &exists );
  975. if( exists == FALSE )
  976. displayLabel.format( L"%S", saveGameInfo->mapLabel.str() );
  977. } // end if
  978. // pick color for text (we alternate it each game)
  979. Color color;
  980. if( saveGameInfo->saveFileType == SAVE_FILE_TYPE_MISSION )
  981. color = GameMakeColor( 200, 255, 200, 255 );
  982. else if( count & 0x1 )
  983. color = GameMakeColor( 255, 128, 0, 255 );
  984. else
  985. color = GameMakeColor( 255, 192, 0, 255 );
  986. // add string to listbox
  987. index = GadgetListBoxAddEntryText( listbox, displayLabel, color, -1, 0 );
  988. GadgetListBoxAddEntryText( listbox, displayTimeBuffer, color, index, 1 );
  989. GadgetListBoxAddEntryText( listbox, displayDateBuffer, color, index, 2 );
  990. // add this available game info in the user data pointer of that listbox item
  991. GadgetListBoxSetItemData( listbox, info, index );
  992. } // end for, info
  993. // select the top "new game" entry
  994. GadgetListBoxSetSelected( listbox, 0 );
  995. } // end pupulateSaveGameListbox
  996. ///////////////////////////////////////////////////////////////////////////////////////////////////
  997. // PRIVATE METHODS ////////////////////////////////////////////////////////////////////////////////
  998. ///////////////////////////////////////////////////////////////////////////////////////////////////
  999. // ------------------------------------------------------------------------------------------------
  1000. /** Iterate the save game files */
  1001. // ------------------------------------------------------------------------------------------------
  1002. void GameState::iterateSaveFiles( IterateSaveFileCallback callback, void *userData )
  1003. {
  1004. // sanity
  1005. if( callback == NULL )
  1006. return;
  1007. // save the current directory
  1008. char currentDirectory[ _MAX_PATH ];
  1009. GetCurrentDirectory( _MAX_PATH, currentDirectory );
  1010. // switch into the save directory
  1011. SetCurrentDirectory( getSaveDirectory().str() );
  1012. // iterate all items in the directory
  1013. WIN32_FIND_DATA item; // search item
  1014. HANDLE hFile = INVALID_HANDLE_VALUE; // handle for search resources
  1015. Bool done = FALSE;
  1016. Bool first = TRUE;
  1017. while( done == FALSE )
  1018. {
  1019. // if our first time through we need to start the search
  1020. if( first )
  1021. {
  1022. // start search
  1023. hFile = FindFirstFile( "*", &item );
  1024. if( hFile == INVALID_HANDLE_VALUE )
  1025. return;
  1026. // we are no longer on our first item
  1027. first = FALSE;
  1028. } // end if, first
  1029. // see if this is a file, and therefore a possible save file
  1030. if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
  1031. {
  1032. // see if there is a ".sav" at end of this filename
  1033. Char *c = strrchr( item.cFileName, '.' );
  1034. if( c && stricmp( c, ".sav" ) == 0 )
  1035. {
  1036. // construction asciistring filename
  1037. AsciiString filename;
  1038. filename.set( item.cFileName );
  1039. // call the callback
  1040. callback( filename, userData );
  1041. } // end if, a save file
  1042. } // end if
  1043. // on to the next file
  1044. if( FindNextFile( hFile, &item ) == 0 )
  1045. done = TRUE;
  1046. } // end while
  1047. // close search resources
  1048. FindClose( hFile );
  1049. // restore the current directory
  1050. SetCurrentDirectory( currentDirectory );
  1051. } // end iterateSaveFiles
  1052. // ------------------------------------------------------------------------------------------------
  1053. /** Save game to xfer or load game using xfer */
  1054. // ------------------------------------------------------------------------------------------------
  1055. void GameState::friend_xferSaveDataForCRC( Xfer *xfer, SnapshotType which )
  1056. {
  1057. DEBUG_LOG(("GameState::friend_xferSaveDataForCRC() - SnapshotType %d\n", which));
  1058. SaveGameInfo *gameInfo = getSaveGameInfo();
  1059. gameInfo->description.clear();
  1060. gameInfo->saveFileType = SAVE_FILE_TYPE_NORMAL;
  1061. gameInfo->missionMapName.clear();
  1062. gameInfo->pristineMapName.clear();
  1063. xferSaveData(xfer, which);
  1064. }
  1065. // ------------------------------------------------------------------------------------------------
  1066. /** Save game to xfer or load game using xfer */
  1067. // ------------------------------------------------------------------------------------------------
  1068. void GameState::xferSaveData( Xfer *xfer, SnapshotType which )
  1069. {
  1070. // sanity
  1071. if( xfer == NULL )
  1072. throw SC_INVALID_XFER;
  1073. // save or load all blocks
  1074. if( xfer->getXferMode() == XFER_SAVE )
  1075. {
  1076. DEBUG_LOG(("GameState::xferSaveData() - XFER_SAVE\n"));
  1077. // save all blocks
  1078. AsciiString blockName;
  1079. SnapshotBlock *blockInfo;
  1080. SnapshotBlockListIterator it;
  1081. for( it = m_snapshotBlockList[which].begin(); it != m_snapshotBlockList[which].end(); ++it )
  1082. {
  1083. // get list data
  1084. blockInfo = &(*it);
  1085. // get block name
  1086. blockName = blockInfo->blockName;
  1087. DEBUG_LOG(("Looking at block '%s'\n", blockName.str()));
  1088. //
  1089. // for mission save files, we only save the game state block and campaign manager
  1090. // because anything else is not needed.
  1091. //
  1092. if( getSaveGameInfo()->saveFileType != SAVE_FILE_TYPE_MISSION ||
  1093. (blockName.compareNoCase( GAME_STATE_BLOCK_STRING ) == 0 ||
  1094. blockName.compareNoCase( CAMPAIGN_BLOCK_STRING ) == 0) )
  1095. {
  1096. // xfer block name
  1097. xfer->xferAsciiString( &blockName );
  1098. // xfer this block
  1099. try
  1100. {
  1101. // begin new data block
  1102. xfer->beginBlock();
  1103. // xfer block data
  1104. xfer->xferSnapshot( blockInfo->snapshot );
  1105. // end this block
  1106. xfer->endBlock();
  1107. } // end try
  1108. catch( ... )
  1109. {
  1110. DEBUG_CRASH(( "Error saving block '%s' in file '%s'\n",
  1111. blockName.str(), xfer->getIdentifier() ));
  1112. throw;
  1113. } // end catch
  1114. } // end if
  1115. } // end for, all snapshots
  1116. // write an end of file token
  1117. AsciiString eofToken = SAVE_FILE_EOF;
  1118. xfer->xferAsciiString( &eofToken );
  1119. } // end if, save
  1120. else
  1121. {
  1122. DEBUG_LOG(("GameState::xferSaveData() - not XFER_SAVE\n"));
  1123. AsciiString token;
  1124. Int blockSize;
  1125. Bool done = FALSE;
  1126. SnapshotBlock *blockInfo;
  1127. // read all data blocks in the file
  1128. while( done == FALSE )
  1129. {
  1130. // read next token
  1131. xfer->xferAsciiString( &token );
  1132. // check for end of file token
  1133. if( token.compareNoCase( SAVE_FILE_EOF ) == 0 )
  1134. {
  1135. // all done
  1136. done = TRUE;
  1137. } // end if
  1138. else
  1139. {
  1140. // find matching token in the save file lexicon
  1141. blockInfo = findBlockInfoByToken( token, which );
  1142. if( blockInfo == NULL )
  1143. {
  1144. // log the block not found
  1145. DEBUG_LOG(( "GameState::xferSaveData - Skipping unknown block '%s'\n", token.str() ));
  1146. //
  1147. // block was not found, this could have been a block from an older file
  1148. // format where the block was removed, skip the block data and try to continue
  1149. //
  1150. Int dataSize = xfer->beginBlock();
  1151. xfer->skip( dataSize );
  1152. // continue with while loop reading block tokens
  1153. continue;
  1154. } // end if
  1155. try
  1156. {
  1157. // read block start
  1158. blockSize = xfer->beginBlock();
  1159. // parse this data
  1160. xfer->xferSnapshot( blockInfo->snapshot );
  1161. // read block end
  1162. xfer->endBlock();
  1163. } // end try
  1164. catch( ... )
  1165. {
  1166. DEBUG_CRASH(( "Error loading block '%s' in file '%s'\n",
  1167. blockInfo->blockName.str(), xfer->getIdentifier() ));
  1168. throw;
  1169. } // end catch
  1170. } // end else, valid data block token
  1171. } // end while, not done
  1172. } // end else, load
  1173. } // end xferSaveData
  1174. // ------------------------------------------------------------------------------------------------
  1175. /** Add a snapshot to the post process list for later */
  1176. // ------------------------------------------------------------------------------------------------
  1177. void GameState::addPostProcessSnapshot( Snapshot *snapshot )
  1178. {
  1179. // sanity
  1180. if( snapshot == NULL )
  1181. {
  1182. DEBUG_CRASH(( "GameState::addPostProcessSnapshot - invalid parameters\n" ));
  1183. return;
  1184. } // end if
  1185. /*
  1186. //
  1187. // This is n^2 and gets real real, REAL slow on game maps. jba.
  1188. // Please keep this code around tho, it can be useful in debugging save games
  1189. //
  1190. // verify the snapshot isn't in the list already
  1191. SnapshotListIterator it;
  1192. for( it = m_snapshotPostProcessList.begin(); it != m_snapshotPostProcessList.end(); ++it )
  1193. {
  1194. if( (*it) == snapshot )
  1195. {
  1196. DEBUG_CRASH(( "GameState::addPostProcessSnapshot - snapshot is already in list!\n" ));
  1197. return;
  1198. } // end if
  1199. } // end for, it
  1200. */
  1201. // add to the list
  1202. m_snapshotPostProcessList.push_back( snapshot );
  1203. } // end addPostProcessSnapshot
  1204. // ------------------------------------------------------------------------------------------------
  1205. /** Post process entry point after all game data has been xferd from disk */
  1206. // ------------------------------------------------------------------------------------------------
  1207. void GameState::gameStatePostProcessLoad( void )
  1208. {
  1209. // post process each snapshot that registered with us
  1210. SnapshotListIterator it;
  1211. Snapshot *snapshot;
  1212. for( it = m_snapshotPostProcessList.begin(); it != m_snapshotPostProcessList.end(); /*emtpy*/ )
  1213. {
  1214. // get snapshot
  1215. snapshot = *it;
  1216. // increment iterator
  1217. ++it;
  1218. // do processing
  1219. snapshot->loadPostProcess();
  1220. } // end for
  1221. // clear the snapshot post process list as we are now done with it
  1222. m_snapshotPostProcessList.clear();
  1223. // evil... must ensure this is updated prior to the script engine running the first time.
  1224. ThePartitionManager->update();
  1225. } // end loadPostProcess
  1226. // ------------------------------------------------------------------------------------------------
  1227. /** Xfer method for the game state itself
  1228. * Version Info:
  1229. * 1: Initial version
  1230. * 2: Added save file type and mission map name (regular save vs automatic mission save) */
  1231. // ------------------------------------------------------------------------------------------------
  1232. void GameState::xfer( Xfer *xfer )
  1233. {
  1234. // version
  1235. XferVersion currentVersion = 2;
  1236. XferVersion version = currentVersion;
  1237. xfer->xferVersion( &version, currentVersion );
  1238. // get structure for our current game info
  1239. SaveGameInfo *saveGameInfo = getSaveGameInfo();
  1240. // version 2
  1241. if( version >= 2 )
  1242. {
  1243. // file type
  1244. xfer->xferUser( &saveGameInfo->saveFileType, sizeof( SaveFileType ) );
  1245. // mission map name
  1246. xfer->xferAsciiString( &saveGameInfo->missionMapName );
  1247. } // end if
  1248. // current system time
  1249. SYSTEMTIME systemTime;
  1250. GetLocalTime( &systemTime );
  1251. // date and time
  1252. saveGameInfo->date.year = systemTime.wYear;
  1253. xfer->xferUnsignedShort( &saveGameInfo->date.year );
  1254. saveGameInfo->date.month = systemTime.wMonth;
  1255. xfer->xferUnsignedShort( &saveGameInfo->date.month );
  1256. saveGameInfo->date.day = systemTime.wDay;
  1257. xfer->xferUnsignedShort( &saveGameInfo->date.day );
  1258. saveGameInfo->date.dayOfWeek = systemTime.wDayOfWeek;
  1259. xfer->xferUnsignedShort( &saveGameInfo->date.dayOfWeek );
  1260. saveGameInfo->date.hour = systemTime.wHour;
  1261. xfer->xferUnsignedShort( &saveGameInfo->date.hour );
  1262. saveGameInfo->date.minute = systemTime.wMinute;
  1263. xfer->xferUnsignedShort( &saveGameInfo->date.minute );
  1264. saveGameInfo->date.second = systemTime.wSecond;
  1265. xfer->xferUnsignedShort( &saveGameInfo->date.second );
  1266. saveGameInfo->date.milliseconds = systemTime.wMilliseconds;
  1267. xfer->xferUnsignedShort( &saveGameInfo->date.milliseconds );
  1268. // user description
  1269. xfer->xferUnicodeString( &saveGameInfo->description );
  1270. Bool exists = FALSE;
  1271. Dict *dict = MapObject::getWorldDict();
  1272. if( dict )
  1273. saveGameInfo->mapLabel = dict->getAsciiString( TheKey_mapName, &exists );
  1274. // if no label was found, we'll use the map name (just filename, no directory info)
  1275. if( exists == FALSE || saveGameInfo->mapLabel == AsciiString::TheEmptyString )
  1276. {
  1277. char string[ _MAX_PATH ];
  1278. strcpy( string, TheGlobalData->m_mapName.str() );
  1279. char *p = strrchr( string, '\\' );
  1280. if( p == NULL )
  1281. saveGameInfo->mapLabel = TheGlobalData->m_mapName;
  1282. else
  1283. {
  1284. p++; // skip the '\' we're on
  1285. saveGameInfo->mapLabel.set( p );
  1286. } // end else
  1287. } // end if
  1288. // xfer map label
  1289. xfer->xferAsciiString( &saveGameInfo->mapLabel );
  1290. // campaign info
  1291. Campaign *campaign = TheCampaignManager->getCurrentCampaign();
  1292. if( campaign )
  1293. {
  1294. // campaign side
  1295. saveGameInfo->campaignSide = campaign->m_name;
  1296. xfer->xferAsciiString( &saveGameInfo->campaignSide );
  1297. // campaign mission number
  1298. saveGameInfo->missionNumber = TheCampaignManager->getCurrentMissionNumber();
  1299. xfer->xferInt( &saveGameInfo->missionNumber );
  1300. } // end if
  1301. else
  1302. {
  1303. // write empty campaign side
  1304. saveGameInfo->campaignSide = AsciiString::TheEmptyString;
  1305. xfer->xferAsciiString( &saveGameInfo->campaignSide );
  1306. // invalid mission number
  1307. saveGameInfo->missionNumber = CampaignManager::INVALID_MISSION_NUMBER;
  1308. xfer->xferInt( &saveGameInfo->missionNumber );
  1309. } // end else
  1310. } // end xfer