diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua
index 2708a6b55..79f91a9e2 100644
--- a/docs/game_data/spel2.lua
+++ b/docs/game_data/spel2.lua
@@ -1336,6 +1336,9 @@ function speechbubble_visible() end
function cancel_toast() end
---@return nil
function cancel_speechbubble() end
+---Returns RawInput, a game structure for raw keyboard and controller state
+---@return RawInput
+function get_raw_input() end
---Seed the game prng.
---@param seed integer
---@return nil
@@ -2241,13 +2244,32 @@ do
---@field timer integer
---@field slide_position number
+---@class InputDevice
+ ---@field input_index integer
+ ---@field buttons integer
+
---@class GameProps
- ---@field buttons integer @Might be used for some menu inputs not found in buttons_menu
- ---@field buttons_extra integer @Might be used for some menu inputs not found in buttons_menu
- ---@field buttons_menu_previous MENU_INPUT @Previous state of buttons_menu
- ---@field buttons_menu MENU_INPUT @Inputs used to control all the menus, separate from player inputs. You can probably capture and edit this in ON.PRE_UPDATE.
+ ---@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
+ ---@field input_menu_previous MENU_INPUT @Previous state of buttons_menu
---@field game_has_focus boolean
- ---@field modal_open integer
+ ---@field menu_open integer
+ ---@field input_index integer[] @size: 5 @Input index for players 1-4 and maybe for the menu controls. -1: disabled, 0..3: keyboards, 4..7: Xinput, 8..11: other controllers
+
+---@class RawInput
+ ---@field keyboard KeyboardKey[] @size: 112 @State of all keyboard buttons in a random game order as usual
+ ---@field controller ControllerInput[] @size: 12 @State of controller buttons per controller. Zero-based indexing, i.e. use game_props.input_index directly to index this.
+
+---@class KeyboardKey
+ ---@field down boolean @Key is being held
+
+---@class ControllerInput
+ ---@field buttons ControllerButton[] @size: 16
+
+---@class ControllerButton
+ ---@field down boolean @Button is being held
+ ---@field pressed boolean @Button was just pressed down this frame
---@class PRNG
---@field seed fun(self, seed: integer): nil @Same as `seed_prng`
diff --git a/docs/src/includes/_enums.md b/docs/src/includes/_enums.md
index 3396d67a8..292738700 100644
--- a/docs/src/includes/_enums.md
+++ b/docs/src/includes/_enums.md
@@ -871,6 +871,8 @@ Name | Data | Description
[POST_LEVEL_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LEVEL_DESTRUCTION) | ON::POST_LEVEL_DESTRUCTION | Runs right after the current level has been unloaded and all entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
[PRE_LAYER_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LAYER_DESTRUCTION) | ON::PRE_LAYER_DESTRUCTION | Params: [LAYER](#LAYER) layer
Runs right before a layer is unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
[POST_LAYER_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LAYER_DESTRUCTION) | ON::POST_LAYER_DESTRUCTION | Params: [LAYER](#LAYER) layer
Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
+[PRE_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_PROCESS_INPUT) | ON::PRE_PROCESS_INPUT | Runs right before the game gets input from various devices and writes to a bunch of buttons-variables. Return true to disable all game input completely.
+[POST_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_PROCESS_INPUT) | ON::POST_PROCESS_INPUT | Runs right after the game gets input from various devices and writes to a bunch of buttons-variables. Probably the first chance you have to capture or edit buttons_gameplay or buttons_menu sort of things.
## PARTICLEEMITTER
diff --git a/docs/src/includes/_events.md b/docs/src/includes/_events.md
index 309a790b1..511d91bc8 100644
--- a/docs/src/includes/_events.md
+++ b/docs/src/includes/_events.md
@@ -605,3 +605,17 @@ Params: [LAYER](#LAYER) layer
Runs right before a layer is unloaded and any
> Search script examples for [ON.POST_LAYER_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LAYER_DESTRUCTION)
Params: [LAYER](#LAYER) layer
Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
+
+## ON.PRE_PROCESS_INPUT
+
+
+> Search script examples for [ON.PRE_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_PROCESS_INPUT)
+
+Runs right before the game gets input from various devices and writes to a bunch of buttons-variables. Return true to disable all game input completely.
+
+## ON.POST_PROCESS_INPUT
+
+
+> Search script examples for [ON.POST_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_PROCESS_INPUT)
+
+Runs right after the game gets input from various devices and writes to a bunch of buttons-variables. Probably the first chance you have to capture or edit buttons_gameplay or buttons_menu sort of things.
diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md
index e26bf8c08..2ace05e95 100644
--- a/docs/src/includes/_globals.md
+++ b/docs/src/includes/_globals.md
@@ -2064,6 +2064,15 @@ Returns: [ImGuiIO](#ImGuiIO) for raw keyboard, mouse and xinput gamepad stuff.
- 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
+
+
+> Search script examples for [get_raw_input](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_raw_input)
+
+#### [RawInput](#RawInput) get_raw_input()
+
+Returns [RawInput](#RawInput), a game structure for raw keyboard and controller state
+
### mouse_position
diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md
index 302c9f723..39af2b48c 100644
--- a/docs/src/includes/_types.md
+++ b/docs/src/includes/_types.md
@@ -652,6 +652,14 @@ tuple<int, int, int, int> | [get_rgba()](https://github.com/spelunky-fyi/o
[Color](#Color) | [set_ucolor(uColor color)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_ucolor) | Changes color based on given [uColor](#Aliases)
[Color](#Color) | [set(Color other)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set) | Copies the values of different [Color](#Color) to this one
+### ControllerButton
+
+
+Type | Name | Description
+---- | ---- | -----------
+bool | [down](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=down) | [Button](#Button) is being held
+bool | [pressed](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=pressed) | [Button](#Button) was just pressed down this frame
+
### CutsceneBehavior
@@ -744,6 +752,13 @@ Type | Name | Description
[ENT_TYPE](#ENT_TYPE) | [owner_type](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=owner_type) |
int | [owner_uid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=owner_uid) |
+### KeyboardKey
+
+
+Type | Name | Description
+---- | ---- | -----------
+bool | [down](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=down) | Key is being held
+
### Letter
@@ -983,6 +998,13 @@ tuple<float, float> | [split()](https://github.com/spelunky-fyi/overlunky/
## Input types
+### ControllerInput
+
+
+Type | Name | Description
+---- | ---- | -----------
+array<[ControllerButton](#ControllerButton), 16> | [buttons](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=buttons) |
+
### Gamepad
Used in [ImGuiIO](#ImGuiIO)
@@ -1025,6 +1047,14 @@ float | [mousewheel](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=mo
| [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) |
+### InputDevice
+
+
+Type | Name | Description
+---- | ---- | -----------
+int | [input_index](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=input_index) |
+int | [buttons](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=buttons) |
+
### InputMapping
Used in [PlayerSlot](#PlayerSlot)
@@ -1061,6 +1091,14 @@ array<[PlayerSlotSettings](#PlayerSlotSettings), MAX_PLAYERS> | [player_se
[PlayerSlotSettings](#PlayerSlotSettings) | [player_slot_3_settings](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=player_slot_3_settings) |
[PlayerSlotSettings](#PlayerSlotSettings) | [player_slot_4_settings](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=player_slot_4_settings) |
+### RawInput
+
+
+Type | Name | Description
+---- | ---- | -----------
+array<[KeyboardKey](#KeyboardKey), 112> | [keyboard](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=keyboard) | State of all keyboard buttons in a random game order as usual
+array<[ControllerInput](#ControllerInput), 12> | [controller](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=controller) | State of controller buttons per controller. Zero-based indexing, i.e. use game_props.input_index directly to index this.
+
## Journal types
@@ -2787,12 +2825,13 @@ array<int, MAX_PLAYERS> | [buttons_movement](https://github.com/spelunky-f
Type | Name | Description
---- | ---- | -----------
-int | [buttons](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=buttons) | Might be used for some menu inputs not found in buttons_menu
-int | [buttons_extra](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=buttons_extra) | Might be used for some menu inputs not found in buttons_menu
-[MENU_INPUT](#MENU_INPUT) | [buttons_menu_previous](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=buttons_menu_previous) | Previous state of buttons_menu
-[MENU_INPUT](#MENU_INPUT) | [buttons_menu](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=buttons_menu) | Inputs used to control all the menus, separate from player inputs. You can probably capture and edit this in [ON](#ON).PRE_UPDATE.
+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
+[MENU_INPUT](#MENU_INPUT) | [input_menu_previous](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=input_menu_previous) | Previous state of buttons_menu
bool | [game_has_focus](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=game_has_focus) |
-int | [modal_open](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=modal_open) |
+int | [menu_open](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=menu_open) |
+array<int, 5> | [input_index](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=input_index) | Input index for players 1-4 and maybe for the menu controls. -1: disabled, 0..3: keyboards, 4..7: Xinput, 8..11: other controllers
### Items
diff --git a/src/game_api/flags.hpp b/src/game_api/flags.hpp
index ee54ceb8d..139cbc405 100644
--- a/src/game_api/flags.hpp
+++ b/src/game_api/flags.hpp
@@ -767,3 +767,19 @@ std::array renderer_flags1{
"Liquid smoothing",
"unknown",
};
+
+std::array player_inputs{
+ "Disabled",
+ "Keyboard 1",
+ "Keyboard 2",
+ "Keyboard 3",
+ "Keyboard 4",
+ "XInput 1",
+ "XInput 2",
+ "XInput 3",
+ "XInput 4",
+ "Controller 1",
+ "Controller 2",
+ "Controller 3",
+ "Controller 4",
+};
diff --git a/src/game_api/game_manager.cpp b/src/game_api/game_manager.cpp
index a7848320b..746a5a618 100644
--- a/src/game_api/game_manager.cpp
+++ b/src/game_api/game_manager.cpp
@@ -9,3 +9,9 @@ GameManager* get_game_manager()
static GameManager** gm = (GameManager**)get_address("game_manager"sv);
return *gm;
}
+
+RawInput* get_raw_input()
+{
+ static auto offset = get_address("input_table");
+ return reinterpret_cast(offset);
+}
diff --git a/src/game_api/game_manager.hpp b/src/game_api/game_manager.hpp
index cb45461b8..036154fc8 100644
--- a/src/game_api/game_manager.hpp
+++ b/src/game_api/game_manager.hpp
@@ -70,31 +70,65 @@ struct BackgroundMusic
uint32_t unknown22;
};
-struct GameProps
+struct KeyboardKey
{
- /// Might be used for some menu inputs not found in buttons_menu
+ /// Key is being held
+ bool down;
+ size_t unknown;
+};
+
+struct ControllerButton
+{
+ /// Button is being held
+ bool down;
+ /// Button was just pressed down this frame
+ bool pressed;
+};
+
+struct ControllerInput
+{
+ std::array buttons;
+};
+
+struct RawInput
+{
+ /// State of all keyboard buttons in a random game order as usual
+ std::array keyboard;
+ /// State of controller buttons per controller. Zero-based indexing, i.e. use game_props.input_index directly to index this.
+ std::array controller;
+};
+
+struct InputDevice
+{
+ // No idea what these actually do, better not to expose this anyway
+ bool unknown1;
+ bool unknown2;
+ bool menu_input;
+ bool lost_connection;
+ int8_t input_index;
+ uint8_t padding2[3];
uint32_t buttons;
- uint32_t unknown1;
- uint32_t unknown2;
- uint32_t unknown3;
- /// Might be used for some menu inputs not found in buttons_menu
- uint32_t buttons_extra;
- uint32_t unknown4;
- uint32_t unknown5;
- uint32_t unknown6;
+ // a lot more stuff
+};
+
+struct GameProps
+{
+ /// 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.
+ std::array buttons;
+ std::array buttons_previous;
/// Previous state of buttons_menu
MENU_INPUT buttons_menu_previous;
- /// Inputs used to control all the menus, separate from player inputs. You can probably capture and edit this in ON.PRE_UPDATE.
+ /// Inputs used to control all the menus, separate from player inputs. You can probably capture and edit this in ON.POST_PROCESS_INPUT
MENU_INPUT buttons_menu;
- int8_t modal_open;
+ int8_t menu_icon_slot;
bool game_has_focus;
bool unknown9;
bool unknown10;
- // there's more stuff here
- std::array unknown11; // pointers to something
+ /// Yet another place for some buttons in some random order, too tired to make another enum for them
+ std::array input_device;
- int8_t input_index[5]; // not sure, just came up with this name, if not used it's -1
- // for example if you just run the game and use OL to warp somewhere immediately there will be no controller setup, so all of those will be -1
+ /// Input index for players 1-4 and maybe for the menu controls. -1: disabled, 0..3: keyboards, 4..7: Xinput, 8..11: other controllers
+ std::array input_index;
// uint8_t padding_probably1[3];
@@ -160,3 +194,4 @@ struct GameManager
};
GameManager* get_game_manager();
+RawInput* get_raw_input();
diff --git a/src/game_api/script/events.cpp b/src/game_api/script/events.cpp
index 13bc37131..f7503d99b 100644
--- a/src/game_api/script/events.cpp
+++ b/src/game_api/script/events.cpp
@@ -486,3 +486,29 @@ bool pre_set_feat(FEAT feat)
});
return block;
}
+
+bool pre_process_input()
+{
+ bool return_val = false;
+ LuaBackend::for_each_backend(
+ [=, &return_val](LuaBackend::LockedBackend backend)
+ {
+ if (backend->on_pre_process_input())
+ {
+ return_val = true;
+ return false;
+ }
+ return true;
+ });
+ return return_val;
+}
+
+void post_process_input()
+{
+ LuaBackend::for_each_backend(
+ [&](LuaBackend::LockedBackend backend)
+ {
+ backend->on_post_process_input();
+ return true;
+ });
+}
diff --git a/src/game_api/script/events.hpp b/src/game_api/script/events.hpp
index fff659bcd..d4087da49 100644
--- a/src/game_api/script/events.hpp
+++ b/src/game_api/script/events.hpp
@@ -22,6 +22,7 @@ bool pre_init_level();
bool pre_init_layer(LAYER layer);
bool pre_unload_level();
bool pre_unload_layer(LAYER layer);
+bool pre_process_input();
void post_room_generation();
void post_level_generation();
@@ -30,6 +31,7 @@ void post_init_level();
void post_init_layer(LAYER layer);
void post_unload_level();
void post_unload_layer(LAYER layer);
+void post_process_input();
void on_death_message(STRINGID stringid);
std::optional pre_get_feat(FEAT feat);
diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp
index 31edf5209..e48657108 100644
--- a/src/game_api/script/lua_backend.cpp
+++ b/src/game_api/script/lua_backend.cpp
@@ -1856,3 +1856,54 @@ void LuaBackend::on_set_user_data(Entity* ent)
}
}
}
+
+bool LuaBackend::on_pre_process_input()
+{
+ if (!get_enabled())
+ return false;
+
+ auto now = get_frame_count();
+ std::lock_guard lock{global_lua_lock};
+
+ for (auto& [id, callback] : callbacks)
+ {
+ if (is_callback_cleared(id))
+ continue;
+
+ if (callback.screen == ON::PRE_PROCESS_INPUT)
+ {
+ callback.lastRan = now;
+ set_current_callback(-1, id, CallbackType::Normal);
+ if (handle_function(this, callback.func).value_or(false))
+ {
+ clear_current_callback();
+ return true;
+ }
+ clear_current_callback();
+ }
+ }
+ return false;
+}
+
+void LuaBackend::on_post_process_input()
+{
+ if (!get_enabled())
+ return;
+
+ auto now = get_frame_count();
+ std::lock_guard lock{global_lua_lock};
+
+ for (auto& [id, callback] : callbacks)
+ {
+ if (is_callback_cleared(id))
+ continue;
+
+ if (callback.screen == ON::POST_PROCESS_INPUT)
+ {
+ callback.lastRan = now;
+ set_current_callback(-1, id, CallbackType::Normal);
+ handle_function(this, callback.func);
+ clear_current_callback();
+ }
+ }
+}
diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp
index 0693bdf47..5833ccae3 100644
--- a/src/game_api/script/lua_backend.hpp
+++ b/src/game_api/script/lua_backend.hpp
@@ -128,6 +128,8 @@ enum class ON
POST_LEVEL_DESTRUCTION,
PRE_LAYER_DESTRUCTION,
POST_LAYER_DESTRUCTION,
+ PRE_PROCESS_INPUT,
+ POST_PROCESS_INPUT,
};
struct IntOption
@@ -435,6 +437,8 @@ class LuaBackend
bool on_pre_state_update();
void on_set_user_data(Entity* ent);
void load_user_data();
+ bool on_pre_process_input();
+ void on_post_process_input();
};
template
diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp
index 6d03569f2..9372f7ff5 100644
--- a/src/game_api/script/lua_vm.cpp
+++ b/src/game_api/script/lua_vm.cpp
@@ -2391,7 +2391,11 @@ end
"PRE_LAYER_DESTRUCTION",
ON::PRE_LAYER_DESTRUCTION,
"POST_LAYER_DESTRUCTION",
- ON::POST_LAYER_DESTRUCTION);
+ ON::POST_LAYER_DESTRUCTION,
+ "PRE_PROCESS_INPUT",
+ ON::PRE_PROCESS_INPUT,
+ "POST_PROCESS_INPUT",
+ ON::POST_PROCESS_INPUT);
/* ON
// LOGO
@@ -2624,6 +2628,10 @@ end
// POST_LAYER_DESTRUCTION
// Params: LAYER layer
// Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
+ // PRE_PROCESS_INPUT
+ // Runs right before the game gets input from various devices and writes to a bunch of buttons-variables. Return true to disable all game input completely.
+ // POST_PROCESS_INPUT
+ // Runs right after the game gets input from various devices and writes to a bunch of buttons-variables. Probably the first chance you have to capture or edit buttons_gameplay or buttons_menu sort of things.
*/
lua.create_named_table(
diff --git a/src/game_api/script/usertypes/game_manager_lua.cpp b/src/game_api/script/usertypes/game_manager_lua.cpp
index 94f189ab2..4703a17fc 100644
--- a/src/game_api/script/usertypes/game_manager_lua.cpp
+++ b/src/game_api/script/usertypes/game_manager_lua.cpp
@@ -8,8 +8,10 @@
#include // for move, declval
#include // for min, max
-#include "game_manager.hpp" // for GameManager, JournalPopupUI, GameProps
-#include "screen.hpp" // IWYU pragma: keep
+#include "game_manager.hpp" // for GameManager, JournalPopupUI, GameProps
+#include "memory.hpp" // for memory_read TODO:temp
+#include "screen.hpp" // IWYU pragma: keep
+#include "script/sol_helper.hpp" //
namespace NGM
{
@@ -95,20 +97,60 @@ void register_usertypes(sol::state& lua)
&JournalPopupUI::timer,
"slide_position",
&JournalPopupUI::slide_position);
+ lua.new_usertype(
+ "InputDevice",
+ "input_index",
+ &InputDevice::input_index,
+ "buttons",
+ &InputDevice::buttons);
lua.new_usertype(
"GameProps",
+ /// NoDoc
"buttons",
+ [](GameProps& gp) -> uint32_t
+ {
+ return gp.buttons[0];
+ },
+ "input",
&GameProps::buttons,
- "buttons_extra",
- &GameProps::buttons_extra,
- "buttons_menu_previous",
- &GameProps::buttons_menu_previous,
- "buttons_menu",
+ "input_previous",
+ &GameProps::buttons_previous,
+ "input_menu",
&GameProps::buttons_menu,
+ "input_menu_previous",
+ &GameProps::buttons_menu_previous,
"game_has_focus",
&GameProps::game_has_focus,
- "modal_open",
- sol::property([](GameProps& gp)
- { return gp.modal_open == 0; }));
+ "menu_open",
+ sol::property([](GameProps& gp) -> bool
+ { return gp.menu_icon_slot != -1; }),
+ "input_index",
+ &GameProps::input_index);
+
+ lua.new_usertype(
+ "RawInput",
+ "keyboard",
+ &RawInput::keyboard,
+ "controller",
+ //&RawInput::controller,
+ sol::property([](RawInput& r)
+ { return ZeroIndexArray(r.controller) /**/; }));
+ lua.new_usertype(
+ "KeyboardKey",
+ "down",
+ &KeyboardKey::down);
+ lua.new_usertype(
+ "ControllerInput",
+ "buttons",
+ &ControllerInput::buttons);
+ lua.new_usertype(
+ "ControllerButton",
+ "down",
+ &ControllerButton::down,
+ "pressed",
+ &ControllerButton::pressed);
+
+ /// Returns RawInput, a game structure for raw keyboard and controller state
+ lua["get_raw_input"] = get_raw_input;
}
}; // namespace NGM
diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp
index 4da90ec03..fd74b6b5a 100644
--- a/src/game_api/search.cpp
+++ b/src/game_api/search.cpp
@@ -2080,6 +2080,16 @@ std::unordered_map g_address_rules{
.decode_call()
.at_exe(),
},
+ {
+ "input_table"sv,
+ PatternCommandBuffer{}
+ .from_exe_base(0x22e1c940) // TODO
+ },
+ {
+ "process_input"sv,
+ PatternCommandBuffer{}
+ .from_exe_base(0x22c42ae0) // TODO
+ },
};
std::unordered_map g_cached_addresses;
diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp
index 55e6dff5a..e187246b1 100644
--- a/src/game_api/state.cpp
+++ b/src/game_api/state.cpp
@@ -296,6 +296,7 @@ State& State::get()
hook_godmode_functions();
strings_init();
init_state_update_hook();
+ init_process_input_hook();
auto bucket = Bucket::get();
if (!bucket->patches_applied)
@@ -679,6 +680,31 @@ void init_state_update_hook()
}
}
+using OnProcessInput = void(void*);
+OnProcessInput* g_process_input_trampoline{nullptr};
+void ProcessInput(void* s)
+{
+ if (!pre_process_input())
+ {
+ g_process_input_trampoline(s);
+ }
+ post_process_input();
+}
+
+void init_process_input_hook()
+{
+ g_process_input_trampoline = (OnProcessInput*)get_address("process_input");
+ DetourTransactionBegin();
+ DetourUpdateThread(GetCurrentThread());
+ DetourAttach((void**)&g_process_input_trampoline, &ProcessInput);
+
+ const LONG error = DetourTransactionCommit();
+ if (error != NO_ERROR)
+ {
+ DEBUG("Failed hooking process_input stuff: {}\n", error);
+ }
+}
+
uint8_t enum_to_layer(const LAYER layer, std::pair& player_position)
{
if (layer == LAYER::FRONT)
diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp
index a8346e0e6..d7ef6c68f 100644
--- a/src/game_api/state.hpp
+++ b/src/game_api/state.hpp
@@ -374,6 +374,7 @@ struct State
LiquidPhysicsEngine* get_correct_liquid_engine(ENT_TYPE liquid_type);
};
void init_state_update_hook();
+void init_process_input_hook();
uint8_t enum_to_layer(const LAYER layer, std::pair& player_position);
uint8_t enum_to_layer(const LAYER layer);
diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp
index 7191178fc..7cc403bc8 100644
--- a/src/injected/ui.cpp
+++ b/src/injected/ui.cpp
@@ -1913,6 +1913,14 @@ void quick_start(uint8_t screen, uint8_t world, uint8_t level, uint8_t theme)
g_game_manager->main_menu_music->kill(false);
g_game_manager->main_menu_music = nullptr;
}
+
+ if (g_game_manager->game_props->input_index[0] == -1)
+ g_game_manager->game_props->input_index[0] = 0;
+ if (g_game_manager->game_props->input_index[4] == -1)
+ g_game_manager->game_props->input_index[4] = 0;
+
+ // TODO: this doesn't quite work, loads intro after character selection
+ g_state->screen_character_select->available_mine_entrances = 4;
}
std::string get_clipboard()
@@ -8030,6 +8038,8 @@ void render_players()
update_players();
for (auto player : g_players)
{
+ ImGui::Text("%d:", player->input_ptr->player_slot + 1);
+ ImGui::SameLine();
render_uid(player->uid, "players");
}
}
@@ -8278,7 +8288,6 @@ void render_game_props()
{
if (ImGui::MenuItem("Respawn dead players"))
respawn();
- ImGui::TextWrapped("New players spawned here can't be controlled, but can be used to test some things that require multiple players.");
if (ImGui::SliderScalar("Number of players##SetNumPlayers", ImGuiDataType_U8, &g_state->items->player_count, &u8_one, &u8_four, "%d", ImGuiSliderFlags_AlwaysClamp))
{
std::array active_players{false, false, false, false};
@@ -8317,7 +8326,34 @@ void render_game_props()
}
}
}
+ ImGui::SeparatorText("Players");
render_players();
+ ImGui::SeparatorText("Player inputs");
+ ImGui::PushID("PlayerInputIndex");
+ for (unsigned int i = 0; i < 5; ++i)
+ {
+ ImGui::PushID(i);
+ auto label = i < 4 ? fmt::format("Player {}##PlayerInput{}", i + 1, i) : "Menu?";
+ auto index = g_game_manager->game_props->input_index[i];
+ if (ImGui::BeginCombo(label.c_str(), player_inputs[index + 1]))
+ {
+ for (int8_t j = -1; j < 12; j++)
+ {
+ const bool item_selected = (j == index);
+ const char* item_text = player_inputs[j + 1];
+
+ ImGui::PushID(j);
+ if (ImGui::Selectable(item_text, item_selected))
+ g_game_manager->game_props->input_index[i] = j;
+ if (item_selected)
+ ImGui::SetItemDefaultFocus();
+ ImGui::PopID();
+ }
+ ImGui::EndCombo();
+ }
+ ImGui::PopID();
+ }
+ ImGui::PopID();
endmenu();
}
if (submenu("Level flags"))