Skip to content

Input

Two ways to press a key. By name, and the call resolves the player's own bind: input.cast(1) presses whatever ability 1 is bound to, input.parry() presses the parry key. By VK code, which you supply yourself. Prefer names unless you need a key the name list does not cover.

Every function on this page is external. None of them need needs_internal.

Pressing by name

FunctionReturnsDescription
input.tap(name)booleanTaps the bound key: down now, up about 32 ms later. True when the tap started or was queued
input.hold(name, [ms])booleanHolds the bound key. With ms omitted or 0 the key stays down until input.release. True when the hold started or was extended
input.release(name)booleanReleases a key this script is holding. True when this script was holding it
input.cast(slot, [opts])booleanTaps the player's real ability key. slot is 1 to 4. True when the tap started or was queued
input.use_item(slot, [opts])booleanTaps the player's real item key. slot is 1 to 4. Same returns and same refusal rules as input.cast
input.melee([opts])booleanTaps the player's real melee key. The options table is the first argument
input.parry([opts])booleanTaps the player's real parry key. The options table is the first argument
input.bind(name)integer | nilThe key code that name resolves to right now, or nil when it is bound nowhere. Presses nothing

opts is { alt = boolean, hold_ms = number }. alt holds the alt-cast key across the tap and releases it one gap after the tap ends. hold_ms replaces the default 32 ms press length.

Unknown name: argument error. Unbound name: false, with one log line per reason per 5 seconds.

Presses are refused, not queued, while the game window is not focused or the game's cursor is visible (menu, shop, chat). tap, hold, cast, use_item, melee and parry return false then. input.release and the raw key functions do not check.

A tap is key down, key up 32 ms later, and a 16 ms gap before the same key can go down again. Taps on one key are serialised, so two taps in one frame land as two presses.

input.hold(name) with no ms holds until input.release or until the script stops. Holds are released when the script is disabled, errors, resets or reloads. Calling hold again on a held key resets the deadline.

input.cast and input.use_item take slots 1 to 4. The slot enum counts from 0. Out of range is an argument error.

These press a key and nothing more. Check p:is_ability_ready yourself. Aim can stage the shot instead and needs the internal.

input.bind(name) returns the key a name resolves to, without pressing or logging. It is also the only way to read the melee bind.

Tap through the player's real keys, with a reload guard:

lua
local taps = 0

function on_tick()
    local me = local_player()
    if not me then return end

    local ammo = me:get_ammo()
    if ammo and ammo < 2 then input.tap("reload"); return end
    if input.tap("attack") then taps = taps + 1 end
end

Button names

Names accepted by every function that takes a name:

attack, attack2, jump, duck, forward, back, moveleft, moveright, use, reload, sprint, dash, ability1, ability2, ability3, ability4, item1, item2, item3, item4, melee, parry, alt_cast, cancel_ability, zipline

  • dash and sprint are the same button, not two keys. Either name works.
  • use presses fine, but it sets no button bit, so input.is_held("use"), input.was_pressed("use") and input.bit("use") all answer nil.
  • Parry is Deadlock's Held Item binding, not melee. A script that presses melee to parry does nothing. See Game Keybinds.
  • Binds are re-read when the game's bindings change, at most every 2 seconds.

Reading the game's buttons

These report what the game processed, not the physical keyboard. input.is_key_held is the physical key.

FunctionReturnsDescription
input.buttons()table | nilThe local player's processed button state. nil before the first successful read
input.is_held(name)boolean | nilThat button is down in the last processed command
input.was_pressed(name)boolean | nilRising edge since the previous read
input.pressed_cmd(name)integer | nilThe command number that button was last pressed on. 0 for a button not pressed this session
input.bit(name)integer | nilThe button bit for that name, for testing buttons().held yourself
input.diag()tableA fresh table of counters

input.buttons() fields:

FieldMeaning
held, changed, queued_down, double, toggle64-bit button masks
pressed, releasedThe edges since the previous read
cmd, prev_cmdThe newest processed command number, and the one before it
forward, left, upThe movement wishes
max_speedThe current max speed
seqBumps whenever the state moved
staleTrue once the feed has gone idle

Read on demand. The button state is only sampled while some script asked for it in the last 2 seconds. buttons, is_held, was_pressed and pressed_cmd keep it alive; bit and diag do not.

