mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 04:49:39 -10:00
Clear red alert data when resetting campaign progress, changing campaigns and switching pilots In CSG read, move RA data clear from the read RA section to the data reset function (i.e. clear if CSG is valid, not if section contains data) git-svn-id: svn://svn.icculus.org/fs2open/trunk/fs2_open@11007 387891d4-d844-0410-90c0-e4c51a9137d3
1110 lines
31 KiB
C++
1110 lines
31 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.
|
|
*
|
|
*/
|
|
|
|
|
|
#define REDALERT_INTERNAL
|
|
#include "missionui/redalert.h"
|
|
#include "model/model.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "gamesequence/gamesequence.h"
|
|
#include "missionui/missionscreencommon.h"
|
|
#include "missionui/missionweaponchoice.h"
|
|
#include "io/key.h"
|
|
#include "graphics/font.h"
|
|
#include "mission/missionbriefcommon.h"
|
|
#include "io/timer.h"
|
|
#include "mission/missioncampaign.h"
|
|
#include "mission/missiongoals.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "hud/hudwingmanstatus.h"
|
|
#include "sound/audiostr.h"
|
|
#include "freespace2/freespace.h"
|
|
#include "globalincs/alphacolors.h"
|
|
#include "sound/fsspeech.h"
|
|
#include "ship/ship.h"
|
|
#include "weapon/weapon.h"
|
|
#include "cfile/cfile.h"
|
|
#include "io/mouse.h"
|
|
#include "ai/aigoals.h"
|
|
#include "mod_table/mod_table.h"
|
|
|
|
#include <stdexcept>
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Red Alert Mission-Level
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static int Red_alert_new_mission_timestamp; // timestamp used to give user a little warning for red alerts
|
|
//static int Red_alert_num_slots_used = 0;
|
|
static int Red_alert_voice_started;
|
|
|
|
SCP_vector<red_alert_ship_status> Red_alert_wingman_status;
|
|
SCP_string Red_alert_precursor_mission;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Red Alert Interface
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
char *Red_alert_fname[GR_NUM_RESOLUTIONS] = {
|
|
"RedAlert",
|
|
"2_RedAlert"
|
|
};
|
|
|
|
char *Red_alert_mask[GR_NUM_RESOLUTIONS] = {
|
|
"RedAlert-m",
|
|
"2_RedAlert-m"
|
|
};
|
|
|
|
// font to use for "incoming transmission"
|
|
int Ra_flash_font[GR_NUM_RESOLUTIONS] = {
|
|
FONT2,
|
|
FONT2
|
|
};
|
|
|
|
int Ra_flash_y[GR_NUM_RESOLUTIONS] = {
|
|
140,
|
|
200
|
|
};
|
|
|
|
/*
|
|
static int Ra_flash_coords[GR_NUM_RESOLUTIONS][2] = {
|
|
{
|
|
61, 108 // GR_640
|
|
},
|
|
{
|
|
61, 108 // GR_1024
|
|
}
|
|
};
|
|
*/
|
|
|
|
#define NUM_BUTTONS 2
|
|
|
|
#define RA_REPLAY_MISSION 0
|
|
#define RA_CONTINUE 1
|
|
|
|
static ui_button_info Buttons[GR_NUM_RESOLUTIONS][NUM_BUTTONS] = {
|
|
{ // GR_640
|
|
ui_button_info("RAB_00", 2, 445, -1, -1, 0),
|
|
ui_button_info("RAB_01", 575, 432, -1, -1, 1),
|
|
},
|
|
{ // GR_1024
|
|
ui_button_info("2_RAB_00", 4, 712, -1, -1, 0),
|
|
ui_button_info("2_RAB_01", 920, 691, -1, -1, 1),
|
|
}
|
|
};
|
|
|
|
#define RED_ALERT_NUM_TEXT 3
|
|
UI_XSTR Red_alert_text[GR_NUM_RESOLUTIONS][RED_ALERT_NUM_TEXT] = {
|
|
{ // GR_640
|
|
{ "Replay", 1405, 46, 451, UI_XSTR_COLOR_PINK, -1, &Buttons[0][RA_REPLAY_MISSION].button },
|
|
{ "Previous Mission", 1452, 46, 462, UI_XSTR_COLOR_PINK, -1, &Buttons[0][RA_REPLAY_MISSION].button },
|
|
{ "Continue", 1069, 564, 413, UI_XSTR_COLOR_PINK, -1, &Buttons[0][RA_CONTINUE].button },
|
|
},
|
|
{ // GR_1024
|
|
{ "Replay", 1405, 75, 722, UI_XSTR_COLOR_PINK, -1, &Buttons[1][RA_REPLAY_MISSION].button },
|
|
{ "Previous Mission", 1452, 75, 733, UI_XSTR_COLOR_PINK, -1, &Buttons[1][RA_REPLAY_MISSION].button },
|
|
{ "Continue", 1069, 902, 661, UI_XSTR_COLOR_PINK, -1, &Buttons[1][RA_CONTINUE].button },
|
|
}
|
|
};
|
|
|
|
// indicies for coordinates
|
|
#define RA_X_COORD 0
|
|
#define RA_Y_COORD 1
|
|
#define RA_W_COORD 2
|
|
#define RA_H_COORD 3
|
|
|
|
|
|
static int Text_delay;
|
|
|
|
int Ra_brief_text_wnd_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{
|
|
14, 151, 522, 289
|
|
},
|
|
{
|
|
52, 241, 785, 463
|
|
}
|
|
};
|
|
|
|
static UI_WINDOW Ui_window;
|
|
// static hud_anim Flash_anim;
|
|
static int Background_bitmap;
|
|
static int Red_alert_inited = 0;
|
|
|
|
static int Red_alert_voice;
|
|
|
|
// open and pre-load the stream buffers for the different voice streams
|
|
void red_alert_voice_load()
|
|
{
|
|
Assert( Briefing != NULL );
|
|
if ( strnicmp(Briefing->stages[0].voice, NOX("none"), 4) && (Briefing->stages[0].voice[0] != '\0') ) {
|
|
Red_alert_voice = audiostream_open( Briefing->stages[0].voice, ASF_VOICE );
|
|
}
|
|
}
|
|
|
|
// close all the briefing voice streams
|
|
void red_alert_voice_unload()
|
|
{
|
|
if ( Red_alert_voice != -1 ) {
|
|
audiostream_close_file(Red_alert_voice, 0);
|
|
Red_alert_voice = -1;
|
|
}
|
|
}
|
|
|
|
// start playback of the red alert voice
|
|
void red_alert_voice_play()
|
|
{
|
|
if ( !Briefing_voice_enabled ) {
|
|
return;
|
|
}
|
|
|
|
if ( Red_alert_voice < 0 ) {
|
|
// play simulated speech?
|
|
if (fsspeech_play_from(FSSPEECH_FROM_BRIEFING)) {
|
|
if (fsspeech_playing()) {
|
|
return;
|
|
}
|
|
|
|
fsspeech_play(FSSPEECH_FROM_BRIEFING, Briefing->stages[0].text.c_str());
|
|
Red_alert_voice_started = 1;
|
|
}
|
|
} else {
|
|
if (audiostream_is_playing(Red_alert_voice)) {
|
|
return;
|
|
}
|
|
|
|
audiostream_play(Red_alert_voice, Master_voice_volume, 0);
|
|
Red_alert_voice_started = 1;
|
|
}
|
|
}
|
|
|
|
// stop playback of the red alert voice
|
|
void red_alert_voice_stop()
|
|
{
|
|
if ( !Red_alert_voice_started )
|
|
return;
|
|
|
|
if (Red_alert_voice < 0) {
|
|
fsspeech_stop();
|
|
} else {
|
|
audiostream_stop(Red_alert_voice, 1, 0); // stream is automatically rewound
|
|
}
|
|
}
|
|
|
|
// pausing and unpausing of red alert voice
|
|
void red_alert_voice_pause()
|
|
{
|
|
if ( !Red_alert_voice_started )
|
|
return;
|
|
|
|
if (Red_alert_voice < 0) {
|
|
fsspeech_pause(true);
|
|
} else {
|
|
audiostream_pause(Red_alert_voice);
|
|
}
|
|
}
|
|
|
|
void red_alert_voice_unpause()
|
|
{
|
|
if ( !Red_alert_voice_started )
|
|
return;
|
|
|
|
if (Red_alert_voice < 0) {
|
|
fsspeech_pause(false);
|
|
} else {
|
|
audiostream_unpause(Red_alert_voice);
|
|
}
|
|
}
|
|
|
|
// a button was pressed, deal with it
|
|
void red_alert_button_pressed(int n)
|
|
{
|
|
switch (n) {
|
|
case RA_CONTINUE:
|
|
// warp the mouse cursor the the middle of the screen for those who control with a mouse
|
|
mouse_set_pos( gr_screen.max_w/2, gr_screen.max_h/2 );
|
|
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
// process the initial orders now (moved from post_process_mission()in missionparse)
|
|
mission_parse_fixup_players();
|
|
ai_post_process_mission();
|
|
}
|
|
|
|
gameseq_post_event(GS_EVENT_ENTER_GAME);
|
|
break;
|
|
|
|
case RA_REPLAY_MISSION:
|
|
if ( Game_mode & GM_CAMPAIGN_MODE ) {
|
|
// TODO: make call to campaign code to set correct mission for loading
|
|
// mission_campaign_play_previous_mission(Red_alert_precursor_mission);
|
|
if ( !mission_campaign_previous_mission() ) {
|
|
gamesnd_play_iface(SND_GENERAL_FAIL);
|
|
break;
|
|
}
|
|
|
|
gameseq_post_event(GS_EVENT_START_GAME);
|
|
} else {
|
|
gamesnd_play_iface(SND_GENERAL_FAIL);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// blit "incoming transmission"
|
|
#define RA_FLASH_CYCLE 0.25f
|
|
float Ra_flash_time = 0.0f;
|
|
int Ra_flash_up = 0;
|
|
void red_alert_blit_title()
|
|
{
|
|
const char *str = XSTR("Incoming Transmission", 1406);
|
|
int w, h;
|
|
|
|
// get the string size
|
|
gr_set_font(Ra_flash_font[gr_screen.res]);
|
|
gr_get_string_size(&w, &h, str);
|
|
|
|
// set alpha color
|
|
color flash_color;
|
|
if(Ra_flash_up){
|
|
gr_init_alphacolor(&flash_color, (int)(255.0f * (Ra_flash_time / RA_FLASH_CYCLE)), 0, 0, 255);
|
|
} else {
|
|
gr_init_alphacolor(&flash_color, (int)(255.0f * (1.0f - (Ra_flash_time / RA_FLASH_CYCLE))), 0, 0, 255);
|
|
}
|
|
|
|
// draw
|
|
gr_set_color_fast(&flash_color);
|
|
gr_string(Ra_brief_text_wnd_coords[gr_screen.res][0] + ((Ra_brief_text_wnd_coords[gr_screen.res][2] - w) / 2), Ra_flash_y[gr_screen.res] - h - 5, str, GR_RESIZE_MENU);
|
|
gr_set_color_fast(&Color_normal);
|
|
|
|
// increment flash time
|
|
Ra_flash_time += flFrametime;
|
|
if(Ra_flash_time >= RA_FLASH_CYCLE){
|
|
Ra_flash_time = 0.0f;
|
|
Ra_flash_up = !Ra_flash_up;
|
|
}
|
|
|
|
// back to the original font
|
|
gr_set_font(FONT1);
|
|
}
|
|
|
|
// Called once when red alert interface is started
|
|
void red_alert_init()
|
|
{
|
|
int i;
|
|
ui_button_info *b;
|
|
|
|
if ( Red_alert_inited ) {
|
|
return;
|
|
}
|
|
|
|
Ui_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
|
|
Ui_window.set_mask_bmap(Red_alert_mask[gr_screen.res]);
|
|
|
|
for (i=0; i<NUM_BUTTONS; i++) {
|
|
b = &Buttons[gr_screen.res][i];
|
|
|
|
b->button.create(&Ui_window, "", b->x, b->y, 60, 30, 0, 1);
|
|
// set up callback for when a mouse first goes over a button
|
|
b->button.set_highlight_action(common_play_highlight_sound);
|
|
b->button.set_bmaps(b->filename);
|
|
b->button.link_hotspot(b->hotspot);
|
|
}
|
|
|
|
// all text
|
|
for(i=0; i<RED_ALERT_NUM_TEXT; i++){
|
|
Ui_window.add_XSTR(&Red_alert_text[gr_screen.res][i]);
|
|
}
|
|
|
|
// set up red alert hotkeys
|
|
Buttons[gr_screen.res][RA_CONTINUE].button.set_hotkey(KEY_CTRLED | KEY_ENTER);
|
|
|
|
// load in background image and flashing red alert animation
|
|
Background_bitmap = bm_load(Red_alert_fname[gr_screen.res]);
|
|
|
|
// hud_anim_init(&Flash_anim, Ra_flash_coords[gr_screen.res][RA_X_COORD], Ra_flash_coords[gr_screen.res][RA_Y_COORD], NOX("AlertFlash"));
|
|
// hud_anim_load(&Flash_anim);
|
|
|
|
Red_alert_voice = -1;
|
|
|
|
if ( !Briefing ) {
|
|
Briefing = &Briefings[0];
|
|
}
|
|
|
|
if ( Briefing->num_stages > 0 ) {
|
|
brief_color_text_init(Briefing->stages[0].text.c_str(), Ra_brief_text_wnd_coords[gr_screen.res][RA_W_COORD], default_redalert_briefing_color, 0);
|
|
}
|
|
|
|
red_alert_voice_load();
|
|
|
|
// we have to reset/setup the shipselect and weaponselect pointers before moving on
|
|
ship_select_common_init();
|
|
weapon_select_common_init();
|
|
|
|
Text_delay = timestamp(200);
|
|
|
|
Red_alert_voice_started = 0;
|
|
Red_alert_inited = 1;
|
|
}
|
|
|
|
// Called once when the red alert interface is exited
|
|
void red_alert_close()
|
|
{
|
|
if (Red_alert_inited) {
|
|
|
|
red_alert_voice_stop();
|
|
red_alert_voice_unload();
|
|
|
|
weapon_select_close_team();
|
|
|
|
if (Background_bitmap >= 0) {
|
|
bm_release(Background_bitmap);
|
|
}
|
|
|
|
Ui_window.destroy();
|
|
// bm_unload(&Flash_anim);
|
|
common_free_interface_palette(); // restore game palette
|
|
game_flush();
|
|
}
|
|
|
|
Red_alert_inited = 0;
|
|
|
|
fsspeech_stop();
|
|
}
|
|
|
|
// called once per frame when game state is GS_STATE_RED_ALERT
|
|
void red_alert_do_frame(float frametime)
|
|
{
|
|
int i, k;
|
|
|
|
// ensure that the red alert interface has been initialized
|
|
if (!Red_alert_inited) {
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
// commit if skipping briefing, but not in multi - Goober5000
|
|
if (!(Game_mode & GM_MULTIPLAYER)) {
|
|
if (The_mission.flags & MISSION_FLAG_NO_BRIEFING)
|
|
{
|
|
red_alert_button_pressed(RA_CONTINUE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
k = Ui_window.process() & ~KEY_DEBUGGED;
|
|
switch (k) {
|
|
case KEY_ESC:
|
|
// gameseq_post_event(GS_EVENT_ENTER_GAME);
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
break;
|
|
} // end switch
|
|
|
|
for (i=0; i<NUM_BUTTONS; i++){
|
|
if (Buttons[gr_screen.res][i].button.pressed()){
|
|
red_alert_button_pressed(i);
|
|
}
|
|
}
|
|
|
|
GR_MAYBE_CLEAR_RES(Background_bitmap);
|
|
if (Background_bitmap >= 0) {
|
|
gr_set_bitmap(Background_bitmap);
|
|
gr_bitmap(0, 0, GR_RESIZE_MENU);
|
|
}
|
|
|
|
Ui_window.draw();
|
|
// hud_anim_render(&Flash_anim, frametime);
|
|
|
|
gr_set_font(FONT1);
|
|
|
|
if ( timestamp_elapsed(Text_delay) ) {
|
|
int finished_wipe = 0;
|
|
if ( Briefing->num_stages > 0 ) {
|
|
finished_wipe = brief_render_text(0, Ra_brief_text_wnd_coords[gr_screen.res][RA_X_COORD], Ra_brief_text_wnd_coords[gr_screen.res][RA_Y_COORD], Ra_brief_text_wnd_coords[gr_screen.res][RA_H_COORD], frametime, 0);
|
|
}
|
|
|
|
if (finished_wipe) {
|
|
red_alert_voice_play();
|
|
}
|
|
}
|
|
|
|
// blit incoming transmission
|
|
red_alert_blit_title();
|
|
|
|
gr_flip();
|
|
}
|
|
|
|
// set the red alert status for the current mission
|
|
void red_alert_invalidate_timestamp()
|
|
{
|
|
Red_alert_new_mission_timestamp = timestamp(-1); // make invalid
|
|
}
|
|
|
|
// Store a ships weapons into a wingman status structure
|
|
void red_alert_store_weapons(red_alert_ship_status *ras, ship_weapon *swp)
|
|
{
|
|
int i;
|
|
weapon_info *wip;
|
|
wep_t weapons;
|
|
|
|
// Make sure there isn't any data from the previous ship.
|
|
ras->primary_weapons.clear();
|
|
ras->secondary_weapons.clear();
|
|
|
|
if (swp == NULL) {
|
|
return;
|
|
}
|
|
|
|
// edited to accommodate ballistics - Goober5000
|
|
for (i = 0; i < swp->num_primary_banks; i++) {
|
|
weapons.index = swp->primary_bank_weapons[i];
|
|
|
|
if (weapons.index < 0) {
|
|
continue;
|
|
}
|
|
|
|
wip = &Weapon_info[weapons.index];
|
|
|
|
if (wip->wi_flags2 & WIF2_BALLISTIC) {
|
|
// to avoid triggering the below condition: this way, minimum ammo will be 2...
|
|
// since the red-alert representation of a conventional primary is 0 -> not used,
|
|
// 1 -> used, I added the representation 2 and above -> ballistic primary
|
|
weapons.count = swp->primary_bank_ammo[i] + 2;
|
|
} else {
|
|
weapons.count = 1;
|
|
}
|
|
|
|
ras->primary_weapons.push_back( weapons );
|
|
}
|
|
|
|
for (i = 0; i < swp->num_secondary_banks; i++) {
|
|
weapons.index = swp->secondary_bank_weapons[i];
|
|
|
|
if (weapons.index < 0) {
|
|
continue;
|
|
}
|
|
|
|
weapons.count = swp->secondary_bank_ammo[i];
|
|
|
|
ras->secondary_weapons.push_back( weapons );
|
|
}
|
|
}
|
|
|
|
// Take the weapons stored in a wingman_status struct, and bash them into the ship weapons struct
|
|
void red_alert_bash_weapons(red_alert_ship_status *ras, ship_weapon *swp)
|
|
{
|
|
int i, list_size = 0;
|
|
|
|
// restore from ship_exited
|
|
if ( (ras->ship_class == RED_ALERT_DESTROYED_SHIP_CLASS) || (ras->ship_class == RED_ALERT_PLAYER_DEL_SHIP_CLASS) ) {
|
|
return;
|
|
}
|
|
|
|
// modified to accommodate ballistics - Goober5000
|
|
list_size = (int)ras->primary_weapons.size();
|
|
CLAMP(list_size, 0, MAX_SHIP_PRIMARY_BANKS);
|
|
for (i = 0; i < list_size; i++) {
|
|
Assert( ras->primary_weapons[i].index >= 0 );
|
|
|
|
swp->primary_bank_weapons[i] = ras->primary_weapons[i].index;
|
|
swp->primary_bank_ammo[i] = ras->primary_weapons[i].count;
|
|
|
|
if (Weapon_info[swp->primary_bank_weapons[i]].wi_flags2 & WIF2_BALLISTIC) {
|
|
// adjust to correct ammo count, per red_alert_store_weapons()
|
|
swp->primary_bank_ammo[i] -= 2;
|
|
}
|
|
}
|
|
swp->num_primary_banks = list_size;
|
|
|
|
// bash secondary weapons
|
|
list_size = (int)ras->secondary_weapons.size();
|
|
CLAMP(list_size, 0, MAX_SHIP_SECONDARY_BANKS);
|
|
for (i = 0; i < list_size; i++) {
|
|
Assert( ras->secondary_weapons[i].index >= 0 );
|
|
|
|
swp->secondary_bank_weapons[i] = ras->secondary_weapons[i].index;
|
|
swp->secondary_bank_ammo[i] = ras->secondary_weapons[i].count;
|
|
}
|
|
swp->num_secondary_banks = list_size;
|
|
}
|
|
|
|
void red_alert_bash_weapons(red_alert_ship_status *ras, p_object *pobjp)
|
|
{
|
|
int i, list_size = 0;
|
|
ship_info *sip;
|
|
subsys_status *sssp = NULL;
|
|
|
|
// restore from ship_exited
|
|
if ( (ras->ship_class == RED_ALERT_DESTROYED_SHIP_CLASS) || (ras->ship_class == RED_ALERT_PLAYER_DEL_SHIP_CLASS) )
|
|
return;
|
|
sip = &Ship_info[ras->ship_class];
|
|
|
|
// parse objects use the "pilot" subsystem
|
|
for (i = 0; i < pobjp->subsys_count; i++)
|
|
{
|
|
if (!stricmp(Subsys_status[pobjp->subsys_index + i].name, "pilot"))
|
|
{
|
|
sssp = &Subsys_status[pobjp->subsys_index + i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sssp == NULL)
|
|
{
|
|
Warning(LOCATION, "Parse object data for ship '%s' doesn't contain the 'Pilot' subsystem!", pobjp->name);
|
|
return;
|
|
}
|
|
|
|
// bash primary weapons
|
|
list_size = (int)ras->primary_weapons.size();
|
|
CLAMP(list_size, 0, MAX_SHIP_PRIMARY_BANKS);
|
|
for (i = 0; i < list_size; i++)
|
|
{
|
|
Assert( ras->primary_weapons[i].index >= 0 );
|
|
sssp->primary_banks[i] = ras->primary_weapons[i].index;
|
|
|
|
if (Weapon_info[sssp->primary_banks[i]].wi_flags2 & WIF2_BALLISTIC)
|
|
{
|
|
float max_count = sip->primary_bank_ammo_capacity[i] / Weapon_info[sssp->primary_banks[i]].cargo_size;
|
|
sssp->primary_ammo[i] = fl2i(100.0f * (ras->primary_weapons[i].count - 2) / max_count + 0.5f);
|
|
}
|
|
else
|
|
sssp->primary_ammo[i] = 100;
|
|
}
|
|
|
|
// bash secondary weapons
|
|
list_size = (int)ras->secondary_weapons.size();
|
|
CLAMP(list_size, 0, MAX_SHIP_SECONDARY_BANKS);
|
|
for (i = 0; i < list_size; i++)
|
|
{
|
|
Assert( ras->secondary_weapons[i].index >= 0 );
|
|
sssp->secondary_banks[i] = ras->secondary_weapons[i].index;
|
|
|
|
float max_count = sip->secondary_bank_ammo_capacity[i] / Weapon_info[sssp->secondary_banks[i]].cargo_size;
|
|
sssp->secondary_ammo[i] = fl2i(100.0f * ras->secondary_weapons[i].count / max_count + 0.5f);
|
|
}
|
|
}
|
|
|
|
void red_alert_bash_subsys_status(red_alert_ship_status *ras, ship *shipp)
|
|
{
|
|
ship_subsys *ss;
|
|
int i, count = 0;
|
|
int list_size;
|
|
|
|
// restore from ship_exited
|
|
if ( (ras->ship_class == RED_ALERT_DESTROYED_SHIP_CLASS) || (ras->ship_class == RED_ALERT_PLAYER_DEL_SHIP_CLASS) ) {
|
|
return;
|
|
}
|
|
|
|
ss = GET_FIRST(&shipp->subsys_list);
|
|
while ( ss != END_OF_LIST( &shipp->subsys_list ) ) {
|
|
// using at() here for the bounds check, although out-of-bounds should
|
|
// probably never happen here
|
|
try {
|
|
ss->current_hits = ras->subsys_current_hits.at(count);
|
|
} catch (std::out_of_range range) {
|
|
break;
|
|
}
|
|
|
|
if (ss->current_hits <= 0) {
|
|
ss->submodel_info_1.blown_off = 1;
|
|
}
|
|
|
|
ss = GET_NEXT( ss );
|
|
count++;
|
|
}
|
|
|
|
list_size = (int)ras->subsys_aggregate_current_hits.size();
|
|
CLAMP(list_size, 0, SUBSYSTEM_MAX);
|
|
for (i = 0; i < list_size; i++) {
|
|
shipp->subsys_info[i].aggregate_current_hits = ras->subsys_aggregate_current_hits[i];
|
|
}
|
|
}
|
|
|
|
extern int insert_subsys_status(p_object *pobjp);
|
|
|
|
void red_alert_bash_subsys_status(red_alert_ship_status *ras, p_object *pobjp)
|
|
{
|
|
int i, j;
|
|
ship_info *sip;
|
|
model_subsystem *psub = NULL;
|
|
subsys_status *sssp = NULL;
|
|
|
|
// restore from ship_exited
|
|
if ( (ras->ship_class == RED_ALERT_DESTROYED_SHIP_CLASS) || (ras->ship_class == RED_ALERT_PLAYER_DEL_SHIP_CLASS) )
|
|
return;
|
|
sip = &Ship_info[ras->ship_class];
|
|
|
|
// do this differently than the other bash_subsys_status... since the p_object may not contain
|
|
// all the subsystems, iterate on the red_alert_ship_status entries rather than the p_object's
|
|
// and create missing subsystems where necessary
|
|
for (i = 0; i < (int) ras->subsys_current_hits.size(); i++)
|
|
{
|
|
psub = &sip->subsystems[i];
|
|
|
|
// in the p_object, subsystem 0 is the pilot, and afterwards the subsystems go in order
|
|
j = i + 1;
|
|
|
|
// this subsystem is in the p_object
|
|
if (j < pobjp->subsys_count)
|
|
{
|
|
sssp = &Subsys_status[pobjp->subsys_index + j];
|
|
}
|
|
// must create subsystem (same technique as in parse_copy_damage)
|
|
else
|
|
{
|
|
// jam in the new subsystem at the end of the existing list for this parse object
|
|
int new_idx = insert_subsys_status(pobjp);
|
|
Assert(new_idx == pobjp->subsys_index + j);
|
|
sssp = &Subsys_status[new_idx];
|
|
|
|
strcpy_s(sssp->name, psub->subobj_name);
|
|
}
|
|
|
|
float max_hits = psub->max_subsys_strength * (pobjp->ship_max_hull_strength / sip->max_hull_strength);
|
|
|
|
float current_hits = ras->subsys_current_hits[i];
|
|
|
|
sssp->percent = 100.0f * (max_hits - current_hits) / max_hits;
|
|
}
|
|
}
|
|
|
|
void red_alert_store_subsys_status(red_alert_ship_status *ras, ship *shipp)
|
|
{
|
|
ship_subsys *ss;
|
|
int i;
|
|
|
|
// Make sure there isn't any data from the previous ship.
|
|
ras->subsys_current_hits.clear();
|
|
ras->subsys_aggregate_current_hits.clear();
|
|
|
|
if (shipp == NULL) {
|
|
return;
|
|
}
|
|
|
|
ss = GET_FIRST(&shipp->subsys_list);
|
|
while ( ss != END_OF_LIST( &shipp->subsys_list ) ) {
|
|
ras->subsys_current_hits.push_back( ss->current_hits );
|
|
|
|
ss = GET_NEXT( ss );
|
|
}
|
|
|
|
for (i = 0; i < SUBSYSTEM_MAX; i++)
|
|
// Pyro3d - Fixes AP8 Based RA crashes
|
|
ras->subsys_aggregate_current_hits.push_back(shipp->subsys_info[i].aggregate_current_hits);
|
|
}
|
|
|
|
|
|
/*
|
|
* Record the current state of the players wingman & ships with the "red-alert-carry" flag
|
|
* Wingmen without the red-alert-carry flag are only stored if they survive
|
|
* dead wingmen must still be handled in red_alert_bash_wingman_status
|
|
*/
|
|
void red_alert_store_wingman_status()
|
|
{
|
|
ship *shipp;
|
|
red_alert_ship_status ras;
|
|
ship_obj *so;
|
|
object *ship_objp;
|
|
|
|
// store the mission filename for the red alert precursor mission
|
|
Red_alert_precursor_mission = Game_current_mission_filename;
|
|
|
|
// Pyro3d - Clear list of stored red alert ships
|
|
// Probably not the best solution, but it prevents an assertion in change_ship_type()
|
|
Red_alert_wingman_status.clear();
|
|
|
|
// store status for all existing ships
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
|
ship_objp = &Objects[so->objnum];
|
|
Assert(ship_objp->type == OBJ_SHIP);
|
|
shipp = &Ships[ship_objp->instance];
|
|
|
|
if ( shipp->flags & SF_DYING ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !(shipp->flags & SF_FROM_PLAYER_WING) && !(shipp->flags & SF_RED_ALERT_STORE_STATUS) ) {
|
|
continue;
|
|
}
|
|
|
|
ras.name = shipp->ship_name;
|
|
ras.hull = Objects[shipp->objnum].hull_strength;
|
|
ras.ship_class = shipp->ship_info_index;
|
|
red_alert_store_weapons(&ras, &shipp->weapons);
|
|
red_alert_store_subsys_status(&ras, shipp);
|
|
|
|
Red_alert_wingman_status.push_back( ras );
|
|
// niffiwan: trying to track down red alert bug creating HUGE pilot files
|
|
Assert( (Red_alert_wingman_status.size() <= MAX_SHIPS) );
|
|
}
|
|
|
|
// store exited ships that did not die
|
|
for (int idx=0; idx<(int)Ships_exited.size(); idx++) {
|
|
if (Ships_exited[idx].flags & SEF_RED_ALERT_CARRY) {
|
|
ras.name = Ships_exited[idx].ship_name;
|
|
ras.hull = float(Ships_exited[idx].hull_strength);
|
|
|
|
// if a ship has been destroyed or removed manually by the player, then mark it as such ...
|
|
if ( Ships_exited[idx].flags & SEF_DESTROYED ) {
|
|
ras.ship_class = RED_ALERT_DESTROYED_SHIP_CLASS;
|
|
}
|
|
else if (Ships_exited[idx].flags & SEF_PLAYER_DELETED) {
|
|
ras.ship_class = RED_ALERT_PLAYER_DEL_SHIP_CLASS;
|
|
}
|
|
// ... otherwise we want to make sure and carry over the ship class
|
|
else {
|
|
Assert( Ships_exited[idx].ship_class >= 0 );
|
|
ras.ship_class = Ships_exited[idx].ship_class;
|
|
}
|
|
|
|
red_alert_store_weapons(&ras, NULL);
|
|
red_alert_store_subsys_status(&ras, NULL);
|
|
|
|
Red_alert_wingman_status.push_back( ras );
|
|
// niffiwan: trying to track down red alert bug creating HUGE pilot files
|
|
Assert( (Red_alert_wingman_status.size() <= MAX_SHIPS) );
|
|
}
|
|
}
|
|
|
|
Assert( !Red_alert_wingman_status.empty() );
|
|
}
|
|
|
|
// Delete a ship in a red alert mission (since it must have died/departed in the previous mission)
|
|
void red_alert_delete_ship(ship *shipp, int ship_state)
|
|
{
|
|
if ( (shipp->wing_status_wing_index >= 0) && (shipp->wing_status_wing_pos >= 0) ) {
|
|
if (ship_state == RED_ALERT_DESTROYED_SHIP_CLASS) {
|
|
hud_set_wingman_status_dead(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
|
|
} else if (ship_state == RED_ALERT_PLAYER_DEL_SHIP_CLASS) {
|
|
hud_set_wingman_status_none(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
|
|
} else {
|
|
Error(LOCATION, "Red Alert: asked to delete ship (%s) with invalid ship state (%d)", shipp->ship_name, ship_state);
|
|
}
|
|
}
|
|
|
|
ship_add_exited_ship( shipp, SEF_PLAYER_DELETED );
|
|
obj_delete(shipp->objnum);
|
|
if ( shipp->wingnum >= 0 ) {
|
|
ship_wing_cleanup( shipp-Ships, &Wings[shipp->wingnum] );
|
|
}
|
|
}
|
|
|
|
// just mark the parse object as never going to arrive
|
|
void red_alert_delete_ship(p_object *pobjp, int ship_state)
|
|
{
|
|
if (ship_state == RED_ALERT_DESTROYED_SHIP_CLASS || ship_state == RED_ALERT_PLAYER_DEL_SHIP_CLASS)
|
|
{
|
|
pobjp->flags2 |= P2_RED_ALERT_DELETED;
|
|
|
|
if (pobjp->wingnum < 0)
|
|
pobjp->flags |= P_SF_CANNOT_ARRIVE;
|
|
}
|
|
else
|
|
Error(LOCATION, "Red Alert: asked to delete ship (%s) with invalid ship state (%d)", pobjp->name, ship_state);
|
|
}
|
|
|
|
/*
|
|
* Take the red alert status information, and adjust the red alert ships accordingly
|
|
* "red alert ships" are wingmen and any ship with the red-alert-carry flag
|
|
* Wingmen without red alert data still need to be handled / removed
|
|
*/
|
|
void red_alert_bash_wingman_status()
|
|
{
|
|
int j;
|
|
ship_obj *so;
|
|
|
|
SCP_vector<red_alert_ship_status>::iterator rasii;
|
|
SCP_vector<p_object>::iterator poii;
|
|
|
|
SCP_hash_map<int, int> Wing_pobjects_deleted;
|
|
SCP_hash_map<int, int>::iterator ii;
|
|
|
|
if ( !(Game_mode & GM_CAMPAIGN_MODE) ) {
|
|
return;
|
|
}
|
|
|
|
if ( Red_alert_wingman_status.empty() ) {
|
|
return;
|
|
}
|
|
|
|
// go through all ships in the game, and see if there is red alert status data for any
|
|
|
|
so = GET_FIRST(&Ship_obj_list);
|
|
for ( ; so != END_OF_LIST(&Ship_obj_list); )
|
|
{
|
|
object *ship_objp = &Objects[so->objnum];
|
|
Assert(ship_objp->type == OBJ_SHIP);
|
|
ship *shipp = &Ships[ship_objp->instance];
|
|
|
|
if ( !(shipp->flags & SF_FROM_PLAYER_WING) && !(shipp->flags & SF_RED_ALERT_STORE_STATUS) ) {
|
|
so = GET_NEXT(so);
|
|
continue;
|
|
}
|
|
|
|
bool ship_data_restored = false;
|
|
int ship_state = RED_ALERT_DESTROYED_SHIP_CLASS;
|
|
|
|
for ( rasii = Red_alert_wingman_status.begin(); rasii != Red_alert_wingman_status.end(); ++rasii )
|
|
{
|
|
red_alert_ship_status *ras = &(*rasii);
|
|
|
|
// red-alert data matches this ship!
|
|
if ( !stricmp(ras->name.c_str(), shipp->ship_name) )
|
|
{
|
|
// we only want to restore ships which haven't been destroyed, or were removed by the player
|
|
if ( (ras->ship_class != RED_ALERT_DESTROYED_SHIP_CLASS) && (ras->ship_class != RED_ALERT_PLAYER_DEL_SHIP_CLASS) )
|
|
{
|
|
// if necessary, restore correct ship class
|
|
if ( ras->ship_class != shipp->ship_info_index )
|
|
{
|
|
if (ras->ship_class >= 0 && ras->ship_class < MAX_SHIP_CLASSES)
|
|
change_ship_type(SHIP_INDEX(shipp), ras->ship_class);
|
|
else
|
|
mprintf(("Invalid ship class specified in red alert data for ship %s. Using mission defaults.\n", shipp->ship_name));
|
|
}
|
|
|
|
// restore hull (but not shields)
|
|
if (ras->hull >= 0.0f && ras->hull <= ship_objp->hull_strength)
|
|
ship_objp->hull_strength = ras->hull;
|
|
else
|
|
mprintf(("Invalid health in red alert data for ship %s. Using mission defaults.\n", shipp->ship_name));
|
|
|
|
// restore weapons and subsys
|
|
red_alert_bash_weapons(ras, &shipp->weapons);
|
|
red_alert_bash_subsys_status(ras, shipp);
|
|
|
|
ship_data_restored = true;
|
|
}
|
|
// must be destroyed or deleted
|
|
else
|
|
{
|
|
ship_state = ras->ship_class;
|
|
}
|
|
|
|
// we won't have two ships with the same name, so bail
|
|
break;
|
|
}
|
|
}
|
|
|
|
// remove ship if it was destroyed, or if there's no red-alert data for it
|
|
if ( !ship_data_restored ) {
|
|
// we need to be a little tricky here because deletion invalidates the ship_obj
|
|
ship_obj *next_so = GET_NEXT(so);
|
|
red_alert_delete_ship(shipp, ship_state);
|
|
so = next_so;
|
|
} else {
|
|
so = GET_NEXT(so);
|
|
}
|
|
}
|
|
|
|
// NOTE: in retail, red alert data was not loaded for ships that arrived later in the mission
|
|
if (!Red_alert_applies_to_delayed_ships)
|
|
return;
|
|
|
|
// go through all ships yet to arrive, and see if there is red alert status data for any
|
|
|
|
for ( poii = Parse_objects.begin(); poii != Parse_objects.end(); ++poii )
|
|
{
|
|
p_object *pobjp = &(*poii);
|
|
|
|
// objects that have already arrived would have been handled in the above loop
|
|
if ( pobjp->created_object != NULL )
|
|
continue;
|
|
|
|
// if we're in a wing, check whether we're in the player wing
|
|
bool from_player_wing = false;
|
|
if (pobjp->wingnum >= 0)
|
|
{
|
|
for (j = 0; j < MAX_STARTING_WINGS; j++)
|
|
{
|
|
if (!stricmp(Starting_wing_names[j], Wings[pobjp->wingnum].name))
|
|
{
|
|
from_player_wing = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// same condition as in ship_obj loop
|
|
if ( !from_player_wing && !(pobjp->flags & P_SF_RED_ALERT_STORE_STATUS) ) {
|
|
continue;
|
|
}
|
|
|
|
bool ship_data_restored = false;
|
|
int ship_state = RED_ALERT_DESTROYED_SHIP_CLASS;
|
|
|
|
for ( rasii = Red_alert_wingman_status.begin(); rasii != Red_alert_wingman_status.end(); ++rasii )
|
|
{
|
|
red_alert_ship_status *ras = &(*rasii);
|
|
|
|
// red-alert data matches this ship!
|
|
if ( !stricmp(ras->name.c_str(), pobjp->name) )
|
|
{
|
|
// we only want to restore ships which haven't been destroyed, or were removed by the player
|
|
if ( (ras->ship_class != RED_ALERT_DESTROYED_SHIP_CLASS) && (ras->ship_class != RED_ALERT_PLAYER_DEL_SHIP_CLASS) )
|
|
{
|
|
// if necessary, restore correct ship class
|
|
if ( ras->ship_class != pobjp->ship_class )
|
|
{
|
|
if (ras->ship_class >= 0 && ras->ship_class < MAX_SHIP_CLASSES)
|
|
swap_parse_object(pobjp, ras->ship_class);
|
|
else
|
|
{
|
|
mprintf(("Invalid ship class specified in red alert data for ship %s. Using mission defaults.\n", pobjp->name));
|
|
|
|
// We will break anyway to this should work
|
|
break;
|
|
}
|
|
}
|
|
|
|
// restore hull (but not shields)
|
|
if (ras->hull >= 0.0f && ras->hull <= (pobjp->initial_hull * pobjp->ship_max_hull_strength / 100.0f))
|
|
pobjp->initial_hull = (int) (ras->hull * 100.0f / pobjp->ship_max_hull_strength);
|
|
else
|
|
mprintf(("Invalid health in red alert data for ship %s. Using mission defaults.\n", pobjp->name));
|
|
|
|
// restore weapons and subsys
|
|
red_alert_bash_weapons(ras, pobjp);
|
|
red_alert_bash_subsys_status(ras, pobjp);
|
|
|
|
ship_data_restored = true;
|
|
}
|
|
// must be destroyed or deleted
|
|
else
|
|
{
|
|
ship_state = ras->ship_class;
|
|
}
|
|
|
|
// we won't have two ships with the same name, so bail
|
|
break;
|
|
}
|
|
}
|
|
|
|
// remove ship if it was destroyed, or if there's no red-alert data for it
|
|
if ( !ship_data_restored )
|
|
{
|
|
red_alert_delete_ship(pobjp, ship_state);
|
|
|
|
if (pobjp->wingnum >= 0)
|
|
Wing_pobjects_deleted[pobjp->wingnum]++;
|
|
}
|
|
}
|
|
|
|
// if all parse objects in a wing have been removed, decrement the count for that wing
|
|
for (ii = Wing_pobjects_deleted.begin(); ii != Wing_pobjects_deleted.end(); ++ii)
|
|
{
|
|
wing *wingp = &Wings[ii->first];
|
|
|
|
if (wingp->num_waves > 0 && wingp->wave_count == ii->second)
|
|
{
|
|
wingp->current_wave++;
|
|
wingp->red_alert_skipped_ships += wingp->wave_count;
|
|
|
|
if (wingp->num_waves == 0)
|
|
{
|
|
wingp->flags |= WF_WING_GONE;
|
|
wingp->flags |= WF_NEVER_EXISTED;
|
|
}
|
|
|
|
// look through all ships yet to arrive...
|
|
for (p_object *pobjp = GET_FIRST(&Ship_arrival_list); pobjp != END_OF_LIST(&Ship_arrival_list); pobjp = GET_NEXT(pobjp))
|
|
{
|
|
// ...and mark the ones in this wing
|
|
if (pobjp->wingnum == ii->first)
|
|
{
|
|
// no waves left to arrive, so mark ships accordingly
|
|
if (wingp->num_waves == 0)
|
|
pobjp->flags |= P_SF_CANNOT_ARRIVE;
|
|
// we skipped one complete wave, so clear the flag so the next wave creates all ships
|
|
else
|
|
pobjp->flags2 &= ~P2_RED_ALERT_DELETED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// return !0 if this is a red alert mission, otherwise return 0
|
|
int red_alert_mission()
|
|
{
|
|
return (The_mission.flags & MISSION_FLAG_RED_ALERT);
|
|
}
|
|
|
|
// called from sexpression code to start a red alert mission
|
|
void red_alert_start_mission()
|
|
{
|
|
// if we are not in campaign mode, go to debriefing
|
|
// if ( !(Game_mode & GM_CAMPAIGN_MODE) ) {
|
|
// gameseq_post_event( GS_EVENT_DEBRIEF ); // proceed to debriefing
|
|
// return;
|
|
// }
|
|
|
|
// check player health here.
|
|
// if we're dead (or about to die), don't start red alert mission.
|
|
if (Player_obj->type == OBJ_SHIP) {
|
|
if (Player_obj->hull_strength > 0) {
|
|
// make sure we don't die
|
|
Player_ship->ship_guardian_threshold = SHIP_GUARDIAN_THRESHOLD_DEFAULT;
|
|
|
|
// do normal red alert stuff
|
|
Red_alert_new_mission_timestamp = timestamp(RED_ALERT_WARN_TIME);
|
|
|
|
// throw down a sound here to make the warning seem ultra-important
|
|
// gamesnd_play_iface(SND_USER_SELECT);
|
|
snd_play(&(Snds[SND_DIRECTIVE_COMPLETE]));
|
|
}
|
|
}
|
|
}
|
|
|
|
// called from HUD code to see if we're red-alerting
|
|
int red_alert_in_progress()
|
|
{
|
|
// it is specifically a question of whether the timestamp is running
|
|
return timestamp_valid(Red_alert_new_mission_timestamp);
|
|
}
|
|
|
|
// called from the game loop to check if we should actually do the red-alert
|
|
void red_alert_maybe_move_to_next_mission()
|
|
{
|
|
// if the timestamp is invalid, do nothing.
|
|
if ( !timestamp_valid(Red_alert_new_mission_timestamp) )
|
|
return;
|
|
|
|
// return if the timestamp hasn't elapsed yet
|
|
if ( !timestamp_elapsed(Red_alert_new_mission_timestamp) )
|
|
return;
|
|
|
|
// basic premise here is to stop the current mission, and then set the next mission in the campaign
|
|
// which better be a red alert mission
|
|
if ( Game_mode & GM_CAMPAIGN_MODE ) {
|
|
red_alert_store_wingman_status();
|
|
mission_goal_fail_incomplete();
|
|
mission_campaign_store_goals_and_events_and_variables();
|
|
scoring_level_close();
|
|
mission_campaign_eval_next_mission();
|
|
mission_campaign_mission_over();
|
|
|
|
// CD CHECK
|
|
gameseq_post_event(GS_EVENT_START_GAME);
|
|
} else {
|
|
gameseq_post_event(GS_EVENT_END_GAME);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* red_alert_clear()
|
|
*
|
|
* clear all red alert "wingman" data
|
|
* Allows data to be cleared from outside REDALERT_INTERNAL code
|
|
*/
|
|
void red_alert_clear()
|
|
{
|
|
Red_alert_wingman_status.clear();
|
|
}
|