-
Notifications
You must be signed in to change notification settings - Fork 0
Keybinds
A keybind row lets the player click it and press a key. The framework stores what they pressed;
registering the shortcut is your job, because UE4SS keybinds belong to the mod that uses them.
"I" -- a bare key
"Ctrl+I" -- a chord
"Alt+N"client.values.YourKey is that string. Parse it before handing anything to RegisterKeyBind:
Key["Ctrl+I"] is nil.
Copy this into your mod. Both shipped consumer mods use exactly this shape.
-- "Ctrl+I" -> "I", { ModifierKey.CONTROL }. A bare "I" -> "I", {}.
-- The framework's modifier names differ from UE4SS's, hence the mapping.
local MODIFIER_NAMES = { CTRL = "CONTROL", ALT = "ALT", SHIFT = "SHIFT" }
local function parseBinding(value)
local parts = {}
for part in tostring(value):gmatch("[^+]+") do
parts[#parts + 1] = part
end
local baseKey = table.remove(parts)
local modifiers = {}
for _, name in ipairs(parts) do
local const = ModifierKey ~= nil and ModifierKey[MODIFIER_NAMES[name:upper()] or ""] or nil
if const == nil then
return nil, nil, name -- a modifier UE4SS does not expose
end
modifiers[#modifiers + 1] = const
end
return baseKey, modifiers, nil
end
-- UE4SS has no UnregisterKeyBind, so a binding cannot be replaced. Register lazily and gate on the
-- current value: a rebind adds one registration and the previous one stays resident but inert.
local registeredBindings = {}
local function ensureBindingRegistered()
local binding = client.values.ToggleKey
if type(binding) ~= "string" or binding == "" or registeredBindings[binding] then
return
end
local baseKey, modifiers, unknownModifier = parseBinding(binding)
if unknownModifier ~= nil then
print("[YourMod] '" .. binding .. "' uses an unsupported modifier: " .. unknownModifier)
return
end
local keyConst = Key[baseKey]
if not keyConst then
print("[YourMod] '" .. tostring(baseKey) .. "' is not a key UE4SS recognises")
return
end
local handler = function()
-- Re-read at press time so a rebind takes effect and the superseded bind does nothing.
if client.values.ToggleKey ~= binding then
return
end
-- This runs on UE4SS's thread, not the game thread. Queue any UObject work.
ExecuteInGameThread(function() doTheThing() end)
end
-- Two-argument form for a bare key, three-argument for a chord. An empty modifier table is not
-- the same as passing none.
local ok
if #modifiers > 0 then
ok = pcall(RegisterKeyBind, keyConst, modifiers, handler)
else
ok = pcall(RegisterKeyBind, keyConst, handler)
end
if ok then
registeredBindings[binding] = true
end
end
ensureBindingRegistered()
client:subscribe(function(key)
if key == "ToggleKey" then
ensureBindingRegistered() -- a rebind needs its own registration
end
end)-
Keys:
AtoZandF1toF12. Deliberately not the whole keyboard, because every offered key becomes a permanent global bind and UE4SS cannot remove one. -
Modifiers:
CtrlandAlt. Shift is not offered. A modifier cannot be observed on its own, so capture registers each key bare, with Ctrl and with Alt, and which callback fires is the only signal of what was held.
The row checks the chosen key and tells the player what it collides with:
- Against the game's own bindings, with the readable action name - "conflicts with Inventory". Bare keys only. The game's binding tables hold unmodified keys, so there is no modifier to compare a chord against and the check has nothing to say about one.
-
Against every other registered mod, chord included. Two mods both claiming
Ctrl+Iis something no amount of reading the game's key config would reveal.
The row lights up vanilla's own warning highlight and names the clash in its caption.
Keybinds do not consume the keypress, and the engine does not treat a combination as one thing.
Both halves matter together: Ctrl+R fires your bind and leaves Palworld to act on R, whose
binding has no modifier flag that could fail to match. A chord only stops the row from warning about
it. It does not stop the game.
So a chord is still the better default, since it keeps two mods off the same shortcut and drops the
warning highlight. But pick a base key the game doesn't use, or accept that the vanilla action
fires alongside yours every time. Palbox IVs' Ctrl+I opens the inventory as well as the panels,
which is survivable; a base key bound to something destructive would not be.
The schema has a modifiers field that pins a modifier and reduces the picker to choosing a base key:
{ key = "ToggleKey", type = "keybind", default = "I", modifiers = { "Ctrl" } }, -- avoidIt does two unhelpful things. The picker silently discards the modifier the player pressed, so
choosing Alt+N stores "N" and leaves the shortcut on Ctrl+N. And it suppresses the conflict
check against the game's bindings, which a player choosing a bare key needs.
Put the full chord in default instead and parse the whole value:
{ key = "ToggleKey", type = "keybind", default = "Ctrl+I" }, -- preferA keybind change fires your subscribe callback like any other option, so re-registering from there
is enough - no restart required. The framework re-reads the committed value from its config file for
this one type, because a keybind row has no native setter that carries the value as an argument.
UE4SS exposes no UnregisterKeyBind. The gate above handles it: the superseded bind stays loaded but
returns immediately, and registrations grow by one per distinct binding chosen in a session. That is
why you should not pre-register every possible key.
Native Mod Options