From cd150a8a79acddc313f1cd4b48ffb32c2707a3e4 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sat, 18 Apr 2026 20:29:54 -0500 Subject: [PATCH 1/2] mission goals to mission objectives --- code/mission/missionparse.cpp | 8 +- code/missioneditor/missionsave.cpp | 20 ++-- code/missioneditor/missionsave.h | 4 + code/parse/parselo.h | 1 + code/parse/sexp.cpp | 91 +++++++++++++------ code/scripting/ade.cpp | 6 +- code/scripting/ade_api.h | 29 +++++- code/scripting/api/libs/mission.cpp | 50 +++++++++- code/scripting/api/objs/goal.cpp | 32 ++++++- fred2/dumpstats.cpp | 4 +- fred2/fred.rc | 36 ++++---- fred2/fredview.cpp | 2 +- fred2/management.cpp | 4 +- fred2/missiongoalsdlg.cpp | 4 +- qtfred/src/mission/Editor.cpp | 6 +- .../dialogs/MissionGoalsDialogModel.cpp | 4 +- qtfred/ui/FredView.ui | 2 +- qtfred/ui/MissionGoalsDialog.ui | 16 ++-- qtfred/ui/MissionStatsDialog.ui | 2 +- qtfred/ui/PreferencesDialog.ui | 2 +- 20 files changed, 234 insertions(+), 89 deletions(-) diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 057a62afe4f..add8e66db9e 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -391,7 +391,7 @@ flag_def_list_new Parse_mission_flags[] = { {"All Teams at War", Mission::Mission_Flags::All_attack, true, false}, {"Use Autopilot Cinematics", Mission::Mission_Flags::Use_ap_cinematics, true, false}, {"Deactivate Hardcoded Autopilot", Mission::Mission_Flags::Deactivate_ap, true, false}, - {"Toggle Showing Goals In Briefing", Mission::Mission_Flags::Toggle_showing_goals, true, false}, + {"Toggle Showing Objectives In Briefing", Mission::Mission_Flags::Toggle_showing_goals, true, false}, {"Mission End to Mainhall", Mission::Mission_Flags::End_to_mainhall, true, false}, {"Override #Command with Command Info", Mission::Mission_Flags::Override_hashcommand, true, true}, {"Toggle Starting in Chase View", Mission::Mission_Flags::Toggle_start_chase_view, true, false}, @@ -424,7 +424,7 @@ parse_object_flag_description Parse_mission_flag_descrip {Mission::Mission_Flags::All_attack, "All teams target each other"}, {Mission::Mission_Flags::Use_ap_cinematics, "Use autopilot cinematics"}, {Mission::Mission_Flags::Deactivate_ap, "Deactivate hardcoded autopilot"}, - {Mission::Mission_Flags::Toggle_showing_goals, "Show mission goals for training missions, hide otherwise"}, + {Mission::Mission_Flags::Toggle_showing_goals, "Show mission objectives for training missions, hide otherwise"}, {Mission::Mission_Flags::End_to_mainhall, "Return to the mainhall after debrief instead of starting the next mission"}, {Mission::Mission_Flags::Override_hashcommand, "Override #Command with the Command info in Mission Specs"}, {Mission::Mission_Flags::Toggle_start_chase_view, "Toggles whether the player starts the mission in chase view"}, @@ -506,7 +506,7 @@ flag_def_list_new Parse_object_flags[] = { parse_object_flag_description Parse_object_flag_descriptions[] = { { Mission::Parse_Object_Flags::SF_Cargo_known, "If set, the ship's cargo can be seen without scanning the ship."}, - { Mission::Parse_Object_Flags::SF_Ignore_count, "Ignore this ship when counting ship types for goals."}, + { Mission::Parse_Object_Flags::SF_Ignore_count, "Ignore this ship when counting ship types for objectives."}, { Mission::Parse_Object_Flags::OF_Protected, "Ship and Turret AI will ignore and not attack ship."}, { Mission::Parse_Object_Flags::SF_Reinforcement, "This ship is a reinforcement ship."}, { Mission::Parse_Object_Flags::OF_No_shields, "Ship will have no shields (ETS will be rebalanced if shields were off and are enabled)."}, @@ -589,7 +589,7 @@ flag_def_list_new Parse_wing_flags[] = { }; parse_object_flag_description Parse_wing_flag_descriptions[] = { - { Ship::Wing_Flags::Ignore_count, "Ignore this wing when counting ship types for goals." }, + { Ship::Wing_Flags::Ignore_count, "Ignore this wing when counting ship types for objectives." }, { Ship::Wing_Flags::Reinforcement, "This wing is a reinforcement wing." }, { Ship::Wing_Flags::No_arrival_music, "Don't play arrival music when wing arrives." }, { Ship::Wing_Flags::No_arrival_message, "Don't play arrival message when wing arrives." }, diff --git a/code/missioneditor/missionsave.cpp b/code/missioneditor/missionsave.cpp index e46fb6b590c..b2b60a786fc 100644 --- a/code/missioneditor/missionsave.cpp +++ b/code/missioneditor/missionsave.cpp @@ -1202,7 +1202,7 @@ int Fred_mission_save::save_briefing() required_string_fred("$Formula:"); parse_comments(); - convert_sexp_to_string(sexp_out, bs->formula, SEXP_SAVE_MODE); + 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++) { @@ -1820,7 +1820,7 @@ int Fred_mission_save::save_cutscenes() required_string_fred("+formula:"); parse_comments(); - convert_sexp_to_string(sexp_out, The_mission.cutscenes[i].formula, SEXP_SAVE_MODE); + convert_sexp_to_string(sexp_out, The_mission.cutscenes[i].formula, sexp_save_mode()); fout(" %s\n", sexp_out.c_str()); } } @@ -1862,7 +1862,7 @@ int Fred_mission_save::save_debriefing() 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); + convert_sexp_to_string(sexp_out, Debriefing->stages[i].formula, sexp_save_mode()); fout(" %s", sexp_out.c_str()); // XSTR @@ -1912,7 +1912,7 @@ int Fred_mission_save::save_events() 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); + convert_sexp_to_string(sexp_out, Mission_events[i].formula, sexp_save_mode()); fout(" %s", sexp_out.c_str()); if (!Mission_events[i].name.empty()) { @@ -2188,7 +2188,7 @@ int Fred_mission_save::save_fiction() // 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); + convert_sexp_to_string(sexp_out, stage.formula, sexp_save_mode()); if (optional_string_fred("$Formula:")) parse_comments(); @@ -2248,7 +2248,7 @@ int Fred_mission_save::save_goals() required_string_fred("$Formula:"); parse_comments(); - convert_sexp_to_string(sexp_out, Mission_goals[i].formula, SEXP_SAVE_MODE); + 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) { @@ -3641,7 +3641,7 @@ int Fred_mission_save::save_objects() required_string_fred("$Arrival Cue:"); parse_comments(); - convert_sexp_to_string(sexp_out, shipp->arrival_cue, SEXP_SAVE_MODE); + convert_sexp_to_string(sexp_out, shipp->arrival_cue, sexp_save_mode()); fout(" %s", sexp_out.c_str()); if (shipp->wingnum >= 0) { @@ -3690,7 +3690,7 @@ int Fred_mission_save::save_objects() required_string_fred("$Departure Cue:"); parse_comments(); - convert_sexp_to_string(sexp_out, shipp->departure_cue, SEXP_SAVE_MODE); + 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); @@ -5063,7 +5063,7 @@ int Fred_mission_save::save_wings() required_string_fred("$Arrival Cue:"); parse_comments(); - convert_sexp_to_string(sexp_out, w.arrival_cue, SEXP_SAVE_MODE); + convert_sexp_to_string(sexp_out, w.arrival_cue, sexp_save_mode()); fout(" %s", sexp_out.c_str()); required_string_fred("$Departure Location:"); @@ -5108,7 +5108,7 @@ int Fred_mission_save::save_wings() required_string_fred("$Departure Cue:"); parse_comments(); - convert_sexp_to_string(sexp_out, w.departure_cue, SEXP_SAVE_MODE); + convert_sexp_to_string(sexp_out, w.departure_cue, sexp_save_mode()); fout(" %s", sexp_out.c_str()); required_string_fred("$Ships:"); diff --git a/code/missioneditor/missionsave.h b/code/missioneditor/missionsave.h index b3e183c53d0..58d58112526 100644 --- a/code/missioneditor/missionsave.h +++ b/code/missioneditor/missionsave.h @@ -60,6 +60,10 @@ class Fred_mission_save { Fred_mission_save() = default; void set_save_format(MissionFormat fmt) { save_config.save_format = fmt; } + + int sexp_save_mode() const { + return (save_config.save_format == MissionFormat::RETAIL) ? SEXP_SAVE_MODE_RETAIL : SEXP_SAVE_MODE; + } void set_template_info(const MissionTemplateInfo& info) { save_config.template_info = info; } void set_view_pos(const vec3d& pos) { save_config.view_pos = pos; } void set_view_orient(const matrix& orient) { save_config.view_orient = orient; } diff --git a/code/parse/parselo.h b/code/parse/parselo.h index 8640962302d..5d5fc5e4f5a 100644 --- a/code/parse/parselo.h +++ b/code/parse/parselo.h @@ -75,6 +75,7 @@ enum class ParseLookupType #define SEXP_SAVE_MODE 1 #define SEXP_ERROR_CHECK_MODE 2 +#define SEXP_SAVE_MODE_RETAIL 3 // Save mode that substitutes retail-compatible operator names for their FSO replacements // Goober5000 - this seems to be a pretty universal function extern bool end_string_at_first_hash_symbol(char *src, bool ignore_doubled_hash = false); diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 2a8dc37e90a..04141654e67 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -194,9 +194,9 @@ SCP_vector Operators = { { "is-true-for-duration", OP_IS_TRUE_FOR_DURATION, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 //Event/Goals Category - { "is-goal-true-delay", OP_GOAL_TRUE_DELAY, 2, 2, SEXP_BOOLEAN_OPERATOR, }, - { "is-goal-false-delay", OP_GOAL_FALSE_DELAY, 2, 2, SEXP_BOOLEAN_OPERATOR, }, - { "is-goal-incomplete", OP_GOAL_INCOMPLETE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, + { "is-objective-true-delay", OP_GOAL_TRUE_DELAY, 2, 2, SEXP_BOOLEAN_OPERATOR, }, + { "is-objective-false-delay", OP_GOAL_FALSE_DELAY, 2, 2, SEXP_BOOLEAN_OPERATOR, }, + { "is-objective-incomplete", OP_GOAL_INCOMPLETE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "is-event-true", OP_EVENT_TRUE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "is-event-true-delay", OP_EVENT_TRUE_DELAY, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-event-true-msecs-delay", OP_EVENT_TRUE_MSECS_DELAY, 2, 3, SEXP_BOOLEAN_OPERATOR, }, @@ -204,9 +204,9 @@ SCP_vector Operators = { { "is-event-false-delay", OP_EVENT_FALSE_DELAY, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-event-false-msecs-delay", OP_EVENT_FALSE_MSECS_DELAY, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-event-incomplete", OP_EVENT_INCOMPLETE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, - { "is-previous-goal-true", OP_PREVIOUS_GOAL_TRUE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, - { "is-previous-goal-false", OP_PREVIOUS_GOAL_FALSE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, - { "is-previous-goal-incomplete", OP_PREVIOUS_GOAL_INCOMPLETE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, + { "is-previous-objective-true", OP_PREVIOUS_GOAL_TRUE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, + { "is-previous-objective-false", OP_PREVIOUS_GOAL_FALSE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, + { "is-previous-objective-incomplete", OP_PREVIOUS_GOAL_INCOMPLETE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-previous-event-true", OP_PREVIOUS_EVENT_TRUE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-previous-event-false", OP_PREVIOUS_EVENT_FALSE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-previous-event-incomplete", OP_PREVIOUS_EVENT_INCOMPLETE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, @@ -4767,6 +4767,18 @@ int get_sexp() strcpy_s(token, "add-to-collision-group-new"); else if (!stricmp(token, "remove-from-collision-group2")) strcpy_s(token, "remove-from-collision-group-new"); + else if (!stricmp(token, "is-goal-true-delay")) + strcpy_s(token, "is-objective-true-delay"); + else if (!stricmp(token, "is-goal-false-delay")) + strcpy_s(token, "is-objective-false-delay"); + else if (!stricmp(token, "is-goal-incomplete")) + strcpy_s(token, "is-objective-incomplete"); + else if (!stricmp(token, "is-previous-goal-true")) + strcpy_s(token, "is-previous-objective-true"); + else if (!stricmp(token, "is-previous-goal-false")) + strcpy_s(token, "is-previous-objective-false"); + else if (!stricmp(token, "is-previous-goal-incomplete")) + strcpy_s(token, "is-previous-objective-incomplete"); op = get_operator_index(token); if (op >= 0) { @@ -5213,6 +5225,26 @@ int num_block_variables() return Num_special_expl_blocks * BLOCK_EXP_SIZE; } +// Reverse aliases used when saving in retail-compatible format: map canonical FSO operator names +// back to the older names that the retail FS2 engine can recognize. +static const std::pair Sexp_retail_operator_aliases[] = { + { "is-objective-true-delay", "is-goal-true-delay" }, + { "is-objective-false-delay", "is-goal-false-delay" }, + { "is-objective-incomplete", "is-goal-incomplete" }, + { "is-previous-objective-true", "is-previous-goal-true" }, + { "is-previous-objective-false", "is-previous-goal-false" }, + { "is-previous-objective-incomplete","is-previous-goal-incomplete" }, +}; + +static const char* lookup_retail_operator_alias(const char* text) +{ + for (const auto& pair : Sexp_retail_operator_aliases) { + if (!stricmp(text, pair.first)) + return pair.second; + } + return nullptr; +} + /** * Stuff this particular SEXP node (just the node, not the tree) into a string representation */ @@ -5298,6 +5330,13 @@ void stuff_sexp_text_string(SCP_string &dest, int node, int mode) ctext_string = "0"; } } + // when saving in retail-compatible format, substitute the retail name for any + // canonical FSO operator name that has a registered retail alias + if (mode == SEXP_SAVE_MODE_RETAIL && Sexp_nodes[node].subtype == SEXP_ATOM_OPERATOR) { + const char* retail_name = lookup_retail_operator_alias(ctext_string); + if (retail_name != nullptr) + ctext_string = retail_name; + } sprintf(dest, "%s ", ctext_string); } } @@ -38387,30 +38426,30 @@ SCP_vector Sexp_help = { "false becomes true).\r\n\r\n" "Returns a boolean value. Takes 1 boolean argument." }, - { OP_PREVIOUS_GOAL_TRUE, "Previous Mission Goal True (Boolean operator)\r\n" - "\tReturns true if the specified goal in the specified mission is true " + { OP_PREVIOUS_GOAL_TRUE, "Previous Mission Objective True (Boolean operator)\r\n" + "\tReturns true if the specified objective in the specified mission is true " "(or succeeded). It returns false otherwise.\r\n\r\n" "Returns a boolean value. Takes 2 required arguments and 1 optional argument...\r\n" "\t1:\tName of the mission.\r\n" - "\t2:\tName of the goal in the mission.\r\n" + "\t2:\tName of the objective in the mission.\r\n" "\t3:\t(Optional) True/False which signifies what this sexpression should return when " "this mission is played as a single mission, or is played as a campaign mission and skipped." }, - { OP_PREVIOUS_GOAL_FALSE, "Previous Mission Goal False (Boolean operator)\r\n" - "\tReturns true if the specified goal in the specified mission " + { OP_PREVIOUS_GOAL_FALSE, "Previous Mission Objective False (Boolean operator)\r\n" + "\tReturns true if the specified objective in the specified mission " "is false (or failed). It returns false otherwise.\r\n\r\n" "Returns a boolean value. Takes 2 required arguments and 1 optional argument...\r\n" "\t1:\tName of the mission.\r\n" - "\t2:\tName of the goal in the mission.\r\n" + "\t2:\tName of the objective in the mission.\r\n" "\t3:\t(Optional) True/False which signifies what this sexpression should return when " "this mission is played as a single mission, or is played as a campaign mission and skipped." }, - { OP_PREVIOUS_GOAL_INCOMPLETE, "Previous Mission Goal Incomplete (Boolean operator)\r\n" - "\tReturns true if the specified goal in the specified mission " + { OP_PREVIOUS_GOAL_INCOMPLETE, "Previous Mission Objective Incomplete (Boolean operator)\r\n" + "\tReturns true if the specified objective in the specified mission " "is incomplete (not true or false). It returns false otherwise.\r\n\r\n" "Returns a boolean value. Takes 2 required arguments and 1 optional argument...\r\n" "\t1:\tName of the mission.\r\n" - "\t2:\tName of the goal in the mission.\r\n" + "\t2:\tName of the objective in the mission.\r\n" "\t3:\t(Optional) True/False which signifies what this sexpression should return when " "this mission is played as a single mission, or is played as a campaign mission and skipped." }, @@ -38441,26 +38480,26 @@ SCP_vector Sexp_help = { "\t3:\t(Optional) True/False which signifies what this sexpression should return when " "this mission is played as a single mission, or is played as a campaign mission and skipped." }, - { OP_GOAL_TRUE_DELAY, "Mission Goal True (Boolean operator)\r\n" - "\tReturns true N seconds after the specified goal in the this mission is true (or succeeded). It returns false otherwise.\r\n\r\n" - "\tThis operator works by checking the mission log. Since goal status is evaluated after event status, a delay of 0 will cause an event to become true on the frame after the goal becomes true.\r\n\r\n" + { OP_GOAL_TRUE_DELAY, "Mission Objective True (Boolean operator)\r\n" + "\tReturns true N seconds after the specified objective in the this mission is true (or succeeded). It returns false otherwise.\r\n\r\n" + "\tThis operator works by checking the mission log. Since objective status is evaluated after event status, a delay of 0 will cause an event to become true on the frame after the objective becomes true.\r\n\r\n" "Returns a boolean value. Takes 2 required arguments and 1 optional argument...\r\n" - "\t1:\tName of the event in the mission.\r\n" + "\t1:\tName of the mission objective.\r\n" "\t2:\tNumber of seconds to delay before returning true."}, - { OP_GOAL_FALSE_DELAY, "Mission Goal False (Boolean operator)\r\n" - "\tReturns true N seconds after the specified goal in the this mission is false (or failed). It returns false otherwise.\r\n\r\n" - "\tThis operator works by checking the mission log. Since goal status is evaluated after event status, a delay of 0 will cause an event to become true on the frame after the goal becomes false.\r\n\r\n" + { OP_GOAL_FALSE_DELAY, "Mission Objective False (Boolean operator)\r\n" + "\tReturns true N seconds after the specified objective in the this mission is false (or failed). It returns false otherwise.\r\n\r\n" + "\tThis operator works by checking the mission log. Since objective status is evaluated after event status, a delay of 0 will cause an event to become true on the frame after the objective becomes false.\r\n\r\n" "Returns a boolean value. Takes 2 required arguments and 1 optional argument...\r\n" - "\t1:\tName of the event in the mission.\r\n" + "\t1:\tName of the mission objective.\r\n" "\t2:\tNumber of seconds to delay before returning true."}, - { OP_GOAL_INCOMPLETE, "Mission Goal Incomplete (Boolean operator)\r\n" - "\tReturns true if the specified goal in the this mission is incomplete. This " + { OP_GOAL_INCOMPLETE, "Mission Objective Incomplete (Boolean operator)\r\n" + "\tReturns true if the specified objective in the this mission is incomplete. This " "sexpression will only be useful in conjunction with another sexpression like " "has-time-elapsed. Used alone, it will return true upon mission startup.\r\n" "Returns a boolean value. Takes 1 argument...\r\n" - "\t1:\tName of the event in the mission."}, + "\t1:\tName of the mission objective."}, { OP_EVENT_TRUE_DELAY, "Mission Event True (Boolean operator)\r\n" "\tReturns true N seconds after the specified event in the this mission is true (or succeeded). It returns false otherwise.\r\n\r\n" diff --git a/code/scripting/ade.cpp b/code/scripting/ade.cpp index b4adbca0e13..34e084511ac 100644 --- a/code/scripting/ade.cpp +++ b/code/scripting/ade.cpp @@ -789,7 +789,9 @@ ade_indexer::ade_indexer(lua_CFunction func, ade_overload_list overloads, const char* desc, const char* ret_type, - const char* ret_desc) + const char* ret_desc, + const gameversion::version& deprecation_version, + const char* deprecation_message) { // Add function for meta ade_table_entry ate; @@ -802,6 +804,8 @@ ade_indexer::ade_indexer(lua_CFunction func, ate.Description = desc; ate.ReturnType = ret_type; ate.ReturnDescription = ret_desc; + ate.DeprecationVersion = deprecation_version; + ate.DeprecationMessage = deprecation_message; LibIdx = ade_manager::getInstance()->getEntry(parent.GetIdx()).AddSubentry(ate); } diff --git a/code/scripting/ade_api.h b/code/scripting/ade_api.h index b538a006ba5..0a846306351 100644 --- a/code/scripting/ade_api.h +++ b/code/scripting/ade_api.h @@ -436,7 +436,9 @@ class ade_indexer : public ade_lib_handle { ade_overload_list overloads, const char* desc, const char* ret_type, - const char* ret_desc); + const char* ret_desc, + const gameversion::version& deprecation_version, + const char* deprecation_message); }; /** @@ -455,7 +457,30 @@ class ade_indexer : public ade_lib_handle { */ #define ADE_INDEXER(parent, args, desc, ret_type, ret_desc) \ static int parent##___indexer_f(lua_State* L); \ - ::scripting::ade_indexer parent##___indexer(parent##___indexer_f, parent, args, desc, ret_type, ret_desc); \ + ::scripting::ade_indexer parent##___indexer(parent##___indexer_f, parent, args, desc, ret_type, ret_desc, \ + ::gameversion::version(), nullptr); \ + static int parent##___indexer_f(lua_State* L) + +/** + * @brief Declare a deprecated indexer of an object + * + * Use this with objects to deal with forms such as vec.x, vec['x'], vec[0]. Format string should be "o*%", where * is + * indexing value, and % is the value to set to when LUA_SETTTING_VAR is set + * + * @param parent The library or object containing the indexer + * @param args Documentation for the type of the value that may be assigned + * @param desc Description of what the variable does + * @param ret_type The type of the returned value + * @param ret_desc Documentation for the returned value + * @param deprecate_version Version starting from which this function is deprecated. + * @param deprecated_msg Message for the deprecation notice. May be nullptr. + * + * @ingroup ade_api + */ +#define ADE_INDEXER_DEPRECATED(parent, args, desc, ret_type, ret_desc, deprecate_version, deprecated_msg) \ + static int parent##___indexer_f(lua_State* L); \ + ::scripting::ade_indexer parent##___indexer(parent##___indexer_f, parent, args, desc, ret_type, ret_desc, \ + deprecate_version, deprecated_msg); \ static int parent##___indexer_f(lua_State* L) //*************************Lua return values************************* diff --git a/code/scripting/api/libs/mission.cpp b/code/scripting/api/libs/mission.cpp index 395c254fda6..007f577eb49 100644 --- a/code/scripting/api/libs/mission.cpp +++ b/code/scripting/api/libs/mission.cpp @@ -23,6 +23,7 @@ #include "mission/missionlog.h" #include "mission/missionmessage.h" #include "mission/missiontraining.h" +#include "mod_table/mod_table.h" #include "missionui/missionbrief.h" #include "missionui/missioncmdbrief.h" #include "missionui/redalert.h" @@ -410,9 +411,15 @@ ADE_FUNC(__len, l_Mission_Events, nullptr, "Number of events in mission", "numbe } //****SUBLIBRARY: Mission/Goals -ADE_LIB_DERIV(l_Mission_Goals, "Goals", nullptr, "Goals", l_Mission); +ADE_LIB_DERIV(l_Mission_Goals, "Goals", nullptr, "Goals (deprecated in favor of Objectives)", l_Mission); -ADE_INDEXER(l_Mission_Goals, "number/string IndexOrName", "Indexes mission goals list", "mission_goal", "Goal handle, or invalid goal handle if index was invalid") +ADE_INDEXER_DEPRECATED(l_Mission_Goals, + "number/string IndexOrName", + "Indexes mission goals list", + "mission_goal", + "Goal handle, or invalid goal handle if index was invalid", + gameversion::version(26, 0, 0), + "mn.Goals is deprecated in favor of mn.Objectives.") { const char* s; if(!ade_get_args(L, "*s", &s)) @@ -434,7 +441,44 @@ ADE_INDEXER(l_Mission_Goals, "number/string IndexOrName", "Indexes mission goals return ade_set_args(L, "o", l_Goal.Set(i)); } -ADE_FUNC(__len, l_Mission_Goals, nullptr, "Number of goals in mission", "number", "Number of goals in mission") +ADE_FUNC_DEPRECATED(__len, + l_Mission_Goals, + nullptr, + "Number of goals in mission", + "number", + "Number of goals in mission", + gameversion::version(26, 0, 0), + "mn.Goals is deprecated in favor of mn.Objectives.") +{ + return ade_set_args(L, "i", static_cast(Mission_goals.size())); +} + +//****SUBLIBRARY: Mission/Mission_Objectives +ADE_LIB_DERIV(l_Mission_Objectives, "Mission_Objectives", "Objectives", "Mission objectives", l_Mission); + +ADE_INDEXER(l_Mission_Objectives, "number/string IndexOrName", "Indexes mission objectives list", "mission_goal", "Objective handle, or invalid objective handle if index was invalid") +{ + const char* s; + if(!ade_get_args(L, "*s", &s)) + return ade_set_error(L, "o", l_Goal.Set(-1)); + + int i = mission_goal_lookup(s); + if (i >= 0) + return ade_set_args(L, "o", l_Goal.Set(i)); + + //Now try as a number + i = atoi(s); + //Lua-->FS2 + i--; + + if(!SCP_vector_inbounds(Mission_goals, i)) + return ade_set_error(L, "o", l_Goal.Set(-1)); + + + return ade_set_args(L, "o", l_Goal.Set(i)); +} + +ADE_FUNC(__len, l_Mission_Objectives, nullptr, "Number of objectives in mission", "number", "Number of objectives in mission") { return ade_set_args(L, "i", static_cast(Mission_goals.size())); } diff --git a/code/scripting/api/objs/goal.cpp b/code/scripting/api/objs/goal.cpp index 59b9d02af8e..df6a90a33c4 100644 --- a/code/scripting/api/objs/goal.cpp +++ b/code/scripting/api/objs/goal.cpp @@ -87,7 +87,21 @@ ADE_VIRTVAR(Team, l_Goal, nullptr, "The goal team", "team", "The goal team") return ade_set_args(L, "o", l_Team.Set(Mission_goals[current].team)); } -ADE_VIRTVAR(isGoalSatisfied, l_Goal, nullptr, "The status of the goal", "number", "0 if failed, 1 if complete, 2 if incomplete") +ADE_VIRTVAR_DEPRECATED(isGoalSatisfied, l_Goal, nullptr, "The status of the goal", "number", "0 if failed, 1 if complete, 2 if incomplete", gameversion::version(26, 0, 0), "Deprecated in favor of isObjectiveSatisfied") +{ + int current; + if (!ade_get_args(L, "o", l_Goal.Get(¤t))) { + return ADE_RETURN_NIL; + } + + if (ADE_SETTING_VAR) { + LuaError(L, "This property is read only."); + } + + return ade_set_args(L, "i", Mission_goals[current].satisfied); +} + +ADE_VIRTVAR(isObjectiveSatisfied, l_Goal, nullptr, "The status of the objective", "number", "0 if failed, 1 if complete, 2 if incomplete") { int current; if (!ade_get_args(L, "o", l_Goal.Get(¤t))) { @@ -115,7 +129,21 @@ ADE_VIRTVAR(Score, l_Goal, nullptr, "The score of the goal", "number", "the scor return ade_set_args(L, "i", Mission_goals[current].score); } -ADE_VIRTVAR(isGoalValid, l_Goal, nullptr, "The goal validity", "boolean", "true if valid, false otherwise") +ADE_VIRTVAR_DEPRECATED(isGoalValid, l_Goal, nullptr, "The goal validity", "boolean", "true if valid, false otherwise", gameversion::version(26, 0, 0), "Deprecated in favor of isObjectiveValid") +{ + int current; + if (!ade_get_args(L, "o", l_Goal.Get(¤t))) { + return ADE_RETURN_NIL; + } + + if (ADE_SETTING_VAR) { + LuaError(L, "This property is read only."); + } + + return ade_set_args(L, "b", !(Mission_goals[current].type & INVALID_GOAL)); +} + +ADE_VIRTVAR(isObjectiveValid, l_Goal, nullptr, "The objective validity", "boolean", "true if valid, false otherwise") { int current; if (!ade_get_args(L, "o", l_Goal.Get(¤t))) { diff --git a/fred2/dumpstats.cpp b/fred2/dumpstats.cpp index 3959c9901ea..a7e12de15d2 100644 --- a/fred2/dumpstats.cpp +++ b/fred2/dumpstats.cpp @@ -463,7 +463,7 @@ void DumpStats::get_objectives_and_goals(CString &buffer) CString temp; int i; - buffer += "\r\nOBJECTIVES AND GOALS\r\n"; + buffer += "\r\nEVENT DIRECTIVES AND MISSION OBJECTIVES\r\n"; // objectives for (i=0; i<(int)Mission_events.size(); i++) { @@ -479,7 +479,7 @@ void DumpStats::get_objectives_and_goals(CString &buffer) // goals for (i=0; i<(int)Mission_goals.size(); i++) { - temp.Format("\tGoal: %s, text: %s", Mission_goals[i].name.c_str(), Mission_goals[i].message.c_str()); + temp.Format("\tObjective: %s, text: %s", Mission_goals[i].name.c_str(), Mission_goals[i].message.c_str()); buffer += temp; switch(Mission_goals[i].type & GOAL_TYPE_MASK) { diff --git a/fred2/fred.rc b/fred2/fred.rc index bbfefe7442b..5d053c4100f 100644 --- a/fred2/fred.rc +++ b/fred2/fred.rc @@ -375,7 +375,7 @@ BEGIN POPUP "Mission" BEGIN MENUITEM "&Mission Specs\tShift+N", 32771 - MENUITEM "Mission &Goals\tShift+G", 32800 + MENUITEM "Mission &Objectives\tShift+G", 32800 MENUITEM "Mission &Events\tShift+E", 32974 MENUITEM "Mission Cutscenes", ID_EDITORS_CUTSCENES MENUITEM SEPARATOR @@ -982,7 +982,7 @@ BEGIN CONTROL "Toggle Starting in Chase View",IDC_TOGGLE_START_CHASE, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,326,161,146,10 CONTROL "2D Mission",IDC_2D_MISSION,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,326,171,123,10 - CONTROL "Toggle Showing Goals In Briefing",IDC_TOGGLE_SHOWING_GOALS, + CONTROL "Toggle Showing Objectives In Briefing",IDC_TOGGLE_SHOWING_GOALS, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,326,181,142,10 CONTROL "Mission End to Mainhall",IDC_END_TO_MAINHALL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,326,191,142,10 CONTROL "Preload Subspace Tunnel",IDC_PRELOAD_SUBSPACE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,326,201,142,10 @@ -1181,7 +1181,7 @@ BEGIN COMBOBOX IDC_OBJ_TEAM,293,98,73,71,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP CONTROL "Objective Invalid",IDC_GOAL_INVALID,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,239,117,69,10 CONTROL "Don't Play Completion Music",IDC_NO_MUSIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,239,127,107,10 - PUSHBUTTON "New Obj.",IDC_BUTTON_NEW_GOAL,233,146,44,14,0,WS_EX_STATICEDGE,HIDC_BUTTON_NEW_GOAL + PUSHBUTTON "New Objective",IDC_BUTTON_NEW_GOAL,233,146,44,14,0,WS_EX_STATICEDGE,HIDC_BUTTON_NEW_GOAL PUSHBUTTON "OK",ID_OK,281,146,44,14 PUSHBUTTON "Cancel",IDCANCEL,329,146,44,14 EDITTEXT IDC_HELP_BOX,6,167,367,95,ES_MULTILINE | ES_READONLY | WS_VSCROLL,WS_EX_TRANSPARENT @@ -1250,7 +1250,7 @@ BEGIN PUSHBUTTON "Squad Logo",IDC_WING_SQUAD_LOGO_BUTTON,7,89,55,14 EDITTEXT IDC_WING_SQUAD_LOGO,65,90,80,14,ES_AUTOHSCROLL | ES_READONLY CONTROL "Reinforcement Unit",IDC_REINFORCEMENT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,10,77,10 - CONTROL "Ignore for Counting Goals",IDC_IGNORE_COUNT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,21,96,10 + CONTROL "Ignore for Counting Objectives",IDC_IGNORE_COUNT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,21,96,10 CONTROL "No Arrival Music",IDC_NO_ARRIVAL_MUSIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,32,67,10 CONTROL "No Arrival Message",IDC_NO_ARRIVAL_MESSAGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,43,77,10 CONTROL "No First Wave Message",IDC_NO_FIRST_WAVE_MESSAGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,54,77,10 @@ -2058,7 +2058,7 @@ BEGIN CONTROL "Reinforcement Unit",IDC_REINFORCEMENT,"Button",BS_3STATE | WS_TABSTOP,7,62,75,10 CONTROL "Protect Ship",IDC_PROTECT_SHIP,"Button",BS_3STATE | WS_TABSTOP,7,72,59,10 CONTROL "Beam Protect Ship",IDC_BEAM_PROTECT_SHIP,"Button",BS_3STATE | WS_TABSTOP,12,93,80,8 - CONTROL "Ignore for Counting Goals",IDC_IGNORE_COUNT,"Button",BS_3STATE | WS_TABSTOP,7,136,93,10 + CONTROL "Ignore for Counting Objectives",IDC_IGNORE_COUNT,"Button",BS_3STATE | WS_TABSTOP,7,136,93,10 CONTROL "Escort Ship",IDC_ESCORT,"Button",BS_3STATE | WS_TABSTOP,7,146,52,10 EDITTEXT IDC_ESCORT_PRIORITY,43,157,26,12,ES_AUTOHSCROLL LTEXT "Priority",IDC_STATIC,19,159,22,8 @@ -3151,18 +3151,18 @@ END IDD_MISSION_GOALS DLGINIT BEGIN - IDC_DISPLAY_GOAL_TYPES_DROP, 0x403, 14, 0 -0x7250, 0x6d69, 0x7261, 0x2079, 0x6f47, 0x6c61, 0x0073, - IDC_DISPLAY_GOAL_TYPES_DROP, 0x403, 16, 0 -0x6553, 0x6f63, 0x646e, 0x7261, 0x2079, 0x6f47, 0x6c61, 0x0073, - IDC_DISPLAY_GOAL_TYPES_DROP, 0x403, 12, 0 -0x6f42, 0x756e, 0x2073, 0x6f47, 0x6c61, 0x0073, - IDC_GOAL_TYPE_DROP, 0x403, 13, 0 -0x7250, 0x6d69, 0x7261, 0x2079, 0x6f47, 0x6c61, "\000" - IDC_GOAL_TYPE_DROP, 0x403, 15, 0 -0x6553, 0x6f63, 0x646e, 0x7261, 0x2079, 0x6f47, 0x6c61, "\000" - IDC_GOAL_TYPE_DROP, 0x403, 11, 0 -0x6f42, 0x756e, 0x2073, 0x6f47, 0x6c61, "\000" + IDC_DISPLAY_GOAL_TYPES_DROP, 0x403, 19, 0 +0x7250, 0x6d69, 0x7261, 0x2079, 0x624f, 0x656a, 0x7463, 0x7669, 0x7365, "\000" + IDC_DISPLAY_GOAL_TYPES_DROP, 0x403, 21, 0 +0x6553, 0x6f63, 0x646e, 0x7261, 0x2079, 0x624f, 0x656a, 0x7463, 0x7669, 0x7365, "\000" + IDC_DISPLAY_GOAL_TYPES_DROP, 0x403, 17, 0 +0x6f42, 0x756e, 0x2073, 0x624f, 0x656a, 0x7463, 0x7669, 0x7365, "\000" + IDC_GOAL_TYPE_DROP, 0x403, 18, 0 +0x7250, 0x6d69, 0x7261, 0x2079, 0x624f, 0x656a, 0x7463, 0x7669, 0x0065, + IDC_GOAL_TYPE_DROP, 0x403, 20, 0 +0x6553, 0x6f63, 0x646e, 0x7261, 0x2079, 0x624f, 0x656a, 0x7463, 0x7669, 0x0065, + IDC_GOAL_TYPE_DROP, 0x403, 16, 0 +0x6f42, 0x756e, 0x2073, 0x624f, 0x656a, 0x7463, 0x7669, 0x0065, IDC_OBJ_TEAM, 0x403, 7, 0 0x6554, 0x6d61, 0x3120, "\000" IDC_OBJ_TEAM, 0x403, 7, 0 @@ -3623,7 +3623,7 @@ END STRINGTABLE BEGIN - ID_EDITORS_GOALS "Activates Mission Goal Editor\nMission Goal Editor" + ID_EDITORS_GOALS "Activates Mission Objective Editor\nMission Objective Editor" ID_EDITORS_WAYPOINTS "Activates Waypoint Editor\nWaypoint Editor" ID_EDITORS_SOUND "Activates Sound Editor\nSound Editor" ID_EDITORS_TERRAIN "Activates Terrain Editor Screen\nTerrain Editor" diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp index 62a135fd166..c8966b3642b 100644 --- a/fred2/fredview.cpp +++ b/fred2/fredview.cpp @@ -3008,7 +3008,7 @@ int CFREDView::global_error_check() } for (i=0; i<(int)Mission_goals.size(); i++){ - if (fred_check_sexp(Mission_goals[i].formula, OPR_BOOL, "mission goal \"%s\"", Mission_goals[i].name.c_str())){ + if (fred_check_sexp(Mission_goals[i].formula, OPR_BOOL, "mission objective \"%s\"", Mission_goals[i].name.c_str())){ return -1; } } diff --git a/fred2/management.cpp b/fred2/management.cpp index 5021609a630..b09427b1703 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -2077,9 +2077,9 @@ int reference_handler(const char *name, sexp_ref_type type, int obj) case sexp_src::MISSION_GOAL: if (!Mission_goals[n].name.empty()) - sprintf(text, "mission goal \"%s\"", Mission_goals[n].name.c_str()); + sprintf(text, "mission objective \"%s\"", Mission_goals[n].name.c_str()); else - sprintf(text, "mission goal #%d", n); + sprintf(text, "mission objective #%d", n); break; diff --git a/fred2/missiongoalsdlg.cpp b/fred2/missiongoalsdlg.cpp index 518704b37ab..8fbe21d85d4 100644 --- a/fred2/missiongoalsdlg.cpp +++ b/fred2/missiongoalsdlg.cpp @@ -394,8 +394,8 @@ void CMissionGoalsDlg::OnButtonNewGoal() m_sig.push_back(-1); m_goals.back().type = m_display_goal_types; // this also marks the goal as valid since bit not set - m_goals.back().name = "Goal name"; - m_goals.back().message = "Mission goal text"; + m_goals.back().name = "Objective name"; + m_goals.back().message = "Mission objective text"; h = m_goals_tree.insert(m_goals.back().name.c_str()); m_goals_tree.item_index = -1; diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index 2f148873679..150a0da5a32 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -1314,9 +1314,9 @@ int Editor::reference_handler(const char* name, sexp_ref_type type, int obj) { case sexp_src::MISSION_GOAL: if (!Mission_goals[n].name.empty()) { - sprintf(text, "mission goal \"%s\"", Mission_goals[n].name.c_str()); + sprintf(text, "mission objective \"%s\"", Mission_goals[n].name.c_str()); } else { - sprintf(text, "mission goal #%d", n); + sprintf(text, "mission objective #%d", n); } break; @@ -2709,7 +2709,7 @@ int Editor::global_error_check_impl() { } for (i = 0; i < (int)Mission_goals.size(); i++) { - if (fred_check_sexp(Mission_goals[i].formula, OPR_BOOL, "mission goal \"%s\"", Mission_goals[i].name.c_str())) { + if (fred_check_sexp(Mission_goals[i].formula, OPR_BOOL, "mission objective \"%s\"", Mission_goals[i].name.c_str())) { return -1; } } diff --git a/qtfred/src/mission/dialogs/MissionGoalsDialogModel.cpp b/qtfred/src/mission/dialogs/MissionGoalsDialogModel.cpp index 293ccb51e8f..924064aadf6 100644 --- a/qtfred/src/mission/dialogs/MissionGoalsDialogModel.cpp +++ b/qtfred/src/mission/dialogs/MissionGoalsDialogModel.cpp @@ -160,8 +160,8 @@ mission_goal& MissionGoalsDialogModel::createNewGoal() { m_sig.push_back(-1); m_goals.back().type = m_display_goal_types; // this also marks the goal as valid since bit not set - m_goals.back().name = "Goal name"; - m_goals.back().message = "Mission goal text"; + m_goals.back().name = "Objective name"; + m_goals.back().message = "Mission objective text"; set_modified(); return m_goals.back(); diff --git a/qtfred/ui/FredView.ui b/qtfred/ui/FredView.ui index 1c957482e86..ddd996a4d32 100644 --- a/qtfred/ui/FredView.ui +++ b/qtfred/ui/FredView.ui @@ -1406,7 +1406,7 @@ - Mission Goals + Mission Objectives Shift+G diff --git a/qtfred/ui/MissionGoalsDialog.ui b/qtfred/ui/MissionGoalsDialog.ui index 0adae32a084..b27e33c1876 100644 --- a/qtfred/ui/MissionGoalsDialog.ui +++ b/qtfred/ui/MissionGoalsDialog.ui @@ -92,17 +92,17 @@ - Primary Goal + Primary Objective - Secondary Goal + Secondary Objective - Bonus Goal + Bonus Objective @@ -183,7 +183,7 @@ - Mission goal text + Mission objective text @@ -203,7 +203,7 @@ - New Obj. + New Objective @@ -211,17 +211,17 @@ - Primary Goals + Primary Objectives - Secondary Goals + Secondary Objectives - Bonus Goals + Bonus Objectives diff --git a/qtfred/ui/MissionStatsDialog.ui b/qtfred/ui/MissionStatsDialog.ui index 7c2374aee25..a3903b6e169 100644 --- a/qtfred/ui/MissionStatsDialog.ui +++ b/qtfred/ui/MissionStatsDialog.ui @@ -174,7 +174,7 @@ - Goals: + Objectives: diff --git a/qtfred/ui/PreferencesDialog.ui b/qtfred/ui/PreferencesDialog.ui index c11c0989a21..0319ddba482 100644 --- a/qtfred/ui/PreferencesDialog.ui +++ b/qtfred/ui/PreferencesDialog.ui @@ -133,7 +133,7 @@ - Mission Goals + Mission Objectives From 31bdced969497627f89e410ba4b2c8874ef6d674 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 4 May 2026 08:57:46 -0500 Subject: [PATCH 2/2] update qtfred help --- .../help-src/doc/concepts/campaign-flow.html | 18 ++--- qtfred/help-src/doc/concepts/sexps.html | 6 +- .../doc/dialogs/MissionGoalsDialog.html | 78 ------------------- .../doc/dialogs/MissionObjectivesDialog.html | 74 ++++++++++++++++++ .../doc/dialogs/MissionSpecsDialog.html | 2 +- .../doc/dialogs/MissionStatsDialog.html | 2 +- qtfred/help-src/doc/fundamentals.html | 10 +-- qtfred/help-src/doc/qtfred.qhp | 6 +- qtfred/help-src/doc/quickstart.html | 4 +- 9 files changed, 98 insertions(+), 102 deletions(-) delete mode 100644 qtfred/help-src/doc/dialogs/MissionGoalsDialog.html create mode 100644 qtfred/help-src/doc/dialogs/MissionObjectivesDialog.html diff --git a/qtfred/help-src/doc/concepts/campaign-flow.html b/qtfred/help-src/doc/concepts/campaign-flow.html index c83471fdcfa..b3c5392be06 100644 --- a/qtfred/help-src/doc/concepts/campaign-flow.html +++ b/qtfred/help-src/doc/concepts/campaign-flow.html @@ -11,7 +11,7 @@

Campaign Flow

A campaign is a sequence of missions linked together in the Campaign Editor. The campaign file controls which mission plays next, branches the story based on outcomes, and -carries state between missions through goals and variables.

+carries state between missions through objectives and variables.

How missions end

A mission ends when the player reaches the debrief screen. There are two @@ -20,21 +20,21 @@

How missions end

ConditionWhen it occursWhat is saved Mission completedThe player accepts the debriefing after flying the mission.Variables with Save on Mission - Completed persistence, plus all goal states. + Completed persistence, plus all objective states. Mission closedThe player exits the mission by any means, including failure or replaying.Variables with Save on Mission Close persistence. -

Goal states

-

Every mission goal ends in one of three states: complete, failed, or +

Objective states

+

Every mission objective ends in one of three states: complete, failed, or incomplete. The campaign SEXP tree reads these states using goal-true and goal-false operators to decide which mission to branch to next. This is how outcomes carry forward; whether an important ship survived, whether a bonus objective was met, and so on.

-

Goals do not automatically determine success or failure. The campaign SEXPs -are what interpret goal states and produce outcomes. A mission where all primary -goals are complete can still branch to a "defeat" mission if the campaign logic +

Objectives do not automatically determine success or failure. The campaign SEXPs +are what interpret objective states and produce outcomes. A mission where all primary +objectives are complete can still branch to a "defeat" mission if the campaign logic is written that way.

Variables across missions

@@ -54,9 +54,9 @@

Branching

use the end-campaign SEXP.

Common branching patterns include:

    -
  • Checking whether a primary goal succeeded to route to a victory or +
  • Checking whether a primary objective succeeded to route to a victory or defeat mission.
  • -
  • Checking whether an optional goal was completed to unlock bonus +
  • Checking whether an optional objective was completed to unlock bonus missions.
  • Reading a variable set earlier in the campaign to take a story branch.
  • diff --git a/qtfred/help-src/doc/concepts/sexps.html b/qtfred/help-src/doc/concepts/sexps.html index a4d22375f7d..26901b47666 100644 --- a/qtfred/help-src/doc/concepts/sexps.html +++ b/qtfred/help-src/doc/concepts/sexps.html @@ -33,15 +33,15 @@

    Where SEXPs appear

    • Mission Events — the primary place to author SEXP logic; see the Mission Events dialog.
    • -
    • Mission Goals — the completion condition for each goal; - see the Mission Goals dialog.
    • +
    • Mission Objectives — the completion condition for each objective; + see the Mission Objectives dialog.
    • Ship arrival/departure cues — conditions on when ships enter and leave the mission.
    • Briefing and debriefing — stage-change conditions.

    Editing SEXPs in QtFRED

    -

    The SEXP tree editor is embedded directly in the Mission Events and Mission Goals +

    The SEXP tree editor is embedded directly in the Mission Events and Mission Objectives dialogs. Right-click any node in the tree to add, replace, or remove operators and arguments. Hover over an operator name for a brief description of what it does.

    diff --git a/qtfred/help-src/doc/dialogs/MissionGoalsDialog.html b/qtfred/help-src/doc/dialogs/MissionGoalsDialog.html deleted file mode 100644 index f7984c628a3..00000000000 --- a/qtfred/help-src/doc/dialogs/MissionGoalsDialog.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - Mission Goals - QtFRED Help - - - - -

    Mission Goals

    -

    Opens via Editors › Mission Goals.

    -

    Defines the goals shown to the player at the end of the briefing and tracked on -the HUD during the mission. When a goal is completed or failed a HUD popup notifies -the player. Goals are hidden during the briefing if the -Toggle Showing Goals In Briefing flag is set in -Mission Specs.

    - -
    The UI uses the term Objective in some places (such as -the New Obj. button) interchangeably with Goal. The -canonical term is Goal, which matches the SEXP operator naming -convention.
    - -

    Goal types

    -

    Goal type is a classification that affects how goals are displayed and which music -plays on completion or failure. The actual consequences of a goal being met or failed -, which debriefing stage the player sees, whether the campaign advances, are -determined by SEXPs in the -Debriefing Editor and the -Campaign Editor, which can query goal state -using operators like goal-true and goal-false.

    - - - - - -
    TypeDescription
    PrimaryShown to the player from the start of the mission. - Completion or failure triggers the primary goal music stinger. By convention - these are the core objectives designers tie campaign progression and debriefing - outcomes to.
    SecondaryShown to the player from the start of the mission. - Completion or failure triggers the secondary goal music stinger. Designers - typically use these for optional objectives whose state is queried in the - debriefing or campaign for bonus outcomes.
    BonusHidden from the player until the goal is achieved, at which - point the goal text appears on the HUD. Their state is queryable by SEXPs the - same as any other goal type.
    - -

    Goal list

    -

    The main area displays all goals as SEXP -trees. The dropdown at the top filters the list to show one type at a time; -Primary, Secondary, or Bonus. Use New Obj. to append a new goal -of the currently filtered type.

    - -

    Goal properties

    - - - - - - - -
    FieldDescription
    TypePrimary, Secondary, or Bonus. Changing this moves the goal - into the corresponding type group.
    NameInternal identifier used in SEXPs such as - goal-true and goal-false, and in mission log - entries. Not shown to the player.
    Goal textText displayed to the player in the briefing goal list - and HUD objective display.
    ScorePoints awarded when this goal is completed.
    TeamIn multiplayer missions, the team this goal belongs - to.
    - -

    Goal flags

    - - - - -
    FlagDescription
    Objective InvalidMarks the goal as invalid from the start of - the mission. Invalid goals are not shown to the player and are not evaluated. - Goals can also be marked valid or invalid at runtime by SEXPs, making this - useful for goals that are conditionally activated during play.
    Don't Play Completion MusicSuppresses the event music stinger - that normally plays when this goal is completed or failed.
    - - diff --git a/qtfred/help-src/doc/dialogs/MissionObjectivesDialog.html b/qtfred/help-src/doc/dialogs/MissionObjectivesDialog.html new file mode 100644 index 00000000000..e5228761294 --- /dev/null +++ b/qtfred/help-src/doc/dialogs/MissionObjectivesDialog.html @@ -0,0 +1,74 @@ + + + + + Mission Objectives - QtFRED Help + + + + +

    Mission Objectives

    +

    Opens via Editors › Mission Objectives.

    +

    Defines the objectives shown to the player at the end of the briefing and tracked on +the HUD during the mission. When an objective is completed or failed a HUD popup notifies +the player. Objectives are hidden during the briefing if the +Toggle Showing Goals In Briefing flag is set in +Mission Specs.

    + +

    Objective types

    +

    Objective type is a classification that affects how objectives are displayed and which +music plays on completion or failure. The actual consequences of an objective being met +or failed — which debriefing stage the player sees, whether the campaign advances +— are determined by SEXPs in the +Debriefing Editor and the +Campaign Editor, which can query objective state +using operators like goal-true and goal-false.

    + + + + + +
    TypeDescription
    PrimaryShown to the player from the start of the mission. + Completion or failure triggers the primary objective music stinger. By convention + these are the core objectives designers tie campaign progression and debriefing + outcomes to.
    SecondaryShown to the player from the start of the mission. + Completion or failure triggers the secondary objective music stinger. Designers + typically use these for optional objectives whose state is queried in the + debriefing or campaign for bonus outcomes.
    BonusHidden from the player until the objective is achieved, at + which point the objective text appears on the HUD. Their state is queryable by + SEXPs the same as any other objective type.
    + +

    Objective list

    +

    The main area displays all objectives as SEXP +trees. The dropdown at the top filters the list to show one type at a time; +Primary, Secondary, or Bonus. Use New Objective to append a new +objective of the currently filtered type.

    + +

    Objective properties

    + + + + + + + +
    FieldDescription
    TypePrimary, Secondary, or Bonus. Changing this moves the objective + into the corresponding type group.
    NameInternal identifier used in SEXPs such as + goal-true and goal-false, and in mission log + entries. Not shown to the player.
    Objective textText displayed to the player in the briefing + objective list and HUD objective display.
    ScorePoints awarded when this objective is completed.
    TeamIn multiplayer missions, the team this objective belongs + to.
    + +

    Objective flags

    + + + + +
    FlagDescription
    Objective InvalidMarks the objective as invalid from the start of + the mission. Invalid objectives are not shown to the player and are not + evaluated. Objectives can also be marked valid or invalid at runtime by SEXPs, + making this useful for objectives that are conditionally activated during + play.
    Don't Play Completion MusicSuppresses the event music stinger + that normally plays when this objective is completed or failed.
    + + diff --git a/qtfred/help-src/doc/dialogs/MissionSpecsDialog.html b/qtfred/help-src/doc/dialogs/MissionSpecsDialog.html index b05acd65eb1..845073b4684 100644 --- a/qtfred/help-src/doc/dialogs/MissionSpecsDialog.html +++ b/qtfred/help-src/doc/dialogs/MissionSpecsDialog.html @@ -35,7 +35,7 @@

    Mission Type

    must then be selected: Co-op, Team vs. Team, or Dogfight (all players against all others). TrainingIntended for tutorial content. A Skip Training - button appears on the briefing screen; selecting it marks all mission goals + button appears on the briefing screen; selecting it marks all mission objectives complete and advances the campaign without the player having to fly the mission. diff --git a/qtfred/help-src/doc/dialogs/MissionStatsDialog.html b/qtfred/help-src/doc/dialogs/MissionStatsDialog.html index f725043826e..f59a2c77091 100644 --- a/qtfred/help-src/doc/dialogs/MissionStatsDialog.html +++ b/qtfred/help-src/doc/dialogs/MissionStatsDialog.html @@ -16,7 +16,7 @@

    Tabs

    + mission events, mission objectives, and messages.
    TabContents
    SummaryTotal counts: ships, wings, waypoint paths, jump nodes, - mission events, mission goals, and messages.
    ShipsEvery ship in the mission listed with its name, class, team, and wing assignment.
    EscortShips with the escort flag enabled in the diff --git a/qtfred/help-src/doc/fundamentals.html b/qtfred/help-src/doc/fundamentals.html index c6eb1410efa..6c2cab45c9e 100644 --- a/qtfred/help-src/doc/fundamentals.html +++ b/qtfred/help-src/doc/fundamentals.html @@ -31,7 +31,7 @@

    1. Plan before you open QtFRED

  • A hostile fighter wing that arrives ~30 seconds in
  • A waypoint path for the transport to follow
  • Events: a timed trigger for the hostile arrival, a radio message when they arrive
  • -
  • A primary mission goal tied to the transport completing its route
  • +
  • A primary mission objective tied to the transport completing its route
  • A two-stage briefing and a debriefing
  • Knowing this upfront prevents building yourself into a corner. You will still @@ -114,14 +114,14 @@

    5. Send a message when the hostiles arrive

    Save and test. The message should appear on screen as Epsilon jumps in.

    -

    6. Add a mission goal

    -

    Open Editors › Mission Goals. Add a primary goal:

    +

    6. Add a mission objective

    +

    Open Editors › Mission Objectives. Add a primary objective:

    • Description: Escort the transport to the rendezvous
    • Completion condition: (are-waypoints-done-delay 0 "Transport 1" "Transport Route")
    -

    Add a secondary goal for destroying Epsilon wing if you like. See +

    Add a secondary objective for destroying Epsilon wing if you like. See SEXPs for an overview of how conditions are built.

    @@ -168,7 +168,7 @@

    9. Final test

  • The transport moves along its route from the start
  • The first 30 seconds are calm
  • Epsilon and the arrival message both trigger at 30 seconds
  • -
  • The primary goal completes when the transport finishes the route
  • +
  • The primary objective completes when the transport finishes the route
  • The briefing camera moves and icons look correct
  • The correct debriefing stage shows depending on outcome
  • diff --git a/qtfred/help-src/doc/qtfred.qhp b/qtfred/help-src/doc/qtfred.qhp index 1ed36d9a3fb..d93b948eb86 100644 --- a/qtfred/help-src/doc/qtfred.qhp +++ b/qtfred/help-src/doc/qtfred.qhp @@ -30,7 +30,7 @@
    -
    +
    @@ -110,7 +110,7 @@ - + @@ -173,7 +173,7 @@ general/SceneBrowserDialog.html dialogs/MissionCutscenesDialog.html dialogs/MissionEventsDialog.html - dialogs/MissionGoalsDialog.html + dialogs/MissionObjectivesDialog.html dialogs/MissionSpecsDialog.html dialogs/CustomWingNamesDialog.html dialogs/CustomStringsDialog.html diff --git a/qtfred/help-src/doc/quickstart.html b/qtfred/help-src/doc/quickstart.html index b07705ef12d..cc0a5d4fee7 100644 --- a/qtfred/help-src/doc/quickstart.html +++ b/qtfred/help-src/doc/quickstart.html @@ -43,8 +43,8 @@

    5. Fill in Mission Specs

    Title and a short Description. These appear in the mission selection screen. Set music tracks if desired.

    -

    6. Add a mission goal

    -

    Open Editors › Mission Goals. Add a primary goal with a +

    6. Add a mission objective

    +

    Open Editors › Mission Objectives. Add a primary objective with a description such as Destroy all hostiles. Set the completion condition to a SEXP that evaluates to true when Epsilon wing is destroyed, for example is-destroyed-delay. See SEXPs for an