mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 17:48:46 -10:00
4351 lines
113 KiB
C++
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;
|
|
}
|