From 124002bb1fdfbcefb4617751f6ff67730b5ca561 Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Wed, 12 Apr 2023 15:07:04 -0600 Subject: [PATCH] Rewrite keybinds module to function more like vanilla keybinds (#33) * Rewrite keybinding module to work like vanilla keybinds * Treat left and right modifier keys separately * Fix minor crash bug * Don't close views if actively watching for keys --- .../mods/dmf/modules/core/keybindings.lua | 602 ++++++++---------- dmf/scripts/mods/dmf/modules/core/options.lua | 29 +- .../mods/dmf/modules/core/safe_calls.lua | 2 +- .../mods/dmf/modules/gui/custom_views.lua | 24 +- .../modules/ui/options/dmf_options_view.lua | 2 +- .../dmf/modules/ui/options/mod_options.lua | 65 +- 6 files changed, 350 insertions(+), 374 deletions(-) diff --git a/dmf/scripts/mods/dmf/modules/core/keybindings.lua b/dmf/scripts/mods/dmf/modules/core/keybindings.lua index 50105af..3714424 100644 --- a/dmf/scripts/mods/dmf/modules/core/keybindings.lua +++ b/dmf/scripts/mods/dmf/modules/core/keybindings.lua @@ -2,164 +2,16 @@ local dmf = get_mod("DMF") local InputUtils = require("scripts/managers/input/input_utils") -local PRIMARY_BINDABLE_KEYS = { - ["keyboard"] = { - [8] = {"Backspace", "backspace"}, - [9] = {"Tab", "tab"}, - [13] = {"Enter", "enter"}, - [20] = {"Caps Lock", "caps lock"}, - [32] = {"Space", "space"}, - [33] = {"Page Up", "page up"}, - [34] = {"Page Down", "page down"}, - [35] = {"End", "end"}, - [36] = {"Home", "home"}, - [37] = {"Left", "left"}, - [38] = {"Up", "up"}, - [39] = {"Right", "right"}, - [40] = {"Down", "down"}, - [45] = {"Insert", "insert"}, - [46] = {"Delete", "delete"}, - [48] = {"0", "0"}, - [49] = {"1", "1"}, - [50] = {"2", "2"}, - [51] = {"3", "3"}, - [52] = {"4", "4"}, - [53] = {"5", "5"}, - [54] = {"6", "6"}, - [55] = {"7", "7"}, - [56] = {"8", "8"}, - [57] = {"9", "9"}, - [65] = {"A", "a"}, - [66] = {"B", "b"}, - [67] = {"C", "c"}, - [68] = {"D", "d"}, - [69] = {"E", "e"}, - [70] = {"F", "f"}, - [71] = {"G", "g"}, - [72] = {"H", "h"}, - [73] = {"I", "i"}, - [74] = {"J", "j"}, - [75] = {"K", "k"}, - [76] = {"L", "l"}, - [77] = {"M", "m"}, - [78] = {"N", "n"}, - [79] = {"O", "o"}, - [80] = {"P", "p"}, - [81] = {"Q", "q"}, - [82] = {"R", "r"}, - [83] = {"S", "s"}, - [84] = {"T", "t"}, - [85] = {"U", "u"}, - [86] = {"V", "v"}, - [87] = {"W", "w"}, - [88] = {"X", "x"}, - [89] = {"Y", "y"}, - [90] = {"Z", "z"}, - [91] = {"Win", "win"}, - [92] = {"RWin", "right win"}, - [96] = {"Num 0", "numpad 0"}, - [97] = {"Num 1", "numpad 1"}, - [98] = {"Num 2", "numpad 2"}, - [99] = {"Num 3", "numpad 3"}, - [100] = {"Num 4", "numpad 4"}, - [101] = {"Num 5", "numpad 5"}, - [102] = {"Num 6", "numpad 6"}, - [103] = {"Num 7", "numpad 7"}, - [104] = {"Num 8", "numpad 8"}, - [105] = {"Num 9", "numpad 9"}, - [106] = {"Num *", "numpad *"}, - [107] = {"Num +", "numpad +"}, - [109] = {"Num -", "numpad -"}, - [110] = {"Num .", "numpad ."}, - [111] = {"Num /", "numpad /"}, - [112] = {"F1", "f1"}, - [113] = {"F2", "f2"}, - [114] = {"F3", "f3"}, - [115] = {"F4", "f4"}, - [116] = {"F5", "f5"}, - [117] = {"F6", "f6"}, - [118] = {"F7", "f7"}, - [119] = {"F8", "f8"}, - [120] = {"F9", "f9"}, - [121] = {"F10", "f10"}, - [122] = {"F11", "f11"}, - [123] = {"F12", "f12"}, - [144] = {"Num Lock", "num lock"}, - [145] = {"Scroll Lock", "scroll lock"}, - [166] = {"Browser Back", "browser back"}, - [167] = {"Browser Forward", "browser forward"}, - [168] = {"Browser Refresh", "browser refresh"}, - [169] = {"Browser Stop", "browser stop"}, - [170] = {"Browser Search", "browser search"}, - [171] = {"Browser Favorites", "browser favorites"}, - [172] = {"Browser Home", "browser home"}, - [173] = {"Volume Mute", "volume mute"}, - [174] = {"Volume Down", "volume down"}, - [175] = {"Volume Up", "volume up"}, - [176] = {"Next Track", "next track"}, - [177] = {"Previous Track", "previous track"}, - [178] = {"Stop", "stop"}, - [179] = {"Play/Pause", "play pause"}, - [180] = {"Mail", "mail"}, - [181] = {"Media", "media"}, - [182] = {"Start Application 1", "start app 1"}, - [183] = {"Start Application 2", "start app 2"}, - [186] = {";", ";"}, - [187] = {"=", "="}, - [188] = {",", ","}, - [189] = {"-", "-"}, - [190] = {".", "."}, - [191] = {"/", "/"}, - [192] = {"`", "`"}, - [219] = {"[", "["}, - [220] = {"\\", "\\"}, - [221] = {"]", "]"}, - [222] = {"'", "'"}, - --?[226] = {"\", "oem_102 (> <)"}, - [256] = {"Num Enter", "numpad enter"} - }, - ["mouse"] = { - [0] = {"Mouse Left", "mouse left"}, - [1] = {"Mouse Right", "mouse right"}, - [2] = {"Mouse Middle", "mouse middle"}, - [3] = {"Mouse Extra 1", "mouse extra 1"}, - [4] = {"Mouse Extra 2", "mouse extra 2"}, - [10] = {"Mouse Wheel Up", "mouse wheel up"}, - [11] = {"Mouse Wheel Down", "mouse wheel down"}, - [12] = {"Mouse Wheel Left", "mouse wheel left"}, - [13] = {"Mouse Wheel Right", "mouse wheel right"} - }, - --[[ -- will work on this if it will be needed - ["gamepad"] = { - [0] = {"", "d_up"}, - [1] = {"", "d_down"}, - [2] = {"", "d_left"}, - [3] = {"", "d_right"}, - [4] = {"", "start"}, - [5] = {"", "back"}, - [6] = {"", "left_thumb"}, - [7] = {"", "right_thumb"}, - [8] = {"", "left_shoulder"}, - [9] = {"", "right_shoulder"}, - [10] = {"", "left_trigger"}, - [11] = {"", "right_trigger"}, - [12] = {"", "a"}, - [13] = {"", "b"}, - [14] = {"", "x"}, - [15] = {"", "y"}, - }]] -} - local MODIFIER_KEYS = { - ["left shift"] = {160, "shift", "keyboard", 161}, - ["right shift"] = {160, "shift", "keyboard", 161}, - ["shift"] = {160, "shift", "keyboard", 161}, - ["left ctrl"] = {162, "ctrl", "keyboard", 163}, - ["right ctrl"] = {162, "ctrl", "keyboard", 163}, - ["ctrl"] = {162, "ctrl", "keyboard", 163}, - ["left alt"] = {164, "alt", "keyboard", 165}, - ["right alt"] = {164, "alt", "keyboard", 165}, - ["alt"] = {164, "alt", "keyboard", 165}, + ["keyboard_left shift"] = {160, "shift", "keyboard", 161}, + ["keyboard_right shift"] = {160, "shift", "keyboard", 161}, + ["keyboard_shift"] = {160, "shift", "keyboard", 161}, + ["keyboard_left ctrl"] = {162, "ctrl", "keyboard", 163}, + ["keyboard_right ctrl"] = {162, "ctrl", "keyboard", 163}, + ["keyboard_ctrl"] = {162, "ctrl", "keyboard", 163}, + ["keyboard_left alt"] = {164, "alt", "keyboard", 165}, + ["keyboard_right alt"] = {164, "alt", "keyboard", 165}, + ["keyboard_alt"] = {164, "alt", "keyboard", 165}, } -- Both are treated equally in keybinds, but these global keys aren't localized @@ -169,43 +21,8 @@ local MODIFIER_KEYS_LOC_ALIAS = { keyboard_shift = "keyboard_left shift", } -local KEYS_INFO = {} - --- Populate KEYS_INFO: Index, readable name, device name -for input_device_name, input_device_keys in pairs(PRIMARY_BINDABLE_KEYS) do - for key_index, key_info in pairs(input_device_keys) do - KEYS_INFO[key_info[2]] = {key_index, key_info[1], input_device_name} - end -end - -for key_id, key_data in pairs(MODIFIER_KEYS) do - KEYS_INFO[key_id] = key_data -end - --- Can't use 'Device.released' because it will break keybinds if button is released when game window is not active. -local CHECK_INPUT_FUNCTIONS = { - keyboard = { - PRESSED = function(key_id) return Keyboard.pressed(KEYS_INFO[key_id][1]) end, - RELEASED = function(key_id) return Keyboard.button(KEYS_INFO[key_id][1]) == 0 end - }, - mouse = { - PRESSED = function(key_id) return Mouse.pressed(KEYS_INFO[key_id][1]) end, - RELEASED = function(key_id) return Mouse.button(KEYS_INFO[key_id][1]) == 0 end - } -} - -local KEYS_INPUT_FUNCTIONS = {} - -for key_id, key_data in pairs(KEYS_INFO) do - KEYS_INPUT_FUNCTIONS[key_id] = { - check_pressed = CHECK_INPUT_FUNCTIONS[key_data[3]].PRESSED, - check_released = CHECK_INPUT_FUNCTIONS[key_data[3]].RELEASED - } -end - local _raw_keybinds_data = {} local _keybinds = {} -local _pressed_key local ERRORS = { PREFIX = { @@ -216,12 +33,17 @@ local ERRORS = { } } +local SUPPORTED_DEVICES = { + "keyboard", + "mouse", +} + -- ##################################################################################################################### -- ##### Local functions ############################################################################################### -- ##################################################################################################################### +-- @TODO: Link this input service to the player's input service and find some way to see if it's blocked local function is_dmf_input_service_active() - -- @TODO: Implement check for active DMF input service return true end @@ -253,118 +75,260 @@ local function perform_keybind_action(data, is_pressed) end +-- ##################################################################################################################### +-- ##### Input service functions ####################################################################################### +-- ##################################################################################################################### + local function is_modifier_pressed(modifier) return (Keyboard.button(MODIFIER_KEYS[modifier][1]) + Keyboard.button(MODIFIER_KEYS[modifier][4])) > 0 end +local function default_boolean() + return false +end + +local function default_vector3() + return Vector3(0, 0, 0) +end + +local function default_float() + return 0 +end + +local function boolean_combine(value_one, value_two) + return value_one or value_two +end + +local function vector3_combine(value_one, value_two) + if Vector3.length(value_two) < Vector3.length(value_one) then + return value_one + else + return value_two + end +end + +local function float_combine(value_one, value_two) + return math.max(value_one, value_two) +end + +local ACTION_TYPES = { + pressed = { + device_func = "pressed", + type = "boolean", + default_device_func = default_boolean, + combine_func = boolean_combine + }, + held = { + device_func = "held", + type = "boolean", + default_device_func = default_boolean, + combine_func = boolean_combine + }, + released = { + device_func = "released", + type = "boolean", + default_device_func = default_boolean, + combine_func = boolean_combine + }, + axis = { + device_func = "axis", + type = "vector3", + default_device_func = default_vector3, + combine_func = vector3_combine + }, + button = { + device_func = "button", + type = "float", + default_device_func = default_float, + combine_func = float_combine + } +} + + +local function _enabler_func(cb, action_type, enablers) + for _, enabler in ipairs(enablers) do + if not enabler.device:held(enabler.index) then + return ACTION_TYPES[action_type].default_device_func() + end + end + + return cb() +end + + +local function _disabler_func(cb, action_type, disablers) + for _, disabler in ipairs(disablers) do + if disabler.device:held(disabler.index) then + return ACTION_TYPES[action_type].default_device_func() + end + end + + return cb() +end + + +local function get_corresponding_device(key_id) + for _, device_type in pairs(SUPPORTED_DEVICES) do + + local device = Managers.input:_find_active_device(device_type) + if device then + + local index = device:button_index(key_id) + if index then + return { + device = device, + index = index, + key_id = key_id, + } + end + + index = device:axis_index(key_id) + if index then + return { + device = device, + index = index, + key_id = key_id, + } + end + end + end +end + + +local function get_key_info(keybind) + local main = keybind.main + local e = keybind.enablers + local d = keybind.disablers + + local info = get_corresponding_device(main) + + if info then + if #e > 0 then + local enablers = {} + + for _, key in ipairs(e) do + local enabler = get_corresponding_device(key) + + if enabler then + enablers[#enablers + 1] = enabler + end + end + + info.enablers = enablers + end + + if #d > 0 then + local disablers = {} + + for _, key in ipairs(d) do + local disabler = get_corresponding_device(key) + + if disabler then + disablers[#disablers + 1] = disabler + end + end + + info.disablers = disablers + end + end + + return info +end + + +local function create_eval_func(keybind) + local info = get_key_info(keybind) + if info then + + local eval_func + if keybind.trigger == "held" then + eval_func = callback(info.device, keybind.trigger, info.index) + else + local function func(device, trigger, index) + return device[trigger](index) + end + + eval_func = callback(func, info.device:raw_device(), keybind.trigger, info.index) + end + + if info.enablers then + eval_func = callback(_enabler_func, eval_func, keybind.trigger, info.enablers) + end + + if info.disablers then + eval_func = callback(_disabler_func, eval_func, keybind.trigger, info.disablers) + end + + return eval_func + end +end + -- ##################################################################################################################### -- ##### DMF internal functions and variables ########################################################################## -- ##################################################################################################################### -- Checks for pressed and released keybinds, performs keybind actions. --- * Checks for both right and left key modifiers (ctrl, alt, shift). --- * If some keybind is pressed, won't check for other keybinds until this keybind is released. --- * If several mods bound the same keys, keybind action will be performed for all of them, when keybind is pressed. --- * Keybind is considered released, when its primary key is released. +-- * Right and left key modifiers (ctrl, alt, shift) are checked separately. +-- * If several mods bound the same keys, keybind action will be performed for all of them when pressed. +-- * Keybind is considered released when it was previously pressed and is no longer. function dmf.check_keybinds() - local pressed_modifiers = { - ctrl = is_modifier_pressed("ctrl"), - alt = is_modifier_pressed("alt"), - shift = is_modifier_pressed("shift") - } - if not _pressed_key then - for primary_key, keybinds_data in pairs(_keybinds) do - if keybinds_data.check_pressed(primary_key) then - for _, keybind_data in ipairs(keybinds_data) do + -- For every keybind + for _, keybind_data in ipairs(_keybinds) do - local all_pressed = true + -- If the keybind is pressed + if keybind_data.eval_func() then - -- Check that every enabler key is pressed - for enabler, _ in pairs(keybind_data.enablers) do - if MODIFIER_KEYS[enabler] then - if not pressed_modifiers[MODIFIER_KEYS[enabler][2]] then - all_pressed = false - break - end - elseif KEYS_INPUT_FUNCTIONS[enabler].check_released(enabler) then - all_pressed = false - break - end - end + -- Peform the keybind action once + if (not keybind_data.pressed) and perform_keybind_action(keybind_data, true) then - -- Check that no modifier keys are pressed that shouldn't be - if all_pressed and ( - (not keybind_data.ctrl and pressed_modifiers.ctrl) or - (not keybind_data.alt and pressed_modifiers.alt) or - (not keybind_data.shift and pressed_modifiers.shift) - ) - then - all_pressed = false - end - - -- Peform the keybind action if everything validates - if all_pressed then - if perform_keybind_action(keybind_data, true) then - if keybind_data.trigger == "held" then - keybind_data.release_action = true - end - _pressed_key = primary_key - end - end + -- Queue the release action if applicable + if keybind_data.trigger == "held" then + keybind_data.release_action = true end - end - end - end - if _pressed_key then - if _keybinds[_pressed_key].check_released(_pressed_key) then - for _, keybind_data in ipairs(_keybinds[_pressed_key]) do - if keybind_data.release_action then - perform_keybind_action(keybind_data, false) - keybind_data.release_action = nil - end + -- Prevent a repeat action + keybind_data.pressed = true + end + + -- If the keybind was previously but is no longer pressed + elseif keybind_data.pressed then + + -- Reactivate the keybind + keybind_data.pressed = nil + + -- Play the release action if applicable + if keybind_data.release_action then + perform_keybind_action(keybind_data, false) + keybind_data.release_action = nil end - _pressed_key = nil end end end --- Converts managable (raw) table of keybinds data to the table designed for the function checking for pressed and --- released keybinds. After initial call requires to be called every time some keybind is added/removed. +-- Converts manageable (raw) table of keybinds data to a table of callbacks for checking pressed and +-- released keybinds. After initial call it must be called every time some keybind is added/removed. function dmf.generate_keybinds() _keybinds = {} for mod, mod_keybinds in pairs(_raw_keybinds_data) do for _, raw_keybind_data in pairs(mod_keybinds) do - local keys = raw_keybind_data.keys - local primary_key = keys[1] - local enabler_keys = {} - for i = 2, #keys do - enabler_keys[keys[i]] = true - end - local keybind_data = { - mod = mod, - global = raw_keybind_data.global, - trigger = raw_keybind_data.trigger, - type = raw_keybind_data.type, - enablers = enabler_keys, - ctrl = enabler_keys["ctrl"] or enabler_keys["left ctrl"] or enabler_keys["right ctrl"], - alt = enabler_keys["alt"] or enabler_keys["left alt"] or enabler_keys["right alt"], - shift = enabler_keys["shift"] or enabler_keys["left shift"] or enabler_keys["right shift"], + mod = mod, + global = raw_keybind_data.global, + trigger = raw_keybind_data.trigger, + type = raw_keybind_data.type, + eval_func = create_eval_func(raw_keybind_data), function_name = raw_keybind_data.function_name, view_name = raw_keybind_data.view_name } - _keybinds[primary_key] = _keybinds[primary_key] or { - check_pressed = KEYS_INPUT_FUNCTIONS[primary_key].check_pressed, - check_released = KEYS_INPUT_FUNCTIONS[primary_key].check_released - } - table.insert(_keybinds[primary_key], keybind_data) + if keybind_data.eval_func then + table.insert(_keybinds, keybind_data) + end end end end @@ -372,7 +336,7 @@ end -- Adds/removes keybinds. function dmf.add_mod_keybind(mod, setting_id, raw_keybind_data) - if #raw_keybind_data.keys > 0 then + if raw_keybind_data.main then _raw_keybinds_data[mod] = _raw_keybinds_data[mod] or {} _raw_keybinds_data[mod][setting_id] = raw_keybind_data elseif _raw_keybinds_data[mod] and _raw_keybinds_data[mod][setting_id] then @@ -388,53 +352,32 @@ end -- Creates DMF input service. It is required to know when non-global keybinds can be triggered. -- (Called every time a level is loaded, or on mods reload) +-- @TODO: Link this input service to the player's input service and find some way to see if it's blocked function dmf.create_keybinds_input_service() - -- @TODO: Link this input service to the player's input service and find some way to see if it's blocked - --[[ - -- To create the DMF input service in Darktide - local input_manager = Managers.input - local service_type = "DMF" - input_manager:add_setting(service_type, aliases, raw_key_table, filter_table, default_devices) - input_manager:get_input_service(service_type) - --]] + -- -- To create the DMF input service in Darktide + -- local input_manager = Managers.input + -- local service_type = "DMF" + -- input_manager:add_setting(service_type, aliases, raw_key_table, filter_table, default_devices) + -- input_manager:get_input_service(service_type) end --- Converts key_index to readable key_id, which is used by DMF to identify keys. --- (Used for capturing keybinds) -function dmf.get_key_id(device, key_index) - local key_info = PRIMARY_BINDABLE_KEYS[device][key_index] - return key_info and key_info[2] -end - - --- Simply tells if key with key_id can be binded as primary key. --- (Used for verifying keybind widgets) +-- @TODO: So far it seems like any key can be a primary keybind, but is this actually true? function dmf.can_bind_as_primary_key(key_id) - return KEYS_INFO[key_id] and not MODIFIER_KEYS[key_id] + return true end --- Builds string with readable keys' names to look like e.g. "Ctrl+Alt+Shift+". --- (Used in keybind widget) -function dmf.build_keybind_string(keys) - local key_unassigned_string = Managers.localization:localize("loc_keybind_unassigned") - local keybind_result = dmf.keys_to_keybind_result(keys) - return keybind_result and InputUtils.localized_string_from_key_info(keybind_result) or key_unassigned_string -end - - --- Translate key watch result to mod options keybind --- (Used in keybind widget) -function dmf.keybind_result_to_keys(keybind_result) +-- Translate keywatch result to array of local key names +-- (Used in keybind widget for compatibility with legacy settings) +function dmf.keywatch_result_to_local_keys(keywatch_result) local keys = {} -- Get the local name of the main key - if keybind_result.main then + if keywatch_result.main then - local global_name = keybind_result.main - local device_type = InputUtils.key_device_type(global_name) - local local_name = InputUtils.local_key_name(global_name, device_type) + local global_name = keywatch_result.main + local local_name = InputUtils.local_key_name(global_name, InputUtils.key_device_type(global_name)) -- Check for a missing or unbindable primary key name if not local_name or not dmf.can_bind_as_primary_key(local_name) then @@ -445,12 +388,9 @@ function dmf.keybind_result_to_keys(keybind_result) end -- Add the enablers keys as additional keys - if keybind_result.enablers then - for _, global_name in ipairs(keybind_result.enablers) do - - local device_type = InputUtils.key_device_type(global_name) - local local_name = InputUtils.local_key_name(global_name, device_type) - + if keywatch_result.enablers then + for _, global_name in ipairs(keywatch_result.enablers) do + local local_name = InputUtils.local_key_name(global_name, InputUtils.key_device_type(global_name)) keys[#keys + 1] = local_name end end @@ -459,21 +399,29 @@ function dmf.keybind_result_to_keys(keybind_result) end --- Translate mod options keybind to key watch result --- (Used in keybind widget) -function dmf.keys_to_keybind_result(keys) - local keybind_result = { +-- Translate array of local key names to keywatch result +-- (Used in keybind widget for compatibility with legacy settings) +function dmf.local_keys_to_keywatch_result(keys) + local keywatch_result = { enablers = {}, disablers = {} } - if not keys or #keys == 0 then + if type(keys) ~= "table" or #keys == 0 then return nil end if keys[1] then local local_name = keys[1] - local global_name = KEYS_INFO[local_name] and InputUtils.local_to_global_name(local_name, KEYS_INFO[local_name][3]) + local global_name + + -- Check all supported devices for the global name + for _, device_type in ipairs(SUPPORTED_DEVICES) do + global_name = InputUtils.local_to_global_name(local_name, device_type) + if get_corresponding_device(global_name) then + break + end + end -- End early if our main key doesn't exist, and return an empty result if not global_name then @@ -483,23 +431,31 @@ function dmf.keys_to_keybind_result(keys) if MODIFIER_KEYS_LOC_ALIAS[global_name] then global_name = MODIFIER_KEYS_LOC_ALIAS[global_name] end - keybind_result.main = global_name + keywatch_result.main = global_name end -- Add all remaining keys to the enablers list for i = 2, #keys do local local_name = keys[i] - local global_name = KEYS_INFO[local_name] and InputUtils.local_to_global_name(local_name, KEYS_INFO[local_name][3]) + local global_name + + -- Check all supported devices for the global name + for _, device_type in ipairs(SUPPORTED_DEVICES) do + global_name = InputUtils.local_to_global_name(local_name,device_type) + if get_corresponding_device(global_name) then + break + end + end if global_name then if MODIFIER_KEYS_LOC_ALIAS[global_name] then global_name = MODIFIER_KEYS_LOC_ALIAS[global_name] end - keybind_result.enablers[#keybind_result.enablers + 1] = global_name + keywatch_result.enablers[#keywatch_result.enablers + 1] = global_name end end - return keybind_result + return keywatch_result end -- ##################################################################################################################### diff --git a/dmf/scripts/mods/dmf/modules/core/options.lua b/dmf/scripts/mods/dmf/modules/core/options.lua index e10fa31..e8fbe82 100644 --- a/dmf/scripts/mods/dmf/modules/core/options.lua +++ b/dmf/scripts/mods/dmf/modules/core/options.lua @@ -534,18 +534,23 @@ local function initialize_default_settings_and_keybinds(mod, initialized_widgets mod:set(data.setting_id, data.default_value) end if data.type == "keybind" then - dmf.add_mod_keybind( - mod, - data.setting_id, - { - global = data.keybind_global, - trigger = data.keybind_trigger, - type = data.keybind_type, - keys = mod:get(data.setting_id), - function_name = data.function_name, - view_name = data.view_name - } - ) + local keywatch_result = dmf.local_keys_to_keywatch_result(mod:get(data.setting_id)) + if keywatch_result and keywatch_result.main then + dmf.add_mod_keybind( + mod, + data.setting_id, + { + global = data.keybind_global, + trigger = data.keybind_trigger, + type = data.keybind_type, + main = keywatch_result.main, + enablers = keywatch_result.enablers, + disablers = keywatch_result.disablers, + function_name = data.function_name, + view_name = data.view_name + } + ) + end end end end diff --git a/dmf/scripts/mods/dmf/modules/core/safe_calls.lua b/dmf/scripts/mods/dmf/modules/core/safe_calls.lua index 2b25193..866aaeb 100644 --- a/dmf/scripts/mods/dmf/modules/core/safe_calls.lua +++ b/dmf/scripts/mods/dmf/modules/core/safe_calls.lua @@ -87,7 +87,7 @@ function dmf.safe_call_dofile(mod, error_prefix_data, file_path) show_error(mod, error_prefix_data, "file path should be a string.") return false end - return dmf.safe_call(mod, error_prefix_data, dofile, mod, file_path) + return dmf.safe_call(mod, error_prefix_data, dofile, file_path) end diff --git a/dmf/scripts/mods/dmf/modules/gui/custom_views.lua b/dmf/scripts/mods/dmf/modules/gui/custom_views.lua index 97b866f..3ed848a 100644 --- a/dmf/scripts/mods/dmf/modules/gui/custom_views.lua +++ b/dmf/scripts/mods/dmf/modules/gui/custom_views.lua @@ -7,6 +7,8 @@ local _custom_views_data = {} local _ingame_ui local _loaded_views = {} +local _key_watch = false + local ERRORS = { THROWABLE = { -- inject_view: @@ -312,13 +314,13 @@ end -- Track the creation of the view loader -dmf:hook_safe(ViewLoader, "init", function() +dmf:hook_safe(CLASS.ViewLoader, "init", function() _custom_view_persistent_data.loader_initialized = true end) -- Track the loading of views, set the loader flag if class selection is reached -dmf:hook_safe(UIManager, "load_view", function(self, view_name) +dmf:hook_safe(CLASS.UIManager, "load_view", function(self, view_name) if view_name == "class_selection_view" then _custom_view_persistent_data.loader_initialized = true end @@ -326,13 +328,13 @@ dmf:hook_safe(UIManager, "load_view", function(self, view_name) end) -- Track the unloading of views -dmf:hook_safe(UIManager, "unload_view", function(self, view_name) +dmf:hook_safe(CLASS.UIManager, "unload_view", function(self, view_name) _loaded_views[view_name] = nil end) -- Store the view handler for later use and inject views -dmf:hook_safe(UIViewHandler, "init", function(self) +dmf:hook_safe(CLASS.UIViewHandler, "init", function(self) _ingame_ui = self for view_name, _ in pairs(_custom_views_data) do if not dmf.safe_call_nrc(self, {ERRORS.PREFIX.ingameui_hook_injection, view_name}, inject_view, view_name) then @@ -341,6 +343,16 @@ dmf:hook_safe(UIViewHandler, "init", function(self) end end) +-- Track the start of key watches +dmf:hook_safe(CLASS.InputManager, "start_key_watch", function(self) + _key_watch = true +end) + +-- Track the end of key watches +dmf:hook_safe(CLASS.InputManager, "stop_key_watch", function(self) + _key_watch = false +end) + -- ##################################################################################################################### -- ##### DMF internal functions and variables ########################################################################## -- ##################################################################################################################### @@ -368,8 +380,8 @@ function dmf.keybind_toggle_view(mod, view_name, keybind_transition_data, can_pe -- If the view is open, this is a toggle close if Managers.ui:view_active(view_name) then - -- Don't close the view if it's already closing - if not Managers.ui:is_view_closing(view_name) then + -- Don't close the view if it's already closing or we have an active key watch + if not Managers.ui:is_view_closing(view_name) and not _key_watch then local force_close = true Managers.ui:close_view(view_name, force_close) end diff --git a/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view.lua b/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view.lua index 6d84291..dec9be4 100644 --- a/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view.lua +++ b/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view.lua @@ -228,7 +228,7 @@ DMFOptionsView._restart_popup_info = function (self) close_on_pressed = true, callback = callback(function () self._popup_id = nil - local view_name = "options_view" + local view_name = "dmf_options_view" self._require_restart = false Managers.ui:close_view(view_name) diff --git a/dmf/scripts/mods/dmf/modules/ui/options/mod_options.lua b/dmf/scripts/mods/dmf/modules/ui/options/mod_options.lua index 5084242..940eb24 100644 --- a/dmf/scripts/mods/dmf/modules/ui/options/mod_options.lua +++ b/dmf/scripts/mods/dmf/modules/ui/options/mod_options.lua @@ -6,11 +6,13 @@ local _type_template_map = {} local _devices = { "keyboard", - "mouse" + "mouse", } + local _cancel_keys = { "keyboard_esc" } + local _reserved_keys = {} local ERRORS = { @@ -249,8 +251,8 @@ _type_template_map["dropdown"] = create_dropdown_template -- ######### Keybind ######### -- ########################### -local set_keybind = function (self, keybind_data, keys) - keybind_data.keys = keys +local set_keybind = function (self, keybind_data, keywatch_result) + keybind_data.keys = keywatch_result local mod = get_mod(keybind_data.mod_name) dmf.add_mod_keybind( @@ -260,18 +262,19 @@ local set_keybind = function (self, keybind_data, keys) global = keybind_data.keybind_global, trigger = keybind_data.keybind_trigger, type = keybind_data.keybind_type, - keys = keybind_data.keys, + main = keywatch_result.main, + enablers = keywatch_result.enablers, + disablers = keywatch_result.disablers, function_name = keybind_data.function_name, view_name = keybind_data.view_name, } ) - mod:set(keybind_data.setting_id, keybind_data.keys, true) + mod:set(keybind_data.setting_id, dmf.keywatch_result_to_local_keys(keywatch_result), true) end -- Create keybind template local create_keybind_template = function (self, params) - local template = { widget_type = "keybind", service_type = "Ingame", @@ -287,8 +290,8 @@ local create_keybind_template = function (self, params) indentation_level = params.depth, mod_name = params.mod_name, setting_id = params.setting_id, - keys = dmf.keys_to_keybind_result(params.keys), - default_value = dmf.keys_to_keybind_result(params.default_value) or {}, + keys = dmf.local_keys_to_keywatch_result(params.keys), + default_value = dmf.local_keys_to_keywatch_result(params.default_value) or {}, on_activated = function (new_value, old_value) @@ -320,11 +323,11 @@ local create_keybind_template = function (self, params) end -- Get the keys of the new value - local keys = dmf.keybind_result_to_keys(new_value) + local keys = dmf.keywatch_result_to_local_keys(new_value) -- Set the new keybind unless it would unbind the mod options menu if keys and #keys > 0 or params.setting_id ~= "open_dmf_options" then - set_keybind(self, params, keys) + set_keybind(self, params, new_value) return true end @@ -332,10 +335,10 @@ local create_keybind_template = function (self, params) end, get_function = function (template) - local keys = get_mod(template.mod_name):get(template.setting_id) - local keybind_result = dmf.keys_to_keybind_result(keys) + local saved_keys = get_mod(template.mod_name):get(template.setting_id) + local keywatch_result = dmf.local_keys_to_keywatch_result(saved_keys) - return keybind_result + return keywatch_result end, } @@ -397,14 +400,14 @@ end -- Insert a new item into a table before any items that pass the item_tester function local function insert_before(tbl, item_tester, new_item) - local copy = {} - for _, item in ipairs(tbl) do - if item_tester(item) then - table.insert(copy, new_item) - end - table.insert(copy, item) - end - return copy + local copy = {} + for _, item in ipairs(tbl) do + if item_tester(item) then + table.insert(copy, new_item) + end + table.insert(copy, item) + end + return copy end @@ -425,16 +428,16 @@ dmf:add_global_localize_strings({ }) local dmf_option_definition = { - text = "mods_options", - type = "button", - icon = "content/ui/materials/icons/system/escape/settings", - trigger_function = function() - local context = { - can_exit = true, - } - local view_name = "dmf_options_view" - Managers.ui:open_view(view_name, nil, nil, nil, nil, context) - end, + text = "mods_options", + type = "button", + icon = "content/ui/materials/icons/system/escape/settings", + trigger_function = function() + local context = { + can_exit = true, + } + local view_name = "dmf_options_view" + Managers.ui:open_view(view_name, nil, nil, nil, nil, context) + end, } local function is_options_button(item)