From 53435532b055e65975389ee79d61026842b95bb1 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 6 May 2026 13:08:41 -0500 Subject: [PATCH 1/2] large ship collision group mission flag --- code/mission/mission_flags.h | 1 + code/mission/missionparse.cpp | 27 ++++++++++++++- code/mission/missionparse.h | 1 + code/missioneditor/missionsave.cpp | 4 +++ .../dialogs/MissionSpecDialogModel.cpp | 18 ++++++++++ .../mission/dialogs/MissionSpecDialogModel.h | 3 ++ qtfred/src/ui/dialogs/MissionSpecDialog.cpp | 15 +++++++++ qtfred/src/ui/dialogs/MissionSpecDialog.h | 2 ++ qtfred/ui/MissionSpecDialog.ui | 33 +++++++++++++++++++ 9 files changed, 103 insertions(+), 1 deletion(-) diff --git a/code/mission/mission_flags.h b/code/mission/mission_flags.h index 178b1eddbfd..8189433525c 100644 --- a/code/mission/mission_flags.h +++ b/code/mission/mission_flags.h @@ -36,6 +36,7 @@ namespace Mission { Neb2_fog_color_override, // Whether to use explicit fog colors instead of checking the palette - Goober5000 Fullneb_background_bitmaps, // Show background bitmaps despite fullneb Preload_subspace, // Preload the subspace tunnel for both the sexp and specs checkbox (for scripts) - MjnMixael + Large_ships_no_collide_by_default, // Automatically puts all large ships in a shared collision group NUM_VALUES }; diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 057a62afe4f..c575612facd 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -182,6 +182,8 @@ SCP_unordered_set Fred_migrated_immobile_ships; int Num_path_restrictions; path_restriction_t Path_restrictions[MAX_PATH_RESTRICTIONS]; +constexpr int DEFAULT_LARGE_SHIP_NO_COLLIDE_COLLISION_GROUP = 0; + extern int debrief_find_persona_index(); static bool mission_has_layer_name(const mission* pm, const SCP_string& layerName) { @@ -397,7 +399,8 @@ flag_def_list_new Parse_mission_flags[] = { {"Toggle Starting in Chase View", Mission::Mission_Flags::Toggle_start_chase_view, true, false}, {"Nebula Fog Color Override", Mission::Mission_Flags::Neb2_fog_color_override, true, true}, {"Full Nebula Background Bitmaps", Mission::Mission_Flags::Fullneb_background_bitmaps, true, true}, - {"Preload Subspace Tunnel", Mission::Mission_Flags::Preload_subspace, true, false} + {"Preload Subspace Tunnel", Mission::Mission_Flags::Preload_subspace, true, false}, + {"Large Ships Do Not Collide By Default", Mission::Mission_Flags::Large_ships_no_collide_by_default, true, false} }; parse_object_flag_description Parse_mission_flag_descriptions[] = { @@ -431,6 +434,7 @@ parse_object_flag_description Parse_mission_flag_descrip {Mission::Mission_Flags::Neb2_fog_color_override, "Whether to use explicit fog colors instead of checking the palette"}, {Mission::Mission_Flags::Fullneb_background_bitmaps, "Show background bitmaps despite full nebula"}, {Mission::Mission_Flags::Preload_subspace, "Preload the subspace tunnel for both the sexp and specs checkbox"}, + {Mission::Mission_Flags::Large_ships_no_collide_by_default, "Automatically places all large ships in the configured collision group, preventing large ships from colliding with each other"}, }; const size_t Num_parse_mission_flags = sizeof(Parse_mission_flags) / sizeof(flag_def_list_new); @@ -844,6 +848,18 @@ void parse_mission_info(mission *pm, bool basic = false) stuff_int(&pm->contrail_threshold); } + if (optional_string("+Large Ship Collision Group:")) { + stuff_int(&pm->large_ship_no_collide_collision_group); + + if (pm->large_ship_no_collide_collision_group < 0 || pm->large_ship_no_collide_collision_group > 31) { + WarningEx(LOCATION, + "Invalid large ship collision group id %d specified. Valid IDs range from 0 to 31. Using group %d instead.\n", + pm->large_ship_no_collide_collision_group, + DEFAULT_LARGE_SHIP_NO_COLLIDE_COLLISION_GROUP); + pm->large_ship_no_collide_collision_group = DEFAULT_LARGE_SHIP_NO_COLLIDE_COLLISION_GROUP; + } + } + if (optional_string("+Volumetric Nebula:")) { pm->volumetrics.emplace().parse_volumetric_nebula(); } @@ -2238,6 +2254,11 @@ int parse_create_object_sub(p_object *p_objp, bool standalone_ship) // Goober5000 - set the collision group if one was provided Objects[objnum].collision_group_id = p_objp->collision_group_id; + // Mission-level performance helper: large ships may be grouped so they skip mutual collision checks. + if (The_mission.flags[Mission::Mission_Flags::Large_ships_no_collide_by_default] && sip->is_big_or_huge()) { + Objects[objnum].collision_group_id |= (1 << The_mission.large_ship_no_collide_collision_group); + } + // Goober5000 - set some fields that the mission log might need (if logged via parse_bring_in_docked_wing just below) shipp->display_name = p_objp->display_name; shipp->alt_type_index = p_objp->alt_type_index; @@ -7178,6 +7199,7 @@ void mission::Reset() envmap_name[ 0 ] = '\0'; contrail_threshold = CONTRAIL_THRESHOLD_DEFAULT; + large_ship_no_collide_collision_group = DEFAULT_LARGE_SHIP_NO_COLLIDE_COLLISION_GROUP; ambient_light_level = DEFAULT_AMBIENT_LIGHT_LEVEL; sound_environment.id = -1; @@ -9557,6 +9579,9 @@ bool check_for_24_3_data() bool check_for_25_1_data() { + if (The_mission.flags[Mission::Mission_Flags::Large_ships_no_collide_by_default]) + return true; + if (count_items_with_value(Props) > 0) return true; diff --git a/code/mission/missionparse.h b/code/mission/missionparse.h index eea950e897e..3af9f4eb4e4 100644 --- a/code/mission/missionparse.h +++ b/code/mission/missionparse.h @@ -207,6 +207,7 @@ typedef struct mission { char envmap_name[MAX_FILENAME_LEN]; int skybox_flags; int contrail_threshold; + int large_ship_no_collide_collision_group; int ambient_light_level; std::optional volumetrics; sound_env sound_environment; diff --git a/code/missioneditor/missionsave.cpp b/code/missioneditor/missionsave.cpp index e46fb6b590c..fb13384156b 100644 --- a/code/missioneditor/missionsave.cpp +++ b/code/missioneditor/missionsave.cpp @@ -2568,6 +2568,10 @@ int Fred_mission_save::save_mission_info() if (The_mission.contrail_threshold != CONTRAIL_THRESHOLD_DEFAULT) { fout("\n$Contrail Speed Threshold: %d\n", The_mission.contrail_threshold); } + + if (The_mission.flags[Mission::Mission_Flags::Large_ships_no_collide_by_default]) { + fout("\n+Large Ship Collision Group: %d\n", The_mission.large_ship_no_collide_collision_group); + } } { diff --git a/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp b/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp index fa8101a3de4..ffd6453dd9e 100644 --- a/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp +++ b/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp @@ -70,6 +70,7 @@ void MissionSpecDialogModel::initializeData() { _m_contrail_threshold = The_mission.contrail_threshold; _m_contrail_threshold_flag = (_m_contrail_threshold != CONTRAIL_THRESHOLD_DEFAULT); + _m_large_ship_no_collide_collision_group = The_mission.large_ship_no_collide_collision_group; _m_custom_data = The_mission.custom_data; _m_custom_strings = The_mission.custom_strings; @@ -125,6 +126,7 @@ bool MissionSpecDialogModel::apply() { The_mission.support_ships.max_support_ships = (_m_disallow_support) ? 0 : -1; The_mission.support_ships.max_hull_repair_val = _m_max_hull_repair_val; The_mission.support_ships.max_subsys_repair_val = _m_max_subsys_repair_val; + The_mission.large_ship_no_collide_collision_group = _m_large_ship_no_collide_collision_group; // Copy mission flags The_mission.flags = _m_flags; @@ -418,6 +420,22 @@ void MissionSpecDialogModel::setMissionFlagDirect(Mission::Mission_Flags flag, b } } +void MissionSpecDialogModel::setLargeShipNoCollideCollisionGroup(int group) +{ + if (group < 0) { + group = 0; + } else if (group > 31) { + group = 31; + } + + modify(_m_large_ship_no_collide_collision_group, group); +} + +int MissionSpecDialogModel::getLargeShipNoCollideCollisionGroup() const +{ + return _m_large_ship_no_collide_collision_group; +} + bool MissionSpecDialogModel::getMissionFlag(Mission::Mission_Flags flag) const { return _m_flags[flag]; } diff --git a/qtfred/src/mission/dialogs/MissionSpecDialogModel.h b/qtfred/src/mission/dialogs/MissionSpecDialogModel.h index 0b623a3625a..636ac027b98 100644 --- a/qtfred/src/mission/dialogs/MissionSpecDialogModel.h +++ b/qtfred/src/mission/dialogs/MissionSpecDialogModel.h @@ -42,6 +42,7 @@ class MissionSpecDialogModel : public AbstractDialogModel { float _m_max_subsys_repair_val; bool _m_contrail_threshold_flag; int _m_contrail_threshold; + int _m_large_ship_no_collide_collision_group; SCP_map _m_custom_data; SCP_vector _m_custom_strings; sound_env _m_sound_env; @@ -119,6 +120,8 @@ class MissionSpecDialogModel : public AbstractDialogModel { void setMissionFlag(const SCP_string& flag_name, bool enabled); void setMissionFlagDirect(Mission::Mission_Flags flag, bool enabled); + void setLargeShipNoCollideCollisionGroup(int group); + int getLargeShipNoCollideCollisionGroup() const; bool getMissionFlag(Mission::Mission_Flags flag) const; const SCP_vector>& getMissionFlagsList(); static SCP_vector> getMissionFlagDescriptions(); diff --git a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp index 339b9d60e19..a5ee42e72b8 100644 --- a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp @@ -134,6 +134,7 @@ void MissionSpecDialog::initFlagList() // per flag immediate apply to the model connect(ui->flagList, &fso::fred::FlagListWidget::flagToggled, this, [this](const QString& name, bool checked) { _model->setMissionFlag(name.toUtf8().constData(), checked); + updateLargeShipCollisionGroup(); }); } @@ -149,6 +150,15 @@ void MissionSpecDialog::updateFlags() } ui->flagList->setFlags(toWidget); + updateLargeShipCollisionGroup(); +} + +void MissionSpecDialog::updateLargeShipCollisionGroup() +{ + const auto enabled = _model->getMissionFlag(Mission::Mission_Flags::Large_ships_no_collide_by_default); + ui->largeShipCollisionGroupLabel->setVisible(enabled); + ui->largeShipCollisionGroup->setVisible(enabled); + ui->largeShipCollisionGroup->setValue(_model->getLargeShipNoCollideCollisionGroup()); } void MissionSpecDialog::updateMissionType() { @@ -433,6 +443,11 @@ void MissionSpecDialog::on_musicPackCombo_currentIndexChanged(int index) { _model->setSubEventMusic(subMusic); } +void MissionSpecDialog::on_largeShipCollisionGroup_valueChanged(int value) +{ + _model->setLargeShipNoCollideCollisionGroup(value); +} + void MissionSpecDialog::on_aiProfileCombo_currentIndexChanged(int index) { auto aipIndex = ui->aiProfileCombo->itemData(index).value(); diff --git a/qtfred/src/ui/dialogs/MissionSpecDialog.h b/qtfred/src/ui/dialogs/MissionSpecDialog.h index 64884aa02be..ed279f9acf0 100644 --- a/qtfred/src/ui/dialogs/MissionSpecDialog.h +++ b/qtfred/src/ui/dialogs/MissionSpecDialog.h @@ -64,6 +64,7 @@ private slots: // Right column // flags are dynamically generated and connected + void on_largeShipCollisionGroup_valueChanged(int value); void on_aiProfileCombo_currentIndexChanged(int index); // General @@ -84,6 +85,7 @@ private slots: void initFlagList(); void updateFlags(); + void updateLargeShipCollisionGroup(); void updateMissionType(); void updateCmdMessage(); diff --git a/qtfred/ui/MissionSpecDialog.ui b/qtfred/ui/MissionSpecDialog.ui index b3517fa6f48..ff6a6c9caa8 100644 --- a/qtfred/ui/MissionSpecDialog.ui +++ b/qtfred/ui/MissionSpecDialog.ui @@ -822,6 +822,39 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Large Ship Collision Group + + + + + + + 0 + + + 31 + + + + + From 77ba0cd692be3e1226bc732656e613bd0eefb95c Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Thu, 7 May 2026 16:16:43 -0500 Subject: [PATCH 2/2] update help doc --- qtfred/help-src/doc/dialogs/MissionSpecsDialog.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/qtfred/help-src/doc/dialogs/MissionSpecsDialog.html b/qtfred/help-src/doc/dialogs/MissionSpecsDialog.html index b05acd65eb1..399096ae618 100644 --- a/qtfred/help-src/doc/dialogs/MissionSpecsDialog.html +++ b/qtfred/help-src/doc/dialogs/MissionSpecsDialog.html @@ -140,5 +140,15 @@

Flags

box to find a specific flag by name, or use Select All / Select None to bulk-toggle. Hover over any flag label for a tooltip description.

+

Some flags reveal an additional control when enabled:

+ + + + + +
FlagControlDescription
Large Ships Do Not Collide By DefaultLarge Ship Collision GroupCollision group ID (0–31) that large and huge ships are automatically + placed into when the mission loads. Ships sharing the same group skip + collision checks against each other, reducing physics overhead in + missions with many capital ships. Default is 0.