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 the
6 * source.
7 *
8 */
9
10
11
12 #include "stdafx.h"
13
14 #include "CampaignEditorDlg.h"
15 #include "CampaignTreeView.h"
16 #include "CampaignTreeWnd.h"
17 #include "FredRender.h"
18 #include "FredView.h"
19 #include "MainFrm.h"
20 #include "Management.h"
21 #include "MissionSave.h"
22
23 #include "ai/aigoals.h"
24 #include "asteroid/asteroid.h"
25 #include "cfile/cfile.h"
26 #include "gamesnd/eventmusic.h"
27 #include "globalincs/linklist.h"
28 #include "globalincs/version.h"
29 #include "iff_defs/iff_defs.h"
30 #include "jumpnode/jumpnode.h"
31 #include "lighting/lighting.h"
32 #include "localization/fhash.h"
33 #include "localization/localize.h"
34 #include "math/vecmat.h"
35 #include "mission/missionbriefcommon.h"
36 #include "mission/missioncampaign.h"
37 #include "mission/missiongoals.h"
38 #include "mission/missionmessage.h"
39 #include "mission/missionparse.h"
40 #include "missionui/fictionviewer.h"
41 #include "missionui/missioncmdbrief.h"
42 #include "nebula/neb.h"
43 #include "object/objectdock.h"
44 #include "object/objectshield.h"
45 #include "osapi/osapi.h"
46 #include "parse/sexp.h"
47 #include "parse/sexp_container.h"
48 #include "sound/ds.h"
49 #include "sound/sound.h"
50 #include "starfield/nebula.h"
51 #include "starfield/starfield.h"
52 #include "weapon/weapon.h"
53
54 #include <freespace.h>
55
autosave_mission_file(char * pathname)56 int CFred_mission_save::autosave_mission_file(char *pathname)
57 {
58 char backup_name[256], name2[256];
59 int i;
60
61 auto len = strlen(pathname);
62 strcpy_s(backup_name, pathname);
63 strcpy_s(name2, pathname);
64 sprintf(backup_name + len, ".%.3d", BACKUP_DEPTH);
65 cf_delete(backup_name, CF_TYPE_MISSIONS);
66 for (i = BACKUP_DEPTH; i > 1; i--) {
67 sprintf(backup_name + len, ".%.3d", i - 1);
68 sprintf(name2 + len, ".%.3d", i);
69 cf_rename(backup_name, name2, CF_TYPE_MISSIONS);
70 }
71
72 strcpy(backup_name + len, ".001");
73
74 save_mission_internal(backup_name);
75
76 return err;
77 }
78
bypass_comment(const char * comment,const char * end)79 void CFred_mission_save::bypass_comment(const char *comment, const char *end)
80 {
81 char *ch = strstr(raw_ptr, comment);
82 if (ch != NULL) {
83 if (end != NULL) {
84 char *ep = strstr(raw_ptr, end);
85 if (ep != NULL && ep < ch)
86 return;
87 }
88 char *writep = ch;
89 char *readp = strchr(writep, '\n');
90
91 // copy all characters past it
92 while (*readp != '\0') {
93 *writep = *readp;
94
95 writep++;
96 readp++;
97 }
98
99 *writep = '\0';
100 }
101 }
102
convert_special_tags_to_retail(char * text,int max_len)103 void CFred_mission_save::convert_special_tags_to_retail(char *text, int max_len)
104 {
105 replace_all(text, "$quote", "''", max_len);
106 replace_all(text, "$semicolon", ",", max_len);
107 }
108
convert_special_tags_to_retail(SCP_string & text)109 void CFred_mission_save::convert_special_tags_to_retail(SCP_string &text)
110 {
111 replace_all(text, "$quote", "''");
112 replace_all(text, "$semicolon", ",");
113 }
114
convert_special_tags_to_retail()115 void CFred_mission_save::convert_special_tags_to_retail()
116 {
117 int i, team, stage;
118
119 if (Mission_save_format != FSO_FORMAT_RETAIL)
120 return;
121
122 for (team = 0; team < Num_teams; team++) {
123 // command briefing
124 for (stage = 0; stage < Cmd_briefs[team].num_stages; stage++) {
125 convert_special_tags_to_retail(Cmd_briefs[team].stage[stage].text);
126 }
127
128 // briefing
129 for (stage = 0; stage < Briefings[team].num_stages; stage++) {
130 convert_special_tags_to_retail(Briefings[team].stages[stage].text);
131 }
132
133 // debriefing
134 for (stage = 0; stage < Debriefings[team].num_stages; stage++) {
135 convert_special_tags_to_retail(Debriefings[team].stages[stage].text);
136 }
137 }
138
139 for (i = Num_builtin_messages; i < Num_messages; i++) {
140 convert_special_tags_to_retail(Messages[i].message, MESSAGE_LENGTH);
141 }
142 }
143
fout(char * format,...)144 int CFred_mission_save::fout(char *format, ...)
145 {
146 // don't output anything if we're saving in retail and have version-specific comments active
147 if (Mission_save_format == FSO_FORMAT_RETAIL && !fso_ver_comment.empty()) {
148 return 0;
149 }
150
151 SCP_string str;
152 va_list args;
153
154 if (err) {
155 return err;
156 }
157
158 va_start(args, format);
159 vsprintf(str, format, args);
160 va_end(args);
161
162 cfputs(str.c_str(), fp);
163 return 0;
164 }
165
fout_ext(char * pre_str,char * format,...)166 int CFred_mission_save::fout_ext(char *pre_str, char *format, ...)
167 {
168 // don't output anything if we're saving in retail and have version-specific comments active
169 if (Mission_save_format == FSO_FORMAT_RETAIL && !fso_ver_comment.empty()) {
170 return 0;
171 }
172
173 SCP_string str_scp;
174 SCP_string str_out_scp;
175 va_list args;
176 int str_id;
177
178 if (err) {
179 return err;
180 }
181
182 va_start(args, format);
183 vsprintf(str_scp, format, args);
184 va_end(args);
185
186 if (pre_str) {
187 str_out_scp = pre_str;
188 }
189
190 // lookup the string in the hash table
191 str_id = fhash_string_exists(str_scp.c_str());
192
193 // doesn't exist, so assign it an ID of -1 and stick it in the table
194 if (str_id <= -2) {
195 str_out_scp += " XSTR(\"";
196 str_out_scp += str_scp;
197 str_out_scp += "\", -1)";
198
199 // add the string to the table
200 fhash_add_str(str_scp.c_str(), -1);
201 }
202 // _does_ exist, so just write it out as it is
203 else {
204 char buf[10];
205 sprintf(buf, "%d", str_id);
206
207 str_out_scp += " XSTR(\"";
208 str_out_scp += str_scp;
209 str_out_scp += "\", ";
210 str_out_scp += buf;
211 str_out_scp += ")";
212 }
213
214 char *str_out_c = vm_strdup(str_out_scp.c_str());
215
216 // this could be a multi-line string, so we've got to handle it all properly
217 if (!fso_ver_comment.empty()) {
218 bool first_line = true;
219 char *str_p = str_out_c;
220
221 char *ch = strchr(str_out_c, '\n');
222
223 // if we have something, and it's not just at the end, then process it specially
224 if ((ch != NULL) && (*(ch + 1) != '\0')) {
225 do {
226 if (*(ch + 1) != '\0') {
227 *ch = '\0';
228
229 if (first_line) {
230 first_line = false;
231 } else {
232 cfputs(fso_ver_comment.back().c_str(), fp);
233 cfputs(" ", fp);
234 }
235
236 cfputs(str_p, fp);
237 cfputc('\n', fp);
238
239 str_p = ch + 1;
240 } else {
241 if (first_line) {
242 first_line = false;
243 } else {
244 cfputs(fso_ver_comment.back().c_str(), fp);
245 cfputs(" ", fp);
246 }
247
248 cfputs(str_p, fp);
249
250 str_p = ch + 1;
251
252 break;
253 }
254 } while ((ch = strchr(str_p, '\n')) != NULL);
255
256 // be sure to account for any ending elements too
257 if (strlen(str_p)) {
258 cfputs(fso_ver_comment.back().c_str(), fp);
259 cfputs(" ", fp);
260 cfputs(str_p, fp);
261 }
262
263 vm_free(str_out_c);
264 return 0;
265 }
266 }
267
268 cfputs(str_out_c, fp);
269
270 vm_free(str_out_c);
271 return 0;
272 }
273
fout_version(char * format,...)274 int CFred_mission_save::fout_version(char *format, ...)
275 {
276 SCP_string str_scp;
277 char *ch = NULL;
278 va_list args;
279
280 if (err) {
281 return err;
282 }
283
284 // if we're saving in retail format, we can completely skip anything with a version-specific comment
285 if (Mission_save_format == FSO_FORMAT_RETAIL && !fso_ver_comment.empty()) {
286 return 0;
287 }
288
289 // output the version first thing, but skip the special case where we use
290 // fout_version() for multiline value strings (typically indicated by an initial space)
291 if ((Mission_save_format == FSO_FORMAT_COMPATIBILITY_MODE) && (*format != ' ') && !fso_ver_comment.empty()) {
292 while (*format == '\n') {
293 str_scp.append(1, *format);
294 format++;
295 }
296
297 str_scp.append(fso_ver_comment.back().c_str());
298 str_scp.append(" ");
299
300 cfputs(str_scp.c_str(), fp);
301
302 str_scp = "";
303 }
304
305 va_start(args, format);
306 vsprintf(str_scp, format, args);
307 va_end(args);
308
309 char *str_c = vm_strdup(str_scp.c_str());
310
311 // this could be a multi-line string, so we've got to handle it all properly
312 if ((Mission_save_format == FSO_FORMAT_COMPATIBILITY_MODE) && !fso_ver_comment.empty()) {
313 bool first_line = true;
314 char *str_p = str_c;
315
316 ch = strchr(str_c, '\n');
317
318 // if we have something, and it's not just at the end, then process it specially
319 if ((ch != NULL) && (*(ch + 1) != '\0')) {
320 do {
321 if (*(ch + 1) != '\0') {
322 *ch = '\0';
323
324 if (first_line) {
325 first_line = false;
326 } else {
327 cfputs(fso_ver_comment.back().c_str(), fp);
328 cfputs(" ", fp);
329 }
330
331 cfputs(str_p, fp);
332 cfputc('\n', fp);
333
334 str_p = ch + 1;
335 } else {
336 if (first_line) {
337 first_line = false;
338 } else {
339 cfputs(fso_ver_comment.back().c_str(), fp);
340 cfputs(" ", fp);
341 }
342
343 cfputs(str_p, fp);
344
345 str_p = ch + 1;
346
347 break;
348 }
349 } while ((ch = strchr(str_p, '\n')) != NULL);
350
351 // be sure to account for any ending elements too
352 if (strlen(str_p)) {
353 cfputs(fso_ver_comment.back().c_str(), fp);
354 cfputs(" ", fp);
355 cfputs(str_p, fp);
356 }
357
358 vm_free(str_c);
359 return 0;
360 }
361 }
362
363 cfputs(str_c, fp);
364
365 vm_free(str_c);
366 return 0;
367 }
368
parse_comments(int newlines)369 void CFred_mission_save::parse_comments(int newlines)
370 {
371 char *comment_start = NULL;
372 int state = 0, same_line = 0, first_comment = 1, tab = 0, flag = 0;
373 bool version_added = false;
374
375 if (newlines < 0) {
376 newlines = -newlines;
377 tab = 1;
378 }
379
380 if (newlines)
381 same_line = 1;
382
383 if (fred_parse_flag || !Token_found_flag || !token_found || (token_found && (*Parse_text_raw == '\0'))) {
384 while (newlines-- > 0)
385 fout("\n");
386
387 if (tab && token_found)
388 fout_version("\t%s", token_found);
389 else if (token_found)
390 fout_version("%s", token_found);
391 else if (tab)
392 fout("\t");
393
394 return;
395 }
396
397 while (*raw_ptr != '\0') {
398 // state values (as far as I could figure out):
399 // 0 - raw_ptr not inside comment
400 // 1 - raw_ptr inside /**/ comment
401 // 2 - raw_ptr inside ; (newline-delimited) comment
402 // 3,4 - raw_ptr inside ;; (FSO version) comment
403 if (!state) {
404 if (token_found && (*raw_ptr == *token_found))
405 if (!strnicmp(raw_ptr, token_found, strlen(token_found))) {
406 same_line = newlines - 1 + same_line;
407 while (same_line-- > 0)
408 fout("\n");
409
410 if (tab) {
411 fout_version("\t");
412 fout("%s", token_found);
413 } else {
414 fout_version("%s", token_found);
415 }
416
417 // If you have a bunch of lines that all start with the same token (like, say, "+Subsystem:"),
418 // this makes it so it won't just repeatedly match the first one. -MageKing17
419 raw_ptr++;
420
421 if (version_added)
422 fso_comment_pop();
423
424 return;
425 }
426
427 if ((*raw_ptr == '/') && (raw_ptr[1] == '*')) {
428 comment_start = raw_ptr;
429 state = 1;
430 }
431
432 if ((*raw_ptr == ';') && (raw_ptr[1] != '!')) {
433 comment_start = raw_ptr;
434 state = 2;
435
436 // check for a FSO version comment, but if we can't understand it then
437 // just handle it as a regular comment
438 if ((raw_ptr[1] == ';') && (raw_ptr[2] == 'F') && (raw_ptr[3] == 'S') && (raw_ptr[4] == 'O')) {
439 int major, minor, build, revis;
440 int s_num = scan_fso_version_string(raw_ptr, &major, &minor, &build, &revis);
441
442 // hack for releases
443 if (FS_VERSION_REVIS < 1000) {
444 s_num = 3;
445 }
446
447 if ((s_num == 3) && ((major < FS_VERSION_MAJOR) || ((major == FS_VERSION_MAJOR) && ((minor < FS_VERSION_MINOR) || ((minor == FS_VERSION_MINOR) && (build <= FS_VERSION_BUILD)))))) {
448 state = 3;
449 } else if ((s_num == 4) && ((major < FS_VERSION_MAJOR) || ((major == FS_VERSION_MAJOR) && ((minor < FS_VERSION_MINOR) || ((minor == FS_VERSION_MINOR) && ((build < FS_VERSION_BUILD) || ((build == FS_VERSION_BUILD) && (revis <= FS_VERSION_REVIS)))))))) {
450 state = 3;
451 } else {
452 state = 4;
453 }
454 }
455 }
456
457 if (*raw_ptr == '\n')
458 flag = 1;
459
460 if (flag && state && !(state == 3))
461 fout("\n");
462
463 } else {
464 if (*raw_ptr == '\n') {
465 if (state == 2) {
466 if (first_comment && !flag)
467 fout("\t\t");
468
469 *raw_ptr = 0;
470 fout("%s\n", comment_start);
471 *raw_ptr = '\n';
472 state = first_comment = same_line = flag = 0;
473 } else if (state == 4) {
474 same_line = newlines - 2 + same_line;
475 while (same_line-- > 0)
476 fout("\n");
477
478 if (*(raw_ptr - 1) == '\r') {
479 *(raw_ptr - 1) = '\0';
480 } else {
481 *raw_ptr = 0;
482 }
483
484 fout("%s\n", comment_start);
485
486 if (*(raw_ptr - 1) == '\0') {
487 *(raw_ptr - 1) = '\r';
488 } else {
489 *raw_ptr = '\n';
490 }
491
492 state = first_comment = same_line = flag = 0;
493 }
494 }
495
496 if ((*raw_ptr == '*') && (raw_ptr[1] == '/') && (state == 1)) {
497 if (first_comment && !flag)
498 fout("\t\t");
499
500 const char tmp = raw_ptr[2];
501 raw_ptr[2] = 0;
502 fout("%s", comment_start);
503 raw_ptr[2] = tmp;
504 state = first_comment = flag = 0;
505 }
506
507 if ((*raw_ptr == ';') && (raw_ptr[1] == ';') && (state == 3)) {
508 const char tmp = raw_ptr[2];
509 raw_ptr[2] = 0;
510 if (version_added)
511 fso_comment_pop();
512 else
513 version_added = true;
514 fso_comment_push(comment_start);
515 raw_ptr[2] = tmp;
516 state = first_comment = flag = 0;
517 raw_ptr++;
518 }
519 }
520
521 raw_ptr++;
522 }
523
524 if (version_added)
525 fso_comment_pop();
526
527 return;
528 }
529
save_ai_goals(ai_goal * goalp,int ship)530 void CFred_mission_save::save_ai_goals(ai_goal *goalp, int ship)
531 {
532 char *str = NULL, buf[80];
533 int i, valid, flag = 1;
534
535 for (i = 0; i < MAX_AI_GOALS; i++) {
536 if (goalp[i].ai_mode == AI_GOAL_NONE)
537 continue;
538
539 if (flag) {
540 if (optional_string_fred("$AI Goals:", "$Name:"))
541 parse_comments();
542 else
543 fout("\n$AI Goals:");
544
545 fout(" ( goals ");
546 flag = 0;
547 }
548
549 if (goalp[i].ai_mode == AI_GOAL_CHASE_ANY) {
550 fout("( ai-chase-any %d ) ", goalp[i].priority);
551
552 } else if (goalp[i].ai_mode == AI_GOAL_UNDOCK) {
553 fout("( ai-undock %d ) ", goalp[i].priority);
554
555 } else if (goalp[i].ai_mode == AI_GOAL_KEEP_SAFE_DISTANCE) {
556 fout("( ai-keep-safe-distance %d ) ", goalp[i].priority);
557
558 } else if (goalp[i].ai_mode == AI_GOAL_PLAY_DEAD) {
559 fout("( ai-play-dead %d ) ", goalp[i].priority);
560
561 } else if (goalp[i].ai_mode == AI_GOAL_PLAY_DEAD_PERSISTENT) {
562 fout("( ai-play-dead-persistent %d ) ", goalp[i].priority);
563
564 } else if (goalp[i].ai_mode == AI_GOAL_WARP) {
565 fout("( ai-warp-out %d ) ", goalp[i].priority);
566
567 } else {
568 valid = 1;
569 if (!goalp[i].target_name) {
570 Warning(LOCATION, "Ai goal has no target where one is required");
571
572 } else {
573 sprintf(buf, "\"%s\"", goalp[i].target_name);
574 switch (goalp[i].ai_mode) {
575 case AI_GOAL_WAYPOINTS:
576 str = "ai-waypoints";
577 break;
578
579 case AI_GOAL_WAYPOINTS_ONCE:
580 str = "ai-waypoints-once";
581 break;
582
583 case AI_GOAL_DESTROY_SUBSYSTEM:
584 if (goalp[i].docker.index == -1 || !goalp[i].docker.index) {
585 valid = 0;
586 Warning(LOCATION, "AI destroy subsystem goal invalid subsystem name\n");
587
588 } else {
589 sprintf(buf, "\"%s\" \"%s\"", goalp[i].target_name, goalp[i].docker.name);
590 str = "ai-destroy-subsystem";
591 }
592
593 break;
594
595 case AI_GOAL_DOCK:
596 if (ship < 0) {
597 valid = 0;
598 Warning(LOCATION, "Wings aren't allowed to have a docking goal\n");
599
600 } else if (goalp[i].docker.index == -1 || !goalp[i].docker.index) {
601 valid = 0;
602 Warning(LOCATION, "AI dock goal for \"%s\" has invalid docker point "
603 "(docking with \"%s\")\n", Ships[ship].ship_name, goalp[i].target_name);
604
605 } else if (goalp[i].dockee.index == -1 || !goalp[i].dockee.index) {
606 valid = 0;
607 Warning(LOCATION, "AI dock goal for \"%s\" has invalid dockee point "
608 "(docking with \"%s\")\n", Ships[ship].ship_name, goalp[i].target_name);
609
610 } else {
611 sprintf(buf, "\"%s\" \"%s\" \"%s\"", goalp[i].target_name,
612 goalp[i].docker.name, goalp[i].dockee.name);
613
614 str = "ai-dock";
615 }
616 break;
617
618 case AI_GOAL_CHASE:
619 str = "ai-chase";
620 break;
621
622 case AI_GOAL_CHASE_WING:
623 str = "ai-chase-wing";
624 break;
625
626 case AI_GOAL_CHASE_SHIP_CLASS:
627 str = "ai-chase-ship-class";
628 break;
629
630 case AI_GOAL_GUARD:
631 str = "ai-guard";
632 break;
633
634 case AI_GOAL_GUARD_WING:
635 str = "ai-guard-wing";
636 break;
637
638 case AI_GOAL_DISABLE_SHIP:
639 str = "ai-disable-ship";
640 break;
641
642 case AI_GOAL_DISARM_SHIP:
643 str = "ai-disarm-ship";
644 break;
645
646 case AI_GOAL_IGNORE:
647 str = "ai-ignore";
648 break;
649
650 case AI_GOAL_IGNORE_NEW:
651 str = "ai-ignore-new";
652 break;
653
654 case AI_GOAL_EVADE_SHIP:
655 str = "ai-evade-ship";
656 break;
657
658 case AI_GOAL_STAY_NEAR_SHIP:
659 str = "ai-stay-near-ship";
660 break;
661
662 case AI_GOAL_STAY_STILL:
663 str = "ai-stay-still";
664 break;
665
666 default:
667 Assert(0);
668 }
669
670 if (valid)
671 fout("( %s %s %d ) ", str, buf, goalp[i].priority);
672 }
673 }
674
675 fso_comment_pop();
676 }
677
678 if (!flag)
679 fout(")");
680
681 fso_comment_pop(true);
682 }
683
save_asteroid_fields()684 int CFred_mission_save::save_asteroid_fields()
685 {
686 int i;
687
688 fred_parse_flag = 0;
689 required_string_fred("#Asteroid Fields");
690 parse_comments(2);
691
692 for (i = 0; i < 1 /*MAX_ASTEROID_FIELDS*/; i++) {
693 if (!Asteroid_field.num_initial_asteroids)
694 continue;
695
696 required_string_fred("$Density:");
697 parse_comments(2);
698 fout(" %d", Asteroid_field.num_initial_asteroids);
699
700 // field type
701 if (optional_string_fred("+Field Type:")) {
702 parse_comments();
703 } else {
704 fout("\n+Field Type:");
705 }
706 fout(" %d", Asteroid_field.field_type);
707
708 // debris type
709 if (optional_string_fred("+Debris Genre:")) {
710 parse_comments();
711 } else {
712 fout("\n+Debris Genre:");
713 }
714 fout(" %d", Asteroid_field.debris_genre);
715
716 // field_debris_type (only if ship genre)
717 if (Asteroid_field.debris_genre == DG_SHIP) {
718 for (int idx = 0; idx < 3; idx++) {
719 if (Asteroid_field.field_debris_type[idx] != -1) {
720 if (optional_string_fred("+Field Debris Type:")) {
721 parse_comments();
722 } else {
723 fout("\n+Field Debris Type:");
724 }
725 fout(" %d", Asteroid_field.field_debris_type[idx]);
726 }
727 }
728 } else {
729 // asteroid subtypes stored in field_debris_type as -1 or 1
730 for (auto idx = 0; idx < 3; idx++) {
731 if (Asteroid_field.field_debris_type[idx] != -1) {
732 if (optional_string_fred("+Field Debris Type:")) {
733 parse_comments();
734 } else {
735 fout("\n+Field Debris Type:");
736 }
737 fout(" %d", idx);
738 }
739 }
740 }
741
742
743 required_string_fred("$Average Speed:");
744 parse_comments();
745 fout(" %f", vm_vec_mag(&Asteroid_field.vel));
746
747 required_string_fred("$Minimum:");
748 parse_comments();
749 save_vector(Asteroid_field.min_bound);
750
751 required_string_fred("$Maximum:");
752 parse_comments();
753 save_vector(Asteroid_field.max_bound);
754
755 if (Asteroid_field.has_inner_bound == 1) {
756 if (optional_string_fred("+Inner Bound:")) {
757 parse_comments();
758 } else {
759 fout("\n+Inner Bound:");
760 }
761
762 required_string_fred("$Minimum:");
763 parse_comments();
764 save_vector(Asteroid_field.inner_min_bound);
765
766 required_string_fred("$Maximum:");
767 parse_comments();
768 save_vector(Asteroid_field.inner_max_bound);
769 }
770
771 fso_comment_pop();
772 }
773
774 fso_comment_pop(true);
775
776 return err;
777 }
778
save_bitmaps()779 int CFred_mission_save::save_bitmaps()
780 {
781 int i;
782 uint j;
783
784 fred_parse_flag = 0;
785 required_string_fred("#Background bitmaps");
786 parse_comments(2);
787 fout("\t\t;! %d total\n", stars_get_num_bitmaps());
788
789 required_string_fred("$Num stars:");
790 parse_comments();
791 fout(" %d", Num_stars);
792
793 required_string_fred("$Ambient light level:");
794 parse_comments();
795 fout(" %d", The_mission.ambient_light_level);
796
797 // neb2 stuff
798 if (The_mission.flags[Mission::Mission_Flags::Fullneb]) {
799 required_string_fred("+Neb2:");
800 parse_comments();
801 fout(" %s\n", Neb2_texture_name);
802
803 required_string_fred("+Neb2Flags:");
804 parse_comments();
805 fout(" %d\n", Neb2_poof_flags);
806 }
807 // neb 1 stuff
808 else {
809 if (Nebula_index >= 0) {
810 if (optional_string_fred("+Nebula:")) {
811 parse_comments();
812 } else {
813 fout("\n+Nebula:");
814 }
815 fout(" %s", Nebula_filenames[Nebula_index]);
816
817 required_string_fred("+Color:");
818 parse_comments();
819 fout(" %s", Nebula_colors[Mission_palette]);
820
821 required_string_fred("+Pitch:");
822 parse_comments();
823 fout(" %d", Nebula_pitch);
824
825 required_string_fred("+Bank:");
826 parse_comments();
827 fout(" %d", Nebula_bank);
828
829 required_string_fred("+Heading:");
830 parse_comments();
831 fout(" %d", Nebula_heading);
832 }
833 }
834
835 fso_comment_pop();
836
837 // Goober5000 - save all but the lowest priority using the special comment tag
838 for (i = 0; i < (int)Backgrounds.size(); i++) {
839 bool tag = (i < (int)Backgrounds.size() - 1);
840 background_t *background = &Backgrounds[i];
841
842 fso_comment_push(";;FSO 3.6.9;;");
843 if (optional_string_fred("$Bitmap List:")) {
844 parse_comments(2);
845 } else {
846 fout_version("\n\n$Bitmap List:");
847 }
848
849 if (!tag) {
850 fso_comment_pop(true);
851 }
852
853 // save suns by filename
854 for (j = 0; j < background->suns.size(); j++) {
855 starfield_list_entry *sle = &background->suns[j];
856
857 // filename
858 required_string_fred("$Sun:");
859 parse_comments();
860 fout(" %s", sle->filename);
861
862 // angles
863 required_string_fred("+Angles:");
864 parse_comments();
865 fout(" %f %f %f", sle->ang.p, sle->ang.b, sle->ang.h);
866
867 // scale
868 required_string_fred("+Scale:");
869 parse_comments();
870 fout(" %f", sle->scale_x);
871 }
872
873 // save background bitmaps by filename
874 for (j = 0; j < background->bitmaps.size(); j++) {
875 starfield_list_entry *sle = &background->bitmaps[j];
876
877 // filename
878 required_string_fred("$Starbitmap:");
879 parse_comments();
880 fout(" %s", sle->filename);
881
882 // angles
883 required_string_fred("+Angles:");
884 parse_comments();
885 fout(" %f %f %f", sle->ang.p, sle->ang.b, sle->ang.h);
886
887 // scale
888 required_string_fred("+ScaleX:");
889 parse_comments();
890 fout(" %f", sle->scale_x);
891 required_string_fred("+ScaleY:");
892 parse_comments();
893 fout(" %f", sle->scale_y);
894
895 // div
896 required_string_fred("+DivX:");
897 parse_comments();
898 fout(" %d", sle->div_x);
899 required_string_fred("+DivY:");
900 parse_comments();
901 fout(" %d", sle->div_y);
902 }
903
904 fso_comment_pop();
905 }
906
907 // taylor's environment map thingy
908 if (strlen(The_mission.envmap_name) > 0) { //-V805
909 fso_comment_push(";;FSO 3.6.9;;");
910 if (optional_string_fred("$Environment Map:")) {
911 parse_comments(2);
912 fout(" %s", The_mission.envmap_name);
913 } else {
914 fout_version("\n\n$Environment Map: %s", The_mission.envmap_name);
915 }
916 fso_comment_pop();
917 } else {
918 bypass_comment(";;FSO 3.6.9;; $Environment Map:");
919 }
920
921 fso_comment_pop(true);
922
923 return err;
924 }
925
save_briefing()926 int CFred_mission_save::save_briefing()
927 {
928 int i, j, k, nb;
929 SCP_string sexp_out;
930 brief_stage *bs;
931 brief_icon *bi;
932
933 for (nb = 0; nb < Num_teams; nb++) {
934
935 required_string_fred("#Briefing");
936 parse_comments(2);
937
938 required_string_fred("$start_briefing");
939 parse_comments();
940
941 save_custom_bitmap("$briefing_background_640:", "$briefing_background_1024:", Briefings[nb].background[GR_640], Briefings[nb].background[GR_1024]);
942 save_custom_bitmap("$ship_select_background_640:", "$ship_select_background_1024:", Briefings[nb].ship_select_background[GR_640], Briefings[nb].ship_select_background[GR_1024]);
943 save_custom_bitmap("$weapon_select_background_640:", "$weapon_select_background_1024:", Briefings[nb].weapon_select_background[GR_640], Briefings[nb].weapon_select_background[GR_1024]);
944
945 Assert(Briefings[nb].num_stages <= MAX_BRIEF_STAGES);
946 required_string_fred("$num_stages:");
947 parse_comments();
948 fout(" %d", Briefings[nb].num_stages);
949
950 for (i = 0; i < Briefings[nb].num_stages; i++) {
951 bs = &Briefings[nb].stages[i];
952
953 required_string_fred("$start_stage");
954 parse_comments();
955
956 required_string_fred("$multi_text");
957 parse_comments();
958
959 // XSTR
960 fout_ext("\n", "%s", bs->text.c_str());
961
962 required_string_fred("$end_multi_text", "$start_stage");
963 parse_comments();
964
965 if (!drop_white_space(bs->voice)[0]) {
966 strcpy_s(bs->voice, "None");
967 }
968
969 required_string_fred("$voice:");
970 parse_comments();
971 fout(" %s", bs->voice);
972
973 required_string_fred("$camera_pos:");
974 parse_comments();
975 save_vector(bs->camera_pos);
976
977 required_string_fred("$camera_orient:");
978 parse_comments();
979 save_matrix(bs->camera_orient);
980
981 required_string_fred("$camera_time:");
982 parse_comments();
983 fout(" %d", bs->camera_time);
984
985 required_string_fred("$num_lines:");
986 parse_comments();
987 fout(" %d", bs->num_lines);
988
989 for (k = 0; k < bs->num_lines; k++) {
990 required_string_fred("$line_start:");
991 parse_comments();
992 fout(" %d", bs->lines[k].start_icon);
993
994 required_string_fred("$line_end:");
995 parse_comments();
996 fout(" %d", bs->lines[k].end_icon);
997
998 fso_comment_pop();
999 }
1000
1001 required_string_fred("$num_icons:");
1002 parse_comments();
1003 Assert(bs->num_icons <= MAX_STAGE_ICONS);
1004 fout(" %d", bs->num_icons);
1005
1006 required_string_fred("$Flags:");
1007 parse_comments();
1008 fout(" %d", bs->flags);
1009
1010 required_string_fred("$Formula:");
1011 parse_comments();
1012 convert_sexp_to_string(sexp_out, bs->formula, SEXP_SAVE_MODE);
1013 fout(" %s", sexp_out.c_str());
1014
1015 for (j = 0; j < bs->num_icons; j++) {
1016 bi = &bs->icons[j];
1017
1018 required_string_fred("$start_icon");
1019 parse_comments();
1020
1021 required_string_fred("$type:");
1022 parse_comments();
1023 fout(" %d", bi->type);
1024
1025 required_string_fred("$team:");
1026 parse_comments();
1027 fout(" %s", Iff_info[bi->team].iff_name);
1028
1029 required_string_fred("$class:");
1030 parse_comments();
1031 if (bi->ship_class < 0)
1032 bi->ship_class = 0;
1033
1034 fout(" %s", Ship_info[bi->ship_class].name);
1035
1036 required_string_fred("$pos:");
1037 parse_comments();
1038 save_vector(bi->pos);
1039
1040 if (drop_white_space(bi->label)[0]) {
1041 if (optional_string_fred("$label:"))
1042 parse_comments();
1043 else
1044 fout("\n$label:");
1045
1046 fout_ext(" ", "%s", bi->label);
1047 }
1048
1049 if (optional_string_fred("+id:"))
1050 parse_comments();
1051 else
1052 fout("\n+id:");
1053
1054 fout(" %d", bi->id);
1055
1056 required_string_fred("$hlight:");
1057 parse_comments();
1058 fout(" %d", (bi->flags & BI_HIGHLIGHT) ? 1 : 0);
1059
1060 if (Mission_save_format != FSO_FORMAT_RETAIL) {
1061 required_string_fred("$mirror:");
1062 parse_comments();
1063 fout(" %d", (bi->flags & BI_MIRROR_ICON) ? 1 : 0);
1064 }
1065
1066 if ((Mission_save_format != FSO_FORMAT_RETAIL) && (bi->flags & BI_USE_WING_ICON)) {
1067 required_string_fred("$use wing icon:");
1068 parse_comments();
1069 fout(" %d", (bi->flags & BI_USE_WING_ICON) ? 1 : 0);
1070 }
1071
1072 required_string_fred("$multi_text");
1073 parse_comments();
1074
1075 // sprintf(out,"\n%s", bi->text);
1076 // fout(out);
1077
1078 required_string_fred("$end_multi_text");
1079 parse_comments();
1080
1081 required_string_fred("$end_icon");
1082 parse_comments();
1083
1084 fso_comment_pop();
1085 }
1086
1087 required_string_fred("$end_stage");
1088 parse_comments();
1089
1090 fso_comment_pop();
1091 }
1092 required_string_fred("$end_briefing");
1093 parse_comments();
1094
1095 fso_comment_pop();
1096 }
1097
1098 fso_comment_pop(true);
1099
1100 return err;
1101 }
1102
save_campaign_file(char * pathname)1103 int CFred_mission_save::save_campaign_file(char *pathname)
1104 {
1105 int i, j, m, flag;
1106
1107 Campaign_tree_formp->save_tree(); // flush all changes so they get saved.
1108 Campaign_tree_viewp->sort_elements();
1109 reset_parse();
1110 fred_parse_flag = 0;
1111
1112 pathname = cf_add_ext(pathname, FS_CAMPAIGN_FILE_EXT);
1113 fp = cfopen(pathname, "wt", CFILE_NORMAL, CF_TYPE_MISSIONS);
1114 if (!fp) {
1115 nprintf(("Error", "Can't open campaign file to save.\n"));
1116 return -1;
1117 }
1118
1119 required_string_fred("$Name:");
1120 parse_comments(0);
1121 fout(" %s", Campaign.name);
1122
1123 Assert((Campaign.type >= 0) && (Campaign.type < MAX_CAMPAIGN_TYPES));
1124 required_string_fred("$Type:");
1125 parse_comments();
1126 fout(" %s", campaign_types[Campaign.type]);
1127
1128 // XSTR
1129 if (Campaign.desc) {
1130 required_string_fred("+Description:");
1131 parse_comments();
1132 fout_ext("\n", "%s", Campaign.desc);
1133 fout("\n$end_multi_text");
1134 }
1135
1136 if (Campaign.type != CAMPAIGN_TYPE_SINGLE) {
1137 required_string_fred("+Num Players:");
1138 parse_comments();
1139 fout(" %d", Campaign.num_players);
1140 }
1141
1142 // campaign flags - Goober5000
1143 if (Mission_save_format != FSO_FORMAT_RETAIL) {
1144 optional_string_fred("$Flags:");
1145 parse_comments();
1146 fout(" %d\n", Campaign.flags);
1147 }
1148
1149 // write out the ships and weapons which the player can start the campaign with
1150 optional_string_fred("+Starting Ships: (");
1151 parse_comments(2);
1152 for (i = 0; i < ship_info_size(); i++) {
1153 if (Campaign.ships_allowed[i])
1154 fout(" \"%s\" ", Ship_info[i].name);
1155 }
1156 fout(")\n");
1157
1158 optional_string_fred("+Starting Weapons: (");
1159 parse_comments();
1160 for (i = 0; i < weapon_info_size(); i++) {
1161 if (Campaign.weapons_allowed[i])
1162 fout(" \"%s\" ", Weapon_info[i].name);
1163 }
1164 fout(")\n");
1165
1166 fred_parse_flag = 0;
1167 for (i = 0; i < Campaign.num_missions; i++) {
1168 m = Sorted[i];
1169 required_string_either_fred("$Mission:", "#End");
1170 required_string_fred("$Mission:");
1171 parse_comments(2);
1172 fout(" %s", Campaign.missions[m].name);
1173
1174 if (strlen(Campaign.missions[i].briefing_cutscene)) {
1175 if (optional_string_fred("+Briefing Cutscene:", "$Mission"))
1176 parse_comments();
1177 else
1178 fout("\n+Briefing Cutscene:");
1179
1180 fout(" %s", Campaign.missions[i].briefing_cutscene);
1181 }
1182
1183 required_string_fred("+Flags:", "$Mission:");
1184 parse_comments();
1185
1186 // Goober5000
1187 if (Mission_save_format != FSO_FORMAT_RETAIL) {
1188 // don't save Bastion flag
1189 fout(" %d", Campaign.missions[m].flags & ~CMISSION_FLAG_BASTION);
1190
1191 // new main hall stuff
1192 if (optional_string_fred("+Main Hall:", "$Mission:"))
1193 parse_comments();
1194 else
1195 fout("\n+Main Hall:");
1196
1197 fout(" %s", Campaign.missions[m].main_hall.c_str());
1198 } else {
1199 // save Bastion flag properly
1200 fout(" %d", Campaign.missions[m].flags | ((Campaign.missions[m].main_hall != "") ? CMISSION_FLAG_BASTION : 0));
1201 }
1202
1203 if (Campaign.missions[m].debrief_persona_index > 0) {
1204 fso_comment_push(";;FSO 3.6.8;;");
1205 if (optional_string_fred("+Debriefing Persona Index:")) {
1206 parse_comments(1);
1207 fout(" %d", Campaign.missions[m].debrief_persona_index);
1208 } else {
1209 fout_version("\n+Debriefing Persona Index: %d", Campaign.missions[m].debrief_persona_index);
1210 }
1211 fso_comment_pop();
1212 } else {
1213 bypass_comment(";;FSO 3.6.8;; +Debriefing Persona Index:");
1214 }
1215
1216 // save campaign link sexp
1217 bool mission_loop = false;
1218 bool mission_fork = false;
1219 flag = 0;
1220 for (j = 0; j < Total_links; j++) {
1221 if (Links[j].from == m) {
1222 if (!flag) {
1223 if (optional_string_fred("+Formula:", "$Mission:"))
1224 parse_comments();
1225 else
1226 fout("\n+Formula:");
1227
1228 fout(" ( cond\n");
1229 flag = 1;
1230 }
1231
1232 //save_campaign_sexp(Links[j].sexp, Campaign.missions[Links[j].to].name);
1233 if (Links[j].is_mission_loop) {
1234 mission_loop = true;
1235 } else if (Links[j].is_mission_fork && (Mission_save_format != FSO_FORMAT_RETAIL)) {
1236 mission_fork = true;
1237 } else {
1238 save_campaign_sexp(Links[j].sexp, Links[j].to);
1239 }
1240 }
1241 }
1242
1243 if (flag) {
1244 fout(")");
1245 }
1246
1247 // now save campaign loop sexp
1248 if (mission_loop || mission_fork) {
1249 if (mission_loop)
1250 required_string_fred("\n+Mission Loop:");
1251 else
1252 required_string_fred("\n+Mission Fork:");
1253 parse_comments();
1254
1255 int num_mission_special = 0;
1256 for (j = 0; j < Total_links; j++) {
1257 if ((Links[j].from == m) && (Links[j].is_mission_loop || Links[j].is_mission_fork)) {
1258
1259 num_mission_special++;
1260
1261 if ((num_mission_special == 1) && Links[j].mission_branch_txt) {
1262 if (mission_loop)
1263 required_string_fred("+Mission Loop Text:");
1264 else
1265 required_string_fred("+Mission Fork Text:");
1266 parse_comments();
1267 fout_ext("\n", "%s", Links[j].mission_branch_txt);
1268 fout("\n$end_multi_text");
1269 }
1270
1271 if ((num_mission_special == 1) && Links[j].mission_branch_brief_anim) {
1272 if (mission_loop)
1273 required_string_fred("+Mission Loop Brief Anim:");
1274 else
1275 required_string_fred("+Mission Fork Brief Anim:");
1276 parse_comments();
1277 fout_ext("\n", "%s", Links[j].mission_branch_brief_anim);
1278 fout("\n$end_multi_text");
1279 }
1280
1281 if ((num_mission_special == 1) && Links[j].mission_branch_brief_sound) {
1282 if (mission_loop)
1283 required_string_fred("+Mission Loop Brief Sound:");
1284 else
1285 required_string_fred("+Mission Fork Brief Sound:");
1286 parse_comments();
1287 fout_ext("\n", "%s", Links[j].mission_branch_brief_sound);
1288 fout("\n$end_multi_text");
1289 }
1290
1291 if (num_mission_special == 1) {
1292 // write out mission loop formula
1293 fout("\n+Formula:");
1294 fout(" ( cond\n");
1295 save_campaign_sexp(Links[j].sexp, Links[j].to);
1296 fout(")");
1297 }
1298 if (mission_fork) {
1299 fout("Option: ", Campaign.missions[Links[j].to].name);
1300 }
1301 }
1302 }
1303 if (mission_loop && num_mission_special > 1) {
1304 char buffer[1024];
1305 sprintf(buffer, "Multiple branching loop error from mission %s\nEdit campaign for *at most* 1 loop from each mission.", Campaign.missions[m].name);
1306 Message(os::dialogs::MESSAGEBOX_ERROR, buffer);
1307 }
1308 }
1309
1310 if (optional_string_fred("+Level:", "$Mission:")) {
1311 parse_comments();
1312 } else {
1313 fout("\n\n+Level:");
1314 }
1315
1316 fout(" %d", Campaign.missions[m].level);
1317
1318 if (optional_string_fred("+Position:", "$Mission:")) {
1319 parse_comments();
1320 } else {
1321 fout("\n+Position:");
1322 }
1323
1324 fout(" %d", Campaign.missions[m].pos);
1325
1326 fso_comment_pop();
1327 }
1328
1329 required_string_fred("#End");
1330 parse_comments(2);
1331 token_found = NULL;
1332 parse_comments();
1333 fout("\n");
1334
1335 cfclose(fp);
1336 if (err)
1337 mprintf(("Campaign saving error code #%d\n", err));
1338 else
1339 Campaign_wnd->error_checker();
1340
1341 fso_comment_pop(true);
1342
1343 return err;
1344 }
1345
save_campaign_sexp(int node,int link_num)1346 void CFred_mission_save::save_campaign_sexp(int node, int link_num)
1347 {
1348 SCP_string sexp_out;
1349 Assert(node >= 0);
1350
1351 // if the link num is -1, then this is a end-of-campaign location
1352 if (link_num != -1) {
1353 if (build_sexp_string(sexp_out, node, 2, SEXP_SAVE_MODE)) {
1354 fout(" (\n %s\n ( next-mission \"%s\" )\n )\n", sexp_out.c_str(), Campaign.missions[link_num].name);
1355 } else {
1356 fout(" ( %s( next-mission \"%s\" ) )\n", sexp_out.c_str(), Campaign.missions[link_num].name);
1357 }
1358 } else {
1359 if (build_sexp_string(sexp_out, node, 2, SEXP_SAVE_MODE)) {
1360 fout(" (\n %s\n ( end-of-campaign )\n )\n", sexp_out.c_str());
1361 } else {
1362 fout(" ( %s( end-of-campaign ) )\n", sexp_out.c_str());
1363 }
1364 }
1365 }
1366
save_cmd_brief()1367 int CFred_mission_save::save_cmd_brief()
1368 {
1369 int stage;
1370
1371 stage = 0;
1372 required_string_fred("#Command Briefing");
1373 parse_comments(2);
1374
1375 save_custom_bitmap("$Background 640:", "$Background 1024:", Cur_cmd_brief->background[GR_640], Cur_cmd_brief->background[GR_1024], 1);
1376
1377 for (stage = 0; stage < Cur_cmd_brief->num_stages; stage++) {
1378 required_string_fred("$Stage Text:");
1379 parse_comments(2);
1380
1381 // XSTR
1382 fout_ext("\n", "%s", Cur_cmd_brief->stage[stage].text.c_str());
1383
1384 required_string_fred("$end_multi_text", "$Stage Text:");
1385 parse_comments();
1386
1387 required_string_fred("$Ani Filename:");
1388 parse_comments();
1389 fout(" %s", Cur_cmd_brief->stage[stage].ani_filename);
1390
1391 required_string_fred("+Wave Filename:", "$Ani Filename:");
1392 parse_comments();
1393 fout(" %s", Cur_cmd_brief->stage[stage].wave_filename);
1394
1395 fso_comment_pop();
1396 }
1397
1398 fso_comment_pop(true);
1399
1400 return err;
1401 }
1402
save_cmd_briefs()1403 int CFred_mission_save::save_cmd_briefs()
1404 {
1405 int i;
1406
1407 for (i = 0; i < Num_teams; i++) {
1408 Cur_cmd_brief = &Cmd_briefs[i];
1409 save_cmd_brief();
1410 }
1411
1412 return err;
1413 }
1414
fso_comment_push(char * ver)1415 void CFred_mission_save::fso_comment_push(char *ver)
1416 {
1417 if (fso_ver_comment.empty()) {
1418 fso_ver_comment.push_back(SCP_string(ver));
1419 return;
1420 }
1421
1422 SCP_string before = fso_ver_comment.back();
1423
1424 int major, minor, build, revis;
1425 int in_major, in_minor, in_build, in_revis;
1426 int elem1, elem2;
1427
1428 elem1 = scan_fso_version_string(fso_ver_comment.back().c_str(), &major, &minor, &build, &revis);
1429 elem2 = scan_fso_version_string(ver, &in_major, &in_minor, &in_build, &in_revis);
1430
1431 // check consistency
1432 if (elem1 == 3 && elem2 == 4 || elem1 == 4 && elem2 == 3) {
1433 elem1 = elem2 = 3;
1434 } else if ((elem1 >= 3 && elem2 >= 3) && (revis < 1000 || in_revis < 1000)) {
1435 elem1 = elem2 = 3;
1436 }
1437
1438 if ((elem1 == 3) && ((major > in_major) || ((major == in_major) && ((minor > in_minor) || ((minor == in_minor) && (build > in_build)))))) {
1439 // the push'd version is older than our current version, so just push a copy of the previous version
1440 fso_ver_comment.push_back(before);
1441 } else if ((elem1 == 4) && ((major > in_major) || ((major == in_major) && ((minor > in_minor) || ((minor == in_minor) && ((build > in_build) || ((build == in_build) || (revis > in_revis)))))))) {
1442 // the push'd version is older than our current version, so just push a copy of the previous version
1443 fso_ver_comment.push_back(before);
1444 } else {
1445 fso_ver_comment.push_back(SCP_string(ver));
1446 }
1447 }
1448
fso_comment_pop(bool pop_all)1449 void CFred_mission_save::fso_comment_pop(bool pop_all)
1450 {
1451 if (fso_ver_comment.empty()) {
1452 return;
1453 }
1454
1455 if (pop_all) {
1456 fso_ver_comment.clear();
1457 return;
1458 }
1459
1460 fso_ver_comment.pop_back();
1461 }
1462
save_common_object_data(object * objp,ship * shipp)1463 int CFred_mission_save::save_common_object_data(object *objp, ship *shipp)
1464 {
1465 int j, z;
1466 ship_subsys *ptr = NULL;
1467 ship_info *sip = NULL;
1468 ship_weapon *wp = NULL;
1469 float temp_max_hull_strength;
1470
1471 sip = &Ship_info[shipp->ship_info_index];
1472
1473 if ((int) objp->phys_info.speed) {
1474 if (optional_string_fred("+Initial Velocity:", "$Name:", "+Subsystem:"))
1475 parse_comments();
1476 else
1477 fout("\n+Initial Velocity:");
1478
1479 fout(" %d", (int) objp->phys_info.speed);
1480 }
1481
1482 // Goober5000
1483 if (Mission_save_format != FSO_FORMAT_RETAIL && (shipp->special_hitpoints > 0)) {
1484 temp_max_hull_strength = (float) shipp->special_hitpoints;
1485 } else {
1486 temp_max_hull_strength = sip->max_hull_strength;
1487 }
1488
1489 if ((int) objp->hull_strength != temp_max_hull_strength) {
1490 if (optional_string_fred("+Initial Hull:", "$Name:", "+Subsystem:"))
1491 parse_comments();
1492 else
1493 fout("\n+Initial Hull:");
1494
1495 fout(" %d", (int) objp->hull_strength);
1496 }
1497
1498 if ((int) shield_get_strength(objp) != 100) {
1499 if (optional_string_fred("+Initial Shields:", "$Name:", "+Subsystem:"))
1500 parse_comments();
1501 else
1502 fout("\n+Initial Shields:");
1503
1504 fout(" %d", (int) objp->shield_quadrant[0]);
1505 }
1506
1507 // save normal ship weapons info
1508 required_string_fred("+Subsystem:", "$Name:");
1509 parse_comments();
1510 fout(" Pilot");
1511
1512 wp = &shipp->weapons;
1513 z = 0;
1514 j = wp->num_primary_banks;
1515 while (j-- && (j >= 0))
1516 if (wp->primary_bank_weapons[j] != sip->primary_bank_weapons[j])
1517 z = 1;
1518
1519 if (z) {
1520 if (optional_string_fred("+Primary Banks:", "$Name:", "+Subsystem:"))
1521 parse_comments();
1522 else
1523 fout("\n+Primary Banks:");
1524
1525 fout(" ( ");
1526 for (j = 0; j < wp->num_primary_banks; j++) {
1527 if (wp->primary_bank_weapons[j] != -1) { // Just in case someone has set a weapon bank to empty
1528 fout("\"%s\" ", Weapon_info[wp->primary_bank_weapons[j]].name);
1529 } else {
1530 fout("\"\" ");
1531 }
1532 }
1533
1534 fout(")");
1535 }
1536
1537 z = 0;
1538 j = wp->num_secondary_banks;
1539 while (j-- && (j >= 0))
1540 if (wp->secondary_bank_weapons[j] != sip->secondary_bank_weapons[j])
1541 z = 1;
1542
1543 if (z) {
1544 if (optional_string_fred("+Secondary Banks:", "$Name:", "+Subsystem:"))
1545 parse_comments();
1546 else
1547 fout("\n+Secondary Banks:");
1548
1549 fout(" ( ");
1550 for (j = 0; j < wp->num_secondary_banks; j++) {
1551 if (wp->secondary_bank_weapons[j] != -1) {
1552 fout("\"%s\" ", Weapon_info[wp->secondary_bank_weapons[j]].name);
1553 } else {
1554 fout("\"\" ");
1555 }
1556 }
1557
1558 fout(")");
1559 }
1560
1561 z = 0;
1562 j = wp->num_secondary_banks;
1563 while (j-- && (j >= 0))
1564 if (wp->secondary_bank_ammo[j] != 100)
1565 z = 1;
1566
1567 if (z) {
1568 if (optional_string_fred("+Sbank Ammo:", "$Name:", "+Subsystem:"))
1569 parse_comments();
1570 else
1571 fout("\n+Sbank Ammo:");
1572
1573 fout(" ( ");
1574 for (j = 0; j < wp->num_secondary_banks; j++)
1575 fout("%d ", wp->secondary_bank_ammo[j]);
1576
1577 fout(")");
1578 }
1579
1580 ptr = GET_FIRST(&shipp->subsys_list);
1581 Assert(ptr);
1582
1583 while (ptr != END_OF_LIST(&shipp->subsys_list) && ptr) {
1584 // Crashing here!
1585 if ((ptr->current_hits) || (ptr->system_info && ptr->system_info->type == SUBSYSTEM_TURRET) || (ptr->subsys_cargo_name > 0)) {
1586 if (optional_string_fred("+Subsystem:", "$Name:"))
1587 parse_comments();
1588 else
1589 fout("\n+Subsystem:");
1590
1591 fout(" %s", ptr->system_info->subobj_name);
1592 }
1593
1594 if (ptr->current_hits) {
1595 if (optional_string_fred("$Damage:", "$Name:", "+Subsystem:"))
1596 parse_comments();
1597 else
1598 fout("\n$Damage:");
1599
1600 fout(" %d", (int) ptr->current_hits);
1601 }
1602
1603 if (ptr->subsys_cargo_name > 0) {
1604 if (optional_string_fred("+Cargo Name:", "$Name:", "+Subsystem:"))
1605 parse_comments();
1606 else
1607 fout("\n+Cargo Name:");
1608
1609 fout_ext(NULL, "%s", Cargo_names[ptr->subsys_cargo_name]);
1610 }
1611
1612 if (ptr->system_info->type == SUBSYSTEM_TURRET)
1613 save_turret_info(ptr, SHIP_INDEX(shipp));
1614
1615 ptr = GET_NEXT(ptr);
1616
1617 fso_comment_pop();
1618 }
1619
1620 /* for (j=0; j<shipp->status_count; j++) {
1621 required_string_fred("$Status Description:");
1622 parse_comments(-1);
1623 fout(" %s", Status_desc_names[shipp->status_type[j]]);
1624
1625 required_string_fred("$Status:");
1626 parse_comments(-1);
1627 fout(" %s", Status_type_names[shipp->status[j]]);
1628
1629 required_string_fred("$Target:");
1630 parse_comments(-1);
1631 fout(" %s", Status_target_names[shipp->target[j]]);
1632 }*/
1633
1634 fso_comment_pop(true);
1635
1636 return err;
1637 }
1638
save_containers()1639 int CFred_mission_save::save_containers()
1640 {
1641 if (Mission_save_format == FSO_FORMAT_RETAIL) {
1642 return 0;
1643 }
1644
1645 const auto& containers = get_all_sexp_containers();
1646
1647 if (containers.empty()) {
1648 fso_comment_pop(true);
1649 return 0;
1650 }
1651
1652 required_string_fred("#Sexp_containers");
1653 parse_comments(2);
1654
1655 bool list_found = false;
1656 bool map_found = false;
1657
1658 // What types of container do we have?
1659 for (const auto &container : containers) {
1660 if (container.is_list()) {
1661 list_found = true;
1662 } else if (container.is_map()) {
1663 map_found = true;
1664 }
1665 if (list_found && map_found) {
1666 // no point in continuing to check
1667 break;
1668 }
1669 }
1670
1671 if (list_found) {
1672 required_string_fred("$Lists");
1673 parse_comments(2);
1674
1675 for (const auto &container : containers) {
1676 if (container.is_list()) {
1677 fout("\n$Name: %s", container.container_name.c_str());
1678 if (any(container.type & ContainerType::STRING_DATA)) {
1679 fout("\n$Data Type: String");
1680 } else if (any(container.type & ContainerType::NUMBER_DATA)) {
1681 fout("\n$Data Type: Number");
1682 }
1683
1684 if (any(container.type & ContainerType::STRICTLY_TYPED_DATA)) {
1685 fout("\n+Strictly Typed Data");
1686 }
1687
1688 fout("\n$Data: ( ");
1689 for (const auto &list_entry : container.list_data) {
1690 fout("\"%s\" ", list_entry.c_str());
1691 }
1692
1693 fout(")\n");
1694
1695 save_container_options(container);
1696 }
1697 }
1698
1699 required_string_fred("$End Lists");
1700 parse_comments(1);
1701 }
1702
1703 if (map_found) {
1704 required_string_fred("$Maps");
1705 parse_comments(2);
1706
1707 for (const auto &container : containers) {
1708 if (container.is_map()) {
1709 fout("\n$Name: %s", container.container_name.c_str());
1710 if (any(container.type & ContainerType::STRING_DATA)) {
1711 fout("\n$Data Type: String");
1712 } else if (any(container.type & ContainerType::NUMBER_DATA)) {
1713 fout("\n$Data Type: Number");
1714 }
1715
1716 if (any(container.type & ContainerType::NUMBER_KEYS)) {
1717 fout("\n$Key Type: Number");
1718 } else {
1719 fout("\n$Key Type: String");
1720 }
1721
1722 if (any(container.type & ContainerType::STRICTLY_TYPED_KEYS)) {
1723 fout("\n+Strictly Typed Keys");
1724 }
1725
1726 if (any(container.type & ContainerType::STRICTLY_TYPED_DATA)) {
1727 fout("\n+Strictly Typed Data");
1728 }
1729
1730 fout("\n$Data: ( ");
1731 for (const auto &map_entry : container.map_data) {
1732 fout("\"%s\" \"%s\" ", map_entry.first.c_str(), map_entry.second.c_str());
1733 }
1734
1735 fout(")\n");
1736
1737 save_container_options(container);
1738 }
1739 }
1740
1741 required_string_fred("$End Maps");
1742 parse_comments(1);
1743 }
1744
1745 return err;
1746 }
1747
save_container_options(const sexp_container & container)1748 void CFred_mission_save::save_container_options(const sexp_container &container)
1749 {
1750 if (any(container.type & ContainerType::NETWORK)) {
1751 fout("+Network Container\n");
1752 }
1753
1754 if (container.is_eternal()) {
1755 fout("+Eternal\n");
1756 }
1757
1758 if (any(container.type & ContainerType::SAVE_ON_MISSION_CLOSE)) {
1759 fout("+Save On Mission Close\n");
1760 } else if (any(container.type & ContainerType::SAVE_ON_MISSION_PROGRESS)) {
1761 fout("+Save On Mission Progress\n");
1762 }
1763
1764 fout("\n");
1765 }
1766
save_custom_bitmap(const char * expected_string_640,const char * expected_string_1024,const char * string_field_640,const char * string_field_1024,int blank_lines)1767 void CFred_mission_save::save_custom_bitmap(const char *expected_string_640, const char *expected_string_1024, const char *string_field_640, const char *string_field_1024, int blank_lines)
1768 {
1769 if (Mission_save_format != FSO_FORMAT_RETAIL) {
1770 if ((*string_field_640 != '\0') || (*string_field_1024 != '\0')) {
1771 while (blank_lines-- > 0)
1772 fout("\n");
1773 }
1774
1775 if (*string_field_640 != '\0') {
1776 fout("\n%s %s", expected_string_640, string_field_640);
1777 }
1778
1779 if (*string_field_1024 != '\0') {
1780 fout("\n%s %s", expected_string_1024, string_field_1024);
1781 }
1782 }
1783 }
1784
save_cutscenes()1785 int CFred_mission_save::save_cutscenes()
1786 {
1787 char type[NAME_LENGTH];
1788 SCP_string sexp_out;
1789
1790 // Let's just assume it has them for now -
1791 if (!(The_mission.cutscenes.empty())) {
1792 if (Mission_save_format != FSO_FORMAT_RETAIL) {
1793 if (optional_string_fred("#Cutscenes")) {
1794 parse_comments(2);
1795 } else {
1796 fout_version("\n\n#Cutscenes\n\n");
1797 }
1798
1799 for (uint i = 0; i < The_mission.cutscenes.size(); i++) {
1800 if (strlen(The_mission.cutscenes[i].filename)) {
1801 // determine the name of this cutscene type
1802 switch (The_mission.cutscenes[i].type) {
1803 case MOVIE_PRE_FICTION:
1804 strcpy_s(type, "$Fiction Viewer Cutscene:");
1805 break;
1806 case MOVIE_PRE_CMD_BRIEF:
1807 strcpy_s(type, "$Command Brief Cutscene:");
1808 break;
1809 case MOVIE_PRE_BRIEF:
1810 strcpy_s(type, "$Briefing Cutscene:");
1811 break;
1812 case MOVIE_PRE_GAME:
1813 strcpy_s(type, "$Pre-game Cutscene:");
1814 break;
1815 case MOVIE_PRE_DEBRIEF:
1816 strcpy_s(type, "$Debriefing Cutscene:");
1817 break;
1818 case MOVIE_END_CAMPAIGN:
1819 strcpy_s(type, "$Campaign End Cutscene:");
1820 break;
1821 default:
1822 Int3();
1823 continue;
1824 }
1825
1826 if (optional_string_fred(type)) {
1827 parse_comments();
1828 fout(" %s", The_mission.cutscenes[i].filename);
1829 } else {
1830 fout_version("%s %s\n", type, The_mission.cutscenes[i].filename);
1831 }
1832
1833 required_string_fred("+formula:");
1834 parse_comments();
1835 convert_sexp_to_string(sexp_out, The_mission.cutscenes[i].formula, SEXP_SAVE_MODE);
1836 fout(" %s", sexp_out.c_str());
1837 }
1838 }
1839 required_string_fred("#end");
1840 parse_comments();
1841 } else {
1842 MessageBox(nullptr, "Warning: This mission contains cutscene data, but you are saving in the retail mission format. This information will be lost.", "Incompatibility with retail mission format", MB_OK);
1843 }
1844 }
1845
1846 fso_comment_pop(true);
1847 return err;
1848 }
1849
save_debriefing()1850 int CFred_mission_save::save_debriefing()
1851 {
1852 int j, i;
1853 SCP_string sexp_out;
1854
1855 for (j = 0; j < Num_teams; j++) {
1856
1857 Debriefing = &Debriefings[j];
1858
1859 required_string_fred("#Debriefing_info");
1860 parse_comments(2);
1861
1862 save_custom_bitmap("$Background 640:", "$Background 1024:", Debriefing->background[GR_640], Debriefing->background[GR_1024], 1);
1863
1864 required_string_fred("$Num stages:");
1865 parse_comments(2);
1866 fout(" %d", Debriefing->num_stages);
1867
1868 for (i = 0; i < Debriefing->num_stages; i++) {
1869 required_string_fred("$Formula:");
1870 parse_comments(2);
1871 convert_sexp_to_string(sexp_out, Debriefing->stages[i].formula, SEXP_SAVE_MODE);
1872 fout(" %s", sexp_out.c_str());
1873
1874 // XSTR
1875 required_string_fred("$Multi text");
1876 parse_comments();
1877 fout_ext("\n ", "%s", Debriefing->stages[i].text.c_str());
1878
1879 required_string_fred("$end_multi_text");
1880 parse_comments();
1881
1882 if (!drop_white_space(Debriefing->stages[i].voice)[0]) {
1883 strcpy_s(Debriefing->stages[i].voice, "None");
1884 }
1885
1886 required_string_fred("$Voice:");
1887 parse_comments();
1888 fout(" %s", Debriefing->stages[i].voice);
1889
1890 // XSTR
1891 required_string_fred("$Recommendation text:");
1892 parse_comments();
1893 fout_ext("\n ", "%s", Debriefing->stages[i].recommendation_text.c_str());
1894
1895 required_string_fred("$end_multi_text");
1896 parse_comments();
1897
1898 fso_comment_pop();
1899 }
1900 }
1901
1902 fso_comment_pop(true);
1903
1904 return err;
1905 }
1906
save_events()1907 int CFred_mission_save::save_events()
1908 {
1909 SCP_string sexp_out;
1910 int i, j, add_flag;
1911
1912 fred_parse_flag = 0;
1913 required_string_fred("#Events");
1914 parse_comments(2);
1915 fout("\t\t;! %d total\n", Num_mission_events);
1916
1917 for (i = 0; i < Num_mission_events; i++) {
1918 required_string_either_fred("$Formula:", "#Goals");
1919 required_string_fred("$Formula:");
1920 parse_comments(i ? 2 : 1);
1921 convert_sexp_to_string(sexp_out, Mission_events[i].formula, SEXP_SAVE_MODE);
1922 fout(" %s", sexp_out.c_str());
1923
1924 if (*Mission_events[i].name) {
1925 if (optional_string_fred("+Name:", "$Formula:")) {
1926 parse_comments();
1927 } else {
1928 fout("\n+Name:");
1929 }
1930
1931 fout(" %s", Mission_events[i].name);
1932 }
1933
1934 if (optional_string_fred("+Repeat Count:", "$Formula:")) {
1935 parse_comments();
1936 } else {
1937 fout("\n+Repeat Count:");
1938 }
1939
1940 // if we have a trigger count but no repeat count, we want the event to loop until it has triggered enough times
1941 if (Mission_events[i].repeat_count == 1 && Mission_events[i].trigger_count != 1) {
1942 fout(" -1");
1943 } else {
1944 fout(" %d", Mission_events[i].repeat_count);
1945 }
1946
1947 if (Mission_save_format != FSO_FORMAT_RETAIL && Mission_events[i].trigger_count != 1) {
1948 fso_comment_push(";;FSO 3.6.11;;");
1949 if (optional_string_fred("+Trigger Count:", "$Formula:")) {
1950 parse_comments();
1951 } else {
1952 fout_version("\n+Trigger Count:");
1953 }
1954 fso_comment_pop();
1955
1956 fout(" %d", Mission_events[i].trigger_count);
1957 }
1958
1959 if (optional_string_fred("+Interval:", "$Formula:")) {
1960 parse_comments();
1961 } else {
1962 fout("\n+Interval:");
1963 }
1964
1965 fout(" %d", Mission_events[i].interval);
1966
1967 if (Mission_events[i].score != 0) {
1968 if (optional_string_fred("+Score:", "$Formula:")) {
1969 parse_comments();
1970 } else {
1971 fout("\n+Score:");
1972 }
1973 fout(" %d", Mission_events[i].score);
1974 }
1975
1976 if (Mission_events[i].chain_delay >= 0) {
1977 if (optional_string_fred("+Chained:", "$Formula:")) {
1978 parse_comments();
1979 } else {
1980 fout("\n+Chained:");
1981 }
1982
1983 fout(" %d", Mission_events[i].chain_delay);
1984 }
1985
1986 //XSTR
1987 if (Mission_events[i].objective_text) {
1988 if (optional_string_fred("+Objective:", "$Formula:")) {
1989 parse_comments();
1990 } else {
1991 fout("\n+Objective:");
1992 }
1993
1994 fout_ext(" ", "%s", Mission_events[i].objective_text);
1995 }
1996
1997 //XSTR
1998 if (Mission_events[i].objective_key_text) {
1999 if (optional_string_fred("+Objective key:", "$Formula:")) {
2000 parse_comments();
2001 } else {
2002 fout("\n+Objective key:");
2003 }
2004
2005 fout_ext(" ", "%s", Mission_events[i].objective_key_text);
2006 }
2007
2008 // save team
2009 if (Mission_events[i].team >= 0) {
2010 if (optional_string_fred("+Team:")) {
2011 parse_comments();
2012 } else {
2013 fout("\n+Team:");
2014 }
2015 fout(" %d", Mission_events[i].team);
2016 }
2017
2018 // save flags, if any
2019 if (Mission_save_format != FSO_FORMAT_RETAIL) {
2020 // we need to lazily-write the tag because we should only write it if there are also flags to write
2021 // (some of the flags are transient, internal flags)
2022 bool wrote_tag = false;
2023
2024 for (j = 0; j < Num_mission_event_flags; ++j) {
2025 if (Mission_events[i].flags & Mission_event_flags[j].def) {
2026 if (!wrote_tag) {
2027 wrote_tag = true;
2028
2029 fso_comment_push(";;FSO 20.0.0;;");
2030 if (optional_string_fred("+Event Flags: (", "$Formula:")) {
2031 parse_comments();
2032 }
2033 else {
2034 fout_version("\n+Event Flags: (");
2035 }
2036 fso_comment_pop();
2037 }
2038
2039 fout(" \"%s\"", Mission_event_flags[j].name);
2040 }
2041 }
2042
2043 if (wrote_tag)
2044 fout(" )");
2045 }
2046
2047 if (Mission_save_format != FSO_FORMAT_RETAIL && Mission_events[i].mission_log_flags != 0) {
2048 fso_comment_push(";;FSO 3.6.11;;");
2049 if (optional_string_fred("+Event Log Flags: (", "$Formula:")) {
2050 parse_comments();
2051 } else {
2052 fout_version("\n+Event Log Flags: (");
2053 }
2054 fso_comment_pop();
2055
2056 for (j = 0; j < MAX_MISSION_EVENT_LOG_FLAGS; j++) {
2057 add_flag = 1 << j;
2058 if (Mission_events[i].mission_log_flags & add_flag) {
2059 fout(" \"%s\"", Mission_event_log_flags[j]);
2060 }
2061 }
2062 fout(" )");
2063 }
2064
2065 // save event annotations
2066 if (Mission_save_format != FSO_FORMAT_RETAIL && !Event_annotations.empty())
2067 {
2068 bool at_least_one = false;
2069 fso_comment_push(";;FSO 21.0.0;;");
2070 event_annotation default_ea;
2071
2072 // see if there is an annotation for this event
2073 for (const auto &ea : Event_annotations)
2074 {
2075 if (ea.path.empty() || ea.path.front() != i)
2076 continue;
2077
2078 if (!at_least_one)
2079 {
2080 if (optional_string_fred("$Annotations Start", "$Formula:"))
2081 parse_comments();
2082 else
2083 fout_version("\n$Annotations Start");
2084 at_least_one = true;
2085 }
2086
2087 if (ea.comment != default_ea.comment)
2088 {
2089 if (optional_string_fred("+Comment:", "$Formula:"))
2090 parse_comments();
2091 else
2092 fout_version("\n+Comment:");
2093
2094 auto copy = ea.comment;
2095 lcl_fred_replace_stuff(copy);
2096 fout(" %s", copy.c_str());
2097
2098 if (optional_string_fred("$end_multi_text", "$Formula:"))
2099 parse_comments();
2100 else
2101 fout_version("\n$end_multi_text");
2102 }
2103
2104 if (ea.r != default_ea.r || ea.g != default_ea.g || ea.b != default_ea.b)
2105 {
2106 if (optional_string_fred("+Background Color:", "$Formula:"))
2107 parse_comments();
2108 else
2109 fout_version("\n+Background Color:");
2110
2111 fout(" %d, %d, %d", ea.r, ea.g, ea.b);
2112 }
2113
2114 if (ea.path.size() > 1)
2115 {
2116 if (optional_string_fred("+Path:", "$Formula:"))
2117 parse_comments();
2118 else
2119 fout_version("\n+Path:");
2120
2121 bool comma = false;
2122 auto it = ea.path.begin();
2123 for (++it; it != ea.path.end(); ++it)
2124 {
2125 if (comma)
2126 fout(",");
2127 comma = true;
2128 fout(" %d", *it);
2129 }
2130 }
2131 }
2132
2133 if (at_least_one)
2134 {
2135 if (optional_string_fred("$Annotations End", "$Formula:"))
2136 parse_comments();
2137 else
2138 fout_version("\n$Annotations End");
2139 }
2140
2141 fso_comment_pop();
2142 }
2143 }
2144
2145 fso_comment_pop(true);
2146
2147 return err;
2148 }
2149
save_fiction()2150 int CFred_mission_save::save_fiction()
2151 {
2152 if (mission_has_fiction()) {
2153 if (Mission_save_format != FSO_FORMAT_RETAIL) {
2154 if (optional_string_fred("#Fiction Viewer"))
2155 parse_comments(2);
2156 else
2157 fout("\n\n#Fiction Viewer");
2158
2159 // we have multiple stages now, so save them all
2160 for (SCP_vector<fiction_viewer_stage>::iterator stage = Fiction_viewer_stages.begin(); stage != Fiction_viewer_stages.end(); ++stage) {
2161 fout("\n");
2162
2163 // save file
2164 required_string_fred("$File:");
2165 parse_comments();
2166 fout(" %s", stage->story_filename);
2167
2168 // save font
2169 if (strlen(stage->font_filename) > 0) //-V805
2170 {
2171 if (optional_string_fred("$Font:"))
2172 parse_comments();
2173 else
2174 fout("\n$Font:");
2175 fout(" %s", stage->font_filename);
2176 } else
2177 optional_string_fred("$Font:");
2178
2179 // save voice
2180 if (strlen(stage->voice_filename) > 0) //-V805
2181 {
2182 if (optional_string_fred("$Voice:"))
2183 parse_comments();
2184 else
2185 fout("\n$Voice:");
2186 fout(" %s", stage->voice_filename);
2187 } else
2188 optional_string_fred("$Voice:");
2189
2190 // save UI
2191 if (strlen(stage->ui_name) > 0) {
2192 if (optional_string_fred("$UI:"))
2193 parse_comments();
2194 else
2195 fout("\n$UI:");
2196 fout(" %s", stage->ui_name);
2197 } else
2198 optional_string_fred("$UI:");
2199
2200 // save background
2201 save_custom_bitmap("$Background 640:", "$Background 1024:", stage->background[GR_640], stage->background[GR_1024]);
2202
2203 // save sexp formula if we have one
2204 if (stage->formula >= 0 && stage->formula != Locked_sexp_true) {
2205 SCP_string sexp_out;
2206 convert_sexp_to_string(sexp_out, stage->formula, SEXP_SAVE_MODE);
2207
2208 if (optional_string_fred("$Formula:"))
2209 parse_comments();
2210 else
2211 fout("\n$Formula:");
2212 fout(" %s", sexp_out.c_str());
2213 } else
2214 optional_string_fred("$Formula:");
2215 }
2216 } else {
2217 MessageBox(nullptr, "Warning: This mission contains fiction viewer data, but you are saving in the retail mission format.", "Incompatibility with retail mission format", MB_OK);
2218 }
2219 }
2220
2221 fso_comment_pop(true);
2222
2223 return err;
2224 }
2225
save_goals()2226 int CFred_mission_save::save_goals()
2227 {
2228 SCP_string sexp_out;
2229 int i;
2230
2231 fred_parse_flag = 0;
2232 required_string_fred("#Goals");
2233 parse_comments(2);
2234 fout("\t\t;! %d total\n", Num_goals);
2235
2236 for (i = 0; i < Num_goals; i++) {
2237 int type;
2238
2239 required_string_either_fred("$Type:", "#Waypoints");
2240 required_string_fred("$Type:");
2241 parse_comments(i ? 2 : 1);
2242
2243 type = Mission_goals[i].type & GOAL_TYPE_MASK;
2244 fout(" %s", Goal_type_names[type]);
2245
2246 if (*Mission_goals[i].name) {
2247 if (optional_string_fred("+Name:", "$Type:"))
2248 parse_comments();
2249 else
2250 fout("\n+Name:");
2251
2252 fout(" %s", Mission_goals[i].name);
2253 }
2254
2255 // XSTR
2256 required_string_fred("$MessageNew:");
2257 parse_comments();
2258 fout_ext(" ", "%s", Mission_goals[i].message);
2259 fout("\n");
2260 required_string_fred("$end_multi_text");
2261 parse_comments(0);
2262
2263 required_string_fred("$Formula:");
2264 parse_comments();
2265 convert_sexp_to_string(sexp_out, Mission_goals[i].formula, SEXP_SAVE_MODE);
2266 fout(" %s", sexp_out.c_str());
2267
2268 if (Mission_goals[i].type & INVALID_GOAL) {
2269 if (optional_string_fred("+Invalid", "$Type:"))
2270 parse_comments();
2271 else
2272 fout("\n+Invalid");
2273 }
2274
2275 if (Mission_goals[i].flags & MGF_NO_MUSIC) {
2276 if (optional_string_fred("+No music", "$Type:"))
2277 parse_comments();
2278 else
2279 fout("\n+No music");
2280 }
2281
2282 if (Mission_goals[i].score != 0) {
2283 if (optional_string_fred("+Score:", "$Type:"))
2284 parse_comments();
2285 else
2286 fout("\n+Score:");
2287 fout(" %d", Mission_goals[i].score);
2288 }
2289
2290 if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) {
2291 if (optional_string_fred("+Team:", "$Type:"))
2292 parse_comments();
2293 else
2294 fout("\n+Team:");
2295 fout(" %d", Mission_goals[i].team);
2296 }
2297
2298 fso_comment_pop();
2299 }
2300
2301 fso_comment_pop(true);
2302
2303 return err;
2304 }
2305
save_matrix(matrix & m)2306 int CFred_mission_save::save_matrix(matrix &m)
2307 {
2308 fout("\n\t%f, %f, %f,\n", m.vec.rvec.xyz.x, m.vec.rvec.xyz.y, m.vec.rvec.xyz.z);
2309 fout("\t%f, %f, %f,\n", m.vec.uvec.xyz.x, m.vec.uvec.xyz.y, m.vec.uvec.xyz.z);
2310 fout("\t%f, %f, %f", m.vec.fvec.xyz.x, m.vec.fvec.xyz.y, m.vec.fvec.xyz.z);
2311 return 0;
2312 }
2313
save_messages()2314 int CFred_mission_save::save_messages()
2315 {
2316 int i;
2317
2318 fred_parse_flag = 0;
2319 required_string_fred("#Messages");
2320 parse_comments(2);
2321 fout("\t\t;! %d total\n", Num_messages - Num_builtin_messages);
2322
2323 // Goober5000 - special Command info
2324 if (Mission_save_format != FSO_FORMAT_RETAIL) {
2325 if (stricmp(The_mission.command_sender, DEFAULT_COMMAND))
2326 fout("\n$Command Sender: %s", The_mission.command_sender);
2327
2328 if (The_mission.command_persona != Default_command_persona)
2329 fout("\n$Command Persona: %s", Personas[The_mission.command_persona].name);
2330 }
2331
2332 for (i = Num_builtin_messages; i < Num_messages; i++) {
2333 required_string_either_fred("$Name:", "#Reinforcements");
2334 required_string_fred("$Name:");
2335 parse_comments(2);
2336 fout(" %s", Messages[i].name);
2337
2338 // team
2339 required_string_fred("$Team:");
2340 parse_comments(1);
2341 if ((Messages[i].multi_team < 0) || (Messages[i].multi_team >= 2)) {
2342 fout(" %d", -1);
2343 } else {
2344 fout(" %d", Messages[i].multi_team);
2345 }
2346
2347 // XSTR
2348 required_string_fred("$MessageNew:");
2349 parse_comments();
2350 fout_ext(" ", "%s", Messages[i].message);
2351 fout("\n");
2352 required_string_fred("$end_multi_text");
2353 parse_comments(0);
2354
2355 if (Messages[i].persona_index != -1) {
2356 if (optional_string_fred("+Persona:", "$Name:"))
2357 parse_comments();
2358 else
2359 fout("\n+Persona:");
2360
2361 fout(" %s", Personas[Messages[i].persona_index].name);
2362 }
2363
2364 if (Messages[i].avi_info.name) {
2365 if (optional_string_fred("+AVI Name:", "$Name:"))
2366 parse_comments();
2367 else
2368 fout("\n+AVI Name:");
2369
2370 fout(" %s", Messages[i].avi_info.name);
2371 }
2372
2373 if (Messages[i].wave_info.name) {
2374 if (optional_string_fred("+Wave Name:", "$Name:"))
2375 parse_comments();
2376 else
2377 fout("\n+Wave Name:");
2378
2379 fout(" %s", Messages[i].wave_info.name);
2380 }
2381
2382 fso_comment_pop();
2383 }
2384
2385 fso_comment_pop(true);
2386
2387 return err;
2388 }
2389
save_mission_file(char * pathname)2390 int CFred_mission_save::save_mission_file(char *pathname)
2391 {
2392 char backup_name[256], savepath[MAX_PATH_LEN], *p;
2393
2394 strcpy_s(savepath, "");
2395 p = strrchr(pathname, '\\');
2396 if (p) {
2397 *p = '\0';
2398 strcpy_s(savepath, pathname);
2399 *p = '\\';
2400 strcat_s(savepath, "\\");
2401 }
2402 strcat_s(savepath, "saving.xxx");
2403
2404 save_mission_internal(savepath);
2405
2406 if (!err) {
2407 strcpy_s(backup_name, pathname);
2408 if (backup_name[strlen(backup_name) - 4] == '.')
2409 backup_name[strlen(backup_name) - 4] = 0;
2410
2411 strcat_s(backup_name, ".bak");
2412 cf_attrib(pathname, 0, FILE_ATTRIBUTE_READONLY, CF_TYPE_MISSIONS);
2413 cf_delete(backup_name, CF_TYPE_MISSIONS);
2414 cf_rename(pathname, backup_name, CF_TYPE_MISSIONS);
2415 cf_rename(savepath, pathname, CF_TYPE_MISSIONS);
2416 }
2417
2418 return err;
2419 }
2420
save_mission_info()2421 int CFred_mission_save::save_mission_info()
2422 {
2423 required_string_fred("#Mission Info");
2424 parse_comments(0);
2425
2426 required_string_fred("$Version:");
2427 parse_comments(2);
2428 fout(" %.2f", FRED_MISSION_VERSION);
2429
2430 // XSTR
2431 required_string_fred("$Name:");
2432 parse_comments();
2433 fout_ext(" ", "%s", The_mission.name);
2434
2435 required_string_fred("$Author:");
2436 parse_comments();
2437 fout(" %s", The_mission.author);
2438
2439 required_string_fred("$Created:");
2440 parse_comments();
2441 fout(" %s", The_mission.created);
2442
2443 required_string_fred("$Modified:");
2444 parse_comments();
2445 fout(" %s", The_mission.modified);
2446
2447 required_string_fred("$Notes:");
2448 parse_comments();
2449 fout("\n%s", The_mission.notes);
2450
2451 required_string_fred("$End Notes:");
2452 parse_comments(0);
2453
2454 // XSTR
2455 required_string_fred("$Mission Desc:");
2456 parse_comments(2);
2457 fout_ext("\n", "%s", The_mission.mission_desc);
2458 fout("\n");
2459
2460 required_string_fred("$end_multi_text");
2461 parse_comments(0);
2462
2463 #if 0
2464 if (optional_string_fred("+Game Type:"))
2465 parse_comments(2);
2466 else
2467 fout("\n\n+Game Type:");
2468 fout("\n%s", Game_types[The_mission.game_type]);
2469 #endif
2470
2471 if (optional_string_fred("+Game Type Flags:")) {
2472 parse_comments(1);
2473 } else {
2474 fout("\n+Game Type Flags:");
2475 }
2476
2477 fout(" %d", The_mission.game_type);
2478
2479 if (optional_string_fred("+Flags:")) {
2480 parse_comments(1);
2481 } else {
2482 fout("\n+Flags:");
2483 }
2484
2485 fout(" %" PRIu64, The_mission.flags.to_u64());
2486
2487 // maybe write out Nebula intensity
2488 if (The_mission.flags[Mission::Mission_Flags::Fullneb]) {
2489 Assert(Neb2_awacs > 0.0f);
2490 fout("\n+NebAwacs: %f\n", Neb2_awacs);
2491
2492 // storm name
2493 fout("\n+Storm: %s\n", Mission_parse_storm_name);
2494 }
2495
2496 // Goober5000
2497 if (Mission_save_format != FSO_FORMAT_RETAIL) {
2498 // write out the nebula clipping multipliers
2499 fout("\n+Fog Near Mult: %f\n", Neb2_fog_near_mult);
2500 fout("\n+Fog Far Mult: %f\n", Neb2_fog_far_mult);
2501
2502 if (The_mission.contrail_threshold != CONTRAIL_THRESHOLD_DEFAULT) {
2503 fout("\n$Contrail Speed Threshold: %d\n", The_mission.contrail_threshold);
2504 }
2505 }
2506
2507 // For multiplayer missions -- write out the number of player starts and number of respawns
2508 if (The_mission.game_type & MISSION_TYPE_MULTI) {
2509 if (optional_string_fred("+Num Players:"))
2510 parse_comments(2);
2511 else
2512 fout("\n+Num Players:");
2513
2514 fout(" %d", Player_starts);
2515
2516 if (optional_string_fred("+Num Respawns:"))
2517 parse_comments(2);
2518 else
2519 fout("\n+Num Respawns:");
2520
2521 fout(" %d", The_mission.num_respawns);
2522
2523 if (Mission_save_format != FSO_FORMAT_RETAIL) {
2524 fso_comment_push(";;FSO 3.6.11;;");
2525 if (optional_string_fred("+Max Respawn Time:"))
2526 parse_comments(2);
2527 else {
2528 fout_version("\n+Max Respawn Time:");
2529 }
2530 fso_comment_pop();
2531
2532 fout(" %d", The_mission.max_respawn_delay);
2533 } else {
2534 bypass_comment(";;FSO 3.6.11;; +Max Respawn Time:");
2535 }
2536 }
2537
2538 if (Mission_save_format == FSO_FORMAT_RETAIL) {
2539 if (optional_string_fred("+Red Alert:"))
2540 parse_comments(2);
2541 else
2542 fout("\n+Red Alert:");
2543
2544 fout(" %d", (The_mission.flags[Mission::Mission_Flags::Red_alert]) ? 1 : 0);
2545 }
2546
2547 if (Mission_save_format == FSO_FORMAT_RETAIL) //-V581
2548 {
2549 if (optional_string_fred("+Scramble:"))
2550 parse_comments(2);
2551 else
2552 fout("\n+Scramble:");
2553
2554 fout(" %d", (The_mission.flags[Mission::Mission_Flags::Scramble]) ? 1 : 0);
2555 }
2556
2557 if (optional_string_fred("+Disallow Support:")) {
2558 parse_comments(1);
2559 } else {
2560 fout("\n+Disallow Support:");
2561 }
2562 // this is compatible with non-SCP variants - Goober5000
2563 fout(" %d", (The_mission.support_ships.max_support_ships == 0) ? 1 : 0);
2564
2565 // here be WMCoolmon's hull and subsys repair stuff
2566 if (Mission_save_format != FSO_FORMAT_RETAIL) {
2567 if (optional_string_fred("+Hull Repair Ceiling:")) {
2568 parse_comments(1);
2569 } else {
2570 fout("\n+Hull Repair Ceiling:");
2571 }
2572 fout(" %f", The_mission.support_ships.max_hull_repair_val);
2573
2574 if (optional_string_fred("+Subsystem Repair Ceiling:")) {
2575 parse_comments(1);
2576 } else {
2577 fout("\n+Subsystem Repair Ceiling:");
2578 }
2579 fout(" %f", The_mission.support_ships.max_subsys_repair_val);
2580 }
2581
2582 if (Mission_all_attack) {
2583 if (optional_string_fred("+All Teams Attack")) {
2584 parse_comments();
2585 } else {
2586 fout("\n+All Teams Attack");
2587 }
2588 }
2589
2590 if (Entry_delay_time) {
2591 if (optional_string_fred("+Player Entry Delay:"))
2592 parse_comments(2);
2593 else
2594 fout("\n\n+Player Entry Delay:");
2595
2596 fout("\n%f", f2fl(Entry_delay_time));
2597 }
2598
2599 if (optional_string_fred("+Viewer pos:")) {
2600 parse_comments(2);
2601 } else {
2602 fout("\n\n+Viewer pos:");
2603 }
2604
2605 save_vector(view_pos);
2606
2607 if (optional_string_fred("+Viewer orient:")) {
2608 parse_comments();
2609 } else {
2610 fout("\n+Viewer orient:");
2611 }
2612
2613 save_matrix(view_orient);
2614
2615 // squadron info
2616 if (!(The_mission.game_type & MISSION_TYPE_MULTI) && (strlen(The_mission.squad_name) > 0)) { //-V805
2617 // squad name
2618 fout("\n+SquadReassignName: %s", The_mission.squad_name);
2619
2620 // maybe squad logo
2621 if (strlen(The_mission.squad_filename) > 0) { //-V805
2622 fout("\n+SquadReassignLogo: %s", The_mission.squad_filename);
2623 }
2624 }
2625
2626 // Goober5000 - special wing info
2627 if (Mission_save_format != FSO_FORMAT_RETAIL) {
2628 int i;
2629 fout("\n");
2630
2631 // starting wings
2632 if (strcmp(Starting_wing_names[0], "Alpha") || strcmp(Starting_wing_names[1], "Beta") || strcmp(Starting_wing_names[2], "Gamma")) {
2633 fout("\n$Starting wing names: ( ");
2634
2635 for (i = 0; i < MAX_STARTING_WINGS; i++) {
2636 fout("\"%s\" ", Starting_wing_names[i]);
2637 }
2638
2639 fout(")");
2640 }
2641
2642 // squadron wings
2643 if (strcmp(Squadron_wing_names[0], "Alpha") || strcmp(Squadron_wing_names[1], "Beta") || strcmp(Squadron_wing_names[2], "Gamma") || strcmp(Squadron_wing_names[3], "Delta") || strcmp(Squadron_wing_names[4], "Epsilon")) {
2644 fout("\n$Squadron wing names: ( ");
2645
2646 for (i = 0; i < MAX_SQUADRON_WINGS; i++) {
2647 fout("\"%s\" ", Squadron_wing_names[i]);
2648 }
2649
2650 fout(")");
2651 }
2652
2653 // tvt wings
2654 if (strcmp(TVT_wing_names[0], "Alpha") || strcmp(TVT_wing_names[1], "Zeta")) {
2655 fout("\n$Team-versus-team wing names: ( ");
2656
2657 for (i = 0; i < MAX_TVT_WINGS; i++) {
2658 fout("\"%s\" ", TVT_wing_names[i]);
2659 }
2660
2661 fout(")");
2662 }
2663 }
2664
2665 save_custom_bitmap("$Load Screen 640:", "$Load Screen 1024:", The_mission.loading_screen[GR_640], The_mission.loading_screen[GR_1024], 1);
2666
2667 // Phreak's skybox stuff
2668 if (strlen(The_mission.skybox_model) > 0) //-V805
2669 {
2670 char out_str[NAME_LENGTH];
2671 char *period;
2672
2673 // kill off any extension, we will add one here
2674 strcpy_s(out_str, The_mission.skybox_model);
2675 period = strrchr(out_str, '.');
2676 if (period != NULL)
2677 *period = 0;
2678
2679 fso_comment_push(";;FSO 3.6.0;;");
2680 if (optional_string_fred("$Skybox Model:")) {
2681 parse_comments(2);
2682 fout(" %s.pof", out_str);
2683 } else {
2684 fout_version("\n\n$Skybox Model: %s.pof", out_str);
2685 }
2686 fso_comment_pop();
2687 } else {
2688 bypass_comment(";;FSO 3.6.0;; $Skybox Model:");
2689 }
2690
2691 // orientation?
2692 if ((strlen(The_mission.skybox_model) > 0) && !vm_matrix_same(&vmd_identity_matrix, &The_mission.skybox_orientation)) {
2693 fso_comment_push(";;FSO 3.6.14;;");
2694 if (optional_string_fred("+Skybox Orientation:")) {
2695 parse_comments(1);
2696 save_matrix(The_mission.skybox_orientation);
2697 } else {
2698 fout_version("\n+Skybox Orientation:");
2699 save_matrix(The_mission.skybox_orientation);
2700 }
2701 fso_comment_pop();
2702 } else {
2703 bypass_comment(";;FSO 3.6.14;; +Skybox Orientation:");
2704 }
2705
2706 // are skybox flags in use?
2707 if (The_mission.skybox_flags != DEFAULT_NMODEL_FLAGS) {
2708 //char out_str[4096];
2709 fso_comment_push(";;FSO 3.6.11;;");
2710 if (optional_string_fred("+Skybox Flags:")) {
2711 parse_comments(1);
2712 fout(" %d", The_mission.skybox_flags);
2713 } else {
2714 fout_version("\n+Skybox Flags: %d", The_mission.skybox_flags);
2715 }
2716 fso_comment_pop();
2717 } else {
2718 bypass_comment(";;FSO 3.6.11;; +Skybox Flags:");
2719 }
2720
2721 // Goober5000's AI profile stuff
2722 int profile_index = AI_PROFILES_INDEX(The_mission.ai_profile);
2723 Assert(profile_index >= 0 && profile_index < MAX_AI_PROFILES);
2724
2725 fso_comment_push(";;FSO 3.6.9;;");
2726 if (optional_string_fred("$AI Profile:")) {
2727 parse_comments(2);
2728 fout(" %s", The_mission.ai_profile->profile_name);
2729 } else {
2730 fout_version("\n\n$AI Profile: %s", The_mission.ai_profile->profile_name);
2731 }
2732 fso_comment_pop();
2733
2734 // sound environment (EFX/EAX) - taylor
2735 sound_env *m_env = &The_mission.sound_environment;
2736 if ((m_env->id >= 0) && (m_env->id < (int) EFX_presets.size())) {
2737 EFXREVERBPROPERTIES *prop = &EFX_presets[m_env->id];
2738
2739 fso_comment_push(";;FSO 3.6.12;;");
2740
2741 fout_version("\n\n$Sound Environment: %s", prop->name.c_str());
2742
2743 if (m_env->volume != prop->flGain) {
2744 fout_version("\n+Volume: %f", m_env->volume);
2745 }
2746
2747 if (m_env->damping != prop->flDecayHFRatio) {
2748 fout_version("\n+Damping: %f", m_env->damping);
2749 }
2750
2751 if (m_env->decay != prop->flDecayTime) {
2752 fout_version("\n+Decay Time: %f", m_env->decay);
2753 }
2754
2755 fso_comment_pop();
2756 }
2757
2758 return err;
2759 }
2760
save_mission_internal(const char * pathname)2761 void CFred_mission_save::save_mission_internal(const char *pathname)
2762 {
2763 CTime t;
2764
2765 t = CTime::GetCurrentTime();
2766 strcpy_s(The_mission.modified, t.Format("%x at %X"));
2767
2768 reset_parse();
2769 fred_parse_flag = 0;
2770 fp = cfopen(pathname, "wt", CFILE_NORMAL, CF_TYPE_MISSIONS);
2771 if (!fp) {
2772 nprintf(("Error", "Can't open mission file to save.\n"));
2773 err = -1;
2774 return;
2775 }
2776
2777 // Goober5000
2778 convert_special_tags_to_retail();
2779
2780 if (save_mission_info())
2781 err = -2;
2782 else if (save_plot_info())
2783 err = -3;
2784 else if (save_variables())
2785 err = -3;
2786 else if (save_containers())
2787 err = -3;
2788 // else if (save_briefing_info())
2789 // err = -4;
2790 else if (save_cutscenes())
2791 err = -4;
2792 else if (save_fiction())
2793 err = -3;
2794 else if (save_cmd_briefs())
2795 err = -4;
2796 else if (save_briefing())
2797 err = -4;
2798 else if (save_debriefing())
2799 err = -5;
2800 else if (save_players())
2801 err = -6;
2802 else if (save_objects())
2803 err = -7;
2804 else if (save_wings())
2805 err = -8;
2806 else if (save_events())
2807 err = -9;
2808 else if (save_goals())
2809 err = -10;
2810 else if (save_waypoints())
2811 err = -11;
2812 else if (save_messages())
2813 err = -12;
2814 else if (save_reinforcements())
2815 err = -13;
2816 else if (save_bitmaps())
2817 err = -14;
2818 else if (save_asteroid_fields())
2819 err = -15;
2820 else if (save_music())
2821 err = -16;
2822 else {
2823 required_string_fred("#End");
2824 parse_comments(2);
2825 token_found = NULL;
2826 parse_comments();
2827 fout("\n");
2828 }
2829
2830 cfclose(fp);
2831 if (err)
2832 mprintf(("Mission saving error code #%d\n", err));
2833 }
2834
save_music()2835 int CFred_mission_save::save_music()
2836 {
2837 required_string_fred("#Music");
2838 parse_comments(2);
2839
2840 required_string_fred("$Event Music:");
2841 parse_comments(2);
2842 if (Current_soundtrack_num < 0)
2843 fout(" None");
2844 else
2845 fout(" %s", Soundtracks[Current_soundtrack_num].name);
2846
2847 // Goober5000 - save using the special comment prefix
2848 if (stricmp(The_mission.substitute_event_music_name, "None")) {
2849 fso_comment_push(";;FSO 3.6.9;;");
2850 if (optional_string_fred("$Substitute Event Music:")) {
2851 parse_comments(1);
2852 fout(" %s", The_mission.substitute_event_music_name);
2853 } else {
2854 fout_version("\n$Substitute Event Music: %s", The_mission.substitute_event_music_name);
2855 }
2856 fso_comment_pop();
2857 } else {
2858 bypass_comment(";;FSO 3.6.9;; $Substitute Event Music:");
2859 }
2860
2861 required_string_fred("$Briefing Music:");
2862 parse_comments();
2863 if (Mission_music[SCORE_BRIEFING] < 0)
2864 fout(" None");
2865 else
2866 fout(" %s", Spooled_music[Mission_music[SCORE_BRIEFING]].name);
2867
2868 // Goober5000 - save using the special comment prefix
2869 if (stricmp(The_mission.substitute_briefing_music_name, "None")) {
2870 fso_comment_push(";;FSO 3.6.9;;");
2871 if (optional_string_fred("$Substitute Briefing Music:")) {
2872 parse_comments(1);
2873 fout(" %s", The_mission.substitute_briefing_music_name);
2874 } else {
2875 fout_version("\n$Substitute Briefing Music: %s", The_mission.substitute_briefing_music_name);
2876 }
2877 fso_comment_pop();
2878 } else {
2879 bypass_comment(";;FSO 3.6.9;; $Substitute Briefing Music:");
2880 }
2881
2882 // avoid keeping the old one around
2883 bypass_comment(";;FSO 3.6.8;; $Substitute Music:");
2884
2885 // old stuff
2886 if (Mission_music[SCORE_DEBRIEF_SUCCESS] != event_music_get_spooled_music_index("Success")) {
2887 if (optional_string_fred("$Debriefing Success Music:")) {
2888 parse_comments(1);
2889 } else {
2890 fout("\n$Debriefing Success Music:");
2891 }
2892 fout(" %s", Mission_music[SCORE_DEBRIEF_SUCCESS] < 0 ? "None" : Spooled_music[Mission_music[SCORE_DEBRIEF_SUCCESS]].name);
2893 }
2894 if (Mission_save_format != FSO_FORMAT_RETAIL && Mission_music[SCORE_DEBRIEF_AVERAGE] != event_music_get_spooled_music_index("Average")) {
2895 if (optional_string_fred("$Debriefing Average Music:")) {
2896 parse_comments(1);
2897 } else {
2898 fout("\n$Debriefing Average Music:");
2899 }
2900 fout(" %s", Mission_music[SCORE_DEBRIEF_AVERAGE] < 0 ? "None" : Spooled_music[Mission_music[SCORE_DEBRIEF_AVERAGE]].name);
2901 }
2902 if (Mission_music[SCORE_DEBRIEF_FAIL] != event_music_get_spooled_music_index("Failure")) {
2903 if (optional_string_fred("$Debriefing Fail Music:")) {
2904 parse_comments(1);
2905 } else {
2906 fout("\n$Debriefing Fail Music:");
2907 }
2908 fout(" %s", Mission_music[SCORE_DEBRIEF_FAIL] < 0 ? "None" : Spooled_music[Mission_music[SCORE_DEBRIEF_FAIL]].name);
2909 }
2910
2911 // Goober5000 - save using the special comment prefix
2912 if (mission_has_fiction() && Mission_music[SCORE_FICTION_VIEWER] >= 0) {
2913 fso_comment_push(";;FSO 3.6.11;;");
2914 if (optional_string_fred("$Fiction Viewer Music:")) {
2915 parse_comments(1);
2916 fout(" %s", Spooled_music[Mission_music[SCORE_FICTION_VIEWER]].name);
2917 } else {
2918 fout_version("\n$Fiction Viewer Music: %s", Spooled_music[Mission_music[SCORE_FICTION_VIEWER]].name);
2919 }
2920 fso_comment_pop();
2921 } else {
2922 bypass_comment(";;FSO 3.6.11;; $Fiction Viewer Music:");
2923 }
2924
2925 fso_comment_pop(true);
2926
2927 return err;
2928 }
2929
save_warp_params(WarpDirection direction,ship * shipp)2930 int CFred_mission_save::save_warp_params(WarpDirection direction, ship *shipp)
2931 {
2932 if (Mission_save_format == FSO_FORMAT_RETAIL)
2933 return err;
2934
2935 // for writing to file; c.f. parse_warp_params
2936 const char *prefix = (direction == WarpDirection::WARP_IN) ? "$Warpin" : "$Warpout";
2937
2938 WarpParams *shipp_params, *sip_params;
2939 if (direction == WarpDirection::WARP_IN)
2940 {
2941 // if exactly the same params used, no need to output anything
2942 if (shipp->warpin_params_index == Ship_info[shipp->ship_info_index].warpin_params_index)
2943 return err;
2944
2945 shipp_params = &Warp_params[shipp->warpin_params_index];
2946 sip_params = &Warp_params[Ship_info[shipp->ship_info_index].warpin_params_index];
2947 }
2948 else
2949 {
2950 // if exactly the same params used, no need to output anything
2951 if (shipp->warpout_params_index == Ship_info[shipp->ship_info_index].warpout_params_index)
2952 return err;
2953
2954 shipp_params = &Warp_params[shipp->warpout_params_index];
2955 sip_params = &Warp_params[Ship_info[shipp->ship_info_index].warpout_params_index];
2956 }
2957
2958 if (shipp_params->warp_type != sip_params->warp_type)
2959 {
2960 // is it a fireball?
2961 if (shipp_params->warp_type & WT_DEFAULT_WITH_FIREBALL)
2962 {
2963 fout("\n%s type: %s", prefix, Fireball_info[shipp_params->warp_type & WT_FLAG_MASK].unique_id);
2964 }
2965 // probably a warp type
2966 else if (shipp_params->warp_type >= 0 && shipp_params->warp_type < Num_warp_types)
2967 {
2968 fout("\n%s type: %s", prefix, Warp_types[shipp_params->warp_type]);
2969 }
2970 }
2971
2972 if (shipp_params->snd_start != sip_params->snd_start)
2973 {
2974 if (shipp_params->snd_start.isValid())
2975 fout("\n%s Start Sound: %s", prefix, gamesnd_get_game_sound(shipp_params->snd_start)->name.c_str());
2976 }
2977
2978 if (shipp_params->snd_end != sip_params->snd_end)
2979 {
2980 if (shipp_params->snd_end.isValid())
2981 fout("\n%s End Sound: %s", prefix, gamesnd_get_game_sound(shipp_params->snd_end)->name.c_str());
2982 }
2983
2984 if (direction == WarpDirection::WARP_OUT && shipp_params->warpout_engage_time != sip_params->warpout_engage_time)
2985 {
2986 if (shipp_params->warpout_engage_time > 0)
2987 fout("\n%s engage time: %.2f", prefix, i2fl(shipp_params->warpout_engage_time) / 1000.0f);
2988 }
2989
2990 if (shipp_params->speed != sip_params->speed)
2991 {
2992 if (shipp_params->speed > 0.0f)
2993 fout("\n%s speed: %.2f", prefix, shipp_params->speed);
2994 }
2995
2996 if (shipp_params->time != sip_params->time)
2997 {
2998 if (shipp_params->time > 0)
2999 fout("\n%s time: %.2f", prefix, i2fl(shipp_params->time) / 1000.0f);
3000 }
3001
3002 if (shipp_params->accel_exp != sip_params->accel_exp)
3003 {
3004 if (shipp_params->accel_exp > 0.0f)
3005 fout("\n%s %s exp: %.2f", prefix, direction == WarpDirection::WARP_IN ? "decel" : "accel", shipp_params->accel_exp);
3006 }
3007
3008 if (shipp_params->radius != sip_params->radius)
3009 {
3010 if (shipp_params->radius > 0.0f)
3011 fout("\n%s radius: %.2f", prefix, shipp_params->radius);
3012 }
3013
3014 if (stricmp(shipp_params->anim, sip_params->anim) != 0)
3015 {
3016 if (strlen(shipp_params->anim) > 0)
3017 fout("\n%s animation: %s", prefix, shipp_params->anim);
3018 }
3019
3020 if (direction == WarpDirection::WARP_OUT && shipp_params->warpout_player_speed != sip_params->warpout_player_speed)
3021 {
3022 if (shipp_params->warpout_player_speed > 0.0f)
3023 fout("\n$Player warpout speed: %.2f", shipp_params->warpout_player_speed);
3024 }
3025
3026 return err;
3027 }
3028
save_objects()3029 int CFred_mission_save::save_objects()
3030 {
3031 SCP_string sexp_out;
3032 int i, z;
3033 ai_info *aip;
3034 object *objp;
3035 ship *shipp;
3036 ship_info *sip;
3037
3038 required_string_fred("#Objects");
3039 parse_comments(2);
3040 fout("\t\t;! %d total\n", ship_get_num_ships());
3041
3042 for (i = z = 0; i < MAX_SHIPS; i++) {
3043 if (Ships[i].objnum < 0) {
3044 continue;
3045 }
3046
3047 auto j = Objects[Ships[i].objnum].type;
3048 if ((j != OBJ_SHIP) && (j != OBJ_START)) {
3049 continue;
3050 }
3051
3052 shipp = &Ships[i];
3053 objp = &Objects[shipp->objnum];
3054 sip = &Ship_info[shipp->ship_info_index];
3055 required_string_either_fred("$Name:", "#Wings");
3056 required_string_fred("$Name:");
3057 parse_comments(z ? 2 : 1);
3058 fout(" %s\t\t;! Object #%d", shipp->ship_name, i);
3059
3060 // Display name
3061 // The display name is only written if there was one at the start to avoid introducing inconsistencies
3062 if (Mission_save_format != FSO_FORMAT_RETAIL && shipp->has_display_name()) {
3063 char truncated_name[NAME_LENGTH];
3064 strcpy_s(truncated_name, shipp->ship_name);
3065 end_string_at_first_hash_symbol(truncated_name);
3066
3067 // Also, the display name is not written if it's just the truncation of the name at the hash
3068 if (strcmp(shipp->get_display_name(), truncated_name) != 0) {
3069 fout("\n$Display name:");
3070 fout_ext(" ", "%s", shipp->display_name.c_str());
3071 }
3072 }
3073
3074 required_string_fred("\n$Class:");
3075 parse_comments(0);
3076 fout(" %s", Ship_info[shipp->ship_info_index].name);
3077
3078 //alt classes stuff
3079 if (Mission_save_format != FSO_FORMAT_RETAIL) {
3080 for (SCP_vector<alt_class>::iterator ii = shipp->s_alt_classes.begin(); ii != shipp->s_alt_classes.end(); ++ii) {
3081 // is this a variable?
3082 if (ii->variable_index != -1) {
3083 fout("\n$Alt Ship Class: @%s", Sexp_variables[ii->variable_index].variable_name);
3084 } else {
3085 fout("\n$Alt Ship Class: \"%s\"", Ship_info[ii->ship_class].name);
3086 }
3087
3088 // default class?
3089 if (ii->default_to_this_class) {
3090 fout("\n+Default Class:");
3091 }
3092 }
3093 }
3094
3095 // optional alternate type name
3096 if (strlen(Fred_alt_names[i])) {
3097 fout("\n$Alt: %s\n", Fred_alt_names[i]);
3098 }
3099
3100 // optional callsign
3101 if (Mission_save_format != FSO_FORMAT_RETAIL && strlen(Fred_callsigns[i])) {
3102 fout("\n$Callsign: %s\n", Fred_callsigns[i]);
3103 }
3104
3105 required_string_fred("$Team:");
3106 parse_comments();
3107 fout(" %s", Iff_info[shipp->team].iff_name);
3108
3109 if (Mission_save_format != FSO_FORMAT_RETAIL && Ship_info[shipp->ship_info_index].uses_team_colors) {
3110 required_string_fred("$Team Color Setting:");
3111 parse_comments();
3112 fout(" %s", shipp->team_name.c_str());
3113 }
3114
3115 required_string_fred("$Location:");
3116 parse_comments();
3117 save_vector(Objects[shipp->objnum].pos);
3118
3119 required_string_fred("$Orientation:");
3120 parse_comments();
3121 save_matrix(Objects[shipp->objnum].orient);
3122
3123 if (Mission_save_format == FSO_FORMAT_RETAIL) {
3124 required_string_fred("$IFF:");
3125 parse_comments();
3126 fout(" %s", "IFF 1");
3127 }
3128
3129 Assert(shipp->ai_index >= 0);
3130 aip = &Ai_info[shipp->ai_index];
3131
3132 required_string_fred("$AI Behavior:");
3133 parse_comments();
3134 fout(" %s", Ai_behavior_names[aip->behavior]);
3135
3136 if (shipp->weapons.ai_class != Ship_info[shipp->ship_info_index].ai_class) {
3137 if (optional_string_fred("+AI Class:", "$Name:"))
3138 parse_comments();
3139 else
3140 fout("\n+AI Class:");
3141
3142 fout(" %s", Ai_class_names[shipp->weapons.ai_class]);
3143 }
3144
3145 save_ai_goals(Ai_info[shipp->ai_index].goals, i);
3146
3147 // XSTR
3148 required_string_fred("$Cargo 1:");
3149 parse_comments();
3150 fout_ext(" ", "%s", Cargo_names[shipp->cargo1]);
3151
3152 save_common_object_data(&Objects[shipp->objnum], &Ships[i]);
3153
3154 if (shipp->wingnum >= 0) {
3155 shipp->arrival_location = ARRIVE_AT_LOCATION;
3156 }
3157
3158 required_string_fred("$Arrival Location:");
3159 parse_comments();
3160 fout(" %s", Arrival_location_names[shipp->arrival_location]);
3161
3162 if (shipp->arrival_location != ARRIVE_AT_LOCATION) {
3163 if (optional_string_fred("+Arrival Distance:", "$Name:")) {
3164 parse_comments();
3165 } else {
3166 fout("\n+Arrival Distance:");
3167 }
3168
3169 fout(" %d", shipp->arrival_distance);
3170 if (optional_string_fred("$Arrival Anchor:", "$Name:")) {
3171 parse_comments();
3172 } else {
3173 fout("\n$Arrival Anchor:");
3174 }
3175
3176 z = shipp->arrival_anchor;
3177 if (z & SPECIAL_ARRIVAL_ANCHOR_FLAG) {
3178 // get name
3179 char tmp[NAME_LENGTH + 15];
3180 stuff_special_arrival_anchor_name(tmp, z, Mission_save_format == FSO_FORMAT_RETAIL ? 1 : 0);
3181
3182 // save it
3183 fout(" %s", tmp);
3184 } else if (z >= 0) {
3185 fout(" %s", Ships[z].ship_name);
3186 } else {
3187 fout(" <error>");
3188 }
3189 }
3190
3191 // Goober5000
3192 if (Mission_save_format != FSO_FORMAT_RETAIL) {
3193 if ((shipp->arrival_location == ARRIVE_FROM_DOCK_BAY) && (shipp->arrival_path_mask > 0)) {
3194 int anchor_shipnum;
3195 polymodel *pm;
3196
3197 anchor_shipnum = shipp->arrival_anchor;
3198 Assert(anchor_shipnum >= 0 && anchor_shipnum < MAX_SHIPS);
3199
3200 fout("\n+Arrival Paths: ( ");
3201
3202 pm = model_get(Ship_info[Ships[anchor_shipnum].ship_info_index].model_num);
3203 for (auto n = 0; n < pm->ship_bay->num_paths; n++) {
3204 if (shipp->arrival_path_mask & (1 << n)) {
3205 fout("\"%s\" ", pm->paths[pm->ship_bay->path_indexes[n]].name);
3206 }
3207 }
3208
3209 fout(")");
3210 }
3211 }
3212
3213 if (shipp->arrival_delay) {
3214 if (optional_string_fred("+Arrival Delay:", "$Name:"))
3215 parse_comments();
3216 else
3217 fout("\n+Arrival Delay:");
3218
3219 fout(" %d", shipp->arrival_delay);
3220 }
3221
3222 required_string_fred("$Arrival Cue:");
3223 parse_comments();
3224 convert_sexp_to_string(sexp_out, shipp->arrival_cue, SEXP_SAVE_MODE);
3225 fout(" %s", sexp_out.c_str());
3226
3227 if (shipp->wingnum >= 0) {
3228 shipp->departure_location = DEPART_AT_LOCATION;
3229 }
3230
3231 required_string_fred("$Departure Location:");
3232 parse_comments();
3233 fout(" %s", Departure_location_names[shipp->departure_location]);
3234
3235 if (shipp->departure_location != DEPART_AT_LOCATION) {
3236 required_string_fred("$Departure Anchor:");
3237 parse_comments();
3238
3239 if (shipp->departure_anchor >= 0)
3240 fout(" %s", Ships[shipp->departure_anchor].ship_name);
3241 else
3242 fout(" <error>");
3243 }
3244
3245 // Goober5000
3246 if (Mission_save_format != FSO_FORMAT_RETAIL) {
3247 if ((shipp->departure_location == DEPART_AT_DOCK_BAY) && (shipp->departure_path_mask > 0)) {
3248 int anchor_shipnum;
3249 polymodel *pm;
3250
3251 anchor_shipnum = shipp->departure_anchor;
3252 Assert(anchor_shipnum >= 0 && anchor_shipnum < MAX_SHIPS);
3253
3254 fout("\n+Departure Paths: ( ");
3255
3256 pm = model_get(Ship_info[Ships[anchor_shipnum].ship_info_index].model_num);
3257 for (auto n = 0; n < pm->ship_bay->num_paths; n++) {
3258 if (shipp->departure_path_mask & (1 << n)) {
3259 fout("\"%s\" ", pm->paths[pm->ship_bay->path_indexes[n]].name);
3260 }
3261 }
3262
3263 fout(")");
3264 }
3265 }
3266
3267 if (shipp->departure_delay) {
3268 if (optional_string_fred("+Departure delay:", "$Name:"))
3269 parse_comments();
3270 else
3271 fout("\n+Departure delay:");
3272
3273 fout(" %d", shipp->departure_delay);
3274 }
3275
3276 required_string_fred("$Departure Cue:");
3277 parse_comments();
3278 convert_sexp_to_string(sexp_out, shipp->departure_cue, SEXP_SAVE_MODE);
3279 fout(" %s", sexp_out.c_str());
3280
3281 save_warp_params(WarpDirection::WARP_IN, shipp);
3282 save_warp_params(WarpDirection::WARP_OUT, shipp);
3283
3284 required_string_fred("$Determination:");
3285 parse_comments();
3286 fout(" 10"); // dummy value for backwards compatibility
3287
3288 if (optional_string_fred("+Flags:", "$Name:")) {
3289 parse_comments();
3290 fout(" (");
3291 } else
3292 fout("\n+Flags: (");
3293
3294 if (shipp->flags[Ship::Ship_Flags::Cargo_revealed])
3295 fout(" \"cargo-known\"");
3296 if (shipp->flags[Ship::Ship_Flags::Ignore_count])
3297 fout(" \"ignore-count\"");
3298 if (objp->flags[Object::Object_Flags::Protected])
3299 fout(" \"protect-ship\"");
3300 if (shipp->flags[Ship::Ship_Flags::Reinforcement])
3301 fout(" \"reinforcement\"");
3302 if (objp->flags[Object::Object_Flags::No_shields])
3303 fout(" \"no-shields\"");
3304 if (shipp->flags[Ship::Ship_Flags::Escort])
3305 fout(" \"escort\"");
3306 if (objp->type == OBJ_START)
3307 fout(" \"player-start\"");
3308 if (shipp->flags[Ship::Ship_Flags::No_arrival_music])
3309 fout(" \"no-arrival-music\"");
3310 if (shipp->flags[Ship::Ship_Flags::No_arrival_warp])
3311 fout(" \"no-arrival-warp\"");
3312 if (shipp->flags[Ship::Ship_Flags::No_departure_warp])
3313 fout(" \"no-departure-warp\"");
3314 if (Objects[shipp->objnum].flags[Object::Object_Flags::Invulnerable])
3315 fout(" \"invulnerable\"");
3316 if (shipp->flags[Ship::Ship_Flags::Hidden_from_sensors])
3317 fout(" \"hidden-from-sensors\"");
3318 if (shipp->flags[Ship::Ship_Flags::Scannable])
3319 fout(" \"scannable\"");
3320 if (Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::Kamikaze])
3321 fout(" \"kamikaze\"");
3322 if (Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::No_dynamic])
3323 fout(" \"no-dynamic\"");
3324 if (shipp->flags[Ship::Ship_Flags::Red_alert_store_status])
3325 fout(" \"red-alert-carry\"");
3326 if (objp->flags[Object::Object_Flags::Beam_protected])
3327 fout(" \"beam-protect-ship\"");
3328 if (objp->flags[Object::Object_Flags::Flak_protected])
3329 fout(" \"flak-protect-ship\"");
3330 if (objp->flags[Object::Object_Flags::Laser_protected])
3331 fout(" \"laser-protect-ship\"");
3332 if (objp->flags[Object::Object_Flags::Missile_protected])
3333 fout(" \"missile-protect-ship\"");
3334 if (shipp->ship_guardian_threshold != 0)
3335 fout(" \"guardian\"");
3336 if (objp->flags[Object::Object_Flags::Special_warpin])
3337 fout(" \"special-warp\"");
3338 if (shipp->flags[Ship::Ship_Flags::Vaporize])
3339 fout(" \"vaporize\"");
3340 if (shipp->flags[Ship::Ship_Flags::Stealth])
3341 fout(" \"stealth\"");
3342 if (shipp->flags[Ship::Ship_Flags::Friendly_stealth_invis])
3343 fout(" \"friendly-stealth-invisible\"");
3344 if (shipp->flags[Ship::Ship_Flags::Dont_collide_invis])
3345 fout(" \"don't-collide-invisible\"");
3346 //for compatibility reasons ship locked or weapons locked are saved as both locked in retail mode
3347 if ((Mission_save_format == FSO_FORMAT_RETAIL) && ((shipp->flags[Ship::Ship_Flags::Ship_locked]) || (shipp->flags[Ship::Ship_Flags::Weapons_locked])))
3348 fout(" \"locked\"");
3349 fout(" )");
3350
3351 // flags2 added by Goober5000 --------------------------------
3352 if (Mission_save_format != FSO_FORMAT_RETAIL) {
3353 if (optional_string_fred("+Flags2:", "$Name:")) {
3354 parse_comments();
3355 fout(" (");
3356 } else
3357 fout("\n+Flags2: (");
3358
3359 if (shipp->flags[Ship::Ship_Flags::Primitive_sensors])
3360 fout(" \"primitive-sensors\"");
3361 if (shipp->flags[Ship::Ship_Flags::No_subspace_drive])
3362 fout(" \"no-subspace-drive\"");
3363 if (shipp->flags[Ship::Ship_Flags::Navpoint_carry])
3364 fout(" \"nav-carry-status\"");
3365 if (shipp->flags[Ship::Ship_Flags::Affected_by_gravity])
3366 fout(" \"affected-by-gravity\"");
3367 if (shipp->flags[Ship::Ship_Flags::Toggle_subsystem_scanning])
3368 fout(" \"toggle-subsystem-scanning\"");
3369 if (objp->flags[Object::Object_Flags::Targetable_as_bomb])
3370 fout(" \"targetable-as-bomb\"");
3371 if (shipp->flags[Ship::Ship_Flags::No_builtin_messages])
3372 fout(" \"no-builtin-messages\"");
3373 if (shipp->flags[Ship::Ship_Flags::Primaries_locked])
3374 fout(" \"primaries-locked\"");
3375 if (shipp->flags[Ship::Ship_Flags::Secondaries_locked])
3376 fout(" \"secondaries-locked\"");
3377 if (shipp->flags[Ship::Ship_Flags::No_death_scream])
3378 fout(" \"no-death-scream\"");
3379 if (shipp->flags[Ship::Ship_Flags::Always_death_scream])
3380 fout(" \"always-death-scream\"");
3381 if (shipp->flags[Ship::Ship_Flags::Navpoint_needslink])
3382 fout(" \"nav-needslink\"");
3383 if (shipp->flags[Ship::Ship_Flags::Hide_ship_name])
3384 fout(" \"hide-ship-name\"");
3385 if (shipp->flags[Ship::Ship_Flags::Set_class_dynamically])
3386 fout(" \"set-class-dynamically\"");
3387 if (shipp->flags[Ship::Ship_Flags::Lock_all_turrets_initially])
3388 fout(" \"lock-all-turrets\"");
3389 if (shipp->flags[Ship::Ship_Flags::Afterburner_locked])
3390 fout(" \"afterburners-locked\"");
3391 if (shipp->flags[Ship::Ship_Flags::Force_shields_on])
3392 fout(" \"force-shields-on\"");
3393 if (objp->flags[Object::Object_Flags::Immobile])
3394 fout(" \"immobile\"");
3395 if (shipp->flags[Ship::Ship_Flags::No_ets])
3396 fout(" \"no-ets\"");
3397 if (shipp->flags[Ship::Ship_Flags::Cloaked])
3398 fout(" \"cloaked\"");
3399 if (shipp->flags[Ship::Ship_Flags::Ship_locked])
3400 fout(" \"ship-locked\"");
3401 if (shipp->flags[Ship::Ship_Flags::Weapons_locked])
3402 fout(" \"weapons-locked\"");
3403 if (shipp->flags[Ship::Ship_Flags::Scramble_messages])
3404 fout(" \"scramble-messages\"");
3405 if (!(objp->flags[Object::Object_Flags::Collides]))
3406 fout(" \"no_collide\"");
3407 if (shipp->flags[Ship::Ship_Flags::No_disabled_self_destruct])
3408 fout(" \"no-disabled-self-destruct\"");
3409 fout(" )");
3410 }
3411 // -----------------------------------------------------------
3412
3413 fout("\n+Respawn priority: %d", shipp->respawn_priority); // HA! Newline added by Goober5000
3414
3415 if (shipp->flags[Ship::Ship_Flags::Escort]) {
3416 if (optional_string_fred("+Escort priority:", "$Name:")) {
3417 parse_comments();
3418 } else {
3419 fout("\n+Escort priority:");
3420 }
3421
3422 fout(" %d", shipp->escort_priority);
3423 }
3424
3425 // special explosions
3426 if (Mission_save_format != FSO_FORMAT_RETAIL) {
3427 if (shipp->use_special_explosion) {
3428 fso_comment_push(";;FSO 3.6.13;;");
3429 if (optional_string_fred("$Special Explosion:", "$Name:")) {
3430 parse_comments();
3431
3432 required_string_fred("+Special Exp Damage:");
3433 parse_comments();
3434 fout(" %d", shipp->special_exp_damage);
3435
3436 required_string_fred("+Special Exp Blast:");
3437 parse_comments();
3438 fout(" %d", shipp->special_exp_blast);
3439
3440 required_string_fred("+Special Exp Inner Radius:");
3441 parse_comments();
3442 fout(" %d", shipp->special_exp_inner);
3443
3444 required_string_fred("+Special Exp Outer Radius:");
3445 parse_comments();
3446 fout(" %d", shipp->special_exp_outer);
3447
3448 if (shipp->use_shockwave && (shipp->special_exp_shockwave_speed > 0)) {
3449 optional_string_fred("+Special Exp Shockwave Speed:");
3450 parse_comments();
3451 fout(" %d", shipp->special_exp_shockwave_speed);
3452 } else {
3453 bypass_comment(";;FSO 3.6.13;; +Special Exp Shockwave Speed:", "$Name:");
3454 }
3455
3456 if (shipp->special_exp_deathroll_time > 0) {
3457 optional_string_fred("+Special Exp Death Roll Time:");
3458 parse_comments();
3459 fout(" %d", shipp->special_exp_deathroll_time);
3460 } else {
3461 bypass_comment(";;FSO 3.6.13;; +Special Exp Death Roll Time:", "$Name:");
3462 }
3463 } else {
3464 fout_version("\n$Special Explosion:");
3465
3466 fout_version("\n+Special Exp Damage:");
3467 fout(" %d", shipp->special_exp_damage);
3468
3469 fout_version("\n+Special Exp Blast:");
3470 fout(" %d", shipp->special_exp_blast);
3471
3472 fout_version("\n+Special Exp Inner Radius:");
3473 fout(" %d", shipp->special_exp_inner);
3474
3475 fout_version("\n+Special Exp Outer Radius:");
3476 fout(" %d", shipp->special_exp_outer);
3477
3478 if (shipp->use_shockwave && (shipp->special_exp_shockwave_speed > 0)) {
3479 fout_version("\n+Special Exp Shockwave Speed:");
3480 fout(" %d", shipp->special_exp_shockwave_speed);
3481 }
3482
3483 if (shipp->special_exp_deathroll_time > 0) {
3484 fout_version("\n+Special Exp Death Roll Time:");
3485 fout(" %d", shipp->special_exp_deathroll_time);
3486 }
3487
3488 }
3489 fso_comment_pop();
3490 } else {
3491 bypass_comment(";;FSO 3.6.13;; +Special Exp Shockwave Speed:", "$Name:");
3492 bypass_comment(";;FSO 3.6.13;; +Special Exp Death Roll Time:", "$Name:");
3493 }
3494 }
3495 // retail format special explosions
3496 else {
3497 if (shipp->use_special_explosion) {
3498 int special_exp_index;
3499
3500 if (has_special_explosion_block_index(&Ships[i], &special_exp_index)) {
3501 fout("\n+Special Exp index:");
3502 fout(" %d", special_exp_index);
3503 } else {
3504 CString text = "You are saving in the retail mission format, but ";
3505 text += "the mission has too many special explosions defined. \"";
3506 text += shipp->ship_name;
3507 text += "\" has therefore lost any special explosion data that was defined for it. ";
3508 text += "\" Either remove special explosions or SEXP variables if you need it to have one ";
3509 MessageBox(NULL, text, "Too many variables!", MB_OK);
3510
3511 }
3512 }
3513 }
3514
3515 // Goober5000 ------------------------------------------------
3516 if (Mission_save_format != FSO_FORMAT_RETAIL) {
3517 if (shipp->special_hitpoints) {
3518 fso_comment_push(";;FSO 3.6.13;;");
3519 if (optional_string_fred("+Special Hitpoints:", "$Name:")) {
3520 parse_comments();
3521 } else {
3522 fout_version("\n+Special Hitpoints:");
3523 }
3524 fso_comment_pop();
3525
3526 fout(" %d", shipp->special_hitpoints);
3527 } else {
3528 bypass_comment(";;FSO 3.6.13;; +Special Hitpoints:", "$Name:");
3529 }
3530
3531 if (shipp->special_shield >= 0) {
3532 fso_comment_push(";;FSO 3.6.13;;");
3533 if (optional_string_fred("+Special Shield Points:", "$Name:")) {
3534 parse_comments();
3535 } else {
3536 fout_version("\n+Special Shield Points:");
3537 }
3538 fso_comment_pop();
3539
3540 fout(" %d", shipp->special_shield);
3541 } else {
3542 bypass_comment(";;FSO 3.6.13;; +Special Shield Points:", "$Name:");
3543 }
3544 }
3545 // -----------------------------------------------------------
3546
3547 if (Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::Kamikaze]) {
3548 if (optional_string_fred("+Kamikaze Damage:", "$Name:")) {
3549 parse_comments();
3550 } else {
3551 fout("\n+Kamikaze Damage:");
3552 }
3553
3554 fout(" %d", Ai_info[shipp->ai_index].kamikaze_damage);
3555 }
3556
3557 if (shipp->hotkey != -1) {
3558 if (optional_string_fred("+Hotkey:", "$Name:")) {
3559 parse_comments();
3560 } else {
3561 fout("\n+Hotkey:");
3562 }
3563
3564 fout(" %d", shipp->hotkey);
3565 }
3566
3567 // mwa -- new code to save off information about initially docked ships.
3568 // Goober5000 - newer code to save off information about initially docked ships. ;)
3569 if (object_is_docked(&Objects[shipp->objnum])) {
3570 // possible incompatibility
3571 if (Mission_save_format == FSO_FORMAT_RETAIL && !dock_check_docked_one_on_one(&Objects[shipp->objnum])) {
3572 static bool warned = false;
3573 if (!warned) {
3574 CString text = "You are saving in the retail mission format, but \"";
3575 text += shipp->ship_name;
3576 text += "\" is docked to more than one ship. If you wish to run this mission in retail, ";
3577 text += "you should remove the additional ships and save the mission again.";
3578 MessageBox(NULL, text, "Incompatibility with retail mission format", MB_OK);
3579
3580 warned = true; // to avoid zillions of boxes
3581 }
3582 }
3583
3584 // save one-on-one groups as if they were retail
3585 if (dock_check_docked_one_on_one(&Objects[shipp->objnum])) {
3586 // retail format only saved information for non-leaders
3587 if (!(shipp->flags[Ship::Ship_Flags::Dock_leader])) {
3588 save_single_dock_instance(&Ships[i], Objects[shipp->objnum].dock_list);
3589 }
3590 }
3591 // multiply docked
3592 else {
3593 // save all instances for all ships
3594 for (dock_instance *dock_ptr = Objects[shipp->objnum].dock_list; dock_ptr != NULL; dock_ptr = dock_ptr->next) {
3595 save_single_dock_instance(&Ships[i], dock_ptr);
3596 }
3597 }
3598 }
3599
3600 // check the ship flag about killing off the ship before a mission starts. Write out the appropriate
3601 // variable if necessary
3602 if (shipp->flags[Ship::Ship_Flags::Kill_before_mission]) {
3603 if (optional_string_fred("+Destroy At:", "$Name:"))
3604 parse_comments();
3605 else
3606 fout("\n+Destroy At: ");
3607
3608 fout(" %d", shipp->final_death_time);
3609 }
3610
3611 // possibly write out the orders that this ship will accept. We'll only do it if the orders
3612 // are not the default set of orders
3613 if (shipp->orders_accepted != ship_get_default_orders_accepted(&Ship_info[shipp->ship_info_index])) {
3614 if (optional_string_fred("+Orders Accepted:", "$Name:"))
3615 parse_comments();
3616 else
3617 fout("\n+Orders Accepted:");
3618
3619 fout(" %d\t\t;! note that this is a bitfield!!!", shipp->orders_accepted);
3620 }
3621
3622 if (shipp->group >= 0) {
3623 if (optional_string_fred("+Group:", "$Name:"))
3624 parse_comments();
3625 else
3626 fout("\n+Group:");
3627
3628 fout(" %d", shipp->group);
3629 }
3630
3631 // always write out the score to ensure backwards compatibility. If the score is the same as the value
3632 // in the table write out a flag to tell the game to simply use whatever is in the table instead
3633 if (Ship_info[shipp->ship_info_index].score == shipp->score) {
3634 fso_comment_push(";;FSO 3.6.10;;");
3635 if (optional_string_fred("+Use Table Score:", "$Name:")) {
3636 parse_comments();
3637 } else {
3638 fout_version("\n+Use Table Score:");
3639 }
3640 fso_comment_pop();
3641 } else {
3642 bypass_comment(";;FSO 3.6.10;; +Use Table Score:", "$Name:");
3643 }
3644
3645 if (optional_string_fred("+Score:", "$Name:"))
3646 parse_comments();
3647 else
3648 fout("\n+Score:");
3649
3650 fout(" %d", shipp->score);
3651
3652
3653 if (Mission_save_format != FSO_FORMAT_RETAIL && shipp->assist_score_pct != 0) {
3654 fso_comment_push(";;FSO 3.6.10;;");
3655 if (optional_string_fred("+Assist Score Percentage:")) {
3656 parse_comments();
3657 } else {
3658 fout_version("\n+Assist Score Percentage:");
3659 }
3660 fso_comment_pop();
3661
3662 fout(" %f", shipp->assist_score_pct);
3663 } else {
3664 bypass_comment(";;FSO 3.6.10;; +Assist Score Percentage:", "$Name:");
3665 }
3666
3667 // deal with the persona for this ship as well.
3668 if (shipp->persona_index != -1) {
3669 if (optional_string_fred("+Persona Index:", "$Name:"))
3670 parse_comments();
3671 else
3672 fout("\n+Persona Index:");
3673
3674 fout(" %d", shipp->persona_index);
3675 }
3676
3677 // Goober5000 - deal with texture replacement ----------------
3678 if (!Fred_texture_replacements.empty()) {
3679 bool needs_header = true;
3680 fso_comment_push(";;FSO 3.6.8;;");
3681
3682 for (SCP_vector<texture_replace>::iterator ii = Fred_texture_replacements.begin(); ii != Fred_texture_replacements.end(); ++ii) {
3683 if (!stricmp(shipp->ship_name, ii->ship_name) && !(ii->from_table)) {
3684 if (needs_header) {
3685 if (optional_string_fred("$Texture Replace:")) {
3686 parse_comments(1);
3687 } else {
3688 fout_version("\n$Texture Replace:");
3689 }
3690
3691 needs_header = false;
3692 }
3693
3694 // write out this entry
3695 if (optional_string_fred("+old:")) {
3696 parse_comments(1);
3697 fout(" %s", ii->old_texture);
3698 } else {
3699 fout_version("\n+old: %s", ii->old_texture);
3700 }
3701
3702 if (optional_string_fred("+new:")) {
3703 parse_comments(1);
3704 fout(" %s", ii->new_texture);
3705 } else {
3706 fout_version("\n+new: %s", ii->new_texture);
3707 }
3708 }
3709 }
3710
3711 fso_comment_pop();
3712 } else {
3713 bypass_comment(";;FSO 3.6.8;; $Texture Replace:", "$Name:");
3714 }
3715
3716 // end of texture replacement -------------------------------
3717
3718 z++;
3719
3720 fso_comment_pop();
3721 }
3722
3723 fso_comment_pop(true);
3724
3725 return err;
3726 }
3727
save_players()3728 int CFred_mission_save::save_players()
3729 {
3730 bool wrote_fso_data = false;
3731 int i, j;
3732 int var_idx;
3733 int used_pool[MAX_WEAPON_TYPES];
3734
3735 if (optional_string_fred("#Alternate Types:")) { // Make sure the parser doesn't get out of sync
3736 required_string_fred("#end");
3737 }
3738 if (optional_string_fred("#Callsigns:")) {
3739 required_string_fred("#end");
3740 }
3741
3742 // write out alternate name list
3743 if (Mission_alt_type_count > 0) {
3744 fout("\n\n#Alternate Types:\n");
3745
3746 // write them all out
3747 for (i = 0; i < Mission_alt_type_count; i++) {
3748 fout("$Alt: %s\n", Mission_alt_types[i]);
3749 }
3750
3751 // end
3752 fout("\n#end\n");
3753 }
3754
3755 // write out callsign list
3756 if (Mission_save_format != FSO_FORMAT_RETAIL && Mission_callsign_count > 0) {
3757 fout("\n\n#Callsigns:\n");
3758
3759 // write them all out
3760 for (i = 0; i < Mission_callsign_count; i++) {
3761 fout("$Callsign: %s\n", Mission_callsigns[i]);
3762 }
3763
3764 // end
3765 fout("\n#end\n");
3766 }
3767
3768 required_string_fred("#Players");
3769 parse_comments(2);
3770 fout("\t\t;! %d total\n", Player_starts);
3771
3772 for (i = 0; i < Num_teams; i++) {
3773 required_string_fred("$Starting Shipname:");
3774 parse_comments();
3775 Assert(Player_start_shipnum >= 0);
3776 fout(" %s", Ships[Player_start_shipnum].ship_name);
3777
3778 required_string_fred("$Ship Choices:");
3779 parse_comments();
3780 fout(" (\n");
3781
3782 for (j = 0; j < Team_data[i].num_ship_choices; j++) {
3783 // Check to see if a variable name should be written for the class rather than a number
3784 if (strlen(Team_data[i].ship_list_variables[j])) {
3785 var_idx = get_index_sexp_variable_name(Team_data[i].ship_list_variables[j]);
3786 Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES);
3787 wrote_fso_data = true;
3788
3789 fout("\t@%s\t", Sexp_variables[var_idx].variable_name);
3790 } else {
3791 fout("\t\"%s\"\t", Ship_info[Team_data[i].ship_list[j]].name);
3792 }
3793
3794 // Now check if we should write a variable or a number for the amount of ships available
3795 if (strlen(Team_data[i].ship_count_variables[j])) {
3796 var_idx = get_index_sexp_variable_name(Team_data[i].ship_count_variables[j]);
3797 Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES);
3798 wrote_fso_data = true;
3799
3800 fout("@%s\n", Sexp_variables[var_idx].variable_name);
3801 } else {
3802 fout("%d\n", Team_data[i].ship_count[j]);
3803 }
3804 }
3805
3806 fout(")");
3807
3808 if (optional_string_fred("+Weaponry Pool:", "$Starting Shipname:")) {
3809 parse_comments(2);
3810 } else {
3811 fout("\n\n+Weaponry Pool:");
3812 }
3813
3814 fout(" (\n");
3815 generate_weaponry_usage_list(i, used_pool);
3816
3817 for (j = 0; j < Team_data[i].num_weapon_choices; j++) {
3818 // first output the weapon name or a variable that sets it
3819 if (strlen(Team_data[i].weaponry_pool_variable[j])) {
3820 var_idx = get_index_sexp_variable_name(Team_data[i].weaponry_pool_variable[j]);
3821 Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES);
3822 wrote_fso_data = true;
3823
3824 fout("\t@%s\t", Sexp_variables[var_idx].variable_name);
3825 } else {
3826 fout("\t\"%s\"\t", Weapon_info[Team_data[i].weaponry_pool[j]].name);
3827 }
3828
3829 // now output the amount of this weapon or a variable that sets it. If this weapon is in the used pool and isn't
3830 // set by a variable we should add the amount of weapons used by the wings to it and zero the entry so we know
3831 // that we have dealt with it
3832 if (strlen(Team_data[i].weaponry_amount_variable[j])) {
3833 var_idx = get_index_sexp_variable_name(Team_data[i].weaponry_amount_variable[j]);
3834 Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES);
3835 wrote_fso_data = true;
3836
3837 fout("@%s\n", Sexp_variables[var_idx].variable_name);
3838 } else {
3839 if (strlen(Team_data[i].weaponry_pool_variable[j])) {
3840 fout("%d\n", Team_data[i].weaponry_count[j]);
3841 } else {
3842 fout("%d\n", Team_data[i].weaponry_count[j] + used_pool[Team_data[i].weaponry_pool[j]]);
3843 used_pool[Team_data[i].weaponry_pool[j]] = 0;
3844 }
3845 }
3846 }
3847
3848 // now we add anything left in the used pool as a static entry
3849 for (j = 0; j < weapon_info_size(); j++) {
3850 if (used_pool[j] > 0) {
3851 fout("\t\"%s\"\t%d\n", Weapon_info[j].name, used_pool[j]);
3852 }
3853 }
3854
3855 fout(")");
3856
3857 // sanity check
3858 if (Mission_save_format == FSO_FORMAT_RETAIL && wrote_fso_data) {
3859 // this is such an unlikely (and hard-to-fix) case that a warning should be sufficient
3860 MessageBox(nullptr, "Warning: This mission contains variable-based team loadout information, but you are saving in the retail mission format. Retail FRED and FS2 will not be able to read this information.", "Incompatibility with retail mission format", MB_OK);
3861 }
3862
3863 // Goober5000 - mjn.mixael's required weapon feature
3864 bool uses_required_weapon = false;
3865 for (j = 0; j < weapon_info_size(); j++) {
3866 if (Team_data[i].weapon_required[j]) {
3867 uses_required_weapon = true;
3868 break;
3869 }
3870 }
3871 if (Mission_save_format != FSO_FORMAT_RETAIL && uses_required_weapon) {
3872 if (optional_string_fred("+Required for mission:", "$Starting Shipname:"))
3873 parse_comments(2);
3874 else
3875 fout("\n+Required for mission:");
3876
3877 fout(" (");
3878 for (j = 0; j < weapon_info_size(); j++) {
3879 if (Team_data[i].weapon_required[j])
3880 fout(" \"%s\"", Weapon_info[j].name);
3881 }
3882 fout(" )");
3883 }
3884
3885 fso_comment_pop();
3886 }
3887
3888 fso_comment_pop(true);
3889
3890 return err;
3891 }
3892
save_plot_info()3893 int CFred_mission_save::save_plot_info()
3894 {
3895 if (Mission_save_format == FSO_FORMAT_RETAIL) {
3896 if (optional_string_fred("#Plot Info")) {
3897 parse_comments(2);
3898
3899 // XSTR
3900 required_string_fred("$Tour:");
3901 parse_comments(2);
3902 fout_ext(" ", "Blah");
3903
3904 required_string_fred("$Pre-Briefing Cutscene:");
3905 parse_comments();
3906 fout(" Blah");
3907
3908 required_string_fred("$Pre-Mission Cutscene:");
3909 parse_comments();
3910 fout(" Blah");
3911
3912 required_string_fred("$Next Mission Success:");
3913 parse_comments();
3914 fout(" Blah");
3915
3916 required_string_fred("$Next Mission Partial:");
3917 parse_comments();
3918 fout(" Blah");
3919
3920 required_string_fred("$Next Mission Failure:");
3921 parse_comments();
3922 fout(" Blah");
3923 } else {
3924 fout("\n\n#Plot Info\n\n");
3925
3926 fout("$Tour: ");
3927 fout_ext(NULL, "Blah");
3928 fout("\n");
3929 fout("$Pre-Briefing Cutscene: Blah\n");
3930 fout("$Pre-Mission Cutscene: Blah\n");
3931 fout("$Next Mission Success: Blah\n");
3932 fout("$Next Mission Partial: Blah\n");
3933 fout("$Next Mission Failure: Blah\n");
3934
3935 fout("\n");
3936 }
3937 }
3938
3939 fso_comment_pop(true);
3940
3941 return err;
3942 }
3943
save_reinforcements()3944 int CFred_mission_save::save_reinforcements()
3945 {
3946 int i, j, type;
3947
3948 fred_parse_flag = 0;
3949 required_string_fred("#Reinforcements");
3950 parse_comments(2);
3951 fout("\t\t;! %d total\n", Num_reinforcements);
3952
3953 for (i = 0; i < Num_reinforcements; i++) {
3954 required_string_either_fred("$Name:", "#Background bitmaps");
3955 required_string_fred("$Name:");
3956 parse_comments(i ? 2 : 1);
3957 fout(" %s", Reinforcements[i].name);
3958
3959 type = TYPE_ATTACK_PROTECT;
3960 for (j = 0; j < MAX_SHIPS; j++)
3961 if ((Ships[j].objnum != -1) && !stricmp(Ships[j].ship_name, Reinforcements[i].name)) {
3962 if (Ship_info[Ships[j].ship_info_index].flags[Ship::Info_Flags::Support])
3963 type = TYPE_REPAIR_REARM;
3964 break;
3965 }
3966
3967 required_string_fred("$Type:");
3968 parse_comments();
3969 fout(" %s", Reinforcement_type_names[type]);
3970
3971 required_string_fred("$Num times:");
3972 parse_comments();
3973 fout(" %d", Reinforcements[i].uses);
3974
3975 if (optional_string_fred("+Arrival Delay:", "$Name:"))
3976 parse_comments();
3977 else
3978 fout("\n+Arrival Delay:");
3979 fout(" %d", Reinforcements[i].arrival_delay);
3980
3981 if (optional_string_fred("+No Messages:", "$Name:"))
3982 parse_comments();
3983 else
3984 fout("\n+No Messages:");
3985 fout(" (");
3986 for (j = 0; j < MAX_REINFORCEMENT_MESSAGES; j++) {
3987 if (strlen(Reinforcements[i].no_messages[j]))
3988 fout(" \"%s\"", Reinforcements[i].no_messages[j]);
3989 }
3990 fout(" )");
3991
3992 if (optional_string_fred("+Yes Messages:", "$Name:"))
3993 parse_comments();
3994 else
3995 fout("\n+Yes Messages:");
3996 fout(" (");
3997 for (j = 0; j < MAX_REINFORCEMENT_MESSAGES; j++) {
3998 if (strlen(Reinforcements[i].yes_messages[j]))
3999 fout(" \"%s\"", Reinforcements[i].yes_messages[j]);
4000 }
4001 fout(" )");
4002
4003 fso_comment_pop();
4004 }
4005
4006 fso_comment_pop(true);
4007
4008 return err;
4009 }
4010
save_single_dock_instance(ship * shipp,dock_instance * dock_ptr)4011 void CFred_mission_save::save_single_dock_instance(ship *shipp, dock_instance *dock_ptr)
4012 {
4013 Assert(shipp && dock_ptr);
4014 Assert(dock_ptr->docked_objp->type == OBJ_SHIP || dock_ptr->docked_objp->type == OBJ_START);
4015
4016 // get ships and objects
4017 object *objp = &Objects[shipp->objnum];
4018 object *other_objp = dock_ptr->docked_objp;
4019 ship *other_shipp = &Ships[other_objp->instance];
4020
4021 // write other ship
4022 if (optional_string_fred("+Docked With:", "$Name:"))
4023 parse_comments();
4024 else
4025 fout("\n+Docked With:");
4026 fout(" %s", other_shipp->ship_name);
4027
4028
4029 // Goober5000 - hm, Volition seems to have reversed docker and dockee here
4030
4031 // write docker (actually dockee) point
4032 required_string_fred("$Docker Point:", "$Name:");
4033 parse_comments();
4034 fout(" %s", model_get_dock_name(Ship_info[other_shipp->ship_info_index].model_num, dock_find_dockpoint_used_by_object(other_objp, objp)));
4035
4036 // write dockee (actually docker) point
4037 required_string_fred("$Dockee Point:", "$Name:");
4038 parse_comments();
4039 fout(" %s", model_get_dock_name(Ship_info[shipp->ship_info_index].model_num, dock_find_dockpoint_used_by_object(objp, other_objp)));
4040
4041 fso_comment_pop(true);
4042 }
4043
save_turret_info(ship_subsys * ptr,int ship)4044 void CFred_mission_save::save_turret_info(ship_subsys *ptr, int ship)
4045 {
4046 int i, z;
4047 ship_weapon *wp = &ptr->weapons;
4048
4049 if (wp->ai_class != Ship_info[Ships[ship].ship_info_index].ai_class) {
4050 if (optional_string_fred("+AI Class:", "$Name:", "+Subsystem:"))
4051 parse_comments();
4052 else
4053 fout("\n+AI Class:");
4054
4055 fout(" %s", Ai_class_names[wp->ai_class]);
4056 }
4057
4058 z = 0;
4059 i = wp->num_primary_banks;
4060 while (i--)
4061 if (wp->primary_bank_weapons[i] != ptr->system_info->primary_banks[i])
4062 z = 1;
4063
4064 if (z) {
4065 if (optional_string_fred("+Primary Banks:", "$Name:", "+Subsystem:"))
4066 parse_comments();
4067 else
4068 fout("\n+Primary Banks:");
4069
4070 fout(" ( ");
4071 for (i = 0; i < wp->num_primary_banks; i++) {
4072 if (wp->primary_bank_weapons[i] != -1) { // Just in case someone has set a weapon bank to empty
4073 fout("\"%s\" ", Weapon_info[wp->primary_bank_weapons[i]].name);
4074 } else {
4075 fout("\"\" ");
4076 }
4077 }
4078 fout(")");
4079 }
4080
4081 z = 0;
4082 i = wp->num_secondary_banks;
4083 while (i--)
4084 if (wp->secondary_bank_weapons[i] != ptr->system_info->secondary_banks[i])
4085 z = 1;
4086
4087 if (z) {
4088 if (optional_string_fred("+Secondary Banks:", "$Name:", "+Subsystem:"))
4089 parse_comments();
4090 else
4091 fout("\n+Secondary Banks:");
4092
4093 fout(" ( ");
4094 for (i = 0; i < wp->num_secondary_banks; i++) {
4095 if (wp->secondary_bank_weapons[i] != -1) {
4096 fout("\"%s\" ", Weapon_info[wp->secondary_bank_weapons[i]].name);
4097 } else {
4098 fout("\"\" ");
4099 }
4100 }
4101 fout(")");
4102 }
4103
4104 z = 0;
4105 i = wp->num_secondary_banks;
4106 while (i--)
4107 if (wp->secondary_bank_ammo[i] != 100)
4108 z = 1;
4109
4110 if (z) {
4111 if (optional_string_fred("+Sbank Ammo:", "$Name:", "+Subsystem:"))
4112 parse_comments();
4113 else
4114 fout("\n+Sbank Ammo:");
4115
4116 fout(" ( ");
4117 for (i = 0; i < wp->num_secondary_banks; i++)
4118 fout("%d ", wp->secondary_bank_ammo[i]);
4119
4120 fout(")");
4121 }
4122
4123 fso_comment_pop(true);
4124 }
4125
save_variables()4126 int CFred_mission_save::save_variables()
4127 {
4128 char *type;
4129 char number[] = "number";
4130 char string[] = "string";
4131 char block[] = "block";
4132 int i;
4133 int num_block_vars = 0;
4134
4135 // sort sexp_variables
4136 sexp_variable_sort();
4137
4138 // get count
4139 int num_variables = sexp_variable_count();
4140
4141 if (Mission_save_format == FSO_FORMAT_RETAIL) {
4142 generate_special_explosion_block_variables();
4143 num_block_vars = num_block_variables();
4144 }
4145 int total_variables = num_variables + num_block_vars;
4146
4147 if (total_variables > 0) {
4148
4149 // write 'em out
4150 required_string_fred("#Sexp_variables");
4151 parse_comments(2);
4152
4153 required_string_fred("$Variables:");
4154 parse_comments(2);
4155
4156 fout("\n(");
4157 // parse_comments();
4158
4159 for (i = 0; i < num_variables; i++) {
4160 if (Sexp_variables[i].type & SEXP_VARIABLE_NUMBER) {
4161 type = number;
4162 } else {
4163 type = string;
4164 }
4165 // index "var name" "default" "type"
4166 fout("\n\t\t%d\t\t\"%s\"\t\t\"%s\"\t\t\"%s\"", i, Sexp_variables[i].variable_name, Sexp_variables[i].text, type);
4167
4168 // persistent and network variables
4169 if (Mission_save_format != FSO_FORMAT_RETAIL) {
4170 // Network variable - Karajorma
4171 if (Sexp_variables[i].type & SEXP_VARIABLE_NETWORK) {
4172 fout("\t\t\"%s\"", "network-variable");
4173 }
4174
4175 if (Sexp_variables[i].type & SEXP_VARIABLE_SAVE_TO_PLAYER_FILE) {
4176 fout("\t\t\"%s\"", "eternal");
4177 }
4178
4179 // player-persistent - Goober5000
4180 if (Sexp_variables[i].type & SEXP_VARIABLE_SAVE_ON_MISSION_CLOSE) {
4181 fout("\t\t\"%s\"", "save-on-mission-close");
4182 // campaign-persistent - Goober5000
4183 } else if (Sexp_variables[i].type & SEXP_VARIABLE_SAVE_ON_MISSION_PROGRESS) {
4184 fout("\t\t\"%s\"", "save-on-mission-progress");
4185 }
4186 }
4187
4188 // parse_comments();
4189 }
4190
4191 for (i = MAX_SEXP_VARIABLES - num_block_vars; i < MAX_SEXP_VARIABLES; i++) {
4192 type = block;
4193 fout("\n\t\t%d\t\t\"%s\"\t\t\"%s\"\t\t\"%s\"", i, Block_variables[i].variable_name, Block_variables[i].text, type);
4194 }
4195
4196 fout("\n)");
4197
4198 fso_comment_pop();
4199 }
4200
4201 fso_comment_pop(true);
4202
4203 return err;
4204 }
4205
save_vector(vec3d & v)4206 int CFred_mission_save::save_vector(vec3d &v)
4207 {
4208 fout(" %f, %f, %f", v.xyz.x, v.xyz.y, v.xyz.z);
4209 return 0;
4210 }
4211
save_waypoints()4212 int CFred_mission_save::save_waypoints()
4213 {
4214 //object *ptr;
4215
4216 fred_parse_flag = 0;
4217 required_string_fred("#Waypoints");
4218 parse_comments(2);
4219 fout("\t\t;! %d lists total\n", Waypoint_lists.size());
4220
4221 SCP_list<CJumpNode>::iterator jnp;
4222 for (jnp = Jump_nodes.begin(); jnp != Jump_nodes.end(); ++jnp) {
4223 required_string_fred("$Jump Node:", "$Jump Node Name:");
4224 parse_comments(2);
4225 save_vector(jnp->GetSCPObject()->pos);
4226
4227 required_string_fred("$Jump Node Name:", "$Jump Node:");
4228 parse_comments();
4229 fout(" %s", jnp->GetName());
4230
4231 if (Mission_save_format != FSO_FORMAT_RETAIL) {
4232 if (jnp->IsSpecialModel()) {
4233 if (optional_string_fred("+Model File:", "$Jump Node:"))
4234 parse_comments();
4235 else
4236 fout("\n+Model File:");
4237
4238 int model = jnp->GetModelNumber();
4239 polymodel *pm = model_get(model);
4240 fout(" %s", pm->filename);
4241 }
4242
4243 if (jnp->IsColored()) {
4244 if (optional_string_fred("+Alphacolor:", "$Jump Node:"))
4245 parse_comments();
4246 else
4247 fout("\n+Alphacolor:");
4248
4249 color jn_color = jnp->GetColor();
4250 fout(" %u %u %u %u", jn_color.red, jn_color.green, jn_color.blue, jn_color.alpha);
4251 }
4252
4253 int hidden_is_there = optional_string_fred("+Hidden:", "$Jump Node:");
4254 if (hidden_is_there)
4255 parse_comments();
4256
4257 if (hidden_is_there || jnp->IsHidden()) {
4258 if (!hidden_is_there)
4259 fout("\n+Hidden:");
4260
4261 if (jnp->IsHidden())
4262 fout(" %s", "true");
4263 else
4264 fout(" %s", "false");
4265 }
4266 }
4267
4268 fso_comment_pop();
4269 }
4270
4271 SCP_list<waypoint_list>::iterator ii;
4272 for (ii = Waypoint_lists.begin(); ii != Waypoint_lists.end(); ++ii) {
4273 required_string_either_fred("$Name:", "#Messages");
4274 required_string_fred("$Name:");
4275 parse_comments((ii == Waypoint_lists.begin()) ? 1 : 2);
4276 fout(" %s", ii->get_name());
4277
4278 required_string_fred("$List:");
4279 parse_comments();
4280 fout(" (\t\t;! %d points in list\n", ii->get_waypoints().size());
4281
4282 save_waypoint_list(&(*ii));
4283 fout(")");
4284
4285 fso_comment_pop();
4286 }
4287
4288 fso_comment_pop(true);
4289
4290 return err;
4291 }
4292
save_waypoint_list(waypoint_list * wp_list)4293 int CFred_mission_save::save_waypoint_list(waypoint_list *wp_list)
4294 {
4295 Assert(wp_list != NULL);
4296 SCP_vector<waypoint>::iterator ii;
4297
4298 for (ii = wp_list->get_waypoints().begin(); ii != wp_list->get_waypoints().end(); ++ii) {
4299 vec3d *pos = ii->get_pos();
4300 fout("\t( %f, %f, %f )\n", pos->xyz.x, pos->xyz.y, pos->xyz.z);
4301 }
4302
4303 return 0;
4304 }
4305
save_wings()4306 int CFred_mission_save::save_wings()
4307 {
4308 SCP_string sexp_out;
4309 int i, j, z, ship, count = 0;
4310
4311 fred_parse_flag = 0;
4312 required_string_fred("#Wings");
4313 parse_comments(2);
4314 fout("\t\t;! %d total", Num_wings);
4315
4316 for (i = 0; i < MAX_WINGS; i++) {
4317 if (!Wings[i].wave_count)
4318 continue;
4319
4320 count++;
4321 required_string_either_fred("$Name:", "#Events");
4322 required_string_fred("$Name:");
4323 parse_comments(2);
4324 fout(" %s", Wings[i].name);
4325
4326 // squad logo - Goober5000
4327 if (Mission_save_format != FSO_FORMAT_RETAIL) {
4328 if (strlen(Wings[i].wing_squad_filename) > 0) //-V805
4329 {
4330 if (optional_string_fred("+Squad Logo:", "$Name:"))
4331 parse_comments();
4332 else
4333 fout("\n+Squad Logo:");
4334
4335 fout(" %s", Wings[i].wing_squad_filename);
4336 }
4337 }
4338
4339 required_string_fred("$Waves:");
4340 parse_comments();
4341 fout(" %d", Wings[i].num_waves);
4342
4343 required_string_fred("$Wave Threshold:");
4344 parse_comments();
4345 fout(" %d", Wings[i].threshold);
4346
4347 required_string_fred("$Special Ship:");
4348 parse_comments();
4349 fout(" %d\t\t;! %s", Wings[i].special_ship,
4350 Ships[Wings[i].ship_index[Wings[i].special_ship]].ship_name);
4351
4352 if (Mission_save_format != FSO_FORMAT_RETAIL) {
4353 if (Wings[i].formation >= 0 && Wings[i].formation < (int)Wing_formations.size())
4354 {
4355 if (optional_string_fred("+Formation:", "$Name:")) {
4356 parse_comments();
4357 }
4358 else {
4359 fout("\n+Formation:");
4360 }
4361
4362 fout(" %s", Wing_formations[Wings[i].formation].name);
4363 }
4364 }
4365
4366 required_string_fred("$Arrival Location:");
4367 parse_comments();
4368 fout(" %s", Arrival_location_names[Wings[i].arrival_location]);
4369
4370 if (Wings[i].arrival_location != ARRIVE_AT_LOCATION) {
4371 if (optional_string_fred("+Arrival Distance:", "$Name:"))
4372 parse_comments();
4373 else
4374 fout("\n+Arrival Distance:");
4375
4376 fout(" %d", Wings[i].arrival_distance);
4377 if (optional_string_fred("$Arrival Anchor:", "$Name:"))
4378 parse_comments();
4379 else
4380 fout("\n$Arrival Anchor:");
4381
4382 z = Wings[i].arrival_anchor;
4383 if (z & SPECIAL_ARRIVAL_ANCHOR_FLAG) {
4384 // get name
4385 char tmp[NAME_LENGTH + 15];
4386 stuff_special_arrival_anchor_name(tmp, z, Mission_save_format == FSO_FORMAT_RETAIL ? 1 : 0);
4387
4388 // save it
4389 fout(" %s", tmp);
4390 } else if (z >= 0) {
4391 fout(" %s", Ships[z].ship_name);
4392 } else {
4393 fout(" <error>");
4394 }
4395 }
4396
4397 // Goober5000
4398 if (Mission_save_format != FSO_FORMAT_RETAIL) {
4399 if ((Wings[i].arrival_location == ARRIVE_FROM_DOCK_BAY) && (Wings[i].arrival_path_mask > 0)) {
4400 int anchor_shipnum;
4401 polymodel *pm;
4402
4403 anchor_shipnum = Wings[i].arrival_anchor;
4404 Assert(anchor_shipnum >= 0 && anchor_shipnum < MAX_SHIPS);
4405
4406 fout("\n+Arrival Paths: ( ");
4407
4408 pm = model_get(Ship_info[Ships[anchor_shipnum].ship_info_index].model_num);
4409 for (auto n = 0; n < pm->ship_bay->num_paths; n++) {
4410 if (Wings[i].arrival_path_mask & (1 << n)) {
4411 fout("\"%s\" ", pm->paths[pm->ship_bay->path_indexes[n]].name);
4412 }
4413 }
4414
4415 fout(")");
4416 }
4417 }
4418
4419 if (Wings[i].arrival_delay) {
4420 if (optional_string_fred("+Arrival delay:", "$Name:"))
4421 parse_comments();
4422 else
4423 fout("\n+Arrival delay:");
4424
4425 fout(" %d", Wings[i].arrival_delay);
4426 }
4427
4428 required_string_fred("$Arrival Cue:");
4429 parse_comments();
4430 convert_sexp_to_string(sexp_out, Wings[i].arrival_cue, SEXP_SAVE_MODE);
4431 fout(" %s", sexp_out.c_str());
4432
4433 required_string_fred("$Departure Location:");
4434 parse_comments();
4435 fout(" %s", Departure_location_names[Wings[i].departure_location]);
4436
4437 if (Wings[i].departure_location != DEPART_AT_LOCATION) {
4438 required_string_fred("$Departure Anchor:");
4439 parse_comments();
4440
4441 if (Wings[i].departure_anchor >= 0)
4442 fout(" %s", Ships[Wings[i].departure_anchor].ship_name);
4443 else
4444 fout(" <error>");
4445 }
4446
4447 // Goober5000
4448 if (Mission_save_format != FSO_FORMAT_RETAIL) {
4449 if ((Wings[i].departure_location == DEPART_AT_DOCK_BAY) && (Wings[i].departure_path_mask > 0)) {
4450 int anchor_shipnum;
4451 polymodel *pm;
4452
4453 anchor_shipnum = Wings[i].departure_anchor;
4454 Assert(anchor_shipnum >= 0 && anchor_shipnum < MAX_SHIPS);
4455
4456 fout("\n+Departure Paths: ( ");
4457
4458 pm = model_get(Ship_info[Ships[anchor_shipnum].ship_info_index].model_num);
4459 for (auto n = 0; n < pm->ship_bay->num_paths; n++) {
4460 if (Wings[i].departure_path_mask & (1 << n)) {
4461 fout("\"%s\" ", pm->paths[pm->ship_bay->path_indexes[n]].name);
4462 }
4463 }
4464
4465 fout(")");
4466 }
4467 }
4468
4469 if (Wings[i].departure_delay) {
4470 if (optional_string_fred("+Departure delay:", "$Name:"))
4471 parse_comments();
4472 else
4473 fout("\n+Departure delay:");
4474
4475 fout(" %d", Wings[i].departure_delay);
4476 }
4477
4478 required_string_fred("$Departure Cue:");
4479 parse_comments();
4480 convert_sexp_to_string(sexp_out, Wings[i].departure_cue, SEXP_SAVE_MODE);
4481 fout(" %s", sexp_out.c_str());
4482
4483 required_string_fred("$Ships:");
4484 parse_comments();
4485 fout(" (\t\t;! %d total\n", Wings[i].wave_count);
4486
4487 for (j = 0; j < Wings[i].wave_count; j++) {
4488 ship = Wings[i].ship_index[j];
4489 // if (Objects[Ships[ship].objnum].type == OBJ_START)
4490 // fout("\t\"Player 1\"\n");
4491 // else
4492 fout("\t\"%s\"\n", Ships[Wings[i].ship_index[j]].ship_name);
4493 }
4494
4495 fout(")");
4496
4497 save_ai_goals(Wings[i].ai_goals, -1);
4498
4499 if (Wings[i].hotkey != -1) {
4500 if (optional_string_fred("+Hotkey:", "$Name:"))
4501 parse_comments();
4502 else
4503 fout("\n+Hotkey:");
4504
4505 fout(" %d", Wings[i].hotkey);
4506 }
4507
4508 if (optional_string_fred("+Flags:", "$Name:")) {
4509 parse_comments();
4510 fout(" (");
4511 } else
4512 fout("\n+Flags: (");
4513
4514 if (Wings[i].flags[Ship::Wing_Flags::Ignore_count])
4515 fout(" \"ignore-count\"");
4516 if (Wings[i].flags[Ship::Wing_Flags::Reinforcement])
4517 fout(" \"reinforcement\"");
4518 if (Wings[i].flags[Ship::Wing_Flags::No_arrival_music])
4519 fout(" \"no-arrival-music\"");
4520 if (Wings[i].flags[Ship::Wing_Flags::No_arrival_message])
4521 fout(" \"no-arrival-message\"");
4522 if (Wings[i].flags[Ship::Wing_Flags::No_arrival_warp])
4523 fout(" \"no-arrival-warp\"");
4524 if (Wings[i].flags[Ship::Wing_Flags::No_departure_warp])
4525 fout(" \"no-departure-warp\"");
4526 if (Wings[i].flags[Ship::Wing_Flags::No_dynamic])
4527 fout(" \"no-dynamic\"");
4528
4529 fout(" )");
4530
4531 if (Wings[i].wave_delay_min) {
4532 if (optional_string_fred("+Wave Delay Min:", "$Name:"))
4533 parse_comments();
4534 else
4535 fout("\n+Wave Delay Min:");
4536
4537 fout(" %d", Wings[i].wave_delay_min);
4538 }
4539
4540 if (Wings[i].wave_delay_max) {
4541 if (optional_string_fred("+Wave Delay Max:", "$Name:"))
4542 parse_comments();
4543 else
4544 fout("\n+Wave Delay Max:");
4545
4546 fout(" %d", Wings[i].wave_delay_max);
4547 }
4548
4549 fso_comment_pop();
4550 }
4551
4552 fso_comment_pop(true);
4553
4554 Assert(count == Num_wings);
4555 return err;
4556 }
4557