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