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