Lua scripting

amnesia has a built-in Lua 5.4 scripting layer. Scripts run inside the client with the same memory access the cheat has — read the game, draw anything, drive built-in features, react to events and run timers, all without a rebuild.

Scripts are trusted. A script has the client's privileges and can write memory. Only run scripts you trust. The sandbox blocks io/os/package/dofile/loadfile/native ffi and caps execution, but that isn't a security boundary.

Getting started

Scripts live in:

%APPDATA%\Amnesia\scripts\*.lua

Open the menu → Scripts tab:

Nothing runs until you start it. Stop unregisters everything the script registered (handlers, timers, UI).

notify("hello from lua")

events.on_render(function()
    draw.text(20, 20, "hello", { 255, 255, 255, 255 })
end)

Conventions

Globals

FunctionDescription
notify(text, [ms])Toast notification (default 2500 ms).
log(text)Print to the Scripts console.
w2s(x, y, z) → sx, sy | nilWorld → screen projection.

players

FunctionDescription
players.all()Iterator — for _, p in players.all() do … end.
players.list()Array of player tables (indexable, #).
players.get_local() / players.me()Your player table, or nil when not in a game.
players.count()Number of tracked players (excludes you).
You aren't in players.all() / list() / count() — use players.get_local() for your own data. Its is_dead stays correct even in games that delete your character on death.

Player fields

FieldType
addressinstance pointer
character, root_part, humanoidpointers
name / display_namestring
health / max_healthnumber
team / user_idinteger
visiblebool (not occluded)
is_local / is_deadbool
distancenumber (studs, from you)
position / head_position / velocityarray {x, y, z} (index 1–3)
toolstring — equipped Tool name ("" if none)
for _, p in players.all() do
    if not p.is_local and p.health > 0 and p.distance < 100 then
        esp.box(p, { 255, 80, 80, 255 }, 2)
    end
end

esp

FunctionDescription
esp.box(p, color, [thick])Draws a box around the player.
esp.name(p, color)Name tag above the player.
esp.health_bar(p)Vertical health bar left of the player.
esp.tracer(p, color, [thick])Line from your crosshair to the player.

draw

FunctionNotes
draw.line(x1, y1, x2, y2, color, [thick])
draw.rect(x, y, w, h, color, [thick], [filled])
draw.rect_filled(x, y, w, h, color)shortcut
draw.rect_rounded(x, y, w, h, radius, color, [filled], [thick])
draw.circle(x, y, r, color, [thick], [filled])
draw.circle_filled(x, y, r, color)shortcut
draw.text(x, y, text, color)
draw.text_size(text) → w, hmeasure text
draw.triangle(x1,y1,x2,y2,x3,y3, color, [thick], [filled])
draw.quad(x1,y1,x2,y2,x3,y3,x4,y4, color, [thick])
draw.polyline({ {x,y}, {x,y}, … }, color, [thick])up to 8 points
draw.line3d(x1,y1,z1, x2,y2,z2, color, [thick])world-space line
draw.text3d(x, y, z, text, color)world-space text

game

Info

FunctionDescription
game.place_id()Current PlaceId.
game.tick()GetTickCount() (ms).
game.time() → h, m, sLocal time.
game.fps()Render FPS.
game.locked()true once an experience is loaded.
game.mouse() → x, yCursor position.
game.screen() → w, hViewport size.
game.engaged()Player you're currently shooting (address), or 0.
game.aim_target()Aimbot's locked target (address), or 0.
game.camera()Camera instance (address).
game.view_matrix() → {16 floats}View matrix.
game.menu_open()true while the config menu is open.
game.look_at(x, y, z)Point the camera at a world position; returns true on success.

Movement

FunctionDescription
game.teleport(x, y, z)Teleport your character (position write).
game.move_to(x, y, z) / game.walk_to(x, y, z)Walk toward a point via Humanoid:MoveTo.
game.move_to_part(inst)Walk to an instance (Humanoid.MoveToPart).
game.set_move_dir(x, y, z)Drive the Humanoid walk direction (smooth).
game.set_velocity(vx, vy, vz)Write the root part's velocity.
game.jump()Make the character jump.
game.visible(ax,ay,az, bx,by,bz)true if there's a clear line between two world points.

Instances

FunctionDescription
game.workspace()The Workspace instance (address).
game.service(classOrName)A DataModel service by ClassName or Name, or 0.
game.children(inst)Array of direct children (addresses).
game.name(inst)Instance Name.
game.class(inst)Instance ClassName.
game.find_child(inst, name)First direct child with that Name, or nil.
game.find_class(inst, class)First direct child of that ClassName, or nil.
game.part_position(inst){ x, y, z } world position of a BasePart, or nil.
game.part_size(inst){ x, y, z } size of a BasePart, or nil.
game.material(inst)BasePart material enum value.
game.mesh(inst) / game.texture(inst)MeshPart MeshId / Texture asset string.
game.string_value(inst) / game.set_string(inst, text)Read / write a StringValue.
game.set_transparency(inst, v) / game.set_reflectance(inst, v)Write Transparency / Reflectance (0–1).
game.move_children(from, to) → countReparent every direct child of from into to.
game.team(player)Team address of a Player.
game.team_color(player)TeamColor value of a Player.
game.team_name(team)Name of a Team instance ("" if none).
Instance traversal. game.children walks one level at a time and find_child / find_class only look at direct children — recurse yourself (breadth-first, with a cap). Reading is one driver round-trip per call, so cache what you find and re-scan on a timer, not every frame.

Viewmodel helpers (game-specific)

FunctionDescription
game.swap_viewmodel(weapon, bundle) → codeRivals: move Bundles/<bundle> parts into the weapon template.
game.swap_bundle_into(target, bundle) → codeSame, into an explicit target instance.
game.set_texture(inst, id) / game.set_mesh(inst, id)Raw MeshPart asset write — experimental; the engine usually doesn't reload the asset.
game.set_color(inst, r, g, b)Raw packed colour write — experimental.
Viewmodel edits are client-side only and undone when the game rebuilds the model (respawn / re-equip). Re-apply after a rebuild. Prefer game.set_transparency / set_reflectance; writing Texture / MeshId does not reload and can crash the client.

memory

FunctionDescription
memory.read_int(addr) / read_float / read_uint / read_bool / read_doubletyped reads
memory.read_string(addr, [len])reads len bytes (default 64)
memory.read_bytes(addr, len) → { … }array of bytes
memory.write_int(addr, v) / write_float(addr, v)typed writes
memory.write_bytes(addr, { … })writes a byte array

input

FunctionDescription
input.key_down(vk)true while the key is held (read).
input.mouse_down(vk)true while the mouse button is held (read).
input.set(vk, down)hold / release a key (synthesized input).
input.press(vk)tap a key once.
input.hold(vk, [ms])hold a key for ms (default 200).
input.move_mouse(dx, dy)relative mouse move (to steer the camera).
Synthesized input goes to the focused window, so Roblox must be foreground while a script presses keys / moves the mouse. game.set_move_dir / game.set_velocity are memory and need no focus.

settings

Live read/write of built-in feature settings.

settings.get("aim.fov")              -- number
settings.set("silent.enabled", true) -- bool

Available keys

fflags

Read/write Roblox FastFlags by name — the same version-matched list the FFlag Editor uses.

FunctionDescription
fflags.get(name)Current int value, or nil if the flag isn't in the list.
fflags.set(name, value)Write an int; returns true on success.
fflags.has(name)true if the flag exists in the list.
local cap = fflags.get("DFIntTaskSchedulerTargetFps")
if cap then fflags.set("DFIntTaskSchedulerTargetFps", 60) end
The list is tied to the client version (version-5a21b87) and flags reset when Roblox restarts. Only int flags are exposed (bool flags are stored differently). Writing the wrong flag can break the client — test on a safe flag first.

events

FunctionCallback args
events.on_frame(fn)runs every tick
events.on_render(fn)runs every draw (queue draws here)
events.on_player_added(fn)(player)
events.on_player_removed(fn)(address)
events.on_damage(fn)(player, oldHealth, newHealth)
events.on_kill(fn)(player) — health hit 0, or a tracked player's character vanished while alive
Roblox gives no "who killed whom", so on_kill fires for any tracked death. Filter with game.engaged() / game.aim_target() to keep only your kills. Players that disconnect also show up as a disappearance.

timer

FunctionDescription
timer.after(ms, fn) → idrun once.
timer.every(ms, fn) → idrun repeatedly.
timer.cancel(id)cancel a timer.

ui

Registers controls rendered under the Scripts tab (persist for the session).

FunctionReturns
ui.checkbox(label, default)bool
ui.slider(label, default, min, max)number
ui.keybind(label, default)vk
ui.combo(label, defaultIndex, options)selected index (0-based)
ui.text(label)— (label/divider)
ui.get(label) / ui.set(label, value)read / write a control by label
ui.reserve(x, y, w, h)mark a screen rect as occupied so toasts avoid it
ui.checkbox("Show boxes", true)
ui.slider("Max distance", 150, 0, 500)

events.on_frame(function()
    if ui.get("Show boxes") then
        local maxd = ui.get("Max distance")
        for _, p in players.all() do
            if not p.is_local and p.distance < maxd then
                esp.box(p, { 255, 80, 80, 255 }, 2)
            end
        end
    end
end)

win & media

FunctionDescription
win.window_title(process)Title of a visible top-level window owned by process (e.g. "Spotify.exe"), or nil.
win.foreground_title()Title of the currently focused window.
media.now_playing()title, artist, album, playing, position, duration from the OS media session, or nil.
media.play_pause() / next() / previous()Control the OS media session.

misc

FunctionDescription
clipboard.get() / clipboard.set(text)Read / write clipboard text.
sound.play(path)Play a .wav file asynchronously.
http.get(url) → body | nilHTTPS GET.
http.post(url, body) → body | nilHTTPS POST.

Examples

Simple ESP

events.on_render(function()
    for _, p in players.all() do
        if p.is_local or p.health <= 0 then goto continue end
        local sx, sy = w2s(p.head_position[1], p.head_position[2], p.head_position[3])
        if sx then
            draw.text(sx - 20, sy - 24, p.name, { 255, 255, 255, 255 })
        end
        ::continue::
    end
end)

Kill webhook

local hook = "https://discord.com/api/webhooks/…"

events.on_kill(function(p)
    http.post(hook, '{"content":"killed ' .. p.name .. '"}')
end)

Now-playing widget

events.on_render(function()
    local title, artist, album, playing = media.now_playing()
    if title then
        draw.text(20, 80, title .. " — " .. artist, { 220, 220, 235, 255 })
    end
end)

Limitations