Files
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

4235 lines
118 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 "anim/animplay.h"
#include "anim/packunpack.h"
#include "cfile/cfile.h"
#include "gamehelp/contexthelp.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "globalincs/utility.h"
#include "graphics/shadows.h"
#include "graphics/matrix.h"
#include "hud/hudbrackets.h"
#include "io/mouse.h"
#include "io/timer.h"
#include "lighting/lighting.h"
#include "lighting/lighting_profiles.h"
#include "localization/localize.h"
#include "menuui/snazzyui.h"
#include "missionui/chatbox.h"
#include "missionui/missionbrief.h"
#include "missionui/missionscreencommon.h"
#include "missionui/missionshipchoice.h"
#include "missionui/missionweaponchoice.h"
#include "model/model.h"
#include "model/modelrender.h"
#include "mod_table/mod_table.h"
#include "network/multi.h"
#include "network/multi_pmsg.h"
#include "network/multimsgs.h"
#include "network/multiteamselect.h"
#include "network/multiui.h"
#include "network/multiutil.h"
#include "parse/parselo.h"
#include "popup/popup.h"
#include "render/3d.h"
#include "render/batching.h"
#include "ship/ship.h"
#include "weapon/weapon.h"
#define IS_BANK_PRIMARY(x) (x < MAX_SHIP_PRIMARY_BANKS)
#define IS_BANK_SECONDARY(x) (x >= MAX_SHIP_PRIMARY_BANKS)
#define IS_LIST_PRIMARY(x) (Weapon_info[x].subtype != WP_MISSILE)
#define IS_LIST_SECONDARY(x) (Weapon_info[x].subtype == WP_MISSILE)
//////////////////////////////////////////////////////////////////
// Game-wide globals
//////////////////////////////////////////////////////////////////
// This game-wide global flag is set to 1 to indicate that the weapon
// select screen has been opened and memory allocated. This flag
// is needed so we can know if weapon_select_close() needs to called if
// restoring a game from the Options screen invoked from weapon select
int Weapon_select_open = 0;
//////////////////////////////////////////////////////////////////
// UI Data
//////////////////////////////////////////////////////////////////
typedef struct wl_bitmap_group
{
int first_frame;
int num_frames;
} wl_bitmap_group;
#define WEAPON_ANIM_LOOP_FRAME 52 // frame (from 0) to loop weapon anim
#define WEAPON_ICON_FRAME_NORMAL 0
#define WEAPON_ICON_FRAME_HOT 1
#define WEAPON_ICON_FRAME_SELECTED 2
#define WEAPON_ICON_FRAME_DISABLED 3
constexpr int NUM_WL_LAYOUTS = 2;
#define MAX_WEAPON_BUTTONS 8
#define MIN_WEAPON_BUTTONS 7
#define NUM_WEAPON_BUTTONS (Uses_apply_all_button ? MAX_WEAPON_BUTTONS : MIN_WEAPON_BUTTONS)
// Weapn loadout specific buttons
#define WL_BUTTON_SCROLL_PRIMARY_UP 0
#define WL_BUTTON_SCROLL_PRIMARY_DOWN 1
#define WL_BUTTON_SCROLL_SECONDARY_UP 2
#define WL_BUTTON_SCROLL_SECONDARY_DOWN 3
#define WL_BUTTON_RESET 4
#define WL_BUTTON_DUMMY 5
#define WL_BUTTON_MULTI_LOCK 6
#define WL_BUTTON_APPLY_ALL 7
// mask regions for icons in the scrollable lists
// NOLINTBEGIN(modernize-macro-to-enum)
#define ICON_PRIMARY_0 28
#define ICON_PRIMARY_1 29
#define ICON_PRIMARY_2 30
#define ICON_PRIMARY_3 31
#define ICON_SECONDARY_0 10
#define ICON_SECONDARY_1 11
#define ICON_SECONDARY_2 12
#define ICON_SECONDARY_3 13
// NOLINTEND(modernize-macro-to-enum)
constexpr int NUM_PRIMARY_MASK_REGIONS = 4;
constexpr int NUM_SECONDARY_MASK_REGIONS = 4;
// mask regions for icons that sit above the ship
// NOLINTBEGIN(modernize-macro-to-enum)
#define ICON_SHIP_PRIMARY_0 32
#define ICON_SHIP_PRIMARY_1 33
#define ICON_SHIP_PRIMARY_2 34
#define ICON_SHIP_SECONDARY_0 35
#define ICON_SHIP_SECONDARY_1 36
#define ICON_SHIP_SECONDARY_2 37
#define ICON_SHIP_SECONDARY_3 38
// NOLINTEND(modernize-macro-to-enum)
extern int anim_timer_start;
int Weapon_select_overlay_id = -1;
// convenient struct for handling all button controls
struct wl_buttons {
const char *filename;
int x, y, xt, yt;
int hotspot;
UI_BUTTON button; // because we have a class inside this struct, we need the constructor below..
wl_buttons(const char *name, int x1, int y1, int xt1, int yt1, int h) : filename(name), x(x1), y(y1), xt(xt1), yt(yt1), hotspot(h) {}
};
static wl_buttons Buttons[GR_NUM_RESOLUTIONS][MAX_WEAPON_BUTTONS] = {
{
wl_buttons("WLB_27", 24, 276, -1, -1, 27), // WL_BUTTON_SCROLL_PRIMARY_UP
wl_buttons("WLB_26", 24, 125, -1, -1, 26), // WL_BUTTON_SCROLL_PRIMARY_DOWN
wl_buttons("WLB_09", 24, 454, -1, -1, 9), // WL_BUTTON_SCROLL_SECONDARY_UP
wl_buttons("WLB_08", 24, 303, -1, -1, 8), // WL_BUTTON_SCROLL_SECONDARY_DOWN
wl_buttons("ssb_39", 571, 347, -1, -1, 39), // WL_BUTTON_RESET
wl_buttons("ssb_39", 0, 0, -1, -1, 99), // WL_BUTTON_DUMMY
wl_buttons("TSB_34", 603, 374, -1, -1, 34), // WL_BUTTON_MULTI_LOCK
wl_buttons("WLB_40", 0, 90, -1, -1, 40) // WL_BUTTON_APPLY_ALL
},
{
wl_buttons("2_WLB_27", 39, 442, -1, -1, 27),
wl_buttons("2_WLB_26", 39, 200, -1, -1, 26),
wl_buttons("2_WLB_09", 39, 727, -1, -1, 9),
wl_buttons("2_WLB_08", 39, 485, -1, -1, 8),
wl_buttons("2_ssb_39", 913, 556, -1, -1, 39),
wl_buttons("2_ssb_39", 0, 0, -1, -1, 99),
wl_buttons("2_TSB_34", 966, 599, -1, -1, 34),
wl_buttons("2_WLB_40", 0, 138, -1, -1, 40)
}
};
static const char *Wl_mask_single[NUM_WL_LAYOUTS][GR_NUM_RESOLUTIONS] = {
{
"weaponloadout-m",
"2_weaponloadout-m"
},
{
"weaponloadout-mb",
"2_weaponloadout-mb"
}
};
static const char *Wl_mask_multi[NUM_WL_LAYOUTS][GR_NUM_RESOLUTIONS] = {
{
"weaponloadoutmulti-m",
"2_weaponloadoutmulti-m"
},
{
"weaponloadoutmulti-mb",
"2_weaponloadoutmulti-mb"
}
};
static const char *Wl_loadout_select_mask[NUM_WL_LAYOUTS][GR_NUM_RESOLUTIONS] = {
{
"weaponloadout-m",
"2_weaponloadout-m"
},
{
"weaponloadout-mb",
"2_weaponloadout-mb"
}
};
static const char *Weapon_select_background_fname[NUM_WL_LAYOUTS][GR_NUM_RESOLUTIONS] = {
{
"WeaponLoadout",
"2_WeaponLoadout"
},
{
"WeaponLoadoutb",
"2_WeaponLoadoutb"
}
};
static const char *Weapon_select_multi_background_fname[NUM_WL_LAYOUTS][GR_NUM_RESOLUTIONS] = {
{
"WeaponLoadoutMulti",
"2_WeaponLoadoutMulti"
},
{
"WeaponLoadoutMultib",
"2_WeaponLoadoutMultib"
}
};
static int Uses_apply_all_button = 0;
int Weapon_select_background_bitmap; // bitmap for weapon select brackground
static MENU_REGION Weapon_select_region[NUM_COMMON_REGIONS + 27]; // see initialization
static int Num_weapon_select_regions;
// Mask bitmap pointer and Mask bitmap_id
static bitmap* WeaponSelectMaskPtr; // bitmap pointer to the weapon select mask bitmap
static ubyte* WeaponSelectMaskData; // pointer to actual bitmap data
static int Weaponselect_mask_w, Weaponselect_mask_h;
static int WeaponSelectMaskBitmap; // bitmap id of the weapon select mask bitmap
static int Weapon_slot_bitmap;
UI_WINDOW Weapon_ui_window;
static int Weapon_button_scrollable[MAX_WEAPON_BUTTONS] = {0, 0, 0, 0, 0, 0, 0, 0};
#define MAX_WEAPON_ICONS_ON_SCREEN 8
// X and Y locations of the weapon icons in the scrollable lists
static int Wl_weapon_icon_coords[GR_NUM_RESOLUTIONS][MAX_WEAPON_ICONS_ON_SCREEN][2] = {
{
{27, 152},
{27, 182},
{27, 212},
{27, 242},
{36, 331},
{36, 361},
{36, 391},
{36, 421}
},
{
{59, 251},
{59, 299},
{59, 347},
{59, 395},
{59, 538},
{59, 586},
{59, 634},
{59, 682}
}
};
static int Wl_bank_coords[GR_NUM_RESOLUTIONS][MAX_SHIP_WEAPONS][2] = {
{
{106,127},
{106,158},
{106,189},
{322,127},
{322,158},
{322,189},
{322,220},
},
{
{170,203},
{170,246},
{170,290},
{552,203},
{552,246},
{552,290},
{552,333},
}
};
static int Wl_bank_count_draw_flags[MAX_SHIP_WEAPONS] = {
0, 0, 0, // primaries -- don't draw counts
1, 1, 1, 1 // secondaries -- do draw counts
};
static int Weapon_anim_class = -1;
static int Last_wl_ship_class;
static int Wl_overhead_coords[GR_NUM_RESOLUTIONS][2] = {
{
// GR_640
91, 117
},
{
// GR_1024
156, 183
}
};
static int Wl_weapon_ani_coords[GR_NUM_RESOLUTIONS][2] = {
{
408, 82 // GR_640
},
{
648, 128 // GR_1024
}
};
static int Wl_weapon_ani_coords_multi[GR_NUM_RESOLUTIONS][2] = {
{
408, 143 // GR_640
},
{
648, 226 // GR_1024
}
};
static int Wl_weapon_desc_coords[GR_NUM_RESOLUTIONS][2] = {
{
508, 283 // GR_640
},
{
813, 453 // GR_1024
}
};
static int Wl_delta_x, Wl_delta_y;
static int Wl_ship_name_coords[GR_NUM_RESOLUTIONS][2] = {
{
85, 106
},
{
136, 170
}
};
///////////////////////////////////////////////////////////////////////
// UI data structs
///////////////////////////////////////////////////////////////////////
typedef struct wl_ship_class_info
{
int overhead_bitmap = -1;
int model_num = -1;
generic_anim animation;
wl_ship_class_info()
{
generic_anim_init(&animation, nullptr);
}
} wl_ship_class_info;
// overhead-view data, keyed by ship class; an absent entry means nothing is loaded for that class
SCP_map<int, wl_ship_class_info> Wl_ships;
struct wl_icon_info
{
int icon_bmaps[NUM_ICON_FRAMES];
int laser_bmap = -1;
int model_index = -1;
bool can_use_for_ship = false;
TriStateBool can_use_for_bank = TriStateBool::UNKNOWN_;
generic_anim animation;
wl_icon_info()
{
for (int &bmap : icon_bmaps)
bmap = -1;
generic_anim_init(&animation, nullptr);
}
};
// ui info for weapon icons, keyed by weapon class; entries hold usability flags for all weapon classes, but icon bitmaps/models are loaded only for pool weapons
SCP_map<int, wl_icon_info> Wl_icons_teams[MAX_TVT_TEAMS];
SCP_map<int, wl_icon_info> *Wl_icons = nullptr;
SCP_vector<int> Plist; // weapon classes in the primary icon list, in pool order
int Plist_start; // scroll offset into Plist
SCP_vector<int> Slist; // weapon classes in the secondary icon list, in pool order
int Slist_start; // scroll offset into Slist
static int Selected_wl_slot = -1; // Currently selected ship slot
static int Selected_wl_class = -1; // Class of weapon that is selected
static int Hot_wl_slot = -1; // Ship slot that mouse is over (0..MAX_WSS_SLOTS-1)
static int Hot_weapon_icon = -1; // Icon number (0-7) which has mouse over it
static int Hot_weapon_bank = -1; // index (0-7) for weapon slot on ship that has a droppable icon over it
static int Hot_weapon_bank_icon = -1;
static generic_anim Cur_Anim;
static int Wl_mouse_down_on_region = -1;
// weapon desc stuff
#define WEAPON_DESC_WIPE_TIME 1.5f // time in seconds for wipe to occur (over WEAPON_DESC_MAX_LENGTH number of chars)
static int Weapon_desc_wipe_done = 0;
static float Weapon_desc_wipe_time_elapsed = 0.0f;
static char Weapon_desc_lines[WEAPON_DESC_MAX_LINES][WEAPON_DESC_MAX_LENGTH]; // 1st 2 lines are title, rest are desc
// maximum width the weapon title can be -- used in the line breaking
int Weapon_title_max_width[GR_NUM_RESOLUTIONS] = { 200, 320 };
static int Wl_new_weapon_title_coords[GR_NUM_RESOLUTIONS][2] = {
{
408, 75 // GR_640
},
{
648, 118 // GR_1024
}
};
static int Wl_new_weapon_title_coords_multi[GR_NUM_RESOLUTIONS][2] = {
{
408, 136 // GR_640
},
{
648, 216 // GR_1024
}
};
static int Wl_new_weapon_desc_coords[GR_NUM_RESOLUTIONS][2] = {
{
408, 247 // GR_640
},
{
648, 395 // GR_1024
}
};
static int Wl_new_weapon_desc_coords_multi[GR_NUM_RESOLUTIONS][2] = {
{
408, 308 // GR_640
},
{
648, 493 // GR_1024
}
};
// ship select text
#define WEAPON_SELECT_NUM_TEXT 2
UI_XSTR Weapon_select_text[GR_NUM_RESOLUTIONS][WEAPON_SELECT_NUM_TEXT] = {
{ // GR_640
{ "Reset", 1337, 580, 337, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][WL_BUTTON_RESET].button },
{ "Lock", 1270, 602, 364, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][WL_BUTTON_MULTI_LOCK].button }
},
{ // GR_1024
{ "Reset", 1337, 938, 546, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][WL_BUTTON_RESET].button },
{ "Lock", 1270, 964, 584, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][WL_BUTTON_MULTI_LOCK].button }
}
};
///////////////////////////////////////////////////////////////////////
// Carried Icon
///////////////////////////////////////////////////////////////////////
typedef struct carried_icon
{
int weapon_class; // weapon class of the carried icon (-1 if carried from bank)
int num; // number of units of weapon
int from_bank; // bank index that icon came from (0..2 primary, 3..6 secondary). -1 if from list
int from_slot; // ship slot that weapon is part of
int from_x, from_y;
} carried_icon;
static carried_icon Carried_wl_icon;
// forward declarations
void draw_wl_icons();
void wl_draw_ship_weapons(int index);
void wl_pick_icon_from_list(int index);
void pick_from_ship_slot(int num);
void start_weapon_animation(int weapon_class);
void stop_weapon_animation();
int wl_get_pilot_subsys_index(p_object *pobjp);
void wl_reset_to_defaults();
void wl_set_selected_slot(int slot_num);
void wl_maybe_reset_selected_slot();
void wl_maybe_reset_selected_weapon_class();
void wl_render_icon_count(int num, int x, int y);
void wl_render_weapon_desc();
void wl_apply_current_loadout_to_all_ships_in_current_wing();
// carry icon functions
void wl_reset_carried_icon();
int wl_icon_being_carried();
void wl_set_carried_icon(int from_bank, int from_slot, int weapon_class);
const char *wl_tooltip_handler(const char *str)
{
if (Selected_wl_class < 0)
return NULL;
if (!stricmp(str, "@weapon_desc")) {
const char *str2;
int x, y, w, h;
str2 = coalesce(Weapon_info[Selected_wl_class].desc.get(), "");
gr_get_string_size(&w, &h, str2);
x = Wl_weapon_desc_coords[gr_screen.res][0] - w / 2;
y = Wl_weapon_desc_coords[gr_screen.res][1] - 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 NULL;
}
return NULL;
}
/**
* Reset the data inside Carried_wl_icon
*/
void wl_reset_carried_icon()
{
Carried_wl_icon.weapon_class = -1;
Carried_wl_icon.num = 0;
Carried_wl_icon.from_bank = -1;
Carried_wl_icon.from_slot = -1;
}
/**
* Is an icon being carried?
*/
int wl_icon_being_carried()
{
if ( Carried_wl_icon.weapon_class >= 0 ) {
return 1;
}
return 0;
}
/**
* Set carried icon data
*/
void wl_set_carried_icon(int from_bank, int from_slot, int weapon_class)
{
int mx,my;
Carried_wl_icon.from_bank = from_bank;
Carried_wl_icon.from_slot = from_slot;
Carried_wl_icon.weapon_class = weapon_class;
mouse_get_pos_unscaled( &mx, &my );
Carried_wl_icon.from_x=mx;
Carried_wl_icon.from_y=my;
Buttons[gr_screen.res][WL_BUTTON_DUMMY].button.capture_mouse();
}
/**
* Determine if the carried icon has moved
*/
int wl_carried_icon_moved()
{
int mx, my;
mouse_get_pos_unscaled( &mx, &my );
if ( Carried_wl_icon.from_x != mx || Carried_wl_icon.from_y != my) {
return 1;
}
return 0;
}
/**
* @return the index for the pilot subsystem in the parse object
*/
int wl_get_pilot_subsys_index(p_object *pobjp)
{
int pilot_index, start_index, end_index, i;
// see if there is a PILOT subystem
start_index = pobjp->subsys_index;
end_index = start_index + pobjp->subsys_count;
pilot_index = -1;
for ( i = start_index; i < end_index; i++ ) {
if ( !subsystem_stricmp(Subsys_status[i].name, NOX("pilot") ) ) {
pilot_index = i;
break;
}
}
if ( pilot_index == -1 ) {
Error(LOCATION,"Parse object doesn't have a pilot subsystem\n");
return -1;
}
return pilot_index;
}
// ---------------------------------------------------------------------------------
// weapon_button_do()
//
void weapon_button_do(int i)
{
switch ( i ) {
case WL_BUTTON_SCROLL_PRIMARY_UP:
if ( common_scroll_up_pressed(&Plist_start, sz2i(Plist.size()), NUM_PRIMARY_MASK_REGIONS) ) {
gamesnd_play_iface(InterfaceSounds::SCROLL);
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
break;
case WL_BUTTON_SCROLL_PRIMARY_DOWN:
if ( common_scroll_down_pressed(&Plist_start, sz2i(Plist.size()), NUM_PRIMARY_MASK_REGIONS) ) {
gamesnd_play_iface(InterfaceSounds::SCROLL);
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
break;
case WL_BUTTON_SCROLL_SECONDARY_UP:
if ( common_scroll_up_pressed(&Slist_start, sz2i(Slist.size()), NUM_SECONDARY_MASK_REGIONS) ) {
gamesnd_play_iface(InterfaceSounds::SCROLL);
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
break;
case WL_BUTTON_SCROLL_SECONDARY_DOWN:
if ( common_scroll_down_pressed(&Slist_start, sz2i(Slist.size()), NUM_SECONDARY_MASK_REGIONS) ) {
gamesnd_play_iface(InterfaceSounds::SCROLL);
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
break;
case WL_BUTTON_RESET:
wl_reset_to_defaults();
break;
case WL_BUTTON_MULTI_LOCK:
Assert(Game_mode & GM_MULTIPLAYER);
// the "lock" button has been pressed
multi_ts_lock_pressed();
// disable the button if it is now locked
if(multi_ts_is_locked()){
Buttons[gr_screen.res][WL_BUTTON_MULTI_LOCK].button.disable();
}
break;
case WL_BUTTON_APPLY_ALL:
wl_apply_current_loadout_to_all_ships_in_current_wing();
break;
default:
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, "Button %d is not yet implemented", i);
break;
}
}
/**
* Check if any weapons loadout screen buttons have been pressed, and
* call weapon_button_do() if they have.
*/
void weapon_check_buttons()
{
int i;
wl_buttons *b;
for ( i = 0; i < NUM_WEAPON_BUTTONS; i++ ) {
b = &Buttons[gr_screen.res][i];
if ( b->button.pressed() ) {
weapon_button_do(i);
}
}
}
/**
* Redraw any weapon loadout 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 wl_redraw_pressed_buttons()
{
int i;
wl_buttons *b;
common_redraw_pressed_buttons();
for ( i = 0; i < NUM_WEAPON_BUTTONS; i++ ) {
b = &Buttons[gr_screen.res][i];
if ( b->button.button_down() ) {
b->button.draw_forced(2);
}
}
}
// ---------------------------------------------------------------------------------
// weapon_buttons_init()
//
void weapon_buttons_init()
{
wl_buttons *b;
int i;
for ( i = 0; i < NUM_WEAPON_BUTTONS; i++ ) {
b = &Buttons[gr_screen.res][i];
b->button.create( &Weapon_ui_window, "", Buttons[gr_screen.res][i].x, Buttons[gr_screen.res][i].y, 60, 30, Weapon_button_scrollable[i]);
// 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(Buttons[gr_screen.res][i].filename);
b->button.link_hotspot(Buttons[gr_screen.res][i].hotspot);
}
if ( Game_mode & GM_MULTIPLAYER ) {
Buttons[gr_screen.res][WL_BUTTON_RESET].button.hide();
Buttons[gr_screen.res][WL_BUTTON_RESET].button.disable();
// if we're not the host of the game (or a team captain in team vs. team mode), disable the lock button
if(Netgame.type_flags & NG_TYPE_TEAM){
if(!(Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
Buttons[gr_screen.res][WL_BUTTON_MULTI_LOCK].button.disable();
}
} else {
if(!(Net_player->flags & NETINFO_FLAG_GAME_HOST)){
Buttons[gr_screen.res][WL_BUTTON_MULTI_LOCK].button.disable();
}
}
} else {
Buttons[gr_screen.res][WL_BUTTON_MULTI_LOCK].button.hide();
Buttons[gr_screen.res][WL_BUTTON_MULTI_LOCK].button.disable();
}
// add all xstrs
for(i=0; i<WEAPON_SELECT_NUM_TEXT; i++) {
Weapon_ui_window.add_XSTR(&Weapon_select_text[gr_screen.res][i]);
}
Buttons[gr_screen.res][WL_BUTTON_DUMMY].button.hide();
Buttons[gr_screen.res][WL_BUTTON_DUMMY].button.disable();
}
// ---------------------------------------------------------------------------------
// draw_3d_overhead_view()
//
void draw_3d_overhead_view(int model_num,
int ship_class,
float* rotation_buffer,
float frametime,
int weapon_array[MAX_SHIP_WEAPONS],
int selected_weapon_class,
int hovered_weapon_slot,
int x1,
int y1,
int x2,
int y2,
int resize_mode,
int bank1_x,
int bank1_y,
int bank2_x,
int bank2_y,
int bank3_x,
int bank3_y,
int bank4_x,
int bank4_y,
int bank5_x,
int bank5_y,
int bank6_x,
int bank6_y,
int bank7_x,
int bank7_y,
int bank_prim_offset,
int bank_sec_offset,
int bank_y_offset,
overhead_style style,
const SCP_string& tcolor)
{
lighting_profiles::set_non_mission_profile non_mission_lighting_profile;
ship_info* sip = &Ship_info[ship_class];
if (model_num < 0) {
mprintf(("Couldn't load model file '%s' in missionweaponchoice.cpp\n", sip->pof_file));
} else {
matrix object_orient = IDENTITY_MATRIX;
angles rot_angles;
float zoom;
zoom = sip->closeup_zoom * 1.3f;
if (style == OH_TOP_VIEW) {
rot_angles.p = -(PI_2);
rot_angles.b = 0.0f;
rot_angles.h = 0.0f;
vm_angles_2_matrix(&object_orient, &rot_angles);
} else if (style == OH_ROTATING) {
float rev_rate;
rev_rate = REVOLUTION_RATE;
if (sip->is_big_ship()) {
rev_rate *= 1.7f;
}
if (sip->is_huge_ship()) {
rev_rate *= 3.0f;
}
*rotation_buffer += PI2 * frametime / rev_rate;
while (*rotation_buffer > PI2) {
*rotation_buffer -= PI2;
}
rot_angles.p = -0.6f;
rot_angles.b = 0.0f;
rot_angles.h = 0.0f;
vm_angles_2_matrix(&object_orient, &rot_angles);
rot_angles.p = 0.0f;
rot_angles.b = 0.0f;
rot_angles.h = *rotation_buffer;
vm_rotate_matrix_by_angles(&object_orient, &rot_angles);
} else {
Error(LOCATION, "Got call to draw overhead ship with invalid style!");
}
model_render_params render_info;
gr_set_clip(x1, y1, x2, y2, resize_mode);
g3_start_frame(1);
g3_set_view_matrix(&sip->closeup_pos, &vmd_identity_matrix, zoom);
render_info.set_detail_level_lock(0);
// setup lights
common_setup_room_lights();
Glowpoint_use_depth_buffer = false;
model_clear_instance(model_num);
int model_instance = -1;
auto cache_result = model_get_cached_ui_render_instance(model_num, &model_instance);
// Only set up the instance when it was freshly created; the cached instance persists across frames.
if (cache_result == TriStateBool::TRUE_) {
model_set_up_techroom_instance(sip, model_instance);
}
polymodel* pm = model_get(model_num);
if (sip->replacement_textures.size() > 0) {
render_info.set_replacement_textures(model_num, sip->replacement_textures);
}
if (shadow_maybe_start_frame(Shadow_disable_overrides.disable_mission_select_weapons)) {
gr_reset_clip();
shadows_start_render(&vmd_identity_matrix,
&Eye_position,
Proj_fov,
Proj_fov,
gr_screen.clip_aspect,
SCP_vector {{-sip->closeup_pos.xyz.z + pm->rad,
-sip->closeup_pos.xyz.z + pm->rad + 200.0f,
-sip->closeup_pos.xyz.z + pm->rad + 2000.0f,
-sip->closeup_pos.xyz.z + pm->rad + 10000.0f}});
render_info.set_flags(MR_NO_TEXTURING | MR_NO_LIGHTING | MR_AUTOCENTER);
model_render_immediate(&render_info, model_num, model_instance, &object_orient, &vmd_zero_vector);
shadows_end_render();
gr_set_clip(x1, y1, x2, y2, resize_mode);
}
gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, Min_draw_distance, Max_draw_distance);
gr_set_view_matrix(&Eye_position, &vmd_identity_matrix);
render_info.set_flags(MR_AUTOCENTER | MR_NO_FOGGING);
if (sip->uses_team_colors) {
SCP_string tc = tcolor.empty() ? sip->default_team_name : tcolor;
render_info.set_team_color(tc, "none", 0, 0);
}
model_render_immediate(&render_info, model_num, model_instance, &object_orient, &vmd_zero_vector);
Glowpoint_use_depth_buffer = true;
batching_render_all();
shadow_end_frame();
// NOW render the lines for weapons
gr_reset_clip();
vertex draw_point;
vec3d subobj_pos;
int x, y;
int xc, yc;
int num_found = 0;
int bank_coords[MAX_SHIP_WEAPONS][2] = {
{bank1_x, bank1_y},
{bank2_x, bank2_y},
{bank3_x, bank3_y},
{bank4_x, bank4_y},
{bank5_x, bank5_y},
{bank6_x, bank6_y},
{bank7_x, bank7_y},
};
// Render selected primary lines
for (x = 0; x < pm->n_guns; x++) {
if ((weapon_array[x] == selected_weapon_class && hovered_weapon_slot < 0) ||
x == hovered_weapon_slot) {
Assert(num_found < MAX_SHIP_SECONDARY_BANKS);
gr_set_color_fast(&Overhead_line_colors[num_found]);
gr_circle(bank_coords[x][0] + bank_prim_offset, bank_coords[x][1] + bank_y_offset, 5, resize_mode);
for (y = 0; y < pm->gun_banks[x].num_slots; y++) {
// Stuff
if (pm->flags & PM_FLAG_AUTOCEN) {
vm_vec_sub(&subobj_pos, &pm->gun_banks[x].pnt[y], &pm->autocenter);
vm_vec_unrotate(&subobj_pos, &subobj_pos, &object_orient);
} else {
vm_vec_unrotate(&subobj_pos, &pm->gun_banks[x].pnt[y], &object_orient);
}
g3_rotate_vertex(&draw_point, &subobj_pos);
g3_project_vertex(&draw_point);
int resize = resize_mode;
if (resize_mode == GR_RESIZE_MENU) {
resize = GR_RESIZE_MENU_NO_OFFSET;
}
gr_unsize_screen_posf(&draw_point.screen.xyw.x, &draw_point.screen.xyw.y, nullptr, nullptr, resize);
xc = fl2i(draw_point.screen.xyw.x + x1);
yc = fl2i(draw_point.screen.xyw.y + y1);
// get the curve right.
int curve;
if ((xc > bank_coords[x][0] + bank_prim_offset) && (bank_coords[x][1] + bank_y_offset < yc))
curve = 1;
else if ((xc < bank_coords[x][0] + bank_prim_offset) && (bank_coords[x][1] + bank_y_offset < yc))
curve = 0;
else if ((xc > bank_coords[x][0] + bank_prim_offset) && (bank_coords[x][1] + bank_y_offset > yc))
curve = 3;
else
curve = 2;
int lineendx;
int lineendy;
if (curve == 0) {
lineendx = xc + 4;
} else {
lineendx = xc - 4;
}
gr_line(bank_coords[x][0] + bank_prim_offset,
bank_coords[x][1] + bank_y_offset,
lineendx,
bank_coords[x][1] + bank_y_offset,
resize_mode);
if (curve == 0 || curve == 2)
lineendx = xc;
if (curve == 0 || curve == 1) {
lineendy = bank_coords[x][1] + bank_y_offset;
} else {
lineendy = bank_coords[x][1] + (bank_y_offset / 2);
}
gr_curve(lineendx, lineendy, 5, curve, resize_mode);
if (curve == 0 || curve == 1) {
lineendy = bank_coords[x][1] + static_cast<int>(lround(bank_y_offset * 1.5));
} else {
lineendy = bank_coords[x][1] + (bank_y_offset / 2);
}
gr_line(xc, lineendy, xc, yc, resize_mode);
gr_circle(xc, yc, 5, resize_mode);
}
num_found++;
}
}
num_found = 0;
// Render selected secondary lines
for (x = 0; x < pm->n_missiles; x++) {
if ((weapon_array[x + MAX_SHIP_PRIMARY_BANKS] == selected_weapon_class &&
hovered_weapon_slot < 0) ||
x + MAX_SHIP_PRIMARY_BANKS == hovered_weapon_slot) {
Assert(num_found < MAX_SHIP_PRIMARY_BANKS);
gr_set_color_fast(&Overhead_line_colors[num_found]);
gr_circle(bank_coords[x + MAX_SHIP_PRIMARY_BANKS][0] + bank_sec_offset,
bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + bank_y_offset,
5,
resize_mode);
for (y = 0; y < pm->missile_banks[x].num_slots; y++) {
if (pm->flags & PM_FLAG_AUTOCEN) {
vm_vec_sub(&subobj_pos, &pm->missile_banks[x].pnt[y], &pm->autocenter);
vm_vec_unrotate(&subobj_pos, &subobj_pos, &object_orient);
} else {
vm_vec_unrotate(&subobj_pos, &pm->missile_banks[x].pnt[y], &object_orient);
}
g3_rotate_vertex(&draw_point, &subobj_pos);
g3_project_vertex(&draw_point);
int resize = resize_mode;
if (resize_mode == GR_RESIZE_MENU) {
resize = GR_RESIZE_MENU_NO_OFFSET;
}
gr_unsize_screen_posf(&draw_point.screen.xyw.x, &draw_point.screen.xyw.y, nullptr, nullptr, resize);
xc = fl2i(draw_point.screen.xyw.x + x1);
yc = fl2i(draw_point.screen.xyw.y + y1);
// get the curve right.
int curve;
if ((xc > bank_coords[x + MAX_SHIP_PRIMARY_BANKS][0] + bank_sec_offset) &&
(bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + bank_y_offset < yc))
curve = 1;
else if ((xc < bank_coords[x + MAX_SHIP_PRIMARY_BANKS][0] + bank_sec_offset) &&
(bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + bank_y_offset < yc))
curve = 0;
else if ((xc > bank_coords[x + MAX_SHIP_PRIMARY_BANKS][0] + bank_sec_offset) &&
(bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + bank_y_offset > yc))
curve = 3;
else
curve = 2;
int lineendx;
int lineendy;
if (curve == 1 || curve == 3)
lineendx = xc - 4;
else
lineendx = xc + 4;
gr_line(bank_coords[x + MAX_SHIP_PRIMARY_BANKS][0] + bank_sec_offset,
bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + bank_y_offset,
lineendx,
bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + bank_y_offset,
resize_mode);
if (curve == 1 || curve == 2) {
lineendy = bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + (bank_y_offset / 2);
} else {
lineendy = bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + bank_y_offset;
}
gr_curve(xc, lineendy, 5, curve, resize_mode);
if (curve == 1 || curve == 2) {
lineendy = bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + (bank_y_offset / 2);
} else {
lineendy = bank_coords[x + MAX_SHIP_PRIMARY_BANKS][1] + static_cast<int>(lround(bank_y_offset * 1.5));
}
gr_line(xc, lineendy, xc, yc, resize_mode);
gr_circle(xc, yc, 5, resize_mode);
}
num_found++;
}
}
// Cleanup
gr_end_view_matrix();
gr_end_proj_matrix();
g3_end_frame();
}
}
// ---------------------------------------------------------------------------------
// wl_render_overhead_view()
//
void wl_render_overhead_view(float frametime)
{
//For 3d ships
static float WeapSelectScreenShipRot = 0.0f;
int new_ship = 0;
static int display_type = -1;
if ( Selected_wl_slot == -1 ) {
return;
}
wl_ship_class_info *wl_ship;
int ship_class;
Assert( Wss_slots != NULL );
ship_class = Wss_slots[Selected_wl_slot].ship_class;
if (ship_class < 0 || ship_class >= ship_info_size())
{
Warning(LOCATION, "Invalid ship class (%d) passed for render_overhead_view", ship_class);
return;
}
ship_info * sip = &Ship_info[ship_class];
// check if ship class has changed and maybe play sound
if (Last_wl_ship_class != ship_class) {
if (Last_wl_ship_class != -1) {
gamesnd_play_iface(InterfaceSounds::ICON_DROP);
}
Last_wl_ship_class = ship_class;
new_ship = 1;
}
wl_ship = &Wl_ships[ship_class];
ship_class = Wss_slots[Selected_wl_slot].ship_class;
if (new_ship)
{
display_type = -1;
if (Use_3d_overhead_ship || !strlen(sip->overhead_filename))
{
if (wl_ship->model_num < 0)
{
wl_ship->model_num = model_load(sip, true);
model_page_in_textures(wl_ship->model_num, ship_class);
}
if(wl_ship->model_num > -1)
{
if (Use_3d_overhead_ship)
{
display_type = 2;
}
else
{
display_type = 1;
}
}
}
if(display_type < 0)
{
if ( wl_ship->overhead_bitmap < 0 )
{
//Load the anim?
if (gr_screen.res == GR_640)
{
// lo-res
wl_ship->overhead_bitmap = bm_load_animation(sip->overhead_filename, nullptr, nullptr, nullptr, nullptr, false, CF_TYPE_INTERFACE);
} else {
// high-res
char filename[NAME_LENGTH+2] = "2_";
strcat_s(filename, sip->overhead_filename);
wl_ship->overhead_bitmap = bm_load_animation(sip->overhead_filename, nullptr, nullptr, nullptr, nullptr, false, CF_TYPE_INTERFACE);
}
// load the bitmap
if (gr_screen.res == GR_640)
{
// lo-res
wl_ship->overhead_bitmap = bm_load(sip->overhead_filename);
} else {
// high-res
char filename[NAME_LENGTH+2] = "2_";
strcat_s(filename, sip->overhead_filename);
wl_ship->overhead_bitmap = bm_load(filename);
}
}
//Did we load anything?
if ( wl_ship->overhead_bitmap < 0 )
{
Warning(LOCATION, "Unable to load overhead image for ship '%s', generating one instead", sip->name);
display_type = 1;
} else {
display_type = 0;
}
}
}
//Maybe do 2D
if(display_type == 0 && wl_ship->overhead_bitmap > -1)
{
gr_set_bitmap(wl_ship->overhead_bitmap);
gr_bitmap(Wl_overhead_coords[gr_screen.res][0], Wl_overhead_coords[gr_screen.res][1], GR_RESIZE_MENU);
}
else
{
draw_3d_overhead_view(wl_ship->model_num,
ship_class,
&WeapSelectScreenShipRot,
frametime,
Wss_slots[Selected_wl_slot].wep,
Selected_wl_class,
Hot_weapon_bank,
Wl_overhead_coords[gr_screen.res][0],
Wl_overhead_coords[gr_screen.res][1],
gr_screen.res == 0 ? 291 : 467,
gr_screen.res == 0 ? 226 : 362,
GR_RESIZE_MENU,
Wl_bank_coords[gr_screen.res][0][0],
Wl_bank_coords[gr_screen.res][0][1],
Wl_bank_coords[gr_screen.res][1][0],
Wl_bank_coords[gr_screen.res][1][1],
Wl_bank_coords[gr_screen.res][2][0],
Wl_bank_coords[gr_screen.res][2][1],
Wl_bank_coords[gr_screen.res][3][0],
Wl_bank_coords[gr_screen.res][3][1],
Wl_bank_coords[gr_screen.res][4][0],
Wl_bank_coords[gr_screen.res][4][1],
Wl_bank_coords[gr_screen.res][5][0],
Wl_bank_coords[gr_screen.res][5][1],
Wl_bank_coords[gr_screen.res][6][0],
Wl_bank_coords[gr_screen.res][6][1]);
}
//Draw ship name
char name[NAME_LENGTH + CALLSIGN_LEN];
ss_return_name(Selected_wl_slot/MAX_WING_SLOTS, Selected_wl_slot%MAX_WING_SLOTS, name);
gr_set_color_fast(&Color_normal);
gr_string(Wl_ship_name_coords[gr_screen.res][0], Wl_ship_name_coords[gr_screen.res][1], name, GR_RESIZE_MENU);
}
// ---------------------------------------------------------------------------------
// wl_get_ship_class()
//
//
int wl_get_ship_class(int wl_slot)
{
Assert( Wss_slots != NULL );
return Wss_slots[wl_slot].ship_class;
}
/**
* Return true if weapon_flags indicates a weapon that is legal for use in current game type.
* Function added by MK on 9/6/99 to support separate legal loadouts for dogfight missions.
* name changed by Goober5000 to better reflect what it actually does
*/
int eval_weapon_flag_for_game_type(int weapon_flags)
{
int rval = 0;
if (MULTI_DOGFIGHT) {
if (weapon_flags & DOGFIGHT_WEAPON)
rval = 1;
}
else
if (weapon_flags & REGULAR_WEAPON)
rval = 1;
return rval;
}
/**
* Return the weapon class shown at the given on-screen row of a scrolling icon list,
* or -1 if that row is past the end of the list.
*/
static int wl_get_weapon_class_from_list(const SCP_vector<int> &list, int list_start, int row)
{
size_t list_index = list_start + row;
if ( list_index >= list.size() )
return -1;
return list[list_index];
}
/**
* Go through the possible weapons to choose from, and flag some as disabled since
* that ship class cannot use that kind of weapon. The weapon filter is specified
* in ships.tbl, where each ship has a list of all the possible weapons it can use.
* @param ship_class the ship class that might equip this weapon
* @param bank_index index of bank (0..2 primary, 0..6 secondary), or <0 if the bank is not being considered
*/
void wl_set_disabled_weapons(int ship_class, int bank_index)
{
if ( ship_class < 0 )
return;
Assert(ship_class >= 0 && ship_class < ship_info_size());
Assert(bank_index < MAX_SHIP_PRIMARY_BANKS + MAX_SHIP_SECONDARY_BANKS);
Assert( (Wl_pool != nullptr) && (Wl_icons != nullptr) && (Wss_slots != nullptr) );
auto sip = &Ship_info[ship_class];
// Set the flags for every weapon class, not just the ones with pool or slot entries at this
// moment: a script may drop a weapon into a bank between calls (see the Loadout_Weapon
// setter), and its icon flags must already be valid when the player interacts with it.
// Flag-only entries in the icon map are cheap; icon bitmaps still load only for pool weapons.
for ( int weapon_class = 0; weapon_class < weapon_info_size(); weapon_class++ )
{
auto &wi = Weapon_info[weapon_class];
auto &icon = (*Wl_icons)[weapon_class];
// Determine whether this weapon is allowed on this ship class in the current type of mission.
// As of 9/6/99, the only difference is dogfight missions have a different list of legal weapons.
icon.can_use_for_ship = eval_weapon_flag_for_game_type(sip->allowed_weapons[weapon_class]);
// Also determine whether the weapon can be used on this bank
if ( bank_index < 0 || !icon.can_use_for_ship )
icon.can_use_for_bank = TriStateBool::UNKNOWN_;
else if ( (wi.is_primary() && bank_index >= MAX_SHIP_PRIMARY_BANKS) || (wi.is_secondary() && bank_index < MAX_SHIP_PRIMARY_BANKS) )
icon.can_use_for_bank = TriStateBool::UNKNOWN_;
else if ( !eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[bank_index]) || eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[bank_index][weapon_class]) )
icon.can_use_for_bank = TriStateBool::TRUE_;
else
icon.can_use_for_bank = TriStateBool::FALSE_;
}
}
/**
* A slot index was clicked on, maybe change Selected_wl_slot
*/
void maybe_select_wl_slot(int block, int slot)
{
int sidx;
if ( Wss_num_wings <= 0 )
return;
Assert( Wss_slots != NULL );
sidx = block*MAX_WING_SLOTS + slot;
if ( Wss_slots[sidx].ship_class < 0 ) {
return;
}
wl_set_selected_slot(sidx);
}
/**
* Change to the weapon that corresponds to index in the weapon list
* @param index weapon icon index
*/
void maybe_select_new_weapon(int index)
{
int region_index, weapon_class;
// if a weapon is being carried, do nothing
if ( wl_icon_being_carried() ) {
return;
}
if ( index < NUM_PRIMARY_MASK_REGIONS ) {
region_index = ICON_PRIMARY_0 + index;
} else {
region_index = ICON_SECONDARY_0 + (index - NUM_PRIMARY_MASK_REGIONS);
}
if ( Wl_mouse_down_on_region != region_index ) {
return;
}
if ( index < NUM_PRIMARY_MASK_REGIONS ) {
weapon_class = wl_get_weapon_class_from_list(Plist, Plist_start, index);
} else {
weapon_class = wl_get_weapon_class_from_list(Slist, Slist_start, index - NUM_PRIMARY_MASK_REGIONS);
}
if ( weapon_class >= 0 ) {
Selected_wl_class = weapon_class;
wl_pick_icon_from_list(index);
}
}
/**
* Change to the weapon that corresponds to the ship weapon slot
* @param index index of bank (0..2 primary, 0..6 secondary)
*/
void maybe_select_new_ship_weapon(int index)
{
int *wep, *wep_count;
if ( Selected_wl_slot == -1 )
return;
if ( wl_icon_being_carried() ) {
return;
}
Assert( Wss_slots != NULL );
wep = Wss_slots[Selected_wl_slot].wep;
wep_count = Wss_slots[Selected_wl_slot].wep_count;
if ( wep[index] < 0 || wep_count[index] <= 0 ) {
return;
}
if ( Wl_mouse_down_on_region != (ICON_SHIP_PRIMARY_0+index) ) {
return;
}
Selected_wl_class = wep[index];
// some weapons are bank-specific, so re-check the disabled weapons
wl_set_disabled_weapons(Wss_slots[Selected_wl_slot].ship_class, index);
}
/**
* Initialize Wl_pool[] to mission default
*/
void wl_init_pool(team_data *td)
{
Assert( Wl_pool != NULL );
Wl_pool->clear();
for ( auto &entry : td->weapon_choices ) {
(*Wl_pool)[entry.class_index] += entry.count; // read from mission
}
}
/**
* Free source anim data if allocated
*/
void wl_unload_all_anims()
{
if(Cur_Anim.num_frames > 0)
generic_anim_unload(&Cur_Anim);
}
/**
* Load the icons for a specific ship class
*/
void wl_load_icons(int weapon_class)
{
wl_icon_info *icon;
int first_frame = -1;
int num_frames = 0, i;
weapon_info *wip = &Weapon_info[weapon_class];
Assert( Wl_icons != NULL );
icon = &(*Wl_icons)[weapon_class];
if (!Use_3d_weapon_icons || (wip->render_type == WRT_LASER && !VALID_FNAME(wip->tech_model)))
{
first_frame = bm_load_animation(Weapon_info[weapon_class].icon_filename, &num_frames, nullptr, nullptr, nullptr, false, CF_TYPE_INTERFACE);
for ( i = 0; (i < num_frames) && (i < NUM_ICON_FRAMES); i++ ) {
icon->icon_bmaps[i] = first_frame+i;
}
}
multi_send_anti_timeout_ping();
if ( icon->model_index == -1 && ( ( VALID_FNAME(wip->tech_model) && !VALID_FNAME(wip->anim_filename) ) || (first_frame == -1) ) )
{
if(VALID_FNAME(wip->tech_model))
{
icon->model_index = model_load(wip->tech_model, nullptr, ErrorType::WARNING);
}
if(wip->render_type != WRT_LASER && icon->model_index == -1)
{
icon->model_index = model_load(wip->pofbitmap_name);
}
}
}
/**
* Release the bitmaps and models held by one team's weapon icon map, while leaving the map populated.
*/
static void wl_unload_team_icons(SCP_map<int, wl_icon_info> &icons)
{
for ( auto &[weapon_class, icon] : icons ) {
for ( int &bmap : icon.icon_bmaps ) {
if ( bmap >= 0 ) {
bm_release(bmap);
bmap = -1;
}
}
if ( icon.model_index >= 0 ) {
model_unload(icon.model_index);
icon.model_index = -1;
}
if ( icon.laser_bmap >= 0 ) {
bm_unload(icon.laser_bmap);
icon.laser_bmap = -1;
}
}
}
/**
* Release and drop every team's weapon icon map
*/
static void wl_unload_all_teams_icons()
{
for ( auto &team_icons : Wl_icons_teams ) {
wl_unload_team_icons(team_icons);
team_icons.clear();
}
}
/**
* Load all the icons for weapons in the pool
*/
void wl_load_all_icons()
{
Assert( (Wl_icons != nullptr) && (Wl_pool != nullptr) );
// drop any icon data from a previous load of this team
wl_unload_team_icons(*Wl_icons);
Wl_icons->clear();
// note: unlike the ship pool, icons are only loaded for classes with ships remaining
for ( const auto &[weapon_class, count] : *Wl_pool ) {
if ( count > 0 ) {
wl_load_icons(weapon_class);
}
}
}
/**
* Free any allocated ship-class specific data
*/
void wl_free_ship_class_data()
{
for ( auto &[ship_class, wl_ship] : Wl_ships ) {
if ( wl_ship.overhead_bitmap != -1 ) {
bm_release(wl_ship.overhead_bitmap);
wl_ship.overhead_bitmap = -1;
}
if ( wl_ship.model_num != -1 ) {
// this should unload the model from memory only if it's not used in the mission
model_unload(wl_ship.model_num);
wl_ship.model_num = -1;
}
if(wl_ship.animation.num_frames)
generic_anim_unload(&wl_ship.animation);
}
Wl_ships.clear();
}
/**
* init ship-class specific data
*/
void wl_init_ship_class_data()
{
wl_free_ship_class_data();
}
/**
* Set selected slot to first placed ship
*/
void wl_reset_selected_slot()
{
int i;
Selected_wl_slot = -1;
Assert( Wss_slots != NULL );
// in multiplayer, select the slot of the player's ship by default
if((Game_mode & GM_MULTIPLAYER) && !MULTI_PERM_OBSERVER(Net_players[MY_NET_PLAYER_NUM]) && (Wss_slots[Net_player->p_info.ship_index].ship_class >= 0)){
wl_set_selected_slot(Net_player->p_info.ship_index);
return;
}
for ( i=0; i<MAX_WSS_SLOTS; i++ ) {
if ( !ss_disabled_slot(i, false) ) {
if ( ss_wing_slot_is_console_player(i) && (Wss_slots[i].ship_class >= 0) ) {
wl_set_selected_slot(i);
return;
}
}
}
// Didn't locate player ship, so just select the first ship we find
for ( i=0; i<MAX_WSS_SLOTS; i++ ) {
if ( Wss_slots[i].ship_class >= 0 ) {
wl_set_selected_slot(i);
break;
}
}
}
/**
* Called whenever it is possible that the current selected slot has had it's ship disappear
*/
void wl_maybe_reset_selected_slot()
{
int reset=0;
Assert( Wss_slots != NULL );
if ( Selected_wl_slot == -1 ) {
reset = 1;
}
if ( Wss_slots[Selected_wl_slot].ship_class < 0 ) {
reset = 1;
}
if ( reset ) {
wl_reset_selected_slot();
}
}
/**
* If Selected_wl_class is -1, choose the first weapon available from the pool for an animation
* - on second thought, choose the first weapon that is on the ship, then go to the pools
*/
void wl_maybe_reset_selected_weapon_class()
{
int i;
if ( (Selected_wl_class >= 0) && !(Always_reset_selected_wep_on_loadout_open) )
return;
Assert( Wss_slots != NULL );
// try to locate a weapon class to show animation for
// first check for a weapon on the ship
for (i=0; i<MAX_SHIP_WEAPONS; i++) {
// if player has a weapon in bank i, set it as the selected type
if (Wss_slots[0].wep_count[i] >= 0) {
Selected_wl_class = Wss_slots[0].wep[i];
return;
}
}
// then check for a primary weapon in the pool
if ( !Plist.empty() ) {
Selected_wl_class = Plist.front();
return;
}
// finally, if no others found yet, check for a secondary weapon in the pool
if ( !Slist.empty() ) {
Selected_wl_class = Slist.front();
return;
}
}
/**
* Call when Selected_wl_slot needs to be changed
*/
void wl_set_selected_slot(int slot_num)
{
Selected_wl_slot = slot_num;
if ( Selected_wl_slot >= 0 ) {
Assert( Wss_slots != NULL );
wl_set_disabled_weapons(Wss_slots[slot_num].ship_class, -1);
}
}
/**
* Determine how many ballistics of type 'wi_index' will fit into capacity - Goober5000
*/
int wl_calc_ballistic_fit(int wi_index, int capacity)
{
if ( wi_index < 0 ) {
return 0;
}
Assert(Weapon_info[wi_index].subtype == WP_LASER);
Assert(Weapon_info[wi_index].wi_flags[Weapon::Info_Flags::Ballistic]);
return (int)std::lround( capacity / Weapon_info[wi_index].cargo_size );
}
/**
* Determine how many missiles of type 'wi_index' will fit into capacity
*/
int wl_calc_missile_fit(int wi_index, int capacity)
{
if ( wi_index < 0 ) {
return 0;
}
Assert(Weapon_info[wi_index].subtype == WP_MISSILE);
return (int)std::lround( capacity / Weapon_info[wi_index].cargo_size );
}
/**
* Fill out the weapons for this ship_class
*/
void wl_get_ship_class_weapons(int ship_class, int *wep, int *wep_count)
{
ship_info *sip;
int i;
Assert(ship_class >= 0 && ship_class < ship_info_size());
sip = &Ship_info[ship_class];
// reset weapons arrays
for ( i=0; i < MAX_SHIP_WEAPONS; i++ ) {
wep[i] = -1;
wep_count[i] = -1; // -1 means weapon bank doesn't exist.. 0 just means it is empty
}
for ( i = 0; i < sip->num_primary_banks; i++ )
{
wep[i] = sip->primary_bank_weapons[i];
wep_count[i] = 1;
}
for ( i = 0; i < sip->num_secondary_banks; i++ )
{
wep[i+MAX_SHIP_PRIMARY_BANKS] = sip->secondary_bank_weapons[i];
wep_count[i+MAX_SHIP_PRIMARY_BANKS] = wl_calc_missile_fit(sip->secondary_bank_weapons[i], sip->secondary_bank_ammo_capacity[i]);
}
}
/**
* Fill out the wep[] and wep_count[] arrays for a ship
*/
void wl_get_ship_weapons(int ship_index, int *wep, int *wep_count)
{
int i;
ship_weapon *swp;
Assert(ship_index >= 0);
Assert(Ships[ship_index].wingnum >= 0);
swp = &Ships[ship_index].weapons;
for ( i = 0; i < swp->num_primary_banks; i++ )
{
wep[i] = swp->primary_bank_weapons[i];
wep_count[i] = 1;
if ( wep[i] == -1 ) {
wep_count[i] = 0;
}
}
for ( i = 0; i < swp->num_secondary_banks; i++ )
{
wep[i+MAX_SHIP_PRIMARY_BANKS] = swp->secondary_bank_weapons[i];
wep_count[i+MAX_SHIP_PRIMARY_BANKS] = swp->secondary_bank_ammo[i];
if ( wep[i+MAX_SHIP_PRIMARY_BANKS] == -1 ) {
wep_count[i+MAX_SHIP_PRIMARY_BANKS] = 0;
}
}
}
/**
* Set wep and wep_count from a ship which sits in the ship arrivals list at index sa_index
*/
void wl_get_parseobj_weapons(int sa_index, int ship_class, int *wep, int *wep_count)
{
int i, pilot_index;
subsys_status *ss;
ship_info *sip;
p_object *pobjp;
pobjp = &Parse_objects[sa_index];
sip = &Ship_info[ship_class];
pilot_index = wl_get_pilot_subsys_index(pobjp);
if ( pilot_index == -1 )
return;
ss = &Subsys_status[pilot_index];
if ( ss->primary_banks[0] != SUBSYS_STATUS_NO_CHANGE ) {
for ( i=0; i < MAX_SHIP_PRIMARY_BANKS; i++ ) {
wep[i] = ss->primary_banks[i];
}
}
if ( ss->secondary_banks[0] != SUBSYS_STATUS_NO_CHANGE ) {
for ( i=0; i < MAX_SHIP_SECONDARY_BANKS; i++ ) {
wep[i+MAX_SHIP_PRIMARY_BANKS] = ss->secondary_banks[i];
}
}
// ammo counts could still be modified
for ( i=0; i < MAX_SHIP_SECONDARY_BANKS; i++ )
{
if ( wep[i+MAX_SHIP_PRIMARY_BANKS] >= 0 )
{
wep_count[i+MAX_SHIP_PRIMARY_BANKS] = wl_calc_missile_fit(wep[i+MAX_SHIP_PRIMARY_BANKS], (int)std::lround(ss->secondary_ammo[i]/100.0f * sip->secondary_bank_ammo_capacity[i]));
}
}
}
/**
* Ensure that there aren't any bogus weapons assigned by default
*/
void wl_cull_illegal_weapons(int ship_class, int *wep, int *wep_count)
{
auto sip = &Ship_info[ship_class];
// if we have *no* allowed weapon list, weapons are unrestricted
if (sip->allowed_weapons.weapon_and_flags.empty())
return;
int i, check_flag;
for ( i=0; i < MAX_SHIP_WEAPONS; i++ )
{
if ( wep[i] < 0 ) {
continue;
}
check_flag = sip->allowed_weapons[wep[i]];
// possibly change flag if it's restricted
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[i]))
{
check_flag = sip->allowed_bank_restricted_weapons[i][wep[i]];
}
if ( !eval_weapon_flag_for_game_type(check_flag) ) {
// wep[i] = -1;
wep_count[i] = 0;
}
}
}
/**
* Get the weapons info that should be on ship by default
*/
void wl_get_default_weapons(int ship_class, int slot_num, int *wep, int *wep_count)
{
int original_ship_class, i;
Assert(slot_num >= 0 && slot_num < MAX_WSS_SLOTS);
// clear out wep and wep_count
for ( i = 0; i < MAX_SHIP_WEAPONS; i++ ) {
wep[i] = -1;
wep_count[i] = -1;
}
if ( ship_class < 0 )
return;
original_ship_class = ss_return_original_ship_class(slot_num);
if ( original_ship_class != ship_class ) {
wl_get_ship_class_weapons(ship_class, wep, wep_count);
} else {
int sa_index; // ship arrival index
sa_index = ss_return_saindex(slot_num);
if ( sa_index >= 0 ) {
// still a parse object
wl_get_ship_class_weapons(ship_class, wep, wep_count);
wl_get_parseobj_weapons(sa_index, ship_class, wep, wep_count);
} else {
// ship has been created
int ship_index = -1;
p_object *pobjp;
ss_return_ship(slot_num/MAX_WING_SLOTS, slot_num%MAX_WING_SLOTS, &ship_index, &pobjp);
Assert(ship_index != -1);
wl_get_ship_weapons(ship_index, wep, wep_count);
}
}
// ensure that there aren't any bogus weapons assigned by default
wl_cull_illegal_weapons(ship_class, wep, wep_count);
}
/**
* Add a weapon_class to ui lists
*/
void wl_add_index_to_list(int wi_index)
{
if ( Weapon_info[wi_index].subtype == WP_MISSILE ) {
if ( !Slist.contains(wi_index) )
Slist.push_back(wi_index);
} else {
if ( !Plist.contains(wi_index) )
Plist.push_back(wi_index);
}
}
/**
* Remove the weapons specified by wep[] and wep_count[] from Wl_pool[].
*/
void wl_remove_weps_from_pool(int *wep, int *wep_count, int ship_class)
{
int bank, wi_index;
auto &si = Ship_info[ship_class];
Assert( Wl_pool != NULL );
for ( bank = 0; bank < MAX_SHIP_WEAPONS; bank++ ) {
wi_index = wep[bank];
if ( wi_index >= 0 ) {
int pool_count = Wl_pool->value_or(wi_index, 0);
if ( (wep_count[bank] > 0) && ((pool_count - wep_count[bank]) >= 0) ) {
(*Wl_pool)[wi_index] -= wep_count[bank];
} else {
// not enough weapons in pool
// TEMP HACK: FRED doesn't fill in a weapons pool if there are no starting wings... so
// add to the pool. This should be fixed.
if ( Wss_num_wings <= 0 ) {
wl_add_index_to_list(wi_index);
} else {
if ( (pool_count <= 0) || (wep_count[bank] == 0) ) {
// fresh out of this weapon, pick an alternate pool weapon if we can
for (const auto &new_index : Player_weapon_precedence) {
Assertion(new_index >= 0, "Somehow, a negative index (%d) got into Player_weapon_precedence; this should not happen. Get a coder!", new_index);
if ( Wl_pool->value_or(new_index, 0) <= 0 ) {
continue;
}
// AL 3-31-98: Only pick another primary if primary, etc
if ( Weapon_info[wi_index].subtype != Weapon_info[new_index].subtype ) {
continue;
}
// check whether the weapon is allowed in general for this ship
if ( !eval_weapon_flag_for_game_type(si.allowed_weapons[new_index]) ) {
continue;
}
// check whether the weapon is allowed for this bank
if (eval_weapon_flag_for_game_type(si.restricted_loadout_flag[bank])) {
if (!eval_weapon_flag_for_game_type(si.allowed_bank_restricted_weapons[bank][new_index])) {
continue;
}
}
wep[bank] = new_index;
wi_index = new_index;
break;
}
}
int new_wep_count = wep_count[bank];
if ( Weapon_info[wi_index].subtype == WP_MISSILE )
{
int secondary_bank_index;
secondary_bank_index = bank - MAX_SHIP_PRIMARY_BANKS;
if ( secondary_bank_index < 0 ) {
Int3();
secondary_bank_index = 0;
}
new_wep_count = wl_calc_missile_fit(wi_index, si.secondary_bank_ammo_capacity[secondary_bank_index]);
}
// re-read the count, since the precedence loop may have picked a different weapon
pool_count = Wl_pool->value_or(wi_index, 0);
wep_count[bank] = MIN(new_wep_count, pool_count);
Assert(wep_count[bank] >= 0);
if ( wep_count[bank] > 0 ) {
(*Wl_pool)[wi_index] -= wep_count[bank];
} else {
// nothing to take
wep[bank] = -1;
}
}
}
}
}
}
/**
* Init the weapons portion of Wss_slots[] and the ui data in Wl_slots[]
* @note It is assumed that Wl_pool[] has been initialized, and Wss_slots[].ship_class is correctly set
*/
void wl_fill_slots()
{
int i, j;
int wep[MAX_SHIP_WEAPONS];
int wep_count[MAX_SHIP_WEAPONS];
Assert( Wss_slots != NULL );
for ( i = 0; i < MAX_WSS_SLOTS; i++ ) {
if ( Wss_slots[i].ship_class < 0 ){
continue;
}
// get the weapons info that should be on ship by default
wl_get_default_weapons(Wss_slots[i].ship_class, i, wep, wep_count);
wl_remove_weps_from_pool(wep, wep_count, Wss_slots[i].ship_class);
// copy to Wss_slots[]
for ( j = 0; j < MAX_SHIP_WEAPONS; j++ ) {
Wss_slots[i].wep[j] = wep[j];
Wss_slots[i].wep_count[j] = wep_count[j];
}
}
}
/**
* Set up the primary and secondary icons lists that hold the weapons the player can choose from
*/
void wl_init_icon_lists()
{
Assert( Wl_pool != NULL );
Plist_start = 0;
Slist_start = 0;
Plist.clear();
Slist.clear();
for ( const auto &[weapon_class, count] : *Wl_pool ) {
if ( count > 0 ) {
if ( Weapon_info[weapon_class].subtype == WP_MISSILE ) {
Slist.push_back(weapon_class);
} else {
Plist.push_back(weapon_class);
}
}
}
}
/**
* Set the necessary pointers
*/
void wl_set_team_pointers(int team)
{
Assert( (team >= 0) && (team < MAX_TVT_TEAMS) );
Wl_icons = &Wl_icons_teams[team];
}
/**
* Reset the necessary pointers to defaults
*/
void wl_reset_team_pointers()
{
Assert( !Weapon_select_open );
if ( Weapon_select_open )
return;
Wl_icons = nullptr;
}
/**
* Initialize team specific weapon select data structures
*/
void weapon_select_init_team(int team_num)
{
common_set_team_pointers(team_num);
wl_init_pool(&Team_data[team_num]);
wl_init_icon_lists();
wl_init_ship_class_data();
wl_load_all_icons();
wl_fill_slots();
}
/**
* Release the icons, anims, and ship class data that weapon_select_init_team() set up
*/
void weapon_select_common_close()
{
wl_unload_all_teams_icons();
wl_unload_all_anims();
wl_free_ship_class_data();
}
/**
* This init is called even before the weapons loadout screen is entered. It is called when the
* briefing state is entered.
*/
void weapon_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)
weapon_select_init_team(idx);
}
}
weapon_select_init_team(Common_team);
if (!API_Access) {
wl_reset_selected_slot();
wl_reset_carried_icon();
wl_maybe_reset_selected_weapon_class();
}
}
/**
* Called to load the bitmaps and set up the mask regions for
* the weapon loadout screen. common_select_init() is called to load the animations
* and bitmaps which are in common with the ship select and briefing screens.
*
* @note The Weapon_select_open flag is set to 1 when weapon_select_init() completes successfully
*/
void weapon_select_init()
{
common_set_interface_palette("WeaponPalette");
common_flash_button_init();
// for multiplayer, change the state in my netplayer structure
if ( Game_mode & GM_MULTIPLAYER )
Net_player->state = NETPLAYER_STATE_WEAPON_SELECT;
Weapon_anim_class = -1;
set_active_ui(&Weapon_ui_window);
Current_screen = ON_WEAPON_SELECT;
Last_wl_ship_class = -1;
wl_maybe_reset_selected_slot();
Assert( Wss_slots != NULL );
wl_set_disabled_weapons(Wss_slots[Selected_wl_slot].ship_class, -1);
Weapon_select_overlay_id = help_overlay_get_index(WL_OVERLAY);
help_overlay_set_state(Weapon_select_overlay_id,gr_screen.res,0);
if ( Weapon_select_open ) {
wl_maybe_reset_selected_weapon_class();
common_buttons_maybe_reload(&Weapon_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","weapon_select_init() returning without doing anything\n"));
// if we're in multiplayer always select the player's ship
wl_reset_selected_slot();
return;
}
nprintf(("Alan","entering weapon_select_init()\n"));
common_select_init();
// Goober5000
// first determine which layout to use
Uses_apply_all_button = 1; // assume true
Weapon_select_background_bitmap = mission_ui_background_load(Briefing->weapon_select_background[gr_screen.res], Weapon_select_background_fname[Uses_apply_all_button][gr_screen.res], Weapon_select_multi_background_fname[Uses_apply_all_button][gr_screen.res]);
if (Weapon_select_background_bitmap < 0) // failed to load
{
Uses_apply_all_button = 0; // nope, sorry
Weapon_select_background_bitmap = mission_ui_background_load(NULL, Weapon_select_background_fname[Uses_apply_all_button][gr_screen.res], Weapon_select_multi_background_fname[Uses_apply_all_button][gr_screen.res]);
}
WeaponSelectMaskBitmap = bm_load(Wl_loadout_select_mask[Uses_apply_all_button][gr_screen.res]);
if (WeaponSelectMaskBitmap < 0) {
Error(LOCATION,"Could not load in '%s'!", Wl_loadout_select_mask[Uses_apply_all_button][gr_screen.res]);
}
Weaponselect_mask_w = -1;
Weaponselect_mask_h = -1;
// get a pointer to bitmap by using bm_lock()
WeaponSelectMaskPtr = bm_lock(WeaponSelectMaskBitmap, 8, BMP_AABITMAP | BMP_MASK_BITMAP);
WeaponSelectMaskData = (ubyte*)WeaponSelectMaskPtr->data;
Assert(WeaponSelectMaskData != NULL);
bm_get_info(WeaponSelectMaskBitmap, &Weaponselect_mask_w, &Weaponselect_mask_h);
// Set up the mask regions
// initialize the different regions of the menu that will react when the mouse moves over it
Num_weapon_select_regions = 0;
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", COMMON_BRIEFING_REGION, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", COMMON_SS_REGION, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", COMMON_WEAPON_REGION, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", COMMON_COMMIT_REGION, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", COMMON_HELP_REGION, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", COMMON_OPTIONS_REGION, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_0_SHIP_0, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_0_SHIP_1, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_0_SHIP_2, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_0_SHIP_3, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_1_SHIP_0, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_1_SHIP_1, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_1_SHIP_2, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_1_SHIP_3, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_2_SHIP_0, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_2_SHIP_1, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_2_SHIP_2, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", WING_2_SHIP_3, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_PRIMARY_0, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_PRIMARY_1, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_PRIMARY_2, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_PRIMARY_3, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SECONDARY_0, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SECONDARY_1, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SECONDARY_2, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SECONDARY_3, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SHIP_PRIMARY_0, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SHIP_PRIMARY_1, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SHIP_PRIMARY_2, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SHIP_SECONDARY_0, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SHIP_SECONDARY_1, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SHIP_SECONDARY_2, 0);
snazzy_menu_add_region(&Weapon_select_region[Num_weapon_select_regions++], "", ICON_SHIP_SECONDARY_3, 0);
// init common UI
Weapon_ui_window.create( 0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0 );
if(Game_mode & GM_MULTIPLAYER){
Weapon_ui_window.set_mask_bmap(Wl_mask_multi[Uses_apply_all_button][gr_screen.res]);
} else {
Weapon_ui_window.set_mask_bmap(Wl_mask_single[Uses_apply_all_button][gr_screen.res]);
}
Weapon_ui_window.tooltip_handler = wl_tooltip_handler;
common_buttons_init(&Weapon_ui_window);
weapon_buttons_init();
Weapon_select_open = 1;
// if we're in multiplayer always select the player's ship
wl_reset_selected_slot();
//Load the slot bitmap
Weapon_slot_bitmap = bm_load("weapon_slot");
}
// ----------------------------------------------------------------
// wl_dump_carried_icon()
void wl_dump_carried_icon()
{
if ( wl_icon_being_carried() ) {
// Add back into the weapon pool
if ( Carried_wl_icon.from_bank >= 0 ) {
// return to list
wl_drop(Carried_wl_icon.from_bank, -1, -1, Carried_wl_icon.weapon_class, Carried_wl_icon.from_slot);
} else {
if ( wl_carried_icon_moved() ) {
gamesnd_play_iface(InterfaceSounds::ICON_DROP);
}
}
wl_reset_carried_icon();
}
}
/**
* Drop the Carried_wl_icon onto the specified slot. The slot numbering is:
*
* 0->2: primary weapons
* 3-6: secondary weapons
*
* @note These are the slots that exist beside the overhead view of the ship.
* on the weapons loadout screen.
*/
int drop_icon_on_slot(int bank_num)
{
if ( Selected_wl_slot == -1 ) {
return 0;
}
if(Game_mode & GM_MULTIPLAYER){
if(multi_ts_disabled_slot(Selected_wl_slot)){
return 0;
}
} else {
if ( ss_disabled_slot( Selected_wl_slot, false ) ){
return 0;
}
}
Assert( Wss_slots != NULL );
// check if slot exists
if ( Wss_slots[Selected_wl_slot].wep_count[bank_num] < 0 ) {
return 0;
}
if ( !wl_carried_icon_moved() ) {
wl_reset_carried_icon();
return 0;
}
wl_drop(Carried_wl_icon.from_bank, Carried_wl_icon.weapon_class, bank_num, -1, Selected_wl_slot);
return 1;
}
// ----------------------------------------------------------------
// maybe_drop_icon_on_slot()
//
void maybe_drop_icon_on_slot(int bank_num)
{
int dropped=0;
if ( !mouse_down(MOUSE_LEFT_BUTTON) ) {
if ( wl_icon_being_carried() )
dropped = drop_icon_on_slot(bank_num);
if ( dropped ) {
wl_reset_carried_icon();
}
}
}
// ---------------------------------------------------------------------------------
// do_mouse_over_list_weapon()
//
void do_mouse_over_list_weapon(int index)
{
Hot_weapon_icon = index;
int region_index;
if ( index < NUM_PRIMARY_MASK_REGIONS ) {
region_index = ICON_PRIMARY_0 + index;
} else {
region_index = ICON_SECONDARY_0 + (index - NUM_PRIMARY_MASK_REGIONS);
}
if ( Wl_mouse_down_on_region != region_index ){
return;
}
if ( mouse_down(MOUSE_LEFT_BUTTON) )
wl_pick_icon_from_list(index);
}
/**
* Mouse over ship weapon
*
* @param index Bank index on ship (0..6)
* @return 0 icon was not dropped on a slot
* @return 1 icon was dropped on a slot
*/
int do_mouse_over_ship_weapon(int index)
{
int dropped_on_slot, is_moved, mx, my;
dropped_on_slot = 0;
Assert(Selected_wl_slot >= 0);
Assert(Wss_slots != NULL);
if ( ss_disabled_slot( Selected_wl_slot , false) )
return 0;
Hot_weapon_bank_icon = index; // bank icon will be drawn highlighted
if ( mouse_down(MOUSE_LEFT_BUTTON) ) {
if ( Wl_mouse_down_on_region == (ICON_SHIP_PRIMARY_0+index) ){
pick_from_ship_slot(index);
}
} else {
int was_carried = wl_icon_being_carried();
maybe_drop_icon_on_slot(index);
if ( was_carried && !wl_icon_being_carried() ) {
mouse_get_pos_unscaled( &mx, &my );
if ( Carried_wl_icon.from_x != mx || Carried_wl_icon.from_y != my) {
dropped_on_slot = 1;
}
}
}
// set Hot_weapon_bank if a droppable icon is being held over a slot that
// can accept that icon
is_moved = 0;
mouse_get_pos_unscaled( &mx, &my );
if ( Carried_wl_icon.from_x != mx || Carried_wl_icon.from_y != my) {
is_moved = 1;
}
if ( wl_icon_being_carried() && is_moved ) {
if ( Weapon_info[Carried_wl_icon.weapon_class].subtype != WP_MISSILE ) {
if ( (index < MAX_SHIP_PRIMARY_BANKS) && (Wss_slots[Selected_wl_slot].wep_count[index] >= 0) )
Hot_weapon_bank = index;
} else {
if ( index >= MAX_SHIP_PRIMARY_BANKS && ( Wss_slots[Selected_wl_slot].wep_count[index] >= 0) )
Hot_weapon_bank = index;
}
}
return dropped_on_slot;
}
/**
* Maybe flash a button if player hasn't done anything for a while
*/
void wl_maybe_flash_button()
{
if ( common_flash_bright() ) {
// commit button
if ( Common_buttons[Current_screen-1][gr_screen.res][3].button.button_hilighted() ) {
common_flash_button_init();
} else {
Common_buttons[Current_screen-1][gr_screen.res][3].button.draw_forced(1);
}
}
}
void weapon_select_render(float /*frametime*/)
{
if ( !Background_playing ) {
GR_MAYBE_CLEAR_RES(Weapon_select_background_bitmap);
gr_set_bitmap(Weapon_select_background_bitmap);
gr_bitmap(0, 0, GR_RESIZE_MENU);
}
}
/**
* Draw the weapon description text
* @note this wipes in
*/
void wl_render_weapon_desc(float frametime)
{
int *weapon_desc_coords;
int *weapon_title_coords;
int line_height = gr_get_font_height() + 1;
// retrieve the correct set of text coordinates
if (Game_mode & GM_MULTIPLAYER) {
weapon_desc_coords = Wl_new_weapon_desc_coords_multi[gr_screen.res];
weapon_title_coords = Wl_new_weapon_title_coords_multi[gr_screen.res];
} else {
weapon_desc_coords = Wl_new_weapon_desc_coords[gr_screen.res];
weapon_title_coords = Wl_new_weapon_title_coords[gr_screen.res];
}
// render the normal version of the weapom desc
char bright_char[WEAPON_DESC_MAX_LINES]; // one bright char per line
if (!Weapon_desc_wipe_done) {
// draw mid-wipe version
// decide which char is last (and bright)
size_t bright_char_index = (size_t)(Weapon_desc_wipe_time_elapsed * WEAPON_DESC_MAX_LENGTH / WEAPON_DESC_WIPE_TIME);
int i, w, h;
// draw weapon title (above weapon anim)
for (i=0; i<2; i++) {
size_t curr_len = strlen(Weapon_desc_lines[i]);
if (bright_char_index < curr_len) {
// save bright char and plunk in some nulls to shorten string
bright_char[i] = Weapon_desc_lines[i][bright_char_index];
Weapon_desc_lines[i][bright_char_index] = '\0';
// draw the strings
gr_set_color_fast(&Color_white);
gr_string(weapon_title_coords[0], weapon_title_coords[1]+(line_height*i), Weapon_desc_lines[i], GR_RESIZE_MENU);
// draw the bright letters
gr_set_color_fast(&Color_bright_white);
gr_get_string_size(&w, &h, Weapon_desc_lines[i], 1.0f, curr_len);
gr_printf_menu(weapon_title_coords[0]+w, weapon_title_coords[1]+(line_height*i), "%c", bright_char[i]);
// restore the bright char to the string
Weapon_desc_lines[i][bright_char_index] = bright_char[i];
} else {
// draw the string
gr_set_color_fast(&Color_white);
gr_string(weapon_title_coords[0], weapon_title_coords[1]+(line_height*i), Weapon_desc_lines[i], GR_RESIZE_MENU);
}
}
// draw weapon desc (below weapon anim)
for (i=2; i<WEAPON_DESC_MAX_LINES; i++) {
size_t curr_len = strlen(Weapon_desc_lines[i]);
if (bright_char_index < curr_len) {
// save bright char and plunk in some nulls to shorten string
bright_char[i] = Weapon_desc_lines[i][bright_char_index];
Weapon_desc_lines[i][bright_char_index] = '\0';
// draw the string
gr_set_color_fast(&Color_white);
gr_string(weapon_desc_coords[0], weapon_desc_coords[1]+(line_height*(i-2)), Weapon_desc_lines[i], GR_RESIZE_MENU);
// draw the bright letters
gr_set_color_fast(&Color_bright_white);
gr_get_string_size(&w, &h, Weapon_desc_lines[i], 1.0f, curr_len);
gr_printf_menu(weapon_desc_coords[0]+w, weapon_desc_coords[1]+(line_height*(i-2)), "%c", bright_char[i]);
// restore the bright char to the string
Weapon_desc_lines[i][bright_char_index] = bright_char[i];
} else {
// draw the string
gr_set_color_fast(&Color_white);
gr_string(weapon_desc_coords[0], weapon_desc_coords[1]+(line_height*(i-2)), Weapon_desc_lines[i], GR_RESIZE_MENU);
}
}
// update time
Weapon_desc_wipe_time_elapsed += frametime;
if (Weapon_desc_wipe_time_elapsed >= WEAPON_DESC_WIPE_TIME) {
// wipe is done,set flag and stop sound
Weapon_desc_wipe_done = 1;
}
} else {
// draw full version
// FIXME - change to use a for loop
gr_set_color_fast(&Color_white);
gr_string(weapon_title_coords[0], weapon_title_coords[1], Weapon_desc_lines[0], GR_RESIZE_MENU);
gr_string(weapon_title_coords[0], weapon_title_coords[1] + line_height, Weapon_desc_lines[1], GR_RESIZE_MENU);
gr_string(weapon_desc_coords[0], weapon_desc_coords[1], Weapon_desc_lines[2], GR_RESIZE_MENU);
gr_string(weapon_desc_coords[0], weapon_desc_coords[1] + line_height, Weapon_desc_lines[3], GR_RESIZE_MENU);
gr_string(weapon_desc_coords[0], weapon_desc_coords[1] + line_height * 2, Weapon_desc_lines[4], GR_RESIZE_MENU);
gr_string(weapon_desc_coords[0], weapon_desc_coords[1] + line_height * 3, Weapon_desc_lines[5], GR_RESIZE_MENU);
}
}
/**
* Re-inits wiping vars and causes the current text to wipe in again
*/
void wl_weapon_desc_start_wipe()
{
size_t currchar_src = 0;
int currline_dest = 2, currchar_dest = 0, i;
int w, h;
size_t title_len = strlen(Weapon_info[Selected_wl_class].title);
// init wipe vars
Weapon_desc_wipe_time_elapsed = 0.0f;
Weapon_desc_wipe_done = 0;
// break title into two lines if too long
strcpy_s(Weapon_desc_lines[0], Weapon_info[Selected_wl_class].title);
gr_get_string_size(&w, &h, Weapon_info[Selected_wl_class].title, 1.0f, title_len);
if (w > Weapon_title_max_width[gr_screen.res]) {
// split
currchar_src = (size_t)(((float)title_len / (float)w) * Weapon_title_max_width[gr_screen.res]); // char to start space search at
while (Weapon_desc_lines[0][currchar_src] != ' ') {
currchar_src--;
if (currchar_src <= 0) {
currchar_src = title_len;
break;
}
}
Weapon_desc_lines[0][currchar_src] = '\0'; // shorten line 0
strcpy_s(Weapon_desc_lines[1], &(Weapon_desc_lines[0][currchar_src+1])); // copy remainder into line 1
} else {
// entire title in line 0, thus line 1 is empty
Weapon_desc_lines[1][0] = '\0';
}
// break current description into lines (break at the /n's)
currchar_src = 0;
if (Weapon_info[Selected_wl_class].desc) {
auto desc = Weapon_info[Selected_wl_class].desc.get();
while (desc[currchar_src] != '\0') {
if (desc[currchar_src] == '\n') {
// break here
if (currchar_src != 0) { // protect against leading /n's
Weapon_desc_lines[currline_dest][currchar_dest] = '\0';
currline_dest++;
currchar_dest = 0;
}
} else {
// straight copy
Weapon_desc_lines[currline_dest][currchar_dest] = desc[currchar_src];
currchar_dest++;
}
currchar_src++;
}
}
// wrap up the line processing
Weapon_desc_lines[currline_dest][currchar_dest] = '\0';
for (i=currline_dest+1; i<WEAPON_DESC_MAX_LINES; i++) {
Weapon_desc_lines[i][0] = '\0';
}
}
/**
* Calls to common_ functions are made for those functions which are common to the
* ship select and briefing screens.
*/
void weapon_select_do(float frametime)
{
int k, wl_choice, snazzy_action;
if ( !Weapon_select_open )
weapon_select_init();
wl_choice = snazzy_menu_do(WeaponSelectMaskData, Weaponselect_mask_w, Weaponselect_mask_h, Num_weapon_select_regions, Weapon_select_region, &snazzy_action, 0);
if ( wl_choice >= 0 ) {
if ( snazzy_action == SNAZZY_CLICKED ) {
nprintf(("Alan","got one\n"));
}
}
Hot_wl_slot = -1;
Hot_weapon_icon = -1;
Hot_weapon_bank = -1;
Hot_weapon_bank_icon = -1;
k = common_select_do(frametime);
// Check common keypresses
common_check_keys(k);
if ( Mouse_down_last_frame ) {
Wl_mouse_down_on_region = wl_choice;
}
if ( wl_choice > -1 ) {
switch(wl_choice) {
case ICON_PRIMARY_0:
do_mouse_over_list_weapon(0);
break;
case ICON_PRIMARY_1:
do_mouse_over_list_weapon(1);
break;
case ICON_PRIMARY_2:
do_mouse_over_list_weapon(2);
break;
case ICON_PRIMARY_3:
do_mouse_over_list_weapon(3);
break;
case ICON_SECONDARY_0:
do_mouse_over_list_weapon(4);
break;
case ICON_SECONDARY_1:
do_mouse_over_list_weapon(5);
break;
case ICON_SECONDARY_2:
do_mouse_over_list_weapon(6);
break;
case ICON_SECONDARY_3:
do_mouse_over_list_weapon(7);
break;
case ICON_SHIP_PRIMARY_0:
if ( do_mouse_over_ship_weapon(0) )
wl_choice = -1;
break;
case ICON_SHIP_PRIMARY_1:
if ( do_mouse_over_ship_weapon(1) )
wl_choice = -1;
break;
case ICON_SHIP_PRIMARY_2:
if ( do_mouse_over_ship_weapon(2) )
wl_choice = -1;
break;
case ICON_SHIP_SECONDARY_0:
if ( do_mouse_over_ship_weapon(3) )
wl_choice = -1;
break;
case ICON_SHIP_SECONDARY_1:
if ( do_mouse_over_ship_weapon(4) )
wl_choice = -1;
break;
case ICON_SHIP_SECONDARY_2:
if ( do_mouse_over_ship_weapon(5) )
wl_choice = -1;
break;
case ICON_SHIP_SECONDARY_3:
if ( do_mouse_over_ship_weapon(6) )
wl_choice = -1;
break;
case WING_0_SHIP_0:
Hot_wl_slot = 0;
break;
case WING_0_SHIP_1:
Hot_wl_slot = 1;
break;
case WING_0_SHIP_2:
Hot_wl_slot = 2;
break;
case WING_0_SHIP_3:
Hot_wl_slot = 3;
break;
case WING_1_SHIP_0:
Hot_wl_slot = 4;
break;
case WING_1_SHIP_1:
Hot_wl_slot = 5;
break;
case WING_1_SHIP_2:
Hot_wl_slot = 6;
break;
case WING_1_SHIP_3:
Hot_wl_slot = 7;
break;
case WING_2_SHIP_0:
Hot_wl_slot = 8;
break;
case WING_2_SHIP_1:
Hot_wl_slot = 9;
break;
case WING_2_SHIP_2:
Hot_wl_slot = 10;
break;
case WING_2_SHIP_3:
Hot_wl_slot = 11;
break;
default:
break;
} // end switch
}
if ( !mouse_down(MOUSE_LEFT_BUTTON) ) {
wl_dump_carried_icon();
}
// Check for a mouse click on buttons
common_check_buttons();
weapon_check_buttons();
// Check for the mouse clicks over a region
if ( wl_choice > -1 && snazzy_action == SNAZZY_CLICKED ) {
switch (wl_choice) {
case ICON_PRIMARY_0:
maybe_select_new_weapon(0);
break;
case ICON_PRIMARY_1:
maybe_select_new_weapon(1);
break;
case ICON_PRIMARY_2:
maybe_select_new_weapon(2);
break;
case ICON_PRIMARY_3:
maybe_select_new_weapon(3);
break;
case ICON_SECONDARY_0:
maybe_select_new_weapon(4);
break;
case ICON_SECONDARY_1:
maybe_select_new_weapon(5);
break;
case ICON_SECONDARY_2:
maybe_select_new_weapon(6);
break;
case ICON_SECONDARY_3:
maybe_select_new_weapon(7);
break;
case ICON_SHIP_PRIMARY_0:
maybe_select_new_ship_weapon(0);
break;
case ICON_SHIP_PRIMARY_1:
maybe_select_new_ship_weapon(1);
break;
case ICON_SHIP_PRIMARY_2:
maybe_select_new_ship_weapon(2);
break;
case ICON_SHIP_SECONDARY_0:
maybe_select_new_ship_weapon(3);
break;
case ICON_SHIP_SECONDARY_1:
maybe_select_new_ship_weapon(4);
break;
case ICON_SHIP_SECONDARY_2:
maybe_select_new_ship_weapon(5);
break;
case ICON_SHIP_SECONDARY_3:
maybe_select_new_ship_weapon(6);
break;
case WING_0_SHIP_0:
maybe_select_wl_slot(0,0);
break;
case WING_0_SHIP_1:
maybe_select_wl_slot(0,1);
break;
case WING_0_SHIP_2:
maybe_select_wl_slot(0,2);
break;
case WING_0_SHIP_3:
maybe_select_wl_slot(0,3);
break;
case WING_1_SHIP_0:
maybe_select_wl_slot(1,0);
break;
case WING_1_SHIP_1:
maybe_select_wl_slot(1,1);
break;
case WING_1_SHIP_2:
maybe_select_wl_slot(1,2);
break;
case WING_1_SHIP_3:
maybe_select_wl_slot(1,3);
break;
case WING_2_SHIP_0:
maybe_select_wl_slot(2,0);
break;
case WING_2_SHIP_1:
maybe_select_wl_slot(2,1);
break;
case WING_2_SHIP_2:
maybe_select_wl_slot(2,2);
break;
case WING_2_SHIP_3:
maybe_select_wl_slot(2,3);
break;
default:
break;
} // end switch
}
gr_reset_clip();
weapon_select_render(frametime);
int *weapon_ani_coords;
if (Game_mode & GM_MULTIPLAYER) {
weapon_ani_coords = Wl_weapon_ani_coords_multi[gr_screen.res];
} else {
weapon_ani_coords = Wl_weapon_ani_coords[gr_screen.res];
}
if (Selected_wl_class != -1 && Use_3d_weapon_select) {
static float WeapSelectScreenWeapRot = 0.0f;
int modelIdx = -1;
weapon_info *wip = &Weapon_info[Selected_wl_class];
//Get the model
if (VALID_FNAME(wip->tech_model)) {
modelIdx = model_load(wip->tech_model, nullptr, ErrorType::WARNING);
}
if (wip->render_type != WRT_LASER && modelIdx == -1) {
modelIdx = model_load(wip->pofbitmap_name, nullptr, ErrorType::FATAL_ERROR);
}
select_effect_params params;
params.effect = wip->selection_effect;
params.fs2_grid_color = wip->fs2_effect_grid_color;
params.fs2_scanline_color = wip->fs2_effect_scanline_color;
params.fs2_grid_density = wip->fs2_effect_grid_density;
params.fs2_wireframe_color = wip->fs2_effect_wireframe_color;
model_render_params render_info;
draw_model_rotating(&render_info,
-1,
modelIdx,
weapon_ani_coords[0],
weapon_ani_coords[1],
gr_screen.res == 0 ? 202 : 332,
gr_screen.res == 0 ? 185 : 260,
&WeapSelectScreenWeapRot,
&wip->closeup_pos,
wip->closeup_zoom * 0.65f,
REVOLUTION_RATE,
MR_IS_MISSILE | MR_AUTOCENTER | MR_NO_FOGGING,
GR_RESIZE_MENU,
params);
} else if ( Weapon_anim_class != -1 && ( Selected_wl_class == Weapon_anim_class )) {
Assert(Selected_wl_class >= 0 && Selected_wl_class < weapon_info_size());
if ( Weapon_anim_class != Selected_wl_class )
start_weapon_animation(Selected_wl_class);
generic_anim_render(&Cur_Anim, (help_overlay_active(Weapon_select_overlay_id)) ? 0 : frametime, weapon_ani_coords[0], weapon_ani_coords[1], true);
}
if ( !Background_playing ) {
Weapon_ui_window.draw();
wl_redraw_pressed_buttons();
draw_wl_icons();
wl_render_overhead_view(frametime);
wl_draw_ship_weapons(Selected_wl_slot);
for ( int i = 0; i < MAX_WING_BLOCKS; i++ ) {
draw_wing_block(i, Hot_wl_slot, Selected_wl_slot, -1, false, true);
}
common_render_selected_screen_button();
}
// maybe blit the multiplayer "locked" button
if((Game_mode & GM_MULTIPLAYER) && multi_ts_is_locked()){
Buttons[gr_screen.res][WL_BUTTON_MULTI_LOCK].button.draw_forced(2);
}
if ( wl_icon_being_carried() ) {
int mx, my, sx, sy;
Assert(Carried_wl_icon.weapon_class < weapon_info_size());
Assert( (Wss_slots != NULL) && (Wl_icons != NULL) );
mouse_get_pos_unscaled( &mx, &my );
sx = mx + Wl_delta_x;
sy = my + Wl_delta_y;
if ( (*Wl_icons)[Carried_wl_icon.weapon_class].can_use_for_ship )
{
wl_icon_info *icon = &(*Wl_icons)[Carried_wl_icon.weapon_class];
weapon_info *wip = &Weapon_info[Carried_wl_icon.weapon_class];
if(icon->icon_bmaps[WEAPON_ICON_FRAME_SELECTED] != -1)
{
gr_set_color_fast(&Color_blue);
gr_set_bitmap(icon->icon_bmaps[WEAPON_ICON_FRAME_SELECTED]);
gr_bitmap(sx, sy, GR_RESIZE_MENU);
} else {
gr_set_color_fast(&Icon_colors[ICON_FRAME_SELECTED]);
int w = 56;
int h = 24;
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(icon->model_index != -1)
{
//Draw the model
draw_model_icon(icon->model_index, MR_NO_FOGGING | MR_NO_LIGHTING, sx, sy, w, h, nullptr, wip, 0.4f, GR_RESIZE_MENU);
}
else if(icon->laser_bmap != -1)
{
//Draw laser bitmap
gr_set_clip(sx, sy, 56, 24, GR_RESIZE_MENU);
gr_set_bitmap(icon->laser_bmap);
gr_bitmap(0, 0, GR_RESIZE_MENU);
gr_reset_clip();
} else {
//Draw the weapon name, crappy last-ditch effort to not crash.
int half_x, half_y;
auto weapon_name = Weapon_info[Carried_wl_icon.weapon_class].get_display_name();
// Center-align and fit the text for display
gr_get_string_size(&half_x, &half_y, weapon_name);
half_x = sx +((56 - half_x) / 2);
half_y = sy +((28 - half_y) / 2); // Was ((24 - half_y) / 2) Zacam
gr_string(half_x, half_y, weapon_name, GR_RESIZE_MENU);
}
}
}
// draw number to prevent it from disappearing on clicks
if ( Carried_wl_icon.from_bank >= MAX_SHIP_PRIMARY_BANKS ) {
if ( mx == Carried_wl_icon.from_x && my == Carried_wl_icon.from_y ) {
int num_missiles = Wss_slots[Carried_wl_icon.from_slot].wep_count[Carried_wl_icon.from_bank];
wl_render_icon_count(num_missiles, Wl_bank_coords[gr_screen.res][Carried_wl_icon.from_bank][0], Wl_bank_coords[gr_screen.res][Carried_wl_icon.from_bank][1]);
}
}
// check so see if this is really a legal weapon to carry away
// (don't check can_use_for_bank here because we may drop the weapon on a bank that wasn't previously selected)
if ( !(*Wl_icons)[Carried_wl_icon.weapon_class].can_use_for_ship )
{
int diffx, diffy;
diffx = abs(Carried_wl_icon.from_x-mx);
diffy = abs(Carried_wl_icon.from_y-my);
if ( (diffx > 2) || (diffy > 2) ) {
int ship_class = Wss_slots[Selected_wl_slot].ship_class;
auto ship_class_name = Ship_info[ship_class].get_display_name();
auto weapon_name = Weapon_info[Carried_wl_icon.weapon_class].get_display_name();
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("A %s is unable to carry %s weaponry", 633), ship_class_name, weapon_name);
wl_dump_carried_icon();
}
}
}
if ( Weapon_anim_class != Selected_wl_class) {
start_weapon_animation(Selected_wl_class);
}
// render weapon description text
wl_render_weapon_desc(frametime);
wl_maybe_flash_button();
// should render the chatbox as close to the end as possible so it overlaps all controls
if(!Background_playing){
// render some extra stuff in multiplayer
if(Game_mode & GM_MULTIPLAYER){
// render the chatbox
chatbox_render();
// draw tooltips
Weapon_ui_window.draw_tooltip();
// render the status indicator for the voice system
multi_common_voice_display_status();
}
}
// blit help overlay if active
help_overlay_maybe_blit(Weapon_select_overlay_id, gr_screen.res);
gr_flip();
// 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 ) {
if(Game_mode & GM_MULTIPLAYER){
multi_ts_commit_pressed();
} else {
commit_pressed();
}
Commit_pressed = 0;
}
}
/**
* Free the bitmap slot and memory that was allocated
* to store the mask bitmap.
*
* @note Weapon_select_open is cleared when this function completes.
*/
void weapon_select_close()
{
if ( !Weapon_select_open ) {
nprintf(("Alan","weapon_select_close() returning without doing anything\n"));
return;
}
nprintf(("Alan", "Entering weapon_select_close()\n"));
// done with the bitmaps, so unlock it
bm_unlock(WeaponSelectMaskBitmap);
Weapon_ui_window.destroy();
// unload bitmaps
bm_release(WeaponSelectMaskBitmap);
Selected_wl_class = -1;
Weapon_select_open = 0;
if(Weapon_slot_bitmap != -1)
{
bm_release(Weapon_slot_bitmap);
}
}
/**
* Renders the number next to the weapon icon
*
* @param num the actual count to be printed
* @param x x screen position OF THE ICON (NOT where you want the text, this is calculated to prevent overlapping)
* @param y y screen position OF THE ICON (NOT where you want the text, this is calculated to prevent overlapping)
*/
void wl_render_icon_count(int num, int x, int y)
{
char buf[32];
int num_w, num_h;
int number_to_draw = (num >= 10000) ? 9999 : num; // cap count @ 9999 - Goober5000 bumped from 999
Assert(number_to_draw >= 0);
sprintf(buf, "%d", number_to_draw);
gr_get_string_size(&num_w, &num_h, buf, 1.0f, strlen(buf));
// render
gr_set_color_fast(&Color_white);
gr_string(x-num_w-4, y+8, buf, GR_RESIZE_MENU);
}
/**
* Render icon
*
* @param index weapon class, identifying which weapon to draw
* @param x x screen position to draw icon at
* @param y y screen position to draw icon at
* @param num count for weapon
* @param draw_num_flag 0 if not to draw count for weapon, nonzero otherwise
* @param hot_mask value that should match Hot_weapon_icon to show mouse is over
* @param hot_bank_mask value that should match Hot_weapon_bank_icon to show mouse is over
* @param select_mask value that should match Selected_wl_class to show icon is selected
*/
void wl_render_icon(int index, int x, int y, int num, int draw_num_flag, int hot_mask, int hot_bank_mask, int select_mask)
{
int bitmap_id = -1;
color *color_to_draw = NULL;
wl_icon_info *icon;
if ( Selected_wl_slot == -1 )
return;
icon = &(*Wl_icons)[index];
if ( icon->icon_bmaps[0] == -1 && icon->model_index == -1 && icon->laser_bmap == -1) {
wl_load_icons(index);
}
// assume default bitmap is to be used
if(icon->icon_bmaps[0] != -1)
bitmap_id = icon->icon_bmaps[WEAPON_ICON_FRAME_NORMAL]; // normal frame
else
color_to_draw = &Icon_colors[ICON_FRAME_NORMAL];
// next check if ship has mouse over it
if ( !wl_icon_being_carried() ) {
if ( (Hot_weapon_icon > -1 && Hot_weapon_icon == hot_mask)
|| (Hot_weapon_bank_icon > -1 && Hot_weapon_bank_icon == hot_bank_mask))
{
if(icon->icon_bmaps[0] != -1)
bitmap_id = icon->icon_bmaps[WEAPON_ICON_FRAME_HOT]; // normal frame
else
color_to_draw = &Icon_colors[ICON_FRAME_HOT];
}
}
// if icon is selected
if ( Selected_wl_class > -1 ) {
if ( Selected_wl_class == select_mask) {
if(icon->icon_bmaps[0] != -1)
bitmap_id = icon->icon_bmaps[WEAPON_ICON_FRAME_SELECTED]; // selected icon
else
color_to_draw = &Icon_colors[ICON_FRAME_SELECTED];
}
}
// if icon is disabled
if ( !icon->can_use_for_ship || icon->can_use_for_bank == TriStateBool::FALSE_ ) {
if(icon->icon_bmaps[0] != -1)
bitmap_id = icon->icon_bmaps[WEAPON_ICON_FRAME_DISABLED]; // disabled icon
else
color_to_draw = &Icon_colors[ICON_FRAME_DISABLED];
}
if(bitmap_id != -1)
{
gr_set_color_fast(&Color_blue);
gr_set_bitmap(bitmap_id);
gr_bitmap(x, y, GR_RESIZE_MENU);
}
else
{
// 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, x, y, x + 56, y + 24, GR_RESIZE_MENU);
line_draw_list.flush();
if(icon->model_index != -1)
{
//Draw the model
draw_model_icon(icon->model_index, MR_NO_FOGGING | MR_NO_LIGHTING, x, y, 56, 24, nullptr, &Weapon_info[index], 0.4f, GR_RESIZE_MENU);
}
else if(icon->laser_bmap != -1)
{
gr_set_clip(x, y, 56, 24, GR_RESIZE_MENU);
gr_set_bitmap(icon->laser_bmap);
gr_bitmap(0, 0, GR_RESIZE_MENU);
gr_reset_clip();
}
else
{
//Draw the weapon name, crappy last-ditch effort to not crash.
int half_x, half_y;
auto weapon_name = Weapon_info[index].get_display_name();
// Center-align and fit the text for display
gr_get_string_size(&half_x, &half_y, weapon_name);
half_x = x +((56 - half_x) / 2);
half_y = y +((28 - half_y) / 2); // Was ((24 - half_y) / 2) Zacam
gr_string(half_x, half_y, weapon_name, GR_RESIZE_MENU);
}
}
// draw the number of the item
// now, right justified
if ( draw_num_flag != 0 ) {
wl_render_icon_count(num, x, y);
}
}
/**
* Draw the icons for the weapons that are currently on the selected ship
*
* @param index Slot to draw weapons for
*/
void wl_draw_ship_weapons(int index)
{
int i;
int *wep, *wep_count;
if ( index == -1 )
return;
Assert(index >= 0 && index < MAX_WSS_SLOTS);
Assert(Wss_slots != NULL);
wep = Wss_slots[index].wep;
wep_count = Wss_slots[index].wep_count;
for ( i = 0; i < MAX_SHIP_WEAPONS; i++ )
{
if(i < Ship_info[Wss_slots[index].ship_class].num_primary_banks || (i >= MAX_SHIP_PRIMARY_BANKS && ((i - MAX_SHIP_PRIMARY_BANKS) < Ship_info[Wss_slots[index].ship_class].num_secondary_banks)))
{
if(Weapon_slot_bitmap != -1)
{
gr_set_bitmap(Weapon_slot_bitmap);
gr_bitmap( Wl_bank_coords[gr_screen.res][i][0] - 2, Wl_bank_coords[gr_screen.res][i][1] - 2, GR_RESIZE_MENU);
}
else
{
gr_set_color_fast(&Icon_colors[WEAPON_ICON_FRAME_NORMAL]);
graphics::line_draw_list line_draw_list;
draw_brackets_square( &line_draw_list, Wl_bank_coords[gr_screen.res][i][0], Wl_bank_coords[gr_screen.res][i][1], Wl_bank_coords[gr_screen.res][i][0] + 56, Wl_bank_coords[gr_screen.res][i][1] + 24, GR_RESIZE_MENU);
line_draw_list.flush();
}
}
if ( Carried_wl_icon.from_bank == i && Carried_wl_icon.from_slot == index ) {
continue;
}
if ( (wep[i] != -1) && (wep_count[i] > 0) )
{
wl_render_icon( wep[i], Wl_bank_coords[gr_screen.res][i][0], Wl_bank_coords[gr_screen.res][i][1], wep_count[i], Wl_bank_count_draw_flags[i], -1, i, wep[i]);
}
}
}
/**
* Draw icon with number
*
* @param list_count list position on screen (0-7)
* @param weapon_class class of weapon
*/
void draw_wl_icon_with_number(int list_count, int weapon_class)
{
Assert( list_count >= 0 && list_count < 8 );
Assert( (Wl_icons != NULL) && (Wl_pool != NULL) );
if ( (*Wl_icons)[weapon_class].can_use_for_ship && (*Wl_icons)[weapon_class].can_use_for_bank != TriStateBool::FALSE_ )
{
gr_set_color_fast(&Icon_colors[WEAPON_ICON_FRAME_NORMAL]);
}
else
{
gr_set_color_fast(&Icon_colors[WEAPON_ICON_FRAME_DISABLED]);
}
wl_render_icon(weapon_class, Wl_weapon_icon_coords[gr_screen.res][list_count][0], Wl_weapon_icon_coords[gr_screen.res][list_count][1],
Wl_pool->value_or(weapon_class, 0), 1, list_count, -1, weapon_class);
}
/**
* Draw the weapon icons that are available
*/
void draw_wl_icons()
{
int count=0;
for ( size_t i = Plist_start; i < Plist.size(); ++i ) {
draw_wl_icon_with_number(count, Plist[i]);
if ( ++count >= NUM_PRIMARY_MASK_REGIONS )
break;
}
count=0;
for ( size_t i = Slist_start; i < Slist.size(); ++i ) {
draw_wl_icon_with_number(count+NUM_PRIMARY_MASK_REGIONS, Slist[i]);
if ( ++count >= NUM_SECONDARY_MASK_REGIONS )
break;
}
}
/**
* Determine if an icon from the scrollable weapon 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
*
* @param index (0..7)
*/
void wl_pick_icon_from_list(int index)
{
int weapon_class, mx, my;
// if this is a multiplayer game and the player is an observer, he can never pick any weapons up
if((Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_OBSERVER)){
return;
}
// if a weapon is being carried, do nothing
if ( wl_icon_being_carried() ) {
return;
}
if ( index < NUM_PRIMARY_MASK_REGIONS ) {
weapon_class = wl_get_weapon_class_from_list(Plist, Plist_start, index);
} else {
weapon_class = wl_get_weapon_class_from_list(Slist, Slist_start, index - NUM_PRIMARY_MASK_REGIONS);
}
// there isn't a weapon there at all!
if ( weapon_class < 0 )
return;
Assert( Wl_pool != NULL );
// no weapons left of that class
if ( Wl_pool->value_or(weapon_class, 0) <= 0 ) {
return;
}
wl_set_carried_icon(-1, -1, weapon_class);
common_flash_button_init();
mouse_get_pos_unscaled( &mx, &my );
Wl_delta_x = Wl_weapon_icon_coords[gr_screen.res][index][0] - mx;
Wl_delta_y = Wl_weapon_icon_coords[gr_screen.res][index][1] - my;
}
/**
* Pick from ship slot
*
* @param num index into shipb banks (0..2 primary, 3..6 secondary)
*/
void pick_from_ship_slot(int num)
{
int mx, my, *wep, *wep_count;
Assert(num < MAX_SHIP_WEAPONS);
if ( Selected_wl_slot == -1 )
return;
if ( wl_icon_being_carried() )
return;
if ( ss_disabled_slot( Selected_wl_slot, false ) )
return;
Assert( (Wss_slots != NULL) && (Wl_icons != NULL) );
wep = Wss_slots[Selected_wl_slot].wep;
wep_count = Wss_slots[Selected_wl_slot].wep_count;
// check if a weapon even exists in that slot
if ( (wep[num] < 0) || (wep_count[num] <= 0) ) {
return;
}
Assert((*Wl_icons)[wep[num]].can_use_for_ship);
// we can't Assert on can_use_for_bank here because we may have clicked on a bank that isn't selected
wl_set_carried_icon(num, Selected_wl_slot, wep[num]);
common_flash_button_init();
mouse_get_pos_unscaled( &mx, &my );
Wl_delta_x = Wl_bank_coords[gr_screen.res][num][0] - mx;
Wl_delta_y = Wl_bank_coords[gr_screen.res][num][1] - my;
Carried_wl_icon.from_x = mx;
Carried_wl_icon.from_y = my;
}
/**
* Determine if this slot has no weapons
*/
int wl_slots_all_empty(wss_unit *slot)
{
int i;
for ( i = 0; i < MAX_SHIP_WEAPONS; i++ ) {
if ( (slot->wep_count[i] > 0) && (slot->wep[i] >= 0) )
return 0;
}
return 1;
}
/**
* Change a ship's weapons based on the information contained in the
* Weapon_data[] structure that is filled in during weapon loadout
*
* @return -1 if the player ship has no weapons
* @return 0 if function finished without errors
*/
int wl_update_ship_weapons(int objnum, wss_unit *slot )
{
ship_info *sip = &Ship_info[Ships[Player_obj->instance].ship_info_index];
if(sip->num_secondary_banks <= 0 && sip->num_primary_banks <= 0)
{
return 0;
}
// AL 11-15-97: Ensure that the player ship hasn't removed all
// weapons from their ship. This will cause a warning to appear.
if ( objnum == OBJ_INDEX(Player_obj) && Weapon_select_open ) {
if ( wl_slots_all_empty(slot) && (sip->num_primary_banks > 0 || sip->num_secondary_banks > 0)) {
return -1;
}
}
wl_bash_ship_weapons(&Ships[Objects[objnum].instance].weapons, slot);
return 0;
}
/**
* Set the Pilot subsystem of a parse_object to the weapons that are setup
* for the wing_block,wing_slot ship
*
* @param pobjp Pointer to parse object that references Pilot subsystem
* @param slot Pointer to slot object
*/
void wl_update_parse_object_weapons(p_object *pobjp, wss_unit *slot)
{
int i, j, sidx, pilot_index, max_count;
subsys_status *ss;
Assert(slot->ship_class >= 0);
pilot_index = wl_get_pilot_subsys_index(pobjp);
if ( pilot_index == -1 )
return;
ss = &Subsys_status[pilot_index];
for ( i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++ ) {
ss->primary_banks[i] = -1;
}
for ( i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++ ) {
ss->secondary_banks[i] = -1;
}
j = 0;
for ( i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++ )
{
if ( (slot->wep_count[i] > 0) && (slot->wep[i] >= 0) )
{
ss->primary_banks[j] = slot->wep[i];
// ballistic primaries - Goober5000
if (Weapon_info[slot->wep[i]].wi_flags[Weapon::Info_Flags::Ballistic])
{
// Important: the primary_ammo[] value is a percentage of max capacity!
// which means that it's always 100%, since ballistic primaries are completely
// full upon launch - Goober5000
ss->primary_ammo[j] = 100;
}
else
{
ss->primary_ammo[j] = 0;
}
j++;
}
}
j = 0;
for ( i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++ )
{
sidx = i+MAX_SHIP_PRIMARY_BANKS;
if ( (slot->wep_count[sidx] > 0) && (slot->wep[sidx] >= 0) )
{
ss->secondary_banks[j] = slot->wep[sidx];
// Important: the secondary_ammo[] value is a percentage of max capacity!
max_count = wl_calc_missile_fit(slot->wep[sidx], Ship_info[slot->ship_class].secondary_bank_ammo_capacity[j]);
ss->secondary_ammo[j] = (int)std::lround(i2fl(slot->wep_count[sidx]) / max_count * 100.0f);
j++;
}
}
}
/**
* Start the current weapon animation from playing.
*/
void start_weapon_animation(int weapon_class)
{
char *p;
char animation_filename[CF_MAX_FILENAME_LENGTH+4];
anim_timer_start = timer_get_milliseconds();
if ( weapon_class < 0 )
return;
if ( weapon_class == Weapon_anim_class )
return;
gamesnd_play_iface(InterfaceSounds::WEAPON_ANIM_START);
//load a new animation if it's different to what's already playing
if(strcmp(Cur_Anim.filename, Weapon_info[weapon_class].anim_filename) != 0) {
//unload the previous anim
generic_anim_unload(&Cur_Anim);
//load animation here, we now only have one loaded
p = strchr( Weapon_info[weapon_class].anim_filename, '.' );
if(p)
*p = '\0';
if (gr_screen.res == GR_1024) {
strcpy_s(animation_filename, "2_");
strcat_s(animation_filename, Weapon_info[weapon_class].anim_filename);
}
else {
strcpy_s(animation_filename, Weapon_info[weapon_class].anim_filename);
}
generic_anim_init(&Cur_Anim, animation_filename);
Cur_Anim.ani.bg_type = bm_get_type(Weapon_select_background_bitmap);
if(generic_anim_stream(&Cur_Anim) == -1) {
//we've failed to load an animation, load an image and treat it like a 1 frame animation
Cur_Anim.first_frame = bm_load(Weapon_info[weapon_class].anim_filename); //if we fail here, the value is still -1
if(Cur_Anim.first_frame != -1) {
Cur_Anim.num_frames = 1;
}
}
}
// start the text wipe
wl_weapon_desc_start_wipe();
Weapon_anim_class = weapon_class;
if ( (*Wl_icons)[weapon_class].model_index >= 0 )
return;
}
/**
* Reset the weapons loadout to the defaults in the mission
*/
void wl_reset_to_defaults()
{
// don't reset of weapons pool in multiplayer
if(Game_mode & GM_MULTIPLAYER){
return;
}
wl_init_pool(&Team_data[Common_team]);
wl_init_icon_lists();
wl_fill_slots();
wl_reset_selected_slot();
wl_reset_carried_icon();
wl_maybe_reset_selected_weapon_class();
}
/**
* Bash ship weapons, based on what is stored in the stored weapons loadout
* @note Wss_slots[] is assumed to be correctly set
*/
void wl_bash_ship_weapons(ship_weapon *swp, wss_unit *slot)
{
int i, j, sidx;
j = 0;
for (i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) {
// set to default value first thing
swp->primary_bank_weapons[i] = -1;
// now set to true value
if ( (slot->wep_count[i] > 0) && (slot->wep[i] >= 0) ) {
swp->primary_bank_weapons[j] = slot->wep[i];
// ballistic primaries - Goober5000
if (Weapon_info[swp->primary_bank_weapons[j]].wi_flags[Weapon::Info_Flags::Ballistic]) {
// this is a bit tricky: we must recalculate ammo for full capacity
// since wep_count for primaries does not store the ammo; ballistic
// primaries always come with a full magazine
swp->primary_bank_ammo[j] = wl_calc_ballistic_fit(swp->primary_bank_weapons[j], Ship_info[slot->ship_class].primary_bank_ammo_capacity[i]);
} else {
swp->primary_bank_ammo[j] = 0;
}
j++;
}
}
// update our # of primary banks
swp->num_primary_banks = j;
j = 0;
for (i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++) {
// set to default value first thing
swp->secondary_bank_weapons[i] = -1;
// now set the true value
sidx = i+MAX_SHIP_PRIMARY_BANKS;
if ( (slot->wep_count[sidx] > 0) && (slot->wep[sidx] >= 0) ) {
swp->secondary_bank_weapons[j] = slot->wep[sidx];
swp->secondary_bank_ammo[j] = slot->wep_count[sidx];
if (Weapon_info[slot->wep[sidx]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo])
swp->secondary_bank_start_ammo[j] = 0;
else
swp->secondary_bank_start_ammo[j] = (int)std::lround(Ship_info[slot->ship_class].secondary_bank_ammo_capacity[i] / Weapon_info[swp->secondary_bank_weapons[j]].cargo_size);
j++;
}
}
// update our # of secondary banks
swp->num_secondary_banks = j;
}
/**
* Utility function for swapping two banks
*/
void wl_swap_weapons(int ship_slot, int from_bank, int to_bank)
{
wss_unit *slot;
int tmp;
Assert( Wss_slots != NULL );
slot = &Wss_slots[ship_slot];
if ( from_bank == to_bank || (from_bank >= MAX_SHIP_WEAPONS || from_bank < 0 ) || (to_bank >= MAX_SHIP_WEAPONS || to_bank < 0 ) ) {
return;
}
// swap weapon type
tmp = slot->wep[from_bank];
slot->wep[from_bank] = slot->wep[to_bank];
slot->wep[to_bank] = tmp;
// swap weapon count
tmp = slot->wep_count[from_bank];
slot->wep_count[from_bank] = slot->wep_count[to_bank];
slot->wep_count[to_bank] = tmp;
}
/**
* Utility function used to put back overflow into the weapons pool
*/
void wl_saturate_bank(int ship_slot, int bank)
{
wss_unit *slot;
int max_count, overflow;
Assert( Wss_slots != NULL );
slot = &Wss_slots[ship_slot];
if ( (slot->wep[bank] < 0) || (slot->wep_count[bank] <= 0) ) {
return;
}
max_count = wl_calc_missile_fit(slot->wep[bank], Ship_info[slot->ship_class].secondary_bank_ammo_capacity[bank - MAX_SHIP_PRIMARY_BANKS]);
overflow = slot->wep_count[bank] - max_count;
if ( overflow > 0 ) {
Assert( Wl_pool != NULL );
slot->wep_count[bank] -= overflow;
// add overflow back to pool
(*Wl_pool)[slot->wep[bank]] += overflow;
}
}
// exit: 0 -> no data changed
// 1 -> data changed
// sound => gets filled with sound id to play
// updated for specific bank by Goober5000
int wl_swap_slot_slot(int from_bank, int to_bank, int ship_slot, interface_snd_id *sound, net_player *pl)
{
wss_unit *slot;
int class_mismatch_flag, forced_update;
Assert( Wss_slots != NULL );
slot = &Wss_slots[ship_slot];
// usually zero, unless we have a strange update thingy
forced_update = 0;
if ( slot->ship_class == -1 ) {
Int3(); // should not be possible
return forced_update;
}
// do nothing if swapping with self
if ( from_bank == to_bank ) {
*sound=InterfaceSounds::ICON_DROP_ON_WING;
return forced_update; // no update
}
// ensure that source bank exists and has something to pick from
if ( slot->wep[from_bank] == -1 || slot->wep_count[from_bank] <= 0 ) {
return forced_update;
}
// ensure that the dest bank exists
if ( slot->wep_count[to_bank] < 0 ) {
return forced_update;
}
Assert( Wl_pool != NULL );
// ensure banks are compatible as far as primary and secondary
class_mismatch_flag = (IS_BANK_PRIMARY(from_bank) && IS_BANK_SECONDARY(to_bank)) || (IS_BANK_SECONDARY(from_bank) && IS_BANK_PRIMARY(to_bank));
// further ensure that restrictions aren't breached
if (!class_mismatch_flag) {
ship_info *sip = &Ship_info[slot->ship_class];
// check the to-bank first
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[to_bank])) {
if (!eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[to_bank][slot->wep[from_bank]])) {
SCP_string txt;
sprintf(txt, XSTR("This bank is unable to carry %s weaponry", 1628), Weapon_info[slot->wep[from_bank]].get_display_name());
if ( !(Game_mode & GM_MULTIPLAYER) || (Netgame.host == pl) ) {
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, txt.c_str());
} else if (pl != NULL) {
send_game_chat_packet(Netgame.host, txt.c_str(), MULTI_MSG_TARGET, pl, nullptr, 1);
}
return forced_update;
}
}
// check the from-bank
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[from_bank])) {
if (!eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[from_bank][slot->wep[to_bank]])) {
// going from "from" to "to" is valid (from previous if), but going the other way isn't...
// so return the "to" to the list and just move the "from"
// put to_bank back into list
(*Wl_pool)[slot->wep[to_bank]] += slot->wep_count[to_bank]; // return to list
slot->wep[to_bank] = -1; // remove from slot
slot->wep_count[to_bank] = 0;
*sound=InterfaceSounds::ICON_DROP; // unless it changes later
forced_update = 1; // because we can't return right away
}
}
}
if ( class_mismatch_flag ) {
// put from_bank back into list
(*Wl_pool)[slot->wep[from_bank]] += slot->wep_count[from_bank]; // return to list
slot->wep[from_bank] = -1; // remove from slot
slot->wep_count[from_bank] = 0;
*sound=InterfaceSounds::ICON_DROP;
return 1;
}
// case 1: primaries (easy, even with ballistics, because ammo is always maximized)
if ( IS_BANK_PRIMARY(from_bank) && IS_BANK_PRIMARY(to_bank) ) {
wl_swap_weapons(ship_slot, from_bank, to_bank);
*sound=InterfaceSounds::ICON_DROP_ON_WING;
return 1;
}
// case 2: secondaries (harder)
if ( IS_BANK_SECONDARY(from_bank) && IS_BANK_SECONDARY(to_bank) ) {
// case 2a: secondaries are the same type
if ( slot->wep[from_bank] == slot->wep[to_bank] ) {
int dest_max, dest_can_fit, source_can_give;
dest_max = wl_calc_missile_fit(slot->wep[to_bank], Ship_info[slot->ship_class].secondary_bank_ammo_capacity[to_bank - MAX_SHIP_PRIMARY_BANKS]);
dest_can_fit = dest_max - slot->wep_count[to_bank];
if ( dest_can_fit <= 0 ) {
// dest bank is already full.. nothing to do here
return forced_update;
}
// see how much source can give
source_can_give = MIN(dest_can_fit, slot->wep_count[from_bank]);
if ( source_can_give > 0 ) {
slot->wep_count[to_bank] += source_can_give; // add to dest
slot->wep_count[from_bank] -= source_can_give; // take from source
*sound=InterfaceSounds::ICON_DROP_ON_WING;
return 1;
} else {
return forced_update;
}
}
// case 2b: secondaries are different types
if ( slot->wep[from_bank] != slot->wep[to_bank] ) {
// swap 'em
wl_swap_weapons(ship_slot, from_bank, to_bank);
// put back some on list if required
wl_saturate_bank(ship_slot, from_bank);
wl_saturate_bank(ship_slot, to_bank);
*sound=InterfaceSounds::ICON_DROP_ON_WING;
return 1;
}
}
Int3(); // should never get here
return forced_update;
}
// exit: 0 -> no data changed
// 1 -> data changed
// sound => gets filled with sound id to play
int wl_dump_to_list(int from_bank, int to_list, int ship_slot, interface_snd_id *sound)
{
wss_unit *slot;
Assert( (Wss_slots != NULL) && (Wl_pool != NULL) );
slot = &Wss_slots[ship_slot];
// ensure that source bank exists and has something to pick from
if ( slot->wep[from_bank] == -1 || slot->wep_count[from_bank] <= 0 ) {
return 0;
}
// put weapon bank to the list
(*Wl_pool)[to_list] += slot->wep_count[from_bank]; // return to list
slot->wep[from_bank] = -1; // remove from slot
slot->wep_count[from_bank] = 0;
*sound=InterfaceSounds::ICON_DROP;
return 1;
}
// exit: 0 -> no data changed
// 1 -> data changed
// sound => gets filled with sound id to play
int wl_grab_from_list(int from_list, int to_bank, int ship_slot, interface_snd_id *sound, net_player *pl)
{
int update=0;
wss_unit *slot;
Assert( (Wss_slots != NULL) && (Wl_pool != NULL) );
slot = &Wss_slots[ship_slot];
int max_fit;
// ensure that the banks are both of the same class
if ( (IS_LIST_PRIMARY(from_list) && IS_BANK_SECONDARY(to_bank)) || (IS_LIST_SECONDARY(from_list) && IS_BANK_PRIMARY(to_bank)) )
{
// do nothing
*sound=InterfaceSounds::ICON_DROP;
return 0;
}
// ensure that dest bank exists
if ( slot->wep_count[to_bank] < 0 ) {
*sound=InterfaceSounds::ICON_DROP;
return 0;
}
// bank should be empty:
Assert(slot->wep_count[to_bank] == 0);
Assert(slot->wep[to_bank] < 0);
// ensure that pool has weapon
if ( Wl_pool->value_or(from_list, 0) <= 0 ) {
return 0;
}
ship_info *sip = &Ship_info[slot->ship_class];
// ensure that this bank will accept the weapon...
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[to_bank])) {
if (!eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[to_bank][from_list])) {
SCP_string txt;
sprintf(txt, XSTR("This bank is unable to carry %s weaponry", 1628), Weapon_info[from_list].get_display_name());
if ( !(Game_mode & GM_MULTIPLAYER) || (Netgame.host == pl) ) {
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, txt.c_str());
} else if (pl != NULL) {
send_game_chat_packet(Netgame.host, txt.c_str(), MULTI_MSG_TARGET, pl, nullptr, 1);
}
return 0;
}
}
// we will have some sort of success from this point
update = 1;
// find how much dest bank can fit
if ( to_bank < MAX_SHIP_PRIMARY_BANKS ) {
max_fit = 1;
} else {
max_fit = wl_calc_missile_fit(from_list, Ship_info[slot->ship_class].secondary_bank_ammo_capacity[to_bank-MAX_SHIP_PRIMARY_BANKS]);
}
// take weapon from list
int pool_count = Wl_pool->value_or(from_list, 0);
if ( pool_count < max_fit ) {
max_fit = pool_count;
update=2;
}
(*Wl_pool)[from_list] -= max_fit;
// put on the slot
slot->wep[to_bank] = from_list;
slot->wep_count[to_bank] = max_fit;
*sound=InterfaceSounds::ICON_DROP_ON_WING;
return update;
}
// exit: 0 -> no data changed
// 1 -> data changed
// sound => gets filled with sound id to play
int wl_swap_list_slot(int from_list, int to_bank, int ship_slot, interface_snd_id *sound, net_player *pl)
{
wss_unit *slot;
Assert( (Wss_slots != NULL) && (Wl_pool != NULL) );
slot = &Wss_slots[ship_slot];
int max_fit;
// ensure that the banks are both of the same class
if ( (IS_LIST_PRIMARY(from_list) && IS_BANK_SECONDARY(to_bank)) || (IS_LIST_SECONDARY(from_list) && IS_BANK_PRIMARY(to_bank)) ) {
// do nothing
*sound=InterfaceSounds::ICON_DROP;
return 0;
}
// ensure that dest bank exists
if ( slot->wep_count[to_bank] < 0 ) {
return 0;
}
// bank should have something in it
Assert(slot->wep_count[to_bank] > 0);
Assert(slot->wep[to_bank] >= 0);
// ensure that pool has weapon
if ( Wl_pool->value_or(from_list, 0) <= 0 ) {
return 0;
}
ship_info *sip = &Ship_info[slot->ship_class];
// ensure that this bank will accept the weapon
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[to_bank])) {
if (!eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[to_bank][from_list])) {
SCP_string txt;
sprintf(txt, XSTR("This bank is unable to carry %s weaponry", 1628), Weapon_info[from_list].get_display_name());
if ( !(Game_mode & GM_MULTIPLAYER) || (Netgame.host == pl) ) {
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, txt.c_str());
} else if (pl != NULL) {
send_game_chat_packet(Netgame.host, txt.c_str(), MULTI_MSG_TARGET, pl, nullptr, 1);
}
return 0;
}
}
// dump slot weapon back into list
(*Wl_pool)[slot->wep[to_bank]] += slot->wep_count[to_bank];
slot->wep_count[to_bank] = 0;
slot->wep[to_bank] = -1;
// put weapon on ship from list
// find how much dest bank can fit
if ( to_bank < MAX_SHIP_PRIMARY_BANKS ) {
max_fit = 1;
} else {
max_fit = wl_calc_missile_fit(from_list, Ship_info[slot->ship_class].secondary_bank_ammo_capacity[to_bank-MAX_SHIP_PRIMARY_BANKS]);
}
// take weapon from list
int pool_count = Wl_pool->value_or(from_list, 0);
if ( pool_count < max_fit ) {
max_fit = pool_count;
}
(*Wl_pool)[from_list] -= max_fit;
// put on the slot
slot->wep[to_bank] = from_list;
slot->wep_count[to_bank] = max_fit;
*sound=InterfaceSounds::ICON_DROP_ON_WING;
return 1;
}
/**
* Update any interface data that may be dependent on Wss_slots[]
* @todo Implement
*/
void wl_synch_interface()
{
}
int wl_apply(int mode,int from_bank,int from_list,int to_bank,int to_list,int ship_slot,int player_index, bool dont_play_sound)
{
int update=0;
interface_snd_id sound;
net_player *pl;
// get the appropriate net player
if(Game_mode & GM_MULTIPLAYER){
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
} else {
pl = NULL;
}
switch(mode){
case WSS_SWAP_SLOT_SLOT:
update = wl_swap_slot_slot(from_bank, to_bank, ship_slot, &sound, pl);
break;
case WSS_DUMP_TO_LIST:
update = wl_dump_to_list(from_bank, to_list, ship_slot, &sound);
break;
case WSS_GRAB_FROM_LIST:
update = wl_grab_from_list(from_list, to_bank, ship_slot, &sound, pl);
break;
case WSS_SWAP_LIST_SLOT:
update = wl_swap_list_slot(from_list, to_bank, ship_slot, &sound, pl);
break;
}
// only play this sound if the move was done locally (by the host in other words)
if ( (sound.isValid()) && (player_index == -1) && !dont_play_sound) {
gamesnd_play_iface(sound);
}
if ( update ) {
if ( MULTIPLAYER_HOST ) {
int size;
ubyte wss_data[MAX_PACKET_SIZE];
size = store_wss_data(wss_data, MAX_PACKET_SIZE-20,sound,player_index);
Assert(pl != NULL);
send_wss_update_packet(pl->p_info.team,wss_data, size);
}
if(Game_mode & GM_MULTIPLAYER){
Assert(pl != NULL);
// if the pool we're using has changed, synch stuff up
if(pl->p_info.team == Net_player->p_info.team){
wl_synch_interface();
}
} else {
wl_synch_interface();
}
}
return update;
}
int wl_drop(int from_bank,int from_list,int to_bank,int to_list, int ship_slot, int player_index, bool dont_play_sound)
{
int mode;
int update=0;
net_player *pl;
// get the appropriate net player
if(Game_mode & GM_MULTIPLAYER){
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
} else {
pl = NULL;
}
common_flash_button_init();
if ( !(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_HOST ) {
if(MULTI_TEAM){
Assert(pl != NULL);
// set the global pointers to the right pools
common_set_team_pointers(pl->p_info.team);
}
mode = wss_get_mode(from_bank, from_list, to_bank, to_list, ship_slot);
if ( mode >= 0 ) {
update = wl_apply(mode, from_bank, from_list, to_bank, to_list, ship_slot, player_index, dont_play_sound);
}
if(MULTI_TEAM){
// set the global pointers to the right pools
common_set_team_pointers(Net_player->p_info.team);
}
} else {
send_wss_request_packet(Net_player->player_id, from_bank, from_list, to_bank, to_list, ship_slot, -1, WSS_WEAPON_SELECT);
}
return update;
}
// Goober5000
void wl_apply_current_loadout_to_all_ships_in_current_wing()
{
int source_wss_slot, cur_wss_slot;
int cur_wing_block, cur_wing_slot, cur_bank;
int weapon_type_to_add, result;
size_t i;
ship_info *sip, *source_sip;
char ship_name[NAME_LENGTH];
// error stuff
bool error_flag = false;
SCP_vector<SCP_string> error_messages;
// make sure we're not holding anything
wl_dump_carried_icon();
// find the currently selected ship (or the squadron leader if none)
source_wss_slot = Selected_wl_slot;
if (source_wss_slot == -1)
source_wss_slot = 0;
// get its class
source_sip = &Ship_info[Wss_slots[source_wss_slot].ship_class];
// find which wing this ship is part of
cur_wing_block = source_wss_slot / MAX_WING_SLOTS;
// for all ships in the wing
for (cur_wing_slot = 0; cur_wing_slot < MAX_WING_SLOTS; cur_wing_slot++)
{
Assert( Wss_slots != NULL );
// get the slot for this ship
cur_wss_slot = cur_wing_block * MAX_WING_SLOTS + cur_wing_slot;
// make sure there is actually a ship here
if (Wss_slots[cur_wss_slot].ship_class < 0)
continue;
// get the ship's name and class
sip = &Ship_info[Wss_slots[cur_wss_slot].ship_class];
ss_return_name(cur_wing_block, cur_wing_slot, ship_name);
// don't process the selected ship
if (cur_wss_slot == source_wss_slot)
continue;
// must be valid for us to change
if (!ss_valid_slot(cur_wss_slot))
continue;
// copy weapons from source slot to this slot
for (cur_bank = 0; cur_bank < MAX_SHIP_WEAPONS; cur_bank++)
{
// this bank must exist on both the source ship and the destination ship
if (cur_bank < MAX_SHIP_PRIMARY_BANKS)
{
if (cur_bank >= source_sip->num_primary_banks || cur_bank >= sip->num_primary_banks)
continue;
}
else
{
if (cur_bank-MAX_SHIP_PRIMARY_BANKS >= source_sip->num_secondary_banks || cur_bank-MAX_SHIP_PRIMARY_BANKS >= sip->num_secondary_banks)
continue;
}
// dump the destination ship's weapons
wl_drop(cur_bank, -1, -1, Wss_slots[cur_wss_slot].wep[cur_bank], cur_wss_slot, -1, true);
// weapons must be present on the source ship
if (Wss_slots[source_wss_slot].wep[cur_bank] < 0)
continue;
// determine the weapon we need
weapon_type_to_add = Wss_slots[source_wss_slot].wep[cur_bank];
auto wep_display_name = Weapon_info[weapon_type_to_add].get_display_name();
// make sure this ship can accept this weapon
if (!eval_weapon_flag_for_game_type(sip->allowed_weapons[weapon_type_to_add]))
{
SCP_string temp;
sprintf(temp, XSTR("%s is unable to carry %s weaponry", 1629), ship_name, wep_display_name);
error_messages.push_back(std::move(temp));
error_flag = true;
continue;
}
// make sure this bank can accept this weapon
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[cur_bank]))
{
if (!eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[cur_bank][weapon_type_to_add]))
{
SCP_string temp;
if (cur_bank < MAX_SHIP_PRIMARY_BANKS)
sprintf(temp, XSTR("%s is unable to carry %s weaponry in primary bank %d", 1630), ship_name, wep_display_name, cur_bank+1);
else
sprintf(temp, XSTR("%s is unable to carry %s weaponry in secondary bank %d", 1631), ship_name, wep_display_name, cur_bank+1-MAX_SHIP_PRIMARY_BANKS);
error_messages.push_back(std::move(temp));
error_flag = true;
continue;
}
}
// add from the weapon pool
result = wl_drop(-1, weapon_type_to_add, cur_bank, -1, cur_wss_slot, -1, true);
// bank left unfilled or partially filled
if ((result == 0) || (result == 2))
{
SCP_string temp;
sprintf(temp, XSTR("Insufficient %s available to arm %s", 1632), Weapon_info[weapon_type_to_add].get_display_name(), ship_name);
error_messages.push_back(std::move(temp));
error_flag = true;
continue;
}
}
}
// play sound
gamesnd_play_iface(InterfaceSounds::ICON_DROP_ON_WING);
// display error messages
if (error_flag)
{
SCP_string full_error_message = XSTR("The following errors were encountered:\n", 1641);
size_t j;
bool is_duplicate;
// copy all messages
for (i = 0; i < error_messages.size(); i++)
{
// check for duplicate messages
is_duplicate = false;
for (j = 0; j < i; j++)
{
if (error_messages[i] == error_messages[j])
{
is_duplicate = true;
break;
}
}
if (is_duplicate)
continue;
// copy message
full_error_message += "\n";
full_error_message += error_messages[i];
}
// display popup
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, full_error_message.c_str());
}
}