savegame.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. /*
  2. ** Command & Conquer Renegade(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. *** Confidential - Westwood Studios ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : Commando *
  23. * *
  24. * $Archive:: /Commando/Code/Combat/savegame.cpp $*
  25. * *
  26. * $Author:: Tom_s $*
  27. * *
  28. * $Modtime:: 3/07/02 12:05p $*
  29. * *
  30. * $Revision:: 45 $*
  31. * *
  32. *---------------------------------------------------------------------------------------------*
  33. * Functions: *
  34. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  35. #include "savegame.h"
  36. #include "definitionmgr.h"
  37. #include "debug.h"
  38. #include "chunkio.h"
  39. #include "ffactory.h"
  40. #include "combatsaveload.h"
  41. #include "physstaticsavesystem.h"
  42. #include "physdynamicsavesystem.h"
  43. #include "audiosaveload.h"
  44. #include "matrix3d.h"
  45. #include "scripts.h"
  46. #include "combat.h"
  47. #include "backgroundmgr.h"
  48. #include "conversationmgr.h"
  49. #include "weathermgr.h"
  50. #include "wwmemlog.h"
  51. #include "translatedb.h"
  52. #include "mapmgr.h"
  53. #include "encyclopediamgr.h"
  54. #include "ffactorylist.h"
  55. #include "mixfile.h"
  56. #include "texturethumbnail.h"
  57. #include "systeminfolog.h"
  58. #include "wwprofile.h"
  59. #include <stdlib.h>
  60. #include "specialbuilds.h"
  61. /*
  62. **
  63. */
  64. StringClass SaveGameManager::MapFilename;
  65. StringClass SaveGameManager::CurrentGameFilename;
  66. WideStringClass SaveGameManager::Description;
  67. int SaveGameManager::MissionDescriptionID = 0;
  68. const char * SaveGameManager::DefaultDefinitionFilename = "Objects.DDB";
  69. /*
  70. **
  71. */
  72. enum {
  73. #ifdef BETACLIENT
  74. //
  75. // This CHUNKID_LEVEL_INFO tweaking is temporary, to disallow the aircraft maps
  76. // to be used outside of the beta. The maps distributed with the Beta client must have
  77. // 1011991648 replaced with 1011991650 and 1011991649 replaced with 1011991651.
  78. //
  79. CHUNKID_LEVEL_INFO = 1011991650,
  80. #else
  81. CHUNKID_LEVEL_INFO = 1011991648,
  82. #endif
  83. CHUNKID_LEVEL_DATA,
  84. MICROCHUNKID_MAP_FILENAME = 1,
  85. MICROCHUNKID_MISSION_DESCRIPTION,
  86. MICROCHUNKID_DESCRIPTION,
  87. };
  88. /*
  89. **
  90. */
  91. void _cdecl SaveGameManager::Save_Game( const char * filename, ... )
  92. {
  93. Debug_Say(( "Save Game %s\n", filename ));
  94. CurrentGameFilename = filename;
  95. FileClass * file = _TheWritingFileFactory->Get_File( filename );
  96. WWASSERT(file);
  97. file->Open(FileClass::WRITE);
  98. ChunkSaveClass csave(file);
  99. csave.Begin_Chunk( CHUNKID_LEVEL_INFO );
  100. WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_MAP_FILENAME, MapFilename );
  101. WRITE_MICRO_CHUNK_WIDESTRING( csave, MICROCHUNKID_DESCRIPTION, Description );
  102. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MISSION_DESCRIPTION, MissionDescriptionID );
  103. csave.End_Chunk();
  104. csave.Begin_Chunk( CHUNKID_LEVEL_DATA );
  105. _ConversationMgrSaveLoad.Set_Category_To_Save (ConversationMgrClass::CATEGORY_LEVEL);
  106. SaveLoadSystemClass::Save( csave, _CombatSaveLoad );
  107. SaveLoadSystemClass::Save( csave, _ConversationMgrSaveLoad );
  108. SaveLoadSystemClass::Save( csave, _PhysDynamicSaveSystem );
  109. SaveLoadSystemClass::Save( csave, _TheEncyclopediaMgrSaveLoadSubsystem );
  110. SaveLoadSystemClass::Save( csave, _DynamicAudioSaveLoadSubsystem );
  111. SaveLoadSystemClass::Save( csave, _TheMapMgrSaveLoadSubsystem );
  112. va_list arg_list;
  113. va_start( arg_list, filename );
  114. bool done = false;
  115. while ( !done ) {
  116. SaveLoadSubSystemClass * sub_system = va_arg( arg_list, SaveLoadSubSystemClass * );
  117. if ( sub_system != NULL ) {
  118. SaveLoadSystemClass::Save( csave, *sub_system );
  119. } else {
  120. done = true;
  121. }
  122. }
  123. va_end (arg_list);
  124. csave.End_Chunk();
  125. file->Close();
  126. _TheWritingFileFactory->Return_File(file);
  127. }
  128. void SaveGameManager::Pre_Load_Game
  129. (
  130. const char * filename,
  131. StringClass & filename_to_load,
  132. StringClass & lsd_filename
  133. )
  134. {
  135. //
  136. // Get the root name and extension from the filename
  137. //
  138. char root_name[_MAX_FNAME] = { 0 };
  139. char extension[_MAX_EXT] = { 0 };
  140. ::_splitpath (filename, NULL, NULL, root_name, extension);
  141. SystemInfoLog::Set_Current_Level(root_name);
  142. filename_to_load = filename;
  143. //
  144. // Reset the search order
  145. //
  146. if (FileFactoryListClass::Get_Instance () != NULL)
  147. {
  148. FileFactoryListClass::Get_Instance ()->Reset_Search_Start();
  149. }
  150. //
  151. // Is this a mix file?
  152. //
  153. if (::strcmpi (extension, ".mix") == 0) {
  154. StringClass thumb_filename(root_name,true);
  155. thumb_filename+=".thu";
  156. ThumbnailManagerClass::Add_Thumbnail_Manager(thumb_filename,filename);
  157. //
  158. // Build the dynamic data filename from mix file's root name
  159. //
  160. filename_to_load.Format ("%s.ldd", root_name);
  161. lsd_filename .Format ("%s.lsd", root_name);
  162. //
  163. // HACK HACK - Put the level 9 mix file first...
  164. //
  165. if ( ::lstrcmpi (filename, "M09.mix") == 0 &&
  166. FileFactoryListClass::Get_Instance () != NULL)
  167. {
  168. FileFactoryListClass::Get_Instance ()->Set_Search_Start(filename);
  169. }
  170. } else if (::strcmpi (extension, ".lsd") == 0) {
  171. lsd_filename = filename;
  172. filename_to_load.Format ("%s.ldd", root_name);
  173. } else {
  174. //
  175. // Dig out the name of the map we'll use with this file
  176. //
  177. StringClass map_name(0,true);
  178. if (Peek_Map_Name (filename, map_name)) {
  179. char mix_root_name[_MAX_FNAME] = { 0 };
  180. ::_splitpath ((const char *)map_name, NULL, NULL, mix_root_name, NULL);
  181. //
  182. // Build the mix filename from the map name...
  183. //
  184. StringClass mix_filename(0, true);
  185. lsd_filename.Format ("%s.lsd", mix_root_name);
  186. mix_filename.Format ("%s.mix", mix_root_name);
  187. //
  188. // HACK HACK - Put the level 9 mix file first...
  189. //
  190. if ( ::lstrcmpi (mix_filename, "M09.mix") == 0 &&
  191. FileFactoryListClass::Get_Instance () != NULL)
  192. {
  193. FileFactoryListClass::Get_Instance ()->Set_Search_Start(mix_filename);
  194. }
  195. StringClass thumb_filename(mix_root_name,true);
  196. thumb_filename+=".thu";
  197. ThumbnailManagerClass::Add_Thumbnail_Manager(thumb_filename,mix_filename);
  198. }
  199. }
  200. return ;
  201. }
  202. void SaveGameManager::Load_Game( const char * filename )
  203. {
  204. WWLOG_PREPARE_TIME_AND_MEMORY("Load_Game");
  205. WWMEMLOG(MEM_GAMEDATA);
  206. Debug_Say(( "Load Game %s\n", filename ));
  207. CurrentGameFilename = filename;
  208. FileClass * file = _TheFileFactory->Get_File( filename );
  209. WWASSERT( file );
  210. file->Open( FileClass::READ );
  211. ChunkLoadClass cload(file);
  212. WWLOG_INTERMEDIATE("Open file");
  213. while (cload.Open_Chunk()) {
  214. switch(cload.Cur_Chunk_ID()) {
  215. case CHUNKID_LEVEL_INFO:
  216. while (cload.Open_Micro_Chunk()) {
  217. switch(cload.Cur_Micro_Chunk_ID()) {
  218. READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_MAP_FILENAME, MapFilename );
  219. READ_MICRO_CHUNK( cload, MICROCHUNKID_MISSION_DESCRIPTION, MissionDescriptionID );
  220. READ_MICRO_CHUNK_WIDESTRING( cload, MICROCHUNKID_DESCRIPTION, Description );
  221. default:
  222. Debug_Say(( "Unrecognized Level Info chunkID\n" ));
  223. break;
  224. }
  225. cload.Close_Micro_Chunk();
  226. }
  227. {
  228. // Load level specific Defs
  229. StringClass temp_ddb(MapFilename,true);
  230. WWASSERT( temp_ddb.Get_Length() > 4 );
  231. temp_ddb.Erase( MapFilename.Get_Length()-4, 4 );
  232. temp_ddb += ".ddb";
  233. Load_Definitions(temp_ddb);
  234. }
  235. WWLOG_INTERMEDIATE("Load_Definitions");
  236. // Load the static data
  237. Load_Level();
  238. WWLOG_INTERMEDIATE("Load_Level");
  239. break;
  240. case CHUNKID_LEVEL_DATA:
  241. if (CombatManager::I_Am_Server()) {
  242. SaveLoadSystemClass::Load( cload, false );
  243. }
  244. WWLOG_INTERMEDIATE("Load");
  245. break;
  246. default:
  247. Debug_Say(( "Unrecognized Level chunkID\n" ));
  248. break;
  249. }
  250. cload.Close_Chunk();
  251. }
  252. file->Close();
  253. _TheFileFactory->Return_File(file);
  254. WWLOG_INTERMEDIATE("Rest of the stuff");
  255. }
  256. bool SaveGameManager::Smart_Peek_Description
  257. (
  258. const char * filename,
  259. WideStringClass & description,
  260. WideStringClass & mission_name
  261. )
  262. {
  263. //
  264. // Get the root name and extension from the filename
  265. //
  266. char root_name[_MAX_FNAME] = { 0 };
  267. char extension[_MAX_EXT] = { 0 };
  268. ::_splitpath (filename, NULL, NULL, root_name, extension);
  269. StringClass filename_to_load(filename,true);
  270. //
  271. // Is this a mix file?
  272. //
  273. FileFactoryClass * mix_factory = NULL;
  274. if (::strcmpi (extension, ".mix") == 0) {
  275. //
  276. // Configure a mix file factory for this mix file
  277. //
  278. Debug_Say(( "Adding Temp MIX file factory %s\n", filename ));
  279. if ( FileFactoryListClass::Get_Instance() != NULL ) {
  280. mix_factory = new MixFileFactoryClass( filename, _TheFileFactory );
  281. FileFactoryListClass::Get_Instance()->Add_FileFactory( mix_factory, filename );
  282. }
  283. //
  284. // Build the dynamic data filename from mix file's root name
  285. //
  286. filename_to_load.Format ("%s.ldd", root_name);
  287. }
  288. //
  289. // Peek at the information inside this mix file...
  290. //
  291. bool retval = Peek_Description (filename_to_load, description, mission_name);
  292. //
  293. // Remove the temporary mix file factory we added
  294. //
  295. if (mix_factory != NULL) {
  296. FileFactoryListClass::Get_Instance()->Remove_FileFactory(mix_factory);
  297. delete mix_factory;
  298. mix_factory = NULL;
  299. }
  300. return retval;
  301. }
  302. bool SaveGameManager::Peek_Description
  303. (
  304. const char * filename,
  305. WideStringClass & description,
  306. WideStringClass & mission_name
  307. )
  308. {
  309. //
  310. // Open the file as a chunk
  311. //
  312. FileClass * file = _TheFileFactory->Get_File(filename);
  313. WWASSERT(file != NULL);
  314. file->Open(FileClass::READ);
  315. ChunkLoadClass cload(file);
  316. bool retval = false;
  317. int mission_name_id = 0;
  318. StringClass map_filename(0,true);
  319. //
  320. // Loop until we've found the header chunk
  321. //
  322. while (retval == false && cload.Open_Chunk()) {
  323. switch(cload.Cur_Chunk_ID()) {
  324. case CHUNKID_LEVEL_INFO:
  325. while (cload.Open_Micro_Chunk()) {
  326. switch(cload.Cur_Micro_Chunk_ID()) {
  327. //
  328. // Read the header chunks
  329. //
  330. READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_MAP_FILENAME, map_filename );
  331. READ_MICRO_CHUNK( cload, MICROCHUNKID_MISSION_DESCRIPTION, mission_name_id );
  332. READ_MICRO_CHUNK_WIDESTRING( cload, MICROCHUNKID_DESCRIPTION, description );
  333. }
  334. cload.Close_Micro_Chunk();
  335. }
  336. retval = true;
  337. break;
  338. }
  339. cload.Close_Chunk();
  340. }
  341. //
  342. // Either load the mission name from the translation database
  343. // or simply return the map filename
  344. //
  345. if (mission_name_id == 0) {
  346. mission_name.Convert_From ( map_filename );
  347. WCHAR *extension = ::wcsrchr (mission_name, L'.');
  348. if (extension != NULL) {
  349. extension[0] = 0;
  350. }
  351. } else {
  352. mission_name = TRANSLATE(mission_name_id);
  353. }
  354. //
  355. // Close the file
  356. //
  357. file->Close();
  358. _TheFileFactory->Return_File(file);
  359. return retval;
  360. }
  361. bool SaveGameManager::Peek_Map_Name( const char * filename, StringClass &map_name )
  362. {
  363. //
  364. // Open the file as a chunk
  365. //
  366. FileClass * file = _TheFileFactory->Get_File(filename);
  367. WWASSERT(file != NULL);
  368. file->Open(FileClass::READ);
  369. ChunkLoadClass cload(file);
  370. bool retval = false;
  371. //
  372. // Loop until we've found the header chunk
  373. //
  374. while (retval == false && cload.Open_Chunk()) {
  375. switch(cload.Cur_Chunk_ID()) {
  376. case CHUNKID_LEVEL_INFO:
  377. while (retval == false && cload.Open_Micro_Chunk()) {
  378. switch(cload.Cur_Micro_Chunk_ID()) {
  379. //
  380. // Read the map name string from chunk
  381. //
  382. case MICROCHUNKID_MAP_FILENAME:
  383. LOAD_MICRO_CHUNK_WWSTRING( cload, map_name );
  384. retval = true;
  385. break;
  386. }
  387. cload.Close_Micro_Chunk();
  388. }
  389. break;
  390. }
  391. cload.Close_Chunk();
  392. }
  393. //
  394. // Close the file
  395. //
  396. file->Close();
  397. _TheFileFactory->Return_File(file);
  398. return retval;
  399. }
  400. /*
  401. **
  402. */
  403. void SaveGameManager::Save_Level( void )
  404. {
  405. Debug_Say(( "Save Level %s\n", MapFilename ));
  406. Save_Save_Load_System( MapFilename,
  407. &_PhysStaticDataSaveSystem,
  408. &_PhysStaticObjectsSaveSystem,
  409. &_StaticAudioSaveLoadSubsystem,
  410. &_TheBackgroundMgr,
  411. &_TheWeatherMgr,
  412. &_TheMapMgrSaveLoadSubsystem,
  413. NULL );
  414. }
  415. void SaveGameManager::Load_Level( void )
  416. {
  417. Debug_Say(( "Load Level %s\n", MapFilename ));
  418. Load_Save_Load_System( MapFilename, false ); // false = no automatic post load processing (needs to be called explicitly)
  419. }
  420. /*
  421. **
  422. */
  423. void SaveGameManager::Save_Definitions( const char * filename )
  424. {
  425. Debug_Say(( "Save Definitions %s\n", filename ));
  426. Save_Save_Load_System( filename, &_TheDefinitionMgr, NULL );
  427. }
  428. void SaveGameManager::Load_Definitions( const char * filename )
  429. {
  430. WWMEMLOG(MEM_GAMEDATA);
  431. Debug_Say(( "Load Definitions %s\n", filename ));
  432. Load_Save_Load_System( filename, true ); // true = automatic post load processing
  433. }
  434. /*
  435. **
  436. */
  437. void _cdecl SaveGameManager::Save_Save_Load_System( const char * filename, ... )
  438. {
  439. FileClass * file = _TheWritingFileFactory->Get_File( filename );
  440. WWASSERT(file);
  441. file->Open(FileClass::WRITE);
  442. ChunkSaveClass csave(file);
  443. va_list arg_list;
  444. va_start( arg_list, filename );
  445. bool done = false;
  446. while ( !done ) {
  447. SaveLoadSubSystemClass * sub_system = va_arg( arg_list, SaveLoadSubSystemClass * );
  448. if ( sub_system != NULL ) {
  449. SaveLoadSystemClass::Save( csave, *sub_system );
  450. } else {
  451. done = true;
  452. }
  453. }
  454. va_end (arg_list);
  455. file->Close();
  456. _TheWritingFileFactory->Return_File(file);
  457. }
  458. void SaveGameManager::Load_Save_Load_System( const char * filename, bool auto_post_load )
  459. {
  460. FileClass * file = _TheFileFactory->Get_File( filename );
  461. if ( file != NULL ) {
  462. file->Open( FileClass::READ );
  463. ChunkLoadClass cload(file);
  464. SaveLoadSystemClass::Load( cload, auto_post_load );
  465. file->Close();
  466. _TheFileFactory->Return_File(file);
  467. } else {
  468. Debug_Say(( "Failed to load file %s\n", filename ));
  469. // WWASSERT( file );
  470. }
  471. }