/* * 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 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; jstatus_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::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::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(" "); } } // 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(" "); } // 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::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::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::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::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(" "); } } // 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(" "); } // 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; }