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