Input
Input manages client-side input actions and runtime bind handlers.
Use logical actions (for example change_camera_view) instead of hardcoding keys in gameplay scripts.
The same client input runtime is shared by:
- core game UI
- main menu
- debug console
- chat
- client-side Lua resources
Runtime Performance​
Resource-defined input actions and registry changes are batched internally.
This matters for:
- resource startup
- resource unload
- restoring default controls
The engine coalesces input-registry change notifications and the controls UI rebuild, so adding/removing many Lua actions does not trigger one full controls-menu rebuild per action.
Error Handling Pattern​
- Success:
err == nil - Failure:
erris an error code string
Scope Model​
Inputis client-only.- Server scripts do not read keyboard/controller input directly.
- Server-side authority still applies to gameplay actions triggered by input.
- Pointer visibility/capture is managed separately through
Cursor.
Node Relationship​
Inputitself is not a node graph API.- Typical usage is driving local
NodeUIinteractions and camera/client-visual behavior. - If input should change authoritative gameplay state, send a server-authorized request (
Command/Event/API call), do not mutateNodeSimdirectly.
Focus and Text Input​
When a native UI text field has focus:
- Lua input binds are suppressed
- Lua hotkeys are suppressed
- the core chat-open action is suppressed
This prevents gameplay/chat actions from firing while the player is typing into a Lua UI input field.
If a resource also needs a visible pointer while the field is focused, acquire a cursor handle through Cursor.Acquire(...).
Input Contexts​
Use input contexts when a resource opens interactive UI and needs to suppress background input safely.
Typical examples:
- block gameplay actions while a panel is open
- block
open_chatwhile typing into a custom UI - suppress Lua binds except one allowlisted toggle action
Contexts are handle-based, like Cursor:
Input.PushContext(...)Input.PopContext(handleId)
Contexts are automatically cleaned up when:
- the resource stops
- the client disconnects
- the client Lua runtime resets
Reserved core actions are not suppressed by Lua input contexts:
toggle_menutoggle_debug_console
Core UI now uses the same model internally:
- chat input
- main menu
- debug console
- modal dialogs
Foreground interactive UI suspends gameplay input at the core layer:
- free camera movement/look
- gameplay movement/combat actions
- future built-in driving controls
This suspension is engine-owned. Resources should not implement their own gameplay-stop hacks on top of it.
Interactive UI Pattern​
For an interactive Lua panel, use both:
Cursor.Acquireto show a pointerInput.PushContext(...)to suppress background actions
Typical pattern:
local panelVisible = false
local cursorHandle = nil
local inputContextHandle = nil
local function openPanel()
if panelVisible then
return
end
panelVisible = true
cursorHandle = Cursor.Acquire({
visible = true,
capture = "free",
reason = "admin_panel"
})
inputContextHandle = Input.PushContext({
blockLuaBinds = true,
blockGameActions = true,
blockMenuActions = true,
allowActions = { "admin_panel_toggle" },
reason = "admin_panel"
})
end
local function closePanel()
if not panelVisible then
return
end
panelVisible = false
if inputContextHandle then
Input.PopContext(inputContextHandle)
inputContextHandle = nil
end
if cursorHandle then
Cursor.Release(cursorHandle)
cursorHandle = nil
end
end
For text inputs inside the panel:
- keep the panel nodes alive while typing
- update list/detail content in place instead of recreating the focused input
- let native focus handling suppress background chat/binds
Function List​
| Function | Description | Scope |
|---|---|---|
Input.AddAction | Register a logical input action. | C |
Input.RemoveAction | Remove a logical input action. | C |
Input.HasAction | Check whether an action exists. | C |
Input.IsPressed | Check whether an action is currently pressed. | C |
Input.AddBind | Add a callback bind for an action trigger. | C |
Input.RemoveBind | Remove one bind by id. | C |
Input.RemoveBinds | Remove all binds for one action or all actions. | C |
Input.ListBinds | List active bind handlers. | C |
Input.SetBindings | Set physical key/control bindings for an action. | C |
Input.GetBindings | Get physical key/control bindings for an action. | C |
Input.PushContext | Acquire a temporary input suppression context. | C |
Input.PopContext | Release one input context handle. | C |
Input.GetContextState | Read effective input-context state. | C |
Event List​
| Event | Description | Scope |
|---|---|---|
Built-in events | None in Input v1. | C |
input bind callback | Callback contract used by Input.AddBind(...). | C |
Trigger and Binding Model​
Action name:
- logical, stable identifier (
change_camera_view,open_phone,nitro_boost).
Trigger phase:
down: when action becomes pressed.up: when action is released.both: callback on press and release.change: callback when analog strength changes.
Physical binding entry (for SetBindings):
- key binding:
{ type = "key", key = "V" } - key binding with modifiers:
{ type = "key", key = "Z", ctrl = true } - mouse binding:
{ type = "mouse", button = "Left" } - gamepad binding:
{ type = "gamepad", button = "A" } - control binding:
{ type = "control", control = "fire" }
Events​
Input Bind Callback Contract​
Input uses bind callbacks instead of global named hooks.
Callback context fields:
ctx.action(string)ctx.phase(down|up|change)ctx.device(keyboard|mouse|gamepad|unknown)ctx.strength(number,0..1)ctx.previousStrength(number,0..1)ctx.timestamp(number, ms epoch)
Example:
local bindId, err = Input.AddBind("change_camera_view", function(ctx)
print("action=" .. ctx.action .. " phase=" .. ctx.phase .. " device=" .. ctx.device)
end, { phase = "down" })
Functions​
Input.AddAction​
C Client Only
local err = Input.AddAction(actionName, options)
Parameters:
actionName(string)options(table, optional):category(string)persistent(boolean, defaulttrue)
Returns:
err(nil| string)
Input.RemoveAction​
C Client Only
local err = Input.RemoveAction(actionName)
Parameters:
actionName(string)
Returns:
err(nil| string)
Input.HasAction​
C Client Only
local exists, err = Input.HasAction(actionName)
Parameters:
actionName(string)
Returns:
exists(boolean | nil)err(nil| string)
Input.IsPressed​
C Client Only
local pressed, err = Input.IsPressed(actionName)
Parameters:
actionName(string)
Returns:
pressed(boolean | nil)err(nil| string)
Input.AddBind​
C Client Only
local bindId, err = Input.AddBind(actionName, handler, options)
Parameters:
actionName(string)handler(function)options(table, optional):phase("down" | "up" | "both" | "change", default"down")priority(number, higher runs first)consume(boolean, defaultfalse)
Handler signature:
function handler(ctx)
-- ctx.action
-- ctx.phase -- "down" | "up" | "change"
-- ctx.device -- "keyboard" | "mouse" | "gamepad" | "unknown"
-- ctx.strength -- 0..1
-- ctx.previousStrength -- 0..1
-- ctx.timestamp -- ms
end
Returns:
bindId(string | nil)err(nil| string)
Input.RemoveBind​
C Client Only
local err = Input.RemoveBind(bindId)
Parameters:
bindId(string)
Returns:
err(nil| string)
Input.RemoveBinds​
C Client Only
local err = Input.RemoveBinds(actionName)
Parameters:
actionName(string, optional):- omitted/nil removes all bind handlers created by script context.
Returns:
err(nil| string)
Input.ListBinds​
C Client Only
local binds, err = Input.ListBinds(actionName)
Parameters:
actionName(string, optional)
Returns:
binds(table | nil)err(nil| string)
Input.SetBindings​
C Client Only
local err = Input.SetBindings(actionName, bindings)
Parameters:
actionName(string)bindings(table): list of physical binding entries
Returns:
err(nil| string)
Notes:
- this updates physical mapping for the local client.
- keep one logical action name even if physical keys differ per user/device.
- key bindings support optional modifier flags:
ctrlshiftaltmeta
Input.GetBindings​
C Client Only
local bindings, err = Input.GetBindings(actionName)
Parameters:
actionName(string)
Returns:
bindings(table | nil)err(nil| string)
Input.PushContext​
C Client Only
local handleId, err = Input.PushContext(options)
Parameters:
options(table, optional):blockLuaBinds(boolean, defaultfalse)blockGameActions(boolean, defaultfalse)blockMenuActions(boolean, defaultfalse)blockUINavigation(boolean, defaultfalse)blockMouse(boolean, defaultfalse)exclusive(boolean, defaultfalse): foreground capture; while any exclusive context is active, only its own allowed actions remain activesuspendGameplay(boolean, defaultfalse): suspends gameplay input readers such as free camera and future driving controlsallowActions(table, optional): list of action names that remain allowedreason(string, optional)
Returns:
handleId(string | nil)err(nil| string)
Example:
local ctxHandle, err = Input.PushContext({
blockLuaBinds = true,
blockGameActions = true,
blockMenuActions = true,
exclusive = true,
suspendGameplay = true,
allowActions = { "admin_panel_toggle:admin-panel:v3" },
reason = "admin_panel"
})
Input.PopContext​
C Client Only
local err = Input.PopContext(handleId)
Parameters:
handleId(string)
Returns:
err(nil| string)
Input.GetContextState​
C Client Only
local state, err = Input.GetContextState()
Returns:
state(table | nil)err(nil| string)
State fields:
activeContextCount(number)blockLuaBinds(boolean)blockGameActions(boolean)blockMenuActions(boolean)blockUINavigation(boolean)blockMouse(boolean)gameplaySuspended(boolean)allowedActionCount(number)err(nil| string)
Command Integration​
Input and Command are complementary:
Inputhandles device input and actions.Commandhandles command parsing/execution.
Example pattern:
- bind action callback with
Input.AddBind(...). - callback calls
Command.Execute(...)when desired. - do not execute with
sourceType=\"system\"from direct player input paths.
Event-First Guidance​
- Prefer
Input.AddBind(...)callbacks over polling-style checks. - Use
phase="down"for one-shot actions (camera switch, menu toggle, interact). - Use
phase="both"for press/release logic. - Use
phase="change"for analog-driven behavior.
Error Codes​
ERR_NOT_FOUNDERR_ALREADY_EXISTSERR_INVALID_ACTIONERR_INVALID_HANDLERERR_INVALID_BINDINGERR_INVALID_PHASEERR_PERMISSION_DENIEDERR_BUSY
Quick Examples​
Register action and default keyboard bind:
local err = Input.AddAction("change_camera_view", {
category = "Vehicle",
persistent = true
})
if err then return print(err) end
local bindErr = Input.SetBindings("change_camera_view", {
{ type = "key", key = "V" }
})
React to action press:
local bindId, err = Input.AddBind("change_camera_view", function(ctx)
TriggerEvent("custom:camera_view_cycle_requested", {
phase = ctx.phase
})
end, {
phase = "down"
})
Keyboard shortcut with modifiers:
Input.AddAction("editor_undo", {
category = "Editor",
persistent = false
})
Input.SetBindings("editor_undo", {
{ type = "key", key = "Z", ctrl = true }
})
Input.AddBind("editor_undo", function(_ctx)
TriggerEvent("custom:map_editor_command_undo", {}, {
target = "server",
reliable = true
})
end, {
phase = "down"
})
Multiple bindings for the same logical action:
Input.AddAction("editor_redo", {
category = "Editor",
persistent = false
})
Input.SetBindings("editor_redo", {
{ type = "key", key = "Y", ctrl = true },
{ type = "key", key = "Z", ctrl = true, shift = true }
})
Bind to command execution:
Input.AddAction("quick_respawn")
Input.SetBindings("quick_respawn", {
{ type = "key", key = "F5" }
})
Input.AddBind("quick_respawn", function(ctx)
Command.Execute("/respawn", { sourceType = "chat" })
end)