2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Mission/MissionCampaign.cpp $
15 * source for dealing with campaigns
18 * Revision 1.4 2002/06/09 04:41:22 relnev
19 * added copyright header
21 * Revision 1.3 2002/06/09 03:16:04 relnev
24 * removed unneeded asm, old sdl 2d setup.
26 * fixed crash caused by opengl_get_region.
28 * Revision 1.2 2002/06/02 04:26:34 relnev
31 * Revision 1.1.1.1 2002/05/03 03:28:09 root
35 * 23 9/14/99 4:35a Dave
36 * Argh. Added all kinds of code to handle potential crashes in debriefing
39 * 22 9/09/99 11:40p Dave
40 * Handle an Assert() in beam code. Added supernova sounds. Play the right
41 * 2 end movies properly, based upon what the player did in the mission.
43 * 21 9/09/99 9:34a Jefff
44 * fixed a potential exit-loop bug
46 * 20 9/07/99 6:55p Jefff
47 * functionality to break out of a loop. hacked functionality to jump to
48 * a specific mission in a campaign -- doesnt grant ships/weapons from
49 * skipped missions tho.
51 * 19 9/07/99 2:19p Jefff
52 * clear skip mission player vars on mission skip
54 * 18 9/06/99 9:45p Jefff
55 * break out of loop and skip mission support
57 * 17 9/06/99 6:38p Dave
58 * Improved CD detection code.
60 * 16 9/03/99 1:32a Dave
61 * CD checking by act. Added support to play 2 cutscenes in a row
62 * seamlessly. Fixed super low level cfile bug related to files in the
63 * root directory of a CD. Added cheat code to set campaign mission # in
66 * 15 8/27/99 12:04a Dave
67 * Campaign loop screen.
69 * 14 8/04/99 5:36p Andsager
70 * Show upsell screens at end of demo campaign before returning to main
73 * 13 2/05/99 3:50p Anoop
74 * Removed dumb campaign mission stats saving from multiplayer.
76 * 12 12/17/98 2:43p Andsager
77 * Modify fred campaign save file to include optional mission loops
79 * 11 12/12/98 3:17p Andsager
80 * Clean up mission eval, goal, event and mission scoring.
82 * 10 12/10/98 9:59a Andsager
83 * Fix some bugs with mission loops
85 * 9 12/09/98 1:56p Andsager
86 * Initial checkin of mission loop
88 * 8 11/05/98 5:55p Dave
89 * Big pass at reducing #includes
91 * 7 11/03/98 4:48p Johnson
92 * Fixed campaign file versioning bug left over from Silent Threat port.
94 * 6 10/23/98 3:51p Dave
95 * Full support for tstrings.tbl and foreign languages. All that remains
96 * is to make it active in Fred.
98 * 5 10/13/98 2:47p Andsager
99 * Remove reference to Tech_shivan_species_avail
101 * 4 10/13/98 9:28a Dave
102 * Started neatening up freespace.h. Many variables renamed and
103 * reorganized. Added AlphaColors.[h,cpp]
105 * 3 10/07/98 6:27p Dave
106 * Globalized mission and campaign file extensions. Removed Silent Threat
107 * special code. Moved \cache \players and \multidata into the \data
110 * 2 10/07/98 10:53a Dave
113 * 1 10/07/98 10:49a Dave
115 * 95 9/10/98 1:17p Dave
116 * Put in code to flag missions and campaigns as being MD or not in Fred
117 * and Freespace. Put in multiplayer support for filtering out MD
118 * missions. Put in multiplayer popups for warning of non-valid missions.
120 * 94 9/01/98 4:25p Dave
121 * Put in total (I think) backwards compatibility between mission disk
122 * freespace and non mission disk freespace, including pilot files and
123 * campaign savefiles.
125 * 93 7/06/98 4:10p Hoffoss
126 * Fixed some bugs that presented themselves when trying to use a pilot
127 * that has a no longer existent campaign active. Also expanded the
128 * campaign load code to actually return a proper error code, instead of
129 * always trapping errors internally and crashing, and always returning 0.
131 * 92 6/17/98 9:30a Allender
132 * fixed red alert replay stats clearing problem
134 * 91 6/01/98 11:43a John
135 * JAS & MK: Classified all strings for localization.
137 * 90 5/25/98 1:29p Allender
138 * end mission sequencing
140 * 89 5/21/98 9:25p Allender
141 * endgame movie always viewable at end of campaign
143 * 88 5/13/98 5:14p Allender
144 * red alert support to go back to previous mission
146 * 87 5/12/98 4:16p Hoffoss
147 * Fixed bug where not all missions in all campaigns were being filtered
148 * out of stand alone mission listing in simulator room.
150 * 86 5/05/98 3:29p Hoffoss
151 * Changed code so description is BEFORE num players in campaign file.
152 * Other code is relying on this ordering.
154 * 85 5/05/98 12:19p Dave
155 * campaign description goes *after* num players
157 * 84 5/04/98 5:52p Comet
158 * Fixed bug with Galatea/Bastion selection when finishing missions.
160 * 83 5/01/98 2:46p Duncan
161 * fix a cfile problem with campaigns related to the new cfile stuff
163 * 82 5/01/98 12:34p John
164 * Added code to force FreeSpace to run in the same dir as exe and made
165 * all the parse error messages a little nicer.
167 * 81 4/30/98 7:01p Duncan
168 * AL: don't allow deletion of campaign files in multiplayer
170 * 80 4/30/98 4:53p John
171 * Restructured and cleaned up cfile code. Added capability to read off
172 * of CD-ROM drive and out of multiple pack files.
188 #include "missioncampaign.h"
189 #include "gamesequence.h"
192 #include "missionload.h"
193 #include "freespace.h"
197 #include "missiongoals.h"
198 // #include "movie.h"
200 #include "techmenu.h"
201 #include "eventmusic.h"
202 #include "alphacolors.h"
203 #include "localize.h"
204 #include "supernova.h"
206 // mission disk stuff
207 #define CAMPAIGN_SAVEFILE_MAX_SHIPS_OLD 75
208 #define CAMPAIGN_SAVEFILE_MAX_WEAPONS_OLD 44
210 #define CAMPAIGN_INITIAL_RELEASE_FILE_VERSION 6
212 // campaign wasn't ended
213 int Campaign_ended_in_mission = 0;
215 // stuff for selecting campaigns. We need to keep both arrays around since we display the
216 // list of campaigns by name, but must load campaigns by filename
217 char *Campaign_names[MAX_CAMPAIGNS];
218 char *Campaign_file_names[MAX_CAMPAIGNS];
221 char *campaign_types[MAX_CAMPAIGN_TYPES] =
230 // modules local variables to deal with getting new ships/weapons available to the player
231 int Num_granted_ships, Num_granted_weapons; // per mission counts of new ships and weapons
232 int Granted_ships[MAX_SHIP_TYPES];
233 int Granted_weapons[MAX_WEAPON_TYPES];
235 // variables to control the UI stuff for loading campaigns
236 LOCAL UI_WINDOW Campaign_window;
237 LOCAL UI_LISTBOX Campaign_listbox;
238 LOCAL UI_BUTTON Campaign_okb, Campaign_cancelb;
243 // variables with deal with the campaign save file
244 #define CAMPAIGN_FILE_VERSION 12
245 //#define CAMPAIGN_FILE_COMPATIBLE_VERSION CAMPAIGN_INITIAL_RELEASE_FILE_VERSION
246 #define CAMPAIGN_FILE_COMPATIBLE_VERSION CAMPAIGN_FILE_VERSION
247 #define CAMPAIGN_FILE_ID 0xbeefcafe
249 // variables with deal with the campaign stats save file
250 #define CAMPAIGN_STATS_FILE_VERSION 1
251 #define CAMPAIGN_STATS_FILE_COMPATIBLE_VERSION 1
252 #define CAMPAIGN_STATS_FILE_ID 0xabbadaad
254 // mission_campaign_get_name returns a string (which is malloced in this routine) of the name
255 // of the given freespace campaign file. In the type field, we return if the campaign is a single
256 // player or multiplayer campaign. The type field will only be valid if the name returned is non-NULL
257 int mission_campaign_get_info(char *filename, char *name, int *type, int *max_players, char **desc)
260 char campaign_type[NAME_LENGTH], fname[MAX_FILENAME_LEN];
262 Assert( name != NULL );
263 Assert( type != NULL );
268 strcpy(fname, filename);
269 if ((strlen(fname) < 4) || stricmp(fname + strlen(fname) - 4, FS_CAMPAIGN_FILE_EXT)){
270 strcat(fname, FS_CAMPAIGN_FILE_EXT);
273 Assert(strlen(fname) < MAX_FILENAME_LEN);
275 if ((rval = setjmp(parse_abort)) != 0) {
277 // close localization
283 Error(LOCATION, "Error parsing '%s'\r\nError code = %i.\r\n", fname, rval);
286 read_file_text( fname );
288 required_string("$Name:");
290 stuff_string( name, F_NAME, NULL );
291 if ( name == NULL ) {
293 nprintf(("Warning", "No name found for campaign file %s\n", filename));
295 // close localization
301 required_string( "$Type:" );
302 stuff_string( campaign_type, F_NAME, NULL );
305 for (i=0; i<MAX_CAMPAIGN_TYPES; i++) {
306 if ( !stricmp(campaign_type, campaign_types[i]) ) {
313 if (optional_string("+Description:")) {
314 *desc = stuff_and_malloc_string(F_MULTITEXT, NULL, MISSION_DESC_LENGTH);
318 // if this is a multiplayer campaign, get the max players
320 skip_to_string("+Num Players:");
321 stuff_int(max_players);
324 // if we found a valid campaign type
326 // close localization
333 Int3(); // get Allender -- incorrect type found
335 // close localization
341 // parses campaign and returns a list of missions in it. Returns number of missions added to
342 // the 'list', and up to 'max' missions may be added to 'list'. Returns negative on error.
344 int mission_campaign_get_mission_list(char *filename, char **list, int max)
346 int rval, i, num = 0;
347 char name[NAME_LENGTH];
349 filename = cf_add_ext(filename, FS_CAMPAIGN_FILE_EXT);
351 // read the mission file and get the list of mission filenames
352 if ((rval = setjmp(parse_abort)) != 0) {
353 // since we can't return count of allocated elements, free them instead
354 for (i=0; i<num; i++)
360 read_file_text(filename);
363 while (skip_to_string("$Mission:") > 0) {
364 stuff_string(name, F_NAME, NULL);
366 list[num++] = strdup(name);
373 void mission_campaign_maybe_add( char *filename, int multiplayer )
375 char name[NAME_LENGTH];
376 int type,max_players;
378 if ( mission_campaign_get_info( filename, name, &type, &max_players) ) {
379 if ( !multiplayer && ( type == CAMPAIGN_TYPE_SINGLE) ) {
380 Campaign_names[Num_campaigns] = strdup(name);
381 Campaign_file_names[Num_campaigns] = strdup(filename);
387 // mission_campaign_build_list() builds up the list of campaigns that the user might
388 // be able to pick from. It uses the multiplayer flag to tell if we should display a list
389 // of single or multiplayer campaigns. This routine sets the Num_campaigns and Campaign_names
391 void mission_campaign_build_list( int multiplayer )
401 mission_campaign_maybe_add( BUILTIN_CAMPAIGN, multiplayer);
403 memset(wild_card, 0, 256);
404 strcpy(wild_card, NOX("data\\missions\\*"));
405 strcat(wild_card, FS_CAMPAIGN_FILE_EXT);
406 find_handle = _findfirst( wild_card, &find );
407 if( find_handle != -1 ) {
408 if ( !(find.attrib & _A_SUBDIR) && stricmp(find.name, BUILTIN_CAMPAIGN) ){
409 mission_campaign_maybe_add( find.name, multiplayer);
412 while( !_findnext( find_handle, &find ) ) {
413 if ( !(find.attrib & _A_SUBDIR) && stricmp(find.name, BUILTIN_CAMPAIGN) ) {
414 if ( Num_campaigns >= MAX_CAMPAIGNS ){
415 //MessageBox( -2,-2, 1, "Only the first 300 files will be displayed.", "Ok" );
418 mission_campaign_maybe_add( find.name, multiplayer);
426 // gets optional ship/weapon information
427 void mission_campaign_get_sw_info()
429 int i, count, ship_list[MAX_SHIP_TYPES], weapon_list[MAX_WEAPON_TYPES];
431 // set allowable ships to the SIF_PLAYER_SHIPs
432 memset( Campaign.ships_allowed, 0, sizeof(Campaign.ships_allowed) );
433 for (i = 0; i < MAX_SHIP_TYPES; i++ ) {
434 if ( Ship_info[i].flags & SIF_PLAYER_SHIP )
435 Campaign.ships_allowed[i] = 1;
438 for (i = 0; i < MAX_WEAPON_TYPES; i++ )
439 Campaign.weapons_allowed[i] = 1;
441 if ( optional_string("+Starting Ships:") ) {
442 for (i = 0; i < MAX_SHIP_TYPES; i++ )
443 Campaign.ships_allowed[i] = 0;
445 count = stuff_int_list(ship_list, MAX_SHIP_TYPES, SHIP_INFO_TYPE);
447 // now set the array elements stating which ships we are allowed
448 for (i = 0; i < count; i++ ) {
449 if ( Ship_info[ship_list[i]].flags & SIF_PLAYER_SHIP )
450 Campaign.ships_allowed[ship_list[i]] = 1;
454 if ( optional_string("+Starting Weapons:") ) {
455 for (i = 0; i < MAX_WEAPON_TYPES; i++ )
456 Campaign.weapons_allowed[i] = 0;
458 count = stuff_int_list(weapon_list, MAX_WEAPON_TYPES, WEAPON_POOL_TYPE);
460 // now set the array elements stating which ships we are allowed
461 for (i = 0; i < count; i++ )
462 Campaign.weapons_allowed[weapon_list[i]] = 1;
466 // mission_campaign_load starts a new campaign. It reads in the mission information in the campaign file
467 // It also sets up all variables needed inside of the game to deal with starting mission numbers, etc
469 // Note: Due to difficulties in generalizing this function, parts of it are duplicated throughout
470 // this file. If you change the format of the campaign file, you should be sure these related
471 // functions work properly and update them if it breaks them.
472 int mission_campaign_load( char *filename, int load_savefile )
475 char name[NAME_LENGTH], type[NAME_LENGTH];
477 filename = cf_add_ext(filename, FS_CAMPAIGN_FILE_EXT);
482 // read the mission file and get the list of mission filenames
483 if ((rval = setjmp(parse_abort)) != 0) {
484 mprintf(("Error parsing '%s'\r\nError code = %i.\r\n", filename, rval));
486 // close localization
489 return CAMPAIGN_ERROR_CORRUPT;
492 // be sure to remove all old malloced strings of Mission_names
493 // we must also free any goal stuff that was from a previous campaign
494 // this also frees sexpressions so the next call to init_sexp will be able to reclaim
495 // nodes previously used by another campaign.
496 mission_campaign_close();
498 strcpy( Campaign.filename, filename );
500 // only initialize the sexpression stuff when Fred isn't running. It'll screw things up major
502 if ( !Fred_running ){
503 init_sexp(); // must initialize the sexpression stuff
506 read_file_text( filename );
508 memset( &Campaign, 0, sizeof(Campaign) );
510 // copy filename to campaign structure minus the extension
511 len = strlen(filename) - 4;
512 Assert(len < MAX_FILENAME_LEN);
513 strncpy(Campaign.filename, filename, len);
514 Campaign.filename[len] = 0;
516 required_string("$Name:");
517 stuff_string( name, F_NAME, NULL );
519 //Store campaign name in the global struct
520 strcpy( Campaign.name, name );
522 required_string( "$Type:" );
523 stuff_string( type, F_NAME, NULL );
525 for (i = 0; i < MAX_CAMPAIGN_TYPES; i++ ) {
526 if ( !stricmp(type, campaign_types[i]) ) {
532 if ( i == MAX_CAMPAIGN_TYPES )
533 Error(LOCATION, "Unknown campaign type %s!", type);
535 Campaign.desc = NULL;
536 if (optional_string("+Description:"))
537 Campaign.desc = stuff_and_malloc_string(F_MULTITEXT, NULL, MISSION_DESC_LENGTH);
539 // if the type is multiplayer -- get the number of players
540 Campaign.num_players = 0;
541 if ( Campaign.type != CAMPAIGN_TYPE_SINGLE) {
542 required_string("+Num players:");
543 stuff_int( &(Campaign.num_players) );
546 // parse the optional ship/weapon information
547 mission_campaign_get_sw_info();
549 // parse the mission file and actually read in the mission stuff
550 Campaign.num_missions = 0;
551 while ( required_string_either("#End", "$Mission:") ) {
554 required_string("$Mission:");
555 stuff_string(name, F_NAME, NULL);
556 cm = &Campaign.missions[Campaign.num_missions];
557 cm->name = strdup(name);
559 cm->briefing_cutscene[0] = 0;
560 if ( optional_string("+Briefing Cutscene:") )
561 stuff_string( cm->briefing_cutscene, F_NAME, NULL );
564 if (optional_string("+Flags:"))
565 stuff_int(&cm->flags);
568 if ( optional_string("+Formula:") ) {
569 cm->formula = get_sexp_main();
570 if ( !Fred_running ) {
571 Assert ( cm->formula != -1 );
572 sexp_mark_persistent( cm->formula );
575 if ( cm->formula == -1 ){
576 // close localization
579 return CAMPAIGN_ERROR_SEXP_EXHAUSTED;
584 // Do misison looping stuff
585 cm->has_mission_loop = 0;
586 if ( optional_string("+Mission Loop:") ) {
587 cm->has_mission_loop = 1;
590 cm->mission_loop_desc = NULL;
591 if ( optional_string("+Mission Loop Text:")) {
592 cm->mission_loop_desc = stuff_and_malloc_string(F_MULTITEXT, NULL, MISSION_DESC_LENGTH);
595 cm->mission_loop_brief_anim = NULL;
596 if ( optional_string("+Mission Loop Brief Anim:")) {
597 cm->mission_loop_brief_anim = stuff_and_malloc_string(F_MULTITEXT, NULL, MAX_FILENAME_LEN);
600 cm->mission_loop_brief_sound = NULL;
601 if ( optional_string("+Mission Loop Brief Sound:")) {
602 cm->mission_loop_brief_sound = stuff_and_malloc_string(F_MULTITEXT, NULL, MAX_FILENAME_LEN);
605 cm->mission_loop_formula = -1;
606 if ( optional_string("+Formula:") ) {
607 cm->mission_loop_formula = get_sexp_main();
608 if ( !Fred_running ) {
609 Assert ( cm->mission_loop_formula != -1 );
610 sexp_mark_persistent( cm->mission_loop_formula );
613 if ( cm->mission_loop_formula == -1 ){
614 // close localization
617 return CAMPAIGN_ERROR_SEXP_EXHAUSTED;
622 if (optional_string("+Level:")) {
623 stuff_int( &cm->level );
624 if ( cm->level == 0 ) // check if the top (root) of the whole tree
625 Campaign.next_mission = Campaign.num_missions;
628 Campaign.realign_required = 1;
630 if (optional_string("+Position:"))
631 stuff_int( &cm->pos );
633 Campaign.realign_required = 1;
647 Campaign.num_missions++;
651 // set up the other variables for the campaign stuff. After initializing, we must try and load
652 // the campaign save file for this player. Since all campaign loads go through this routine, I
653 // think this place should be the only necessary place to load the campaign save stuff. The campaign
654 // save file will get written when a mission has ended by player choice.
655 Campaign.next_mission = 0;
656 Campaign.prev_mission = -1;
657 Campaign.current_mission = -1;
659 // loading the campaign will get us to the current and next mission that the player must fly
660 // plus load all of the old goals that future missions might rely on.
661 if (!Fred_running && load_savefile && (Campaign.type == CAMPAIGN_TYPE_SINGLE)) {
662 mission_campaign_savefile_load(Campaign.filename);
665 // close localization
671 // mission_campaign_load_by_name() loads up a freespace campaign given the filename. This routine
672 // is used to load up campaigns when a pilot file is loaded. Generally, the
673 // filename will probably be the freespace campaign file, but not necessarily.
674 int mission_campaign_load_by_name( char *filename )
676 char name[NAME_LENGTH],test[5];
677 int type,max_players;
679 // make sure to tack on .fsc on the end if its not there already
680 if(strlen(filename) > 0){
681 if(strlen(filename) > 4){
682 strcpy(test,filename+(strlen(filename)-4));
683 if(strcmp(test, FS_CAMPAIGN_FILE_EXT)!=0){
684 strcat(filename, FS_CAMPAIGN_FILE_EXT);
687 strcat(filename, FS_CAMPAIGN_FILE_EXT);
690 Error(LOCATION,"Tried to load campaign file with illegal length/extension!");
693 if (!mission_campaign_get_info(filename, name, &type, &max_players)){
698 Campaign_file_names[Num_campaigns] = filename;
699 Campaign_names[Num_campaigns] = name;
701 mission_campaign_load(filename);
705 int mission_campaign_load_by_name_csfe( char *filename, char *callsign )
707 Game_mode |= GM_NORMAL;
708 strcpy(Player->callsign, callsign);
709 return mission_campaign_load_by_name( filename);
713 // mission_campaign_init initializes some variables then loads the default Freespace single player campaign.
714 void mission_campaign_init()
716 memset(&Campaign, 0, sizeof(Campaign) );
719 // Fill in the root of the campaign save filename
720 void mission_campaign_savefile_generate_root(char *filename)
722 char base[_MAX_FNAME];
724 Assert ( strlen(Campaign.filename) != 0 );
726 // build up the filename for the save file. There could be a problem with filename length,
727 // but this problem can get fixed in several ways -- ignore the problem for now though.
728 _splitpath( Campaign.filename, NULL, NULL, base, NULL );
729 Assert ( (strlen(base) + strlen(Player->callsign) + 1) < _MAX_FNAME );
731 sprintf( filename, NOX("%s.%s."), Player->callsign, base );
734 // mission_campaign_savefile_save saves the state of the campaign. This function will probably always be called
735 // then the player is done flying a mission in the campaign path. It will save the missions played, the
736 // state of the goals, etc.
738 int mission_campaign_savefile_save()
740 char filename[_MAX_FNAME];
742 int i,j, mission_count;
744 memset(filename, 0, _MAX_FNAME);
745 mission_campaign_savefile_generate_root(filename);
747 // name the file differently depending on whether we're in single player or multiplayer mode
748 // single player : *.csg
749 strcat( filename, NOX("csg"));
751 fp = cfopen(filename,"wb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
756 // Write out campaign file info
757 cfwrite_int( CAMPAIGN_FILE_ID,fp );
758 cfwrite_int( CAMPAIGN_FILE_VERSION,fp );
760 // put in the file signature (single or multiplayer campaign) - see MissionCampaign.h for the #defines
761 cfwrite_int( CAMPAIGN_SINGLE_PLAYER_SIG, fp );
763 // do we need to write out the filename of the campaign?
764 cfwrite_string_len( Campaign.filename, fp );
765 cfwrite_int( Campaign.prev_mission, fp );
766 cfwrite_int( Campaign.next_mission, fp );
767 cfwrite_int( Campaign.loop_reentry, fp );
768 cfwrite_int( Campaign.loop_enabled, fp );
770 // write out the information for ships/weapons which this player is allowed to use
771 cfwrite_int(Num_ship_types, fp);
772 cfwrite_int(Num_weapon_types, fp);
773 for ( i = 0; i < Num_ship_types; i++ ){
774 cfwrite_char( Campaign.ships_allowed[i], fp );
777 for ( i = 0; i < Num_weapon_types; i++ ){
778 cfwrite_char( Campaign.weapons_allowed[i], fp );
781 // write out the completed mission matrix. Used to tell which missions the player
782 // can replay in the simulator. Also, each completed mission contains a list of the goals
783 // that were in the mission along with the goal completion status.
784 cfwrite_int( Campaign.num_missions_completed, fp );
785 for (i = 0; i < MAX_CAMPAIGN_MISSIONS; i++ ) {
786 if ( Campaign.missions[i].completed ) {
787 cfwrite_int( i, fp );
788 cfwrite_int( Campaign.missions[i].num_goals, fp );
789 for ( j = 0; j < Campaign.missions[i].num_goals; j++ ) {
790 cfwrite_string_len( Campaign.missions[i].goals[j].name, fp );
791 cfwrite_char( Campaign.missions[i].goals[j].status, fp );
793 cfwrite_int( Campaign.missions[i].num_events, fp );
794 for ( j = 0; j < Campaign.missions[i].num_events; j++ ) {
795 cfwrite_string_len( Campaign.missions[i].events[j].name, fp );
796 cfwrite_char( Campaign.missions[i].events[j].status, fp );
800 cfwrite_int(Campaign.missions[i].flags, fp);
807 // ugh! due to horrible bug, the stats saved at the end of every level were not written
808 // out to disk. Write out a seperate file to do this. We will only read it in if we actually
810 memset(filename, 0, _MAX_FNAME);
811 mission_campaign_savefile_generate_root(filename);
813 // name the file differently depending on whether we're in single player or multiplayer mode
814 // single player : *.csg
815 strcat( filename, NOX("css"));
817 fp = cfopen(filename,"wb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
822 // Write out campaign file info
823 cfwrite_int( CAMPAIGN_STATS_FILE_ID,fp );
824 cfwrite_int( CAMPAIGN_STATS_FILE_VERSION,fp );
826 // determine how many missions we are saving -- I think that this method is safer than the method
827 // I used for release
829 for ( i = 0; i < Campaign.num_missions; i++ ) {
830 if ( Campaign.missions[i].completed ) {
835 // write out the stats information to disk.
836 cfwrite_int( mission_count, fp );
837 for (i = 0; i < Campaign.num_missions; i++ ) {
838 if ( Campaign.missions[i].completed ) {
839 cfwrite_int( i, fp );
840 cfwrite( &Campaign.missions[i].stats, sizeof(scoring_struct), 1, fp );
849 // The following function always only ever ever ever called by CSFE!!!!!
850 int campaign_savefile_save(char *pname)
852 if (Campaign.type == CAMPAIGN_TYPE_SINGLE)
853 Game_mode &= ~GM_MULTIPLAYER;
855 Game_mode |= GM_MULTIPLAYER;
857 strcpy(Player->callsign, pname);
858 //memcpy(&Campaign, camp, sizeof(campaign));
859 return mission_campaign_savefile_save();
863 // the below two functions is internal to this module. It is here so that I can group the save/load
864 // functions together.
867 // mission_campaign_savefile_delete deletes any save file in the players directory for the given
869 void mission_campaign_savefile_delete( char *cfilename, int is_multi )
871 char filename[_MAX_FNAME], base[_MAX_FNAME];
873 _splitpath( cfilename, NULL, NULL, base, NULL );
875 if ( Player->flags & PLAYER_FLAGS_IS_MULTI ) {
876 return; // no such thing as a multiplayer campaign savefile
879 sprintf( filename, NOX("%s.%s.csg"), Player->callsign, base );
881 cf_delete( filename, CF_TYPE_SINGLE_PLAYERS );
884 void campaign_delete_save( char *cfn, char *pname)
886 strcpy(Player->callsign, pname);
887 mission_campaign_savefile_delete(cfn);
890 // next function deletes all the save files for this particular pilot. Just call cfile function
891 // which will delete multiple files
892 // Player_select_mode tells us whether we are deleting single or multiplayer files
893 void mission_campaign_delete_all_savefiles( char *pilot_name, int is_multi )
895 int dir_type, num_files, i;
896 char *names[MAX_CAMPAIGNS], spec[MAX_FILENAME_LEN + 2], *ext;
898 int (*filter_save)(char *filename);
901 return; // can't have multiplayer campaign save files
905 dir_type = CF_TYPE_SINGLE_PLAYERS;
907 sprintf(spec, NOX("%s.*%s"), pilot_name, ext);
909 // HACK HACK HACK HACK!!!! cf_get_file_list is not reentrant. Pretty dumb because it should
910 // be. I have to save any file filters
911 filter_save = Get_file_list_filter;
912 Get_file_list_filter = NULL;
913 num_files = cf_get_file_list(MAX_CAMPAIGNS, names, dir_type, spec);
914 Get_file_list_filter = filter_save;
916 for (i=0; i<num_files; i++) {
917 strcpy(filename, names[i]);
918 strcat(filename, ext);
919 cf_delete(filename, dir_type);
924 // mission_campaign_savefile_load takes a filename of a campaign file as a parameter and loads all
925 // of the information stored in the campaign file.
926 void mission_campaign_savefile_load( char *cfilename )
928 char filename[_MAX_FNAME], base[_MAX_FNAME];
929 int id, version, i, num, j, num_stats_blocks;
933 Assert ( strlen(cfilename) != 0 );
935 // probably only called from single player games anymore!!! should be anyway
936 Assert( Game_mode & GM_NORMAL ); // get allender or DaveB. trying to save campaign in multiplayer
938 // build up the filename for the save file. There could be a problem with filename length,
939 // but this problem can get fixed in several ways -- ignore the problem for now though.
940 _splitpath( cfilename, NULL, NULL, base, NULL );
941 Assert ( (strlen(base) + strlen(Player->callsign) + 1) < _MAX_FNAME );
943 if(Game_mode & GM_MULTIPLAYER)
944 sprintf( filename, NOX("%s.%s.msg"), Player->callsign, base );
946 sprintf( filename, NOX("%s.%s.csg"), Player->callsign, base );
948 fp = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS );
952 id = cfread_int( fp );
953 if ( id != CAMPAIGN_FILE_ID ) {
954 Warning(LOCATION, "Campaign save file has invalid signature");
959 version = cfread_int( fp );
960 if ( version < CAMPAIGN_FILE_COMPATIBLE_VERSION ) {
961 Warning(LOCATION, "Campaign save file too old -- not compatible. Deleting file.\nYou can continue from here without trouble\n\n");
963 cf_delete( filename, CF_TYPE_SINGLE_PLAYERS );
967 // verify that we are loading the correct type of campaign file for the mode that we are in.
969 type_sig = cfread_int( fp );
971 type_sig = CAMPAIGN_SINGLE_PLAYER_SIG;
973 Assert( ((Game_mode & GM_MULTIPLAYER) && (type_sig==CAMPAIGN_MULTI_PLAYER_SIG)) || (!(Game_mode & GM_MULTIPLAYER) && (type_sig==CAMPAIGN_SINGLE_PLAYER_SIG)) );
975 Campaign.type = type_sig == CAMPAIGN_SINGLE_PLAYER_SIG ? CAMPAIGN_TYPE_SINGLE : CAMPAIGN_TYPE_MULTI_COOP;
977 // read in the filename of the campaign and compare the filenames to be sure that
978 // we are reading data that really belongs to this campaign. I think that this check
980 cfread_string_len( filename, _MAX_FNAME, fp );
981 /*if ( stricmp( filename, cfilename) ) { // Used to be !stricmp. How did this ever work? --MK, 11/9/97
982 Warning(LOCATION, "Campaign save file appears corrupt because of mismatching filenames.");
987 Campaign.prev_mission = cfread_int( fp );
988 Campaign.next_mission = cfread_int( fp );
989 Campaign.loop_reentry = cfread_int( fp );
990 Campaign.loop_enabled = cfread_int( fp );
992 // load information about ships/weapons allowed
993 int ship_count, weapon_count;
995 // if earlier than mission disk version, use old MAX_SHIP_TYPES, otherwise read from the file
996 if(version <= CAMPAIGN_INITIAL_RELEASE_FILE_VERSION){
997 ship_count = CAMPAIGN_SAVEFILE_MAX_SHIPS_OLD;
998 weapon_count = CAMPAIGN_SAVEFILE_MAX_WEAPONS_OLD;
1000 ship_count = cfread_int(fp);
1001 weapon_count = cfread_int(fp);
1004 for ( i = 0; i < ship_count; i++ ){
1005 Campaign.ships_allowed[i] = cfread_ubyte( fp );
1008 for ( i = 0; i < weapon_count; i++ ){
1009 Campaign.weapons_allowed[i] = cfread_ubyte( fp );
1012 // read in the completed mission matrix. Used to tell which missions the player
1013 // can replay in the simulator. Also, each completed mission contains a list of the goals
1014 // that were in the mission along with the goal completion status.
1015 Campaign.num_missions_completed = cfread_int( fp );
1016 for (i = 0; i < Campaign.num_missions_completed; i++ ) {
1017 num = cfread_int( fp );
1018 Campaign.missions[num].completed = 1;
1019 Campaign.missions[num].num_goals = cfread_int( fp );
1021 // be sure to malloc out space for the goals stuff, then zero the memory!!! Don't do malloc
1022 // if there are no goals
1023 Campaign.missions[num].goals = (mgoal *)malloc( Campaign.missions[num].num_goals * sizeof(mgoal) );
1024 if ( Campaign.missions[num].num_goals > 0 ) {
1025 memset( Campaign.missions[num].goals, 0, sizeof(mgoal) * Campaign.missions[num].num_goals );
1026 Assert( Campaign.missions[num].goals != NULL );
1029 // now read in the goal information for this mission
1030 for ( j = 0; j < Campaign.missions[num].num_goals; j++ ) {
1031 cfread_string_len( Campaign.missions[num].goals[j].name, NAME_LENGTH, fp );
1032 Campaign.missions[num].goals[j].status = cfread_char( fp );
1035 // get the events from the savefile
1036 Campaign.missions[num].num_events = cfread_int( fp );
1038 // be sure to malloc out space for the events stuff, then zero the memory!!! Don't do malloc
1039 // if there are no events
1040 // if (Campaign.missions[num].events < 0)
1041 // Campaign.missions[num].events = 0;
1042 Campaign.missions[num].events = (mevent *)malloc( Campaign.missions[num].num_events * sizeof(mevent) );
1043 if ( Campaign.missions[num].num_events > 0 ) {
1044 memset( Campaign.missions[num].events, 0, sizeof(mevent) * Campaign.missions[num].num_events );
1045 Assert( Campaign.missions[num].events != NULL );
1048 // now read in the event information for this mission
1049 for ( j = 0; j < Campaign.missions[num].num_events; j++ ) {
1050 cfread_string_len( Campaign.missions[num].events[j].name, NAME_LENGTH, fp );
1051 Campaign.missions[num].events[j].status = cfread_char( fp );
1055 Campaign.missions[num].flags = cfread_int(fp);
1061 // now, try and read in the campaign stats saved information. This code was added for the 1.03 patch
1062 // since the stats data was never written out to disk. We try and open the file, and if we cannot find
1063 // it, then simply return
1064 sprintf( filename, NOX("%s.%s.css"), Player->callsign, base );
1066 fp = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS );
1070 id = cfread_int( fp );
1071 if ( id != CAMPAIGN_STATS_FILE_ID ) {
1072 Warning(LOCATION, "Campaign stats save file has invalid signature");
1077 version = cfread_int( fp );
1078 if ( version < CAMPAIGN_STATS_FILE_COMPATIBLE_VERSION ) {
1079 Warning(LOCATION, "Campaign save file too old -- not compatible. Deleting file.\nYou can continue from here without trouble\n\n");
1081 cf_delete( filename, CF_TYPE_SINGLE_PLAYERS );
1085 num_stats_blocks = cfread_int( fp );
1086 for (i = 0; i < num_stats_blocks; i++ ) {
1087 num = cfread_int( fp );
1088 cfread( &Campaign.missions[num].stats, sizeof(scoring_struct), 1, fp );
1094 // the following code only ever called by CSFE!!!!
1095 void campaign_savefile_load(char *fname, char *pname)
1097 if (Campaign.type==CAMPAIGN_TYPE_SINGLE) {
1098 Game_mode &= ~GM_MULTIPLAYER;
1099 Game_mode &= GM_NORMAL;
1102 Game_mode |= GM_MULTIPLAYER;
1103 strcpy(Player->callsign, pname);
1104 mission_campaign_savefile_load(fname);
1107 // mission_campaign_next_mission sets up the internal veriables of the campaign
1108 // structure so the player can play the next mission. If there are no more missions
1109 // available in the campaign, this function returns -1, else 0 if the mission was
1111 int mission_campaign_next_mission()
1113 if ( (Campaign.next_mission == -1) || (strlen(Campaign.name) == 0) ) // will be set to -1 when there is no next mission
1116 Campaign.current_mission = Campaign.next_mission;
1117 strncpy( Game_current_mission_filename, Campaign.missions[Campaign.current_mission].name, MAX_FILENAME_LEN );
1119 // check for end of loop.
1120 if (Campaign.current_mission == Campaign.loop_reentry) {
1121 Campaign.loop_enabled = 0;
1124 // reset the number of persistent ships and weapons for the next campaign mission
1125 Num_granted_ships = 0;
1126 Num_granted_weapons = 0;
1130 // mission_campaign_previous_mission() gets called to go to the previous mission in
1131 // the campaign. Used only for Red Alert missions
1132 int mission_campaign_previous_mission()
1134 if ( !(Game_mode & GM_CAMPAIGN_MODE) )
1137 if ( Campaign.prev_mission == -1 )
1140 Campaign.current_mission = Campaign.prev_mission;
1141 Campaign.next_mission = Campaign.current_mission;
1142 Campaign.num_missions_completed--;
1143 Campaign.missions[Campaign.next_mission].completed = 0;
1144 mission_campaign_savefile_save();
1146 // reset the player stats to be the stats from this level
1147 memcpy( &Player->stats, &Campaign.missions[Campaign.current_mission].stats, sizeof(Player->stats) );
1149 strncpy( Game_current_mission_filename, Campaign.missions[Campaign.current_mission].name, MAX_FILENAME_LEN );
1150 Num_granted_ships = 0;
1151 Num_granted_weapons = 0;
1157 // determine what the next mission is after the current one. Because this evaluates an sexp,
1158 // and that could check just about anything, the results are only going to be valid in
1160 // DA 12/09/98 -- To allow for mission loops, need to maintain call with store stats
1161 int mission_campaign_eval_next_mission( int store_stats )
1167 Campaign.next_mission = -1;
1168 cur = Campaign.current_mission;
1169 name = Campaign.missions[cur].name;
1171 mission = &Campaign.missions[cur];
1173 // first we must save the status of the current missions goals in the campaign mission structure.
1174 // After that, we can determine which mission is tagged as the next mission. Finally, we
1175 // can save the campaign save file
1176 // we might have goal and event status if the player replayed a mission
1177 if ( mission->num_goals > 0 ) {
1178 free( mission->goals );
1181 mission->num_goals = Num_goals;
1182 if ( mission->num_goals > 0 ) {
1183 mission->goals = (mgoal *)malloc( sizeof(mgoal) * Num_goals );
1184 Assert( mission->goals != NULL );
1187 // copy the needed info from the Mission_goal struct to our internal structure
1188 for (i = 0; i < Num_goals; i++ ) {
1189 if ( strlen(Mission_goals[i].name) == 0 ) {
1190 char name[NAME_LENGTH];
1192 sprintf(name, NOX("Goal #%d"), i);
1193 //Warning(LOCATION, "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name);
1194 strcpy( mission->goals[i].name, name);
1196 strcpy( mission->goals[i].name, Mission_goals[i].name );
1197 Assert ( Mission_goals[i].satisfied != GOAL_INCOMPLETE ); // should be true or false at this point!!!
1198 mission->goals[i].status = (char)Mission_goals[i].satisfied;
1201 // do the same thing for events as we did for goals
1202 // we might have goal and event status if the player replayed a mission
1203 if ( mission->num_events > 0 ) {
1204 free( mission->events );
1207 mission->num_events = Num_mission_events;
1208 if ( mission->num_events > 0 ) {
1209 mission->events = (mevent *)malloc( sizeof(mevent) * Num_mission_events );
1210 Assert( mission->events != NULL );
1213 // copy the needed info from the Mission_goal struct to our internal structure
1214 for (i = 0; i < Num_mission_events; i++ ) {
1215 if ( strlen(Mission_events[i].name) == 0 ) {
1216 char name[NAME_LENGTH];
1218 sprintf(name, NOX("Event #%d"), i);
1219 nprintf(("Warning", "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name));
1220 strcpy( mission->events[i].name, name);
1222 strcpy( mission->events[i].name, Mission_events[i].name );
1224 // getting status for the events is a little different. If the formula value for the event entry
1225 // is -1, then we know the value of the result field will never change. If the formula is
1226 // not -1 (i.e. still being evaluated at mission end time), we will write "incomplete" for the
1228 if ( Mission_events[i].formula == -1 ) {
1229 if ( Mission_events[i].result )
1230 mission->events[i].status = EVENT_SATISFIED;
1232 mission->events[i].status = EVENT_FAILED;
1237 // maybe store the alltime stats which would be current at the end of this mission
1238 if ( store_stats ) {
1239 memcpy( &mission->stats, &Player->stats, sizeof(Player->stats) );
1240 scoring_backout_accept( &mission->stats );
1243 if ( store_stats ) { // second (last) time through, so use choose loop_mission if chosen
1244 if ( Campaign.loop_enabled ) {
1245 Campaign.next_mission = Campaign.loop_mission;
1247 // evaluate next mission (straight path)
1248 if (Campaign.missions[cur].formula != -1) {
1249 flush_sexp_tree(Campaign.missions[cur].formula); // force formula to be re-evaluated
1250 eval_sexp(Campaign.missions[cur].formula); // this should reset Campaign.next_mission to proper value
1255 // evaluate next mission (straight path)
1256 if (Campaign.missions[cur].formula != -1) {
1257 flush_sexp_tree(Campaign.missions[cur].formula); // force formula to be re-evaluated
1258 eval_sexp(Campaign.missions[cur].formula); // this should reset Campaign.next_mission to proper value
1261 // evaluate mission loop mission (if any) so it can be used if chosen
1262 if ( Campaign.missions[cur].has_mission_loop ) {
1263 int copy_next_mission = Campaign.next_mission;
1264 // Set temporarily to -1 so we know if loop formula fails to assign
1265 Campaign.next_mission = -1; // Cannot exit campaign from loop
1266 if (Campaign.missions[cur].mission_loop_formula != -1) {
1267 flush_sexp_tree(Campaign.missions[cur].mission_loop_formula); // force formula to be re-evaluated
1268 eval_sexp(Campaign.missions[cur].mission_loop_formula); // this should reset Campaign.next_mission to proper value
1271 Campaign.loop_mission = Campaign.next_mission;
1272 Campaign.next_mission = copy_next_mission;
1276 if (Campaign.next_mission == -1)
1277 nprintf(("allender", "No next mission to proceed to.\n"));
1279 nprintf(("allender", "Next mission is number %d [%s]\n", Campaign.next_mission, Campaign.missions[Campaign.next_mission].name));
1281 return Campaign.next_mission;
1284 // Evaluate next campaign mission - set as Campaign.next_mission. Also set Campaign.loop_mission
1285 void mission_campaign_eval_next_mission()
1287 Campaign.next_mission = -1;
1288 int cur = Campaign.current_mission;
1290 // evaluate next mission (straight path)
1291 if (Campaign.missions[cur].formula != -1) {
1292 flush_sexp_tree(Campaign.missions[cur].formula); // force formula to be re-evaluated
1293 eval_sexp(Campaign.missions[cur].formula); // this should reset Campaign.next_mission to proper value
1296 // evaluate mission loop mission (if any) so it can be used if chosen
1297 if ( Campaign.missions[cur].has_mission_loop ) {
1298 int copy_next_mission = Campaign.next_mission;
1299 // Set temporarily to -1 so we know if loop formula fails to assign
1300 Campaign.next_mission = -1;
1301 if (Campaign.missions[cur].mission_loop_formula != -1) {
1302 flush_sexp_tree(Campaign.missions[cur].mission_loop_formula); // force formula to be re-evaluated
1303 eval_sexp(Campaign.missions[cur].mission_loop_formula); // this should reset Campaign.next_mission to proper value
1306 Campaign.loop_mission = Campaign.next_mission;
1307 Campaign.next_mission = copy_next_mission;
1310 if (Campaign.next_mission == -1) {
1311 nprintf(("allender", "No next mission to proceed to.\n"));
1313 nprintf(("allender", "Next mission is number %d [%s]\n", Campaign.next_mission, Campaign.missions[Campaign.next_mission].name));
1318 // Store mission's goals and events in Campaign struct
1319 void mission_campaign_store_goals_and_events()
1325 cur = Campaign.current_mission;
1326 name = Campaign.missions[cur].name;
1328 mission = &Campaign.missions[cur];
1330 // first we must save the status of the current missions goals in the campaign mission structure.
1331 // After that, we can determine which mission is tagged as the next mission. Finally, we
1332 // can save the campaign save file
1333 // we might have goal and event status if the player replayed a mission
1334 if ( mission->num_goals > 0 ) {
1335 free( mission->goals );
1338 mission->num_goals = Num_goals;
1339 if ( mission->num_goals > 0 ) {
1340 mission->goals = (mgoal *)malloc( sizeof(mgoal) * Num_goals );
1341 Assert( mission->goals != NULL );
1344 // copy the needed info from the Mission_goal struct to our internal structure
1345 for (i = 0; i < Num_goals; i++ ) {
1346 if ( strlen(Mission_goals[i].name) == 0 ) {
1347 char name[NAME_LENGTH];
1349 sprintf(name, NOX("Goal #%d"), i);
1350 //Warning(LOCATION, "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name);
1351 strcpy( mission->goals[i].name, name);
1353 strcpy( mission->goals[i].name, Mission_goals[i].name );
1354 Assert ( Mission_goals[i].satisfied != GOAL_INCOMPLETE ); // should be true or false at this point!!!
1355 mission->goals[i].status = (char)Mission_goals[i].satisfied;
1358 // do the same thing for events as we did for goals
1359 // we might have goal and event status if the player replayed a mission
1360 if ( mission->num_events > 0 ) {
1361 free( mission->events );
1364 mission->num_events = Num_mission_events;
1365 if ( mission->num_events > 0 ) {
1366 mission->events = (mevent *)malloc( sizeof(mevent) * Num_mission_events );
1367 Assert( mission->events != NULL );
1370 // copy the needed info from the Mission_goal struct to our internal structure
1371 for (i = 0; i < Num_mission_events; i++ ) {
1372 if ( strlen(Mission_events[i].name) == 0 ) {
1373 char name[NAME_LENGTH];
1375 sprintf(name, NOX("Event #%d"), i);
1376 nprintf(("Warning", "Mission goal in mission %s must have a +Name field! using %s for campaign save file\n", mission->name, name));
1377 strcpy( mission->events[i].name, name);
1379 strcpy( mission->events[i].name, Mission_events[i].name );
1381 // getting status for the events is a little different. If the formula value for the event entry
1382 // is -1, then we know the value of the result field will never change. If the formula is
1383 // not -1 (i.e. still being evaluated at mission end time), we will write "incomplete" for the
1385 if ( Mission_events[i].formula == -1 ) {
1386 if ( Mission_events[i].result )
1387 mission->events[i].status = EVENT_SATISFIED;
1389 mission->events[i].status = EVENT_FAILED;
1395 // this function is called when the player's mission is over. It updates the internal store of goals
1396 // and their status then saves the state of the campaign in the campaign file. This gets called
1397 // after player accepts mission results in debriefing.
1398 void mission_campaign_mission_over()
1403 // I don't think that we should have a record for these -- maybe we might?????? If we do,
1404 // then we should free them
1405 if ( !(Game_mode & GM_CAMPAIGN_MODE) ){
1409 mission_num = Campaign.current_mission;
1410 Assert( mission_num != -1 );
1411 mission = &Campaign.missions[mission_num];
1413 // determine if any ships/weapons were granted this mission
1414 for ( i=0; i<Num_granted_ships; i++ ){
1415 Campaign.ships_allowed[Granted_ships[i]] = 1;
1418 for ( i=0; i<Num_granted_weapons; i++ ){
1419 Campaign.weapons_allowed[Granted_weapons[i]] = 1;
1422 // DKA 12/11/98 - Unneeded already evaluated and stored
1423 // determine what new mission we are moving to.
1424 // mission_campaign_eval_next_mission(1);
1426 // update campaign.mission stats (used to allow backout inRedAlert)
1427 memcpy( &mission->stats, &Player->stats, sizeof(Player->stats) );
1428 if(!(Game_mode & GM_MULTIPLAYER)){
1429 scoring_backout_accept( &mission->stats );
1432 // if we are moving to a new mission, then change our data. If we are staying on the same mission,
1433 // then we don't want to do anything. Remove information about goals/events
1434 if ( Campaign.next_mission != mission_num ) {
1435 Campaign.prev_mission = mission_num;
1436 Campaign.current_mission = -1;
1437 Campaign.num_missions_completed++;
1438 Campaign.missions[mission_num].completed = 1;
1440 // save the scoring values from the previous mission at the start of this mission -- for red alert
1442 // save the state of the campaign in the campaign save file and move to the end_game state
1443 if (Campaign.type == CAMPAIGN_TYPE_SINGLE) {
1444 mission_campaign_savefile_save();
1448 // free up the goals and events which were just malloced. It's kind of like erasing any fact
1449 // that the player played this mission in the campaign.
1450 free( mission->goals );
1451 mission->num_goals = 0;
1453 free( mission->events );
1454 mission->num_events = 0;
1456 Sexp_nodes[mission->formula].value = SEXP_UNKNOWN;
1460 if (Campaign.missions[Campaign.next_mission].flags & CMISSION_FLAG_BASTION){
1461 Player->on_bastion = 1;
1463 Player->on_bastion = 0;
1466 mission_campaign_next_mission(); // sets up whatever needs to be set to actually play next mission
1469 // called when the game closes -- to get rid of memory errors for Bounds checker
1470 void mission_campaign_close()
1475 free(Campaign.desc);
1477 // be sure to remove all old malloced strings of Mission_names
1478 // we must also free any goal stuff that was from a previous campaign
1479 for ( i=0; i<Campaign.num_missions; i++ ) {
1480 if ( Campaign.missions[i].name ){
1481 free(Campaign.missions[i].name);
1484 if (Campaign.missions[i].notes){
1485 free(Campaign.missions[i].notes);
1488 if ( Campaign.missions[i].num_goals > 0 ){
1489 free ( Campaign.missions[i].goals );
1492 if ( Campaign.missions[i].num_events > 0 ){
1493 free ( Campaign.missions[i].events );
1496 if ( !Fred_running ){
1497 sexp_unmark_persistent(Campaign.missions[i].formula); // free any sexpression nodes used by campaign.
1500 Campaign.missions[i].num_goals = 0;
1501 Campaign.missions[i].num_events = 0;
1505 // extract the mission filenames for a campaign.
1507 // filename => name of campaign file
1508 // dest => storage for the mission filename, must be already allocated
1509 // num => output parameter for the number of mission filenames in the campaign
1511 // note that dest should allocate at least dest[MAX_CAMPAIGN_MISSIONS][NAME_LENGTH]
1512 int mission_campaign_get_filenames(char *filename, char dest[][NAME_LENGTH], int *num)
1516 // read the mission file and get the list of mission filenames
1517 if ((rval = setjmp(parse_abort)) != 0) {
1521 read_file_text(filename);
1522 Assert(strlen(filename) < MAX_FILENAME_LEN - 1); // make sure no overflow
1525 required_string("$Name:");
1526 advance_to_eoln(NULL);
1528 required_string( "$Type:" );
1529 advance_to_eoln(NULL);
1531 // parse the mission file and actually read in the mission stuff
1533 while ( skip_to_string("$Mission:") == 1 ) {
1534 stuff_string(dest[*num], F_NAME, NULL);
1542 // function to read the goals and events from a mission in a campaign file and store that information
1543 // in the campaign structure for use in the campaign editor, error checking, etc
1544 void read_mission_goal_list(int num)
1546 char *filename, notes[NOTES_LENGTH], goals[MAX_GOALS][NAME_LENGTH];
1547 char events[MAX_MISSION_EVENTS][NAME_LENGTH];
1548 int i, z, r, event_count, count = 0;
1550 filename = Campaign.missions[num].name;
1551 if ((r = setjmp(parse_abort))>0) {
1552 Warning(LOCATION, "Error reading \"%s\" (code = %d)", filename, r);
1556 // open localization
1559 read_file_text(filename);
1562 // first, read the mission notes for this mission. Used in campaign editor
1563 if (skip_to_string("#Mission Info")) {
1564 if (skip_to_string("$Notes:")) {
1565 stuff_string(notes, F_NOTES, NULL);
1566 if (Campaign.missions[num].notes){
1567 free(Campaign.missions[num].notes);
1570 Campaign.missions[num].notes = (char *) malloc(strlen(notes) + 1);
1571 strcpy(Campaign.missions[num].notes, notes);
1576 // skip to events section in the mission file. Events come before goals, so we process them first
1577 if ( skip_to_string("#Events") ) {
1579 if (skip_to_string("$Formula:", "#Goals") != 1){
1583 z = skip_to_string("+Name:", "$Formula:");
1589 stuff_string(events[event_count], F_NAME, NULL);
1591 sprintf(events[event_count], NOX("Event #%d"), event_count + 1);
1595 Assert(event_count < MAX_MISSION_EVENTS);
1600 if (skip_to_string("#Goals")) {
1602 if (skip_to_string("$Type:", "#End") != 1){
1606 z = skip_to_string("+Name:", "$Type:");
1612 stuff_string(goals[count], F_NAME, NULL);
1614 sprintf(goals[count], NOX("Goal #%d"), count + 1);
1618 Assert(count < MAX_GOALS);
1622 Campaign.missions[num].num_goals = count;
1624 Campaign.missions[num].goals = (mgoal *) malloc(count * sizeof(mgoal));
1625 Assert(Campaign.missions[num].goals); // make sure we got the memory
1626 memset(Campaign.missions[num].goals, 0, count * sizeof(mgoal));
1628 for (i=0; i<count; i++){
1629 strcpy(Campaign.missions[num].goals[i].name, goals[i]);
1633 Campaign.missions[num].num_events = event_count;
1635 Campaign.missions[num].events = (mevent *)malloc(event_count * sizeof(mevent));
1636 Assert ( Campaign.missions[num].events );
1637 memset(Campaign.missions[num].events, 0, event_count * sizeof(mevent));
1639 for (i = 0; i < event_count; i++ ){
1640 strcpy(Campaign.missions[num].events[i].name, events[i]);
1644 // close localization
1648 // function to return index into Campaign's list of missions of the mission with the given
1649 // filename. This function tried to be a little smart about filename looking for the .fsm
1650 // extension since filenames are stored with the extension in the campaign file. Returns
1651 // index of mission in campaign structure. -1 if mission name not found.
1652 int mission_campaign_find_mission( char *name )
1655 char realname[_MAX_PATH];
1657 // look for an extension on the file. If no extension, add default ".fsm" onto the
1658 // end of the filename
1659 strcpy(realname, name );
1660 if ( strchr(name, '.') == NULL ){
1661 sprintf(realname, NOX("%s%s"), name, FS_MISSION_FILE_EXT );
1664 for (i = 0; i < Campaign.num_missions; i++ ) {
1665 if ( !stricmp(realname, Campaign.missions[i].name) ){
1673 void mission_campaign_maybe_play_movie(int type)
1678 // only support pre mission movies for now.
1679 Assert ( type == CAMPAIGN_MOVIE_PRE_MISSION );
1681 if ( !(Game_mode & GM_CAMPAIGN_MODE) )
1684 mission = Campaign.current_mission;
1685 Assert( mission != -1 );
1687 // get a possible filename for a movie to play.
1690 case CAMPAIGN_MOVIE_PRE_MISSION:
1691 if ( strlen(Campaign.missions[mission].briefing_cutscene) )
1692 filename = Campaign.missions[mission].briefing_cutscene;
1700 // no filename, no movie!
1705 // movie_play( filename );
1708 // return nonzero if the passed filename is a multiplayer campaign, 0 otherwise
1709 int mission_campaign_parse_is_multi(char *filename, char *name)
1714 read_file_text( filename );
1717 required_string("$Name:");
1718 stuff_string( temp, F_NAME, NULL );
1720 strcpy( name, temp );
1722 required_string( "$Type:" );
1723 stuff_string( temp, F_NAME, NULL );
1725 for (i = 0; i < MAX_CAMPAIGN_TYPES; i++ ) {
1726 if ( !stricmp(temp, campaign_types[i]) ) {
1731 Error(LOCATION, "Unknown campaign type %s", temp );
1735 // functions to save persistent information during a mission -- which then might or might not get
1736 // saved out when the mission is over depending on whether player replays mission or commits.
1737 void mission_campaign_save_persistent( int type, int sindex )
1739 // based on the type of information, save it off for possible saving into the campsign
1740 // savefile when the mission is over
1741 if ( type == CAMPAIGN_PERSISTENT_SHIP ) {
1742 Assert( Num_granted_ships < MAX_SHIP_TYPES );
1743 Granted_ships[Num_granted_ships] = sindex;
1744 Num_granted_ships++;
1745 } else if ( type == CAMPAIGN_PERSISTENT_WEAPON ) {
1746 Assert( Num_granted_weapons < MAX_WEAPON_TYPES );
1747 Granted_weapons[Num_granted_weapons] = sindex;
1748 Num_granted_weapons++;
1753 // returns 0: loaded, !0: error
1754 int mission_load_up_campaign()
1756 if (strlen(Player->current_campaign))
1757 return mission_campaign_load(Player->current_campaign);
1759 return mission_campaign_load(BUILTIN_CAMPAIGN);
1762 // for end of campaign in the single player game. Called when the end of campaign state is
1763 // entered, which is triggered when the end-campaign sexpression is hit
1765 void mission_campaign_end_init()
1767 // no need to do any initialization.
1770 void mission_campaign_end_do()
1773 event_music_level_close();
1774 mission_goal_fail_incomplete();
1775 scoring_level_close();
1776 mission_campaign_mission_over();
1778 // eventually we'll want to play one of two options (good ending or bad ending)
1779 // did the supernova blow?
1780 if(Supernova_status == SUPERNOVA_HIT){
1782 // movie_play_two("endpart1.mve", "endprt2b.mve"); // good ending
1785 // movie_play_two("endpart1.mve", "endprt2a.mve"); // good ending
1789 gameseq_post_event( GS_EVENT_END_DEMO );
1791 gameseq_post_event( GS_EVENT_MAIN_MENU );
1795 void mission_campaign_end_close()
1797 // nothing to do here.
1801 // skip to the next mission in the campaign
1802 // this also posts the state change by default. pass 0 to override that
1803 void mission_campaign_skip_to_next(int start_game)
1805 // mark all goals/events complete
1806 // these do not really matter, since is-previous-event-* and is-previous-goal-* sexps check
1807 // to see if the mission was skipped, and use defaults accordingly.
1808 mission_goal_mark_objectives_complete();
1809 mission_goal_mark_events_complete();
1811 // mark mission as skipped
1812 Campaign.missions[Campaign.current_mission].flags |= CMISSION_FLAG_SKIPPED;
1815 mission_campaign_store_goals_and_events();
1817 // now set the next mission
1818 mission_campaign_eval_next_mission();
1820 // clear out relevant player vars
1821 Player->failures_this_session = 0;
1822 Player->show_skip_popup = 1;
1825 // proceed to next mission or main hall
1826 if ((Campaign.missions[Campaign.current_mission].has_mission_loop) && (Campaign.loop_mission != -1)) {
1827 // go to loop solicitation
1828 gameseq_post_event(GS_EVENT_LOOP_BRIEF);
1830 // closes out mission stuff, sets up next one
1831 mission_campaign_mission_over();
1833 if ( Campaign.next_mission == -1 ) {
1834 // go to main hall, tha campaign is over!
1835 gameseq_post_event(GS_EVENT_MAIN_MENU);
1837 // go to next mission
1838 gameseq_post_event(GS_EVENT_START_GAME);
1845 // breaks your ass out of the loop
1846 // this also posts the state change
1847 void mission_campaign_exit_loop()
1849 // set campaign to loop reentry point
1850 Campaign.next_mission = Campaign.loop_reentry;
1851 Campaign.current_mission = -1;
1852 Campaign.loop_enabled = 0;
1854 // set things up for next mission
1855 mission_campaign_next_mission();
1856 gameseq_post_event(GS_EVENT_START_GAME);
1860 // used for jumping to a particular campaign mission
1861 // all pvs missions marked skipped
1862 // this relies on correct mission ordering in the campaign file
1863 void mission_campaign_jump_to_mission(char *name)
1868 // load in the campaign junk
1869 mission_load_up_campaign();
1871 // tack the .fs2 onto the input name
1872 strcpy(dest_name, name);
1873 strcat(name, ".fs2");
1875 // search for our mission
1876 for (i=0; i<Campaign.num_missions; i++) {
1877 if ((Campaign.missions[i].name != NULL) && !stricmp(Campaign.missions[i].name, name) ) {
1878 Campaign.next_mission = i;
1879 Campaign.prev_mission = i-1;
1880 mission_campaign_next_mission();
1881 Game_mode |= GM_CAMPAIGN_MODE;
1882 gameseq_post_event(GS_EVENT_START_GAME);
1885 Campaign.missions[i].flags |= CMISSION_FLAG_SKIPPED;
1886 Campaign.num_missions_completed = i;
1890 // if we got here, no match was found
1891 // restart the campaign
1892 mission_campaign_savefile_delete(Campaign.filename);
1893 mission_campaign_load(Campaign.filename);