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
| 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.
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:
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. Either name works.usecan be pressed, but it sets no button bit, soinput.is_held("use"),input.was_pressed("use")andinput.bit("use")all returnnil.- Parry is Deadlock's Held Item binding, not melee. A script that presses
meleeto 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.
| 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 | Goes up whenever the state changes |
stale | True 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.
| 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 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]) | true | A 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) | boolean | True if vk corresponds to a mouse button |
input.get_cursor_pos() | vec2 | Current cursor screen position in pixels |
input.wheel_delta() | integer | Mouse 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]) | boolean | Relative 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:
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 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:
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