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:
- Editor — filename box + Lua code editor (line numbers, syntax highlighting, monospace) with Run/Restart, Stop, Save, New.
- Scripts — Run All / Stop All, and per-file Run / Stop / Edit. Running scripts show a green
*.
- Script UI — controls registered with
ui.* appear here.
- Console —
log() output and errors.
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
- Colours are
{ r, g, b, a } with each channel 0–255; a is opacity.
- Coordinates for
draw.* are screen pixels. draw.line3d / draw.text3d take world coords.
- w2s returns screen
x, y or nil if the point is behind the camera.
- VK codes — use the
keys table (keys.SPACE, keys.E, keys.MOUSE1, …) or a raw number.
- Optional arguments can be omitted to use defaults.
Globals
| Function | Description |
notify(text, [ms]) | Toast notification (default 2500 ms). |
log(text) | Print to the Scripts console. |
w2s(x, y, z) → sx, sy | nil | World → screen projection. |
players
| Function | Description |
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
| Field | Type |
address | instance pointer |
character, root_part, humanoid | pointers |
name / display_name | string |
health / max_health | number |
team / user_id | integer |
visible | bool (not occluded) |
is_local / is_dead | bool |
distance | number (studs, from you) |
position / head_position / velocity | array {x, y, z} (index 1–3) |
tool | string — 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
| Function | Description |
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
| Function | Notes |
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, h | measure 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
| Function | Description |
game.place_id() | Current PlaceId. |
game.tick() | GetTickCount() (ms). |
game.time() → h, m, s | Local time. |
game.fps() | Render FPS. |
game.locked() | true once an experience is loaded. |
game.mouse() → x, y | Cursor position. |
game.screen() → w, h | Viewport 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
| Function | Description |
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
| Function | Description |
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) → count | Reparent 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)
| Function | Description |
game.swap_viewmodel(weapon, bundle) → code | Rivals: move Bundles/<bundle> parts into the weapon template. |
game.swap_bundle_into(target, bundle) → code | Same, 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
| Function | Description |
memory.read_int(addr) / read_float / read_uint / read_bool / read_double | typed 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 |
| Function | Description |
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
aim. — enabled, fov, smooth, wallCheck, showFov, prediction, projectileSpeed
silent. — enabled, fov, holdMs, wallCheck, spray, maxAngle, seamless
trigger. — enabled, delayMs, wallCheck
movement. — walkSpeed, walkSpeedVal, jumpPower, jumpPowerVal, autoBhop, fly, flySpeed, noclip
esp. — enabled, skeleton, names, distance, health, boxBg, wallCheck, teamCheck, ignoreDead, thickness, targetInfo, radar
world. — removeBlur, removeBloom, removeDof, removeSunRays, removeAtmosphere, exposureEnabled, exposure, fovEnabled, fov, fogEnabled, fogStart, fogEnd
fflags
Read/write Roblox FastFlags by name — the same version-matched list the FFlag Editor uses.
| Function | Description |
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
| Function | Callback 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
| Function | Description |
timer.after(ms, fn) → id | run once. |
timer.every(ms, fn) → id | run repeatedly. |
timer.cancel(id) | cancel a timer. |
ui
Registers controls rendered under the Scripts tab (persist for the session).
| Function | Returns |
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)
| Function | Description |
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
| Function | Description |
clipboard.get() / clipboard.set(text) | Read / write clipboard text. |
sound.play(path) | Play a .wav file asynchronously. |
http.get(url) → body | nil | HTTPS GET. |
http.post(url, body) → body | nil | HTTPS 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
- Scripts run on the overlay thread only (inside their callbacks).
- Input synthesis is available (
input.set / press / hold / move_mouse) but targets the focused window, so Roblox must be foreground; game.set_move_dir / set_velocity are memory-driven and need no focus.
- No
io / os / package / dofile / loadfile / native ffi. A 2M-instruction cap per call — the budget resets for every callback, so long-running scripts don't accumulate into it.
- Object writes are available (
game.set_*, game.move_children), but the engine only refreshes some properties; raw writes can crash. Prefer editing templates then re-equipping.
- Game-specific scripts are fragile — instance names/paths change between game updates.
- Config values set via
settings.* follow normal config save/load; script-local variables don't persist across sessions.