From d1e178980b33fdea5c37a38b9202f85d527b2035 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 18 Aug 2026 00:17:45 -0400 Subject: [PATCH] restore mission/goal/event listing in FRED2's campaign editor The sexp tree refactor (#7301) moved the OPF listing logic into a shared model that asks the editor for campaign-aware lists via virtual methods on SexpTreeEditorInterface. QtFRED's campaign editor got the necessary overrides, but FRED2's never did, so its OPF_MISSION_NAME / OPF_GOAL_NAME / OPF_EVENT_NAME dropdowns fell back to the currently loaded mission's data instead of the campaign's. Backport the overrides to FRED2, restoring its pre-refactor behavior (including listing only missions at earlier campaign levels), and hoist QtFRED's loadAndFindCampaignMission helper into missioneditor/common so both editors share one copy. Co-Authored-By: Claude Fable 5 --- code/missioneditor/common.cpp | 19 +++++++ code/missioneditor/common.h | 4 ++ fred2/campaigneditordlg.cpp | 50 +++++++++++++++++++ fred2/campaigneditordlg.h | 6 +++ .../src/ui/dialogs/CampaignEditorDialog.cpp | 26 ++-------- 5 files changed, 82 insertions(+), 23 deletions(-) diff --git a/code/missioneditor/common.cpp b/code/missioneditor/common.cpp index ce3293e10a5..2486294a27c 100644 --- a/code/missioneditor/common.cpp +++ b/code/missioneditor/common.cpp @@ -5,6 +5,7 @@ #include "mission/missionparse.h" #include "iff_defs/iff_defs.h" #include "jumpnode/jumpnode.h" +#include "mission/missioncampaign.h" #include "object/object.h" #include "object/waypoint.h" #include "prop/prop.h" @@ -318,6 +319,24 @@ SCP_string check_name_conflict(const char *entity_type, const char *name, int ex return ""; // no error } +int load_and_find_campaign_mission(const char *mission_filename) +{ + if (!mission_filename[0]) + return -1; + + int idx = mission_campaign_find_mission(mission_filename); + if (idx < 0) + return -1; + + if (Campaign.missions[idx].flags & CMISSION_FLAG_FRED_LOAD_PENDING) + { + read_mission_goal_list(idx); + Campaign.missions[idx].flags &= ~CMISSION_FLAG_FRED_LOAD_PENDING; + } + + return idx; +} + void reassign_ship_slot(int from, int to, const FredShipSlotConfig& cfg, bool resort_obj_list) { Assertion(Fred_running, "reassign_ship_slot is FRED-only: it does not fix up game-context references like Player_ship or runtime Ai_info shipnums"); diff --git a/code/missioneditor/common.h b/code/missioneditor/common.h index 6622afcece0..268169f44bd 100644 --- a/code/missioneditor/common.h +++ b/code/missioneditor/common.h @@ -71,6 +71,10 @@ bool set_single_player_start(int objnum); // against the entity currently being renamed. SCP_string check_name_conflict(const char *entity_type, const char *name, int exclude_ship = -1, int exclude_wing = -1, int exclude_waypoint_list = -1, int exclude_jump_node = -1); +// Resolve a mission filename to its index in Campaign.missions[], lazy-loading the mission's goal/event name lists from disk if the +// FRED_LOAD_PENDING flag is still set. Returns -1 if the mission isn't in the campaign. +int load_and_find_campaign_mission(const char *mission_filename); + struct FredShipSlotConfig { char (*fred_alt_names)[NAME_LENGTH + 1] = nullptr; diff --git a/fred2/campaigneditordlg.cpp b/fred2/campaigneditordlg.cpp index bcf73fe2290..198cbd8b911 100644 --- a/fred2/campaigneditordlg.cpp +++ b/fred2/campaigneditordlg.cpp @@ -20,7 +20,9 @@ #include "cfile/cfile.h" #include "FREDDoc.h" #include "parse/parselo.h" +#include "mission/missioncampaign.h" #include "mission/missiongoals.h" +#include "missioneditor/common.h" #ifdef _DEBUG #undef THIS_FILE @@ -79,6 +81,54 @@ int campaign_editor::onRootDeleted(int formula_node) void campaign_editor::onRootInserted(int old_formula, int new_formula) { insert_handler(old_formula, new_formula); } void campaign_editor::onRootMoved(int node1, int node2, bool insert_before) { move_handler(node1, node2, insert_before); } +SCP_vector campaign_editor::getMissionNames() +{ + SCP_vector list; + if (Cur_campaign_mission < 0) + return list; + + // only list missions the player could have already played: the current mission and + // any mission at an earlier level in the campaign tree + for (int i = 0; i < Campaign.num_missions; i++) + { + if ((i == Cur_campaign_mission) || (Campaign.missions[i].level < Campaign.missions[Cur_campaign_mission].level)) + list.emplace_back(Campaign.missions[i].name); + } + + return list; +} + +bool campaign_editor::hasDefaultMissionName() +{ + return Cur_campaign_mission >= 0; +} + +SCP_vector campaign_editor::getMissionGoals(const SCP_string& reference_name) +{ + SCP_vector list; + int idx = load_and_find_campaign_mission(reference_name.c_str()); + if (idx < 0) + return list; + + for (const auto& goal : Campaign.missions[idx].goals) + list.emplace_back(goal.name); + + return list; +} + +SCP_vector campaign_editor::getMissionEvents(const SCP_string& reference_name) +{ + SCP_vector list; + int idx = load_and_find_campaign_mission(reference_name.c_str()); + if (idx < 0) + return list; + + for (const auto& event : Campaign.missions[idx].events) + list.emplace_back(event.name); + + return list; +} + campaign_editor::~campaign_editor() { } diff --git a/fred2/campaigneditordlg.h b/fred2/campaigneditordlg.h index e5ec0789114..308c533c489 100644 --- a/fred2/campaigneditordlg.h +++ b/fred2/campaigneditordlg.h @@ -44,6 +44,12 @@ class campaign_editor : public CFormView, public SexpTreeEditorInterface void onRootInserted(int old_formula, int new_formula) override; void onRootMoved(int node1, int node2, bool insert_before) override; + SCP_vector getMissionNames() override; + bool hasDefaultMissionName() override; + + SCP_vector getMissionGoals(const SCP_string& reference_name) override; + SCP_vector getMissionEvents(const SCP_string& reference_name) override; + // Form Data public: void mission_selected(int num); diff --git a/qtfred/src/ui/dialogs/CampaignEditorDialog.cpp b/qtfred/src/ui/dialogs/CampaignEditorDialog.cpp index b6699447985..14a227d3b50 100644 --- a/qtfred/src/ui/dialogs/CampaignEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/CampaignEditorDialog.cpp @@ -4,6 +4,7 @@ #include #include "mission/missioncampaign.h" +#include "missioneditor/common.h" #include "ui/widgets/sexp_tree_view.h" #include "ui/widgets/SimpleListSelectDialog.h" #include "ui/util/default_dir.h" @@ -180,31 +181,10 @@ bool CampaignEditorDialog::hasDefaultMissionName() return _model && !_model->getCampaignMissions().empty(); } -// Resolve a mission filename (as it appears in the sexp tree) to its index in the -// global Campaign.missions[] table that syncCampaignMissionList() mirrors from -// _model->getCampaignMissions(). Lazy-loads the goal/event list from disk if the -// FRED_LOAD_PENDING flag is still set. Returns -1 if the mission isn't in the -// campaign yet (e.g. the user is typing the name from scratch). -static int loadAndFindCampaignMission(const SCP_string& reference_name) -{ - if (reference_name.empty()) { - return -1; - } - int idx = mission_campaign_find_mission(reference_name.c_str()); - if (idx < 0) { - return -1; - } - if (Campaign.missions[idx].flags & CMISSION_FLAG_FRED_LOAD_PENDING) { - read_mission_goal_list(idx); - Campaign.missions[idx].flags &= ~CMISSION_FLAG_FRED_LOAD_PENDING; - } - return idx; -} - SCP_vector CampaignEditorDialog::getMissionGoals(const SCP_string& reference_name) { SCP_vector list; - const int idx = loadAndFindCampaignMission(reference_name); + const int idx = load_and_find_campaign_mission(reference_name.c_str()); if (idx < 0) { return list; } @@ -217,7 +197,7 @@ SCP_vector CampaignEditorDialog::getMissionGoals(const SCP_string& r SCP_vector CampaignEditorDialog::getMissionEvents(const SCP_string& reference_name) { SCP_vector list; - const int idx = loadAndFindCampaignMission(reference_name); + const int idx = load_and_find_campaign_mission(reference_name.c_str()); if (idx < 0) { return list; }