Files
Goober5000andClaude Opus 4.7 30d81a83fb store campaign allowed-class lists in sets
Convert the allowed-ships and allowed-weapons lists to sets, rather than naive vectors that stored a true/false for every class.  Since these are ordered sets, iteration occurs in the same sequence as iteration of `Ship_info` or `Weapon_info`.  Associated data structures such as `Granted_ships` and `Granted_weapons` are converted as well.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-08-04 22:14:58 -04:00

338 lines
9.2 KiB
C++

#include "campaignsave.h"
#include "globalincs/linklist.h"
#include "globalincs/version.h"
#include "mission/missioncampaign.h"
#include "parse/parselo.h"
#include <freespace.h>
// Bodies copied from the old implementations in missionsave.cpp,
// unchanged except for the new class name and include.
int Fred_campaign_save::save_campaign_file(const char* pathname, const SCP_vector<campaign_link>& links)
{
// This is original FRED code. These were moved to the call sites as the data should be fully
// prepared before calling this function.
// TODO check QtFRED's tree is sorted and saved before calling this function.
// Campaign_tree_formp->save_tree(); // flush all changes so they get saved.
// Campaign_tree_viewp->sort_elements();
reset_parse();
raw_ptr = Parse_text_raw;
fred_parse_flag = 0;
pathname = cf_add_ext(pathname, FS_CAMPAIGN_FILE_EXT);
fp = cfopen(pathname, "wt", 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_ext(" ", "%s", Campaign.name);
// TODO make type an enum to clean this up
Assert((Campaign.type >= 0) && (Campaign.type < MAX_CAMPAIGN_TYPES));
required_string_fred("$Type:");
parse_comments();
fout(" %s", campaign_types[Campaign.type]);
// XSTR
if (!Campaign.description.empty()) {
required_string_fred("+Description:");
parse_comments();
fout_ext("\n", "%s", Campaign.description.c_str());
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 (save_config.save_format != MissionFormat::RETAIL) {
if (optional_string_fred("$Flags:")) {
parse_comments();
} else {
fout("\n$Flags:");
}
fout(" %d", Campaign.flags);
}
if (save_config.save_format != MissionFormat::RETAIL && !Campaign.custom_data.empty()) {
if (optional_string_fred("$begin_custom_data_map")) {
parse_comments(2);
} else {
fout("\n\n$begin_custom_data_map");
}
for (const auto& pair : Campaign.custom_data) {
fout("\n +Val: %s %s", pair.first.c_str(), pair.second.c_str());
}
if (optional_string_fred("$end_custom_data_map")) {
parse_comments();
} else {
fout("\n$end_custom_data_map");
}
}
// write out the ships and weapons which the player can start the campaign with
if (optional_string_fred("+Starting Ships:")) {
parse_comments(2);
} else {
fout("\n\n+Starting Ships:");
}
fout(" (");
for (int ship_class : Campaign.ships_allowed) {
fout(" \"%s\"", Ship_info[ship_class].name);
}
fout(" )");
if (optional_string_fred("+Starting Weapons:")) {
parse_comments(2);
} else {
fout("\n\n+Starting Weapons:");
}
fout(" (");
for (int weapon_class : Campaign.weapons_allowed) {
fout(" \"%s\"", Weapon_info[weapon_class].name);
}
fout(" )");
fred_parse_flag = 0;
for (int i = 0; i < Campaign.num_missions; i++) {
// Expect to get Campaign.missions ordered from FRED
cmission& cm = Campaign.missions[i];
required_string_either_fred("$Mission:", "#End");
required_string_fred("$Mission:");
parse_comments(2);
fout(" %s", cm.name);
if (strlen(cm.briefing_cutscene) > 0) {
if (optional_string_fred("+Briefing Cutscene:", "$Mission"))
parse_comments();
else
fout("\n+Briefing Cutscene:");
fout(" %s", cm.briefing_cutscene);
}
required_string_fred("+Flags:", "$Mission:");
parse_comments();
// don't save any internal flags
auto flags_to_save = cm.flags & CMISSION_EXTERNAL_FLAG_MASK;
// Goober5000
if (save_config.save_format != MissionFormat::RETAIL) {
// don't save Bastion flag
fout(" %d", flags_to_save & ~CMISSION_FLAG_BASTION);
// new main hall stuff
if (optional_string_fred("+Main Hall:", "$Mission:"))
parse_comments();
else
fout("\n+Main Hall:");
fout(" %s", cm.main_hall.c_str());
} else {
// save Bastion flag properly
fout(" %d", flags_to_save | ((!cm.main_hall.empty() && cm.main_hall != "0") ? CMISSION_FLAG_BASTION : 0));
}
if (!cm.substitute_main_hall.empty()) {
fso_comment_push(";;FSO 3.7.2;;");
if (optional_string_fred("+Substitute Main Hall:")) {
parse_comments(1);
fout(" %s", cm.substitute_main_hall.c_str());
} else {
fout_version("\n+Substitute Main Hall: %s", cm.substitute_main_hall.c_str());
}
fso_comment_pop();
} else {
bypass_comment(";;FSO 3.7.2;; +Substitute Main Hall:");
}
if (cm.debrief_persona_index > 0) {
fso_comment_push(";;FSO 3.6.8;;");
if (optional_string_fred("+Debriefing Persona Index:")) {
parse_comments(1);
fout(" %d", cm.debrief_persona_index);
} else {
fout_version("\n+Debriefing Persona Index: %d", cm.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;
int flag = 0;
for (auto& link : links) {
if (link.from == i) {
if (!flag) {
if (optional_string_fred("+Formula:", "$Mission:"))
parse_comments();
else
fout("\n+Formula:");
fout(" ( cond\n");
flag = 1;
}
if (link.is_mission_loop) {
mission_loop = true;
} else if (link.is_mission_fork && (save_config.save_format != MissionFormat::RETAIL)) {
mission_fork = true;
} else {
save_campaign_sexp(link.sexp, link.to);
}
}
}
if (flag) {
fout(")");
}
// now save campaign loop sexp
if (mission_loop || mission_fork) {
if (mission_loop)
required_string_fred("+Mission Loop:");
else
required_string_fred("+Mission Fork:");
parse_comments();
int num_mission_special = 0;
for (auto& link : links) {
if ((link.from == i) && (link.is_mission_loop || link.is_mission_fork)) {
num_mission_special++;
if ((num_mission_special == 1) && link.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", link.mission_branch_txt);
fout("\n$end_multi_text");
}
if ((num_mission_special == 1) && link.mission_branch_brief_anim) {
auto str = mission_loop ? "+Mission Loop Brief Anim:" : "+Mission Fork Brief Anim:";
if (optional_string_fred(str))
parse_comments();
else
fout("\n%s", str);
if (save_config.save_format != MissionFormat::RETAIL) {
fout(" %s", link.mission_branch_brief_anim);
} else {
// previous formats used multitext, which causes problems
fout_ext("\n", "%s", link.mission_branch_brief_anim);
fout("\n$end_multi_text");
}
}
if ((num_mission_special == 1) && link.mission_branch_brief_sound) {
auto str = mission_loop ? "+Mission Loop Brief Sound:" : "+Mission Fork Brief Sound:";
if (optional_string_fred(str))
parse_comments();
else
fout("\n%s", str);
if (save_config.save_format != MissionFormat::RETAIL) {
fout(" %s", link.mission_branch_brief_sound);
} else {
// previous formats used multitext, which causes problems
fout_ext("\n", "%s", link.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(link.sexp, link.to);
fout(")");
}
if (mission_fork) {
fout("\n+Option: %s", Campaign.missions[link.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.",
cm.name);
Message(os::dialogs::MESSAGEBOX_ERROR, buffer);
}
}
if (optional_string_fred("+Level:", "$Mission:")) {
parse_comments();
} else {
fout("\n+Level:");
}
fout(" %d", cm.level);
if (optional_string_fred("+Position:", "$Mission:")) {
parse_comments();
} else {
fout("\n+Position:");
}
fout(" %d", cm.pos);
fso_comment_pop();
}
required_string_fred("#End");
parse_comments(2);
token_found = nullptr;
parse_comments();
fout("\n");
cfclose(fp);
fso_comment_pop(true);
// Mission editor should run the error checker *before* calling the save function
Assertion(!err, "Nothing in here should have a side effect to raise the mission error saving flag.");
return err;
}
void Fred_campaign_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());
}
}
}