/* * 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 #include "ai/aigoals.h" #include "anim/animplay.h" #include "anim/packunpack.h" #include "cfile/cfile.h" #include "freespace.h" #include "gamehelp/contexthelp.h" #include "gamesequence/gamesequence.h" #include "gamesnd/gamesnd.h" #include "globalincs/alphacolors.h" #include "globalincs/linklist.h" #include "hud/hudbrackets.h" #include "hud/hudparse.h" #include "hud/hudwingmanstatus.h" #include "io/key.h" #include "io/mouse.h" #include "io/timer.h" #include "lighting/lighting.h" #include "localization/localize.h" #include "menuui/snazzyui.h" #include "mission/missionhotkey.h" #include "mission/missionparse.h" #include "missionui/missionbrief.h" #include "missionui/missionscreencommon.h" #include "missionui/missionshipchoice.h" #include "missionui/missionweaponchoice.h" #include "mod_table/mod_table.h" #include "network/multi.h" #include "network/multimsgs.h" #include "network/multiteamselect.h" #include "network/multiui.h" #include "network/multiutil.h" #include "parse/parselo.h" #include "pilotfile/pilotfile.h" #include "playerman/player.h" #include "popup/popup.h" #include "render/3d.h" #include "ship/ship.h" #include "sound/fsspeech.h" #include "species_defs/species_defs.h" #include "weapon/weapon.h" ////////////////////////////////////////////////////// // Game-wide Globals ////////////////////////////////////////////////////// char default_player_ship[255]; int Select_default_ship = 0; int Ship_select_open = 0; // This game-wide global flag is set to 1 to indicate that the ship // select screen has been opened and memory allocated. This flag // is needed so we can know if ship_select_close() needs to called if // restoring a game from the Options screen invoked from ship select int Commit_pressed; // flag to indicate that the commit button was pressed // use a flag, so the ship_create() can be done at the end of the loop ////////////////////////////////////////////////////// // Module Globals ////////////////////////////////////////////////////// static int Ship_anim_class = -1; // ship class that is playing as an animation static int Ss_delta_x, Ss_delta_y; // used to offset the carried icon to make it smoothly leave static position // UnknownPlayer // float ShipSelectScreenShipRot = 0.0f; int ShipSelectModelNum = -1; int anim_timer_start = 0; //static matrix ShipScreenOrient = IDENTITY_MATRIX; ////////////////////////////////////////////////////// // UI Data structs ////////////////////////////////////////////////////// typedef struct ss_icon_info { int icon_bmaps[NUM_ICON_FRAMES]; int current_icon_bitmap = -1; int model_index = -1; generic_anim ss_anim; ss_icon_info() { for (int &bmap : icon_bmaps) bmap = -1; generic_anim_init(&ss_anim, nullptr); } } ss_icon_info; //ss_wing_info Ss_wings[MAX_WING_BLOCKS]; // holds ui info for wings and wing slots ss_wing_info Ss_wings_teams[MAX_TVT_TEAMS][MAX_WING_BLOCKS]; ss_wing_info *Ss_wings = NULL; // ui info for ship icons, keyed by ship class; an absent entry means no icon data is loaded for that class SCP_map Ss_icons_teams[MAX_TVT_TEAMS]; SCP_map *Ss_icons = nullptr; int Ss_mouse_down_on_region = -1; int Selected_ss_class; // set to ship class of selected ship, -1 if none selected int Hot_ss_icon; // index that icon is over in list (0..MAX_WING_SLOTS-1) int Hot_ss_slot; // index for slot that mouse is over (0..MAX_WSS_SLOTS) //////////////////////////////////////////////////////////// // Ship Select UI //////////////////////////////////////////////////////////// UI_WINDOW Ship_select_ui_window; int Ship_select_overlay_id = -1; static int Ship_anim_coords[GR_NUM_RESOLUTIONS][2] = { { 257, 84 // GR_640 }, { 412, 135 // GR_1024 } }; static int Ship_info_coords[GR_NUM_RESOLUTIONS][2] = { { 28, 78 // GR_640 }, { 45, 125 // GR_1024 } }; // coordinate lookup indicies #define SHIP_SELECT_X_COORD 0 #define SHIP_SELECT_Y_COORD 1 #define SHIP_SELECT_W_COORD 2 #define SHIP_SELECT_H_COORD 3 // NK: changed from 37 to 51 for new FS2 animations #define SHIP_ANIM_LOOP_FRAME 51 #define MAX_ICONS_ON_SCREEN 4 // (x,y) pairs for ship icon and ship icon number int Ship_list_coords[GR_NUM_RESOLUTIONS][MAX_ICONS_ON_SCREEN][4] = { { {23,331,4,341}, {23,361,4,371}, {23,391,4,401}, {23,421,4,431} }, { {29,530,10,540}, {29,578,10,588}, {29,626,10,636}, {29,674,10,684} } }; // Store the x locations for the icons in the wing formations int Wing_icon_coords[GR_NUM_RESOLUTIONS][MAX_WSS_SLOTS][2] = { { {124,345}, {100,376}, {148,376}, {124,407}, {222,345}, {198,376}, {246,376}, {222,407}, {320,345}, {296,376}, {344,376}, {320,407} }, { {218,584}, {194,615}, {242,615}, {218,646}, {373,584}, {349,615}, {397,615}, {373,646}, {531,584}, {507,615}, {555,615}, {531,646} } }; ////////////////////////////////////////////////////// // List of icons to show on ship selection list ////////////////////////////////////////////////////// static SCP_vector SS_active_list; // ship classes with a positive pool count, in ascending class order static int SS_active_list_start; ////////////////////////////////////////////////////// // Background bitmaps data for ship_select ////////////////////////////////////////////////////// static const char* Ship_select_background_fname[GR_NUM_RESOLUTIONS] = { "ShipSelect", "2_ShipSelect" }; static const char* Ship_select_background_mask_fname[GR_NUM_RESOLUTIONS] = { "ShipSelect-m", "2_ShipSelect-m" }; int Ship_select_background_bitmap; ////////////////////////////////////////////////////// // Ship select specific buttons ////////////////////////////////////////////////////// #define NUM_SS_BUTTONS 4 #define SS_BUTTON_SCROLL_UP 0 #define SS_BUTTON_SCROLL_DOWN 1 #define SS_BUTTON_RESET 2 #define SS_BUTTON_DUMMY 3 // needed to capture mouse for drag/drop icons // convenient struct for handling all button controls struct ss_buttons { const char *filename; int x, y, xt, yt; int hotspot; int scrollable; UI_BUTTON button; // because we have a class inside this struct, we need the constructor below.. ss_buttons(const char *name, int x1, int y1, int xt1, int yt1, int h, int s) : filename(name), x(x1), y(y1), xt(xt1), yt(yt1), hotspot(h), scrollable(s) {} }; static ss_buttons Ship_select_buttons[GR_NUM_RESOLUTIONS][NUM_SS_BUTTONS] = { { // GR_640 ss_buttons("ssb_08", 5, 303, -1, -1, 8, 0), // SCROLL UP ss_buttons("ssb_09", 5, 454, -1, -1, 9, 0), // SCROLL DOWN ss_buttons("ssb_39", 571, 347, -1, -1, 39,0), // RESET ss_buttons("ssb_39", 0, 0, -1, -1, 99,0) // dummy for drag n' drop }, { // GR_1024 ss_buttons("2_ssb_08", 8, 485, -1, -1, 8, 0), // SCROLL UP ss_buttons("2_ssb_09", 8, 727, -1, -1, 9, 0), // SCROLL DOWN ss_buttons("2_ssb_39", 913, 556, -1, -1, 39,0), // RESET ss_buttons("2_ssb_39", 0, 0, -1, -1, 99,0) // dummy for drag n' drop } }; // ship select text #define SHIP_SELECT_NUM_TEXT 1 UI_XSTR Ship_select_text[GR_NUM_RESOLUTIONS][SHIP_SELECT_NUM_TEXT] = { { // GR_640 { "Reset", 1337, 580, 337, UI_XSTR_COLOR_GREEN, -1, &Ship_select_buttons[0][SS_BUTTON_RESET].button } }, { // GR_1024 { "Reset", 1337, 938, 546, UI_XSTR_COLOR_GREEN, -1, &Ship_select_buttons[1][SS_BUTTON_RESET].button } } }; // Mask bitmap pointer and Mask bitmap_id static bitmap* ShipSelectMaskPtr; // bitmap pointer to the ship select mask bitmap static ubyte* ShipSelectMaskData; // pointer to actual bitmap data static int Shipselect_mask_w, Shipselect_mask_h; static int ShipSelectMaskBitmap; // bitmap id of the ship select mask bitmap static MENU_REGION Region[NUM_SHIP_SELECT_REGIONS]; static int Num_mask_regions; //stuff for ht&l. vars and such extern fov_t View_zoom; extern float Canv_h2, Canv_w2; ////////////////////////////////////////////////////// // Drag and Drop variables ////////////////////////////////////////////////////// typedef struct ss_carry_icon_info { int from_slot; // slot index (0..MAX_WSS_SLOTS-1), -1 if carried from list int ship_class; // ship class of carried icon int from_x, from_y; } ss_carry_icon_info; ss_carry_icon_info Carried_ss_icon; //////////////////////////////////////////////////////////////////// // Internal function prototypes //////////////////////////////////////////////////////////////////// // render functions void draw_ship_icons(); void draw_ship_icon_with_number(int screen_offset, int ship_class); void start_ship_animation(int ship_class, int play_sound=0); // pick-up int pick_from_ship_list(int screen_offset, int ship_class); void pick_from_wing(int wb_num, int ws_num); // ui related void ship_select_button_do(int i); void ship_select_common_init(bool API_Access); void ss_reset_selected_ship(); void ss_restore_loadout(); void maybe_change_selected_wing_ship(int wb_num, int ws_num); // init functions void ss_init_pool(team_data *pteam); commit_pressed_status create_wings(); // loading/unloading void ss_init_units(); anim* ss_load_individual_animation(int ship_class); // Carry icon functions int ss_icon_being_carried(); void ss_reset_carried_icon(); void ss_set_carried_icon(int from_slot, int ship_class); #define SHIP_DESC_X 445 #define SHIP_DESC_Y 273 const char *ss_tooltip_handler(const char *str) { if (Selected_ss_class < 0) return NULL; if (!stricmp(str, NOX("@ship_name"))) { return Ship_info[Selected_ss_class].name; } else if (!stricmp(str, NOX("@ship_type"))) { return Ship_info[Selected_ss_class].type_str.get(); } else if (!stricmp(str, NOX("@ship_maneuverability"))) { return Ship_info[Selected_ss_class].maneuverability_str.get(); } else if (!stricmp(str, NOX("@ship_armor"))) { return Ship_info[Selected_ss_class].armor_str.get(); } else if (!stricmp(str, NOX("@ship_manufacturer"))) { return Ship_info[Selected_ss_class].manufacturer_str.get(); } else if (!stricmp(str, NOX("@ship_desc"))) { char *str2; int x, y, w, h; str2 = Ship_info[Selected_ss_class].desc.get(); if (str2 == NULL) return NULL; gr_get_string_size(&w, &h, str2); x = SHIP_DESC_X - w / 2; y = SHIP_DESC_Y - h / 2; gr_set_color_fast(&Color_black); gr_rect(x - 5, y - 5, w + 10, h + 10, GR_RESIZE_MENU); gr_set_color_fast(&Color_bright_white); gr_string(x, y, str2, GR_RESIZE_MENU); return str2; } return NULL; } // Is an icon being carried? int ss_icon_being_carried() { if ( Carried_ss_icon.ship_class >= 0 ) { return 1; } return 0; } // Clear out carried icon info void ss_reset_carried_icon() { Carried_ss_icon.from_slot = -1; Carried_ss_icon.ship_class = -1; } // return !0 if carried icon has moved from where it was picked up int ss_carried_icon_moved() { int mx, my; mouse_get_pos_unscaled( &mx, &my ); if ( Carried_ss_icon.from_x != mx || Carried_ss_icon.from_y != my) { return 1; } return 0; } // Set carried icon data void ss_set_carried_icon(int from_slot, int ship_class) { Carried_ss_icon.from_slot = from_slot; Carried_ss_icon.ship_class = ship_class; // Set the mouse to captured Ship_select_buttons[gr_screen.res][SS_BUTTON_DUMMY].button.capture_mouse(); } // Build up the ship selection active list, which is a list of all ships that the player // can choose from. void init_active_list() { Assert( Ss_pool != NULL ); SS_active_list.clear(); SS_active_list_start = 0; // build the active list (map iteration is in ascending class order, which is the display order) for ( const auto &[ship_class, count] : *Ss_pool ) { if ( count > 0 ) { SS_active_list.push_back(ship_class); } } } void ship_select_check_buttons() { int i; ss_buttons *b; for ( i = 0; i < NUM_SS_BUTTONS; i++ ) { b = &Ship_select_buttons[gr_screen.res][i]; if ( b->button.pressed() ) { ship_select_button_do(b->hotspot); } } } // reset the ship selection to the mission defaults void ss_reset_to_default() { if ( Game_mode & GM_MULTIPLAYER ) { Int3(); return; } ss_init_pool(&Team_data[Common_team]); ss_init_units(); init_active_list(); ss_reset_selected_ship(); ss_reset_carried_icon(); // reset weapons wl_reset_to_defaults(); start_ship_animation(Selected_ss_class, 1); } // ------------------------------------------------------------------- // ship_select_redraw_pressed_buttons() // // Redraw any ship select buttons that are pressed down. This function is needed // since we sometimes need to draw pressed buttons last to ensure the entire // button gets drawn (and not overlapped by other buttons) // void ship_select_redraw_pressed_buttons() { int i; ss_buttons *b; common_redraw_pressed_buttons(); for ( i = 0; i < NUM_SS_BUTTONS; i++ ) { b = &Ship_select_buttons[gr_screen.res][i]; if ( b->button.pressed() ) { b->button.draw_forced(2); } } } void ship_select_buttons_init() { ss_buttons *b; int i; for ( i = 0; i < NUM_SS_BUTTONS; i++ ) { b = &Ship_select_buttons[gr_screen.res][i]; b->button.create( &Ship_select_ui_window, "", b->x, b->y, 60, 30, b->scrollable); // set up callback for when a mouse first goes over a button b->button.set_highlight_action( common_play_highlight_sound ); b->button.set_bmaps(b->filename); b->button.link_hotspot(b->hotspot); } // add all xstrs for(i=0; ip_info.ship_index/MAX_WING_SLOTS,Net_player->p_info.ship_index%MAX_WING_SLOTS); } set_active_ui(&Ship_select_ui_window); Current_screen = ON_SHIP_SELECT; Ss_mouse_down_on_region = -1; Ship_select_overlay_id = help_overlay_get_index(SS_OVERLAY); help_overlay_set_state(Ship_select_overlay_id,gr_screen.res,0); if ( Ship_select_open ) { //reset the animation Ship_anim_class = -1; start_ship_animation( Selected_ss_class ); common_buttons_maybe_reload(&Ship_select_ui_window); // AL 11-21-97: this is necessary since we may returning from the hotkey // screen, which can release common button bitmaps. common_reset_buttons(); nprintf(("Alan","ship_select_init() returning without doing anything\n")); return; } nprintf(("Alan","entering ship_select_init()\n")); common_select_init(); ShipSelectMaskBitmap = bm_load(Ship_select_background_mask_fname[gr_screen.res]); if (ShipSelectMaskBitmap < 0) { if (gr_screen.res == GR_640) { Error(LOCATION,"Could not load in 'shipselect-m'!"); } else if (gr_screen.res == GR_1024) { Error(LOCATION,"Could not load in '2_shipselect-m'!"); } } Shipselect_mask_w = -1; Shipselect_mask_h = -1; // get a pointer to bitmap by using bm_lock() ShipSelectMaskPtr = bm_lock(ShipSelectMaskBitmap, 8, BMP_AABITMAP | BMP_MASK_BITMAP); ShipSelectMaskData = (ubyte*)ShipSelectMaskPtr->data; bm_get_info(ShipSelectMaskBitmap, &Shipselect_mask_w, &Shipselect_mask_h); // Set up the mask regions // initialize the different regions of the menu that will react when the mouse moves over it Num_mask_regions = 0; snazzy_menu_add_region(&Region[Num_mask_regions++], "", COMMON_BRIEFING_REGION, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", COMMON_SS_REGION, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", COMMON_WEAPON_REGION, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", COMMON_COMMIT_REGION, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", COMMON_HELP_REGION, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", COMMON_OPTIONS_REGION, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", SHIP_SELECT_SHIP_SCROLL_UP, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", SHIP_SELECT_SHIP_SCROLL_DOWN, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", SHIP_SELECT_ICON_0, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", SHIP_SELECT_ICON_1, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", SHIP_SELECT_ICON_2, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", SHIP_SELECT_ICON_3, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_0_SHIP_0, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_0_SHIP_1, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_0_SHIP_2, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_0_SHIP_3, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_1_SHIP_0, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_1_SHIP_1, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_1_SHIP_2, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_1_SHIP_3, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_2_SHIP_0, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_2_SHIP_1, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_2_SHIP_2, 0); snazzy_menu_add_region(&Region[Num_mask_regions++], "", WING_2_SHIP_3, 0); Ship_select_open = 1; // This game-wide global flag is set to 1 to indicate that the ship // select screen has been opened and memory allocated. This flag // is needed so we can know if ship_select_close() needs to called if // restoring a game from the Options screen invoked from ship select // init ship selection masks and buttons Ship_select_ui_window.create( 0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0 ); Ship_select_ui_window.set_mask_bmap(Ship_select_background_mask_fname[gr_screen.res]); Ship_select_ui_window.tooltip_handler = ss_tooltip_handler; common_buttons_init(&Ship_select_ui_window); ship_select_buttons_init(); // init ship selection background bitmap Ship_select_background_bitmap = mission_ui_background_load(Briefing->ship_select_background[gr_screen.res], Ship_select_background_fname[gr_screen.res]); // init ship selection ship model rendering window start_ship_animation( Selected_ss_class ); } // Return the ship class for the icon specified by index. Need to iterate through the active // list of icons to find out what ship class for this icon // // input: index => list index (0..3) // exit: ship class, -1 if none // int ss_get_ship_class_from_list(int index) { if ( index < 0 || index >= MAX_ICONS_ON_SCREEN ) return -1; size_t list_index = SS_active_list_start + index; if ( list_index >= SS_active_list.size() ) return -1; return SS_active_list[list_index]; } // --------------------------------------------------------------------- // maybe_pick_up_list_icon() // void maybe_pick_up_list_icon(int offset) { int ship_class; ship_class = ss_get_ship_class_from_list(offset); if ( ship_class != -1 ) { pick_from_ship_list(offset, ship_class); } } // --------------------------------------------------------------------- // maybe_change_selected_ship() // void maybe_change_selected_ship(int offset) { int ship_class; ship_class = ss_get_ship_class_from_list(offset); if ( ship_class == -1 ) return; if ( Ss_mouse_down_on_region != (SHIP_SELECT_ICON_0+offset) ) { return; } if ( Selected_ss_class == -1 ) { Selected_ss_class = ship_class; start_ship_animation(Selected_ss_class, 1); } else if ( Selected_ss_class != ship_class ) { Selected_ss_class = ship_class; start_ship_animation(Selected_ss_class, 1); } else Assert( Selected_ss_class == ship_class ); } void maybe_change_selected_wing_ship(int wb_num, int ws_num) { Assert(wb_num >= 0 && wb_num < MAX_WING_BLOCKS); Assert(ws_num >= 0 && ws_num < MAX_WING_SLOTS); Assert( (Ss_wings != NULL) && (Wss_slots != NULL) ); if ( Ss_wings[wb_num].wingnum < 0 ) { return; } if ( Selected_ss_class != -1 && Selected_ss_class != Wss_slots[wb_num*MAX_WING_SLOTS+ws_num].ship_class ) { Selected_ss_class = Wss_slots[wb_num*MAX_WING_SLOTS+ws_num].ship_class; start_ship_animation(Selected_ss_class, 1); } } // --------------------------------------------------------------------- // do_mouse_over_wing_slot() // // returns: 0 => icon wasn't dropped onto slot // 1 => icon was dropped onto slot int do_mouse_over_wing_slot(int block, int slot) { Hot_ss_slot = block*MAX_WING_SLOTS + slot; if ( !mouse_down(MOUSE_LEFT_BUTTON) ) { if ( ss_icon_being_carried() ) { if ( ss_disabled_slot(block*MAX_WING_SLOTS+slot) ) { gamesnd_play_iface(InterfaceSounds::ICON_DROP); return 0; } if ( !ss_carried_icon_moved() ) { ss_reset_carried_icon(); return 0; } ss_drop(Carried_ss_icon.from_slot, Carried_ss_icon.ship_class, Hot_ss_slot, -1); ss_reset_carried_icon(); } } else { if ( Ss_mouse_down_on_region == (WING_0_SHIP_0+block*MAX_WING_SLOTS+slot) ) { pick_from_wing(block, slot); } } return 1; } void do_mouse_over_list_slot(int index) { Hot_ss_icon = index; if ( Ss_mouse_down_on_region != (SHIP_SELECT_ICON_0+index) ){ return; } if ( mouse_down(MOUSE_LEFT_BUTTON) ) maybe_pick_up_list_icon(index); } // Icon has been dropped, but not onto a wing slot void ss_maybe_drop_icon() { if ( Drop_icon_mflag ) { if ( ss_icon_being_carried() ) { // Add back into the ship entry list if ( Carried_ss_icon.from_slot >= 0 ) { // return to list ss_drop(Carried_ss_icon.from_slot, -1, -1, Carried_ss_icon.ship_class); } else { if ( ss_carried_icon_moved() ) { gamesnd_play_iface(InterfaceSounds::ICON_DROP); } } ss_reset_carried_icon(); } } } // maybe flash a button if player hasn't done anything for a while void ss_maybe_flash_button() { if ( common_flash_bright() ) { // weapon loadout button if ( Common_buttons[Current_screen-1][gr_screen.res][2].button.button_hilighted() ) { common_flash_button_init(); } else { Common_buttons[Current_screen-1][gr_screen.res][2].button.draw_forced(1); } } } // blit any active ship information text void ship_select_blit_ship_info() { int y_start, line_height; ship_info *sip; char str[100]; color *header_clr = &Color_white; color *text = &Color_green; // if we don't have a valid ship selected, do nothing if(Selected_ss_class == -1){ return; } // get the ship class sip = &Ship_info[Selected_ss_class]; // starting line y_start = Ship_info_coords[gr_screen.res][SHIP_SELECT_Y_COORD]; line_height = gr_get_font_height() + 1; memset(str,0,100); // blit the ship class (name) gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Class",739), GR_RESIZE_MENU); y_start += line_height; if(strlen(sip->get_display_name())){ gr_set_color_fast(text); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, sip->get_display_name(), GR_RESIZE_MENU); } y_start += line_height; // blit the ship type gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Type",740), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); if(sip->type_str){ gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, sip->type_str.get(), GR_RESIZE_MENU); } else { gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, ship_get_type(sip), GR_RESIZE_MENU); } y_start+=line_height; // blit the ship length gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Length",741), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); if(sip->ship_length){ if (Lcl_gr || Lcl_pl) { // in german and polish, drop the s from Meters and make sure M is caps char *sp = strstr(sip->ship_length.get(), "Meters"); if (sp) { sp[5] = ' '; // make the old s a space now } } gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, sip->ship_length.get(), GR_RESIZE_MENU); } else if(ShipSelectModelNum >= 0) { polymodel *pm = model_get(ShipSelectModelNum); sprintf( str, "%d", fl2i(pm->maxs.xyz.z - pm->mins.xyz.z) ); strcat_s(str, " M"); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, str, GR_RESIZE_MENU); } else { gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, XSTR("Unknown", 497), GR_RESIZE_MENU); } y_start += line_height; // blit the max velocity gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Max Velocity",742), GR_RESIZE_MENU); y_start += line_height; sprintf(str, XSTR("%d m/s",743),fl2i((float)sip->max_vel.xyz.z * Hud_speed_multiplier)); gr_set_color_fast(text); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start,str, GR_RESIZE_MENU); y_start += line_height; // blit the maneuverability gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Maneuverability",744), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); if(sip->maneuverability_str){ gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, sip->maneuverability_str.get(), GR_RESIZE_MENU); } else if(ShipSelectModelNum >= 0) { int sum = fl2i(sip->rotation_time.xyz.x + sip->rotation_time.xyz.y); if(sum <= 6) strcpy_s(str, XSTR("Excellent", 1789)); else if(sum < 7) strcpy_s(str, XSTR("High", 1790)); else if(sum < 8) strcpy_s(str, XSTR("Good", 1791)); else if(sum < 9) strcpy_s(str, XSTR("Average", 1792)); else if(sum < 10) strcpy_s(str, XSTR("Poor", 1793)); else if(sum < 15) strcpy_s(str, XSTR("Very Poor", 1794)); else strcpy_s(str, XSTR("Extremely Poor", 1795)); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, str, GR_RESIZE_MENU); } else { gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, XSTR("Unknown", 497), GR_RESIZE_MENU); } y_start += line_height; // blit the armor gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Armor",745), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); if(sip->armor_str){ gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, sip->armor_str.get(), GR_RESIZE_MENU); } else { int sum = fl2i(sip->max_hull_strength + sip->max_shield_strength); if(sum <= 600) strcpy_s(str, XSTR("Light", 1796)); else if(sum <= 700) strcpy_s(str, XSTR("Average", 1797)); else if(sum <= 900) strcpy_s(str, XSTR("Medium", 1798)); else if(sum <= 1100) strcpy_s(str, XSTR("Heavy", 1799)); else if(sum <= 1300) strcpy_s(str, XSTR("Very Heavy", 1800)); else if(sum <= 2000) strcpy_s(str, XSTR("Ultra Heavy", 1801)); else if(sum <= 30000) strcpy_s(str, XSTR("Light Capital", 1802)); else if(sum <= 75000) strcpy_s(str, XSTR("Medium Capital", 1803)); else if(sum <= 200000) strcpy_s(str, XSTR("Heavy Capital", 1804)); else if(sum <= 800000) strcpy_s(str, XSTR("Very Heavy Capital", 1805)); else strcpy_s(str, XSTR("Ultra Heavy Capital", 1806)); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start,str, GR_RESIZE_MENU); } y_start += line_height; // blit the gun mounts gr_set_color_fast(header_clr); if(sip->gun_mounts) { gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Gun Mounts",746), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, sip->gun_mounts.get(), GR_RESIZE_MENU); } else if(ShipSelectModelNum >= 0) { //Calculate the number of gun mounts gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Gun Mounts",746), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); int i; int sum = 0; polymodel *pm = model_get(ShipSelectModelNum); for(i = 0; i < pm->n_guns; i++) { sum += pm->gun_banks[i].num_slots; } if(sum != 0) sprintf(str, "%d", sum); else strcpy_s(str, XSTR("None", 1673)); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, str, GR_RESIZE_MENU); } else { gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Gun Banks",1626), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); if(sip->num_primary_banks) { sprintf(str, "%d", sip->num_primary_banks); } else { strcpy_s(str, XSTR("None", 1673)); } gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, str, GR_RESIZE_MENU); } y_start += line_height; // blit the missile banks gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Missile Banks",747), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); if(sip->missile_banks){ gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, sip->missile_banks.get(), GR_RESIZE_MENU); } else { if(sip->num_secondary_banks) { sprintf(str, "%d", sip->num_secondary_banks); } else { strcpy_s(str, XSTR("None", 1673)); } gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, str, GR_RESIZE_MENU); } y_start += line_height; if(ShipSelectModelNum >= 0) { int num_turrets = 0; int x; for(x = 0; x < sip->n_subsystems; x++) { if ( (sip->subsystems[x].type == SUBSYSTEM_TURRET) && !(sip->subsystems[x].flags[Model::Subsystem_Flags::Hide_turret_from_loadout_stats]) ) num_turrets++; /* for(y = 0; y < MAX_SHIP_PRIMARY_BANKS || y < MAX_SHIP_SECONDARY_BANKS; y++) { if(y < MAX_SHIP_PRIMARY_BANKS) { if(sip->subsystems[x].primary_banks[y] != -1) { num_turrets++; break; } } if(y < MAX_SHIP_SECONDARY_BANKS) { if(sip->subsystems[x].secondary_banks[y] != -1) { num_turrets++; break; } } } */ } if(num_turrets) { gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Turrets",1627), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); sprintf(str, "%d", num_turrets); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, str, GR_RESIZE_MENU); y_start += line_height; } } // blit the manufacturer gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Manufacturer",748), GR_RESIZE_MENU); y_start += line_height; gr_set_color_fast(text); if(sip->manufacturer_str){ gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, sip->manufacturer_str.get(), GR_RESIZE_MENU); } else { gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, Species_info[sip->species].species_name, GR_RESIZE_MENU); } y_start += line_height; // blit the _short_ text description, if it exists // split the text info up if (!sip->desc || !sip->desc[0]) return; gr_set_color_fast(header_clr); gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD], y_start, XSTR("Description",1571), GR_RESIZE_MENU); y_start += line_height; int n_lines; int n_chars[MAX_BRIEF_LINES]; const char *p_str[MAX_BRIEF_LINES]; char Ship_select_ship_info_text[1500]; char Ship_select_ship_info_lines[MAX_NUM_SHIP_DESC_LINES][SHIP_SELECT_SHIP_INFO_MAX_LINE_LEN]; int Ship_select_ship_info_line_count; strcpy_s(Ship_select_ship_info_text, sip->desc.get()); if(Ship_select_ship_info_text[0] != '\0'){ // split the string into multiple lines // MageKing17: Changed to use the widths determined by Yarn here: https://scp.indiegames.us/mantis/view.php?id=3144#c16516 n_lines = split_str(Ship_select_ship_info_text, gr_screen.res == GR_640 ? 204 : 328, n_chars, p_str, MAX_NUM_SHIP_DESC_LINES, SHIP_SELECT_SHIP_INFO_MAX_LINE_LEN); // copy the split up lines into the text lines array for (int idx = 0;idx -1 ) { switch(ship_select_choice) { case SHIP_SELECT_ICON_0: do_mouse_over_list_slot(0); break; case SHIP_SELECT_ICON_1: do_mouse_over_list_slot(1); break; case SHIP_SELECT_ICON_2: do_mouse_over_list_slot(2); break; case SHIP_SELECT_ICON_3: do_mouse_over_list_slot(3); break; case WING_0_SHIP_0: if ( do_mouse_over_wing_slot(0,0) ) ship_select_choice = -1; break; case WING_0_SHIP_1: if ( do_mouse_over_wing_slot(0,1) ) ship_select_choice = -1; break; case WING_0_SHIP_2: if ( do_mouse_over_wing_slot(0,2) ) ship_select_choice = -1; break; case WING_0_SHIP_3: if ( do_mouse_over_wing_slot(0,3) ) ship_select_choice = -1; break; case WING_1_SHIP_0: if ( do_mouse_over_wing_slot(1,0) ) ship_select_choice = -1; break; case WING_1_SHIP_1: if ( do_mouse_over_wing_slot(1,1) ) ship_select_choice = -1; break; case WING_1_SHIP_2: if ( do_mouse_over_wing_slot(1,2) ) ship_select_choice = -1; break; case WING_1_SHIP_3: if ( do_mouse_over_wing_slot(1,3) ) ship_select_choice = -1; break; case WING_2_SHIP_0: if ( do_mouse_over_wing_slot(2,0) ) ship_select_choice = -1; break; case WING_2_SHIP_1: if ( do_mouse_over_wing_slot(2,1) ) ship_select_choice = -1; break; case WING_2_SHIP_2: if ( do_mouse_over_wing_slot(2,2) ) ship_select_choice = -1; break; case WING_2_SHIP_3: if ( do_mouse_over_wing_slot(2,3) ) ship_select_choice = -1; break; default: break; } // end switch } // check buttons common_check_buttons(); ship_select_check_buttons(); // Check for the mouse clicks over a region if ( ship_select_choice > -1 && snazzy_action == SNAZZY_CLICKED ) { switch (ship_select_choice) { case SHIP_SELECT_ICON_0: maybe_change_selected_ship(0); break; case SHIP_SELECT_ICON_1: maybe_change_selected_ship(1); break; case SHIP_SELECT_ICON_2: maybe_change_selected_ship(2); break; case SHIP_SELECT_ICON_3: maybe_change_selected_ship(3); break; case WING_0_SHIP_0: maybe_change_selected_wing_ship(0,0); break; case WING_0_SHIP_1: maybe_change_selected_wing_ship(0,1); break; case WING_0_SHIP_2: maybe_change_selected_wing_ship(0,2); break; case WING_0_SHIP_3: maybe_change_selected_wing_ship(0,3); break; case WING_1_SHIP_0: maybe_change_selected_wing_ship(1,0); break; case WING_1_SHIP_1: maybe_change_selected_wing_ship(1,1); break; case WING_1_SHIP_2: maybe_change_selected_wing_ship(1,2); break; case WING_1_SHIP_3: maybe_change_selected_wing_ship(1,3); break; case WING_2_SHIP_0: maybe_change_selected_wing_ship(2,0); break; case WING_2_SHIP_1: maybe_change_selected_wing_ship(2,1); break; case WING_2_SHIP_2: maybe_change_selected_wing_ship(2,2); break; case WING_2_SHIP_3: maybe_change_selected_wing_ship(2,3); break; default: break; } // end switch } ss_maybe_drop_icon(); Assert( Ss_icons != NULL ); if ( !Background_playing ) { GR_MAYBE_CLEAR_RES(Ship_select_background_bitmap); gr_set_bitmap(Ship_select_background_bitmap); gr_bitmap(0, 0, GR_RESIZE_MENU); Ship_select_ui_window.draw(); ship_select_redraw_pressed_buttons(); common_render_selected_screen_button(); } if (!Use_3d_ship_select && ((Selected_ss_class >= 0) && ((*Ss_icons)[Selected_ss_class].ss_anim.num_frames > 0))) { GR_DEBUG_SCOPE("Render ship animation"); generic_anim_render(&(*Ss_icons)[Selected_ss_class].ss_anim, (help_overlay_active(Ship_select_overlay_id)) ? 0 : frametime, Ship_anim_coords[gr_screen.res][0], Ship_anim_coords[gr_screen.res][1], true); } else { GR_DEBUG_SCOPE("Render ship models"); // The new rendering code for 3D ships courtesy your friendly UnknownPlayer :) // check we have a valid ship class selected if ( (Selected_ss_class >= 0) && (ShipSelectModelNum >= 0) ) { ship_info *sip = &Ship_info[Selected_ss_class]; float rev_rate = REVOLUTION_RATE; if (sip->is_big_ship()) { rev_rate *= 1.7f; } if (sip->is_huge_ship()) { rev_rate *= 3.0f; } model_render_params render_info; if (sip->uses_team_colors) { render_info.set_team_color(sip->default_team_name, "none", 0, 0); } if (sip->replacement_textures.size() > 0) { render_info.set_replacement_textures(ShipSelectModelNum, sip->replacement_textures); } select_effect_params params; params.effect = sip->selection_effect; params.fs2_grid_color = sip->fs2_effect_grid_color; params.fs2_scanline_color = sip->fs2_effect_scanline_color; params.fs2_grid_density = sip->fs2_effect_grid_density; params.fs2_wireframe_color = sip->fs2_effect_wireframe_color; draw_model_rotating( &render_info, Selected_ss_class, ShipSelectModelNum, Ship_anim_coords[gr_screen.res][0], Ship_anim_coords[gr_screen.res][1], Tech_ship_display_coords[gr_screen.res][2], Tech_ship_display_coords[gr_screen.res][3], &ShipSelectScreenShipRot, &sip->closeup_pos, sip->closeup_zoom * 1.3f, rev_rate, MR_AUTOCENTER | MR_NO_FOGGING, GR_RESIZE_MENU, params); } } // The background transition plays once. Display ship icons after Background done playing if ( !Background_playing ) { GR_DEBUG_SCOPE("Draw icons"); draw_ship_icons(); for ( int i = 0; i < MAX_WING_BLOCKS; i++ ) { draw_wing_block(i, Hot_ss_slot, -1, Selected_ss_class, true, false); } } if ( ss_icon_being_carried() ) { int mouse_x, mouse_y, sx, sy; mouse_get_pos_unscaled( &mouse_x, &mouse_y ); sx = mouse_x + Ss_delta_x; sy = mouse_y + Ss_delta_y; if((*Ss_icons)[Carried_ss_icon.ship_class].icon_bmaps[ICON_FRAME_SELECTED] != -1) { gr_set_bitmap((*Ss_icons)[Carried_ss_icon.ship_class].icon_bmaps[ICON_FRAME_SELECTED]); gr_bitmap(sx, sy, GR_RESIZE_MENU); } else { ship_info *sip = &Ship_info[Carried_ss_icon.ship_class]; if((*Ss_icons)[Carried_ss_icon.ship_class].model_index == -1) { (*Ss_icons)[Carried_ss_icon.ship_class].model_index = model_load(sip, true); mprintf(("SL WARNING: Had to attempt to page in model for %s paged in manually! Result: %d\n", sip->name, (*Ss_icons)[Carried_ss_icon.ship_class].model_index)); } gr_set_color_fast(&Icon_colors[ICON_FRAME_SELECTED]); int w = 32; int h = 28; graphics::line_draw_list line_draw_list; draw_brackets_square(&line_draw_list, sx, sy, sx + w, sy + h, GR_RESIZE_MENU); line_draw_list.flush(); if((*Ss_icons)[Carried_ss_icon.ship_class].model_index != -1) { draw_model_icon((*Ss_icons)[Carried_ss_icon.ship_class].model_index, MR_AUTOCENTER | MR_NO_FOGGING | MR_NO_LIGHTING, sx, sy, w, h, sip, nullptr, 0.8f, GR_RESIZE_MENU); } } } // draw out ship information ship_select_blit_ship_info(); ss_maybe_flash_button(); // blit help overlay if active help_overlay_maybe_blit(Ship_select_overlay_id, gr_screen.res); gr_reset_clip(); gr_flip(); /////////////////////////////////// // Done Rendering and Drawing 3D // /////////////////////////////////// if ( Game_mode & GM_MULTIPLAYER ) { if ( Selected_ss_class >= 0 ) Net_player->p_info.ship_class = Selected_ss_class; } // If the commit button was pressed, do the commit button actions. Done at the end of the // loop so there isn't a skip in the animation (since ship_create() can take a long time if // the ship model is not in memory if ( Commit_pressed ) { commit_pressed(); Commit_pressed = 0; } } // release the ship select model, if one is loaded static void ss_unload_select_model() { if (ShipSelectModelNum >= 0) { model_unload(ShipSelectModelNum); ShipSelectModelNum = -1; } } // ------------------------------------------------------------------------ // ship_select_close() is called once when the ship select screen is exited // // void ship_select_close() { key_flush(); if ( !Ship_select_open ) { nprintf(("Alan","ship_select_close() returning without doing anything\n")); return; } nprintf(("Alan", "Entering ship_select_close()\n")); // done with the bitmaps, so unlock it bm_unlock(ShipSelectMaskBitmap); // unload the bitmaps bm_release(ShipSelectMaskBitmap); Ship_select_ui_window.destroy(); Ship_anim_class = -1; Ship_select_open = 0; // This game-wide global flag is set to 0 to indicate that the ship // select screen has been closed and memory freed. This flag // is needed so we can know if ship_select_close() needs to called if // restoring a game from the Options screen invoked from ship select } /** * Release the bitmaps and animation held by one team's icon map, while leaving the map populated. */ static void ss_unload_team_icons(SCP_map &icons) { for ( auto &[ship_class, icon] : icons ) { for ( int &bmap : icon.icon_bmaps ) { if ( bmap >= 0 ) { bm_release(bmap); bmap = -1; } } if ( icon.ss_anim.num_frames ) { generic_anim_unload(&icon.ss_anim); } } } // ------------------------------------------------------------------------ // draw_ship_icons() will request which icons to draw on screen. void draw_ship_icons() { for ( int i = 0; i < MAX_ICONS_ON_SCREEN; i++ ) { size_t list_index = SS_active_list_start + i; if ( list_index >= SS_active_list.size() ) break; draw_ship_icon_with_number(i, SS_active_list[list_index]); } } // ------------------------------------------------------------------------ // draw_ship_icon_with_number() will draw a ship icon on screen with the // number of available ships to the left. // // void draw_ship_icon_with_number(int screen_offset, int ship_class) { GR_DEBUG_SCOPE("Draw single ship icon"); char buf[32]; int num_x,num_y; ss_icon_info *ss_icon; color *color_to_draw = NULL; //shader *shader_to_use; Assert( screen_offset >= 0 && screen_offset <= 3 ); Assert( ship_class >= 0 ); Assert( (Ss_pool != NULL) && (Ss_icons != NULL) ); ss_icon = &(*Ss_icons)[ship_class]; num_x = Ship_list_coords[gr_screen.res][screen_offset][2]; num_y = Ship_list_coords[gr_screen.res][screen_offset][3]; // assume default bitmap is to be used if(ss_icon->current_icon_bitmap != -1) ss_icon->current_icon_bitmap = ss_icon->icon_bmaps[ICON_FRAME_NORMAL]; else color_to_draw = &Icon_colors[ICON_FRAME_NORMAL]; // next check if ship has mouse over it if ( Hot_ss_icon > -1 ) { Assert(Hot_ss_icon <= 3); if ( Hot_ss_icon == screen_offset ) { if(ss_icon->model_index == -1) ss_icon->current_icon_bitmap = ss_icon->icon_bmaps[ICON_FRAME_HOT]; else color_to_draw = &Icon_colors[ICON_FRAME_HOT]; } } // highest precedence is if the ship is selected if ( Selected_ss_class > -1 ) { if ( Selected_ss_class == ship_class ) { if(ss_icon->model_index == -1) ss_icon->current_icon_bitmap = ss_icon->icon_bmaps[ICON_FRAME_SELECTED]; else color_to_draw = &Icon_colors[ICON_FRAME_SELECTED]; } } if ( Ss_pool->value_or(ship_class, -1) <= 0 ) { return; } // blit the icon if(ss_icon->current_icon_bitmap != -1) { gr_set_bitmap(ss_icon->current_icon_bitmap); gr_bitmap(Ship_list_coords[gr_screen.res][screen_offset][0], Ship_list_coords[gr_screen.res][screen_offset][1], GR_RESIZE_MENU); } else { ship_info *sip = &Ship_info[ship_class]; if(ss_icon->model_index == -1) { ss_icon->model_index = model_load(sip, true); mprintf(("SL WARNING: Had to attempt to page in model for %s paged in manually! Result: %d\n", sip->name, ss_icon->model_index)); } // the icon bitmap can be invalid without a color having been chosen, e.g. when the icon ani is missing frames if (color_to_draw == nullptr) color_to_draw = &Icon_colors[ICON_FRAME_NORMAL]; gr_set_color_fast(color_to_draw); graphics::line_draw_list line_draw_list; draw_brackets_square(&line_draw_list, Ship_list_coords[gr_screen.res][screen_offset][0], Ship_list_coords[gr_screen.res][screen_offset][1], Ship_list_coords[gr_screen.res][screen_offset][0] + 32, Ship_list_coords[gr_screen.res][screen_offset][1] + 28, GR_RESIZE_MENU); line_draw_list.flush(); if(ss_icon->model_index != -1) { draw_model_icon(ss_icon->model_index, MR_AUTOCENTER | MR_NO_FOGGING | MR_NO_LIGHTING, Ship_list_coords[gr_screen.res][screen_offset][0],Ship_list_coords[gr_screen.res][screen_offset][1], 32, 28, sip, nullptr, 0.8f, GR_RESIZE_MENU); } } // blit the number sprintf(buf, "%d", Ss_pool->value_or(ship_class, -1) ); gr_set_color_fast(&Color_white); gr_string(num_x, num_y, buf, GR_RESIZE_MENU); } // ------------------------------------------------------------------------ // this loads an individual animation file // it attempts to load a hires version (ie, it attaches a "2_" in front of the // filename. if no hires version is available, it defaults to the lowres // ------------------------------------------------------------------------ // start_ship_animation() will start a ship animation playing, and will // load the compressed anim from disk if required. // // UPDATE: this code now initializes a 3d model of a ship to spin like it does // in the tech room - UnknownPlayer void start_ship_animation(int ship_class, int /*play_sound*/) { char *p; char animation_filename[CF_MAX_FILENAME_LENGTH+4]; if (ship_class < 0) { mprintf(("No ship class passed in to start_ship_animation\n")); ss_unload_select_model(); return; } ship_info *sip = &Ship_info[ship_class]; anim_timer_start = timer_get_milliseconds(); if ( Use_3d_ship_select || !strlen(sip->anim_filename) ) { //Unload Anim if one was playing if(Ship_anim_class > 0 && (*Ss_icons)[Ship_anim_class].ss_anim.num_frames > 0) { generic_anim_unload(&(*Ss_icons)[Ship_anim_class].ss_anim); Ship_anim_class = -1; } // Load the necessary model file; the previous one is released only afterwards so that // re-selecting the same ship doesn't free and reload it int previous_model = ShipSelectModelNum; ShipSelectModelNum = model_load(sip, true); if (previous_model >= 0) { model_unload(previous_model); } // page in ship textures properly (takes care of nondimming pixels) model_page_in_textures(ShipSelectModelNum, ship_class); if (ShipSelectModelNum < 0) { mprintf(("Couldn't load model file %s in missionshipchoice.cpp\n", sip->pof_file)); } } else { Assert( ship_class >= 0 ); Assert( Ss_icons != NULL ); if (Ship_anim_class == ship_class) { return; } //If there was a model loaded for the previous ship, unload it ss_unload_select_model(); //unload the previous anim if(Ship_anim_class > 0 && (*Ss_icons)[Ship_anim_class].ss_anim.num_frames > 0) generic_anim_unload(&(*Ss_icons)[Ship_anim_class].ss_anim); //load animation here, we now only have one loaded p = strchr(Ship_info[ship_class].anim_filename, '.' ); if(p) *p = '\0'; if (gr_screen.res == GR_1024) { strcpy_s(animation_filename, "2_"); strcat_s(animation_filename, Ship_info[ship_class].anim_filename); } else { strcpy_s(animation_filename, Ship_info[ship_class].anim_filename); } generic_anim_init(&(*Ss_icons)[ship_class].ss_anim, animation_filename); (*Ss_icons)[ship_class].ss_anim.ani.bg_type = bm_get_type(Ship_select_background_bitmap); if(generic_anim_stream(&(*Ss_icons)[ship_class].ss_anim) == -1) { //we've failed to load an animation, load an image and treat it like a 1 frame animation (*Ss_icons)[ship_class].ss_anim.first_frame = bm_load(Ship_info[ship_class].anim_filename); //if we fail here, the value is still -1 if((*Ss_icons)[ship_class].ss_anim.first_frame != -1) { (*Ss_icons)[ship_class].ss_anim.num_frames = 1; } } Ship_anim_class = ship_class; } // if ( play_sound ) { gamesnd_play_iface(InterfaceSounds::SHIP_ICON_CHANGE); // } } bool is_weapon_carried(int weapon_index) { for (int slot = 0; slot < MAX_WING_BLOCKS*MAX_WING_SLOTS; slot++) { // a ship must exist in this slot if (Wss_slots[slot].ship_class >= 0) { for (int bank = 0; bank < MAX_SHIP_WEAPONS; bank++) { // there must be a weapon here if (Wss_slots[slot].wep_count[bank] > 0) { if (Wss_slots[slot].wep[bank] == weapon_index) return true; } } } } return false; } bool check_for_gaps_in_weapon_slots() { for (int slot = 0; slot < MAX_WING_BLOCKS*MAX_WING_SLOTS; ++slot) { // a ship must exist in this slot if (Wss_slots[slot].ship_class >= 0) { // if the player can't modify the weapons, then an empty bank is not his fault auto ss_slot = &Ss_wings[slot / MAX_WING_SLOTS].ss_slots[slot % MAX_WING_SLOTS]; if (ss_slot->status & WING_SLOT_WEAPONS_DISABLED) continue; ship_info *sip = &Ship_info[Wss_slots[slot].ship_class]; bool empty_slot = false; // check primary banks for (int bank = 0; bank < sip->num_primary_banks; ++bank) // NOLINT(modernize-loop-convert) { // is the slot empty? if (Wss_slots[slot].wep_count[bank] <= 0) { empty_slot = true; } // is there a full slot following a gap? else if (empty_slot) { return true; } } empty_slot = false; // check secondary banks for (int bank = 0; bank < sip->num_secondary_banks; ++bank) { // is the slot empty? if (Wss_slots[slot].wep_count[MAX_SHIP_PRIMARY_BANKS + bank] <= 0) { empty_slot = true; } // is there a full slot following a gap? else if (empty_slot) { return true; } } } } return false; } // ------------------------------------------------------------------------ // commit_pressed() is called when the commit button from any of the briefing/ship select/ weapon // select screens is pressed. The ship selected is created, and the interface music is stopped. commit_pressed_status commit_pressed(bool API_Access) { int player_ship_info_index; if ( Wss_num_wings > 0 ) { if(!(Game_mode & GM_MULTIPLAYER)){ commit_pressed_status rc; rc = create_wings(); if (rc != commit_pressed_status::SUCCESS) { if (!API_Access) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } return rc; } } } else if(Player_obj != NULL) { if ( Selected_ss_class == -1 ) { player_ship_info_index = Team_data[Common_team].default_ship; } else { Assert(Selected_ss_class >= 0 ); player_ship_info_index = Selected_ss_class; } update_player_ship( player_ship_info_index ); if ( wl_update_ship_weapons(Ships[Player_obj->instance].objnum, &Wss_slots[0]) == -1 ) { popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("Player ship has no weapons", 461)); return commit_pressed_status::PLAYER_NO_WEAPONS; } } // Goober5000 - mjn.mixael's required weapon feature int num_required_weapons = 0; int num_satisfied_weapons = 0; SCP_string weapon_list; for (int weapon_class : Team_data[Common_team].required_weapons) { // add it to the message list num_required_weapons++; if (num_required_weapons > 1) weapon_list.append(1, EOLN); weapon_list.append(Weapon_info[weapon_class].get_display_name()); // see if it's carried by any ship if (is_weapon_carried(weapon_class)) num_satisfied_weapons++; } if (num_satisfied_weapons < num_required_weapons) { if (num_required_weapons == 1) { popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("The %s is required for this mission, but it has not been added to any ship loadout.", 1624), weapon_list.c_str()); return commit_pressed_status::NO_REQUIRED_WEAPON; } else if (num_required_weapons > 1) { popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("The following weapons are required for this mission, but at least one of them has not been " "added to any ship loadout:\n\n%s", 1625), weapon_list.c_str()); return commit_pressed_status::NO_REQUIRED_WEAPON_MULTIPLE; } } // Goober5000 - check that there are no gaps in weapon slots (Mantis 2715) // Any incomplete loadouts that make it through the loadout screen will be condensed so that gaps are moved to the bottom. This check adds that responsibility to the player. // Note: don't check for training, scramble, or red-alert missions // Note2: don't check missions without briefings either if (check_for_gaps_in_weapon_slots() && !(The_mission.game_type & MISSION_TYPE_TRAINING) && The_mission.flags.none_of(Mission::Mission_Flags::Scramble,Mission::Mission_Flags::Red_alert,Mission::Mission_Flags::No_briefing)) { popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("At least one ship has an empty weapon bank before a full weapon bank.\n\nAll weapon banks must " "have weapons assigned, or if there are any gaps, they must be at the bottom of the set of banks.", 1642), weapon_list.c_str()); return commit_pressed_status::BANK_GAP_ERROR; } // Check to ensure that the hotkeys are still pointing to valid objects. It is possible // for the player to assign a ship to a hotkey, then go and delete that ship in the // ship selection, and then try to start the mission. This function will detect those objects, // and remove them from the hotkey linked lists. mission_hotkey_validate(); // Goober5000 - no sound when skipping briefing if (!(The_mission.flags[Mission::Mission_Flags::No_briefing])) gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED); // plieblang - resume simulated speech if necessary if (!Player->auto_advance) { fsspeech_pause(false); } // save the player loadout if ( !(Game_mode & GM_MULTIPLAYER) ) { strcpy_s(Player_loadout.filename, Game_current_mission_filename); strcpy_s(Player_loadout.last_modified, The_mission.modified); wss_save_loadout(); } // warp the mouse cursor the the middle of the screen for those who control with a mouse mouse_set_pos( gr_screen.max_w/2, gr_screen.max_h/2 ); // move to the next stage // in multiplayer this is the final mission sync if(Game_mode & GM_MULTIPLAYER){ if (MULTIPLAYER_MASTER) { // process the initial orders now (moved from post_process_mission()in missionparse) mission_parse_fixup_players(); ai_post_process_mission(); } Multi_sync_mode = MULTI_SYNC_POST_BRIEFING; gameseq_post_event(GS_EVENT_MULTI_MISSION_SYNC); // otherwise tell the standalone to move everyone into this state and continue if((Net_player->flags & NETINFO_FLAG_GAME_HOST) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER)){ send_mission_sync_packet(MULTI_SYNC_POST_BRIEFING); } } // in single player we jump directly into the mission else { Pilot.save_savefile(); gameseq_post_event(GS_EVENT_ENTER_GAME); } return commit_pressed_status::SUCCESS; } // ------------------------------------------------------------------------ // pick_from_ship_list() will determine if an icon from the ship selection // list can be picked up (for drag and drop). It calculates the difference // in x & y between the icon and the mouse, so we can move the icon with the // mouse in a realistic way int pick_from_ship_list(int screen_offset, int ship_class) { int rval = -1; Assert(ship_class >= 0); Assert( Ss_pool != NULL ); if ( Wss_num_wings == 0 ) return rval; // If carrying an icon, then do nothing if ( ss_icon_being_carried() ) return rval; if ( Ss_pool->value_or(ship_class, -1) > 0 ) { int mouse_x, mouse_y; ss_set_carried_icon(-1, ship_class); mouse_get_pos_unscaled( &mouse_x, &mouse_y ); Ss_delta_x = Ship_list_coords[gr_screen.res][screen_offset][0] - mouse_x; Ss_delta_y = Ship_list_coords[gr_screen.res][screen_offset][1] - mouse_y; rval = 0; } common_flash_button_init(); return rval; } // ------------------------------------------------------------------------ // pick_from_wing() will determine if an icon from the wing formation (wb_num) // and slot number (ws_num) can be picked up (for drag and drop). It calculates // the difference in x & y between the icon and the mouse, so we can move the icon with the // mouse in a realistic way void pick_from_wing(int wb_num, int ws_num) { int slot_index; Assert(wb_num >= 0 && wb_num < MAX_WING_BLOCKS); Assert(ws_num >= 0 && ws_num < MAX_WING_SLOTS); Assert( (Ss_wings != NULL) && (Wss_slots != NULL) ); ss_wing_info *wb; ss_slot_info *ws; wb = &Ss_wings[wb_num]; ws = &wb->ss_slots[ws_num]; slot_index = wb_num*MAX_WING_SLOTS+ws_num; if ( wb->wingnum < 0 ) return; // Take care of case where the mouse button goes from up to down in one frame while // carrying an icon if ( Drop_on_wing_mflag && ss_icon_being_carried() ) { if ( !ss_disabled_slot(slot_index) ) { ss_drop(Carried_ss_icon.from_slot, Carried_ss_icon.ship_class, slot_index, -1); ss_reset_carried_icon(); gamesnd_play_iface(InterfaceSounds::ICON_DROP_ON_WING); } } if ( ss_icon_being_carried() ) return; if ( ss_disabled_slot(slot_index) ) { return; } if ( ws->status & WING_SLOT_IGNORE_SHIPS) { return; } switch ( ws->status & ~WING_SLOT_WEAPONS_DISABLED ) { case WING_SLOT_EMPTY: case WING_SLOT_EMPTY|WING_SLOT_IS_PLAYER: // TODO: add fail sound return; break; case WING_SLOT_FILLED|WING_SLOT_IS_PLAYER: case WING_SLOT_FILLED: { int mouse_x, mouse_y; Assert(Wss_slots[slot_index].ship_class >= 0); ss_set_carried_icon(slot_index, Wss_slots[slot_index].ship_class); mouse_get_pos_unscaled( &mouse_x, &mouse_y ); Ss_delta_x = Wing_icon_coords[gr_screen.res][slot_index][0] - mouse_x; Ss_delta_y = Wing_icon_coords[gr_screen.res][slot_index][1] - mouse_y; Carried_ss_icon.from_x = mouse_x; Carried_ss_icon.from_y = mouse_y; } break; default: Int3(); break; } // end switch common_flash_button_init(); } // ------------------------------------------------------------------------ // draw_wing_block() will draw the wing icons for the wing formation number // passed in as a parameter. // // input: wb_num => wing block number (numbering starts at 0) // hot_slot => index of slot that mouse is over // selected_slot => index of slot that is selected // class_select => all ships of this class are drawn selected (send -1 to not use) void draw_wing_block(int wb_num, int hot_slot, int selected_slot, int class_select, bool ship_selection, bool always_highlight_ply ) { GR_DEBUG_SCOPE("Wing block"); ss_wing_info *wb; ss_slot_info *ws; ss_icon_info *icon; wing *wp; int i, w, h, sx, sy, slot_index, mask; int bitmap_to_draw = -1; color *color_to_draw = NULL; //shader *shader_to_use = NULL; Assert(wb_num >= 0 && wb_num < MAX_WING_BLOCKS); Assert( (Ss_wings != NULL) && (Wss_slots != NULL) && (Ss_icons != NULL) ); wb = &Ss_wings[wb_num]; if ( wb->wingnum == -1 ) return; // print the wing name under the wing wp = &Wings[wb->wingnum]; auto wing_name = wp->get_display_name(); gr_get_string_size(&w, &h, wing_name); sx = Wing_icon_coords[gr_screen.res][wb_num*MAX_WING_SLOTS][0] + 16 - w/2; sy = Wing_icon_coords[gr_screen.res][wb_num*MAX_WING_SLOTS + 3][1] + 32 + h; gr_set_color_fast(&Color_normal); gr_string(sx, sy, wing_name, GR_RESIZE_MENU); for ( i = 0; i < MAX_WING_SLOTS; i++ ) { GR_DEBUG_SCOPE("Single ship"); bitmap_to_draw = -1; color_to_draw = nullptr; ws = &wb->ss_slots[i]; slot_index = wb_num*MAX_WING_SLOTS + i; if ( Wss_slots[slot_index].ship_class >= 0 ) { icon = &(*Ss_icons)[Wss_slots[slot_index].ship_class]; } else { icon = NULL; } mask = ~WING_SLOT_LOCKED; mask &= ~WING_SLOT_IS_PLAYER; if (ship_selection) { mask &= ~WING_SLOT_WEAPONS_DISABLED; } else { mask &= ~WING_SLOT_SHIPS_DISABLED; } switch(ws->status & mask ) { case WING_SLOT_FILLED: case WING_SLOT_FILLED|WING_SLOT_IS_PLAYER: Assert(icon); if ( class_select >= 0 ) { // only ship select if ( Carried_ss_icon.from_slot == slot_index ) { if ( ss_carried_icon_moved() ) { bitmap_to_draw = Wing_slot_empty_bitmap; } else { bitmap_to_draw = -1; } break; } } if ( ws->status & WING_SLOT_LOCKED ) { if(icon->model_index == -1) bitmap_to_draw = icon->icon_bmaps[ICON_FRAME_DISABLED]; else color_to_draw = &Icon_colors[ICON_FRAME_DISABLED]; // in multiplayer, determine if this it the special case where the slot is disabled, and // it is also _my_ slot (ie, team captains/hosts have not locked players yet) if((Game_mode & GM_MULTIPLAYER) && multi_ts_disabled_high_slot(slot_index)){ if(icon->model_index == -1) bitmap_to_draw = icon->icon_bmaps[ICON_FRAME_DISABLED_HIGH]; else color_to_draw = &Icon_colors[ICON_FRAME_DISABLED_HIGH]; } break; } if(icon->model_index == -1) bitmap_to_draw = icon->icon_bmaps[ICON_FRAME_NORMAL]; else color_to_draw = &Icon_colors[ICON_FRAME_NORMAL]; if ( selected_slot == slot_index || class_select == Wss_slots[slot_index].ship_class) { if(icon->model_index == -1) bitmap_to_draw = icon->icon_bmaps[ICON_FRAME_SELECTED]; else color_to_draw = &Icon_colors[ICON_FRAME_SELECTED]; } else if ( hot_slot == slot_index ) { if ( mouse_down(MOUSE_LEFT_BUTTON) ) { if(icon->model_index == -1) bitmap_to_draw = icon->icon_bmaps[ICON_FRAME_SELECTED]; else color_to_draw = &Icon_colors[ICON_FRAME_SELECTED]; } else { if(icon->model_index == -1) bitmap_to_draw = icon->icon_bmaps[ICON_FRAME_HOT]; else color_to_draw = &Icon_colors[ICON_FRAME_HOT]; } } if ( ws->status & WING_SLOT_IS_PLAYER && (always_highlight_ply || (selected_slot != slot_index)) ) { if(icon->model_index == -1) bitmap_to_draw = icon->icon_bmaps[ICON_FRAME_PLAYER]; else color_to_draw = &Icon_colors[ICON_FRAME_PLAYER]; } break; case WING_SLOT_EMPTY: case WING_SLOT_EMPTY|WING_SLOT_IS_PLAYER: bitmap_to_draw = Wing_slot_empty_bitmap; break; default: if ( icon ) { if(icon->model_index == -1) bitmap_to_draw = icon->icon_bmaps[ICON_FRAME_DISABLED]; else color_to_draw = &Icon_colors[ICON_FRAME_DISABLED]; } else { bitmap_to_draw = Wing_slot_disabled_bitmap; } break; } // end switch if ( bitmap_to_draw != -1 ) { gr_set_bitmap(bitmap_to_draw); gr_bitmap(Wing_icon_coords[gr_screen.res][slot_index][0], Wing_icon_coords[gr_screen.res][slot_index][1], GR_RESIZE_MENU); } else if(color_to_draw != NULL) { ship_info *sip = &Ship_info[Wss_slots[slot_index].ship_class]; gr_set_color_fast(color_to_draw); graphics::line_draw_list line_draw_list; draw_brackets_square(&line_draw_list, Wing_icon_coords[gr_screen.res][slot_index][0], Wing_icon_coords[gr_screen.res][slot_index][1], Wing_icon_coords[gr_screen.res][slot_index][0] + 32, Wing_icon_coords[gr_screen.res][slot_index][1] + 28, GR_RESIZE_MENU); line_draw_list.flush(); draw_model_icon(icon->model_index, MR_AUTOCENTER | MR_NO_FOGGING | MR_NO_LIGHTING, Wing_icon_coords[gr_screen.res][slot_index][0], Wing_icon_coords[gr_screen.res][slot_index][1], 32, 28, sip, nullptr, 0.8f, GR_RESIZE_MENU); } } } // called by multiplayer team select to set the slot based flags void ss_make_slot_empty(int slot_index) { int wing_num,slot_num; ss_wing_info *wb; ss_slot_info *ws; Assert( Ss_wings != NULL ); // calculate the wing # wing_num = slot_index / MAX_WING_SLOTS; slot_num = slot_index % MAX_WING_SLOTS; // get the wing and slot entries wb = &Ss_wings[wing_num]; ws = &wb->ss_slots[slot_num]; // set the flags ws->status &= ~(WING_SLOT_FILLED | WING_SLOT_SHIPS_DISABLED | WING_SLOT_WEAPONS_DISABLED); ws->status |= WING_SLOT_EMPTY; } // called by multiplayer team select to set the slot based flags void ss_make_slot_full(int slot_index) { int wing_num,slot_num; ss_wing_info *wb; ss_slot_info *ws; Assert( Ss_wings != NULL ); // calculate the wing # wing_num = slot_index / MAX_WING_SLOTS; slot_num = slot_index % MAX_WING_SLOTS; // get the wing and slot entries wb = &Ss_wings[wing_num]; ws = &wb->ss_slots[slot_num]; // set the flags ws->status &= ~(WING_SLOT_EMPTY | WING_SLOT_SHIPS_DISABLED | WING_SLOT_WEAPONS_DISABLED); ws->status |= WING_SLOT_FILLED; } void ss_blit_ship_icon(int x,int y,int ship_class,int bmap_num) { // blit the bitmap in the correct location if(ship_class == -1) { gr_set_bitmap(Wing_slot_empty_bitmap); gr_bitmap(x,y,GR_RESIZE_MENU); } else { Assert( Ss_icons != NULL ); ss_icon_info *icon = &(*Ss_icons)[ship_class]; if(icon->icon_bmaps[bmap_num] != -1) { Assert(icon->icon_bmaps[bmap_num] != -1); gr_set_bitmap(icon->icon_bmaps[bmap_num]); gr_bitmap(x,y,GR_RESIZE_MENU); } else { ship_info *sip = &Ship_info[ship_class]; if(icon->model_index == -1) { icon->model_index = model_load(sip, true); mprintf(("SL WARNING: Had to attempt to page in model for %s paged in manually! Result: %d\n", sip->name, icon->model_index)); } if(icon->model_index != -1) { gr_set_color_fast(&Icon_colors[bmap_num]); graphics::line_draw_list line_draw_list; draw_brackets_square(&line_draw_list, x, y, x + 32, y + 28, GR_RESIZE_MENU); line_draw_list.flush(); draw_model_icon(icon->model_index, MR_AUTOCENTER | MR_NO_FOGGING | MR_NO_LIGHTING, x, y, 32, 28, sip, nullptr, 0.8f, GR_RESIZE_MENU); } } } } // ------------------------------------------------------------------------ // unload_ship_icons() frees the memory that was used to hold the bitmaps // for ship icons // void unload_wing_icons() { if ( Wing_slot_empty_bitmap != -1 ) { bm_release(Wing_slot_empty_bitmap); Wing_slot_empty_bitmap = -1; } if ( Wing_slot_disabled_bitmap != -1 ) { bm_release(Wing_slot_disabled_bitmap); Wing_slot_disabled_bitmap = -1; } } // ------------------------------------------------------------------------ // create_wings() will ensure the correct ships are in the player wings // for the game. It works by calling change_ship_type() on the wing ships // so they match what the player selected. ship_create() is called for the // player ship (and current_count, ship_index[] is updated), since it is not yet // part of the wing structure. // // returns: 0 ==> success // !0 ==> failure commit_pressed_status create_wings() { ss_wing_info *wb; ss_slot_info *ws; wing *wp; int shipnum, objnum, slot_index; int cleanup_ship_index[MAX_WING_SLOTS]; int i,j,k; Assert( (Ss_wings != NULL) && (Wss_slots != NULL) ); for ( i = 0; i < MAX_WING_BLOCKS; i++ ) { wb = &Ss_wings[i]; if ( wb->wingnum == -1 ) continue; wp = &Wings[wb->wingnum]; for ( j = 0; j < MAX_WING_SLOTS; j++ ) { slot_index = i*MAX_WING_SLOTS+j; ws = &wb->ss_slots[j]; if ((ws->status & WING_SLOT_FILLED ) || (ws->status & WING_SLOT_SHIPS_DISABLED ) || (ws->status & WING_SLOT_WEAPONS_DISABLED )){ if ( wp->ship_index[j] >= 0 ) { Assert(Ships[wp->ship_index[j]].objnum >= 0); } if ( ws->status & WING_SLOT_IS_PLAYER ) { update_player_ship(Wss_slots[slot_index].ship_class); if ( wl_update_ship_weapons(Ships[Player_obj->instance].objnum, &Wss_slots[i*MAX_WING_SLOTS+j]) == -1 ) { popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("Player ship has no weapons", 461)); return commit_pressed_status::PLAYER_NO_WEAPONS; } objnum = OBJ_INDEX(Player_obj); shipnum = Objects[objnum].instance; } else { // We should always update the parse object information, even if the ship is present at start, // because the wing might have more than one wave or scripting functions might need accurate data bool found_pobj = false; for ( auto &p_obj: Parse_objects ) { if ( p_obj.wingnum == WING_INDEX(wp) ) { if ( p_obj.pos_in_wing == j ) { p_obj.ship_class = Wss_slots[slot_index].ship_class; wl_update_parse_object_weapons(&p_obj, &Wss_slots[i*MAX_WING_SLOTS+j]); found_pobj = true; break; } } } Assert(found_pobj); if (!wb->is_late) { // AL 10/04/97 // Change the ship type of the ship if different than current. // NOTE: This will reset the weapons for this ship. I think this is // the right thing to do, since the ships may have different numbers // of weapons and may not have the same allowed weapon types if ( Ships[wp->ship_index[j]].ship_info_index != Wss_slots[slot_index].ship_class ) change_ship_type(wp->ship_index[j], Wss_slots[slot_index].ship_class); wl_update_ship_weapons(Ships[wp->ship_index[j]].objnum, &Wss_slots[i*MAX_WING_SLOTS+j]); } } } else if (ws->status & WING_SLOT_EMPTY) { if ( ws->status & WING_SLOT_IS_PLAYER ) { popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("Player %s must select a place in player wing", 462), Player->callsign); return commit_pressed_status::PLAYER_NO_SLOT; } } } // end for (wing slot) } // end for (wing block) for ( i = 0; i < MAX_WING_BLOCKS; i++ ) { wb = &Ss_wings[i]; if ( wb->wingnum == -1 ) continue; wp = &Wings[wb->wingnum]; for ( k = 0; k < MAX_WING_SLOTS; k++ ) { cleanup_ship_index[k] = -1; } for ( j = 0; j < MAX_WING_SLOTS; j++ ) { ws = &wb->ss_slots[j]; switch( ws->status & ~WING_SLOT_WEAPONS_DISABLED ) { case WING_SLOT_EMPTY: // delete ship that is not going to be used by the wing if ( wb->is_late ) { list_remove( &Ship_arrival_list, &Parse_objects[ws->sa_index]); wp->wave_count--; Assert(wp->wave_count >= 0); } else { shipnum = wp->ship_index[j]; Assert( shipnum >= 0 && shipnum < MAX_SHIPS ); cleanup_ship_index[j] = shipnum; ship_add_exited_ship( &Ships[shipnum], Ship::Exit_Flags::Player_deleted ); obj_delete(Ships[shipnum].objnum); hud_set_wingman_status_none( Ships[shipnum].wing_status_wing_index, Ships[shipnum].wing_status_wing_pos); } break; default: break; } // end switch } // end for (wing slot) for ( k = 0; k < MAX_WING_SLOTS; k++ ) { if ( cleanup_ship_index[k] != -1 ) { ship_wing_cleanup( cleanup_ship_index[k], wp ); } } } // end for (wing block) return commit_pressed_status::SUCCESS; } // ---------------------------------------------------------------------------- // update_player_ship() // // Updates the ship class of the player ship // // parameters: si_index => ship info index of ship class to change to // // void update_player_ship(int si_index) { Assert( si_index >= 0 ); Assert( Player_obj != NULL); // AL 10/04/97 // Change the ship type of the player ship if different than current. // NOTE: This will reset the weapons for this ship. I think this is // the right thing to do, since the ships may have different numbers // of weapons and may not have the same allowed weapon types if ( Player_ship->ship_info_index != si_index ) change_ship_type(Player_obj->instance, si_index); Player->last_ship_flown_si_index = si_index; } /* * create a default player ship * * @note: only used for quick start missions * * @param use_last_flown select ship that was last flown on a mission (default parameter set to 1) * * @return 0 => success, !0 => failure */ int create_default_player_ship(int use_last_flown) { int player_ship_class=-1; // find the ship that matches the string stored in default_player_ship if ( use_last_flown ) { player_ship_class = Players[Player_num].last_ship_flown_si_index; } else { for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it) { if ( !stricmp(it->name, default_player_ship) ) { player_ship_class = (int)std::distance(Ship_info.cbegin(), it); Players[Player_num].last_ship_flown_si_index = player_ship_class; break; } } if (player_ship_class == -1) return 1; } // if we still haven't found the last flown ship, handle the error semi-gracefully if (player_ship_class == -1) { popup(PF_TITLE_BIG | PF_TITLE_RED | PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR("Error!\n\nCannot find " "a valid last flown ship\n\nHave you played any missions since activating this mod/campaign?", 1619)); return 1; } else { update_player_ship(player_ship_class); } // debug code to keep using descent style physics if the player starts a new game #ifndef NDEBUG if ( use_descent ) { use_descent = 0; toggle_player_object(); } #endif return 0; } // return the original ship class for the specified slot int ss_return_original_ship_class(int slot_num) { int wnum, snum; Assert( Ss_wings != NULL ); wnum = slot_num/MAX_WING_SLOTS; snum = slot_num%MAX_WING_SLOTS; return Ss_wings[wnum].ss_slots[snum].original_ship_class; } // return the ship arrival index for the slot (-1 means no ship arrival index) int ss_return_saindex(int slot_num) { int wnum, snum; Assert( Ss_wings != NULL ); wnum = slot_num/MAX_WING_SLOTS; snum = slot_num%MAX_WING_SLOTS; return Ss_wings[wnum].ss_slots[snum].sa_index; } // ---------------------------------------------------------------------------- // ss_return_ship() // // For a given wing slot, return the ship index if the ship has been created. // Otherwise, find the index into Parse_objects[] for the ship // // input: wing_block => wing block of ship to find // wing_slot => wing slot of ship to find // ship_index => OUTPUT parameter: the Ships[] index of the ship in the wing slot // This value will be -1 if there is no ship created yet // ppobjp => OUTPUT parameter: returns a pointer to a parse object for // the ship that hasn't been created yet. Set to NULL if the // ship has already been created // // returns: the original ship class of the ship, or -1 if the ship doesn't exist // // NOTE: For the player wing, the player is not yet in the wp->ship_index[].. so // that is why there is an offset of 1 when getting ship indicies from the player // wing. The player is special cased by looking at the status of the wing slot // int ss_return_ship(int wing_block, int wing_slot, int *ship_index, p_object **ppobjp) { *ship_index = -1; *ppobjp = NULL; ss_slot_info *ws; Assert( Ss_wings != NULL ); if (!Wss_num_wings) { *ppobjp = NULL; *ship_index = Player_obj->instance; return Player_ship->ship_info_index; } if ( Ss_wings[wing_block].wingnum < 0 ) { return -1; } ws = &Ss_wings[wing_block].ss_slots[wing_slot]; // Check to see if ship is on the ship arrivals list if ( ws->sa_index != -1 ) { *ship_index = -1; *ppobjp = &Parse_objects[ws->sa_index]; } else { *ship_index = Wings[Ss_wings[wing_block].wingnum].ship_index[wing_slot]; Assert(*ship_index != -1); } return ws->original_ship_class; } // return the name of the ship in the specified wing position... if the ship is the // player ship, return the player callsign // // input: ensure at least NAME_LENGTH bytes allocated for name buffer void ss_return_name(int wing_block, int wing_slot, char *name) { ss_slot_info *ws; wing *wp; Assert( Ss_wings != NULL ); ws = &Ss_wings[wing_block].ss_slots[wing_slot]; wp = &Wings[Ss_wings[wing_block].wingnum]; if (!Wss_num_wings) { strcpy(name, Player->callsign); return; } // Check to see if ship is on the ship arrivals list if ( ws->sa_index != -1 ) { strcpy(name, Parse_objects[ws->sa_index].get_display_name()); } else { ship *sp; sp = &Ships[wp->ship_index[wing_slot]]; // in multiplayer, return the callsigns of the players who are in the ships if(Game_mode & GM_MULTIPLAYER){ int player_index = multi_find_player_by_object(&Objects[sp->objnum]); if(player_index != -1){ strcpy(name,Net_players[player_index].m_player->callsign); } else { strcpy(name, sp->get_display_name()); } } else { strcpy(name, sp->get_display_name()); } } } int ss_get_selected_ship() { return Selected_ss_class; } // Set selected ship to the first occupied wing slot, or first ship in pool if no slots are occupied void ss_reset_selected_ship() { int i; Assert( (Ss_pool != NULL) && (Wss_slots != NULL) ); Selected_ss_class = -1; if ( Wss_num_wings <= 0 ) { Selected_ss_class = Team_data[Common_team].default_ship; return; } // get first ship class found on slots for ( i = 0; i < MAX_WSS_SLOTS; i++ ) { if ( Wss_slots[i].ship_class >= 0 ) { Selected_ss_class = Wss_slots[i].ship_class; return; } } // get the first ship class found in the pool for ( const auto &[ship_class, count] : *Ss_pool ) { if ( count > 0 ) { Selected_ss_class = ship_class; return; } } // If we still haven't found a ship to display, then leave it as -1. This results in no ship info // being displayed in the UI, but is not a game breaking issue. -Mjn /*if ( Selected_ss_class == -1 ) { Int3(); return; }*/ } // There may be ships that are in wings but not in Team_data[0]. Since we still want to show those // icons in the ship selection list, the code below checks for these cases. If a ship is found in // a wing, and is not in Team_data[0], it is appended to the end of the ship_choices list // that is in Team_data[0] // // exit: number of distinct ship classes available to choose from int ss_fixup_team_data(team_data *tdata) { int i, j; p_object *p_objp; team_data *p_team_data = tdata; auto append_class = [p_team_data](int ship_class) { auto &entry = p_team_data->ship_choices.emplace_back(); entry.class_index = ship_class; entry.count = 0; }; for ( i = 0; i < MAX_STARTING_WINGS; i++ ) { wing *wp; if ( Starting_wings[i] == -1 ) continue; wp = &Wings[Starting_wings[i]]; for ( j = 0; j < wp->current_count; j++ ) { if ( !std::any_of(p_team_data->ship_choices.begin(), p_team_data->ship_choices.end(), [&](const auto &e) { return e.class_index == Ships[wp->ship_index[j]].ship_info_index; }) ) { append_class(Ships[wp->ship_index[j]].ship_info_index); } } // end for, go get next ship in wing if ( wp->current_count == 0 ) { for ( p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp) ) { if ( p_objp->wingnum == WING_INDEX(wp) ) { if ( !std::any_of(p_team_data->ship_choices.begin(), p_team_data->ship_choices.end(), [&](const auto &e) { return e.class_index == p_objp->ship_class; }) ) { append_class(p_objp->ship_class); } } } } } // end for, go to next wing if ( p_team_data->ship_choices.empty() && p_team_data->default_ship >= 0 ) { // ensure that the default player ship is in the choices too append_class(p_team_data->default_ship); } return sz2i(p_team_data->ship_choices.size()); } // set numbers of ships in pool to default values void ss_init_pool(team_data *pteam) { Assert( Ss_pool != NULL ); Ss_pool->clear(); // set number of available ships based on counts in team_data // (auto-insert starts new entries at 0, so classes listed with a count of 0 stay in the pool as exhausted) for ( auto &entry : pteam->ship_choices ) { (*Ss_pool)[entry.class_index] += entry.count; } } // load the icons for a specific ship class void ss_load_icons(int ship_class) { ss_icon_info *icon; Assert( Ss_icons != NULL ); icon = &(*Ss_icons)[ship_class]; ship_info *sip = &Ship_info[ship_class]; if (!Use_3d_ship_icons && strlen(sip->icon_filename)) { int first_frame, num_frames, i; first_frame = bm_load_animation(sip->icon_filename, &num_frames); Assertion(first_frame != -1, "Failed to load icon %s\n", sip->icon_filename); // Could not load in icon frames.. get Alan if (first_frame == -1) return; for ( i = 0; i < num_frames; i++ ) { icon->icon_bmaps[i] = first_frame+i; } // set the current bitmap for the ship icon icon->current_icon_bitmap = icon->icon_bmaps[ICON_FRAME_NORMAL]; } else { icon->model_index = model_load(sip, true); model_page_in_textures(icon->model_index, ship_class); } } // load all the icons for ships in the pool void ss_load_all_icons() { Assert( (Ss_pool != nullptr) && (Ss_icons != nullptr) ); // drop any icon data from a previous load of this team ss_unload_team_icons(*Ss_icons); Ss_icons->clear(); // note: unlike the weapon pool, all pool members get icons, including exhausted 0-count classes for ( const auto &[ship_class, count] : *Ss_pool ) { ss_load_icons(ship_class); } } // determine if the slot is disabled int ss_disabled_slot(int slot_num, bool ship_selection) { int status; if ( Wss_num_wings <= 0 ){ return 0; } Assert( Ss_wings != NULL ); // HACK HACK HACK - call the team select function in multiplayer if(Game_mode & GM_MULTIPLAYER) { return multi_ts_disabled_slot(slot_num); } status = Ss_wings[slot_num/MAX_WING_SLOTS].ss_slots[slot_num%MAX_WING_SLOTS].status; if (ship_selection) { return ( status & WING_SLOT_IGNORE_SHIPS ); } else { return ( status & WING_SLOT_IGNORE_WEAPONS ); } } // Goober5000 - determine if the slot is valid int ss_valid_slot(int slot_num) { int status; if (ss_disabled_slot(slot_num)) return 0; Assert( Ss_wings != NULL ); status = Ss_wings[slot_num/MAX_WING_SLOTS].ss_slots[slot_num%MAX_WING_SLOTS].status; return (status & WING_SLOT_FILLED) && !(status & WING_SLOT_EMPTY); } // reset the slot data void ss_clear_slots() { int i,j; ss_slot_info *slot; Assert( (Wss_slots != NULL) && (Ss_wings != NULL) ); for ( i = 0; i < MAX_WSS_SLOTS; i++ ) { Wss_slots[i].ship_class = -1; for ( j = 0; j < MAX_SHIP_WEAPONS; j++ ) { Wss_slots[i].wep[j] = 0; Wss_slots[i].wep_count[j] = 0; } } for ( i = 0; i < MAX_WING_BLOCKS; i++ ) { for ( j = 0; j < MAX_WING_SLOTS; j++ ) { slot = &Ss_wings[i].ss_slots[j]; slot->status = WING_SLOT_LOCKED; slot->sa_index = -1; slot->original_ship_class = -1; slot->in_mission = false; } } } // initialize all wing struct stuff void ss_clear_wings() { int idx; Assert( Ss_wings != NULL ); for(idx=0;idxwingnum = Starting_wings[starting_wing_num]; Wss_num_wings++; wp = &Wings[ss_wing->wingnum]; // niffiwan: don't overrun the array if (wp->current_count > MAX_WING_SLOTS) { Warning(LOCATION, "Starting Wing '%s' has '%d' ships. Truncating ship selection to 'MAX_WING_SLOTS'\n", Starting_wing_names[ss_wing->wingnum],wp->current_count); ss_wing->num_slots = MAX_WING_SLOTS; } else { ss_wing->num_slots = wp->current_count; } // deal with wing arriving after mission start if ( wp->current_count == 0 || wp->ship_index[0] == -1 ) { p_object *p_objp; // Temporarily fill in the current count and initialize the ship list in the wing // This gets cleaned up before the mission is started for ( p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp) ) { if ( p_objp->wingnum == WING_INDEX(wp) ) { // niffiwan: don't overrun the array if (ss_wing->num_slots >= MAX_WING_SLOTS) { Warning(LOCATION, "Starting Wing '%s' has more than 'MAX_WING_SLOTS' ships\n", Starting_wing_names[ss_wing->wingnum]); break; } slot = &ss_wing->ss_slots[ss_wing->num_slots++]; slot->sa_index = POBJ_INDEX(p_objp); slot->original_ship_class = p_objp->ship_class; } ss_wing->is_late = 1; } } } // Determine if a ship is actually a console player ship int ss_wing_slot_is_console_player(int index) { int wingnum, slotnum; wingnum=index/MAX_WING_SLOTS; slotnum=index%MAX_WING_SLOTS; if ( wingnum >= Wss_num_wings ) { return 0; } Assert( Ss_wings != NULL ); if ( Ss_wings[wingnum].ss_slots[slotnum].status & WING_SLOT_IS_PLAYER ) { return 1; } return 0; } // init the ship selection portion of the units, and set up the ui data void ss_init_units() { int i,j; wing *wp; ss_slot_info *ss_slot; ss_wing_info *ss_wing; Assert( (Ss_wings != NULL) && (Wss_slots != NULL) ); for ( i = 0; i < Wss_num_wings; i++ ) { ss_wing = &Ss_wings[i]; if ( ss_wing->wingnum < 0 ) { Int3(); continue; } wp = &Wings[ss_wing->wingnum]; for ( j = 0; j < ss_wing->num_slots; j++ ) { ss_slot = &ss_wing->ss_slots[j]; ss_slot->in_mission = true; if ( ss_slot->sa_index == -1 ) { ss_slot->original_ship_class = Ships[wp->ship_index[j]].ship_info_index; } //set the lock to the default if (Game_mode & GM_MULTIPLAYER) { ss_slot->status = WING_SLOT_LOCKED; } else { ss_slot->status = 0; } // Set the type of slot. Check if the slot is marked as locked, if so then the player is not // going to be able to modify that ship. if ( ss_slot->sa_index == -1 ) { int objnum; if ( Ships[wp->ship_index[j]].flags[Ship::Ship_Flags::Ship_locked] ) { ss_slot->status |= WING_SLOT_SHIPS_DISABLED; } if ( Ships[wp->ship_index[j]].flags[Ship::Ship_Flags::Weapons_locked] ) { ss_slot->status |= WING_SLOT_WEAPONS_DISABLED; } // if neither the ship or weapon has been locked, mark the slot as filled if (!(ss_slot->status & WING_SLOT_DISABLED)) { ss_slot->status = WING_SLOT_FILLED; } objnum = Ships[wp->ship_index[j]].objnum; if ( Objects[objnum].flags[Object::Object_Flags::Player_ship] ) { if ( ss_slot->status & WING_SLOT_LOCKED ) { // Int3(); // Get Alan // just unflag it ss_slot->status &= ~(WING_SLOT_LOCKED); } ss_slot->status |= WING_SLOT_FILLED; if ( objnum == OBJ_INDEX(Player_obj) ) { ss_slot->status |= WING_SLOT_IS_PLAYER; } } } else { if (Parse_objects[ss_slot->sa_index].flags[Mission::Parse_Object_Flags::SF_Ship_locked]) { ss_slot->status |= WING_SLOT_SHIPS_DISABLED; } if ( Parse_objects[ss_slot->sa_index].flags[Mission::Parse_Object_Flags::SF_Weapons_locked] ) { ss_slot->status |= WING_SLOT_WEAPONS_DISABLED; } // if the slot is not marked as locked, it's filled if (!(ss_slot->status & WING_SLOT_DISABLED)) { ss_slot->status = WING_SLOT_FILLED; } if ( Parse_objects[ss_slot->sa_index].flags[Mission::Parse_Object_Flags::OF_Player_start] ) { if ( ss_slot->status & WING_SLOT_LOCKED ) { // Int3(); // Get Alan // just unflag it ss_slot->status &= ~(WING_SLOT_LOCKED); } ss_slot->status |= WING_SLOT_FILLED; ss_slot->status |= WING_SLOT_IS_PLAYER; } } // Assign the ship class to the unit Wss_slots[i*MAX_WING_SLOTS+j].ship_class = ss_slot->original_ship_class; } // end for } // end for // lock/unlock any necessary slots for multiplayer if(Game_mode & GM_MULTIPLAYER){ ss_recalc_multiplayer_slots(); } } // set the necessary pointers void ss_set_team_pointers(int team) { Assert( (team >= 0) && (team < MAX_TVT_TEAMS) ); Ss_wings = Ss_wings_teams[team]; Ss_icons = &Ss_icons_teams[team]; } // reset the necessary pointers to defaults void ss_reset_team_pointers() { Assert( !Ship_select_open ); if ( Ship_select_open ) return; Ss_wings = NULL; Ss_icons = nullptr; } // initialize team specific stuff void ship_select_init_team_data(int team_num) { int idx; // set up the pointers to initialize the data structures. common_set_team_pointers(team_num); ss_fixup_team_data(&Team_data[team_num]); ss_init_pool(&Team_data[team_num]); ss_clear_slots(); // reset data for slots ss_clear_wings(); // determine how many wings we should be checking for Wss_num_wings = 0; if(MULTI_TEAM){ // now setup wings for easy reference ss_init_wing_info(0,team_num); } else { // now setup wings for easy reference for (idx = 0; idx < MAX_STARTING_WINGS; idx++) { ss_init_wing_info(Wss_num_wings, idx); } } // if there are no wings, don't call the init_units() function if ( Wss_num_wings <= 0 ) { Wss_slots[0].ship_class = Team_data[team_num].default_ship; return; } ss_init_units(); } // called when the briefing is entered void ship_select_common_init(bool API_Access) { // In team-vs-team, initialize every other team first; we always initialize our own team // last so that the team pointers -- which common_set_team_pointers repoints to whichever // team was initialized most recently -- are left pointing at our team for the rest of the screen. if(MULTI_TEAM){ for(int idx=0;idx max_start ) { old_list_start = max_start; } SS_active_list_start = old_list_start; for ( i = 0; i < MAX_WSS_SLOTS; i++ ) { slot = &Ss_wings[i/MAX_WING_SLOTS].ss_slots[i%MAX_WING_SLOTS]; if ( Wss_slots[i].ship_class == -1 ) { if ( slot->status & WING_SLOT_FILLED ) { slot->status &= ~WING_SLOT_FILLED; slot->status |= WING_SLOT_EMPTY; } } else { if ( slot->status & WING_SLOT_EMPTY ) { slot->status &= ~WING_SLOT_EMPTY; slot->status |= WING_SLOT_FILLED; } } } } // exit: data changed flag int ss_swap_slot_slot(int from_slot, int to_slot, interface_snd_id *sound) { int i, tmp; if ( from_slot == to_slot ) { *sound=InterfaceSounds::ICON_DROP_ON_WING; return 0; } // ensure from_slot has a ship to pick up if ( Wss_slots[from_slot].ship_class < 0 ) { *sound=InterfaceSounds::ICON_DROP; return 0; } // swap ship class tmp = Wss_slots[from_slot].ship_class; Wss_slots[from_slot].ship_class = Wss_slots[to_slot].ship_class; Wss_slots[to_slot].ship_class = tmp; // swap weapons for ( i = 0; i < MAX_SHIP_WEAPONS; i++ ) { tmp = Wss_slots[from_slot].wep[i]; Wss_slots[from_slot].wep[i] = Wss_slots[to_slot].wep[i]; Wss_slots[to_slot].wep[i] = tmp; tmp = Wss_slots[from_slot].wep_count[i]; Wss_slots[from_slot].wep_count[i] = Wss_slots[to_slot].wep_count[i]; Wss_slots[to_slot].wep_count[i] = tmp; } *sound=InterfaceSounds::ICON_DROP_ON_WING; return 1; } // exit: data changed flag int ss_dump_to_list(int from_slot, int to_list, interface_snd_id *sound) { int i; wss_unit *slot; Assert( (Ss_pool != NULL) && (Wl_pool != NULL) && (Wss_slots != NULL) ); slot = &Wss_slots[from_slot]; // ensure from_slot has a ship to pick up if ( slot->ship_class < 0 ) { *sound=InterfaceSounds::ICON_DROP; return 0; } // put ship back in list (*Ss_pool)[to_list]++; // return to list slot->ship_class = -1; // remove from slot // put weapons back in list for ( i = 0; i < MAX_SHIP_WEAPONS; i++ ) { if ( (slot->wep[i] >= 0) && (slot->wep_count[i] > 0) ) { (*Wl_pool)[slot->wep[i]] += slot->wep_count[i]; slot->wep[i] = -1; slot->wep_count[i] = 0; } } *sound=InterfaceSounds::ICON_DROP; return 1; } // exit: data changed flag int ss_grab_from_list(int from_list, int to_slot, interface_snd_id *sound) { wss_unit *slot; int i, wep[MAX_SHIP_WEAPONS], wep_count[MAX_SHIP_WEAPONS]; Assert( (Ss_pool != NULL) && (Wss_slots != NULL) ); slot = &Wss_slots[to_slot]; // ensure that pool has ship if ( Ss_pool->value_or(from_list, -1) <= 0 ) { *sound=InterfaceSounds::ICON_DROP; return 0; } Assert(slot->ship_class < 0 ); // slot should be empty // take ship from list->slot (*Ss_pool)[from_list]--; slot->ship_class = from_list; // take weapons from list->slot wl_get_default_weapons(from_list, to_slot, wep, wep_count); wl_remove_weps_from_pool(wep, wep_count, slot->ship_class); for ( i = 0; i < MAX_SHIP_WEAPONS; i++ ) { slot->wep[i] = wep[i]; slot->wep_count[i] = wep_count[i]; } *sound=InterfaceSounds::ICON_DROP_ON_WING; return 1; } // exit: data changed flag int ss_swap_list_slot(int from_list, int to_slot, interface_snd_id *sound) { int i, wep[MAX_SHIP_WEAPONS], wep_count[MAX_SHIP_WEAPONS]; wss_unit *slot; Assert( (Ss_pool != NULL) && (Wl_pool != NULL) && (Wss_slots != NULL) ); // ensure that pool has ship if ( Ss_pool->value_or(from_list, -1) <= 0 ) { *sound=InterfaceSounds::ICON_DROP; return 0; } slot = &Wss_slots[to_slot]; Assert(slot->ship_class >= 0 ); // slot should be filled // put ship from slot->list (*Ss_pool)[Wss_slots[to_slot].ship_class]++; // put weapons from slot->list for ( i = 0; i < MAX_SHIP_WEAPONS; i++ ) { if ( (slot->wep[i] >= 0) && (slot->wep_count[i] > 0) ) { (*Wl_pool)[slot->wep[i]] += slot->wep_count[i]; slot->wep[i] = -1; slot->wep_count[i] = 0; } } // take ship from list->slot (*Ss_pool)[from_list]--; slot->ship_class = from_list; // take weapons from list->slot wl_get_default_weapons(from_list, to_slot, wep, wep_count); wl_remove_weps_from_pool(wep, wep_count, slot->ship_class); for ( i = 0; i < MAX_SHIP_WEAPONS; i++ ) { slot->wep[i] = wep[i]; slot->wep_count[i] = wep_count[i]; } *sound=InterfaceSounds::ICON_DROP_ON_WING; return 1; } void ss_apply(int mode, int from_slot, int from_list, int to_slot, int to_list,int player_index) { int update=0; interface_snd_id sound; switch(mode){ case WSS_SWAP_SLOT_SLOT: update = ss_swap_slot_slot(from_slot, to_slot, &sound); break; case WSS_DUMP_TO_LIST: update = ss_dump_to_list(from_slot, to_list, &sound); break; case WSS_GRAB_FROM_LIST: update = ss_grab_from_list(from_list, to_slot, &sound); break; case WSS_SWAP_LIST_SLOT: update = ss_swap_list_slot(from_list, to_slot, &sound); break; } // only play this sound if the move was done locally (by the host in other words) if ( (sound.isValid()) && (player_index == -1) ) { gamesnd_play_iface(sound); } if ( update ) { // NO LONGER USED - THERE IS A MULTIPLAYER VERSION OF THIS SCREEN NOW /* if ( MULTIPLAYER_HOST ) { int size; ubyte wss_data[MAX_PACKET_SIZE-20]; size = store_wss_data(wss_data, MAX_PACKET_SIZE-20, sound); send_wss_update_packet(wss_data, size, player_index); } */ ss_synch_interface(); } } void ss_drop(int from_slot,int from_list,int to_slot,int to_list,int player_index) { int mode; common_flash_button_init(); mode = wss_get_mode(from_slot, from_list, to_slot, to_list, -1); if ( mode >= 0 ) { ss_apply(mode, from_slot, from_list, to_slot, to_list,player_index); } } // lock/unlock any necessary slots for multiplayer void ss_recalc_multiplayer_slots() { int i,j; ss_slot_info *ss_slot; ss_wing_info *ss_wing; // no wings if ( Wss_num_wings <= 0 ) { Assert( Wss_slots != NULL ); Wss_slots[0].ship_class = Team_data[Common_team].default_ship; return; } Assert( Ss_wings != NULL ); for ( i = 0; i < Wss_num_wings; i++ ) { ss_wing = &Ss_wings[i]; if ( ss_wing->wingnum < 0 ) { Int3(); continue; } // NOTE : the method below will eventually have to change to account for all possible netgame options for ( j = 0; j < ss_wing->num_slots; j++ ) { // get the slot pointer ss_slot = &ss_wing->ss_slots[j]; if (ss_slot->sa_index == -1) { // lock all slots by default ss_slot->status |= WING_SLOT_LOCKED; // if this is my slot, then unlock it if(!multi_ts_disabled_slot((i*MAX_WING_SLOTS)+j)){ ss_slot->status &= ~WING_SLOT_LOCKED; } } } } }