/* * 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 "debugconsole/console.h" #include "freespace.h" #include "gamehelp/contexthelp.h" #include "gamesequence/gamesequence.h" #include "gamesnd/gamesnd.h" #include "globalincs/alphacolors.h" #include "graphics/font.h" #include "hud/hudsquadmsg.h" #include "io/joy.h" #include "io/key.h" #include "io/timer.h" #include "missionui/missionscreencommon.h" #include "network/multi_pmsg.h" #include "network/multiutil.h" #include "scripting/scripting.h" #include "pilotfile/pilotfile.h" #include "popup/popup.h" #include "ui/ui.h" #include "ui/uidefs.h" #ifndef NDEBUG #include "hud/hud.h" #endif #define NUM_BUTTONS 19 #define NUM_TABS 4 // coordinate indicies #define CONTROL_X_COORD 0 #define CONTROL_Y_COORD 1 #define CONTROL_W_COORD 2 #define CONTROL_H_COORD 3 const char* Conflict_background_bitmap_fname[GR_NUM_RESOLUTIONS] = { "ControlConfig", // GR_640 "2_ControlConfig" // GR_1024 }; const char* Conflict_background_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = { "ControlConfig-m", // GR_640 "2_ControlConfig-m" // GR_1024 }; // control list area int Control_list_coords[GR_NUM_RESOLUTIONS][4] = { { 32, 58, 198, 259 // GR_640 }, { 32, 94, 904, 424 // GR_1024 } }; // width of the control name section of the list int Control_list_ctrl_w[GR_NUM_RESOLUTIONS] = { 350, // GR_640 600 // GR_1024 }; // x start position of the binding area section of the list int Control_list_key_x[GR_NUM_RESOLUTIONS] = { 397, // GR_640 712 // GR_1024 }; // width of the binding area section of the list int Control_list_key_w[GR_NUM_RESOLUTIONS] = { 198, // GR_640 230 // GR_1024 }; // display the "more..." text under the control list int Control_more_coords[GR_NUM_RESOLUTIONS][2] = { { 320, 326 // GR_640 }, { 500, 542 // GR_1024 } }; // area to display "conflicts with..." text int Conflict_wnd_coords[GR_NUM_RESOLUTIONS][4] = { { 32, 313, 250, 32 // GR_640 }, { 48, 508, 354, 46 // GR_1024 } }; // conflict warning anim coords int Conflict_warning_coords[GR_NUM_RESOLUTIONS][2] = { { -1, 420 // GR_640 }, { -1, 669 // GR_1024 } }; // for flashing the conflict text #define CONFLICT_FLASH_TIME 250 int Conflict_stamp = -1; int Conflict_bright = 0; #define LIST_BUTTONS_MAX 42 #define JOY_AXIS 0x80000 static int Num_cc_lines; static struct { const char *label; int cc_index; // index into Control_config of item int y; // Y coordinate of line int kx, kw, jx, jw; // x start and width of keyboard and joystick bound text } Cc_lines[CCFG_MAX]; // struct to hold backup config_item elements so we can undo them struct config_item_undo { int size; int *index; // array (size) of Control_config indices of replaced elements config_item *list; // array (size) of original elements int reset_to_preset; // if >=0, then we ignore the above list and simply reset to the given preset instead config_item_undo *next; }; config_item Control_config_backup[CCFG_MAX]; int Axis_map_to[] = { JOY_X_AXIS, JOY_Y_AXIS, JOY_RX_AXIS, -1, -1 }; int Axis_map_to_defaults[] = { JOY_X_AXIS, JOY_Y_AXIS, JOY_RX_AXIS, -1, -1 }; // all this stuff is localized/externalized #define NUM_AXIS_TEXT 6 #define NUM_MOUSE_TEXT 5 #define NUM_MOUSE_AXIS_TEXT 2 #define NUM_INVERT_TEXT 2 char *Joy_axis_action_text[NUM_JOY_AXIS_ACTIONS]; char *Joy_axis_text[NUM_AXIS_TEXT]; char *Mouse_button_text[NUM_MOUSE_TEXT]; char *Mouse_axis_text[NUM_MOUSE_AXIS_TEXT]; char *Invert_text[NUM_INVERT_TEXT]; int Control_check_count = 0; static int Tab; // which tab we are currently in static int Binding_mode = 0; // are we waiting for a key to bind it? static int Bind_time = 0; static int Search_mode = 0; // are we waiting for a key to bind it? static int Last_key = -1; static int Selected_line = 0; // line that is currently selected for binding static int Selected_item = -1; // -1 = none, 0 = key, 1 = button static int Scroll_offset; static int Axis_override = -1; static int Background_bitmap; static int Conflicts_tabs[NUM_TABS]; static UI_BUTTON List_buttons[LIST_BUTTONS_MAX]; // buttons for each line of text in list static UI_WINDOW Ui_window; static unsigned int Defaults_cycle_pos; // the controls preset that was last selected int Control_config_overlay_id; static struct { int key; // index of other control in conflict with this one int joy; // index of other control in conflict with this one } Conflicts[CCFG_MAX]; int Conflicts_axes[NUM_JOY_AXIS_ACTIONS]; #define TARGET_TAB 0 #define SHIP_TAB 1 #define WEAPON_TAB 2 #define COMPUTER_TAB 3 #define SCROLL_UP_BUTTON 4 #define SCROLL_DOWN_BUTTON 5 #define ALT_TOGGLE 6 #define SHIFT_TOGGLE 7 #define INVERT_AXIS 8 #define CANCEL_BUTTON 9 #define UNDO_BUTTON 10 #define RESET_BUTTON 11 #define SEARCH_MODE 12 #define BIND_BUTTON 13 #define HELP_BUTTON 14 #define ACCEPT_BUTTON 15 #define CLEAR_OTHER_BUTTON 16 #define CLEAR_ALL_BUTTON 17 #define CLEAR_BUTTON 18 ui_button_info CC_Buttons[GR_NUM_RESOLUTIONS][NUM_BUTTONS] = { { // GR_640 ui_button_info("CCB_00", 32, 348, 17, 384, 0), // target tab ui_button_info("CCB_01", 101, 348, 103, 384, 1), // ship tab ui_button_info("CCB_02", 173, 352, 154, 384, 2), // weapon tab ui_button_info("CCB_03", 242, 347, 244, 384, 3), // computer/misc tab ui_button_info("CCB_04", 614, 73, -1, -1, 4), // scroll up ui_button_info("CCB_05", 614, 296, -1, -1, 5), // scroll down ui_button_info("CCB_06", 17, 452, 12, 440, 6), // alt toggle ui_button_info("CCB_07", 56, 452, 50, 440, 7), // shift toggle ui_button_info("CCB_09", 162, 452, 155, 440, 9), // invert ui_button_info("CCB_10", 404, 1, 397, 45, 10), // cancel ui_button_info("CCB_11", 582, 347, 586, 386, 11), // undo ui_button_info("CCB_12", 576, 1, 578, 45, 12), // default ui_button_info("CCB_13", 457, 4, 453, 45, 13), // search ui_button_info("CCB_14", 516, 4, 519, 45, 14), // bind ui_button_info("CCB_15", 540, 428, 500, 440, 15), // help ui_button_info("CCB_16", 574, 432, 571, 412, 16), // accept ui_button_info("CCB_18", 420, 346, 417, 386, 18), // clear other ui_button_info("CCB_19", 476, 346, 474, 386, 19), // clear all ui_button_info("CCB_20", 524, 346, 529, 386, 20), // clear button }, { // GR_1024 ui_button_info("2_CCB_00", 51, 557, 27, 615, 0), // target tab ui_button_info("2_CCB_01", 162, 557, 166, 615, 1), // ship tab ui_button_info("2_CCB_02", 277, 563, 246, 615, 2), // weapon tab ui_button_info("2_CCB_03", 388, 555, 391, 615, 3), // computer/misc tab ui_button_info("2_CCB_04", 982, 117, -1, -1, 4), // scroll up ui_button_info("2_CCB_05", 982, 474, -1, -1, 5), // scroll down ui_button_info("2_CCB_06", 28, 723, 24, 704, 6), // alt toggle ui_button_info("2_CCB_07", 89, 723, 80, 704, 7), // shift toggle ui_button_info("2_CCB_09", 260, 723, 249, 704, 9), // invert ui_button_info("2_CCB_10", 646, 2, 635, 71, 10), // cancel ui_button_info("2_CCB_11", 932, 555, 938, 619, 11), // undo ui_button_info("2_CCB_12", 921, 1, 923, 71, 12), // default ui_button_info("2_CCB_13", 732, 6, 726, 71, 13), // search ui_button_info("2_CCB_14", 825, 6, 831, 71, 14), // bind ui_button_info("2_CCB_15", 864, 685, 800, 704, 15), // help ui_button_info("2_CCB_16", 919, 692, 914, 660, 16), // accept ui_button_info("2_CCB_18", 672, 553, 668, 619, 18), // clear other ui_button_info("2_CCB_19", 761, 553, 749, 619, 19), // clear all ui_button_info("2_CCB_20", 838, 553, 846, 619, 20), // clear button } }; // strings #define CC_NUM_TEXT 20 UI_XSTR CC_text[GR_NUM_RESOLUTIONS][CC_NUM_TEXT] = { { // GR_640 { "Targeting", 1340, 17, 384, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][TARGET_TAB].button }, { "Ship", 1341, 103, 384, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][SHIP_TAB].button }, { "Weapons", 1065, 154, 384, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][WEAPON_TAB].button }, { "Misc", 1411, 244, 384, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][COMPUTER_TAB].button }, { "Alt", 1510, 12, 440, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][ALT_TOGGLE].button }, { "Shift", 1511, 50, 440, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][SHIFT_TOGGLE].button }, { "Invert", 1342, 155, 440, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][INVERT_AXIS].button }, { "Cancel", 641, 397, 45, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[0][CANCEL_BUTTON].button }, { "Undo", 1343, 586, 386, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][UNDO_BUTTON].button }, { "Defaults", 1344, 568, 45, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][RESET_BUTTON].button }, { "Search", 1345, 453, 45, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][SEARCH_MODE].button }, { "Bind", 1346, 519, 45, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[0][BIND_BUTTON].button }, { "Help", 928, 500, 440, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][HELP_BUTTON].button }, { "Accept", 1035, 571, 412, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[0][ACCEPT_BUTTON].button }, { "Clear", 1347, 417, 386, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][CLEAR_OTHER_BUTTON].button }, { "Conflict", 1348, 406, 396, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][CLEAR_OTHER_BUTTON].button }, { "Clear", 1413, 474, 386, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][CLEAR_ALL_BUTTON].button }, { "All", 1349, 483, 396, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[0][CLEAR_ALL_BUTTON].button }, { "Clear", 1414, 529, 388, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[0][CLEAR_BUTTON].button }, { "Selected", 1350, 517, 396, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[0][CLEAR_BUTTON].button }, }, { // GR_1024 { "Targeting", 1340, 47, 615, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][TARGET_TAB].button }, { "Ship", 1341, 176, 615, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][SHIP_TAB].button }, { "Weapons", 1065, 266, 615, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][WEAPON_TAB].button }, { "Misc", 1411, 401, 615, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][COMPUTER_TAB].button }, { "Alt", 1510, 29, 704, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][ALT_TOGGLE].button }, { "Shift", 1511, 85, 704, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][SHIFT_TOGGLE].button }, { "Invert", 1342, 254, 704, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][INVERT_AXIS].button }, { "Cancel", 641, 655, 71, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[1][CANCEL_BUTTON].button }, { "Undo", 1343, 938, 619, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][UNDO_BUTTON].button }, { "Defaults", 1344, 923, 71, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][RESET_BUTTON].button }, { "Search", 1345, 746, 71, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][SEARCH_MODE].button }, { "Bind", 1346, 846, 71, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[1][BIND_BUTTON].button }, { "Help", 928, 800, 704, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][HELP_BUTTON].button }, { "Accept", 1035, 914, 660, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[1][ACCEPT_BUTTON].button }, { "Clear", 1347, 683, 619, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][CLEAR_OTHER_BUTTON].button }, { "Conflict", 1348, 666, 634, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][CLEAR_OTHER_BUTTON].button }, { "Clear", 1413, 759, 619, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][CLEAR_ALL_BUTTON].button }, { "All", 1349, 772, 634, UI_XSTR_COLOR_GREEN, -1, &CC_Buttons[1][CLEAR_ALL_BUTTON].button }, { "Clear", 1414, 871, 619, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[1][CLEAR_BUTTON].button }, { "Selected", 1350, 852, 634, UI_XSTR_COLOR_PINK, -1, &CC_Buttons[1][CLEAR_BUTTON].button }, } }; // linked list head of undo items config_item_undo *Config_item_undo; // same indices as Scan_code_text[]. Indicates if a scancode is allowed to be bound. int Config_allowed[] = { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; #ifndef NDEBUG int Show_controls_info = 0; DCF_BOOL(show_controls_info, Show_controls_info); #endif static int Axes_origin[JOY_NUM_AXES]; static int joy_get_unscaled_reading(int raw) { int rng; rng = JOY_AXIS_MAX - JOY_AXIS_MIN; raw -= JOY_AXIS_MIN; // adjust for linear range starting at 0 // cap at limits if (raw < 0) raw = 0; if (raw > rng) raw = rng; return (int) ((std::uint64_t) raw * (std::uint64_t) F1_0 / (std::uint64_t) rng); // convert to 0 - F1_0 range, 64bit ints used to avoid uint overflow } int joy_get_scaled_reading(int raw) { int x, d, dead_zone, rng; float percent, sensitivity_percent, non_sensitivity_percent; raw -= JOY_AXIS_CENTER; dead_zone = (JOY_AXIS_MAX - JOY_AXIS_MIN) * Joy_dead_zone_size / 100; if (raw < -dead_zone) { rng = JOY_AXIS_CENTER - JOY_AXIS_MIN - dead_zone; d = -raw - dead_zone; } else if (raw > dead_zone) { rng = JOY_AXIS_MAX - JOY_AXIS_CENTER - dead_zone; d = raw - dead_zone; } else return 0; if (d > rng) d = rng; Assert(Joy_sensitivity >= 0 && Joy_sensitivity <= 9); // compute percentages as a range between 0 and 1 sensitivity_percent = (float) Joy_sensitivity / 9.0f; non_sensitivity_percent = (float) (9 - Joy_sensitivity) / 9.0f; // find percent of max axis is at percent = (float) d / (float) rng; // work sensitivity on axis value percent = (percent * sensitivity_percent + percent * percent * percent * percent * percent * non_sensitivity_percent); x = (int) ((float) F1_0 * percent); //nprintf(("AI", "d=%6i, sens=%3i, percent=%6.3f, val=%6i, ratio=%6.3f\n", d, Joy_sensitivity, percent, (raw<0) ? -x : x, (float) d/x)); if (raw < 0) return -x; return x; } void control_config_detect_axis_reset() { joystick_read_raw_axis(JOY_NUM_AXES, Axes_origin); } int control_config_detect_axis() { int i, d, axis = -1, delta = 16384; int axes_values[JOY_NUM_AXES]; int dx, dy, dz, fudge = 7; joystick_read_raw_axis(JOY_NUM_AXES, axes_values); for (i=0; i delta) { axis = i; delta = d; } } if ( (axis == -1) && Use_mouse_to_fly ) { mouse_get_delta( &dx, &dy, &dz ); if ( (dx > fudge) || (dx < -fudge) ) { axis = 0; } else if ( (dy > fudge) || (dy < -fudge) ) { axis = 1; } else if ( (dz > fudge) || (dz < -fudge) ) { axis = 2; } } return axis; } void control_config_conflict_check() { int i, j; for (i=0; i= 0) && (Control_config[i].key_id == Control_config[j].key_id)) { // If one of the conflicting keys is disabled, then this silently clears // the disabled key to prevent the conflict; a conflict is recorded only // if both keys are enabled if (Control_config[i].disabled) Control_config[i].key_id = (short) -1; else if (Control_config[j].disabled) Control_config[j].key_id = (short) -1; else { Conflicts[i].key = j; Conflicts[j].key = i; Conflicts_tabs[ Control_config[i].tab ] = 1; Conflicts_tabs[ Control_config[j].tab ] = 1; } } if ((Control_config[i].joy_id >= 0) && (Control_config[i].joy_id == Control_config[j].joy_id)) { // Same as above if (Control_config[i].disabled) Control_config[i].joy_id = (short) -1; else if (Control_config[j].disabled) Control_config[j].joy_id = (short) -1; else { Conflicts[i].joy = j; Conflicts[j].joy = i; Conflicts_tabs[ Control_config[i].tab ] = 1; Conflicts_tabs[ Control_config[j].tab ] = 1; } } } } for (i=0; i= 0) && (Axis_map_to[i] == Axis_map_to[j])) { Conflicts_axes[i] = j; Conflicts_axes[j] = i; Conflicts_tabs[SHIP_TAB] = 1; } } } } // do list setup required prior to rendering and checking for the controls listing. Called when list changes void control_config_list_prepare() { int j, y, z; int font_height = gr_get_font_height(); Num_cc_lines = y = z = 0; while (z < CCFG_MAX) { if (Control_config[z].tab == Tab && !Control_config[z].disabled) { if (Control_config[z].indexXSTR > 1) { Cc_lines[Num_cc_lines].label = XSTR(Control_config[z].text, Control_config[z].indexXSTR, true); } else if (Control_config[z].indexXSTR == 1) { Cc_lines[Num_cc_lines].label = XSTR(Control_config[z].text, CONTROL_CONFIG_XSTR + z, true); } else { Cc_lines[Num_cc_lines].label = Control_config[z].text; } Cc_lines[Num_cc_lines].cc_index = z; Cc_lines[Num_cc_lines++].y = y; y += font_height + 2; } z++; } if (Tab == SHIP_TAB) { for (j=0; j= Num_cc_lines)) { return 0; } y = Cc_lines[n].y - Cc_lines[Scroll_offset].y; if ((y < 0) || (y + gr_get_font_height() > Control_list_coords[gr_screen.res][CONTROL_H_COORD])){ return 0; } return 1; } // allocates the required space for one undo block and put it in the beginning of the linked list (top of a stack). // Returns a pointer to this newly allocated block config_item_undo *get_undo_block(int size) { config_item_undo *ptr; ptr = (config_item_undo *) vm_malloc( sizeof(config_item_undo) ); Assert(ptr); ptr->next = Config_item_undo; Config_item_undo = ptr; ptr->size = size; if (size) { ptr->index = (int *) vm_malloc( sizeof(int) * size ); Assert(ptr->index); ptr->list = (config_item *) vm_malloc( sizeof(config_item) * size ); Assert(ptr->list); } else { ptr->index = NULL; ptr->list = NULL; } ptr->reset_to_preset = -1; return ptr; } // frees one undo block. The first one in the list (top of the stack) to be precise. void free_undo_block() { config_item_undo *ptr; ptr = Config_item_undo; if (!ptr) { return; } Config_item_undo = ptr->next; if (ptr->size) { vm_free(ptr->list); vm_free(ptr->index); } vm_free(ptr); } // undo the most recent binding changes int control_config_undo_last() { int i, z, tab; if (!Config_item_undo) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return -1; } if (Config_item_undo->reset_to_preset > -1) { control_config_reset_defaults(Config_item_undo->reset_to_preset); } else { if (Config_item_undo->index[0] & JOY_AXIS) { tab = SHIP_TAB; } else { tab = Control_config[Config_item_undo->index[0]].tab; } for (i=1; isize; i++) { if (Config_item_undo->index[i] & JOY_AXIS) { if (tab != SHIP_TAB) { tab = -1; } } else { if (Control_config[Config_item_undo->index[i]].tab != tab) { tab = -1; } } } if (tab >= 0) { Tab = tab; } for (i=0; isize; i++) { z = Config_item_undo->index[i]; if (z & JOY_AXIS) { config_item *ptr; z &= ~JOY_AXIS; ptr = &Config_item_undo->list[i]; Axis_map_to[z] = ptr->joy_id; Invert_axis[z] = ptr->used; } else { Control_config[z] = Config_item_undo->list[i]; } } } free_undo_block(); control_config_conflict_check(); control_config_list_prepare(); gamesnd_play_iface(InterfaceSounds::USER_SELECT); return 0; } void control_config_save_axis_undo(int axis) { config_item_undo *ptr; config_item item; memset( &item, 0, sizeof(config_item) ); item.joy_id = (short) Axis_map_to[axis]; item.used = Invert_axis[axis]; ptr = get_undo_block(1); ptr->index[0] = axis | JOY_AXIS; ptr->list[0] = item; } void control_config_bind_key(int i, int key) { config_item_undo *ptr; ptr = get_undo_block(1); ptr->index[0] = i; ptr->list[0] = Control_config[i]; Control_config[i].key_id = (short) key; } void control_config_bind_joy(int i, int joy) { config_item_undo *ptr; ptr = get_undo_block(1); ptr->index[0] = i; ptr->list[0] = Control_config[i]; Control_config[i].joy_id = (short) joy; } void control_config_bind_axis(int i, int axis) { control_config_save_axis_undo(i); Axis_map_to[i] = axis; } int control_config_remove_binding() { int z; config_item_undo *ptr; if (Selected_line < 0) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return -1; } z = Cc_lines[Selected_line].cc_index; if (z & JOY_AXIS) { z &= ~JOY_AXIS; if (Axis_map_to[z] < 0) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return -1; } control_config_save_axis_undo(z); Axis_map_to[z] = -1; control_config_conflict_check(); control_config_list_prepare(); gamesnd_play_iface(InterfaceSounds::USER_SELECT); Selected_item = -1; return 0; } if ((Control_config[z].joy_id < 0) && (Control_config[z].key_id < 0)) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return -1; } ptr = get_undo_block(1); ptr->index[0] = z; ptr->list[0] = Control_config[z]; if (Selected_item && (Control_config[z].joy_id >= 0)) { // if not just key selected (which would be 0) Control_config[z].joy_id = (short) -1; } if ((Selected_item != 1) && (Control_config[z].key_id >= 0)) { // if not just joy button selected (1) Control_config[z].key_id = (short) -1; } control_config_conflict_check(); control_config_list_prepare(); gamesnd_play_iface(InterfaceSounds::USER_SELECT); Selected_item = -1; return 0; } int control_config_clear_other() { int z, i, j, total = 0; config_item_undo *ptr; if (Selected_line < 0) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return -1; } z = Cc_lines[Selected_line].cc_index; if (z & JOY_AXIS) { config_item item; z &= ~JOY_AXIS; if (Axis_map_to[z] < 0) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return -1; } for (i=0; iindex[j] = i | JOY_AXIS; ptr->list[j] = item; j++; Axis_map_to[i] = -1; } } control_config_conflict_check(); control_config_list_prepare(); gamesnd_play_iface(InterfaceSounds::USER_SELECT); return 0; } for (i=0; iindex[j] = i; ptr->list[j] = Control_config[i]; j++; if (Control_config[i].key_id == Control_config[z].key_id) { Control_config[i].key_id = (short) -1; } if (Control_config[i].joy_id == Control_config[z].joy_id) { Control_config[i].joy_id = (short) -1; } } } } control_config_conflict_check(); control_config_list_prepare(); gamesnd_play_iface(InterfaceSounds::USER_SELECT); return 0; } int control_config_clear_all() { int i, j, total = 0; config_item_undo *ptr; // first, determine how many bindings need to be changed for (i=0; i= 0) || (Control_config[i].joy_id >= 0)) { total++; } } if (!total) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return -1; } // now, back up the old bindings so we can undo if we want to ptr = get_undo_block(total); for (i=j=0; i= 0) || (Control_config[i].joy_id >= 0)) { ptr->index[j] = i; ptr->list[j] = Control_config[i]; j++; } } Assert(j == total); for (i=0; i= 0); if ( axis > 1 ) { if (Axis_map_to_defaults[axis] < 0) { return -1; } auto joystick = io::joystick::getCurrentJoystick(); if (joystick == nullptr || Axis_map_to_defaults[axis] >= joystick->numAxes()) { return -1; } } return Axis_map_to_defaults[axis]; } int control_config_do_reset() { int i, j, total = 0; config_item_undo *ptr; config_item item; config_item *preset; bool cycling_presets = false; // If there are presets, then we'll cycle to the next preset and reset to that if (!Control_config_presets.empty()) { cycling_presets = true; if (++Defaults_cycle_pos >= Control_config_presets.size()) Defaults_cycle_pos = 0; preset = Control_config_presets[Defaults_cycle_pos]; } else { // If there are no presets, then we'll always reset to the hardcoded defaults preset = Control_config; } // first, determine how many bindings need to be changed for (i=0; iindex[j] = i; ptr->list[j] = Control_config[i]; j++; } } for (i=0; iindex[j] = i | JOY_AXIS; ptr->list[j] = item; j++; } } Assert(j == total); if (cycling_presets) control_config_reset_defaults(Defaults_cycle_pos); else control_config_reset_defaults(); control_config_conflict_check(); control_config_list_prepare(); gamesnd_play_iface(InterfaceSounds::RESET_PRESSED); return 0; } // This sets all the controls to the default values in the given preset // If no preset is given, the hardcoded defaults of Control_config are used void control_config_reset_defaults(int presetnum) { int i; config_item *preset; if (presetnum >= 0) preset = Control_config_presets[presetnum]; else preset = Control_config; // Reset keyboard defaults for (i=0; i Scroll_offset); while (!cc_line_query_visible(Selected_line)) { Selected_line--; } Selected_item = -1; gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } } void control_config_scroll_line_up() { if (Selected_line) { Selected_line--; if (Selected_line < Scroll_offset) { Scroll_offset = Selected_line; } Selected_item = -1; gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } } void control_config_scroll_screen_down() { if (Cc_lines[Num_cc_lines - 1].y + gr_get_font_height() > Cc_lines[Scroll_offset].y + Control_list_coords[gr_screen.res][CONTROL_H_COORD]) { Scroll_offset++; while (!cc_line_query_visible(Selected_line)) { Selected_line++; Assert(Selected_line < Num_cc_lines); } Selected_item = -1; gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } } void control_config_scroll_line_down() { if (Selected_line < Num_cc_lines - 1) { Selected_line++; Assert(Selected_line > Scroll_offset); while (!cc_line_query_visible(Selected_line)) { Scroll_offset++; } Selected_item = -1; gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } } void control_config_toggle_modifier(int bit) { int k, z; z = Cc_lines[Selected_line].cc_index; Assert(!(z & JOY_AXIS)); k = Control_config[z].key_id; if (k < 0) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return; } control_config_bind_key(z, k ^ bit); control_config_conflict_check(); gamesnd_play_iface(InterfaceSounds::USER_SELECT); } void control_config_toggle_invert() { int z; z = Cc_lines[Selected_line].cc_index; Assert(z & JOY_AXIS); z &= ~JOY_AXIS; control_config_save_axis_undo(z); Invert_axis[z] = !Invert_axis[z]; } void control_config_do_bind() { int i; game_flush(); // if ((Selected_line < 0) || (Cc_lines[Selected_line].cc_index & JOY_AXIS)) { if (Selected_line < 0) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return; } for (i=0; ihotspot < 0) { // temporary b->button.create(&Ui_window, NOX("Clear other"), b->x, b->y, 150, 30, 0, 1); // temporary b->button.set_highlight_action(common_play_highlight_sound); continue; } b->button.create(&Ui_window, "", b->x, b->y, 60, 30, ((i == SCROLL_UP_BUTTON) || (i == SCROLL_DOWN_BUTTON)), 1); // set up callback for when a mouse first goes over a button b->button.set_highlight_action(common_play_highlight_sound); if (i<4) { b->button.set_bmaps(b->filename, 5, 1); // a bit of a hack here, but buttons 0-3 need 4 frames loaded } else { b->button.set_bmaps(b->filename); } b->button.link_hotspot(b->hotspot); } // create all text for(i=0; i= 0) { Axis_override = i; bind = 1; } k = game_poll(); Ui_window.use_hack_to_get_around_stupid_problem_flag = 1; Ui_window.process(0); if (k == KEY_ESC) { strcpy_s(bound_string, XSTR( "Canceled", 206)); bound_timestamp = timestamp(2500); control_config_do_cancel(); } else { if (k == KEY_ENTER) { bind = 1; } for (i=0; i= 0) { control_config_bind_axis(z, Axis_override); strcpy_s(bound_string, Joy_axis_text[Axis_override]); font::force_fit_string(bound_string, 39, Conflict_wnd_coords[gr_screen.res][CONTROL_W_COORD]); bound_timestamp = timestamp(2500); control_config_conflict_check(); control_config_list_prepare(); control_config_do_cancel(); } else { control_config_do_cancel(1); } } } } else { if (help_overlay_active(Control_config_overlay_id)) { CC_Buttons[gr_screen.res][HELP_BUTTON].button.reset_status(); Ui_window.set_ignore_gadgets(1); } k = game_poll(); Ui_window.use_hack_to_get_around_stupid_problem_flag = 1; Ui_window.process(0); if ( (k > 0) || B1_JUST_RELEASED ) { if (help_overlay_active(Control_config_overlay_id)) { help_overlay_set_state(Control_config_overlay_id, gr_screen.res, 0); Ui_window.set_ignore_gadgets(0); k = 0; } } if ( !help_overlay_active(Control_config_overlay_id) ) { Ui_window.set_ignore_gadgets(0); } if (k == KEY_ESC) { strcpy_s(bound_string, XSTR( "Canceled", 206)); bound_timestamp = timestamp(2500); control_config_do_cancel(); } else { switch (k & KEY_MASK) { case KEY_LSHIFT: case KEY_RSHIFT: case KEY_LALT: case KEY_RALT: Last_key = k & KEY_MASK; k = 0; break; } if (Cc_lines[Selected_line].cc_index == BANK_WHEN_PRESSED || Cc_lines[Selected_line].cc_index == GLIDE_WHEN_PRESSED) { if ( (Last_key >= 0) && (k <= 0) && !keyd_pressed[Last_key] ) { k = Last_key; } } if ((k > 0) && !Config_allowed[k & KEY_MASK]) { popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR( "That is a non-bindable key. Please try again.", 207)); k = 0; } k &= (KEY_MASK | KEY_SHIFTED | KEY_ALTED); if (k > 0) { z = Cc_lines[Selected_line].cc_index; Assert(!(z & JOY_AXIS)); control_config_bind_key(z, k); strcpy_s(bound_string, textify_scancode(k)); font::force_fit_string(bound_string, 39, Conflict_wnd_coords[gr_screen.res][CONTROL_W_COORD]); bound_timestamp = timestamp(2500); control_config_conflict_check(); control_config_list_prepare(); control_config_do_cancel(); } for (i=0; i 0) || B1_JUST_RELEASED ) { if ( help_overlay_active(Control_config_overlay_id) ) { help_overlay_set_state(Control_config_overlay_id, gr_screen.res, 0); Ui_window.set_ignore_gadgets(0); k = 0; } } if ( !help_overlay_active(Control_config_overlay_id) ) { Ui_window.set_ignore_gadgets(0); } if (k == KEY_ESC) { control_config_do_cancel(); } else { if ((k > 0) && !Config_allowed[k & KEY_MASK]) { k = 0; } k &= (KEY_MASK | KEY_SHIFTED | KEY_ALTED); z = -1; if (k > 0) { for (i=0; i= 0) { Tab = Control_config[z].tab; control_config_list_prepare(); Selected_line = Scroll_offset = 0; for (i=0; i 0) || B1_JUST_RELEASED ) { if ( help_overlay_active(Control_config_overlay_id) ) { help_overlay_set_state(Control_config_overlay_id, gr_screen.res, 0); Ui_window.set_ignore_gadgets(0); k = 0; } } if ( !help_overlay_active(Control_config_overlay_id) ) { Ui_window.set_ignore_gadgets(0); } switch (k) { case KEY_DOWN: // select next line control_config_scroll_line_down(); break; case KEY_UP: // select previous line control_config_scroll_line_up(); break; case KEY_SHIFTED | KEY_TAB: // activate previous tab Tab--; if (Tab < 0) { Tab = NUM_TABS - 1; } Scroll_offset = Selected_line = 0; control_config_list_prepare(); gamesnd_play_iface(InterfaceSounds::SCREEN_MODE_PRESSED); break; case KEY_TAB: // activate next tab Tab++; if (Tab >= NUM_TABS) { Tab = 0; } Scroll_offset = Selected_line = 0; control_config_list_prepare(); gamesnd_play_iface(InterfaceSounds::SCREEN_MODE_PRESSED); break; case KEY_LEFT: Selected_item--; if (Selected_item == -2) { Selected_item = 1; if (Cc_lines[Selected_line].jw < 1) { Selected_item = 0; if (Cc_lines[Selected_line].kw < 1) { Selected_item = -1; } } } gamesnd_play_iface(InterfaceSounds::SCROLL); break; case KEY_RIGHT: Selected_item++; if ((Selected_item == 1) && (Cc_lines[Selected_line].jw < 1)) { Selected_item = -1; } else if (!Selected_item && (Cc_lines[Selected_line].kw < 1)) { Selected_item = -1; } else if (Selected_item > 1) { Selected_item = -1; } gamesnd_play_iface(InterfaceSounds::SCROLL); break; case KEY_BACKSP: // undo control_config_undo_last(); break; case KEY_ESC: control_config_cancel_exit(); break; } // end switch } for (i=0; i= Cc_lines[Selected_line].kx) && (x < Cc_lines[Selected_line].kx + Cc_lines[Selected_line].kw)) { Selected_item = 0; } if ((x >= Cc_lines[Selected_line].jx) && (x < Cc_lines[Selected_line].jx + Cc_lines[Selected_line].jw)) { Selected_item = 1; } gamesnd_play_iface(InterfaceSounds::USER_SELECT); } if (List_buttons[i].double_clicked()) { control_config_do_bind(); } } GR_MAYBE_CLEAR_RES(Background_bitmap); if (Background_bitmap >= 0) { gr_set_bitmap(Background_bitmap); gr_bitmap(0, 0, GR_RESIZE_MENU); } // highlight tab with conflict Ui_window.draw(); for (i=z=0; i= 0) { z = Cc_lines[Selected_line].cc_index; if (z & JOY_AXIS) { if (Invert_axis[z & ~JOY_AXIS]) { CC_Buttons[gr_screen.res][INVERT_AXIS].button.draw_forced(2); } } else { z = Control_config[z].key_id; if (z >= 0) { if (z & KEY_SHIFTED) { CC_Buttons[gr_screen.res][SHIFT_TOGGLE].button.draw_forced(2); } if (z & KEY_ALTED) { CC_Buttons[gr_screen.res][ALT_TOGGLE].button.draw_forced(2); } } } } if (Binding_mode) { CC_Buttons[gr_screen.res][BIND_BUTTON].button.draw_forced(2); } z = Cc_lines[Selected_line].cc_index; x = Conflict_wnd_coords[gr_screen.res][CONTROL_X_COORD] + Conflict_wnd_coords[gr_screen.res][CONTROL_W_COORD] / 2; y = Conflict_wnd_coords[gr_screen.res][CONTROL_Y_COORD] + Conflict_wnd_coords[gr_screen.res][CONTROL_H_COORD] / 2; if (Binding_mode) { int t; t = (int) (timer * 3); if (t % 2) { gr_set_color_fast(&Color_text_normal); gr_get_string_size(&w, NULL, XSTR( "?", 208)); gr_printf_menu(x - w / 2, y - font_height / 2, "%s", XSTR( "?", 208)); } } else if (!(z & JOY_AXIS) && ((Conflicts[z].key >= 0) || (Conflicts[z].joy >= 0))) { i = Conflicts[z].key; if (i < 0) { i = Conflicts[z].joy; } gr_set_color_fast(&Color_text_normal); str = XSTR( "Control conflicts with:", 209); gr_get_string_size(&w, NULL, str); gr_printf_menu(x - w / 2, y - font_height, "%s", str); if (Control_config[i].indexXSTR > 1) { strcpy_s(buf, XSTR(Control_config[i].text, Control_config[i].indexXSTR, true)); } else if (Control_config[i].indexXSTR == 1) { strcpy_s(buf, XSTR(Control_config[i].text, CONTROL_CONFIG_XSTR + i, true)); } else { strcpy_s(buf, Control_config[i].text); } font::force_fit_string(buf, 255, Conflict_wnd_coords[gr_screen.res][CONTROL_W_COORD]); gr_get_string_size(&w, NULL, buf); gr_printf_menu(x - w / 2, y, "%s", buf); } else if (*bound_string) { gr_set_color_fast(&Color_text_normal); gr_get_string_size(&w, NULL, bound_string); gr_printf_menu(x - w / 2, y - font_height / 2, "%s", bound_string); if (timestamp_elapsed(bound_timestamp)) { *bound_string = 0; } } if (Cc_lines[Num_cc_lines - 1].y + font_height > Cc_lines[Scroll_offset].y + Control_list_coords[gr_screen.res][CONTROL_H_COORD]) { gr_set_color_fast(&Color_white); gr_printf_menu(Control_more_coords[gr_screen.res][CONTROL_X_COORD], Control_more_coords[gr_screen.res][CONTROL_Y_COORD], "%s", XSTR( "More...", 210)); } conflict = 0; line = Scroll_offset; while (cc_line_query_visible(line)) { z = Cc_lines[line].cc_index; y = Control_list_coords[gr_screen.res][CONTROL_Y_COORD] + Cc_lines[line].y - Cc_lines[Scroll_offset].y; List_buttons[line - Scroll_offset].update_dimensions(Control_list_coords[gr_screen.res][CONTROL_X_COORD], y, Control_list_coords[gr_screen.res][CONTROL_W_COORD], font_height); List_buttons[line - Scroll_offset].enable(!Binding_mode); Cc_lines[line].kw = Cc_lines[line].jw = 0; if (line == Selected_line){ c = &Color_text_selected; } else if (line == select_tease_line) { c = &Color_text_subselected; } else { c = &Color_text_normal; } gr_set_color_fast(c); if (Cc_lines[line].label) { strcpy_s(buf, Cc_lines[line].label); font::force_fit_string(buf, 255, Control_list_ctrl_w[gr_screen.res]); gr_printf_menu(Control_list_coords[gr_screen.res][CONTROL_X_COORD], y, "%s", buf); } if (!(z & JOY_AXIS)) { k = Control_config[z].key_id; j = Control_config[z].joy_id; x = Control_list_key_x[gr_screen.res]; *buf = 0; if ((k < 0) && (j < 0)) { gr_set_color_fast(&Color_grey); gr_printf_menu(x, y, "%s", XSTR( "None", 211)); } else { if (k >= 0) { strcpy_s(buf, textify_scancode(k)); if (Conflicts[z].key >= 0) { if (c == &Color_text_normal) gr_set_color_fast(&Color_text_error); else { gr_set_color_fast(&Color_text_error_hi); conflict++; } } else if (Selected_item == 1) { gr_set_color_fast(&Color_text_normal); } else { gr_set_color_fast(c); } gr_printf_menu(x, y, "%s", buf); Cc_lines[line].kx = x - Control_list_coords[gr_screen.res][CONTROL_X_COORD]; gr_get_string_size(&w, NULL, buf); Cc_lines[line].kw = w; x += w; if (j >= 0) { gr_set_color_fast(&Color_text_normal); gr_printf_menu(x, y, "%s", XSTR( ", ", 212)); gr_get_string_size(&w, NULL, XSTR( ", ", 212)); x += w; } } if (j >= 0) { strcpy_s(buf, Joy_button_text[j]); if (Conflicts[z].joy >= 0) { if (c == &Color_text_normal) { gr_set_color_fast(&Color_text_error); } else { gr_set_color_fast(&Color_text_error_hi); conflict++; } } else if (!Selected_item) { gr_set_color_fast(&Color_text_normal); } else { gr_set_color_fast(c); } font::force_fit_string(buf, 255, Control_list_key_w[gr_screen.res] + Control_list_key_x[gr_screen.res] - x); gr_printf_menu(x, y, "%s", buf); Cc_lines[line].jx = x - Control_list_coords[gr_screen.res][CONTROL_X_COORD]; gr_get_string_size(&Cc_lines[line].jw, NULL, buf); } } } else { x = Control_list_key_x[gr_screen.res]; j = Axis_map_to[z & ~JOY_AXIS]; if (Binding_mode && (line == Selected_line)) { j = Axis_override; } if (j < 0) { gr_set_color_fast(&Color_grey); gr_printf_menu(x, y, "%s", XSTR( "None", 211)); } else { if (Conflicts_axes[z & ~JOY_AXIS] >= 0) { if (c == &Color_text_normal) { gr_set_color_fast(&Color_text_error); } else { gr_set_color_fast(&Color_text_error_hi); conflict++; } } else if (!Selected_item) { gr_set_color_fast(&Color_text_normal); } else { gr_set_color_fast(c); } gr_string(x, y, Joy_axis_text[j], GR_RESIZE_MENU); } } line++; } CC_Buttons[gr_screen.res][CLEAR_OTHER_BUTTON].button.enable(conflict); i = line - Scroll_offset; while (i < LIST_BUTTONS_MAX) { List_buttons[i++].disable(); } // If multiple controls presets are provided, display which one is in use if (Control_config_presets.size() > 1) { SCP_string preset_str; int matching_preset = -1; for (i=0; i<(int)Control_config_presets.size(); i++) { bool this_preset_matches = true; config_item *this_preset = Control_config_presets[i]; for (j=0; j= 0) { sprintf(preset_str, "Controls: %s", Control_config_preset_names[matching_preset].c_str()); } else { sprintf(preset_str, "Controls: custom"); } gr_get_string_size(&w, NULL, preset_str.c_str()); gr_set_color_fast(&Color_text_normal); if (gr_screen.res == GR_640) { gr_string(16, (24 - font_height) / 2, preset_str.c_str(), GR_RESIZE_MENU); } else { gr_string(24, (40 - font_height) / 2, preset_str.c_str(), GR_RESIZE_MENU); } } // blit help overlay if active help_overlay_maybe_blit(Control_config_overlay_id, gr_screen.res); gr_flip(); } float check_control_timef(int id) { float t1, t2; // if type isn't continuous, we shouldn't be using this function, cause it won't work. Assert(Control_config[id].type == CC_TYPE_CONTINUOUS); // first, see if control actually used (makes sure modifiers match as well) if (!check_control(id)) { Control_config[id].continuous_ongoing = false; return 0.0f; } t1 = key_down_timef(Control_config[id].key_id); if (t1) { control_used(id); } t2 = joy_down_time(Control_config[id].joy_id); if (t2) { control_used(id); } if (t1 + t2) { // We want to set this to true only after visiting control_used() (above) // to allow it to tell the difference between an ongoing continuous action // started before and a continuous action being started right now. Control_config[id].continuous_ongoing = true; return t1 + t2; } return 1.0f; } void control_check_indicate() { #ifndef NDEBUG if (Show_controls_info) { gr_set_color_fast(&HUD_color_debug); gr_printf_no_resize(gr_screen.center_offset_x + gr_screen.center_w - 154, gr_screen.center_offset_y + 5, NOX("Ctrls checked: %d"), Control_check_count); } #endif Control_check_count = 0; } int check_control_used(int id, int key) { int z, mask; static int last_key = 0; Control_check_count++; if (key < 0) { key = last_key; } last_key = key; // if we're in multiplayer text enter (for chat) mode, check to see if we should ignore controls if ((Game_mode & GM_MULTIPLAYER) && multi_ignore_controls()){ return 0; } if (Control_config[id].disabled) return 0; if (Control_config[id].type == CC_TYPE_CONTINUOUS) { if (joy_down(Control_config[id].joy_id) || joy_down_count(Control_config[id].joy_id, 1)) { control_used(id); return 1; } if ((Control_config[id].joy_id >= 0) && (Control_config[id].joy_id < MOUSE_NUM_BUTTONS)) { if (mouse_down(1 << Control_config[id].joy_id) || mouse_down_count(1 << Control_config[id].joy_id)) { control_used(id); return 1; } } // check what current modifiers are pressed mask = 0; if (keyd_pressed[KEY_LSHIFT] || key_down_count(KEY_LSHIFT) || keyd_pressed[KEY_RSHIFT] || key_down_count(KEY_RSHIFT)) { mask |= KEY_SHIFTED; } if (keyd_pressed[KEY_LALT] || key_down_count(KEY_LALT) || keyd_pressed[KEY_RALT] || key_down_count(KEY_RALT)) { mask |= KEY_ALTED; } z = Control_config[id].key_id; if (z >= 0) { if ( (z != KEY_LALT) && (z != KEY_RALT) && (z != KEY_LSHIFT) && (z != KEY_RSHIFT) ) { // if current modifiers don't match action's modifiers, don't register control active. if ((z & (KEY_SHIFTED | KEY_ALTED)) != mask) { return 0; } } z &= KEY_MASK; if (keyd_pressed[z] || key_down_count(z)) { control_used(id); return 1; } } return 0; } if ((Control_config[id].key_id == key) || joy_down_count(Control_config[id].joy_id, 1) || ((Control_config[id].joy_id >= 0) && (Control_config[id].joy_id < MOUSE_NUM_BUTTONS) && mouse_down_count(1 << Control_config[id].joy_id))) { //mprintf(("Key used %d\n", key)); control_used(id); return 1; } return 0; } /** * Wrapper for check_control_used. Allows the game to ignore the key if told to do so by the ignore-key SEXP. */ int check_control(int id, int key) { if (check_control_used(id, key)) { if (Ignored_keys[id]) { if (Ignored_keys[id] > 0) { Ignored_keys[id]--; } return 0; } return 1; } if (Control_config[id].continuous_ongoing) { // If we reach this point, then it means this is a continuous control // which has just been released Script_system.SetHookVar("Action", 's', Control_config[id].text); Script_system.RunCondition(CHA_ONACTIONSTOPPED, nullptr, id); Script_system.RemHookVar("Action"); Control_config[id].continuous_ongoing = false; } return 0; } // get heading, pitch, bank, throttle abs. and throttle rel. values. void control_get_axes_readings(int *h, int *p, int *b, int *ta, int *tr) { int axes_values[JOY_NUM_AXES]; joystick_read_raw_axis(JOY_NUM_AXES, axes_values); // joy_get_scaled_reading will return a value represents the joystick pos from -1 to +1 (fixed point) *h = 0; if (Axis_map_to[0] >= 0) { *h = joy_get_scaled_reading(axes_values[Axis_map_to[0]]); } *p = 0; if (Axis_map_to[1] >= 0) { *p = joy_get_scaled_reading(axes_values[Axis_map_to[1]]); } *b = 0; if (Axis_map_to[2] >= 0) { *b = joy_get_scaled_reading(axes_values[Axis_map_to[2]]); } *ta = 0; if (Axis_map_to[3] >= 0) { *ta = joy_get_unscaled_reading(axes_values[Axis_map_to[3]]); } *tr = 0; if (Axis_map_to[4] >= 0) { *tr = joy_get_scaled_reading(axes_values[Axis_map_to[4]]); } if (Invert_axis[0]) { *h = -(*h); } if (Invert_axis[1]) { *p = -(*p); } if (Invert_axis[2]) { *b = -(*b); } if (Invert_axis[3]) { *ta = F1_0 - *ta; } if (Invert_axis[4]) { *tr = -(*tr); } return; } int Last_frame_timestamp; void control_used(int id) { // if we have set this key to be ignored, ignore it if (Ignored_keys[id]) { return; } // This check needs to be done because the control code might call this function more than once per frame, // and we don't want to run the hooks more than once per frame if (Control_config[id].used < Last_frame_timestamp) { if (!Control_config[id].continuous_ongoing) { Script_system.SetHookVar("Action", 's', Control_config[id].text); Script_system.RunCondition(CHA_ONACTION, nullptr, id); Script_system.RemHookVar("Action"); if (Control_config[id].type == CC_TYPE_CONTINUOUS) Control_config[id].continuous_ongoing = true; } Control_config[id].used = timestamp(); } } void control_config_clear_used_status() { int i; for (i=0; i