Skip to main content

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: err is an error code string

Scope Model​

  • Input is 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​

  • Input itself is not a node graph API.
  • Typical usage is driving local NodeUI interactions and camera/client-visual behavior.
  • If input should change authoritative gameplay state, send a server-authorized request (Command/Event/API call), do not mutate NodeSim directly.

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_chat while 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_menu
  • toggle_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.Acquire to show a pointer
  • Input.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​

FunctionDescriptionScope
Input.AddActionRegister a logical input action.C
Input.RemoveActionRemove a logical input action.C
Input.HasActionCheck whether an action exists.C
Input.IsPressedCheck whether an action is currently pressed.C
Input.AddBindAdd a callback bind for an action trigger.C
Input.RemoveBindRemove one bind by id.C
Input.RemoveBindsRemove all binds for one action or all actions.C
Input.ListBindsList active bind handlers.C
Input.SetBindingsSet physical key/control bindings for an action.C
Input.GetBindingsGet physical key/control bindings for an action.C
Input.PushContextAcquire a temporary input suppression context.C
Input.PopContextRelease one input context handle.C
Input.GetContextStateRead effective input-context state.C

Event List​

EventDescriptionScope
Built-in eventsNone in Input v1.C
input bind callbackCallback 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, default true)

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, default false)

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:
    • ctrl
    • shift
    • alt
    • meta

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, default false)
  • blockGameActions (boolean, default false)
  • blockMenuActions (boolean, default false)
  • blockUINavigation (boolean, default false)
  • blockMouse (boolean, default false)
  • exclusive (boolean, default false): foreground capture; while any exclusive context is active, only its own allowed actions remain active
  • suspendGameplay (boolean, default false): suspends gameplay input readers such as free camera and future driving controls
  • allowActions (table, optional): list of action names that remain allowed
  • reason (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:

  • Input handles device input and actions.
  • Command handles 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_FOUND
  • ERR_ALREADY_EXISTS
  • ERR_INVALID_ACTION
  • ERR_INVALID_HANDLER
  • ERR_INVALID_BINDING
  • ERR_INVALID_PHASE
  • ERR_PERMISSION_DENIED
  • ERR_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)