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
| Function | Returns | Description |
|---|---|---|
input.tap(name) | boolean | Taps the bound key: down now, up about 32 ms later. True when the tap started or was queued |
input.hold(name, [ms]) | boolean | Holds 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) | boolean | Releases a key this script is holding. True when this script was holding it |
input.cast(slot, [opts]) | boolean | Taps the player's real ability key. slot is 1 to 4. True when the tap started or was queued |
input.use_item(slot, [opts]) | boolean | Taps the player's real item key. slot is 1 to 4. Same returns and same refusal rules as input.cast |
input.melee([opts]) | boolean | Taps the player's real melee key. The options table is the first argument |
input.parry([opts]) | boolean | Taps the player's real parry key. The options table is the first argument |
input.bind(name) | integer | nil | The 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:
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
endButton 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
dashandsprintare the same button, not two keys. Either name works.usepresses fine, but it sets no button bit, soinput.is_held("use"),input.was_pressed("use")andinput.bit("use")all answernil.- Parry is Deadlock's Held Item binding, not melee. A script that presses
meleeto 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.
| Function | Returns | Description |
|---|---|---|
input.buttons() | table | nil | The local player's processed button state. nil before the first successful read |
input.is_held(name) | boolean | nil | That button is down in the last processed command |
input.was_pressed(name) | boolean | nil | Rising edge since the previous read |
input.pressed_cmd(name) | integer | nil | The command number that button was last pressed on. 0 for a button not pressed this session |
input.bit(name) | integer | nil | The button bit for that name, for testing buttons().held yourself |
input.diag() | table | A fresh table of counters |
input.buttons() fields:
| Field | Meaning |
|---|---|
held, changed, queued_down, double, toggle | 64-bit button masks |
pressed, released | The edges since the previous read |
cmd, prev_cmd | The newest processed command number, and the one before it |
forward, left, up | The movement wishes |
max_speed | The current max speed |
seq | Bumps whenever the state moved |
stale | True 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.
| Function | Returns | Description |
|---|---|---|
input.is_key_held(vk) | boolean | Key is currently held |
input.is_key_pressed(vk) | boolean | Edge-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) | boolean | True if vk corresponds to a mouse button |
input.get_cursor_pos() | vec2 | Current cursor screen position in pixels |
input.move_mouse(delta, [speed], [detach]) | boolean | Relative 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:
function on_tick()
if input.is_key_held(0x02) then -- right mouse button
-- aimbot logic
end
endPress an ability through the game's bound key:
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:
local on = false
function on_tick()
if input.is_key_pressed(0x70) then -- F1
on = not on
toast(on and "ON" or "OFF")
end
endSnap to a target's head:
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
endOn-screen readout of the buttons the game processed:
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