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

254 lines
9.1 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.
*
*/
#ifndef _MISSION_SCREEN_COMMON_HEADER_FILE
#define _MISSION_SCREEN_COMMON_HEADER_FILE
#include "globalincs/globals.h"
#include "gamesnd/gamesnd.h"
#include "mod_table/mod_table.h"
#include "model/model.h"
#include "ui/ui.h"
#define BACKGROUND_FRAME_TO_START_SHIP_ANIM 87
#define BUTTON_SLIDE_IN_FRAME 1
///////////////////////////////////////////////////////
// Common to briefing/ship selection/weapons loadout
///////////////////////////////////////////////////////
#define REVOLUTION_RATE 5.2f
#define COMMON_BRIEFING_REGION 0
#define COMMON_SS_REGION 1
#define COMMON_WEAPON_REGION 2
#define COMMON_COMMIT_REGION 5
#define COMMON_HELP_REGION 6
#define COMMON_OPTIONS_REGION 7
#define NUM_COMMON_REGIONS 6
#define NUM_COMMON_BUTTONS 6
struct brief_common_buttons {
const char *filename;
int x, y;
int xt, yt;
int hotspot;
int repeat;
UI_BUTTON button; // because we have a class inside this struct, we need the constructor below..
brief_common_buttons(const char *name, int x1, int y1, int xt1, int yt1, int h, int r = 0) : filename(name), x(x1), y(y1), xt(xt1), yt(yt1), hotspot(h), repeat(r) {}
};
extern brief_common_buttons Common_buttons[3][GR_NUM_RESOLUTIONS][NUM_COMMON_BUTTONS];
extern int Background_playing;
extern int Common_select_inited;
extern int Current_screen;
extern int Common_team;
extern int Drop_icon_mflag;
extern int Drop_on_wing_mflag;
extern int Brief_mouse_up_flag;
extern int Mouse_down_last_frame;
extern int Wing_slot_empty_bitmap;
extern int Wing_slot_disabled_bitmap;
extern UI_TIMESTAMP Flash_timer; // timestamp used to start flashing
extern UI_TIMESTAMP Flash_toggle; // timestamp used to toggle flashing
extern int Flash_bright; // state of button to flash
extern int anim_timer_start;
void common_button_do(int i);
//If new enums are added here be sure to also update the description for the API version in scripting\api\libs\ui.cpp - Mjn
enum class commit_pressed_status {
SUCCESS,
GENERAL_FAIL,
PLAYER_NO_WEAPONS,
NO_REQUIRED_WEAPON,
NO_REQUIRED_WEAPON_MULTIPLE,
BANK_GAP_ERROR,
PLAYER_NO_SLOT,
MULTI_PLAYERS_NO_SHIPS,
MULTI_NOT_ALL_ASSIGNED,
MULTI_NO_PRIMARY,
MULTI_NO_SECONDARY
};
// common_select_init() performs initialization common to the briefing/ship select/weapon select
// screens. This includes loading/setting the palette, loading the background animation, loading
// the screen switching animations, loading the button animation frames
void common_select_init(bool API_Access = false);
int common_select_do(float frametime);
void common_select_close();
void common_select_level_close();
void common_draw_buttons();
void common_check_buttons();
void common_check_keys(int k);
commit_pressed_status commit_pressed(bool API_Access = false);
void common_render(float frametime);
void common_buttons_init(UI_WINDOW *ui_window);
void common_buttons_maybe_reload(UI_WINDOW *ui_window);
void common_render_selected_screen_button();
void common_reset_buttons();
void common_redraw_pressed_buttons();
void common_maybe_clear_focus();
void ship_select_common_init(bool API_Access = false);
void common_setup_room_lights();
int mission_ui_background_load(const char *custom_background, const char *single_background, const char *multi_background = NULL);
void common_set_interface_palette(const char *filename = NULL); // set the interface palette
void common_free_interface_palette(); // restore game palette
void load_wing_icons(const char *filename);
void unload_wing_icons();
void common_flash_button_init();
int common_flash_bright();
void set_active_ui(UI_WINDOW *ui_window);
// music functions exported for multiplayer team selection screen to start briefing music
const char *common_music_get_filename(int score_index);
void common_music_init( int score_index );
void common_music_do();
void common_music_close();
int common_num_cutscenes_valid(int movie_type);
void common_maybe_play_cutscene(int movie_type, bool restart_music = false, int music = 0);
void common_play_cutscene(const char* filename, bool restart_music = false, int music = 0);
int common_scroll_down_pressed(int *start, int size, int max_show);
int common_scroll_up_pressed(int *start, int size, int max_show);
//////////////////////////////////////////////////////////////////////////////////////
// NEWSTUFF BEGIN
//////////////////////////////////////////////////////////////////////////////////////
#define MAX_WING_SLOTS 4
#define MAX_WING_BLOCKS 3
#define MAX_WSS_SLOTS (MAX_WING_BLOCKS*MAX_WING_SLOTS)
#define WING_SLOT_FILLED (1<<0)
#define WING_SLOT_EMPTY (1<<1)
#define WING_SLOT_IS_PLAYER (1<<3)
#define WING_SLOT_LOCKED (1<<4)
#define WING_SLOT_SHIPS_DISABLED (1<<5)
#define WING_SLOT_WEAPONS_DISABLED (1<<6)
#define WING_SLOT_DISABLED (WING_SLOT_SHIPS_DISABLED|WING_SLOT_WEAPONS_DISABLED)
#define WING_SLOT_IGNORE_SHIPS (WING_SLOT_SHIPS_DISABLED|WING_SLOT_LOCKED)
#define WING_SLOT_IGNORE_WEAPONS (WING_SLOT_WEAPONS_DISABLED|WING_SLOT_LOCKED)
// different operations used in xx_apply()
#define WSS_DUMP_TO_LIST 0
#define WSS_GRAB_FROM_LIST 1
#define WSS_SWAP_SLOT_SLOT 2
#define WSS_SWAP_LIST_SLOT 3
// icons
#define NUM_ICON_FRAMES 6
#define ICON_FRAME_NORMAL 0
#define ICON_FRAME_HOT 1
#define ICON_FRAME_SELECTED 2
#define ICON_FRAME_PLAYER 3
#define ICON_FRAME_DISABLED 4
#define ICON_FRAME_DISABLED_HIGH 5
//Colors
extern color Icon_colors[NUM_ICON_FRAMES];
extern shader Icon_shaders[NUM_ICON_FRAMES];
//////////////////////////////////////////////
// Slots
//////////////////////////////////////////////
typedef struct wss_unit {
int ship_class;
int wep[MAX_SHIP_WEAPONS];
int wep_count[MAX_SHIP_WEAPONS];
} wss_unit;
extern wss_unit Wss_slots_teams[MAX_TVT_TEAMS][MAX_WSS_SLOTS];
extern wss_unit *Wss_slots;
extern int Wss_num_wings; // number of player wings
extern int Wss_num_wings_teams[MAX_TVT_TEAMS];
//////////////////////////////////////////////
// Weapon pool
//////////////////////////////////////////////
// weapon class index -> count remaining; an absent entry means the class is not in this mission's loadout
extern SCP_map<int, int> Wl_pool_teams[MAX_TVT_TEAMS];
extern SCP_map<int, int> *Wl_pool;
//////////////////////////////////////////////
// Ship pool
//////////////////////////////////////////////
// ship class index -> count remaining; an absent entry means the class is not in this mission's loadout
extern SCP_map<int, int> Ss_pool_teams[MAX_TVT_TEAMS];
extern SCP_map<int, int> *Ss_pool;
//////////////////////////////////////////////
// Saving loadout
//////////////////////////////////////////////
typedef struct loadout_data
{
char filename[MAX_FILENAME_LEN]; // mission filename
char last_modified[DATE_TIME_LENGTH]; // when mission was last modified
wss_unit unit_data[MAX_WSS_SLOTS]; // ship and weapon data
SCP_map<int, int> weapon_pool; // available weapons: class index -> count; absent serialized as 0
SCP_map<int, int> ship_pool; // available ships: class index -> count; absent serialized as -1 (Ss_pool's not-in-loadout sentinel)
} loadout_data;
extern loadout_data Player_loadout;
struct select_effect_params {
int effect; // effect type (0 = none/rotate, 1 = FS1, 2 = FS2)
color fs2_grid_color; // color of the grid in FS2 effect
color fs2_scanline_color; // color of the scanlines in FS2 effect
int fs2_grid_density; // density of the grid in FS2 effect
color fs2_wireframe_color; // color of the model wireframe in FS2 effect
select_effect_params() : effect(2), fs2_grid_color(Default_fs2_effect_grid_color), fs2_scanline_color(Default_fs2_effect_scanline_color), fs2_grid_density(Default_fs2_effect_grid_density), fs2_wireframe_color(Default_fs2_effect_wireframe_color)
{
}
};
void wss_save_loadout();
void wss_maybe_restore_loadout();
void wss_direct_restore_loadout();
int wss_get_mode(int from_slot, int from_list, int to_slot, int to_list, int wl_ship_slot);
int store_wss_data(ubyte *data, const unsigned int max_size, interface_snd_id sound, int player_index);
int restore_wss_data(ubyte *data);
class ship_info;
struct weapon_info;
void draw_model_icon(int model_id, uint64_t flags, int x1, int x2, int y1, int y2, ship_info* sip = nullptr, weapon_info* wip = nullptr, float zoom_multiplier = 1.0f, int resize_mode = GR_RESIZE_FULL);
void draw_model_rotating(model_render_params *render_info, int ship_class, int model_id, int x1, int y1, int x2, int y2, float *rotation_buffer, const vec3d *closeup_pos=nullptr, float closeup_zoom = .65f, float rev_rate = REVOLUTION_RATE, uint64_t flags = MR_AUTOCENTER | MR_NO_FOGGING, int resize_mode=GR_RESIZE_FULL, select_effect_params effect_params = select_effect_params{});
void common_set_team_pointers(int team);
void common_reset_team_pointers();
void common_fire_stage_script_hook(int old_stage, int new_stage);
///////////////////////////////////////////////////////////
// NEWSTUFF END
///////////////////////////////////////////////////////////
#endif