/*
 * 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);
}
