From acbe8f5fb5bfd520a7569df6399e07161865512b Mon Sep 17 00:00:00 2001 From: Dillon Skaggs Date: Mon, 10 Feb 2025 22:05:51 -0600 Subject: [PATCH] feat(extra-natives): add `REGISTER_RAW_KEYMAP` and `REMAP_RAW_KEYMAP` --- .../extra-natives-five/src/InputNatives.cpp | 196 +++++++++++++++++- ext/native-decls/RegisterRawKeymap.md | 40 ++++ ext/native-decls/RemapRawKeymap.md | 39 ++++ 3 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 ext/native-decls/RegisterRawKeymap.md create mode 100644 ext/native-decls/RemapRawKeymap.md diff --git a/code/components/extra-natives-five/src/InputNatives.cpp b/code/components/extra-natives-five/src/InputNatives.cpp index ecccf40e45..fe3d8347c0 100644 --- a/code/components/extra-natives-five/src/InputNatives.cpp +++ b/code/components/extra-natives-five/src/InputNatives.cpp @@ -5,6 +5,8 @@ #include #include "nutsnbolts.h" +#include "ResourceCallbackComponent.h" +#include "console/Console.Base.h" constexpr int KEYS_COUNT = 256; @@ -124,6 +126,137 @@ static void IsRawKeyUp(fx::ScriptContext& context) } } +using OptionalRef = std::optional; + +class RawKeymap +{ +public: + // The keymap name, this should be unique as we will use this to + // remap the keyIndex during regular play + std::string m_keymapName; + // the resource that the keymap should be bound to + std::string m_resource; + // the ref to the key down, this is optional + OptionalRef m_keyDownRef; + // the ref to the key up, this is optional + OptionalRef m_keyUpRef; + // the key index that will be used for this keymap + uint8_t m_keyIndex; + // if the keymap can be disabled when DisableRawKeyThisFrame is called + bool m_canBeDisabled : 1; + // if the keymap was being held down before, used so we don't ignore key ups + // if the key was disabled while being pressed + bool m_wasTriggered : 1; + + RawKeymap(const std::string& keymap, const std::string& resourceName, OptionalRef& keyDown, OptionalRef& keyUp, uint8_t keyIndex, bool canBeDisabled): + m_keymapName(keymap), + m_resource(resourceName), + m_keyDownRef(std::move(keyDown)), + m_keyUpRef(std::move(keyUp)), + m_keyIndex(keyIndex), + m_canBeDisabled(canBeDisabled) + { + } +}; + +// TODO: eastl::vector_map working here +class RawKeymapContainer +{ + std::map>> m_rawKeymaps; + // cross-map to easily find the index + std::map m_usedKeys {}; +public: + RawKeymapContainer() + { + for (uint8_t key = 0; key < (KEYS_COUNT - 1); ++key) + { + m_rawKeymaps[key] = std::map>(); + } + } + + void AddKeymap(const std::shared_ptr& keymap) + { + if (m_usedKeys.find(keymap->m_keymapName) != m_usedKeys.end()) + { + // TODO: use scripting error + console::PrintError(keymap->m_resource, "%s already has a keymap bound by another resource", keymap->m_keymapName.c_str()); + return; + } + + m_usedKeys[keymap->m_keymapName] = keymap->m_keyIndex; + + auto& vector = m_rawKeymaps[keymap->m_keyIndex]; + vector[keymap->m_keymapName] = keymap; + } + + void ChangeKeymapIndex(const std::string& keyName, uint8_t newKeyIndex) + { + const auto it = m_usedKeys.find(keyName); + if (it != m_usedKeys.end()) + { + auto oldIndex = it->second; + auto& oldKeymap = m_rawKeymaps[oldIndex]; + + // We shouldn't have to do a find here because our m_usedKeys should always be up to date. + auto keyData = oldKeymap[keyName]; + oldKeymap.erase(keyName); + + auto& newKeymap = m_rawKeymaps[newKeyIndex]; + newKeymap[keyName] = keyData; + + // update our used keys to the latest + m_usedKeys[keyName] = newKeyIndex; + } + } + + void RemoveKeymap(const std::shared_ptr& keymap) + { + m_usedKeys.erase(keymap->m_keymapName); + auto& container = m_rawKeymaps[keymap->m_keyIndex]; + container.erase(keymap->m_keymapName); + } + + void Update() + { + const auto rm = fx::ResourceManager::GetCurrent(); + for (auto& [key, keymaps] : m_rawKeymaps) + { + if (keymaps.empty()) + { + continue; + } + + bool wasPressed = ioKeyboard_KeyPressed(key); + bool wasReleased = ioKeyboard_KeyReleased(key); + + if (wasPressed || wasReleased) + { + bool isDisabled = disabledKeys.find(key) != disabledKeys.end(); + for (auto& [_, keymap] : keymaps) + { + if (isDisabled && keymap->m_canBeDisabled && !keymap->m_wasTriggered) + { + continue; + } + + if (wasPressed && keymap->m_keyDownRef && !keymap->m_wasTriggered) + { + keymap->m_wasTriggered = true; + rm->CallReference(keymap->m_keyDownRef->GetRef()); + } + + if (wasReleased && keymap->m_keyUpRef && keymap->m_wasTriggered) + { + keymap->m_wasTriggered = false; + rm->CallReference(keymap->m_keyUpRef->GetRef()); + } + } + } + } + + } +}; + static HookFunction initFunction([]() { #ifdef IS_RDR3 @@ -134,10 +267,12 @@ static HookFunction initFunction([]() ioKeyboardActive = hook::get_address(hook::get_pattern("8B 2D ? ? ? ? 48 8B 03"), 2, 6); ioKeyboardKeys = hook::get_address(hook::get_pattern("48 8D 2D ? ? ? ? 49 C1 E6"), 3, 7); #endif + static RawKeymapContainer rawKeymaps; // reset the disabled keys every frame - OnGameFrame.Connect([] + OnGameFrame.Connect([&] { + rawKeymaps.Update(); disabledKeys.clear(); }); @@ -161,4 +296,63 @@ static HookFunction initFunction([]() disabledKeys.insert(rawKeyIndex); } }); + + fx::ScriptEngine::RegisterNativeHandler("REMAP_RAW_KEYMAP", [](fx::ScriptContext& context) + { + auto name = std::string { context.CheckArgument(0) }; + auto rawKeyIndex = context.GetArgument(1); + if (IsRawKeyInvalidOrDisabled(rawKeyIndex)) + { + return; + } + + rawKeymaps.ChangeKeymapIndex(name, rawKeyIndex); + }); + + fx::ScriptEngine::RegisterNativeHandler("REGISTER_RAW_KEYMAP", [](fx::ScriptContext& context) + { + fx::OMPtr runtime; + + if (!FX_SUCCEEDED(fx::GetCurrentScriptRuntime(&runtime))) + { + return; + } + + fx::Resource* resource = reinterpret_cast(runtime->GetParentObject()); + + if (!resource) + { + return; + } + + auto name = std::string { context.CheckArgument(0) }; + + auto onKeyUp = context.GetArgument(1); + auto onKeyDown = context.GetArgument(2); + + OptionalRef onKeyUpRef = onKeyUp ? std::optional(fx::FunctionRef { onKeyUp }) : std::nullopt; + OptionalRef onKeyDownRef = onKeyDown ? std::optional(fx::FunctionRef { onKeyDown }) : std::nullopt; + + auto index = context.GetArgument(3); + + if (IsRawKeyInvalidOrDisabled(index)) + { + return; + } + + auto canBeDisabled = context.GetArgument(4); + + auto resourceName = resource->GetName(); + + auto keyData = std::make_shared(name, resourceName, onKeyUpRef, onKeyDownRef, index, canBeDisabled); + + auto keyDataClone = std::shared_ptr(keyData); + + rawKeymaps.AddKeymap(keyData); + + resource->OnStop.Connect([keyData = std::move(keyDataClone)]() + { + rawKeymaps.RemoveKeymap(keyData); + }); + }); }); diff --git a/ext/native-decls/RegisterRawKeymap.md b/ext/native-decls/RegisterRawKeymap.md new file mode 100644 index 0000000000..ece3046488 --- /dev/null +++ b/ext/native-decls/RegisterRawKeymap.md @@ -0,0 +1,40 @@ +--- +ns: CFX +apiset: shared +--- +## REGISTER_RAW_KEYMAP + +```c +void REGISTER_RAW_KEYMAP(char* keymapName, func onKeyUp, func onKeyDown, int rawKeyIndex, BOOL canBeDisabled); +``` + +Registers a keymap that will be triggered whenever `rawKeyIndex` is pressed or released. + +`onKeyUp` and `onKeyDown` will not provide any arguments. +```ts +function onStateChange(); +``` + +## Parameters +* **keymapName**: A **unique** name that the keymap will be bound to, duplicates will result in the keymap not being registered. +* **onKeyUp**: The function to run when the key is no longer being pressed. +* **onKeyDown**: The function to run when the key is being pressed. +* **rawKeyIndex**: The virtual key to bind this keymap to, see a list [here](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes) +* **canBeDisabled**: If calls to [DISABLE_RAW_KEY_THIS_FRAME](#_0x8BCF0014) will disable this keymap, if a keymap was disabled when the key was pressed down it will still call `onKeyUp` on release. + +## Examples +```lua +function on_key_up() + print("key no longer pressed") +end + +function on_key_down() + print("key is pressed") +end + +local KEY_E = 69 +local canBeDisabled = false + + +RegisterRawKeymap("our_keymap", on_key_up, on_key_down, KEY_E, canBeDisabled) +``` diff --git a/ext/native-decls/RemapRawKeymap.md b/ext/native-decls/RemapRawKeymap.md new file mode 100644 index 0000000000..4899e3e2b9 --- /dev/null +++ b/ext/native-decls/RemapRawKeymap.md @@ -0,0 +1,39 @@ +--- +ns: CFX +apiset: client +--- +## REMAP_RAW_KEYMAP + +```c +void REMAP_RAW_KEYMAP(char* keymapName, int newRawKeyIndex); +``` + +Remaps the keymap bound to `keymapName` to `newRawKeyIndex` + +Virtual key codes can be found [here](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes) + +## Parameters +* **keymapName**: the name given to the keymap in [REGISTER_RAW_KEYMAP](#_0x49C1F6DC) +* **newRawKeyIndex**: Index of raw key from keyboard. + + +## Examples +```lua + +function on_key_up() + print("key no longer pressed") +end + +function on_key_down() + print("key is pressed") +end + +local KEY_SPACE = 32 +local canBeDisabled = false + +local KEY_E = 69 + +RegisterRawKeymap("our_keymap", on_key_up, on_key_down, KEY_SPACE, canBeDisabled) + +RemapRawKeymap("our_keymap", KEY_E) +```