/* * 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 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; ivoice) <= 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= 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; inum_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; inum_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; ibutton.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; iflags & 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(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){ 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("", 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; ibutton_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 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= 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; itext.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; istats; 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= 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; inet_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); }