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