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; diff --git a/code/hud/hudsquadmsg.cpp b/code/hud/hudsquadmsg.cpp index 57a33fa5216..2c335b57b13 100644 --- a/code/hud/hudsquadmsg.cpp +++ b/code/hud/hudsquadmsg.cpp @@ -123,8 +123,8 @@ 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; +SCP_vector> Parsed_comm_orders; int player_order::orderingCounter = 0; @@ -158,24 +158,40 @@ 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: + return sinfop->is_fighter_bomber(); + case SmallCraftFlavor::ALL_FIGHTERS: + return sinfop->is_fighter(); + case SmallCraftFlavor::ALL_BOMBERS: + return sinfop->is_bomber(); + } + UNREACHABLE("Invalid SmallCraftFlavor of %i in 'is_smallcraft_flavor()'", static_cast(flavor)); + return false; +} + 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()) { + Comm_order_types = Parsed_comm_orders; + } else { + 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 (auto& order : Player_orders) @@ -198,6 +214,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); @@ -269,7 +286,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 +299,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 +1004,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; @@ -1002,7 +1020,8 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num ) // 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; } @@ -1019,14 +1038,21 @@ 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; } } */ - 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) ) @@ -1049,8 +1075,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 @@ -1060,7 +1087,7 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num ) // 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; } @@ -1071,7 +1098,8 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num ) } } - // 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]) @@ -1079,14 +1107,28 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num ) 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 - if ( !(Ship_info[shipp->ship_info_index].is_fighter_bomber()) ) + // don't send message to craft of the wrong type + if (!is_smallcraft_flavor(&Ship_info[shipp->ship_info_index], flavor)) { continue; + } // skip departing/dying ships if ( shipp->is_dying_or_departing() ) @@ -1096,14 +1138,17 @@ void hud_squadmsg_send_to_all_fighters( int command, int player_num ) if (!shipp->orders_accepted.contains(command)) continue; - if (send_message) { - hud_add_issued_order("All Fighters", command); - if ( hud_squadmsg_send_ship_command(objp->instance, command, send_message, SQUADMSG_HISTORY_UPDATE, player_num) ) { + // 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(flavor, command); + 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); } } @@ -1112,7 +1157,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 +1782,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 +1798,11 @@ 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 + // 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 } } @@ -1769,102 +1815,90 @@ 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 != 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) { + 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 { + // 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)) + && 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 (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 ); } } @@ -1927,13 +1961,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 +2156,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 +2175,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 +2185,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 @@ -2152,15 +2196,33 @@ 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; - // don't send message to non fighter wings - if (!(Ship_info[shipp->ship_info_index].is_fighter_bomber())) + // 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) { + 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 partial_accept |= local_accepted; // 'or'ing will tell us if at least one accepts @@ -2188,9 +2250,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 +2602,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: @@ -2560,22 +2634,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) @@ -2596,10 +2687,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); } @@ -2870,7 +2967,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); @@ -2892,19 +2991,15 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config) bool isSelectedItem = (i == Selected_menu_item); char text[256]; + // in config mode we are previewing the first page of the Comms Menu, and MsgItems is empty + int item_instance; if (!config) { - strcpy_s(text, MsgItems[First_menu_item + i].text.c_str()); + 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 @@ -2926,7 +3021,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..3978dbfa58e 100644 --- a/code/hud/hudsquadmsg.h +++ b/code/hud/hudsquadmsg.h @@ -23,11 +23,22 @@ #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 + +// 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; @@ -64,16 +75,32 @@ 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 +enum SmallCraftFlavor : int { + ALL_FIGHTERS_AND_BOMBERS, + ALL_FIGHTERS, + ALL_BOMBERS, +}; -#define NUM_COMM_ORDER_TYPES 6 +enum CommOrderType : int { + MSG_SHIPS, + MSG_WINGS, + MSG_ALL_FIGHTERS_AND_BOMBERS, + MSG_ALL_FIGHTERS, + MSG_ALL_BOMBERS, + REINFORCEMENTS, + 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, +}; -#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 +// 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 @@ -179,7 +206,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/missioneditor/sexp_tree_opf.cpp b/code/missioneditor/sexp_tree_opf.cpp index dc29f09fa04..5e44c50095d 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()); @@ -2623,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) @@ -2655,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) @@ -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 cebd454ea26..a0a816029b5 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; @@ -504,6 +505,38 @@ void parse_mod_table(const char *filename) stuff_boolean(&Always_show_selected_item_in_comms_gauge); } + 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:")) { + 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(CommOrderType::MSG_SHIPS, text); + } else if (!stricmp(order_type.c_str(), "MESSAGE WINGS")) { + 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(CommOrderType::MSG_ALL_FIGHTERS_AND_BOMBERS, text); + } else if (!stricmp(order_type.c_str(), "MESSAGE ALL FIGHTERS")) { + 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(CommOrderType::MSG_ALL_BOMBERS, text); + } else if (!stricmp(order_type.c_str(), "REINFORCEMENTS")) { + Parsed_comm_orders.emplace_back(CommOrderType::REINFORCEMENTS, text); + } else if (!stricmp(order_type.c_str(), "REARM/REPAIR SUBSYSTEMS")) { + Parsed_comm_orders.emplace_back(CommOrderType::REARM_REPAIR, text); + } else if (!stricmp(order_type.c_str(), "ABORT REARM")) { + 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()); + } + } + } + optional_string("#SEXP SETTINGS"); if (optional_string("$Loop SEXPs Then Arguments:")) { @@ -1968,6 +2001,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/network/multi.h b/code/network/multi.h index 721bf9fc4d2..5a1111bf5a8 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 2b7386bbdde..50f06d12140 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/parse/sexp.cpp b/code/parse/sexp.cpp index c1c13ae05e4..c93e022ed75 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; } } @@ -32516,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) @@ -40068,14 +40072,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" diff --git a/code/ship/ship.h b/code/ship/ship.h index a0cb2b59894..37be6562a75 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -1630,6 +1630,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(); } 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);