Skip to content

Input ​

There are two ways to press a key. Pressing by name uses the player's own bind: input.cast(1) presses whatever ability 1 is bound to, and input.parry() presses the parry key. Pressing by VK code uses a key 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.

An unknown name is an argument error. An unbound name returns false, with one log line per reason per 5 seconds.

While the game window is not focused or the game's cursor is visible (menu, shop, chat), presses are refused and not queued. tap, hold, cast, use_item, melee and parry return false then.

The raw key and mouse functions further down (press_key, key_down, click, move_mouse and the rest) do not return false, but what they send is dropped in the same situations, and keys your script holds are released. They are not pressed again when the cursor hides or focus returns. Release the key first (key_up, or input.release for a name), then press it again. Escape is the one exception: while the game window is focused but its cursor is showing, press_key, key_down and key_up still send Escape, so a script can close the game menu.

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. More taps while one is already waiting are merged into it.

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, and on any tick it does not run because is_enabled returned false or the hero filter skipped it. 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 only press a key. Check p:is_ability_ready yourself. Aim can stage the shot instead, but it 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. Either name works.
  • use can be pressed, but it sets no button bit, so input.is_held("use"), input.was_pressed("use") and input.bit("use") all return nil.
  • Parry is Deadlock's Held Item binding, not melee. A script that presses melee to parry gets a melee attack instead. 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 reads 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
seqGoes up whenever the state changes
staleTrue once the feed has gone idle

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

When no script has asked for it in 2 seconds, the last state is kept and stale is set. The next call returns that stale state and restarts the read, and fresh values arrive on the next tick. Only calls made before the first successful read return nil. on_shot carries cmd only while the feed is being read. See Events.

input.buttons() returns the same table every call and updates it in place. Copy the fields you need. Do not keep or modify the table.

Useful input.diag() counters: refused (presses that were blocked, almost always because of focus or the 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 key is only tracked after 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 press that never happened. Don't use it for a startup toggle on a key the user may be holding at load. Tracked keys are shared across scripts, so "first poll" means the first poll by any script.
input.press_key(vk)-A tap: down now, up 32 ms later, the same as input.tap. A press of a key a script is still pressing waits for it. Only one press per key waits at a time, and more presses while one waits are merged into it
input.key_down(vk)-Press without releasing. The key stays down until key_up. It is also released in the same cases as input.hold, including any tick the script does not run
input.key_up(vk)-Release a held key
input.click([button], [ms])trueA mouse click: down now, up ms later. button is "left" (default), "right", "middle", "x1" or "x2". Case does not matter, and any other name raises an error. ms is how long the button stays down, default 32 (two game ticks), which is long enough for the game to see it. Values outside 16 to 1000 are clamped to that range. A click on a button a script is still pressing waits for it
input.is_mouse_key(vk)booleanTrue if vk corresponds to a mouse button
input.get_cursor_pos()vec2Current cursor screen position in pixels
input.wheel_delta()integerMouse wheel notches since the previous tick: positive when scrolled away from you, 0 when the wheel did not move. Always 0 while the game is not focused and the menu is closed
input.move_mouse(delta, [speed], [detach])booleanRelative mouse movement. delta is a vec2 of raw mouse counts, not pixels. targeting.compute_aim_delta returns the same units. With speed above 0 (default 0), only that fraction of delta is moved, clamped to 0.01 to 1. detach is accepted but has no effect. Returns true
input.snap_to(player, bone, [proj_speed])-Moves the crosshair toward a target bone, using the targeting.compute_aim_delta result. The move is smoothed and limited to 12 counts per call. bone accepts a string (bone name), integer (raw skeleton index), or vec3 (explicit world position)

Mouse buttons (VK.LBUTTON, VK.RBUTTON, VK.MBUTTON, VK.XBUTTON1, VK.XBUTTON2) work with press_key, key_down and key_up too, and are sent as real mouse clicks.

input.snap_to also takes a vec3 world position instead of a player. That path ignores bone and applies no projectile lead, so input.snap_to(world_pos) and input.snap_to(world_pos, 0) are the same. With a player, pass bone. input.snap_to(target) alone gives 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 to slot.ability4, slot.item1 to 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