Skip to content

API Input and Controls

Piggo41 edited this page Sep 30, 2026 · 7 revisions

Input and controls

Lua API / Input and controls

Native hotkeys create user-configurable commands in Minecraft's Controls screen. Named input maps instead separate game actions from physical keys and mouse buttons; they can observe exact press/release transitions and consume an event before Minecraft performs its normal movement, inventory, GUI, or world action. Legacy key_input and mouse_input events remain observational and cannot consume native behavior.

On this page

Register a Controls hotkey

betamoon.input:hotkey(definition)

Registers a keyboard command through ModLoader's native key system and returns a hotkey handle. The command appears in Minecraft's Controls screen, and a player's saved remap is restored on later launches. Use hotkeys for global mod commands that players should configure alongside Minecraft's own keys; use an input map when an action needs context priority, held/released state, mouse input, modifiers, or native-input consumption.

Field Required Meaning
key yes Namespaced key, stored canonically as namespace:hotkey/name.
label yes User-facing name shown in Controls; at most 128 characters.
default yes One unmodified keyboard binding such as key.g; key.none starts unbound.
repeatWhileHeld no Invoke on each ModLoader input tick while held; default false invokes once per press.
local openBuilder = betamoon.input:hotkey {
    key = "mymod:open_builder",
    label = "Open Builder",
    default = "key.b",
}

openBuilder:on(function(event)
    showBuilder()
end)

The callback event has key, binding, and keyCode, where binding and keyCode reflect the player's current Controls assignment. getBinding() returns the current normalized binding, isActive() reports whether the script registration is live, on(nil) removes the callback, and close() detaches the registration. Hotkey callbacks follow ModLoader's world-tick polling and do not consume Minecraft input; use a handled input-map action when consumption is required. A callback error disables that callback and reports the script error without affecting other hotkeys.

ModLoader has no key-unregistration operation. Script unload therefore detaches the callback but retains a dormant Controls entry for the rest of the Minecraft session. Redeclaring the same canonical key during hot reload reuses that entry and preserves its current remap. Changing repeatWhileHeld for a retained key requires a Minecraft restart; the label may change immediately. A changed default is used on a fresh process only when no saved assignment exists; it never overwrites a player's remap.

Register an input map

betamoon.input:map(definition)

Registers an owner-scoped map and returns an active input-map handle. key accepts namespace:name shorthand and is stored canonically as namespace:input_map/name. Action names use lowercase letters, digits, _, and -.

Field Required Meaning
key yes Namespaced input-map key.
actions yes Table from local action name to a binding string or { default = binding }. A binding may be a string or array.
priority no Default context priority, -10000 through 10000; default 0.
captures no Native families captured by the default context.
local controls = betamoon.input:map {
    key = "mymod:builder",
    priority = 10,
    actions = {
        place = { default = "mouse.left" },
        rotate = { default = "key.r" },
        cancel = { default = "key.escape" },
        zoom = { default = { "mouse.wheel_up", "mouse.wheel_down" } },
    },
}

controls.key is the canonical key. Duplicate keys, duplicate action names, overlapping bindings, unknown fields, invalid devices, and maps outside a Lua script raise an error.

Physical bindings

Bindings are case-insensitive when declared and are returned in normalized lowercase form.

Form Examples
Keyboard key.r, key.escape, key.space, key.f5
Named mouse buttons mouse.left, mouse.right, mouse.middle
Numbered mouse buttons mouse.button1 through mouse.button32; button 1 is the left button
Wheel mouse.wheel, mouse.wheel_up, mouse.wheel_down
Required modifiers ctrl+key.r, shift+mouse.left, ctrl+shift+key.s

Required modifiers may be held in addition to other modifiers. For example, ctrl+key.r still matches while Shift is also held. mouse.wheel reports the signed raw wheel delta; directional wheel bindings match only their direction.

Action callbacks

controls:on(action, callback)

Sets the default context's callback for an action and returns controls. Passing nil removes it. A callback runs only for semantic transitions, not once per render frame or repeatedly while held.

controls:on("place", function(event)
    if event.phase == "pressed" then
        beginPlacement()
    end
    return betamoon.callbackResults.handled
end)

The callback receives:

Field Meaning
map, action Canonical map key and local action name.
phase pressed or released. Wheel input is a pressed pulse.
pressed, held, released State snapshot for the current input cycle. A wheel pulse is pressed and released, but not held.
amount 1/0 for digital input, or signed raw wheel delta.
cycle Monotonic input-cycle number.
device, code Physical source. Omitted for a synthesized lifecycle release.

Return callbackResults.pass or nil to continue to lower contexts and Minecraft. handled and deny both stop lower input contexts and consume the native event; use handled for an action your game performed and deny for an action intentionally refused. An error disables only that context and falls back to the next context or native input.

Action state

controls:getState(action)

Returns a detached { cycle, pressed, held, released, amount } snapshot. pressed and released last for one input cycle. held remains stable without invoking Lua repeatedly.

local movement = controls:getState("place")
if movement.held then
    updatePreview()
end

controls:getBindings(action)

Returns an array of the action's current normalized binding strings.

Context layers and capture

controls:activate(options)

Adds another context for the same map and returns a context handle. Options accept priority and captures. Higher priority runs first; at equal priority, the newest active context runs first. Each context has token, priority, on(action, callback), isActive(), and close().

local wheel = controls:activate {
    priority = 100,
    captures = { "movement", "look", "world_actions" },
}

wheel:on("cancel", function()
    wheel:close()
    return betamoon.callbackResults.handled
end)

Capture names are:

Family Native behavior suppressed while active
actions Native input for physical events matching this map's actions.
movement Forward/back/strafe/jump/sneak key forwarding.
look Mouse-derived player rotation.
world_actions Attack, use, place, and held block breaking.
inventory Inventory/drop keys, hotbar number keys, and wheel slot selection.
gui Keyboard and mouse events while a GUI screen is open.

Captures are independent: a spell wheel can suppress movement and look without blocking the inventory or every named action. Closing a context immediately reveals the next context.

Rebinding

controls:rebind(action, binding, options)

Replaces the effective binding with a string or array and returns controls. By default, a physical binding that can activate together with another action in the same map raises a conflict error. This includes aliases such as mouse.left/mouse.button1 and modifier subsets such as key.r/ctrl+key.r. { allowConflict = true } permits it.

controls:rebind("rotate", "key.g")
controls:rebind("cancel", { "key.escape", "mouse.right" }, { allowConflict = true })

controls:resetBinding(action)

Restores the declaration's default binding and returns controls. Rebinds are runtime values in this release; they reset when the owning script unloads or reloads.

Lifecycle and limits

Input maps and contexts are cleaned up automatically on script unload. Hotkey callbacks are detached while their native Controls entries remain retained as described above. Focus, screen, world, and player replacement synthesize releases before physical state is cleared, preventing stuck held actions. A map supports at most 128 actions and eight effective bindings per action. The client supports up to 256 retained native hotkeys, 256 maps, 256 active contexts, and 1024 semantic transitions in one cycle.

Clone this wiki locally