Files
Goober5000 ef73e2572d unlimited mission titles
Some code refactoring to allow mission titles to be strings of any length.  Titles that are too long to actually display (based on string width) will be visibly truncated using `font::force_fit_string`, but the underlying title will not be affected.  Multiplayer is still subject to the 31-character length limit, so titles sent over the network will still have characters truncated.

Also removes some redundant calls to `gr_get_string_size`, since `font::force_fit_string` returns the fitted string width.
2026-05-12 11:31:20 -04:00

2614 lines
77 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 "cfile/cfile.h"
#include "gamehelp/contexthelp.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/eventmusic.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "globalincs/globals.h"
#include "graphics/font.h"
#include "iff_defs/iff_defs.h"
#include "io/key.h"
#include "io/timer.h"
#include "localization/localize.h"
#include "mission/missionbriefcommon.h"
#include "mission/missioncampaign.h"
#include "mission/missiongoals.h"
#include "mission/missiontraining.h"
#include "missionui/chatbox.h"
#include "missionui/missiondebrief.h"
#include "missionui/missionpause.h"
#include "missionui/missionscreencommon.h"
#include "network/multi.h"
#include "network/multi_campaign.h"
#include "network/multi_endgame.h"
#include "network/multi_kick.h"
#include "network/multi_pinfo.h"
#include "network/multimsgs.h"
#include "network/multiui.h"
#include "network/multiutil.h"
#include "osapi/osapi.h"
#include "parse/parselo.h"
#include "parse/sexp_container.h"
#include "pilotfile/pilotfile.h"
#include "playerman/player.h"
#include "popup/popup.h"
#include "ship/ship.h"
#include "sound/audiostr.h"
#include "sound/fsspeech.h"
#include "stats/medals.h"
#include "stats/stats.h"
#include "ui/uidefs.h"
#define MAX_TOTAL_DEBRIEF_LINES 200
#define TEXT_TYPE_NORMAL 1
#define TEXT_TYPE_RECOMMENDATION 2
#define DEBRIEF_NUM_STATS_PAGES 4
#define DEBRIEF_MISSION_STATS 0
#define DEBRIEF_MISSION_KILLS 1
#define DEBRIEF_ALLTIME_STATS 2
#define DEBRIEF_ALLTIME_KILLS 3
const int DEBRIEFING_FONT = font::FONT1;
extern float Brief_text_wipe_time_elapsed;
// 3rd coord is max width in pixels
int Debrief_title_coords[GR_NUM_RESOLUTIONS][3] = {
{ // GR_640
18, 118, 174
},
{ // GR_1024
28, 193, 280
}
};
int Debrief_text_wnd_coords[GR_NUM_RESOLUTIONS][4] = {
{ // GR_640
43, 140, 339, 303
},
{ // GR_1024
69, 224, 535, 485
}
};
int Debrief_text_x2[GR_NUM_RESOLUTIONS] = {
276, // GR_640
450 // GR_1024
};
int Debrief_stage_info_coords[GR_NUM_RESOLUTIONS][2] = {
{ // GR_640
379, 137
},
{ // GR_1024
578, 224
}
};
int Debrief_more_coords[GR_NUM_RESOLUTIONS][2] = {
{ // GR_640
323, 453
},
{ // GR_1024
323, 453
}
};
#define MULTI_LIST_TEAM_OFFSET 16
int Debrief_multi_list_team_max_display[GR_NUM_RESOLUTIONS] = {
9, // GR_640
12 // GR_1024
};
int Debrief_list_coords[GR_NUM_RESOLUTIONS][4] = {
{ // GR_640
416, 280, 195, 101
},
{ // GR_1024
666, 448, 312, 162
}
};
int Debrief_award_wnd_coords[GR_NUM_RESOLUTIONS][2] = {
{ // GR_640
411, 126
},
{ // GR_1024
658, 203
}
};
int Debrief_award_coords[GR_NUM_RESOLUTIONS][2] = {
{ // GR_640
416, 140
},
{ // GR_1024
666, 224
}
};
// 0=x, 1=y, 2=width of the field
int Debrief_medal_text_coords[GR_NUM_RESOLUTIONS][3] = {
{ // GR_640
423, 247, 189
},
{ // GR_1024
666, 333, 67
}
};
// 0=x, 1=y, 2=height of the field
int Debrief_award_text_coords[GR_NUM_RESOLUTIONS][3] = {
{ // GR_640
416, 210, 42
},
{ // GR_1024
666, 333, 67
}
};
// 0 = with medal
// 1 = without medal (text will use medal space)
#define DB_WITH_MEDAL 0
#define DB_WITHOUT_MEDAL 1
int Debrief_award_text_width[GR_NUM_RESOLUTIONS][2] = {
{ // GR_640
123, 203
},
{ // GR_1024
196, 312
}
};
const char *Debrief_single_name[GR_NUM_RESOLUTIONS] = {
"DebriefSingle", // GR_640
"2_DebriefSingle" // GR_1024
};
const char *Debrief_multi_name[GR_NUM_RESOLUTIONS] = {
"DebriefMulti", // GR_640
"2_DebriefMulti" // GR_1024
};
const char *Debrief_mask_name[GR_NUM_RESOLUTIONS] = {
"Debrief-m", // GR_640
"2_Debrief-m" // GR_1024
};
#define NUM_BUTTONS 18
#define NUM_TABS 2
#define DEBRIEF_TAB 0
#define STATS_TAB 1
#define TEXT_SCROLL_UP 2
#define TEXT_SCROLL_DOWN 3
#define REPLAY_MISSION 4
#define RECOMMENDATIONS 5
#define FIRST_STAGE 6
#define PREV_STAGE 7
#define NEXT_STAGE 8
#define LAST_STAGE 9
#define MULTI_PINFO_POPUP 10
#define MULTI_KICK 11
#define MEDALS_BUTTON 12
#define PLAYER_SCROLL_UP 13
#define PLAYER_SCROLL_DOWN 14
#define HELP_BUTTON 15
#define OPTIONS_BUTTON 16
#define ACCEPT_BUTTON 17
#define REPEAT 1
//XSTR:OFF
const char* Debrief_loading_bitmap_fname[GR_NUM_RESOLUTIONS] = {
"PleaseWait", // GR_640
"2_PleaseWait" // GR_1024
};
//XSTR:ON
typedef struct {
char text[NAME_LENGTH+1]; // name of ship type with a colon
int num; // how many ships of this type player has killed
} debrief_stats_kill_info;
typedef struct {
int net_player_index; // index into Net_players[] array
int rank_bitmap; // bitmap id for rank
char callsign[CALLSIGN_LEN];
} debrief_multi_list_info;
static ui_button_info Buttons[GR_NUM_RESOLUTIONS][NUM_BUTTONS] = {
{ // GR_640
ui_button_info("DB_00", 6, 1, 37, 7, 0), // debriefing
ui_button_info("DB_01", 6, 21, 37, 23, 1), // statistics
ui_button_info("DB_02", 1, 195, -1, -1, 2), // scroll stats up
ui_button_info("DB_03", 1, 236, -1, -1, 3), // scroll stats down
ui_button_info("DB_04", 1, 428, 49, 447, 4), // replay mission
ui_button_info("DB_05", 17, 459, 49, 464, 5), // recommendations
ui_button_info("DB_06", 323, 454, -1, -1, 6), // first page
ui_button_info("DB_07", 348, 454, -1, -1, 7), // prev page
ui_button_info("DB_08", 372, 454, -1, -1, 8), // next page
ui_button_info("DB_09", 396, 454, -1, -1, 9), // last page
ui_button_info("DB_10", 441, 384, 433, 413, 10), // pilot info
ui_button_info("DB_11", 511, 384, 510, 413, 11), // kick
ui_button_info("DB_12", 613, 226, -1, -1, 12), // medals
ui_button_info("DB_13", 615, 329, -1, -1, 13), // scroll pilots up
ui_button_info("DB_14", 615, 371, -1, -1, 14), // scroll pilots down
ui_button_info("DB_15", 538, 431, 500, 440, 15), // help
ui_button_info("DB_16", 538, 455, 479, 464, 16), // options
ui_button_info("DB_17", 573, 432, 572, 413, 17), // accept
},
{ // GR_1024
ui_button_info("2_DB_00", 10, 1, 59, 12, 0), // debriefing
ui_button_info("2_DB_01", 10, 33, 59, 37, 1), // statistics
ui_button_info("2_DB_02", 1, 312, -1, -1, 2), // scroll stats up
ui_button_info("2_DB_03", 1, 378, -1, -1, 3), // scroll stats down
ui_button_info("2_DB_04", 1, 685, 79, 715, 4), // replay mission
ui_button_info("2_DB_05", 28, 735, 79, 743, 5), // recommendations
ui_button_info("2_DB_06", 517, 726, -1, -1, 6), // first page
ui_button_info("2_DB_07", 556, 726, -1, -1, 7), // prev page
ui_button_info("2_DB_08", 595, 726, -1, -1, 8), // next page
ui_button_info("2_DB_09", 633, 726, -1, -1, 9), // last page
ui_button_info("2_DB_10", 706, 615, 700, 661, 10), // pilot info
ui_button_info("2_DB_11", 817, 615, 816, 661, 11), // kick
ui_button_info("2_DB_12", 981, 362, -1, -1, 12), // medals
ui_button_info("2_DB_13", 984, 526, -1, -1, 13), // scroll pilots up
ui_button_info("2_DB_14", 984, 594, -1, -1, 14), // scroll pilots down
ui_button_info("2_DB_15", 861, 689, 801, 705, 15), // help
ui_button_info("2_DB_16", 861, 728, 777, 744, 16), // options
ui_button_info("2_DB_17", 917, 692, 917, 692, 17), // accept
}
};
// text
#define NUM_DEBRIEF_TEXT 10
#define MP_TEXT_INDEX_1 4
#define MP_TEXT_INDEX_2 5
#define MP_TEXT_INDEX_3 6
UI_XSTR Debrief_strings[GR_NUM_RESOLUTIONS][NUM_DEBRIEF_TEXT] = {
{ // GR_640
{ "Debriefing", 804, 37, 7, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][DEBRIEF_TAB].button },
{ "Statistics", 1333, 37, 26, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][STATS_TAB].button },
{ "Replay Mission", 444, 49, 447, UI_XSTR_COLOR_PINK, -1, &Buttons[0][REPLAY_MISSION].button },
{ "Recommendations", 1334, 49, 464, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][RECOMMENDATIONS].button },
{ "Pilot", 1310, 433, 413, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][MULTI_PINFO_POPUP].button },
{ "Info", 1311, 433, 423, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][MULTI_PINFO_POPUP].button },
{ "Kick", 1266, 510, 413, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][MULTI_KICK].button },
{ "Help", 928, 500, 440, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][HELP_BUTTON].button },
{ "Options", 1036, 479, 464, UI_XSTR_COLOR_GREEN, -1, &Buttons[0][OPTIONS_BUTTON].button },
{ "Accept", 1035, 572, 413, UI_XSTR_COLOR_PINK, -1, &Buttons[0][ACCEPT_BUTTON].button },
},
{ // GR_1024
{ "Debriefing", 804, 59, 12, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][DEBRIEF_TAB].button },
{ "Statistics", 1333, 59, 47, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][STATS_TAB].button },
{ "Replay Mission", 444, 79, 715, UI_XSTR_COLOR_PINK, -1, &Buttons[1][REPLAY_MISSION].button },
{ "Recommendations", 1334, 79, 743, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][RECOMMENDATIONS].button },
{ "Pilot", 1310, 700, 661, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][MULTI_PINFO_POPUP].button },
{ "Info", 1311, 700, 679, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][MULTI_PINFO_POPUP].button },
{ "Kick", 1266, 816, 661, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][MULTI_KICK].button },
{ "Help", 928, 801, 705, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][HELP_BUTTON].button },
{ "Options", 1036, 780, 744, UI_XSTR_COLOR_GREEN, -1, &Buttons[1][OPTIONS_BUTTON].button },
{ "Accept", 1035, 917, 672, UI_XSTR_COLOR_PINK, -1, &Buttons[1][ACCEPT_BUTTON].button },
}
};
char Debrief_current_callsign[CALLSIGN_LEN+10];
static UI_WINDOW Debrief_ui_window;
static UI_BUTTON List_region;
static int Background_bitmap; // bitmap for the background of the debriefing
static int Award_bg_bitmap;
static int Debrief_multi_loading_bitmap;
static int Rank_bitmap;
static int Medal_bitmap;
static int Badge_bitmap;
int Promoted;
static int Debrief_accepted;
int Turned_traitor;
int Must_replay_mission;
static int Current_mode;
static int New_mode;
static int Recommend_active;
static int Award_active;
static int Text_offset;
static int Num_text_lines = 0;
static int Debrief_inited = 0;
static int New_stage;
static int Current_stage;
static int Num_stages;
static int Num_debrief_stages;
static int Stage_voice = -1;
static UI_TIMESTAMP Debrief_music_timeout;
static int Multi_list_size;
static int Multi_list_offset;
int Debrief_overlay_id;
int Debrief_multi_stages_loaded = 0;
int Debrief_multi_voice_loaded = 0;
// static int Debrief_voice_ask_for_cd;
// voice id's for debriefing text
static int Debrief_voices[MAX_DEBRIEF_STAGES];
#define DEBRIEF_VOICE_DELAY 400 // time to delay voice playback when a new stage starts
static UI_TIMESTAMP Debrief_cue_voice; // timestamp to cue the playback of the voice
static int Debrief_first_voice_flag = 1; // used to delay the first voice playback extra long
static int Debriefing_paused = 0;
int Max_debrief_Lines;
// pointer used for getting to debriefing information
debriefing Traitor_debriefing; // used when player is a traitor
// pointers to the active stages for this debriefing
static debrief_stage *Debrief_stages[MAX_DEBRIEF_STAGES];
debrief_stage Promotion_stage, Badge_stage;
static debrief_stats_kill_info *Debrief_stats_kills = NULL;
static debrief_multi_list_info Multi_list[MAX_PLAYERS];
int Multi_list_select;
// flag indicating if we should display info for the given player (in multiplayer)
int Debrief_should_show_popup = 1;
// already shown skip mission popup?
static int Debrief_skip_popup_already_shown = 0;
void debrief_text_init();
bool debrief_can_accept();
void debrief_kick_selected_player();
// promotion voice selection stuff
#define NUM_VOLITION_CAMPAIGNS 1
typedef struct {
char campaign_name[32];
int num_missions;
} v_campaign;
v_campaign Volition_campaigns[NUM_VOLITION_CAMPAIGNS] = {
{
BUILTIN_CAMPAIGN, // the only campaign for now, but this leaves room for a mission pack
35 // make sure this is equal to the number of missions you gave in the corresponding Debrief_promotion_voice_mapping
}
};
// data for which voice goes w/ which mission
typedef struct voice_map {
char mission_file[32];
int persona_index;
} voice_map;
voice_map Debrief_promotion_voice_mapping[NUM_VOLITION_CAMPAIGNS][MAX_CAMPAIGN_MISSIONS] = {
{ // FreeSpace2 campaign
{ "SM1-01.fs2", 1 },
{ "SM1-02.fs2", 1 },
{ "SM1-03.fs2", 1 },
{ "SM1-04.fs2", 2 },
{ "SM1-05.fs2", 2 },
{ "SM1-06.fs2", 2 },
{ "SM1-07.fs2", 2 },
{ "SM1-08.fs2", 3 },
{ "SM1-09.fs2", 3 },
{ "SM1-10.fs2", 3 },
{ "SM2-01.fs2", 6 },
{ "SM2-02.fs2", 6 },
{ "SM2-03.fs2", 6 },
{ "SM2-04.fs2", 7 },
{ "SM2-05.fs2", 7 },
{ "SM2-06.fs2", 7 },
{ "SM2-07.fs2", 8 },
{ "SM2-08.fs2", 8 },
{ "SM2-09.fs2", 8 },
{ "SM2-10.fs2", 8 },
{ "SM3-01.fs2", 8 },
{ "SM3-02.fs2", 8 },
{ "SM3-03.fs2", 8 },
{ "SM3-04.fs2", 8 },
{ "SM3-05.fs2", 8 },
{ "SM3-06.fs2", 9 },
{ "SM3-07.fs2", 9 },
{ "SM3-08.fs2", 9 },
{ "SM3-09.fs2", 9 },
{ "SM3-10.fs2", 9 }, // no debriefing for 3-10
{ "loop1-1.fs2", 4 },
{ "loop1-2.fs2", 4 },
{ "loop1-3.fs2", 5 },
{ "loop2-1.fs2", 4 },
{ "loop2-2.fs2", 4 }
}
};
static const char* Debrief_award_background[GR_NUM_RESOLUTIONS] = {
"DebriefAward",
"2_DebriefAward"
};
#define AWARD_TEXT_MAX_LINES 5
#define AWARD_TEXT_MAX_LINE_LENGTH 128
char Debrief_award_text[AWARD_TEXT_MAX_LINES][AWARD_TEXT_MAX_LINE_LENGTH];
int Debrief_award_text_num_lines = 0;
// prototypes, you know you love 'em
void debrief_add_award_text(const char *str);
void debrief_award_text_clear();
void debrief_replace_stage_text(debrief_stage &stage);
// functions
const char *debrief_tooltip_handler(const char *str)
{
if (!stricmp(str, NOX("@.Medal"))) {
if (Award_active){
return XSTR( "Medal", 435);
}
} else if (!stricmp(str, NOX("@.Rank"))) {
if (Award_active){
return XSTR( "Rank", 436);
}
} else if (!stricmp(str, NOX("@.Badge"))) {
if (Award_active){
return XSTR( "Badge", 437);
}
} else if (!stricmp(str, NOX("@Medal"))) {
if (Medal_bitmap >= 0){
return Medals[Player->stats.m_medal_earned].get_display_name();
}
} else if (!stricmp(str, NOX("@Rank"))) {
if (Rank_bitmap >= 0){
return Ranks[Promoted].name;
}
} else if (!stricmp(str, NOX("@Badge"))) {
if (Badge_bitmap >= 0){
return Medals[Player->stats.m_badge_earned.back()].get_display_name();
}
}
return NULL;
}
// initialize the array of handles to the different voice streams
void debrief_voice_init()
{
int i;
for (i=0; i<MAX_DEBRIEF_STAGES; i++) {
Debrief_voices[i] = -1;
}
}
void debrief_load_voice_file(int voice_num, char *name)
{
int load_attempts = 0;
while(1) {
if ( load_attempts++ > 5 ) {
break;
}
Debrief_voices[voice_num] = audiostream_open( name, ASF_VOICE );
if ( Debrief_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 debrief_voice_load_all()
{
int i;
// Debrief_voice_ask_for_cd = 1;
for ( i=0; i<Num_debrief_stages; i++ ) {
if ( strlen(Debrief_stages[i]->voice) <= 0 ) {
continue;
}
if ( strnicmp(Debrief_stages[i]->voice, NOX("none"), 4) != 0 ) {
debrief_load_voice_file(i, Debrief_stages[i]->voice);
// Debrief_voices[i] = audiostream_open(Debrief_stages[i]->voice, ASF_VOICE);
}
}
}
// close all the briefing voice streams
void debrief_voice_unload_all()
{
int i;
for ( i=0; i<MAX_DEBRIEF_STAGES; i++ ) {
if ( Debrief_voices[i] != -1 ) {
audiostream_close_file(Debrief_voices[i], 0);
Debrief_voices[i] = -1;
}
}
}
// start playback of the voice for a particular briefing stage
void debrief_voice_play()
{
if (!Briefing_voice_enabled || (Current_mode != DEBRIEF_TAB)){
return;
}
// no more stages? We are done then.
if (Stage_voice >= Num_debrief_stages){
return;
}
// if in delayed start, see if delay has elapsed and start voice if so
if (Debrief_cue_voice.isValid()) {
if (!ui_timestamp_elapsed(Debrief_cue_voice)){
return;
}
Stage_voice++; // move up to next voice
if ((Stage_voice < Num_debrief_stages) && (Debrief_voices[Stage_voice] >= 0)) {
audiostream_play(Debrief_voices[Stage_voice], Master_voice_volume, 0);
Debrief_cue_voice = UI_TIMESTAMP::invalid(); // indicate no longer in delayed start checking
}
return;
}
// see if voice is still playing. If so, do nothing yet.
if ((Stage_voice >= 0) && audiostream_is_playing(Debrief_voices[Stage_voice])){
return;
}
// set voice to play in a little while from now.
Debrief_cue_voice = ui_timestamp(DEBRIEF_VOICE_DELAY);
}
// stop playback of the voice for a particular briefing stage
void debrief_voice_stop()
{
if ((Stage_voice < 0) || (Stage_voice > Num_debrief_stages) || (Debrief_voices[Stage_voice] < 0))
return;
audiostream_stop(Debrief_voices[Stage_voice], 1, 0); // stream is automatically rewound
Stage_voice = -1;
fsspeech_stop();
}
extern int Briefing_music_handle;
void debrief_pause()
{
if (Debriefing_paused)
return;
Debriefing_paused = 1;
if (Briefing_music_handle >= 0) {
audiostream_pause(Briefing_music_handle);
}
if ((Stage_voice < 0) || (Stage_voice > Num_debrief_stages) || (Debrief_voices[Stage_voice] < 0))
return;
audiostream_pause(Debrief_voices[Stage_voice]);
fsspeech_pause(true);
}
void debrief_unpause()
{
if (!Debriefing_paused)
return;
Debriefing_paused = 1;
if (Briefing_music_handle >= 0) {
audiostream_unpause(Briefing_music_handle);
}
if ((Stage_voice < 0) || (Stage_voice > Num_debrief_stages) || (Debrief_voices[Stage_voice] < 0))
return;
audiostream_unpause(Debrief_voices[Stage_voice]);
fsspeech_pause(false);
}
// function to deal with inserting possible promition and badge stages into the debriefing
// on the clients
void debrief_multi_fixup_stages()
{
int i;
// possibly insert the badge stage first, them the promotion stage since they are
// inserted at the front of the debrief stages.
if ( Badge_bitmap >= 0 ) {
// move all stages forward one. Don't
for ( i = Num_debrief_stages; i > 0; i-- ) {
Debrief_stages[i] = Debrief_stages[i-1];
}
Debrief_stages[0] = &Badge_stage;
Num_debrief_stages++;
}
if ( Promoted >= 0) {
// move all stages forward one
for ( i = Num_debrief_stages; i > 0; i-- ) {
Debrief_stages[i] = Debrief_stages[i-1];
}
Debrief_stages[0] = &Promotion_stage;
Num_debrief_stages++;
}
}
// function called from multiplayer clients to set up the debriefing information for them
// (sent from the server).
void debrief_set_multi_clients( int stage_count, int active_stages[] )
{
int i;
// set up the right briefing for this guy
if(MULTI_TEAM){
Debriefing = &Debriefings[Net_player->p_info.team];
} else {
Debriefing = &Debriefings[0];
}
// see if this client was promoted -- if so, then add the first stage.
Num_debrief_stages = 0;
// set the pointers to the debriefings for this client
for (i = 0; i < stage_count; i++) {
Debrief_stages[Num_debrief_stages++] = &Debriefing->stages[active_stages[i]];
// in multi, debriefing text replacement must occur client-side
// update most recently added stage only, in case replacement has side effects
debrief_replace_stage_text(*Debrief_stages[Num_debrief_stages - 1]);
}
Debrief_multi_stages_loaded = 1;
}
// evaluate all stages for all teams. Server of a multiplayer game will have to send that
// information to all clients after leaving this screen.
void debrief_multi_server_stuff()
{
debriefing *debriefp;
int stage_active[MAX_TVT_TEAMS][MAX_DEBRIEF_STAGES], *stages[MAX_TVT_TEAMS];
int i, j, num_stages, stage_count[MAX_TVT_TEAMS];
memset( stage_active, 0, sizeof(stage_active) );
for (i=0; i<Num_teams; i++) {
debriefp = &Debriefings[i];
num_stages = 0;
stages[i] = stage_active[i];
for (j=0; j<debriefp->num_stages; j++) {
if ( eval_sexp(debriefp->stages[j].formula) ) {
stage_active[i][num_stages] = j;
num_stages++;
}
}
stage_count[i] = num_stages;
}
// if we're in campaign mode, evaluate campaign stuff
if (Netgame.campaign_mode == MP_CAMPAIGN) {
multi_campaign_eval_debrief();
}
// send the information to all clients.
send_debrief_info( stage_count, stages );
}
// --------------------------------------------------------------------------------------
// debrief_set_stages_and_multi_stuff()
//
// Set up the active stages for this debriefing
//
// returns: number of active debriefing stages
//
int debrief_set_stages_and_multi_stuff()
{
int i;
debriefing *debriefp;
if ( MULTIPLAYER_CLIENT ) {
return 0;
}
Num_debrief_stages = 0;
if ( Game_mode & GM_MULTIPLAYER ) {
debrief_multi_server_stuff();
}
// check to see if player is a traitor (looking at his team). If so, use the special
// traitor debriefing. Only done in single player
debriefp = Debriefing;
if ( !(Game_mode & GM_MULTIPLAYER) ) {
// although the no traitor flag check seems redundant it allows the mission designer to turn the traitor
// debriefing off before the mission ends and supply one themselves
if ((Player_ship->team == Iff_traitor) && !(The_mission.flags[Mission::Mission_Flags::No_traitor]))
debriefp = &Traitor_debriefing;
}
Num_debrief_stages = 0;
if (Promoted >= 0) {
Debrief_stages[Num_debrief_stages++] = &Promotion_stage;
}
if (Badge_bitmap >= 0) {
Debrief_stages[Num_debrief_stages++] = &Badge_stage;
}
for (i=0; i<debriefp->num_stages; i++) {
if (eval_sexp(debriefp->stages[i].formula) == SEXP_TRUE) {
// replacement in single-player and for host/server in multi
debrief_replace_stage_text(debriefp->stages[i]);
Debrief_stages[Num_debrief_stages++] = &debriefp->stages[i];
}
}
return Num_debrief_stages;
}
// init the buttons that are specific to the debriefing screen
void debrief_buttons_init()
{
ui_button_info *b;
int i;
for ( i=0; i<NUM_BUTTONS; i++ ) {
b = &Buttons[gr_screen.res][i];
b->button.create( &Debrief_ui_window, "", b->x, b->y, 60, 30, 0 /*b->flags & REPEAT*/, 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);
}
// add all xstrs
for(i=0; i<NUM_DEBRIEF_TEXT; i++){
// multiplayer specific text
if((i == MP_TEXT_INDEX_1) || (i == MP_TEXT_INDEX_2) || (i == MP_TEXT_INDEX_3)){
// only add if in multiplayer mode
if(Game_mode & GM_MULTIPLAYER){
Debrief_ui_window.add_XSTR(&Debrief_strings[gr_screen.res][i]);
}
}
// all other text
else {
Debrief_ui_window.add_XSTR(&Debrief_strings[gr_screen.res][i]);
}
}
// set up hotkeys for buttons so we draw the correct animation frame when a key is pressed
Buttons[gr_screen.res][NEXT_STAGE].button.set_hotkey(KEY_RIGHT);
Buttons[gr_screen.res][PREV_STAGE].button.set_hotkey(KEY_LEFT);
Buttons[gr_screen.res][LAST_STAGE].button.set_hotkey(KEY_SHIFTED | KEY_RIGHT);
Buttons[gr_screen.res][FIRST_STAGE].button.set_hotkey(KEY_SHIFTED | KEY_LEFT);
Buttons[gr_screen.res][TEXT_SCROLL_UP].button.set_hotkey(KEY_UP);
Buttons[gr_screen.res][TEXT_SCROLL_DOWN].button.set_hotkey(KEY_DOWN);
Buttons[gr_screen.res][ACCEPT_BUTTON].button.set_hotkey(KEY_CTRLED+KEY_ENTER);
// if in multiplayer, disable the button for all players except the host
// also disable for squad war matches
if(Game_mode & GM_MULTIPLAYER){
if((Netgame.type_flags & NG_TYPE_SW) || !(Net_player->flags & NETINFO_FLAG_GAME_HOST)){
Buttons[gr_screen.res][REPLAY_MISSION].button.disable();
}
}
}
// --------------------------------------------------------------------------------------
// debrief_ui_init()
//
void debrief_ui_init()
{
// init ship selection masks and buttons
common_set_interface_palette("DebriefPalette"); // set the interface palette
Debrief_ui_window.create( 0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0 );
Debrief_ui_window.set_mask_bmap(Debrief_mask_name[gr_screen.res]);
Debrief_ui_window.tooltip_handler = debrief_tooltip_handler;
debrief_buttons_init();
// load in help overlay bitmap
Debrief_overlay_id = help_overlay_get_index(DEBRIEFING_OVERLAY);
help_overlay_set_state(Debrief_overlay_id,gr_screen.res,0);
if ( Game_mode & GM_MULTIPLAYER ) {
// close down any old instances of the chatbox
chatbox_close();
// create the new one
chatbox_create();
List_region.create(&Debrief_ui_window, "", Debrief_list_coords[gr_screen.res][0], Debrief_list_coords[gr_screen.res][1], Debrief_list_coords[gr_screen.res][2], Debrief_list_coords[gr_screen.res][3], 0, 1);
List_region.hide();
}
Background_bitmap = mission_ui_background_load(Debriefing->background[gr_screen.res], Debrief_single_name[gr_screen.res], Debrief_multi_name[gr_screen.res]);
if ( Background_bitmap < 0 ) {
Warning(LOCATION, "Could not load the background bitmap for debrief screen");
}
Award_bg_bitmap = bm_load(Debrief_award_background[gr_screen.res]);
Debrief_multi_loading_bitmap = bm_load(Debrief_loading_bitmap_fname[gr_screen.res]);
}
// Goober5000
int debrief_find_persona_index()
{
int i, j;
if ((Campaign.current_mission >= 0) && (Campaign.missions[Campaign.current_mission].name))
{
// Goober5000 - first see if the campaign supplied a persona index
// (0 means use the Volition default)
if (Campaign.missions[Campaign.current_mission].debrief_persona_index != 0)
return Campaign.missions[Campaign.current_mission].debrief_persona_index;
// search through all official campaigns for our current campaign
for (i = 0; i < NUM_VOLITION_CAMPAIGNS; i++)
{
if (!stricmp(Campaign.filename, Volition_campaigns[i].campaign_name))
{
// now search through the mission filenames
for (j = 0; j < Volition_campaigns[i].num_missions; j++)
{
// found it!
if (!stricmp(Campaign.missions[Campaign.current_mission].name, Debrief_promotion_voice_mapping[i][j].mission_file))
{
return Debrief_promotion_voice_mapping[i][j].persona_index;
}
}
}
}
}
// not found
return -1;
}
// Goober5000
// V sez: "defaults to number 9 (Petrarch) for non-volition missions
// this is an ugly, nasty, hateful way of doing this, but it saves us changing the missions at this point"
void debrief_choose_voice(char *voice_dest, size_t buf_size, char *voice_base, int persona_index, int default_to_base = 0)
{
if (persona_index >= 0)
{
// get voice file, also check for too long base file names via the return value of snprintf
if (snprintf(voice_dest, buf_size, NOX("%d_%s"), persona_index, voice_base) < static_cast<int>(buf_size))
{
// if it exists, we're done
if (cf_exists_full_ext(voice_dest, CF_TYPE_VOICE_DEBRIEFINGS, NUM_AUDIO_EXT, audio_ext_list))
return;
}
}
// that didn't work, so use the default
// use the base file; don't prepend anything to it
if (default_to_base)
{
strncpy(voice_dest, voice_base, buf_size);
}
// default to Petrarch
else
{
strncpy(voice_dest, "9_", buf_size);
strncat(voice_dest, voice_base, buf_size-2);
}
// Ensure null terminator
voice_dest[buf_size-1] = '\0';
}
void debrief_choose_medal_variant(char *buf, int medal_earned, int zero_based_version_index)
{
Assert(buf != NULL && medal_earned >= 0 && zero_based_version_index >= 0);
// start with the regular file name, adapted for resolution
sprintf(buf, NOX("%s%s"), Resolution_prefixes[gr_screen.res], Medals[medal_earned].debrief_bitmap);
// if the medal has multiple versions, we may want to choose a specific bitmap
char *p = strstr(buf, "##");
if (p != NULL)
{
int version;
char number[8];
// possible to earn more than we have variants for
if (zero_based_version_index >= Medals[medal_earned].num_versions)
version = Medals[medal_earned].num_versions - 1;
else
version = zero_based_version_index;
// also, this is dumb
if (Medals[medal_earned].version_starts_at_1)
version++;
sprintf(number, NOX("%.2d"), version);
Assert(strlen(number) == 2);
strncpy(p, number, 2);
}
}
void debrief_award_init()
{
char buf[80];
Rank_bitmap = -1;
Medal_bitmap = -1;
Badge_bitmap = -1;
Promoted = -1;
// be sure there are no old award texts floating around
debrief_award_text_clear();
// handle medal earned
if ( Player->stats.m_medal_earned != -1 ) {
debrief_choose_medal_variant(buf, Player->stats.m_medal_earned, Player->stats.medal_counts[Player->stats.m_medal_earned] - 1);
Medal_bitmap = bm_load(buf);
debrief_add_award_text(Medals[Player->stats.m_medal_earned].get_display_name());
}
// handle promotions
if ( Player->stats.m_promotion_earned != -1 ) {
Promoted = Player->stats.m_promotion_earned;
debrief_choose_medal_variant(buf, Rank_medal_index, Promoted);
Rank_bitmap = bm_load(buf);
// see if we have a persona
int persona_index = The_mission.debriefing_persona;
// use persona-specific promotion text if it exists; otherwise, use default
if (Ranks[Promoted].promotion_text.find(persona_index) != Ranks[Promoted].promotion_text.end()) {
Promotion_stage.text = Ranks[Promoted].promotion_text[persona_index];
} else {
Promotion_stage.text = Ranks[Promoted].promotion_text[-1];
}
Promotion_stage.recommendation_text = "";
// choose appropriate promotion voice for this mission
debrief_choose_voice(Promotion_stage.voice, sizeof(Promotion_stage.voice), Ranks[Promoted].promotion_voice_base, persona_index);
debrief_add_award_text(get_rank_display_name(&Ranks[Promoted]).c_str());
}
// handle badge earned
// only grant badge if earned and allowed. (no_promotion really means no promotion and no badges)
if ( Player->stats.m_badge_earned.size() ) {
debrief_choose_medal_variant(buf, Player->stats.m_badge_earned.back(), Player->stats.medal_counts[Player->stats.m_badge_earned.back()] - 1);
Badge_bitmap = bm_load(buf);
// see if we have a persona
int persona_index = The_mission.debriefing_persona;
// use persona-specific badge text if it exists; otherwise, use default
if (Medals[Player->stats.m_badge_earned.back()].promotion_text.find(persona_index) != Medals[Player->stats.m_badge_earned.back()].promotion_text.end()) {
Badge_stage.text = Medals[Player->stats.m_badge_earned.back()].promotion_text[persona_index];
} else {
Badge_stage.text = Medals[Player->stats.m_badge_earned.back()].promotion_text[-1];
}
Badge_stage.recommendation_text = "";
// choose appropriate badge voice for this mission
debrief_choose_voice(Badge_stage.voice, sizeof(Badge_stage.voice), Medals[Player->stats.m_badge_earned.back()].voice_base, persona_index);
debrief_add_award_text(Medals[Player->stats.m_badge_earned.back()].get_display_name());
}
if ((Rank_bitmap >= 0) || (Medal_bitmap >= 0) || (Badge_bitmap >= 0)) {
Award_active = 1;
} else {
Award_active = 0;
}
}
// debrief_traitor_init() initializes local data which could be used if the player leaves the
// mission a traitor. The same debriefing always gets played
void debrief_traitor_init()
{
Debrief_accepted = 0;
Turned_traitor = Must_replay_mission = 0;
if (Player_ship->team == Iff_traitor) {
Turned_traitor = 1;
// if traitor, set up persona-specific traitor debriefing
auto stagep = &Traitor_debriefing.stages[0];
// see if we are using a traitor override
if (The_mission.traitor_override != nullptr) {
stagep->text = The_mission.traitor_override->text;
strcpy_s(stagep->voice, The_mission.traitor_override->voice_filename);
stagep->recommendation_text = The_mission.traitor_override->recommendation_text;
} else {
// see if we have a persona
int persona_index = The_mission.debriefing_persona;
// use persona-specific traitor text if it exists; otherwise, use default
if (Traitor.debriefing_text.find(persona_index) != Traitor.debriefing_text.end())
stagep->text = Traitor.debriefing_text[persona_index];
else
stagep->text = Traitor.debriefing_text[-1];
// choose appropriate traitor voice for this mission
debrief_choose_voice(stagep->voice, sizeof(stagep->voice), Traitor.traitor_voice_base, persona_index, 1);
stagep->recommendation_text = Traitor.recommendation_text;
}
}
if (!(Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_CAMPAIGN_MODE)) {
if (Campaign.next_mission == Campaign.current_mission){
Must_replay_mission = 1;
}
}
// disable the accept button if in single player and I am a traitor
if (Turned_traitor || Must_replay_mission) {
Buttons[gr_screen.res][ACCEPT_BUTTON].button.hide();
// kill off any stats
Player->flags &= ~PLAYER_FLAGS_PROMOTED;
scoring_level_init( &Player->stats );
}
}
// initialization for listing of players in game
void debrief_multi_list_init()
{
Multi_list_size = 0; // number of net players to choose from
Multi_list_offset = 0;
Multi_list_select = -1;
if ( !(Game_mode & GM_MULTIPLAYER) )
return;
debrief_rebuild_player_list();
// switch stats display to this newly selected player
set_player_stats(Multi_list[0].net_player_index);
strcpy_s(Debrief_current_callsign, Multi_list[0].callsign);
}
void debrief_multi_list_scroll_up()
{
// if we're at the beginning of the list, don't do anything
if(Multi_list_offset == 0){
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
return;
}
// otherwise scroll up
Multi_list_offset--;
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
}
void debrief_multi_list_scroll_down()
{
// if we can scroll down no further
if(Multi_list_size < Debrief_multi_list_team_max_display[gr_screen.res]){
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
return;
}
if((Multi_list_offset + Debrief_multi_list_team_max_display[gr_screen.res]) >= Multi_list_size){
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
return;
}
// otherwise scroll down
Multi_list_offset++;
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
}
// draw the connected net players
void debrief_multi_list_draw()
{
int y, z, font_height,idx;
char str[CALLSIGN_LEN+5];
net_player *np;
font_height = gr_get_font_height();
// if we currently have no item picked, pick a reasonable one
if((Multi_list_size >= 0) && (Multi_list_select == -1)){
// select the entry which corresponds to the local player
Multi_list_select = 0;
for(idx=0;idx<Multi_list_size;idx++){
if(Multi_list[idx].net_player_index == MY_NET_PLAYER_NUM){
Multi_list_select = idx;
// switch stats display to this newly selected player
set_player_stats(Multi_list[idx].net_player_index);
strcpy_s(Debrief_current_callsign, Multi_list[idx].callsign);
break;
}
}
}
// draw the list itself
y = 0;
z = Multi_list_offset;
while (y + font_height <= Debrief_list_coords[gr_screen.res][3]){
np = &Net_players[Multi_list[z].net_player_index];
if (z >= Multi_list_size){
break;
}
// set the proper text color for the highlight
if(np->flags & NETINFO_FLAG_GAME_HOST){
if(Multi_list_select == z){
gr_set_color_fast(&Color_text_active_hi);
} else {
gr_set_color_fast(&Color_bright);
}
} else {
if(Multi_list_select == z){
gr_set_color_fast(&Color_text_active);
} else {
gr_set_color_fast(&Color_text_normal);
}
}
// blit the proper indicator - skipping observers
if(!((np->flags & NETINFO_FLAG_OBSERVER) && !(np->flags & NETINFO_FLAG_OBS_PLAYER))){
if(Netgame.type_flags & NG_TYPE_TEAM){
// team 0
if(np->p_info.team == 0){
// draw his "selected" icon
if(((np->state == NETPLAYER_STATE_DEBRIEF_ACCEPT) || (np->state == NETPLAYER_STATE_DEBRIEF_REPLAY)) && (Multi_common_icons[MICON_TEAM0_SELECT] != -1)){
gr_set_bitmap(Multi_common_icons[MICON_TEAM0_SELECT]);
gr_bitmap(Debrief_list_coords[gr_screen.res][0], Debrief_list_coords[gr_screen.res][1] + y - 2, GR_RESIZE_MENU);
}
// draw his "normal" icon
else if(Multi_common_icons[MICON_TEAM0] != -1){
gr_set_bitmap(Multi_common_icons[MICON_TEAM0]);
gr_bitmap(Debrief_list_coords[gr_screen.res][0], Debrief_list_coords[gr_screen.res][1] + y - 2, GR_RESIZE_MENU);
}
} else if(np->p_info.team == 1){
// draw his "selected" icon
if(((np->state == NETPLAYER_STATE_DEBRIEF_ACCEPT) || (np->state == NETPLAYER_STATE_DEBRIEF_REPLAY)) && (Multi_common_icons[MICON_TEAM1_SELECT] != -1)){
gr_set_bitmap(Multi_common_icons[MICON_TEAM1_SELECT]);
gr_bitmap(Debrief_list_coords[gr_screen.res][0], Debrief_list_coords[gr_screen.res][1] + y - 2, GR_RESIZE_MENU);
}
// draw his "normal" icon
else if(Multi_common_icons[MICON_TEAM1] != -1){
gr_set_bitmap(Multi_common_icons[MICON_TEAM1]);
gr_bitmap(Debrief_list_coords[gr_screen.res][0], Debrief_list_coords[gr_screen.res][1] + y - 2, GR_RESIZE_MENU);
}
}
} else {
// draw the team 0 selected icon
if(((np->state == NETPLAYER_STATE_DEBRIEF_ACCEPT) || (np->state == NETPLAYER_STATE_DEBRIEF_REPLAY)) && (Multi_common_icons[MICON_TEAM0_SELECT] != -1)){
gr_set_bitmap(Multi_common_icons[MICON_TEAM0_SELECT]);
gr_bitmap(Debrief_list_coords[gr_screen.res][0], Debrief_list_coords[gr_screen.res][1] + y - 2, GR_RESIZE_MENU);
}
}
}
strcpy_s(str,Multi_list[z].callsign);
if(Net_players[Multi_list[z].net_player_index].flags & NETINFO_FLAG_OBSERVER && !(Net_players[Multi_list[z].net_player_index].flags & NETINFO_FLAG_OBS_PLAYER)){
strcat_s(str,XSTR( "(O)", 438));
}
// bli
gr_string(Debrief_list_coords[gr_screen.res][0] + MULTI_LIST_TEAM_OFFSET, Debrief_list_coords[gr_screen.res][1] + y, str, GR_RESIZE_MENU);
y += font_height;
z++;
}
}
void debrief_kick_selected_player()
{
if(Multi_list_select >= 0){
Assert(Net_player->flags & NETINFO_FLAG_GAME_HOST);
multi_kick_player(Multi_list[Multi_list_select].net_player_index);
}
}
// get optional mission popup text
void debrief_assemble_optional_mission_popup_text(char *buffer, char *mission_loop_desc)
{
Assert(buffer != NULL);
// base message
if (mission_loop_desc == NULL) {
strcpy(buffer, XSTR("<No Mission Loop Description Available>", 1490));
mprintf(("No mission loop description available\n"));
} else {
strcpy(buffer, mission_loop_desc);
}
strcat(buffer, XSTR("\n\n\nDo you want to play the optional mission?", 1491));
}
bool debrief_can_accept()
{
return !(/*Cheats_enabled ||*/ Turned_traitor || Must_replay_mission);
}
// what to do when the accept button is hit
void debrief_accept(int ok_to_post_start_game_event, bool API_Access)
{
int go_loop = 0;
//Skip this bit if we're in API mode because the API handles these popups differently
if ( !debrief_can_accept() && (Game_mode & GM_CAMPAIGN_MODE) && !API_Access) {
const char *str;
int z;
if (Game_mode & GM_MULTIPLAYER) {
return;
}
if (Player_ship->team == Iff_traitor){
str = XSTR( "Your career is over, Traitor! You can't accept new missions!", 439);
}/* else if (Cheats_enabled) {
str = XSTR( "You are a cheater. You cannot accept this mission!", 440);
}*/ else {
str = XSTR( "You have failed this mission and cannot accept. What do you you wish to do instead?", 441);
}
z = popup(0, 3, XSTR( "Return to &Debriefing", 442), XSTR( "Go to &Flight Deck", 443), XSTR( "&Replay Mission", 444), str);
if (z == 2){
gameseq_post_event(GS_EVENT_START_GAME); // restart mission
} else if ( z == 1 ) {
gameseq_post_event(GS_EVENT_END_GAME); // return to main hall, tossing stats
}
return;
}
Debrief_accepted = 1;
// save mission stats
if (Game_mode & GM_MULTIPLAYER) {
// note that multi_debrief_accept_hit() will handle all mission_campaign_* calls on its own
// as well as doing stats transfers, etc.
multi_debrief_accept_hit();
} else {
int play_commit_sound = 1;
// only write the player's stats if he's accepted
// if we are just playing a single mission, then don't do many of the things
// that need to be done. Nothing much should happen when just playing a single
// mission that isn't in a campaign.
if ( Game_mode & GM_CAMPAIGN_MODE ) {
mission_campaign_store_variables(SEXP_VARIABLE_SAVE_ON_MISSION_PROGRESS, false);
mission_campaign_store_containers(ContainerType::SAVE_ON_MISSION_PROGRESS, false);
// check for possible mission loop
// check for (1) mission loop available, (2) don't have to repeat last mission
if(!(Game_mode & GM_MULTIPLAYER)){
int cur = Campaign.current_mission;
bool require_repeat_mission = (Campaign.current_mission == Campaign.next_mission);
if (Campaign.missions[cur].flags & CMISSION_FLAG_HAS_LOOP) {
Assert(Campaign.loop_mission != CAMPAIGN_LOOP_MISSION_UNINITIALIZED);
}
if ( (Campaign.missions[cur].flags & CMISSION_FLAG_HAS_LOOP) && (Campaign.loop_mission != -1) && !require_repeat_mission ) {
/*
char buffer[512];
debrief_assemble_optional_mission_popup_text(buffer, Campaign.missions[cur].mission_loop_desc);
int choice = popup(0 , 2, POPUP_NO, POPUP_YES, buffer);
if (choice == 1) {
Campaign.loop_enabled = 1;
Campaign.next_mission = Campaign.loop_mission;
}
*/
go_loop = 1;
}
}
// loopy loopy time
if (go_loop && ok_to_post_start_game_event) {
gameseq_post_event( GS_EVENT_LOOP_BRIEF );
}
// continue as normal
else {
// end the mission
// if we can loop, but don't want to right now, then setup so that we can later
mission_campaign_mission_over( (go_loop) ? false : true );
// check to see if we are out of the loop now
if ( Campaign.next_mission == Campaign.loop_reentry ) {
Campaign.loop_enabled = 0;
}
// check if campaign is over, or if FREDer wants the mainhall
if ( Campaign.next_mission == -1 || (The_mission.flags[Mission::Mission_Flags::End_to_mainhall]) ) {
gameseq_post_event(GS_EVENT_MAIN_MENU);
} else {
if ( ok_to_post_start_game_event ) {
gameseq_post_event(GS_EVENT_START_GAME);
} else {
play_commit_sound = 0;
}
}
}
} else {
gameseq_post_event(GS_EVENT_MAIN_MENU);
}
// Goober5000
if ( play_commit_sound && !(The_mission.flags[Mission::Mission_Flags::Toggle_debriefing])) {
gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED);
}
game_flush();
}
}
void debrief_next_tab()
{
New_mode = Current_mode + 1;
if (New_mode >= NUM_TABS)
New_mode = 0;
}
void debrief_prev_tab()
{
New_mode = Current_mode - 1;
if (New_mode < 0)
New_mode = NUM_TABS - 1;
}
// --------------------------------------------------------------------------------------
// debrief_next_stage()
//
void debrief_next_stage()
{
if (Current_stage < Num_stages - 1) {
New_stage = Current_stage + 1;
gamesnd_play_iface(InterfaceSounds::BRIEF_STAGE_CHG);
} else
gamesnd_play_iface(InterfaceSounds::BRIEF_STAGE_CHG_FAIL);
}
// --------------------------------------------------------------------------------------
// debrief_prev_stage()
//
void debrief_prev_stage()
{
if (Current_stage) {
New_stage = Current_stage - 1;
gamesnd_play_iface(InterfaceSounds::BRIEF_STAGE_CHG);
} else
gamesnd_play_iface(InterfaceSounds::BRIEF_STAGE_CHG_FAIL);
}
// --------------------------------------------------------------------------------------
// debrief_first_stage()
void debrief_first_stage()
{
if (Current_stage) {
New_stage = 0;
gamesnd_play_iface(InterfaceSounds::BRIEF_STAGE_CHG);
} else
gamesnd_play_iface(InterfaceSounds::BRIEF_STAGE_CHG_FAIL);
}
// --------------------------------------------------------------------------------------
// debrief_last_stage()
void debrief_last_stage()
{
if (Current_stage != Num_stages - 1) {
New_stage = Num_stages - 1;
gamesnd_play_iface(InterfaceSounds::BRIEF_STAGE_CHG);
} else
gamesnd_play_iface(InterfaceSounds::BRIEF_STAGE_CHG_FAIL);
}
// draw what stage number the debriefing is on
void debrief_render_stagenum()
{
int w;
char buf[64];
if (Num_stages < 2)
return;
sprintf(buf, XSTR( "%d of %d", 445), Current_stage + 1, Num_stages);
gr_get_string_size(&w, NULL, buf);
gr_set_color_fast(&Color_bright_blue);
gr_string(Debrief_stage_info_coords[gr_screen.res][0] - w, Debrief_stage_info_coords[gr_screen.res][1], buf, GR_RESIZE_MENU);
gr_set_color_fast(&Color_white);
}
// render the mission difficulty at the specified y location
void debrief_render_mission_difficulty(int y_loc)
{
gr_string(0, y_loc, XSTR( "Skill Level", 1509), GR_RESIZE_MENU);
gr_string(Debrief_text_x2[gr_screen.res], y_loc, Skill_level_names(Game_skill_level), GR_RESIZE_MENU);
}
// render the mission time at the specified y location
void debrief_render_mission_time(int y_loc)
{
char time_str[30];
game_format_time(Missiontime, time_str);
gr_string(0, y_loc, XSTR( "Mission Time", 446), GR_RESIZE_MENU);
gr_string(Debrief_text_x2[gr_screen.res], y_loc, time_str, GR_RESIZE_MENU);
}
// render out the stats info to the scroll window
//
void debrief_stats_render()
{
int i, y, font_height;
gr_set_color_fast(&Color_blue);
gr_set_clip(Debrief_text_wnd_coords[gr_screen.res][0], Debrief_text_wnd_coords[gr_screen.res][1], Debrief_text_wnd_coords[gr_screen.res][2], Debrief_text_wnd_coords[gr_screen.res][3], GR_RESIZE_MENU);
gr_string(0, 0, Debrief_current_callsign, GR_RESIZE_MENU);
font_height = gr_get_font_height();
y = 30;
gr_set_color_fast(&Color_white);
debrief_render_mission_difficulty(y);
y += 2*font_height;
switch ( Current_stage ) {
case DEBRIEF_MISSION_STATS:
// display mission completion time
debrief_render_mission_time(y);
y += 2*font_height;
show_stats_label(StatsType::MISSION_STATS, 0, y, font_height);
show_stats_numbers(StatsType::MISSION_STATS, Debrief_text_x2[gr_screen.res], y, font_height);
break;
case DEBRIEF_ALLTIME_STATS:
show_stats_label(StatsType::ALL_TIME_EVER_STATS, 0, y, font_height);
show_stats_numbers(StatsType::ALL_TIME_EVER_STATS, Debrief_text_x2[gr_screen.res], y, font_height);
break;
case DEBRIEF_ALLTIME_KILLS:
case DEBRIEF_MISSION_KILLS:
i = Text_offset;
while (y + font_height <= Debrief_text_wnd_coords[gr_screen.res][3]) {
if (i >= Num_text_lines)
break;
if (!i) {
if ( Current_stage == DEBRIEF_MISSION_KILLS )
gr_printf_menu(0, y, "%s", XSTR( "Mission Kills by Ship Type", 447));
else
gr_printf_menu(0, y, "%s", XSTR( "All-time Kills by Ship Type", 448));
} else if (i > 1) {
//Assert: Was debrief_setup_ship_kill_stats called?
Assert(Debrief_stats_kills != NULL);
gr_printf_menu(0, y, "%s", Debrief_stats_kills[i - 2].text);
gr_printf_menu(Debrief_text_x2[gr_screen.res], y, "%d", Debrief_stats_kills[i - 2].num);
}
y += font_height;
i++;
}
if (Num_text_lines == 2) {
if ( Current_stage == DEBRIEF_MISSION_KILLS )
gr_printf_menu(0, y, "%s", XSTR( "(No ship kills this mission)", 449));
else
gr_printf_menu(0, y, "%s", XSTR( "(No ship kills)", 450));
}
break;
default:
Int3();
break;
}
gr_reset_clip();
}
// do action for when the replay button is pressed
void debrief_replay_pressed()
{
if (!Turned_traitor && !Must_replay_mission && (Game_mode & GM_CAMPAIGN_MODE)) {
int choice;
choice = popup(0, 2, POPUP_CANCEL, XSTR( "&Replay", 451), XSTR( "If you choose to replay this mission, you will be required to complete it again before proceeding to future missions.\n\nIn addition, any statistics gathered during this mission will be discarded if you choose to replay.", 452));
if (choice != 1){
return;
}
}
gameseq_post_event(GS_EVENT_START_GAME); // restart mission
gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED);
}
// -------------------------------------------------------------------
// debrief_redraw_pressed_buttons()
//
// Redraw any debriefing buttons that are pressed down. This function is needed
// since we sometimes need to draw pressed buttons last to ensure the entire
// button gets drawn (and not overlapped by other buttons)
//
void debrief_redraw_pressed_buttons()
{
int i;
UI_BUTTON *b;
for ( i=0; i<NUM_BUTTONS; i++ ) {
b = &Buttons[gr_screen.res][i].button;
// don't draw the recommendations button if we're in stats mode
if ( b->button_down() ) {
b->draw_forced(2);
}
}
}
// debrief specific button with hotspot 'i' has been pressed, so perform the associated action
//
void debrief_button_pressed(int num)
{
switch (num) {
case DEBRIEF_TAB:
Buttons[gr_screen.res][RECOMMENDATIONS].button.enable();
// Debrief_ui_window.use_hack_to_get_around_stupid_problem_flag = 0;
if (num != Current_mode){
gamesnd_play_iface(InterfaceSounds::SCREEN_MODE_PRESSED);
}
New_mode = num;
break;
case STATS_TAB:
// Debrief_ui_window.use_hack_to_get_around_stupid_problem_flag = 1; // allows failure sound to be played
Buttons[gr_screen.res][RECOMMENDATIONS].button.disable();
if (num != Current_mode){
gamesnd_play_iface(InterfaceSounds::SCREEN_MODE_PRESSED);
}
New_mode = num;
break;
case TEXT_SCROLL_UP:
if (Text_offset) {
Text_offset--;
gamesnd_play_iface(InterfaceSounds::SCROLL);
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
break;
case TEXT_SCROLL_DOWN:
if (Max_debrief_Lines < (Num_text_lines - Text_offset)) {
Text_offset++;
gamesnd_play_iface(InterfaceSounds::SCROLL);
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
break;
case REPLAY_MISSION:
if(Game_mode & GM_MULTIPLAYER){
multi_debrief_replay_hit();
} else {
debrief_replay_pressed();
}
break;
case RECOMMENDATIONS:
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
Recommend_active = !Recommend_active;
debrief_text_init();
break;
case FIRST_STAGE:
debrief_first_stage();
break;
case PREV_STAGE:
debrief_prev_stage();
break;
case NEXT_STAGE:
debrief_next_stage();
break;
case LAST_STAGE:
debrief_last_stage();
break;
case HELP_BUTTON:
gamesnd_play_iface(InterfaceSounds::HELP_PRESSED);
launch_context_help();
break;
case OPTIONS_BUTTON:
gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
gameseq_post_event( GS_EVENT_OPTIONS_MENU );
break;
case ACCEPT_BUTTON:
debrief_accept();
break;
case MEDALS_BUTTON:
gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
gameseq_post_event(GS_EVENT_VIEW_MEDALS);
break;
case PLAYER_SCROLL_UP:
debrief_multi_list_scroll_up();
break;
case PLAYER_SCROLL_DOWN:
debrief_multi_list_scroll_down();
break;
case MULTI_PINFO_POPUP:
Debrief_should_show_popup = 1;
break;
case MULTI_KICK:
debrief_kick_selected_player();
break;
} // end swtich
}
void debrief_setup_ship_kill_stats(int /*stage_num*/)
{
int i;
debrief_stats_kill_info *kill_info;
Assert(Current_stage < DEBRIEF_NUM_STATS_PAGES);
if ( Current_stage == DEBRIEF_MISSION_STATS || Current_stage == DEBRIEF_ALLTIME_STATS )
return;
if(Debrief_stats_kills == NULL)
{
Debrief_stats_kills = new debrief_stats_kill_info[Ship_info.size()];
}
auto stats_type = ( Current_stage == DEBRIEF_MISSION_KILLS ) ? StatsType::MISSION_STATS : StatsType::ALL_TIME_EVER_STATS;
// wookieejedi - Show kills by ship type, but if using display name
// then consolidate values for similarly named entries
std::map<SCP_string, int, SCP_string_lcase_less_than> kill_map;
Num_text_lines = 0;
i = 0;
for ( auto it = Ship_info.begin(); it != Ship_info.end(); i++, ++it ) {
int num_kills = stats_get_kills(stats_type, i);
// code used to add in mission kills, but the new system assumes that the player will accept, so
// all time stats already have mission stats added in.
if ( num_kills <= 0 ){
continue;
}
// wookieejedi - consolidate by display name or ship class name
const char* name_key = it->get_display_name();
kill_map[name_key] += num_kills;
}
// wookieejedi - now copy into Debrief_stats_kills[] for display
for (const auto& [name, count] : kill_map) {
kill_info = &Debrief_stats_kills[Num_text_lines++];
kill_info->num = count;
strcpy_s(kill_info->text, name.c_str());
strcat_s(kill_info->text, NOX(":"));
}
Num_text_lines += 2;
}
// Iterate through the debriefing buttons, checking if they are pressed
void debrief_check_buttons()
{
int i;
for ( i=0; i<NUM_BUTTONS; i++ ) {
if ( Buttons[gr_screen.res][i].button.pressed() ) {
debrief_button_pressed(i);
}
}
int y, z;
if ( !(Game_mode & GM_MULTIPLAYER) )
return;
if (List_region.pressed()) {
List_region.get_mouse_pos(NULL, &y);
z = Multi_list_offset + y / gr_get_font_height();
if ((z >= 0) && (z < Multi_list_size)) {
// switch stats display to this newly selected player
set_player_stats(Multi_list[z].net_player_index);
strcpy_s(Debrief_current_callsign, Multi_list[z].callsign);
Multi_list_select = z;
debrief_setup_ship_kill_stats(Current_stage);
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
}
}
// if the player was double clicked on - we should popup a player info popup
/*
if (List_region.double_clicked()) {
Debrief_should_show_popup = 1;
}
*/
}
// setup the debriefing text lines for rendering
void debrief_text_init()
{
int r_count = 0;
const char *src;
int i;
// If no wav files are being used use speech simulation
bool use_sim_speech = true;
for (i = 0; i < MAX_DEBRIEF_STAGES; i++) {
if(Debrief_voices[i] != -1) {
use_sim_speech = false;
break;
}
}
// Make sure that the text wrapping and the rendering code use the same font
font::set_font(DEBRIEFING_FONT);
Num_text_lines = Text_offset = brief_color_text_init("", Debrief_text_wnd_coords[gr_screen.res][2], default_debriefing_color, 0, 0); // Initialize color stuff -MageKing17
fsspeech_start_buffer();
if (Current_mode == DEBRIEF_TAB) {
for (i=0; i<Num_debrief_stages; i++) {
if (i)
// add a blank line between stages
Num_text_lines += brief_color_text_init("\n", Debrief_text_wnd_coords[gr_screen.res][2], default_debriefing_color, 0, MAX_DEBRIEF_LINES, true);
src = Debrief_stages[i]->text.c_str();
if (*src) {
Num_text_lines += brief_color_text_init(src, Debrief_text_wnd_coords[gr_screen.res][2], default_debriefing_color, 0, MAX_DEBRIEF_LINES, true);
if (use_sim_speech && !Recommend_active) {
fsspeech_stuff_buffer(src);
fsspeech_stuff_buffer("\n");
}
}
if (Recommend_active) {
src = Debrief_stages[i]->recommendation_text.c_str();
if ((i == Num_debrief_stages - 1) && !r_count && !*src)
src = XSTR( "We have no recommendations for you.", 1054);
if (*src) {
Num_text_lines += brief_color_text_init("\n", Debrief_text_wnd_coords[gr_screen.res][2], default_recommendation_color, 0, MAX_DEBRIEF_LINES, true);
Num_text_lines += brief_color_text_init(src, Debrief_text_wnd_coords[gr_screen.res][2], default_recommendation_color, 0, MAX_DEBRIEF_LINES, true);
r_count++;
if (use_sim_speech) {
fsspeech_stuff_buffer(src);
fsspeech_stuff_buffer("\n");
}
}
}
}
Brief_text_wipe_time_elapsed = BRIEF_TEXT_WIPE_TIME; // Skip the wipe effect
if(use_sim_speech) {
fsspeech_play_buffer(FSSPEECH_FROM_BRIEFING);
}
return;
}
// not in debriefing mode, must be in stats mode
Num_text_lines = 0;
debrief_setup_ship_kill_stats(Current_stage);
}
//Move this into its own method to allow easier API access - Mjn
int debrief_select_music()
{
if (Turned_traitor || ((Game_mode & GM_CAMPAIGN_MODE) && (Campaign.next_mission == Campaign.current_mission))) {
// you failed the mission, so you get the fail music
return SCORE_DEBRIEFING_FAILURE;
} else if (mission_goals_met()) {
// you completed all primaries and secondaries, so you get the win music
return SCORE_DEBRIEFING_SUCCESS;
} else {
// you somehow passed the mission, so you get a little something for your efforts.
return SCORE_DEBRIEFING_AVERAGE;
}
}
// --------------------------------------------------------------------------------------
//
// start up the appropriate music
static void debrief_init_music()
{
Debrief_music_timeout = UI_TIMESTAMP::invalid();
int score = debrief_select_music();
// if multi client then give a slight delay before playing average music
// since we'd like to eval the goals once more for slow clients
if ( MULTIPLAYER_CLIENT && (score == SCORE_DEBRIEFING_AVERAGE) ) {
Debrief_music_timeout = ui_timestamp(2000);
return;
}
common_music_init(score);
}
void debrief_init(bool API_Access)
{
Assert(!Debrief_inited);
// Campaign.loop_enabled = 0;
Campaign.loop_mission = CAMPAIGN_LOOP_MISSION_UNINITIALIZED;
// MageKing17 - Set the font so that wordwrapping in brief_color_text_init() calculates based on the same font as the debriefing itself.
font::set_font(DEBRIEFING_FONT);
// set up the right briefing for this guy
if(MULTI_TEAM){
Debriefing = &Debriefings[Net_player->p_info.team];
} else {
Debriefing = &Debriefings[0];
}
// no longer is mission
Game_mode &= ~(GM_IN_MISSION);
game_flush();
Current_mode = -1;
New_mode = DEBRIEF_TAB;
Recommend_active = Award_active = 0;
Current_stage = -1;
New_stage = 0;
Debrief_cue_voice = UI_TIMESTAMP::invalid();
Num_text_lines = 0;
Debrief_first_voice_flag = 1;
Debrief_multi_voice_loaded = 0;
if ( (Game_mode & GM_CAMPAIGN_MODE) && ( !MULTIPLAYER_CLIENT ) ) {
// MUST store goals and events first - may be used to evaluate next mission
// store goals and events
mission_campaign_store_goals_and_events();
// evaluate next mission
mission_campaign_eval_next_mission();
}
// call traitor init before calling scoring_level_close. traitor init will essentially nullify
// any stats
if ( !(Game_mode & GM_MULTIPLAYER) ) { // only do for single player
debrief_traitor_init(); // initialize data needed if player becomes traitor.
}
// call scoring level close for my stats. Needed for award_init. The stats will
// be backed out if used chooses to replace them.
scoring_level_close();
if (!API_Access) {
debrief_ui_init(); // init UI items
}
debrief_award_init();
show_stats_init();
debrief_voice_init();
debrief_multi_list_init();
// rank_bitmaps_clear();
// rank_bitmaps_load();
strcpy_s(Debrief_current_callsign, Player->callsign);
// Only setup Debrief_stages and Recommendations if not running through API access.
if (!API_Access) {
// set up the Debrief_stages[] and Recommendations[] arrays. Only do the following stuff
// for non-clients (i.e. single and game server). Multiplayer clients will get their debriefing
// info directly from the server.
if (!MULTIPLAYER_CLIENT) {
debrief_set_stages_and_multi_stuff();
if (Num_debrief_stages <= 0) {
Num_debrief_stages = 0;
} else if (!API_Access) { // Do not load voice files in API mode -Mjn
debrief_voice_load_all();
}
} else {
// multiplayer client may have already received their debriefing info. If they have not,
// then set the num debrief stages to 0
if (!Debrief_multi_stages_loaded) {
Num_debrief_stages = 0;
}
}
}
/*
if (mission_evaluate_primary_goals() == PRIMARY_GOALS_COMPLETE) {
common_music_init(SCORE_DEBRIEFING_SUCCESS);
} else {
common_music_init(SCORE_DEBRIEFING_FAILURE);
}
*/
// Just calculate this once instead of every frame. -MageKing17
Max_debrief_Lines = Debrief_text_wnd_coords[gr_screen.res][3]/gr_get_font_height(); //Make the max number of lines dependent on the font height.
// start up the appropriate music only if we're not in API mode - Mjn
if (!API_Access) {
debrief_init_music();
}
if ((Game_mode & GM_CAMPAIGN_MODE) && (Campaign.next_mission == Campaign.current_mission)) {
// Better luck next time; increase retries.
Player->failures_this_session++;
} else {
// Clear retry count regardless of whether or not the player accepts.
Player->failures_this_session = 0;
}
if (Game_mode & GM_MULTIPLAYER) {
multi_debrief_init();
// if i'm not the host of the game, disable the multi kick button
if (!API_Access && !(Net_player->flags & NETINFO_FLAG_GAME_HOST)) {
Buttons[gr_screen.res][MULTI_KICK].button.disable();
}
} else {
if (!API_Access) {
Buttons[gr_screen.res][PLAYER_SCROLL_UP].button.disable();
Buttons[gr_screen.res][PLAYER_SCROLL_DOWN].button.disable();
Buttons[gr_screen.res][MULTI_PINFO_POPUP].button.disable();
Buttons[gr_screen.res][MULTI_KICK].button.disable();
Buttons[gr_screen.res][PLAYER_SCROLL_UP].button.hide();
Buttons[gr_screen.res][PLAYER_SCROLL_DOWN].button.hide();
Buttons[gr_screen.res][MULTI_PINFO_POPUP].button.hide();
Buttons[gr_screen.res][MULTI_KICK].button.hide();
}
}
if (!API_Access && !Award_active) {
Buttons[gr_screen.res][MEDALS_BUTTON].button.disable();
Buttons[gr_screen.res][MEDALS_BUTTON].button.hide();
}
Debrief_skip_popup_already_shown = 0;
Debrief_inited = 1;
}
// --------------------------------------------------------------------------------------
// debrief_close()
void debrief_close(bool API_Access)
{
int i;
scoring_struct *sc;
Assert(Debrief_inited);
// if the mission wasn't accepted, clear out my stats
// we need to evaluate a little differently for multiplayer since the conditions for "accepting"
// are a little bit different
if (Game_mode & GM_MULTIPLAYER) {
// if stats weren't accepted, backout my own stats
if (multi_debrief_stats_accept_code() != 1) {
if(MULTIPLAYER_MASTER){
for(i=0; i<MAX_PLAYERS; i++){
if(MULTI_CONNECTED(Net_players[i]) && !MULTI_STANDALONE(Net_players[i]) && !MULTI_PERM_OBSERVER(Net_players[i]) && (Net_players[i].m_player != NULL)){
sc = &Net_players[i].m_player->stats;
scoring_backout_accept(sc);
if (Net_player == &Net_players[i]) {
Pilot.update_stats_backout( sc, (The_mission.game_type == MISSION_TYPE_TRAINING) );
}
}
}
} else {
scoring_backout_accept( &Player->stats );
Pilot.update_stats_backout( &Player->stats, (The_mission.game_type == MISSION_TYPE_TRAINING) );
}
}
} else {
// single player
if( !Debrief_accepted || !(Game_mode & GM_CAMPAIGN_MODE) ){
// Make sure we don't skip any missions in a campaign.
if ( Game_mode & GM_CAMPAIGN_MODE ) {
Campaign.next_mission = Campaign.current_mission;
}
scoring_backout_accept( &Player->stats );
Pilot.update_stats_backout( &Player->stats, (The_mission.game_type == MISSION_TYPE_TRAINING) );
}
}
// if dude passed the misson and accepted, reset his show skip popup flag
if (Debrief_accepted) {
Player->show_skip_popup = 1;
}
// clear out debrief info parsed from mission file - taylor
mission_debrief_common_reset();
// clear out award text
Debrief_award_text_num_lines = 0;
//These don't need to be handled when accessing through the API -Mjn
if (!API_Access) {
debrief_voice_unload_all();
common_music_close();
chatbox_close();
}
// unload bitmaps
// Not used by the API
if (!API_Access && Background_bitmap >= 0) {
bm_release(Background_bitmap);
}
// Not used by the API
if (!API_Access && Award_bg_bitmap >= 0) {
bm_release(Award_bg_bitmap);
}
if (Rank_bitmap >= 0){
bm_release(Rank_bitmap);
}
if (Medal_bitmap >= 0){
bm_release(Medal_bitmap);
}
if (Badge_bitmap >= 0){
bm_release(Badge_bitmap);
}
if (!API_Access) {
Debrief_ui_window.destroy();
common_free_interface_palette(); // restore game palette
}
show_stats_close();
if (Game_mode & GM_MULTIPLAYER){
multi_debrief_close();
}
if(Debrief_stats_kills != NULL)
{
delete[] Debrief_stats_kills;
Debrief_stats_kills = NULL;
}
game_flush();
Stage_voice = -1;
Debriefing_paused = 0;
Debrief_inited = 0;
}
// handle keypresses in debriefing
void debrief_do_keys(int new_k)
{
switch (new_k) {
case KEY_TAB:
debrief_next_tab();
break;
case KEY_SHIFTED | KEY_TAB:
debrief_prev_tab();
break;
case KEY_ESC: {
int pf_flags;
int choice;
// multiplayer accept popup is a little bit different
if (Game_mode & GM_MULTIPLAYER) {
multi_debrief_esc_hit();
} else {
// display the normal debrief popup
if (!Turned_traitor && !Must_replay_mission && (Game_mode & GM_CAMPAIGN_MODE)) {
pf_flags = PF_BODY_BIG; // | PF_USE_AFFIRMATIVE_ICON | PF_USE_NEGATIVE_ICON;
choice = popup(pf_flags, 3, POPUP_CANCEL, XSTR( "&Yes", 454), XSTR( "&No, retry later", 455), XSTR( "Accept this mission outcome?", 456));
if (choice == 1) { // accept and continue on
debrief_accept(0);
gameseq_post_event(GS_EVENT_MAIN_MENU);
}
if (choice < 1)
break;
} else if ( ( Turned_traitor || Must_replay_mission ) && (Game_mode & GM_CAMPAIGN_MODE)) {
// need to popup saying that mission was a failure and must be replayed
choice = popup(0, 2, POPUP_NO, POPUP_YES, XSTR( "Because this mission was a failure, you must replay this mission when you continue your campaign.\n\nReturn to the Flight Deck?", 457));
if (choice <= 0) {
break;
}
}
// Return to Main Hall
gameseq_post_event(GS_EVENT_END_GAME);
}
}
default:
break;
} // end switch
}
// uuuuuugly
void debrief_draw_award_text()
{
int start_y, curr_y, i, x, sw;
int fh = gr_get_font_height();
int field_width = (Medal_bitmap > 0) ? Debrief_award_text_width[gr_screen.res][DB_WITH_MEDAL] : Debrief_award_text_width[gr_screen.res][DB_WITHOUT_MEDAL];
// vertically centered within field
start_y = Debrief_award_text_coords[gr_screen.res][1] + ((Debrief_award_text_coords[gr_screen.res][2] - (fh * Debrief_award_text_num_lines)) / 2);
curr_y = start_y;
// draw the strings
for (i=0; i<Debrief_award_text_num_lines && i < AWARD_TEXT_MAX_LINES; i++) {
gr_get_string_size(&sw, NULL, Debrief_award_text[i]);
x = (Medal_bitmap < 0) ? (Debrief_award_text_coords[gr_screen.res][0] + (field_width - sw) / 2) : Debrief_award_text_coords[gr_screen.res][0];
if (i==AWARD_TEXT_MAX_LINES-1) x += 7; // hack because of the shape of the box
gr_set_color_fast(&Color_white);
gr_string(x, curr_y, Debrief_award_text[i], GR_RESIZE_MENU);
// adjust y pos, including a little extra between the "pairs"
curr_y += fh;
if ((i == 1) || (i == 3)) {
curr_y += ((gr_screen.res == GR_640) ? 2 : 6);
}
}
}
// clears out text array so we don't have old award text showing up on new awards.
void debrief_award_text_clear() {
int i;
Debrief_award_text_num_lines = 0;
for (i=0; i<AWARD_TEXT_MAX_LINES; i++) {
//Debrief_award_text[i][0] = 0;
memset(Debrief_award_text[i], 0, sizeof(char)*AWARD_TEXT_MAX_LINE_LENGTH);
}
}
// this is the nastiest code I have ever written. if you are modifying this, i feel bad for you.
void debrief_add_award_text(const char *str)
{
Assert(Debrief_award_text_num_lines < AWARD_TEXT_MAX_LINES);
if (Debrief_award_text_num_lines >= AWARD_TEXT_MAX_LINES) {
return;
}
char *line2;
int field_width = (Medal_bitmap > 0) ? Debrief_award_text_width[gr_screen.res][DB_WITH_MEDAL] : Debrief_award_text_width[gr_screen.res][DB_WITHOUT_MEDAL];
// copy in the line
strcpy_s(Debrief_award_text[Debrief_award_text_num_lines], str);
if (!Disable_built_in_translations) {
// maybe translate for displaying
if (Lcl_gr) {
lcl_translate_medal_name_gr(Debrief_award_text[Debrief_award_text_num_lines]);
} else if (Lcl_pl) {
lcl_translate_medal_name_pl(Debrief_award_text[Debrief_award_text_num_lines]);
}
}
Debrief_award_text_num_lines++;
// if its too long, split once ONLY
// assumes text isnt > 2 lines, but this is a safe assumption due to the char limits of the ranks/badges/etc
if (Debrief_award_text_num_lines < AWARD_TEXT_MAX_LINES) {
line2 = split_str_once(Debrief_award_text[Debrief_award_text_num_lines-1], field_width);
if (line2 != NULL) {
sprintf(Debrief_award_text[Debrief_award_text_num_lines], " %s", line2); // indent a space
}
Debrief_award_text_num_lines++; // leave blank line even if it all fits into 1
}
}
// called once per frame to drive all the input reading and rendering
void debrief_do_frame(float frametime)
{
int k=0, new_k=0;
const char *please_wait_str = XSTR("Please Wait", 1242);
const size_t max_buf_len = 255;
char buf[max_buf_len+1];
Assert(Debrief_inited);
// Goober5000 - accept immediately if skipping debriefing
if (The_mission.flags[Mission::Mission_Flags::Toggle_debriefing])
{
// make sure that we can actually advance - we don't want an endless loop!!!
if ( !((/*Cheats_enabled ||*/ Turned_traitor || Must_replay_mission) && (Game_mode & GM_CAMPAIGN_MODE)) )
{
debrief_accept();
return;
}
}
int str_w, str_h;
// first thing is to load the files
if ( MULTIPLAYER_CLIENT && !Debrief_multi_stages_loaded ) {
// draw the background, etc
GR_MAYBE_CLEAR_RES(Background_bitmap);
if (Background_bitmap >= 0) {
gr_set_bitmap(Background_bitmap);
gr_bitmap(0, 0, GR_RESIZE_MENU);
}
Debrief_ui_window.draw();
chatbox_render();
if ( Debrief_multi_loading_bitmap > -1 ){
gr_set_bitmap(Debrief_multi_loading_bitmap);
gr_bitmap( Please_wait_coords[gr_screen.res][0], Please_wait_coords[gr_screen.res][1], GR_RESIZE_MENU );
}
// draw "Please Wait"
gr_set_color_fast(&Color_normal);
font::set_font(font::FONT2);
gr_get_string_size(&str_w, &str_h, please_wait_str);
gr_string((gr_screen.max_w_unscaled - str_w) / 2, (gr_screen.max_h_unscaled - str_h) / 2, please_wait_str, GR_RESIZE_MENU);
font::set_font(font::FONT1);
gr_flip();
// make sure we run the debrief do frame
if (Game_mode & GM_MULTIPLAYER) {
multi_debrief_do_frame();
}
// esc pressed?
os_poll();
int keypress = game_check_key();
if(keypress == KEY_ESC){
// popup to leave
multi_quit_game(PROMPT_CLIENT);
}
return;
}
// if multiplayer client, and not loaded voice, then load it
if ( MULTIPLAYER_CLIENT && !Debrief_multi_voice_loaded ) {
debrief_multi_fixup_stages();
debrief_voice_load_all();
Debrief_multi_voice_loaded = 1;
}
if ( help_overlay_active(Debrief_overlay_id) ) {
Buttons[gr_screen.res][HELP_BUTTON].button.reset_status();
Debrief_ui_window.set_ignore_gadgets(1);
}
k = chatbox_process();
if ( Game_mode & GM_NORMAL ) {
new_k = Debrief_ui_window.process(k);
} else {
new_k = Debrief_ui_window.process(k, 0);
}
if ( (k > 0) || (new_k > 0) || B1_JUST_RELEASED ) {
if ( help_overlay_active(Debrief_overlay_id) ) {
help_overlay_set_state(Debrief_overlay_id, gr_screen.res, 0);
Debrief_ui_window.set_ignore_gadgets(0);
k = 0;
new_k = 0;
}
}
if ( !help_overlay_active(Debrief_overlay_id) ) {
Debrief_ui_window.set_ignore_gadgets(0);
}
// don't show pilot info popup by default
Debrief_should_show_popup = 0;
// see if the mode has changed and handle it if so.
if ( Current_mode != New_mode ) {
debrief_voice_stop();
Current_mode = New_mode;
Current_stage = -1;
New_stage = 0;
if (New_mode == DEBRIEF_TAB) {
Num_stages = 1;
Debrief_cue_voice = UI_TIMESTAMP::invalid();
Stage_voice = -1;
if (Debrief_first_voice_flag) {
Debrief_cue_voice = ui_timestamp(DEBRIEF_VOICE_DELAY * 3);
Debrief_first_voice_flag = 0;
}
} else {
Num_stages = DEBRIEF_NUM_STATS_PAGES;
}
}
if ((Num_stages > 0) && (New_stage != Current_stage)) {
Current_stage = New_stage;
debrief_text_init();
}
debrief_voice_play();
// multi clients get a slight delay before music start to check goals again
if ( Debrief_music_timeout.isValid() ) {
if ( ui_timestamp_elapsed(Debrief_music_timeout) ) {
Debrief_music_timeout = UI_TIMESTAMP::invalid();
if ( mission_goals_met() ) {
common_music_init(SCORE_DEBRIEFING_SUCCESS);
} else {
common_music_init(SCORE_DEBRIEFING_AVERAGE);
}
common_music_do();
}
} else {
common_music_do();
}
if (Game_mode & GM_MULTIPLAYER) {
multi_debrief_do_frame();
}
// Now do all the rendering for the frame
GR_MAYBE_CLEAR_RES(Background_bitmap);
if (Background_bitmap >= 0) {
gr_set_bitmap(Background_bitmap);
gr_bitmap(0, 0, GR_RESIZE_MENU);
}
// draw the awarded stuff, G
if ( Award_active && (Award_bg_bitmap >= 0) ) {
gr_set_bitmap(Award_bg_bitmap);
gr_bitmap(Debrief_award_wnd_coords[gr_screen.res][0], Debrief_award_wnd_coords[gr_screen.res][1], GR_RESIZE_MENU);
if (Rank_bitmap >= 0) {
gr_set_bitmap(Rank_bitmap);
gr_bitmap(Debrief_award_coords[gr_screen.res][0], Debrief_award_coords[gr_screen.res][1], GR_RESIZE_MENU);
}
if (Medal_bitmap >= 0) {
gr_set_bitmap(Medal_bitmap);
gr_bitmap(Debrief_award_coords[gr_screen.res][0], Debrief_award_coords[gr_screen.res][1], GR_RESIZE_MENU);
}
if (Badge_bitmap >= 0) {
gr_set_bitmap(Badge_bitmap);
gr_bitmap(Debrief_award_coords[gr_screen.res][0], Debrief_award_coords[gr_screen.res][1], GR_RESIZE_MENU);
}
// draw medal/badge/rank labels
debrief_draw_award_text();
}
Debrief_ui_window.draw();
debrief_redraw_pressed_buttons();
Buttons[gr_screen.res][Current_mode].button.draw_forced(2);
if (Recommend_active && (Current_mode != STATS_TAB)) {
Buttons[gr_screen.res][RECOMMENDATIONS].button.draw_forced(2);
}
// draw the title of the mission
gr_set_color_fast(&Color_bright_white);
strncpy(buf, The_mission.name.c_str(), max_buf_len);
buf[max_buf_len] = '\0';
font::force_fit_string(buf, max_buf_len, Debrief_title_coords[gr_screen.res][2]);
gr_string(Debrief_title_coords[gr_screen.res][0], Debrief_title_coords[gr_screen.res][1], buf, GR_RESIZE_MENU);
#if !defined(NDEBUG)
gr_set_color_fast(&Color_normal);
gr_printf_menu(Debrief_title_coords[gr_screen.res][0], Debrief_title_coords[gr_screen.res][1] - 10, NOX("[name: %s, mod: %s]"), Mission_filename, The_mission.modified);
#endif
// Set the font for the debriefing instead of relying on the implicit font-setting of Debrief_ui_window.draw() -MageKing17
font::set_font(DEBRIEFING_FONT);
// draw the screen-specific text
switch (Current_mode) {
case DEBRIEF_TAB:
if ( Num_debrief_stages <= 0 ) {
gr_set_color_fast(&Color_white);
Assert( Game_current_mission_filename != NULL );
gr_printf_menu(Debrief_text_wnd_coords[gr_screen.res][0], Debrief_text_wnd_coords[gr_screen.res][1], XSTR( "No Debriefing for mission: %s", 458), Game_current_mission_filename);
} else {
brief_render_text(Text_offset, Debrief_text_wnd_coords[gr_screen.res][0], Debrief_text_wnd_coords[gr_screen.res][1], Debrief_text_wnd_coords[gr_screen.res][3], frametime);
}
break;
case STATS_TAB:
debrief_stats_render();
break;
} // end switch
if ( (Max_debrief_Lines + Text_offset) < Num_text_lines ) {
int w;
gr_set_color_fast(&Color_more_indicator);
gr_get_string_size(&w, NULL, XSTR( "More", 459));
gr_printf_menu(Debrief_text_wnd_coords[gr_screen.res][0] + Debrief_text_wnd_coords[gr_screen.res][2] / 2 - w / 2, Debrief_text_wnd_coords[gr_screen.res][1] + Debrief_text_wnd_coords[gr_screen.res][3], "%s", XSTR( "More", 459));
}
debrief_render_stagenum();
debrief_multi_list_draw();
// render some extra stuff in multiplayer
if (Game_mode & GM_MULTIPLAYER) {
// render the chatbox last
chatbox_render();
// draw tooltips
Debrief_ui_window.draw_tooltip();
// render the status indicator for the voice system
multi_common_voice_display_status();
}
// AL 3-6-98: Needed to move key reading here, since popups are launched from this code, and we don't
// want to include the mouse pointer which is drawn in the flip
if ( !help_overlay_active(Debrief_overlay_id) ) {
debrief_check_buttons();
debrief_do_keys(new_k);
}
// blit help overlay if active
help_overlay_maybe_blit(Debrief_overlay_id, gr_screen.res);
gr_flip();
// maybe show skip mission popup
if ( Must_replay_mission && (!Debrief_skip_popup_already_shown) && (Player->show_skip_popup) && (Game_mode & GM_NORMAL) && (Game_mode & GM_CAMPAIGN_MODE) && (Player->failures_this_session >= PLAYER_MISSION_FAILURE_LIMIT) && !(Game_mode & GM_MULTIPLAYER)) {
int popup_choice = popup(0, 3, XSTR("Do Not Skip This Mission", 1473),
XSTR("Advance To The Next Mission", 1474),
XSTR("Don't Show Me This Again", 1475),
XSTR("You have failed this mission five times. If you like, you may advance to the next mission.", 1472) );
switch (popup_choice) {
case 0:
// stay on this mission, so proceed to normal debrief
// in other words, do nothing.
break;
case 1:
// skip this mission
mission_campaign_skip_to_next();
gameseq_post_event(GS_EVENT_START_GAME);
break;
case 2:
// don't show this again
Player->show_skip_popup = 0;
break;
}
Debrief_skip_popup_already_shown = 1;
}
// check to see if we should be showing a pilot info popup in multiplayer (if a guy was double clicked)
if ((Game_mode & GM_MULTIPLAYER) && Debrief_should_show_popup) {
Assert((Multi_list_select >= 0) && (Multi_list_select < Multi_list_size));
multi_pinfo_popup(&Net_players[Multi_list[Multi_list_select].net_player_index]);
Debrief_should_show_popup = 0;
}
}
void debrief_rebuild_player_list()
{
int i;
net_player *np;
debrief_multi_list_info *list;
Multi_list_size = 0; // number of net players to choose from
for ( i=0; i<MAX_PLAYERS; i++ ) {
np = &Net_players[i];
// remember not to include the standalone.
if ( MULTI_CONNECTED((*np)) && !MULTI_STANDALONE((*np))){
list = &Multi_list[Multi_list_size++];
list->net_player_index = i;
strcpy_s(list->callsign, np->m_player->callsign);
// make sure to leave some room to blit the team indicator
font::force_fit_string(list->callsign, CALLSIGN_LEN - 1, Debrief_list_coords[gr_screen.res][2] - MULTI_LIST_TEAM_OFFSET);
}
} // end for
}
void debrief_handle_player_drop()
{
debrief_rebuild_player_list();
}
void debrief_maybe_auto_accept()
{
if (debrief_can_accept()) {
debrief_accept(0);
}
}
void debrief_disable_accept()
{
}
// Goober5000 - replace any variables with their values
// karajorma/jg18 - replace container references as well
// MjnMixael - replace keybinds also
void debrief_replace_stage_text(debrief_stage &stage)
{
sexp_replace_variable_names_with_values(stage.text);
sexp_replace_variable_names_with_values(stage.recommendation_text);
sexp_container_replace_refs_with_values(stage.text);
sexp_container_replace_refs_with_values(stage.recommendation_text);
message_translate_tokens(stage.text);
message_translate_tokens(stage.recommendation_text);
}