Lua API Reference

2LT exposes a Lua 5.4 scripting environment with direct access to world state, overlay drawing, input injection, the Pathing system, and full object/enemy queries.

Lua 5.4 stdlib base · math · string · table screen coords normalized 0–1 color format ARGB hex

Runtime

Available standard libraries

base, math, string, table. There is no io, os, coroutine, or package.

Color format

All color values are ARGB hex integers. 0xFFFFA040 is fully opaque orange — alpha occupies the high byte.

Screen coordinates

All screen positions are normalized game-window percentages. 0, 0 is top-left; 1, 1 is bottom-right of the game window.

Stop behavior

sleep() and every blocking wait throw when the user clicks Stop. Do not catch this — use on_stop for cleanup instead.

Return convention

Most functions return ok, error. Check ok first; error is a string describing the failure when ok is false.

Node indices

All path node indices are 1-based, matching standard Lua table conventions.

Utility

Status, waits, retry, cleanup, and key state.
Logging, status, and cleanup
Report progress and register cleanup handlers that run when the script ends.
log(msg)
script_status(msg)
get_time()           -- ms since script start
script_should_stop() -- bool
on_stop(fn)          -- ok, error
script_status sets the live status line on the Scripts page. on_stop registers a callback that fires on any exit reason. The callback receives reason and an optional err string. Reason values: "complete", "error", "stop", "script_replacement". Multiple callbacks are stacked and run in reverse registration order.
Example
on_stop(function(reason, err)
    overlay_clear("debug")
    send_mouse_up(1)
    if reason == "error" then log(err) end
end)

script_status("Waiting for launch")
log("elapsed " .. get_time() .. "ms")
Waits and retries
Block safely while still allowing Stop to interrupt the script.
sleep(ms)
wait_until(predicate, timeout_ms?, interval_ms?) -- ok, error
retry(count, delay_ms, action)                  -- ok, error, attempts
wait_until polls a predicate every interval_ms (default 25, clamp 5–1000) until truthy. retry calls an action up to count times with delay_ms between attempts; the action receives the attempt number and should return a truthy first value on success plus an optional error string on failure. Count is clamped 1–1000, delay 0–120000 ms.
Example
local ok, err = wait_until(function()
    return activity_get_state() == "ACTIVE"
end, 5000)

local ok, err, attempts = retry(3, 500, function(attempt)
    script_status("Attempt " .. attempt)
    return activity_get_state() == "ACTIVE", "not active yet"
end)
Key state
Read Windows virtual-key state from Lua.
is_key_down(vk_code)    -- bool
is_key_pressed(vk_code) -- bool, fires once per down transition
is_key_pressed fires once per down transition, useful for toggles. Use Windows virtual-key codes — F1–F12 are 0x700x7B.
Example
if is_key_pressed(0x70) then  -- F1
    log("toggled")
end

Activity

Activity reads, waits, ID writes, and the launch flow.
Activity state reads
Read destination name, state string, and full metadata.
activity_get_name(timeout_ms?)   -- name, error
activity_get_state(timeout_ms?)  -- state, error
activity_get_info()              -- table
Activity state values: "ACTIVE", "setup:orbit", "cleanup", "NONE". activity_get_state is the simplest check for orbit, loading, and active-world transitions.
Example
local state, err = activity_get_state(1000)
if state then log("state: " .. state) end
Wait for activity state
Block until one or more specific states are reached.
activity_wait_state(state_or_states, timeout_ms?) -- ok, state_or_error
Pass a string for a single accepted state or a table of strings to accept any of several. Stop interrupts the wait. On timeout the second return value is an error string.
Example
local ok, state = activity_wait_state({ "ACTIVE", "setup:orbit" }, 15000)
if not ok then log(state) end
Set or launch activity
Write an activity ID or run the calibrated orbit launch flow.
activity_set_id(id, timeout_ms?)  -- ok, error
activity_launch(id, timeout_ms?)  -- ok, error
activity_launch sets the activity ID then runs the calibrated orbit launch click sequence: opens the Director, selects the Tower tab, clicks the Courtyard LZ, sets the ID, and clicks Launch. Retries up to three times. Calibrate the launch positions on the Scripts tab first.
Example
local ok, err = activity_launch(123456, 30000)
if not ok then error(err) end

Features

