]> icculus.org git repositories - taylor/freespace2.git/blob - src/mission/missioncampaign.cpp
Initial revision
[taylor/freespace2.git] / src / mission / missioncampaign.cpp
1 /*
2  * $Logfile: /Freespace2/code/Mission/MissionCampaign.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * source for dealing with campaigns
8  *
9  * $Log$
10  * Revision 1.1  2002/05/03 03:28:09  root
11  * Initial revision
12  *
13  * 
14  * 23    9/14/99 4:35a Dave
15  * Argh. Added all kinds of code to handle potential crashes in debriefing
16  * code.
17  * 
18  * 22    9/09/99 11:40p Dave
19  * Handle an Assert() in beam code. Added supernova sounds. Play the right
20  * 2 end movies properly, based upon what the player did in the mission.
21  * 
22  * 21    9/09/99 9:34a Jefff
23  * fixed a potential exit-loop bug
24  * 
25  * 20    9/07/99 6:55p Jefff
26  * functionality to break out of a loop.  hacked functionality to jump to
27  * a specific mission in a campaign -- doesnt grant ships/weapons from
28  * skipped missions tho.
29  * 
30  * 19    9/07/99 2:19p Jefff
31  * clear skip mission player vars on mission skip
32  * 
33  * 18    9/06/99 9:45p Jefff
34  * break out of loop and skip mission support
35  * 
36  * 17    9/06/99 6:38p Dave
37  * Improved CD detection code.
38  * 
39  * 16    9/03/99 1:32a Dave
40  * CD checking by act. Added support to play 2 cutscenes in a row
41  * seamlessly. Fixed super low level cfile bug related to files in the
42  * root directory of a CD. Added cheat code to set campaign mission # in
43  * main hall.
44  * 
45  * 15    8/27/99 12:04a Dave
46  * Campaign loop screen.
47  * 
48  * 14    8/04/99 5:36p Andsager
49  * Show upsell screens at end of demo campaign before returning to main
50  * hall.
51  * 
52  * 13    2/05/99 3:50p Anoop
53  * Removed dumb campaign mission stats saving from multiplayer.
54  * 
55  * 12    12/17/98 2:43p Andsager
56  * Modify fred campaign save file to include optional mission loops
57  * 
58  * 11    12/12/98 3:17p Andsager
59  * Clean up mission eval, goal, event and mission scoring.
60  * 
61  * 10    12/10/98 9:59a Andsager
62  * Fix some bugs with mission loops
63  * 
64  * 9     12/09/98 1:56p Andsager
65  * Initial checkin of mission loop
66  * 
67  * 8     11/05/98 5:55p Dave
68  * Big pass at reducing #includes
69  * 
70  * 7     11/03/98 4:48p Johnson
71  * Fixed campaign file versioning bug left over from Silent Threat port.
72  * 
73  * 6     10/23/98 3:51p Dave
74  * Full support for tstrings.tbl and foreign languages. All that remains
75  * is to make it active in Fred.
76  * 
77  * 5     10/13/98 2:47p Andsager
78  * Remove reference to Tech_shivan_species_avail
79  * 
80  * 4     10/13/98 9:28a Dave
81  * Started neatening up freespace.h. Many variables renamed and
82  * reorganized. Added AlphaColors.[h,cpp]
83  * 
84  * 3     10/07/98 6:27p Dave
85  * Globalized mission and campaign file extensions. Removed Silent Threat
86  * special code. Moved \cache \players and \multidata into the \data
87  * directory.
88  * 
89  * 2     10/07/98 10:53a Dave
90  * Initial checkin.
91  * 
92  * 1     10/07/98 10:49a Dave
93  * 
94  * 95    9/10/98 1:17p Dave
95  * Put in code to flag missions and campaigns as being MD or not in Fred
96  * and Freespace. Put in multiplayer support for filtering out MD
97  * missions. Put in multiplayer popups for warning of non-valid missions.
98  * 
99  * 94    9/01/98 4:25p Dave
100  * Put in total (I think) backwards compatibility between mission disk
101  * freespace and non mission disk freespace, including pilot files and
102  * campaign savefiles.
103  * 
104  * 93    7/06/98 4:10p Hoffoss
105  * Fixed some bugs that presented themselves when trying to use a pilot
106  * that has a no longer existent campaign active.  Also expanded the
107  * campaign load code to actually return a proper error code, instead of
108  * always trapping errors internally and crashing, and always returning 0.
109  * 
110  * 92    6/17/98 9:30a Allender
111  * fixed red alert replay stats clearing problem
112  * 
113  * 91    6/01/98 11:43a John
114  * JAS & MK:  Classified all strings for localization.
115  * 
116  * 90    5/25/98 1:29p Allender
117  * end mission sequencing
118  * 
119  * 89    5/21/98 9:25p Allender
120  * endgame movie always viewable at end of campaign
121  * 
122  * 88    5/13/98 5:14p Allender
123  * red alert support to go back to previous mission
124  * 
125  * 87    5/12/98 4:16p Hoffoss
126  * Fixed bug where not all missions in all campaigns were being filtered
127  * out of stand alone mission listing in simulator room.
128  * 
129  * 86    5/05/98 3:29p Hoffoss
130  * Changed code so description is BEFORE num players in campaign file.
131  * Other code is relying on this ordering.
132  * 
133  * 85    5/05/98 12:19p Dave
134  * campaign description goes *after* num players
135  * 
136  * 84    5/04/98 5:52p Comet
137  * Fixed bug with Galatea/Bastion selection when finishing missions.
138  * 
139  * 83    5/01/98 2:46p Duncan
140  * fix a cfile problem with campaigns related to the new cfile stuff
141  * 
142  * 82    5/01/98 12:34p John
143  * Added code to force FreeSpace to run in the same dir as exe and made
144  * all the parse error messages a little nicer.
145  * 
146  * 81    4/30/98 7:01p Duncan
147  * AL: don't allow deletion of campaign files in multiplayer
148  * 
149  * 80    4/30/98 4:53p John
150  * Restructured and cleaned up cfile code.  Added capability to read off
151  * of CD-ROM drive and out of multiple pack files.
152  *
153  * $NoKeywords: $
154  */
155
156 #include <stdio.h>
157 #ifndef PLAT_UNIX
158 #include <direct.h>
159 #include <io.h>
160 #endif
161 #include <string.h>
162 #include <setjmp.h>
163 #include <errno.h>
164
165 #include "key.h"
166 #include "ui.h"
167 #include "missioncampaign.h"
168 #include "gamesequence.h"
169 #include "2d.h"
170 #include "parselo.h"
171 #include "missionload.h"
172 #include "freespace.h"
173 #include "sexp.h"
174 #include "cfile.h"
175 #include "player.h"
176 #include "missiongoals.h"
177 // #include "movie.h"
178 #include "multi.h"
179 #include "techmenu.h"
180 #include "eventmusic.h"
181 #include "alphacolors.h"
182 #include "localize.h"
183 #include "supernova.h"
184
185 // mission disk stuff
186 #define CAMPAIGN_SAVEFILE_MAX_SHIPS_OLD                                         75
187 #define CAMPAIGN_SAVEFILE_MAX_WEAPONS_OLD                                               44
188
189 #define CAMPAIGN_INITIAL_RELEASE_FILE_VERSION                           6
190
191 // campaign wasn't ended
192 int Campaign_ended_in_mission = 0;
193
194 // stuff for selecting campaigns.  We need to keep both arrays around since we display the
195 // list of campaigns by name, but must load campaigns by filename
196 char *Campaign_names[MAX_CAMPAIGNS];
197 char *Campaign_file_names[MAX_CAMPAIGNS];
198 int     Num_campaigns;
199
200 char *campaign_types[MAX_CAMPAIGN_TYPES] = 
201 {
202 //XSTR:OFF
203         "single",
204         "multi coop",
205         "multi teams"
206 //XSTR:ON
207 };
208
209 // modules local variables to deal with getting new ships/weapons available to the player
210 int Num_granted_ships, Num_granted_weapons;             // per mission counts of new ships and weapons
211 int Granted_ships[MAX_SHIP_TYPES];
212 int Granted_weapons[MAX_WEAPON_TYPES];
213
214 // variables to control the UI stuff for loading campaigns
215 LOCAL int Campaign_ui_active = 0;
216 LOCAL UI_WINDOW Campaign_window;
217 LOCAL UI_LISTBOX Campaign_listbox;
218 LOCAL UI_BUTTON Campaign_okb, Campaign_cancelb;
219
220 // the campaign!!!!!
221 campaign Campaign;
222
223 // variables with deal with the campaign save file
224 #define CAMPAIGN_FILE_VERSION                                                   12
225 //#define CAMPAIGN_FILE_COMPATIBLE_VERSION              CAMPAIGN_INITIAL_RELEASE_FILE_VERSION
226 #define CAMPAIGN_FILE_COMPATIBLE_VERSION                        CAMPAIGN_FILE_VERSION
227 #define CAMPAIGN_FILE_ID                                                                0xbeefcafe
228
229 // variables with deal with the campaign stats save file
230 #define CAMPAIGN_STATS_FILE_VERSION                                     1
231 #define CAMPAIGN_STATS_FILE_COMPATIBLE_VERSION  1
232 #define CAMPAIGN_STATS_FILE_ID                                          0xabbadaad
233
234 // mission_campaign_get_name returns a string (which is malloced in this routine) of the name
235 // of the given freespace campaign file.  In the type field, we return if the campaign is a single
236 // player or multiplayer campaign.  The type field will only be valid if the name returned is non-NULL
237 int mission_campaign_get_info(char *filename, char *name, int *type, int *max_players, char **desc)
238 {
239         int rval, i;
240         char campaign_type[NAME_LENGTH], fname[MAX_FILENAME_LEN];
241
242         Assert( name != NULL );
243         Assert( type != NULL );
244
245         // open localization
246         lcl_ext_open();
247
248         strcpy(fname, filename);
249         if ((strlen(fname) < 4) || stricmp(fname + strlen(fname) - 4, FS_CAMPAIGN_FILE_EXT)){
250                 strcat(fname, FS_CAMPAIGN_FILE_EXT);
251         }
252
253         Assert(strlen(fname) < MAX_FILENAME_LEN);
254
255         if ((rval = setjmp(parse_abort)) != 0) {
256                 if (rval == 5){
257                         // close localization
258                         lcl_ext_close();
259
260                         return 0;
261                 }
262
263                 Error(LOCATION, "Error parsing '%s'\r\nError code = %i.\r\n", fname, rval);
264
265         } else {
266                 read_file_text( fname );
267                 reset_parse();
268                 required_string("$Name:");
269
270                 stuff_string( name, F_NAME, NULL );
271                 if ( name == NULL ) {
272                         Int3();
273                         nprintf(("Warning", "No name found for campaign file %s\n", filename));
274
275                         // close localization
276                         lcl_ext_close();
277
278                         return 0;
279                 }
280
281                 required_string( "$Type:" );
282                 stuff_string( campaign_type, F_NAME, NULL );
283
284                 *type = -1;
285                 for (i=0; i<MAX_CAMPAIGN_TYPES; i++) {
286                         if ( !stricmp(campaign_type, campaign_types[i]) ) {
287                                 *type = i;
288                         }
289                 }
290
291                 if (desc) {
292                         *desc = NULL;
293                         if (optional_string("+Description:")) {
294                                 *desc = stuff_and_malloc_string(F_MULTITEXT, NULL, MISSION_DESC_LENGTH);
295                         }
296                 }
297
298                 // if this is a multiplayer campaign, get the max players
299                 if ((*type) > 0) {
300                         skip_to_string("+Num Players:");
301                         stuff_int(max_players);
302                 }               
303
304                 // if we found a valid campaign type
305                 if ((*type) >= 0) {
306                         // close localization
307                         lcl_ext_close();
308
309                         return 1;
310                 }
311         }
312
313         Int3();         // get Allender -- incorrect type found
314
315         // close localization
316         lcl_ext_close();
317
318         return 0;
319 }
320
321 // parses campaign and returns a list of missions in it.  Returns number of missions added to
322 // the 'list', and up to 'max' missions may be added to 'list'.  Returns negative on error.
323 //
324 int mission_campaign_get_mission_list(char *filename, char **list, int max)
325 {
326         int rval, i, num = 0;
327         char name[NAME_LENGTH];
328
329         filename = cf_add_ext(filename, FS_CAMPAIGN_FILE_EXT);
330
331         // read the mission file and get the list of mission filenames
332         if ((rval = setjmp(parse_abort)) != 0) {
333                 // since we can't return count of allocated elements, free them instead
334                 for (i=0; i<num; i++)
335                         free(list[i]);
336
337                 return -1;
338
339         } else {
340                 read_file_text(filename);
341                 reset_parse();
342
343                 while (skip_to_string("$Mission:") > 0) {
344                         stuff_string(name, F_NAME, NULL);
345                         if (num < max)
346                                 list[num++] = strdup(name);
347                 }
348         }
349
350         return num;
351 }
352
353 void mission_campaign_maybe_add( char *filename, int multiplayer )
354 {
355         char name[NAME_LENGTH];
356         int type,max_players;
357
358         if ( mission_campaign_get_info( filename, name, &type, &max_players) ) {
359                 if ( !multiplayer && ( type == CAMPAIGN_TYPE_SINGLE) ) {
360                         Campaign_names[Num_campaigns] = strdup(name);
361                         Campaign_file_names[Num_campaigns] = strdup(filename);
362                         Num_campaigns++;
363                 }
364         }
365 }
366
367 // mission_campaign_build_list() builds up the list of campaigns that the user might
368 // be able to pick from.  It uses the multiplayer flag to tell if we should display a list
369 // of single or multiplayer campaigns.  This routine sets the Num_campaigns and Campaign_names
370 // global variables
371 void mission_campaign_build_list( int multiplayer )
372 {
373 #ifdef PLAT_UNIX
374         STUB_FUNCTION;
375 #else
376         int find_handle;
377         _finddata_t find;
378         char wild_card[256];
379
380         Num_campaigns = 0;
381         mission_campaign_maybe_add( BUILTIN_CAMPAIGN, multiplayer);
382
383         memset(wild_card, 0, 256);
384         strcpy(wild_card, NOX("data\\missions\\*"));
385         strcat(wild_card, FS_CAMPAIGN_FILE_EXT);
386         find_handle = _findfirst( wild_card, &find );
387         if( find_handle != -1 ) {
388                 if ( !(find.attrib & _A_SUBDIR) && stricmp(find.name, BUILTIN_CAMPAIGN) ){
389                         mission_campaign_maybe_add( find.name, multiplayer);
390                 }
391
392                 while( !_findnext( find_handle, &find ) )       {
393                         if ( !(find.attrib & _A_SUBDIR) && stricmp(find.name, BUILTIN_CAMPAIGN) )       {
394                                 if ( Num_campaigns >= MAX_CAMPAIGNS ){
395                                         //MessageBox( -2,-2, 1, "Only the first 300 files will be displayed.", "Ok" );
396                                         break;
397                                 } else {
398                                         mission_campaign_maybe_add( find.name, multiplayer);
399                                 }
400                         }
401                 }
402         }
403 #endif
404 }
405
406 // gets optional ship/weapon information
407 void mission_campaign_get_sw_info()
408 {
409         int i, count, ship_list[MAX_SHIP_TYPES], weapon_list[MAX_WEAPON_TYPES];
410
411         // set allowable ships to the SIF_PLAYER_SHIPs
412         memset( Campaign.ships_allowed, 0, sizeof(Campaign.ships_allowed) );
413         for (i = 0; i < MAX_SHIP_TYPES; i++ ) {
414                 if ( Ship_info[i].flags & SIF_PLAYER_SHIP )
415                         Campaign.ships_allowed[i] = 1;
416         }
417
418         for (i = 0; i < MAX_WEAPON_TYPES; i++ )
419                 Campaign.weapons_allowed[i] = 1;
420
421         if ( optional_string("+Starting Ships:") ) {
422                 for (i = 0; i < MAX_SHIP_TYPES; i++ )
423                         Campaign.ships_allowed[i] = 0;
424
425                 count = stuff_int_list(ship_list, MAX_SHIP_TYPES, SHIP_INFO_TYPE);
426
427                 // now set the array elements stating which ships we are allowed
428                 for (i = 0; i < count; i++ ) {
429                         if ( Ship_info[ship_list[i]].flags & SIF_PLAYER_SHIP )
430                                 Campaign.ships_allowed[ship_list[i]] = 1;
431                 }
432         }
433
434         if ( optional_string("+Starting Weapons:") ) {
435                 for (i = 0; i < MAX_WEAPON_TYPES; i++ )
436                         Campaign.weapons_allowed[i] = 0;
437
438                 count = stuff_int_list(weapon_list, MAX_WEAPON_TYPES, WEAPON_POOL_TYPE);
439
440                 // now set the array elements stating which ships we are allowed
441                 for (i = 0; i < count; i++ )
442                         Campaign.weapons_allowed[weapon_list[i]] = 1;
443         }
444 }
445
446 // mission_campaign_load starts a new campaign.  It reads in the mission information in the campaign file
447 // It also sets up all variables needed inside of the game to deal with starting mission numbers, etc
448 //
449 // Note: Due to difficulties in generalizing this function, parts of it are duplicated throughout
450 // this file.  If you change the format of the campaign file, you should be sure these related
451 // functions work properly and update them if it breaks them.
452 int mission_campaign_load( char *filename, int load_savefile )
453 {
454         int len, rval, i;
455         char name[NAME_LENGTH], type[NAME_LENGTH];
456
457         filename = cf_add_ext(filename, FS_CAMPAIGN_FILE_EXT);
458
459         // open localization
460         lcl_ext_open(); 
461
462         // read the mission file and get the list of mission filenames
463         if ((rval = setjmp(parse_abort)) != 0) {
464                 mprintf(("Error parsing '%s'\r\nError code = %i.\r\n", filename, rval));
465
466                 // close localization
467                 lcl_ext_close();
468
469                 return CAMPAIGN_ERROR_CORRUPT;
470
471         } else {
472                 // be sure to remove all old malloced strings of Mission_names
473                 // we must also free any goal stuff that was from a previous campaign
474                 // this also frees sexpressions so the next call to init_sexp will be able to reclaim
475                 // nodes previously used by another campaign.
476                 mission_campaign_close();
477
478                 strcpy( Campaign.filename, filename );
479
480                 // only initialize the sexpression stuff when Fred isn't running.  It'll screw things up major
481                 // if it does
482                 if ( !Fred_running ){
483                         init_sexp();            // must initialize the sexpression stuff
484                 }
485
486                 read_file_text( filename );
487                 reset_parse();
488                 memset( &Campaign, 0, sizeof(Campaign) );
489
490                 // copy filename to campaign structure minus the extension
491                 len = strlen(filename) - 4;
492                 Assert(len < MAX_FILENAME_LEN);
493                 strncpy(Campaign.filename, filename, len);
494                 Campaign.filename[len] = 0;
495
496                 required_string("$Name:");
497                 stuff_string( name, F_NAME, NULL );
498                 
499                 //Store campaign name in the global struct
500                 strcpy( Campaign.name, name );
501
502                 required_string( "$Type:" );
503                 stuff_string( type, F_NAME, NULL );
504
505                 for (i = 0; i < MAX_CAMPAIGN_TYPES; i++ ) {
506                         if ( !stricmp(type, campaign_types[i]) ) {
507                                 Campaign.type = i;
508                                 break;
509                         }
510                 }
511
512                 if ( i == MAX_CAMPAIGN_TYPES )
513                         Error(LOCATION, "Unknown campaign type %s!", type);
514
515                 Campaign.desc = NULL;
516                 if (optional_string("+Description:"))
517                         Campaign.desc = stuff_and_malloc_string(F_MULTITEXT, NULL, MISSION_DESC_LENGTH);
518
519                 // if the type is multiplayer -- get the number of players
520                 Campaign.num_players = 0;
521                 if ( Campaign.type != CAMPAIGN_TYPE_SINGLE) {
522                         required_string("+Num players:");
523                         stuff_int( &(Campaign.num_players) );
524                 }               
525
526                 // parse the optional ship/weapon information
527                 mission_campaign_get_sw_info();
528
529                 // parse the mission file and actually read in the mission stuff
530                 Campaign.num_missions = 0;
531                 while ( required_string_either("#End", "$Mission:") ) {
532                         cmission *cm;
533
534                         required_string("$Mission:");
535                         stuff_string(name, F_NAME, NULL);
536                         cm = &Campaign.missions[Campaign.num_missions];
537                         cm->name = strdup(name);
538
539                         cm->briefing_cutscene[0] = 0;
540                         if ( optional_string("+Briefing Cutscene:") )
541                                 stuff_string( cm->briefing_cutscene, F_NAME, NULL );
542
543                         cm->flags = 0;
544                         if (optional_string("+Flags:"))
545                                 stuff_int(&cm->flags);
546
547                         cm->formula = -1;
548                         if ( optional_string("+Formula:") ) {
549                                 cm->formula = get_sexp_main();
550                                 if ( !Fred_running ) {
551                                         Assert ( cm->formula != -1 );
552                                         sexp_mark_persistent( cm->formula );
553
554                                 } else {
555                                         if ( cm->formula == -1 ){
556                                                 // close localization
557                                                 lcl_ext_close();
558
559                                                 return CAMPAIGN_ERROR_SEXP_EXHAUSTED;
560                                         }
561                                 }
562                         }
563
564                         // Do misison looping stuff
565                         cm->has_mission_loop = 0;
566                         if ( optional_string("+Mission Loop:") ) {
567                                 cm->has_mission_loop = 1;
568                         }
569
570                         cm->mission_loop_desc = NULL;
571                         if ( optional_string("+Mission Loop Text:")) {
572                                 cm->mission_loop_desc = stuff_and_malloc_string(F_MULTITEXT, NULL, MISSION_DESC_LENGTH);
573                         }
574
575                         cm->mission_loop_brief_anim = NULL;
576                         if ( optional_string("+Mission Loop Brief Anim:")) {
577                                 cm->mission_loop_brief_anim = stuff_and_malloc_string(F_MULTITEXT, NULL, MAX_FILENAME_LEN);
578                         }
579
580                         cm->mission_loop_brief_sound = NULL;
581                         if ( optional_string("+Mission Loop Brief Sound:")) {
582                                 cm->mission_loop_brief_sound = stuff_and_malloc_string(F_MULTITEXT, NULL, MAX_FILENAME_LEN);
583                         }
584
585                         cm->mission_loop_formula = -1;
586                         if ( optional_string("+Formula:") ) {
587                                 cm->mission_loop_formula = get_sexp_main();
588                                 if ( !Fred_running ) {
589                                         Assert ( cm->mission_loop_formula != -1 );
590                                         sexp_mark_persistent( cm->mission_loop_formula );
591
592                                 } else {
593                                         if ( cm->mission_loop_formula == -1 ){
594                                                 // close localization
595                                                 lcl_ext_close();
596
597                                                 return CAMPAIGN_ERROR_SEXP_EXHAUSTED;
598                                         }
599                                 }
600                         }
601
602                         if (optional_string("+Level:")) {
603                                 stuff_int( &cm->level );
604                                 if ( cm->level == 0 )  // check if the top (root) of the whole tree
605                                         Campaign.next_mission = Campaign.num_missions;
606
607                         } else
608                                 Campaign.realign_required = 1;
609
610                         if (optional_string("+Position:"))
611                                 stuff_int( &cm->pos );
612                         else
613                                 Campaign.realign_required = 1;
614
615                         if (Fred_running) {
616                                 cm->num_goals = -1;
617                                 cm->num_events = -1;
618                                 cm->notes = NULL;
619
620                         } else {
621                                 cm->num_goals = 0;
622                                 cm->num_events = 0;
623                         }
624
625                         cm->goals = NULL;
626                         cm->events = NULL;
627                         Campaign.num_missions++;
628                 }
629         }
630
631         // set up the other variables for the campaign stuff.  After initializing, we must try and load
632         // the campaign save file for this player.  Since all campaign loads go through this routine, I
633         // think this place should be the only necessary place to load the campaign save stuff.  The campaign
634         // save file will get written when a mission has ended by player choice.
635         Campaign.next_mission = 0;
636         Campaign.prev_mission = -1;
637         Campaign.current_mission = -1;  
638
639         // loading the campaign will get us to the current and next mission that the player must fly
640         // plus load all of the old goals that future missions might rely on.
641         if (!Fred_running && load_savefile && (Campaign.type == CAMPAIGN_TYPE_SINGLE)) {
642                 mission_campaign_savefile_load(Campaign.filename);
643         }
644
645         // close localization
646         lcl_ext_close();
647
648         return 0;
649 }
650
651 // mission_campaign_load_by_name() loads up a freespace campaign given the filename.  This routine
652 // is used to load up campaigns when a pilot file is loaded.  Generally, the
653 // filename will probably be the freespace campaign file, but not necessarily.
654 int mission_campaign_load_by_name( char *filename )
655 {
656         char name[NAME_LENGTH],test[5];
657         int type,max_players;
658
659         // make sure to tack on .fsc on the end if its not there already
660         if(strlen(filename) > 0){
661                 if(strlen(filename) > 4){
662                         strcpy(test,filename+(strlen(filename)-4));
663                         if(strcmp(test, FS_CAMPAIGN_FILE_EXT)!=0){
664                                 strcat(filename, FS_CAMPAIGN_FILE_EXT);
665                         }
666                 } else {
667                         strcat(filename, FS_CAMPAIGN_FILE_EXT);
668                 }
669         } else {
670                 Error(LOCATION,"Tried to load campaign file with illegal length/extension!");
671         }
672
673         if (!mission_campaign_get_info(filename, name, &type, &max_players)){
674                 return -1;      
675         }
676
677         Num_campaigns = 0;
678         Campaign_file_names[Num_campaigns] = filename;
679         Campaign_names[Num_campaigns] = name;
680         Num_campaigns++;
681         mission_campaign_load(filename);                
682         return 0;
683 }
684
685 int mission_campaign_load_by_name_csfe( char *filename, char *callsign )
686 {
687         Game_mode |= GM_NORMAL;
688         strcpy(Player->callsign, callsign);
689         return mission_campaign_load_by_name( filename);
690 }
691
692
693 // mission_campaign_init initializes some variables then loads the default Freespace single player campaign.
694 void mission_campaign_init()
695 {
696         memset(&Campaign, 0, sizeof(Campaign) );
697 }
698
699 // Fill in the root of the campaign save filename
700 void mission_campaign_savefile_generate_root(char *filename)
701 {
702 #ifdef PLAT_UNIX
703         STUB_FUNCTION;
704 #else
705         char base[_MAX_FNAME];
706
707         Assert ( strlen(Campaign.filename) != 0 );
708
709         // build up the filename for the save file.  There could be a problem with filename length,
710         // but this problem can get fixed in several ways -- ignore the problem for now though.
711         _splitpath( Campaign.filename, NULL, NULL, base, NULL );
712         Assert ( (strlen(base) + strlen(Player->callsign) + 1) < _MAX_FNAME );
713
714         sprintf( filename, NOX("%s.%s."), Player->callsign, base );
715 #endif
716 }
717
718 // mission_campaign_savefile_save saves the state of the campaign.  This function will probably always be called
719 // then the player is done flying a mission in the campaign path.  It will save the missions played, the
720 // state of the goals, etc.
721
722 int mission_campaign_savefile_save()
723 {
724         char filename[_MAX_FNAME];
725         CFILE *fp;
726         int i,j, mission_count;
727
728         memset(filename, 0, _MAX_FNAME);
729         mission_campaign_savefile_generate_root(filename);
730
731         // name the file differently depending on whether we're in single player or multiplayer mode
732         // single player : *.csg
733         strcat( filename, NOX("csg"));  
734
735         fp = cfopen(filename,"wb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
736
737         if (!fp)
738                 return errno;
739
740         // Write out campaign file info
741         cfwrite_int( CAMPAIGN_FILE_ID,fp );
742         cfwrite_int( CAMPAIGN_FILE_VERSION,fp );
743
744         // put in the file signature (single or multiplayer campaign) - see MissionCampaign.h for the #defines
745         cfwrite_int( CAMPAIGN_SINGLE_PLAYER_SIG, fp );
746
747         // do we need to write out the filename of the campaign?
748         cfwrite_string_len( Campaign.filename, fp );
749         cfwrite_int( Campaign.prev_mission, fp );
750         cfwrite_int( Campaign.next_mission, fp );
751         cfwrite_int( Campaign.loop_reentry, fp );
752         cfwrite_int( Campaign.loop_enabled, fp );
753
754         // write out the information for ships/weapons which this player is allowed to use
755         cfwrite_int(Num_ship_types, fp);
756         cfwrite_int(Num_weapon_types, fp);
757         for ( i = 0; i < Num_ship_types; i++ ){
758                 cfwrite_char( Campaign.ships_allowed[i], fp );
759         }
760
761         for ( i = 0; i < Num_weapon_types; i++ ){
762                 cfwrite_char( Campaign.weapons_allowed[i], fp );
763         }
764
765         // write out the completed mission matrix.  Used to tell which missions the player
766         // can replay in the simulator.  Also, each completed mission contains a list of the goals
767         // that were in the mission along with the goal completion status.
768         cfwrite_int( Campaign.num_missions_completed, fp );
769         for (i = 0; i < MAX_CAMPAIGN_MISSIONS; i++ ) {
770                 if ( Campaign.missions[i].completed ) {
771                         cfwrite_int( i, fp );
772                         cfwrite_int( Campaign.missions[i].num_goals, fp );
773                         for ( j = 0; j < Campaign.missions[i].num_goals; j++ ) {
774                                 cfwrite_string_len( Campaign.missions[i].goals[j].name, fp );
775                                 cfwrite_char( Campaign.missions[i].goals[j].status, fp );
776                         }
777                         cfwrite_int( Campaign.missions[i].num_events, fp );
778                         for ( j = 0; j < Campaign.missions[i].num_events; j++ ) {
779                                 cfwrite_string_len( Campaign.missions[i].events[j].name, fp );
780                                 cfwrite_char( Campaign.missions[i].events[j].status, fp );
781                         }
782
783                         // write flags
784                         cfwrite_int(Campaign.missions[i].flags, fp);
785                 }
786         }
787
788         cfclose( fp );
789
790         // 6/17/98
791         // ugh!  due to horrible bug, the stats saved at the end of every level were not written
792         // out to disk.  Write out a seperate file to do this.  We will only read it in if we actually
793         // find the file.
794         memset(filename, 0, _MAX_FNAME);
795         mission_campaign_savefile_generate_root(filename);
796
797         // name the file differently depending on whether we're in single player or multiplayer mode
798         // single player : *.csg
799         strcat( filename, NOX("css"));
800
801         fp = cfopen(filename,"wb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
802
803         if (!fp)
804                 return errno;
805
806         // Write out campaign file info
807         cfwrite_int( CAMPAIGN_STATS_FILE_ID,fp );
808         cfwrite_int( CAMPAIGN_STATS_FILE_VERSION,fp );
809
810         // determine how many missions we are saving -- I think that this method is safer than the method
811         // I used for release
812         mission_count = 0;
813         for ( i = 0; i < Campaign.num_missions; i++ ) {
814                 if ( Campaign.missions[i].completed ) {
815                         mission_count++;
816                 }
817         }
818
819         // write out the stats information to disk.     
820         cfwrite_int( mission_count, fp );
821         for (i = 0; i < Campaign.num_missions; i++ ) {
822                 if ( Campaign.missions[i].completed ) {
823                         cfwrite_int( i, fp );
824                         cfwrite( &Campaign.missions[i].stats, sizeof(scoring_struct), 1, fp );
825                 }
826         }
827
828         cfclose( fp );
829         
830         return 0;
831 }
832
833 // The following function always only ever ever ever called by CSFE!!!!!
834 int campaign_savefile_save(char *pname)
835 {
836         if (Campaign.type == CAMPAIGN_TYPE_SINGLE)
837                 Game_mode &= ~GM_MULTIPLAYER;
838         else
839                 Game_mode |= GM_MULTIPLAYER;
840
841         strcpy(Player->callsign, pname);
842         //memcpy(&Campaign, camp, sizeof(campaign));
843         return mission_campaign_savefile_save();
844 }
845
846
847 // the below two functions is internal to this module.  It is here so that I can group the save/load
848 // functions together.
849 //
850
851 // mission_campaign_savefile_delete deletes any save file in the players directory for the given
852 // campaign filename
853 void mission_campaign_savefile_delete( char *cfilename, int is_multi )
854 {
855 #ifdef PLAT_UNIX
856         STUB_FUNCTION;
857 #else
858         char filename[_MAX_FNAME], base[_MAX_FNAME];
859
860         _splitpath( cfilename, NULL, NULL, base, NULL );
861
862         if ( Player->flags & PLAYER_FLAGS_IS_MULTI ) {
863                 return; // no such thing as a multiplayer campaign savefile
864         }
865
866         sprintf( filename, NOX("%s.%s.csg"), Player->callsign, base );
867
868         cf_delete( filename, CF_TYPE_SINGLE_PLAYERS );
869 #endif
870 }
871
872 void campaign_delete_save( char *cfn, char *pname)
873 {
874         strcpy(Player->callsign, pname);
875         mission_campaign_savefile_delete(cfn);
876 }
877
878 // next function deletes all the save files for this particular pilot.  Just call cfile function
879 // which will delete multiple files
880 // Player_select_mode tells us whether we are deleting single or multiplayer files
881 void mission_campaign_delete_all_savefiles( char *pilot_name, int is_multi )
882 {
883         int dir_type, num_files, i;
884         char *names[MAX_CAMPAIGNS], spec[MAX_FILENAME_LEN + 2], *ext;
885         char filename[1024];
886         int (*filter_save)(char *filename);
887
888         if ( is_multi ) {
889                 return;                         // can't have multiplayer campaign save files
890         }
891
892         ext = NOX(".csg");
893         dir_type = CF_TYPE_SINGLE_PLAYERS;
894
895         sprintf(spec, NOX("%s.*%s"), pilot_name, ext);
896
897         // HACK HACK HACK HACK!!!!  cf_get_file_list is not reentrant.  Pretty dumb because it should
898         // be.  I have to save any file filters
899         filter_save = Get_file_list_filter;
900         Get_file_list_filter = NULL;
901         num_files = cf_get_file_list(MAX_CAMPAIGNS, names, dir_type, spec);
902         Get_file_list_filter = filter_save;
903
904         for (i=0; i<num_files; i++) {
905                 strcpy(filename, names[i]);
906                 strcat(filename, ext);
907                 cf_delete(filename, dir_type);
908                 free(names[i]);
909         }
910 }
911
912 // mission_campaign_savefile_load takes a filename of a campaign file as a parameter and loads all
913 // of the information stored in the campaign file.
914 void mission_campaign_savefile_load( char *cfilename )
915 {
916 #ifdef PLAT_UNIX
917         STUB_FUNCTION;
918 #else
919         char filename[_MAX_FNAME], base[_MAX_FNAME];
920         int id, version, i, num, j, num_stats_blocks;
921         int type_sig;
922         CFILE *fp;
923
924         Assert ( strlen(cfilename) != 0 );
925
926         // probably only called from single player games anymore!!! should be anyway
927         Assert( Game_mode & GM_NORMAL );                // get allender or DaveB.  trying to save campaign in multiplayer
928
929         // build up the filename for the save file.  There could be a problem with filename length,
930         // but this problem can get fixed in several ways -- ignore the problem for now though.
931         _splitpath( cfilename, NULL, NULL, base, NULL );
932         Assert ( (strlen(base) + strlen(Player->callsign) + 1) < _MAX_FNAME );
933
934         if(Game_mode & GM_MULTIPLAYER)
935                 sprintf( filename, NOX("%s.%s.msg"), Player->callsign, base );
936         else
937                 sprintf( filename, NOX("%s.%s.csg"), Player->callsign, base );
938
939         fp = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS );
940         if ( !fp )
941                 return;
942
943         id = cfread_int( fp );
944         if ( id != CAMPAIGN_FILE_ID ) {
945                 Warning(LOCATION, "Campaign save file has invalid signature");
946                 cfclose( fp );
947                 return;
948         }
949
950         version = cfread_int( fp );
951         if ( version < CAMPAIGN_FILE_COMPATIBLE_VERSION ) {
952                 Warning(LOCATION, "Campaign save file too old -- not compatible.  Deleting file.\nYou can continue from here without trouble\n\n");
953                 cfclose( fp );
954                 cf_delete( filename, CF_TYPE_SINGLE_PLAYERS );
955                 return;
956         }
957
958         // verify that we are loading the correct type of campaign file for the mode that we are in.
959         if(version >= 3)
960                 type_sig = cfread_int( fp );
961         else
962                 type_sig = CAMPAIGN_SINGLE_PLAYER_SIG;
963         // the actual check
964         Assert( ((Game_mode & GM_MULTIPLAYER) && (type_sig==CAMPAIGN_MULTI_PLAYER_SIG)) || (!(Game_mode & GM_MULTIPLAYER) && (type_sig==CAMPAIGN_SINGLE_PLAYER_SIG)) );
965
966         Campaign.type = type_sig == CAMPAIGN_SINGLE_PLAYER_SIG ? CAMPAIGN_TYPE_SINGLE : CAMPAIGN_TYPE_MULTI_COOP;
967
968         // read in the filename of the campaign and compare the filenames to be sure that
969         // we are reading data that really belongs to this campaign.  I think that this check
970         // is redundant.
971         cfread_string_len( filename, _MAX_FNAME, fp );
972         /*if ( stricmp( filename, cfilename) ) {        //      Used to be !stricmp.  How did this ever work? --MK, 11/9/97
973                 Warning(LOCATION, "Campaign save file appears corrupt because of mismatching filenames.");
974                 cfclose(fp);
975                 return;
976         }*/
977
978         Campaign.prev_mission = cfread_int( fp );
979         Campaign.next_mission = cfread_int( fp );
980         Campaign.loop_reentry = cfread_int( fp );
981         Campaign.loop_enabled = cfread_int( fp );
982
983         //  load information about ships/weapons allowed        
984         int ship_count, weapon_count;
985
986         // if earlier than mission disk version, use old MAX_SHIP_TYPES, otherwise read from the file           
987         if(version <= CAMPAIGN_INITIAL_RELEASE_FILE_VERSION){
988                 ship_count = CAMPAIGN_SAVEFILE_MAX_SHIPS_OLD;
989                 weapon_count = CAMPAIGN_SAVEFILE_MAX_WEAPONS_OLD;
990         } else {
991                 ship_count = cfread_int(fp);
992                 weapon_count = cfread_int(fp);
993         }
994
995         for ( i = 0; i < ship_count; i++ ){
996                 Campaign.ships_allowed[i] = cfread_ubyte( fp );
997         }
998
999         for ( i = 0; i < weapon_count; i++ ){
1000                 Campaign.weapons_allowed[i] = cfread_ubyte( fp );
1001         }       
1002
1003         // read in the completed mission matrix.  Used to tell which missions the player
1004         // can replay in the simulator.  Also, each completed mission contains a list of the goals
1005         // that were in the mission along with the goal completion status.
1006         Campaign.num_missions_completed = cfread_int( fp );
1007         for (i = 0; i < Campaign.num_missions_completed; i++ ) {
1008                 num = cfread_int( fp );
1009                 Campaign.missions[num].completed = 1;
1010                 Campaign.missions[num].num_goals = cfread_int( fp );
1011                 
1012                 // be sure to malloc out space for the goals stuff, then zero the memory!!!  Don't do malloc
1013                 // if there are no goals
1014                 Campaign.missions[num].goals = (mgoal *)malloc( Campaign.missions[num].num_goals * sizeof(mgoal) );
1015                 if ( Campaign.missions[num].num_goals > 0 ) {
1016                         memset( Campaign.missions[num].goals, 0, sizeof(mgoal) * Campaign.missions[num].num_goals );
1017                         Assert( Campaign.missions[num].goals != NULL );
1018                 }
1019
1020                 // now read in the goal information for this mission
1021                 for ( j = 0; j < Campaign.missions[num].num_goals; j++ ) {
1022                         cfread_string_len( Campaign.missions[num].goals[j].name, NAME_LENGTH, fp );
1023                         Campaign.missions[num].goals[j].status = cfread_char( fp );
1024                 }
1025
1026                 // get the events from the savefile
1027                 Campaign.missions[num].num_events = cfread_int( fp );
1028                 
1029                 // be sure to malloc out space for the events stuff, then zero the memory!!!  Don't do malloc
1030                 // if there are no events
1031 //              if (Campaign.missions[num].events < 0)
1032 //                      Campaign.missions[num].events = 0;
1033                 Campaign.missions[num].events = (mevent *)malloc( Campaign.missions[num].num_events * sizeof(mevent) );
1034                 if ( Campaign.missions[num].num_events > 0 ) {
1035                         memset( Campaign.missions[num].events, 0, sizeof(mevent) * Campaign.missions[num].num_events );
1036                         Assert( Campaign.missions[num].events != NULL );
1037                 }
1038                 
1039                 // now read in the event information for this mission
1040                 for ( j = 0; j < Campaign.missions[num].num_events; j++ ) {
1041                         cfread_string_len( Campaign.missions[num].events[j].name, NAME_LENGTH, fp );
1042                         Campaign.missions[num].events[j].status = cfread_char( fp );
1043                 }
1044
1045                 // now read flags
1046                 Campaign.missions[num].flags = cfread_int(fp);
1047         }       
1048
1049         cfclose( fp );
1050
1051         // 4/17/98
1052         // now, try and read in the campaign stats saved information.  This code was added for the 1.03 patch
1053         // since the stats data was never written out to disk.  We try and open the file, and if we cannot find
1054         // it, then simply return
1055         sprintf( filename, NOX("%s.%s.css"), Player->callsign, base );
1056
1057         fp = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS );
1058         if ( !fp )
1059                 return;
1060
1061         id = cfread_int( fp );
1062         if ( id != CAMPAIGN_STATS_FILE_ID ) {
1063                 Warning(LOCATION, "Campaign stats save file has invalid signature");
1064                 cfclose( fp );
1065                 return;
1066         }
1067
1068         version = cfread_int( fp );
1069         if ( version < CAMPAIGN_STATS_FILE_COMPATIBLE_VERSION ) {
1070                 Warning(LOCATION, "Campaign save file too old -- not compatible.  Deleting file.\nYou can continue from here without trouble\n\n");
1071                 cfclose( fp );
1072                 cf_delete( filename, CF_TYPE_SINGLE_PLAYERS );
1073                 return;
1074         }
1075
1076         num_stats_blocks = cfread_int( fp );
1077         for (i = 0; i < num_stats_blocks; i++ ) {
1078                 num = cfread_int( fp );
1079                 cfread( &Campaign.missions[num].stats, sizeof(scoring_struct), 1, fp );
1080         }
1081
1082         cfclose(fp);
1083
1084 #endif
1085 }
1086
1087 // the following code only ever called by CSFE!!!!
1088 void campaign_savefile_load(char *fname, char *pname)
1089 {
1090         if (Campaign.type==CAMPAIGN_TYPE_SINGLE) {
1091                 Game_mode &= ~GM_MULTIPLAYER;
1092                 Game_mode &= GM_NORMAL;
1093         }
1094         else
1095                 Game_mode |= GM_MULTIPLAYER;
1096         strcpy(Player->callsign, pname);
1097         mission_campaign_savefile_load(fname);
1098 }
1099
1100 // mission_campaign_next_mission sets up the internal veriables of the campaign
1101 // structure so the player can play the next mission.  If there are no more missions
1102 // available in the campaign, this function returns -1, else 0 if the mission was
1103 // set successfully
1104 int mission_campaign_next_mission()
1105 {
1106         if ( (Campaign.next_mission == -1) || (strlen(Campaign.name) == 0) ) // will be set to -1 when there is no next mission
1107                 return -1;
1108
1109         Campaign.current_mission = Campaign.next_mission;       
1110         strncpy( Game_current_mission_filename, Campaign.missions[Campaign.current_mission].name, MAX_FILENAME_LEN );
1111
1112         // check for end of loop.
1113         if (Campaign.current_mission == Campaign.loop_reentry) {
1114                 Campaign.loop_enabled = 0;
1115         }
1116
1117         // reset the number of persistent ships and weapons for the next campaign mission
1118         Num_granted_ships = 0;
1119         Num_granted_weapons = 0;
1120         return 0;
1121 }
1122
1123 // mission_campaign_previous_mission() gets called to go to the previous mission in
1124 // the campaign.  Used only for Red Alert missions
1125 int mission_campaign_previous_mission()
1126 {
1127         if ( !(Game_mode & GM_CAMPAIGN_MODE) )
1128                 return 0;
1129
1130         if ( Campaign.prev_mission == -1 )
1131                 return 0;
1132
1133         Campaign.current_mission = Campaign.prev_mission;
1134         Campaign.next_mission = Campaign.current_mission;
1135         Campaign.num_missions_completed--;
1136         Campaign.missions[Campaign.next_mission].completed = 0;
1137         mission_campaign_savefile_save();
1138
1139         // reset the player stats to be the stats from this level
1140         memcpy( &Player->stats, &Campaign.missions[Campaign.current_mission].stats, sizeof(Player->stats) );
1141
1142         strncpy( Game_current_mission_filename, Campaign.missions[Campaign.current_mission].name, MAX_FILENAME_LEN );
1143         Num_granted_ships = 0;
1144         Num_granted_weapons = 0;
1145
1146         return 1;
1147 }
1148
1149 /*
1150 // determine what the next mission is after the current one.  Because this evaluates an sexp,
1151 // and that could check just about anything, the results are only going to be valid in
1152 // certain places.
1153 // DA 12/09/98 -- To allow for mission loops, need to maintain call with store stats
1154 int mission_campaign_eval_next_mission( int store_stats )
1155 {
1156         char *name;
1157         int cur, i;
1158         cmission *mission;
1159
1160         Campaign.next_mission = -1;
1161         cur = Campaign.current_mission;
1162         name = Campaign.missions[cur].name;
1163
1164         mission = &Campaign.missions[cur];
1165
1166         // first we must save the status of the current missions goals in the campaign mission structure.
1167         // After that, we can determine which mission is tagged as the next mission.  Finally, we
1168         // can save the campaign save file
1169         // we might have goal and event status if the player replayed a mission
1170         if ( mission->num_goals > 0 ) {
1171                 free( mission->goals );
1172         }
1173
1174         mission->num_goals = Num_goals;
1175         if ( mission->num_goals > 0 ) {
1176                 mission->goals = (mgoal *)malloc( sizeof(mgoal) * Num_goals );
1177                 Assert( mission->goals != NULL );
1178         }
1179
1180         // copy the needed info from the Mission_goal struct to our internal structure
1181         for (i = 0; i < Num_goals; i++ ) {
1182                 if ( strlen(Mission_goals[i].name) == 0 ) {
1183                         char name[NAME_LENGTH];
1184
1185                         sprintf(name, NOX("Goal #%d"), i);
1186                         //Warning(LOCATION, "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name);
1187                         strcpy( mission->goals[i].name, name);
1188                 } else
1189                         strcpy( mission->goals[i].name, Mission_goals[i].name );
1190                 Assert ( Mission_goals[i].satisfied != GOAL_INCOMPLETE );               // should be true or false at this point!!!
1191                 mission->goals[i].status = (char)Mission_goals[i].satisfied;
1192         }
1193
1194         // do the same thing for events as we did for goals
1195         // we might have goal and event status if the player replayed a mission
1196         if ( mission->num_events > 0 ) {
1197                 free( mission->events );
1198         }
1199
1200         mission->num_events = Num_mission_events;
1201         if ( mission->num_events > 0 ) {
1202                 mission->events = (mevent *)malloc( sizeof(mevent) * Num_mission_events );
1203                 Assert( mission->events != NULL );
1204         }
1205
1206         // copy the needed info from the Mission_goal struct to our internal structure
1207         for (i = 0; i < Num_mission_events; i++ ) {
1208                 if ( strlen(Mission_events[i].name) == 0 ) {
1209                         char name[NAME_LENGTH];
1210
1211                         sprintf(name, NOX("Event #%d"), i);
1212                         nprintf(("Warning", "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name));
1213                         strcpy( mission->events[i].name, name);
1214                 } else
1215                         strcpy( mission->events[i].name, Mission_events[i].name );
1216
1217                 // getting status for the events is a little different.  If the formula value for the event entry
1218                 // is -1, then we know the value of the result field will never change.  If the formula is
1219                 // not -1 (i.e. still being evaluated at mission end time), we will write "incomplete" for the
1220                 // event evaluation
1221                 if ( Mission_events[i].formula == -1 ) {
1222                         if ( Mission_events[i].result )
1223                                 mission->events[i].status = EVENT_SATISFIED;
1224                         else
1225                                 mission->events[i].status = EVENT_FAILED;
1226                 } else
1227                         Int3();
1228         }
1229
1230         // maybe store the alltime stats which would be current at the end of this mission
1231         if ( store_stats ) {
1232                 memcpy( &mission->stats, &Player->stats, sizeof(Player->stats) );
1233                 scoring_backout_accept( &mission->stats );
1234         }
1235
1236         if ( store_stats ) {    // second (last) time through, so use choose loop_mission if chosen
1237                 if ( Campaign.loop_enabled ) {
1238                         Campaign.next_mission = Campaign.loop_mission;
1239                 } else {
1240                         // evaluate next mission (straight path)
1241                         if (Campaign.missions[cur].formula != -1) {
1242                                 flush_sexp_tree(Campaign.missions[cur].formula);  // force formula to be re-evaluated
1243                                 eval_sexp(Campaign.missions[cur].formula);  // this should reset Campaign.next_mission to proper value
1244                         }
1245                 }
1246         } else {
1247
1248                 // evaluate next mission (straight path)
1249                 if (Campaign.missions[cur].formula != -1) {
1250                         flush_sexp_tree(Campaign.missions[cur].formula);  // force formula to be re-evaluated
1251                         eval_sexp(Campaign.missions[cur].formula);  // this should reset Campaign.next_mission to proper value
1252                 }
1253
1254                 // evaluate mission loop mission (if any) so it can be used if chosen
1255                 if ( Campaign.missions[cur].has_mission_loop ) {
1256                         int copy_next_mission = Campaign.next_mission;
1257                         // Set temporarily to -1 so we know if loop formula fails to assign
1258                         Campaign.next_mission = -1;  // Cannot exit campaign from loop
1259                         if (Campaign.missions[cur].mission_loop_formula != -1) {
1260                                 flush_sexp_tree(Campaign.missions[cur].mission_loop_formula);  // force formula to be re-evaluated
1261                                 eval_sexp(Campaign.missions[cur].mission_loop_formula);  // this should reset Campaign.next_mission to proper value
1262                         }
1263
1264                         Campaign.loop_mission = Campaign.next_mission;
1265                         Campaign.next_mission = copy_next_mission;
1266                 }
1267         }
1268
1269         if (Campaign.next_mission == -1)
1270                 nprintf(("allender", "No next mission to proceed to.\n"));
1271         else
1272                 nprintf(("allender", "Next mission is number %d [%s]\n", Campaign.next_mission, Campaign.missions[Campaign.next_mission].name));
1273
1274         return Campaign.next_mission;
1275 } */
1276
1277 // Evaluate next campaign mission - set as Campaign.next_mission.  Also set Campaign.loop_mission
1278 void mission_campaign_eval_next_mission()
1279 {
1280         Campaign.next_mission = -1;
1281         int cur = Campaign.current_mission;
1282
1283         // evaluate next mission (straight path)
1284         if (Campaign.missions[cur].formula != -1) {
1285                 flush_sexp_tree(Campaign.missions[cur].formula);  // force formula to be re-evaluated
1286                 eval_sexp(Campaign.missions[cur].formula);  // this should reset Campaign.next_mission to proper value
1287         }
1288
1289         // evaluate mission loop mission (if any) so it can be used if chosen
1290         if ( Campaign.missions[cur].has_mission_loop ) {
1291                 int copy_next_mission = Campaign.next_mission;
1292                 // Set temporarily to -1 so we know if loop formula fails to assign
1293                 Campaign.next_mission = -1;
1294                 if (Campaign.missions[cur].mission_loop_formula != -1) {
1295                         flush_sexp_tree(Campaign.missions[cur].mission_loop_formula);  // force formula to be re-evaluated
1296                         eval_sexp(Campaign.missions[cur].mission_loop_formula);  // this should reset Campaign.next_mission to proper value
1297                 }
1298
1299                 Campaign.loop_mission = Campaign.next_mission;
1300                 Campaign.next_mission = copy_next_mission;
1301         }
1302
1303         if (Campaign.next_mission == -1) {
1304                 nprintf(("allender", "No next mission to proceed to.\n"));
1305         } else {
1306                 nprintf(("allender", "Next mission is number %d [%s]\n", Campaign.next_mission, Campaign.missions[Campaign.next_mission].name));
1307         }
1308
1309 }
1310
1311 // Store mission's goals and events in Campaign struct
1312 void mission_campaign_store_goals_and_events()
1313 {
1314         char *name;
1315         int cur, i;
1316         cmission *mission;
1317
1318         cur = Campaign.current_mission;
1319         name = Campaign.missions[cur].name;
1320
1321         mission = &Campaign.missions[cur];
1322
1323         // first we must save the status of the current missions goals in the campaign mission structure.
1324         // After that, we can determine which mission is tagged as the next mission.  Finally, we
1325         // can save the campaign save file
1326         // we might have goal and event status if the player replayed a mission
1327         if ( mission->num_goals > 0 ) {
1328                 free( mission->goals );
1329         }
1330
1331         mission->num_goals = Num_goals;
1332         if ( mission->num_goals > 0 ) {
1333                 mission->goals = (mgoal *)malloc( sizeof(mgoal) * Num_goals );
1334                 Assert( mission->goals != NULL );
1335         }
1336
1337         // copy the needed info from the Mission_goal struct to our internal structure
1338         for (i = 0; i < Num_goals; i++ ) {
1339                 if ( strlen(Mission_goals[i].name) == 0 ) {
1340                         char name[NAME_LENGTH];
1341
1342                         sprintf(name, NOX("Goal #%d"), i);
1343                         //Warning(LOCATION, "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name);
1344                         strcpy( mission->goals[i].name, name);
1345                 } else
1346                         strcpy( mission->goals[i].name, Mission_goals[i].name );
1347                 Assert ( Mission_goals[i].satisfied != GOAL_INCOMPLETE );               // should be true or false at this point!!!
1348                 mission->goals[i].status = (char)Mission_goals[i].satisfied;
1349         }
1350
1351         // do the same thing for events as we did for goals
1352         // we might have goal and event status if the player replayed a mission
1353         if ( mission->num_events > 0 ) {
1354                 free( mission->events );
1355         }
1356
1357         mission->num_events = Num_mission_events;
1358         if ( mission->num_events > 0 ) {
1359                 mission->events = (mevent *)malloc( sizeof(mevent) * Num_mission_events );
1360                 Assert( mission->events != NULL );
1361         }
1362
1363         // copy the needed info from the Mission_goal struct to our internal structure
1364         for (i = 0; i < Num_mission_events; i++ ) {
1365                 if ( strlen(Mission_events[i].name) == 0 ) {
1366                         char name[NAME_LENGTH];
1367
1368                         sprintf(name, NOX("Event #%d"), i);
1369                         nprintf(("Warning", "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name));
1370                         strcpy( mission->events[i].name, name);
1371                 } else
1372                         strcpy( mission->events[i].name, Mission_events[i].name );
1373
1374                 // getting status for the events is a little different.  If the formula value for the event entry
1375                 // is -1, then we know the value of the result field will never change.  If the formula is
1376                 // not -1 (i.e. still being evaluated at mission end time), we will write "incomplete" for the
1377                 // event evaluation
1378                 if ( Mission_events[i].formula == -1 ) {
1379                         if ( Mission_events[i].result )
1380                                 mission->events[i].status = EVENT_SATISFIED;
1381                         else
1382                                 mission->events[i].status = EVENT_FAILED;
1383                 } else
1384                         Int3();
1385         }
1386 }
1387
1388 // this function is called when the player's mission is over.  It updates the internal store of goals
1389 // and their status then saves the state of the campaign in the campaign file.  This gets called
1390 // after player accepts mission results in debriefing.
1391 void mission_campaign_mission_over()
1392 {
1393         int mission_num, i;
1394         cmission *mission;
1395
1396         // I don't think that we should have a record for these -- maybe we might??????  If we do,
1397         // then we should free them
1398         if ( !(Game_mode & GM_CAMPAIGN_MODE) ){
1399                 return;
1400         }
1401
1402         mission_num = Campaign.current_mission;
1403         Assert( mission_num != -1 );
1404         mission = &Campaign.missions[mission_num];
1405
1406         // determine if any ships/weapons were granted this mission
1407         for ( i=0; i<Num_granted_ships; i++ ){
1408                 Campaign.ships_allowed[Granted_ships[i]] = 1;
1409         }
1410
1411         for ( i=0; i<Num_granted_weapons; i++ ){
1412                 Campaign.weapons_allowed[Granted_weapons[i]] = 1;       
1413         }
1414
1415         // DKA 12/11/98 - Unneeded already evaluated and stored
1416         // determine what new mission we are moving to.
1417         //      mission_campaign_eval_next_mission(1);
1418
1419         // update campaign.mission stats (used to allow backout inRedAlert)
1420         memcpy( &mission->stats, &Player->stats, sizeof(Player->stats) );
1421         if(!(Game_mode & GM_MULTIPLAYER)){
1422                 scoring_backout_accept( &mission->stats );
1423         }
1424
1425         // if we are moving to a new mission, then change our data.  If we are staying on the same mission,
1426         // then we don't want to do anything.  Remove information about goals/events
1427         if ( Campaign.next_mission != mission_num ) {
1428                 Campaign.prev_mission = mission_num;
1429                 Campaign.current_mission = -1;
1430                 Campaign.num_missions_completed++;
1431                 Campaign.missions[mission_num].completed = 1;
1432
1433                 // save the scoring values from the previous mission at the start of this mission -- for red alert
1434
1435                 // save the state of the campaign in the campaign save file and move to the end_game state
1436                 if (Campaign.type == CAMPAIGN_TYPE_SINGLE) {
1437                         mission_campaign_savefile_save();
1438                 }
1439
1440         } else {
1441                 // free up the goals and events which were just malloced.  It's kind of like erasing any fact
1442                 // that the player played this mission in the campaign.
1443                 free( mission->goals );
1444                 mission->num_goals = 0;
1445
1446                 free( mission->events );
1447                 mission->num_events = 0;
1448
1449                 Sexp_nodes[mission->formula].value = SEXP_UNKNOWN;
1450         }
1451
1452         Assert(Player);
1453         if (Campaign.missions[Campaign.next_mission].flags & CMISSION_FLAG_BASTION){
1454                 Player->on_bastion = 1;
1455         } else {
1456                 Player->on_bastion = 0;
1457         }
1458
1459         mission_campaign_next_mission();                        // sets up whatever needs to be set to actually play next mission
1460 }
1461
1462 // called when the game closes -- to get rid of memory errors for Bounds checker
1463 void mission_campaign_close()
1464 {
1465         int i;
1466
1467         if (Campaign.desc)
1468                 free(Campaign.desc);
1469
1470         // be sure to remove all old malloced strings of Mission_names
1471         // we must also free any goal stuff that was from a previous campaign
1472         for ( i=0; i<Campaign.num_missions; i++ ) {
1473                 if ( Campaign.missions[i].name ){
1474                         free(Campaign.missions[i].name);
1475                 }
1476
1477                 if (Campaign.missions[i].notes){
1478                         free(Campaign.missions[i].notes);
1479                 }
1480
1481                 if ( Campaign.missions[i].num_goals > 0 ){
1482                         free ( Campaign.missions[i].goals );
1483                 }
1484
1485                 if ( Campaign.missions[i].num_events > 0 ){
1486                         free ( Campaign.missions[i].events );
1487                 }
1488
1489                 if ( !Fred_running ){
1490                         sexp_unmark_persistent(Campaign.missions[i].formula);           // free any sexpression nodes used by campaign.
1491                 }
1492
1493                 Campaign.missions[i].num_goals = 0;
1494                 Campaign.missions[i].num_events = 0;
1495         }
1496 }
1497
1498 // extract the mission filenames for a campaign.  
1499 //
1500 // filename     =>      name of campaign file
1501 //      dest            => storage for the mission filename, must be already allocated
1502 // num          => output parameter for the number of mission filenames in the campaign
1503 //
1504 // note that dest should allocate at least dest[MAX_CAMPAIGN_MISSIONS][NAME_LENGTH]
1505 int mission_campaign_get_filenames(char *filename, char dest[][NAME_LENGTH], int *num)
1506 {
1507         int     rval;
1508
1509         // read the mission file and get the list of mission filenames
1510         if ((rval = setjmp(parse_abort)) != 0) {
1511                 return rval;
1512
1513         } else {
1514                 read_file_text(filename);
1515                 Assert(strlen(filename) < MAX_FILENAME_LEN - 1);  // make sure no overflow
1516
1517                 reset_parse();
1518                 required_string("$Name:");
1519                 advance_to_eoln(NULL);
1520
1521                 required_string( "$Type:" );
1522                 advance_to_eoln(NULL);
1523
1524                 // parse the mission file and actually read in the mission stuff
1525                 *num = 0;
1526                 while ( skip_to_string("$Mission:") == 1 ) {
1527                         stuff_string(dest[*num], F_NAME, NULL);
1528                         (*num)++;
1529                 }
1530         }
1531
1532         return 0;
1533 }
1534
1535 // function to read the goals and events from a mission in a campaign file and store that information
1536 // in the campaign structure for use in the campaign editor, error checking, etc
1537 void read_mission_goal_list(int num)
1538 {
1539         char *filename, notes[NOTES_LENGTH], goals[MAX_GOALS][NAME_LENGTH];
1540         char events[MAX_MISSION_EVENTS][NAME_LENGTH];
1541         int i, z, r, event_count, count = 0;
1542
1543         filename = Campaign.missions[num].name;
1544         if ((r = setjmp(parse_abort))>0) {
1545                 Warning(LOCATION, "Error reading \"%s\" (code = %d)", filename, r);
1546                 return;
1547         }
1548
1549         // open localization
1550         lcl_ext_open(); 
1551         
1552         read_file_text(filename);
1553         init_parse();
1554
1555         // first, read the mission notes for this mission.  Used in campaign editor
1556         if (skip_to_string("#Mission Info")) {
1557                 if (skip_to_string("$Notes:")) {
1558                         stuff_string(notes, F_NOTES, NULL);
1559                         if (Campaign.missions[num].notes){
1560                                 free(Campaign.missions[num].notes);
1561                         }
1562
1563                         Campaign.missions[num].notes = (char *) malloc(strlen(notes) + 1);
1564                         strcpy(Campaign.missions[num].notes, notes);
1565                 }
1566         }
1567
1568         event_count = 0;
1569         // skip to events section in the mission file.  Events come before goals, so we process them first
1570         if ( skip_to_string("#Events") ) {
1571                 while (1) {
1572                         if (skip_to_string("$Formula:", "#Goals") != 1){
1573                                 break;
1574                         }
1575
1576                         z = skip_to_string("+Name:", "$Formula:");
1577                         if (!z){
1578                                 break;
1579                         }
1580
1581                         if (z == 1){
1582                                 stuff_string(events[event_count], F_NAME, NULL);
1583                         } else {
1584                                 sprintf(events[event_count], NOX("Event #%d"), event_count + 1);
1585                         }
1586
1587                         event_count++;
1588                         Assert(event_count < MAX_MISSION_EVENTS);
1589                 }
1590         }
1591
1592         count = 0;
1593         if (skip_to_string("#Goals")) {
1594                 while (1) {
1595                         if (skip_to_string("$Type:", "#End") != 1){
1596                                 break;
1597                         }
1598
1599                         z = skip_to_string("+Name:", "$Type:");
1600                         if (!z){
1601                                 break;
1602                         }
1603
1604                         if (z == 1){
1605                                 stuff_string(goals[count], F_NAME, NULL);
1606                         } else {
1607                                 sprintf(goals[count], NOX("Goal #%d"), count + 1);
1608                         }
1609
1610                         count++;
1611                         Assert(count < MAX_GOALS);
1612                 }
1613         }
1614
1615         Campaign.missions[num].num_goals = count;
1616         if (count) {
1617                 Campaign.missions[num].goals = (mgoal *) malloc(count * sizeof(mgoal));
1618                 Assert(Campaign.missions[num].goals);  // make sure we got the memory
1619                 memset(Campaign.missions[num].goals, 0, count * sizeof(mgoal));
1620
1621                 for (i=0; i<count; i++){
1622                         strcpy(Campaign.missions[num].goals[i].name, goals[i]);
1623                 }
1624         }
1625                 // copy the events
1626         Campaign.missions[num].num_events = event_count;
1627         if (event_count) {
1628                 Campaign.missions[num].events = (mevent *)malloc(event_count * sizeof(mevent));
1629                 Assert ( Campaign.missions[num].events );
1630                 memset(Campaign.missions[num].events, 0, event_count * sizeof(mevent));
1631
1632                 for (i = 0; i < event_count; i++ ){
1633                         strcpy(Campaign.missions[num].events[i].name, events[i]);
1634                 }
1635         }
1636
1637         // close localization
1638         lcl_ext_close();
1639 }
1640
1641 // function to return index into Campaign's list of missions of the mission with the given
1642 // filename.  This function tried to be a little smart about filename looking for the .fsm
1643 // extension since filenames are stored with the extension in the campaign file.  Returns
1644 // index of mission in campaign structure.  -1 if mission name not found.
1645 int mission_campaign_find_mission( char *name )
1646 {
1647         int i;
1648         char realname[_MAX_PATH];
1649
1650         // look for an extension on the file.  If no extension, add default ".fsm" onto the
1651         // end of the filename
1652         strcpy(realname, name );
1653         if ( strchr(name, '.') == NULL ){
1654                 sprintf(realname, NOX("%s%s"), name, FS_MISSION_FILE_EXT );
1655         }
1656
1657         for (i = 0; i < Campaign.num_missions; i++ ) {
1658                 if ( !stricmp(realname, Campaign.missions[i].name) ){
1659                         return i;
1660                 }
1661         }
1662
1663         return -1;
1664 }
1665
1666 void mission_campaign_maybe_play_movie(int type)
1667 {
1668         int mission;
1669         char *filename;
1670
1671         // only support pre mission movies for now.
1672         Assert ( type == CAMPAIGN_MOVIE_PRE_MISSION );
1673
1674         if ( !(Game_mode & GM_CAMPAIGN_MODE) )
1675                 return;
1676
1677         mission = Campaign.current_mission;
1678         Assert( mission != -1 );
1679
1680         // get a possible filename for a movie to play.
1681         filename = NULL;
1682         switch( type ) {
1683         case CAMPAIGN_MOVIE_PRE_MISSION:
1684                 if ( strlen(Campaign.missions[mission].briefing_cutscene) )
1685                         filename = Campaign.missions[mission].briefing_cutscene;
1686                 break;
1687
1688         default:
1689                 Int3();
1690                 break;
1691         }
1692
1693         // no filename, no movie!
1694         if ( !filename )
1695                 return;
1696
1697         // no soup for you!
1698         // movie_play( filename );
1699 }
1700
1701 // return nonzero if the passed filename is a multiplayer campaign, 0 otherwise
1702 int mission_campaign_parse_is_multi(char *filename, char *name)
1703 {       
1704         int i;
1705         char temp[50];
1706         
1707         read_file_text( filename );
1708         reset_parse();
1709         
1710         required_string("$Name:");
1711         stuff_string( temp, F_NAME, NULL );     
1712         if ( name )
1713                 strcpy( name, temp );
1714
1715         required_string( "$Type:" );
1716         stuff_string( temp, F_NAME, NULL );
1717
1718         for (i = 0; i < MAX_CAMPAIGN_TYPES; i++ ) {
1719                 if ( !stricmp(temp, campaign_types[i]) ) {
1720                         return i;
1721                 }
1722         }
1723
1724         Error(LOCATION, "Unknown campaign type %s", temp );
1725         return -1;
1726 }
1727
1728 // functions to save persistent information during a mission -- which then might or might not get
1729 // saved out when the mission is over depending on whether player replays mission or commits.
1730 void mission_campaign_save_persistent( int type, int sindex )
1731 {
1732         // based on the type of information, save it off for possible saving into the campsign
1733         // savefile when the mission is over
1734         if ( type == CAMPAIGN_PERSISTENT_SHIP ) {
1735                 Assert( Num_granted_ships < MAX_SHIP_TYPES );
1736                 Granted_ships[Num_granted_ships] = sindex;
1737                 Num_granted_ships++;
1738         } else if ( type == CAMPAIGN_PERSISTENT_WEAPON ) {
1739                 Assert( Num_granted_weapons < MAX_WEAPON_TYPES );
1740                 Granted_weapons[Num_granted_weapons] = sindex;
1741                 Num_granted_weapons++;
1742         } else
1743                 Int3();
1744 }
1745
1746 // returns 0: loaded, !0: error
1747 int mission_load_up_campaign()
1748 {
1749         if (strlen(Player->current_campaign))
1750                 return mission_campaign_load(Player->current_campaign);
1751         else
1752                 return mission_campaign_load(BUILTIN_CAMPAIGN);
1753 }
1754
1755 // for end of campaign in the single player game.  Called when the end of campaign state is
1756 // entered, which is triggered when the end-campaign sexpression is hit
1757
1758 void mission_campaign_end_init()
1759 {
1760         // no need to do any initialization.
1761 }
1762
1763 void mission_campaign_end_do()
1764 {
1765         // play the movies
1766         event_music_level_close();
1767         mission_goal_fail_incomplete();
1768         scoring_level_close();
1769         mission_campaign_mission_over();
1770
1771         // eventually we'll want to play one of two options (good ending or bad ending)
1772         // did the supernova blow?
1773         if(Supernova_status == SUPERNOVA_HIT){
1774                 // no soup for you!
1775                 // movie_play_two("endpart1.mve", "endprt2b.mve");                      // good ending
1776         } else {
1777                 // no soup for you!
1778                 // movie_play_two("endpart1.mve", "endprt2a.mve");                      // good ending
1779         }       
1780
1781 #ifdef FS2_DEMO
1782         gameseq_post_event( GS_EVENT_END_DEMO );
1783 #else   
1784         gameseq_post_event( GS_EVENT_MAIN_MENU );
1785 #endif
1786 }
1787
1788 void mission_campaign_end_close()
1789 {
1790         // nothing to do here.
1791 }
1792
1793
1794 // skip to the next mission in the campaign
1795 // this also posts the state change by default.  pass 0 to override that
1796 void mission_campaign_skip_to_next(int start_game)
1797 {
1798         // mark all goals/events complete
1799         // these do not really matter, since is-previous-event-* and is-previous-goal-* sexps check
1800         // to see if the mission was skipped, and use defaults accordingly.
1801         mission_goal_mark_objectives_complete();
1802         mission_goal_mark_events_complete();
1803
1804         // mark mission as skipped
1805         Campaign.missions[Campaign.current_mission].flags |= CMISSION_FLAG_SKIPPED;
1806
1807         // store
1808         mission_campaign_store_goals_and_events();
1809
1810         // now set the next mission
1811         mission_campaign_eval_next_mission();
1812
1813         // clear out relevant player vars
1814         Player->failures_this_session = 0;
1815         Player->show_skip_popup = 1;
1816
1817         if (start_game) {
1818                 // proceed to next mission or main hall
1819                 if ((Campaign.missions[Campaign.current_mission].has_mission_loop) && (Campaign.loop_mission != -1)) {
1820                         // go to loop solicitation
1821                         gameseq_post_event(GS_EVENT_LOOP_BRIEF);
1822                 } else {
1823                         // closes out mission stuff, sets up next one
1824                         mission_campaign_mission_over();
1825
1826                         if ( Campaign.next_mission == -1 ) {
1827                                 // go to main hall, tha campaign is over!
1828                                 gameseq_post_event(GS_EVENT_MAIN_MENU);
1829                         } else {
1830                                 // go to next mission
1831                                 gameseq_post_event(GS_EVENT_START_GAME);
1832                         }
1833                 }
1834         }
1835 }
1836
1837
1838 // breaks your ass out of the loop
1839 // this also posts the state change
1840 void mission_campaign_exit_loop()
1841 {
1842         // set campaign to loop reentry point
1843         Campaign.next_mission = Campaign.loop_reentry;
1844         Campaign.current_mission = -1;
1845         Campaign.loop_enabled = 0;
1846
1847         // set things up for next mission
1848         mission_campaign_next_mission();
1849         gameseq_post_event(GS_EVENT_START_GAME);
1850 }
1851
1852
1853 // used for jumping to a particular campaign mission
1854 // all pvs missions marked skipped
1855 // this relies on correct mission ordering in the campaign file
1856 void mission_campaign_jump_to_mission(char *name)
1857 {
1858         int i = 0;
1859         char dest_name[64];
1860
1861         // load in the campaign junk
1862         mission_load_up_campaign();
1863
1864         // tack the .fs2 onto the input name
1865         strcpy(dest_name, name);
1866         strcat(name, ".fs2");
1867
1868         // search for our mission
1869         for (i=0; i<Campaign.num_missions; i++) {
1870                 if ((Campaign.missions[i].name != NULL) && !stricmp(Campaign.missions[i].name, name) ) {
1871                         Campaign.next_mission = i;
1872                         Campaign.prev_mission = i-1;
1873                         mission_campaign_next_mission();
1874                         Game_mode |= GM_CAMPAIGN_MODE;
1875                         gameseq_post_event(GS_EVENT_START_GAME);
1876                         return;
1877                 } else {
1878                         Campaign.missions[i].flags |= CMISSION_FLAG_SKIPPED;
1879                         Campaign.num_missions_completed = i;
1880                 }
1881         }
1882
1883         // if we got here, no match was found
1884         // restart the campaign
1885         mission_campaign_savefile_delete(Campaign.filename);
1886         mission_campaign_load(Campaign.filename);
1887 }
1888