Files
Goober5000andClaude Opus 5 09fc7055b5 Fix FRED and qtFRED memory leaks at exit (#7784)
Neither editor had a teardown to match FSO's game_shutdown(), so the engine
data they set up was never freed.  Add editor_free_engine_data() to free it,
and call it from a new fred_shutdown() in FRED and from qtFRED's shutdown(),
before the graphics system closes.

Also:
- Delete FRED's modeless editors if they are still open at exit.
- Make FRED's dialog tooltips and sexp tree image lists members, since
  they were leaked or depended on OnDestroy() being called.
- Parent qtFRED's item models so they are deleted with their widgets.
- Enable the CRT leak report in qtFRED, which previously had none.
- In qtFRED, call shutdown() last in closeEvent(), since it destroys the
  window.
- Make the preferences path and user SID strings function-level statics,
  so MFC's leak dump no longer reports them as leaks.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-21 05:12:35 +02:00

2637 lines
70 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 "mission/missionbriefcommon.h"
#include "anim/animplay.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "globalincs/linklist.h"
#include "iff_defs/iff_defs.h"
#include "io/mouse.h"
#include "io/timer.h"
#include "localization/localize.h"
#include "math/fvi.h"
#include "mission/missiongrid.h"
#include "mission/missionparse.h"
#include "missionui/missionbrief.h"
#include "missionui/missioncmdbrief.h"
#include "missionui/missiondebrief.h"
#include "mod_table/mod_table.h"
#include "options/Option.h"
#include "parse/parselo.h"
#include "playerman/player.h"
#include "render/3d.h"
#include "ship/ship.h"
#include "sound/audiostr.h"
#include "sound/fsspeech.h"
#include "species_defs/species_defs.h"
// --------------------------------------------------------------------------------------
// briefing screen
// --------------------------------------------------------------------------------------
brief_screen bscreen;
// briefing screen sections
#define BRIEF_CUP_X1 400
#define BRIEF_CUP_Y1 70
#define BRIEF_CUP_X2 639
#define BRIEF_CUP_Y2 245
#define BRIEF_CUPINFO_X1 445
#define BRIEF_CUPINFO_Y1 247
#define BRIEF_CUPINFO_X2 639
#define BRIEF_CUPINFO_Y2 438
float Briefing_Icon_Scale_Factor = 1.0;
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
static auto IconScaleFactor __UNUSED = options::OptionBuilder<float>("Game.BriefIconScaleFactor",
std::pair<const char*, int>{"Briefing Icon Scale Factor", 1857},
std::pair<const char*, int>{"Scales the size of the briefing icons", 1858})
.category(std::make_pair("Game", 1824))
.range(0.2f, 4.0f) //Upper limit is somewhat arbitrary
.level(options::ExpertLevel::Advanced)
.default_val(1.0)
.bind_to(&Briefing_Icon_Scale_Factor)
.importance(55)
.finish();
const char *Brief_static_name[GR_NUM_RESOLUTIONS] = {
"BriefMap",
"2_BriefMap"
};
int Brief_static_coords[GR_NUM_RESOLUTIONS][2] = {
{ // GR_640
10, 130
},
{ // GR_1024
15, 208
}
};
int Brief_bmap_coords[GR_NUM_RESOLUTIONS][2] = {
{ // GR_640
0, 115
},
{ // GR_1024
0, 184
}
};
int Brief_grid_coords[GR_NUM_RESOLUTIONS][4] = {
{ // GR_640
19, 147, 555, 232
},
{ // GR_1024
30, 235, 888, 371
}
};
int Brief_text_coords[GR_NUM_RESOLUTIONS][4] = {
{ // GR_640
28, 399, 395, 74
},
{ // GR_1024
46, 637, 630, 120
}
};
int Brief_stage_text_coords[GR_NUM_RESOLUTIONS][2] = {
{ // GR_640
138, 117
},
{ // GR_1024
227, 194
}
};
int Brief_stage_text_coords_multi[GR_NUM_RESOLUTIONS][2] = {
{ // GR_640
479, 385
},
{ // GR_1024
821, 616
}
};
int Brief_text_max_lines[GR_NUM_RESOLUTIONS] = {
8, 13
};
#define LOOKAT_DIST 500.0f
#define STAGE_ADVANCE_DELAY 1000 // time in ms to wait after voice stops before advancing stage
extern const float BRIEF_TEXT_WIPE_TIME = 1.5f; // time in seconds for wipe to occur
// --------------------------------------------------------------------------------------
// Game-wide global data
// --------------------------------------------------------------------------------------
briefing Briefings[MAX_TVT_TEAMS]; // there is exactly one briefing per mission
debriefing Debriefings[MAX_TVT_TEAMS]; // there can be multiple debriefings per mission
briefing *Briefing; // pointer used in code -- points to correct briefing
debriefing *Debriefing; // pointer to correct debriefing
bool Briefing_voice_enabled = true; // flag which turn on/off voice playback of briefings/debriefings
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
static auto BriefingVoiceOption __UNUSED = options::OptionBuilder<bool>("Audio.BriefingVoice",
std::pair<const char*, int>{"Briefing voice", 1368},
std::pair<const char*, int>{"Enable or disable voice playback in the briefing", 1716})
.category(std::make_pair("Audio", 1826))
.level(options::ExpertLevel::Beginner)
.default_val(true)
.bind_to(&Briefing_voice_enabled)
.importance(4)
.flags({options::OptionFlags::RetailBuiltinOption})
.finish();
// --------------------------------------------------------------------------------------
// Module global data
// --------------------------------------------------------------------------------------
static int Last_new_stage;
int Cur_brief_id;
const unicode::codepoint_t BRIEF_META_CHAR = UNICODE_CHAR('$');
const int HIGHEST_COLOR_STACK_INDEX = 9;
// camera related
static vec3d Current_cam_pos; // current camera position
static vec3d Target_cam_pos; // desired camera position
static matrix Current_cam_orient; // current camera orientation
static matrix Target_cam_orient; // desired camera orientation
static matrix Start_cam_orient; // start camera orientation
static vec3d Start_cam_pos; // position of camera at the start of a translation
static vec3d Cam_vel; // camera velocity
static vec3d Current_lookat_pos; // lookat point
static vec3d Target_lookat_pos; // lookat point
static vec3d Start_lookat_pos;
static vec3d Lookat_vel; // lookat point velocity
static float Start_cam_move; // time at which camera started moving (seconds)
static float Total_move_time; // time in which camera should move from current pos to target pos (seconds)
static float Elapsed_time;
static float Start_dist;
static float End_dist;
static float Dist_change_rate;
static vec3d Acc_limit;
static vec3d Vel_limit;
static float Total_dist;
static float Peak_speed;
static float Cam_accel;
static float Last_dist;
static vec3d W_init;
// flag to indicate that the sound for a spinning highlight animation has played
static sound_handle Brief_stage_highlight_sound_handle = sound_handle::invalid();
// used for scrolling briefing text ( if necessary )
int Num_brief_text_lines[MAX_TEXT_STREAMS];
int Top_brief_text_line;
// Used to support drawing colored text for the briefing. Gets complicated since we
// need to be able to draw one character at a time as well when the briefing text
// first appears.
typedef struct colored_char
{
unicode::codepoint_t letter;
char color; // tag to look up in Tagged_Colors
} colored_char;
typedef SCP_vector<colored_char> briefing_line;
typedef SCP_vector<briefing_line> briefing_stream;
static briefing_stream Colored_stream[MAX_TEXT_STREAMS];
#define BRIGHTEN_LEAD 2
float Brief_text_wipe_time_elapsed;
static int Max_briefing_line_len;
static int Voice_started_time;
static int Voice_ended_time;
static sound_handle Brief_text_wipe_snd; // sound handle of sound effect for text wipe
static int Play_brief_voice;
// animation stuff
static int Play_highlight_flag;
static int Cam_target_reached;
static int Cam_movement_done;
// moving icons
typedef struct icon_move_info
{
icon_move_info *next, *prev;
int used;
int id;
vec3d start;
vec3d finish;
vec3d current;
// used to move icons smoothly
vec3d direction;
float total_dist;
float accel;
float total_move_time;
float peak_speed;
int reached_dest;
float last_dist;
} icon_move_info;
#define MAX_MOVING_ICONS MAX_STAGE_ICONS
icon_move_info Icon_movers[MAX_MOVING_ICONS];
icon_move_info Icon_move_list; // head of linked list
// fading out icons
typedef struct icon_fade_info
{
hud_anim fade_anim;
vec3d pos;
float scale_factor;
bool mirror;
int team;
} fade_icon;
#define MAX_FADING_ICONS MAX_STAGE_ICONS
icon_fade_info Fading_icons[MAX_FADING_ICONS];
int Num_fade_icons;
// voice id's for briefing text
int Brief_voices[MAX_BRIEF_STAGES];
cmd_brief *Cur_cmd_brief;
cmd_brief Cmd_briefs[MAX_TVT_TEAMS];
SCP_vector<briefing_icon_info> Briefing_icon_info;
// --------------------------------------------------------------------------------------
// forward declarations
// --------------------------------------------------------------------------------------
void brief_render_elements(vec3d *pos, grid *gridp);
void brief_render_icons(int stage_num, float frametime);
void brief_maybe_create_new_grid(grid *gridp, vec3d *pos, matrix *orient, int force = 0);
grid *brief_create_grid(grid *gridp, vec3d *forward, vec3d *right, vec3d *center, int nrows, int ncols, float square_size);
grid *brief_create_default_grid(void);
void brief_render_grid(grid *gridp, const color& color);
void brief_modify_grid(grid *gridp);
void brief_rpd_line(vec3d *v0, vec3d *v1, const color& color);
void brief_set_text_color(char color_tag);
extern void get_camera_limits(const matrix *start_camera, const matrix *end_camera, float time, vec3d *acc_max, vec3d *w_max);
int brief_text_wipe_finished();
// parses, fills and returns a briefing_icon_info struct
briefing_icon_info parse_briefing_icons() {
char name[MAX_FILENAME_LEN];
briefing_icon_info bii;
// parse regular frames
required_string("$Name:");
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
generic_anim_init(&bii.regular, name);
// parse fade frames
required_string("$Name:");
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
hud_anim_init(&bii.fade, 0, 0, name);
// parse highlighting frames
required_string("$Name:");
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
hud_anim_init(&bii.highlight, 0, 0, name);
return bii;
}
// parses and adds a briefing_icon_info into the global vector
int add_briefing_icons() {
briefing_icon_info bii = parse_briefing_icons();
Briefing_icon_info.push_back(bii);
return (int)Briefing_icon_info.size() - 1;
}
void brief_icon_parse_cleanup() {
// assign "borrows" icons
for (species_info &spinfo : Species_info) {
if (spinfo.borrows_bii_index_species < 0)
continue;
int lender_species = spinfo.borrows_bii_index_species;
for (int i = 0; i < MIN_BRIEF_ICONS; i++) {
spinfo.bii_indices[i] = Species_info[lender_species].bii_indices[i];
}
}
SCP_string errormsg = "The following species are missing briefing icons:\n";
bool missing_icons = false;
for (species_info spinfo : Species_info) {
bool missing_icon_species = false;
for (int i = 0; i < MIN_BRIEF_ICONS; i++) {
if (spinfo.bii_indices[i] < 0) {
mprintf(("Species %s missing %s icon\n", spinfo.species_name, Icon_names[i]));
missing_icons = missing_icon_species = true;
}
}
if (missing_icon_species) {
errormsg += spinfo.species_name;
errormsg += "\n";
}
}
if (missing_icons) {
errormsg += "An exact list of the missing entries has been logged.";
Warning(LOCATION, "%s", errormsg.c_str());
}
}
static size_t Num_icons_in_table = 0;
// This is explicitely used to count the number of icons listed in the tbl
// so that we can correctly parse each icon into a species without any off-by-N errors.
// This allows modular icons.tbls to build upon the retail icons.tbl.
void brief_pre_parse_icons()
{
try {
read_file_text("icons.tbl", CF_TYPE_TABLES);
reset_parse();
required_string("#Start");
//If we're in the new format, then nothing to do!
if (check_for_string("$Species:"))
return;
char junk[MAX_FILENAME_LEN];
while (optional_string("$Name:")) {
stuff_string(junk, F_NAME, MAX_FILENAME_LEN);
Num_icons_in_table++;
}
required_string("#End");
}
catch (const parse::ParseException& e) {
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "icons.tbl", e.what()));
}
}
// --------------------------------------------------------------------------------------
// brief_parse_icon_tbl()
//
//
void brief_parse_icon_tbl(const char* filename)
{
size_t species;
char name[MAX_FILENAME_LEN];
Assert(!Species_info.empty());
try
{
read_file_text(filename, CF_TYPE_TABLES);
reset_parse();
required_string("#Start");
// get all the species who are using unique icons (i.e. not borrowing from another species)
SCP_vector<int> unique_icons_species;
for (int i = 0; i < (int)Species_info.size(); i++) {
if (Species_info[i].borrows_bii_index_species == -1)
unique_icons_species.push_back(i);
}
bool new_style_parsing = false;
if (Parsing_modular_table || check_for_string("$Species:")) //Do not allow modular tables to use the old format! -Mjn
new_style_parsing = true;
if (new_style_parsing) {
while (optional_string("$Species:")) {
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
int spinfo_index = species_info_lookup(name);
if (spinfo_index < 0) {
Warning(LOCATION, "Unable to find species %s, in icons.tbl.", name);
}
while (optional_string("$Icon Type:")) {
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
// find the matching icon type
int icon_type = [&]() {
for (int i = 0; i < MIN_BRIEF_ICONS; i++) {
if (strcmp(Icon_names[i], name) == 0) {
// found it
return i;
}
}
// couldnt find it...
return -1;
}();
if (icon_type < 0) {
Warning(LOCATION, "Icon Type %s is invalid, in icons.tbl.", name);
}
int &existing_bii_index = Species_info[spinfo_index].bii_indices[icon_type];
if (existing_bii_index >= 0) { // overwriting
briefing_icon_info bii = parse_briefing_icons();
if (icon_type >= 0 && spinfo_index >= 0)
Briefing_icon_info[existing_bii_index] = bii;
} else { // adding a new one
int new_bii_index = add_briefing_icons();
if (icon_type >= 0 && spinfo_index >= 0)
existing_bii_index = new_bii_index;
}
}
}
}
else { // old style
//Calculate how many species we're going to try to parse icons for using the pre-parsed count
if (Num_icons_in_table % MIN_BRIEF_ICONS != 0)
Warning(LOCATION,
"An incorrect number of icons was found in icons.tbl. There should be %i icons per species listed!",
MIN_BRIEF_ICONS);
size_t Num_icon_sets = Num_icons_in_table / MIN_BRIEF_ICONS;
if (Num_icon_sets % BRIEF_ICON_TYPES != 0)
Error(LOCATION,
"There was somehow a number of icons divisible by %i, but is now not divisible by %i. Get a "
"coder!",
MIN_BRIEF_ICONS,
BRIEF_ICON_TYPES);
size_t Num_species_in_table = Num_icon_sets / BRIEF_ICON_TYPES;
for (int icon_type = 0; icon_type < MIN_BRIEF_ICONS; icon_type++) { // NOLINT(modernize-loop-convert)
for (species = 0; species < Num_species_in_table; species++) {
// if this check isn't true we're missing entries and will complain about it later in brief_icon_parse_cleanup()
if (check_for_string("$Name:")) {
Species_info[unique_icons_species[species]].bii_indices[icon_type] = add_briefing_icons();
}
}
}
//This can still be reached if the above Warning is ignored-Mjn
const size_t max_icons = unique_icons_species.size() * MIN_BRIEF_ICONS;
if (!check_for_string("#End"))
Warning(LOCATION, "Too many icons in icons.tbl; only the first " SIZE_T_ARG " will be used", max_icons);
}
}
catch (const parse::ParseException& e)
{
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "icons.tbl", e.what()));
}
}
void brief_icons_init() {
Briefing_icon_info.clear();
//This is super dumb, but retail format is bad and should feel bad for making me do this-Mjn
brief_pre_parse_icons();
brief_parse_icon_tbl("icons.tbl");
parse_modular_table(NOX("*-ico.tbm"), brief_parse_icon_tbl);
brief_icon_parse_cleanup();
}
void brief_set_icon_color(int team)
{
gr_set_color_fast(iff_get_color_by_team(team, -1, 0));
}
// --------------------------------------------------------------------------------------
// brief_move_icon_reset()
//
//
void brief_move_icon_reset()
{
int i;
list_init(&Icon_move_list);
for ( i = 0; i < MAX_MOVING_ICONS; i++ )
Icon_movers[i].used = 0;
}
/**
* Does one time initialization of the briefing and debriefing structures.
* Namely setting all malloc'ble pointers to NULL. Called once at game startup.
*/
void mission_brief_common_init()
{
int i,j;
// setup brief text colors
gr_init_alphacolor( &Brief_color_green, 50, 100, 50, 255 );
gr_init_alphacolor( &Brief_color_red, 140, 20, 20, 255 );
gr_init_alphacolor( &Brief_color_legacy_neutral, 255, 0, 0, iff_get_alpha_value(false));
// extra catch to reset everything that's already loaded - taylor
mission_brief_common_reset();
mission_debrief_common_reset();
if ( Fred_running ) {
// If Fred is running malloc out max space
for (i = 0; i < MAX_TVT_TEAMS; i++) {
for (j = 0; j < MAX_BRIEF_STAGES; j++) {
Briefings[i].stages[j].text = "";
if (Briefings[i].stages[j].icons == NULL) {
Briefings[i].stages[j].icons = (brief_icon *)vm_malloc(sizeof(brief_icon) * MAX_STAGE_ICONS);
Assert( Briefings[i].stages[j].icons != NULL );
memset( Briefings[i].stages[j].icons, 0, sizeof(brief_icon) * MAX_STAGE_ICONS );
}
if (Briefings[i].stages[j].lines == NULL) {
Briefings[i].stages[j].lines = (brief_line *)vm_malloc(sizeof(brief_line) * MAX_BRIEF_STAGE_LINES);
Assert( Briefings[i].stages[j].lines != NULL );
memset( Briefings[i].stages[j].lines, 0, sizeof(brief_line) * MAX_BRIEF_STAGE_LINES );
}
Briefings[i].stages[j].num_icons = 0;
Briefings[i].stages[j].num_lines = 0;
}
}
for (i = 0; i < MAX_TVT_TEAMS; i++) {
for (j = 0; j < MAX_DEBRIEF_STAGES; j++) {
Debriefings[i].stages[j].text = "";
Debriefings[i].stages[j].recommendation_text = "";
}
}
} else {
// If game is running don't malloc anything
for (i=0; i<MAX_TVT_TEAMS; i++ ) {
for (j=0; j<MAX_BRIEF_STAGES; j++ ) {
Briefings[i].stages[j].text = "";
Briefings[i].stages[j].num_icons = 0;
Briefings[i].stages[j].icons = NULL;
Briefings[i].stages[j].num_lines = 0;
Briefings[i].stages[j].lines = NULL;
}
}
for (i=0; i<MAX_TVT_TEAMS; i++ ) {
for (j=0; j<MAX_DEBRIEF_STAGES; j++ ) {
Debriefings[i].stages[j].text = "";
Debriefings[i].stages[j].recommendation_text = "";
}
}
}
}
/**
* Frees all the memory allocated in the briefing and debriefing structures and sets all pointers to NULL.
*/
void mission_brief_common_reset()
{
int i, j;
for (i = 0; i < MAX_TVT_TEAMS; i++) {
Briefings[i].num_stages = 0;
for (j = 0; j < MAX_BRIEF_STAGES; j++) {
Briefings[i].stages[j].num_icons = 0;
Briefings[i].stages[j].num_lines = 0;
Briefings[i].stages[j].text = "";
if (Fred_running) {
if ( Briefings[i].stages[j].icons ) {
memset( Briefings[i].stages[j].icons, 0, sizeof(brief_icon) * MAX_STAGE_ICONS );
Briefings[i].stages[j].icons->ship_class = -1;
Briefings[i].stages[j].icons->modelnum = -1;
Briefings[i].stages[j].icons->model_instance_num = -1;
Briefings[i].stages[j].icons->bitmap_id = -1;
}
if ( Briefings[i].stages[j].lines )
memset( Briefings[i].stages[j].lines, 0, sizeof(brief_line) * MAX_BRIEF_STAGE_LINES );
} else {
if ( Briefings[i].stages[j].icons ) {
vm_free(Briefings[i].stages[j].icons);
Briefings[i].stages[j].icons = NULL;
}
if ( Briefings[i].stages[j].lines ) {
vm_free(Briefings[i].stages[j].lines);
Briefings[i].stages[j].lines = NULL;
}
}
}
}
}
/**
* Split from above since we need to clear them separately
* @see mission_brief_common_reset()
*/
void mission_debrief_common_reset()
{
int i, j;
for (i = 0; i < MAX_TVT_TEAMS; i++) {
Debriefings[i].num_stages = 0;
for (j = 0; j < MAX_DEBRIEF_STAGES; j++) {
Debriefings[i].stages[j].text = "";
Debriefings[i].stages[j].recommendation_text = "";
}
}
}
/**
* Frees the briefing icon and line buffers that the mission editors allocate in mission_brief_common_init()
* and keep for their whole session. Called at editor shutdown.
*/
void mission_brief_common_close()
{
int i, j;
for (i = 0; i < MAX_TVT_TEAMS; i++)
{
for (j = 0; j < MAX_BRIEF_STAGES; j++)
{
if ( Briefings[i].stages[j].icons )
{
vm_free(Briefings[i].stages[j].icons);
Briefings[i].stages[j].icons = nullptr;
}
if ( Briefings[i].stages[j].lines )
{
vm_free(Briefings[i].stages[j].lines);
Briefings[i].stages[j].lines = nullptr;
}
Briefings[i].stages[j].num_icons = 0;
Briefings[i].stages[j].num_lines = 0;
}
}
}
// --------------------------------------------------------------------------------------
// brief_reset()
//
//
void brief_reset()
{
mission_brief_common_reset();
Briefing = NULL;
Cur_brief_id = 1;
}
// --------------------------------------------------------------------------------------
// debrief_reset()
//
//
void debrief_reset()
{
mission_debrief_common_reset();
Debriefing = NULL;
// MWA 4/27/98 -- must initialize this variable here since we cannot do it as debrief
// init time because race conditions between all players in the game make that type of
// initialization unsafe.
Debrief_multi_stages_loaded = 0;
}
/**
* Set up the screen regions. A mulitplayer briefing will look different than a single player briefing.
*/
void brief_init_screen(int /*multiplayer_flag*/)
{
bscreen.map_x1 = Brief_grid_coords[gr_screen.res][0];
bscreen.map_x2 = Brief_grid_coords[gr_screen.res][0] + Brief_grid_coords[gr_screen.res][2];
bscreen.map_y1 = Brief_grid_coords[gr_screen.res][1];
bscreen.map_y2 = Brief_grid_coords[gr_screen.res][1] + Brief_grid_coords[gr_screen.res][3];
bscreen.resize = GR_RESIZE_MENU;
}
bool brief_special_closeup(int briefing_icon_type)
{
switch (briefing_icon_type)
{
case ICON_PLANET:
case ICON_ASTEROID_FIELD:
case ICON_WAYPOINT:
case ICON_UNKNOWN:
case ICON_UNKNOWN_WING:
case ICON_JUMP_NODE:
return true;
}
return false;
}
briefing_icon_info *brief_get_icon_info(brief_icon *bi)
{
if (bi->ship_class < 0)
return nullptr;
ship_info *sip = &Ship_info[bi->ship_class];
// ship info might override the usual briefing icon
if ((!brief_special_closeup(bi->type) || Custom_briefing_icons_always_override_standard_icons) && sip->bii_index_ship >= 0)
{
if (bi->flags & BI_USE_WING_ICON)
{
if (bi->flags & BI_USE_CARGO_ICON)
{
if (sip->bii_index_wing_with_cargo >= 0)
return &Briefing_icon_info[sip->bii_index_wing_with_cargo];
else
mprintf(("Ship '%s' is missing the wing-with-cargo briefing icon!\n", sip->name));
}
else
{
if (sip->bii_index_wing >= 0)
return &Briefing_icon_info[sip->bii_index_wing];
else
mprintf(("Ship '%s' is missing the wing briefing icon!\n", sip->name));
}
}
else
{
if (bi->flags & BI_USE_CARGO_ICON)
{
if (sip->bii_index_ship_with_cargo >= 0)
return &Briefing_icon_info[sip->bii_index_ship_with_cargo];
else
mprintf(("Ship '%s' is missing the ship-with-cargo briefing icon!\n", sip->name));
}
}
// this will be reached if we just want the plain ship icon, or if we specified icon modifiers which didn't exist
return &Briefing_icon_info[sip->bii_index_ship];
}
if (sip->species < 0)
return NULL;
int bii_index = Species_info[sip->species].bii_indices[bi->type];
if (bii_index < 0)
return NULL;
return &Briefing_icon_info[bii_index];
}
void brief_preload_icon_anim(brief_icon *bi)
{
briefing_icon_info *bii = brief_get_icon_info(bi);
if (bii == NULL)
return;
generic_anim *ga = &bii->regular;
if ( !stricmp(NOX("none"), ga->filename) )
return;
// force read of data from disk, so we don't glitch on initial playback
if ( ga->first_frame == -1 ) {
ga->first_frame = bm_load_animation(ga->filename, &ga->num_frames);
if ( ga->first_frame < 0 )
Warning(LOCATION, "Failed to load icon %s!", ga->filename);
}
}
void brief_preload_fade_anim(brief_icon *bi)
{
briefing_icon_info *bii = brief_get_icon_info(bi);
if (bii == NULL)
return;
hud_anim *ha = &bii->fade;
if ( !stricmp(NOX("none"), ha->filename) )
return;
// force read of data from disk, so we don't glitch on initial playback
if ( ha->first_frame == -1 )
hud_anim_load(ha);
if ( ha->first_frame >= 0 ) {
gr_set_bitmap(ha->first_frame);
gr_aabitmap(0, 0);
}
}
void brief_preload_highlight_anim(brief_icon *bi)
{
briefing_icon_info *bii = brief_get_icon_info(bi);
if (bii == NULL)
return;
hud_anim *ha = &bii->highlight;
if ( !stricmp(NOX("none"), ha->filename) )
return;
// force read of data from disk, so we don't glitch on initial playback
if ( ha->first_frame == -1 )
hud_anim_load(ha);
if ( ha->first_frame >= 0 ) {
bi->highlight_anim = *ha;
gr_set_bitmap(ha->first_frame);
gr_aabitmap(0, 0);
}
}
/**
* Preload highlight, fadein and fadeout animations that are used in each stage
*/
void brief_preload_anims()
{
int num_icons, num_stages, i, j;
brief_icon *bi;
num_stages = Briefing->num_stages;
for ( i = 0; i < num_stages; i++ ) {
num_icons = Briefing->stages[i].num_icons;
for ( j = 0; j < num_icons; j++ ) {
bi = &Briefing->stages[i].icons[j];
brief_preload_icon_anim(bi);
brief_preload_fade_anim(bi);
if ( bi->flags & BI_HIGHLIGHT ) {
brief_preload_highlight_anim(bi);
}
}
}
}
// --------------------------------------------------------------------------------------
// brief_init_map()
//
//
void brief_init_map()
{
vec3d *pos;
matrix *orient;
Assert( Briefing != NULL );
pos = &Briefing->stages[0].camera_pos;
orient = &Briefing->stages[0].camera_orient;
vm_vec_zero(&Current_lookat_pos);
vm_vec_zero(&Target_lookat_pos);
Elapsed_time = 0.0f;
Total_move_time = 0.0f;
The_grid = brief_create_default_grid();
brief_maybe_create_new_grid(The_grid, pos, orient, 1);
brief_move_icon_reset();
brief_preload_anims();
Brief_text_wipe_snd = sound_handle::invalid();
Last_new_stage = -1;
Num_fade_icons=0;
}
#ifdef _MSC_VER
#pragma optimize("", off)
#endif
void brief_render_fade_outs(float frametime)
{
int i,bx,by,w,h;
float bxf,byf;
vertex tv; // temp vertex used to find screen position for text
fade_icon *fi;
for (i=0; i<Num_fade_icons; i++) {
fi = &Fading_icons[i];
float scale_factor = fi->scale_factor;
if (!Fred_running) {
scale_factor *= Briefing_Icon_Scale_Factor;
}
g3_rotate_vertex(&tv, &fi->pos);
if (!(tv.flags & PF_PROJECTED))
g3_project_vertex(&tv);
if (!(tv.flags & PF_OVERFLOW) ) { // make sure point projected before drawing text
brief_set_icon_color(fi->team);
if ( fi->fade_anim.first_frame < 0 ) {
continue;
}
float scaled_w, scaled_h;
bm_get_info( fi->fade_anim.first_frame, &w, &h, nullptr);
float screenX = tv.screen.xyw.x;
float screenY = tv.screen.xyw.y;
int this_resize = bscreen.resize;
if (bscreen.resize == GR_RESIZE_MENU) {
this_resize = GR_RESIZE_MENU_NO_OFFSET;
}
gr_unsize_screen_posf(&screenX, &screenY, nullptr, nullptr, this_resize);
scaled_w = w * scale_factor;
scaled_h = h * scale_factor;
bxf = screenX - scaled_w / 2.0f + 0.5f;
byf = screenY - scaled_h / 2.0f + 0.5f;
bx = fl2i(bxf);
by = fl2i(byf);
if ( fi->fade_anim.first_frame >= 0 ) {
fi->fade_anim.sx = bx;
fi->fade_anim.sy = by;
hud_anim_render(&fi->fade_anim, frametime, 1, 0, 0, 0, bscreen.resize, fi->mirror, scale_factor);
}
}
}
}
/**
* Figure out how far an icon should move based on the elapsed time
*/
float brief_icon_get_dist_moved(icon_move_info *mi, float elapsed_time)
{
float time, dist_moved=0.0f;
// first half of movement
if ( elapsed_time < mi->total_move_time/2.0f ) {
dist_moved=0.5f*mi->accel*elapsed_time*elapsed_time; // d = 1/2at^2
return dist_moved;
}
// second half of movement
time=elapsed_time - mi->total_move_time/2.0f;
dist_moved=(mi->total_dist/2.0f)+(mi->peak_speed*time) - 0.5f*mi->accel*time*time;
return dist_moved;
}
/**
* Draw a line between two icons on the briefing screen
*/
void brief_render_icon_line(int stage_num, int line_num)
{
brief_line *bl;
brief_stage *bs;
brief_icon *icon[2];
int i;
vertex icon_vertex[2];
int icon_status[2] = {0,0};
int icon_w, icon_h;
float icon_x[2], icon_y[2];
bl = &Briefing->stages[stage_num].lines[line_num];
bs = &Briefing->stages[stage_num];
if(bl->start_icon < 0 || bl->start_icon >= bs->num_icons)
{
Warning(LOCATION, "Start icon (%d/%d) missing for line %d in briefing stage %d", bl->start_icon, bs->num_icons, line_num, stage_num);
//Remove line
bs->num_lines--;
for(i = line_num; i < bs->num_lines; i++)
bs->lines[i] = bs->lines[i+1];
return;
}
if(bl->end_icon < 0 || bl->end_icon >= Briefing->stages[stage_num].num_icons)
{
Warning(LOCATION, "End icon (%d/%d) missing for line %d in briefing stage %d", bl->end_icon, bs->num_icons, line_num, stage_num);
//Remove line
bs->num_lines--;
for(i = line_num; i < bs->num_lines; i++)
bs->lines[i] = bs->lines[i+1];
return;
}
icon[0] = &Briefing->stages[stage_num].icons[bl->start_icon];
icon[1] = &Briefing->stages[stage_num].icons[bl->end_icon];
// project icons
for (i=0; i<2; i++) {
g3_rotate_vertex(&icon_vertex[i],&icon[i]->pos);
if (!(icon_vertex[i].flags&PF_PROJECTED))
g3_project_vertex(&icon_vertex[i]);
if (!(icon_vertex[i].flags & PF_OVERFLOW) ) { // make sure point projected before drawing text
icon_status[i]=1;
}
}
if ( !icon_status[0] || !icon_status[1] ) {
return;
}
// get screen (x,y) for icons
for (i=0; i<2; i++) {
brief_common_get_icon_dimensions(&icon_w, &icon_h, icon[i]);
icon_x[i] = icon_vertex[i].screen.xyw.x;
icon_y[i] = icon_vertex[i].screen.xyw.y;
}
brief_set_icon_color(icon[0]->team);
gr_line(fl2i(icon_x[0]), fl2i(icon_y[0]), fl2i(icon_x[1]), fl2i(icon_y[1]), GR_RESIZE_NONE);
}
/**
* Draw a briefing icon
*
* @param stage_num briefing stage number (start at 0)
* @param icon_num icon number in stage
* @param frametime time elapsed in seconds
* @param selected FRED only (will be 0 or non-zero)
* @param scale_factor scale icon by this amount (default 1.0f)
*/
void brief_render_icon(int stage_num, int icon_num, float frametime, int selected, float scale_factor)
{
brief_icon *bi, *closeup_icon;
generic_anim *ga;
vertex tv; // temp vertex used to find screen position for text
vec3d *pos = NULL;
int bx,by,bc,w,h,icon_w,icon_h,icon_bitmap=-1;
float bxf, byf, dist=0.0f;
bool mirror_icon;
Assert( Briefing != NULL );
bi = &Briefing->stages[stage_num].icons[icon_num];
mirror_icon = (bi->flags & BI_MIRROR_ICON) ? true : false;
if (bi->scale_factor != 1.0f) {
scale_factor *= bi->scale_factor;
}
if (!Fred_running) {
scale_factor *= Briefing_Icon_Scale_Factor;
}
icon_move_info *mi, *next;
int interp_pos_found = 0;
mi = GET_FIRST(&Icon_move_list);
if (mi)
while ( mi != &Icon_move_list ) {
next = mi->next;
if ( ( mi->id != 0 ) && ( mi->id == bi->id ) ) {
if ( !mi->reached_dest ) {
dist = brief_icon_get_dist_moved(mi, Elapsed_time);
if ( dist < mi->last_dist ) {
mi->reached_dest=1;
mi->last_dist=0.0f;
}
mi->last_dist=dist;
}
if ( !mi->reached_dest ) {
vec3d dist_moved;
vm_vec_copy_scale(&dist_moved, &mi->direction, dist);
vm_vec_add(&mi->current, &mi->start, &dist_moved);
} else {
mi->current = mi->finish;
}
pos = &mi->current;
interp_pos_found = 1;
break;
}
mi = next;
}
if ( !interp_pos_found )
pos = &bi->pos;
brief_render_elements(pos, The_grid);
g3_rotate_vertex(&tv,pos);
if (!(tv.flags&PF_PROJECTED))
g3_project_vertex(&tv);
if (!(tv.flags & PF_OVERFLOW) ) { // make sure point projected before drawing text
brief_set_icon_color(bi->team);
briefing_icon_info *bii = brief_get_icon_info(bi);
if (bii == NULL) {
return;
}
ga = &bii->regular;
if (ga->first_frame < 0) {
return;
}
brief_common_get_icon_dimensions(&icon_w, &icon_h, bi);
closeup_icon = brief_get_closeup_icon();
if ( bi == closeup_icon || selected ) {
icon_bitmap = ga->first_frame+1;
}
else {
icon_bitmap = ga->first_frame;
}
float scaled_w, scaled_h;
float sx = tv.screen.xyw.x;
float sy = tv.screen.xyw.y;
int this_resize = bscreen.resize;
if (bscreen.resize == GR_RESIZE_MENU) {
this_resize = GR_RESIZE_MENU_NO_OFFSET;
}
gr_unsize_screen_posf(&sx, &sy, NULL, NULL, this_resize);
scaled_w = icon_w * scale_factor;
scaled_h = icon_h * scale_factor;
bxf = sx - scaled_w / 2.0f + 0.5f;
byf = sy - scaled_h / 2.0f + 0.5f;
bx = fl2i(bxf);
by = fl2i(byf);
bc = fl2i(sx);
// render highlight anim frame
if ( (bi->flags & BI_SHOWHIGHLIGHT) && (bi->flags & BI_HIGHLIGHT) ) {
hud_anim *ha = &bi->highlight_anim;
if ( ha->first_frame >= 0 ) {
ha->sx = bi->hold_x;
if (bi->label[0] != '\0') {
ha->sy = bi->hold_y - (int)std::lround(gr_get_font_height()/2.0f) - 2;
} else {
ha->sy = bi->hold_y;
}
//hud_set_iff_color(bi->team);
brief_set_icon_color(bi->team);
hud_anim_render(ha, frametime, 1, 0, 1, 0, bscreen.resize, mirror_icon, scale_factor);
if (!Brief_stage_highlight_sound_handle.isValid()) {
if ( !Fred_running) {
Brief_stage_highlight_sound_handle = snd_play(gamesnd_get_interface_sound(InterfaceSounds::ICON_HIGHLIGHT));
}
}
}
}
// render fade-in anim frame
if ( bi->flags & BI_FADEIN ) {
hud_anim *ha = &bi->fade_anim;
if ( ha->first_frame >= 0 ) {
ha->sx = bx;
ha->sy = by;
brief_set_icon_color(bi->team);
if (hud_anim_render(ha, frametime, 1, 0, 0, 1, bscreen.resize, mirror_icon, scale_factor) == 0) {
bi->flags &= ~BI_FADEIN;
}
} else {
bi->flags &= ~BI_FADEIN;
}
}
if ( !(bi->flags & BI_FADEIN) ) {
gr_set_bitmap(icon_bitmap);
gr_aabitmap(bx, by, bscreen.resize, mirror_icon, scale_factor);
// draw text centered over the icon (make text darker)
if ( bi->type == ICON_FIGHTER_PLAYER || bi->type == ICON_BOMBER_PLAYER ) {
gr_get_string_size(&w,&h,Players[Player_num].callsign);
gr_string(bc - fl2i(w / 2.0f), by - h, Players[Player_num].callsign, bscreen.resize);
}
else {
if (Lcl_gr && !Disable_built_in_translations) {
char buf[128];
strcpy_s(buf, bi->label);
lcl_translate_brief_icon_name_gr(buf);
gr_get_string_size(&w, &h, buf);
gr_string(bc - fl2i(w / 2.0f), by - h, buf, bscreen.resize);
} else if (Lcl_pl && !Disable_built_in_translations) {
char buf[128];
strcpy_s(buf, bi->label);
lcl_translate_brief_icon_name_pl(buf);
gr_get_string_size(&w, &h, buf);
gr_string(bc - fl2i(w / 2.0f), by - h, buf, bscreen.resize);
} else {
gr_get_string_size(&w,&h,bi->label);
gr_string(bc - fl2i(w / 2.0f), by - h, bi->label, bscreen.resize);
}
}
// show icon as selected (FRED only)
if ( selected ) {
gr_get_string_size(&w,&h,NOX("(S)"));
gr_printf(bc - fl2i(w/2.0f), by - h*2, NOX("(S)"));
}
}
// store screen x,y,w,h
bi->x = bx;
bi->y = by;
bi->w = fl2i(scaled_w);
bi->h = fl2i(scaled_h);
} // end if vertex is projected
}
#ifdef _MSC_VER
#pragma optimize("", on)
#endif
// -------------------------------------------------------------------------------------
// brief_render_icons()
//
void brief_render_icons(int stage_num, float frametime)
{
int i, num_icons, num_lines;
Assert( Briefing != NULL );
num_icons = Briefing->stages[stage_num].num_icons;
num_lines = Briefing->stages[stage_num].num_lines;
if ( Cam_target_reached ) {
for ( i = 0; i < num_lines; i++ ) {
brief_render_icon_line(stage_num, i);
}
}
for ( i = 0; i < num_icons; i++ ) {
Briefing->stages[stage_num].icons[i].w = 0;
Briefing->stages[stage_num].icons[i].h = 0;
brief_render_icon(stage_num, i, frametime, 0);
}
}
/**
* See if there are any highlight animations to play
*/
void brief_start_highlight_anims(int stage_num)
{
brief_stage *bs;
brief_icon *bi;
int x,y,i,anim_w,anim_h;
Assert( Briefing != NULL );
bs = &Briefing->stages[stage_num];
for ( i = 0; i < bs->num_icons; i++ ) {
bi = &bs->icons[i];
if ( bi->flags & BI_HIGHLIGHT ) {
bi->flags &= ~BI_SHOWHIGHLIGHT;
if ( bi->highlight_anim.first_frame < 0 ) {
continue;
}
bi->highlight_anim.time_elapsed=0.0f;
bm_get_info( bi->highlight_anim.first_frame, &anim_w, &anim_h, NULL);
if (bi->scale_factor != 1.0f) {
anim_w = static_cast<int>(anim_w * bi->scale_factor);
anim_h = static_cast<int>(anim_h * bi->scale_factor);
}
x = fl2i( i2fl(bi->x) + bi->w/2.0f - anim_w/2.0f );
y = fl2i( i2fl(bi->y) + bi->h/2.0f - anim_h/2.0f );
bi->hold_x = x;
bi->hold_y = y;
bi->flags |= BI_SHOWHIGHLIGHT;
bi->highlight_anim.time_elapsed=0.0f;
}
}
}
// -------------------------------------------------------------------------------------
// brief_render_map()
//
//
void brief_render_map(int stage_num, float frametime)
{
gr_set_clip(bscreen.map_x1 + 1, bscreen.map_y1 + 1, bscreen.map_x2 - bscreen.map_x1 - 1, bscreen.map_y2 - bscreen.map_y1 - 2, bscreen.resize);
if (stage_num >= Briefing->num_stages) {
gr_reset_clip();
return;
}
Assert(Briefing);
g3_start_frame(0);
g3_set_view_matrix(&Current_cam_pos, &Current_cam_orient, Briefing_window_FOV);
brief_maybe_create_new_grid(The_grid, &Current_cam_pos, &Current_cam_orient);
if (Briefing->stages[stage_num].draw_grid)
brief_render_grid(The_grid, Briefing->stages[stage_num].grid_color);
brief_render_fade_outs(frametime);
// go ahead and render everything that is in the active objects list
brief_render_icons(stage_num, frametime);
if ( Cam_target_reached && brief_text_wipe_finished() ) {
if ( Play_highlight_flag ) {
brief_start_highlight_anims(stage_num);
Play_highlight_flag = 0;
}
}
anim_render_all(ON_BRIEFING_SELECT, frametime);
gr_reset_clip();
g3_end_frame();
}
/**
* Display what stage of the briefing is active
*/
void brief_blit_stage_num(int stage_num, int stage_max)
{
char buf[64];
Assert( Briefing != NULL );
gr_set_color_fast(&Color_text_heading);
sprintf(buf, XSTR( "Stage %d of %d", 394), stage_num + 1, stage_max);
if (Game_mode & GM_MULTIPLAYER) {
gr_printf_menu(Brief_stage_text_coords_multi[gr_screen.res][0], Brief_stage_text_coords_multi[gr_screen.res][1], "%s", buf);
} else {
gr_printf_menu(Brief_stage_text_coords[gr_screen.res][0], Brief_stage_text_coords[gr_screen.res][1], "%s", buf);
}
}
/**
* Render a line of text for the briefings. Lines are drawn in as a wipe, with leading bright
* white characters. Have to jump through some hoops since we support colored words. This means
* that we need to process the line one character at a time.
*
* @param line_num number of the line of the briefing page to be drawn
* @param x horizontal position where the text is drawn
* @param y vertical position where the text is drawn
* @param instance index of Colored_stream of the text page to display
*/
void brief_render_line(int line_num, int x, int y, int instance) {
Assert(0 <= instance && instance < (int) (sizeof(Colored_stream) / sizeof(*Colored_stream)));
SCP_vector<colored_char>* src = &Colored_stream[instance].at(line_num);
// empty strings do not have to be drawn
size_t src_len = src->size();
if (src_len == 0) {
return;
}
// truncate_len is the number of characters currently displayed including the bright white characters
size_t truncate_len = fl2i(Brief_text_wipe_time_elapsed / BRIEF_TEXT_WIPE_TIME * Max_briefing_line_len);
if (truncate_len > src_len) {
truncate_len = src_len;
}
// truncate_len is going to be the number of characters displayed with normal intensity
// bright_len is the additional characters displayed with high intensity
// bright_len+truncate_len<len chars are displayed
size_t bright_len = 0;
if (truncate_len < src_len) {
if (truncate_len <= BRIGHTEN_LEAD) {
bright_len = truncate_len;
truncate_len = 0;
} else {
bright_len = BRIGHTEN_LEAD;
truncate_len -= BRIGHTEN_LEAD;
}
}
size_t char_seq_pos = 0; //Cursor position into the following character sequence
char char_seq[MAX_BRIEF_LINE_LEN];
int offset = 0; //offset is the horizontal position of the screen where strings are drawn
gr_set_color_fast(&Color_white);
// PART1: Draw the the briefing line part with normal intensity and word colors.
// The following algorithm builds a character sequence of color 'last_color' into 'line' buffer.
// When the color changes, the buffer is drawn and reset.
{
char last_color = src->at(0).color;
for (size_t current_pos = 0; current_pos < truncate_len; current_pos++) {
colored_char& current_char = src->at(current_pos);
//when the current color changes, the accumulated character sequence is drawn.
if (current_char.color != last_color) {
//add a 0 terminal character to make line a valid C string
Assert(char_seq_pos < sizeof(char_seq));
char_seq[char_seq_pos] = 0;
{
// Draw coloured text, and increment cariage position
int w = 0, h = 0;
brief_set_text_color(last_color);
gr_string(x + offset, y, char_seq, GR_RESIZE_MENU);
gr_get_string_size(&w, &h, char_seq);
offset += w;
}
//clear char buffer
char_seq_pos = 0;
last_color = current_char.color;
}
auto encoded_size = unicode::encoded_size(current_char.letter);
Assert(char_seq_pos + encoded_size - 1 < sizeof(char_seq));
unicode::encode(current_char.letter, &char_seq[char_seq_pos]);
char_seq_pos += encoded_size;
}
// Draw the final chunk of acumulated characters
// Add a 0 terminal character to make line a valid C string
Assert(char_seq_pos < sizeof(char_seq));
char_seq[char_seq_pos] = 0;
{
// Draw coloured text, and increment cariage position
int w = 0, h = 0;
brief_set_text_color(last_color);
gr_string(x + offset, y, char_seq, GR_RESIZE_MENU);
gr_get_string_size(&w, &h, char_seq);
offset += w;
}
}
{
// PART2: Draw leading bright white characters
char_seq_pos = 0;
for (size_t current_pos = truncate_len; current_pos < truncate_len + bright_len; current_pos++) {
auto encoded_size = unicode::encoded_size(src->at(current_pos).letter);
Assert(char_seq_pos + encoded_size - 1 < sizeof(char_seq));
unicode::encode(src->at(current_pos).letter, &char_seq[char_seq_pos]);
char_seq_pos += encoded_size;
}
Assert(char_seq_pos < (int) sizeof(char_seq));
char_seq[char_seq_pos] = 0;
gr_set_color_fast(&Color_bright_white);
gr_string(x + offset, y, char_seq, GR_RESIZE_MENU);
}
}
int brief_text_wipe_finished()
{
if ( Brief_text_wipe_time_elapsed > (BRIEF_TEXT_WIPE_TIME+0.5f) ) {
return 1;
}
return 0;
}
/**
* brief_render_text()
*
* @param line_offset
* @param x
* @param y
* @param h
* @param frametime time in seconds of previous frame
* @param instance optional parameter. Used to indicate which text stream is used. This value is 0 unless multiple text streams are required
* @param line_spacing
* @return
*/
int brief_render_text(int line_offset, int x, int y, int h, float frametime, int instance, int line_spacing)
{
int fh, line, yy;
fh = gr_get_font_height();
if (Brief_text_wipe_time_elapsed == 0) {
if (snd_is_playing(Brief_text_wipe_snd)) {
snd_stop(Brief_text_wipe_snd);
}
gamesnd_play_iface(InterfaceSounds::BRIEF_TEXT_WIPE);
Play_brief_voice = 1;
}
Brief_text_wipe_time_elapsed += frametime;
line = line_offset;
yy = 0;
while (yy + fh <= h) {
if (line >= Num_brief_text_lines[instance])
break;
brief_render_line(line, x, y + yy, instance);
line++;
yy += fh + line_spacing;
}
if ( brief_text_wipe_finished() && (Play_brief_voice) ) {
Play_brief_voice = 0;
return 1;
}
return 0;
}
/**
* Draw the lines that show objects positions on the grid
*
*/
void brief_render_elements(vec3d *pos, grid* gridp)
{
vec3d gpos; // Location of point on grid.
plane tplane;
vec3d *gv;
if ( pos->xyz.y < 1 && pos->xyz.y > -1 )
return;
tplane.A = gridp->gmatrix.vec.uvec.xyz.x;
tplane.B = gridp->gmatrix.vec.uvec.xyz.y;
tplane.C = gridp->gmatrix.vec.uvec.xyz.z;
tplane.D = gridp->planeD;
compute_point_on_plane(&gpos, &tplane, pos);
gv = &gridp->gmatrix.vec.uvec;
if (gv->xyz.x * pos->xyz.x + gv->xyz.y * pos->xyz.y + gv->xyz.z * pos->xyz.z < -gridp->planeD)
gr_set_color(127, 127, 127);
else
gr_set_color(255, 255, 255); // white
}
// ------------------------------------------------------------------------------------
// brief_reset_icons()
//
void brief_reset_icons(int stage_num)
{
brief_stage *bs;
brief_icon *bi;
int i;
Assert( Briefing != NULL );
bs = &Briefing->stages[stage_num];
for ( i = 0; i < bs->num_icons; i++ ) {
bi = &bs->icons[i];
bi->flags &= ~BI_SHOWHIGHLIGHT;
}
}
/**
* Direct camera to look at target
*
* @param pos target position for the camera
* @param orient target orientation for the camera
* @param time time in ms to reach target
*/
vec3d brief_get_current_cam_pos()
{
return Current_cam_pos;
}
matrix brief_get_current_cam_orient()
{
return Current_cam_orient;
}
void brief_set_camera_target(vec3d *pos, matrix *orient, int time)
{
float time_in_seconds;
time_in_seconds = time / 1000.0f;
if ( time == 0 ) {
Current_cam_pos = *pos;
Current_cam_orient = *orient;
}
Target_cam_pos = *pos;
Target_cam_orient = *orient;
Start_cam_orient = Current_cam_orient;
Start_cam_pos = Current_cam_pos; // we need this when checking if camera movement complete
Start_cam_move = timer_get_milliseconds()*1000.0f; // start time, convert to seconds
Total_move_time = time_in_seconds;
Elapsed_time = 0.0f;
vm_vec_scale_add(&Start_lookat_pos, &Start_cam_pos, &Start_cam_orient.vec.fvec, LOOKAT_DIST);
vm_vec_scale_add(&Target_lookat_pos, &Target_cam_pos, &Target_cam_orient.vec.fvec, LOOKAT_DIST);
Play_highlight_flag = 1; // once target reached, play highlight anims
Cam_target_reached = 0;
Cam_movement_done=0;
anim_release_all_instances(ON_BRIEFING_SELECT); // stop any briefing-specific anims
// calculate camera velocity
vm_vec_sub(&Cam_vel, pos, &Current_cam_pos);
if ( !IS_VEC_NULL_SQ_SAFE(&Cam_vel) ) {
vm_vec_normalize(&Cam_vel);
}
// calculate lookat point velocity
vm_vec_sub(&Lookat_vel, &Target_lookat_pos, &Current_lookat_pos);
vm_vec_scale(&Lookat_vel, 1.0f/time_in_seconds);
Start_dist = vm_vec_dist(&Start_cam_pos, &Start_lookat_pos);
End_dist = vm_vec_dist(&Target_cam_pos, &Target_lookat_pos);
Dist_change_rate = (End_dist - Start_dist) / time_in_seconds;
Total_dist=vm_vec_dist(&Start_cam_pos, &Target_cam_pos);
Peak_speed=Total_dist/Total_move_time*2.0f;
Cam_accel = 4*Total_dist/(Total_move_time*Total_move_time);
Last_dist=0.0f;
vm_vec_zero(&W_init);
get_camera_limits(&Start_cam_orient, &Target_cam_orient, Total_move_time, &Acc_limit, &Vel_limit);
}
void brief_cancel_pending_highlight_anims()
{
Play_highlight_flag = 0;
}
bool brief_verify_color_tag(unicode::codepoint_t color_tag)
{
if (color_tag > 127) {
// The tag will always be out of range. This is done to be sure that the case below does not accidentally
// produce a valid tag character.
SCP_string tag_str;
unicode::encode(color_tag, std::back_inserter(tag_str));
Warning(LOCATION, "Invalid text color tag '$%s' used in mission: '%s'.\n", tag_str.c_str(), Mission_filename);
return false;
}
char char_tag = (char)color_tag;
if ( Tagged_Colors.find(char_tag) == Tagged_Colors.end() ) {
Warning(LOCATION, "Invalid text color tag '$%c' used in mission: '%s'.\n", char_tag, Mission_filename);
return false;
}
return true;
}
void brief_set_text_color(char color_tag)
{
gr_set_color_fast(Tagged_Colors[color_tag]);
}
/**
* Checks if a character is a word separator
* @param character the character to be analysed.
* @return true when the given character is a word separator, and false when the character is part of a word.
*/
bool is_a_word_separator(unicode::codepoint_t character)
{
return character <= 32; // all control characters including space, newline, and tab
}
/**
* Builds a vector of colored characters from a string containing color markups
* and stores it to Colored_stream table.
*
* A color markup is made of a minimum of three characters:
* '$' + a char standing for a color + contigous multiple spaces (chars t n and ' ')
* The markup is completely removed from the resulting character sequence.
*
* @param src a not null pointer to a C string terminated by a /0 char.
* @param instance index into Colored_stream where the result should be placed. Value is 0 unless multiple text streams are required.
* @param[in,out] default_color_stack pointer to an array containing a stack of default colors (for color spans)
* @param[in,out] color_stack_index pointer to the current index in the above stack
* @return number of character of the resulting sequence.
*/
int brief_text_colorize(char *src, int instance, char default_color_stack[], int &color_stack_index)
{
Assert(src);
Assert((0 <= instance) && (instance < (int)(sizeof(Colored_stream) / sizeof(*Colored_stream))));
briefing_line dest_line; //the resulting vector of colored character
char active_color_index; //the current drawing color
active_color_index = default_color_stack[color_stack_index];
unicode::codepoint_range range(src);
auto end_iter = std::end(range);
for (auto iter = std::begin(range); iter != end_iter; ++iter)
{
// Is the character a color markup?
// text markup consists of a '$' plus a character plus an optional space
if (iter + 1 != end_iter && *iter == BRIEF_META_CHAR)
{
++iter; // Consume the $ character
// it's possible that there's a closing brace here
if (*iter == UNICODE_CHAR('}'))
{
if (color_stack_index > 0)
{
color_stack_index--;
active_color_index = default_color_stack[color_stack_index];
}
++iter; // consume the }
}
// breaking character
else if (*iter == UNICODE_CHAR('|'))
{
active_color_index = default_color_stack[color_stack_index];
++iter; // consume the |
}
// normal $c or $c{
else
{
if (brief_verify_color_tag(*iter)) {
active_color_index = (char)*iter;
}
++iter; // Consume the color identifier and focus on the white character (if any)
// special case: color spans (different default color within braces)
// (there's a slim chance that src[i] could be the null-terminator, but that's okay here)
if (*iter == UNICODE_CHAR('{'))
{
if (color_stack_index < HIGHEST_COLOR_STACK_INDEX)
{
color_stack_index++;
default_color_stack[color_stack_index] = active_color_index;
}
++iter; // consume the {
}
}
// Skip every whitespace until the next word is reached
while ( (iter != end_iter) && is_white_space(*iter) )
++iter;
//The next character is not a whitespace, let's process it as usual
//(subtract 1 because the for loop will add it again)
--iter;
continue;
}
// When the word is terminated, reset color to default
if ( (is_white_space(*iter) ) || ( is_a_word_separator(*iter) ))
active_color_index = default_color_stack[color_stack_index];
// Append the character to the result structure
colored_char dest;
dest.letter = *iter;
dest.color = active_color_index;
dest_line.push_back(dest);
}
Colored_stream[instance].push_back(dest_line);
return (int)dest_line.size();
}
/**
* Initialise briefing coloured text
*
* @param src paragraph of text to process
* @param w max width of line in pixels
* @param[in] default_color optional, default color for this text (defaults to '\0', which gets converted to the first defined color tag (should be 'w'))
* @param instance optional parameter, used when multiple text streams are required (default value is 0)
* @param max_lines maximum number of lines
* @param[in] append add on to the existing lines instead of replacing them (defaults to false)
*/
int brief_color_text_init(const char* src, int w, const char default_color, int instance, int max_lines, const bool append)
{
int i, n_lines, len;
SCP_vector<int> n_chars;
SCP_vector<const char*> p_str;
char tmp_brief_line[MAX_BRIEF_LINE_LEN];
// manage different default colors (don't use a SCP_ stack because eh)
char default_color_stack[HIGHEST_COLOR_STACK_INDEX + 1];
int color_stack_index = 0;
// start off with white, or whatever our default is
if (default_color == '\0' || !brief_verify_color_tag(default_color)) { // call brief_verify_color_tag() to make sure our default is actually a defined color tag
default_color_stack[0] = Color_Tags[0];
} else {
default_color_stack[0] = default_color;
}
Assert(src != NULL);
n_lines = split_str(src, w, n_chars, p_str, MAX_BRIEF_LINE_LEN, BRIEF_META_CHAR);
Assert(n_lines >= 0);
//for compatability reasons truncate text from everything except the fiction viewer
if ((max_lines > 0) && (n_lines > max_lines)) {
n_lines = max_lines;
}
if (!append) {
Max_briefing_line_len = 1;
Colored_stream[instance].clear();
} else if (n_lines == 0 && !strcmp(src, "\n")) { // Silly hack to allow inserting blank lines for debriefings -MageKing17
n_lines = 1;
p_str.push_back(0);
n_chars.push_back(0);
}
for (i=0; i<n_lines; i++) {
Assert(n_chars[i] < MAX_BRIEF_LINE_LEN);
strncpy(tmp_brief_line, p_str[i], n_chars[i]);
tmp_brief_line[n_chars[i]] = 0;
drop_leading_white_space(tmp_brief_line);
len = brief_text_colorize(&tmp_brief_line[0], instance, default_color_stack, color_stack_index);
if (len > Max_briefing_line_len)
Max_briefing_line_len = len;
}
Brief_text_wipe_time_elapsed = 0.0f;
Play_brief_voice = 0;
if (append) {
Num_brief_text_lines[instance] += n_lines;
} else {
Num_brief_text_lines[instance] = n_lines;
}
return n_lines;
}
/**
* Get free handle to a move icon
*
* @return -1 on failure, a handle to a free move icon struct on success
*/
int brief_get_free_move_icon()
{
int i;
for ( i = 0; i < MAX_MOVING_ICONS; i++ ) {
if ( Icon_movers[i].used == 0 )
break;
}
if ( i == MAX_MOVING_ICONS )
return -1;
Icon_movers[i].used = 1;
return i;
}
/**
* Set move list in briefing
*
* @param new_stage new stage number that briefing is now moving to
* @param current_stage current stage that the briefing is on
* @param time time in seconds
*/
int brief_set_move_list(int new_stage, int current_stage, float time)
{
brief_stage *newb, *cb;
icon_move_info *imi;
int i,j,k,num_movers,is_gone=0;
vec3d zero_v = ZERO_VECTOR;
Assert(new_stage != current_stage);
Assert( Briefing != NULL );
newb = &Briefing->stages[new_stage];
cb = &Briefing->stages[current_stage];
num_movers = 0;
for ( i = 0; i < cb->num_icons; i++ ) {
is_gone=1;
for ( j = 0; j < newb->num_icons; j++ ) {
if ( ( cb->icons[i].id != 0 ) && ( cb->icons[i].id == newb->icons[j].id ) ) {
is_gone=0;
if ( vm_vec_cmp(&cb->icons[i].pos, &newb->icons[j].pos) ) {
k = brief_get_free_move_icon();
if ( k == -1 ) {
Warning(LOCATION, "Too many briefing icons are moving simultaneously!");
return 0;
}
imi = &Icon_movers[k];
imi->id = cb->icons[i].id;
imi->start = cb->icons[i].pos;
imi->finish = newb->icons[j].pos;
imi->current = imi->start;
list_append(&Icon_move_list, imi);
imi->total_dist = vm_vec_dist(&imi->start, &imi->finish);
imi->total_move_time = time;
imi->peak_speed = imi->total_dist/imi->total_move_time*2.0f;
imi->accel = 4*imi->total_dist/(time*time);
imi->last_dist=0.0f;
imi->reached_dest=0;
imi->direction = zero_v;
vm_vec_sub(&imi->direction, &imi->finish, &imi->start);
if ( !IS_VEC_NULL_SQ_SAFE(&imi->direction) ) {
vm_vec_normalize(&imi->direction);
}
num_movers++;
}
}
}
// Set up fading icon (to fade out)
if (is_gone == 1) {
if ( Num_fade_icons >= MAX_FADING_ICONS ) {
Int3();
Num_fade_icons = 0;
}
briefing_icon_info *bii = brief_get_icon_info(&cb->icons[i]);
if (bii == NULL) {
return 0;
}
Fading_icons[Num_fade_icons].fade_anim = bii->fade;
Fading_icons[Num_fade_icons].pos = cb->icons[i].pos;
Fading_icons[Num_fade_icons].scale_factor = cb->icons[i].scale_factor;
Fading_icons[Num_fade_icons].mirror = (cb->icons[i].flags & BI_MIRROR_ICON) != 0;
Fading_icons[Num_fade_icons].team = cb->icons[i].team;
Num_fade_icons++;
}
}
// flag new icons for fading in
for ( i=0; i<newb->num_icons; i++ ) {
int is_new = 1;
newb->icons[i].flags &= ~BI_FADEIN;
for ( j=0; j<cb->num_icons; j++ ) {
if ( ( cb->icons[j].id != 0 ) && ( cb->icons[j].id == newb->icons[i].id ) ) {
is_new=0;
}
}
if ( is_new ) {
briefing_icon_info *bii = brief_get_icon_info(&newb->icons[i]);
if (bii == NULL) {
return 0;
}
newb->icons[i].flags |= BI_FADEIN;
newb->icons[i].fade_anim = bii->fade;
newb->icons[i].fade_anim.time_elapsed = 0.0f;
}
}
return num_movers;
}
void brief_clear_fade_out_icons()
{
Num_fade_icons = 0;
}
/**
* Set new stage in briefing
*
* @param pos target position for the camera
* @param orient target orientation for the camera
* @param time time in ms to reach target
* @param stage_num stage number of briefing (start numbering at 0)
*/
void brief_set_new_stage(vec3d *pos, matrix *orient, int time, int stage_num)
{
const char *msg;
int num_movers, new_time, not_objv = 1;
Assert( Briefing != NULL );
new_time = time;
if (stage_num >= Briefing->num_stages) {
not_objv = 0; // turns out this is an objectives stage
new_time = 0;
}
if ( stage_num == Last_new_stage ) {
return;
}
num_movers = 0;
brief_move_icon_reset();
brief_clear_fade_out_icons();
if ( (Last_new_stage != -1) && not_objv ) {
num_movers = brief_set_move_list(stage_num, Last_new_stage, new_time / 1000.0f);
}
if ( (Last_new_stage != -1) && (num_movers == 0) && not_objv ) {
if ( !vm_vec_cmp( &Briefing->stages[stage_num].camera_pos, &Briefing->stages[Last_new_stage].camera_pos) ) {
if ( !vm_vec_cmp( &Briefing->stages[stage_num].camera_orient.vec.fvec, &Briefing->stages[Last_new_stage].camera_orient.vec.fvec) ){
new_time = 0;
}
}
}
if (not_objv) {
msg = Briefing->stages[stage_num].text.c_str();
} else {
msg = XSTR( "Please review your objectives for this mission.", 395);
}
if (gr_screen.res == GR_640) {
// GR_640
Num_brief_text_lines[0] = brief_color_text_init(msg, MAX_BRIEF_LINE_W_640, default_briefing_color);
} else {
// GR_1024
Num_brief_text_lines[0] = brief_color_text_init(msg, MAX_BRIEF_LINE_W_1024, default_briefing_color);
}
Top_brief_text_line = 0;
if (not_objv){
brief_set_camera_target(pos, orient, new_time);
}
if ( snd_is_playing(Brief_stage_highlight_sound_handle) ) {
snd_stop(Brief_stage_highlight_sound_handle);
}
Voice_started_time = 0;
Voice_ended_time = 0;
Brief_stage_highlight_sound_handle = sound_handle::invalid();
Last_new_stage = stage_num;
}
/**
* Calculate how far the camera should have moved
*/
float brief_camera_get_dist_moved(float elapsed_time)
{
float time, dist_moved=0.0f;
// first half of movement
if ( elapsed_time < Total_move_time/2.0f ) {
dist_moved=0.5f*Cam_accel*elapsed_time*elapsed_time; // d = 1/2at^2
return dist_moved;
}
// second half of movement
time=elapsed_time - Total_move_time/2.0f;
dist_moved=(Total_dist/2.0f)+(Peak_speed*time) - 0.5f*Cam_accel*time*time;
return dist_moved;
}
/**
* Update the camera position
*/
void brief_camera_move(float frametime, int /*stage_num*/)
{
vec3d dist_moved;
float dist;
vec3d w_out;
matrix result;
Elapsed_time += frametime;
if ( Cam_target_reached )
return;
// Update orientation
if ( (Elapsed_time < Total_move_time) ) {
vm_angular_move_matrix(&Target_cam_orient, &Current_cam_orient, &W_init, frametime, &result, &w_out, &Vel_limit, &Acc_limit, false);
Current_cam_orient = result;
W_init = w_out;
}
// use absolute pos to update position
if ( vm_vec_cmp( &Current_cam_pos, &Target_cam_pos ) ) {
dist = brief_camera_get_dist_moved(Elapsed_time);
if ( dist < Last_dist ) {
Cam_movement_done=1;
Last_dist=0.0f;
}
Last_dist=dist;
if ( Cam_movement_done == 0 ) {
vm_vec_copy_scale(&dist_moved, &Cam_vel, dist);
vm_vec_add(&Current_cam_pos, &Start_cam_pos, &dist_moved);
} else {
Current_cam_pos=Target_cam_pos;
}
}
else {
Cam_movement_done=1;
Current_cam_pos=Target_cam_pos;
}
if ( Cam_movement_done && (Elapsed_time >= Total_move_time) ) {
Cam_target_reached=1;
}
}
/**
* Project the viewer's position onto the grid plane.
* If more than threshold distance from grid center, move grid center.
*/
void brief_maybe_create_new_grid(grid* gridp, vec3d *pos, matrix *orient, int force)
{
int roundoff;
plane tplane;
vec3d gpos, tmp, c;
float dist_to_plane;
float square_size, ux, uy, uz;
ux = tplane.A = gridp->gmatrix.vec.uvec.xyz.x;
uy = tplane.B = gridp->gmatrix.vec.uvec.xyz.y;
uz = tplane.C = gridp->gmatrix.vec.uvec.xyz.z;
tplane.D = gridp->planeD;
compute_point_on_plane(&c, &tplane, pos);
dist_to_plane = fl_abs(vm_dist_to_plane(pos, &gridp->gmatrix.vec.uvec, &c));
square_size = 1.0f;
while (dist_to_plane >= 25.0f)
{
square_size *= 10.0f;
dist_to_plane /= 10.0f;
}
if (fvi_ray_plane(&gpos, &gridp->center, &gridp->gmatrix.vec.uvec, pos, &orient->vec.fvec, 0.0f)<0.0f) {
vec3d p;
vm_vec_scale_add(&p,pos,&orient->vec.fvec, 100.0f );
compute_point_on_plane(&gpos, &tplane, &p );
}
if (vm_vec_dist(&gpos, &c) > 50.0f * square_size)
{
vm_vec_sub(&tmp, &gpos, &c);
vm_vec_normalize(&tmp);
vm_vec_scale_add(&gpos, &c, &tmp, 50.0f * square_size);
}
roundoff = (int) square_size * 10;
if (!ux)
gpos.xyz.x = fl_roundoff(gpos.xyz.x, roundoff);
if (!uy)
gpos.xyz.y = fl_roundoff(gpos.xyz.y, roundoff);
if (!uz)
gpos.xyz.z = fl_roundoff(gpos.xyz.z, roundoff);
if ((square_size != gridp->square_size) ||
(gpos.xyz.x != gridp->center.xyz.x) ||
(gpos.xyz.y != gridp->center.xyz.y) ||
(gpos.xyz.z != gridp->center.xyz.z) || force)
{
gridp->square_size = square_size;
gridp->center = gpos;
brief_modify_grid(gridp);
}
}
// Create a grid
// *forward is vector pointing forward
// *right is vector pointing right
// *center is center point of grid
// length is length of grid
// width is width of grid
// square_size is size of a grid square
// For example:
// *forward = (0.0, 0.0, 1.0)
// *right = (1.0, 0.0, 0.0)
// *center = (0.0, 0.0, 0.0)
// nrows = 10
// ncols = 50.0
// square_size = 10.0
// will generate a grid of squares 10 long by 5 wide.
// Each grid square will be 10.0 x 10.0 units.
// The center of the grid will be at the global origin.
// The grid will be parallel to the xz plane (because the normal is 0,1,0).
// (In fact, it will be the xz plane because it is centered on the origin.)
//
// Stuffs grid in *gridp. If gridp == NULL, mallocs and returns a grid.
grid *brief_create_grid(grid *gridp, vec3d *forward, vec3d *right, vec3d *center, int nrows, int ncols, float square_size)
{
int i, ncols2, nrows2, d = 1;
vec3d dfvec, drvec, cur, cur2, tvec, uvec, save, save2;
Assert(square_size > 0.0);
if (double_fine_gridlines)
d = 2;
if (gridp == NULL)
gridp = (grid *) vm_malloc(sizeof(grid));
Assert(gridp);
gridp->center = *center;
gridp->square_size = square_size;
// Create the plane equation.
Assert(!IS_VEC_NULL(forward));
Assert(!IS_VEC_NULL(right));
vm_vec_copy_normalize(&dfvec, forward);
vm_vec_copy_normalize(&drvec, right);
vm_vec_cross(&uvec, &dfvec, &drvec);
Assert(!IS_VEC_NULL(&uvec));
gridp->gmatrix.vec.uvec = uvec;
gridp->planeD = -(center->xyz.x * uvec.xyz.x + center->xyz.y * uvec.xyz.y + center->xyz.z * uvec.xyz.z);
Assert(!fl_is_nan(gridp->planeD));
gridp->gmatrix.vec.fvec = dfvec;
gridp->gmatrix.vec.rvec = drvec;
vm_vec_scale(&dfvec, square_size);
vm_vec_scale(&drvec, square_size);
vm_vec_scale_add(&cur, center, &dfvec, (float) -nrows * d / 2);
vm_vec_scale_add2(&cur, &drvec, (float) -ncols * d / 2);
vm_vec_scale_add(&cur2, center, &dfvec, (float) -nrows * 5 / 2);
vm_vec_scale_add2(&cur2, &drvec, (float) -ncols * 5 / 2);
save = cur;
save2 = cur2;
gridp->ncols = ncols;
gridp->nrows = nrows;
ncols2 = ncols / 2;
nrows2 = nrows / 2;
Assert(ncols < MAX_GRIDLINE_POINTS && nrows < MAX_GRIDLINE_POINTS);
// Create the points along the edges of the grid, so we can just draw lines
// between them to form the grid.
for (i=0; i<=ncols*d; i++) {
gridp->gpoints1[i] = cur; // small, dark gridline points
vm_vec_scale_add(&tvec, &cur, &dfvec, (float) nrows * d);
gridp->gpoints2[i] = tvec;
vm_vec_add2(&cur, &drvec);
}
for (i=0; i<=ncols2; i++) {
gridp->gpoints5[i] = cur2; // large, brighter gridline points
vm_vec_scale_add(&tvec, &cur2, &dfvec, (float) nrows2 * 10);
gridp->gpoints6[i] = tvec;
vm_vec_scale_add2(&cur2, &drvec, 10.0f);
}
cur = save;
cur2 = save2;
for (i=0; i<=nrows*d; i++) {
gridp->gpoints3[i] = cur; // small, dark gridline points
vm_vec_scale_add(&tvec, &cur, &drvec, (float) ncols * d);
gridp->gpoints4[i] = tvec;
vm_vec_add2(&cur, &dfvec);
}
for (i=0; i<=nrows2; i++) {
gridp->gpoints7[i] = cur2; // large, brighter gridline points
vm_vec_scale_add(&tvec, &cur2, &drvec, (float) ncols2 * 10);
gridp->gpoints8[i] = tvec;
vm_vec_scale_add2(&cur2, &dfvec, 10.0f);
}
return gridp;
}
/**
* Create a nice default grid.
* Centered at origin, 10x10, 10.0 size squares, in xz plane.
*/
grid *brief_create_default_grid(void)
{
grid *rgrid;
vec3d fvec, rvec, cvec;
vm_vec_make(&fvec, 0.0f, 0.0f, 1.0f);
vm_vec_make(&rvec, 1.0f, 0.0f, 0.0f);
vm_vec_make(&cvec, 0.0f, -10.0f, 0.0f);
rgrid = brief_create_grid(&Global_grid, &fvec, &rvec, &cvec, 100, 100, 5.0f);
physics_init(&rgrid->physics);
rgrid->physics.flags |= (PF_ACCELERATES | PF_SLIDE_ENABLED);
return rgrid;
}
/**
* Rotate and project points and draw a line.
*/
void brief_rpd_line(vec3d *v0, vec3d *v1, const color& clr)
{
vertex tv0, tv1;
g3_rotate_vertex(&tv0, v0);
g3_rotate_vertex(&tv1, v1);
gr_set_color_fast(&clr);
g3_draw_line(&tv0, &tv1);
}
/**
* Renders a grid
*
* @param gridp Grid defined in a grid struct to render
*/
void brief_render_grid(grid *gridp, const color& gridc)
{
int i, ncols, nrows;
ncols = gridp->ncols;
nrows = gridp->nrows;
if (double_fine_gridlines)
{
ncols *= 2;
nrows *= 2;
}
gr_set_color(30,30,30);
// Draw the column lines.
for (i=0; i<=ncols; i++)
brief_rpd_line(&gridp->gpoints1[i], &gridp->gpoints2[i], gridc);
// Draw the row lines.
for (i=0; i<=nrows; i++)
brief_rpd_line(&gridp->gpoints3[i], &gridp->gpoints4[i], gridc);
}
void brief_modify_grid(grid *gridp)
{
brief_create_grid(gridp, &gridp->gmatrix.vec.fvec, &gridp->gmatrix.vec.rvec, &gridp->center,
gridp->nrows, gridp->ncols, gridp->square_size);
}
void brief_unload_anims()
{
for (SCP_vector<briefing_icon_info>::iterator ii = Briefing_icon_info.begin(); ii != Briefing_icon_info.end(); ++ii)
{
if (ii->regular.first_frame >= 0)
{
bm_unload(ii->regular.first_frame);
ii->regular.first_frame = -1;
}
if (ii->fade.first_frame >= 0)
{
bm_unload(ii->fade.first_frame);
ii->fade.first_frame = -1;
}
if (ii->highlight.first_frame >= 0)
{
bm_unload(ii->highlight.first_frame);
ii->highlight.first_frame = -1;
}
}
}
void brief_common_close()
{
brief_unload_anims();
}
void brief_restart_text_wipe()
{
Voice_started_time = 0;
Voice_ended_time = 0;
Brief_text_wipe_time_elapsed = 0.0f;
}
/**
* Initialize the array of handles to the different voice streams
*/
void brief_voice_init()
{
int i;
for ( i = 0; i < MAX_BRIEF_STAGES; i++ ) {
Brief_voices[i] = -1;
}
}
void brief_load_voice_file(int voice_num, char *name)
{
int load_attempts = 0;
while(1) {
if ( load_attempts++ > 5 ) {
break;
}
Brief_voices[voice_num] = audiostream_open( name, ASF_VOICE );
if ( Brief_voices[voice_num] >= 0 ) {
break;
}
// Don't bother to ask for the CD in multiplayer
if ( Game_mode & GM_MULTIPLAYER ) {
break;
}
}
}
/**
* Open and pre-load the stream buffers for the different voice streams
*/
void brief_voice_load_all()
{
int i;
brief_stage *bs;
Assert( Briefing != NULL );
for ( i = 0; i < Briefing->num_stages; i++ ) {
bs = &Briefing->stages[i];
if ( strnicmp(bs->voice, NOX("none"), 4) != 0 ) {
brief_load_voice_file(i, bs->voice);
}
}
}
/**
* Close all the briefing voice streams
*/
void brief_voice_unload_all()
{
int i;
for ( i = 0; i < MAX_BRIEF_STAGES; i++ ) {
if ( Brief_voices[i] != -1 ) {
audiostream_close_file(Brief_voices[i], 0);
Brief_voices[i] = -1;
}
}
}
/**
* Start playback of the voice for a particular briefing stage
*/
void brief_voice_play(int stage_num)
{
if (!Voice_started_time) {
Voice_started_time = timer_get_milliseconds();
Voice_ended_time = 0;
}
if ( !Briefing_voice_enabled ) {
return;
}
if ( Brief_voices[stage_num] < 0 ) {
// play simulated speech?
if (fsspeech_play_from(FSSPEECH_FROM_BRIEFING)) {
if (fsspeech_playing()) {
return;
}
fsspeech_play(FSSPEECH_FROM_BRIEFING, Briefing->stages[stage_num].text.c_str());
}
} else {
if ( audiostream_is_playing( Brief_voices[stage_num]) ) {
return;
}
audiostream_play(Brief_voices[stage_num], Master_voice_volume, 0);
}
}
/**
* Stop playback of the voice for a particular briefing stage
*/
void brief_voice_stop(int stage_num)
{
if (Brief_voices[stage_num] < 0) {
fsspeech_stop();
} else {
audiostream_stop(Brief_voices[stage_num], 1, 0); // stream is automatically rewound
}
}
/**
* Pause playback of the voice for a particular briefing stage, to resume just
* call brief_voice_unpause() again
*/
void brief_voice_pause(int stage_num)
{
if (Brief_voices[stage_num] < 0) {
fsspeech_stop();
} else {
audiostream_pause(Brief_voices[stage_num]);
}
}
void brief_voice_unpause(int stage_num)
{
if (Brief_voices[stage_num] < 0) {
fsspeech_stop();
} else {
audiostream_unpause(Brief_voices[stage_num]);
}
}
void brief_reset_last_new_stage()
{
Last_new_stage = -1;
}
/**
* Get the dimensions for a briefing icon
*/
void brief_common_get_icon_dimensions(int *w, int *h, brief_icon *bi)
{
Assert(bi != NULL);
// in case anything goes wrong
*w=0;
*h=0;
briefing_icon_info *bii = brief_get_icon_info(bi);
if (bii == NULL) {
return;
}
if (bii->regular.first_frame >= 0) {
bm_get_info(bii->regular.first_frame, w, h, NULL);
}
}
void cmd_brief_reset()
{
int i, j;
static int inited = 0;
if (inited) {
for (i=0; i<MAX_TVT_TEAMS; i++) {
for (j=0; j<Cmd_briefs[i].num_stages; j++) {
Cmd_briefs[i].stage[j].text = "";
}
}
}
inited = 1;
for (i=0; i<MAX_TVT_TEAMS; i++)
Cmd_briefs[i].num_stages = 0;
}
/**
* Should briefing advance to the next stage?
*/
int brief_time_to_advance(int stage_num)
{
if (brief_get_closeup_icon() != NULL)
return 0;
if (Briefing_paused)
return 0;
if ( !Player->auto_advance )
return 0;
if (!brief_text_wipe_finished())
return 0;
if (Voice_ended_time && (timer_get_milliseconds() - Voice_ended_time >= STAGE_ADVANCE_DELAY))
return 1;
// check normal speech
if (Briefing_voice_enabled && (Brief_voices[stage_num] >= 0)) {
if (audiostream_is_playing(Brief_voices[stage_num])) {
return 0;
}
if (!Voice_ended_time) {
Voice_ended_time = timer_get_milliseconds();
}
return 0;
}
// check simulated speech
if (Briefing_voice_enabled && (Brief_voices[stage_num] < 0) && fsspeech_play_from(FSSPEECH_FROM_BRIEFING)) {
if (fsspeech_playing()) {
return 0;
}
if (!Voice_ended_time) {
Voice_ended_time = timer_get_milliseconds();
}
return 0;
}
// if we get here, there is no voice, so we simulate the time it would take instead
if (!Voice_ended_time)
Voice_ended_time = Voice_started_time + MAX(5000, Num_brief_text_lines[0] * 3500);
return 0;
}