Files
fs2open.github.com/code/missionui/redalert.cpp
T
niffiwan 179c2ceb82 Fix mantis 3065 (clear RA data)
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
2014-08-17 18:50:12 +10:00

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();
}