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