Temporary feature setup and scriptable values.
Feature toggles
Enable and disable named 2LT features from a script.
feature_enable(name, timeout_ms?)             -- ok, error
feature_disable(name, timeout_ms?)            -- ok, error
feature_enable_for_script(name, timeout_ms?)  -- ok, error
feature_enable_for_script queues an automatic disable when the script exits or is stopped, so no cleanup code is needed in on_stop.
Example
local ok, err = feature_enable_for_script("pve_esp")
if not ok then error(err) end
Feature values
Read or write scriptable feature values.
feature_get_value(name, timeout_ms?)                   -- value, error
feature_get_value_info(name, timeout_ms?)              -- info, error
feature_set_value(name, value, timeout_ms?)            -- ok, error
feature_set_value_for_script(name, value, timeout_ms?) -- ok, error
feature_set_value_for_script captures the original value and restores it automatically during script cleanup. Prefer this when the change is meant to be temporary.
Example
local old = feature_get_value("aim_fov")
feature_set_value_for_script("aim_fov", 10.0)

Player

World reads, writes, and movement helpers.
World reads
Read player position, velocity, view angles, and direction vectors.
player_loaded()       -- bool
get_position()        -- x, y, z
get_velocity()        -- x, y, z
get_view_angles()     -- yaw, pitch
get_forward_vector()  -- x, y, z
get_right_vector()    -- x, y, z
get_up_vector()       -- x, y, z (always 0, 0, 1)
distance_to(x, y, z)  -- meters
Forward and right vectors are camera-relative and flattened onto the X/Y plane. Check player_loaded() before reads during long loading screens.
Example
local x, y, z = get_position()
log(string.format("pos %.1f %.1f %.1f  dist %.1f",
    x, y, z, distance_to(0, 0, 0)))
World writes
Write player position, velocity, or view angles directly.
set_position(x, y, z)
set_position_x(v), set_position_y(v), set_position_z(v)
set_velocity(x, y, z)
set_velocity_x(v), set_velocity_y(v), set_velocity_z(v)
set_view_angles(yaw, pitch)
set_position also writes a mini velocity of 0, 0, 0.1 alongside the position. This nudge is necessary because Destiny's physics engine only picks up the new position when velocity changes — without it the engine overwrites your write and the player doesn't actually move. Use go_to or path_run when you need blocking movement with arrival detection.
Example
local x, y, z = get_position()
set_position(x, y, z + 1.5)
set_velocity(0, 0, 0)
Movement helpers
Move relative to the camera or travel to a world point with arrival detection.
move_forward(distance, speed?)  -- blocking
move_right(distance, speed?)
move_up(distance, speed?)
go_to(x, y, z, options?)       -- ok, error
Relative helpers use velocity writes and block until the distance is covered or movement stalls for 3 s. Default speed is 10 m/s. go_to accepts { mode, speed, radius, timeout_ms }. Movement mode is "velocity" or "teleport" ("tp" is also accepted).
Example
local x, y, z = get_position()
local ok, err = go_to(x + 3, y, z, {
    mode = "velocity",
    speed = 8,
    radius = 0.5,
    timeout_ms = 5000
})

Input

Coordinates, calibrated clicks, action input, keys, and mouse.
Cursor and coordinate conversion
Read cursor/window state and convert between desktop pixels, screen percent, and world space.
cursor_get_position()                   -- x, y (desktop pixels)
window_get_size()                       -- width, height (pixels)
screen_to_desktop(x_pct, y_pct)        -- x, y
desktop_to_screen(x, y)                -- x_pct, y_pct
world_to_screen(x, y, z)               -- ok, x_pct, y_pct
cursor_set_position(x, y, timeout_ms?)  -- ok, error
world_to_screen returns false as its first value when the point is behind the camera. All returned percentages are in the 0–1 range.
Example
local x, y, z = get_position()
local ok, sx, sy = world_to_screen(x, y, z + 1)
if ok then
    draw_screen_marker(sx, sy, { color = 0xFF00FF00 })