The first call after a gap returns nil and the value lands next tick. When the window lapses the last state is kept and stale is set. on_shot carries cmd only while the feed is being read. See Events.

input.buttons() returns the same table every call, updated in place. Copy the fields you need; do not keep or modify it.

input.diag() returns a fresh table of counters. The useful ones: refused (presses turned away, almost always focus or cursor), binds_loaded (false means no bindings were found), bind_reloads, cursor_visible, holds, pending_taps.

Keys and the mouse

All keys here use Windows VK codes; see Types & Constants for the common ones.

FunctionReturnsDescription
input.is_key_held(vk)booleanKey is currently held
input.is_key_pressed(vk)booleanEdge-triggered, true once per press. A VK gets enrolled for tracking the first time you poll it, so the first call for a key always returns false. If that key is already held at that moment, the next tick reports one spurious press that never happened. Don't use it for a startup toggle on a key the user may be holding at load. The tracked set is shared across scripts, so "first poll" means the first poll by any script.
input.press_key(vk)-Press and immediately release
input.key_down(vk)-Press without releasing
input.key_up(vk)-Release a held key
input.is_mouse_key(vk)booleanTrue if vk corresponds to a mouse button
input.get_cursor_pos()vec2Current cursor screen position in pixels
input.move_mouse(delta, [speed], [detach])booleanRelative mouse movement. delta is a vec2 of raw mouse counts, not pixels. Those are the units targeting.compute_aim_delta returns. speed > 0 enables smoothed movement (default 0 = instant). detach is the detach factor (default 0). Returns true
input.snap_to(player, bone, [proj_speed])-Snap the crosshair to a target bone. Applies ease-out smoothing (fraction = 1−exp(−deltaMs/60), tau=60 ms) to the targeting.compute_aim_delta result via a persistent accumulator (accum_x/y), integer-truncates, clamps output to ±12 counts (MAX_C=12), carries the remainder, and dispatches a MoveMouse action only when dx≠0 or dy≠0. bone accepts a string (bone name), integer (raw skeleton index), or vec3 (explicit world position)

Argument 1 may be a vec3 world position instead of a player. That path ignores bone and applies no projectile lead; input.snap_to(world_pos) and input.snap_to(world_pos, 0) are the same. With a player, supply bone: input.snap_to(target) alone resolves to a zero delta and moves nothing.

Examples

Hold-to-rage style:

lua
function on_tick()
    if input.is_key_held(0x02) then  -- right mouse button
        -- aimbot logic
    end
end

Press an ability through the game's bound key:

lua
local key = slot_to_key(slot.ability3)
input.press_key(key)

The slot fields are all lowercase (slot.ability1-slot.ability4, slot.item1-slot.item4, slot.jump, slot.slide, slot.weapon_primary, ...). There is no slot.ABILITY_3. It evaluates to nil, and slot_to_key then raises bad argument #1 to 'slot_to_key' (number expected, got nil).

Edge-triggered toggle:

lua
local on = false

function on_tick()
    if input.is_key_pressed(0x70) then  -- F1
        on = not on
        toast(on and "ON" or "OFF")
    end
end

Snap to a target's head:

lua
function on_tick()
    if not input.is_key_held(0x02) then return end  -- hold right mouse
    local target = targeting.find_closest_by_fov(8.0, 30.0)
    if target and target:is_visible() then
        local proj_speed = local_player():get_active_projectile_speed()
        input.snap_to(target, "head", proj_speed)
    end
end

On-screen readout of the buttons the game processed:

lua
local NAMES = { "attack", "attack2", "jump", "duck", "reload", "sprint",
                "ability1", "ability2", "ability3", "ability4", "melee", "parry" }
local jumps = 0

function on_tick()
    local b = input.buttons()
    if input.was_pressed("jump") then jumps = jumps + 1 end
    if not b then
        draw.text(40, 300, "input.buttons(): nil (no read yet)", 0xFF8C8C8C)
        return
    end
    local held = {}
    for _, n in ipairs(NAMES) do if input.is_held(n) then held[#held + 1] = n end end
    draw.text(40, 300, string.format("cmd %d  %s  jumps %d%s",
        b.cmd, #held > 0 and table.concat(held, " ") or "-", jumps,
        b.stale and "  (stale)" or ""), 0xFFE6E6E6)
end