From 6ecbfcd10d53898ffb57f6fea4dc94cf0afff45f Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Tue, 18 Aug 2026 02:12:35 -0700 Subject: [PATCH 1/7] rebase --- code/hud/hudsquadmsg.cpp | 280 ++++++++++++++++++++--------------- code/hud/hudsquadmsg.h | 35 +++-- code/mod_table/mod_table.cpp | 32 ++++ code/mod_table/mod_table.h | 1 + code/ship/ship.h | 2 + 5 files changed, 222 insertions(+), 128 deletions(-) diff --git a/code/hud/hudsquadmsg.cpp b/code/hud/hudsquadmsg.cpp index 57a33fa5216..753ae892b17 100644 --- a/code/hud/hudsquadmsg.cpp +++ b/code/hud/hudsquadmsg.cpp @@ -123,8 +123,7 @@ int keys_used[] = { KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_ #define ID1 1 #define ID2 2 - -SCP_string Comm_order_types[NUM_COMM_ORDER_TYPES]; +SCP_vector> Comm_order_types; int player_order::orderingCounter = 0; @@ -158,26 +157,47 @@ const SCP_set target_messages = []() { return setunion; }(); +bool is_smallcraft_flavor(ship_info *sinfop, SmallCraftFlavor flavor) { + switch (flavor) { + case SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS: + return sinfop->is_fighter_bomber(); + break; + case SmallCraftFlavor::ALL_FIGHTERS: + return sinfop->is_fighter(); + break; + case SmallCraftFlavor::ALL_BOMBERS: + return sinfop->is_bomber(); + break; + } + UNREACHABLE("Invalid SmallCraftFlavor of %i in 'is_smallcraft_flavor()'", flavor); +} + void hud_init_comm_orders() { int i; - const char *temp_comm_order_types[] = - { - XSTR("Ships", 293), - XSTR("Wings", 294), - XSTR("All Fighters", 295), - XSTR("Reinforcements", 296), - XSTR("Rearm/Repair Subsys", 297), - XSTR("Abort Rearm", 298) - }; - - for (i = 0; i < NUM_COMM_ORDER_TYPES; i++) - { - Comm_order_types[i] = temp_comm_order_types[i]; + if (!Parsed_comm_orders.empty()) { + for (i = 0; i < sz2i(Parsed_comm_orders.size()); i++) { + Comm_order_types.emplace_back(static_cast(Parsed_comm_orders[i].first), Parsed_comm_orders[i].second); + } + } else { + std::array, NUM_DEFAULT_COMM_ORDER_TYPES> Default_comm_order_types = + { + std::pair(CommOrderType::MSG_SHIPS, XSTR("Ships", 293)), + std::pair(CommOrderType::MSG_WINGS, XSTR("Wings", 294)), + std::pair(CommOrderType::MSG_ALL_FIGHTERS_AND_BOMBERS, XSTR("All Fighters", 295)), + std::pair(CommOrderType::REINFORCEMENTS, XSTR("Reinforcements", 296)), + std::pair(CommOrderType::REARM_REPAIR, XSTR("Rearm/Repair Subsys", 297)), + std::pair(CommOrderType::ABORT_REARM, XSTR("Abort Rearm", 298)), + }; + for (i = 0; i < sz2i(Default_comm_order_types.size()); i++) { + Comm_order_types.emplace_back(Default_comm_order_types[i]); + } } + + for (auto& order : Player_orders) order.localize(); @@ -269,7 +289,7 @@ void hud_squadmsg_end() // function which returns true if there are fighters/bombers on the players team in the mission // In debug versions, we will allow messaging to enemies -bool hud_squadmsg_exist_fighters( ) +bool hud_squadmsg_exist_fighters_bombers(SmallCraftFlavor flavor) { ship_obj *so; object *objp; @@ -282,11 +302,12 @@ bool hud_squadmsg_exist_fighters( ) continue; shipp = &Ships[objp->instance]; - Assertion(shipp->objnum != -1, "hud_squadmsg_exist_fighters() discovered that ship #%d ('%s') has an objnum of -1. Since the ship was retrieved from its object number (%d), this should be impossible; get a coder!\n", objp->instance, shipp->ship_name, so->objnum); + Assertion(shipp->objnum != -1, "hud_squadmsg_exist_fighters_bombers() discovered that ship #%d ('%s') has an objnum of -1. Since the ship was retrieved from its object number (%d), this should be impossible; get a coder!\n", objp->instance, shipp->ship_name, so->objnum); // ship must be a fighter/bomber - if (!(Ship_info[shipp->ship_info_index].is_fighter_bomber())) + if (!is_smallcraft_flavor(&Ship_info[shipp->ship_info_index], flavor)) { continue; + } // this ship satisfies everything if (hud_squadmsg_ship_valid(shipp, objp)) @@ -986,7 +1007,7 @@ bool hud_squadmsg_run_order_issued_hook(int command, ship* sendingShip, ship* re } // function to send an order to all fighters/bombers. -void hud_squadmsg_send_to_all_fighters( int command, int player_num ) +void hud_squadmsg_send_to_all_fighters( int command, int player_num, SmallCraftFlavor flavor ) { ai_info *aip; ship *shipp, *ordering_shipp; @@ -1019,7 +1040,7 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num ) if ( command == IGNORE_TARGET_ITEM ) { // if we were messaging a ship directly, set flag to send no messages. We will send one // specifically from the ship player is ordering - if ( (Msg_instance != MESSAGE_ALL_FIGHTERS) && (Squad_msg_mode == SM_MODE_SHIP_COMMAND) ) { + if ( (Msg_instance != MESSAGE_ALL_FIGHTERS_BOMBERS) && (Squad_msg_mode == SM_MODE_SHIP_COMMAND) ) { do_ship = 1; send_message = 0; } @@ -1049,8 +1070,9 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num ) continue; // can't message if ship not fighter/bomber if the command isn't to everyone. - if ( !(Ship_info[Wings[i].special_ship_ship_info_index].is_fighter_bomber()) ) + if (!is_smallcraft_flavor(&Ship_info[Wings[i].special_ship_ship_info_index], flavor)) { continue; + } // don't send the command if the "wing" won't accept the command. We do this by looking at // the set of orders accepted for the wing leader @@ -1085,8 +1107,9 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num ) continue; // don't send message to non fighter wings - if ( !(Ship_info[shipp->ship_info_index].is_fighter_bomber()) ) + if (!is_smallcraft_flavor(&Ship_info[shipp->ship_info_index], flavor)) { continue; + } // skip departing/dying ships if ( shipp->is_dying_or_departing() ) @@ -1112,7 +1135,7 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num ) // guy we orders /* Goober5000 - yet again with the weird logic if ( do_ship ) { - Assert( Msg_instance != MESSAGE_ALL_FIGHTERS ); + Assert( Msg_instance != MESSAGE_ALL_FIGHTERS_BOMBERS ); hud_squadmsg_send_ship_command( Msg_instance, command, 1 ); } */ @@ -1737,7 +1760,7 @@ void hud_squadmsg_type_select( ) { int k, i; - int num_order_types = NUM_COMM_ORDER_TYPES; + int num_order_types = sz2i(Comm_order_types.size()); int lua_order_count = 0; @@ -1753,10 +1776,10 @@ void hud_squadmsg_type_select( ) for (i = 0; i < num_order_types; i++) { - if (i < NUM_COMM_ORDER_TYPES) { - MsgItems.push_back({0, 1, Comm_order_types[i]}); // assume active + if (i < sz2i(Comm_order_types.size())) { + MsgItems.push_back({Comm_order_types[i].first, 1, Comm_order_types[i].second}); // assume active } else { - MsgItems.push_back({0, 1, lua_cat_list[i - NUM_COMM_ORDER_TYPES]}); // assume active + MsgItems.push_back({0, 1, lua_cat_list[i - sz2i(Comm_order_types.size())]}); // assume active } } @@ -1769,102 +1792,89 @@ void hud_squadmsg_type_select( ) goto do_main_menu; } - // based on ship counts, wing counts, shortcut active, grey out possible menu choices - if ( !hud_squadmsg_count_ships(0) ) - MsgItems[TYPE_SHIP_ITEM].active = 0; - - if ( !hud_squadmsg_count_wings(0) ) - MsgItems[TYPE_WING_ITEM].active = 0; - - // check to be sure that we have some fighters/bombers on the players team that we - // can message - if ( !hud_squadmsg_exist_fighters() ){ - MsgItems[TYPE_ALL_FIGHTERS_ITEM].active = 0; - } - - if ((Player_ship != NULL) && !hud_squadmsg_reinforcements_available(Player_ship->team)) { - MsgItems[TYPE_REINFORCEMENT_ITEM].active = 0; + for ( auto item : MsgItems ) { + if (((hud_communications_state(Player_ship) != COMM_OK) + || ((Game_mode & GM_MULTIPLAYER) && !multi_can_message(Net_player))) + && (item.instance != CommOrderType::REARM_REPAIR) + && (item.instance != CommOrderType::ABORT_REARM)) { + item.active = 0; + continue; + } + switch (item.instance) { + case CommOrderType::MSG_SHIPS: + item.active = hud_squadmsg_count_ships(0); + break; + case CommOrderType::MSG_WINGS: + item.active = hud_squadmsg_count_wings(0); + break; + case CommOrderType::MSG_ALL_FIGHTERS_AND_BOMBERS: + item.active = hud_squadmsg_exist_fighters_bombers(SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS); + break; + case CommOrderType::MSG_ALL_FIGHTERS: + item.active = hud_squadmsg_exist_fighters_bombers(SmallCraftFlavor::ALL_FIGHTERS); + break; + case CommOrderType::MSG_ALL_BOMBERS: + item.active = hud_squadmsg_exist_fighters_bombers(SmallCraftFlavor::ALL_BOMBERS); + break; + case CommOrderType::REINFORCEMENTS: + item.active = (Player_ship != NULL) && !hud_squadmsg_reinforcements_available(Player_ship->team) && Msg_shortcut_command == -1; + break; + case CommOrderType::REARM_REPAIR: + if (Hide_main_rearm_items_in_comms_gauge) { + item.active = -1; + } else { + item.active = (!(Ai_info[Ships[Player_obj->instance].ai_index].ai_flags.any_of(AI::AI_Flags::Being_repaired,AI::AI_Flags::Awaiting_repair) + || mission_is_repair_scheduled(Player_obj))) + && is_support_allowed(Player_obj) + && hud_squadmsg_can_rearm(Player_ship) + && Msg_shortcut_command == -1; + } + break; + case CommOrderType::ABORT_REARM: + if (Hide_main_rearm_items_in_comms_gauge) { + item.active = -1; + } else { + item.active = (Ai_info[Ships[Player_obj->instance].ai_index].ai_flags.any_of(AI::AI_Flags::Being_repaired,AI::AI_Flags::Awaiting_repair) + || mission_is_repair_scheduled(Player_obj)) + && is_support_allowed(Player_obj) + && Msg_shortcut_command == -1; + } + break; + } } - MsgItems[TYPE_REPAIR_REARM_ITEM].active = Hide_main_rearm_items_in_comms_gauge ? -1 : 1; - MsgItems[TYPE_REPAIR_REARM_ABORT_ITEM].active = Hide_main_rearm_items_in_comms_gauge ? -1 : 0; - for(const auto& cat : lua_cat_list){ if (ai_lua_get_general_orders(false, false, cat).size() == 0) { - MsgItems[NUM_COMM_ORDER_TYPES + lua_order_count].active = 0; + MsgItems[sz2i(Comm_order_types.size()) + lua_order_count].active = 0; } lua_order_count++; } - // AL: 10/13/97 - // If the player ship communications are severely damaged, then the player - // will only be able to call for repair/rearm ships - // - // also, only allow support ship if this player is not allowed to messaage. - if ( (hud_communications_state(Player_ship) != COMM_OK) || ((Game_mode & GM_MULTIPLAYER) && !multi_can_message(Net_player)) ) { - for (auto &item : MsgItems){ - item.active = 0; - } - - MsgItems[TYPE_REPAIR_REARM_ITEM].active = Hide_main_rearm_items_in_comms_gauge ? -1 : 1; - } - - // check to see if the player is awaiting repair or being repaired. Active the abort and inactive the repair items - // check to see if the player is scheduled to be repaired by incoming ship - if (Ai_info[Ships[Player_obj->instance].ai_index].ai_flags.any_of(AI::AI_Flags::Being_repaired,AI::AI_Flags::Awaiting_repair)) { - MsgItems[TYPE_REPAIR_REARM_ITEM].active = 0; - MsgItems[TYPE_REPAIR_REARM_ABORT_ITEM].active = 1; - } - else if ( mission_is_repair_scheduled(Player_obj) ) { - MsgItems[TYPE_REPAIR_REARM_ITEM].active = 0; - MsgItems[TYPE_REPAIR_REARM_ABORT_ITEM].active = 1; - } - // if no support available, can't call one in - else if ( !is_support_allowed(Player_obj) ) { - MsgItems[TYPE_REPAIR_REARM_ITEM].active = Hide_main_rearm_items_in_comms_gauge ? -1 : 0; - MsgItems[TYPE_REPAIR_REARM_ABORT_ITEM].active = Hide_main_rearm_items_in_comms_gauge ? -1 : 0; - } - - // de-activate the rearm/repair item if the player has a full load of missiles and - // all subsystems at full strength. We will only check if this item hasn't been marked - // inactive because of some other reason - if ( MsgItems[TYPE_REPAIR_REARM_ITEM].active > 0 ) { - - if ( !hud_squadmsg_can_rearm(Player_ship) ){ - MsgItems[TYPE_REPAIR_REARM_ITEM].active = 0; - } - } - - // if using keyboard shortcut, these items are always inactive or hidden - if ( Msg_shortcut_command != -1 ) { - MsgItems[TYPE_REINFORCEMENT_ITEM].active = 0; - MsgItems[TYPE_REPAIR_REARM_ITEM].active = Hide_main_rearm_items_in_comms_gauge ? -1 : 0; - MsgItems[TYPE_REPAIR_REARM_ABORT_ITEM].active = Hide_main_rearm_items_in_comms_gauge ? -1 : 0; - } - do_main_menu: strcpy_s(Squad_msg_title, XSTR( "Message What", 316)); k = hud_squadmsg_get_key(); if ( k != -1 ) { // when k != -1, we have a key that associates with menu item Assert ( k < sz2i(MsgItems.size()) ); - if ( k == TYPE_SHIP_ITEM ){ + if ( MsgItems[k].instance == CommOrderType::MSG_SHIPS ){ hud_squadmsg_do_mode( SM_MODE_SHIP_SELECT ); - } else if ( k == TYPE_WING_ITEM ) { + } else if ( MsgItems[k].instance == CommOrderType::MSG_WINGS ) { hud_squadmsg_do_mode( SM_MODE_WING_SELECT ); - } else if ( k == TYPE_ALL_FIGHTERS_ITEM ) { + } else if ( MsgItems[k].instance == CommOrderType::MSG_ALL_FIGHTERS_AND_BOMBERS ) { + hud_squadmsg_do_mode( SM_MODE_ALL_FIGHTERS_BOMBERS ); + } else if ( MsgItems[k].instance == CommOrderType::MSG_ALL_FIGHTERS ) { hud_squadmsg_do_mode( SM_MODE_ALL_FIGHTERS ); - } - - if ( Msg_shortcut_command == -1 ) { - if ( k == TYPE_REINFORCEMENT_ITEM ) { + } else if ( MsgItems[k].instance == CommOrderType::MSG_ALL_BOMBERS) { + hud_squadmsg_do_mode( SM_MODE_ALL_BOMBERS ); + } else if ( Msg_shortcut_command == -1 ) { + if ( MsgItems[k].instance == CommOrderType::REINFORCEMENTS ) { hud_squadmsg_do_mode( SM_MODE_REINFORCEMENTS ); player_set_next_all_alone_msg_timestamp(); - } else if (k == TYPE_REPAIR_REARM_ITEM && !Hide_main_rearm_items_in_comms_gauge) { + } else if (MsgItems[k].instance == CommOrderType::REARM_REPAIR && !Hide_main_rearm_items_in_comms_gauge) { hud_squadmsg_do_mode( SM_MODE_REPAIR_REARM ); - } else if (k == TYPE_REPAIR_REARM_ABORT_ITEM && !Hide_main_rearm_items_in_comms_gauge) { + } else if (MsgItems[k].instance == CommOrderType::ABORT_REARM && !Hide_main_rearm_items_in_comms_gauge) { hud_squadmsg_do_mode( SM_MODE_REPAIR_REARM_ABORT ); - } else if (k >= NUM_COMM_ORDER_TYPES) { - Lua_sqd_msg_cat = lua_cat_list[k - NUM_COMM_ORDER_TYPES]; + } else if (k >= sz2i(Comm_order_types.size())) { + Lua_sqd_msg_cat = lua_cat_list[k - sz2i(Comm_order_types.size())]; hud_squadmsg_do_mode( SM_MODE_GENERAL ); } } @@ -1927,13 +1937,23 @@ void hud_squadmsg_wing_select() // code which gives an order to all fighters/bombers. If there is a message shortcut active, then // make that order apply to all fighters/bombers. Otherwise, move to the ship_command menu -void hud_squadmsg_msg_all_fighters() +void hud_squadmsg_msg_all_fighters(SmallCraftFlavor flavor) { if ( Msg_shortcut_command == -1 ) { - Msg_instance = MESSAGE_ALL_FIGHTERS; + switch (flavor) { + case SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS: + Msg_instance = MESSAGE_ALL_FIGHTERS_BOMBERS; + break; + case SmallCraftFlavor::ALL_FIGHTERS: + Msg_instance = MESSAGE_ALL_FIGHTERS; + break; + case SmallCraftFlavor::ALL_BOMBERS: + Msg_instance = MESSAGE_ALL_BOMBERS; + break; + } hud_squadmsg_do_mode( SM_MODE_SHIP_COMMAND ); } else { - hud_squadmsg_send_to_all_fighters( Msg_shortcut_command ); + hud_squadmsg_send_to_all_fighters( Msg_shortcut_command, -1, flavor ); hud_squadmsg_toggle(); } } @@ -2112,7 +2132,7 @@ void hud_squadmsg_ship_command() // see if messaging all ships or just one. Messaging all ships will mean all default orders // show on comm menu. - if ( Msg_instance != MESSAGE_ALL_FIGHTERS ) { + if ( Msg_instance != MESSAGE_ALL_FIGHTERS_BOMBERS && Msg_instance != MESSAGE_ALL_FIGHTERS && Msg_instance != MESSAGE_ALL_BOMBERS ) { orders = Ships[Msg_instance].orders_accepted; const auto& default_orders_accepted = ship_get_default_orders_accepted(&Ship_info[Ships[Msg_instance].ship_info_index]); default_orders.insert(default_orders_accepted.cbegin(), default_orders_accepted.cend()); @@ -2131,7 +2151,7 @@ void hud_squadmsg_ship_command() MsgItems.back().active = 1; // if the order cannot be carried out by the ship, then item should be inactive - if ((Msg_instance != MESSAGE_ALL_FIGHTERS) && !hud_squadmsg_ship_order_valid(Msg_instance, (int)order_id)) + if ((Msg_instance != MESSAGE_ALL_FIGHTERS_BOMBERS && Msg_instance != MESSAGE_ALL_FIGHTERS && Msg_instance != MESSAGE_ALL_BOMBERS) && !hud_squadmsg_ship_order_valid(Msg_instance, (int)order_id)) MsgItems.back().active = 0; // do some other checks to possibly gray out other items. @@ -2141,7 +2161,7 @@ void hud_squadmsg_ship_command() // if messaging all fighters, see if we should gray out the order if no one will accept it, // or modify the text if only some of the ships will accept it - if (Msg_instance == MESSAGE_ALL_FIGHTERS) { + if (Msg_instance == MESSAGE_ALL_FIGHTERS_BOMBERS || Msg_instance == MESSAGE_ALL_FIGHTERS || Msg_instance == MESSAGE_ALL_BOMBERS) { ship_obj* so; ship* shipp; bool partial_accept, all_accept; // value which tells us what to do with menu item @@ -2157,9 +2177,22 @@ void hud_squadmsg_ship_command() if (shipp->team != Player_ship->team) continue; - // don't send message to non fighter wings - if (!(Ship_info[shipp->ship_info_index].is_fighter_bomber())) + // don't send message to non fighter or bomber wings + bool is_valid = true; + switch (Msg_instance) { + case MESSAGE_ALL_FIGHTERS_BOMBERS: + is_valid = Ship_info[shipp->ship_info_index].is_fighter_bomber(); + break; + case MESSAGE_ALL_FIGHTERS: + is_valid = Ship_info[shipp->ship_info_index].is_fighter(); + break; + case MESSAGE_ALL_BOMBERS: + is_valid = Ship_info[shipp->ship_info_index].is_bomber(); + break; + } + if (!is_valid) { continue; + } bool local_accepted = shipp->orders_accepted.contains(order_id); all_accept &= local_accepted; // 'and'ing will either keep this bit set or zero it properly @@ -2188,9 +2221,13 @@ void hud_squadmsg_ship_command() Assert ( k < sz2i(MsgItems.size()) ); // when messaging all fighters or ignoring target, call the send_to_all_fighters routine // Goober5000 - ignore no longer sends to all fighters - if (Msg_instance == MESSAGE_ALL_FIGHTERS) - hud_squadmsg_send_to_all_fighters(MsgItems[k].instance); - else + if (Msg_instance == MESSAGE_ALL_FIGHTERS_BOMBERS) { + hud_squadmsg_send_to_all_fighters(MsgItems[k].instance, -1, SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS); + } else if (Msg_instance == MESSAGE_ALL_FIGHTERS) { + hud_squadmsg_send_to_all_fighters(MsgItems[k].instance, -1, SmallCraftFlavor::ALL_FIGHTERS); + } else if (Msg_instance == MESSAGE_ALL_BOMBERS) { + hud_squadmsg_send_to_all_fighters(MsgItems[k].instance, -1, SmallCraftFlavor::ALL_BOMBERS); + } else hud_squadmsg_send_ship_command(Msg_instance, MsgItems[k].instance, 1, SQUADMSG_HISTORY_ADD_ENTRY); hud_squadmsg_toggle(); @@ -2536,8 +2573,16 @@ int hud_squadmsg_do_frame( ) hud_squadmsg_repair_rearm_abort(1); // note we return right away. repair/rearm code handles messaging, etc break; + case SM_MODE_ALL_FIGHTERS_BOMBERS: + hud_squadmsg_msg_all_fighters(SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS); + break; + case SM_MODE_ALL_FIGHTERS: - hud_squadmsg_msg_all_fighters(); + hud_squadmsg_msg_all_fighters(SmallCraftFlavor::ALL_FIGHTERS); + break; + + case SM_MODE_ALL_BOMBERS: + hud_squadmsg_msg_all_fighters(SmallCraftFlavor::ALL_BOMBERS); break; case SM_MODE_GENERAL: @@ -2891,9 +2936,10 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config) int item_num; bool isSelectedItem = (i == Selected_menu_item); char text[256]; + mmode_item item = MsgItems[First_menu_item + i]; if (!config) { - strcpy_s(text, MsgItems[First_menu_item + i].text.c_str()); + strcpy_s(text, item.text.c_str()); } else { // in config mode, so create just the first page of the Comms Menu // as other functions, such as hud_squadmsg_type_select() will not be run in config mode @@ -2926,7 +2972,7 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config) } bool item_visible = config - ? (!Hide_main_rearm_items_in_comms_gauge) || ((i != TYPE_REPAIR_REARM_ITEM) && (i != TYPE_REPAIR_REARM_ABORT_ITEM)) + ? (!Hide_main_rearm_items_in_comms_gauge) || ((item.instance != CommOrderType::REARM_REPAIR) && (item.instance != CommOrderType::ABORT_REARM)) : (MsgItems[First_menu_item + i].active >= 0); if (item_visible) { // first print an icon to indicate selected item diff --git a/code/hud/hudsquadmsg.h b/code/hud/hudsquadmsg.h index 9283f50f2e6..da506bc1e67 100644 --- a/code/hud/hudsquadmsg.h +++ b/code/hud/hudsquadmsg.h @@ -23,11 +23,15 @@ #define SM_MODE_REINFORCEMENTS 6 //call for reinforcements #define SM_MODE_REPAIR_REARM 7 //repair/rearm player ship #define SM_MODE_REPAIR_REARM_ABORT 8 //abort repair/rearm of player ship -#define SM_MODE_ALL_FIGHTERS 9 //message all fighters/bombers +#define SM_MODE_ALL_FIGHTERS_BOMBERS 9 //message all fighters/bombers #define SM_MODE_GENERAL 10 //general orders, usually luaAI +#define SM_MODE_ALL_FIGHTERS 11 +#define SM_MODE_ALL_BOMBERS 12 // define for trapping messages send to "all fighters" -#define MESSAGE_ALL_FIGHTERS -999 +#define MESSAGE_ALL_FIGHTERS -997 +#define MESSAGE_ALL_BOMBERS -998 +#define MESSAGE_ALL_FIGHTERS_BOMBERS -999 class object; struct reinforcements; @@ -64,16 +68,25 @@ struct reinforcements; #define MAX_MENU_DISPLAY 10 // max number that can be displayed -// following are defines and character strings that are used as part of messaging mode +#define NUM_DEFAULT_COMM_ORDER_TYPES 6 -#define NUM_COMM_ORDER_TYPES 6 +enum SmallCraftFlavor { + ALL_FIGHTERS_AND_BOMBERS, + ALL_FIGHTERS, + ALL_BOMBERS, +}; -#define TYPE_SHIP_ITEM 0 -#define TYPE_WING_ITEM 1 -#define TYPE_ALL_FIGHTERS_ITEM 2 -#define TYPE_REINFORCEMENT_ITEM 3 -#define TYPE_REPAIR_REARM_ITEM 4 -#define TYPE_REPAIR_REARM_ABORT_ITEM 5 +enum CommOrderType { + MSG_SHIPS, + MSG_WINGS, + MSG_ALL_FIGHTERS_AND_BOMBERS, + MSG_ALL_FIGHTERS, + MSG_ALL_BOMBERS, + REINFORCEMENTS, + REARM_REPAIR, + ABORT_REARM, + MAX_COMM_ORDER_TYPES, +}; typedef struct mmode_item { int instance; // instance in Ships/Wings array of this menu item @@ -179,7 +192,7 @@ extern void hud_squadmsg_rearm_shortcut(); extern int hud_squadmsg_send_ship_command( int shipnum, int command, int send_message, int update_history = SQUADMSG_HISTORY_ADD_ENTRY, int player_num = -1 ); extern int hud_squadmsg_send_wing_command( int wingnum, int command, int send_message, int update_history = SQUADMSG_HISTORY_ADD_ENTRY, int player_num = -1 ); -extern void hud_squadmsg_send_to_all_fighters( int command, int player_num = -1 ); +extern void hud_squadmsg_send_to_all_fighters( int command, int player_num = -1, SmallCraftFlavor flavor = SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS ); extern void hud_squadmsg_call_reinforcement(reinforcements &reinforcement, int player_num = -1); extern int hud_squadmsg_reinforcements_available(int team); diff --git a/code/mod_table/mod_table.cpp b/code/mod_table/mod_table.cpp index cebd454ea26..309e86bf1c5 100644 --- a/code/mod_table/mod_table.cpp +++ b/code/mod_table/mod_table.cpp @@ -25,6 +25,7 @@ #include "sound/sound.h" #include "starfield/supernova.h" #include "playerman/player.h" +#include "hud/hudsquadmsg.h" int Directive_wait_time; bool True_loop_argument_sexps; @@ -190,6 +191,7 @@ float Min_radius_for_persistent_debris; bool Zero_radius_explosions_skip_fireballs; bool Render_insignias_as_decals; bool Link_special_point_subsystems_to_destroyed_submodels; +SCP_vector> Parsed_comm_orders; #ifdef WITH_DISCORD @@ -504,6 +506,35 @@ void parse_mod_table(const char *filename) stuff_boolean(&Always_show_selected_item_in_comms_gauge); } + if (optional_string("$Available squad orders:")) { + SCP_string order_type; + SCP_string text; + while (optional_string("+Order Type:")) { + stuff_string(order_type, F_NAME); + required_string("+Text:"); + stuff_string(text, F_NAME); + if (!stricmp(order_type.c_str(), "MESSAGE SHIPS")) { + Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_SHIPS, text)); + } else if (!stricmp(order_type.c_str(), "MESSAGE WINGS")) { + Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_WINGS, text)); + } else if (!stricmp(order_type.c_str(), "MESSAGE ALL FIGHTERS AND BOMBERS")) { + Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_ALL_FIGHTERS_AND_BOMBERS, text)); + } else if (!stricmp(order_type.c_str(), "MESSAGE ALL FIGHTERS")) { + Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_ALL_FIGHTERS, text)); + } else if (!stricmp(order_type.c_str(), "MESSAGE ALL BOMBERS")) { + Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_ALL_BOMBERS, text)); + } else if (!stricmp(order_type.c_str(), "REINFORCEMENTS")) { + Parsed_comm_orders.emplace_back(std::pair(CommOrderType::REINFORCEMENTS, text)); + } else if (!stricmp(order_type.c_str(), "REARM/REPAIR SUBSYSTEMS")) { + Parsed_comm_orders.emplace_back(std::pair(CommOrderType::REARM_REPAIR, text)); + } else if (!stricmp(order_type.c_str(), "ABORT REARM")) { + Parsed_comm_orders.emplace_back(std::pair(CommOrderType::ABORT_REARM, text)); + } else { + Error(LOCATION, "Game Settings Table: Invalid squad order type %s found!", order_type.c_str()); + } + } + } + optional_string("#SEXP SETTINGS"); if (optional_string("$Loop SEXPs Then Arguments:")) { @@ -1968,6 +1999,7 @@ void mod_table_reset() Zero_radius_explosions_skip_fireballs = false; Render_insignias_as_decals = false; Link_special_point_subsystems_to_destroyed_submodels = false; + Parsed_comm_orders.clear(); } void mod_table_set_version_flags() diff --git a/code/mod_table/mod_table.h b/code/mod_table/mod_table.h index ea1a9be939a..5f9bc7826fa 100644 --- a/code/mod_table/mod_table.h +++ b/code/mod_table/mod_table.h @@ -210,6 +210,7 @@ extern float Min_radius_for_persistent_debris; extern bool Zero_radius_explosions_skip_fireballs; extern bool Render_insignias_as_decals; extern bool Link_special_point_subsystems_to_destroyed_submodels; +extern SCP_vector> Parsed_comm_orders; void mod_table_init(); void mod_table_post_process(); diff --git a/code/ship/ship.h b/code/ship/ship.h index 0aaf086a2f7..ec93662649d 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -1619,6 +1619,8 @@ class ship_info inline bool is_flyable() const { return flags.none_of(Ship::Info_Flags::Cargo,Ship::Info_Flags::Navbuoy,Ship::Info_Flags::Sentrygun); } // AL 11-24-97: this useful to know for targeting reasons // note: code that previously used is_harmless() / SIF_HARMLESS now uses several flags defined in objecttypes.tbl // inline bool is_harmless() const { return flags[Ship::Info_Flags::Cargo, Ship::Info_Flags::Navbuoy, Ship::Info_Flags::Escapepod]; } // AL 12-3-97: ships that are not a threat + inline bool is_fighter() const { return flags[Ship::Info_Flags::Fighter]; } + inline bool is_bomber() const { return flags[Ship::Info_Flags::Bomber]; } inline bool is_fighter_bomber() const { return flags.any_of(Ship::Info_Flags::Fighter,Ship::Info_Flags::Bomber); } inline bool is_big_or_huge() const { return is_big_ship() || is_huge_ship(); } inline bool avoids_shockwaves() const { return is_small_ship(); } From ebd5de9633bf99a3b053aed49a64530782a8206d Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Tue, 18 Aug 2026 02:41:20 -0700 Subject: [PATCH 2/7] clang --- code/hud/hudsquadmsg.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/code/hud/hudsquadmsg.cpp b/code/hud/hudsquadmsg.cpp index 753ae892b17..380334ffcf2 100644 --- a/code/hud/hudsquadmsg.cpp +++ b/code/hud/hudsquadmsg.cpp @@ -170,6 +170,7 @@ bool is_smallcraft_flavor(ship_info *sinfop, SmallCraftFlavor flavor) { break; } UNREACHABLE("Invalid SmallCraftFlavor of %i in 'is_smallcraft_flavor()'", flavor); + return false; } From ee1b38adedd41f9031527c9990cae683eba9f3bc Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Tue, 18 Aug 2026 03:01:00 -0700 Subject: [PATCH 3/7] clang --- code/hud/hudsquadmsg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/hud/hudsquadmsg.cpp b/code/hud/hudsquadmsg.cpp index 380334ffcf2..545c62cd75f 100644 --- a/code/hud/hudsquadmsg.cpp +++ b/code/hud/hudsquadmsg.cpp @@ -1818,7 +1818,7 @@ void hud_squadmsg_type_select( ) item.active = hud_squadmsg_exist_fighters_bombers(SmallCraftFlavor::ALL_BOMBERS); break; case CommOrderType::REINFORCEMENTS: - item.active = (Player_ship != NULL) && !hud_squadmsg_reinforcements_available(Player_ship->team) && Msg_shortcut_command == -1; + item.active = (Player_ship != nullptr) && !hud_squadmsg_reinforcements_available(Player_ship->team) && Msg_shortcut_command == -1; break; case CommOrderType::REARM_REPAIR: if (Hide_main_rearm_items_in_comms_gauge) { From 66f53bfb600f407a27ddcc113d3743454e64a443 Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Sat, 5 Sep 2026 11:39:40 -0400 Subject: [PATCH 4/7] cleanup 1 --- code/hud/hudsquadmsg.cpp | 86 ++++++++++++++++++++++-------------- code/hud/hudsquadmsg.h | 8 +++- code/mod_table/mod_table.cpp | 2 +- code/network/multi.h | 3 +- code/network/multimsgs.cpp | 22 +++++---- code/sound/voicerec.cpp | 16 +++++-- 6 files changed, 87 insertions(+), 50 deletions(-) diff --git a/code/hud/hudsquadmsg.cpp b/code/hud/hudsquadmsg.cpp index 545c62cd75f..2b4415cf1df 100644 --- a/code/hud/hudsquadmsg.cpp +++ b/code/hud/hudsquadmsg.cpp @@ -157,19 +157,16 @@ const SCP_set target_messages = []() { return setunion; }(); -bool is_smallcraft_flavor(ship_info *sinfop, SmallCraftFlavor flavor) { +static bool is_smallcraft_flavor(const ship_info *sinfop, SmallCraftFlavor flavor) { switch (flavor) { case SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS: return sinfop->is_fighter_bomber(); - break; case SmallCraftFlavor::ALL_FIGHTERS: return sinfop->is_fighter(); - break; case SmallCraftFlavor::ALL_BOMBERS: return sinfop->is_bomber(); - break; } - UNREACHABLE("Invalid SmallCraftFlavor of %i in 'is_smallcraft_flavor()'", flavor); + UNREACHABLE("Invalid SmallCraftFlavor of %i in 'is_smallcraft_flavor()'", static_cast(flavor)); return false; } @@ -178,6 +175,8 @@ void hud_init_comm_orders() { int i; + Comm_order_types.clear(); + if (!Parsed_comm_orders.empty()) { for (i = 0; i < sz2i(Parsed_comm_orders.size()); i++) { Comm_order_types.emplace_back(static_cast(Parsed_comm_orders[i].first), Parsed_comm_orders[i].second); @@ -197,8 +196,6 @@ void hud_init_comm_orders() } } - - for (auto& order : Player_orders) order.localize(); @@ -1024,7 +1021,8 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num, SmallCraftF // check for multiplayer mode if(MULTIPLAYER_CLIENT) { - send_player_order_packet(SQUAD_MSG_ALL, 0, command); + // for SQUAD_MSG_ALL the index field is unused, so it carries the flavor to the server + send_player_order_packet(SQUAD_MSG_ALL, static_cast(flavor), command); return; } @@ -1048,7 +1046,14 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num, SmallCraftF } */ - for ( i = 0; i < Num_wings; i++ ) { + // A wing is ordered as a unit, which means its members are classified by the wing leader alone. + // That is fine when the order goes to fighters and bombers alike, but for the fighters-only and + // bombers-only flavors it would both miss the non-matching leader's matching wingmates and catch + // the matching leader's non-matching ones. So for those flavors, skip the wing pass entirely and + // let the per-ship pass below order every matching craft individually. + const bool order_wings_as_units = (flavor == SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS); + + for ( i = 0; order_wings_as_units && (i < Num_wings); i++ ) { int shipnum; if ( (Wings[i].flags[Ship::Wing_Flags::Gone]) || (Wings[i].current_count == 0) ) @@ -1094,7 +1099,8 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num, SmallCraftF } } - // now find any friendly fighter/bomber ships not in wings + // now find the remaining friendly small craft: those not in wings, plus -- when we skipped the wing + // pass above -- every matching ship regardless of wing membership for (auto so: list_range(&Ship_obj_list)) { auto objp = &Objects[so->objnum]; if (objp->flags[Object::Object_Flags::Should_be_dead]) @@ -1102,12 +1108,25 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num, SmallCraftF if ( objp->type != OBJ_SHIP ) continue; - // don't send messge to ships not on player's team, or that are in a wing. + // don't send message to ships not on player's team shipp = &Ships[objp->instance]; - if ( (shipp->team != ordering_shipp->team) || (shipp->wingnum != -1) ) + if ( shipp->team != ordering_shipp->team ) + continue; + + // never order the ship that is giving the order, nor the instructor + if ( (shipp == ordering_shipp) || is_instructor(objp) ) + continue; + + // ships in a wing were already ordered as part of that wing + if ( order_wings_as_units && (shipp->wingnum != -1) ) + continue; + + // ...and when they weren't, still honor the wing-level checks the wing pass would have made + if ( !order_wings_as_units && (shipp->wingnum != -1) + && (Wings[shipp->wingnum].flags[Ship::Wing_Flags::Gone] || Wings[shipp->wingnum].flags[Ship::Wing_Flags::Departing]) ) continue; - // don't send message to non fighter wings + // don't send message to craft of the wrong type if (!is_smallcraft_flavor(&Ship_info[shipp->ship_info_index], flavor)) { continue; } @@ -1120,14 +1139,17 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num, SmallCraftF if (!shipp->orders_accepted.contains(command)) continue; - if (send_message) { + // the wing pass picks its responder with SHIP_GET_UNSILENCED, which never picks a player ship; + // don't let one respond here either. It still receives the order -- we just keep looking for + // an AI ship to acknowledge it. + if ( send_message && !(objp->flags[Object::Object_Flags::Player_ship]) ) { hud_add_issued_order("All Fighters", command); - if ( hud_squadmsg_send_ship_command(objp->instance, command, send_message, SQUADMSG_HISTORY_UPDATE, player_num) ) { + if ( hud_squadmsg_send_ship_command(objp->instance, command, 1, SQUADMSG_HISTORY_UPDATE, player_num) ) { send_message = 0; } } else { - hud_squadmsg_send_ship_command(objp->instance, command, send_message, SQUADMSG_HISTORY_NO_UPDATE, player_num); + hud_squadmsg_send_ship_command(objp->instance, command, 0, SQUADMSG_HISTORY_NO_UPDATE, player_num); } } @@ -1780,7 +1802,8 @@ void hud_squadmsg_type_select( ) if (i < sz2i(Comm_order_types.size())) { MsgItems.push_back({Comm_order_types[i].first, 1, Comm_order_types[i].second}); // assume active } else { - MsgItems.push_back({0, 1, lua_cat_list[i - sz2i(Comm_order_types.size())]}); // assume active + // tag these so they can't be mistaken for a built-in order type below + MsgItems.push_back({CommOrderType::LUA_GENERAL_CATEGORY, 1, lua_cat_list[i - sz2i(Comm_order_types.size())]}); // assume active } } @@ -1793,7 +1816,7 @@ void hud_squadmsg_type_select( ) goto do_main_menu; } - for ( auto item : MsgItems ) { + for ( auto &item : MsgItems ) { if (((hud_communications_state(Player_ship) != COMM_OK) || ((Game_mode & GM_MULTIPLAYER) && !multi_can_message(Net_player))) && (item.instance != CommOrderType::REARM_REPAIR) @@ -1818,7 +1841,7 @@ void hud_squadmsg_type_select( ) item.active = hud_squadmsg_exist_fighters_bombers(SmallCraftFlavor::ALL_BOMBERS); break; case CommOrderType::REINFORCEMENTS: - item.active = (Player_ship != nullptr) && !hud_squadmsg_reinforcements_available(Player_ship->team) && Msg_shortcut_command == -1; + item.active = (Player_ship != nullptr) && hud_squadmsg_reinforcements_available(Player_ship->team) && Msg_shortcut_command == -1; break; case CommOrderType::REARM_REPAIR: if (Hide_main_rearm_items_in_comms_gauge) { @@ -1874,7 +1897,7 @@ void hud_squadmsg_type_select( ) hud_squadmsg_do_mode( SM_MODE_REPAIR_REARM ); } else if (MsgItems[k].instance == CommOrderType::ABORT_REARM && !Hide_main_rearm_items_in_comms_gauge) { hud_squadmsg_do_mode( SM_MODE_REPAIR_REARM_ABORT ); - } else if (k >= sz2i(Comm_order_types.size())) { + } else if (MsgItems[k].instance == CommOrderType::LUA_GENERAL_CATEGORY) { Lua_sqd_msg_cat = lua_cat_list[k - sz2i(Comm_order_types.size())]; hud_squadmsg_do_mode( SM_MODE_GENERAL ); } @@ -2916,7 +2939,9 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config) } } } else { - nitems = 6; + // hud_squadmsg_type_select() does not run in config mode, so MsgItems is empty here and the + // preview is built straight from the order types instead + nitems = MIN(sz2i(Comm_order_types.size()), MAX_MENU_DISPLAY); } int sx = x + fl2i(Item_start_offsets[0] * scale); @@ -2937,21 +2962,16 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config) int item_num; bool isSelectedItem = (i == Selected_menu_item); char text[256]; - mmode_item item = MsgItems[First_menu_item + i]; + // in config mode we are previewing the first page of the Comms Menu, and MsgItems is empty + int item_instance; if (!config) { + const mmode_item &item = MsgItems[First_menu_item + i]; + item_instance = item.instance; strcpy_s(text, item.text.c_str()); } else { - // in config mode, so create just the first page of the Comms Menu - // as other functions, such as hud_squadmsg_type_select() will not be run in config mode - const char* temp_comm_order_types[] = {XSTR("Ships", 293), - XSTR("Wings", 294), - XSTR("All Fighters", 295), - XSTR("Reinforcements", 296), - XSTR("Rearm/Repair Subsys", 297), - XSTR("Abort Rearm", 298) - }; - strcpy_s(text, temp_comm_order_types[i]); + item_instance = Comm_order_types[i].first; + strcpy_s(text, Comm_order_types[i].second.c_str()); } // blit the background @@ -2973,7 +2993,7 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config) } bool item_visible = config - ? (!Hide_main_rearm_items_in_comms_gauge) || ((item.instance != CommOrderType::REARM_REPAIR) && (item.instance != CommOrderType::ABORT_REARM)) + ? (!Hide_main_rearm_items_in_comms_gauge) || ((item_instance != CommOrderType::REARM_REPAIR) && (item_instance != CommOrderType::ABORT_REARM)) : (MsgItems[First_menu_item + i].active >= 0); if (item_visible) { // first print an icon to indicate selected item diff --git a/code/hud/hudsquadmsg.h b/code/hud/hudsquadmsg.h index da506bc1e67..a2d27f04048 100644 --- a/code/hud/hudsquadmsg.h +++ b/code/hud/hudsquadmsg.h @@ -70,13 +70,13 @@ struct reinforcements; #define NUM_DEFAULT_COMM_ORDER_TYPES 6 -enum SmallCraftFlavor { +enum SmallCraftFlavor : int { ALL_FIGHTERS_AND_BOMBERS, ALL_FIGHTERS, ALL_BOMBERS, }; -enum CommOrderType { +enum CommOrderType : int { MSG_SHIPS, MSG_WINGS, MSG_ALL_FIGHTERS_AND_BOMBERS, @@ -86,6 +86,10 @@ enum CommOrderType { REARM_REPAIR, ABORT_REARM, MAX_COMM_ORDER_TYPES, + + // not selectable from the table; tags menu items that came from a Lua general-order category. + // deliberately after MAX_COMM_ORDER_TYPES so that it stays outside the range of real order types + LUA_GENERAL_CATEGORY, }; typedef struct mmode_item { diff --git a/code/mod_table/mod_table.cpp b/code/mod_table/mod_table.cpp index 309e86bf1c5..9d7071c49ec 100644 --- a/code/mod_table/mod_table.cpp +++ b/code/mod_table/mod_table.cpp @@ -530,7 +530,7 @@ void parse_mod_table(const char *filename) } else if (!stricmp(order_type.c_str(), "ABORT REARM")) { Parsed_comm_orders.emplace_back(std::pair(CommOrderType::ABORT_REARM, text)); } else { - Error(LOCATION, "Game Settings Table: Invalid squad order type %s found!", order_type.c_str()); + error_display(0, "Game Settings Table: Invalid squad order type %s found! Skipping this entry.", order_type.c_str()); } } } diff --git a/code/network/multi.h b/code/network/multi.h index 80e8608b4f5..bd752f89c1c 100644 --- a/code/network/multi.h +++ b/code/network/multi.h @@ -77,9 +77,10 @@ class player; // Version 61 - 4/17/2023 - Added compatibility for whackable asteroids (added force) // Version 62 - 5/26/2025 - Added some modular curve input data to turret firing packets; 5/31/2025 - Added another input // Version 63 - 8/4/2026 - Added target forward speed to turret and flak fired packets +// Version 64 - 9/5/2026 - Player order packet always carries its index field, which holds the small craft flavor for "message all" // STANDALONE_ONLY -#define MULTI_FS_SERVER_VERSION 63 +#define MULTI_FS_SERVER_VERSION 64 #define MULTI_FS_SERVER_COMPATIBLE_VERSION MULTI_FS_SERVER_VERSION diff --git a/code/network/multimsgs.cpp b/code/network/multimsgs.cpp index f482bb121e3..4b4c0cdae37 100644 --- a/code/network/multimsgs.cpp +++ b/code/network/multimsgs.cpp @@ -4422,10 +4422,9 @@ void send_player_order_packet(int type, int index, int cmd) val = (ubyte)type; ADD_DATA(val); // ship order or wing order, or message all fighters - // if we are not messaging all ships or wings, add the index, which is the shipnum or wingnum - if ( val != SQUAD_MSG_ALL ){ - ADD_INT(index); // net signature of target ship - } + // for ship and wing orders the index is the shipnum or wingnum; for SQUAD_MSG_ALL it is the + // SmallCraftFlavor, so that all-fighters and all-bombers don't degrade to fighters-and-bombers + ADD_INT(index); ADD_INT(cmd); // the command itself @@ -4469,10 +4468,7 @@ void process_player_order_packet(ubyte *data, header *hinfo) offset = HEADER_LENGTH; GET_DATA( type ); - if ( type != SQUAD_MSG_ALL ){ - GET_INT( index ); - } - + GET_INT( index ); // shipnum or wingnum, or the SmallCraftFlavor for SQUAD_MSG_ALL GET_INT( command ); GET_USHORT( target_net_signature ); GET_SHORT( t_subsys ); @@ -4550,7 +4546,15 @@ void process_player_order_packet(ubyte *data, header *hinfo) } else if ( type == SQUAD_MSG_WING ) { hud_squadmsg_send_wing_command(index, command, 1, SQUADMSG_HISTORY_ADD_ENTRY, player_num); } else if ( type == SQUAD_MSG_ALL ) { - hud_squadmsg_send_to_all_fighters( command, player_num ); + // here the index is the SmallCraftFlavor; validate it, since it came off the wire + auto flavor = SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS; + if ( (index >= SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS) && (index <= SmallCraftFlavor::ALL_BOMBERS) ) { + flavor = static_cast(index); + } else { + mprintf(("Received player order packet with invalid small craft flavor %d; assuming all fighters and bombers\n", index)); + } + + hud_squadmsg_send_to_all_fighters( command, player_num, flavor ); } Assert(tobjnum_save != Ships[aip->shipnum].objnum); // make sure not targeting self diff --git a/code/sound/voicerec.cpp b/code/sound/voicerec.cpp index 6efdb9c049d..6b4c62e0d48 100644 --- a/code/sound/voicerec.cpp +++ b/code/sound/voicerec.cpp @@ -77,7 +77,7 @@ CComPtr cpAudio; // Pointer for Audio Input Device const bool DEBUG_ON = false; extern int button_function(int n); -extern void hud_squadmsg_msg_all_fighters(); +extern void hud_squadmsg_msg_all_fighters(SmallCraftFlavor flavor); extern void hud_squadmsg_shortcut( int command ); extern bool hud_squadmsg_ship_valid(ship *shipp, object *objp = nullptr); extern bool hud_squadmsg_wing_valid(wing *wingp); @@ -125,9 +125,17 @@ void doVid_Action(int action) } - if(Msg_instance == MESSAGE_ALL_FIGHTERS || Squad_msg_mode == SM_MODE_ALL_FIGHTERS ) + if(Msg_instance == MESSAGE_ALL_FIGHTERS_BOMBERS || Squad_msg_mode == SM_MODE_ALL_FIGHTERS_BOMBERS ) { - hud_squadmsg_send_to_all_fighters(Msg_shortcut_command); + hud_squadmsg_send_to_all_fighters(Msg_shortcut_command, -1, SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS); + } + else if(Msg_instance == MESSAGE_ALL_FIGHTERS || Squad_msg_mode == SM_MODE_ALL_FIGHTERS ) + { + hud_squadmsg_send_to_all_fighters(Msg_shortcut_command, -1, SmallCraftFlavor::ALL_FIGHTERS); + } + else if(Msg_instance == MESSAGE_ALL_BOMBERS || Squad_msg_mode == SM_MODE_ALL_BOMBERS ) + { + hud_squadmsg_send_to_all_fighters(Msg_shortcut_command, -1, SmallCraftFlavor::ALL_BOMBERS); } else if(Squad_msg_mode == SM_MODE_SHIP_COMMAND) { @@ -497,7 +505,7 @@ void VOICEREC_execute_command(ISpPhrase *pPhrase) case VID_AllFighters: case VID_AllWings: - hud_squadmsg_msg_all_fighters(); + hud_squadmsg_msg_all_fighters(SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS); // can have the action to perform spoken directly afterwards if (pElements->pProperties->pFirstChild) { doVid_Action(pElements->pProperties->pFirstChild->vValue.ulVal); From 16add7e40b3f53c77df20e8750a2ca7ae3cf01d9 Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Sun, 6 Sep 2026 23:18:01 -0400 Subject: [PATCH 5/7] sexp cleanup --- code/hud/hudsquadmsg.cpp | 79 ++++++++++++++++++++-------- code/hud/hudsquadmsg.h | 7 +++ code/missioneditor/sexp_tree_opf.cpp | 6 ++- code/mod_table/mod_table.cpp | 3 ++ code/parse/sexp.cpp | 12 +++-- 5 files changed, 81 insertions(+), 26 deletions(-) diff --git a/code/hud/hudsquadmsg.cpp b/code/hud/hudsquadmsg.cpp index 2b4415cf1df..cdad8775dec 100644 --- a/code/hud/hudsquadmsg.cpp +++ b/code/hud/hudsquadmsg.cpp @@ -157,6 +157,13 @@ const SCP_set target_messages = []() { return setunion; }(); +// order_to values in the squad message history for orders that went to every small craft of a given +// flavor rather than to one ship or wing. Real recipients are stored as get_parse_name_index(), which +// is always >= 0, so these can never collide with one. +#define ORDER_TO_ALL_FIGHTERS_BOMBERS -1 +#define ORDER_TO_ALL_FIGHTERS -2 +#define ORDER_TO_ALL_BOMBERS -3 + static bool is_smallcraft_flavor(const ship_info *sinfop, SmallCraftFlavor flavor) { switch (flavor) { case SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS: @@ -216,6 +223,7 @@ SCP_vector Squadmsg_history; // forward declarations void hud_add_issued_order(const char *name, int order); +void hud_add_issued_order(SmallCraftFlavor flavor, int order); void hud_update_last_order(const char *target, int order_source, int special_index); bool hud_squadmsg_is_target_order_valid(size_t order, ai_info *aip = nullptr, bool isWing = false); bool hud_squadmsg_ship_valid(ship *shipp, object *objp = nullptr); @@ -1088,7 +1096,7 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num, SmallCraftF // send the command to the wing if ( Wings[i].current_count > 0 ) { if (send_message) { - hud_add_issued_order("All Fighters", command); + hud_add_issued_order(flavor, command); if ( hud_squadmsg_send_wing_command(i, command, send_message, SQUADMSG_HISTORY_UPDATE, player_num) ) { send_message = 0; } @@ -1143,7 +1151,7 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num, SmallCraftF // don't let one respond here either. It still receives the order -- we just keep looking for // an AI ship to acknowledge it. if ( send_message && !(objp->flags[Object::Object_Flags::Player_ship]) ) { - hud_add_issued_order("All Fighters", command); + hud_add_issued_order(flavor, command); if ( hud_squadmsg_send_ship_command(objp->instance, command, 1, SQUADMSG_HISTORY_UPDATE, player_num) ) { send_message = 0; } @@ -1858,9 +1866,10 @@ void hud_squadmsg_type_select( ) if (Hide_main_rearm_items_in_comms_gauge) { item.active = -1; } else { + // note that being repaired or awaiting repair enables the abort regardless of whether + // support is still allowed; otherwise revoking support mid-repair would strand the player item.active = (Ai_info[Ships[Player_obj->instance].ai_index].ai_flags.any_of(AI::AI_Flags::Being_repaired,AI::AI_Flags::Awaiting_repair) || mission_is_repair_scheduled(Player_obj)) - && is_support_allowed(Player_obj) && Msg_shortcut_command == -1; } break; @@ -2196,11 +2205,16 @@ void hud_squadmsg_ship_command() if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; - // don't send messge to ships not on player's team, or that are in a wing. + // don't count ships not on player's team shipp = &Ships[Objects[so->objnum].instance]; if (shipp->team != Player_ship->team) continue; + // nor the ship giving the order or the instructor, since hud_squadmsg_send_to_all_fighters() + // will not order them either + if ((shipp == Player_ship) || is_instructor(&Objects[so->objnum])) + continue; + // don't send message to non fighter or bomber wings bool is_valid = true; switch (Msg_instance) { @@ -2629,22 +2643,39 @@ int hud_squadmsg_do_frame( ) return 0; } +// helper for the two functions below: pushes a history entry with an already-resolved order_to +static void hud_add_issued_order_to(int order_to, int order) +{ + squadmsg_history latest_order; + + latest_order.order_to = order_to; + latest_order.order = order; + latest_order.order_time = Missiontime; + + //stick it in history + Squadmsg_history.push_back(latest_order); +} + void hud_add_issued_order(const char *name, int order) -{ - squadmsg_history *latest_order = new squadmsg_history(); +{ + hud_add_issued_order_to(get_parse_name_index(name), order); +} - if (!strcmp(name, "All Fighters")) { - latest_order->order_to = -1; - } - else { - latest_order->order_to = get_parse_name_index(name); +// for the "message all " orders, which have no single named recipient +void hud_add_issued_order(SmallCraftFlavor flavor, int order) +{ + switch (flavor) { + case SmallCraftFlavor::ALL_FIGHTERS_AND_BOMBERS: + hud_add_issued_order_to(ORDER_TO_ALL_FIGHTERS_BOMBERS, order); + return; + case SmallCraftFlavor::ALL_FIGHTERS: + hud_add_issued_order_to(ORDER_TO_ALL_FIGHTERS, order); + return; + case SmallCraftFlavor::ALL_BOMBERS: + hud_add_issued_order_to(ORDER_TO_ALL_BOMBERS, order); + return; } - latest_order->order = order; - latest_order->order_time = Missiontime; - - //stick it in history - Squadmsg_history.push_back(*latest_order); - delete latest_order; + UNREACHABLE("Invalid SmallCraftFlavor of %i in 'hud_add_issued_order()'", static_cast(flavor)); } void hud_update_last_order(const char *target, int order_source, int special_index) @@ -2665,10 +2696,16 @@ void hud_update_last_order(const char *target, int order_source, int special_ind int hud_query_order_issued(const char *to, const char *order_name, const char *target_name, int timestamp, const char *from, const char *special_argument) { - int i, order = -1, ship_or_wing = -1, target = -1, source = -1; - - // if the desired order was not sent to all fighters - if (strcmp(to, "") != 0) { + int i, order = -1, ship_or_wing = ORDER_TO_ALL_FIGHTERS_BOMBERS, target = -1, source = -1; + + // the "message all " orders have no named recipient, so they are matched by sentinel instead + if (!strcmp(to, SEXP_ORDER_TO_ALL_FIGHTERS_BOMBERS)) { + ship_or_wing = ORDER_TO_ALL_FIGHTERS_BOMBERS; + } else if (!strcmp(to, SEXP_ORDER_TO_ALL_FIGHTERS)) { + ship_or_wing = ORDER_TO_ALL_FIGHTERS; + } else if (!strcmp(to, SEXP_ORDER_TO_ALL_BOMBERS)) { + ship_or_wing = ORDER_TO_ALL_BOMBERS; + } else { ship_or_wing = get_parse_name_index(to); } diff --git a/code/hud/hudsquadmsg.h b/code/hud/hudsquadmsg.h index a2d27f04048..823a7b89c40 100644 --- a/code/hud/hudsquadmsg.h +++ b/code/hud/hudsquadmsg.h @@ -33,6 +33,13 @@ #define MESSAGE_ALL_BOMBERS -998 #define MESSAGE_ALL_FIGHTERS_BOMBERS -999 +// recipient names accepted by the order and query-orders SEXPs for orders that were sent to every small +// craft rather than to a specific ship or wing. "" is retail and covers fighters and bombers +// alike; the other two match the fighters-only and bombers-only comm menu items. +#define SEXP_ORDER_TO_ALL_FIGHTERS_BOMBERS "" +#define SEXP_ORDER_TO_ALL_FIGHTERS "" +#define SEXP_ORDER_TO_ALL_BOMBERS "" + class object; struct reinforcements; diff --git a/code/missioneditor/sexp_tree_opf.cpp b/code/missioneditor/sexp_tree_opf.cpp index dc29f09fa04..501c288959a 100644 --- a/code/missioneditor/sexp_tree_opf.cpp +++ b/code/missioneditor/sexp_tree_opf.cpp @@ -802,7 +802,9 @@ sexp_list_item *SexpTreeOPF::get_listing_opf_order_recipient() const { sexp_list_item head; - head.add_data(""); + head.add_data(SEXP_ORDER_TO_ALL_FIGHTERS_BOMBERS); + head.add_data(SEXP_ORDER_TO_ALL_FIGHTERS); + head.add_data(SEXP_ORDER_TO_ALL_BOMBERS); head.add_list(get_listing_opf_ship()); head.add_list(get_listing_opf_wing()); @@ -3108,7 +3110,7 @@ int SexpTreeOPF::get_default_value(sexp_list_item* item, int op, int i) const break; case OPF_ORDER_RECIPIENT: - str = ""; + str = SEXP_ORDER_TO_ALL_FIGHTERS_BOMBERS; break; case OPF_SHIP_OR_NONE: diff --git a/code/mod_table/mod_table.cpp b/code/mod_table/mod_table.cpp index 9d7071c49ec..3f6e94fc24d 100644 --- a/code/mod_table/mod_table.cpp +++ b/code/mod_table/mod_table.cpp @@ -507,6 +507,9 @@ void parse_mod_table(const char *filename) } if (optional_string("$Available squad orders:")) { + // this list replaces any list from a previously parsed table, rather than adding to it + Parsed_comm_orders.clear(); + SCP_string order_type; SCP_string text; while (optional_string("+Order Type:")) { diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index c1c13ae05e4..b98d4491baf 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -2545,7 +2545,9 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad } if (desired_argument_type == OPF_ORDER_RECIPIENT) { - if (!strcmp ("", CTEXT(node))) { + if (!strcmp (SEXP_ORDER_TO_ALL_FIGHTERS_BOMBERS, CTEXT(node)) + || !strcmp (SEXP_ORDER_TO_ALL_FIGHTERS, CTEXT(node)) + || !strcmp (SEXP_ORDER_TO_ALL_BOMBERS, CTEXT(node))) { break; } } @@ -40068,14 +40070,18 @@ SCP_vector Sexp_help = { "\tDeprecated - Use Query-Orders in any new mission.\r\n\r\n" "\tBecomes true when the player had given the specified ship or wing the specified order.\r\n\r\n" "Returns a boolean value. Takes 2 or 3 arguments...\r\n" - "\t1:\tName of ship or wing to check if given order to.\r\n" + "\t1:\tName of ship or wing to check if given order to. This can also be for an\r\n" + "\t\torder sent to all fighters and bombers, for an order sent to all\r\n" + "\t\tfighters, or for an order sent to all bombers.\r\n" "\t2:\tName of order to check if player has given.\r\n" "\t3:\tName of the target of the order (optional)." }, { OP_QUERY_ORDERS, "Query-Orders (Boolean training operator)\r\n" "\tBecomes true when the player had given the specified ship or wing the specified order.\r\n\r\n" "Returns a boolean value. Takes 2 or more arguments...\r\n" - "\t1:\tName of ship or wing to check if given order to.\r\n" + "\t1:\tName of ship or wing to check if given order to. This can also be for an\r\n" + "\t\torder sent to all fighters and bombers, for an order sent to all\r\n" + "\t\tfighters, or for an order sent to all bombers.\r\n" "\t2:\tName of order to check if player has given.\r\n" "\t3:\tMaximum length of time since order was given. Use 0 for any time in the mission.\r\n" "\t4:\tName of the target of the order (optional).\r\n" From ec72c02ac64f2dd35ad37d9e6a906f6f1ce7deb6 Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Mon, 7 Sep 2026 10:53:32 -0400 Subject: [PATCH 6/7] cleanup --- code/hud/hudsquadmsg.cpp | 27 +++++++++------------------ code/hud/hudsquadmsg.h | 7 +++++-- code/missioneditor/sexp_tree_opf.cpp | 2 +- code/mod_table/mod_table.cpp | 17 ++++++++--------- code/mod_table/mod_table.h | 1 - code/parse/sexp.cpp | 6 ++++-- 6 files changed, 27 insertions(+), 33 deletions(-) diff --git a/code/hud/hudsquadmsg.cpp b/code/hud/hudsquadmsg.cpp index cdad8775dec..2c335b57b13 100644 --- a/code/hud/hudsquadmsg.cpp +++ b/code/hud/hudsquadmsg.cpp @@ -124,6 +124,7 @@ int keys_used[] = { KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_ #define ID2 2 SCP_vector> Comm_order_types; +SCP_vector> Parsed_comm_orders; int player_order::orderingCounter = 0; @@ -180,27 +181,17 @@ static bool is_smallcraft_flavor(const ship_info *sinfop, SmallCraftFlavor flavo void hud_init_comm_orders() { - int i; - - Comm_order_types.clear(); - if (!Parsed_comm_orders.empty()) { - for (i = 0; i < sz2i(Parsed_comm_orders.size()); i++) { - Comm_order_types.emplace_back(static_cast(Parsed_comm_orders[i].first), Parsed_comm_orders[i].second); - } + Comm_order_types = Parsed_comm_orders; } else { - std::array, NUM_DEFAULT_COMM_ORDER_TYPES> Default_comm_order_types = - { - std::pair(CommOrderType::MSG_SHIPS, XSTR("Ships", 293)), - std::pair(CommOrderType::MSG_WINGS, XSTR("Wings", 294)), - std::pair(CommOrderType::MSG_ALL_FIGHTERS_AND_BOMBERS, XSTR("All Fighters", 295)), - std::pair(CommOrderType::REINFORCEMENTS, XSTR("Reinforcements", 296)), - std::pair(CommOrderType::REARM_REPAIR, XSTR("Rearm/Repair Subsys", 297)), - std::pair(CommOrderType::ABORT_REARM, XSTR("Abort Rearm", 298)), + Comm_order_types = { + { CommOrderType::MSG_SHIPS, XSTR("Ships", 293) }, + { CommOrderType::MSG_WINGS, XSTR("Wings", 294) }, + { CommOrderType::MSG_ALL_FIGHTERS_AND_BOMBERS, XSTR("All Fighters", 295) }, + { CommOrderType::REINFORCEMENTS, XSTR("Reinforcements", 296) }, + { CommOrderType::REARM_REPAIR, XSTR("Rearm/Repair Subsys", 297) }, + { CommOrderType::ABORT_REARM, XSTR("Abort Rearm", 298) }, }; - for (i = 0; i < sz2i(Default_comm_order_types.size()); i++) { - Comm_order_types.emplace_back(Default_comm_order_types[i]); - } } for (auto& order : Player_orders) diff --git a/code/hud/hudsquadmsg.h b/code/hud/hudsquadmsg.h index 823a7b89c40..3978dbfa58e 100644 --- a/code/hud/hudsquadmsg.h +++ b/code/hud/hudsquadmsg.h @@ -75,8 +75,6 @@ struct reinforcements; #define MAX_MENU_DISPLAY 10 // max number that can be displayed -#define NUM_DEFAULT_COMM_ORDER_TYPES 6 - enum SmallCraftFlavor : int { ALL_FIGHTERS_AND_BOMBERS, ALL_FIGHTERS, @@ -99,6 +97,11 @@ enum CommOrderType : int { LUA_GENERAL_CATEGORY, }; +// the comm menu order types parsed from "$Available squad orders:" in game_settings.tbl, with the +// text to display for each. empty if the mod did not specify a list, in which case the retail +// defaults are used instead; see hud_init_comm_orders(). +extern SCP_vector> Parsed_comm_orders; + typedef struct mmode_item { int instance; // instance in Ships/Wings array of this menu item int active; // active items are in bold text (1) -- inactive items greyed out (0) -- hidden objects not rendered (-1) diff --git a/code/missioneditor/sexp_tree_opf.cpp b/code/missioneditor/sexp_tree_opf.cpp index 501c288959a..5e44c50095d 100644 --- a/code/missioneditor/sexp_tree_opf.cpp +++ b/code/missioneditor/sexp_tree_opf.cpp @@ -2625,6 +2625,7 @@ int SexpTreeOPF::query_default_argument_available(int op, int i) const case OPF_SHIP_WING_POINT: case OPF_SHIP_WING_WHOLETEAM: case OPF_SHIP_WING_SHIPONTEAM_POINT: + case OPF_ORDER_RECIPIENT: // a recipient is a ship, a wing, or one of the "" tokens ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { if (ptr->type == OBJ_SHIP || ptr->type == OBJ_START) @@ -2657,7 +2658,6 @@ int SexpTreeOPF::query_default_argument_available(int op, int i) const return 0; case OPF_SHIP_NOT_PLAYER: - case OPF_ORDER_RECIPIENT: ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { if (ptr->type == OBJ_SHIP) diff --git a/code/mod_table/mod_table.cpp b/code/mod_table/mod_table.cpp index 3f6e94fc24d..a0a816029b5 100644 --- a/code/mod_table/mod_table.cpp +++ b/code/mod_table/mod_table.cpp @@ -191,7 +191,6 @@ float Min_radius_for_persistent_debris; bool Zero_radius_explosions_skip_fireballs; bool Render_insignias_as_decals; bool Link_special_point_subsystems_to_destroyed_submodels; -SCP_vector> Parsed_comm_orders; #ifdef WITH_DISCORD @@ -517,21 +516,21 @@ void parse_mod_table(const char *filename) required_string("+Text:"); stuff_string(text, F_NAME); if (!stricmp(order_type.c_str(), "MESSAGE SHIPS")) { - Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_SHIPS, text)); + Parsed_comm_orders.emplace_back(CommOrderType::MSG_SHIPS, text); } else if (!stricmp(order_type.c_str(), "MESSAGE WINGS")) { - Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_WINGS, text)); + Parsed_comm_orders.emplace_back(CommOrderType::MSG_WINGS, text); } else if (!stricmp(order_type.c_str(), "MESSAGE ALL FIGHTERS AND BOMBERS")) { - Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_ALL_FIGHTERS_AND_BOMBERS, text)); + Parsed_comm_orders.emplace_back(CommOrderType::MSG_ALL_FIGHTERS_AND_BOMBERS, text); } else if (!stricmp(order_type.c_str(), "MESSAGE ALL FIGHTERS")) { - Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_ALL_FIGHTERS, text)); + Parsed_comm_orders.emplace_back(CommOrderType::MSG_ALL_FIGHTERS, text); } else if (!stricmp(order_type.c_str(), "MESSAGE ALL BOMBERS")) { - Parsed_comm_orders.emplace_back(std::pair(CommOrderType::MSG_ALL_BOMBERS, text)); + Parsed_comm_orders.emplace_back(CommOrderType::MSG_ALL_BOMBERS, text); } else if (!stricmp(order_type.c_str(), "REINFORCEMENTS")) { - Parsed_comm_orders.emplace_back(std::pair(CommOrderType::REINFORCEMENTS, text)); + Parsed_comm_orders.emplace_back(CommOrderType::REINFORCEMENTS, text); } else if (!stricmp(order_type.c_str(), "REARM/REPAIR SUBSYSTEMS")) { - Parsed_comm_orders.emplace_back(std::pair(CommOrderType::REARM_REPAIR, text)); + Parsed_comm_orders.emplace_back(CommOrderType::REARM_REPAIR, text); } else if (!stricmp(order_type.c_str(), "ABORT REARM")) { - Parsed_comm_orders.emplace_back(std::pair(CommOrderType::ABORT_REARM, text)); + Parsed_comm_orders.emplace_back(CommOrderType::ABORT_REARM, text); } else { error_display(0, "Game Settings Table: Invalid squad order type %s found! Skipping this entry.", order_type.c_str()); } diff --git a/code/mod_table/mod_table.h b/code/mod_table/mod_table.h index 5f9bc7826fa..ea1a9be939a 100644 --- a/code/mod_table/mod_table.h +++ b/code/mod_table/mod_table.h @@ -210,7 +210,6 @@ extern float Min_radius_for_persistent_debris; extern bool Zero_radius_explosions_skip_fireballs; extern bool Render_insignias_as_decals; extern bool Link_special_point_subsystems_to_destroyed_submodels; -extern SCP_vector> Parsed_comm_orders; void mod_table_init(); void mod_table_post_process(); diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index b98d4491baf..c93e022ed75 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -32518,10 +32518,12 @@ int query_operator_argument_type(int op_index, int argnum) } case OP_ORDER: - if (argnum == 1) + if (argnum == 0) + return OPF_ORDER_RECIPIENT; + else if (argnum == 1) return OPF_AI_ORDER; else - return OPF_SHIP_WING; // arg 0 or 2 + return OPF_SHIP_WING; // arg 2 case OP_QUERY_ORDERS: if (argnum == 0) From fc1e88c0ac4fdbcb5317c49eac5d240a3040ef3e Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Sun, 13 Sep 2026 09:08:47 -0400 Subject: [PATCH 7/7] allow equal wing and ship priorities --- code/ai/ai_flags.h | 1 + code/ai/ai_profiles.cpp | 2 ++ code/ai/aigoals.cpp | 11 ++++++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/code/ai/ai_flags.h b/code/ai/ai_flags.h index 977dbffde7e..59039f977dc 100644 --- a/code/ai/ai_flags.h +++ b/code/ai/ai_flags.h @@ -200,6 +200,7 @@ namespace AI { // b) completion no longer requires moving 0.1m in a single frame (framerate-dependent) Fix_shockwave_expire_before_do_damage, // shockwaves whose lifetime is shorter than one frame apply their area damage at least once before expiring Fix_small_ai_recover_after_engines_repaired, // ensure small ship AI can switch back to useful AI modes if engines get repaired + Player_wing_orders_same_priority_as_ship_orders, // player orders to a wing get the same goal priority as orders to an individual ship, rather than a lower one NUM_VALUES }; diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index 30c9b5de33c..cb950366ca5 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -767,6 +767,8 @@ void parse_ai_profiles_tbl(const char *filename) set_flag(profile, "$fix fighter/bomber AI recovers after engines repaired:", AI::Profile_Flags::Fix_small_ai_recover_after_engines_repaired); + set_flag(profile, "$player wing orders have same priority as ship orders:", AI::Profile_Flags::Player_wing_orders_same_priority_as_ship_orders); + // end of options ---------------------------------------- // if we've been through once already and are at the same place, force a move diff --git a/code/ai/aigoals.cpp b/code/ai/aigoals.cpp index 99d1aa6d4cd..088478214c3 100644 --- a/code/ai/aigoals.cpp +++ b/code/ai/aigoals.cpp @@ -441,6 +441,11 @@ void ai_mission_wing_goal_complete( int wingnum, ai_goal *remove_goalp ) submode = remove_goalp->ai_submode; priority = remove_goalp->priority; name = remove_goalp->target_name; + auto type = remove_goalp->type; + + // if player wing orders share the priority of ship orders, priority alone can no longer tell a wing's copy + // of a goal apart from an identical order given to one of its ships, so compare the goal type as well + bool match_type = The_mission.ai_profile->flags[AI::Profile_Flags::Player_wing_orders_same_priority_as_ship_orders]; Assert ( name ); // should not be NULL!!!! @@ -459,7 +464,7 @@ void ai_mission_wing_goal_complete( int wingnum, ai_goal *remove_goalp ) if ( (aigp->ai_mode == AI_GOAL_NONE) || !aigp->target_name ) continue; - if ( (aigp->ai_mode == mode) && (aigp->ai_submode == submode) && (aigp->priority == priority) && !stricmp(name, aigp->target_name) ) { + if ( (aigp->ai_mode == mode) && (aigp->ai_submode == submode) && (aigp->priority == priority) && (!match_type || aigp->type == type) && !stricmp(name, aigp->target_name) ) { ai_remove_ship_goal( aip, j ); ai_do_default_behavior( &Objects[Ships[aip->shipnum].objnum] ); // do the default behavior break; // we are all done @@ -473,7 +478,7 @@ void ai_mission_wing_goal_complete( int wingnum, ai_goal *remove_goalp ) if ( (aigp->ai_mode == AI_GOAL_NONE) || !aigp->target_name ) continue; - if ( (aigp->ai_mode == mode) && (aigp->ai_submode == submode) && (aigp->priority == priority) && !stricmp(name, aigp->target_name) ) { + if ( (aigp->ai_mode == mode) && (aigp->ai_submode == submode) && (aigp->priority == priority) && (!match_type || aigp->type == type) && !stricmp(name, aigp->target_name) ) { ai_goal_reset(aigp); break; } @@ -813,7 +818,7 @@ void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, c if ( (mode == AI_GOAL_STAY_NEAR_SHIP) || (mode == AI_GOAL_KEEP_SAFE_DISTANCE) ) aigp->priority = PLAYER_PRIORITY_SUPPORT_LOW; - else if ( aigp->type == ai_goal_type::PLAYER_WING ) // NOLINT(readability-braces-around-statements) + else if ( aigp->type == ai_goal_type::PLAYER_WING && !The_mission.ai_profile->flags[AI::Profile_Flags::Player_wing_orders_same_priority_as_ship_orders] ) // NOLINT(readability-braces-around-statements) aigp->priority = PLAYER_PRIORITY_WING; // player wing goals not as high as ship goals else aigp->priority = PLAYER_PRIORITY_SHIP;