end
Percent-based screen input
Move, click, or drag using normalized game-window coordinates.
move_cursor_screen(x_pct, y_pct, options?)              -- ok, error
click_screen(x_pct, y_pct, options?)                    -- ok, error
drag_screen(x1_pct, y1_pct, x2_pct, y2_pct, options?)  -- ok, error
input_do(action_name, timeout_ms?)                      -- ok, error
Use percent-based input in shared scripts so users need no raw desktop pixel coordinates. Options include timeout_ms, button, hold_ms, and settle_ms. input_do resolves and fires a Destiny action binding by name.
Example
click_screen(0.88, 0.91, { timeout_ms = 12000 })
drag_screen(0.30, 0.40, 0.70, 0.40, { button = 1, hold_ms = 100 })
Destiny action input
Use the player's actual Destiny keybinds instead of hard-coded virtual keys.
send_input(action_name, hold_ms?)  -- ok, error
send_input_down(action_name)       -- ok, error
send_input_up(action_name)         -- ok, error
input_get_binding(action_name)     -- primary, secondary, error
input_reload_bindings()           -- ok, error
Action names come from Destiny's cvars.xml. Common examples: "fire", "reload", "interact", "move_forward", "ui_open_director". send_input_down actions are auto-released when the script stops; raw send_mouse_down / send_key_down calls must be paired with a release in on_stop.
Example
send_input("interact")

send_input_down("fire")
sleep(500)
send_input_up("fire")
Low-level key and mouse input
Inject raw virtual-key or mouse button events directly.
send_key_down(vk),   send_key_up(vk)
send_key(vk, hold_ms?)                  -- blocking, interruptible
send_mouse_down(btn), send_mouse_up(btn)
send_click(btn, hold_ms?)               -- blocking, interruptible
Pair every raw down with an up. Mouse button 1 = left click, 2 = right click. send_key and send_click block for the hold duration and are interrupted by Stop. Prefer send_input for Destiny-bound actions so scripts work regardless of the player's keybinds.
Example
send_key(0x45)  -- E key

send_mouse_down(1)
on_stop(function() send_mouse_up(1) end)

Overlay

Screen/world drawing, persistent handles, markers, and cleanup.
Temporary overlay draws
Draw short-lived world or screen visuals for a fixed duration.
draw_world_text(text, x, y, z, options?)
draw_world_line(x1, y1, z1, x2, y2, z2, options?)
draw_world_cube(x, y, z, options?)
draw_world_marker(x, y, z, options?)
draw_screen_text(text, x_pct, y_pct, options?)
draw_screen_line(x1_pct, y1_pct, x2_pct, y2_pct, options?)
draw_screen_rect(x_pct, y_pct, w_pct, h_pct, options?)
draw_screen_marker(x_pct, y_pct, options?)
Common options: duration_ms, color, fill_color, thickness, size, shape, label, align, filled, layer. Colors are ARGB hex. Screen coordinates are normalized 0–1.
Example
draw_world_marker(x, y, z, {
    label = "target",
    shape = "cube",
    duration_ms = 2000,
    color = 0xFFFFA040
})
draw_screen_text("status", 0.5, 0.08, { align = "center" })
Persistent overlay handles
Create overlays once and update them cheaply each tick.
overlay_create_world_text(text, x, y, z, options?)          -- handle
overlay_update_world_text(handle, text, x, y, z, options?)
overlay_create_world_line(x1, y1, z1, x2, y2, z2, options?) -- handle
overlay_update_world_line(handle, x1, y1, z1, x2, y2, z2, options?)
overlay_create_world_cube(x, y, z, options?)                -- handle
overlay_update_world_cube(handle, x, y, z, options?)
overlay_create_screen_text(text, x_pct, y_pct, options?)    -- handle
overlay_update_screen_text(handle, text, x_pct, y_pct, options?)
overlay_create_screen_line(x1_pct, y1_pct, x2_pct, y2_pct, options?) -- handle
overlay_update_screen_line(handle, x1_pct, y1_pct, x2_pct, y2_pct, options?)
overlay_create_screen_rect(x_pct, y_pct, w_pct, h_pct, options?) -- handle
overlay_update_screen_rect(handle, x_pct, y_pct, w_pct, h_pct, options?)
Persistent handles ignore duration_ms and stay visible until overlay_remove, overlay_clear, or script cleanup. Omitting options on an update call preserves the current style.
Example
local h = overlay_create_screen_text("running", 0.5, 0.08, {
    layer = "hud",
    align = "center"
})
overlay_update_screen_text(h, "step 2", 0.5, 0.08)
overlay_remove(h)
Markers and cleanup
Persistent labeled world/screen points and safe overlay cleanup.
marker_create_world(x, y, z, options?)          -- handle
marker_update_world(handle, x, y, z, options?)  -- ok
marker_create_screen(x_pct, y_pct, options?)    -- handle
marker_update_screen(handle, x_pct, y_pct, options?) -- ok
marker_remove(handle)     -- ok
overlay_remove(handle)    -- ok, works on any persistent handle
overlay_clear(layer?)     -- count removed
overlay_remove works on any persistent handle including marker handles. overlay_clear with no argument clears all script-owned overlays. Register an on_stop callback to ensure overlays are removed when the script ends.
Example
local layer = "route"
local h = marker_create_world(x, y, z, { layer = layer, label = "start" })
on_stop(function() overlay_clear(layer) end)

