/* * 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 "controlconfig/controlsconfig.h" #include "freespace.h" #include "gamehelp/gameplayhelp.h" #include "gamesequence/gamesequence.h" #include "gamesnd/gamesnd.h" #include "globalincs/alphacolors.h" #include "io/key.h" #include "mission/missionmessage.h" #include "missionui/missionscreencommon.h" #include "sound/audiostr.h" #include "ui/ui.h" #include "weapon/weapon.h" // text positioning constants #define TITLE_Y 35 #define Y_START 70 #define X_OFFSET_1 70 // pixel offset for description of key from where key binding starts // increased this from 193 to 250 to account for allowing multiple keys -Mjn #define KEY_DESCRIPTION_OFFSET 250 // different gameplay help pages enum : int { // The first 9 are the basic retail pages GP_HELP_BASIC_KEYS, GP_HELP_MOVEMENT_KEYS, GP_HELP_COMMON_TARGET_KEYS, GP_HELP_ADVANCED_TARGET_KEYS, GP_HELP_MESSAGING, GP_HELP_WEAPON_KEYS, GP_HELP_THROTTLE_AND_ETS_KEYS, GP_HELP_VIEW_KEYS, GP_HELP_MISC_KEYS, // Plus one for multi only GP_HELP_MULTI_KEYS, // Any others defined here will have unique sections within the vector // but will need special handling to display in the retail UI GP_HELP_FUNCTION_KEYS, First_available_section }; #define GP_FIRST_SCREEN 0 // keep up to date #define GP_LAST_SCREEN_SINGLE 8 // keep up to date #define GP_LAST_SCREEN_MULTI 9 // keep up to date // set this to GP_LAST_SCREEN_SINGLE or GP_LAST_SCREEN_MULTI based upon what game mode we're in static int Gp_last_screen; #define NUM_BUTTONS 3 #define PREVIOUS_PAGE_BUTTON 0 #define NEXT_PAGE_BUTTON 1 #define CONTINUE_BUTTON 2 struct gameplay_help_buttons { const char *filename; int x, y; int hotspot; int tab; int flags; UI_BUTTON button; // because we have a class inside this struct, we need the constructor below.. gameplay_help_buttons(const char *name, int x1, int y1, int h) : filename(name), x(x1), y(y1), hotspot(h) {} }; static gameplay_help_buttons Buttons[GR_NUM_RESOLUTIONS][NUM_BUTTONS] = { //XSTR:OFF { gameplay_help_buttons("F1B_00", 15, 389, 0), gameplay_help_buttons("F1B_01", 60, 389, 1), gameplay_help_buttons("F1B_02", 574, 431, 2) }, { gameplay_help_buttons("2_F1B_00", 24, 622, 0), gameplay_help_buttons("2_F1B_01", 96, 622, 1), gameplay_help_buttons("2_F1B_02", 919, 689, 2) }, //XSTR:ON }; #define GAME_HELP_NUM_TEXT 2 static UI_XSTR Game_help_text[GR_NUM_RESOLUTIONS][GAME_HELP_NUM_TEXT] = { { // GR_640 { "Press ESC to return to the game", 1441, 263, 389, UI_XSTR_COLOR_GREEN, -1, NULL }, { "Continue", 1069, 571, 413, UI_XSTR_COLOR_GREEN, -1, &Buttons[gr_screen.res][CONTINUE_BUTTON].button } }, { // GR_1024 { "Press ESC to return to the game", 1441, 421, 622, UI_XSTR_COLOR_GREEN, -1, NULL }, { "Continue", 1069, 928, 663, UI_XSTR_COLOR_GREEN, -1, &Buttons[gr_screen.res][CONTINUE_BUTTON].button } } }; static const char *Game_help_filename[GR_NUM_RESOLUTIONS] = { "F1", "2_F1" }; static const char *Game_help_mask_filename[GR_NUM_RESOLUTIONS] = { "F1-m", "2_F1-m" }; static UI_WINDOW Ui_window; static int Background_bitmap; static int Gameplay_help_inited = 0; static int Current_help_page; SCP_vector Help_text; void gameplay_help_init_control_line(int id, gameplay_help_section &thisHelp) { char buf[256]; auto ci = &Control_config[id]; // do not draw if a mod has disabled this control --wookieejedi if (ci->disabled) { return; } buf[0] = 0; strcpy_s(buf, ci->first.textify().c_str()); if (!ci->second.empty()) { strcat_s(buf, XSTR(", ", 129)); strcat_s(buf, ci->second.textify().c_str()); } if (ci->empty()) { strcpy_s(buf, XSTR("no binding", 130)); } thisHelp.key.push_back(buf); thisHelp.text.push_back(XSTR(ci->text.c_str(), CONTROL_CONFIG_XSTR + id)); } void gameplay_help_init_control_line_raw(const char* control_text, const char* control_description, gameplay_help_section &thisHelp) { thisHelp.key.push_back(control_text); thisHelp.text.push_back(control_description); } // displays the given help key and text at the specified // x and y coordinates void gameplay_help_blit_help_text(int x, int y, const SCP_string *control_text, const SCP_string *control_description) { gr_string(x, y, control_text->c_str(), GR_RESIZE_MENU); gr_string(x + KEY_DESCRIPTION_OFFSET, y, control_description->c_str(), GR_RESIZE_MENU); } // displays a help section header at the specified y coordinate and an x coordinate // calculated from the header string's length void gameplay_help_blit_header_text(int y_offset, const SCP_string *header) { gr_set_color_fast(&Color_bright); int w; gr_get_string_size(&w, nullptr, header->c_str()); gr_printf_menu((gr_screen.clip_width - w) / 2, y_offset, "%s", header->c_str()); gr_set_color_fast(&Color_normal); } // game_play_help_set_title() will display the title for the help screen and // set the font for the rest of the screen void gameplay_help_set_title(const SCP_string *title) { int sy=TITLE_Y; char buf[128]; gr_set_color_fast(&Color_bright); int w; gr_get_string_size(&w, nullptr, title->c_str()); gr_printf_menu((gr_screen.clip_width_unscaled - w) / 2,sy,"%s", title->c_str()); sprintf(buf, XSTR("Page %d of %d", 132), Current_help_page + 1, Gp_last_screen + 1); gr_get_string_size(&w, NULL, buf); gr_printf_menu((gr_screen.clip_width_unscaled - w) / 2, sy + gr_get_font_height() + 2, "%s", buf); gr_set_color_fast(&Color_normal); } // Inits all the gameplay help text and returns it // in a nice organized vector of help sections SCP_vector gameplay_help_init_text() { SCP_vector complete_help_text; for (int i = 0; i < First_available_section; i++) { gameplay_help_section thisHelp; switch (i) { case GP_HELP_BASIC_KEYS: thisHelp.title = XSTR("Basic Keys", 133); thisHelp.header = ""; gameplay_help_init_control_line(END_MISSION, thisHelp); gameplay_help_init_control_line(FIRE_PRIMARY, thisHelp); gameplay_help_init_control_line(MAX_THROTTLE, thisHelp); gameplay_help_init_control_line(ZERO_THROTTLE, thisHelp); gameplay_help_init_control_line(TARGET_NEXT, thisHelp); gameplay_help_init_control_line(TARGET_PREV, thisHelp); gameplay_help_init_control_line(TOGGLE_AUTO_TARGETING, thisHelp); break; case GP_HELP_MOVEMENT_KEYS: thisHelp.title = XSTR("Movement Keys", 147); thisHelp.header = ""; gameplay_help_init_control_line(FORWARD_THRUST, thisHelp); gameplay_help_init_control_line(MAX_THROTTLE, thisHelp); gameplay_help_init_control_line(ONE_THIRD_THROTTLE, thisHelp); gameplay_help_init_control_line(TWO_THIRDS_THROTTLE, thisHelp); gameplay_help_init_control_line(MINUS_5_PERCENT_THROTTLE, thisHelp); gameplay_help_init_control_line(PLUS_5_PERCENT_THROTTLE, thisHelp); gameplay_help_init_control_line(ZERO_THROTTLE, thisHelp); gameplay_help_init_control_line(AFTERBURNER, thisHelp); gameplay_help_init_control_line(REVERSE_THRUST, thisHelp); gameplay_help_init_control_line(PITCH_FORWARD, thisHelp); gameplay_help_init_control_line(PITCH_BACK, thisHelp); gameplay_help_init_control_line(YAW_LEFT, thisHelp); gameplay_help_init_control_line(YAW_RIGHT, thisHelp); gameplay_help_init_control_line(BANK_LEFT, thisHelp); gameplay_help_init_control_line(BANK_RIGHT, thisHelp); gameplay_help_init_control_line(BANK_WHEN_PRESSED, thisHelp); break; case GP_HELP_COMMON_TARGET_KEYS: thisHelp.title = XSTR("Basic Targeting Keys", 148); thisHelp.header = ""; gameplay_help_init_control_line(TARGET_NEXT, thisHelp); gameplay_help_init_control_line(TARGET_PREV, thisHelp); gameplay_help_init_control_line(TOGGLE_AUTO_TARGETING, thisHelp); gameplay_help_init_control_line(TARGET_SHIP_IN_RETICLE, thisHelp); gameplay_help_init_control_line(TARGET_NEXT_CLOSEST_HOSTILE, thisHelp); gameplay_help_init_control_line(TARGET_PREV_CLOSEST_HOSTILE, thisHelp); gameplay_help_init_control_line(TARGET_NEXT_CLOSEST_FRIENDLY, thisHelp); gameplay_help_init_control_line(TARGET_PREV_CLOSEST_FRIENDLY, thisHelp); gameplay_help_init_control_line(TARGET_NEXT_SUBOBJECT, thisHelp); gameplay_help_init_control_line(TARGET_PREV_SUBOBJECT, thisHelp); gameplay_help_init_control_line(TARGET_SUBOBJECT_IN_RETICLE, thisHelp); gameplay_help_init_control_line(MATCH_TARGET_SPEED, thisHelp); gameplay_help_init_control_line(TOGGLE_AUTO_MATCH_TARGET_SPEED, thisHelp); break; case GP_HELP_ADVANCED_TARGET_KEYS: thisHelp.title = XSTR("Advanced Targeting Keys", 149); thisHelp.header = ""; gameplay_help_init_control_line(TARGET_CLOSEST_SHIP_ATTACKING_SELF, thisHelp); gameplay_help_init_control_line(TARGET_CLOSEST_SHIP_ATTACKING_TARGET, thisHelp); gameplay_help_init_control_line(TARGET_LAST_TRANMISSION_SENDER, thisHelp); gameplay_help_init_control_line(TARGET_TARGETS_TARGET, thisHelp); gameplay_help_init_control_line(TARGET_NEXT_ESCORT_SHIP, thisHelp); gameplay_help_init_control_line(TARGET_CLOSEST_REPAIR_SHIP, thisHelp); gameplay_help_init_control_line(TARGET_NEXT_UNINSPECTED_CARGO, thisHelp); gameplay_help_init_control_line(TARGET_PREV_UNINSPECTED_CARGO, thisHelp); gameplay_help_init_control_line(TARGET_NEWEST_SHIP, thisHelp); gameplay_help_init_control_line(TARGET_NEXT_BOMB, thisHelp); gameplay_help_init_control_line(TARGET_PREV_BOMB, thisHelp); gameplay_help_init_control_line(STOP_TARGETING_SHIP, thisHelp); gameplay_help_init_control_line(TARGET_NEXT_LIVE_TURRET, thisHelp); gameplay_help_init_control_line(TARGET_PREV_LIVE_TURRET, thisHelp); gameplay_help_init_control_line(STOP_TARGETING_SUBSYSTEM, thisHelp); gameplay_help_init_control_line_raw(XSTR("F5...F12", 143), XSTR("Select target assigned to that hotkey", 150), thisHelp); gameplay_help_init_control_line_raw(XSTR("Shift-F5...F12", 151), XSTR("Add/remove target from that hotkey", 152), thisHelp); gameplay_help_init_control_line_raw(XSTR("Alt-Shift-F5...F12", 153), XSTR("Clear that hotkey", 154), thisHelp); break; case GP_HELP_MESSAGING: thisHelp.title = XSTR("Messaging Keys", 155); thisHelp.header = ""; gameplay_help_init_control_line(SQUADMSG_MENU, thisHelp); gameplay_help_init_control_line(REARM_MESSAGE, thisHelp); gameplay_help_init_control_line(ATTACK_MESSAGE, thisHelp); gameplay_help_init_control_line(DISARM_MESSAGE, thisHelp); gameplay_help_init_control_line(DISABLE_MESSAGE, thisHelp); gameplay_help_init_control_line(CAPTURE_MESSAGE, thisHelp); gameplay_help_init_control_line(ENGAGE_MESSAGE, thisHelp); gameplay_help_init_control_line(FORM_MESSAGE, thisHelp); gameplay_help_init_control_line(IGNORE_MESSAGE, thisHelp); gameplay_help_init_control_line(PROTECT_MESSAGE, thisHelp); gameplay_help_init_control_line(COVER_MESSAGE, thisHelp); gameplay_help_init_control_line(WARP_MESSAGE, thisHelp); gameplay_help_init_control_line_raw(XSTR("F5...F12", 143), XSTR("send specified order to these target(s)", 156), thisHelp); break; case GP_HELP_WEAPON_KEYS: thisHelp.title = XSTR("Weapon Keys", 157); thisHelp.header = ""; gameplay_help_init_control_line(FIRE_PRIMARY, thisHelp); gameplay_help_init_control_line(FIRE_SECONDARY, thisHelp); gameplay_help_init_control_line(CYCLE_NEXT_PRIMARY, thisHelp); gameplay_help_init_control_line(CYCLE_PREV_PRIMARY, thisHelp); gameplay_help_init_control_line(CYCLE_SECONDARY, thisHelp); gameplay_help_init_control_line(CYCLE_NUM_MISSLES, thisHelp); gameplay_help_init_control_line(LAUNCH_COUNTERMEASURE, thisHelp); break; case GP_HELP_THROTTLE_AND_ETS_KEYS: thisHelp.title = XSTR("Energy Management Keys", 158); thisHelp.header = ""; gameplay_help_init_control_line(SHIELD_EQUALIZE, thisHelp); gameplay_help_init_control_line(SHIELD_XFER_TOP, thisHelp); gameplay_help_init_control_line(SHIELD_XFER_BOTTOM, thisHelp); gameplay_help_init_control_line(SHIELD_XFER_LEFT, thisHelp); gameplay_help_init_control_line(SHIELD_XFER_RIGHT, thisHelp); gameplay_help_init_control_line(INCREASE_WEAPON, thisHelp); gameplay_help_init_control_line(DECREASE_WEAPON, thisHelp); gameplay_help_init_control_line(INCREASE_SHIELD, thisHelp); gameplay_help_init_control_line(DECREASE_SHIELD, thisHelp); gameplay_help_init_control_line(INCREASE_ENGINE, thisHelp); gameplay_help_init_control_line(DECREASE_ENGINE, thisHelp); gameplay_help_init_control_line(ETS_EQUALIZE, thisHelp); /* gameplay_help_init_control_line(XFER_LASER, thisHelp); gameplay_help_init_control_line(XFER_SHIELD, thisHelp); */ break; case GP_HELP_MISC_KEYS: thisHelp.title = XSTR("Miscellaneous Keys", 159); thisHelp.header = ""; // ending mission gameplay_help_init_control_line(END_MISSION, thisHelp); gameplay_help_init_control_line_raw(XSTR("ESC", 160), XSTR("invoke abort mission popup", 161), thisHelp); // time compression gameplay_help_init_control_line(TIME_SPEED_UP, thisHelp); gameplay_help_init_control_line(TIME_SLOW_DOWN, thisHelp); // radar keys gameplay_help_init_control_line(RADAR_RANGE_CYCLE, thisHelp); // escort view keys gameplay_help_init_control_line(ADD_REMOVE_ESCORT, thisHelp); gameplay_help_init_control_line(ESCORT_CLEAR, thisHelp); // Pause, Print Screenshot gameplay_help_init_control_line_raw(XSTR("Pause", 162), XSTR("pause game", 163), thisHelp); gameplay_help_init_control_line_raw(XSTR("Print Scrn", 164), XSTR("take screen shot", 165), thisHelp); break; case GP_HELP_VIEW_KEYS: thisHelp.title = XSTR("View Keys", 166); thisHelp.header = ""; gameplay_help_init_control_line(PADLOCK_UP, thisHelp); gameplay_help_init_control_line(PADLOCK_DOWN, thisHelp); gameplay_help_init_control_line(PADLOCK_LEFT, thisHelp); gameplay_help_init_control_line(PADLOCK_RIGHT, thisHelp); gameplay_help_init_control_line(VIEW_CHASE, thisHelp); gameplay_help_init_control_line(VIEW_EXTERNAL, thisHelp); gameplay_help_init_control_line(VIEW_EXTERNAL_TOGGLE_CAMERA_LOCK, thisHelp); gameplay_help_init_control_line(VIEW_SLEW, thisHelp); gameplay_help_init_control_line(VIEW_DIST_INCREASE, thisHelp); gameplay_help_init_control_line(VIEW_DIST_DECREASE, thisHelp); gameplay_help_init_control_line(VIEW_CENTER, thisHelp); gameplay_help_init_control_line(VIEW_OTHER_SHIP, thisHelp); break; case GP_HELP_MULTI_KEYS: thisHelp.title = XSTR("Multiplayer Keys", 167); thisHelp.header = XSTR("Ingame messaging keys (tap for text, hold for voice)", 168); gameplay_help_init_control_line(MULTI_MESSAGE_ALL, thisHelp); gameplay_help_init_control_line(MULTI_MESSAGE_FRIENDLY, thisHelp); gameplay_help_init_control_line(MULTI_MESSAGE_HOSTILE, thisHelp); gameplay_help_init_control_line(MULTI_MESSAGE_TARGET, thisHelp); break; case GP_HELP_FUNCTION_KEYS: thisHelp.title = XSTR("Function Keys", 134); thisHelp.header = ""; gameplay_help_init_control_line_raw(XSTR("F1", 135), XSTR("context-sensitive help", 136), thisHelp); gameplay_help_init_control_line_raw(XSTR("F2", 137), XSTR("options screen (available anywhere in game)", 138), thisHelp); gameplay_help_init_control_line_raw(XSTR("F3", 139), XSTR("hotkey assignment", 140), thisHelp); gameplay_help_init_control_line_raw(XSTR("F4", 141), XSTR("HUD message scroll-back", 142), thisHelp); gameplay_help_init_control_line_raw(XSTR("F5...F12", 143), XSTR("hotkeys", 144), thisHelp); gameplay_help_init_control_line_raw(XSTR("Shift-Esc", 145), XSTR("quit FreeSpace 2 immediately", 146), thisHelp); break; default: continue; } complete_help_text.push_back(std::move(thisHelp)); } return complete_help_text; } // called once when the gameplay help state is entered void gameplay_help_init() { int i; gameplay_help_buttons *b; if ( Gameplay_help_inited ) { return; } // init all the help text Help_text.clear(); //Make sure the array is empty before we start. Help_text = gameplay_help_init_text(); common_set_interface_palette("InterfacePalette"); // set the interface palette Ui_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0); Ui_window.set_mask_bmap(Game_help_mask_filename[gr_screen.res]); for (i=0; ibutton.create(&Ui_window, "", b->x, b->y, 60, 30, 0, 1); // set up callback for when a mouse first goes over a button b->button.set_highlight_action(common_play_highlight_sound); b->button.set_bmaps(b->filename); b->button.link_hotspot(b->hotspot); } // add all xstrs for (i=0; i Gp_last_screen) { Current_help_page = GP_FIRST_SCREEN; } gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS); } // called when the screen is exited void gameplay_help_leave() { // unpause all game sounds weapon_unpause_sounds(); audiostream_unpause_all(); message_resume_all(); gameseq_post_event(GS_EVENT_PREVIOUS_STATE); game_flush(); } // deal with a keypress on the gameplay help screen void gameplay_help_process_key(int k) { switch ( k ) { case KEY_ESC: gameplay_help_leave(); break; case KEY_ENTER: case KEY_SPACEBAR: case KEY_TAB: //gameplay_help_goto_next_screen(); Buttons[gr_screen.res][NEXT_PAGE_BUTTON].button.press_button(); break; case KEY_SHIFTED | KEY_TAB: Buttons[gr_screen.res][PREVIOUS_PAGE_BUTTON].button.press_button(); // gameplay_help_goto_prev_screen(); break; default: break; } // end switch } // deal with buttons being pressed on the gameplay help screen void gameplay_help_button_pressed(int n) { switch (n) { case PREVIOUS_PAGE_BUTTON: gameplay_help_goto_prev_screen(); break; case NEXT_PAGE_BUTTON: gameplay_help_goto_next_screen(); break; case CONTINUE_BUTTON: gameplay_help_leave(); gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED); break; default: Int3(); break; } } // Draw the help text onto the screen void gameplay_help_draw_text() { int x_offset, y_offset, separation; separation = gr_get_font_height() + 3; gameplay_help_section thisHelp = Help_text[Current_help_page]; gameplay_help_set_title(&thisHelp.title); x_offset = X_OFFSET_1; y_offset = Y_START; // if we have a header then display it if (thisHelp.header.length() > 0) { gameplay_help_blit_header_text(y_offset, &thisHelp.header); y_offset += separation; } // blit help text for each key for (int i = 0; i < (int)thisHelp.key.size(); i++) { y_offset += separation; gameplay_help_blit_help_text(x_offset, y_offset, &thisHelp.key[i], &thisHelp.text[i]); } // Special handling of function keys to display on the same page as the basic keys if (Current_help_page == GP_HELP_BASIC_KEYS) { y_offset += separation; y_offset += separation; thisHelp = Help_text[GP_HELP_FUNCTION_KEYS]; gameplay_help_blit_header_text(y_offset, &thisHelp.title); y_offset += separation; y_offset += separation; // blit help text for each key for (int i = 0; i < (int)thisHelp.key.size(); i++) { y_offset += separation; gameplay_help_blit_help_text(x_offset, y_offset, &thisHelp.key[i], &thisHelp.text[i]); } } } // gameplay_help_do_frame() is the function that displays help when acutally playing the game void gameplay_help_do_frame(float /*frametime*/) { int i, k; // ensure the gameplay help interface has been initialized if (!Gameplay_help_inited) { Int3(); return; } // make sure game sounds are paused weapon_pause_sounds(); audiostream_pause_all(); message_pause_all(); k = Ui_window.process() & ~KEY_DEBUGGED; gameplay_help_process_key(k); for (i=0; i= 0) { gr_set_bitmap(Background_bitmap); gr_bitmap(0, 0, GR_RESIZE_MENU); } Ui_window.draw(); gameplay_help_draw_text(); gr_flip(); }