diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 5d37200cc..4e6c9bf39 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1705,14 +1705,19 @@ function get_image_size(path) end ---Current mouse cursor position in screen coordinates. ---@return number, number function mouse_position() end +---@return nil +function key_name() end ---Returns: [ImGuiIO](https://spelunky-fyi.github.io/overlunky/#ImGuiIO) for raw keyboard, mouse and xinput gamepad stuff. ---- ----- Note: The clicked/pressed actions only make sense in `ON.GUIFRAME`. ----- Note: You can use KEY or standard VK keycodes to index `keys` or the other functions. ----- Note: Overlunky/etc will eat all keys it is currently configured to use, your script will only get leftovers. ----- Note: Gamepad is basically [XINPUT_GAMEPAD](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad) but variables are renamed and values are normalized to -1.0..1.0 range. ---@return ImGuiIO function get_io() end +---Returns unique id >= 0 for the callback to be used in [clear_callback](https://spelunky-fyi.github.io/overlunky/#clear_callback) or -1 if the key could not be registered. +---Add callback function to be called on a hotkey, using Windows hotkey api. These hotkeys will override all game and UI input and can work even when the game is unfocused. They are by design very intrusive and won't let anything else use the same key combo. Can't detect if input is active in another instance, use ImGuiIO if you need Playlunky hotkeys to react to Overlunky input state. Key is a KEY combo (e.g. `KEY.OL_MOD_CTRL | KEY.X`), possibly returned by GuiDrawContext:key_picker. Doesn't work with mouse buttons. +---The callback signature is nil on_hotkey(KEY key) +---@param cb fun(key: KEY): nil +---@param key KEY +---@param flags HOTKEY_TYPE +---@return CallbackId +function set_hotkey(cb, key, flags) end ---Force the LUT texture for the given layer (or both) until it is reset. ---Pass `nil` in the first parameter to reset ---@param texture_id TEXTURE? @@ -2324,6 +2329,7 @@ do ---@field slide_position number ---@class GameProps + ---@field buttons integer[] @size: MAX_PLAYERS @Used for player input and might be used for some menu inputs not found in buttons_menu. You can probably capture and edit this in ON.POST_PROCESS_INPUT. These are raw inputs, without things like autorun applied. ---@field input integer[] @size: MAX_PLAYERS @Used for player input and might be used for some menu inputs not found in buttons_menu. You can probably capture and edit this in ON.POST_PROCESS_INPUT. These are raw inputs, without things like autorun applied. ---@field input_previous integer[] @size: MAX_PLAYERS ---@field input_menu MENU_INPUT @Inputs used to control all the menus, separate from player inputs. You can probably capture and edit this in ON.POST_PROCESS_INPUT @@ -4010,6 +4016,7 @@ function Movable:generic_update_world(move, sprint_factor, disable_gravity, on_r ---@field smoke2 ParticleEmitterInfo ---@class CookFire : Torch + ---@field lit any @&Torch::is_lit ---@field emitted_light Illumination ---@field particles_smoke ParticleEmitterInfo ---@field particles_flames ParticleEmitterInfo @@ -5007,6 +5014,7 @@ function CustomSound:play(paused, sound_type) end ---@field win_section fun(self, title: string, callback: function): nil @Add a collapsing accordion section, put contents in the callback function. ---@field win_indent fun(self, width: number): nil @Indent contents, or unindent if negative ---@field win_width fun(self, width: number): nil @Sets next item width (width>1: width in pixels, width<0: to the right of window, -1Shows a fullscreen key picker with a message, with the accepted input type (keyboard/mouse) filtered by flags. The picker won't show before all previously held keys have been released and other key pickers have returned a valid key. local GuiDrawContext = nil ---Draws a rectangle on screen from top-left to bottom-right. ---@param left number @@ -5108,8 +5116,7 @@ function GuiDrawContext:win_pushid(id) end ---@class ImGuiIO ---@field displaysize Vec2 ---@field framerate number - ---@field wantkeyboard boolean - ---@field keysdown boolean[] @size: ImGuiKey_COUNT + ---@field wantkeyboard any @wantkeyboard ---@field keys boolean[] @size: ImGuiKey_COUNT ---@field keydown fun(key: number | string): boolean ---@field keypressed fun(key: number | string, repeat?: boolean ): boolean @@ -5118,14 +5125,16 @@ function GuiDrawContext:win_pushid(id) end ---@field keyshift boolean ---@field keyalt boolean ---@field keysuper boolean - ---@field wantmouse boolean + ---@field modifierdown any @modifierdown + ---@field wantmouse any @wantmouse ---@field mousepos Vec2 ---@field mousedown boolean[] @size: 5 ---@field mouseclicked boolean[] @size: 5 ---@field mousedoubleclicked boolean[] @size: 5 + ---@field mousereleased boolean[] @size: 5 ---@field mousewheel number ---@field gamepad Gamepad - ---@field gamepads any @[](unsignedintindex){g_WantUpdateHasGamepad=true;returnget_gamepad(index)/**/;} + ---@field gamepads any @[](unsignedintindex){g_WantUpdateHasGamepad=true ---@field showcursor boolean ---@class VanillaRenderContext @@ -6438,10 +6447,16 @@ function LogicMagmamanSpawn:remove_spawn(ms) end ---@field modifiers_block integer @Bitmask of modifier KEYs that will block all game input ---@field modifiers_clear_input boolean @Enable to clear affected input when modifiers are held, disable to ignore all input events, i.e. keep held button state as it was before pressing the modifier key +---@class SharedIO + ---@field wantkeyboard boolean? + ---@field wantmouse boolean? + ---@class Bucket ---@field data table @You can store arbitrary simple values here in Playlunky to be read in on Overlunky script for example. ---@field overlunky Overlunky @Access Overlunky options here, nil if Overlunky is not loaded. ---@field pause PauseAPI @PauseAPI is used by Overlunky and can be used to control the Overlunky pause options from scripts. Can be accessed from the global `pause` more easily. + ---@field io SharedIO @Shared part of ImGuiIO to block keyboard/mouse input across API instances. + ---@field count integer @Number of API instances present end --## Static class functions diff --git a/docs/parse_source.py b/docs/parse_source.py index d99891d15..a1e399cb8 100644 --- a/docs/parse_source.py +++ b/docs/parse_source.py @@ -670,6 +670,8 @@ def run_parse(): if not var: continue var = var.split(",") + if len(var) < 2: + continue if var[0] == "sol::base_classes" or var[0] == "sol::no_constructor": continue if "NoDoc" in var[0]: diff --git a/docs/src/includes/_enums.md b/docs/src/includes/_enums.md index 254f8aed1..c2b7f0caf 100644 --- a/docs/src/includes/_enums.md +++ b/docs/src/includes/_enums.md @@ -455,6 +455,19 @@ Name | Data | Description [SMALL_SAD](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=GHOST_BEHAVIOR.SMALL_SAD) | GHOST_BEHAVIOR::SMALL_SAD | [SMALL_HAPPY](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=GHOST_BEHAVIOR.SMALL_HAPPY) | GHOST_BEHAVIOR::SMALL_HAPPY | +## HOTKEY_TYPE + + +> Search script examples for [HOTKEY_TYPE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=HOTKEY_TYPE) + + + +Name | Data | Description +---- | ---- | ----------- +[NORMAL](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=HOTKEY_TYPE.NORMAL) | HOTKEY_TYPE::NORMAL | Suppressed when the game window is inactive or inputting text in this tool instance (get_io().wantkeyboard == true). Can't detect if OL is in a text input and script is running in PL though. Use [ImGuiIO](#ImGuiIO) if you need to do that.
+[GLOBAL](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=HOTKEY_TYPE.GLOBAL) | HOTKEY_TYPE::GLOBAL | Enabled even when the game window is inactive and will capture keys even from other programs.
+[INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=HOTKEY_TYPE.INPUT) | HOTKEY_TYPE::INPUT | Enabled even when inputting text and will override normal text input keys.
+ ## HUNDUNFLAGS @@ -634,6 +647,19 @@ Name | Data | Description [A](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=KEY.A) | 65 | ...check [lua_enums.txt](game_data/lua_enums.txt)... | | +## KEY_TYPE + + +> Search script examples for [KEY_TYPE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=KEY_TYPE) + + + +Name | Data | Description +---- | ---- | ----------- +[ANY](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=KEY_TYPE.ANY) | KEY_TYPE::ANY | +[KEYBOARD](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=KEY_TYPE.KEYBOARD) | KEY_TYPE::KEYBOARD | +[MOUSE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=KEY_TYPE.MOUSE) | KEY_TYPE::MOUSE | + ## LAYER diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 820138887..15054eb45 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -1917,6 +1917,17 @@ Set engine target frametime (1/framerate, default 1/60). Always capped by your G Set engine target frametime when game is unfocused (1/framerate, default 1/33). Always capped by the engine frametime. Set to 0 to go as fast as possible. Call without arguments to reset. +### set_hotkey + + +> Search script examples for [set_hotkey](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_hotkey) + +#### [CallbackId](#Aliases) set_hotkey([](function cb, [KEY](#KEY) key, [HOTKEY_TYPE](#HOTKEY_TYPE) flags) + +Returns unique id >= 0 for the callback to be used in [clear_callback](#clear_callback) or -1 if the key could not be registered. +Add callback function to be called on a hotkey, using Windows hotkey api. These hotkeys will override all game and UI input and can work even when the game is unfocused. They are by design very intrusive and won't let anything else use the same key combo. Can't detect if input is active in another instance, use [ImGuiIO](#ImGuiIO) if you need Playlunky hotkeys to react to [Overlunky](#Overlunky) input state. Key is a [KEY](#KEY) combo (e.g. `KEY.OL_MOD_CTRL | KEY.X`), possibly returned by GuiDrawContext:key_picker. Doesn't work with mouse buttons. +
The callback signature is nil on_hotkey([KEY](#KEY) key) + ### set_infinite_loop_detection_enabled @@ -2165,11 +2176,6 @@ Converts (x, y, BUTTON) to [INPUTS](#INPUTS) Returns: [ImGuiIO](#ImGuiIO) for raw keyboard, mouse and xinput gamepad stuff. -- Note: The clicked/pressed actions only make sense in `ON.GUIFRAME`. -- Note: You can use [KEY](#KEY) or standard VK keycodes to index `keys` or the other functions. -- Note: [Overlunky](#Overlunky)/etc will eat all keys it is currently configured to use, your script will only get leftovers. -- Note: [Gamepad](#Gamepad) is basically [XINPUT_GAMEPAD](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad) but variables are renamed and values are normalized to -1.0..1.0 range. - ### get_raw_input @@ -3494,6 +3500,14 @@ Will return the string of currently choosen language Convert the hash to stringid Check [strings00_hashed.str](https://github.com/spelunky-fyi/overlunky/blob/main/docs/game_data/strings00_hashed.str) for the hash values, or extract assets with modlunky and check those. +### key_name + + +> Search script examples for [key_name](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=key_name) + +#### nil key_name() + + ### set_level_string diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index f8134cf3e..d86cefc32 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -247,6 +247,7 @@ bool | [win_imagebutton(string label, IMAGE image, float width, float height, fl nil | [win_section(string title, function callback)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_section) | Add a collapsing accordion section, put contents in the callback function. nil | [win_indent(float width)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_indent) | Indent contents, or unindent if negative nil | [win_width(float width)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_width) | Sets next item width (width>1: width in pixels, width<0: to the right of window, -1Shows a fullscreen key picker with a message, with the accepted input type (keyboard/mouse) filtered by flags. The picker won't show before all previously held keys have been released and other key pickers have returned a valid key. ### LoadContext @@ -606,6 +607,8 @@ Type | Name | Description map<string, any> | [data](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=data) | You can store arbitrary simple values here in Playlunky to be read in on [Overlunky](#Overlunky) script for example. [Overlunky](#Overlunky) | [overlunky](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=overlunky) | Access [Overlunky](#Overlunky) options here, nil if [Overlunky](#Overlunky) is not loaded. [PauseAPI](#PauseAPI) | [pause](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=pause) | [PauseAPI](#PauseAPI) is used by [Overlunky](#Overlunky) and can be used to control the [Overlunky](#Overlunky) pause options from scripts. Can be accessed from the global `pause` more easily. +[SharedIO](#SharedIO) | [io](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=io) | Shared part of [ImGuiIO](#ImGuiIO) to block keyboard/mouse input across API instances. +int | [count](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=count) | Number of API instances present ### Color @@ -967,6 +970,14 @@ nil | [save()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=save) | nil | [clear()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear) | Delete the [SaveState](#SaveState) and free the memory. The [SaveState](#SaveState) can't be used after this. [StateMemory](#StateMemory) | [get_state()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_state) | Access the [StateMemory](#StateMemory) inside a [SaveState](#SaveState) +### SharedIO + + +Type | Name | Description +---- | ---- | ----------- +optional<bool> | [wantkeyboard](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=wantkeyboard) | +optional<bool> | [wantmouse](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=wantmouse) | + ### ShortTileCodeDef Used in [get_short_tile_code](#get_short_tile_code), [get_short_tile_code_definition](#get_short_tile_code_definition) and [PostRoomGenerationContext](#PostRoomGenerationContext) @@ -1068,31 +1079,43 @@ float | [ry](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ry) | ### ImGuiIO -Used in [get_io](#get_io) +Used in [get_io](#get_io), also see [set_hotkey](#set_hotkey) and [GuiDrawContext](#GuiDrawContext)::key_picker. + +- Functions are static, not class methods expecting `self` (call with `get_io().keydown(key)`, not `:`) +- The clicked/pressed actions only work in `ON.GUIFRAME` (they are true for one GUIFRAME), but get_io() can be used anywhere for the other parts. +- You can use `KEY` or other standard virtual keycodes < 0xFF to index `keys[]` or in the functions. +- You can use chords `KEY.OL_MOD_CTRL | KEY.X` in the keydown/keypressed/keyreleased functions. +- You can also use mouse buttons (e.g. `KEY.OL_MOUSE_1`) or anything returned by GuiDrawContext:key_picker in the keydown/keypressed/keyreleased functions. +- A modifier key as a keycode (`keypressed(KEY.LCONTROL)`) is not the same as modifier flags OL_MOD_... `keypressed(KEY.OL_MOD_CTRL)` won't work, `keypressed(KEY.OL_MOD_CTRL | KEY.LSHIFT)` will trigger on "Ctrl+Shift" but not "Shift+Ctrl" +- All held modifiers must be present in the chord, e.g. `keypressed(KEY.OL_MOD_CTRL | KEY.X)` won't trigger when Ctrl+Shift+X is pressed, `keydown(KEY.Y)` won't trigger when Ctrl+Y is pressed. +- Most fields are read only, except `wantkeyboard`, `wantmouse` and `showcursor`. +- Setting `wantkeyboard` early (e.g. already when `modifierdown(KEY.OL_MOD_CTRL | KEY.X)`) will prevent the game and UI from reacting to your actual combo later (e.g. `keypressed(KEY.OL_MOD_CTRL | KEY.X)`) +- [Gamepad](#Gamepad) is basically [XINPUT_GAMEPAD](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad) but variables are renamed and values are normalized to -1.0..1.0 range. Type | Name | Description ---- | ---- | ----------- [Vec2](#Vec2) | [displaysize](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=displaysize) | float | [framerate](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=framerate) | -bool | [wantkeyboard](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=wantkeyboard) | -bool | [keysdown[ImGuiKey_COUNT]](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keysdown) | -bool | [keys[ImGuiKey_COUNT]](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keys) | ZeroIndexArray, use [KEY](#KEY) to index
- | [keydown](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keydown) | bool keydown([KEY](#KEY) keycode)
bool keydown(char key)
- | [keypressed](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keypressed) | bool keypressed([KEY](#KEY) keycode, bool repeat = false)
bool keypressed(char key, bool repeat = false)
- | [keyreleased](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keyreleased) | bool keyreleased([KEY](#KEY) keycode)
bool keyreleased(char key)
+ | [wantkeyboard](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=wantkeyboard) | True if anyone else (i.e. some input box, OL hotkey) is already capturing keyboard or reacted to this keypress and you probably shouldn't.
Set this to true every GUIFRAME while you want to capture keyboard and disable UI key bindings and game keys. Won't affect UI or game keys on this frame though, that train has already sailed. Also see [Bucket](#Bucket)::[Overlunky](#Overlunky) for other ways to override key bindings.
Do not set this to false, unless you want the player input to bleed through input fields.
+bool | [keys[ImGuiKey_COUNT]](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keys) | ZeroIndexArray of currently held keys, indexed by [KEY](#KEY) <= 0xFF
+ | [keydown](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keydown) | Returns true if key or chord (e.g `KEY.X | KEY.OL_MOD_CTRL`) is down.
bool keydown(KEY keychord)
bool keydown(char key)
+ | [keypressed](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keypressed) | Returns true if key or chord (e.g `KEY.X | KEY.OL_MOD_CTRL`) was pressed this GUIFRAME.
bool keypressed(KEY keychord, bool repeat = false)
bool keypressed(char key, bool repeat = false)
+ | [keyreleased](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keyreleased) | Returns true if key or chord (e.g `KEY.X | KEY.OL_MOD_CTRL`) was released this GUIFRAME.
bool keyreleased(KEY keychord)
bool keyreleased(char key)
bool | [keyctrl](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keyctrl) | bool | [keyshift](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keyshift) | bool | [keyalt](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keyalt) | bool | [keysuper](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keysuper) | -bool | [wantmouse](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=wantmouse) | + | [modifierdown](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=modifierdown) | bool modifierdown([KEY](#KEY) keychord)
Returns true if modifiers in chord (e.g. `KEY.OL_MOD_CTRL | KEY.OL_MOD_SHIFT | KEY.OL_MOD_ALT`) are down, ignores other keys in chord.
+ | [wantmouse](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=wantmouse) | True if anyone else (i.e. hovering some window) is already capturing mouse and you probably shouldn't.
Set this to true if you want to capture mouse and override UI mouse binding.
[Vec2](#Vec2) | [mousepos](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=mousepos) | bool | [mousedown[5]](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=mousedown) | bool | [mouseclicked[5]](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=mouseclicked) | bool | [mousedoubleclicked[5]](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=mousedoubleclicked) | +bool | [mousereleased[5]](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=mousereleased) | float | [mousewheel](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=mousewheel) | [Gamepad](#Gamepad) | [gamepad](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=gamepad) | | [gamepads](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=gamepads) | [Gamepad](#Gamepad) gamepads(int index)
This is the XInput index 1..4, might not be the same as the player slot.
-bool | [showcursor](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=showcursor) | +bool | [showcursor](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=showcursor) | True when the cursor is visible.
Set to true to force the cursor visible.
### InputMapping @@ -2848,6 +2871,7 @@ array<int, MAX_PLAYERS> | [buttons_movement](https://github.com/spelunky-f Type | Name | Description ---- | ---- | ----------- +array<int, MAX_PLAYERS> | [buttons](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=buttons) | Used for player input and might be used for some menu inputs not found in buttons_menu. You can probably capture and edit this in [ON](#ON).POST_PROCESS_INPUT. These are raw inputs, without things like autorun applied. array<int, MAX_PLAYERS> | [input](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=input) | Used for player input and might be used for some menu inputs not found in buttons_menu. You can probably capture and edit this in [ON](#ON).POST_PROCESS_INPUT. These are raw inputs, without things like autorun applied. array<int, MAX_PLAYERS> | [input_previous](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=input_previous) | [MENU_INPUT](#MENU_INPUT) | [input_menu](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=input_menu) | Inputs used to control all the menus, separate from player inputs. You can probably capture and edit this in [ON](#ON).POST_PROCESS_INPUT @@ -5998,6 +6022,7 @@ Derived from [Entity](#Entity) [Movable](#Movable) [Torch](#Torch) Type | Name | Description ---- | ---- | ----------- + | [lit](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=lit) | [Illumination](#Illumination) | [emitted_light](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=emitted_light) | [ParticleEmitterInfo](#ParticleEmitterInfo) | [particles_smoke](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=particles_smoke) | [ParticleEmitterInfo](#ParticleEmitterInfo) | [particles_flames](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=particles_flames) | diff --git a/examples/imguiio.lua b/examples/imguiio.lua index b45bfb57d..3f45aad7e 100644 --- a/examples/imguiio.lua +++ b/examples/imguiio.lua @@ -1,21 +1,10 @@ -meta.name = 'Raw input test' +meta.name = 'ImGuiIO input and hotkeys' meta.version = 'WIP' -meta.description = 'Test raw keyboard, mouse and gamepad input.' +meta.description = 'Examples on imgui keyboard, mouse and gamepad input, also set_hotkey.' meta.author = 'Dregu' -local iio = get_io() -local prev_keys = '' - set_callback(function() - -- Show currently pressed keys. Note: Overlunky will eat all keys configured as hotkeys. Modifiers Ctrl, Alt and Super probably don't even right. - local keys = '' - for i,v in ipairs(iio.keysdown) do - if v then keys = keys .. string.char(i-1) end - end - if keys ~= prev_keys then - print(keys) - end - prev_keys = keys + local iio = get_io() -- Press S to realize S was pressed, ignoring when inputting text in OL if iio.keypressed('S') then @@ -30,9 +19,9 @@ set_callback(function() if iio.mouseclicked[1] then local x, y = mouse_position() if iio.wantmouse then - print("Clicked inside Overlunky window, plz ignore") + print("Clicked inside Overlunky window or 'Mouse Controls' is enabled, plz ignore") else - print(F"Clicked {x},{y} and it's fair game") + print(F "Clicked {x},{y} and it's fair game") end end @@ -40,17 +29,38 @@ set_callback(function() if iio.mousewheel ~= 0 and not iio.wantmouse then local dir = 'up' if iio.mousewheel < 0 then dir = 'down' end - print(F'Wheel going {dir}') + print(F 'Wheel going {dir}') end -- Draw raw gamepad data if one is connected if iio.gamepad.enabled then draw_circle_filled(iio.gamepad.rx, iio.gamepad.ry, 0.02, 0x660000ff) end + + -- if someone is already using keyboard for input, we probably shouldn't + -- also true when OL has a hotkey that matches to this event + if iio.wantkeyboard then + return + end + + -- capture keyboard input from game and ui while holding ctrl + alt + if iio.modifierdown(KEY.OL_MOD_CTRL | KEY.OL_MOD_ALT) then + -- if we set this early, it will unregister the games raw input for the next part, + -- disabling game keys, and also ui keys + iio.wantkeyboard = true + end + + -- capture single hotkey combo + if iio.keypressed(KEY.OL_MOD_CTRL | KEY.OL_MOD_ALT | KEY.A) then + print(key_name(KEY.OL_MOD_CTRL | KEY.OL_MOD_ALT | KEY.A)) + -- this is too late to try to get exclusive input for this key combo, it has already passed through the ui, and it's also too late to disable the rawinput handle the game uses + --io.wantkeyboard = true + end end, ON.GUIFRAME) -- XInput Gamepad set_callback(function() + local iio = get_io() if #players < 1 then return end if not iio.gamepad.enabled then return end @@ -58,24 +68,69 @@ set_callback(function() -- Fly around with right stick if math.abs(iio.gamepad.rx) > 0.1 then - players[1].velocityx = iio.gamepad.rx/5 + players[1].velocityx = iio.gamepad.rx / 5 players[1].velocityy = 0.01 players[1].falling_timer = 0 end if math.abs(iio.gamepad.ry) > 0.1 then - players[1].velocityy = iio.gamepad.ry/5 + players[1].velocityy = iio.gamepad.ry / 5 players[1].falling_timer = 0 end -- Throw bombs when holding triggers if iio.gamepad.lt > 0.5 and (get_frame() % 4) == 0 then - spawn(ENT_TYPE.ITEM_BOMB, x-0.2, y, l, -1, 0.06) + spawn(ENT_TYPE.ITEM_BOMB, x - 0.2, y, l, -1, 0.06) end if iio.gamepad.rt > 0.5 and (get_frame() % 4) == 0 then - spawn(ENT_TYPE.ITEM_BOMB, x+0.2, y, l, 1, 0.06) + spawn(ENT_TYPE.ITEM_BOMB, x + 0.2, y, l, 1, 0.06) end -- Check if a button is down -- Get your button flags from https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad#members for now... if (iio.gamepad.buttons & 0x100) > 0 then print("Holding L1") end -end, ON.FRAME) +end, ON.POST_UPDATE) + +-- create some global hotkeys +function hotkey_handler(key) print(key_name(key)) end + +-- will be suppressed by inactive window or active input fields in this tool instance +set_hotkey(hotkey_handler, KEY.Z | KEY.OL_MOD_CTRL, HOTKEY_TYPE.NORMAL) + +-- won't be suppressed by inactive window or input fields +set_hotkey(hotkey_handler, KEY.X | KEY.OL_MOD_ALT, HOTKEY_TYPE.GLOBAL | HOTKEY_TYPE.INPUT) + + +-- smart key handler that reacts to named key presses, but also +-- opens the key picker when any key is still unbound, +-- checks if ui is already capturing input +-- and tells overlunky to ignore picked keys +keys = { + first = KEY.OL_MOD_CTRL | KEY.A, + second = nil, -- choose with key picker when enabled + third = -1, -- choose with key picker when enabled +} +function key_handler(ctx, name) + if not keys[name] or keys[name] == -1 then + keys[name] = ctx:key_picker(F "Pick key for: {name}", KEY_TYPE.ANY) + -- key picker will return -1 until a key has been released + if keys[name] ~= -1 then + print(F "Picked {keys[name]} {key_name(keys[name])} {name}") + if get_bucket().overlunky then + get_bucket().overlunky.ignore_keycodes:clear() + for _, v in pairs(keys) do + get_bucket().overlunky.ignore_keycodes:insert(nil, v) + end + end + end + elseif not test_mask(keys[name], KEY_TYPE.MOUSE) and not get_io().wantkeyboard then + return get_io().keypressed(keys[name]) + elseif test_mask(keys[name], KEY_TYPE.MOUSE) and not get_io().wantmouse then + return get_io().keypressed(keys[name]) + end +end + +set_callback(function(ctx) + if key_handler(ctx, "first") then print("First action with " .. key_name(keys.first)) end + if key_handler(ctx, "second") then print("Second action with " .. key_name(keys.second)) end + if key_handler(ctx, "third") then print("Third action with " .. key_name(keys.third)) end +end, ON.GUIFRAME) diff --git a/src/game_api/aliases.hpp b/src/game_api/aliases.hpp index 6e3e349bc..9d22aeac0 100644 --- a/src/game_api/aliases.hpp +++ b/src/game_api/aliases.hpp @@ -46,7 +46,7 @@ static_cast>(Rhs)); \ } -using CallbackId = uint32_t; +using CallbackId = int32_t; using Flags = uint32_t; using uColor = uint32_t; using SPAWN_TYPE = uint32_t; // NoAlias @@ -72,6 +72,22 @@ using RAW_KEY = int8_t; // NoAlias inline constexpr uint8_t MAX_PLAYERS = 4; +enum class HOTKEY_TYPE : int32_t +{ + NORMAL = 0, + GLOBAL = 1 << 0, + INPUT = 1 << 1, +}; +ENUM_CLASS_FLAGS(HOTKEY_TYPE); + +enum class KEY_TYPE : int32_t +{ + ANY = 0, + KEYBOARD = 0xFF, + MOUSE = 0x400, +}; +ENUM_CLASS_FLAGS(KEY_TYPE); + enum class LAYER : int32_t { FRONT = 0, diff --git a/src/game_api/bucket.cpp b/src/game_api/bucket.cpp index 869e3a506..b258d3baf 100644 --- a/src/game_api/bucket.cpp +++ b/src/game_api/bucket.cpp @@ -17,6 +17,7 @@ Bucket* Bucket::get() auto new_bucket = new Bucket(); write_mem_prot(bucket_offset, new_bucket, true); new_bucket->pause_api = new PauseAPI(); + new_bucket->io = new SharedIO(); return new_bucket; } diff --git a/src/game_api/bucket.hpp b/src/game_api/bucket.hpp index e635ba111..db1de4167 100644 --- a/src/game_api/bucket.hpp +++ b/src/game_api/bucket.hpp @@ -122,6 +122,12 @@ struct PauseAPI void post_input(); }; +struct SharedIO +{ + std::optional WantCaptureKeyboard; + std::optional WantCaptureMouse; +}; + class Bucket { public: @@ -143,6 +149,10 @@ class Bucket bool forward_blocked_events{false}; // Set to true when the callback was blocked by Overlunky and should also be blocked in Playlunky bool blocked_event{false}; + /// Shared part of ImGuiIO to block keyboard/mouse input across API instances. + SharedIO* io{nullptr}; + /// Number of API instances present + int count{0}; private: Bucket() = default; diff --git a/src/game_api/hook_handler.hpp b/src/game_api/hook_handler.hpp index 353784468..3ec67d664 100644 --- a/src/game_api/hook_handler.hpp +++ b/src/game_api/hook_handler.hpp @@ -10,7 +10,8 @@ enum class CallbackType Normal, Entity, Screen, - Theme + Theme, + HotKey }; template < diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 45896d558..5d426a7e9 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -11,6 +11,7 @@ #include // for vector #include "aliases.hpp" // for IMAGE, JournalPageType +#include "bucket.hpp" // for Bucket #include "constants.hpp" // for no_return_str #include "entities_chars.hpp" // for Player #include "entity.hpp" // for Entity, get_entity_ptr @@ -33,9 +34,12 @@ #include "usertypes/level_lua.hpp" // for PreHandleRoomTilesContext #include "usertypes/save_context.hpp" // for LoadContext, SaveContext #include "usertypes/vanilla_render_lua.hpp" // for VanillaRenderContext +#include "window_api.hpp" // for get_window std::recursive_mutex g_all_backends_mutex; std::vector> g_all_backends; +std::unordered_map g_hotkeys; +int g_hotkey_count = 0; LuaBackend::LuaBackend(SoundManager* sound_mgr, LuaConsole* con) : lua{get_lua_vm(sound_mgr), sol::create}, vm{acquire_lua_vm(sound_mgr)}, sound_manager{sound_mgr}, console{con} @@ -116,6 +120,17 @@ void LuaBackend::clear_all_callbacks() } extra_spawn_callbacks.clear(); + for (auto& [id, callback] : hotkey_callbacks) + { + if (g_hotkeys.contains(callback.hotkeyid)) + { + if (g_hotkeys[callback.hotkeyid].active) + UnregisterHotKey(get_window(), callback.hotkeyid); + g_hotkeys.erase(callback.hotkeyid); + } + } + hotkey_callbacks.clear(); + HookHandler::clear_all_hooks(); HookHandler::clear_all_hooks(); HookHandler::clear_all_hooks(); @@ -312,6 +327,16 @@ bool LuaBackend::update() callbacks.erase(id); load_callbacks.erase(id); save_callbacks.erase(id); + if (hotkey_callbacks.contains(id)) + { + if (g_hotkeys.contains(hotkey_callbacks[id].hotkeyid)) + { + if (g_hotkeys[hotkey_callbacks[id].hotkeyid].active) + UnregisterHotKey(get_window(), hotkey_callbacks[id].hotkeyid); + g_hotkeys.erase(hotkey_callbacks[id].hotkeyid); + } + } + hotkey_callbacks.erase(id); std::erase_if(pre_tile_code_callbacks, [id](auto& cb) { return cb.id == id; }); @@ -559,6 +584,7 @@ bool LuaBackend::update() void LuaBackend::draw(ImDrawList* dl) { + static const auto bucket = Bucket::get(); if (!pre_draw() || !get_enabled()) return; @@ -591,6 +617,31 @@ void LuaBackend::draw(ImDrawList* dl) callback.lastRan = now; } } + + for (auto& [id, callback] : hotkey_callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (g_hotkeys.contains(callback.hotkeyid) && (g_hotkeys[callback.hotkeyid].flags & HOTKEY_TYPE::INPUT) != HOTKEY_TYPE::INPUT && (g_hotkeys[callback.hotkeyid].suppressflags == HOTKEY_TYPE::NORMAL)) + { + if (g_hotkeys[callback.hotkeyid].active && (ImGui::GetIO().WantCaptureKeyboard || bucket->io->WantCaptureKeyboard.value_or(false))) + UnregisterHotKey(get_window(), callback.hotkeyid); + else if (!g_hotkeys[callback.hotkeyid].active && !(ImGui::GetIO().WantCaptureKeyboard || bucket->io->WantCaptureKeyboard.value_or(false))) + RegisterHotKey(get_window(), callback.hotkeyid, g_hotkeys[callback.hotkeyid].mod, g_hotkeys[callback.hotkeyid].key); + g_hotkeys[callback.hotkeyid].active = !(ImGui::GetIO().WantCaptureKeyboard || bucket->io->WantCaptureKeyboard.value_or(false)); + } + + auto now = get_frame_count(); + while (callback.queue > 0) + { + set_current_callback(-1, id, CallbackType::HotKey); + handle_function(this, callback.func, callback.key); + clear_current_callback(); + callback.lastRan = now; + callback.queue--; + } + } } catch (const sol::error& e) { @@ -1910,3 +1961,74 @@ void LuaBackend::post_load_state(int slot, StateMemory* loaded) } } } + +int LuaBackend::register_hotkey(HotKeyCallback cb, HOTKEY_TYPE flags) +{ + const int OL_KEY_CTRL = 0x100; + const int OL_KEY_SHIFT = 0x200; + const int OL_KEY_ALT = 0x800; + + int vk = cb.key & 0xff; + int mod = 0; + if (cb.key & OL_KEY_CTRL) + mod |= MOD_CONTROL; + if (cb.key & OL_KEY_SHIFT) + mod |= MOD_SHIFT; + if (cb.key & OL_KEY_ALT) + mod |= MOD_ALT; + + int id = g_hotkey_count; + + if (RegisterHotKey(get_window(), id, mod, vk)) + { + cb.hotkeyid = id; + auto hotkey = HotKey{mod, vk, this, cbcount, true, flags, HOTKEY_TYPE::NORMAL}; + g_hotkeys[id] = hotkey; + hotkey_callbacks[cbcount] = cb; + g_hotkey_count++; + return cbcount++; + } + else + { + return -1; + } +} + +void LuaBackend::hotkey_callback(int cb) +{ + if (!get_enabled() || !hotkey_callbacks.contains(cb)) + return; + hotkey_callbacks[cb].queue++; +} + +void LuaBackend::wm_hotkey(int keyid) +{ + if (!g_hotkeys.contains(keyid)) + return; + g_hotkeys[keyid].backend->hotkey_callback(g_hotkeys[keyid].cb); +} + +void LuaBackend::wm_activate(bool active) +{ + for (auto& [id, hotkey] : g_hotkeys) + { + if (active) + { + if (!hotkey.active) + { + RegisterHotKey(get_window(), id, hotkey.mod, hotkey.key); + hotkey.active = true; + hotkey.suppressflags &= ~HOTKEY_TYPE::GLOBAL; + } + } + else + { + if (hotkey.active && (hotkey.flags & HOTKEY_TYPE::GLOBAL) != HOTKEY_TYPE::GLOBAL) + { + UnregisterHotKey(get_window(), id); + hotkey.active = false; + hotkey.suppressflags |= HOTKEY_TYPE::GLOBAL; + } + } + } +} diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index 8b511f099..8553b5518 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -270,6 +270,15 @@ struct SavedUserData std::unordered_map powerups; }; +struct HotKeyCallback +{ + sol::function func; + KEY key; + int lastRan; + int queue; + int hotkeyid; +}; + struct StateMemory; class SoundManager; class LuaConsole; @@ -303,6 +312,7 @@ class LuaBackend std::unordered_map callbacks; std::unordered_map load_callbacks; std::unordered_map save_callbacks; + std::unordered_map hotkey_callbacks; std::vector vanilla_sound_callbacks; std::vector pre_tile_code_callbacks; std::vector post_tile_code_callbacks; @@ -448,6 +458,11 @@ class LuaBackend void load_user_data(); bool on_pre(ON event); void on_post(ON event); + + void hotkey_callback(int cb); + int register_hotkey(HotKeyCallback cb, HOTKEY_TYPE flags); + static void wm_activate(bool active); + static void wm_hotkey(int keyid); }; template @@ -462,3 +477,14 @@ class LockableLuaBackend : public LuaBackend return self->LockAs(); } }; + +struct HotKey +{ + int mod; + int key; + LuaBackend* backend; + int cb; + bool active; + HOTKEY_TYPE flags; + HOTKEY_TYPE suppressflags; +}; diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 0d47bea9b..f6845ecc9 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -528,6 +528,7 @@ end switch (caller.type) { case CallbackType::Normal: + case CallbackType::HotKey: backend->clear_callbacks.push_back(caller.id); break; case CallbackType::Entity: @@ -546,6 +547,16 @@ end } }); + lua.create_named_table("HOTKEY_TYPE", "NORMAL", HOTKEY_TYPE::NORMAL, "GLOBAL", HOTKEY_TYPE::GLOBAL, "INPUT", HOTKEY_TYPE::INPUT); + /* HOTKEY_TYPE + // NORMAL + // Suppressed when the game window is inactive or inputting text in this tool instance (get_io().wantkeyboard == true). Can't detect if OL is in a text input and script is running in PL though. Use ImGuiIO if you need to do that. + // GLOBAL + // Enabled even when the game window is inactive and will capture keys even from other programs. + // INPUT + // Enabled even when inputting text and will override normal text input keys. + */ + /// Table of options set in the UI, added with the [register_option_functions](#Option-functions), but `nil` before any options are registered. You can also write your own options in here or override values defined in the register functions/UI before or after they are registered. Check the examples for many different use cases and saving options to disk. // lua["options"] = lua.create_named_table("options"); diff --git a/src/game_api/script/usertypes/bucket_lua.cpp b/src/game_api/script/usertypes/bucket_lua.cpp index 946df3716..484452109 100644 --- a/src/game_api/script/usertypes/bucket_lua.cpp +++ b/src/game_api/script/usertypes/bucket_lua.cpp @@ -55,11 +55,17 @@ void register_usertypes(sol::state& lua) /// NoDoc lua["pause"][sol::metatable_key]["__call"] = &PauseAPI::set_paused; + auto sharedio_type = lua.new_usertype("SharedIO", sol::no_constructor); + sharedio_type["wantkeyboard"] = &SharedIO::WantCaptureKeyboard; + sharedio_type["wantmouse"] = &SharedIO::WantCaptureMouse; + /// Shared memory structure used for Playlunky-Overlunky interoperability auto bucket_type = lua.new_usertype("Bucket", sol::no_constructor); bucket_type["data"] = &Bucket::data; bucket_type["overlunky"] = sol::readonly(&Bucket::overlunky); bucket_type["pause"] = &Bucket::pause_api; + bucket_type["io"] = &Bucket::io; + bucket_type["count"] = sol::readonly(&Bucket::count); /// Returns the Bucket of data stored in shared memory between Overlunky and Playlunky // lua["get_bucket"] = []() -> Bucket* diff --git a/src/game_api/script/usertypes/gui_lua.cpp b/src/game_api/script/usertypes/gui_lua.cpp index e76a28fb6..a5e19ee2e 100644 --- a/src/game_api/script/usertypes/gui_lua.cpp +++ b/src/game_api/script/usertypes/gui_lua.cpp @@ -20,6 +20,7 @@ #include // for max, min, pair, get, make_pair #include // for XINPUT_STATE, XINPUT_CAPABILITIES +#include "bucket.hpp" #include "file_api.hpp" // for create_d3d11_texture_from_file #include "math.hpp" // for Vec2 #include "script.hpp" // for ScriptMessage, ScriptImage @@ -37,6 +38,14 @@ static HMODULE g_XInputDLL = NULL; static PFN_XInputGetCapabilities g_XInputGetCapabilities = NULL; static PFN_XInputGetState g_XInputGetState = NULL; +const int OL_KEY_CTRL = 0x100; +const int OL_KEY_SHIFT = 0x200; +const int OL_KEY_ALT = 0x800; +const int OL_BUTTON_MOUSE = 0x400; +const int OL_MOUSE_WHEEL = 0x10; +const int OL_WHEEL_DOWN = 0x11; +const int OL_WHEEL_UP = 0x12; + Vec2::Vec2(const ImVec2& p) : x(p.x), y(p.y){}; @@ -560,9 +569,215 @@ void GuiDrawContext::draw_layer(DRAW_LAYER layer) { drawlist = layer; } +int64_t GuiDrawContext::key_picker(std::string message, KEY_TYPE flags) +{ + int64_t ret = -1; + auto win = ImGui::FindWindowByName("ScriptKeyPicker"); + if (win && win->Active) + return ret; + static const ImVec4 bg(0.0f, 0.0f, 0.0f, 1.0f); + static const float size = 32.0f; + static std::unordered_set pressed_keys; + static bool active{false}; + // make sure no keys are held before we open the picker initially + if (!active) + { + for (int i = 0; i < 0xFF; ++i) + if (ImGui::IsKeyDown((ImGuiKey)i) || ImGui::IsKeyReleased((ImGuiKey)i)) + return ret; + for (int i = 0; i < ImGuiMouseButton_COUNT; ++i) + if (ImGui::GetIO().MouseDown[i] || ImGui::GetIO().MouseReleased[i]) + return ret; + } + active = true; + ImGuiIO& io = ImGui::GetIO(); + io.WantCaptureKeyboard = true; + auto base = ImGui::GetMainViewport(); + ImGui::SetNextWindowSize(base->Size); + ImGui::SetNextWindowPos(base->Pos); + ImGui::SetNextWindowViewport(base->ID); + ImGui::SetNextWindowBgAlpha(0.66f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, bg); + ImGui::SetNextWindowFocus(); + ImGui::Begin( + "ScriptKeyPicker", + NULL, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking); + ImGui::SetKeyboardFocusHere(); + ImGui::InvisibleButton("ScriptKeyPickerCanvas", ImGui::GetContentRegionMax(), ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + ImDrawList* dl = ImGui::GetForegroundDrawList(); + ImFont* font = io.Fonts->Fonts.back(); + for (auto pickfont : io.Fonts->Fonts) + { + if (floor(size) <= floor(pickfont->FontSize)) + { + font = pickfont; + break; + } + } + ImVec2 textsize = font->CalcTextSizeA(size, 9999.0, 9999.0, message.c_str()); + dl->AddText(font, size, {base->Pos.x + base->Size.x / 2 - textsize.x / 2, base->Pos.y + base->Size.y / 2 - textsize.y / 2}, ImColor(1.0f, 1.0f, 1.0f, .8f), message.c_str()); + + if (flags == KEY_TYPE::ANY || (flags & KEY_TYPE::MOUSE) == KEY_TYPE::MOUSE) + { + // Buttons + for (size_t i = 0; i < 5; ++i) + { + if (io.MouseDown[i]) + { + size_t keycode = 0x400 + i + 1; + if (ImGui::GetIO().KeyCtrl) + keycode += 0x100; + if (ImGui::GetIO().KeyShift) + keycode += 0x200; + if (ImGui::GetIO().KeyAlt) + keycode += 0x800; + ret = keycode; + } + } + + // Wheel + if (io.MouseWheel != 0) + { + size_t keycode = 0x400; + if (io.MouseWheel < 0) + keycode += OL_WHEEL_DOWN; + else if (io.MouseWheel > 0) + keycode += OL_WHEEL_UP; + if (ImGui::GetIO().KeyCtrl) + keycode += 0x100; + if (ImGui::GetIO().KeyShift) + keycode += 0x200; + if (ImGui::GetIO().KeyAlt) + keycode += 0x800; + ret = keycode; + } + } + + // Keys + if (flags == KEY_TYPE::ANY || (flags & KEY_TYPE::KEYBOARD) == KEY_TYPE::KEYBOARD) + { + for (size_t i = 0; i < 0xFF; ++i) + { + // make sure the released key was also pressed while the picker was open + if (ImGui::IsKeyPressed((ImGuiKey)i)) + { + pressed_keys.insert(i); + } + if (ImGui::IsKeyReleased((ImGuiKey)i) && pressed_keys.count(i)) + { + size_t keycode = i; + if (ImGui::GetIO().KeyCtrl) + keycode += 0x100; + if (ImGui::GetIO().KeyShift) + keycode += 0x200; + if (ImGui::GetIO().KeyAlt) + keycode += 0x800; + ret = keycode; + } + } + } + ImGui::End(); + ImGui::PopStyleColor(); + if (ret != -1) + { + active = false; + pressed_keys.clear(); + } + return ret; +} namespace NGui { +bool modifierdown(int chord) +{ + int key = chord & 0x4ff; + if (key == VK_RMENU) + { + if (ImGui::GetIO().KeyShift) + key |= OL_KEY_SHIFT; + return chord == key; + } + if (ImGui::GetIO().KeyCtrl && key != VK_LCONTROL && key != VK_RCONTROL) + key |= OL_KEY_CTRL; + if (ImGui::GetIO().KeyShift && key != VK_LSHIFT && key != VK_RSHIFT) + key |= OL_KEY_SHIFT; + if (ImGui::GetIO().KeyAlt && key != VK_MENU && key != VK_RMENU) + key |= OL_KEY_ALT; + return chord == key; +}; + +std::string key_name(int64_t keycode) +{ + UCHAR virtualKey = keycode & 0xff; + CHAR szName[128]; + int result = 0; + std::string name; + if ((keycode & 0xff) == 0) + { + name = "None"; + } + else if (!(keycode & OL_BUTTON_MOUSE)) // keyboard + { + UINT scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC); + switch (virtualKey) + { + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + case VK_RCONTROL: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + case VK_APPS: + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_INSERT: + case VK_DELETE: + case VK_DIVIDE: + case VK_NUMLOCK: + scanCode |= KF_EXTENDED; + [[fallthrough]]; + default: + result = GetKeyNameTextA(scanCode << 16, szName, 128); + } + if (result == 0) + { + name = "Unknown"; + } + std::string keyname(szName); + name = keyname; + } + else // mouse + { + std::stringstream buttonss; + if (!(keycode & OL_MOUSE_WHEEL)) + buttonss << "Mouse" << (keycode & 0xff); + else if ((keycode & 0xff) == OL_WHEEL_DOWN) + buttonss << "WheelDown"; + else if ((keycode & 0xff) == OL_WHEEL_UP) + buttonss << "WheelUp"; + name = buttonss.str(); + } + + if (keycode & OL_KEY_SHIFT) + { + name = "Shift+" + name; + } + if (keycode & OL_KEY_ALT) + { + name = "Alt+" + name; + } + if (keycode & OL_KEY_CTRL) + { + name = "Ctrl+" + name; + } + return name; +} + void register_usertypes(sol::state& lua) { const char* xinput_dll_names[] = @@ -638,6 +853,7 @@ void register_usertypes(sol::state& lua) guidrawcontext_type["win_section"] = &GuiDrawContext::win_section; guidrawcontext_type["win_indent"] = &GuiDrawContext::win_indent; guidrawcontext_type["win_width"] = &GuiDrawContext::win_width; + guidrawcontext_type["key_picker"] = &GuiDrawContext::key_picker; /// Converts a color to int to be used in drawing functions. Use values from `0..255`. lua["rgba"] = [](int r, int g, int b, int a) -> uColor @@ -777,116 +993,197 @@ void register_usertypes(sol::state& lua) auto keydown = sol::overload( [](int keycode) { - return ImGui::IsKeyDown((ImGuiKey)keycode); + if (keycode & OL_BUTTON_MOUSE) + { + if (!(keycode & OL_MOUSE_WHEEL)) + { + auto butt = (ImGuiKey)(ImGuiKey_Mouse_BEGIN + (keycode & 0xff) - 1); + return modifierdown(keycode) && ImGui::IsKeyDown(butt); + } + else if ((keycode & 0xff) == OL_WHEEL_DOWN) + return ImGui::GetIO().MouseWheel < 0; + else if ((keycode & 0xff) == OL_WHEEL_UP) + return ImGui::GetIO().MouseWheel > 0; + return false; + } + else + return modifierdown(keycode) && ImGui::IsKeyDown((ImGuiKey)(keycode & 0xff)); }, [](char key) { return ImGui::IsKeyDown((ImGuiKey)(int)key); }); + auto keycodepressed = [&](int keycode, bool repeat) + { + if (keycode & OL_BUTTON_MOUSE) + { + if (!(keycode & OL_MOUSE_WHEEL)) + { + auto butt = (ImGuiKey)(ImGuiKey_Mouse_BEGIN + (keycode & 0xff) - 1); + return modifierdown(keycode) && ImGui::IsKeyPressed(butt, repeat); + } + else if ((keycode & 0xff) == OL_WHEEL_DOWN) + return modifierdown(keycode) && ImGui::GetIO().MouseWheel < 0; + else if ((keycode & 0xff) == OL_WHEEL_UP) + return modifierdown(keycode) && ImGui::GetIO().MouseWheel > 0; + return false; + } + else + return modifierdown(keycode) && ImGui::IsKeyPressed((ImGuiKey)(keycode & 0xff), repeat); + }; auto keypressed = sol::overload( - [](int keycode) + [&](int keycode) { - return ImGui::IsKeyPressed((ImGuiKey)keycode, false); + return keycodepressed(keycode, false); }, - [](int keycode, bool repeat) + [&](int keycode, bool repeat) { - return ImGui::IsKeyPressed((ImGuiKey)keycode, repeat); + return keycodepressed(keycode, repeat); }, - [](char key) + [&](char key) { return ImGui::IsKeyPressed((ImGuiKey)(int)key, false); }, - [](char key, bool repeat) + [&](char key, bool repeat) { return ImGui::IsKeyPressed((ImGuiKey)(int)key, repeat); }); auto keyreleased = sol::overload( [](int keycode) { - return ImGui::IsKeyReleased((ImGuiKey)keycode); + if (keycode & OL_BUTTON_MOUSE) + { + if (!(keycode & OL_MOUSE_WHEEL)) + { + auto butt = (ImGuiKey)(ImGuiKey_Mouse_BEGIN + (keycode & 0xff) - 1); + return modifierdown(keycode) && ImGui::IsKeyReleased(butt); + } + else if ((keycode & 0xff) == OL_WHEEL_DOWN) + return ImGui::GetIO().MouseWheel < 0; + else if ((keycode & 0xff) == OL_WHEEL_UP) + return ImGui::GetIO().MouseWheel > 0; + return false; + } + else + return modifierdown(keycode) && ImGui::IsKeyReleased((ImGuiKey)(keycode & 0xff)); }, [](char key) { return ImGui::IsKeyReleased((ImGuiKey)(int)key); }); - /// Used in [get_io](#get_io) - lua.new_usertype( - "ImGuiIO", - "displaysize", - sol::property([](ImGuiIO& io) -> Vec2 - { return Vec2(io.DisplaySize) /**/; }), - "framerate", - &ImGuiIO::Framerate, - "wantkeyboard", - &ImGuiIO::WantCaptureKeyboard, - /// NoDoc - "keysdown", - sol::property([](ImGuiIO& io) - { return std::ref(io.KeysDown) /**/; }), - "keys", - sol::property([](ImGuiIO& io) - { return ZeroIndexArray(io.KeysDown) /**/; }), - "keydown", - keydown, - "keypressed", - keypressed, - "keyreleased", - keyreleased, - "keyctrl", - &ImGuiIO::KeyCtrl, - "keyshift", - &ImGuiIO::KeyShift, - "keyalt", - &ImGuiIO::KeyAlt, - "keysuper", - &ImGuiIO::KeySuper, - "wantmouse", - &ImGuiIO::WantCaptureMouse, - "mousepos", + + auto wantmouse = sol::property([](ImGuiIO& io) -> bool + { + ImGuiContext& g = *GImGui; + if (ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && g.HoveredWindow && strcmp(g.HoveredWindow->Name, "Clickhandler") == 0) + return Bucket::get()->io->WantCaptureMouse.value_or(io.WantCaptureMouse) /**/; + return io.WantCaptureMouse; }, + [](ImGuiIO& io, bool want) + { + Bucket::get()->io->WantCaptureMouse = want; + io.WantCaptureMouse = want; + }); + + auto wantkeyboard = sol::property([](ImGuiIO& io) -> bool + { return io.WantCaptureKeyboard; }, + [](ImGuiIO& io, bool want) + { + Bucket::get()->io->WantCaptureKeyboard = want; + io.WantCaptureKeyboard = want; + }); + + /// Used in [get_io](#get_io), also see [set_hotkey](#set_hotkey) and GuiDrawContext::key_picker. + /// + /// - Functions are static, not class methods expecting `self` (call with `get_io().keydown(key)`, not `:`) + /// - The clicked/pressed actions only work in `ON.GUIFRAME` (they are true for one GUIFRAME), but get_io() can be used anywhere for the other parts. + /// - You can use `KEY` or other standard virtual keycodes < 0xFF to index `keys[]` or in the functions. + /// - You can use chords `KEY.OL_MOD_CTRL | KEY.X` in the keydown/keypressed/keyreleased functions. + /// - You can also use mouse buttons (e.g. `KEY.OL_MOUSE_1`) or anything returned by GuiDrawContext:key_picker in the keydown/keypressed/keyreleased functions. + /// - A modifier key as a keycode (`keypressed(KEY.LCONTROL)`) is not the same as modifier flags OL_MOD_... `keypressed(KEY.OL_MOD_CTRL)` won't work, `keypressed(KEY.OL_MOD_CTRL | KEY.LSHIFT)` will trigger on "Ctrl+Shift" but not "Shift+Ctrl" + /// - All held modifiers must be present in the chord, e.g. `keypressed(KEY.OL_MOD_CTRL | KEY.X)` won't trigger when Ctrl+Shift+X is pressed, `keydown(KEY.Y)` won't trigger when Ctrl+Y is pressed. + /// - Most fields are read only, except `wantkeyboard`, `wantmouse` and `showcursor`. + /// - Setting `wantkeyboard` early (e.g. already when `modifierdown(KEY.OL_MOD_CTRL | KEY.X)`) will prevent the game and UI from reacting to your actual combo later (e.g. `keypressed(KEY.OL_MOD_CTRL | KEY.X)`) + /// - Gamepad is basically [XINPUT_GAMEPAD](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad) but variables are renamed and values are normalized to -1.0..1.0 range. + auto imguiio_type = lua.new_usertype("ImGuiIO", sol::no_constructor); + imguiio_type["displaysize"] = sol::property([](ImGuiIO& io) -> Vec2 - { return Vec2(io.MousePos) /**/; }), - "mousedown", - sol::property([](ImGuiIO& io) - { return std::ref(io.MouseDown) /**/; }), - "mouseclicked", - sol::property([](ImGuiIO& io) - { return std::ref(io.MouseClicked) /**/; }), - "mousedoubleclicked", - sol::property([](ImGuiIO& io) - { return std::ref(io.MouseDoubleClicked) /**/; }), - "mousewheel", - &ImGuiIO::MouseWheel, - "gamepad", - sol::property([]() -> Gamepad - { + { return Vec2(io.DisplaySize) /**/; }); + imguiio_type["framerate"] = &ImGuiIO::Framerate; + imguiio_type["wantkeyboard"] = std::move(wantkeyboard); + /// NoDoc + imguiio_type["keysdown"] = sol::property([](ImGuiIO& io) + { return std::ref(io.KeysDown) /**/; }); + imguiio_type["keys"] = sol::property([](ImGuiIO& io) + { return ZeroIndexArray(io.KeysDown) /**/; }); + imguiio_type["keydown"] = keydown; + imguiio_type["keypressed"] = keypressed; + imguiio_type["keyreleased"] = keyreleased; + imguiio_type["keyctrl"] = &ImGuiIO::KeyCtrl; + imguiio_type["keyshift"] = &ImGuiIO::KeyShift; + imguiio_type["keyalt"] = &ImGuiIO::KeyAlt; + imguiio_type["keysuper"] = &ImGuiIO::KeySuper; + imguiio_type["modifierdown"] = modifierdown; + imguiio_type["wantmouse"] = std::move(wantmouse); + imguiio_type["mousepos"] = sol::property([](ImGuiIO& io) -> Vec2 + { return Vec2(io.MousePos); }); + imguiio_type["mousedown"] = sol::property([](ImGuiIO& io) + { return std::ref(io.MouseDown); }); + imguiio_type["mouseclicked"] = sol::property([](ImGuiIO& io) + { return std::ref(io.MouseClicked); }); + imguiio_type["mousedoubleclicked"] = sol::property([](ImGuiIO& io) + { return std::ref(io.MouseDoubleClicked); }); + imguiio_type["mousereleased"] = sol::property([](ImGuiIO& io) + { return std::ref(io.MouseReleased); }); + imguiio_type["mousewheel"] = &ImGuiIO::MouseWheel; + imguiio_type["gamepad"] = sol::property([]() -> Gamepad + { g_WantUpdateHasGamepad = true; - return get_gamepad(1) /**/; }), - "gamepads", - [](unsigned int index) - { - g_WantUpdateHasGamepad = true; - return get_gamepad(index) /**/; - }, - "showcursor", - &ImGuiIO::MouseDrawCursor); + return get_gamepad(1); }); + imguiio_type["gamepads"] = [](unsigned int index) + { + g_WantUpdateHasGamepad = true; + return get_gamepad(index); + }; + imguiio_type["showcursor"] = &ImGuiIO::MouseDrawCursor; /* ImGuiIO // keys - // ZeroIndexArray, use KEY to index + // ZeroIndexArray of currently held keys, indexed by KEY <= 0xFF // keydown - // bool keydown(KEY keycode) + // Returns true if key or chord (e.g `KEY.X | KEY.OL_MOD_CTRL`) is down. + // bool keydown(KEY keychord) // bool keydown(char key) // keypressed - // bool keypressed(KEY keycode, bool repeat = false) + // Returns true if key or chord (e.g `KEY.X | KEY.OL_MOD_CTRL`) was pressed this GUIFRAME. + // bool keypressed(KEY keychord, bool repeat = false) // bool keypressed(char key, bool repeat = false) // keyreleased - // bool keyreleased(KEY keycode) + // Returns true if key or chord (e.g `KEY.X | KEY.OL_MOD_CTRL`) was released this GUIFRAME. + // bool keyreleased(KEY keychord) // bool keyreleased(char key) + // modifierdown + // bool modifierdown(KEY keychord) + // Returns true if modifiers in chord (e.g. `KEY.OL_MOD_CTRL | KEY.OL_MOD_SHIFT | KEY.OL_MOD_ALT`) are down, ignores other keys in chord. // gamepads // Gamepad gamepads(int index) // This is the XInput index 1..4, might not be the same as the player slot. + // wantkeyboard + // True if anyone else (i.e. some input box, OL hotkey) is already capturing keyboard or reacted to this keypress and you probably shouldn't. + // Set this to true every GUIFRAME while you want to capture keyboard and disable UI key bindings and game keys. Won't affect UI or game keys on this frame though, that train has already sailed. Also see Bucket::Overlunky for other ways to override key bindings. + // Do not set this to false, unless you want the player input to bleed through input fields. + // wantmouse + // True if anyone else (i.e. hovering some window) is already capturing mouse and you probably shouldn't. + // Set this to true if you want to capture mouse and override UI mouse binding. + // showcursor + // True when the cursor is visible. + // Set to true to force the cursor visible. */ + // Returns human readable string from KEY chord (e.g. "Ctrl+X", "Unknown" or "None") + lua["key_name"] = key_name; + + lua.create_named_table("KEY_TYPE", "ANY", KEY_TYPE::ANY, "KEYBOARD", KEY_TYPE::KEYBOARD, "MOUSE", KEY_TYPE::MOUSE); + lua.create_named_table("GAMEPAD", "UP", 0x0001, "DOWN", 0x0002, "LEFT", 0x0004, "RIGHT", 0x0008, "START", 0x0010, "BACK", 0x0020, "LEFT_THUMB", 0x0040, "RIGHT_THUMB", 0x0080, "LEFT_SHOULDER", 0x0100, "RIGHT_SHOULDER", 0x0200, "A", 0x1000, "B", 0x2000, "X", 0x4000, "Y", 0x8000); lua.create_named_table("GAMEPAD_FLAG", "UP", 1, "DOWN", 2, "LEFT", 3, "RIGHT", 4, "START", 5, "BACK", 6, "LEFT_THUMB", 7, "RIGHT_THUMB", 8, "LEFT_SHOULDER", 9, "RIGHT_SHOULDER", 10, "A", 13, "B", 14, "X", 15, "Y", 16); @@ -894,14 +1191,27 @@ void register_usertypes(sol::state& lua) lua.create_named_table("INPUT_FLAG", "JUMP", 1, "WHIP", 2, "BOMB", 3, "ROPE", 4, "RUN", 5, "DOOR", 6, "MENU", 7, "JOURNAL", 8, "LEFT", 9, "RIGHT", 10, "UP", 11, "DOWN", 12); /// Returns: [ImGuiIO](#ImGuiIO) for raw keyboard, mouse and xinput gamepad stuff. - /// - /// - Note: The clicked/pressed actions only make sense in `ON.GUIFRAME`. - /// - Note: You can use KEY or standard VK keycodes to index `keys` or the other functions. - /// - Note: Overlunky/etc will eat all keys it is currently configured to use, your script will only get leftovers. - /// - Note: Gamepad is basically [XINPUT_GAMEPAD](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad) but variables are renamed and values are normalized to -1.0..1.0 range. // lua["get_io"] = []() -> ImGuiIO lua["get_io"] = ImGui::GetIO; + /// Returns unique id >= 0 for the callback to be used in [clear_callback](#clear_callback) or -1 if the key could not be registered. + /// Add callback function to be called on a hotkey, using Windows hotkey api. These hotkeys will override all game and UI input and can work even when the game is unfocused. They are by design very intrusive and won't let anything else use the same key combo. Can't detect if input is active in another instance, use ImGuiIO if you need Playlunky hotkeys to react to Overlunky input state. Key is a KEY combo (e.g. `KEY.OL_MOD_CTRL | KEY.X`), possibly returned by GuiDrawContext:key_picker. Doesn't work with mouse buttons. + ///
The callback signature is nil on_hotkey(KEY key) + lua["set_hotkey"] = sol::overload([](sol::function cb, KEY key, HOTKEY_TYPE flags) -> CallbackId + { + if (key & OL_BUTTON_MOUSE) + return -1; + auto backend = LuaBackend::get_calling_backend() /**/; + auto luaCb = HotKeyCallback{cb, key, -1, false, 0}; + return backend->register_hotkey(luaCb, flags) /**/; }, + [](sol::function cb, KEY key) -> CallbackId + { + if (key & OL_BUTTON_MOUSE) + return -1; + auto backend = LuaBackend::get_calling_backend() /**/; + auto luaCb = HotKeyCallback{cb, key, -1, false, 0}; + return backend->register_hotkey(luaCb, HOTKEY_TYPE::NORMAL) /**/; }); + lua.create_named_table("DRAW_LAYER", "BACKGROUND", DRAW_LAYER::BACKGROUND, "FOREGROUND", DRAW_LAYER::FOREGROUND, "WINDOW", DRAW_LAYER::WINDOW); /// Deprecated diff --git a/src/game_api/script/usertypes/gui_lua.hpp b/src/game_api/script/usertypes/gui_lua.hpp index cc39d628f..f200b9b5e 100644 --- a/src/game_api/script/usertypes/gui_lua.hpp +++ b/src/game_api/script/usertypes/gui_lua.hpp @@ -119,6 +119,9 @@ class GuiDrawContext void win_indent(float width); /// Sets next item width (width>1: width in pixels, width<0: to the right of window, -1count++; if (!bucket->patches_applied) { DEBUG("Applying patches"); diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index 8345bec20..68bd709d2 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -408,3 +408,5 @@ uint32_t lowbias32_r(uint32_t x); int64_t get_global_frame_count(); int64_t get_global_update_count(); void update_camera_position(); + +bool get_forward_events(); diff --git a/src/game_api/window_api.cpp b/src/game_api/window_api.cpp index 44c329f06..d2c174e96 100644 --- a/src/game_api/window_api.cpp +++ b/src/game_api/window_api.cpp @@ -13,6 +13,8 @@ #include "bucket.hpp" #include "logger.h" #include "memory.hpp" +#include "script/lua_backend.hpp" +#include "state.hpp" bool detect_wine() { @@ -112,6 +114,11 @@ LRESULT CALLBACK hkWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lPar { static const auto bucket = Bucket::get(); + if (message == WM_HOTKEY) + LuaBackend::wm_hotkey((int)wParam); + else if (message == WM_ACTIVATE) + LuaBackend::wm_activate((bool)wParam); + bucket->pause_api->modifiers_down = 0; if (ImGui::GetIO().KeyCtrl) bucket->pause_api->modifiers_down |= 0x100; @@ -121,6 +128,13 @@ LRESULT CALLBACK hkWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lPar bucket->pause_api->modifiers_down |= 0x800; bool consumed_input = g_OnInputCallback ? g_OnInputCallback(message, wParam, lParam) : false; + + /*if (bucket->io->WantCaptureKeyboard.value_or(false) && (message == WM_KEYDOWN || message == WM_KEYUP)) + consumed_input = true;*/ + + if (get_forward_events() && bucket->io->WantCaptureMouse.value_or(false) && message >= WM_LBUTTONDOWN && message <= WM_MOUSEWHEEL) + consumed_input = true; + if (!consumed_input) { LRESULT imgui_result = ImGui_ImplWin32_WndProcHandler(window, message, wParam, lParam); @@ -129,6 +143,7 @@ LRESULT CALLBACK hkWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lPar return imgui_result; } } + if (ImGui::GetIO().WantCaptureKeyboard && message == WM_KEYDOWN) { return DefWindowProc(window, message, wParam, lParam); @@ -222,6 +237,8 @@ LRESULT CALLBACK hkKeyboard(const int code, const WPARAM wParam, const LPARAM lP static bool skip_hkPresent = false; HRESULT STDMETHODCALLTYPE hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags) { + ImGuiContext& g = *GImGui; + static const auto bucket = Bucket::get(); SyncInterval = g_SyncInterval; if (skip_hkPresent) @@ -248,6 +265,22 @@ HRESULT STDMETHODCALLTYPE hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterva } } + if (bucket->count > 1) + { + if (!get_forward_events()) + { + bucket->io->WantCaptureMouse = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && g.HoveredWindow && strcmp(g.HoveredWindow->Name, "Clickhandler"); + bucket->io->WantCaptureKeyboard = ImGui::GetIO().WantCaptureKeyboard; + } + else + { + if (bucket->io->WantCaptureKeyboard.has_value()) + ImGui::GetIO().WantCaptureKeyboard = bucket->io->WantCaptureKeyboard.value(); + if (bucket->io->WantCaptureMouse.has_value()) + ImGui::GetIO().WantCaptureMouse = bucket->io->WantCaptureMouse.value(); + } + } + if (g_PreDrawCallback) { g_PreDrawCallback(); @@ -276,7 +309,7 @@ HRESULT STDMETHODCALLTYPE hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterva } { - if (ImGui::GetIO().WantCaptureKeyboard) + if (ImGui::GetIO().WantCaptureKeyboard || bucket->io->WantCaptureKeyboard.value_or(false)) { if (HWND window = HID_GetRegisteredDeviceWindow(g_HidKeyboard)) { @@ -305,6 +338,12 @@ HRESULT STDMETHODCALLTYPE hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterva g_PostDrawCallback(); } + if (get_forward_events() || bucket->count == 1) + { + bucket->io->WantCaptureKeyboard = std::nullopt; + bucket->io->WantCaptureMouse = std::nullopt; + } + return g_OrigSwapChainPresent(pSwapChain, SyncInterval, Flags); } diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index ab9fe3fcb..abd626c61 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -765,7 +765,7 @@ std::string key_string(int64_t keycode) } if (result == 0) { - name = "Mystery key"; + name = "Unknown"; } std::string keyname(szName); name = keyname; @@ -786,14 +786,14 @@ std::string key_string(int64_t keycode) { name = "Shift+" + name; } - if (keycode & OL_KEY_CTRL) - { - name = "Ctrl+" + name; - } if (keycode & OL_KEY_ALT) { name = "Alt+" + name; } + if (keycode & OL_KEY_CTRL) + { + name = "Ctrl+" + name; + } return name; } @@ -2844,7 +2844,7 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) auto& io = ImGui::GetIO(); ImGuiWindow* current = g.NavWindow; - if (nCode == WM_KEYUP && !io.WantCaptureKeyboard) + if (nCode == WM_KEYUP && !io.WantCaptureKeyboard && !g_bucket->io->WantCaptureKeyboard.value_or(false)) { if (pressed("speedhack_turbo", wParam)) { @@ -2894,6 +2894,9 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) return true; } + if (g_bucket->io->WantCaptureKeyboard.value_or(false)) + return false; + if (pressed("hide_ui", wParam)) { hide_ui = !hide_ui; @@ -5060,7 +5063,7 @@ void render_clickhandler() ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDocking); if (ImGui::IsWindowHovered()) io.WantCaptureMouse = options["mouse_control"]; - if (io.MouseWheel != 0 && ImGui::IsWindowHovered()) + if (io.MouseWheel != 0 && ImGui::IsWindowHovered() && !g_bucket->io->WantCaptureMouse.value_or(false)) { if (clicked("mouse_zoom_out") || (held("mouse_camera_drag") && io.MouseWheel < 0)) { @@ -5272,7 +5275,7 @@ void render_clickhandler() } using namespace std::chrono_literals; auto now = std::chrono::system_clock::now(); - if (options["mouse_control"] && now > last_focus_time + 200ms && (!options["menu_ui"] || mouse_pos().y > ImGui::GetTextLineHeight())) + if (options["mouse_control"] && now > last_focus_time + 200ms && (!options["menu_ui"] || mouse_pos().y > ImGui::GetTextLineHeight()) && !g_bucket->io->WantCaptureMouse.value_or(false)) { ImGui::InvisibleButton("canvas", ImGui::GetContentRegionMax(), ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); if (ImGui::BeginDragDropTarget()) @@ -5888,10 +5891,12 @@ void render_keyconfig() if (io.MouseDown[i]) { size_t keycode = 0x400 + i + 1; - if (io.KeysDown[VK_CONTROL]) + if (ImGui::GetIO().KeyCtrl) keycode += 0x100; - if (io.KeysDown[VK_SHIFT]) + if (ImGui::GetIO().KeyShift) keycode += 0x200; + if (ImGui::GetIO().KeyAlt) + keycode += 0x800; keys[g_change_key] = keycode; save_config(cfgfile); g_change_key = ""; @@ -5906,19 +5911,21 @@ void render_keyconfig() keycode += OL_WHEEL_DOWN; else if (io.MouseWheel > 0) keycode += OL_WHEEL_UP; - if (io.KeysDown[VK_CONTROL]) + if (ImGui::GetIO().KeyCtrl) keycode += 0x100; - if (io.KeysDown[VK_SHIFT]) + if (ImGui::GetIO().KeyShift) keycode += 0x200; + if (ImGui::GetIO().KeyAlt) + keycode += 0x800; keys[g_change_key] = keycode; save_config(cfgfile); g_change_key = ""; } // Keys - for (size_t i = 0; i < VK_LSHIFT; ++i) + for (size_t i = 0; i < 0xFF; ++i) { - if (ImGui::IsKeyDown((ImGuiKey)i)) + if (ImGui::IsKeyReleased((ImGuiKey)i)) { size_t keycode = i; if (ImGui::GetIO().KeyCtrl)