Objects

SObjects, enemies, aiming, aim locks, and velocity locks.

Every entity in the game is an SObject — light sources, bullet holes, enemy weapons, projectiles, players, enemies, vehicles, and more. Each type is identified by a type_id and subtype_id.

To find the IDs for a specific entity, use the SObject Inspector tool in the ESP tab of the 2LT menu.

SObject queries
Find world objects by type, subtype, state, distance, and anchor point.
find_closest_sobject(type_id, max_distance?, include_state_ff?)
    -- found, x, y, z, distance, state_flag, subtype_id

find_closest_sobject_subtype(type_id, subtype_id, max_distance?, include_state_ff?)
    -- found, x, y, z, distance, state_flag, subtype_id

find_closest_sobject_filtered(filter)
    -- found, x, y, z, distance, state_flag, subtype_id

find_closest_sobject_near(type_id, x, y, z, radius, include_state_ff?, min_player_distance?)
    -- found, x, y, z, distance, state_flag, subtype_id

find_closest_sobject_near_subtype(type_id, subtype_id, x, y, z, radius, ...)
    -- found, x, y, z, distance, state_flag, subtype_id

wait_sobject(filter, timeout_ms?)
    -- object_table, error
SObjects are identified by type_id and subtype_id, each in range 0–255. wait_sobject blocks and returns a table with x, y, z, distance, type_id, subtype_id, state_flag, and address.

The state_flag (FF flag) is an in-motion flag — 2LT uses it to differentiate live enemies from dead bodies. A non-zero value means the entity is in motion; zero means it is still. By default queries only return entities with a zero state flag. Pass include_state_ff = true to include entities regardless of their motion state.
Filter table keys
{
    type_id = 14,
    subtype_id = 3,               -- optional
    radius = 20,
    near = { x=0, y=0, z=0 },   -- anchor; or x/y/z as root keys
    include_state_ff = true,
    min_player_distance = 0
}
Example
local obj, err = wait_sobject({
    type_id = 14,
    subtype_id = 3,
    radius = 20,
    include_state_ff = true
}, 2500)
if obj then
    draw_world_marker(obj.x, obj.y, obj.z, { label = "found" })
end
Object watches
Detect newly spawned or type-changed objects after an action.
watch_sobjects_near(type_id, x, y, z, radius, include_state_ff?, min_player_distance?)
    -- watch_id
watch_sobjects_near_subtype(type_id, subtype_id, x, y, z, radius, ...)
    -- watch_id
watch_sobjects_near_filtered(filter)
    -- watch_id
find_new_sobject(watch_id, timeout_ms?)
    -- found, x, y, z, distance, state_flag, subtype_id

watch_sobject_transition_filtered(filter)
    -- watch_id
find_changed_sobject(watch_id, timeout_ms?)
    -- found, x, y, z, distance, state_flag, subtype_id
Call a watch function before your action to snapshot current matching objects, then call find_new_sobject to detect objects that appeared after the snapshot. Use transition watches when an existing address changes type or subtype rather than a brand-new address spawning.
Example
local ex, ey, ez = get_position()
local watch = watch_sobjects_near_subtype(14, 3, ex, ey, ez, 20, true)
send_input("interact")
local found, x, y, z = find_new_sobject(watch, 2500)
if found then draw_world_marker(x, y, z) end
Enemies and aiming
Find resolved PvE enemies and aim at world positions or bone targets.
find_closest_enemy(entity_id, max_distance?)          -- found, x, y, z, distance
wait_enemy_gone(entity_id, max_distance?, timeout_ms?) -- ok, error
aim_at(x, y, z)                                       -- bool
aim_at_closest_enemy(entity_id, max_distance?)        -- bool
Enemy entity IDs are 16-bit values (0–65535) shown by PvE ESP after resolving an enemy object. aim_at_closest_enemy resolves to the head bone position when available and falls back to the entity root. aim_at writes camera angles for any arbitrary world coordinate.
Example
if aim_at_closest_enemy(18041, 150) then
    send_click(1, 50)
