2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Mission/MissionCampaign.cpp $
15 * source for dealing with campaigns
18 * Revision 1.8 2003/06/11 18:30:33 taylor
21 * Revision 1.7 2003/05/25 02:30:42 taylor
24 * Revision 1.6 2002/07/24 00:20:42 relnev
27 * Revision 1.5 2002/06/21 03:34:05 relnev
28 * implemented a stub and fixed a path
30 * Revision 1.4 2002/06/09 04:41:22 relnev
31 * added copyright header
33 * Revision 1.3 2002/06/09 03:16:04 relnev
36 * removed unneeded asm, old sdl 2d setup.
38 * fixed crash caused by opengl_get_region.
40 * Revision 1.2 2002/06/02 04:26:34 relnev
43 * Revision 1.1.1.1 2002/05/03 03:28:09 root
47 * 23 9/14/99 4:35a Dave
48 * Argh. Added all kinds of code to handle potential crashes in debriefing
51 * 22 9/09/99 11:40p Dave
52 * Handle an SDL_assert() in beam code. Added supernova sounds. Play the right
53 * 2 end movies properly, based upon what the player did in the mission.
55 * 21 9/09/99 9:34a Jefff
56 * fixed a potential exit-loop bug
58 * 20 9/07/99 6:55p Jefff
59 * functionality to break out of a loop. hacked functionality to jump to
60 * a specific mission in a campaign -- doesnt grant ships/weapons from
61 * skipped missions tho.
63 * 19 9/07/99 2:19p Jefff
64 * clear skip mission player vars on mission skip
66 * 18 9/06/99 9:45p Jefff
67 * break out of loop and skip mission support
69 * 17 9/06/99 6:38p Dave
70 * Improved CD detection code.
72 * 16 9/03/99 1:32a Dave
73 * CD checking by act. Added support to play 2 cutscenes in a row
74 * seamlessly. Fixed super low level cfile bug related to files in the
75 * root directory of a CD. Added cheat code to set campaign mission # in
78 * 15 8/27/99 12:04a Dave
79 * Campaign loop screen.
81 * 14 8/04/99 5:36p Andsager
82 * Show upsell screens at end of demo campaign before returning to main
85 * 13 2/05/99 3:50p Anoop
86 * Removed dumb campaign mission stats saving from multiplayer.
88 * 12 12/17/98 2:43p Andsager
89 * Modify fred campaign save file to include optional mission loops
91 * 11 12/12/98 3:17p Andsager
92 * Clean up mission eval, goal, event and mission scoring.
94 * 10 12/10/98 9:59a Andsager
95 * Fix some bugs with mission loops
97 * 9 12/09/98 1:56p Andsager
98 * Initial checkin of mission loop
100 * 8 11/05/98 5:55p Dave
101 * Big pass at reducing #includes
103 * 7 11/03/98 4:48p Johnson
104 * Fixed campaign file versioning bug left over from Silent Threat port.
106 * 6 10/23/98 3:51p Dave
107 * Full support for tstrings.tbl and foreign languages. All that remains
108 * is to make it active in Fred.
110 * 5 10/13/98 2:47p Andsager
111 * Remove reference to Tech_shivan_species_avail
113 * 4 10/13/98 9:28a Dave
114 * Started neatening up freespace.h. Many variables renamed and
115 * reorganized. Added AlphaColors.[h,cpp]
117 * 3 10/07/98 6:27p Dave
118 * Globalized mission and campaign file extensions. Removed Silent Threat
119 * special code. Moved \cache \players and \multidata into the \data
122 * 2 10/07/98 10:53a Dave
125 * 1 10/07/98 10:49a Dave
127 * 95 9/10/98 1:17p Dave
128 * Put in code to flag missions and campaigns as being MD or not in Fred
129 * and Freespace. Put in multiplayer support for filtering out MD
130 * missions. Put in multiplayer popups for warning of non-valid missions.
132 * 94 9/01/98 4:25p Dave
133 * Put in total (I think) backwards compatibility between mission disk
134 * freespace and non mission disk freespace, including pilot files and
135 * campaign savefiles.
137 * 93 7/06/98 4:10p Hoffoss
138 * Fixed some bugs that presented themselves when trying to use a pilot
139 * that has a no longer existent campaign active. Also expanded the
140 * campaign load code to actually return a proper error code, instead of
141 * always trapping errors internally and crashing, and always returning 0.
143 * 92 6/17/98 9:30a Allender
144 * fixed red alert replay stats clearing problem
146 * 91 6/01/98 11:43a John
147 * JAS & MK: Classified all strings for localization.
149 * 90 5/25/98 1:29p Allender
150 * end mission sequencing
152 * 89 5/21/98 9:25p Allender
153 * endgame movie always viewable at end of campaign
155 * 88 5/13/98 5:14p Allender
156 * red alert support to go back to previous mission
158 * 87 5/12/98 4:16p Hoffoss
159 * Fixed bug where not all missions in all campaigns were being filtered
160 * out of stand alone mission listing in simulator room.
162 * 86 5/05/98 3:29p Hoffoss
163 * Changed code so description is BEFORE num players in campaign file.
164 * Other code is relying on this ordering.
166 * 85 5/05/98 12:19p Dave
167 * campaign description goes *after* num players
169 * 84 5/04/98 5:52p Comet
170 * Fixed bug with Galatea/Bastion selection when finishing missions.
172 * 83 5/01/98 2:46p Duncan
173 * fix a cfile problem with campaigns related to the new cfile stuff
175 * 82 5/01/98 12:34p John
176 * Added code to force FreeSpace to run in the same dir as exe and made
177 * all the parse error messages a little nicer.
179 * 81 4/30/98 7:01p Duncan
180 * AL: don't allow deletion of campaign files in multiplayer
182 * 80 4/30/98 4:53p John
183 * Restructured and cleaned up cfile code. Added capability to read off
184 * of CD-ROM drive and out of multiple pack files.
194 #include <sys/types.h>
195 #include <sys/stat.h>
205 #include "missioncampaign.h"
206 #include "gamesequence.h"
209 #include "missionload.h"
210 #include "freespace.h"
214 #include "missiongoals.h"
217 #include "techmenu.h"
218 #include "eventmusic.h"
219 #include "alphacolors.h"
220 #include "localize.h"
221 #include "supernova.h"
223 // mission disk stuff
224 #define CAMPAIGN_SAVEFILE_MAX_SHIPS_OLD 75
225 #define CAMPAIGN_SAVEFILE_MAX_WEAPONS_OLD 44
227 #define CAMPAIGN_INITIAL_RELEASE_FILE_VERSION 6
229 // campaign wasn't ended
230 int Campaign_ended_in_mission = 0;
232 // stuff for selecting campaigns. We need to keep both arrays around since we display the
233 // list of campaigns by name, but must load campaigns by filename
234 char *Campaign_names[MAX_CAMPAIGNS];
235 char *Campaign_file_names[MAX_CAMPAIGNS];
238 const char *campaign_types[MAX_CAMPAIGN_TYPES] =
247 // modules local variables to deal with getting new ships/weapons available to the player
248 int Num_granted_ships, Num_granted_weapons; // per mission counts of new ships and weapons
249 int Granted_ships[MAX_SHIP_TYPES];
250 int Granted_weapons[MAX_WEAPON_TYPES];
252 // variables to control the UI stuff for loading campaigns
253 static UI_WINDOW Campaign_window;
254 static UI_LISTBOX Campaign_listbox;
255 static UI_BUTTON Campaign_okb, Campaign_cancelb;
260 // variables with deal with the campaign save file
261 #if defined(FS2_DEMO)
262 #define CAMPAIGN_FILE_VERSION 10
263 #define CAMPAIGN_FILE_COMPATIBLE_VERSION CAMPAIGN_FILE_VERSION
264 #elif defined(MAKE_FS1)
265 #define CAMPAIGN_FILE_VERSION 7
266 #define CAMPAIGN_FILE_COMPATIBLE_VERSION CAMPAIGN_INITIAL_RELEASE_FILE_VERSION
268 #define CAMPAIGN_FILE_VERSION 12
269 //#define CAMPAIGN_FILE_COMPATIBLE_VERSION CAMPAIGN_INITIAL_RELEASE_FILE_VERSION
270 #define CAMPAIGN_FILE_COMPATIBLE_VERSION CAMPAIGN_FILE_VERSION
272 #define CAMPAIGN_FILE_ID 0xbeefcafe
274 // variables with deal with the campaign stats save file
275 #define CAMPAIGN_STATS_FILE_VERSION 1
276 #define CAMPAIGN_STATS_FILE_COMPATIBLE_VERSION 1
277 #define CAMPAIGN_STATS_FILE_ID 0xabbadaad
279 // mission_campaign_get_name returns a string (which is malloced in this routine) of the name
280 // of the given freespace campaign file. In the type field, we return if the campaign is a single
281 // player or multiplayer campaign. The type field will only be valid if the name returned is non-NULL
282 int mission_campaign_get_info(const char *filename, char *name, int *type, int *max_players, char **desc)
285 char campaign_type[NAME_LENGTH], fname[MAX_FILENAME_LEN];
287 SDL_assert( name != NULL );
288 SDL_assert( type != NULL );
293 SDL_strlcpy(fname, filename, SDL_arraysize(fname));
294 if ((strlen(fname) < 4) || SDL_strcasecmp(fname + strlen(fname) - 4, FS_CAMPAIGN_FILE_EXT)){
295 SDL_strlcat(fname, FS_CAMPAIGN_FILE_EXT, SDL_arraysize(fname));
298 SDL_assert(strlen(fname) < MAX_FILENAME_LEN);
301 read_file_text( fname );
303 required_string("$Name:");
305 stuff_string( name, F_NAME, NULL );
306 if ( name == NULL ) {
308 nprintf(("Warning", "No name found for campaign file %s\n", filename));
310 // close localization
316 required_string( "$Type:" );
317 stuff_string( campaign_type, F_NAME, NULL );
320 for (i=0; i<MAX_CAMPAIGN_TYPES; i++) {
321 if ( !SDL_strcasecmp(campaign_type, campaign_types[i]) ) {
328 if (optional_string("+Description:")) {
329 *desc = stuff_and_malloc_string(F_MULTITEXT, NULL, MISSION_DESC_LENGTH);
333 // if this is a multiplayer campaign, get the max players
335 skip_to_string("+Num Players:");
336 stuff_int(max_players);
339 // if we found a valid campaign type
341 // close localization
346 } catch (parse_error_t rval) {
347 if (rval == PARSE_ERROR_FILE_NOT_FOUND) {
348 // close localization
354 Error(LOCATION, "Error parsing '%s'\r\nError code = %i.\r\n", fname, (int)rval);
357 Int3(); // get Allender -- incorrect type found
359 // close localization
365 // parses campaign and returns a list of missions in it. Returns number of missions added to
366 // the 'list', and up to 'max' missions may be added to 'list'. Returns negative on error.
368 int mission_campaign_get_mission_list(const char *filename, char **list, int max)
371 char name[NAME_LENGTH];
373 filename = cf_add_ext(filename, FS_CAMPAIGN_FILE_EXT);
375 // read the mission file and get the list of mission filenames
377 read_file_text(filename);
380 while (skip_to_string("$Mission:") > 0) {
381 stuff_string(name, F_NAME, NULL);
383 list[num++] = strdup(name);
385 } catch (parse_error_t) {
386 // since we can't return count of allocated elements, free them instead
387 for (i=0; i<num; i++)
396 // gets optional ship/weapon information
397 void mission_campaign_get_sw_info()
399 int i, count, ship_list[MAX_SHIP_TYPES], weapon_list[MAX_WEAPON_TYPES];
401 // set allowable ships to the SIF_PLAYER_SHIPs
402 memset( Campaign.ships_allowed, 0, sizeof(Campaign.ships_allowed) );
403 for (i = 0; i < MAX_SHIP_TYPES; i++ ) {
404 if ( Ship_info[i].flags & SIF_PLAYER_SHIP )
405 Campaign.ships_allowed[i] = 1;
408 for (i = 0; i < MAX_WEAPON_TYPES; i++ )
409 Campaign.weapons_allowed[i] = 1;
411 if ( optional_string("+Starting Ships:") ) {
412 for (i = 0; i < MAX_SHIP_TYPES; i++ )
413 Campaign.ships_allowed[i] = 0;
415 count = stuff_int_list(ship_list, MAX_SHIP_TYPES, SHIP_INFO_TYPE);
417 // now set the array elements stating which ships we are allowed
418 for (i = 0; i < count; i++ ) {
419 if ( Ship_info[ship_list[i]].flags & SIF_PLAYER_SHIP )
420 Campaign.ships_allowed[ship_list[i]] = 1;
424 if ( optional_string("+Starting Weapons:") ) {
425 for (i = 0; i < MAX_WEAPON_TYPES; i++ )
426 Campaign.weapons_allowed[i] = 0;
428 count = stuff_int_list(weapon_list, MAX_WEAPON_TYPES, WEAPON_POOL_TYPE);
430 // now set the array elements stating which ships we are allowed
431 for (i = 0; i < count; i++ )
432 Campaign.weapons_allowed[weapon_list[i]] = 1;
436 // mission_campaign_load starts a new campaign. It reads in the mission information in the campaign file
437 // It also sets up all variables needed inside of the game to deal with starting mission numbers, etc
439 // Note: Due to difficulties in generalizing this function, parts of it are duplicated throughout
440 // this file. If you change the format of the campaign file, you should be sure these related
441 // functions work properly and update them if it breaks them.
442 int mission_campaign_load( const char *filename, int load_savefile )
445 char name[NAME_LENGTH], type[NAME_LENGTH];
447 filename = cf_add_ext(filename, FS_CAMPAIGN_FILE_EXT);
452 // read the mission file and get the list of mission filenames
454 // be sure to remove all old malloced strings of Mission_names
455 // we must also free any goal stuff that was from a previous campaign
456 // this also frees sexpressions so the next call to init_sexp will be able to reclaim
457 // nodes previously used by another campaign.
458 mission_campaign_close();
460 SDL_strlcpy( Campaign.filename, filename, SDL_arraysize(Campaign.filename) );
462 // only initialize the sexpression stuff when Fred isn't running. It'll screw things up major
464 if ( !Fred_running ){
465 init_sexp(); // must initialize the sexpression stuff
468 read_file_text( filename );
470 memset( &Campaign, 0, sizeof(Campaign) );
472 // copy filename to campaign structure minus the extension
473 len = SDL_min(strlen(filename) - 4 + 1, SDL_arraysize(Campaign.filename));
474 SDL_strlcpy(Campaign.filename, filename, len);
476 required_string("$Name:");
477 stuff_string( name, F_NAME, NULL );
479 //Store campaign name in the global struct
480 SDL_strlcpy( Campaign.name, name, SDL_arraysize(Campaign.name) );
482 required_string( "$Type:" );
483 stuff_string( type, F_NAME, NULL );
485 for (i = 0; i < MAX_CAMPAIGN_TYPES; i++ ) {
486 if ( !SDL_strcasecmp(type, campaign_types[i]) ) {
492 if ( i == MAX_CAMPAIGN_TYPES )
493 Error(LOCATION, "Unknown campaign type %s!", type);
495 Campaign.desc = NULL;
496 if (optional_string("+Description:"))
497 Campaign.desc = stuff_and_malloc_string(F_MULTITEXT, NULL, MISSION_DESC_LENGTH);
499 // if the type is multiplayer -- get the number of players
500 Campaign.num_players = 0;
501 if ( Campaign.type != CAMPAIGN_TYPE_SINGLE) {
502 required_string("+Num players:");
503 stuff_int( &(Campaign.num_players) );
507 // check if mission disk -
508 // doesn't do anything but prevent non fatal error messages
509 optional_string("+Missiondisk");
512 // parse the optional ship/weapon information
513 mission_campaign_get_sw_info();
515 // parse the mission file and actually read in the mission stuff
516 Campaign.num_missions = 0;
517 while ( required_string_either("#End", "$Mission:") ) {
520 required_string("$Mission:");
521 stuff_string(name, F_NAME, NULL);
522 cm = &Campaign.missions[Campaign.num_missions];
523 cm->name = strdup(name);
525 cm->briefing_cutscene[0] = 0;
526 if ( optional_string("+Briefing Cutscene:") )
527 stuff_string( cm->briefing_cutscene, F_NAME, NULL );
530 if (optional_string("+Flags:"))
531 stuff_int(&cm->flags);
534 if ( optional_string("+Formula:") ) {
535 cm->formula = get_sexp_main();
536 if ( !Fred_running ) {
537 SDL_assert ( cm->formula != -1 );
538 sexp_mark_persistent( cm->formula );
541 if ( cm->formula == -1 ){
542 // close localization
545 return CAMPAIGN_ERROR_SEXP_EXHAUSTED;
550 // Do misison looping stuff
551 cm->has_mission_loop = 0;
552 if ( optional_string("+Mission Loop:") ) {
553 cm->has_mission_loop = 1;
556 cm->mission_loop_desc = NULL;
557 if ( optional_string("+Mission Loop Text:")) {
558 cm->mission_loop_desc = stuff_and_malloc_string(F_MULTITEXT, NULL, MISSION_DESC_LENGTH);
561 cm->mission_loop_brief_anim = NULL;
562 if ( optional_string("+Mission Loop Brief Anim:")) {
563 cm->mission_loop_brief_anim = stuff_and_malloc_string(F_MULTITEXT, NULL, MAX_FILENAME_LEN);
566 cm->mission_loop_brief_sound = NULL;
567 if ( optional_string("+Mission Loop Brief Sound:")) {
568 cm->mission_loop_brief_sound = stuff_and_malloc_string(F_MULTITEXT, NULL, MAX_FILENAME_LEN);
571 cm->mission_loop_formula = -1;
572 if ( optional_string("+Formula:") ) {
573 cm->mission_loop_formula = get_sexp_main();
574 if ( !Fred_running ) {
575 SDL_assert ( cm->mission_loop_formula != -1 );
576 sexp_mark_persistent( cm->mission_loop_formula );
579 if ( cm->mission_loop_formula == -1 ){
580 // close localization
583 return CAMPAIGN_ERROR_SEXP_EXHAUSTED;
588 if (optional_string("+Level:")) {
589 stuff_int( &cm->level );
590 if ( cm->level == 0 ) // check if the top (root) of the whole tree
591 Campaign.next_mission = Campaign.num_missions;
594 Campaign.realign_required = 1;
596 if (optional_string("+Position:"))
597 stuff_int( &cm->pos );
599 Campaign.realign_required = 1;
613 Campaign.num_missions++;
615 } catch (parse_error_t rval) {
616 (void)rval; // suppress unused warning in release build
617 mprintf(("Error parsing '%s'\r\nError code = %i.\r\n", filename, (int)rval));
619 // close localization
622 return CAMPAIGN_ERROR_CORRUPT;
625 // set up the other variables for the campaign stuff. After initializing, we must try and load
626 // the campaign save file for this player. Since all campaign loads go through this routine, I
627 // think this place should be the only necessary place to load the campaign save stuff. The campaign
628 // save file will get written when a mission has ended by player choice.
629 Campaign.next_mission = 0;
630 Campaign.prev_mission = -1;
631 Campaign.current_mission = -1;
633 // loading the campaign will get us to the current and next mission that the player must fly
634 // plus load all of the old goals that future missions might rely on.
635 if (!Fred_running && load_savefile && (Campaign.type == CAMPAIGN_TYPE_SINGLE)) {
636 mission_campaign_savefile_load(Campaign.filename);
639 // close localization
645 // mission_campaign_load_by_name() loads up a freespace campaign given the filename. This routine
646 // is used to load up campaigns when a pilot file is loaded. Generally, the
647 // filename will probably be the freespace campaign file, but not necessarily.
648 int mission_campaign_load_by_name( const char *filename )
650 char real_filename[MAX_FILENAME_LEN+1] = { 0 };
651 char name[NAME_LENGTH],test[5];
652 int type,max_players;
654 // make sure to tack on .fsc on the end if its not there already
655 if(strlen(filename) > 0){
656 SDL_strlcpy(real_filename, filename, MAX_FILENAME_LEN);
658 if(strlen(real_filename) > 4){
659 SDL_strlcpy(test, real_filename+(strlen(real_filename)-4), SDL_arraysize(test));
660 if(strcmp(test, FS_CAMPAIGN_FILE_EXT)!=0){
661 SDL_strlcat(real_filename, FS_CAMPAIGN_FILE_EXT, SDL_arraysize(real_filename));
664 SDL_strlcat(real_filename, FS_CAMPAIGN_FILE_EXT, SDL_arraysize(real_filename));
667 Error(LOCATION,"Tried to load campaign file with illegal length/extension!");
670 if (!mission_campaign_get_info(real_filename, name, &type, &max_players)){
675 Campaign_file_names[Num_campaigns] = real_filename;
676 Campaign_names[Num_campaigns] = name;
678 mission_campaign_load(real_filename);
682 int mission_campaign_load_by_name_csfe( const char *filename, const char *callsign )
684 Game_mode |= GM_NORMAL;
685 SDL_strlcpy(Player->callsign, callsign, SDL_arraysize(Player->callsign));
686 return mission_campaign_load_by_name( filename);
690 // mission_campaign_init initializes some variables then loads the default Freespace single player campaign.
691 void mission_campaign_init()
693 memset(&Campaign, 0, sizeof(Campaign) );
696 // Fill in the root of the campaign save filename
697 void mission_campaign_savefile_generate_root(char *filename, const int max_len)
699 char base[MAX_FILENAME_LEN];
701 SDL_assert ( strlen(Campaign.filename) != 0 );
703 // build up the filename for the save file. There could be a problem with filename length,
704 // but this problem can get fixed in several ways -- ignore the problem for now though.
705 base_filename(Campaign.filename, base, SDL_arraysize(base));
706 SDL_assert ( (int)(strlen(base) + strlen(Player->callsign)) < max_len );
708 SDL_snprintf( filename, max_len, NOX("%s.%s."), Player->callsign, base );
711 // mission_campaign_savefile_save saves the state of the campaign. This function will probably always be called
712 // then the player is done flying a mission in the campaign path. It will save the missions played, the
713 // state of the goals, etc.
715 int mission_campaign_savefile_save()
717 char filename[MAX_PATH_LEN];
719 int i,j, mission_count;
721 memset(filename, 0, sizeof(filename));
722 mission_campaign_savefile_generate_root(filename, SDL_arraysize(filename));
724 // name the file differently depending on whether we're in single player or multiplayer mode
725 // single player : *.csg
726 SDL_strlcat( filename, NOX("csg"), SDL_arraysize(filename) );
728 fp = cfopen(filename,"wb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
733 // Write out campaign file info
734 cfwrite_int( CAMPAIGN_FILE_ID,fp );
735 cfwrite_int( CAMPAIGN_FILE_VERSION,fp );
737 // put in the file signature (single or multiplayer campaign) - see MissionCampaign.h for the #defines
738 cfwrite_int( CAMPAIGN_SINGLE_PLAYER_SIG, fp );
740 // do we need to write out the filename of the campaign?
741 cfwrite_string_len( Campaign.filename, fp );
742 cfwrite_int( Campaign.prev_mission, fp );
743 cfwrite_int( Campaign.next_mission, fp );
745 if (CAMPAIGN_FILE_VERSION >= 12) {
746 cfwrite_int( Campaign.loop_reentry, fp );
747 cfwrite_int( Campaign.loop_enabled, fp );
750 // write out the information for ships/weapons which this player is allowed to use
751 cfwrite_int(Num_ship_types, fp);
752 cfwrite_int(Num_weapon_types, fp);
753 for ( i = 0; i < Num_ship_types; i++ ){
754 cfwrite_char( Campaign.ships_allowed[i], fp );
757 for ( i = 0; i < Num_weapon_types; i++ ){
758 cfwrite_char( Campaign.weapons_allowed[i], fp );
761 // write out the completed mission matrix. Used to tell which missions the player
762 // can replay in the simulator. Also, each completed mission contains a list of the goals
763 // that were in the mission along with the goal completion status.
764 cfwrite_int( Campaign.num_missions_completed, fp );
765 for (i = 0; i < MAX_CAMPAIGN_MISSIONS; i++ ) {
766 if ( Campaign.missions[i].completed ) {
767 cfwrite_int( i, fp );
768 cfwrite_int( Campaign.missions[i].num_goals, fp );
769 for ( j = 0; j < Campaign.missions[i].num_goals; j++ ) {
770 cfwrite_string_len( Campaign.missions[i].goals[j].name, fp );
771 cfwrite_char( Campaign.missions[i].goals[j].status, fp );
773 cfwrite_int( Campaign.missions[i].num_events, fp );
774 for ( j = 0; j < Campaign.missions[i].num_events; j++ ) {
775 cfwrite_string_len( Campaign.missions[i].events[j].name, fp );
776 cfwrite_char( Campaign.missions[i].events[j].status, fp );
780 cfwrite_int(Campaign.missions[i].flags, fp);
788 // ugh! due to horrible bug, the stats saved at the end of every level were not written
789 // out to disk. Write out a seperate file to do this. We will only read it in if we actually
791 memset(filename, 0, sizeof(filename));
792 mission_campaign_savefile_generate_root(filename, SDL_arraysize(filename));
794 // name the file differently depending on whether we're in single player or multiplayer mode
795 // single player : *.csg
796 SDL_strlcat( filename, NOX("css"), SDL_arraysize(filename) );
798 fp = cfopen(filename,"wb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
803 // Write out campaign file info
804 cfwrite_int( CAMPAIGN_STATS_FILE_ID,fp );
805 cfwrite_int( CAMPAIGN_STATS_FILE_VERSION,fp );
807 // determine how many missions we are saving -- I think that this method is safer than the method
808 // I used for release
810 for ( i = 0; i < Campaign.num_missions; i++ ) {
811 if ( Campaign.missions[i].completed ) {
816 // write out the stats information to disk.
817 cfwrite_int( mission_count, fp );
818 for (i = 0; i < Campaign.num_missions; i++ ) {
819 if ( Campaign.missions[i].completed ) {
820 cfwrite_int( i, fp );
821 cfwrite( &Campaign.missions[i].stats, sizeof(scoring_struct), 1, fp );
830 // The following function always only ever ever ever called by CSFE!!!!!
831 int campaign_savefile_save(const char *pname)
833 if (Campaign.type == CAMPAIGN_TYPE_SINGLE)
834 Game_mode &= ~GM_MULTIPLAYER;
836 Game_mode |= GM_MULTIPLAYER;
838 SDL_strlcpy(Player->callsign, pname, SDL_arraysize(Player->callsign));
839 //memcpy(&Campaign, camp, sizeof(campaign));
840 return mission_campaign_savefile_save();
844 // the below two functions is internal to this module. It is here so that I can group the save/load
845 // functions together.
848 // mission_campaign_savefile_delete deletes any save file in the players directory for the given
850 void mission_campaign_savefile_delete( const char *cfilename, int is_multi )
852 char filename[MAX_PATH_LEN], base[MAX_FILENAME_LEN];
854 base_filename(cfilename, base, SDL_arraysize(base));
856 if ( Player->flags & PLAYER_FLAGS_IS_MULTI ) {
857 return; // no such thing as a multiplayer campaign savefile
860 SDL_snprintf( filename, SDL_arraysize(filename), NOX("%s.%s.csg"), Player->callsign, base );
862 cf_delete( filename, CF_TYPE_SINGLE_PLAYERS );
865 void campaign_delete_save( const char *cfn, const char *pname)
867 SDL_strlcpy(Player->callsign, pname, SDL_arraysize(Player->callsign));
868 mission_campaign_savefile_delete(cfn);
871 // next function deletes all the save files for this particular pilot. Just call cfile function
872 // which will delete multiple files
873 // Player_select_mode tells us whether we are deleting single or multiplayer files
874 void mission_campaign_delete_all_savefiles( const char *pilot_name, int is_multi )
876 int dir_type, num_files, i;
877 char *names[MAX_CAMPAIGNS], spec[MAX_FILENAME_LEN + 2];
880 int (*filter_save)(const char *filename);
883 return; // can't have multiplayer campaign save files
887 dir_type = CF_TYPE_SINGLE_PLAYERS;
889 SDL_snprintf(spec, SDL_arraysize(spec), NOX("%s.*%s"), pilot_name, ext);
891 // HACK HACK HACK HACK!!!! cf_get_file_list is not reentrant. Pretty dumb because it should
892 // be. I have to save any file filters
893 filter_save = Get_file_list_filter;
894 Get_file_list_filter = NULL;
895 num_files = cf_get_file_list(MAX_CAMPAIGNS, names, dir_type, spec);
896 Get_file_list_filter = filter_save;
898 for (i=0; i<num_files; i++) {
899 SDL_strlcpy(filename, names[i], SDL_arraysize(filename));
900 SDL_strlcat(filename, ext, SDL_arraysize(filename));
901 cf_delete(filename, dir_type);
906 // mission_campaign_savefile_load takes a filename of a campaign file as a parameter and loads all
907 // of the information stored in the campaign file.
908 void mission_campaign_savefile_load( const char *cfilename )
910 char filename[MAX_PATH_LEN], base[MAX_FILENAME_LEN];
911 int version, i, num, j, num_stats_blocks;
915 SDL_assert ( strlen(cfilename) != 0 );
917 // probably only called from single player games anymore!!! should be anyway
918 SDL_assert( Game_mode & GM_NORMAL ); // get allender or DaveB. trying to save campaign in multiplayer
920 // build up the filename for the save file. There could be a problem with filename length,
921 // but this problem can get fixed in several ways -- ignore the problem for now though.
922 base_filename(cfilename, base, SDL_arraysize(base));
923 SDL_assert ( (strlen(base) + strlen(Player->callsign)) < SDL_arraysize(filename) );
925 if(Game_mode & GM_MULTIPLAYER)
926 SDL_snprintf( filename, SDL_arraysize(filename), NOX("%s.%s.msg"), Player->callsign, base );
928 SDL_snprintf( filename, SDL_arraysize(filename), NOX("%s.%s.csg"), Player->callsign, base );
930 fp = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS );
934 id = cfread_int( fp );
935 if ( id != CAMPAIGN_FILE_ID ) {
936 Warning(LOCATION, "Campaign save file has invalid signature");
941 version = cfread_int( fp );
942 if ( version < CAMPAIGN_FILE_COMPATIBLE_VERSION ) {
943 Warning(LOCATION, "Campaign save file too old -- not compatible. Deleting file.\nYou can continue from here without trouble\n\n");
945 cf_delete( filename, CF_TYPE_SINGLE_PLAYERS );
949 // verify that we are loading the correct type of campaign file for the mode that we are in.
951 type_sig = cfread_int( fp );
953 type_sig = CAMPAIGN_SINGLE_PLAYER_SIG;
955 SDL_assert( ((Game_mode & GM_MULTIPLAYER) && (type_sig==CAMPAIGN_MULTI_PLAYER_SIG)) || (!(Game_mode & GM_MULTIPLAYER) && (type_sig==CAMPAIGN_SINGLE_PLAYER_SIG)) );
957 Campaign.type = type_sig == CAMPAIGN_SINGLE_PLAYER_SIG ? CAMPAIGN_TYPE_SINGLE : CAMPAIGN_TYPE_MULTI_COOP;
959 // read in the filename of the campaign and compare the filenames to be sure that
960 // we are reading data that really belongs to this campaign. I think that this check
962 cfread_string_len( filename, SDL_arraysize(filename), fp );
963 /*if ( SDL_strcasecmp( filename, cfilename) ) { // Used to be !SDL_strcasecmp. How did this ever work? --MK, 11/9/97
964 Warning(LOCATION, "Campaign save file appears corrupt because of mismatching filenames.");
969 Campaign.prev_mission = cfread_int( fp );
970 Campaign.next_mission = cfread_int( fp );
973 Campaign.loop_reentry = cfread_int( fp );
974 Campaign.loop_enabled = cfread_int( fp );
977 // load information about ships/weapons allowed
978 int ship_count, weapon_count;
980 // if earlier than mission disk version, use old MAX_SHIP_TYPES, otherwise read from the file
981 if(version <= CAMPAIGN_INITIAL_RELEASE_FILE_VERSION){
982 ship_count = CAMPAIGN_SAVEFILE_MAX_SHIPS_OLD;
983 weapon_count = CAMPAIGN_SAVEFILE_MAX_WEAPONS_OLD;
985 ship_count = cfread_int(fp);
986 weapon_count = cfread_int(fp);
989 for ( i = 0; i < ship_count; i++ ){
990 Campaign.ships_allowed[i] = cfread_ubyte( fp );
993 for ( i = 0; i < weapon_count; i++ ){
994 Campaign.weapons_allowed[i] = cfread_ubyte( fp );
997 // read in the completed mission matrix. Used to tell which missions the player
998 // can replay in the simulator. Also, each completed mission contains a list of the goals
999 // that were in the mission along with the goal completion status.
1000 Campaign.num_missions_completed = cfread_int( fp );
1001 for (i = 0; i < Campaign.num_missions_completed; i++ ) {
1002 num = cfread_int( fp );
1003 Campaign.missions[num].completed = 1;
1004 Campaign.missions[num].num_goals = cfread_int( fp );
1006 // be sure to malloc out space for the goals stuff, then zero the memory!!! Don't do malloc
1007 // if there are no goals
1008 Campaign.missions[num].goals = (mgoal *)malloc( Campaign.missions[num].num_goals * sizeof(mgoal) );
1009 if ( Campaign.missions[num].num_goals > 0 ) {
1010 memset( Campaign.missions[num].goals, 0, sizeof(mgoal) * Campaign.missions[num].num_goals );
1011 SDL_assert( Campaign.missions[num].goals != NULL );
1014 // now read in the goal information for this mission
1015 for ( j = 0; j < Campaign.missions[num].num_goals; j++ ) {
1016 cfread_string_len( Campaign.missions[num].goals[j].name, NAME_LENGTH, fp );
1017 Campaign.missions[num].goals[j].status = cfread_char( fp );
1020 // get the events from the savefile
1021 Campaign.missions[num].num_events = cfread_int( fp );
1023 // be sure to malloc out space for the events stuff, then zero the memory!!! Don't do malloc
1024 // if there are no events
1025 // if (Campaign.missions[num].events < 0)
1026 // Campaign.missions[num].events = 0;
1027 Campaign.missions[num].events = (mevent *)malloc( Campaign.missions[num].num_events * sizeof(mevent) );
1028 if ( Campaign.missions[num].num_events > 0 ) {
1029 memset( Campaign.missions[num].events, 0, sizeof(mevent) * Campaign.missions[num].num_events );
1030 SDL_assert( Campaign.missions[num].events != NULL );
1033 // now read in the event information for this mission
1034 for ( j = 0; j < Campaign.missions[num].num_events; j++ ) {
1035 cfread_string_len( Campaign.missions[num].events[j].name, NAME_LENGTH, fp );
1036 Campaign.missions[num].events[j].status = cfread_char( fp );
1041 Campaign.missions[num].flags = cfread_int(fp);
1048 // now, try and read in the campaign stats saved information. This code was added for the 1.03 patch
1049 // since the stats data was never written out to disk. We try and open the file, and if we cannot find
1050 // it, then simply return
1051 SDL_snprintf( filename, SDL_arraysize(filename), NOX("%s.%s.css"), Player->callsign, base );
1053 fp = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS );
1057 id = cfread_int( fp );
1058 if ( id != CAMPAIGN_STATS_FILE_ID ) {
1059 Warning(LOCATION, "Campaign stats save file has invalid signature");
1064 version = cfread_int( fp );
1065 if ( version < CAMPAIGN_STATS_FILE_COMPATIBLE_VERSION ) {
1066 Warning(LOCATION, "Campaign save file too old -- not compatible. Deleting file.\nYou can continue from here without trouble\n\n");
1068 cf_delete( filename, CF_TYPE_SINGLE_PLAYERS );
1072 num_stats_blocks = cfread_int( fp );
1073 for (i = 0; i < num_stats_blocks; i++ ) {
1074 num = cfread_int( fp );
1075 cfread( &Campaign.missions[num].stats, sizeof(scoring_struct), 1, fp );
1081 // the following code only ever called by CSFE!!!!
1082 void campaign_savefile_load(const char *fname, const char *pname)
1084 if (Campaign.type==CAMPAIGN_TYPE_SINGLE) {
1085 Game_mode &= ~GM_MULTIPLAYER;
1086 Game_mode &= GM_NORMAL;
1089 Game_mode |= GM_MULTIPLAYER;
1090 SDL_strlcpy(Player->callsign, pname, SDL_arraysize(Player->callsign));
1091 mission_campaign_savefile_load(fname);
1094 // mission_campaign_next_mission sets up the internal veriables of the campaign
1095 // structure so the player can play the next mission. If there are no more missions
1096 // available in the campaign, this function returns -1, else 0 if the mission was
1098 int mission_campaign_next_mission()
1100 if ( (Campaign.next_mission == -1) || (strlen(Campaign.name) == 0) ) // will be set to -1 when there is no next mission
1103 Campaign.current_mission = Campaign.next_mission;
1104 SDL_strlcpy( Game_current_mission_filename, Campaign.missions[Campaign.current_mission].name, MAX_FILENAME_LEN );
1106 // check for end of loop.
1107 if (Campaign.current_mission == Campaign.loop_reentry) {
1108 Campaign.loop_enabled = 0;
1111 // reset the number of persistent ships and weapons for the next campaign mission
1112 Num_granted_ships = 0;
1113 Num_granted_weapons = 0;
1117 // mission_campaign_previous_mission() gets called to go to the previous mission in
1118 // the campaign. Used only for Red Alert missions
1119 int mission_campaign_previous_mission()
1121 if ( !(Game_mode & GM_CAMPAIGN_MODE) )
1124 if ( Campaign.prev_mission == -1 )
1127 Campaign.current_mission = Campaign.prev_mission;
1128 Campaign.next_mission = Campaign.current_mission;
1129 Campaign.num_missions_completed--;
1130 Campaign.missions[Campaign.next_mission].completed = 0;
1131 mission_campaign_savefile_save();
1133 // reset the player stats to be the stats from this level
1134 memcpy( &Player->stats, &Campaign.missions[Campaign.current_mission].stats, sizeof(Player->stats) );
1136 SDL_strlcpy( Game_current_mission_filename, Campaign.missions[Campaign.current_mission].name, MAX_FILENAME_LEN );
1137 Num_granted_ships = 0;
1138 Num_granted_weapons = 0;
1144 // determine what the next mission is after the current one. Because this evaluates an sexp,
1145 // and that could check just about anything, the results are only going to be valid in
1147 // DA 12/09/98 -- To allow for mission loops, need to maintain call with store stats
1148 int mission_campaign_eval_next_mission( int store_stats )
1154 Campaign.next_mission = -1;
1155 cur = Campaign.current_mission;
1156 name = Campaign.missions[cur].name;
1158 mission = &Campaign.missions[cur];
1160 // first we must save the status of the current missions goals in the campaign mission structure.
1161 // After that, we can determine which mission is tagged as the next mission. Finally, we
1162 // can save the campaign save file
1163 // we might have goal and event status if the player replayed a mission
1164 if ( mission->num_goals > 0 ) {
1165 free( mission->goals );
1168 mission->num_goals = Num_goals;
1169 if ( mission->num_goals > 0 ) {
1170 mission->goals = (mgoal *)malloc( sizeof(mgoal) * Num_goals );
1171 SDL_assert( mission->goals != NULL );
1174 // copy the needed info from the Mission_goal struct to our internal structure
1175 for (i = 0; i < Num_goals; i++ ) {
1176 if ( strlen(Mission_goals[i].name) == 0 ) {
1177 char name[NAME_LENGTH];
1179 sprintf(name, NOX("Goal #%d"), i);
1180 //Warning(LOCATION, "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name);
1181 strcpy( mission->goals[i].name, name);
1183 strcpy( mission->goals[i].name, Mission_goals[i].name );
1184 SDL_assert ( Mission_goals[i].satisfied != GOAL_INCOMPLETE ); // should be true or false at this point!!!
1185 mission->goals[i].status = (char)Mission_goals[i].satisfied;
1188 // do the same thing for events as we did for goals
1189 // we might have goal and event status if the player replayed a mission
1190 if ( mission->num_events > 0 ) {
1191 free( mission->events );
1194 mission->num_events = Num_mission_events;
1195 if ( mission->num_events > 0 ) {
1196 mission->events = (mevent *)malloc( sizeof(mevent) * Num_mission_events );
1197 SDL_assert( mission->events != NULL );
1200 // copy the needed info from the Mission_goal struct to our internal structure
1201 for (i = 0; i < Num_mission_events; i++ ) {
1202 if ( strlen(Mission_events[i].name) == 0 ) {
1203 char name[NAME_LENGTH];
1205 sprintf(name, NOX("Event #%d"), i);
1206 nprintf(("Warning", "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name));
1207 strcpy( mission->events[i].name, name);
1209 strcpy( mission->events[i].name, Mission_events[i].name );
1211 // getting status for the events is a little different. If the formula value for the event entry
1212 // is -1, then we know the value of the result field will never change. If the formula is
1213 // not -1 (i.e. still being evaluated at mission end time), we will write "incomplete" for the
1215 if ( Mission_events[i].formula == -1 ) {
1216 if ( Mission_events[i].result )
1217 mission->events[i].status = EVENT_SATISFIED;
1219 mission->events[i].status = EVENT_FAILED;
1224 // maybe store the alltime stats which would be current at the end of this mission
1225 if ( store_stats ) {
1226 memcpy( &mission->stats, &Player->stats, sizeof(Player->stats) );
1227 scoring_backout_accept( &mission->stats );
1230 if ( store_stats ) { // second (last) time through, so use choose loop_mission if chosen
1231 if ( Campaign.loop_enabled ) {
1232 Campaign.next_mission = Campaign.loop_mission;
1234 // evaluate next mission (straight path)
1235 if (Campaign.missions[cur].formula != -1) {
1236 flush_sexp_tree(Campaign.missions[cur].formula); // force formula to be re-evaluated
1237 eval_sexp(Campaign.missions[cur].formula); // this should reset Campaign.next_mission to proper value
1242 // evaluate next mission (straight path)
1243 if (Campaign.missions[cur].formula != -1) {
1244 flush_sexp_tree(Campaign.missions[cur].formula); // force formula to be re-evaluated
1245 eval_sexp(Campaign.missions[cur].formula); // this should reset Campaign.next_mission to proper value
1248 // evaluate mission loop mission (if any) so it can be used if chosen
1249 if ( Campaign.missions[cur].has_mission_loop ) {
1250 int copy_next_mission = Campaign.next_mission;
1251 // Set temporarily to -1 so we know if loop formula fails to assign
1252 Campaign.next_mission = -1; // Cannot exit campaign from loop
1253 if (Campaign.missions[cur].mission_loop_formula != -1) {
1254 flush_sexp_tree(Campaign.missions[cur].mission_loop_formula); // force formula to be re-evaluated
1255 eval_sexp(Campaign.missions[cur].mission_loop_formula); // this should reset Campaign.next_mission to proper value
1258 Campaign.loop_mission = Campaign.next_mission;
1259 Campaign.next_mission = copy_next_mission;
1263 if (Campaign.next_mission == -1)
1264 nprintf(("allender", "No next mission to proceed to.\n"));
1266 nprintf(("allender", "Next mission is number %d [%s]\n", Campaign.next_mission, Campaign.missions[Campaign.next_mission].name));
1268 return Campaign.next_mission;
1271 // Evaluate next campaign mission - set as Campaign.next_mission. Also set Campaign.loop_mission
1272 void mission_campaign_eval_next_mission()
1274 Campaign.next_mission = -1;
1275 int cur = Campaign.current_mission;
1277 // evaluate next mission (straight path)
1278 if (Campaign.missions[cur].formula != -1) {
1279 flush_sexp_tree(Campaign.missions[cur].formula); // force formula to be re-evaluated
1280 eval_sexp(Campaign.missions[cur].formula); // this should reset Campaign.next_mission to proper value
1283 // evaluate mission loop mission (if any) so it can be used if chosen
1284 if ( Campaign.missions[cur].has_mission_loop ) {
1285 int copy_next_mission = Campaign.next_mission;
1286 // Set temporarily to -1 so we know if loop formula fails to assign
1287 Campaign.next_mission = -1;
1288 if (Campaign.missions[cur].mission_loop_formula != -1) {
1289 flush_sexp_tree(Campaign.missions[cur].mission_loop_formula); // force formula to be re-evaluated
1290 eval_sexp(Campaign.missions[cur].mission_loop_formula); // this should reset Campaign.next_mission to proper value
1293 Campaign.loop_mission = Campaign.next_mission;
1294 Campaign.next_mission = copy_next_mission;
1297 if (Campaign.next_mission == -1) {
1298 nprintf(("allender", "No next mission to proceed to.\n"));
1300 nprintf(("allender", "Next mission is number %d [%s]\n", Campaign.next_mission, Campaign.missions[Campaign.next_mission].name));
1305 // Store mission's goals and events in Campaign struct
1306 void mission_campaign_store_goals_and_events()
1311 cur = Campaign.current_mission;
1313 mission = &Campaign.missions[cur];
1315 // first we must save the status of the current missions goals in the campaign mission structure.
1316 // After that, we can determine which mission is tagged as the next mission. Finally, we
1317 // can save the campaign save file
1318 // we might have goal and event status if the player replayed a mission
1319 if ( mission->num_goals > 0 ) {
1320 free( mission->goals );
1323 mission->num_goals = Num_goals;
1324 if ( mission->num_goals > 0 ) {
1325 mission->goals = (mgoal *)malloc( sizeof(mgoal) * Num_goals );
1326 SDL_assert( mission->goals != NULL );
1329 // copy the needed info from the Mission_goal struct to our internal structure
1330 for (i = 0; i < Num_goals; i++ ) {
1331 if ( strlen(Mission_goals[i].name) == 0 ) {
1332 char name[NAME_LENGTH];
1334 SDL_snprintf(name, SDL_arraysize(name), NOX("Goal #%d"), i);
1335 //Warning(LOCATION, "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name);
1336 SDL_strlcpy( mission->goals[i].name, name, SDL_arraysize(mission->goals[0].name));
1338 SDL_strlcpy( mission->goals[i].name, Mission_goals[i].name, SDL_arraysize(mission->goals[0].name) );
1339 SDL_assert ( Mission_goals[i].satisfied != GOAL_INCOMPLETE ); // should be true or false at this point!!!
1340 mission->goals[i].status = (char)Mission_goals[i].satisfied;
1343 // do the same thing for events as we did for goals
1344 // we might have goal and event status if the player replayed a mission
1345 if ( mission->num_events > 0 ) {
1346 free( mission->events );
1349 mission->num_events = Num_mission_events;
1350 if ( mission->num_events > 0 ) {
1351 mission->events = (mevent *)malloc( sizeof(mevent) * Num_mission_events );
1352 SDL_assert( mission->events != NULL );
1355 // copy the needed info from the Mission_goal struct to our internal structure
1356 for (i = 0; i < Num_mission_events; i++ ) {
1357 if ( strlen(Mission_events[i].name) == 0 ) {
1358 char name[NAME_LENGTH];
1360 SDL_snprintf(name, SDL_arraysize(name), NOX("Event #%d"), i);
1361 nprintf(("Warning", "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name));
1362 SDL_strlcpy( mission->events[i].name, name, SDL_arraysize(mission->events[0].name));
1364 SDL_strlcpy( mission->events[i].name, Mission_events[i].name, SDL_arraysize(mission->events[0].name) );
1366 // getting status for the events is a little different. If the formula value for the event entry
1367 // is -1, then we know the value of the result field will never change. If the formula is
1368 // not -1 (i.e. still being evaluated at mission end time), we will write "incomplete" for the
1370 if ( Mission_events[i].formula == -1 ) {
1371 if ( Mission_events[i].result )
1372 mission->events[i].status = EVENT_SATISFIED;
1374 mission->events[i].status = EVENT_FAILED;
1380 // this function is called when the player's mission is over. It updates the internal store of goals
1381 // and their status then saves the state of the campaign in the campaign file. This gets called
1382 // after player accepts mission results in debriefing.
1383 void mission_campaign_mission_over()
1388 // I don't think that we should have a record for these -- maybe we might?????? If we do,
1389 // then we should free them
1390 if ( !(Game_mode & GM_CAMPAIGN_MODE) ){
1394 mission_num = Campaign.current_mission;
1395 SDL_assert( mission_num != -1 );
1396 mission = &Campaign.missions[mission_num];
1398 // determine if any ships/weapons were granted this mission
1399 for ( i=0; i<Num_granted_ships; i++ ){
1400 Campaign.ships_allowed[Granted_ships[i]] = 1;
1403 for ( i=0; i<Num_granted_weapons; i++ ){
1404 Campaign.weapons_allowed[Granted_weapons[i]] = 1;
1407 // DKA 12/11/98 - Unneeded already evaluated and stored
1408 // determine what new mission we are moving to.
1409 // mission_campaign_eval_next_mission(1);
1411 // update campaign.mission stats (used to allow backout inRedAlert)
1412 memcpy( &mission->stats, &Player->stats, sizeof(Player->stats) );
1413 if(!(Game_mode & GM_MULTIPLAYER)){
1414 scoring_backout_accept( &mission->stats );
1417 // if we are moving to a new mission, then change our data. If we are staying on the same mission,
1418 // then we don't want to do anything. Remove information about goals/events
1419 if ( Campaign.next_mission != mission_num ) {
1420 Campaign.prev_mission = mission_num;
1421 Campaign.current_mission = -1;
1422 Campaign.num_missions_completed++;
1423 Campaign.missions[mission_num].completed = 1;
1425 // save the scoring values from the previous mission at the start of this mission -- for red alert
1427 // save the state of the campaign in the campaign save file and move to the end_game state
1428 if (Campaign.type == CAMPAIGN_TYPE_SINGLE) {
1429 mission_campaign_savefile_save();
1433 // free up the goals and events which were just malloced. It's kind of like erasing any fact
1434 // that the player played this mission in the campaign.
1435 free( mission->goals );
1436 mission->num_goals = 0;
1438 free( mission->events );
1439 mission->num_events = 0;
1441 Sexp_nodes[mission->formula].value = SEXP_UNKNOWN;
1445 if (Campaign.missions[Campaign.next_mission].flags & CMISSION_FLAG_BASTION){
1446 Player->on_bastion = 1;
1448 Player->on_bastion = 0;
1451 mission_campaign_next_mission(); // sets up whatever needs to be set to actually play next mission
1454 // called when the game closes -- to get rid of memory errors for Bounds checker
1455 void mission_campaign_close()
1460 free(Campaign.desc);
1462 // be sure to remove all old malloced strings of Mission_names
1463 // we must also free any goal stuff that was from a previous campaign
1464 for ( i=0; i<Campaign.num_missions; i++ ) {
1465 if ( Campaign.missions[i].name ){
1466 free(Campaign.missions[i].name);
1469 if (Campaign.missions[i].notes){
1470 free(Campaign.missions[i].notes);
1473 if ( Campaign.missions[i].num_goals > 0 ){
1474 free ( Campaign.missions[i].goals );
1477 if ( Campaign.missions[i].num_events > 0 ){
1478 free ( Campaign.missions[i].events );
1481 // the next three are strdup'd return values from parselo.cpp
1482 if (Campaign.missions[i].mission_loop_desc) {
1483 free(Campaign.missions[i].mission_loop_desc);
1486 if (Campaign.missions[i].mission_loop_brief_anim) {
1487 free(Campaign.missions[i].mission_loop_brief_anim);
1490 if (Campaign.missions[i].mission_loop_brief_sound) {
1491 free(Campaign.missions[i].mission_loop_brief_sound);
1494 if ( !Fred_running ){
1495 sexp_unmark_persistent(Campaign.missions[i].formula); // free any sexpression nodes used by campaign.
1498 Campaign.missions[i].num_goals = 0;
1499 Campaign.missions[i].num_events = 0;
1503 // call from game_shutdown() ONLY!!!
1504 void mission_campaign_shutdown()
1508 for (i=0; i<MAX_CAMPAIGNS; i++) {
1509 if (Campaign_names[i] != NULL) {
1510 free(Campaign_names[i]);
1511 Campaign_names[i] = NULL;
1514 if (Campaign_file_names[i] != NULL) {
1515 free(Campaign_file_names[i]);
1516 Campaign_file_names[i] = NULL;
1521 // extract the mission filenames for a campaign.
1523 // filename => name of campaign file
1524 // dest => storage for the mission filename, must be already allocated
1525 // num => output parameter for the number of mission filenames in the campaign
1527 // note that dest should allocate at least dest[MAX_CAMPAIGN_MISSIONS][NAME_LENGTH]
1528 int mission_campaign_get_filenames(const char *filename, char dest[][NAME_LENGTH], int *num)
1530 // read the mission file and get the list of mission filenames
1532 read_file_text(filename);
1533 SDL_assert(strlen(filename) < MAX_FILENAME_LEN - 1); // make sure no overflow
1536 required_string("$Name:");
1537 advance_to_eoln(NULL);
1539 required_string( "$Type:" );
1540 advance_to_eoln(NULL);
1542 // parse the mission file and actually read in the mission stuff
1544 while ( skip_to_string("$Mission:") == 1 ) {
1545 stuff_string(dest[*num], F_NAME, NULL);
1548 } catch (parse_error_t rval) {
1555 // function to read the goals and events from a mission in a campaign file and store that information
1556 // in the campaign structure for use in the campaign editor, error checking, etc
1557 void read_mission_goal_list(int num)
1559 char *filename, notes[NOTES_LENGTH], goals[MAX_GOALS][NAME_LENGTH];
1560 char events[MAX_MISSION_EVENTS][NAME_LENGTH];
1561 int i, z, event_count, count = 0;
1563 filename = Campaign.missions[num].name;
1565 // open localization
1568 // default counts to zero
1573 read_file_text(filename);
1576 // first, read the mission notes for this mission. Used in campaign editor
1577 if (skip_to_string("#Mission Info")) {
1578 if (skip_to_string("$Notes:")) {
1579 stuff_string(notes, F_NOTES, NULL);
1580 if (Campaign.missions[num].notes){
1581 free(Campaign.missions[num].notes);
1584 Campaign.missions[num].notes = (char *) malloc(strlen(notes) + 1);
1585 SDL_strlcpy(Campaign.missions[num].notes, notes, strlen(notes) + 1);
1589 // skip to events section in the mission file. Events come before goals, so we process them first
1590 if ( skip_to_string("#Events") ) {
1592 if (skip_to_string("$Formula:", "#Goals") != 1){
1596 z = skip_to_string("+Name:", "$Formula:");
1602 stuff_string(events[event_count], F_NAME, NULL);
1604 SDL_snprintf(events[event_count], NAME_LENGTH, NOX("Event #%d"), event_count + 1);
1608 SDL_assert(event_count < MAX_MISSION_EVENTS);
1612 if (skip_to_string("#Goals")) {
1614 if (skip_to_string("$Type:", "#End") != 1){
1618 z = skip_to_string("+Name:", "$Type:");
1624 stuff_string(goals[count], F_NAME, NULL);
1626 SDL_snprintf(goals[count], NAME_LENGTH, NOX("Goal #%d"), count + 1);
1630 SDL_assert(count < MAX_GOALS);
1633 } catch (parse_error_t rval) {
1634 Warning(LOCATION, "Error reading \"%s\" (code = %d)", filename, (int)rval);
1637 Campaign.missions[num].num_goals = count;
1639 Campaign.missions[num].goals = (mgoal *) malloc(count * sizeof(mgoal));
1640 SDL_assert(Campaign.missions[num].goals); // make sure we got the memory
1641 memset(Campaign.missions[num].goals, 0, count * sizeof(mgoal));
1643 for (i=0; i<count; i++){
1644 SDL_strlcpy(Campaign.missions[num].goals[i].name, goals[i], SDL_arraysize(Campaign.missions[num].goals[i].name));
1648 Campaign.missions[num].num_events = event_count;
1650 Campaign.missions[num].events = (mevent *)malloc(event_count * sizeof(mevent));
1651 SDL_assert ( Campaign.missions[num].events );
1652 memset(Campaign.missions[num].events, 0, event_count * sizeof(mevent));
1654 for (i = 0; i < event_count; i++ ){
1655 SDL_strlcpy(Campaign.missions[num].events[i].name, events[i], SDL_arraysize(Campaign.missions[num].events[i].name));
1659 // close localization
1663 // function to return index into Campaign's list of missions of the mission with the given
1664 // filename. This function tried to be a little smart about filename looking for the .fsm
1665 // extension since filenames are stored with the extension in the campaign file. Returns
1666 // index of mission in campaign structure. -1 if mission name not found.
1667 int mission_campaign_find_mission( const char *name )
1670 char realname[MAX_FILENAME_LEN];
1672 // look for an extension on the file. If no extension, add default ".fsm" onto the
1673 // end of the filename
1674 if ( SDL_strchr(name, '.') == NULL ){
1675 SDL_snprintf(realname, SDL_arraysize(realname), NOX("%s%s"), name, FS_MISSION_FILE_EXT );
1677 SDL_strlcpy(realname, name, SDL_arraysize(realname));
1680 for (i = 0; i < Campaign.num_missions; i++ ) {
1681 if ( !SDL_strcasecmp(realname, Campaign.missions[i].name) ){
1689 void mission_campaign_maybe_play_movie(int type)
1694 // only support pre mission movies for now.
1695 SDL_assert ( type == CAMPAIGN_MOVIE_PRE_MISSION );
1697 if ( !(Game_mode & GM_CAMPAIGN_MODE) )
1700 mission = Campaign.current_mission;
1701 SDL_assert( mission != -1 );
1703 // get a possible filename for a movie to play.
1706 case CAMPAIGN_MOVIE_PRE_MISSION:
1707 if ( strlen(Campaign.missions[mission].briefing_cutscene) )
1708 filename = Campaign.missions[mission].briefing_cutscene;
1716 // no filename, no movie!
1720 movie_play( filename );
1723 // return nonzero if the passed filename is a multiplayer campaign, 0 otherwise
1724 int mission_campaign_parse_is_multi(const char *filename, char *name, const int max_len)
1730 read_file_text( filename );
1733 required_string("$Name:");
1734 stuff_string( temp, F_NAME, NULL );
1736 SDL_strlcpy( name, temp, max_len );
1738 required_string( "$Type:" );
1739 stuff_string( temp, F_NAME, NULL );
1741 for (i = 0; i < MAX_CAMPAIGN_TYPES; i++ ) {
1742 if ( !SDL_strcasecmp(temp, campaign_types[i]) ) {
1746 } catch (parse_error_t rval) {
1747 Error(LOCATION, "Unable to parse %s! Code = %i.\n", filename, (int)rval);
1750 Error(LOCATION, "Unknown campaign type %s", temp );
1754 // functions to save persistent information during a mission -- which then might or might not get
1755 // saved out when the mission is over depending on whether player replays mission or commits.
1756 void mission_campaign_save_persistent( int type, int sindex )
1758 // based on the type of information, save it off for possible saving into the campsign
1759 // savefile when the mission is over
1760 if ( type == CAMPAIGN_PERSISTENT_SHIP ) {
1761 SDL_assert( Num_granted_ships < MAX_SHIP_TYPES );
1762 Granted_ships[Num_granted_ships] = sindex;
1763 Num_granted_ships++;
1764 } else if ( type == CAMPAIGN_PERSISTENT_WEAPON ) {
1765 SDL_assert( Num_granted_weapons < MAX_WEAPON_TYPES );
1766 Granted_weapons[Num_granted_weapons] = sindex;
1767 Num_granted_weapons++;
1772 // returns 0: loaded, !0: error
1773 int mission_load_up_campaign()
1775 if (strlen(Player->current_campaign))
1776 return mission_campaign_load(Player->current_campaign);
1778 return mission_campaign_load(BUILTIN_CAMPAIGN);
1781 // for end of campaign in the single player game. Called when the end of campaign state is
1782 // entered, which is triggered when the end-campaign sexpression is hit
1784 void mission_campaign_end_init()
1786 // no need to do any initialization.
1789 void mission_campaign_end_do()
1792 event_music_level_close();
1793 mission_goal_fail_incomplete();
1794 scoring_level_close();
1795 mission_campaign_mission_over();
1798 movie_play("endgame.mve");
1800 // eventually we'll want to play one of two options (good ending or bad ending)
1801 // did the supernova blow?
1802 if(Supernova_status == SUPERNOVA_HIT){
1803 movie_play_two("endpart1.mve", "endprt2b.mve"); // good ending
1805 movie_play_two("endpart1.mve", "endprt2a.mve"); // good ending
1809 #if defined(FS2_DEMO) || defined(FS1_DEMO)
1810 gameseq_post_event( GS_EVENT_END_DEMO );
1811 #elif defined(MAKE_FS1)
1812 gameseq_post_event( GS_STATE_END_OF_CAMPAIGN );
1814 gameseq_post_event( GS_EVENT_MAIN_MENU );
1818 void mission_campaign_end_close()
1820 // nothing to do here.
1824 // skip to the next mission in the campaign
1825 // this also posts the state change by default. pass 0 to override that
1826 void mission_campaign_skip_to_next(int start_game)
1828 // mark all goals/events complete
1829 // these do not really matter, since is-previous-event-* and is-previous-goal-* sexps check
1830 // to see if the mission was skipped, and use defaults accordingly.
1831 mission_goal_mark_objectives_complete();
1832 mission_goal_mark_events_complete();
1834 // mark mission as skipped
1835 Campaign.missions[Campaign.current_mission].flags |= CMISSION_FLAG_SKIPPED;
1838 mission_campaign_store_goals_and_events();
1840 // now set the next mission
1841 mission_campaign_eval_next_mission();
1843 // clear out relevant player vars
1844 Player->failures_this_session = 0;
1845 Player->show_skip_popup = 1;
1848 // proceed to next mission or main hall
1849 if ((Campaign.missions[Campaign.current_mission].has_mission_loop) && (Campaign.loop_mission != -1)) {
1850 // go to loop solicitation
1851 gameseq_post_event(GS_EVENT_LOOP_BRIEF);
1853 // closes out mission stuff, sets up next one
1854 mission_campaign_mission_over();
1856 if ( Campaign.next_mission == -1 ) {
1857 // go to main hall, tha campaign is over!
1858 gameseq_post_event(GS_EVENT_MAIN_MENU);
1860 // go to next mission
1861 gameseq_post_event(GS_EVENT_START_GAME);
1868 // breaks your ass out of the loop
1869 // this also posts the state change
1870 void mission_campaign_exit_loop()
1872 // set campaign to loop reentry point
1873 Campaign.next_mission = Campaign.loop_reentry;
1874 Campaign.current_mission = -1;
1875 Campaign.loop_enabled = 0;
1877 // set things up for next mission
1878 mission_campaign_next_mission();
1879 gameseq_post_event(GS_EVENT_START_GAME);
1883 // used for jumping to a particular campaign mission
1884 // all pvs missions marked skipped
1885 // this relies on correct mission ordering in the campaign file
1886 void mission_campaign_jump_to_mission(const char *name)
1889 char dest_name[64] = { 0 };
1891 // load in the campaign junk
1892 mission_load_up_campaign();
1894 // tack the .fs2 onto the input name
1895 SDL_strlcpy(dest_name, cf_add_ext(name, FS_MISSION_FILE_EXT), SDL_arraysize(dest_name));
1897 // search for our mission
1898 for (i=0; i<Campaign.num_missions; i++) {
1899 if ((Campaign.missions[i].name != NULL) && !SDL_strcasecmp(Campaign.missions[i].name, dest_name) ) {
1900 Campaign.next_mission = i;
1901 Campaign.prev_mission = i-1;
1902 mission_campaign_next_mission();
1903 Game_mode |= GM_CAMPAIGN_MODE;
1904 gameseq_post_event(GS_EVENT_START_GAME);
1907 Campaign.missions[i].flags |= CMISSION_FLAG_SKIPPED;
1908 Campaign.num_missions_completed = i;
1912 // if we got here, no match was found
1913 // restart the campaign
1914 mission_campaign_savefile_delete(Campaign.filename);
1915 mission_campaign_load(Campaign.filename);