Files
fs2open.github.com/fred2/missionsave.cpp

4351 lines
113 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "stdafx.h"
#include "CampaignEditorDlg.h"
#include "CampaignTreeView.h"
#include "CampaignTreeWnd.h"
#include "FredRender.h"
#include "FredView.h"
#include "MainFrm.h"
#include "Management.h"
#include "MissionSave.h"
#include "ai/aigoals.h"
#include "asteroid/asteroid.h"
#include "cfile/cfile.h"
#include "gamesnd/eventmusic.h"
#include "globalincs/linklist.h"
#include "globalincs/version.h"
#include "iff_defs/iff_defs.h"
#include "jumpnode/jumpnode.h"
#include "lighting/lighting.h"
#include "localization/fhash.h"
#include "math/vecmat.h"
#include "mission/missionbriefcommon.h"
#include "mission/missioncampaign.h"
#include "mission/missiongoals.h"
#include "mission/missionmessage.h"
#include "mission/missionparse.h"
#include "missionui/fictionviewer.h"
#include "missionui/missioncmdbrief.h"
#include "nebula/neb.h"
#include "object/objectdock.h"
#include "object/objectshield.h"
#include "osapi/osapi.h"
#include "parse/sexp.h"
#include "sound/ds.h"
#include "sound/sound.h"
#include "starfield/nebula.h"
#include "starfield/starfield.h"
#include "weapon/weapon.h"
#include <freespace.h>
int CFred_mission_save::autosave_mission_file(char *pathname)
{
char backup_name[256], name2[256];
int i;
auto len = strlen(pathname);
strcpy_s(backup_name, pathname);
strcpy_s(name2, pathname);
sprintf(backup_name + len, ".%.3d", BACKUP_DEPTH);
cf_delete(backup_name, CF_TYPE_MISSIONS);
for (i = BACKUP_DEPTH; i > 1; i--) {
sprintf(backup_name + len, ".%.3d", i - 1);
sprintf(name2 + len, ".%.3d", i);
cf_rename(backup_name, name2, CF_TYPE_MISSIONS);
}
strcpy(backup_name + len, ".001");
save_mission_internal(backup_name);
return err;
}
void CFred_mission_save::bypass_comment(const char *comment, const char *end)
{
char *ch = strstr(raw_ptr, comment);
if (ch != NULL) {
if (end != NULL) {
char *ep = strstr(raw_ptr, end);
if (ep != NULL && ep < ch)
return;
}
char *writep = ch;
char *readp = strchr(writep, '\n');
// copy all characters past it
while (*readp != '\0') {
*writep = *readp;
writep++;
readp++;
}
*writep = '\0';
}
}
void CFred_mission_save::convert_special_tags_to_retail(char *text, int max_len)
{
replace_all(text, "$quote", "''", max_len);
replace_all(text, "$semicolon", ",", max_len);
}
void CFred_mission_save::convert_special_tags_to_retail(SCP_string &text)
{
replace_all(text, "$quote", "''");
replace_all(text, "$semicolon", ",");
}
void CFred_mission_save::convert_special_tags_to_retail()
{
int i, team, stage;
if (Mission_save_format != FSO_FORMAT_RETAIL)
return;
for (team = 0; team < Num_teams; team++) {
// command briefing
for (stage = 0; stage < Cmd_briefs[team].num_stages; stage++) {
convert_special_tags_to_retail(Cmd_briefs[team].stage[stage].text);
}
// briefing
for (stage = 0; stage < Briefings[team].num_stages; stage++) {
convert_special_tags_to_retail(Briefings[team].stages[stage].text);
}
// debriefing
for (stage = 0; stage < Debriefings[team].num_stages; stage++) {
convert_special_tags_to_retail(Debriefings[team].stages[stage].text);
}
}
for (i = Num_builtin_messages; i < Num_messages; i++) {
convert_special_tags_to_retail(Messages[i].message, MESSAGE_LENGTH);
}
}
int CFred_mission_save::fout(char *format, ...)
{
// don't output anything if we're saving in retail and have version-specific comments active
if (Mission_save_format == FSO_FORMAT_RETAIL && !fso_ver_comment.empty()) {
return 0;
}
SCP_string str;
va_list args;
if (err) {
return err;
}
va_start(args, format);
vsprintf(str, format, args);
va_end(args);
cfputs(str.c_str(), fp);
return 0;
}
int CFred_mission_save::fout_ext(char *pre_str, char *format, ...)
{
// don't output anything if we're saving in retail and have version-specific comments active
if (Mission_save_format == FSO_FORMAT_RETAIL && !fso_ver_comment.empty()) {
return 0;
}
SCP_string str_scp;
SCP_string str_out_scp;
va_list args;
int str_id;
if (err) {
return err;
}
va_start(args, format);
vsprintf(str_scp, format, args);
va_end(args);
if (pre_str) {
str_out_scp = pre_str;
}
// lookup the string in the hash table
str_id = fhash_string_exists(str_scp.c_str());
// doesn't exist, so assign it an ID of -1 and stick it in the table
if (str_id <= -2) {
str_out_scp += " XSTR(\"";
str_out_scp += str_scp;
str_out_scp += "\", -1)";
// add the string to the table
fhash_add_str(str_scp.c_str(), -1);
}
// _does_ exist, so just write it out as it is
else {
char buf[10];
sprintf(buf, "%d", str_id);
str_out_scp += " XSTR(\"";
str_out_scp += str_scp;
str_out_scp += "\", ";
str_out_scp += buf;
str_out_scp += ")";
}
char *str_out_c = vm_strdup(str_out_scp.c_str());
// this could be a multi-line string, so we've got to handle it all properly
if (!fso_ver_comment.empty()) {
bool first_line = true;
char *str_p = str_out_c;
char *ch = strchr(str_out_c, '\n');
// if we have something, and it's not just at the end, then process it specially
if ((ch != NULL) && (*(ch + 1) != '\0')) {
do {
if (*(ch + 1) != '\0') {
*ch = '\0';
if (first_line) {
first_line = false;
} else {
cfputs(fso_ver_comment.back().c_str(), fp);
cfputs(" ", fp);
}
cfputs(str_p, fp);
cfputc('\n', fp);
str_p = ch + 1;
} else {
if (first_line) {
first_line = false;
} else {
cfputs(fso_ver_comment.back().c_str(), fp);
cfputs(" ", fp);
}
cfputs(str_p, fp);
str_p = ch + 1;
break;
}
} while ((ch = strchr(str_p, '\n')) != NULL);
// be sure to account for any ending elements too
if (strlen(str_p)) {
cfputs(fso_ver_comment.back().c_str(), fp);
cfputs(" ", fp);
cfputs(str_p, fp);
}
vm_free(str_out_c);
return 0;
}
}
cfputs(str_out_c, fp);
vm_free(str_out_c);
return 0;
}
int CFred_mission_save::fout_version(char *format, ...)
{
SCP_string str_scp;
char *ch = NULL;
va_list args;
if (err) {
return err;
}
// if we're saving in retail format, we can completely skip anything with a version-specific comment
if (Mission_save_format == FSO_FORMAT_RETAIL && !fso_ver_comment.empty()) {
return 0;
}
// output the version first thing, but skip the special case where we use
// fout_version() for multiline value strings (typically indicated by an initial space)
if ((Mission_save_format == FSO_FORMAT_COMPATIBILITY_MODE) && (*format != ' ') && !fso_ver_comment.empty()) {
while (*format == '\n') {
str_scp.append(1, *format);
format++;
}
str_scp.append(fso_ver_comment.back().c_str());
str_scp.append(" ");
cfputs(str_scp.c_str(), fp);
str_scp = "";
}
va_start(args, format);
vsprintf(str_scp, format, args);
va_end(args);
char *str_c = vm_strdup(str_scp.c_str());
// this could be a multi-line string, so we've got to handle it all properly
if ((Mission_save_format == FSO_FORMAT_COMPATIBILITY_MODE) && !fso_ver_comment.empty()) {
bool first_line = true;
char *str_p = str_c;
ch = strchr(str_c, '\n');
// if we have something, and it's not just at the end, then process it specially
if ((ch != NULL) && (*(ch + 1) != '\0')) {
do {
if (*(ch + 1) != '\0') {
*ch = '\0';
if (first_line) {
first_line = false;
} else {
cfputs(fso_ver_comment.back().c_str(), fp);
cfputs(" ", fp);
}
cfputs(str_p, fp);
cfputc('\n', fp);
str_p = ch + 1;
} else {
if (first_line) {
first_line = false;
} else {
cfputs(fso_ver_comment.back().c_str(), fp);
cfputs(" ", fp);
}
cfputs(str_p, fp);
str_p = ch + 1;
break;
}
} while ((ch = strchr(str_p, '\n')) != NULL);
// be sure to account for any ending elements too
if (strlen(str_p)) {
cfputs(fso_ver_comment.back().c_str(), fp);
cfputs(" ", fp);
cfputs(str_p, fp);
}
vm_free(str_c);
return 0;
}
}
cfputs(str_c, fp);
vm_free(str_c);
return 0;
}
void CFred_mission_save::parse_comments(int newlines)
{
char *comment_start = NULL;
int state = 0, same_line = 0, first_comment = 1, tab = 0, flag = 0;
bool version_added = false;
if (newlines < 0) {
newlines = -newlines;
tab = 1;
}
if (newlines)
same_line = 1;
if (fred_parse_flag || !Token_found_flag || !token_found || (token_found && (*Parse_text_raw == '\0'))) {
while (newlines-- > 0)
fout("\n");
if (tab && token_found)
fout_version("\t%s", token_found);
else if (token_found)
fout_version("%s", token_found);
else if (tab)
fout("\t");
return;
}
while (*raw_ptr != '\0') {
// state values (as far as I could figure out):
// 0 - raw_ptr not inside comment
// 1 - raw_ptr inside /**/ comment
// 2 - raw_ptr inside ; (newline-delimited) comment
// 3,4 - raw_ptr inside ;; (FSO version) comment
if (!state) {
if (token_found && (*raw_ptr == *token_found))
if (!strnicmp(raw_ptr, token_found, strlen(token_found))) {
same_line = newlines - 1 + same_line;
while (same_line-- > 0)
fout("\n");
if (tab) {
fout_version("\t");
fout("%s", token_found);
} else {
fout_version("%s", token_found);
}
// If you have a bunch of lines that all start with the same token (like, say, "+Subsystem:"),
// this makes it so it won't just repeatedly match the first one. -MageKing17
raw_ptr++;
if (version_added)
fso_comment_pop();
return;
}
if ((*raw_ptr == '/') && (raw_ptr[1] == '*')) {
comment_start = raw_ptr;
state = 1;
}
if ((*raw_ptr == ';') && (raw_ptr[1] != '!')) {
comment_start = raw_ptr;
state = 2;
// check for a FSO version comment, but if we can't understand it then
// just handle it as a regular comment
if ((raw_ptr[1] == ';') && (raw_ptr[2] == 'F') && (raw_ptr[3] == 'S') && (raw_ptr[4] == 'O')) {
int major, minor, build, revis;
int s_num = scan_fso_version_string(raw_ptr, &major, &minor, &build, &revis);
// hack for releases
if (FS_VERSION_REVIS < 1000) {
s_num = 3;
}
if ((s_num == 3) && ((major < FS_VERSION_MAJOR) || ((major == FS_VERSION_MAJOR) && ((minor < FS_VERSION_MINOR) || ((minor == FS_VERSION_MINOR) && (build <= FS_VERSION_BUILD)))))) {
state = 3;
} 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)))))))) {
state = 3;
} else {
state = 4;
}
}
}
if (*raw_ptr == '\n')
flag = 1;
if (flag && state && !(state == 3))
fout("\n");
} else {
if (*raw_ptr == '\n') {
if (state == 2) {
if (first_comment && !flag)
fout("\t\t");
*raw_ptr = 0;
fout("%s\n", comment_start);
*raw_ptr = '\n';
state = first_comment = same_line = flag = 0;
} else if (state == 4) {
same_line = newlines - 2 + same_line;
while (same_line-- > 0)
fout("\n");
if (*(raw_ptr - 1) == '\r') {
*(raw_ptr - 1) = '\0';
} else {
*raw_ptr = 0;
}
fout("%s\n", comment_start);
if (*(raw_ptr - 1) == '\0') {
*(raw_ptr - 1) = '\r';
} else {
*raw_ptr = '\n';
}
state = first_comment = same_line = flag = 0;
}
}
if ((*raw_ptr == '*') && (raw_ptr[1] == '/') && (state == 1)) {
if (first_comment && !flag)
fout("\t\t");
const char tmp = raw_ptr[2];
raw_ptr[2] = 0;
fout("%s", comment_start);
raw_ptr[2] = tmp;
state = first_comment = flag = 0;
}
if ((*raw_ptr == ';') && (raw_ptr[1] == ';') && (state == 3)) {
const char tmp = raw_ptr[2];
raw_ptr[2] = 0;
if (version_added)
fso_comment_pop();
else
version_added = true;
fso_comment_push(comment_start);
raw_ptr[2] = tmp;
state = first_comment = flag = 0;
raw_ptr++;
}
}
raw_ptr++;
}
if (version_added)
fso_comment_pop();
return;
}
void CFred_mission_save::save_ai_goals(ai_goal *goalp, int ship)
{
char *str = NULL, buf[80];
int i, valid, flag = 1;
for (i = 0; i < MAX_AI_GOALS; i++) {
if (goalp[i].ai_mode == AI_GOAL_NONE)
continue;
if (flag) {
if (optional_string_fred("$AI Goals:", "$Name:"))
parse_comments();
else
fout("\n$AI Goals:");
fout(" ( goals ");
flag = 0;
}
if (goalp[i].ai_mode == AI_GOAL_CHASE_ANY) {
fout("( ai-chase-any %d ) ", goalp[i].priority);
} else if (goalp[i].ai_mode == AI_GOAL_UNDOCK) {
fout("( ai-undock %d ) ", goalp[i].priority);
} else if (goalp[i].ai_mode == AI_GOAL_KEEP_SAFE_DISTANCE) {
fout("( ai-keep-safe-distance %d ) ", goalp[i].priority);
} else if (goalp[i].ai_mode == AI_GOAL_PLAY_DEAD) {
fout("( ai-play-dead %d ) ", goalp[i].priority);
} else if (goalp[i].ai_mode == AI_GOAL_PLAY_DEAD_PERSISTENT) {
fout("( ai-play-dead-persistent %d ) ", goalp[i].priority);
} else if (goalp[i].ai_mode == AI_GOAL_WARP) {
fout("( ai-warp-out %d ) ", goalp[i].priority);
} else {
valid = 1;
if (!goalp[i].target_name) {
Warning(LOCATION, "Ai goal has no target where one is required");
} else {
sprintf(buf, "\"%s\"", goalp[i].target_name);
switch (goalp[i].ai_mode) {
case AI_GOAL_WAYPOINTS:
str = "ai-waypoints";
break;
case AI_GOAL_WAYPOINTS_ONCE:
str = "ai-waypoints-once";
break;
case AI_GOAL_DESTROY_SUBSYSTEM:
if (goalp[i].docker.index == -1 || !goalp[i].docker.index) {
valid = 0;
Warning(LOCATION, "AI destroy subsystem goal invalid subsystem name\n");
} else {
sprintf(buf, "\"%s\" \"%s\"", goalp[i].target_name, goalp[i].docker.name);
str = "ai-destroy-subsystem";
}
break;
case AI_GOAL_DOCK:
if (ship < 0) {
valid = 0;
Warning(LOCATION, "Wings aren't allowed to have a docking goal\n");
} else if (goalp[i].docker.index == -1 || !goalp[i].docker.index) {
valid = 0;
Warning(LOCATION, "AI dock goal for \"%s\" has invalid docker point "
"(docking with \"%s\")\n", Ships[ship].ship_name, goalp[i].target_name);
} else if (goalp[i].dockee.index == -1 || !goalp[i].dockee.index) {
valid = 0;
Warning(LOCATION, "AI dock goal for \"%s\" has invalid dockee point "
"(docking with \"%s\")\n", Ships[ship].ship_name, goalp[i].target_name);
} else {
sprintf(buf, "\"%s\" \"%s\" \"%s\"", goalp[i].target_name,
goalp[i].docker.name, goalp[i].dockee.name);
str = "ai-dock";
}
break;
case AI_GOAL_CHASE:
str = "ai-chase";
break;
case AI_GOAL_CHASE_WING:
str = "ai-chase-wing";
break;
case AI_GOAL_CHASE_SHIP_CLASS:
str = "ai-chase-ship-class";
break;
case AI_GOAL_GUARD:
str = "ai-guard";
break;
case AI_GOAL_GUARD_WING:
str = "ai-guard-wing";
break;
case AI_GOAL_DISABLE_SHIP:
str = "ai-disable-ship";
break;
case AI_GOAL_DISARM_SHIP:
str = "ai-disarm-ship";
break;
case AI_GOAL_IGNORE:
str = "ai-ignore";
break;
case AI_GOAL_IGNORE_NEW:
str = "ai-ignore-new";
break;
case AI_GOAL_EVADE_SHIP:
str = "ai-evade-ship";
break;
case AI_GOAL_STAY_NEAR_SHIP:
str = "ai-stay-near-ship";
break;
case AI_GOAL_STAY_STILL:
str = "ai-stay-still";
break;
default:
Assert(0);
}
if (valid)
fout("( %s %s %d ) ", str, buf, goalp[i].priority);
}
}
fso_comment_pop();
}
if (!flag)
fout(")");
fso_comment_pop(true);
}
int CFred_mission_save::save_asteroid_fields()
{
int i;
fred_parse_flag = 0;
required_string_fred("#Asteroid Fields");
parse_comments(2);
for (i = 0; i < 1 /*MAX_ASTEROID_FIELDS*/; i++) {
if (!Asteroid_field.num_initial_asteroids)
continue;
required_string_fred("$Density:");
parse_comments(2);
fout(" %d", Asteroid_field.num_initial_asteroids);
// field type
if (optional_string_fred("+Field Type:")) {
parse_comments();
} else {
fout("\n+Field Type:");
}
fout(" %d", Asteroid_field.field_type);
// debris type
if (optional_string_fred("+Debris Genre:")) {
parse_comments();
} else {
fout("\n+Debris Genre:");
}
fout(" %d", Asteroid_field.debris_genre);
// field_debris_type (only if ship genre)
if (Asteroid_field.debris_genre == DG_SHIP) {
for (int idx = 0; idx < 3; idx++) {
if (Asteroid_field.field_debris_type[idx] != -1) {
if (optional_string_fred("+Field Debris Type:")) {
parse_comments();
} else {
fout("\n+Field Debris Type:");
}
fout(" %d", Asteroid_field.field_debris_type[idx]);
}
}
} else {
// asteroid subtypes stored in field_debris_type as -1 or 1
for (auto idx = 0; idx < 3; idx++) {
if (Asteroid_field.field_debris_type[idx] != -1) {
if (optional_string_fred("+Field Debris Type:")) {
parse_comments();
} else {
fout("\n+Field Debris Type:");
}
fout(" %d", idx);
}
}
}
required_string_fred("$Average Speed:");
parse_comments();
fout(" %f", vm_vec_mag(&Asteroid_field.vel));
required_string_fred("$Minimum:");
parse_comments();
save_vector(Asteroid_field.min_bound);
required_string_fred("$Maximum:");
parse_comments();
save_vector(Asteroid_field.max_bound);
if (Asteroid_field.has_inner_bound == 1) {
if (optional_string_fred("+Inner Bound:")) {
parse_comments();
} else {
fout("\n+Inner Bound:");
}
required_string_fred("$Minimum:");
parse_comments();
save_vector(Asteroid_field.inner_min_bound);
required_string_fred("$Maximum:");
parse_comments();
save_vector(Asteroid_field.inner_max_bound);
}
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_bitmaps()
{
int i;
uint j;
fred_parse_flag = 0;
required_string_fred("#Background bitmaps");
parse_comments(2);
fout("\t\t;! %d total\n", stars_get_num_bitmaps());
required_string_fred("$Num stars:");
parse_comments();
fout(" %d", Num_stars);
required_string_fred("$Ambient light level:");
parse_comments();
fout(" %d", The_mission.ambient_light_level);
// neb2 stuff
if (The_mission.flags[Mission::Mission_Flags::Fullneb]) {
required_string_fred("+Neb2:");
parse_comments();
fout(" %s\n", Neb2_texture_name);
required_string_fred("+Neb2Flags:");
parse_comments();
fout(" %d\n", Neb2_poof_flags);
}
// neb 1 stuff
else {
if (Nebula_index >= 0) {
if (optional_string_fred("+Nebula:")) {
parse_comments();
} else {
fout("\n+Nebula:");
}
fout(" %s", Nebula_filenames[Nebula_index]);
required_string_fred("+Color:");
parse_comments();
fout(" %s", Nebula_colors[Mission_palette]);
required_string_fred("+Pitch:");
parse_comments();
fout(" %d", Nebula_pitch);
required_string_fred("+Bank:");
parse_comments();
fout(" %d", Nebula_bank);
required_string_fred("+Heading:");
parse_comments();
fout(" %d", Nebula_heading);
}
}
fso_comment_pop();
// Goober5000 - save all but the lowest priority using the special comment tag
for (i = 0; i < (int)Backgrounds.size(); i++) {
bool tag = (i < (int)Backgrounds.size() - 1);
background_t *background = &Backgrounds[i];
fso_comment_push(";;FSO 3.6.9;;");
if (optional_string_fred("$Bitmap List:")) {
parse_comments(2);
} else {
fout_version("\n\n$Bitmap List:");
}
if (!tag) {
fso_comment_pop(true);
}
// save suns by filename
for (j = 0; j < background->suns.size(); j++) {
starfield_list_entry *sle = &background->suns[j];
// filename
required_string_fred("$Sun:");
parse_comments();
fout(" %s", sle->filename);
// angles
required_string_fred("+Angles:");
parse_comments();
fout(" %f %f %f", sle->ang.p, sle->ang.b, sle->ang.h);
// scale
required_string_fred("+Scale:");
parse_comments();
fout(" %f", sle->scale_x);
}
// save background bitmaps by filename
for (j = 0; j < background->bitmaps.size(); j++) {
starfield_list_entry *sle = &background->bitmaps[j];
// filename
required_string_fred("$Starbitmap:");
parse_comments();
fout(" %s", sle->filename);
// angles
required_string_fred("+Angles:");
parse_comments();
fout(" %f %f %f", sle->ang.p, sle->ang.b, sle->ang.h);
// scale
required_string_fred("+ScaleX:");
parse_comments();
fout(" %f", sle->scale_x);
required_string_fred("+ScaleY:");
parse_comments();
fout(" %f", sle->scale_y);
// div
required_string_fred("+DivX:");
parse_comments();
fout(" %d", sle->div_x);
required_string_fred("+DivY:");
parse_comments();
fout(" %d", sle->div_y);
}
fso_comment_pop();
}
// taylor's environment map thingy
if (strlen(The_mission.envmap_name) > 0) { //-V805
fso_comment_push(";;FSO 3.6.9;;");
if (optional_string_fred("$Environment Map:")) {
parse_comments(2);
fout(" %s", The_mission.envmap_name);
} else {
fout_version("\n\n$Environment Map: %s", The_mission.envmap_name);
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.9;; $Environment Map:");
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_briefing()
{
int i, j, k, nb;
SCP_string sexp_out;
brief_stage *bs;
brief_icon *bi;
for (nb = 0; nb < Num_teams; nb++) {
required_string_fred("#Briefing");
parse_comments(2);
required_string_fred("$start_briefing");
parse_comments();
save_custom_bitmap("$briefing_background_640:", "$briefing_background_1024:", Briefings[nb].background[GR_640], Briefings[nb].background[GR_1024]);
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]);
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]);
Assert(Briefings[nb].num_stages <= MAX_BRIEF_STAGES);
required_string_fred("$num_stages:");
parse_comments();
fout(" %d", Briefings[nb].num_stages);
for (i = 0; i < Briefings[nb].num_stages; i++) {
bs = &Briefings[nb].stages[i];
required_string_fred("$start_stage");
parse_comments();
required_string_fred("$multi_text");
parse_comments();
// XSTR
fout_ext("\n", "%s", bs->text.c_str());
required_string_fred("$end_multi_text", "$start_stage");
parse_comments();
if (!drop_white_space(bs->voice)[0]) {
strcpy_s(bs->voice, "None");
}
required_string_fred("$voice:");
parse_comments();
fout(" %s", bs->voice);
required_string_fred("$camera_pos:");
parse_comments();
save_vector(bs->camera_pos);
required_string_fred("$camera_orient:");
parse_comments();
save_matrix(bs->camera_orient);
required_string_fred("$camera_time:");
parse_comments();
fout(" %d", bs->camera_time);
required_string_fred("$num_lines:");
parse_comments();
fout(" %d", bs->num_lines);
for (k = 0; k < bs->num_lines; k++) {
required_string_fred("$line_start:");
parse_comments();
fout(" %d", bs->lines[k].start_icon);
required_string_fred("$line_end:");
parse_comments();
fout(" %d", bs->lines[k].end_icon);
fso_comment_pop();
}
required_string_fred("$num_icons:");
parse_comments();
Assert(bs->num_icons <= MAX_STAGE_ICONS);
fout(" %d", bs->num_icons);
required_string_fred("$Flags:");
parse_comments();
fout(" %d", bs->flags);
required_string_fred("$Formula:");
parse_comments();
convert_sexp_to_string(sexp_out, bs->formula, SEXP_SAVE_MODE);
fout(" %s", sexp_out.c_str());
for (j = 0; j < bs->num_icons; j++) {
bi = &bs->icons[j];
required_string_fred("$start_icon");
parse_comments();
required_string_fred("$type:");
parse_comments();
fout(" %d", bi->type);
required_string_fred("$team:");
parse_comments();
fout(" %s", Iff_info[bi->team].iff_name);
required_string_fred("$class:");
parse_comments();
if (bi->ship_class < 0)
bi->ship_class = 0;
fout(" %s", Ship_info[bi->ship_class].name);
required_string_fred("$pos:");
parse_comments();
save_vector(bi->pos);
if (drop_white_space(bi->label)[0]) {
if (optional_string_fred("$label:"))
parse_comments();
else
fout("\n$label:");
fout_ext(" ", "%s", bi->label);
}
if (optional_string_fred("+id:"))
parse_comments();
else
fout("\n+id:");
fout(" %d", bi->id);
required_string_fred("$hlight:");
parse_comments();
fout(" %d", (bi->flags & BI_HIGHLIGHT) ? 1 : 0);
if (Mission_save_format != FSO_FORMAT_RETAIL) {
required_string_fred("$mirror:");
parse_comments();
fout(" %d", (bi->flags & BI_MIRROR_ICON) ? 1 : 0);
}
if ((Mission_save_format != FSO_FORMAT_RETAIL) && (bi->flags & BI_USE_WING_ICON)) {
required_string_fred("$use wing icon:");
parse_comments();
fout(" %d", (bi->flags & BI_USE_WING_ICON) ? 1 : 0);
}
required_string_fred("$multi_text");
parse_comments();
// sprintf(out,"\n%s", bi->text);
// fout(out);
required_string_fred("$end_multi_text");
parse_comments();
required_string_fred("$end_icon");
parse_comments();
fso_comment_pop();
}
required_string_fred("$end_stage");
parse_comments();
fso_comment_pop();
}
required_string_fred("$end_briefing");
parse_comments();
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_campaign_file(char *pathname)
{
int i, j, m, flag;
Campaign_tree_formp->save_tree(); // flush all changes so they get saved.
Campaign_tree_viewp->sort_elements();
reset_parse();
fred_parse_flag = 0;
pathname = cf_add_ext(pathname, FS_CAMPAIGN_FILE_EXT);
fp = cfopen(pathname, "wt", CFILE_NORMAL, CF_TYPE_MISSIONS);
if (!fp) {
nprintf(("Error", "Can't open campaign file to save.\n"));
return -1;
}
required_string_fred("$Name:");
parse_comments(0);
fout(" %s", Campaign.name);
Assert((Campaign.type >= 0) && (Campaign.type < MAX_CAMPAIGN_TYPES));
required_string_fred("$Type:");
parse_comments();
fout(" %s", campaign_types[Campaign.type]);
// XSTR
if (Campaign.desc) {
required_string_fred("+Description:");
parse_comments();
fout_ext("\n", "%s", Campaign.desc);
fout("\n$end_multi_text");
}
if (Campaign.type != CAMPAIGN_TYPE_SINGLE) {
required_string_fred("+Num Players:");
parse_comments();
fout(" %d", Campaign.num_players);
}
// campaign flags - Goober5000
if (Mission_save_format != FSO_FORMAT_RETAIL) {
optional_string_fred("$Flags:");
parse_comments();
fout(" %d\n", Campaign.flags);
}
// write out the ships and weapons which the player can start the campaign with
optional_string_fred("+Starting Ships: (");
parse_comments(2);
for (i = 0; i < ship_info_size(); i++) {
if (Campaign.ships_allowed[i])
fout(" \"%s\" ", Ship_info[i].name);
}
fout(")\n");
optional_string_fred("+Starting Weapons: (");
parse_comments();
for (i = 0; i < weapon_info_size(); i++) {
if (Campaign.weapons_allowed[i])
fout(" \"%s\" ", Weapon_info[i].name);
}
fout(")\n");
fred_parse_flag = 0;
for (i = 0; i < Campaign.num_missions; i++) {
m = Sorted[i];
required_string_either_fred("$Mission:", "#End");
required_string_fred("$Mission:");
parse_comments(2);
fout(" %s", Campaign.missions[m].name);
if (strlen(Campaign.missions[i].briefing_cutscene)) {
if (optional_string_fred("+Briefing Cutscene:", "$Mission"))
parse_comments();
else
fout("\n+Briefing Cutscene:");
fout(" %s", Campaign.missions[i].briefing_cutscene);
}
required_string_fred("+Flags:", "$Mission:");
parse_comments();
// Goober5000
if (Mission_save_format != FSO_FORMAT_RETAIL) {
// don't save Bastion flag
fout(" %d", Campaign.missions[m].flags & ~CMISSION_FLAG_BASTION);
// new main hall stuff
if (optional_string_fred("+Main Hall:", "$Mission:"))
parse_comments();
else
fout("\n+Main Hall:");
fout(" %s", Campaign.missions[m].main_hall.c_str());
} else {
// save Bastion flag properly
fout(" %d", Campaign.missions[m].flags | ((Campaign.missions[m].main_hall != "") ? CMISSION_FLAG_BASTION : 0));
}
if (Campaign.missions[m].debrief_persona_index > 0) {
fso_comment_push(";;FSO 3.6.8;;");
if (optional_string_fred("+Debriefing Persona Index:")) {
parse_comments(1);
fout(" %d", Campaign.missions[m].debrief_persona_index);
} else {
fout_version("\n+Debriefing Persona Index: %d", Campaign.missions[m].debrief_persona_index);
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.8;; +Debriefing Persona Index:");
}
// save campaign link sexp
bool mission_loop = false;
bool mission_fork = false;
flag = 0;
for (j = 0; j < Total_links; j++) {
if (Links[j].from == m) {
if (!flag) {
if (optional_string_fred("+Formula:", "$Mission:"))
parse_comments();
else
fout("\n+Formula:");
fout(" ( cond\n");
flag = 1;
}
//save_campaign_sexp(Links[j].sexp, Campaign.missions[Links[j].to].name);
if (Links[j].is_mission_loop) {
mission_loop = true;
} else if (Links[j].is_mission_fork && (Mission_save_format != FSO_FORMAT_RETAIL)) {
mission_fork = true;
} else {
save_campaign_sexp(Links[j].sexp, Links[j].to);
}
}
}
if (flag) {
fout(")");
}
// now save campaign loop sexp
if (mission_loop || mission_fork) {
if (mission_loop)
required_string_fred("\n+Mission Loop:");
else
required_string_fred("\n+Mission Fork:");
parse_comments();
int num_mission_special = 0;
for (j = 0; j < Total_links; j++) {
if ((Links[j].from == m) && (Links[j].is_mission_loop || Links[j].is_mission_fork)) {
num_mission_special++;
if ((num_mission_special == 1) && Links[j].mission_branch_txt) {
if (mission_loop)
required_string_fred("+Mission Loop Text:");
else
required_string_fred("+Mission Fork Text:");
parse_comments();
fout_ext("\n", "%s", Links[j].mission_branch_txt);
fout("\n$end_multi_text");
}
if ((num_mission_special == 1) && Links[j].mission_branch_brief_anim) {
if (mission_loop)
required_string_fred("+Mission Loop Brief Anim:");
else
required_string_fred("+Mission Fork Brief Anim:");
parse_comments();
fout_ext("\n", "%s", Links[j].mission_branch_brief_anim);
fout("\n$end_multi_text");
}
if ((num_mission_special == 1) && Links[j].mission_branch_brief_sound) {
if (mission_loop)
required_string_fred("+Mission Loop Brief Sound:");
else
required_string_fred("+Mission Fork Brief Sound:");
parse_comments();
fout_ext("\n", "%s", Links[j].mission_branch_brief_sound);
fout("\n$end_multi_text");
}
if (num_mission_special == 1) {
// write out mission loop formula
fout("\n+Formula:");
fout(" ( cond\n");
save_campaign_sexp(Links[j].sexp, Links[j].to);
fout(")");
}
if (mission_fork) {
fout("Option: ", Campaign.missions[Links[j].to].name);
}
}
}
if (mission_loop && num_mission_special > 1) {
char buffer[1024];
sprintf(buffer, "Multiple branching loop error from mission %s\nEdit campaign for *at most* 1 loop from each mission.", Campaign.missions[m].name);
Message(os::dialogs::MESSAGEBOX_ERROR, buffer);
}
}
if (optional_string_fred("+Level:", "$Mission:")) {
parse_comments();
} else {
fout("\n\n+Level:");
}
fout(" %d", Campaign.missions[m].level);
if (optional_string_fred("+Position:", "$Mission:")) {
parse_comments();
} else {
fout("\n+Position:");
}
fout(" %d", Campaign.missions[m].pos);
fso_comment_pop();
}
required_string_fred("#End");
parse_comments(2);
token_found = NULL;
parse_comments();
fout("\n");
cfclose(fp);
if (err)
mprintf(("Campaign saving error code #%d\n", err));
else
Campaign_wnd->error_checker();
fso_comment_pop(true);
return err;
}
void CFred_mission_save::save_campaign_sexp(int node, int link_num)
{
SCP_string sexp_out;
Assert(node >= 0);
// if the link num is -1, then this is a end-of-campaign location
if (link_num != -1) {
if (build_sexp_string(sexp_out, node, 2, SEXP_SAVE_MODE)) {
fout(" (\n %s\n ( next-mission \"%s\" )\n )\n", sexp_out.c_str(), Campaign.missions[link_num].name);
} else {
fout(" ( %s( next-mission \"%s\" ) )\n", sexp_out.c_str(), Campaign.missions[link_num].name);
}
} else {
if (build_sexp_string(sexp_out, node, 2, SEXP_SAVE_MODE)) {
fout(" (\n %s\n ( end-of-campaign )\n )\n", sexp_out.c_str());
} else {
fout(" ( %s( end-of-campaign ) )\n", sexp_out.c_str());
}
}
}
int CFred_mission_save::save_cmd_brief()
{
int stage;
stage = 0;
required_string_fred("#Command Briefing");
parse_comments(2);
if (The_mission.game_type & MISSION_TYPE_MULTI)
return err; // no command briefings allowed in multiplayer missions.
save_custom_bitmap("$Background 640:", "$Background 1024:", Cur_cmd_brief->background[GR_640], Cur_cmd_brief->background[GR_1024], 1);
for (stage = 0; stage < Cur_cmd_brief->num_stages; stage++) {
required_string_fred("$Stage Text:");
parse_comments(2);
// XSTR
fout_ext("\n", "%s", Cur_cmd_brief->stage[stage].text.c_str());
required_string_fred("$end_multi_text", "$Stage Text:");
parse_comments();
required_string_fred("$Ani Filename:");
parse_comments();
fout(" %s", Cur_cmd_brief->stage[stage].ani_filename);
required_string_fred("+Wave Filename:", "$Ani Filename:");
parse_comments();
fout(" %s", Cur_cmd_brief->stage[stage].wave_filename);
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_cmd_briefs()
{
int i;
for (i = 0; i < Num_teams; i++) {
Cur_cmd_brief = &Cmd_briefs[i];
save_cmd_brief();
}
return err;
}
void CFred_mission_save::fso_comment_push(char *ver)
{
if (fso_ver_comment.empty()) {
fso_ver_comment.push_back(SCP_string(ver));
return;
}
SCP_string before = fso_ver_comment.back();
int major, minor, build, revis;
int in_major, in_minor, in_build, in_revis;
int elem1, elem2;
elem1 = scan_fso_version_string(fso_ver_comment.back().c_str(), &major, &minor, &build, &revis);
elem2 = scan_fso_version_string(ver, &in_major, &in_minor, &in_build, &in_revis);
// check consistency
if (elem1 == 3 && elem2 == 4 || elem1 == 4 && elem2 == 3) {
elem1 = elem2 = 3;
} else if ((elem1 >= 3 && elem2 >= 3) && (revis < 1000 || in_revis < 1000)) {
elem1 = elem2 = 3;
}
if ((elem1 == 3) && ((major > in_major) || ((major == in_major) && ((minor > in_minor) || ((minor == in_minor) && (build > in_build)))))) {
// the push'd version is older than our current version, so just push a copy of the previous version
fso_ver_comment.push_back(before);
} 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)))))))) {
// the push'd version is older than our current version, so just push a copy of the previous version
fso_ver_comment.push_back(before);
} else {
fso_ver_comment.push_back(SCP_string(ver));
}
}
void CFred_mission_save::fso_comment_pop(bool pop_all)
{
if (fso_ver_comment.empty()) {
return;
}
if (pop_all) {
fso_ver_comment.clear();
return;
}
fso_ver_comment.pop_back();
}
int CFred_mission_save::save_common_object_data(object *objp, ship *shipp)
{
int j, z;
ship_subsys *ptr = NULL;
ship_info *sip = NULL;
ship_weapon *wp = NULL;
float temp_max_hull_strength;
sip = &Ship_info[shipp->ship_info_index];
if ((int) objp->phys_info.speed) {
if (optional_string_fred("+Initial Velocity:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Initial Velocity:");
fout(" %d", (int) objp->phys_info.speed);
}
// Goober5000
if (Mission_save_format != FSO_FORMAT_RETAIL && (shipp->special_hitpoints > 0)) {
temp_max_hull_strength = (float) shipp->special_hitpoints;
} else {
temp_max_hull_strength = sip->max_hull_strength;
}
if ((int) objp->hull_strength != temp_max_hull_strength) {
if (optional_string_fred("+Initial Hull:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Initial Hull:");
fout(" %d", (int) objp->hull_strength);
}
if ((int) shield_get_strength(objp) != 100) {
if (optional_string_fred("+Initial Shields:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Initial Shields:");
fout(" %d", (int) objp->shield_quadrant[0]);
}
// save normal ship weapons info
required_string_fred("+Subsystem:", "$Name:");
parse_comments();
fout(" Pilot");
wp = &shipp->weapons;
z = 0;
j = wp->num_primary_banks;
while (j-- && (j >= 0))
if (wp->primary_bank_weapons[j] != sip->primary_bank_weapons[j])
z = 1;
if (z) {
if (optional_string_fred("+Primary Banks:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Primary Banks:");
fout(" ( ");
for (j = 0; j < wp->num_primary_banks; j++) {
if (wp->primary_bank_weapons[j] != -1) { // Just in case someone has set a weapon bank to empty
fout("\"%s\" ", Weapon_info[wp->primary_bank_weapons[j]].name);
} else {
fout("\"\" ");
}
}
fout(")");
}
z = 0;
j = wp->num_secondary_banks;
while (j-- && (j >= 0))
if (wp->secondary_bank_weapons[j] != sip->secondary_bank_weapons[j])
z = 1;
if (z) {
if (optional_string_fred("+Secondary Banks:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Secondary Banks:");
fout(" ( ");
for (j = 0; j < wp->num_secondary_banks; j++) {
if (wp->secondary_bank_weapons[j] != -1) {
fout("\"%s\" ", Weapon_info[wp->secondary_bank_weapons[j]].name);
} else {
fout("\"\" ");
}
}
fout(")");
}
z = 0;
j = wp->num_secondary_banks;
while (j-- && (j >= 0))
if (wp->secondary_bank_ammo[j] != 100)
z = 1;
if (z) {
if (optional_string_fred("+Sbank Ammo:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Sbank Ammo:");
fout(" ( ");
for (j = 0; j < wp->num_secondary_banks; j++)
fout("%d ", wp->secondary_bank_ammo[j]);
fout(")");
}
ptr = GET_FIRST(&shipp->subsys_list);
Assert(ptr);
while (ptr != END_OF_LIST(&shipp->subsys_list) && ptr) {
// Crashing here!
if ((ptr->current_hits) || (ptr->system_info && ptr->system_info->type == SUBSYSTEM_TURRET) || (ptr->subsys_cargo_name > 0)) {
if (optional_string_fred("+Subsystem:", "$Name:"))
parse_comments();
else
fout("\n+Subsystem:");
fout(" %s", ptr->system_info->subobj_name);
}
if (ptr->current_hits) {
if (optional_string_fred("$Damage:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n$Damage:");
fout(" %d", (int) ptr->current_hits);
}
if (ptr->subsys_cargo_name > 0) {
if (optional_string_fred("+Cargo Name:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Cargo Name:");
fout_ext(NULL, "%s", Cargo_names[ptr->subsys_cargo_name]);
}
if (ptr->system_info->type == SUBSYSTEM_TURRET)
save_turret_info(ptr, SHIP_INDEX(shipp));
ptr = GET_NEXT(ptr);
fso_comment_pop();
}
/* for (j=0; j<shipp->status_count; j++) {
required_string_fred("$Status Description:");
parse_comments(-1);
fout(" %s", Status_desc_names[shipp->status_type[j]]);
required_string_fred("$Status:");
parse_comments(-1);
fout(" %s", Status_type_names[shipp->status[j]]);
required_string_fred("$Target:");
parse_comments(-1);
fout(" %s", Status_target_names[shipp->target[j]]);
}*/
fso_comment_pop(true);
return err;
}
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)
{
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if ((*string_field_640 != '\0') || (*string_field_1024 != '\0')) {
while (blank_lines-- > 0)
fout("\n");
}
if (*string_field_640 != '\0') {
fout("\n%s %s", expected_string_640, string_field_640);
}
if (*string_field_1024 != '\0') {
fout("\n%s %s", expected_string_1024, string_field_1024);
}
}
}
int CFred_mission_save::save_cutscenes()
{
char type[NAME_LENGTH];
SCP_string sexp_out;
// Let's just assume it has them for now -
if (!(The_mission.cutscenes.empty())) {
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (optional_string_fred("#Cutscenes")) {
parse_comments(2);
} else {
fout_version("\n\n#Cutscenes\n\n");
}
for (uint i = 0; i < The_mission.cutscenes.size(); i++) {
if (strlen(The_mission.cutscenes[i].filename)) {
// determine the name of this cutscene type
switch (The_mission.cutscenes[i].type) {
case MOVIE_PRE_FICTION:
strcpy_s(type, "$Fiction Viewer Cutscene:");
break;
case MOVIE_PRE_CMD_BRIEF:
strcpy_s(type, "$Command Brief Cutscene:");
break;
case MOVIE_PRE_BRIEF:
strcpy_s(type, "$Briefing Cutscene:");
break;
case MOVIE_PRE_GAME:
strcpy_s(type, "$Pre-game Cutscene:");
break;
case MOVIE_PRE_DEBRIEF:
strcpy_s(type, "$Debriefing Cutscene:");
break;
case MOVIE_END_CAMPAIGN:
strcpy_s(type, "$Campaign End Cutscene:");
break;
default:
Int3();
continue;
}
if (optional_string_fred(type)) {
parse_comments();
fout(" %s", The_mission.cutscenes[i].filename);
} else {
fout_version("%s %s\n", type, The_mission.cutscenes[i].filename);
}
required_string_fred("+formula:");
parse_comments();
convert_sexp_to_string(sexp_out, The_mission.cutscenes[i].formula, SEXP_SAVE_MODE);
fout(" %s", sexp_out.c_str());
}
}
required_string_fred("#end");
parse_comments();
} else {
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);
}
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_debriefing()
{
int j, i;
SCP_string sexp_out;
for (j = 0; j < Num_teams; j++) {
Debriefing = &Debriefings[j];
required_string_fred("#Debriefing_info");
parse_comments(2);
save_custom_bitmap("$Background 640:", "$Background 1024:", Debriefing->background[GR_640], Debriefing->background[GR_1024], 1);
required_string_fred("$Num stages:");
parse_comments(2);
fout(" %d", Debriefing->num_stages);
for (i = 0; i < Debriefing->num_stages; i++) {
required_string_fred("$Formula:");
parse_comments(2);
convert_sexp_to_string(sexp_out, Debriefing->stages[i].formula, SEXP_SAVE_MODE);
fout(" %s", sexp_out.c_str());
// XSTR
required_string_fred("$Multi text");
parse_comments();
fout_ext("\n ", "%s", Debriefing->stages[i].text.c_str());
required_string_fred("$end_multi_text");
parse_comments();
if (!drop_white_space(Debriefing->stages[i].voice)[0]) {
strcpy_s(Debriefing->stages[i].voice, "None");
}
required_string_fred("$Voice:");
parse_comments();
fout(" %s", Debriefing->stages[i].voice);
// XSTR
required_string_fred("$Recommendation text:");
parse_comments();
fout_ext("\n ", "%s", Debriefing->stages[i].recommendation_text.c_str());
required_string_fred("$end_multi_text");
parse_comments();
fso_comment_pop();
}
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_events()
{
SCP_string sexp_out;
int i, j, add_flag;
fred_parse_flag = 0;
required_string_fred("#Events");
parse_comments(2);
fout("\t\t;! %d total\n", Num_mission_events);
for (i = 0; i < Num_mission_events; i++) {
required_string_either_fred("$Formula:", "#Goals");
required_string_fred("$Formula:");
parse_comments(i ? 2 : 1);
convert_sexp_to_string(sexp_out, Mission_events[i].formula, SEXP_SAVE_MODE);
fout(" %s", sexp_out.c_str());
if (*Mission_events[i].name) {
if (optional_string_fred("+Name:", "$Formula:")) {
parse_comments();
} else {
fout("\n+Name:");
}
fout(" %s", Mission_events[i].name);
}
if (optional_string_fred("+Repeat Count:", "$Formula:")) {
parse_comments();
} else {
fout("\n+Repeat Count:");
}
// if we have a trigger count but no repeat count, we want the event to loop until it has triggered enough times
if (Mission_events[i].repeat_count == 1 && Mission_events[i].trigger_count != 1) {
fout(" -1");
} else {
fout(" %d", Mission_events[i].repeat_count);
}
if (Mission_save_format != FSO_FORMAT_RETAIL && Mission_events[i].trigger_count != 1) {
fso_comment_push(";;FSO 3.6.11;;");
if (optional_string_fred("+Trigger Count:", "$Formula:")) {
parse_comments();
} else {
fout_version("\n+Trigger Count:");
}
fso_comment_pop();
fout(" %d", Mission_events[i].trigger_count);
}
if (optional_string_fred("+Interval:", "$Formula:")) {
parse_comments();
} else {
fout("\n+Interval:");
}
fout(" %d", Mission_events[i].interval);
if (Mission_events[i].score != 0) {
if (optional_string_fred("+Score:", "$Formula:")) {
parse_comments();
} else {
fout("\n+Score:");
}
fout(" %d", Mission_events[i].score);
}
if (Mission_events[i].chain_delay >= 0) {
if (optional_string_fred("+Chained:", "$Formula:")) {
parse_comments();
} else {
fout("\n+Chained:");
}
fout(" %d", Mission_events[i].chain_delay);
}
//XSTR
if (Mission_events[i].objective_text) {
if (optional_string_fred("+Objective:", "$Formula:")) {
parse_comments();
} else {
fout("\n+Objective:");
}
fout_ext(" ", "%s", Mission_events[i].objective_text);
}
//XSTR
if (Mission_events[i].objective_key_text) {
if (optional_string_fred("+Objective key:", "$Formula:")) {
parse_comments();
} else {
fout("\n+Objective key:");
}
fout_ext(" ", "%s", Mission_events[i].objective_key_text);
}
// save team
if (Mission_events[i].team >= 0) {
if (optional_string_fred("+Team:")) {
parse_comments();
} else {
fout("\n+Team:");
}
fout(" %d", Mission_events[i].team);
}
// save flags, if any
if (Mission_save_format != FSO_FORMAT_RETAIL) {
// we need to lazily-write the tag because we should only write it if there are also flags to write
// (some of the flags are transient, internal flags)
bool wrote_tag = false;
for (j = 0; j < Num_mission_event_flags; ++j) {
if (Mission_events[i].flags & Mission_event_flags[j].def) {
if (!wrote_tag) {
wrote_tag = true;
fso_comment_push(";;FSO 20.0.0;;");
if (optional_string_fred("+Event Flags: (", "$Formula:")) {
parse_comments();
}
else {
fout_version("\n+Event Flags: (");
}
fso_comment_pop();
}
fout(" \"%s\"", Mission_event_flags[j].name);
}
}
if (wrote_tag)
fout(" )");
}
if (Mission_save_format != FSO_FORMAT_RETAIL && Mission_events[i].mission_log_flags != 0) {
fso_comment_push(";;FSO 3.6.11;;");
if (optional_string_fred("+Event Log Flags: (", "$Formula:")) {
parse_comments();
} else {
fout_version("\n+Event Log Flags: (");
}
fso_comment_pop();
for (j = 0; j < MAX_MISSION_EVENT_LOG_FLAGS; j++) {
add_flag = 1 << j;
if (Mission_events[i].mission_log_flags & add_flag) {
fout(" \"%s\"", Mission_event_log_flags[j]);
}
}
fout(" )");
}
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_fiction()
{
if (mission_has_fiction()) {
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (optional_string_fred("#Fiction Viewer"))
parse_comments(2);
else
fout("\n\n#Fiction Viewer");
// we have multiple stages now, so save them all
for (SCP_vector<fiction_viewer_stage>::iterator stage = Fiction_viewer_stages.begin(); stage != Fiction_viewer_stages.end(); ++stage) {
fout("\n");
// save file
required_string_fred("$File:");
parse_comments();
fout(" %s", stage->story_filename);
// save font
if (strlen(stage->font_filename) > 0) //-V805
{
if (optional_string_fred("$Font:"))
parse_comments();
else
fout("\n$Font:");
fout(" %s", stage->font_filename);
} else
optional_string_fred("$Font:");
// save voice
if (strlen(stage->voice_filename) > 0) //-V805
{
if (optional_string_fred("$Voice:"))
parse_comments();
else
fout("\n$Voice:");
fout(" %s", stage->voice_filename);
} else
optional_string_fred("$Voice:");
// save UI
if (strlen(stage->ui_name) > 0) {
if (optional_string_fred("$UI:"))
parse_comments();
else
fout("\n$UI:");
fout(" %s", stage->ui_name);
} else
optional_string_fred("$UI:");
// save background
save_custom_bitmap("$Background 640:", "$Background 1024:", stage->background[GR_640], stage->background[GR_1024]);
// save sexp formula if we have one
if (stage->formula >= 0 && stage->formula != Locked_sexp_true) {
SCP_string sexp_out;
convert_sexp_to_string(sexp_out, stage->formula, SEXP_SAVE_MODE);
if (optional_string_fred("$Formula:"))
parse_comments();
else
fout("\n$Formula:");
fout(" %s", sexp_out.c_str());
} else
optional_string_fred("$Formula:");
}
} else {
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);
}
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_goals()
{
SCP_string sexp_out;
int i;
fred_parse_flag = 0;
required_string_fred("#Goals");
parse_comments(2);
fout("\t\t;! %d total\n", Num_goals);
for (i = 0; i < Num_goals; i++) {
int type;
required_string_either_fred("$Type:", "#Waypoints");
required_string_fred("$Type:");
parse_comments(i ? 2 : 1);
type = Mission_goals[i].type & GOAL_TYPE_MASK;
fout(" %s", Goal_type_names[type]);
if (*Mission_goals[i].name) {
if (optional_string_fred("+Name:", "$Type:"))
parse_comments();
else
fout("\n+Name:");
fout(" %s", Mission_goals[i].name);
}
// XSTR
required_string_fred("$MessageNew:");
parse_comments();
fout_ext(" ", "%s", Mission_goals[i].message);
fout("\n");
required_string_fred("$end_multi_text");
parse_comments(0);
required_string_fred("$Formula:");
parse_comments();
convert_sexp_to_string(sexp_out, Mission_goals[i].formula, SEXP_SAVE_MODE);
fout(" %s", sexp_out.c_str());
if (Mission_goals[i].type & INVALID_GOAL) {
if (optional_string_fred("+Invalid", "$Type:"))
parse_comments();
else
fout("\n+Invalid");
}
if (Mission_goals[i].flags & MGF_NO_MUSIC) {
if (optional_string_fred("+No music", "$Type:"))
parse_comments();
else
fout("\n+No music");
}
if (Mission_goals[i].score != 0) {
if (optional_string_fred("+Score:", "$Type:"))
parse_comments();
else
fout("\n+Score:");
fout(" %d", Mission_goals[i].score);
}
if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) {
if (optional_string_fred("+Team:", "$Type:"))
parse_comments();
else
fout("\n+Team:");
fout(" %d", Mission_goals[i].team);
}
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_matrix(matrix &m)
{
fout("\n\t%f, %f, %f,\n", m.vec.rvec.xyz.x, m.vec.rvec.xyz.y, m.vec.rvec.xyz.z);
fout("\t%f, %f, %f,\n", m.vec.uvec.xyz.x, m.vec.uvec.xyz.y, m.vec.uvec.xyz.z);
fout("\t%f, %f, %f", m.vec.fvec.xyz.x, m.vec.fvec.xyz.y, m.vec.fvec.xyz.z);
return 0;
}
int CFred_mission_save::save_messages()
{
int i;
fred_parse_flag = 0;
required_string_fred("#Messages");
parse_comments(2);
fout("\t\t;! %d total\n", Num_messages - Num_builtin_messages);
// Goober5000 - special Command info
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (stricmp(The_mission.command_sender, DEFAULT_COMMAND))
fout("\n$Command Sender: %s", The_mission.command_sender);
if (The_mission.command_persona != Default_command_persona)
fout("\n$Command Persona: %s", Personas[The_mission.command_persona].name);
}
for (i = Num_builtin_messages; i < Num_messages; i++) {
required_string_either_fred("$Name:", "#Reinforcements");
required_string_fred("$Name:");
parse_comments(2);
fout(" %s", Messages[i].name);
// team
required_string_fred("$Team:");
parse_comments(1);
if ((Messages[i].multi_team < 0) || (Messages[i].multi_team >= 2)) {
fout(" %d", -1);
} else {
fout(" %d", Messages[i].multi_team);
}
// XSTR
required_string_fred("$MessageNew:");
parse_comments();
fout_ext(" ", "%s", Messages[i].message);
fout("\n");
required_string_fred("$end_multi_text");
parse_comments(0);
if (Messages[i].persona_index != -1) {
if (optional_string_fred("+Persona:", "$Name:"))
parse_comments();
else
fout("\n+Persona:");
fout(" %s", Personas[Messages[i].persona_index].name);
}
if (Messages[i].avi_info.name) {
if (optional_string_fred("+AVI Name:", "$Name:"))
parse_comments();
else
fout("\n+AVI Name:");
fout(" %s", Messages[i].avi_info.name);
}
if (Messages[i].wave_info.name) {
if (optional_string_fred("+Wave Name:", "$Name:"))
parse_comments();
else
fout("\n+Wave Name:");
fout(" %s", Messages[i].wave_info.name);
}
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_mission_file(char *pathname)
{
char backup_name[256], savepath[MAX_PATH_LEN], *p;
strcpy_s(savepath, "");
p = strrchr(pathname, '\\');
if (p) {
*p = '\0';
strcpy_s(savepath, pathname);
*p = '\\';
strcat_s(savepath, "\\");
}
strcat_s(savepath, "saving.xxx");
save_mission_internal(savepath);
if (!err) {
strcpy_s(backup_name, pathname);
if (backup_name[strlen(backup_name) - 4] == '.')
backup_name[strlen(backup_name) - 4] = 0;
strcat_s(backup_name, ".bak");
cf_attrib(pathname, 0, FILE_ATTRIBUTE_READONLY, CF_TYPE_MISSIONS);
cf_delete(backup_name, CF_TYPE_MISSIONS);
cf_rename(pathname, backup_name, CF_TYPE_MISSIONS);
cf_rename(savepath, pathname, CF_TYPE_MISSIONS);
}
return err;
}
int CFred_mission_save::save_mission_info()
{
required_string_fred("#Mission Info");
parse_comments(0);
required_string_fred("$Version:");
parse_comments(2);
fout(" %.2f", FRED_MISSION_VERSION);
// XSTR
required_string_fred("$Name:");
parse_comments();
fout_ext(" ", "%s", The_mission.name);
required_string_fred("$Author:");
parse_comments();
fout(" %s", The_mission.author);
required_string_fred("$Created:");
parse_comments();
fout(" %s", The_mission.created);
required_string_fred("$Modified:");
parse_comments();
fout(" %s", The_mission.modified);
required_string_fred("$Notes:");
parse_comments();
fout("\n%s", The_mission.notes);
required_string_fred("$End Notes:");
parse_comments(0);
// XSTR
required_string_fred("$Mission Desc:");
parse_comments(2);
fout_ext("\n", "%s", The_mission.mission_desc);
fout("\n");
required_string_fred("$end_multi_text");
parse_comments(0);
#if 0
if (optional_string_fred("+Game Type:"))
parse_comments(2);
else
fout("\n\n+Game Type:");
fout("\n%s", Game_types[The_mission.game_type]);
#endif
if (optional_string_fred("+Game Type Flags:")) {
parse_comments(1);
} else {
fout("\n+Game Type Flags:");
}
fout(" %d", The_mission.game_type);
if (optional_string_fred("+Flags:")) {
parse_comments(1);
} else {
fout("\n+Flags:");
}
fout(" %" PRIu64, The_mission.flags.to_u64());
// maybe write out Nebula intensity
if (The_mission.flags[Mission::Mission_Flags::Fullneb]) {
Assert(Neb2_awacs > 0.0f);
fout("\n+NebAwacs: %f\n", Neb2_awacs);
// storm name
fout("\n+Storm: %s\n", Mission_parse_storm_name);
}
// Goober5000
if (Mission_save_format != FSO_FORMAT_RETAIL) {
// write out the nebula clipping multipliers
fout("\n+Fog Near Mult: %f\n", Neb2_fog_near_mult);
fout("\n+Fog Far Mult: %f\n", Neb2_fog_far_mult);
if (The_mission.contrail_threshold != CONTRAIL_THRESHOLD_DEFAULT) {
fout("\n$Contrail Speed Threshold: %d\n", The_mission.contrail_threshold);
}
}
// For multiplayer missions -- write out the number of player starts and number of respawns
if (The_mission.game_type & MISSION_TYPE_MULTI) {
if (optional_string_fred("+Num Players:"))
parse_comments(2);
else
fout("\n+Num Players:");
fout(" %d", Player_starts);
if (optional_string_fred("+Num Respawns:"))
parse_comments(2);
else
fout("\n+Num Respawns:");
fout(" %d", The_mission.num_respawns);
if (Mission_save_format != FSO_FORMAT_RETAIL) {
fso_comment_push(";;FSO 3.6.11;;");
if (optional_string_fred("+Max Respawn Time:"))
parse_comments(2);
else {
fout_version("\n+Max Respawn Time:");
}
fso_comment_pop();
fout(" %d", The_mission.max_respawn_delay);
} else {
bypass_comment(";;FSO 3.6.11;; +Max Respawn Time:");
}
}
if (Mission_save_format == FSO_FORMAT_RETAIL) {
if (optional_string_fred("+Red Alert:"))
parse_comments(2);
else
fout("\n+Red Alert:");
fout(" %d", (The_mission.flags[Mission::Mission_Flags::Red_alert]) ? 1 : 0);
}
if (Mission_save_format == FSO_FORMAT_RETAIL) //-V581
{
if (optional_string_fred("+Scramble:"))
parse_comments(2);
else
fout("\n+Scramble:");
fout(" %d", (The_mission.flags[Mission::Mission_Flags::Scramble]) ? 1 : 0);
}
if (optional_string_fred("+Disallow Support:")) {
parse_comments(1);
} else {
fout("\n+Disallow Support:");
}
// this is compatible with non-SCP variants - Goober5000
fout(" %d", (The_mission.support_ships.max_support_ships == 0) ? 1 : 0);
// here be WMCoolmon's hull and subsys repair stuff
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (optional_string_fred("+Hull Repair Ceiling:")) {
parse_comments(1);
} else {
fout("\n+Hull Repair Ceiling:");
}
fout(" %f", The_mission.support_ships.max_hull_repair_val);
if (optional_string_fred("+Subsystem Repair Ceiling:")) {
parse_comments(1);
} else {
fout("\n+Subsystem Repair Ceiling:");
}
fout(" %f", The_mission.support_ships.max_subsys_repair_val);
}
if (Mission_all_attack) {
if (optional_string_fred("+All Teams Attack")) {
parse_comments();
} else {
fout("\n+All Teams Attack");
}
}
if (Entry_delay_time) {
if (optional_string_fred("+Player Entry Delay:"))
parse_comments(2);
else
fout("\n\n+Player Entry Delay:");
fout("\n%f", f2fl(Entry_delay_time));
}
if (optional_string_fred("+Viewer pos:")) {
parse_comments(2);
} else {
fout("\n\n+Viewer pos:");
}
save_vector(view_pos);
if (optional_string_fred("+Viewer orient:")) {
parse_comments();
} else {
fout("\n+Viewer orient:");
}
save_matrix(view_orient);
// squadron info
if (!(The_mission.game_type & MISSION_TYPE_MULTI) && (strlen(The_mission.squad_name) > 0)) { //-V805
// squad name
fout("\n+SquadReassignName: %s", The_mission.squad_name);
// maybe squad logo
if (strlen(The_mission.squad_filename) > 0) { //-V805
fout("\n+SquadReassignLogo: %s", The_mission.squad_filename);
}
}
// Goober5000 - special wing info
if (Mission_save_format != FSO_FORMAT_RETAIL) {
int i;
fout("\n");
// starting wings
if (strcmp(Starting_wing_names[0], "Alpha") || strcmp(Starting_wing_names[1], "Beta") || strcmp(Starting_wing_names[2], "Gamma")) {
fout("\n$Starting wing names: ( ");
for (i = 0; i < MAX_STARTING_WINGS; i++) {
fout("\"%s\" ", Starting_wing_names[i]);
}
fout(")");
}
// squadron wings
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")) {
fout("\n$Squadron wing names: ( ");
for (i = 0; i < MAX_SQUADRON_WINGS; i++) {
fout("\"%s\" ", Squadron_wing_names[i]);
}
fout(")");
}
// tvt wings
if (strcmp(TVT_wing_names[0], "Alpha") || strcmp(TVT_wing_names[1], "Zeta")) {
fout("\n$Team-versus-team wing names: ( ");
for (i = 0; i < MAX_TVT_WINGS; i++) {
fout("\"%s\" ", TVT_wing_names[i]);
}
fout(")");
}
}
save_custom_bitmap("$Load Screen 640:", "$Load Screen 1024:", The_mission.loading_screen[GR_640], The_mission.loading_screen[GR_1024], 1);
// Phreak's skybox stuff
if (strlen(The_mission.skybox_model) > 0) //-V805
{
char out_str[NAME_LENGTH];
char *period;
// kill off any extension, we will add one here
strcpy_s(out_str, The_mission.skybox_model);
period = strrchr(out_str, '.');
if (period != NULL)
*period = 0;
fso_comment_push(";;FSO 3.6.0;;");
if (optional_string_fred("$Skybox Model:")) {
parse_comments(2);
fout(" %s.pof", out_str);
} else {
fout_version("\n\n$Skybox Model: %s.pof", out_str);
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.0;; $Skybox Model:");
}
// orientation?
if ((strlen(The_mission.skybox_model) > 0) && !vm_matrix_same(&vmd_identity_matrix, &The_mission.skybox_orientation)) {
fso_comment_push(";;FSO 3.6.14;;");
if (optional_string_fred("+Skybox Orientation:")) {
parse_comments(1);
save_matrix(The_mission.skybox_orientation);
} else {
fout_version("\n+Skybox Orientation:");
save_matrix(The_mission.skybox_orientation);
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.14;; +Skybox Orientation:");
}
// are skybox flags in use?
if (The_mission.skybox_flags != DEFAULT_NMODEL_FLAGS) {
//char out_str[4096];
fso_comment_push(";;FSO 3.6.11;;");
if (optional_string_fred("+Skybox Flags:")) {
parse_comments(1);
fout(" %d", The_mission.skybox_flags);
} else {
fout_version("\n+Skybox Flags: %d", The_mission.skybox_flags);
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.11;; +Skybox Flags:");
}
// Goober5000's AI profile stuff
int profile_index = AI_PROFILES_INDEX(The_mission.ai_profile);
Assert(profile_index >= 0 && profile_index < MAX_AI_PROFILES);
fso_comment_push(";;FSO 3.6.9;;");
if (optional_string_fred("$AI Profile:")) {
parse_comments(2);
fout(" %s", The_mission.ai_profile->profile_name);
} else {
fout_version("\n\n$AI Profile: %s", The_mission.ai_profile->profile_name);
}
fso_comment_pop();
// sound environment (EFX/EAX) - taylor
sound_env *m_env = &The_mission.sound_environment;
if ((m_env->id >= 0) && (m_env->id < (int) EFX_presets.size())) {
EFXREVERBPROPERTIES *prop = &EFX_presets[m_env->id];
fso_comment_push(";;FSO 3.6.12;;");
fout_version("\n\n$Sound Environment: %s", prop->name.c_str());
if (m_env->volume != prop->flGain) {
fout_version("\n+Volume: %f", m_env->volume);
}
if (m_env->damping != prop->flDecayHFRatio) {
fout_version("\n+Damping: %f", m_env->damping);
}
if (m_env->decay != prop->flDecayTime) {
fout_version("\n+Decay Time: %f", m_env->decay);
}
fso_comment_pop();
}
return err;
}
void CFred_mission_save::save_mission_internal(const char *pathname)
{
CTime t;
t = CTime::GetCurrentTime();
strcpy_s(The_mission.modified, t.Format("%x at %X"));
reset_parse();
fred_parse_flag = 0;
fp = cfopen(pathname, "wt", CFILE_NORMAL, CF_TYPE_MISSIONS);
if (!fp) {
nprintf(("Error", "Can't open mission file to save.\n"));
err = -1;
return;
}
// Goober5000
convert_special_tags_to_retail();
if (save_mission_info())
err = -2;
else if (save_plot_info())
err = -3;
else if (save_variables())
err = -3;
// else if (save_briefing_info())
// err = -4;
else if (save_cutscenes())
err = -4;
else if (save_fiction())
err = -3;
else if (save_cmd_briefs())
err = -4;
else if (save_briefing())
err = -4;
else if (save_debriefing())
err = -5;
else if (save_players())
err = -6;
else if (save_objects())
err = -7;
else if (save_wings())
err = -8;
else if (save_events())
err = -9;
else if (save_goals())
err = -10;
else if (save_waypoints())
err = -11;
else if (save_messages())
err = -12;
else if (save_reinforcements())
err = -13;
else if (save_bitmaps())
err = -14;
else if (save_asteroid_fields())
err = -15;
else if (save_music())
err = -16;
else {
required_string_fred("#End");
parse_comments(2);
token_found = NULL;
parse_comments();
fout("\n");
}
cfclose(fp);
if (err)
mprintf(("Mission saving error code #%d\n", err));
}
int CFred_mission_save::save_music()
{
required_string_fred("#Music");
parse_comments(2);
required_string_fred("$Event Music:");
parse_comments(2);
if (Current_soundtrack_num < 0)
fout(" None");
else
fout(" %s", Soundtracks[Current_soundtrack_num].name);
// Goober5000 - save using the special comment prefix
if (stricmp(The_mission.substitute_event_music_name, "None")) {
fso_comment_push(";;FSO 3.6.9;;");
if (optional_string_fred("$Substitute Event Music:")) {
parse_comments(1);
fout(" %s", The_mission.substitute_event_music_name);
} else {
fout_version("\n$Substitute Event Music: %s", The_mission.substitute_event_music_name);
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.9;; $Substitute Event Music:");
}
required_string_fred("$Briefing Music:");
parse_comments();
if (Mission_music[SCORE_BRIEFING] < 0)
fout(" None");
else
fout(" %s", Spooled_music[Mission_music[SCORE_BRIEFING]].name);
// Goober5000 - save using the special comment prefix
if (stricmp(The_mission.substitute_briefing_music_name, "None")) {
fso_comment_push(";;FSO 3.6.9;;");
if (optional_string_fred("$Substitute Briefing Music:")) {
parse_comments(1);
fout(" %s", The_mission.substitute_briefing_music_name);
} else {
fout_version("\n$Substitute Briefing Music: %s", The_mission.substitute_briefing_music_name);
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.9;; $Substitute Briefing Music:");
}
// avoid keeping the old one around
bypass_comment(";;FSO 3.6.8;; $Substitute Music:");
// old stuff
if (Mission_music[SCORE_DEBRIEF_SUCCESS] != event_music_get_spooled_music_index("Success")) {
if (optional_string_fred("$Debriefing Success Music:")) {
parse_comments(1);
} else {
fout("\n$Debriefing Success Music:");
}
fout(" %s", Mission_music[SCORE_DEBRIEF_SUCCESS] < 0 ? "None" : Spooled_music[Mission_music[SCORE_DEBRIEF_SUCCESS]].name);
}
if (Mission_save_format != FSO_FORMAT_RETAIL && Mission_music[SCORE_DEBRIEF_AVERAGE] != event_music_get_spooled_music_index("Average")) {
if (optional_string_fred("$Debriefing Average Music:")) {
parse_comments(1);
} else {
fout("\n$Debriefing Average Music:");
}
fout(" %s", Mission_music[SCORE_DEBRIEF_AVERAGE] < 0 ? "None" : Spooled_music[Mission_music[SCORE_DEBRIEF_AVERAGE]].name);
}
if (Mission_music[SCORE_DEBRIEF_FAIL] != event_music_get_spooled_music_index("Failure")) {
if (optional_string_fred("$Debriefing Fail Music:")) {
parse_comments(1);
} else {
fout("\n$Debriefing Fail Music:");
}
fout(" %s", Mission_music[SCORE_DEBRIEF_FAIL] < 0 ? "None" : Spooled_music[Mission_music[SCORE_DEBRIEF_FAIL]].name);
}
// Goober5000 - save using the special comment prefix
if (mission_has_fiction() && Mission_music[SCORE_FICTION_VIEWER] >= 0) {
fso_comment_push(";;FSO 3.6.11;;");
if (optional_string_fred("$Fiction Viewer Music:")) {
parse_comments(1);
fout(" %s", Spooled_music[Mission_music[SCORE_FICTION_VIEWER]].name);
} else {
fout_version("\n$Fiction Viewer Music: %s", Spooled_music[Mission_music[SCORE_FICTION_VIEWER]].name);
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.11;; $Fiction Viewer Music:");
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_warp_params(WarpDirection direction, ship *shipp)
{
if (Mission_save_format == FSO_FORMAT_RETAIL)
return err;
// for writing to file; c.f. parse_warp_params
const char *prefix = (direction == WarpDirection::WARP_IN) ? "$Warpin" : "$Warpout";
WarpParams *shipp_params, *sip_params;
if (direction == WarpDirection::WARP_IN)
{
// if exactly the same params used, no need to output anything
if (shipp->warpin_params_index == Ship_info[shipp->ship_info_index].warpin_params_index)
return err;
shipp_params = &Warp_params[shipp->warpin_params_index];
sip_params = &Warp_params[Ship_info[shipp->ship_info_index].warpin_params_index];
}
else
{
// if exactly the same params used, no need to output anything
if (shipp->warpout_params_index == Ship_info[shipp->ship_info_index].warpout_params_index)
return err;
shipp_params = &Warp_params[shipp->warpout_params_index];
sip_params = &Warp_params[Ship_info[shipp->ship_info_index].warpout_params_index];
}
if (shipp_params->warp_type != sip_params->warp_type)
{
// is it a fireball?
if (shipp_params->warp_type & WT_DEFAULT_WITH_FIREBALL)
{
fout("\n%s type: %s", prefix, Fireball_info[shipp_params->warp_type & WT_FLAG_MASK].unique_id);
}
// probably a warp type
else if (shipp_params->warp_type >= 0 && shipp_params->warp_type < Num_warp_types)
{
fout("\n%s type: %s", prefix, Warp_types[shipp_params->warp_type]);
}
}
if (shipp_params->snd_start != sip_params->snd_start)
{
if (shipp_params->snd_start.isValid())
fout("\n%s Start Sound: %s", prefix, gamesnd_get_game_sound(shipp_params->snd_start)->name.c_str());
}
if (shipp_params->snd_end != sip_params->snd_end)
{
if (shipp_params->snd_end.isValid())
fout("\n%s End Sound: %s", prefix, gamesnd_get_game_sound(shipp_params->snd_end)->name.c_str());
}
if (direction == WarpDirection::WARP_OUT && shipp_params->warpout_engage_time != sip_params->warpout_engage_time)
{
if (shipp_params->warpout_engage_time > 0)
fout("\n%s engage time: %.2f", prefix, i2fl(shipp_params->warpout_engage_time) / 1000.0f);
}
if (shipp_params->speed != sip_params->speed)
{
if (shipp_params->speed > 0.0f)
fout("\n%s speed: %.2f", prefix, shipp_params->speed);
}
if (shipp_params->time != sip_params->time)
{
if (shipp_params->time > 0)
fout("\n%s time: %.2f", prefix, i2fl(shipp_params->time) / 1000.0f);
}
if (shipp_params->accel_exp != sip_params->accel_exp)
{
if (shipp_params->accel_exp > 0.0f)
fout("\n%s %s exp: %.2f", prefix, direction == WarpDirection::WARP_IN ? "decel" : "accel", shipp_params->accel_exp);
}
if (shipp_params->radius != sip_params->radius)
{
if (shipp_params->radius > 0.0f)
fout("\n%s radius: %.2f", prefix, shipp_params->radius);
}
if (stricmp(shipp_params->anim, sip_params->anim) != 0)
{
if (strlen(shipp_params->anim) > 0)
fout("\n%s animation: %s", prefix, shipp_params->anim);
}
if (direction == WarpDirection::WARP_OUT && shipp_params->warpout_player_speed != sip_params->warpout_player_speed)
{
if (shipp_params->warpout_player_speed > 0.0f)
fout("\n$Player warpout speed: %.2f", shipp_params->warpout_player_speed);
}
return err;
}
int CFred_mission_save::save_objects()
{
SCP_string sexp_out;
int i, z;
ai_info *aip;
object *objp;
ship *shipp;
ship_info *sip;
required_string_fred("#Objects");
parse_comments(2);
fout("\t\t;! %d total\n", ship_get_num_ships());
for (i = z = 0; i < MAX_SHIPS; i++) {
if (Ships[i].objnum < 0) {
continue;
}
auto j = Objects[Ships[i].objnum].type;
if ((j != OBJ_SHIP) && (j != OBJ_START)) {
continue;
}
shipp = &Ships[i];
objp = &Objects[shipp->objnum];
sip = &Ship_info[shipp->ship_info_index];
required_string_either_fred("$Name:", "#Wings");
required_string_fred("$Name:");
parse_comments(z ? 2 : 1);
fout(" %s\t\t;! Object #%d", shipp->ship_name, i);
// Display name
// The display name is only written if there was one at the start to avoid introducing inconsistencies
if (Mission_save_format != FSO_FORMAT_RETAIL && shipp->has_display_name()) {
char truncated_name[NAME_LENGTH];
strcpy_s(truncated_name, shipp->ship_name);
end_string_at_first_hash_symbol(truncated_name);
// Also, the display name is not written if it's just the truncation of the name at the hash
if (strcmp(shipp->get_display_name(), truncated_name) != 0) {
fout("\n$Display name:");
fout_ext(" ", "%s", shipp->display_name.c_str());
}
}
required_string_fred("\n$Class:");
parse_comments(0);
fout(" %s", Ship_info[shipp->ship_info_index].name);
//alt classes stuff
if (Mission_save_format != FSO_FORMAT_RETAIL) {
for (SCP_vector<alt_class>::iterator ii = shipp->s_alt_classes.begin(); ii != shipp->s_alt_classes.end(); ++ii) {
// is this a variable?
if (ii->variable_index != -1) {
fout("\n$Alt Ship Class: @%s", Sexp_variables[ii->variable_index].variable_name);
} else {
fout("\n$Alt Ship Class: \"%s\"", Ship_info[ii->ship_class].name);
}
// default class?
if (ii->default_to_this_class) {
fout("\n+Default Class:");
}
}
}
// optional alternate type name
if (strlen(Fred_alt_names[i])) {
fout("\n$Alt: %s\n", Fred_alt_names[i]);
}
// optional callsign
if (Mission_save_format != FSO_FORMAT_RETAIL && strlen(Fred_callsigns[i])) {
fout("\n$Callsign: %s\n", Fred_callsigns[i]);
}
required_string_fred("$Team:");
parse_comments();
fout(" %s", Iff_info[shipp->team].iff_name);
if (Mission_save_format != FSO_FORMAT_RETAIL && Ship_info[shipp->ship_info_index].uses_team_colors) {
required_string_fred("$Team Color Setting:");
parse_comments();
fout(" %s", shipp->team_name.c_str());
}
required_string_fred("$Location:");
parse_comments();
save_vector(Objects[shipp->objnum].pos);
required_string_fred("$Orientation:");
parse_comments();
save_matrix(Objects[shipp->objnum].orient);
if (Mission_save_format == FSO_FORMAT_RETAIL) {
required_string_fred("$IFF:");
parse_comments();
fout(" %s", "IFF 1");
}
Assert(shipp->ai_index >= 0);
aip = &Ai_info[shipp->ai_index];
required_string_fred("$AI Behavior:");
parse_comments();
fout(" %s", Ai_behavior_names[aip->behavior]);
if (shipp->weapons.ai_class != Ship_info[shipp->ship_info_index].ai_class) {
if (optional_string_fred("+AI Class:", "$Name:"))
parse_comments();
else
fout("\n+AI Class:");
fout(" %s", Ai_class_names[shipp->weapons.ai_class]);
}
save_ai_goals(Ai_info[shipp->ai_index].goals, i);
// XSTR
required_string_fred("$Cargo 1:");
parse_comments();
fout_ext(" ", "%s", Cargo_names[shipp->cargo1]);
save_common_object_data(&Objects[shipp->objnum], &Ships[i]);
if (shipp->wingnum >= 0) {
shipp->arrival_location = ARRIVE_AT_LOCATION;
}
required_string_fred("$Arrival Location:");
parse_comments();
fout(" %s", Arrival_location_names[shipp->arrival_location]);
if (shipp->arrival_location != ARRIVE_AT_LOCATION) {
if (optional_string_fred("+Arrival Distance:", "$Name:")) {
parse_comments();
} else {
fout("\n+Arrival Distance:");
}
fout(" %d", shipp->arrival_distance);
if (optional_string_fred("$Arrival Anchor:", "$Name:")) {
parse_comments();
} else {
fout("\n$Arrival Anchor:");
}
z = shipp->arrival_anchor;
if (z & SPECIAL_ARRIVAL_ANCHOR_FLAG) {
// get name
char tmp[NAME_LENGTH + 15];
stuff_special_arrival_anchor_name(tmp, z, Mission_save_format == FSO_FORMAT_RETAIL ? 1 : 0);
// save it
fout(" %s", tmp);
} else if (z >= 0) {
fout(" %s", Ships[z].ship_name);
} else {
fout(" <error>");
}
}
// Goober5000
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if ((shipp->arrival_location == ARRIVE_FROM_DOCK_BAY) && (shipp->arrival_path_mask > 0)) {
int anchor_shipnum;
polymodel *pm;
anchor_shipnum = shipp->arrival_anchor;
Assert(anchor_shipnum >= 0 && anchor_shipnum < MAX_SHIPS);
fout("\n+Arrival Paths: ( ");
pm = model_get(Ship_info[Ships[anchor_shipnum].ship_info_index].model_num);
for (auto n = 0; n < pm->ship_bay->num_paths; n++) {
if (shipp->arrival_path_mask & (1 << n)) {
fout("\"%s\" ", pm->paths[pm->ship_bay->path_indexes[n]].name);
}
}
fout(")");
}
}
if (shipp->arrival_delay) {
if (optional_string_fred("+Arrival Delay:", "$Name:"))
parse_comments();
else
fout("\n+Arrival Delay:");
fout(" %d", shipp->arrival_delay);
}
required_string_fred("$Arrival Cue:");
parse_comments();
convert_sexp_to_string(sexp_out, shipp->arrival_cue, SEXP_SAVE_MODE);
fout(" %s", sexp_out.c_str());
if (shipp->wingnum >= 0) {
shipp->departure_location = DEPART_AT_LOCATION;
}
required_string_fred("$Departure Location:");
parse_comments();
fout(" %s", Departure_location_names[shipp->departure_location]);
if (shipp->departure_location != DEPART_AT_LOCATION) {
required_string_fred("$Departure Anchor:");
parse_comments();
if (shipp->departure_anchor >= 0)
fout(" %s", Ships[shipp->departure_anchor].ship_name);
else
fout(" <error>");
}
// Goober5000
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if ((shipp->departure_location == DEPART_AT_DOCK_BAY) && (shipp->departure_path_mask > 0)) {
int anchor_shipnum;
polymodel *pm;
anchor_shipnum = shipp->departure_anchor;
Assert(anchor_shipnum >= 0 && anchor_shipnum < MAX_SHIPS);
fout("\n+Departure Paths: ( ");
pm = model_get(Ship_info[Ships[anchor_shipnum].ship_info_index].model_num);
for (auto n = 0; n < pm->ship_bay->num_paths; n++) {
if (shipp->departure_path_mask & (1 << n)) {
fout("\"%s\" ", pm->paths[pm->ship_bay->path_indexes[n]].name);
}
}
fout(")");
}
}
if (shipp->departure_delay) {
if (optional_string_fred("+Departure delay:", "$Name:"))
parse_comments();
else
fout("\n+Departure delay:");
fout(" %d", shipp->departure_delay);
}
required_string_fred("$Departure Cue:");
parse_comments();
convert_sexp_to_string(sexp_out, shipp->departure_cue, SEXP_SAVE_MODE);
fout(" %s", sexp_out.c_str());
save_warp_params(WarpDirection::WARP_IN, shipp);
save_warp_params(WarpDirection::WARP_OUT, shipp);
required_string_fred("$Determination:");
parse_comments();
fout(" 10"); // dummy value for backwards compatibility
if (optional_string_fred("+Flags:", "$Name:")) {
parse_comments();
fout(" (");
} else
fout("\n+Flags: (");
if (shipp->flags[Ship::Ship_Flags::Cargo_revealed])
fout(" \"cargo-known\"");
if (shipp->flags[Ship::Ship_Flags::Ignore_count])
fout(" \"ignore-count\"");
if (objp->flags[Object::Object_Flags::Protected])
fout(" \"protect-ship\"");
if (shipp->flags[Ship::Ship_Flags::Reinforcement])
fout(" \"reinforcement\"");
if (objp->flags[Object::Object_Flags::No_shields])
fout(" \"no-shields\"");
if (shipp->flags[Ship::Ship_Flags::Escort])
fout(" \"escort\"");
if (objp->type == OBJ_START)
fout(" \"player-start\"");
if (shipp->flags[Ship::Ship_Flags::No_arrival_music])
fout(" \"no-arrival-music\"");
if (shipp->flags[Ship::Ship_Flags::No_arrival_warp])
fout(" \"no-arrival-warp\"");
if (shipp->flags[Ship::Ship_Flags::No_departure_warp])
fout(" \"no-departure-warp\"");
if (Objects[shipp->objnum].flags[Object::Object_Flags::Invulnerable])
fout(" \"invulnerable\"");
if (shipp->flags[Ship::Ship_Flags::Hidden_from_sensors])
fout(" \"hidden-from-sensors\"");
if (shipp->flags[Ship::Ship_Flags::Scannable])
fout(" \"scannable\"");
if (Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::Kamikaze])
fout(" \"kamikaze\"");
if (Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::No_dynamic])
fout(" \"no-dynamic\"");
if (shipp->flags[Ship::Ship_Flags::Red_alert_store_status])
fout(" \"red-alert-carry\"");
if (objp->flags[Object::Object_Flags::Beam_protected])
fout(" \"beam-protect-ship\"");
if (objp->flags[Object::Object_Flags::Flak_protected])
fout(" \"flak-protect-ship\"");
if (objp->flags[Object::Object_Flags::Laser_protected])
fout(" \"laser-protect-ship\"");
if (objp->flags[Object::Object_Flags::Missile_protected])
fout(" \"missile-protect-ship\"");
if (shipp->ship_guardian_threshold != 0)
fout(" \"guardian\"");
if (objp->flags[Object::Object_Flags::Special_warpin])
fout(" \"special-warp\"");
if (shipp->flags[Ship::Ship_Flags::Vaporize])
fout(" \"vaporize\"");
if (shipp->flags[Ship::Ship_Flags::Stealth])
fout(" \"stealth\"");
if (shipp->flags[Ship::Ship_Flags::Friendly_stealth_invis])
fout(" \"friendly-stealth-invisible\"");
if (shipp->flags[Ship::Ship_Flags::Dont_collide_invis])
fout(" \"don't-collide-invisible\"");
//for compatibility reasons ship locked or weapons locked are saved as both locked in retail mode
if ((Mission_save_format == FSO_FORMAT_RETAIL) && ((shipp->flags[Ship::Ship_Flags::Ship_locked]) || (shipp->flags[Ship::Ship_Flags::Weapons_locked])))
fout(" \"locked\"");
fout(" )");
// flags2 added by Goober5000 --------------------------------
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (optional_string_fred("+Flags2:", "$Name:")) {
parse_comments();
fout(" (");
} else
fout("\n+Flags2: (");
if (shipp->flags[Ship::Ship_Flags::Primitive_sensors])
fout(" \"primitive-sensors\"");
if (shipp->flags[Ship::Ship_Flags::No_subspace_drive])
fout(" \"no-subspace-drive\"");
if (shipp->flags[Ship::Ship_Flags::Navpoint_carry])
fout(" \"nav-carry-status\"");
if (shipp->flags[Ship::Ship_Flags::Affected_by_gravity])
fout(" \"affected-by-gravity\"");
if (shipp->flags[Ship::Ship_Flags::Toggle_subsystem_scanning])
fout(" \"toggle-subsystem-scanning\"");
if (objp->flags[Object::Object_Flags::Targetable_as_bomb])
fout(" \"targetable-as-bomb\"");
if (shipp->flags[Ship::Ship_Flags::No_builtin_messages])
fout(" \"no-builtin-messages\"");
if (shipp->flags[Ship::Ship_Flags::Primaries_locked])
fout(" \"primaries-locked\"");
if (shipp->flags[Ship::Ship_Flags::Secondaries_locked])
fout(" \"secondaries-locked\"");
if (shipp->flags[Ship::Ship_Flags::No_death_scream])
fout(" \"no-death-scream\"");
if (shipp->flags[Ship::Ship_Flags::Always_death_scream])
fout(" \"always-death-scream\"");
if (shipp->flags[Ship::Ship_Flags::Navpoint_needslink])
fout(" \"nav-needslink\"");
if (shipp->flags[Ship::Ship_Flags::Hide_ship_name])
fout(" \"hide-ship-name\"");
if (shipp->flags[Ship::Ship_Flags::Set_class_dynamically])
fout(" \"set-class-dynamically\"");
if (shipp->flags[Ship::Ship_Flags::Lock_all_turrets_initially])
fout(" \"lock-all-turrets\"");
if (shipp->flags[Ship::Ship_Flags::Afterburner_locked])
fout(" \"afterburners-locked\"");
if (shipp->flags[Ship::Ship_Flags::Force_shields_on])
fout(" \"force-shields-on\"");
if (objp->flags[Object::Object_Flags::Immobile])
fout(" \"immobile\"");
if (shipp->flags[Ship::Ship_Flags::No_ets])
fout(" \"no-ets\"");
if (shipp->flags[Ship::Ship_Flags::Cloaked])
fout(" \"cloaked\"");
if (shipp->flags[Ship::Ship_Flags::Ship_locked])
fout(" \"ship-locked\"");
if (shipp->flags[Ship::Ship_Flags::Weapons_locked])
fout(" \"weapons-locked\"");
if (shipp->flags[Ship::Ship_Flags::Scramble_messages])
fout(" \"scramble-messages\"");
if (!(objp->flags[Object::Object_Flags::Collides]))
fout(" \"no_collide\"");
if (shipp->flags[Ship::Ship_Flags::No_disabled_self_destruct])
fout(" \"no-disabled-self-destruct\"");
fout(" )");
}
// -----------------------------------------------------------
fout("\n+Respawn priority: %d", shipp->respawn_priority); // HA! Newline added by Goober5000
if (shipp->flags[Ship::Ship_Flags::Escort]) {
if (optional_string_fred("+Escort priority:", "$Name:")) {
parse_comments();
} else {
fout("\n+Escort priority:");
}
fout(" %d", shipp->escort_priority);
}
// special explosions
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (shipp->use_special_explosion) {
fso_comment_push(";;FSO 3.6.13;;");
if (optional_string_fred("$Special Explosion:", "$Name:")) {
parse_comments();
required_string_fred("+Special Exp Damage:");
parse_comments();
fout(" %d", shipp->special_exp_damage);
required_string_fred("+Special Exp Blast:");
parse_comments();
fout(" %d", shipp->special_exp_blast);
required_string_fred("+Special Exp Inner Radius:");
parse_comments();
fout(" %d", shipp->special_exp_inner);
required_string_fred("+Special Exp Outer Radius:");
parse_comments();
fout(" %d", shipp->special_exp_outer);
if (shipp->use_shockwave && (shipp->special_exp_shockwave_speed > 0)) {
optional_string_fred("+Special Exp Shockwave Speed:");
parse_comments();
fout(" %d", shipp->special_exp_shockwave_speed);
} else {
bypass_comment(";;FSO 3.6.13;; +Special Exp Shockwave Speed:", "$Name:");
}
if (shipp->special_exp_deathroll_time > 0) {
optional_string_fred("+Special Exp Death Roll Time:");
parse_comments();
fout(" %d", shipp->special_exp_deathroll_time);
} else {
bypass_comment(";;FSO 3.6.13;; +Special Exp Death Roll Time:", "$Name:");
}
} else {
fout_version("\n$Special Explosion:");
fout_version("\n+Special Exp Damage:");
fout(" %d", shipp->special_exp_damage);
fout_version("\n+Special Exp Blast:");
fout(" %d", shipp->special_exp_blast);
fout_version("\n+Special Exp Inner Radius:");
fout(" %d", shipp->special_exp_inner);
fout_version("\n+Special Exp Outer Radius:");
fout(" %d", shipp->special_exp_outer);
if (shipp->use_shockwave && (shipp->special_exp_shockwave_speed > 0)) {
fout_version("\n+Special Exp Shockwave Speed:");
fout(" %d", shipp->special_exp_shockwave_speed);
}
if (shipp->special_exp_deathroll_time > 0) {
fout_version("\n+Special Exp Death Roll Time:");
fout(" %d", shipp->special_exp_deathroll_time);
}
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.13;; +Special Exp Shockwave Speed:", "$Name:");
bypass_comment(";;FSO 3.6.13;; +Special Exp Death Roll Time:", "$Name:");
}
}
// retail format special explosions
else {
if (shipp->use_special_explosion) {
int special_exp_index;
if (has_special_explosion_block_index(&Ships[i], &special_exp_index)) {
fout("\n+Special Exp index:");
fout(" %d", special_exp_index);
} else {
CString text = "You are saving in the retail mission format, but ";
text += "the mission has too many special explosions defined. \"";
text += shipp->ship_name;
text += "\" has therefore lost any special explosion data that was defined for it. ";
text += "\" Either remove special explosions or SEXP variables if you need it to have one ";
MessageBox(NULL, text, "Too many variables!", MB_OK);
}
}
}
// Goober5000 ------------------------------------------------
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (shipp->special_hitpoints) {
fso_comment_push(";;FSO 3.6.13;;");
if (optional_string_fred("+Special Hitpoints:", "$Name:")) {
parse_comments();
} else {
fout_version("\n+Special Hitpoints:");
}
fso_comment_pop();
fout(" %d", shipp->special_hitpoints);
} else {
bypass_comment(";;FSO 3.6.13;; +Special Hitpoints:", "$Name:");
}
if (shipp->special_shield >= 0) {
fso_comment_push(";;FSO 3.6.13;;");
if (optional_string_fred("+Special Shield Points:", "$Name:")) {
parse_comments();
} else {
fout_version("\n+Special Shield Points:");
}
fso_comment_pop();
fout(" %d", shipp->special_shield);
} else {
bypass_comment(";;FSO 3.6.13;; +Special Shield Points:", "$Name:");
}
}
// -----------------------------------------------------------
if (Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::Kamikaze]) {
if (optional_string_fred("+Kamikaze Damage:", "$Name:")) {
parse_comments();
} else {
fout("\n+Kamikaze Damage:");
}
fout(" %d", Ai_info[shipp->ai_index].kamikaze_damage);
}
if (shipp->hotkey != -1) {
if (optional_string_fred("+Hotkey:", "$Name:")) {
parse_comments();
} else {
fout("\n+Hotkey:");
}
fout(" %d", shipp->hotkey);
}
// mwa -- new code to save off information about initially docked ships.
// Goober5000 - newer code to save off information about initially docked ships. ;)
if (object_is_docked(&Objects[shipp->objnum])) {
// possible incompatibility
if (Mission_save_format == FSO_FORMAT_RETAIL && !dock_check_docked_one_on_one(&Objects[shipp->objnum])) {
static bool warned = false;
if (!warned) {
CString text = "You are saving in the retail mission format, but \"";
text += shipp->ship_name;
text += "\" is docked to more than one ship. If you wish to run this mission in retail, ";
text += "you should remove the additional ships and save the mission again.";
MessageBox(NULL, text, "Incompatibility with retail mission format", MB_OK);
warned = true; // to avoid zillions of boxes
}
}
// save one-on-one groups as if they were retail
if (dock_check_docked_one_on_one(&Objects[shipp->objnum])) {
// retail format only saved information for non-leaders
if (!(shipp->flags[Ship::Ship_Flags::Dock_leader])) {
save_single_dock_instance(&Ships[i], Objects[shipp->objnum].dock_list);
}
}
// multiply docked
else {
// save all instances for all ships
for (dock_instance *dock_ptr = Objects[shipp->objnum].dock_list; dock_ptr != NULL; dock_ptr = dock_ptr->next) {
save_single_dock_instance(&Ships[i], dock_ptr);
}
}
}
// check the ship flag about killing off the ship before a mission starts. Write out the appropriate
// variable if necessary
if (shipp->flags[Ship::Ship_Flags::Kill_before_mission]) {
if (optional_string_fred("+Destroy At:", "$Name:"))
parse_comments();
else
fout("\n+Destroy At: ");
fout(" %d", shipp->final_death_time);
}
// possibly write out the orders that this ship will accept. We'll only do it if the orders
// are not the default set of orders
if (shipp->orders_accepted != ship_get_default_orders_accepted(&Ship_info[shipp->ship_info_index])) {
if (optional_string_fred("+Orders Accepted:", "$Name:"))
parse_comments();
else
fout("\n+Orders Accepted:");
fout(" %d\t\t;! note that this is a bitfield!!!", shipp->orders_accepted);
}
if (shipp->group >= 0) {
if (optional_string_fred("+Group:", "$Name:"))
parse_comments();
else
fout("\n+Group:");
fout(" %d", shipp->group);
}
// always write out the score to ensure backwards compatibility. If the score is the same as the value
// in the table write out a flag to tell the game to simply use whatever is in the table instead
if (Ship_info[shipp->ship_info_index].score == shipp->score) {
fso_comment_push(";;FSO 3.6.10;;");
if (optional_string_fred("+Use Table Score:", "$Name:")) {
parse_comments();
} else {
fout_version("\n+Use Table Score:");
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.10;; +Use Table Score:", "$Name:");
}
if (optional_string_fred("+Score:", "$Name:"))
parse_comments();
else
fout("\n+Score:");
fout(" %d", shipp->score);
if (Mission_save_format != FSO_FORMAT_RETAIL && shipp->assist_score_pct != 0) {
fso_comment_push(";;FSO 3.6.10;;");
if (optional_string_fred("+Assist Score Percentage:")) {
parse_comments();
} else {
fout_version("\n+Assist Score Percentage:");
}
fso_comment_pop();
fout(" %f", shipp->assist_score_pct);
} else {
bypass_comment(";;FSO 3.6.10;; +Assist Score Percentage:", "$Name:");
}
// deal with the persona for this ship as well.
if (shipp->persona_index != -1) {
if (optional_string_fred("+Persona Index:", "$Name:"))
parse_comments();
else
fout("\n+Persona Index:");
fout(" %d", shipp->persona_index);
}
// Goober5000 - deal with texture replacement ----------------
if (!Fred_texture_replacements.empty()) {
bool needs_header = true;
fso_comment_push(";;FSO 3.6.8;;");
for (SCP_vector<texture_replace>::iterator ii = Fred_texture_replacements.begin(); ii != Fred_texture_replacements.end(); ++ii) {
if (!stricmp(shipp->ship_name, ii->ship_name) && !(ii->from_table)) {
if (needs_header) {
if (optional_string_fred("$Texture Replace:")) {
parse_comments(1);
} else {
fout_version("\n$Texture Replace:");
}
needs_header = false;
}
// write out this entry
if (optional_string_fred("+old:")) {
parse_comments(1);
fout(" %s", ii->old_texture);
} else {
fout_version("\n+old: %s", ii->old_texture);
}
if (optional_string_fred("+new:")) {
parse_comments(1);
fout(" %s", ii->new_texture);
} else {
fout_version("\n+new: %s", ii->new_texture);
}
}
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.6.8;; $Texture Replace:", "$Name:");
}
// end of texture replacement -------------------------------
z++;
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_players()
{
bool wrote_fso_data = false;
int i, j;
int var_idx;
int used_pool[MAX_WEAPON_TYPES];
if (optional_string_fred("#Alternate Types:")) { // Make sure the parser doesn't get out of sync
required_string_fred("#end");
}
if (optional_string_fred("#Callsigns:")) {
required_string_fred("#end");
}
// write out alternate name list
if (Mission_alt_type_count > 0) {
fout("\n\n#Alternate Types:\n");
// write them all out
for (i = 0; i < Mission_alt_type_count; i++) {
fout("$Alt: %s\n", Mission_alt_types[i]);
}
// end
fout("\n#end\n");
}
// write out callsign list
if (Mission_save_format != FSO_FORMAT_RETAIL && Mission_callsign_count > 0) {
fout("\n\n#Callsigns:\n");
// write them all out
for (i = 0; i < Mission_callsign_count; i++) {
fout("$Callsign: %s\n", Mission_callsigns[i]);
}
// end
fout("\n#end\n");
}
required_string_fred("#Players");
parse_comments(2);
fout("\t\t;! %d total\n", Player_starts);
for (i = 0; i < Num_teams; i++) {
required_string_fred("$Starting Shipname:");
parse_comments();
Assert(Player_start_shipnum >= 0);
fout(" %s", Ships[Player_start_shipnum].ship_name);
required_string_fred("$Ship Choices:");
parse_comments();
fout(" (\n");
for (j = 0; j < Team_data[i].num_ship_choices; j++) {
// Check to see if a variable name should be written for the class rather than a number
if (strlen(Team_data[i].ship_list_variables[j])) {
var_idx = get_index_sexp_variable_name(Team_data[i].ship_list_variables[j]);
Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES);
wrote_fso_data = true;
fout("\t@%s\t", Sexp_variables[var_idx].variable_name);
} else {
fout("\t\"%s\"\t", Ship_info[Team_data[i].ship_list[j]].name);
}
// Now check if we should write a variable or a number for the amount of ships available
if (strlen(Team_data[i].ship_count_variables[j])) {
var_idx = get_index_sexp_variable_name(Team_data[i].ship_count_variables[j]);
Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES);
wrote_fso_data = true;
fout("@%s\n", Sexp_variables[var_idx].variable_name);
} else {
fout("%d\n", Team_data[i].ship_count[j]);
}
}
fout(")");
if (optional_string_fred("+Weaponry Pool:", "$Starting Shipname:")) {
parse_comments(2);
} else {
fout("\n\n+Weaponry Pool:");
}
fout(" (\n");
generate_weaponry_usage_list(i, used_pool);
for (j = 0; j < Team_data[i].num_weapon_choices; j++) {
// first output the weapon name or a variable that sets it
if (strlen(Team_data[i].weaponry_pool_variable[j])) {
var_idx = get_index_sexp_variable_name(Team_data[i].weaponry_pool_variable[j]);
Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES);
wrote_fso_data = true;
fout("\t@%s\t", Sexp_variables[var_idx].variable_name);
} else {
fout("\t\"%s\"\t", Weapon_info[Team_data[i].weaponry_pool[j]].name);
}
// now output the amount of this weapon or a variable that sets it. If this weapon is in the used pool and isn't
// set by a variable we should add the amount of weapons used by the wings to it and zero the entry so we know
// that we have dealt with it
if (strlen(Team_data[i].weaponry_amount_variable[j])) {
var_idx = get_index_sexp_variable_name(Team_data[i].weaponry_amount_variable[j]);
Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES);
wrote_fso_data = true;
fout("@%s\n", Sexp_variables[var_idx].variable_name);
} else {
if (strlen(Team_data[i].weaponry_pool_variable[j])) {
fout("%d\n", Team_data[i].weaponry_count[j]);
} else {
fout("%d\n", Team_data[i].weaponry_count[j] + used_pool[Team_data[i].weaponry_pool[j]]);
used_pool[Team_data[i].weaponry_pool[j]] = 0;
}
}
}
// now we add anything left in the used pool as a static entry
for (j = 0; j < weapon_info_size(); j++) {
if (used_pool[j] > 0) {
fout("\t\"%s\"\t%d\n", Weapon_info[j].name, used_pool[j]);
}
}
fout(")");
// sanity check
if (Mission_save_format == FSO_FORMAT_RETAIL && wrote_fso_data) {
// this is such an unlikely (and hard-to-fix) case that a warning should be sufficient
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);
}
// Goober5000 - mjn.mixael's required weapon feature
bool uses_required_weapon = false;
for (j = 0; j < weapon_info_size(); j++) {
if (Team_data[i].weapon_required[j]) {
uses_required_weapon = true;
break;
}
}
if (Mission_save_format != FSO_FORMAT_RETAIL && uses_required_weapon) {
if (optional_string_fred("+Required for mission:", "$Starting Shipname:"))
parse_comments(2);
else
fout("\n+Required for mission:");
fout(" (");
for (j = 0; j < weapon_info_size(); j++) {
if (Team_data[i].weapon_required[j])
fout(" \"%s\"", Weapon_info[j].name);
}
fout(" )");
}
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_plot_info()
{
if (Mission_save_format == FSO_FORMAT_RETAIL) {
if (optional_string_fred("#Plot Info")) {
parse_comments(2);
// XSTR
required_string_fred("$Tour:");
parse_comments(2);
fout_ext(" ", "Blah");
required_string_fred("$Pre-Briefing Cutscene:");
parse_comments();
fout(" Blah");
required_string_fred("$Pre-Mission Cutscene:");
parse_comments();
fout(" Blah");
required_string_fred("$Next Mission Success:");
parse_comments();
fout(" Blah");
required_string_fred("$Next Mission Partial:");
parse_comments();
fout(" Blah");
required_string_fred("$Next Mission Failure:");
parse_comments();
fout(" Blah");
} else {
fout("\n\n#Plot Info\n\n");
fout("$Tour: ");
fout_ext(NULL, "Blah");
fout("\n");
fout("$Pre-Briefing Cutscene: Blah\n");
fout("$Pre-Mission Cutscene: Blah\n");
fout("$Next Mission Success: Blah\n");
fout("$Next Mission Partial: Blah\n");
fout("$Next Mission Failure: Blah\n");
fout("\n");
}
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_reinforcements()
{
int i, j, type;
fred_parse_flag = 0;
required_string_fred("#Reinforcements");
parse_comments(2);
fout("\t\t;! %d total\n", Num_reinforcements);
for (i = 0; i < Num_reinforcements; i++) {
required_string_either_fred("$Name:", "#Background bitmaps");
required_string_fred("$Name:");
parse_comments(i ? 2 : 1);
fout(" %s", Reinforcements[i].name);
type = TYPE_ATTACK_PROTECT;
for (j = 0; j < MAX_SHIPS; j++)
if ((Ships[j].objnum != -1) && !stricmp(Ships[j].ship_name, Reinforcements[i].name)) {
if (Ship_info[Ships[j].ship_info_index].flags[Ship::Info_Flags::Support])
type = TYPE_REPAIR_REARM;
break;
}
required_string_fred("$Type:");
parse_comments();
fout(" %s", Reinforcement_type_names[type]);
required_string_fred("$Num times:");
parse_comments();
fout(" %d", Reinforcements[i].uses);
if (optional_string_fred("+Arrival Delay:", "$Name:"))
parse_comments();
else
fout("\n+Arrival Delay:");
fout(" %d", Reinforcements[i].arrival_delay);
if (optional_string_fred("+No Messages:", "$Name:"))
parse_comments();
else
fout("\n+No Messages:");
fout(" (");
for (j = 0; j < MAX_REINFORCEMENT_MESSAGES; j++) {
if (strlen(Reinforcements[i].no_messages[j]))
fout(" \"%s\"", Reinforcements[i].no_messages[j]);
}
fout(" )");
if (optional_string_fred("+Yes Messages:", "$Name:"))
parse_comments();
else
fout("\n+Yes Messages:");
fout(" (");
for (j = 0; j < MAX_REINFORCEMENT_MESSAGES; j++) {
if (strlen(Reinforcements[i].yes_messages[j]))
fout(" \"%s\"", Reinforcements[i].yes_messages[j]);
}
fout(" )");
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
void CFred_mission_save::save_single_dock_instance(ship *shipp, dock_instance *dock_ptr)
{
Assert(shipp && dock_ptr);
Assert(dock_ptr->docked_objp->type == OBJ_SHIP || dock_ptr->docked_objp->type == OBJ_START);
// get ships and objects
object *objp = &Objects[shipp->objnum];
object *other_objp = dock_ptr->docked_objp;
ship *other_shipp = &Ships[other_objp->instance];
// write other ship
if (optional_string_fred("+Docked With:", "$Name:"))
parse_comments();
else
fout("\n+Docked With:");
fout(" %s", other_shipp->ship_name);
// Goober5000 - hm, Volition seems to have reversed docker and dockee here
// write docker (actually dockee) point
required_string_fred("$Docker Point:", "$Name:");
parse_comments();
fout(" %s", model_get_dock_name(Ship_info[other_shipp->ship_info_index].model_num, dock_find_dockpoint_used_by_object(other_objp, objp)));
// write dockee (actually docker) point
required_string_fred("$Dockee Point:", "$Name:");
parse_comments();
fout(" %s", model_get_dock_name(Ship_info[shipp->ship_info_index].model_num, dock_find_dockpoint_used_by_object(objp, other_objp)));
fso_comment_pop(true);
}
void CFred_mission_save::save_turret_info(ship_subsys *ptr, int ship)
{
int i, z;
ship_weapon *wp = &ptr->weapons;
if (wp->ai_class != Ship_info[Ships[ship].ship_info_index].ai_class) {
if (optional_string_fred("+AI Class:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+AI Class:");
fout(" %s", Ai_class_names[wp->ai_class]);
}
z = 0;
i = wp->num_primary_banks;
while (i--)
if (wp->primary_bank_weapons[i] != ptr->system_info->primary_banks[i])
z = 1;
if (z) {
if (optional_string_fred("+Primary Banks:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Primary Banks:");
fout(" ( ");
for (i = 0; i < wp->num_primary_banks; i++) {
if (wp->primary_bank_weapons[i] != -1) { // Just in case someone has set a weapon bank to empty
fout("\"%s\" ", Weapon_info[wp->primary_bank_weapons[i]].name);
} else {
fout("\"\" ");
}
}
fout(")");
}
z = 0;
i = wp->num_secondary_banks;
while (i--)
if (wp->secondary_bank_weapons[i] != ptr->system_info->secondary_banks[i])
z = 1;
if (z) {
if (optional_string_fred("+Secondary Banks:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Secondary Banks:");
fout(" ( ");
for (i = 0; i < wp->num_secondary_banks; i++) {
if (wp->secondary_bank_weapons[i] != -1) {
fout("\"%s\" ", Weapon_info[wp->secondary_bank_weapons[i]].name);
} else {
fout("\"\" ");
}
}
fout(")");
}
z = 0;
i = wp->num_secondary_banks;
while (i--)
if (wp->secondary_bank_ammo[i] != 100)
z = 1;
if (z) {
if (optional_string_fred("+Sbank Ammo:", "$Name:", "+Subsystem:"))
parse_comments();
else
fout("\n+Sbank Ammo:");
fout(" ( ");
for (i = 0; i < wp->num_secondary_banks; i++)
fout("%d ", wp->secondary_bank_ammo[i]);
fout(")");
}
fso_comment_pop(true);
}
int CFred_mission_save::save_variables()
{
char *type;
char number[] = "number";
char string[] = "string";
char block[] = "block";
int i;
int num_block_vars = 0;
// sort sexp_variables
sexp_variable_sort();
// get count
int num_variables = sexp_variable_count();
if (Mission_save_format == FSO_FORMAT_RETAIL) {
generate_special_explosion_block_variables();
num_block_vars = num_block_variables();
}
int total_variables = num_variables + num_block_vars;
if (total_variables > 0) {
// write 'em out
required_string_fred("#Sexp_variables");
parse_comments(2);
required_string_fred("$Variables:");
parse_comments(2);
fout("\n(");
// parse_comments();
for (i = 0; i < num_variables; i++) {
if (Sexp_variables[i].type & SEXP_VARIABLE_NUMBER) {
type = number;
} else {
type = string;
}
// index "var name" "default" "type"
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);
// persistent and network variables
if (Mission_save_format != FSO_FORMAT_RETAIL) {
// Network variable - Karajorma
if (Sexp_variables[i].type & SEXP_VARIABLE_NETWORK) {
fout("\t\t\"%s\"", "network-variable");
}
if (Sexp_variables[i].type & SEXP_VARIABLE_SAVE_TO_PLAYER_FILE) {
fout("\t\t\"%s\"", "eternal");
}
// player-persistent - Goober5000
if (Sexp_variables[i].type & SEXP_VARIABLE_SAVE_ON_MISSION_CLOSE) {
fout("\t\t\"%s\"", "save-on-mission-close");
// campaign-persistent - Goober5000
} else if (Sexp_variables[i].type & SEXP_VARIABLE_SAVE_ON_MISSION_PROGRESS) {
fout("\t\t\"%s\"", "save-on-mission-progress");
}
}
// parse_comments();
}
for (i = MAX_SEXP_VARIABLES - num_block_vars; i < MAX_SEXP_VARIABLES; i++) {
type = block;
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);
}
fout("\n)");
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_vector(vec3d &v)
{
fout(" %f, %f, %f", v.xyz.x, v.xyz.y, v.xyz.z);
return 0;
}
int CFred_mission_save::save_waypoints()
{
//object *ptr;
fred_parse_flag = 0;
required_string_fred("#Waypoints");
parse_comments(2);
fout("\t\t;! %d lists total\n", Waypoint_lists.size());
SCP_list<CJumpNode>::iterator jnp;
for (jnp = Jump_nodes.begin(); jnp != Jump_nodes.end(); ++jnp) {
required_string_fred("$Jump Node:", "$Jump Node Name:");
parse_comments(2);
save_vector(jnp->GetSCPObject()->pos);
required_string_fred("$Jump Node Name:", "$Jump Node:");
parse_comments();
fout(" %s", jnp->GetName());
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (jnp->IsSpecialModel()) {
if (optional_string_fred("+Model File:", "$Jump Node:"))
parse_comments();
else
fout("\n+Model File:");
int model = jnp->GetModelNumber();
polymodel *pm = model_get(model);
fout(" %s", pm->filename);
}
if (jnp->IsColored()) {
if (optional_string_fred("+Alphacolor:", "$Jump Node:"))
parse_comments();
else
fout("\n+Alphacolor:");
color jn_color = jnp->GetColor();
fout(" %u %u %u %u", jn_color.red, jn_color.green, jn_color.blue, jn_color.alpha);
}
int hidden_is_there = optional_string_fred("+Hidden:", "$Jump Node:");
if (hidden_is_there)
parse_comments();
if (hidden_is_there || jnp->IsHidden()) {
if (!hidden_is_there)
fout("\n+Hidden:");
if (jnp->IsHidden())
fout(" %s", "true");
else
fout(" %s", "false");
}
}
fso_comment_pop();
}
SCP_list<waypoint_list>::iterator ii;
for (ii = Waypoint_lists.begin(); ii != Waypoint_lists.end(); ++ii) {
required_string_either_fred("$Name:", "#Messages");
required_string_fred("$Name:");
parse_comments((ii == Waypoint_lists.begin()) ? 1 : 2);
fout(" %s", ii->get_name());
required_string_fred("$List:");
parse_comments();
fout(" (\t\t;! %d points in list\n", ii->get_waypoints().size());
save_waypoint_list(&(*ii));
fout(")");
fso_comment_pop();
}
fso_comment_pop(true);
return err;
}
int CFred_mission_save::save_waypoint_list(waypoint_list *wp_list)
{
Assert(wp_list != NULL);
SCP_vector<waypoint>::iterator ii;
for (ii = wp_list->get_waypoints().begin(); ii != wp_list->get_waypoints().end(); ++ii) {
vec3d *pos = ii->get_pos();
fout("\t( %f, %f, %f )\n", pos->xyz.x, pos->xyz.y, pos->xyz.z);
}
return 0;
}
int CFred_mission_save::save_wings()
{
SCP_string sexp_out;
int i, j, z, ship, count = 0;
fred_parse_flag = 0;
required_string_fred("#Wings");
parse_comments(2);
fout("\t\t;! %d total", Num_wings);
for (i = 0; i < MAX_WINGS; i++) {
if (!Wings[i].wave_count)
continue;
count++;
required_string_either_fred("$Name:", "#Events");
required_string_fred("$Name:");
parse_comments(2);
fout(" %s", Wings[i].name);
// squad logo - Goober5000
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (strlen(Wings[i].wing_squad_filename) > 0) //-V805
{
if (optional_string_fred("+Squad Logo:", "$Name:"))
parse_comments();
else
fout("\n+Squad Logo:");
fout(" %s", Wings[i].wing_squad_filename);
}
}
required_string_fred("$Waves:");
parse_comments();
fout(" %d", Wings[i].num_waves);
required_string_fred("$Wave Threshold:");
parse_comments();
fout(" %d", Wings[i].threshold);
required_string_fred("$Special Ship:");
parse_comments();
fout(" %d\t\t;! %s", Wings[i].special_ship,
Ships[Wings[i].ship_index[Wings[i].special_ship]].ship_name);
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if (Wings[i].formation >= 0 && Wings[i].formation < (int)Wing_formations.size())
{
if (optional_string_fred("+Formation:", "$Name:")) {
parse_comments();
}
else {
fout("\n+Formation:");
}
fout(" %s", Wing_formations[Wings[i].formation].name);
}
}
required_string_fred("$Arrival Location:");
parse_comments();
fout(" %s", Arrival_location_names[Wings[i].arrival_location]);
if (Wings[i].arrival_location != ARRIVE_AT_LOCATION) {
if (optional_string_fred("+Arrival Distance:", "$Name:"))
parse_comments();
else
fout("\n+Arrival Distance:");
fout(" %d", Wings[i].arrival_distance);
if (optional_string_fred("$Arrival Anchor:", "$Name:"))
parse_comments();
else
fout("\n$Arrival Anchor:");
z = Wings[i].arrival_anchor;
if (z & SPECIAL_ARRIVAL_ANCHOR_FLAG) {
// get name
char tmp[NAME_LENGTH + 15];
stuff_special_arrival_anchor_name(tmp, z, Mission_save_format == FSO_FORMAT_RETAIL ? 1 : 0);
// save it
fout(" %s", tmp);
} else if (z >= 0) {
fout(" %s", Ships[z].ship_name);
} else {
fout(" <error>");
}
}
// Goober5000
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if ((Wings[i].arrival_location == ARRIVE_FROM_DOCK_BAY) && (Wings[i].arrival_path_mask > 0)) {
int anchor_shipnum;
polymodel *pm;
anchor_shipnum = Wings[i].arrival_anchor;
Assert(anchor_shipnum >= 0 && anchor_shipnum < MAX_SHIPS);
fout("\n+Arrival Paths: ( ");
pm = model_get(Ship_info[Ships[anchor_shipnum].ship_info_index].model_num);
for (auto n = 0; n < pm->ship_bay->num_paths; n++) {
if (Wings[i].arrival_path_mask & (1 << n)) {
fout("\"%s\" ", pm->paths[pm->ship_bay->path_indexes[n]].name);
}
}
fout(")");
}
}
if (Wings[i].arrival_delay) {
if (optional_string_fred("+Arrival delay:", "$Name:"))
parse_comments();
else
fout("\n+Arrival delay:");
fout(" %d", Wings[i].arrival_delay);
}
required_string_fred("$Arrival Cue:");
parse_comments();
convert_sexp_to_string(sexp_out, Wings[i].arrival_cue, SEXP_SAVE_MODE);
fout(" %s", sexp_out.c_str());
required_string_fred("$Departure Location:");
parse_comments();
fout(" %s", Departure_location_names[Wings[i].departure_location]);
if (Wings[i].departure_location != DEPART_AT_LOCATION) {
required_string_fred("$Departure Anchor:");
parse_comments();
if (Wings[i].departure_anchor >= 0)
fout(" %s", Ships[Wings[i].departure_anchor].ship_name);
else
fout(" <error>");
}
// Goober5000
if (Mission_save_format != FSO_FORMAT_RETAIL) {
if ((Wings[i].departure_location == DEPART_AT_DOCK_BAY) && (Wings[i].departure_path_mask > 0)) {
int anchor_shipnum;
polymodel *pm;
anchor_shipnum = Wings[i].departure_anchor;
Assert(anchor_shipnum >= 0 && anchor_shipnum < MAX_SHIPS);
fout("\n+Departure Paths: ( ");
pm = model_get(Ship_info[Ships[anchor_shipnum].ship_info_index].model_num);
for (auto n = 0; n < pm->ship_bay->num_paths; n++) {
if (Wings[i].departure_path_mask & (1 << n)) {
fout("\"%s\" ", pm->paths[pm->ship_bay->path_indexes[n]].name);
}
}
fout(")");
}
}
if (Wings[i].departure_delay) {
if (optional_string_fred("+Departure delay:", "$Name:"))
parse_comments();
else
fout("\n+Departure delay:");
fout(" %d", Wings[i].departure_delay);
}
required_string_fred("$Departure Cue:");
parse_comments();
convert_sexp_to_string(sexp_out, Wings[i].departure_cue, SEXP_SAVE_MODE);
fout(" %s", sexp_out.c_str());
required_string_fred("$Ships:");
parse_comments();
fout(" (\t\t;! %d total\n", Wings[i].wave_count);
for (j = 0; j < Wings[i].wave_count; j++) {
ship = Wings[i].ship_index[j];
// if (Objects[Ships[ship].objnum].type == OBJ_START)
// fout("\t\"Player 1\"\n");
// else
fout("\t\"%s\"\n", Ships[Wings[i].ship_index[j]].ship_name);
}
fout(")");
save_ai_goals(Wings[i].ai_goals, -1);
if (Wings[i].hotkey != -1) {
if (optional_string_fred("+Hotkey:", "$Name:"))
parse_comments();
else
fout("\n+Hotkey:");
fout(" %d", Wings[i].hotkey);
}
if (optional_string_fred("+Flags:", "$Name:")) {
parse_comments();
fout(" (");
} else
fout("\n+Flags: (");
if (Wings[i].flags[Ship::Wing_Flags::Ignore_count])
fout(" \"ignore-count\"");
if (Wings[i].flags[Ship::Wing_Flags::Reinforcement])
fout(" \"reinforcement\"");
if (Wings[i].flags[Ship::Wing_Flags::No_arrival_music])
fout(" \"no-arrival-music\"");
if (Wings[i].flags[Ship::Wing_Flags::No_arrival_message])
fout(" \"no-arrival-message\"");
if (Wings[i].flags[Ship::Wing_Flags::No_arrival_warp])
fout(" \"no-arrival-warp\"");
if (Wings[i].flags[Ship::Wing_Flags::No_departure_warp])
fout(" \"no-departure-warp\"");
if (Wings[i].flags[Ship::Wing_Flags::No_dynamic])
fout(" \"no-dynamic\"");
fout(" )");
if (Wings[i].wave_delay_min) {
if (optional_string_fred("+Wave Delay Min:", "$Name:"))
parse_comments();
else
fout("\n+Wave Delay Min:");
fout(" %d", Wings[i].wave_delay_min);
}
if (Wings[i].wave_delay_max) {
if (optional_string_fred("+Wave Delay Max:", "$Name:"))
parse_comments();
else
fout("\n+Wave Delay Max:");
fout(" %d", Wings[i].wave_delay_max);
}
fso_comment_pop();
}
fso_comment_pop(true);
Assert(count == Num_wings);
return err;
}