mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 17:52:15 -10:00
* 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]>
2006 lines
59 KiB
C++
2006 lines
59 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 <climits> // this is need even when not building debug!!
|
|
#include <type_traits>
|
|
|
|
#include "anim/animplay.h"
|
|
#include "cutscene/cutscenes.h"
|
|
#include "cutscene/movie.h"
|
|
#include "gamehelp/contexthelp.h"
|
|
#include "gamesequence/gamesequence.h"
|
|
#include "gamesnd/eventmusic.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "globalincs/vmallocator.h"
|
|
#include "graphics/2d.h"
|
|
#include "graphics/color.h"
|
|
#include "graphics/light.h"
|
|
#include "graphics/matrix.h"
|
|
#include "graphics/shadows.h"
|
|
#include "hud/hudwingmanstatus.h"
|
|
#include "io/key.h"
|
|
#include "io/mouse.h"
|
|
#include "io/timer.h"
|
|
#include "lighting/lighting.h"
|
|
#include "lighting/lighting_profiles.h"
|
|
#include "missionui/chatbox.h"
|
|
#include "missionui/missionbrief.h"
|
|
#include "missionui/missionscreencommon.h"
|
|
#include "missionui/missionshipchoice.h"
|
|
#include "missionui/missionweaponchoice.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "model/modelrender.h"
|
|
#include "network/multi.h"
|
|
#include "network/multi_endgame.h"
|
|
#include "network/multimsgs.h"
|
|
#include "network/multiteamselect.h"
|
|
#include "network/multiutil.h"
|
|
#include "parse/sexp.h"
|
|
#include "popup/popup.h"
|
|
#include "render/3d.h"
|
|
#include "render/batching.h"
|
|
#include "scripting/global_hooks.h"
|
|
#include "scripting/scripting.h"
|
|
#include "ship/ship.h"
|
|
#include "ui/uidefs.h"
|
|
#include "weapon/weapon.h"
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// Game Globals
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
int Common_select_inited = 0;
|
|
|
|
// Dependent on when mouse button goes up
|
|
int Drop_icon_mflag, Drop_on_wing_mflag, Brief_mouse_up_flag;
|
|
|
|
int Mouse_down_last_frame = 0;
|
|
|
|
// Timers used to flash buttons after timeouts
|
|
#define MSC_FLASH_AFTER_TIME 60000 // time before flashing a button
|
|
#define MSC_FLASH_INTERVAL 200 // time between flashes
|
|
UI_TIMESTAMP Flash_timer; // timestamp used to start flashing
|
|
UI_TIMESTAMP Flash_toggle; // timestamp used to toggle flashing
|
|
int Flash_bright; // state of button to flash
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// Global to modulde
|
|
//////////////////////////////////////////////////////////////////
|
|
int Current_screen;
|
|
int Next_screen;
|
|
static int InterfacePaletteBitmap = -1; // PCX file that holds the interface palette
|
|
color Icon_colors[NUM_ICON_FRAMES];
|
|
shader Icon_shaders[NUM_ICON_FRAMES];
|
|
|
|
loadout_data Player_loadout; // what the ship and weapon loadout is... used since we want to use the
|
|
// same loadout if the mission is played again
|
|
|
|
wss_unit Wss_slots_teams[MAX_TVT_TEAMS][MAX_WSS_SLOTS];
|
|
SCP_map<int, int> Wl_pool_teams[MAX_TVT_TEAMS];
|
|
SCP_map<int, int> Ss_pool_teams[MAX_TVT_TEAMS];
|
|
int Wss_num_wings_teams[MAX_TVT_TEAMS];
|
|
|
|
wss_unit *Wss_slots = NULL;
|
|
SCP_map<int, int> *Wl_pool = nullptr;
|
|
SCP_map<int, int> *Ss_pool = nullptr;
|
|
int Wss_num_wings;
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// Externs
|
|
//////////////////////////////////////////////////////////////////
|
|
extern void ss_set_team_pointers(int team);
|
|
extern void ss_reset_team_pointers();
|
|
extern void wl_set_team_pointers(int team);
|
|
extern void wl_reset_team_pointers();
|
|
extern void ss_reset_selected_ship();
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// UI
|
|
//////////////////////////////////////////////////////////////////
|
|
UI_WINDOW *Active_ui_window;
|
|
|
|
brief_common_buttons Common_buttons[3][GR_NUM_RESOLUTIONS][NUM_COMMON_BUTTONS] = {
|
|
{ // UGH
|
|
{ // GR_640
|
|
brief_common_buttons("CB_00", 7, 3, 37, 7, 0),
|
|
brief_common_buttons("CB_01", 7, 19, 37, 23, 1),
|
|
brief_common_buttons("CB_02", 7, 35, 37, 39, 2),
|
|
brief_common_buttons("CB_05", 571, 425, 572, 413, 5),
|
|
brief_common_buttons("CB_06", 533, 425, 500, 440, 6),
|
|
brief_common_buttons("CB_07", 533, 455, 479, 464, 7),
|
|
},
|
|
{ // GR_1024
|
|
brief_common_buttons("2_CB_00", 12, 5, 59, 12, 0),
|
|
brief_common_buttons("2_CB_01", 12, 31, 59, 37, 1),
|
|
brief_common_buttons("2_CB_02", 12, 56, 59, 62, 2),
|
|
brief_common_buttons("2_CB_05", 914, 681, 937, 671, 5),
|
|
brief_common_buttons("2_CB_06", 854, 681, 822, 704, 6),
|
|
brief_common_buttons("2_CB_07", 854, 724, 800, 743, 7),
|
|
}
|
|
},
|
|
{ // UGH
|
|
{ // GR_640
|
|
brief_common_buttons("CB_00", 7, 3, 37, 7, 0),
|
|
brief_common_buttons("CB_01", 7, 19, 37, 23, 1),
|
|
brief_common_buttons("CB_02", 7, 35, 37, 39, 2),
|
|
brief_common_buttons("CB_05", 571, 425, 572, 413, 5),
|
|
brief_common_buttons("CB_06", 533, 425, 500, 440, 6),
|
|
brief_common_buttons("CB_07", 533, 455, 479, 464, 7),
|
|
},
|
|
{ // GR_1024
|
|
brief_common_buttons("2_CB_00", 12, 5, 59, 12, 0),
|
|
brief_common_buttons("2_CB_01", 12, 31, 59, 37, 1),
|
|
brief_common_buttons("2_CB_02", 12, 56, 59, 62, 2),
|
|
brief_common_buttons("2_CB_05", 914, 681, 937, 671, 5),
|
|
brief_common_buttons("2_CB_06", 854, 681, 822, 704, 6),
|
|
brief_common_buttons("2_CB_07", 854, 724, 800, 743, 7),
|
|
}
|
|
},
|
|
{ // UGH
|
|
{ // GR_640
|
|
brief_common_buttons("CB_00", 7, 3, 37, 7, 0),
|
|
brief_common_buttons("CB_01", 7, 19, 37, 23, 1),
|
|
brief_common_buttons("CB_02", 7, 35, 37, 39, 2),
|
|
brief_common_buttons("CB_05", 571, 425, 572, 413, 5),
|
|
brief_common_buttons("CB_06", 533, 425, 500, 440, 6),
|
|
brief_common_buttons("CB_07", 533, 455, 479, 464, 7),
|
|
},
|
|
{ // GR_1024
|
|
brief_common_buttons("2_CB_00", 12, 5, 59, 12, 0),
|
|
brief_common_buttons("2_CB_01", 12, 31, 59, 37, 1),
|
|
brief_common_buttons("2_CB_02", 12, 56, 59, 62, 2),
|
|
brief_common_buttons("2_CB_05", 914, 681, 937, 671, 5),
|
|
brief_common_buttons("2_CB_06", 854, 681, 822, 704, 6),
|
|
brief_common_buttons("2_CB_07", 854, 724, 800, 743, 7),
|
|
}
|
|
}
|
|
};
|
|
|
|
#define COMMON_BRIEFING_BUTTON 0
|
|
#define COMMON_SS_BUTTON 1
|
|
#define COMMON_WEAPON_BUTTON 2
|
|
#define COMMON_COMMIT_BUTTON 3
|
|
#define COMMON_HELP_BUTTON 4
|
|
#define COMMON_OPTIONS_BUTTON 5
|
|
|
|
int Background_playing; // Flag to indicate background animation is playing
|
|
static anim *Background_anim; // Ids for the anim data that is loaded
|
|
|
|
// value for which Team_data entry to use
|
|
int Common_team;
|
|
|
|
// Ids for the instance of the anim that is playing
|
|
//static anim_instance *Background_anim_instance;
|
|
|
|
int Wing_slot_empty_bitmap;
|
|
int Wing_slot_disabled_bitmap;
|
|
|
|
// prototypes
|
|
int wss_slots_all_empty();
|
|
|
|
// Display the no ships selected error
|
|
void common_show_no_ship_error()
|
|
{
|
|
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR( "At least one ship must be selected before proceeding to weapons loadout", 460));
|
|
}
|
|
|
|
// Check the status of the buttons common to the loadout screens
|
|
void common_check_buttons()
|
|
{
|
|
int i;
|
|
UI_BUTTON *b;
|
|
|
|
for ( i = 0; i < NUM_COMMON_BUTTONS; i++ ) {
|
|
b = &Common_buttons[Current_screen-1][gr_screen.res][i].button;
|
|
if ( b->pressed() ) {
|
|
|
|
common_button_do(i);
|
|
}
|
|
}
|
|
|
|
/*
|
|
// AL 11-23-97: let a joystick button press commit
|
|
if ( joy_down_count(0) || joy_down_count(1) ) {
|
|
Commit_pressed = 1;
|
|
}
|
|
*/
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// common_redraw_pressed_buttons()
|
|
//
|
|
// Redraw any common 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 common_redraw_pressed_buttons()
|
|
{
|
|
int i;
|
|
UI_BUTTON *b;
|
|
|
|
for ( i = 0; i < NUM_COMMON_BUTTONS; i++ ) {
|
|
b = &Common_buttons[Current_screen-1][gr_screen.res][i].button;
|
|
if ( b->button_down() ) {
|
|
b->draw_forced(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void common_buttons_maybe_reload(UI_WINDOW * /*ui_window*/)
|
|
{
|
|
UI_BUTTON *b;
|
|
int i;
|
|
|
|
for ( i = 0; i < NUM_COMMON_BUTTONS; i++ ) {
|
|
b = &Common_buttons[Current_screen-1][gr_screen.res][i].button;
|
|
b->set_bmaps(Common_buttons[Current_screen-1][gr_screen.res][i].filename);
|
|
}
|
|
}
|
|
|
|
void common_buttons_init(UI_WINDOW *ui_window)
|
|
{
|
|
UI_BUTTON *b;
|
|
int i;
|
|
|
|
for ( i = 0; i < NUM_COMMON_BUTTONS; i++ ) {
|
|
b = &Common_buttons[Current_screen-1][gr_screen.res][i].button;
|
|
b->create( ui_window, "", Common_buttons[Current_screen-1][gr_screen.res][i].x, Common_buttons[Current_screen-1][gr_screen.res][i].y, 60, 30, 0, 1);
|
|
// set up callback for when a mouse first goes over a button
|
|
b->set_highlight_action( common_play_highlight_sound );
|
|
b->set_bmaps(Common_buttons[Current_screen-1][gr_screen.res][i].filename);
|
|
b->link_hotspot(Common_buttons[Current_screen-1][gr_screen.res][i].hotspot);
|
|
}
|
|
|
|
// add some text
|
|
ui_window->add_XSTR("Briefing", 1504, Common_buttons[Current_screen-1][gr_screen.res][COMMON_BRIEFING_BUTTON].xt, Common_buttons[Current_screen-1][gr_screen.res][COMMON_BRIEFING_BUTTON].yt, &Common_buttons[Current_screen-1][gr_screen.res][COMMON_BRIEFING_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
ui_window->add_XSTR("Ship Selection", 1067, Common_buttons[Current_screen-1][gr_screen.res][COMMON_SS_BUTTON].xt, Common_buttons[Current_screen-1][gr_screen.res][COMMON_SS_BUTTON].yt, &Common_buttons[Current_screen-1][gr_screen.res][COMMON_SS_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
ui_window->add_XSTR("Weapon Loadout", 1068, Common_buttons[Current_screen-1][gr_screen.res][COMMON_WEAPON_BUTTON].xt, Common_buttons[Current_screen-1][gr_screen.res][COMMON_WEAPON_BUTTON].yt, &Common_buttons[Current_screen-1][gr_screen.res][COMMON_WEAPON_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
ui_window->add_XSTR("Commit", 1062, Common_buttons[Current_screen-1][gr_screen.res][COMMON_COMMIT_BUTTON].xt, Common_buttons[Current_screen-1][gr_screen.res][COMMON_COMMIT_BUTTON].yt, &Common_buttons[Current_screen-1][gr_screen.res][COMMON_COMMIT_BUTTON].button, UI_XSTR_COLOR_PINK);
|
|
ui_window->add_XSTR("Help", 928, Common_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].xt, Common_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].yt, &Common_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
ui_window->add_XSTR("Options", 1036, Common_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].xt, Common_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].yt, &Common_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
|
|
common_reset_buttons();
|
|
|
|
Common_buttons[Current_screen-1][gr_screen.res][COMMON_COMMIT_BUTTON].button.set_hotkey(KEY_CTRLED+KEY_ENTER);
|
|
Common_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].button.set_hotkey(KEY_F1);
|
|
Common_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].button.set_hotkey(KEY_F2);
|
|
|
|
// for scramble or training missions, disable the ship/weapon selection regions
|
|
if ( brief_only_allow_briefing() ) {
|
|
Common_buttons[Current_screen-1][gr_screen.res][COMMON_SS_REGION].button.disable();
|
|
Common_buttons[Current_screen-1][gr_screen.res][COMMON_WEAPON_REGION].button.disable();
|
|
if (Show_locked_status_scramble_missions) {
|
|
ui_window->add_XSTR("Ships/Weapons Are Locked For This Mission", 1882, Common_buttons[Current_screen-1][gr_screen.res][COMMON_WEAPON_BUTTON].xt, Common_buttons[Current_screen-1][gr_screen.res][COMMON_WEAPON_BUTTON].yt + 30, &Common_buttons[Current_screen-1][gr_screen.res][COMMON_WEAPON_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to load background bitmaps as appropriate
|
|
int mission_ui_background_load(const char *custom_background, const char *single_background, const char *multi_background)
|
|
{
|
|
int background_bitmap = -1;
|
|
|
|
if (custom_background && (*custom_background != '\0'))
|
|
{
|
|
background_bitmap = bm_load(custom_background);
|
|
if (background_bitmap < 0)
|
|
mprintf(("Failed to load custom background bitmap %s!\n", custom_background));
|
|
}
|
|
|
|
// if special background failed to load, or if no special background was supplied, load the standard bitmap
|
|
if (background_bitmap < 0)
|
|
{
|
|
if (multi_background && (Game_mode & GM_MULTIPLAYER))
|
|
background_bitmap = bm_load(multi_background);
|
|
else
|
|
background_bitmap = bm_load(single_background);
|
|
}
|
|
|
|
// return what we've got
|
|
return background_bitmap;
|
|
}
|
|
|
|
void set_active_ui(UI_WINDOW *ui_window)
|
|
{
|
|
Active_ui_window = ui_window;
|
|
}
|
|
|
|
const char *common_music_get_filename(int score_index)
|
|
{
|
|
if (Cmdline_freespace_no_music) {
|
|
return "";
|
|
}
|
|
|
|
Assertion(score_index >= 0 && score_index < NUM_SCORES, "Invalid score index %d.", score_index);
|
|
|
|
if (Mission_music[score_index] < 0) {
|
|
if (!Spooled_music.empty()) {
|
|
Mission_music[score_index] = 0;
|
|
nprintf(("Sound",
|
|
"No briefing music is selected, so play first briefing track: %s\n",
|
|
Spooled_music[Mission_music[score_index]].name));
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
return Spooled_music[Mission_music[score_index]].filename;
|
|
}
|
|
|
|
void common_music_init(int score_index)
|
|
{
|
|
const auto file_name = common_music_get_filename(score_index);
|
|
|
|
if (file_name[0] == '\0') {
|
|
return;
|
|
}
|
|
|
|
briefing_load_music(file_name);
|
|
// Use this id to trigger the start of music playing on the briefing screen
|
|
Briefing_music_begin_timestamp = ui_timestamp(BRIEFING_MUSIC_DELAY);
|
|
}
|
|
|
|
void common_music_do()
|
|
{
|
|
if ( Cmdline_freespace_no_music ) {
|
|
return;
|
|
}
|
|
|
|
// Use this id to trigger the start of music playing on the briefing screen
|
|
if ( ui_timestamp_elapsed(Briefing_music_begin_timestamp) ) {
|
|
Briefing_music_begin_timestamp = UI_TIMESTAMP::invalid();
|
|
briefing_start_music();
|
|
}
|
|
}
|
|
|
|
void common_music_close()
|
|
{
|
|
if ( Cmdline_freespace_no_music ) {
|
|
return;
|
|
}
|
|
|
|
if ( Spooled_music.empty() )
|
|
return;
|
|
|
|
briefing_stop_music(true);
|
|
}
|
|
|
|
int common_num_cutscenes_valid(int movie_type)
|
|
{
|
|
int num_valid_cutscenes = 0;
|
|
|
|
for (uint i = 0; i < The_mission.cutscenes.size(); i++) {
|
|
if (movie_type == The_mission.cutscenes[i].type) {
|
|
if (!eval_sexp( The_mission.cutscenes[i].formula )) {
|
|
continue;
|
|
}
|
|
|
|
num_valid_cutscenes++;
|
|
}
|
|
}
|
|
|
|
return num_valid_cutscenes;
|
|
}
|
|
|
|
void common_maybe_play_cutscene(int movie_type, bool restart_music, int music)
|
|
{
|
|
bool music_off = false;
|
|
|
|
for (uint i = 0; i < The_mission.cutscenes.size(); i++) {
|
|
if (movie_type == The_mission.cutscenes[i].type) {
|
|
if (!eval_sexp( The_mission.cutscenes[i].formula )) {
|
|
continue;
|
|
}
|
|
|
|
if ( strlen(The_mission.cutscenes[i].filename) ) {
|
|
common_music_close();
|
|
music_off = true;
|
|
movie::play(The_mission.cutscenes[i].filename); //Play the movie!
|
|
}
|
|
}
|
|
}
|
|
|
|
if (music_off && restart_music) {
|
|
common_music_init(music);
|
|
}
|
|
}
|
|
|
|
void common_play_cutscene(const char* filename, bool restart_music, int music)
|
|
{
|
|
bool music_off = false;
|
|
|
|
common_music_close();
|
|
music_off = true;
|
|
movie::play(filename); // Play the movie!
|
|
|
|
if (music_off && restart_music) {
|
|
common_music_init(music);
|
|
}
|
|
};
|
|
|
|
// function that sets the current palette to the interface palette. This function
|
|
// needs to be followed by common_free_interface_palette() to restore the game palette.
|
|
void common_set_interface_palette(const char *filename)
|
|
{
|
|
static char buf[MAX_FILENAME_LEN + 1] = {0};
|
|
|
|
if (!filename)
|
|
filename = NOX("palette01");
|
|
|
|
Assert(strlen(filename) <= MAX_FILENAME_LEN);
|
|
if ( (InterfacePaletteBitmap != -1) && !stricmp(filename, buf) )
|
|
return; // already set to this palette
|
|
|
|
strcpy_s(buf, filename);
|
|
|
|
// unload the interface bitmap from memory
|
|
if (InterfacePaletteBitmap != -1) {
|
|
bm_release(InterfacePaletteBitmap);
|
|
InterfacePaletteBitmap = -1;
|
|
}
|
|
|
|
// ugh - we don't need this anymore
|
|
/*
|
|
InterfacePaletteBitmap = bm_load(filename);
|
|
if (InterfacePaletteBitmap < 0) {
|
|
Error(LOCATION, "Could not load in \"%s\"!", filename);
|
|
}
|
|
*/
|
|
}
|
|
|
|
// release the interface palette .pcx file, and restore the game palette
|
|
void common_free_interface_palette()
|
|
{
|
|
// unload the interface bitmap from memory
|
|
if (InterfacePaletteBitmap != -1) {
|
|
bm_release(InterfacePaletteBitmap);
|
|
InterfacePaletteBitmap = -1;
|
|
}
|
|
}
|
|
|
|
// Init timers used for flashing buttons
|
|
void common_flash_button_init()
|
|
{
|
|
Flash_timer = ui_timestamp(MSC_FLASH_AFTER_TIME);
|
|
Flash_toggle = UI_TIMESTAMP::immediate();
|
|
Flash_bright = 0;
|
|
}
|
|
|
|
// determine if we should draw a button as bright
|
|
int common_flash_bright()
|
|
{
|
|
if ( ui_timestamp_elapsed(Flash_timer) ) {
|
|
if ( ui_timestamp_elapsed(Flash_toggle) ) {
|
|
Flash_toggle = ui_timestamp(MSC_FLASH_INTERVAL);
|
|
Flash_bright ^= 1;
|
|
}
|
|
}
|
|
|
|
return Flash_bright;
|
|
}
|
|
|
|
// set the necessary pointers
|
|
void common_set_team_pointers(int team)
|
|
{
|
|
Assert( (team >= 0) && (team < MAX_TVT_TEAMS) );
|
|
|
|
Wss_slots = Wss_slots_teams[team];
|
|
Ss_pool = &Ss_pool_teams[team];
|
|
Wl_pool = &Wl_pool_teams[team];
|
|
|
|
ss_set_team_pointers(team);
|
|
wl_set_team_pointers(team);
|
|
}
|
|
|
|
// reset the necessary pointers to defaults
|
|
void common_reset_team_pointers()
|
|
{
|
|
ss_reset_team_pointers();
|
|
wl_reset_team_pointers();
|
|
|
|
// these are done last so that we can make use of the Assert()'s in the above
|
|
// functions to make sure the screens are exited and this is safe
|
|
Wss_slots = NULL;
|
|
Ss_pool = nullptr;
|
|
Wl_pool = nullptr;
|
|
}
|
|
|
|
// common_select_init() will load in animations and bitmaps that are common to the
|
|
// briefing/ship select/weapon select screens. The global Common_select_inited is set
|
|
// after this function is called once, and is only cleared when common_select_close()
|
|
// is called. This prevents multiple loadings of animations/bitmaps.
|
|
//
|
|
// This function also sets the palette based on the file palette01.pcx
|
|
void common_select_init(bool API_Access)
|
|
{
|
|
if ( Common_select_inited ) {
|
|
nprintf(("Alan","common_select_init() returning without doing anything\n"));
|
|
return;
|
|
}
|
|
|
|
nprintf(("Alan","entering common_select_init()\n"));
|
|
|
|
// No anims are playing
|
|
Background_playing = 0;
|
|
Background_anim = NULL;
|
|
|
|
Current_screen = Next_screen = ON_BRIEFING_SELECT;
|
|
|
|
// load in the icons for the wing slots
|
|
load_wing_icons(NOX("iconwing01"));
|
|
|
|
Current_screen = Next_screen = ON_BRIEFING_SELECT;
|
|
|
|
Commit_pressed = 0;
|
|
|
|
Common_select_inited = 1;
|
|
|
|
// this handles the case where the player played a multiplayer game but now is in single player (in one instance
|
|
// of FreeSpace)
|
|
if(!(Game_mode & GM_MULTIPLAYER)){
|
|
chatbox_close();
|
|
}
|
|
|
|
// get the value of the team
|
|
Common_team = 0; // assume the first team -- we'll change this value if we need to
|
|
|
|
if ( (Game_mode & GM_MULTIPLAYER) && IS_MISSION_MULTI_TEAMS )
|
|
Common_team = Net_player->p_info.team;
|
|
|
|
common_set_team_pointers(Common_team);
|
|
|
|
ship_select_common_init(API_Access);
|
|
weapon_select_common_init(API_Access);
|
|
if (!API_Access) {
|
|
common_flash_button_init();
|
|
}
|
|
|
|
if ( Game_mode & GM_MULTIPLAYER ) {
|
|
multi_ts_common_init();
|
|
}
|
|
|
|
// restore loadout from Player_loadout if this is the same mission as the one previously played
|
|
if ( !(Game_mode & GM_MULTIPLAYER) ) {
|
|
if ( !stricmp(Player_loadout.filename, Game_current_mission_filename) ) {
|
|
wss_maybe_restore_loadout();
|
|
ss_synch_interface();
|
|
wl_synch_interface();
|
|
}
|
|
}
|
|
|
|
if (!API_Access) {
|
|
ss_reset_selected_ship();
|
|
|
|
Drop_icon_mflag = 0;
|
|
Drop_on_wing_mflag = 0;
|
|
}
|
|
|
|
// init colors
|
|
gr_init_alphacolor(&Icon_colors[ICON_FRAME_NORMAL], 32, 128, 128, 255);
|
|
gr_init_alphacolor(&Icon_colors[ICON_FRAME_HOT], 48, 160, 160, 255);
|
|
gr_init_alphacolor(&Icon_colors[ICON_FRAME_SELECTED], 64, 192, 192, 255);
|
|
gr_init_alphacolor(&Icon_colors[ICON_FRAME_PLAYER], 192, 128, 64, 255);
|
|
gr_init_alphacolor(&Icon_colors[ICON_FRAME_DISABLED], 175, 175, 175, 255);
|
|
gr_init_alphacolor(&Icon_colors[ICON_FRAME_DISABLED_HIGH], 100, 100, 100, 255);
|
|
// init shaders
|
|
gr_create_shader(&Icon_shaders[ICON_FRAME_NORMAL], 32, 128, 128, 255);
|
|
gr_create_shader(&Icon_shaders[ICON_FRAME_HOT], 48, 160, 160, 255);
|
|
gr_create_shader(&Icon_shaders[ICON_FRAME_SELECTED], 64, 192, 192, 255);
|
|
gr_create_shader(&Icon_shaders[ICON_FRAME_PLAYER], 192, 128, 64, 255);
|
|
gr_create_shader(&Icon_shaders[ICON_FRAME_DISABLED], 175, 175, 175, 255);
|
|
gr_create_shader(&Icon_shaders[ICON_FRAME_DISABLED_HIGH], 100, 100, 100, 255);
|
|
}
|
|
|
|
void common_reset_buttons()
|
|
{
|
|
int i;
|
|
UI_BUTTON *b;
|
|
|
|
for ( i = 0; i < NUM_COMMON_BUTTONS; i++ ) {
|
|
b = &Common_buttons[Current_screen-1][gr_screen.res][i].button;
|
|
b->reset_status();
|
|
}
|
|
|
|
switch(Current_screen) {
|
|
case ON_BRIEFING_SELECT:
|
|
Common_buttons[Current_screen-1][gr_screen.res][COMMON_BRIEFING_REGION].button.skip_first_highlight_callback();
|
|
break;
|
|
case ON_SHIP_SELECT:
|
|
Common_buttons[Current_screen-1][gr_screen.res][COMMON_SS_REGION].button.skip_first_highlight_callback();
|
|
break;
|
|
case ON_WEAPON_SELECT:
|
|
Common_buttons[Current_screen-1][gr_screen.res][COMMON_WEAPON_REGION].button.skip_first_highlight_callback();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// common_select_do() is called once per loop in the interface screens and is used
|
|
// for drawing and changing the common animations and blitting common bitmaps.
|
|
int common_select_do(float /*frametime*/)
|
|
{
|
|
int k, new_k;
|
|
|
|
|
|
if ( help_overlay_active(Briefing_overlay_id) || help_overlay_active(Ship_select_overlay_id) || help_overlay_active(Weapon_select_overlay_id) ) {
|
|
Common_buttons[0][gr_screen.res][COMMON_HELP_BUTTON].button.reset_status();
|
|
Common_buttons[1][gr_screen.res][COMMON_HELP_BUTTON].button.reset_status();
|
|
Common_buttons[2][gr_screen.res][COMMON_HELP_BUTTON].button.reset_status();
|
|
Active_ui_window->set_ignore_gadgets(1);
|
|
} else {
|
|
Active_ui_window->set_ignore_gadgets(0);
|
|
}
|
|
|
|
k = chatbox_process();
|
|
|
|
if ( Game_mode & GM_NORMAL ) {
|
|
new_k = Active_ui_window->process(k);
|
|
} else {
|
|
new_k = Active_ui_window->process(k, 0);
|
|
}
|
|
|
|
if ( (k > 0) || (new_k > 0) || B1_JUST_RELEASED ) {
|
|
if ( help_overlay_active(Briefing_overlay_id) || help_overlay_active(Ship_select_overlay_id) || help_overlay_active(Weapon_select_overlay_id) ) {
|
|
help_overlay_set_state(Briefing_overlay_id, gr_screen.res, 0);
|
|
help_overlay_set_state(Ship_select_overlay_id, gr_screen.res, 0);
|
|
help_overlay_set_state(Weapon_select_overlay_id, gr_screen.res, 0);
|
|
Active_ui_window->set_ignore_gadgets(0);
|
|
k = 0;
|
|
new_k = 0;
|
|
}
|
|
}
|
|
|
|
// test for mouse buttons, must be done after Active_ui_window->process()
|
|
// has been called to work properly
|
|
//
|
|
Drop_icon_mflag = 0;
|
|
Drop_on_wing_mflag = 0;
|
|
Brief_mouse_up_flag = 0;
|
|
Mouse_down_last_frame = 0;
|
|
|
|
// if the left mouse button was released...
|
|
if ( B1_RELEASED ) {
|
|
Drop_icon_mflag = 1;
|
|
Drop_on_wing_mflag = 1;
|
|
}
|
|
|
|
// if the left mouse button was pressed...
|
|
if ( B1_PRESSED ) {
|
|
Mouse_down_last_frame = 1;
|
|
}
|
|
|
|
// basically a "click", only check for the click here to avoid action-on-over on briefing map
|
|
if ( B1_JUST_PRESSED ) {
|
|
Brief_mouse_up_flag = 1;
|
|
}
|
|
|
|
// reset timers for flashing buttons if key pressed
|
|
if ( (k>0) || (new_k>0) ) {
|
|
common_flash_button_init();
|
|
}
|
|
|
|
common_music_do();
|
|
|
|
/*
|
|
if ( Background_playing ) {
|
|
|
|
if ( Background_anim_instance->frame_num == BUTTON_SLIDE_IN_FRAME ) {
|
|
gamesnd_play_iface(SND_BTN_SLIDE);
|
|
}
|
|
|
|
if ( Background_anim_instance->frame_num == Background_anim_instance->stop_at ) {
|
|
// Free up the big honking background animation, since we won't be playing it again
|
|
anim_release_render_instance(Background_anim_instance);
|
|
anim_free(Background_anim);
|
|
|
|
Background_playing = 0;
|
|
Current_screen = Next_screen = ON_BRIEFING_SELECT;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if ( Current_screen != Next_screen ) {
|
|
switch( Next_screen ) {
|
|
case ON_BRIEFING_SELECT:
|
|
gameseq_post_event( GS_EVENT_START_BRIEFING );
|
|
break;
|
|
|
|
case ON_SHIP_SELECT:
|
|
// go to the specialized multiplayer team/ship select screen
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
gameseq_post_event(GS_EVENT_TEAM_SELECT);
|
|
}
|
|
// go to the normal ship select screen
|
|
else {
|
|
gameseq_post_event(GS_EVENT_SHIP_SELECTION);
|
|
}
|
|
break;
|
|
|
|
case ON_WEAPON_SELECT:
|
|
if ( !wss_slots_all_empty() ) {
|
|
gameseq_post_event(GS_EVENT_WEAPON_SELECTION);
|
|
} else {
|
|
common_show_no_ship_error();
|
|
}
|
|
break;
|
|
} // end switch
|
|
}
|
|
|
|
return new_k;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
// common_render()
|
|
//
|
|
void common_render(float frametime)
|
|
{
|
|
if ( !Background_playing ) {
|
|
GR_MAYBE_CLEAR_RES(Brief_background_bitmap);
|
|
gr_set_bitmap(Brief_background_bitmap);
|
|
gr_bitmap(0, 0, GR_RESIZE_MENU);
|
|
}
|
|
|
|
anim_render_all(0, frametime);
|
|
anim_render_all(ON_SHIP_SELECT, frametime);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
// common_render_selected_screen_button()
|
|
//
|
|
// A very ugly piece of special purpose code. This is used to draw the pressed button
|
|
// frame for whatever stage of the briefing/ship select/weapons loadout we are on.
|
|
//
|
|
void common_render_selected_screen_button()
|
|
{
|
|
Common_buttons[Next_screen-1][gr_screen.res][Next_screen-1].button.draw_forced(2);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
// common_button_do() do the button action for the specified pressed button
|
|
//
|
|
void common_button_do(int i)
|
|
{
|
|
if ( i == COMMON_COMMIT_BUTTON ) {
|
|
Commit_pressed = 1;
|
|
return;
|
|
}
|
|
|
|
if ( Background_playing )
|
|
return;
|
|
|
|
switch ( i ) {
|
|
|
|
case COMMON_BRIEFING_BUTTON:
|
|
if ( Current_screen != ON_BRIEFING_SELECT ) {
|
|
gamesnd_play_iface(InterfaceSounds::SCREEN_MODE_PRESSED);
|
|
Next_screen = ON_BRIEFING_SELECT;
|
|
}
|
|
break;
|
|
|
|
case COMMON_WEAPON_BUTTON:
|
|
if ( Current_screen != ON_WEAPON_SELECT ) {
|
|
if ( !wss_slots_all_empty() ) {
|
|
gamesnd_play_iface(InterfaceSounds::SCREEN_MODE_PRESSED);
|
|
Next_screen = ON_WEAPON_SELECT;
|
|
} else {
|
|
common_show_no_ship_error();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case COMMON_SS_BUTTON:
|
|
if ( Current_screen != ON_SHIP_SELECT ) {
|
|
gamesnd_play_iface(InterfaceSounds::SCREEN_MODE_PRESSED);
|
|
Next_screen = ON_SHIP_SELECT;
|
|
}
|
|
break;
|
|
|
|
case COMMON_OPTIONS_BUTTON:
|
|
gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
|
|
gameseq_post_event( GS_EVENT_OPTIONS_MENU );
|
|
break;
|
|
|
|
case COMMON_HELP_BUTTON:
|
|
gamesnd_play_iface(InterfaceSounds::HELP_PRESSED);
|
|
launch_context_help();
|
|
break;
|
|
|
|
} // end switch
|
|
}
|
|
|
|
// common_check_keys() will check for keypresses common to all the interface screens.
|
|
void common_check_keys(int k)
|
|
{
|
|
switch (k) {
|
|
|
|
case KEY_ESC: {
|
|
|
|
if ( Current_screen == ON_BRIEFING_SELECT ) {
|
|
if ( brief_get_closeup_icon() != NULL ) {
|
|
brief_turn_off_closeup_icon();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// prompt the host of a multiplayer game
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
multi_quit_game(PROMPT_ALL);
|
|
} else {
|
|
// go through the single player quit process
|
|
// return to the main menu
|
|
/*
|
|
int return_to_menu, pf_flags;
|
|
pf_flags = PF_USE_AFFIRMATIVE_ICON|PF_USE_NEGATIVE_ICON;
|
|
return_to_menu = popup(pf_flags, 2, POPUP_NO, POPUP_YES, XSTR( "Do you want to return to the Main Hall?\n(Your campaign position will be saved)", -1));
|
|
if ( return_to_menu == 1 ) {
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
}
|
|
*/
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case KEY_CTRLED + KEY_ENTER:
|
|
Commit_pressed = 1;
|
|
break;
|
|
|
|
case KEY_B:
|
|
if ( Current_screen != ON_BRIEFING_SELECT && !Background_playing ) {
|
|
Next_screen = ON_BRIEFING_SELECT;
|
|
}
|
|
break;
|
|
|
|
case KEY_W:
|
|
if ( brief_only_allow_briefing() ) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
break;
|
|
}
|
|
|
|
if ( Current_screen != ON_WEAPON_SELECT && !Background_playing ) {
|
|
if ( !wss_slots_all_empty() ) {
|
|
Next_screen = ON_WEAPON_SELECT;
|
|
} else {
|
|
common_show_no_ship_error();
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case KEY_S:
|
|
|
|
if ( brief_only_allow_briefing() ) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
break;
|
|
}
|
|
|
|
if ( Current_screen != ON_SHIP_SELECT && !Background_playing ) {
|
|
Next_screen = ON_SHIP_SELECT;
|
|
}
|
|
|
|
break;
|
|
|
|
case KEY_SHIFTED+KEY_TAB:
|
|
|
|
if ( brief_only_allow_briefing() ) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
break;
|
|
}
|
|
|
|
if ( !Background_playing ) {
|
|
switch ( Current_screen ) {
|
|
case ON_BRIEFING_SELECT:
|
|
if ( !wss_slots_all_empty() ) {
|
|
Next_screen = ON_WEAPON_SELECT;
|
|
} else {
|
|
common_show_no_ship_error();
|
|
}
|
|
break;
|
|
|
|
case ON_SHIP_SELECT:
|
|
Next_screen = ON_BRIEFING_SELECT;
|
|
break;
|
|
|
|
case ON_WEAPON_SELECT:
|
|
Next_screen = ON_SHIP_SELECT;
|
|
break;
|
|
default:
|
|
Int3();
|
|
break;
|
|
} // end switch
|
|
}
|
|
|
|
break;
|
|
|
|
case KEY_TAB:
|
|
|
|
if ( brief_only_allow_briefing() ) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
break;
|
|
}
|
|
|
|
if ( !Background_playing ) {
|
|
switch ( Current_screen ) {
|
|
case ON_BRIEFING_SELECT:
|
|
Next_screen = ON_SHIP_SELECT;
|
|
break;
|
|
|
|
case ON_SHIP_SELECT:
|
|
if ( !wss_slots_all_empty() ) {
|
|
Next_screen = ON_WEAPON_SELECT;
|
|
} else {
|
|
common_show_no_ship_error();
|
|
}
|
|
break;
|
|
|
|
case ON_WEAPON_SELECT:
|
|
Next_screen = ON_BRIEFING_SELECT;
|
|
break;
|
|
default:
|
|
Int3();
|
|
break;
|
|
} // end switch
|
|
}
|
|
|
|
break;
|
|
|
|
case KEY_P:
|
|
if ( Anim_paused )
|
|
Anim_paused = 0;
|
|
else
|
|
Anim_paused = 1;
|
|
break;
|
|
} // end switch
|
|
}
|
|
|
|
// common_select_close() will release the memory for animations and bitmaps that
|
|
// were loaded in common_select_init(). This function will abort if the Common_select_inited
|
|
// flag is not set. The last thing common_select_close() does in clear the Common_select_inited
|
|
// flag.
|
|
//
|
|
// weapon_select_close() and ship_select_close() are both called, since common_select_close()
|
|
// is the function that is called the interface screens are finally exited.
|
|
void common_select_close()
|
|
{
|
|
if ( !Common_select_inited ) {
|
|
nprintf(("Alan","common_select_close() returning without doing anything\n"));
|
|
return;
|
|
}
|
|
|
|
nprintf(("Alan","entering common_select_close()\n"));
|
|
|
|
weapon_select_close();
|
|
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
multi_ts_close();
|
|
}
|
|
|
|
ship_select_close();
|
|
common_select_level_close();
|
|
brief_close();
|
|
|
|
common_free_interface_palette();
|
|
|
|
// release the bitmpas that were previously extracted from anim files
|
|
unload_wing_icons();
|
|
|
|
// Release any instances that may still exist
|
|
anim_release_all_instances();
|
|
|
|
// free the anim's that were loaded into memory
|
|
/*
|
|
if ( Background_anim ) {
|
|
anim_free(Background_anim);
|
|
Background_anim = NULL;
|
|
}
|
|
*/
|
|
|
|
common_music_close();
|
|
|
|
common_reset_team_pointers();
|
|
|
|
Common_select_inited = 0;
|
|
}
|
|
|
|
// Release the model and bitmap handles held by the select screens, whether or not the screens themselves
|
|
// were ever opened. Called from common_select_close() on a state change and from game_level_close(),
|
|
// because a scripted UI that overrides the state hooks never reaches the former, and by the next mission
|
|
// the model slots those handles name may belong to other models.
|
|
void common_select_level_close()
|
|
{
|
|
weapon_select_common_close();
|
|
ship_select_common_close();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// load_wing_icons() creates the bitmaps for wing icons
|
|
//
|
|
void load_wing_icons(const char *filename)
|
|
{
|
|
int first_frame, num_frames;
|
|
|
|
first_frame = bm_load_animation(filename, &num_frames);
|
|
if ( first_frame == -1 ) {
|
|
Error(LOCATION, "Could not load icons from %s\n", filename);
|
|
return;
|
|
}
|
|
|
|
Wing_slot_disabled_bitmap = first_frame;
|
|
Wing_slot_empty_bitmap = first_frame + 1;
|
|
// Wing_slot_player_empty_bitmap = first_frame + 2;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// common_scroll_up_pressed()
|
|
//
|
|
int common_scroll_up_pressed(int *start, int size, int max_show)
|
|
{
|
|
// check if we even need to scroll at all
|
|
if ( size <= max_show ) {
|
|
return 0;
|
|
}
|
|
|
|
if ( (size - *start) > max_show ) {
|
|
*start += 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// common_scroll_down_pressed()
|
|
//
|
|
int common_scroll_down_pressed(int *start, int size, int max_show)
|
|
{
|
|
// if the whole list fits on screen, the start offset must be 0;
|
|
// reset a stale non-zero offset so the up arrow doesn't get stuck
|
|
if ( size <= max_show ) {
|
|
if ( *start > 0 ) {
|
|
*start = 0;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if ( *start > 0 ) {
|
|
*start -= 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void common_fire_stage_script_hook(int old_stage, int new_stage)
|
|
{
|
|
if (scripting::hooks::OnBriefStage->isActive()) {
|
|
// call a scripting hook for switching stages
|
|
// note that we add 1 because Lua arrays are 1-based
|
|
scripting::hooks::OnBriefStage->run(
|
|
scripting::hook_param_list(
|
|
scripting::hook_param("OldStage", 'i', old_stage + 1),
|
|
scripting::hook_param("NewStage", 'i', new_stage + 1)
|
|
));
|
|
}
|
|
}
|
|
|
|
// NEWSTUFF BEGIN
|
|
|
|
// save ship selection loadout to the Player_loadout struct
|
|
void wss_save_loadout()
|
|
{
|
|
int i,j;
|
|
|
|
Assert( (Ss_pool != NULL) && (Wl_pool != NULL) && (Wss_slots != NULL) );
|
|
|
|
// save the ship pool
|
|
Player_loadout.ship_pool = *Ss_pool;
|
|
|
|
// save the weapons pool
|
|
Player_loadout.weapon_pool = *Wl_pool;
|
|
|
|
// save the ship class / weapons for each slot
|
|
for ( i = 0; i < MAX_WSS_SLOTS; i++ ) {
|
|
Player_loadout.unit_data[i].ship_class = Wss_slots[i].ship_class;
|
|
|
|
for ( j = 0; j < MAX_SHIP_WEAPONS; j++ ) {
|
|
Player_loadout.unit_data[i].wep[j] = Wss_slots[i].wep[j];
|
|
Player_loadout.unit_data[i].wep_count[j] = Wss_slots[i].wep_count[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
// restore ship/weapons loadout from the Player_loadout struct
|
|
void wss_maybe_restore_loadout()
|
|
{
|
|
int i,j;
|
|
wss_unit *slot;
|
|
|
|
Assert( (Ss_pool != NULL) && (Wl_pool != NULL) && (Wss_slots != NULL) );
|
|
|
|
if (Disable_internal_loadout_restoration_system) {
|
|
return;
|
|
}
|
|
|
|
// only restore if mission hasn't changed
|
|
if ( stricmp(Player_loadout.last_modified, The_mission.modified) != 0 ) {
|
|
return;
|
|
}
|
|
|
|
// first we generate a pool of ships and weapons used the last time this mission was played. We also generate a pool of what is
|
|
// available in this mission.
|
|
SCP_vector<int> last_loadout_ships(ship_info_size(), 0);
|
|
SCP_vector<int> this_loadout_ships(ship_info_size(), 0);
|
|
|
|
SCP_vector<int> last_loadout_weapons(weapon_info_size(), 0);
|
|
SCP_vector<int> this_loadout_weapons(weapon_info_size(), 0);
|
|
|
|
// record the ship classes / weapons used last time
|
|
for ( i = 0; i < MAX_WSS_SLOTS; i++ ) {
|
|
slot = &Player_loadout.unit_data[i];
|
|
if ((slot->ship_class >= 0) && (slot->ship_class < ship_info_size())) {
|
|
++last_loadout_ships[slot->ship_class];
|
|
|
|
for ( j = 0; j < MAX_SHIP_WEAPONS; j++ ) {
|
|
if (Weapon_info.in_bounds(slot->wep[j])) {
|
|
last_loadout_weapons[slot->wep[j]] += slot->wep_count[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// record the ships classes / weapons used by the player and wingmen. We don't include the amount in the pools yet
|
|
for ( i = 0; i < MAX_WSS_SLOTS; i++ ) {
|
|
if ((Wss_slots[i].ship_class >= 0) && (Wss_slots[i].ship_class < ship_info_size())) {
|
|
++this_loadout_ships[Wss_slots[i].ship_class];
|
|
|
|
for ( j = 0; j < MAX_SHIP_WEAPONS; j++ ) {
|
|
if (Weapon_info.in_bounds(Wss_slots[i].wep[j])) {
|
|
this_loadout_weapons[Wss_slots[i].wep[j]] += Wss_slots[i].wep_count[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// now compare the two, adding in what was left in the pools. If there are less of a ship or weapon class in the mission now
|
|
// than there were last time, we can't restore and must abort.
|
|
for (const auto &[ship_class, count] : *Ss_pool) {
|
|
if (count >= 1) {
|
|
this_loadout_ships[ship_class] += count;
|
|
}
|
|
}
|
|
for (i = 0; i < ship_info_size(); i++) {
|
|
if ( this_loadout_ships[i] < last_loadout_ships[i]) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (const auto &[weapon_class, count] : *Wl_pool) {
|
|
if (count >= 1) {
|
|
this_loadout_weapons[weapon_class] += count;
|
|
}
|
|
}
|
|
for (i = 0; i < weapon_info_size(); i++) {
|
|
if ( this_loadout_weapons[i] < last_loadout_weapons[i]) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// go through the slots and restore the previous runthrough's loadout. Also remove that ship from total of ships in this mission
|
|
for ( i = 0; i < MAX_WSS_SLOTS; i++ ) {
|
|
slot = &Player_loadout.unit_data[i];
|
|
|
|
if (Ship_info.in_bounds(slot->ship_class)) {
|
|
--this_loadout_ships[slot->ship_class];
|
|
Assertion((this_loadout_ships[slot->ship_class] >= 0), "Attempting to restore the previous missions loadout has resulted in an invalid number of ships available");
|
|
|
|
}
|
|
// restore the ship class for each slot
|
|
Wss_slots[i].ship_class = slot->ship_class;
|
|
|
|
for ( j = 0; j < MAX_SHIP_WEAPONS; j++ ) {
|
|
if (Ship_info.in_bounds(slot->ship_class) && Weapon_info.in_bounds(slot->wep[j])) {
|
|
this_loadout_weapons[slot->wep[j]] -= slot->wep_count[j];
|
|
Assertion((this_loadout_weapons[slot->wep[j]] >= 0), "Attempting to restore the previous missions loadout has resulted in an invalid number of weapons available");
|
|
}
|
|
|
|
Wss_slots[i].wep[j]= slot->wep[j];
|
|
Wss_slots[i].wep_count[j] = slot->wep_count[j];
|
|
}
|
|
}
|
|
|
|
// restore the ship pool. Update counts for classes already in the loadout, then add any class the
|
|
// previous runthrough used that isn't in it (two passes so we don't iterate over mid-loop inserts).
|
|
for (auto &[ship_class, count] : *Ss_pool) {
|
|
count = this_loadout_ships[ship_class];
|
|
}
|
|
for ( i = 0; i < ship_info_size(); i++ ) {
|
|
if (this_loadout_ships[i] > 0 && !Ss_pool->contains(i)) {
|
|
(*Ss_pool)[i] = this_loadout_ships[i];
|
|
}
|
|
}
|
|
|
|
// restore the weapons pool
|
|
for (auto &[weapon_class, count] : *Wl_pool) {
|
|
count = this_loadout_weapons[weapon_class];
|
|
}
|
|
for ( i = 0; i < weapon_info_size(); i++ ) {
|
|
if (this_loadout_weapons[i] > 0 && !Wl_pool->contains(i)) {
|
|
(*Wl_pool)[i] = this_loadout_weapons[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do a direct restore of the Player_loadout ship/weapon data to the wings
|
|
void wss_direct_restore_loadout()
|
|
{
|
|
int i, j;
|
|
wing *wp;
|
|
wss_unit *slot;
|
|
|
|
// only restore if mission hasn't changed
|
|
if ( stricmp(Player_loadout.last_modified, The_mission.modified) != 0 ) {
|
|
return;
|
|
}
|
|
|
|
// niffiwan: if Starting_wings[] has missing wings, Player_loadout.unit_data
|
|
// skips the missing wings. Use a new variable to track the number of valid
|
|
// wings in Starting_wings[], otherwise mission can crash on restart
|
|
int valid_wing_index = -1;
|
|
|
|
for ( i = 0; i < MAX_WING_BLOCKS; i++ ) {
|
|
|
|
if ( Starting_wings[i] < 0 )
|
|
continue;
|
|
|
|
valid_wing_index++;
|
|
wp = &Wings[Starting_wings[i]];
|
|
|
|
// If this wing is still on the arrival list, then update the parse objects
|
|
if ( wp->ship_index[0] == -1 ) {
|
|
p_object *p_objp;
|
|
j=0;
|
|
for ( p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp) ) {
|
|
if ( p_objp->wingnum == WING_INDEX(wp) ) {
|
|
// niffiwan: don't overrun the array
|
|
if (j >= MAX_WING_SLOTS) {
|
|
Warning(LOCATION, "Starting Wing '%s' has more than 'MAX_WING_SLOTS' ships\n", Starting_wing_names[i]);
|
|
break;
|
|
}
|
|
slot = &Player_loadout.unit_data[valid_wing_index*MAX_WING_SLOTS+j];
|
|
p_objp->ship_class = slot->ship_class;
|
|
wl_update_parse_object_weapons(p_objp, slot);
|
|
j++;
|
|
}
|
|
}
|
|
} else {
|
|
int k;
|
|
int cleanup_ship_index[MAX_WING_SLOTS];
|
|
ship *shipp;
|
|
|
|
for ( k = 0; k < MAX_WING_SLOTS; k++ ) {
|
|
cleanup_ship_index[k] = -1;
|
|
}
|
|
|
|
// This wing is already created, so directly update the ships
|
|
for ( j = 0; j < MAX_WING_SLOTS; j++ ) {
|
|
if ( wp->ship_index[j] == -1 ) { // if this is an invalid ship, move on
|
|
continue;
|
|
}
|
|
|
|
slot = &Player_loadout.unit_data[valid_wing_index*MAX_WING_SLOTS+j];
|
|
shipp = &Ships[wp->ship_index[j]];
|
|
if ( shipp->ship_info_index != slot->ship_class ) {
|
|
|
|
if ( slot->ship_class == -1 ) {
|
|
cleanup_ship_index[j] = wp->ship_index[j];
|
|
ship_add_exited_ship( shipp, Ship::Exit_Flags::Player_deleted );
|
|
obj_delete(shipp->objnum);
|
|
hud_set_wingman_status_none( shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
|
|
continue;
|
|
} else {
|
|
change_ship_type(wp->ship_index[j], slot->ship_class);
|
|
}
|
|
}
|
|
wl_bash_ship_weapons(&Ships[wp->ship_index[j]].weapons, slot);
|
|
}
|
|
|
|
for ( k = 0; k < MAX_WING_SLOTS; k++ ) {
|
|
if ( cleanup_ship_index[k] != -1 ) {
|
|
ship_wing_cleanup( cleanup_ship_index[k], wp );
|
|
}
|
|
}
|
|
|
|
}
|
|
} // end for
|
|
}
|
|
int wss_slots_all_empty()
|
|
{
|
|
int i;
|
|
|
|
Assert( Wss_slots != NULL );
|
|
|
|
for ( i = 0; i < MAX_WSS_SLOTS; i++ ) {
|
|
if ( Wss_slots[i].ship_class >= 0 )
|
|
break;
|
|
}
|
|
|
|
if ( i == MAX_WSS_SLOTS )
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
// determine the mode (WSS_...) based on slot/list index values
|
|
int wss_get_mode(int from_slot, int from_list, int to_slot, int to_list, int wl_ship_slot)
|
|
{
|
|
int mode, to_slot_empty=0;
|
|
|
|
Assert( Wss_slots != NULL );
|
|
|
|
if ( wl_ship_slot >= 0 ) {
|
|
// weapons loadout
|
|
if ( to_slot >= 0 ) {
|
|
if ( Wss_slots[wl_ship_slot].wep_count[to_slot] == 0 ) {
|
|
to_slot_empty = 1;
|
|
}
|
|
}
|
|
} else {
|
|
// ship select
|
|
if ( to_slot >= 0 ) {
|
|
if ( Wss_slots[to_slot].ship_class == -1 ){
|
|
to_slot_empty = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// determine mode
|
|
if ( from_slot >= 0 && to_slot >= 0 ) {
|
|
mode = WSS_SWAP_SLOT_SLOT;
|
|
} else if ( from_slot >= 0 && to_list >= 0 ) {
|
|
mode = WSS_DUMP_TO_LIST;
|
|
} else if ( (from_list >= 0) && (to_slot >= 0) && (to_slot_empty) ) {
|
|
mode = WSS_GRAB_FROM_LIST;
|
|
} else if ( (from_list >= 0) && (to_slot >= 0) && (!to_slot_empty) ) {
|
|
mode = WSS_SWAP_LIST_SLOT;
|
|
} else {
|
|
mode = -1; // no changes required
|
|
}
|
|
|
|
return mode;
|
|
}
|
|
|
|
// store all the unit data and pool data
|
|
int store_wss_data(ubyte *data, __UNUSED const unsigned int max_size, interface_snd_id sound, int player_index)
|
|
{
|
|
int j, i, packet_size = 0;
|
|
ubyte val;
|
|
short player_id;
|
|
ushort pool_size;
|
|
|
|
// this function assumes that the data is going to be used over the network
|
|
// so make a non-network version of this function if needed
|
|
Assert( Game_mode & GM_MULTIPLAYER );
|
|
Assert( (Ss_pool != NULL) && (Wl_pool != NULL) && (Wss_slots != NULL) );
|
|
|
|
if ( !(Game_mode & GM_MULTIPLAYER) )
|
|
return 0;
|
|
|
|
// write the ship pool (only positive counts; the pool map can also hold exhausted 0-count entries)
|
|
pool_size = 0;
|
|
for (const auto &[ship_class, count] : *Ss_pool) {
|
|
if (count > 0) {
|
|
++pool_size;
|
|
}
|
|
}
|
|
|
|
ADD_USHORT(pool_size);
|
|
|
|
Assertion((((sizeof(short)+sizeof(short)) * pool_size) + packet_size) < max_size, "Size of ship pool exceeds max data size!");
|
|
|
|
for (const auto &[ship_class, count] : *Ss_pool) {
|
|
if (count > 0) {
|
|
ADD_SHORT(static_cast<short>(ship_class));
|
|
ADD_SHORT(static_cast<short>(count));
|
|
}
|
|
}
|
|
|
|
// write the weapon pool
|
|
pool_size = 0;
|
|
for (const auto &[weapon_class, count] : *Wl_pool) {
|
|
if (count > 0) {
|
|
++pool_size;
|
|
}
|
|
}
|
|
|
|
ADD_USHORT(pool_size);
|
|
|
|
Assertion((((sizeof(short)+sizeof(short)) * pool_size) + packet_size) < max_size, "Size of weapon pool exceeds max data size!");
|
|
|
|
for (const auto &[weapon_class, count] : *Wl_pool) {
|
|
if (count > 0) {
|
|
ADD_SHORT(static_cast<short>(weapon_class));
|
|
ADD_SHORT(static_cast<short>(count));
|
|
}
|
|
}
|
|
|
|
// write the unit data
|
|
val = MAX_WSS_SLOTS;
|
|
ADD_DATA(val);
|
|
val = MAX_SHIP_WEAPONS;
|
|
ADD_DATA(val);
|
|
|
|
Assertion((((sizeof(short) + ((sizeof(short)+sizeof(short)) * MAX_SHIP_WEAPONS)) * MAX_WSS_SLOTS) + packet_size) < max_size, "Size of wss data exceeds max data size!");
|
|
|
|
for (i = 0; i < MAX_WSS_SLOTS; i++) {
|
|
ADD_SHORT(static_cast<short>(Wss_slots[i].ship_class));
|
|
|
|
for (j = 0; j < MAX_SHIP_WEAPONS; j++) {
|
|
ADD_SHORT(static_cast<short>(Wss_slots[i].wep[j]));
|
|
Assert(Wss_slots[i].wep_count[j] < SHRT_MAX);
|
|
ADD_SHORT(static_cast<short>(Wss_slots[i].wep_count[j]));
|
|
}
|
|
}
|
|
|
|
// any sound index
|
|
ADD_SHORT(static_cast<short>(sound.value()));
|
|
|
|
// add a netplayer address to identify who should play the sound
|
|
player_id = -1;
|
|
|
|
if(player_index != -1){
|
|
player_id = Net_players[player_index].player_id;
|
|
}
|
|
|
|
ADD_SHORT(player_id);
|
|
|
|
Assert( packet_size < static_cast<int>(max_size) );
|
|
return packet_size;
|
|
}
|
|
|
|
int restore_wss_data(ubyte *data)
|
|
{
|
|
int i, j, offset = 0;
|
|
ubyte num_slots, num_weapons;
|
|
short b1, b2;
|
|
short player_id, sound;
|
|
ushort pool_size;
|
|
|
|
// this function assumes that the data is going to be used over the network
|
|
// so make a non-network version of this function if needed
|
|
Assert( Game_mode & GM_MULTIPLAYER );
|
|
Assert( (Ss_pool != NULL) && (Wl_pool != NULL) && (Wss_slots != NULL) );
|
|
|
|
if ( !(Game_mode & GM_MULTIPLAYER) )
|
|
return 0;
|
|
|
|
// restore ship pool
|
|
Ss_pool->clear();
|
|
GET_USHORT(pool_size);
|
|
|
|
for (i = 0; i < pool_size; i++) {
|
|
GET_SHORT(b1);
|
|
GET_SHORT(b2);
|
|
|
|
if (Ship_info.in_bounds(b1)) {
|
|
(*Ss_pool)[b1] = b2;
|
|
}
|
|
}
|
|
|
|
// restore weapons pool
|
|
Wl_pool->clear();
|
|
GET_USHORT(pool_size);
|
|
|
|
for (i = 0; i < pool_size; i++) {
|
|
GET_SHORT(b1);
|
|
GET_SHORT(b2);
|
|
|
|
if (Weapon_info.in_bounds(b1)) {
|
|
(*Wl_pool)[b1] = b2;
|
|
}
|
|
}
|
|
|
|
|
|
// restore unit data
|
|
for (i = 0; i < MAX_WSS_SLOTS; i++) {
|
|
Wss_slots[i].ship_class = -1;
|
|
|
|
for (j = 0; j < MAX_SHIP_WEAPONS; j++) {
|
|
Wss_slots[i].wep[j] = -1;
|
|
Wss_slots[i].wep_count[j] = 0;
|
|
}
|
|
}
|
|
|
|
GET_DATA(num_slots);
|
|
GET_DATA(num_weapons);
|
|
|
|
for (i = 0; i < num_slots; i++) {
|
|
GET_SHORT(b1);
|
|
|
|
if (i < MAX_WSS_SLOTS) {
|
|
Wss_slots[i].ship_class = b1;
|
|
}
|
|
|
|
for (j = 0; j < num_weapons; j++) {
|
|
GET_SHORT(b1);
|
|
GET_SHORT(b2);
|
|
|
|
if ( (i < MAX_WSS_SLOTS) && (j < MAX_SHIP_WEAPONS) ) {
|
|
Wss_slots[i].wep[j] = b1;
|
|
Wss_slots[i].wep_count[j] = b2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// read in the sound data
|
|
GET_SHORT(sound);
|
|
|
|
// read in the player address
|
|
GET_SHORT(player_id);
|
|
|
|
// determine if I'm the guy who should be playing the sound
|
|
if((Net_player != NULL) && (Net_player->player_id == player_id)){
|
|
// play the sound
|
|
if (sound >= 0) {
|
|
gamesnd_play_iface(static_cast<InterfaceSounds>(sound));
|
|
}
|
|
}
|
|
|
|
if(!(Game_mode & GM_MULTIPLAYER)){
|
|
ss_synch_interface();
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
void draw_model_icon(int model_id, uint64_t flags, int x, int y, int w, int h, ship_info* sip, weapon_info* wip, float zoom_multiplier, int resize_mode)
|
|
{
|
|
// Can't draw a non-model
|
|
if (model_id < 0)
|
|
return;
|
|
|
|
lighting_profiles::set_non_mission_profile non_mission_lighting_profile;
|
|
|
|
matrix object_orient = IDENTITY_MATRIX;
|
|
angles rot_angles = vmd_zero_angles;
|
|
|
|
if (sip == nullptr)
|
|
{
|
|
//Assume it's a weapon
|
|
rot_angles.h = -(PI_2);
|
|
}
|
|
else if(sip->model_icon_angles.p != 0.0f || sip->model_icon_angles.b != 0.0f || sip->model_icon_angles.h != 0.0f)
|
|
{
|
|
// If non-zero model_icon_angles exists, always use that
|
|
rot_angles = sip->model_icon_angles;
|
|
}
|
|
else if(sip->is_small_ship())
|
|
{
|
|
rot_angles.p = -(PI_2);
|
|
}
|
|
else if((sip->max_speed <= 0.0f) && !(sip->flags[Ship::Info_Flags::Cargo]))
|
|
{
|
|
//Probably an installation or Knossos
|
|
rot_angles.h = PI;
|
|
}
|
|
else
|
|
{
|
|
//Probably a capship
|
|
rot_angles.h = PI_2;
|
|
}
|
|
vm_angles_2_matrix(&object_orient, &rot_angles);
|
|
|
|
gr_set_clip(x, y, w, h, resize_mode);
|
|
g3_start_frame(1);
|
|
if (sip != nullptr)
|
|
{
|
|
const auto& closeup_pos = sip->icon_closeup_pos.value_or(sip->closeup_pos);
|
|
const auto closeup_zoom = sip->icon_closeup_zoom.value_or(sip->closeup_zoom);
|
|
const auto zoom = closeup_zoom * zoom_multiplier * 2.5f;
|
|
|
|
g3_set_view_matrix(&closeup_pos, &vmd_identity_matrix, zoom);
|
|
|
|
gr_set_proj_matrix(Proj_fov * 0.5f, gr_screen.clip_aspect, Min_draw_distance, Max_draw_distance);
|
|
}
|
|
else
|
|
{
|
|
Assertion(wip != nullptr, "Weapon is null, get a coder!");
|
|
|
|
polymodel *pm = model_get(model_id);
|
|
bsp_info *bs = NULL; //tehe
|
|
for(int i = 0; i < pm->n_models; i++)
|
|
{
|
|
if(!pm->submodel[i].flags[Model::Submodel_flags::Is_thruster])
|
|
{
|
|
bs = &pm->submodel[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(bs == NULL)
|
|
{
|
|
bs = &pm->submodel[0];
|
|
}
|
|
|
|
vec3d weap_closeup = wip->icon_closeup_pos.value_or(wip->closeup_pos);
|
|
float y_closeup;
|
|
float tm_zoom = wip->icon_closeup_zoom.value_or(wip->closeup_zoom) * zoom_multiplier;
|
|
|
|
if (!wip->icon_closeup_pos.has_value()) {
|
|
// Find the center of the submodel when no icon-specific position override is defined
|
|
weap_closeup.xyz.x = -(bs->min.xyz.z + (bs->max.xyz.z - bs->min.xyz.z) / 2.0f);
|
|
weap_closeup.xyz.y = -(bs->min.xyz.y + (bs->max.xyz.y - bs->min.xyz.y) / 2.0f);
|
|
weap_closeup.xyz.z = -(bs->rad / tanf(tm_zoom / 2.0f));
|
|
|
|
y_closeup = -(weap_closeup.xyz.y / tanf(tm_zoom / 2.0f));
|
|
if (y_closeup < weap_closeup.xyz.z) {
|
|
weap_closeup.xyz.z = y_closeup;
|
|
}
|
|
if (bs->min.xyz.x < weap_closeup.xyz.z) {
|
|
weap_closeup.xyz.z = bs->min.xyz.x;
|
|
}
|
|
}
|
|
g3_set_view_matrix( &weap_closeup, &vmd_identity_matrix, tm_zoom);
|
|
|
|
gr_set_proj_matrix(Proj_fov * 0.5f, gr_screen.clip_aspect, 0.05f, 1000.0f);
|
|
}
|
|
|
|
model_render_params render_info;
|
|
render_info.set_detail_level_lock(0);
|
|
|
|
gr_set_view_matrix(&Eye_position, &Eye_matrix);
|
|
|
|
if(!(flags & MR_NO_LIGHTING))
|
|
{
|
|
//setup lights
|
|
common_setup_room_lights();
|
|
}
|
|
|
|
if (sip != NULL && sip->replacement_textures.size() > 0)
|
|
{
|
|
render_info.set_replacement_textures(model_id, sip->replacement_textures);
|
|
}
|
|
|
|
Glowpoint_override = true;
|
|
model_clear_instance(model_id);
|
|
int model_instance = -1;
|
|
auto cache_result = model_get_cached_ui_render_instance(model_id, &model_instance);
|
|
// Only set up the instance when it was freshly created; the cached instance persists across frames.
|
|
if (sip != nullptr && cache_result == TriStateBool::TRUE_) {
|
|
model_set_up_techroom_instance(sip, model_instance);
|
|
}
|
|
|
|
render_info.set_flags(flags);
|
|
model_render_immediate(&render_info, model_id, model_instance, &object_orient, &vmd_zero_vector);
|
|
Glowpoint_override = false;
|
|
|
|
gr_end_view_matrix();
|
|
gr_end_proj_matrix();
|
|
|
|
g3_end_frame();
|
|
gr_reset_clip();
|
|
}
|
|
|
|
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, float closeup_zoom, float rev_rate, uint64_t flags, int resize_mode, select_effect_params effect_params)
|
|
{
|
|
//WMC - Can't draw a non-model
|
|
if (model_id < 0)
|
|
return;
|
|
|
|
int model_instance = -1;
|
|
auto cache_result = model_get_cached_ui_render_instance(model_id, &model_instance);
|
|
// Only set up the instance when it was freshly created; the cached instance persists across frames.
|
|
if (!(flags & MR_IS_MISSILE) && SCP_vector_inbounds(Ship_info, ship_class) && cache_result == TriStateBool::TRUE_) {
|
|
model_set_up_techroom_instance(&Ship_info[ship_class], model_instance);
|
|
}
|
|
|
|
lighting_profiles::set_non_mission_profile non_mission_lighting_profile;
|
|
|
|
float time = (timer_get_milliseconds()-anim_timer_start)/1000.0f;
|
|
angles rot_angles, view_angles;
|
|
matrix model_orient;
|
|
|
|
auto pm = model_get(model_id);
|
|
vec3d closeup_pos_default = { { { 0.0f, 0.0f, -(pm->rad * 1.5f) } } };
|
|
if (!closeup_pos || IS_VEC_NULL(closeup_pos))
|
|
closeup_pos = &closeup_pos_default;
|
|
|
|
const bool& shadow_disable_override = flags & MR_IS_MISSILE ? Shadow_disable_overrides.disable_mission_select_weapons : Shadow_disable_overrides.disable_mission_select_ships;
|
|
|
|
if (effect_params.effect == 2) { // FS2 Effect; Phase 0 Expand scanline, Phase 1 scan the grid and wireframe, Phase 2 scan up and reveal the ship, Phase 3 tilt the camera, Phase 4 start rotating the ship
|
|
// rotate the ship as much as required for this frame
|
|
if (time >= 3.6f) // Phase 4
|
|
*rotation_buffer += PI2 * flFrametime / rev_rate;
|
|
else
|
|
*rotation_buffer = PI; // No rotation before Phase 4
|
|
while (*rotation_buffer > PI2){
|
|
*rotation_buffer -= PI2;
|
|
}
|
|
|
|
view_angles.p = -PI_2;
|
|
|
|
if (time >= 3.0f) { // Phase 3
|
|
if (time >= 3.6f) { // done tilting
|
|
view_angles.p = -0.6f;
|
|
} else {
|
|
view_angles.p = (PI_2-0.6f)*(time-3.0f)*1.66667f - PI_2; // Phase 3 Tilt animation
|
|
}
|
|
}
|
|
|
|
view_angles.b = 0.0f;
|
|
view_angles.h = 0.0f;
|
|
vm_angles_2_matrix(&model_orient, &view_angles);
|
|
rot_angles.p = 0.0f;
|
|
rot_angles.b = 0.0f;
|
|
rot_angles.h = *rotation_buffer;
|
|
vm_rotate_matrix_by_angles(&model_orient, &rot_angles);
|
|
gr_set_clip(x1, y1, x2, y2, resize_mode);
|
|
vec3d wire_normal,ship_normal,plane_point;
|
|
|
|
// Clip the wireframe below the scanline
|
|
wire_normal.xyz.x = 0.0f;
|
|
wire_normal.xyz.y = 1.0f;
|
|
wire_normal.xyz.z = 0.0f;
|
|
|
|
// Clip the ship above the scanline
|
|
ship_normal.xyz.x = 0.0f;
|
|
ship_normal.xyz.y = -1.0f;
|
|
ship_normal.xyz.z = 0.0f;
|
|
|
|
//Make the clipping plane
|
|
float clip = -pm->rad*0.7f;
|
|
if (time < 1.5f && time >= 0.5f) // Phase 1 Move down
|
|
clip = pm->rad*(time-1.0f)*1.4f;
|
|
|
|
if (time >= 1.5f)
|
|
clip = pm->rad*(time-2.0f)*(-1.4f); // Phase 2 Move up
|
|
|
|
vm_vec_scale_sub(&plane_point,&vmd_zero_vector,&wire_normal,clip);
|
|
|
|
g3_start_frame(1);
|
|
g3_set_view_matrix(closeup_pos, &vmd_identity_matrix, closeup_zoom);
|
|
gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, Min_draw_distance, Max_draw_distance);
|
|
gr_set_view_matrix(&Eye_position, &Eye_matrix);
|
|
|
|
vec3d start, stop;
|
|
float size = pm->rad*0.7f;
|
|
float start_scale = MIN(time,0.5f)*2.5f;
|
|
float offset = size*0.5f*MIN(MAX(time-3.0f,0.0f),0.6f)*1.66667f;
|
|
|
|
g3_start_instance_angles(&vmd_zero_vector,&view_angles);
|
|
|
|
if (time < 0.5f) { // Do the expanding scanline in phase 0
|
|
gr_set_color(effect_params.fs2_scanline_color.red, effect_params.fs2_scanline_color.green, effect_params.fs2_scanline_color.blue);
|
|
start.xyz.x = size*start_scale;
|
|
start.xyz.y = 0.0f;
|
|
start.xyz.z = -clip;
|
|
stop.xyz.x = -size*start_scale;
|
|
stop.xyz.y = 0.0f;
|
|
stop.xyz.z = -clip;
|
|
//g3_draw_htl_line(&start,&stop);
|
|
g3_render_line_3d(true, &start, &stop);
|
|
}
|
|
g3_done_instance(true);
|
|
|
|
gr_zbuffer_set(GR_ZBUFF_NONE); // Turn off Depthbuffer so we don't get gridlines over the ship or a disappearing scanline
|
|
Glowpoint_use_depth_buffer = false; // Since we don't have one
|
|
if (time >= 0.5f) { // Phase 1 onward draw the grid
|
|
start.xyz.y = -offset;
|
|
start.xyz.z = size+offset*0.5f;
|
|
stop.xyz.y = -offset;
|
|
stop.xyz.z = -size+offset*0.5f;
|
|
gr_set_color(effect_params.fs2_grid_color.red, effect_params.fs2_grid_color.green, effect_params.fs2_grid_color.blue);
|
|
g3_start_instance_angles(&vmd_zero_vector,&view_angles);
|
|
|
|
if (time < 1.5f) {
|
|
stop.xyz.z = -clip;
|
|
}
|
|
|
|
int num_lines = std::max(3, effect_params.fs2_grid_density);
|
|
float x_step = (size * 2.0f) / (num_lines - 1);
|
|
float x_start = -size;
|
|
|
|
for (int i = 0; i < num_lines; ++i) {
|
|
start.xyz.x = stop.xyz.x = x_start + i * x_step;
|
|
g3_render_line_3d(false, &start, &stop);
|
|
}
|
|
|
|
start.xyz.x = size;
|
|
stop.xyz.x = -size;
|
|
|
|
float z_step = (size * 2.0f) / (num_lines - 1);
|
|
float z_start = size + offset * 0.5f;
|
|
|
|
for (int i = 0; i < num_lines; ++i) {
|
|
float z = z_start - i * z_step;
|
|
if ((time < 1.5f) && (z <= -clip))
|
|
break;
|
|
|
|
start.xyz.z = stop.xyz.z = z;
|
|
g3_render_line_3d(false, &start, &stop);
|
|
}
|
|
|
|
g3_done_instance(true);
|
|
|
|
//setup lights
|
|
common_setup_room_lights();
|
|
|
|
// render the ships
|
|
model_clear_instance(model_id);
|
|
render_info->set_detail_level_lock(0);
|
|
|
|
gr_zbuffer_set(true);
|
|
|
|
if(shadow_maybe_start_frame(shadow_disable_override))
|
|
{
|
|
gr_end_view_matrix();
|
|
gr_end_proj_matrix();
|
|
|
|
gr_reset_clip();
|
|
|
|
model_render_params shadow_render_info;
|
|
|
|
shadow_render_info.set_detail_level_lock(0);
|
|
shadow_render_info.set_flags(flags | MR_NO_TEXTURING | MR_NO_LIGHTING);
|
|
|
|
if ( flags & MR_IS_MISSILE ) {
|
|
shadows_start_render(&Eye_matrix, &Eye_position, Proj_fov, Proj_fov, gr_screen.clip_aspect, SCP_vector {{-closeup_pos->xyz.z + pm->rad, -closeup_pos->xyz.z + pm->rad + 20.0f, -closeup_pos->xyz.z + pm->rad + 200.0f, -closeup_pos->xyz.z + pm->rad + 1000.0f}});
|
|
} else {
|
|
shadows_start_render(&Eye_matrix, &Eye_position, Proj_fov, Proj_fov, gr_screen.clip_aspect, SCP_vector {{-closeup_pos->xyz.z + pm->rad, -closeup_pos->xyz.z + pm->rad + 200.0f, -closeup_pos->xyz.z + pm->rad + 2000.0f, -closeup_pos->xyz.z + pm->rad + 10000.0f}});
|
|
}
|
|
|
|
model_render_immediate(&shadow_render_info, model_id, model_instance, &model_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, &Eye_matrix);
|
|
}
|
|
gr_zbuffer_set(false);
|
|
gr_set_color(effect_params.fs2_wireframe_color.red, effect_params.fs2_wireframe_color.green, effect_params.fs2_wireframe_color.blue);
|
|
render_info->set_color(effect_params.fs2_wireframe_color.red, effect_params.fs2_wireframe_color.green, effect_params.fs2_wireframe_color.blue);
|
|
|
|
render_info->set_animated_effect(ANIMATED_SHADER_LOADOUTSELECT_FS2, -clip);
|
|
|
|
if ( (time < 2.5f) && (time >= 0.5f) ) { // Phase 1 and 2 render the wireframe
|
|
render_info->set_clip_plane(plane_point,wire_normal);
|
|
|
|
render_info->set_flags(flags | MR_SHOW_OUTLINE_HTL | MR_NO_POLYS | MR_NO_TEXTURING | MR_NO_LIGHTING);
|
|
|
|
model_render_immediate(render_info, model_id, model_instance, &model_orient, &vmd_zero_vector);
|
|
}
|
|
|
|
if (time >= 1.5f) { // Render the ship in Phase 2 onwards
|
|
render_info->set_clip_plane(plane_point,ship_normal);
|
|
render_info->set_flags(flags);
|
|
|
|
model_render_immediate(render_info, model_id, model_instance, &model_orient, &vmd_zero_vector);
|
|
}
|
|
|
|
if (time < 2.5f) { // Render the scanline in Phase 1 and 2
|
|
gr_set_color(effect_params.fs2_scanline_color.red, effect_params.fs2_scanline_color.green, effect_params.fs2_scanline_color.blue);
|
|
start.xyz.x = size*1.25f;
|
|
start.xyz.y = 0.0f;
|
|
start.xyz.z = -clip;
|
|
stop.xyz.x = -size*1.25f;
|
|
stop.xyz.y = 0.0f;
|
|
stop.xyz.z = -clip;
|
|
g3_start_instance_angles(&vmd_zero_vector,&view_angles);
|
|
//g3_draw_htl_line(&start,&stop);
|
|
g3_render_line_3d(false, &start, &stop);
|
|
g3_done_instance(true);
|
|
}
|
|
}
|
|
|
|
gr_zbuffer_set(GR_ZBUFF_FULL); // Turn off depthbuffer again
|
|
|
|
batching_render_all();
|
|
Glowpoint_use_depth_buffer = true; // Back to normal
|
|
|
|
gr_end_view_matrix();
|
|
gr_end_proj_matrix();
|
|
g3_end_frame();
|
|
gr_reset_clip();
|
|
|
|
} else {
|
|
// rotate the ship as much as required for this frame
|
|
*rotation_buffer += PI2 * flFrametime / rev_rate;
|
|
while (*rotation_buffer > PI2){
|
|
*rotation_buffer -= PI2;
|
|
}
|
|
|
|
view_angles.p = -0.6f;
|
|
view_angles.b = 0.0f;
|
|
view_angles.h = 0.0f;
|
|
vm_angles_2_matrix(&model_orient, &view_angles);
|
|
|
|
rot_angles.p = 0.0f;
|
|
rot_angles.b = 0.0f;
|
|
rot_angles.h = *rotation_buffer;
|
|
vm_rotate_matrix_by_angles(&model_orient, &rot_angles);
|
|
|
|
g3_start_frame(1);
|
|
|
|
// render the wodel
|
|
g3_set_view_matrix(closeup_pos, &vmd_identity_matrix, closeup_zoom);
|
|
|
|
//setup lights
|
|
common_setup_room_lights();
|
|
|
|
model_clear_instance(model_id);
|
|
|
|
render_info->set_detail_level_lock(0);
|
|
|
|
if(shadow_maybe_start_frame(shadow_disable_override))
|
|
{
|
|
if ( flags & MR_IS_MISSILE ) {
|
|
shadows_start_render(&Eye_matrix, &Eye_position, Proj_fov, Proj_fov, gr_screen.clip_aspect, SCP_vector {{-closeup_pos->xyz.z + pm->rad, -closeup_pos->xyz.z + pm->rad + 20.0f, -closeup_pos->xyz.z + pm->rad + 200.0f, -closeup_pos->xyz.z + pm->rad + 1000.0f}});
|
|
} else {
|
|
shadows_start_render(&Eye_matrix, &Eye_position, Proj_fov, Proj_fov, gr_screen.clip_aspect, SCP_vector {{-closeup_pos->xyz.z + pm->rad, -closeup_pos->xyz.z + pm->rad + 200.0f, -closeup_pos->xyz.z + pm->rad + 2000.0f, -closeup_pos->xyz.z + pm->rad + 10000.0f}});
|
|
}
|
|
|
|
model_render_params shadow_render_info;
|
|
|
|
shadow_render_info.set_flags(flags | MR_NO_TEXTURING | MR_NO_LIGHTING);
|
|
shadow_render_info.set_detail_level_lock(0);
|
|
|
|
model_render_immediate(&shadow_render_info, model_id, model_instance, &model_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, &Eye_matrix);
|
|
|
|
gr_set_color(0,128,0);
|
|
|
|
if (effect_params.effect == 1) { // FS1 effect
|
|
render_info->set_animated_effect(ANIMATED_SHADER_LOADOUTSELECT_FS1, MIN(time*0.5f,2.0f));
|
|
render_info->set_flags(flags);
|
|
} else {
|
|
render_info->set_flags(flags);
|
|
}
|
|
|
|
model_render_immediate(render_info, model_id, model_instance, &model_orient, &vmd_zero_vector);
|
|
|
|
batching_render_all();
|
|
|
|
gr_end_view_matrix();
|
|
gr_end_proj_matrix();
|
|
g3_end_frame();
|
|
gr_reset_clip();
|
|
}
|
|
|
|
shadow_end_frame();
|
|
}
|
|
|
|
/**
|
|
* @brief add lights for all the non-gameplay ship rendering instances
|
|
*/
|
|
void common_setup_room_lights()
|
|
{
|
|
light_reset();
|
|
auto tempv = vm_vec_new(-1.0f,0.3f,-1.0f);
|
|
auto tempc = hdr_color(1.0f,0.95f,0.9f, 0.0f, 1.5f);
|
|
light_add_directional(&tempv,-1,false,&tempc);
|
|
tempv.xyz={-0.4f,0.4f,1.1f};
|
|
tempc = hdr_color(0.788f,0.886f,1.0f,0.0f,1.5f);
|
|
light_add_directional(&tempv,-1,false,&tempc);
|
|
tempv.xyz={0.4f,0.1f,0.4f};
|
|
tempc = hdr_color(1.0f,1.0f,1.0f,0.0f,0.4f);
|
|
light_add_directional(&tempv,-1,false,&tempc);
|
|
gr_set_ambient_light(53, 53, 53);
|
|
}
|
|
|
|
// NEWSTUFF END
|