Input
Provides interaction with the game world through skill casting, item usage, movement, and more.
These functions send packets to the game server mimicking legitimate actions. Spamming inputs may send more requests than humanly possible, risking account safety. Use appropriate delays and checks in your scripts.
Game Interaction
core.input.use_skill(skill_id: number)
Attempts to cast a skill. Check cooldown with core.skill_book.is_skill_on_cooldown() first.
core.input.use_item(item_id: number)
Attempts to use an item from inventory. Verify existence with core.inventory.has_item() first.
core.input.talk_to_npc(npc_id: number) -> boolean
Begins dialogue with the specified NPC.
Returns: true if the dialogue packet was sent, false if the NPC isn't on the current map or the player is outside interaction range.
This function no longer teleports the player to the NPC. Position the character within range first (e.g. via core.rusher.rush() or core.input.teleport_safe()) before calling.
core.input.enter_portal()
Enters the portal at the player's current position. Ensure character is standing on a portal.
core.input.press_key(key_code: number)
Presses a key for the player. Requires a valid local player object. Does not use Windows API.
The game treats this as a quick tap (a single key-down event), so movement keys release almost immediately. To hold a key down, use hold_key / release_key instead.
Key codes: Virtual Key Codes Reference
core.input.hold_key(key_code: number)
Sends a key-down and keeps it held — the game keeps treating the key as pressed until you call release_key for the same key. Use this to hold movement or skill keys for a controlled duration instead of tapping. Requires a valid local player object. Does not use the Windows API.
core.input.release_key(key_code: number)
Sends a key-up for a key previously held with hold_key, ending the hold.
Because your script decides when to release, the hold lasts exactly as long as you want — unlike press_key, which releases right away.
Example:
local VK_LEFT = 0x25
local hold_until = 0
function on_tick()
local now = core.get_update_time()
-- Start holding Left, and schedule the release ~600ms out
if hold_until == 0 then
core.input.hold_key(VK_LEFT)
hold_until = now + 600
elseif now >= hold_until then
core.input.release_key(VK_LEFT)
hold_until = 0
end
endcore.input.call_wndproc(msg: number, wparam?: number, lparam?: number) -> number | nil
Invokes the game's window procedure directly with a synthesized message, bypassing the OS message queue. The message is delivered straight to the game's handler, skipping the menu's own input hooks. Useful for feeding key/mouse events the game treats as if they came from Windows.
wparam and lparam follow the standard Win32 layout for the given message (e.g. virtual-key code in wparam for WM_KEYDOWN, packed x/y in lparam for mouse messages). Both default to 0.
Returns: The LRESULT from the window procedure, or nil if the game window is unavailable.
This calls into the game directly with whatever message you provide. Malformed parameters can crash the client. Only use message/parameter combinations that follow Win32 conventions.
Example:
local WM_KEYDOWN, WM_KEYUP = 0x0100, 0x0101
local VK_SPACE = 0x20
-- Tap the spacebar straight through the game's wndproc
core.input.call_wndproc(WM_KEYDOWN, VK_SPACE, 0)
core.input.call_wndproc(WM_KEYUP, VK_SPACE, 0)core.input.get_mouse_position() -> {x: number, y: number} | nil
Returns the current cursor position relative to the game window client area.
Returns: A table with x and y, or nil if the game window is unavailable or the cursor is outside the game window.
Keyboard State & Callbacks
Read live keyboard state, or register callbacks that fire when specific keys are pressed or released. Handy for hotkeys that toggle your script, or for gating actions on a held modifier.
Key codes: Virtual Key Codes Reference
core.input.is_key_down(key_code: number) -> boolean
Returns true while the engine sees the given virtual key as held down. Reads the engine's tracked keyboard state, so it also works for modifier keys (VK_CONTROL 0x11, VK_SHIFT 0x10, VK_MENU 0x12) — useful for combo checks.
core.input.is_key_up(key_code: number) -> boolean
Convenience inverse of is_key_down — true while the key is not held.
core.input.on_key_press(key_code: number, callback: function) -> number
Registers callback to run each time key_code is pressed. The callback receives the key code as its only argument.
Only the key you register for invokes your callback, so there's no per-keypress dispatch in your script. The press event is edge-triggered: holding the key fires the callback once, not repeatedly while the OS auto-repeats.
Returns: A handle you can pass to remove_key_callback. Callbacks are also removed automatically when the script unloads.
core.input.on_key_release(key_code: number, callback: function) -> number
Like on_key_press, but the callback fires when the key is released. Useful for hold-to-act bindings.
core.input.remove_key_callback(handle: number) -> boolean
Unregisters a callback previously returned by on_key_press or on_key_release.
Returns: true if a matching callback was found and removed.
Example:
local VK_F1, VK_F2, VK_CONTROL = 0x70, 0x71, 0x11
local farming = false
-- F1 toggles a loop. The handler only runs on F1 — pressing movement or
-- skill keys never calls it.
core.input.on_key_press(VK_F1, function()
farming = not farming
core.log("farming " .. (farming and "on" or "off"))
end)
-- Combine with is_key_down for modifier combos: Ctrl+F2 to hard-stop.
core.input.on_key_press(VK_F2, function()
if core.input.is_key_down(VK_CONTROL) then
farming = false
end
end)Movement
core.input.teleport(x: number, y: number)
Instantly teleports to coordinates.
Only use for one-time teleports to portals or NPCs. For repeated teleports, use teleport_safe(). Spamming this function will trigger anti-cheat detection.
core.input.teleport_safe(x: number, y: number, x_offset?: number, y_offset?: number)
Teleports toward coordinates over time with optional offsets. Functions like Kami hack.
Parameters:
x_offset(optional) - X coordinate offsety_offset(optional) - Y coordinate offset
The following movement functions do not account for manual player movement.
core.input.is_moving() -> boolean
Returns true while a move_x / move_y request is still in progress (regular or force).
core.input.stop_moving()
Cancels the active move immediately — releases any held arrow key (regular) and clears the cutscene move (force).
Movement: regular vs. force
move_x / move_y take an optional force flag that decides how the player gets there:
force = false(default) — holds the arrow keys through the same input path a real player uses, so the character walks to the target with normal physics, animation, and movement packets. This is the only legitimate movement path — use it for navigation, kiting, patrolling, and repositioning while botting. Because it's just simulated input, it can be blocked by whatever the character is doing: if you're mid-attack or otherwise can't walk, the move may stall.force = true— drives the engine's cutscene move. It will move the character to the target even when regular movement would be blocked (e.g. while attacking), but it looks unnatural, so prefer regular movement and reach forforceonly when you need the move to go through no matter what.
Both also accept an optional timeout_ms (default 10000) that caps how long the move runs before giving up — so a blocked path can't hold a key down forever. force and timeout_ms may be passed in either order.
core.input.move_x(x_coord: number, force?: boolean, timeout_ms?: number)
Moves the player to the specified X coordinate. Defaults to natural movement (force = false); see Movement: regular vs. force.
core.input.move_y(y_coord: number, force?: boolean, timeout_ms?: number)
Moves the player to the specified Y coordinate. With force = false this climbs ladders/ropes via the Up/Down keys; see Movement: regular vs. force.
Example — patrol between two spots using legit movement:
local spots = { 250, -120 } -- X coordinates to walk between
local idx = 1
function on_tick()
-- Don't issue a new move while the current one is still running
if core.input.is_moving() then return end
-- Natural arrow-key movement (the legit path); give it 3s to arrive
core.input.move_x(spots[idx], false, 3000)
-- Advance to the next point for the next arrival
idx = (idx % #spots) + 1
endExample — walk toward the nearest mob, re-aiming as it moves:
function on_tick()
if core.input.is_moving() then return end
local player = core.object_manager.get_local_player()
local map = core.object_manager.get_current_map()
if not (player and player:is_valid() and map and map:is_valid()) then return end
-- Find the mob closest on the X axis
local px = player:get_position().x
local target, best = nil, math.huge
for _, mob in ipairs(map:get_mobs()) do
if mob and mob:is_valid() then
local pos = mob:get_position()
if pos and math.abs(pos.x - px) < best then
best, target = math.abs(pos.x - px), pos.x
end
end
end
-- Natural movement (force omitted) with a short timeout so we re-aim often
if target then core.input.move_x(target, 800) end
endExample — guaranteed reposition with force:
-- Regular movement can stall while attacking; force the move through
-- when it absolutely has to land (at the cost of looking unnatural)
core.input.move_x(target_x, true)