#include "campaignsave.h" #include "globalincs/linklist.h" #include "globalincs/version.h" #include "mission/missioncampaign.h" #include "parse/parselo.h" #include // 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& 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()); } } }