Files
fs2open.github.com/code/missionui/missionshipchoice.cpp
T
Goober5000andClaude Fable 5.1 b37cfda56d Fix a model refcount bug on loadout change, and harden model_unload against stale handles (#7851)
* take a model reference when a ship changes class

ship_model_change() only loaded the model when the class had none, so a ship
switched to a class whose model had been loaded only by a select-screen
preview adopted that preview's single reference.  Closing the screen then
freed the model from under the ship, and the first simulation frame asserted
in model_get().  This is what happened on a replayed mission with a restored
loadout: ship select was skipped, the weapon-select overhead loaded the new
class once, the commit switched the player to it, and the screen close
released it.

Load unconditionally, as ship_create() already does, so the ship holds its own
reference.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* reject stale model ids in model_unload and release select-screen handles at level close

model_unload only used the slot part of the id it was given, so a handle kept
across missions would free whatever model had since been loaded into that slot.
Validate the id against the slot's occupant and warn instead.

Two holders could present such handles: the weapon-select icon models under a
scripted UI that overrides the state hooks (the native close never runs), and
the ship-select 3D model global, which was never reset between missions.
Release the select-screen handles from game_level_close, and unload the
ship-select model on screen close and on every selection change.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* consolidate the select-screen resource release

The weapon side released its icons, anims, and ship class data from two
functions keyed on whether the screen was open, and the level-close path added
a third copy.  Give the weapon side the same shape as the ship side: one
unconditional weapon_select_common_close(), with weapon_select_close() left as
pure UI teardown.  common_select_level_close() now just calls the two common
closes, and common_select_close() calls it in turn.  The ship-select 3D model
is released from ship_select_common_close() so both paths cover it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

---------

Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-10-13 02:19:48 -04:00

3439 lines
98 KiB
C++

/*
* 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 <algorithm>
#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<int, ss_icon_info> Ss_icons_teams[MAX_TVT_TEAMS];
SCP_map<int, ss_icon_info> *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<int> 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; i<SHIP_SELECT_NUM_TEXT; i++){
Ship_select_ui_window.add_XSTR(&Ship_select_text[gr_screen.res][i]);
}
// We don't want to have the reset button appear in multiplayer
if ( Game_mode & GM_MULTIPLAYER ) {
Ship_select_buttons[gr_screen.res][SS_BUTTON_RESET].button.disable();
Ship_select_buttons[gr_screen.res][SS_BUTTON_RESET].button.hide();
}
Ship_select_buttons[gr_screen.res][SS_BUTTON_DUMMY].button.disable();
Ship_select_buttons[gr_screen.res][SS_BUTTON_DUMMY].button.hide();
}
// -------------------------------------------------------------------------------------
// ship_select_button_do() do the button action for the specified pressed button
//
void ship_select_button_do(int i)
{
if ( Background_playing )
return;
switch ( i ) {
case SHIP_SELECT_SHIP_SCROLL_UP:
if ( Current_screen != ON_SHIP_SELECT )
break;
if ( common_scroll_down_pressed(&SS_active_list_start, sz2i(SS_active_list.size()), MAX_ICONS_ON_SCREEN) ) {
gamesnd_play_iface(InterfaceSounds::SCROLL);
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
break;
case SHIP_SELECT_SHIP_SCROLL_DOWN:
if ( Current_screen != ON_SHIP_SELECT )
break;
if ( common_scroll_up_pressed(&SS_active_list_start, sz2i(SS_active_list.size()), MAX_ICONS_ON_SCREEN) ) {
gamesnd_play_iface(InterfaceSounds::SCROLL);
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
break;
case SHIP_SELECT_RESET:
ss_reset_to_default();
break;
} // end switch
}
// ---------------------------------------------------------------------
// ship_select_init() is called once when the ship select screen begins
//
//
void ship_select_init()
{
common_set_interface_palette("ShipPalette");
common_flash_button_init();
// if in multiplayer -- set my state to be ship select
if ( Game_mode & GM_MULTIPLAYER ){
// also set the ship which is mine as the default
maybe_change_selected_wing_ship(Net_player->p_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<n_lines;idx++ ) {
Assert(n_chars[idx] < SHIP_SELECT_SHIP_INFO_MAX_LINE_LEN);
strncpy(Ship_select_ship_info_lines[idx], p_str[idx], n_chars[idx]);
Ship_select_ship_info_lines[idx][n_chars[idx]] = 0;
drop_leading_white_space(Ship_select_ship_info_lines[idx]);
}
// get the line count
Ship_select_ship_info_line_count = n_lines;
} else {
// set the line count to
Ship_select_ship_info_line_count = 0;
}
Assert(Ship_select_ship_info_line_count < MAX_NUM_SHIP_DESC_LINES);
gr_set_color_fast(text);
for(int idx=0;idx<Ship_select_ship_info_line_count;idx++){
gr_string(Ship_info_coords[gr_screen.res][SHIP_SELECT_X_COORD]+4, y_start, Ship_select_ship_info_lines[idx], GR_RESIZE_MENU);
y_start += line_height;
}
}
// ---------------------------------------------------------------------
// ship_select_do() is called once per game frame, and is responsible for
// updating the ship select screen
//
// frametime is in seconds
extern int Tech_ship_display_coords[GR_NUM_RESOLUTIONS][4];
void ship_select_do(float frametime)
{
GR_DEBUG_SCOPE("Ship select");
int k, ship_select_choice, snazzy_action;
ship_select_choice = snazzy_menu_do(ShipSelectMaskData, Shipselect_mask_w, Shipselect_mask_h, Num_mask_regions, Region, &snazzy_action, 0);
Hot_ss_icon = -1;
Hot_ss_slot = -1;
k = common_select_do(frametime);
// Check common keypresses
common_check_keys(k);
if ( Mouse_down_last_frame ) {
Ss_mouse_down_on_region = ship_select_choice;
}
// Check for the mouse over a region (not clicked, just over)
if ( ship_select_choice > -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<int, ss_icon_info> &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;idx<MAX_STARTING_WINGS;idx++){
Ss_wings[idx].wingnum = -1;
Ss_wings[idx].num_slots = 0;
Ss_wings[idx].is_late = 0;
}
}
// set up Wss_num_wings and Wss_wings[] based on Starting_wings[] info
void ss_init_wing_info(int wing_num,int starting_wing_num)
{
wing *wp;
ss_wing_info *ss_wing;
ss_slot_info *slot;
Assert( Ss_wings != NULL );
ss_wing = &Ss_wings[wing_num];
if ( Starting_wings[starting_wing_num] < 0 ) {
return;
}
ss_wing->wingnum = 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<MULTI_TS_MAX_TVT_TEAMS;idx++){
if(idx != Common_team)
ship_select_init_team_data(idx);
}
}
ship_select_init_team_data(Common_team);
if (!API_Access) {
init_active_list();
// load the necessary icons/animations
ss_load_all_icons();
ss_reset_selected_ship();
ss_reset_carried_icon();
}
}
void ship_select_common_close()
{
// unload every team's icons
for ( auto &team_icons : Ss_icons_teams ) {
ss_unload_team_icons(team_icons);
team_icons.clear();
}
ss_unload_select_model();
}
// change any interface data based on updated Wss_slots[] and Ss_pool[]
void ss_synch_interface()
{
int i;
ss_slot_info *slot;
Assert( Ss_wings != NULL );
int old_list_start = SS_active_list_start;
init_active_list(); // build the list of pool ships
// clamp the preserved scroll offset against the largest valid offset
// for the new list size, so the up arrow can still reach items at the
// top after a swap shrinks the active list (e.g., to <= MAX_ICONS_ON_SCREEN)
int max_start = sz2i(SS_active_list.size()) - MAX_ICONS_ON_SCREEN;
if ( max_start < 0 ) {
max_start = 0;
}
if ( old_list_start > 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;
}
}
}
}
}