Skip to content

Commit

Permalink
feat(extra-natives): add REGISTER_RAW_KEYMAP and REMAP_RAW_KEYMAP
Browse files Browse the repository at this point in the history
  • Loading branch information
AvarianKnight committed Feb 11, 2025
1 parent 2ca92f1 commit acbe8f5
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 1 deletion.
196 changes: 195 additions & 1 deletion code/components/extra-natives-five/src/InputNatives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <ScriptEngine.h>

#include "nutsnbolts.h"
#include "ResourceCallbackComponent.h"
#include "console/Console.Base.h"

constexpr int KEYS_COUNT = 256;

Expand Down Expand Up @@ -124,6 +126,137 @@ static void IsRawKeyUp(fx::ScriptContext& context)
}
}

using OptionalRef = std::optional<fx::FunctionRef>;

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<uint8_t, std::map<std::string, std::shared_ptr<RawKeymap>>> m_rawKeymaps;
// cross-map to easily find the index
std::map<std::string, uint8_t> m_usedKeys {};
public:
RawKeymapContainer()
{
for (uint8_t key = 0; key < (KEYS_COUNT - 1); ++key)
{
m_rawKeymaps[key] = std::map<std::string, std::shared_ptr<RawKeymap>>();
}
}

void AddKeymap(const std::shared_ptr<RawKeymap>& 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<RawKeymap>& 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<void>(keymap->m_keyDownRef->GetRef());
}

if (wasReleased && keymap->m_keyUpRef && keymap->m_wasTriggered)
{
keymap->m_wasTriggered = false;
rm->CallReference<void>(keymap->m_keyUpRef->GetRef());
}
}
}
}

}
};

static HookFunction initFunction([]()
{
#ifdef IS_RDR3
Expand All @@ -134,10 +267,12 @@ static HookFunction initFunction([]()
ioKeyboardActive = hook::get_address<int*>(hook::get_pattern("8B 2D ? ? ? ? 48 8B 03"), 2, 6);
ioKeyboardKeys = hook::get_address<keysData*>(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();
});

Expand All @@ -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<const char*>(0) };
auto rawKeyIndex = context.GetArgument<uint32_t>(1);
if (IsRawKeyInvalidOrDisabled<false>(rawKeyIndex))
{
return;
}

rawKeymaps.ChangeKeymapIndex(name, rawKeyIndex);
});

fx::ScriptEngine::RegisterNativeHandler("REGISTER_RAW_KEYMAP", [](fx::ScriptContext& context)
{
fx::OMPtr<IScriptRuntime> runtime;

if (!FX_SUCCEEDED(fx::GetCurrentScriptRuntime(&runtime)))
{
return;
}

fx::Resource* resource = reinterpret_cast<fx::Resource*>(runtime->GetParentObject());

if (!resource)
{
return;
}

auto name = std::string { context.CheckArgument<const char*>(0) };

auto onKeyUp = context.GetArgument<const char*>(1);
auto onKeyDown = context.GetArgument<const char*>(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<uint32_t>(3);

if (IsRawKeyInvalidOrDisabled<false>(index))
{
return;
}

auto canBeDisabled = context.GetArgument<bool>(4);

auto resourceName = resource->GetName();

auto keyData = std::make_shared<RawKeymap>(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);
});
});
});
40 changes: 40 additions & 0 deletions ext/native-decls/RegisterRawKeymap.md
Original file line number Diff line number Diff line change
@@ -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)
```
39 changes: 39 additions & 0 deletions ext/native-decls/RemapRawKeymap.md
Original file line number Diff line number Diff line change
@@ -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)
```

0 comments on commit acbe8f5

Please sign in to comment.