/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #include "ai/aigoals.h" #include "ai/ailua.h" #include "gamesnd/gamesnd.h" #include "globalincs/linklist.h" #include "hud/hudmessage.h" #include "hud/hudsquadmsg.h" #include "hud/hudtargetbox.h" #include "iff_defs/iff_defs.h" #include "io/key.h" #include "io/timer.h" #include "mission/missionlog.h" #include "mission/missionmessage.h" #include "network/multi_pmsg.h" #include "network/multimsgs.h" #include "network/multiutil.h" #include "parse/parselo.h" #include "parse/sexp.h" #include "playerman/player.h" #include "scripting/global_hooks.h" #include "scripting/api/objs/enums.h" #include "scripting/api/objs/oswpt.h" #include "scripting/api/objs/subsystem.h" #include "ship/ship.h" #include "ship/subsysdamage.h" #include "weapon/emp.h" #include "weapon/weapon.h" // defines for different modes in the squad messaging system #define DEFAULT_MSG_TIMEOUT (8 * 1000) // number of seconds * 1000 to get milliseconds #define MSG_KEY_EAT_TIME (300) int Squad_msg_mode; // current mode that the messaging system is in static int Msg_key_used; // local variable which tells if the key being processed // with the messaging system was actually used static int Msg_key; // global which indicates which key was currently pressed static bool Msg_key_set_from_scripting; // is true if the key was set from scripting and not from a player keypress static TIMESTAMP Msg_mode_timestamp; int Msg_instance; // variable which holds ship/wing instance to send the message to int Msg_shortcut_command; // holds command when using a shortcut key static int Msg_target_objnum; // id of the current target of the player static ship_subsys* Msg_targeted_subsys; // pointer to current subsystem which is targeted static int Msg_enemies; // tells us whether or not to message enemy ships or friendlies static int Msg_eat_key_timestamp; // used to temporarily "eat" keys // defined to position the messaging box int Mbox_item_h[GR_NUM_RESOLUTIONS] = { 10, 10 }; int Mbox_item_xoffset[GR_NUM_RESOLUTIONS] = { 17, 17 }; // top of the message box gauge int Mbox_top_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 445, 5 }, { // GR_1024 827, 5 } }; int Mbox_bmap_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 445, 17 }, { // GR_1024 827, 17 } }; // squadmsg menu pgup and pgdn int Menu_pgup_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 590, 9 }, { // GR_1024 937, 9 } }; int Menu_pgdn_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 590, 120 }, { // GR_1024 937, 120 } }; // ----------- // following defines/vars are used to build menus that are used in messaging mode char Squad_msg_title[256] = ""; SCP_vector MsgItems; static bool Rebuild_MsgItems = true; static int First_menu_item = -1; // index of first item in the menu. This tracks what element of comms options collection is displayed as first option, and displays the next 9 options. Changes only by +/- MAX_MENU_ITEMS (10) static int Selected_menu_item = First_menu_item; //!< index of selected item in the menu. Possible index range: 0 - 9, assuming MAX_MENU_ITEMS == 10, and First_menu_item gets initialized static bool Display_selector = false; static SCP_string Lua_sqd_msg_cat; #define MAX_KEYS_NO_SCROLL 10 #define MAX_KEYS_USED 12 // maximum number of keys used for the messaging system // next array is the array of MAX_KEYS_USED size which are the keys to use for messaging mode int keys_used[] = { KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_PAGEUP, KEY_PAGEDOWN }; #define ID1 1 #define ID2 2 SCP_string Comm_order_types[NUM_COMM_ORDER_TYPES]; int player_order::orderingCounter = 0; SCP_vector Player_orders = { player_order("no order", "No Order", -1, -1, NO_ORDER_ITEM), //Required to keep defines in sync with array indices player_order("attack ship", "Destroy my target", 299, -1, ATTACK_TARGET_ITEM), player_order("disable ship", "Disable my target", 300, -1, DISABLE_TARGET_ITEM), player_order("disarm ship", "Disarm my target", 301, -1, DISARM_TARGET_ITEM), player_order("disable subsys","Destroy subsystem", 302, -1, DISABLE_SUBSYSTEM_ITEM), player_order("guard ship", "Protect my target", 303, -1, PROTECT_TARGET_ITEM), player_order("ignore ship", "Ignore my target", 304, -1, IGNORE_TARGET_ITEM), player_order("form on wing","Form on my wing", 305, -1, FORMATION_ITEM), player_order("cover me", "Cover me", 306, -1, COVER_ME_ITEM), player_order("attack any", "Engage enemy", 307, -1, ENGAGE_ENEMY_ITEM), player_order("dock", "Capture my target", 308, -1, CAPTURE_TARGET_ITEM), player_order("rearm me", "Rearm me", 309, -1, REARM_REPAIR_ME_ITEM), player_order("abort rearm", "Abort rearm", 310, -1, ABORT_REARM_REPAIR_ITEM), player_order("depart", "Depart", 311, -1, DEPART_ITEM), player_order("stay near me", "Stay near me", -1, -1, STAY_NEAR_ME_ITEM), player_order("stay near ship", "Stay near my target",-1, -1, STAY_NEAR_TARGET_ITEM), player_order("keep safe dist", "Keep safe distance", -1, -1, KEEP_SAFE_DIST_ITEM) }; const SCP_set default_messages{ ATTACK_TARGET_ITEM , DISABLE_TARGET_ITEM , DISARM_TARGET_ITEM , PROTECT_TARGET_ITEM , IGNORE_TARGET_ITEM , FORMATION_ITEM , COVER_ME_ITEM , ENGAGE_ENEMY_ITEM , DEPART_ITEM , DISABLE_SUBSYSTEM_ITEM }; // note: STAY_NEAR_TARGET_ITEM appears in both enemy and friendly sets const SCP_set enemy_target_messages{ ATTACK_TARGET_ITEM , DISABLE_TARGET_ITEM , DISARM_TARGET_ITEM , IGNORE_TARGET_ITEM , STAY_NEAR_TARGET_ITEM , CAPTURE_TARGET_ITEM , DISABLE_SUBSYSTEM_ITEM }; const SCP_set friendly_target_messages{ PROTECT_TARGET_ITEM , STAY_NEAR_TARGET_ITEM }; const SCP_set target_messages = []() { SCP_set setunion; std::set_union(enemy_target_messages.cbegin(), enemy_target_messages.cend(), friendly_target_messages.cbegin(), friendly_target_messages.cend(), std::inserter(setunion, setunion.end())); return setunion; }(); 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]; } for (auto& order : Player_orders) order.localize(); std::sort(Player_orders.begin(), Player_orders.end(), [](const player_order& o1, const player_order& o2) { return o1.legacy_id < o2.legacy_id; }); } // Text to display on the messaging menu when using the shortcut keys const char *comm_order_get_text(int item) { Assertion(item < (int)Player_orders.size(), "Did not find order with index %d!", item); return Player_orders[item].localized_name.c_str(); } SCP_vector Squadmsg_history; // forward declarations void hud_add_issued_order(const char *name, 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); // function to set up variables needed when messaging mode is started void hud_squadmsg_start() { if (scripting::hooks::OnHudCommMenuOpened->isActive()) { auto paramList = scripting::hook_param_list(scripting::hook_param("Player", 'o', Player_obj)); if (scripting::hooks::OnHudCommMenuOpened->isOverride(paramList)) { scripting::hooks::OnHudCommMenuOpened->run(std::move(paramList)); return; } } Msg_key = -1; Msg_key_set_from_scripting = false; Rebuild_MsgItems = true; // reset the menu items MsgItems.clear(); First_menu_item = 0; Selected_menu_item = First_menu_item; // make first menu item a selected object Display_selector = Always_show_selected_item_in_comms_gauge; Squad_msg_mode = SM_MODE_TYPE_SELECT; // start off at the base state Msg_mode_timestamp = _timestamp(DEFAULT_MSG_TIMEOUT); // initialize our timer to bogus value Msg_shortcut_command = -1; // assume no shortcut key being used Msg_target_objnum = Player_ai->target_objnum; // save the players target object number Msg_targeted_subsys = Player_ai->targeted_subsys; // save the players currently targted subsystem #ifndef NDEBUG Msg_enemies = 0; // tells us if we are messaging enemy ships #endif snd_play( gamesnd_get_game_sound(GameSounds::SQUADMSGING_ON) ); if (scripting::hooks::OnHudCommMenuOpened->isActive()) { auto paramList = scripting::hook_param_list(scripting::hook_param("Player", 'o', Player_obj)); scripting::hooks::OnHudCommMenuOpened->run(std::move(paramList)); } } // function which will clean things up when messaging mode is done void hud_squadmsg_end() { if (scripting::hooks::OnHudCommMenuClosed->isActive()) { auto paramList = scripting::hook_param_list(scripting::hook_param("Player", 'o', Player_obj)); if (scripting::hooks::OnHudCommMenuClosed->isOverride(paramList)) { scripting::hooks::OnHudCommMenuClosed->run(std::move(paramList)); return; } } if ( message_is_playing() == FALSE ) snd_play( gamesnd_get_game_sound(GameSounds::SQUADMSGING_OFF) ); if (scripting::hooks::OnHudCommMenuClosed->isActive()) { auto paramList = scripting::hook_param_list(scripting::hook_param("Player", 'o', Player_obj)); scripting::hooks::OnHudCommMenuClosed->run(std::move(paramList)); } Squad_msg_title[0] = '\0'; } // 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( ) { ship_obj *so; object *objp; ship *shipp; for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { objp = &Objects[so->objnum]; if (objp->flags[Object::Object_Flags::Should_be_dead]) 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); // ship must be a fighter/bomber if (!(Ship_info[shipp->ship_info_index].is_fighter_bomber())) continue; // this ship satisfies everything if (hud_squadmsg_ship_valid(shipp, objp)) return true; } return false; } // Routine to return true if a ship should be put onto the messaging menu. Analagous to hud_squadmsg_wing_valid() below. -MageKing17 bool hud_squadmsg_ship_valid(ship *shipp, object *objp) { Assertion(shipp != nullptr, "hud_squadmsg_ship_valid() was passed a null shipp pointer; get a coder!\n"); if (!objp) // For simplicity's sake, allow it to be called with just the ship pointer objp = &Objects[shipp->objnum]; // ships must be able to receive orders if ( Ship_info[shipp->ship_info_index].class_type < 0 || !(Ship_types[Ship_info[shipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_accept_player_orders]) ) return false; // be sure ship is on correct team #ifdef NDEBUG if (shipp->team != Player_ship->team) return false; #else if (!Msg_enemies && (shipp->team != Player_ship->team)) return false; #endif // departing or dying ships cannot be on list if ( shipp->is_dying_or_departing() ) return false; // cannot be my ship or an instructor if ((objp == Player_obj) || is_instructor(objp)) return false; // ship must be accepting ship type orders if ( shipp->orders_accepted.empty()) return false; // if it is a player ship, we must be in multiplayer if ( (objp->flags[Object::Object_Flags::Player_ship]) && !(Game_mode & GM_MULTIPLAYER) ) return false; // if a messaging shortcut, be sure this ship can process the order if ( Msg_shortcut_command != -1 ) { if (!shipp->orders_accepted.contains(Msg_shortcut_command)) return false; else if ( !hud_squadmsg_ship_order_valid(objp->instance, Msg_shortcut_command) ) return false; } // maybe check comm system if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(shipp) != COMM_OK) return false; // If we got to this point, the ship must be valid. return true; } // function which counts the number of ships available for messaging. Used to determine if // we should grey out a menu or allow a shortcut command to apply. parameter "flag" is used // to tell us whether or not we should add the ship to a menu item or not. We include the // flag so that we don't have to have conditions for messaging ships/wings in two places. int hud_squadmsg_count_ships(int add_to_menu) { int count; ship_obj *so; object *objp; ship *shipp; count = 0; for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { objp = &Objects[so->objnum]; if (objp->flags[Object::Object_Flags::Should_be_dead]) continue; shipp = &Ships[objp->instance]; Assertion(shipp->objnum != -1, "hud_squadmsg_count_ships() 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); if (!hud_squadmsg_ship_valid(shipp, objp)) continue; // this ship satisfies everything count++; if (add_to_menu) { MsgItems.push_back({SHIP_INDEX(shipp), 1, shipp->get_display_name()}); } } return count; } // routine to return true if a wing should be put onto the messaging menu bool hud_squadmsg_wing_valid(wing *wingp) { int idx, ship_num; // a couple of special cases to account for before adding to count (or to menu). Don't count // wings that are leaving or left. if ( wingp->flags.any_of(Ship::Wing_Flags::Gone,Ship::Wing_Flags::Departing) ) return false; // Goober5000 - instead of checking wing leader, let's check all ships in wing; // there are several significant cases when e.g. wings contain ships of different IFFs for (idx = 0; idx < wingp->current_count; idx++) { ship_num = wingp->ship_index[idx]; Assert(ship_num >= 0); // if at least one ship in this wing is valid, that's all we need if (hud_squadmsg_ship_valid(&Ships[ship_num])) return true; } // no ships in the wing were valid return false; } // function like above, except for wings int hud_squadmsg_count_wings( int add_to_menu ) { int count, i, j; count = 0; // add the player starting wings first for ( i = 0; i < MAX_STARTING_WINGS; i++ ) { int wingnum; wingnum = Starting_wings[i]; if ( wingnum == -1 ) continue; if ( hud_squadmsg_wing_valid(&Wings[wingnum]) ) { count++; if ( add_to_menu ) { MsgItems.push_back({wingnum, 1, Wings[wingnum].get_display_name()}); } } } for ( i = 0; i < Num_wings; i++ ) { // if this wing is a player starting wing, skip it since we added it above for ( j = 0; j < MAX_STARTING_WINGS; j++ ) { if ( i == Starting_wings[j] ) break; } if ( j < MAX_STARTING_WINGS ) continue; if ( hud_squadmsg_wing_valid(&Wings[i]) ) { count++; if ( add_to_menu ) { MsgItems.push_back({i, 1, Wings[i].get_display_name()}); } } } return count; } // function to set the current submode in message mode -- also resets variables that // should be reset inbetween submodes void hud_squadmsg_do_mode( int mode ) { Squad_msg_mode = mode; MsgItems.clear(); Rebuild_MsgItems = true; First_menu_item = 0; Selected_menu_item = 0; // keep offset consistent with the reset page across submenu transitions } void hud_squadmsg_page_down() { if ( (First_menu_item + MAX_MENU_DISPLAY) < sz2i(MsgItems.size()) ) { First_menu_item += MAX_MENU_DISPLAY; Assert ( First_menu_item < sz2i(MsgItems.size()) ); } } void hud_squadmsg_page_up() { if ( First_menu_item > 0 ) { First_menu_item -= MAX_MENU_DISPLAY; Assert (First_menu_item >= 0 ); } } //Fuctions that allow selection of specific comms menu items with simple up/down/select buttons void hud_squadmsg_selection_move( bool up ) { if (Rebuild_MsgItems || MsgItems.empty()) return; //Check if comms menu is up if (Player->flags & PLAYER_FLAGS_MSG_MODE) { Display_selector = true; //play scrolling sound and reset the comms window timeout timer, so the window doesn't disappear while we select our item gamesnd_play_iface(InterfaceSounds::SCROLL); Msg_mode_timestamp = _timestamp(DEFAULT_MSG_TIMEOUT); //The selection moves +/-1 through the whole menu, wrapping around at either end. //First_menu_item and Selected_menu_item are just the page and the offset within the page. int num_items = sz2i(MsgItems.size()); int step = up ? -1 : 1; int selected_index = First_menu_item + Selected_menu_item; //skip over any hidden items (such as when using Hide_main_rearm_items_in_comms_gauge). //bound the search by the item count so a fully hidden menu does not loop forever. for (int i = 0; i < num_items; i++) { selected_index = (selected_index + step + num_items) % num_items; //stop once we land on a visible item if (MsgItems[selected_index].active >= 0) break; } Selected_menu_item = selected_index % MAX_MENU_DISPLAY; First_menu_item = selected_index - Selected_menu_item; } } //function that tricks hud_squadmsg_get_key() into thinking player selected a menu item with a num key press //Yes, this is a pretty much a hack, but it's simple and works with every squadmsg type. void hud_squadmsg_selection_select() { if (Rebuild_MsgItems) { return; } //Check if comms menu is up if (Player->flags & PLAYER_FLAGS_MSG_MODE) { //Check if selected option is even active if ((sz2i(MsgItems.size()) > Selected_menu_item + First_menu_item) && (MsgItems[Selected_menu_item + First_menu_item].active > 0)) { Msg_key_used = 1; Msg_key = Selected_menu_item + 2; //+1 because menu items on actual menu start from 1, not 0 //Another +1 because methods that use this later do -1. I'm not sure why they do that, but it works Selected_menu_item = 0; //Reset this value, so the position will reset at the next window } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } } } int hud_squadmsg_get_total_keys() { int num_keys_used; num_keys_used = MAX_KEYS_NO_SCROLL; if ( sz2i(MsgItems.size()) > MAX_MENU_DISPLAY ) num_keys_used = MAX_KEYS_USED; return num_keys_used; } // function called from high level keyboard read code to give the squadmsg code a key. // return 1 is the key was used by the messaging code, 0 otherwise int hud_squadmsg_read_key( int k ) { int i, key_found, num_keys_used; num_keys_used = hud_squadmsg_get_total_keys(); if ( !(Player->flags & PLAYER_FLAGS_MSG_MODE) ) { // check to see if any messaging keys are still down for some length of time // after messaging is over. Return true for a while. if ( !timestamp_elapsed(Msg_eat_key_timestamp) ) { for (i = 0; i < num_keys_used; i++ ) { if ( key_is_pressed(keys_used[i]) ) return 1; } } return 0; } key_found = 0; for (i = 0; i < num_keys_used; i++ ) { if ( k == keys_used[i] ) { if ( key_down_count(k) ) { Msg_key = k; key_found = 1; } if ( key_is_pressed(k) ) { key_found = 1; } // key_down_count(k); break; } } if ( key_found ) return 1; return 0; } // function which reads the keyboard array and determines if a menu key has been hit int hud_squadmsg_get_key() { int k, i, num_keys_used; if ( Msg_key == -1) return -1; bool lua_selected = Msg_key_set_from_scripting; k = Msg_key; Msg_key = -1; Msg_key_set_from_scripting = false; num_keys_used = hud_squadmsg_get_total_keys(); // if the emp effect is active, never accept keypresses if(emp_active_local()){ return -1; } if (lua_selected) { return k; } for ( i = 0; i < num_keys_used; i++ ) { if ( k == keys_used[i] ) { Msg_key_used = 1; // this variable will extend the timer // use a timestamp to prevent top level key code from possibly reprocessing this key Msg_eat_key_timestamp = timestamp(MSG_KEY_EAT_TIME); if ( k == KEY_PAGEDOWN ) { // pageup and pagedown scroll the menu -- deal with these separately!! hud_squadmsg_page_down(); return -1; } else if ( k == KEY_PAGEUP ) { hud_squadmsg_page_up(); return -1; } else if ( k == KEY_ESC ) { hud_squadmsg_toggle(); return -1; } else if ( (i < sz2i(MsgItems.size())) && (Squad_msg_mode == SM_MODE_REINFORCEMENTS) ) { // return any key if selecting reinforcement return i; } // play general fail sound if inactive item hit. else if ((i + First_menu_item < sz2i(MsgItems.size())) && (MsgItems[i + First_menu_item].active == 0)) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } else if ((i + First_menu_item < sz2i(MsgItems.size())) && (MsgItems[i + First_menu_item].active > 0)) { // only return keys that are associated with menu items return i + First_menu_item; } else { Msg_key_used = 0; // if no #-key pressed for visible item, break and allow timer to break; // to continue as if no key was pressed } } } return -1; } // function to return true or false if the given ship can rearm, or be repaired int hud_squadmsg_can_rearm( ship *shipp ) { // player ships which turns traitor cannot rearm if ( (shipp == Player_ship) && (Player_ship->team == Iff_traitor) ) return 0; // 5/6/98 -- MWA Decided to always be able to call in support. return 1; } // calls for repair/rearm of the player ship. Checks for the presence of the support // ship and does the appropriate action if found void hud_squadmsg_repair_rearm( int toggle_state, object *objp) { int robjnum; object *robjp; object *tobj; int multi_player_num; int multi_player_team; // this is essentially a check for multiplayer server/client mode // in multiplayer mode, the server may have to issue this command when received from a client if(objp == NULL) { tobj = Player_obj; multi_player_num = -1; multi_player_team = -1; } else { tobj = objp; multi_player_num = multi_find_player_by_object(objp); Assert(multi_player_num != -1); if (multi_player_num != -1) { multi_player_team = Net_players[multi_player_num].p_info.team; } else { multi_player_team = -1; } } // see if player is already scheduled on arriving support ship. If so, issues appripriate // message and bail if ( is_support_allowed(tobj) ) { if ( mission_is_repair_scheduled( tobj ) ) { message_send_builtin(MESSAGE_ALREADY_ON_WAY, nullptr, nullptr, multi_player_num, multi_player_team); } else { robjnum = hud_support_find_closest( tobj ); if ( robjnum != -1 ) { message_send_builtin(MESSAGE_ALREADY_ON_WAY, &Ships[Objects[robjnum].instance], nullptr, multi_player_num, multi_player_team); } else { // request a rearm. Next function returns -1 if ship is warping in, objnum of repair ship otherwise robjnum = ai_issue_rearm_request( tobj ); if ( robjnum != -1) { robjp = &Objects[robjnum]; message_send_builtin(MESSAGE_ON_WAY, &Ships[robjp->instance], nullptr, multi_player_num, multi_player_team); } else { // if we are in this part of the if statement, a support ship has been warped in to // service us. Issue appropriate message message_send_builtin(MESSAGE_REARM_WARP, nullptr, nullptr, multi_player_num, multi_player_team); } mission_log_add_entry(LOG_PLAYER_CALLED_FOR_REARM, Ships[tobj->instance].ship_name, NULL); } } } //if ( multi_player_num == -1 ) // only do the hud display if it is for me! // hud_support_view_start(); if ( toggle_state ) hud_squadmsg_toggle(); // take us out of message mode } // function which gets called from keyboard code to issues a shortcut command for rearming. void hud_squadmsg_rearm_shortcut() { if ( !hud_squadmsg_can_rearm(Player_ship) ) return; // multiplayer clients need to send this message to the server if ( MULTIPLAYER_CLIENT ) { send_player_order_packet(SQUAD_MSG_SHIP, 0, REARM_REPAIR_ME_ITEM); return; } hud_squadmsg_repair_rearm(0); } // code which is called when a player aborts his rearm request void hud_squadmsg_repair_rearm_abort( int toggle_state, object *obj) { // ai_info *aip; // object *robjp; object *tobj; // this is essentially a check for multiplayer server/client mode // in multiplayer mode, the server may have to issue this command when received from a client if(obj == NULL) tobj = Player_obj; else tobj = obj; // try to abort the request. We shoudln't be in this function unless we are actually // queued for repair. Send a message from support ship if the support ship is in the mission ai_abort_rearm_request( tobj ); // move the next statements outside of the above if-statement. Seems like this place // is the right place, since we want to change state of the messaging system regardless // of what happened above. if ( toggle_state ) hud_squadmsg_toggle(); // take us out of message mode } // Goober5000 - redone and added a bit // returns true if an order is valid for a ship. Applies to things like departure when engines are blown, etc. bool hud_squadmsg_ship_order_valid( int shipnum, int order ) { // Goober5000 Assert( shipnum >= 0 && shipnum < MAX_SHIPS ); ship *shipp = &Ships[shipnum]; switch ( order ) { case DEPART_ITEM: // disabled ships can't depart. if (shipp->flags[Ship::Ship_Flags::Disabled]) return false; // Goober5000: also can't depart if no subspace drives and no valid mothership if ( !(ship_can_warp_full_check(shipp)) ) { // function accounts for ship's wing and returns true if ship has bay departure and mothership present, false otherwise return ship_can_bay_depart(shipp); } break; } return true; } // returns true or false if the Players target is valid for the given order // find_order is true when we need to search the comm_orders array for the order entry. We have // to do this action in some cases since all we know is the actual "value" of the order bool hud_squadmsg_is_target_order_valid(size_t order, ai_info *aip, bool isWing ) { int target_objnum; ship *shipp, *ordering_shipp; object *objp; if ( aip == NULL ) aip = Player_ai; target_objnum = aip->target_objnum; ordering_shipp = &Ships[aip->shipnum]; //If it's a lua order, defer to luaai if (Player_orders[order].lua_id != -1) { return ai_lua_is_valid_ship(Player_orders[order].lua_id, isWing, ordering_shipp) && ai_lua_is_valid_target(Player_orders[order].lua_id, target_objnum, ordering_shipp, order); } // orders which don't operate on targets are always valid if ( target_messages.count(order) == 0 ) return true; // order isn't valid if there is no player target if ( target_objnum == -1 ) { return false; } objp = &Objects[target_objnum]; // target isn't a ship, then return false if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_WEAPON) ) return false; // if it's a weapon, then it needs to be a WIF_BOMB weapon. Only attack order valid, and only // valid on bombs not on the player's team // MageKing17: Now also works on WIF3_FIGHTER_INTERCEPTABLE weapons. if ( objp->type == OBJ_WEAPON ) { if (Weapons[objp->instance].lssm_stage==3){ return false; } if ( order == ATTACK_TARGET_ITEM && ((Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]) || (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Fighter_Interceptable])) && (Weapons[objp->instance].team != ordering_shipp->team) ) { return true; } return false; } Assert( objp->type == OBJ_SHIP ); shipp = &Ships[objp->instance]; // if target is a navbouy, return false if ( Ship_info[shipp->ship_info_index].flags[Ship::Info_Flags::Navbuoy] ){ return false; } // if we are messaging a ship, and that ship is our target, no target type orders are ever active if ( (Squad_msg_mode == SM_MODE_SHIP_COMMAND) && (Objects[target_objnum].instance == Msg_instance) ){ return false; } // check if this order can be issued against the target if (shipp->orders_allowed_against.find(order) == shipp->orders_allowed_against.end()) { return false; } // if the order is a disable order or depart, and the ship is disabled, order isn't active if ( (order == DISABLE_TARGET_ITEM) && (shipp->flags[Ship::Ship_Flags::Disabled]) ){ return false; } // same as above except for disarmed. if ( (order == DISARM_TARGET_ITEM) && ((shipp->subsys_info[SUBSYSTEM_TURRET].type_count == 0) || (shipp->subsys_info[SUBSYSTEM_TURRET].aggregate_current_hits <= 0.0f)) ){ return false; } // if order is disable subsystem, and no subsystem targeted or no hits, then order not valid if ( (order == DISABLE_SUBSYSTEM_ITEM) && ((aip->targeted_subsys == nullptr) || (aip->targeted_subsys->current_hits <= 0.0f)) ){ return false; } // check based on target's and player's team if ( (shipp->team == ordering_shipp->team) && (friendly_target_messages.count(order) > 0) ){ return true; } else if ( (shipp->team != ordering_shipp->team) && (enemy_target_messages.count(order) > 0) ){ return true; } else { return false; } } const SCP_string& hud_squadmsg_get_order_name(int command) { Assertion(SCP_vector_inbounds(Player_orders, command), "Order does not exist! Get a coder!"); return Player_orders[command].hud_name; } scripting::api::lua_enum hud_squadmsg_get_order_scripting_enum(int command) { Assertion(SCP_vector_inbounds(Player_orders, command), "Order does not exist! Get a coder!"); switch (command) { case ATTACK_TARGET_ITEM: return scripting::api::LE_SQUAD_MESSAGE_ATTACK_TARGET; case DISABLE_TARGET_ITEM: return scripting::api::LE_SQUAD_MESSAGE_DISABLE_TARGET; case DISARM_TARGET_ITEM: return scripting::api::LE_SQUAD_MESSAGE_DISARM_TARGET; case PROTECT_TARGET_ITEM: return scripting::api::LE_SQUAD_MESSAGE_PROTECT_TARGET; case IGNORE_TARGET_ITEM: return scripting::api::LE_SQUAD_MESSAGE_IGNORE_TARGET; case FORMATION_ITEM: return scripting::api::LE_SQUAD_MESSAGE_FORMATION; case COVER_ME_ITEM: return scripting::api::LE_SQUAD_MESSAGE_COVER_ME; case ENGAGE_ENEMY_ITEM: return scripting::api::LE_SQUAD_MESSAGE_ENGAGE_ENEMY; case CAPTURE_TARGET_ITEM: return scripting::api::LE_SQUAD_MESSAGE_CAPTURE_TARGET; case REARM_REPAIR_ME_ITEM: return scripting::api::LE_SQUAD_MESSAGE_REARM_REPAIR_ME; case ABORT_REARM_REPAIR_ITEM: return scripting::api::LE_SQUAD_MESSAGE_ABORT_REARM_REPAIR; case STAY_NEAR_ME_ITEM: return scripting::api::LE_SQUAD_MESSAGE_STAY_NEAR_ME; case STAY_NEAR_TARGET_ITEM: return scripting::api::LE_SQUAD_MESSAGE_STAY_NEAR_TARGET; case KEEP_SAFE_DIST_ITEM: return scripting::api::LE_SQUAD_MESSAGE_KEEP_SAFE_DIST; case DEPART_ITEM: return scripting::api::LE_SQUAD_MESSAGE_DEPART; case DISABLE_SUBSYSTEM_ITEM: return scripting::api::LE_SQUAD_MESSAGE_DISABLE_SUBSYSTEM; default: return scripting::api::LE_SQUAD_MESSAGE_LUA_AI; } } // Run the order issued hook. When an order is issued we first check if we should override. If so, then we run the hook and return true // which will skip the rest of the order code. bool hud_squadmsg_run_order_issued_hook(int command, ship* sendingShip, ship* recipientShip, wing* recipientWing, ship* targetShip, ship_subsys* subsys) { bool isOverride = false; if (scripting::hooks::OnHudCommOrderIssued->isActive()) { object_ship_wing_point_team recipient; if (recipientShip != nullptr) { recipient = recipientShip; } else if (recipientWing != nullptr) { recipient = recipientWing; } auto sendingObject = (sendingShip == nullptr) ? nullptr : &Objects[sendingShip->objnum]; auto targetObject = (targetShip == nullptr) ? nullptr : &Objects[targetShip->objnum]; auto paramList = scripting::hook_param_list( scripting::hook_param("Sender", 'o', sendingObject), scripting::hook_param("Recipient", 'o', scripting::api::l_OSWPT.Set(recipient)), scripting::hook_param("Target", 'o', targetObject), scripting::hook_param("Subsystem", 'o', scripting::api::l_Subsystem.Set(scripting::api::ship_subsys_h(targetObject, subsys))), scripting::hook_param("Order", 'o', scripting::api::l_Enum.Set(scripting::api::enum_h(hud_squadmsg_get_order_scripting_enum(command)))), scripting::hook_param("Name", 's', hud_squadmsg_get_order_name(command).c_str()) ); if (scripting::hooks::OnHudCommOrderIssued->isOverride( scripting::hooks::CommOrderConditions{sendingShip, targetObject, &recipient}, paramList)) { isOverride = true; } scripting::hooks::OnHudCommOrderIssued->run( scripting::hooks::CommOrderConditions{sendingShip, targetObject, &recipient}, std::move(paramList)); } return isOverride; } // function to send an order to all fighters/bombers. void hud_squadmsg_send_to_all_fighters( int command, int player_num ) { ai_info *aip; ship *shipp, *ordering_shipp; int i, send_message; // quick short circuit here because of actually showing comm menu even though you cannot message. // just a safety net. if ( (Game_mode & GM_MULTIPLAYER) && (player_num != -1) ) { if ( !multi_can_message(&Net_players[player_num]) ) { return; } } // check for multiplayer mode if(MULTIPLAYER_CLIENT) { send_player_order_packet(SQUAD_MSG_ALL, 0, command); return; } send_message = 1; // internal flag to dictate who sends message aip = Player_ai; if ( player_num != -1 ) aip = &Ai_info[Ships[Objects[Net_players[player_num].m_player->objnum].instance].ai_index]; Assert( aip->shipnum != -1 ); ordering_shipp = &Ships[aip->shipnum]; /* Goober5000 - this relies on the weird calling logic that is now disabled 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) ) { do_ship = 1; send_message = 0; } } */ for ( i = 0; i < Num_wings; i++ ) { int shipnum; if ( (Wings[i].flags[Ship::Wing_Flags::Gone]) || (Wings[i].current_count == 0) ) continue; if ( Wings[i].flags[Ship::Wing_Flags::Departing] ) continue; // get the first ship on the wing list and look at its team and then its type shipnum = Wings[i].ship_index[Wings[i].special_ship]; // if special ship isn't valid then just move on if (shipnum < 0) continue; shipp = &Ships[shipnum]; // can't message if not on players team if ( shipp->team != ordering_shipp->team ) 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()) ) 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 if (!shipp->orders_accepted.contains(command)) continue; // send the command to the wing if ( Wings[i].current_count > 0 ) { if (send_message) { hud_add_issued_order("All Fighters", command); if ( hud_squadmsg_send_wing_command(i, command, send_message, SQUADMSG_HISTORY_UPDATE, player_num) ) { send_message = 0; } } else { hud_squadmsg_send_wing_command(i, command, send_message, SQUADMSG_HISTORY_NO_UPDATE, player_num); } } } // now find any friendly fighter/bomber ships not in wings for (auto so: list_range(&Ship_obj_list)) { auto objp = &Objects[so->objnum]; if (objp->flags[Object::Object_Flags::Should_be_dead]) continue; if ( objp->type != OBJ_SHIP ) continue; // don't send messge to ships not on player's team, or that are in a wing. shipp = &Ships[objp->instance]; if ( (shipp->team != ordering_shipp->team) || (shipp->wingnum != -1) ) continue; // don't send message to non fighter wings if ( !(Ship_info[shipp->ship_info_index].is_fighter_bomber()) ) continue; // skip departing/dying ships if ( shipp->is_dying_or_departing() ) continue; // don't send command if ship won't accept it 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) ) { send_message = 0; } } else { hud_squadmsg_send_ship_command(objp->instance, command, send_message, SQUADMSG_HISTORY_NO_UPDATE, player_num); } } // we might send the ship command again if we are ignoring a target, and the guy // we ordered directly is a ship -- we want the response to come directly from the // guy we orders /* Goober5000 - yet again with the weird logic if ( do_ship ) { Assert( Msg_instance != MESSAGE_ALL_FIGHTERS ); hud_squadmsg_send_ship_command( Msg_instance, command, 1 ); } */ } // Check if any enemy ships are in the mission int hud_squadmsg_enemies_present() { ship *shipp; ship_obj *so; for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; shipp = &Ships[Objects[so->objnum].instance]; if ( shipp->team != Player_ship->team ) return 1; } return 0; } inline bool override_protect_ship_type(ship_info* sip) { return sip->is_fighter_bomber() || sip->flags[Ship::Info_Flags::Freighter] || sip->flags[Ship::Info_Flags::Transport]; } int enemy_message(int message) { return hud_squadmsg_enemies_present() ? message : MESSAGE_YESSIR; } // function which sends a message to a specific ship. This routine can be called from one of two // places. Either after selecting a ship when using a hotkey, or after selecting a command when // using the entire messaging menu system // // if local and addr are non-null, it means the function is being called by the (multiplayer) server in response to // a PLAYER_COMMAND_PACKET int hud_squadmsg_send_ship_command( int shipnum, int command, int send_message, int update_history, int player_num ) { ai_info *ainfo; ai_goal_mode ai_mode; // ai mode... int ai_submode; // ...and submode needed for ship commands ship *target = nullptr; char *target_shipname; int int_data = 0; float float_data = 0.0f; ai_lua_parameters lua_target; int message; int target_team, ship_team; // team id's for the ship getting message and any target the player has ship *ordering_shipp; int special_index = -1; int message_team_filter = -1; // quick short circuit here because of actually showing comm menu even though you cannot message. // just a safety net. if ( (Game_mode & GM_MULTIPLAYER) && (player_num != -1) ) { if ( !multi_can_message(&Net_players[player_num]) ) { return 0; } } // check for multiplayer mode if(MULTIPLAYER_CLIENT){ send_player_order_packet(SQUAD_MSG_SHIP, shipnum, command); return 0; } ai_mode = AI_GOAL_NONE; // needs to be initialized ai_submode = -1234567; ainfo = Player_ai; if ( player_num != -1 ){ ainfo = &Ai_info[Ships[Objects[Net_players[player_num].m_player->objnum].instance].ai_index]; message_team_filter = Net_players[player_num].p_info.team; } Assert( ainfo->shipnum != -1 ); ordering_shipp = &Ships[ainfo->shipnum]; // a shortcut to save on repetitive coding. If the order is a 'target' order, make the default // message be "no target" message = MESSAGE_NOSIR; if (target_messages.count((size_t)command) > 0 && (ainfo->target_objnum == -1)) { message = MESSAGE_NO_TARGET; } if (hud_squadmsg_is_target_order_valid((size_t) command, ainfo)) { target_shipname = nullptr; target_team = -1; if (ainfo->target_objnum != -1) { if (Objects[ainfo->target_objnum].type == OBJ_SHIP) { if (ainfo->target_objnum != Ships[shipnum].objnum) { target = &Ships[Objects[ainfo->target_objnum].instance]; target_shipname = Ships[Objects[ainfo->target_objnum].instance].ship_name; target_team = Ships[Objects[ainfo->target_objnum].instance].team; } } } if (hud_squadmsg_run_order_issued_hook(command, ordering_shipp, &Ships[shipnum], nullptr, target, ainfo->targeted_subsys)) { return 0; // No message was sent } Assert ( ainfo->shipnum != -1 ); ship_team = Ships[ainfo->shipnum].team; // team of the ship issuing the message switch (command) { case ATTACK_TARGET_ITEM: if ( Objects[ainfo->target_objnum].type == OBJ_SHIP ) { Assert( target_shipname ); Assert( ship_team != target_team ); // Orders to override protect if (override_protect_ship_type(&Ship_info[Ships[Objects[ainfo->target_objnum].instance].ship_info_index])) { Objects[ainfo->target_objnum].flags.remove(Object::Object_Flags::Protected); } ai_mode = AI_GOAL_CHASE; ai_submode = SM_ATTACK; } else if ( Objects[ainfo->target_objnum].type == OBJ_WEAPON ) { ai_mode = AI_GOAL_CHASE_WEAPON; ai_submode = Objects[ainfo->target_objnum].instance; // store the instance of the weapon -- ai goals code will deal with it } else { Int3(); } message = MESSAGE_ATTACK_TARGET; break; case DISABLE_TARGET_ITEM: Assert( target_shipname ); Assert( ship_team != target_team ); // Orders to override protect if (override_protect_ship_type(&Ship_info[Ships[Objects[ainfo->target_objnum].instance].ship_info_index])) { Objects[ainfo->target_objnum].flags.remove(Object::Object_Flags::Protected); } ai_mode = (The_mission.ai_profile->flags[AI::Profile_Flags::Hudsquadmsg_tactical_disarm_disable]) ? AI_GOAL_DISABLE_SHIP_TACTICAL : AI_GOAL_DISABLE_SHIP; ai_submode = -SUBSYSTEM_ENGINE; message = MESSAGE_DISABLE_TARGET; break; case DISARM_TARGET_ITEM: Assert( target_shipname ); Assert( ship_team != target_team ); // Orders to override protect if (override_protect_ship_type(&Ship_info[Ships[Objects[ainfo->target_objnum].instance].ship_info_index])) { Objects[ainfo->target_objnum].flags.remove(Object::Object_Flags::Protected); } ai_mode = (The_mission.ai_profile->flags[AI::Profile_Flags::Hudsquadmsg_tactical_disarm_disable]) ? AI_GOAL_DISARM_SHIP_TACTICAL : AI_GOAL_DISARM_SHIP; ai_submode = -SUBSYSTEM_TURRET; message = MESSAGE_DISARM_TARGET; break; case DISABLE_SUBSYSTEM_ITEM: Assert( target_shipname ); Assert( ship_team != target_team ); Assert( ainfo->targeted_subsys != NULL ); Assert( ainfo->targeted_subsys->current_hits > 0.0f); // Orders to override protect if (override_protect_ship_type(&Ship_info[Ships[Objects[ainfo->target_objnum].instance].ship_info_index])) { Objects[ainfo->target_objnum].flags.remove(Object::Object_Flags::Protected); } ai_mode = AI_GOAL_DESTROY_SUBSYSTEM; ai_submode = ship_find_subsys( &Ships[Objects[ainfo->target_objnum].instance], ainfo->targeted_subsys->system_info->subobj_name ); special_index = ai_submode; message = MESSAGE_ATTACK_SUBSYSTEM; break; case CAPTURE_TARGET_ITEM: Assert( target_shipname ); Assert( ship_team != target_team ); Assert(ainfo->target_objnum > -1); Objects[ainfo->target_objnum].flags.set(Object::Object_Flags::Protected); ai_mode = AI_GOAL_DOCK; ai_submode = AIS_DOCK_0; message = MESSAGE_DOCK_YES; break; case PROTECT_TARGET_ITEM: // AL 31-3-98: Can't protect self... this can happen if all fighters // are told to protect another friendly ship if ( ainfo->target_objnum == Ships[shipnum].objnum ) { return 0; } Assert( target_shipname ); Assert( ship_team == target_team ); ai_mode = AI_GOAL_GUARD; ai_submode = AIS_GUARD_PATROL; message = enemy_message(MESSAGE_PROTECT_TARGET); break; case IGNORE_TARGET_ITEM: Assert( target_shipname ); Assert( ship_team != target_team ); ai_mode = AI_GOAL_IGNORE_NEW; ai_submode = 0; message = MESSAGE_IGNORE; break; case FORMATION_ITEM: message = MESSAGE_FORM_ON_MY_WING; target_shipname = ordering_shipp->ship_name; ai_mode = AI_GOAL_FORM_ON_WING; ai_submode = 0; break; case COVER_ME_ITEM: ai_mode = AI_GOAL_GUARD; ai_submode = AIS_GUARD_PATROL; target_shipname = ordering_shipp->ship_name; message = enemy_message(MESSAGE_COVER_ME); break; case ENGAGE_ENEMY_ITEM: ai_mode = AI_GOAL_CHASE_ANY; ai_submode = SM_ATTACK; message = enemy_message(MESSAGE_ENGAGE); target_shipname = nullptr; break; case DEPART_ITEM: ai_mode = AI_GOAL_WARP; ai_submode = -1; message = MESSAGE_WARP_OUT; Ships[shipnum].flags.set(Ship::Ship_Flags::Departure_ordered); break; // the following are support ship options!!! case REARM_REPAIR_ME_ITEM: if (MULTIPLAYER_MASTER && (player_num != -1)) { hud_squadmsg_repair_rearm(0,&Objects[Net_players[player_num].m_player->objnum]); } else { hud_squadmsg_repair_rearm(0); // note we return right away. repair/rearm code handles messaging, etc } // add the order to the squad message history hud_add_issued_order(Ships[shipnum].ship_name, command); hud_update_last_order(ordering_shipp->ship_name, player_num, special_index); return 0; case ABORT_REARM_REPAIR_ITEM: if (MULTIPLAYER_MASTER && (player_num != -1)) { hud_squadmsg_repair_rearm_abort(0,&Objects[Net_players[player_num].m_player->objnum]); } else { hud_squadmsg_repair_rearm_abort(0); // note we return right away. repair/rearm code handles messaging, etc } // add the order to the squad message history hud_add_issued_order(Ships[shipnum].ship_name, command); hud_update_last_order(ordering_shipp->ship_name, player_num, special_index); return 0; case STAY_NEAR_ME_ITEM: case STAY_NEAR_TARGET_ITEM: ai_mode = AI_GOAL_STAY_NEAR_SHIP; ai_submode = -1; float_data = 300.0f; // distance from target ship message = MESSAGE_YESSIR; if (command == STAY_NEAR_ME_ITEM) { target_shipname = ordering_shipp->ship_name; } break; case KEEP_SAFE_DIST_ITEM: ai_mode = AI_GOAL_KEEP_SAFE_DISTANCE; ai_submode = -1; message = MESSAGE_YESSIR; break; default: Assert(command < (int) Player_orders.size()); // get Allender -- illegal message ai_mode = AI_GOAL_LUA; ai_submode = Player_orders[command].lua_id; auto lua_porder = ai_lua_find_player_order(Player_orders[command].lua_id); message = lua_porder->ai_message; if (ainfo->target_objnum != -1) { lua_target = { object_ship_wing_point_team(&Ships[Objects[ainfo->target_objnum].instance]), {} }; } break; } // handle case of messaging one ship. Deal with messaging all fighters next. if (ai_mode != AI_GOAL_NONE) { Assert(ai_submode != -1234567); ai_add_ship_goal_player(ai_goal_type::PLAYER_SHIP, ai_mode, ai_submode, target_shipname, &Ai_info[Ships[shipnum].ai_index], int_data, float_data, lua_target); if (update_history == SQUADMSG_HISTORY_ADD_ENTRY) { hud_add_issued_order(Ships[shipnum].ship_name, command); hud_update_last_order(target_shipname, player_num, special_index); } else if (update_history == SQUADMSG_HISTORY_UPDATE) { hud_update_last_order(target_shipname, player_num, special_index); } } } // if we're in multiplayer mode, and we're the server, determine if this virtual squadmate order should be // sent to other players in the game as an actual "order" if ((Game_mode & GM_MULTIPLAYER) && (message != MESSAGE_NOSIR)) { // if the multi_msg system processed and sent this order to a player, we should not play a response if (multi_msg_eval_ship_squadmsg(shipnum, command, ainfo, player_num)) { send_message = 0; } } // this is the _response_ if (send_message && (!(Ships[shipnum].flags[Ship::Ship_Flags::No_builtin_messages]))) { message_send_builtin(message, &Ships[shipnum], target, player_num, message_team_filter); } return send_message; } // function to send a command to a wing. Like above function, called from one of two places // // if local and addr are non-null, it means the function is being called by the (multiplayer) server in response to // a PLAYER_COMMAND_PACKET // // returns whether or not a message was sent int hud_squadmsg_send_wing_command( int wingnum, int command, int send_message, int update_history, int player_num ) { ai_info *ainfo; ai_goal_mode ai_mode; // ai mode... int ai_submode; // ...and submode needed for ship commands ship *target = nullptr; char *target_shipname; int int_data = 0; float float_data = 0.0f; ai_lua_parameters lua_target; int message_sent, message; int target_team, wing_team; // team for the wing and the player's target ship *ordering_shipp; int special_index = -1; int message_team_filter = -1; // quick short circuit here because of actually showing comm menu even though you cannot message. // just a safety net. if ((Game_mode & GM_MULTIPLAYER) && (player_num != -1)) { if (!multi_can_message(&Net_players[player_num])) { return 0; } } // check for multiplayer mode if (MULTIPLAYER_CLIENT) { send_player_order_packet(SQUAD_MSG_WING, wingnum, command); return 0; } ai_mode = AI_GOAL_NONE; // needs to be initialized ai_submode = -1234567; ainfo = Player_ai; if (player_num != -1) { ainfo = &Ai_info[Ships[Objects[Net_players[player_num].m_player->objnum].instance].ai_index]; message_team_filter = Net_players[player_num].p_info.team; } Assert(ainfo->shipnum != -1); ordering_shipp = &Ships[ainfo->shipnum]; // a shortcut to save on repetative coding message = MESSAGE_NOSIR; if ((target_messages.count((size_t)command) > 0) && (ainfo->target_objnum == -1)) { message = MESSAGE_NO_TARGET; } if (hud_squadmsg_is_target_order_valid((size_t)command, ainfo, true)) { target_shipname = nullptr; target_team = -1; if (ainfo->target_objnum != -1) { if (Objects[ainfo->target_objnum].type == OBJ_SHIP) { target = &Ships[Objects[ainfo->target_objnum].instance]; target_shipname = Ships[Objects[ainfo->target_objnum].instance].ship_name; target_team = Ships[Objects[ainfo->target_objnum].instance].team; } } Assert (ainfo->shipnum != -1); Assert ((wingnum >= 0) && (wingnum < Num_wings)); // get the team for the wing Assert (Wings[wingnum].ship_index[0] != -1); wing_team = Ships[Wings[wingnum].ship_index[0]].team; if (hud_squadmsg_run_order_issued_hook(command, ordering_shipp, nullptr, &Wings[wingnum], target, ainfo->targeted_subsys)) { return 0; // No message was sent } switch (command) { case ATTACK_TARGET_ITEM: if (Objects[ainfo->target_objnum].type == OBJ_SHIP) { Assert(target_shipname); Assert(wing_team != target_team); if ((Ships[Objects[ainfo->target_objnum].instance].wingnum != -1) && (Ships[Objects[ainfo->target_objnum].instance].wingnum == wingnum)) { message = MESSAGE_NOSIR; ai_mode = AI_GOAL_NONE; } else { ai_mode = AI_GOAL_CHASE; ai_submode = SM_ATTACK; message = MESSAGE_ATTACK_TARGET; } } else if (Objects[ainfo->target_objnum].type == OBJ_WEAPON) { ai_mode = AI_GOAL_CHASE_WEAPON; ai_submode = Objects[ainfo->target_objnum].instance; // store the instance of the weapon -- ai goals code will deal with it message = MESSAGE_ATTACK_TARGET; } else { Int3(); } break; case DISABLE_TARGET_ITEM: Assert(target_shipname); Assert(wing_team != target_team); ai_mode = (The_mission.ai_profile->flags[AI::Profile_Flags::Hudsquadmsg_tactical_disarm_disable]) ? AI_GOAL_DISABLE_SHIP_TACTICAL : AI_GOAL_DISABLE_SHIP; ai_submode = -SUBSYSTEM_ENGINE; message = MESSAGE_DISABLE_TARGET; break; case DISARM_TARGET_ITEM: Assert(target_shipname); Assert(wing_team != target_team); ai_mode = (The_mission.ai_profile->flags[AI::Profile_Flags::Hudsquadmsg_tactical_disarm_disable]) ? AI_GOAL_DISARM_SHIP_TACTICAL : AI_GOAL_DISARM_SHIP; ai_submode = -SUBSYSTEM_TURRET; message = MESSAGE_DISARM_TARGET; break; case DISABLE_SUBSYSTEM_ITEM: Assert(target_shipname); Assert(wing_team != target_team); Assert(ainfo->targeted_subsys != NULL); Assert(ainfo->targeted_subsys->current_hits > 0.0f); ai_mode = AI_GOAL_DESTROY_SUBSYSTEM; ai_submode = ship_find_subsys(&Ships[Objects[ainfo->target_objnum].instance], ainfo->targeted_subsys->system_info->subobj_name); special_index = ai_submode; message = MESSAGE_ATTACK_SUBSYSTEM; break; case PROTECT_TARGET_ITEM: Assert(target_shipname); Assert(wing_team == target_team); ai_mode = AI_GOAL_GUARD; ai_submode = AIS_GUARD_PATROL; message = enemy_message(MESSAGE_PROTECT_TARGET); break; case IGNORE_TARGET_ITEM: Assert(target_shipname); Assert(wing_team != target_team); ai_mode = AI_GOAL_IGNORE_NEW; ai_submode = 0; message = MESSAGE_IGNORE; break; case FORMATION_ITEM: message = MESSAGE_FORM_ON_MY_WING; target_shipname = ordering_shipp->ship_name; ai_mode = AI_GOAL_FORM_ON_WING; ai_submode = 0; break; case COVER_ME_ITEM: ai_mode = AI_GOAL_GUARD; ai_submode = AIS_GUARD_PATROL; target_shipname = ordering_shipp->ship_name; message = enemy_message(MESSAGE_COVER_ME); break; case ENGAGE_ENEMY_ITEM: ai_mode = AI_GOAL_CHASE_ANY; ai_submode = SM_ATTACK; message = enemy_message(MESSAGE_ENGAGE); target_shipname = NULL; break; case DEPART_ITEM: ai_mode = AI_GOAL_WARP; ai_submode = -1; message = MESSAGE_WARP_OUT; Wings[wingnum].flags.set(Ship::Wing_Flags::Departure_ordered); break; case REARM_REPAIR_ME_ITEM: case ABORT_REARM_REPAIR_ITEM: case STAY_NEAR_ME_ITEM: case STAY_NEAR_TARGET_ITEM: case KEEP_SAFE_DIST_ITEM: return 0; default: { Assert(command < (int) Player_orders.size()); // get Allender -- illegal message ai_mode = AI_GOAL_LUA; ai_submode = Player_orders[command].lua_id; auto lua_porder = ai_lua_find_player_order(Player_orders[command].lua_id); message = lua_porder->ai_message; if(ainfo->target_objnum != -1) lua_target = { object_ship_wing_point_team(&Ships[Objects[ainfo->target_objnum].instance]), {} }; break; } } if (ai_mode != AI_GOAL_NONE) { Assert(ai_submode != -1234567); ai_add_wing_goal_player(ai_goal_type::PLAYER_WING, ai_mode, ai_submode, target_shipname, wingnum, int_data, float_data, lua_target); if (update_history == SQUADMSG_HISTORY_ADD_ENTRY) { hud_add_issued_order(Wings[wingnum].name, command); hud_update_last_order(target_shipname, player_num, special_index); } else if (update_history == SQUADMSG_HISTORY_UPDATE) { hud_update_last_order(target_shipname, player_num, special_index); } } } // if we're in multiplayer mode, and we're the server, determine if this virtual squadmate order should be // sent to other players in the game as an actual "order" if ((Game_mode & GM_MULTIPLAYER) && (message != MESSAGE_NOSIR)) { // if there's at least one ai ship which got the command, let the response come through if (multi_msg_eval_wing_squadmsg(wingnum, command, ainfo, player_num)) { send_message = 0; } } // this is the _response_ message_sent = 0; if (send_message) { int ship_num; // get a random ship in the wing to send the message to the player ship_num = ship_get_random_ship_in_wing(wingnum, SHIP_GET_UNSILENCED, 0.0, 0, command); // in multiplayer, its possible that all ships in a wing are players. so we'll just send from a random ship if (ship_num == -1 && (Game_mode & GM_MULTIPLAYER)) { ship_num = ship_get_random_ship_in_wing(wingnum, SHIP_GET_ANY_SHIP, 0.0, 0, command); } // only send message if ship is found. There appear to be cases where all ships // in a wing die in the same frame causing the wing to appear valid in the message // menu, but the get_random_ship* functions won't return dying ships. // Karajorma - No valid ships will be found if all the remaining ships have been silence either. if (ship_num != -1) { message_send_builtin(message, &Ships[ship_num], target, player_num, message_team_filter); message_sent = 1; } } return message_sent; } // return number of available reinforcements, 0 if none available int hud_squadmsg_reinforcements_available(int team) { int count = 0; for (const auto &reinforcement: Reinforcements) { // no more left if (reinforcement.num_uses >= reinforcement.uses ){ continue; } // incorrect team if ( team != ship_get_reinforcement_team(reinforcement) ){ continue; } // check the arrival cue sexpression of the ship/wing of this reinforcement. // Goober5000 - if it can't arrive, it doesn't count. This should check // for SEXP_FALSE as well as SEXP_KNOWN_FALSE, otherwise you end up with // a reinforcement menu containing no valid selections. if (int wingnum = wing_name_lookup(reinforcement.name, 1); wingnum != -1) { Assert ( Wings[wingnum].arrival_cue >= 0 ); if ( Sexp_nodes[Wings[wingnum].arrival_cue].value == SEXP_FALSE || Sexp_nodes[Wings[wingnum].arrival_cue].value == SEXP_KNOWN_FALSE ){ continue; } } else { p_object *p_objp; p_objp = mission_parse_get_arrival_ship( reinforcement.name ); if ( p_objp != NULL ) { if ( Sexp_nodes[p_objp->arrival_cue].value == SEXP_FALSE || Sexp_nodes[p_objp->arrival_cue].value == SEXP_KNOWN_FALSE ){ continue; } } else { Int3(); // allender says bogus! reinforcement should be here since it wasn't a wing! continue; } } count++; } return count; } // function to put up window in upper right to allow for player to select the type // of entity to select for a message (i.e. a wing or a ship) void hud_squadmsg_type_select( ) { int k, i; int num_order_types = NUM_COMM_ORDER_TYPES; int lua_order_count = 0; // Now get a list of all lua categories to add. Meow. SCP_vector lua_cat_list = ai_lua_get_general_order_categories(); num_order_types += (int)lua_cat_list.size(); MsgItems.clear(); Rebuild_MsgItems = false; // Add the items 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 } else { MsgItems.push_back({0, 1, lua_cat_list[i - NUM_COMM_ORDER_TYPES]}); // assume active } } // check to see if the player is a traitor. If so, then he will not // be able to do anything from this menu if ((Player_ship != NULL) && (Player_ship->team == Iff_traitor)) { for (auto &item : MsgItems) item.active = 0; 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; } 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; } 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 ){ hud_squadmsg_do_mode( SM_MODE_SHIP_SELECT ); } else if ( k == TYPE_WING_ITEM ) { hud_squadmsg_do_mode( SM_MODE_WING_SELECT ); } else if ( k == TYPE_ALL_FIGHTERS_ITEM ) { hud_squadmsg_do_mode( SM_MODE_ALL_FIGHTERS ); } if ( Msg_shortcut_command == -1 ) { if ( k == TYPE_REINFORCEMENT_ITEM ) { 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) { hud_squadmsg_do_mode( SM_MODE_REPAIR_REARM ); } else if (k == TYPE_REPAIR_REARM_ABORT_ITEM && !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]; hud_squadmsg_do_mode( SM_MODE_GENERAL ); } } } } // function to display a list of ships to send a command to void hud_squadmsg_ship_select() { int k; if ( Rebuild_MsgItems ) { MsgItems.clear(); Rebuild_MsgItems = false; hud_squadmsg_count_ships( 1 ); } strcpy_s(Squad_msg_title, XSTR( "Select Ship", 317)); k = hud_squadmsg_get_key(); if ( k != -1 ) { // if true, we have selected a ship. if ( Msg_shortcut_command == -1 ) { Msg_instance = MsgItems[k].instance; // store the instance id in a global hud_squadmsg_do_mode( SM_MODE_SHIP_COMMAND ); // and move to a new mode } else { // we must convert the Msg_shortcut_command value to a value that the message // system normally uses to select a command. Since the menu //Assert( Msg_shortcut_command != IGNORE_TARGET_ITEM ); hud_squadmsg_send_ship_command( MsgItems[k].instance, Msg_shortcut_command, 1, SQUADMSG_HISTORY_ADD_ENTRY); hud_squadmsg_toggle(); } } } // function to display a list of wings to send a command to void hud_squadmsg_wing_select() { int k; if ( Rebuild_MsgItems ) { MsgItems.clear(); Rebuild_MsgItems = false; hud_squadmsg_count_wings( 1 ); } strcpy_s(Squad_msg_title, XSTR( "Select Wing", 318)); k = hud_squadmsg_get_key(); if ( k != -1 ) { // if true, we have selected a ship. if ( Msg_shortcut_command == -1 ) { // do normal menu stuff when no hoykey active Msg_instance = MsgItems[k].instance; // store the instance id in a global hud_squadmsg_do_mode( SM_MODE_WING_COMMAND ); // and move to a new mode } else { //Assert( Msg_shortcut_command != IGNORE_TARGET_ITEM ); hud_squadmsg_send_wing_command( MsgItems[k].instance, Msg_shortcut_command, 1, SQUADMSG_HISTORY_ADD_ENTRY ); hud_squadmsg_toggle(); } } } // 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() { if ( Msg_shortcut_command == -1 ) { Msg_instance = MESSAGE_ALL_FIGHTERS; hud_squadmsg_do_mode( SM_MODE_SHIP_COMMAND ); } else { hud_squadmsg_send_to_all_fighters( Msg_shortcut_command ); hud_squadmsg_toggle(); } } // called to actually bring in a reinforcement. For single player games, always gets called. // for multiplayer games, always called on the server side. Clients should never get here void hud_squadmsg_call_reinforcement(reinforcements &reinforcement, int /*player_num*/) { int i, delay; p_object *p_objp; // safety net mainly for multiplayer servers in case some odd data desync occurs between // server and clients if ( MULTIPLAYER_MASTER && (reinforcement.num_uses >= reinforcement.uses) ) { return; } // check to see if the reinforcement called was a wing. for (i = 0; i < Num_wings; i++ ) { if ( !stricmp(reinforcement.name, Wings[i].name) ) { // if the wing is currently present, skip this request so we don't waste a "use" if (Wings[i].current_count > 0) { return; } // found a wingname. Call the parse function to create all the ships in this wing // we must set the arrival cue of the wing to true, otherwise, this won't work!! Wings[i].flags.remove(Ship::Wing_Flags::Reinforcement); Wings[i].flags.set(Ship::Wing_Flags::Reset_reinforcement); // set up the arrival delay. If it is 0, then make is some random number of seconds delay = reinforcement.arrival_delay; if ( delay == 0 ) delay = (int)(frand() * 3.0) + 3; Wings[i].arrival_delay = timestamp(delay * 1000); break; } } // if we found no wing name that matched the reinforcement name, then look for a ship // of the same name if ( i == Num_wings ) { p_objp = mission_parse_get_arrival_ship( reinforcement.name ); if ( p_objp ) { // by resetting the reinforcement flag, we will allow code which normally handles arrivals // to make this reinforcement arrive. Doing so keeps the data structures clean. p_objp->flags.remove(Mission::Parse_Object_Flags::SF_Reinforcement); // set up the arrival delay delay = reinforcement.arrival_delay; if ( delay == 0 ) delay = (int)(frand() * 3.0) + 3; // between 3 and 6 seconds to arrive p_objp->arrival_delay = timestamp(delay * 1000); } else { Int3(); // get allender -- I don't think that this can happen!!!! return; } } // increment the number of times this is used. Incremented here on single player and multiplayer // server side only. Clients keep track of own count when they actually call something in. reinforcement.num_uses++; // commented out on 9/9/98 because these messages simply are not used /* // now play a message (if there is one to play) for this reinforcement arrival. The first for loop // determine how many messages there are to play, since the array is packet. Then, if >= 1 message // to play, play one for (i = 0; i < MAX_REINFORCEMENT_MESSAGES; i++ ) if ( !strlen(rp->yes_messages[i]) ) break; //if ( i > 0 ) // message_send_to_player( rp->yes_messages[Random::next(i)], reinforcement.name, MESSAGE_PRIORITY_NORMAL, HUD_SOURCE_FRIENDLY ); */ mission_log_add_entry(LOG_PLAYER_CALLED_FOR_REINFORCEMENT, reinforcement.name, nullptr); } // function to display a list of reinforcements available to the player void hud_squadmsg_reinforcement_select() { if ( Rebuild_MsgItems ) { MsgItems.clear(); Rebuild_MsgItems = false; int i = -1; for (const auto &reinforcement: Reinforcements) { ++i; // start at 0; increment at top of loop due to continues // don't put reinforcements onto the list that have already been used up. if (reinforcement.num_uses >= reinforcement.uses) { continue; } // don't put items which are not on my team if ((Player_ship != nullptr) && (ship_get_reinforcement_team(reinforcement) != Player_ship->team)) { continue; } // check the arrival cue sexpression of the ship/wing of this reinforcement. // Goober5000 - if it can't arrive, it doesn't count. This should check // for SEXP_FALSE as well as SEXP_KNOWN_FALSE, otherwise you end up with // a reinforcement menu containing no valid selections. const char *r_name; if (int wingnum = wing_name_lookup(reinforcement.name, 1); wingnum != -1) { Assert ( Wings[wingnum].arrival_cue >= 0 ); if ( Sexp_nodes[Wings[wingnum].arrival_cue].value == SEXP_FALSE || Sexp_nodes[Wings[wingnum].arrival_cue].value == SEXP_KNOWN_FALSE ){ continue; } r_name = Wings[wingnum].get_display_name(); // this will handle getting rid of the hash if necessary } else { p_object *p_objp; p_objp = mission_parse_get_arrival_ship(reinforcement.name ); if ( p_objp != NULL ) { if ( Sexp_nodes[p_objp->arrival_cue].value == SEXP_FALSE || Sexp_nodes[p_objp->arrival_cue].value == SEXP_KNOWN_FALSE ){ continue; } } else { Int3(); // allender says bogus! reinforcement should be here since it wasn't a wing! continue; } r_name = p_objp->get_display_name(); // this will handle getting rid of the hash if necessary } MsgItems.push_back({i, (reinforcement.flags & RF_IS_AVAILABLE) ? 1 : 0, r_name}); // set the item to active if reinforcement is available } } // hud_squadmsg_display_menu( "Select Reinforcement" ); strcpy_s(Squad_msg_title, XSTR( "Select Ship/Wing", 319)); // AL 11-14-97: Reinforcement didn't fit, so using this for now int k = hud_squadmsg_get_key(); if (k != -1) { int rnum; hud_squadmsg_toggle(); // take us out of message mode rnum = MsgItems[k].instance; // check to see if trying to call a reinforcement not yet available. If so, maybe play message, but // definately bail if ( MsgItems[k].active == 0 ) { return; } // in single player, or a multiplayer master, call in the reinforcement. Clients send a packet to the // server if ( MULTIPLAYER_CLIENT ) { Reinforcements[rnum].num_uses++; // increment this variable here since clients need to maintain a valid count send_player_order_packet(SQUAD_MSG_REINFORCEMENT, rnum, 0); } else { hud_squadmsg_call_reinforcement(Reinforcements[rnum]); } } } bool hud_order_comparator(size_t a, size_t b) { return Player_orders[a].ordering < Player_orders[b].ordering; } // function to display list of commands for a ship void hud_squadmsg_ship_command() { int k; std::set orders; std::set default_orders(&hud_order_comparator); // when adding ship commands, we must look at the type of ship, and what messages that // ship allows. First, place all messages that are possible onto the menu, then // 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 ) { 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()); } else { orders = default_messages; default_orders.insert(default_messages.cbegin(), default_messages.cend()); } MsgItems.clear(); Rebuild_MsgItems = false; for(size_t order_id : default_orders) { MsgItems.push_back({(int)order_id, 0, Player_orders[order_id].localized_name}); // check the bit to see if the command is active if (orders.find(order_id) != orders.end()) 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)) MsgItems.back().active = 0; // do some other checks to possibly gray out other items. // if no target, remove any items which are associated with the players target if (!hud_squadmsg_is_target_order_valid(order_id, nullptr)) MsgItems.back().active = 0; // 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) { ship_obj* so; ship* shipp; bool partial_accept, all_accept; // value which tells us what to do with menu item all_accept = true; partial_accept = false; for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { 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. 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())) 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 } if (!all_accept) { // either modify the text if a partial accept, or grey it out if no one accepts if (partial_accept) { MsgItems.back().text += XSTR("(*)", 320); } else { MsgItems.back().active = 0; } } } } strcpy_s( Squad_msg_title, XSTR( "What Command", 321) ); k = hud_squadmsg_get_key(); // when we get a valid goal, we must add the goal to the ai ship's goal list if ( k != -1 ) { 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 hud_squadmsg_send_ship_command(Msg_instance, MsgItems[k].instance, 1, SQUADMSG_HISTORY_ADD_ENTRY); hud_squadmsg_toggle(); } } void hud_squadmsg_msg_general() { int k; MsgItems.clear(); Rebuild_MsgItems = false; for (size_t order_id = 0; order_id < Player_orders.size(); order_id++) { if (Player_orders[order_id].lua_id <= 0) { continue; } auto lua_porder = ai_lua_find_player_order(Player_orders[order_id].lua_id); //If it's not a general order then do not add it. if (!lua_porder->generalOrder) { continue; } //If it's not part of the selected category then do not add it. if (lua_porder->category != Lua_sqd_msg_cat) { continue; } //Only add it if it is enabled for the mission if (lua_porder->cur_enabled) { MsgItems.push_back({Player_orders[order_id].lua_id, (int)lua_porder->cur_valid, Player_orders[order_id].localized_name}); // do some other checks to possibly gray out other items. // if no target, remove any items which are associated with the players target if (!hud_squadmsg_is_target_order_valid(order_id, nullptr)) MsgItems.back().active = 0; } } strcpy_s(Squad_msg_title, XSTR("What Command", 321)); k = hud_squadmsg_get_key(); // when we get a valid goal, we must add the goal to the ai ship's goal list if (k != -1) { Assert( k < sz2i(MsgItems.size()) ); ai_lua_start_general(MsgItems[k].instance, Player_ai->target_objnum); hud_squadmsg_toggle(); } } // function to display list of command for a wing void hud_squadmsg_wing_command() { int k; wing *wingp; int shipnum; std::set orders; std::set default_orders(&hud_order_comparator); // when adding commands for wings, we will look at all of the ships in the wing, and create // the order list from that set of ships. In the future, we may want to do something else.... wingp = &Wings[Msg_instance]; // or together all of the orders for all the ships in the wing for (int i = 0; i < wingp->current_count; i++ ) { const auto& setAdd = ship_get_default_orders_accepted( &Ship_info[Ships[wingp->ship_index[i]].ship_info_index] ); default_orders.insert(setAdd.begin(), setAdd.end()); } default_orders.erase(CAPTURE_TARGET_ITEM); // we cannot capture any target with a wing. MsgItems.clear(); Rebuild_MsgItems = false; shipnum = wingp->ship_index[wingp->special_ship]; Assertion(shipnum >= 0, "Special ship (%d) for wing '%s' has a negative ship_index (%d). This should not happen; get a coder!\n", wingp->special_ship, wingp->name, shipnum); orders = Ships[shipnum].orders_accepted; // get the orders that the wing leader will accept for ( size_t order_id : default_orders ) { // add the set of default orders to the comm menu. We will currently allow all messages // to be available in the wing. MsgItems.push_back({(int)order_id, 0, Player_orders[order_id].localized_name}); // possibly grey out the menu item depending on whether or not the "wing" will accept this order // the "wing" won't accept the order if the first ship in the wing doesn't accept it. if ( orders.find(order_id) != orders.end() ) MsgItems.back().active = 1; // do some other checks to possibly gray out other items. // if no target, remove any items which are associated with the players target if ( !hud_squadmsg_is_target_order_valid((int)order_id, 0, true) ) MsgItems.back().active = 0; // if no ship in the wing can depart then gray out the departure order if (order_id == DEPART_ITEM) { if (MsgItems.back().active > 0) { int active = 0; for (int i = 0; i < wingp->current_count; i++) { if (hud_squadmsg_ship_order_valid(wingp->ship_index[i], (int)order_id)) { active = 1; break; } } MsgItems.back().active = active; } } } strcpy_s(Squad_msg_title, XSTR( "What Command", 321) ); k = hud_squadmsg_get_key(); if ( k != -1 ) { // ignore target gets sent to everyone. /* Goober5000 - not anymore if (MsgItems[k].instance == IGNORE_TARGET_ITEM) hud_squadmsg_send_to_all_fighters(MsgItems[k].instance); else */ hud_squadmsg_send_wing_command(Msg_instance, MsgItems[k].instance, 1); hud_squadmsg_toggle(); } } //---------------------------------------------------------- // external entry points below!!!! // function is called once per mission start. Initializes those values // which only need to be inited once per mission. void hud_init_squadmsg( void ) { Msg_eat_key_timestamp = timestamp(0); Squadmsg_history.clear(); } // external entry point into code which changes the messaging mode based on the // previous player flag value. I thought it better to isolate all system changes // in this code. void hud_squadmsg_toggle() { // if the emp effect is active, always ignore this if(emp_active_local()){ return; } // if entering this mode, must setup messaging system. Don't start squadmessging if // the player is dead. if ( !(Player->flags & PLAYER_FLAGS_MSG_MODE) ) { if ( Game_mode & GM_DEAD ){ return; } if ( (Game_mode & GM_MULTIPLAYER) && NETPLAYER_IS_OBSERVER(Net_player) ){ return; } hud_squadmsg_start(); } else { hud_squadmsg_end(); } Player->flags ^= PLAYER_FLAGS_MSG_MODE; } // extern entry point to allow messaging of enemies void hud_enemymsg_toggle() { hud_squadmsg_toggle(); // if we just entered message mode, turn on var that says to message enemies if ( Player->flags & PLAYER_FLAGS_MSG_MODE ) Msg_enemies = 1; } // external entry point into code when a keyboard shortcut is used for a command // we are passed in an ID for the command to set internal variables. This command // will be used in place of the last menu in the messaging code void hud_squadmsg_shortcut( int command ) { // check if the communications system is capable of sending a message if ( (hud_communications_state(Player_ship) != COMM_OK) && (command != REARM_REPAIR_ME_ITEM) ) { HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Messaging is restricted due to communications damage", 331)); return; } // observers in multiplayer games cannot have this active either if ( (Game_mode & GM_MULTIPLAYER) && NETPLAYER_IS_OBSERVER(Net_player) ) return; // in multiplayer and I cannot message, don't allow anything except calling in for rearm if ( (Game_mode & GM_MULTIPLAYER) && !multi_can_message(Net_player) && (command != REARM_REPAIR_ME_ITEM) ) gamesnd_play_error_beep(); // player ships which turn traitor cannot rearm if ( Player_ship->team == Iff_traitor ) return; if ( Player->flags & PLAYER_FLAGS_MSG_MODE ) // we are already in messaging mode -- maybe do sometime more interesting? return; hud_squadmsg_toggle(); Msg_shortcut_command = command; // save the command for later use if (Msg_shortcut_command == CAPTURE_TARGET_ITEM) // some commands only apply to wings or ships Squad_msg_mode = SM_MODE_SHIP_SELECT; // -- don't offer choice /* Goober5000 - again with the stupid logic else if ( Msg_shortcut_command == IGNORE_TARGET_ITEM ) { // ignoring target applied to all ships hud_squadmsg_toggle(); // turns off mode which was turned on above hud_squadmsg_send_to_all_fighters( Msg_shortcut_command ); } */ } // external entry point which is called when the player hits a selection key (1-0) while in messaging // mode. If we are in messaging mode, send the shortcut command to the ships that are part of the // passed in selection set. If there is no shortcut command, do nothing. Returns 1 if the key // was used, else 0. This return value is used to tell the key control system that it should // call the normal targeting selection stuff. int hud_squadmsg_hotkey_select( int k ) { htarget_list *hitem, *plist; int send_message; object *objp; Assert ( Player->flags & PLAYER_FLAGS_MSG_MODE ); if ( Msg_shortcut_command == -1 ) return 0; Assert ( (k >= 0) && (k < MAX_KEYED_TARGETS) ); plist = &(Player->keyed_targets[k]); if ( EMPTY(plist) ) // be sure that we have at least one ship in the list return 0; send_message = 1; // for each ship in the selection set list, send the shortcut command that the player // previously entered. Be sure to check that we are not trying to send a command to // an enemy ship. for ( hitem = GET_FIRST(plist); hitem != END_OF_LIST(plist); hitem = GET_NEXT(hitem) ) { objp = hitem->objp; Assert ( objp->type == OBJ_SHIP ); if ( Ships[objp->instance].team != Player_ship->team ) continue; // be sure that this ship can accept this command if (!Ships[objp->instance].orders_accepted.contains(Msg_shortcut_command)) continue; hud_squadmsg_send_ship_command( objp->instance, Msg_shortcut_command, send_message, SQUADMSG_HISTORY_ADD_ENTRY ); send_message = 0; } hud_squadmsg_toggle(); // turn off messaging mode return 1; } // the next function is called once a frame when the player is messaging someone // in his squad. After a period of 5 seconds of inactivity (i.e. no keypress to // select something in the menu), the menu will disappear. This function will only // get called if the player flag PLAYER_FLAG_MSG_MODE is set. Parameter is the key // that was hit this frame int hud_squadmsg_do_frame( ) { int target_changed; Assert ( Player->flags & PLAYER_FLAGS_MSG_MODE ); // be sure that messaging mode is set!!! // if the player is now dead, or the timestamp elapsed, then get out of messaging mode. if ( (Game_mode & GM_DEAD) || timestamp_elapsed(Msg_mode_timestamp) ) { hud_squadmsg_toggle(); return 0; } Msg_key_used = 0; // check the player's current target. Change in target resets the timer target_changed = 0; if ( Msg_target_objnum != Player_ai->target_objnum ) { Msg_target_objnum = Player_ai->target_objnum; target_changed = 1; } if ( Msg_targeted_subsys != Player_ai->targeted_subsys ) { Msg_targeted_subsys = Player_ai->targeted_subsys; target_changed = 1; } // check for multiplayer mode - this is really a special case checker for support ship requesting and aborting if(MULTIPLAYER_CLIENT && (Squad_msg_mode == SM_MODE_REPAIR_REARM || Squad_msg_mode == SM_MODE_REPAIR_REARM_ABORT)){ // send the correct packet if(Squad_msg_mode == SM_MODE_REPAIR_REARM) send_player_order_packet(SQUAD_MSG_SHIP, 0, REARM_REPAIR_ME_ITEM); else send_player_order_packet(SQUAD_MSG_SHIP, 0, ABORT_REARM_REPAIR_ITEM); // make sure to toggle the mode off hud_squadmsg_toggle(); return 1; } switch( Squad_msg_mode ) { case SM_MODE_TYPE_SELECT: hud_squadmsg_type_select(); break; case SM_MODE_SHIP_SELECT: hud_squadmsg_ship_select(); break; case SM_MODE_WING_SELECT: hud_squadmsg_wing_select(); break; case SM_MODE_SHIP_COMMAND: hud_squadmsg_ship_command(); break; case SM_MODE_WING_COMMAND: hud_squadmsg_wing_command(); break; case SM_MODE_REINFORCEMENTS: hud_squadmsg_reinforcement_select(); break; case SM_MODE_REPAIR_REARM: hud_squadmsg_repair_rearm(1); // note we return right away. repair/rearm code handles messaging, etc break; case SM_MODE_REPAIR_REARM_ABORT: hud_squadmsg_repair_rearm_abort(1); // note we return right away. repair/rearm code handles messaging, etc break; case SM_MODE_ALL_FIGHTERS: hud_squadmsg_msg_all_fighters(); break; case SM_MODE_GENERAL: hud_squadmsg_msg_general(); break; default: Int3(); // get allender -- invalid mode in messaging system break; } // be sure to reset the clip region HUD_reset_clip(); // JAS: Is this needed? if ( Msg_key_used || target_changed ) { Msg_mode_timestamp = _timestamp(DEFAULT_MSG_TIMEOUT); return 1; } else return 0; } void hud_add_issued_order(const char *name, int order) { squadmsg_history *latest_order = new squadmsg_history(); if (!strcmp(name, "All Fighters")) { latest_order->order_to = -1; } else { latest_order->order_to = get_parse_name_index(name); } latest_order->order = order; latest_order->order_time = Missiontime; //stick it in history Squadmsg_history.push_back(*latest_order); delete latest_order; } void hud_update_last_order(const char *target, int order_source, int special_index) { squadmsg_history *latest_order = &Squadmsg_history.back(); if (target) { latest_order->target = get_parse_name_index(target); } if (MULTIPLAYER_MASTER && order_source != -1) { latest_order->order_from = Objects[Net_players[order_source].m_player->objnum].instance; } else if (order_source == -1) { latest_order->order_from = Player_obj->instance; } latest_order->special_index = special_index; } 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) { ship_or_wing = get_parse_name_index(to); } // get the target ship if (target_name != NULL) { target = get_parse_name_index(target_name); } if (MULTIPLAYER_MASTER && (from != NULL)) { source = ship_name_lookup(from, 1); } for (i = 0; i < (int) Player_orders.size(); i++) if (!stricmp(order_name, Player_orders[i].hud_name.c_str())) { order = i; break; } // Goober5000 - if not found, check compatibility if (order == -1) { if (!stricmp(order_name, "Attack my target")) { order = ATTACK_TARGET_ITEM; } } Assert(order != -1); for (i = 0; i < (int)Squadmsg_history.size(); i++) { if (Squadmsg_history[i].order == order) { if (ship_or_wing == Squadmsg_history[i].order_to) { if (target == -1 || Squadmsg_history[i].target == target) { if ((!timestamp) || (Squadmsg_history[i].order_time + i2f(timestamp) >= Missiontime) ) { // In multiplayer games we may wish to check who sent the order if (MULTIPLAYER_MASTER) { if ((source != -1) && (Squadmsg_history[i].order_from != source)) { continue; } } // some orders will have additional arguments if (order & DISABLE_SUBSYSTEM_ITEM) { if (special_argument == NULL) { continue; } int target_ship = ship_name_lookup(target_name); if(target_ship<0) { continue; } int subsys_index = ship_find_subsys(&Ships[target_ship], special_argument); // if the order is for s different subsystem if (Squadmsg_history[i].special_index != subsys_index) { continue; } } // if we make it this far then the orders match return 1; } } } } } return 0; } void Hud_set_lua_key(int selection) { Msg_key = selection; Msg_key_set_from_scripting = true; } HudGaugeSquadMessage::HudGaugeSquadMessage(): HudGauge(HUD_OBJECT_SQUAD_MSG, HUD_MESSAGE_BOX, false, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeSquadMessage::initialize() { flash_timer[0] = timestamp(1); flash_timer[1] = timestamp(1); flash_flag = false; HudGauge::initialize(); } void HudGaugeSquadMessage::initHeaderOffsets(int x, int y) { Header_offsets[0] = x; Header_offsets[1] = y; } void HudGaugeSquadMessage::initItemStartOffsets(int x, int y) { Item_start_offsets[0] = x; Item_start_offsets[1] = y; } void HudGaugeSquadMessage::initMiddleFrameStartOffsetY(int y) { Middle_frame_start_offset_y = y; } void HudGaugeSquadMessage::initBottomBgOffset(int offset) { bottom_bg_offset = offset; } void HudGaugeSquadMessage::initItemHeight(int h) { Item_h = h; } void HudGaugeSquadMessage::initItemOffsetX(int x) { Item_offset_x = x; } void HudGaugeSquadMessage::initPgUpOffsets(int x, int y) { Pgup_offsets[0] = x; Pgup_offsets[1] = y; } void HudGaugeSquadMessage::initPgDnOffsets(int x, int y) { Pgdn_offsets[0] = x; Pgdn_offsets[1] = y; } void HudGaugeSquadMessage::initShipNameMaxWidth(int w) { Ship_name_max_width = w; } void HudGaugeSquadMessage::initBitmaps(char *fname_top, char *fname_middle, char *fname_bottom) { Mbox_gauge[0].first_frame = bm_load_animation(fname_top, &Mbox_gauge[0].num_frames); if ( Mbox_gauge[0].first_frame == -1 ) { Warning(LOCATION, "Could not load in ani: %s\n", fname_top); } Mbox_gauge[1].first_frame = bm_load_animation(fname_middle, &Mbox_gauge[1].num_frames); if ( Mbox_gauge[1].first_frame == -1 ) { Warning(LOCATION, "Could not load in ani: %s\n", fname_middle); } Mbox_gauge[2].first_frame = bm_load_animation(fname_bottom, &Mbox_gauge[2].num_frames); if ( Mbox_gauge[2].first_frame == -1 ) { Warning(LOCATION, "Could not load in ani: %s\n", fname_bottom); } } void HudGaugeSquadMessage::startFlashPageScroll(int duration) { flash_timer[0] = timestamp(duration); } bool HudGaugeSquadMessage::maybeFlashPageScroll(bool flash_fast) { bool draw_bright = false; if(!timestamp_elapsed(flash_timer[0])) { if(timestamp_elapsed(flash_timer[1])) { if(flash_fast) { flash_timer[1] = timestamp(fl2i(TBOX_FLASH_INTERVAL/2.0f)); } else { flash_timer[1] = timestamp(TBOX_FLASH_INTERVAL); } flash_flag = !flash_flag; } if(flash_flag) { draw_bright = true; } } return draw_bright; } bool HudGaugeSquadMessage::canRender() const { if(hud_disabled_except_messages() && !message_gauge) { return false; } if (hud_disabled()) { return false; } if ( !(Game_detail_flags & DETAIL_FLAG_HUD) ) { return false; } if ((Viewer_mode & disabled_views)) { return false; } if ((Viewer_mode & (VM_CHASE)) == 0 && only_render_in_chase_view) { return false; } if (render_for_cockpit_toggle > 0) { bool cockpit_active = ship_render_player_cockpit_active(); if (cockpit_active && (render_for_cockpit_toggle == 2)) { return false; } else if (!cockpit_active && (render_for_cockpit_toggle == 1)) { return false; } } if (!( Player->flags & PLAYER_FLAGS_MSG_MODE )) { return false; } if (scripting_render_override) { return false; } return true; } void HudGaugeSquadMessage::render(float /*frametime*/, bool config) { const char* title = Squad_msg_title; if (config) { title = XSTR("Message What", 316); } int x = position[0]; int y = position[1]; float scale = 1.0; if (config) { std::tie(x, y, scale) = hud_config_convert_coord_sys(position[0], position[1], base_w, base_h); } // setup color/font info setGaugeColor(HUD_C_NONE, config); // draw top of frame if ( Mbox_gauge[0].first_frame >= 0 ) { renderBitmap(Mbox_gauge[0].first_frame, x, y, scale, config); } // hud_set_bright_color(); setGaugeColor(HUD_C_BRIGHT, config); if ( title ) { renderString(x + Header_offsets[0], y + fl2i(Header_offsets[1] * scale), title, scale, config); } int msg_items_size = sz2i(MsgItems.size()); int nitems; if (!config) { if (msg_items_size < MAX_MENU_DISPLAY) nitems = msg_items_size; else { if (First_menu_item == 0) // First_menu_item == 0 means first page of items nitems = MAX_MENU_DISPLAY; else if (msg_items_size - First_menu_item <= MAX_MENU_DISPLAY) // check if remaining items fit on one page nitems = msg_items_size - First_menu_item; else { nitems = MAX_MENU_DISPLAY; } } } else { nitems = 6; } int sx = x + fl2i(Item_start_offsets[0] * scale); int sy = y + fl2i(Item_start_offsets[1] * scale); int bx = x; // global x-offset where bitmap gets drawn int by = y + fl2i(Middle_frame_start_offset_y * scale); // global y-offset where bitmap gets drawn bool none_valid = true; // variable to tell us whether all items in the menu are valid or not // use another variable to tell us whether we can message or not. bool messaging_allowed = true; if (!config && (Game_mode & GM_MULTIPLAYER) && !multi_can_message(Net_player) ){ messaging_allowed = false; } for (int i = 0; i < nitems; i++ ) { int item_num; bool isSelectedItem = (i == Selected_menu_item); char text[256]; if (!config) { strcpy_s(text, MsgItems[First_menu_item + i].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]); } // blit the background setGaugeColor(HUD_C_NONE, config); if ( Mbox_gauge[1].first_frame >= 0 ) { renderBitmap(Mbox_gauge[1].first_frame, bx, by, scale, config); } by += fl2i(Item_h * scale); // set the text color if (!config && (MsgItems[First_menu_item + i].active > 0)) { setGaugeColor(HUD_C_BRIGHT, config); } else if (isSelectedItem) { setGaugeColor(HUD_C_NORMAL, config); } else { setGaugeColor(HUD_C_DIM, config); } bool item_visible = config ? (!Hide_main_rearm_items_in_comms_gauge) || ((i != TYPE_REPAIR_REARM_ITEM) && (i != TYPE_REPAIR_REARM_ABORT_ITEM)) : (MsgItems[First_menu_item + i].active >= 0); if (item_visible) { // first print an icon to indicate selected item item_num = (i + 1) % MAX_MENU_DISPLAY; if (isSelectedItem && Display_selector) { renderPrintfWithGauge(sx, sy, EG_SQ1 + i, scale, config, XSTR(">>", 1887), item_num); //allow modders to change string and add number } // or do the number else { renderPrintfWithGauge(sx, sy, EG_SQ1 + i, scale, config, XSTR("%1d.", 1886), item_num); } // then the text font::force_fit_string(text, 255, fl2i(Ship_name_max_width * scale), scale); renderString(sx + fl2i(Item_offset_x * scale), sy, EG_SQ1 + i, text, scale, config); sy += fl2i(Item_h * scale); } // if we have at least one item active, then set the variable so we don't display any // message about no active items if (config || (MsgItems[First_menu_item+i].active > 0) ) none_valid = false; } // maybe draw an extra line in to make room for [pgdn], or for the 'no active items' // display if (!config && (!messaging_allowed || none_valid || ((First_menu_item + nitems) < msg_items_size) || (Msg_shortcut_command != -1))) { // blit the background setGaugeColor(HUD_C_NONE, config); if ( Mbox_gauge[1].first_frame >= 0 ) { renderBitmap(Mbox_gauge[1].first_frame, bx, by, scale, config); } by += fl2i(Item_h * scale); } // draw the bottom of the frame setGaugeColor(HUD_C_NONE, config); if ( Mbox_gauge[2].first_frame >= 0 ) { renderBitmap(Mbox_gauge[2].first_frame, bx, by + fl2i(bottom_bg_offset * scale), scale, config); } // determine if we should put the text "[more]" at top or bottom to indicate you can page up or down if (!config) { startFlashPageScroll(); maybeFlashPageScroll(); } if ( First_menu_item > 0 ) { renderPrintf(x + fl2i(Pgup_offsets[0] * scale), y + fl2i(Pgup_offsets[1] * scale), scale, config, "%s", XSTR( "[pgup]", 312) ); } if ( (First_menu_item + nitems) < msg_items_size ) { renderPrintf(x + fl2i(Pgdn_offsets[0] * scale), y + fl2i(Pgdn_offsets[1] * scale), scale, config, "%s", XSTR( "[pgdn]", 313)); } if ( messaging_allowed ) { if ( none_valid ){ renderPrintf(sx, by - fl2i(Item_h * scale) + 2, scale, config, "%s", XSTR("No valid items", 314)); } else if (!config && Msg_shortcut_command != -1){ renderPrintf( sx, by - fl2i(Item_h * scale) + 2, scale, config, "%s", comm_order_get_text(Msg_shortcut_command)); } } else { // if this player is not allowed to message, then display message saying so renderPrintf(sx, by - fl2i(Item_h * scale) + 2, scale, config, "%s", XSTR("Not allowed to message", 315)); } if (config) { int bmw, bmh; bm_get_info(Mbox_gauge[2].first_frame, &bmw, &bmh); hud_config_set_mouse_coords(gauge_config_id, x, bx + fl2i(bmw * scale), y, by + fl2i((bmh + bottom_bg_offset) * scale)); } } void HudGaugeSquadMessage::pageIn() { for (int i = 0; i < NUM_MBOX_FRAMES; i++ ) { bm_page_in_aabitmap( Mbox_gauge[i].first_frame, Mbox_gauge[i].num_frames ); } }