Violet

Input

On this page

Provides interaction with the game world through skill casting, item usage, movement, and more.

Anti-Cheat Awareness

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.

No Auto-Teleport

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. Sends the key through the game's keyboard handler (the same path a real key-down takes) and briefly pulses it on the GetAsyncKeyState hook, so polling-based input — movement, the held-Up check behind up jumps — sees it too. Requires a valid local player object.

This is the preferred way to send a key press — prefer it over call_wndproc, which calls into the game directly and carries a crash risk. Reach for call_wndproc only when you need something press_key can't send.

The pulse is a quick tap (a single key-down), so a movement key only nudges the character before releasing. To walk, climb, or hold Up for an up jump, hold the key with hold_key / release_key — or use move_x / move_y for coordinate-based movement. See Movement.

Key codes: Virtual Key Codes Reference


core.input.hold_key(key_code: number)

Sends a key-down and keeps it held — both through the game's keyboard handler and on the GetAsyncKeyState hook — until you call release_key for the same key. Because it feeds GetAsyncKeyState, this is the primitive that can actually drive movement: hold an arrow key to walk or climb, or hold Up before a jump to up jump. Use it to hold movement or skill keys for a controlled duration instead of tapping. Requires a valid local player object.


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:

lua
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
end

core.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.

Prefer `press_key` for key presses

For simple key presses (skills, pot hotkeys, a plain jump), prefer press_key — it's safer, doesn't require building Win32 messages, and avoids the crash risk below. Reach for call_wndproc only when you need a message press_key can't send.

Not for movement

Use this for non-movement input — skills, potion/item hotkeys, a plain jump, and similar one-shot keys. It cannot drive directional movement: walking and ladder/rope climbing are read via GetAsyncKeyState, which call_wndproc bypasses — use move_x / move_y. Up jumps also fail: a plain jump works, but the held-Up check behind an up jump is read via GetAsyncKeyState, so it sees Up as released. See Movement.

Crash Risk

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:

lua
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_downtrue 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:

lua
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

Prefer `move_x` / `move_y` for navigation

The game reads directional movement — walking left/right and climbing ladders/ropes up/down — through GetAsyncKeyState. move_x / move_y hold the arrow keys through that path for you and stop at a target coordinate, so they're the right tool for navigation, kiting, and patrolling.

For manual control, hold_key / release_key feed the same GetAsyncKeyState path — hold an arrow to walk or climb, or hold Up before Jump to up jump, then release when done. press_key only pulses the key briefly, so it nudges rather than sustains.

call_wndproc is the exception: it bypasses GetAsyncKeyState entirely, so it cannot drive movement and an up jump through it falls back to a plain jump. Use it only for non-movement, one-shot input.

core.input.teleport(x: number, y: number)

Instantly teleports to coordinates.

Anti-Cheat Risk

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 offset
  • y_offset (optional) - Y coordinate offset

Info

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).


core.input.set_facing(left: boolean)

Turns the character to face a direction in place, without moving — pass true to face left, false to face right. Resolves the move action the same way a quick direction-key tap does, so the new facing is sent to the server and seen by other players. Read the current facing back with player:is_left().

Only the facing flips; the character does not take a step. Most reliable while standing on a foothold — the same condition under which the game itself flips standing facing.

Example — face the nearest mob without walking into it:

lua
function on_tick()
    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

    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 mx = mob:get_position().x
            if math.abs(mx - px) < best then best, target = math.abs(mx - px), mx end
        end
    end

    if target then core.input.set_facing(target < px) end
end

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 for force only 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:

lua
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
end

Example — walk toward the nearest mob, re-aiming as it moves:

lua
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
end

Example — guaranteed reposition with force:

lua
-- 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)