end
wait_enemy_gone(18041, 150, 10000)
Background locks
Keep aim or velocity writes running continuously while Lua waits or does other work.
-- Aim lock
aim_lock_start(entity_id, max_distance?, interval_ms?) -- ok, error
aim_lock_stop()                                        -- true
aim_lock_running()                                     -- bool
aim_lock_has_target()                                  -- bool
aim_lock_get_target()                                  -- found, x, y, z, distance

-- Velocity lock
velocity_lock_start(x, y, z, interval_ms?) -- ok, error
velocity_lock_set(x, y, z)                 -- ok, error (lock must be running)
velocity_lock_stop(stop_velocity?)         -- true
velocity_lock_running()                    -- bool
velocity_lock_get()                        -- running, x, y, z
Both locks spin a background worker thread at the given interval (default 16 ms, clamp 5–250 ms). They stop automatically when the script exits or is stopped. Avoid using velocity_lock and path_run simultaneously unless intentional.
Example
local ok, err = aim_lock_start(18041, 150)
if not ok then error(err) end
send_input_down("fire")
wait_enemy_gone(18041, 150, 8000)
send_input_up("fire")
aim_lock_stop()

Pathing

Routes, node actions, path settings, and path info.
Task-based path run
Build a route, attach optional node actions, and run it as a blocking script task.
path_clear()
path_add_node(x, y, z)
path_on_node(index, fn)  -- ok, error; fn receives node index
path_clear_actions()
path_run(options?)       -- ok, error
Node action callbacks receive the node index. They can sleep, wait, draw overlays, or inject input. path_run stops and returns an error string prefixed with the node index if an action fails. Options: mode, speed, radius, timeout_ms, start_index, end_index.
Example
path_clear()
path_add_node(100, 200, 50)
path_add_node(110, 210, 50)
path_on_node(1, function()
    send_input("interact")
    sleep(1000)
end)
local ok, err = path_run({ mode = "velocity", speed = 10 })
Path settings
Configure how routes move and how arrival is detected.
path_set_move_mode(mode)           -- ok, error
path_get_move_mode()               -- mode string
path_set_speed(speed)
path_set_arrival_radius(radius)
path_set_loop(mode)
path_set_node_delay(index, seconds)
path_go_to_node(index, options?)   -- ok, error
Move mode accepts "velocity", "teleport", or "tp". Loop mode accepts "off", "loop", or "pingpong". All node indices are 1-based.
Example
path_set_move_mode("velocity")
path_set_speed(12)
path_set_arrival_radius(0.5)
path_set_loop("off")
Compatibility runner and callbacks
Use the original queued/event-driven path runner for existing route scripts.
path_start()
path_start_from(index)
path_stop()
path_wait_complete(timeout_ms)
on_node_arrive(fn)  -- fn receives node index
Prefer path_run for new scripts. The compatibility runner is useful for existing route scripts and for paths started from the Pathing UI. on_node_arrive sets a single global arrival callback.
Example
on_node_arrive(function(index)
    log("arrived at node " .. index)
end)
path_start_from(2)
path_wait_complete(0)  -- 0 = no timeout
Path persistence and info
Save/load routes and inspect node data at runtime.
path_save(name)                -- bool
path_load(name)                -- bool
path_get_node_count()          -- count
path_get_current_target()      -- index (1-based)
path_get_node_position(index)  -- x, y, z
path_get_node_label(index)     -- label string
path_get_node_delay(index)     -- seconds
path_is_running()              -- bool
path_set_draw_cubes(bool)
path_set_draw_lines(bool)
Paths are saved to %APPDATA%\2LT\paths\<name>.2ltp. Use the Pathing page's Copy as Lua button to export the current route as a ready-to-run path_run script.
Example
path_load("myroute")
path_set_draw_cubes(true)
path_set_draw_lines(true)
log("nodes: " .. path_get_node_count())
local ok, err = path_run()