Lua · Useful information
Write scripts with AI
AI writes good scripts when it knows the exact API. Copy the prompt below, add what you want at the end, and paste it into any AI chat.
How to use it
- Press Copy on the prompt.
- Paste it into the AI and replace the last line with your idea, for example "a panel that shows how many enemies are alive, red when only one is left".
- Copy the AI's answer, then press From Clipboard on the Lua page. Code fences are removed for you.
- If it stops with an error, press Copy in the error box and paste it back to the AI: "fix this error".
The prompt is built from these docs, so it always matches the current API. AI can still invent functions that do not exist; the error message will say so (
attempt to call a nil value).The prompt
prompt.txt
You write Lua 5.4 scripts for the Adolfjon program (a CS2 overlay). Use ONLY the API below. Answer with one complete .lua file in a single code block.
RULES
- Start the file with a header: -- name:, -- author:, -- version:, -- description:, and -- permissions: (settings, chat, network) only if needed.
- Sandbox: no os, io, package, debug, require, dofile, loadfile, string.dump. load() takes text only.
- Every callback must finish in under 8 ms. No endless loops. Cache work, build colours once at the top.
- Drawing works only inside on_frame(). menu.* works only inside on_menu().
- Colours are integers from color(r,g,b[,a]) with 0-255 channels. Vectors are tables {x=,y=,z=}.
- Many values can be nil (local player when dead, optional entity fields). Always check before use.
- Game memory is read-only. There is no way to write memory, send input or call game functions; do not invent any.
- Script settings: create with ui.* at the top level, read with ui.get(id).
CALLBACKS (define as global functions)
on_frame() -- Every overlay frame. The only place drawing works.
on_tick() -- 64 times a second, at most 4 times per frame. Good for logic that should not depend on FPS.
on_menu() -- Every frame while the script's tab is open. menu.* widgets work only here.
on_unload() -- Right before the script is turned off, reloaded or deleted.
on_notepad_save(id, text) -- The user pressed Save in a notepad this script opened.
on_kill(e) -- You killed someone.
on_death(e) -- You died.
on_round_start() -- A new round began.
on_round_end(e) -- The round ended.
EVENTS (events.on(name, function(e) end))
kill {victim:string, weapon:string, headshot:boolean, knife:boolean, round_kills:integer}
death {}
round_start {}
round_end {won:boolean, winner:integer}
player_hurt {slot:integer, name:string, damage:integer, health:integer, enemy:boolean, is_local:boolean}
player_death {slot:integer, name:string, damage:integer, health:integer, enemy:boolean, is_local:boolean}
bomb_planted {site:string}
bomb_defused {}
bomb_exploded {}
spectator_join {name:string}
spectator_leave {name:string}
shot_fired {slot:integer, name:string, enemy:boolean, is_local:boolean, shots:integer, weapon:string}
flashed {slot:integer, name:string, enemy:boolean, is_local:boolean, duration:number}
spotted {slot:integer, name:string, enemy:boolean}
unspotted {slot:integer, name:string, enemy:boolean}
bomb_defusing {slot:integer, name:string, enemy:boolean}
purchase {slot:integer, name:string, enemy:boolean, is_local:boolean, spent:integer, money:integer}
sound {type:string, slot:integer, enemy:boolean, pos:vector}
footstep {slot:integer, enemy:boolean, pos:vector}
weapon_switch {from:string, to:string}
jump {speed:number}
land {speed:number}
scope {weapon:string}
unscope {weapon:string}
reload {weapon:string}
reload_end {weapon:string}
zone_enter {id:integer, name:string}
zone_leave {id:integer, name:string}
API
color(r, g, b[, a]) -> integer The packed colour -- Packs four channels into one colour. Values outside 0 to 255 are clamped.
color.rgba(r, g, b[, a]) -> integer The packed colour -- Same as color(r, g, b, a).
color.unpack(c) -> integer r, integer g, integer b, integer a -- Splits a colour back into its channels.
color.alpha(c, a) -> integer The new colour -- Returns the same colour with a new alpha. The best way to fade something in or out.
color.lerp(a, b, t) -> integer The mixed colour -- Mixes two colours. t = 0 gives a, t = 1 gives b. Keep t between 0 and 1.
color.hsv(h, s, v[, a]) -> integer The packed colour -- Makes a colour from hue, saturation and value. Handy for rainbow effects.
print(...) -- Writes all arguments to the console, separated by two spaces. Tables print as their address; use json.encode to see what is inside.
util.print(...) -- Same as print.
util.time() -> number Seconds -- Seconds since the program started, with decimals. Use it for timers and animation.
util.random(a, b) -> integer The random number -- A random whole number from a to b, both included. The order does not matter.
input.key_down(vk) -> boolean true while held -- True while the key is held.
input.key_pressed(vk) -> boolean true on the first frame of a press -- True only on the frame the key went down. Use it for toggles.
input.mouse() -> number x, number y -- The Windows cursor position in screen pixels.
input.mouse_down([button]) -> boolean true while held -- True while a mouse button is held.
game.local_player() -> player | nil See the player table below -- You, as a player table. nil when you are dead or not in a match.
game.players() -> player[] An array of player tables -- All living players, you included.
game.map() -> string Map name -- The map name, for example de_mirage. Empty until it is known.
game.mode() -> string Mode name -- The game mode: Competitive, Premier, Casual, Wingman, Deathmatch, Arms Race, or Community - … on community servers. Empty until known.
game.in_match() -> boolean -- True while you are in a match (not in the main menu or loading).
game.time() -> number Seconds -- Seconds since the current round started. 0 until the first round start is seen.
game.round() -> integer Round -- The current round number, starting at 1.
Player table: slot:integer, name:string, team:integer, health:integer, alive:boolean, enemy:boolean, is_local:boolean, weapon:string, pos:vector, head:vector, armor:integer, money:integer, steamid:string
entity.players() -> player[] -- All living players with full detail.
entity.get(slot) -> player | nil -- One player by slot, or nil.
entity.local_player() -> player | nil -- You with full detail, plus view_angles. nil when dead.
entity.grenades() -> grenade[] See [nade](/docs/lua/nade) -- Grenades in the world and fires on the ground. Same as nade.list().
entity.c4() -> bomb See [bomb](/docs/lua/bomb) -- The bomb. Same as bomb.state().
entity.weapons() -> table[] -- Weapons and items lying on the ground.
entity.hostages() -> table[] -- Hostages on hostage maps.
Extra player fields: scoped:boolean, defusing:boolean, shots_fired:integer, flash:number, velocity:vector, on_ground:boolean, eye_angles:table, visible:boolean, bones:table, view_angles:table, place:string, in_buy_zone:boolean, walking:boolean, ping:integer, score:integer, mvps:integer, has_defuser:boolean, has_helmet:boolean, clan:string
globals.time() -> number -- Seconds since the program started. Same as util.time().
globals.frametime() -> number -- Seconds the last frame took. Multiply speeds by it so animation is the same at any FPS.
globals.frame() -> integer -- A frame counter that goes up by one every frame.
globals.map() -> string -- Map name. Same as game.map().
globals.round() -> integer -- Round number. Same as game.round().
globals.round_time() -> number -- Seconds since the round started. Same as game.time().
globals.in_match() -> boolean -- True in a match.
globals.menu_open() -> boolean -- True while the Adolfjon menu is open.
globals.screen() -> number width, number height -- Screen size in pixels. Same as draw.screen().
globals.score() -> integer T score, integer CT score -- Team scores. -1 until known. The first call starts reading; values arrive within a second and refresh twice a second while you keep asking.
server.address() -> string -- The server address as text, or empty.
server.kind() -> integer 0 to 3 -- What kind of server: 0 none, 1 local (offline or listen server), 2 direct IP, 3 Valve relay (matchmaking).
server.mode() -> string -- Game mode text. Same as game.mode().
draw.screen() -> number width, number height -- Screen size in pixels.
draw.line(x1, y1, x2, y2, col[, thickness]) -- A straight line.
draw.rect(x, y, w, h, col[, rounding][, thickness]) -- A rectangle outline.
draw.rect_filled(x, y, w, h, col[, rounding]) -- A filled rectangle.
draw.circle(x, y, radius, col[, thickness]) -- A circle outline.
draw.circle_filled(x, y, radius, col) -- A filled circle.
draw.text(x, y, text, col[, size]) -- Text in the menu font. (x, y) is the top-left corner of the text.
draw.text_size(text[, size]) -> number width, number height -- How big the text will be. Use it to centre or right-align.
draw.text_outline(x, y, text, col[, size]) -- Text with a thin black outline, readable on any background.
draw.gradient(x, y, w, h, col1, col2[, horizontal]) -- A rectangle that fades from one colour to another.
draw.polygon(points, col) -- A filled convex shape (triangle, arrow, diamond). Concave shapes draw wrong.
draw.polyline(points, col[, thickness][, closed]) -- Connected lines through the points.
draw.world_to_screen(x, y, z) -> number screen x, number screen y, boolean -- Turns a world position into screen pixels. Takes three numbers, not a vector.
draw.line3d(a, b, col[, thickness]) -- A line between two world points. Drawn only when both ends are in front of the camera.
draw.circle3d(center, radius, col[, thickness]) -- A flat circle on the ground around a world point (32 segments).
draw.box3d(min, max, col[, thickness]) -- A 3D box outline between two corners.
draw.load_image(file) -> integer | nil -- Loads a PNG or JPG from the script's data folder. Do it once, outside on_frame.
draw.load_image_data(bytes) -> integer | nil Image handle -- Loads an image from bytes in memory, for example the body of an http.get reply.
draw.image(handle, x, y, w, h[, tint][, rounding]) -- Draws an image.
draw.image_size(handle) -> integer width, integer height -- Original size of a loaded image. 0, 0 for a bad handle.
draw.load_font(file[, size]) -> integer | nil -- Loads a .ttf, .otf or .ttc font. Looks in the script's data folder first, then in Windows\Fonts.
draw.font_ready(handle) -> boolean -- True once the font can be used.
draw.text_font(handle, x, y, text, col[, size]) -- Text in a loaded font. Uses the menu font until the font is ready.
draw.text_size_font(handle, text[, size]) -> number width, number height -- Text size in a loaded font.
hud.panel(id, title, w, h) -> number x of the content area, number, boolean -- Draws a panel with a header and returns where its content area starts.
ui.checkbox(id, label[, default]) -> boolean Current value -- An on/off switch.
ui.slider(id, label, min, max[, default]) -> number Current value, kept inside min to max -- A number slider. Whole numbers when min, max and default are all whole and the range is at least 2; otherwise two decimals.
ui.combo(id, label, items[, default]) -> integer 1-based index of the choice -- A dropdown.
ui.color(id, label[, default]) -> integer Packed colour -- A colour picker with alpha.
ui.keybind(id, label[, default]) -> integer Key code, 0 when none -- A key picker.
ui.get(id) -> any | nil nil when no control has this id -- The current value of a control made with the functions above.
menu.text(text) -- A line of text.
menu.section(title) -- A section heading.
menu.separator() -- A thin line.
menu.spacing() -- Some empty space.
menu.same_line() -- Puts the next widget to the right of the last button. Works only right after a button.
menu.button(label) -> boolean true on the frame it was clicked -- A button.
menu.checkbox(label, value) -> boolean New value -- A checkbox.
menu.slider(label, value, min, max[, decimals]) -> number New value -- A slider.
menu.combo(label, index, items) -> integer New 1-based index -- A dropdown.
menu.color(label, col) -> integer New colour -- A colour picker.
menu.keybind(label, vk) -> integer New key code -- A key picker.
menu.input(label, text) -> string New text -- A one-line text field, up to 255 bytes.
menu.tooltip(text) -- A hover tooltip on the widget drawn just before it.
menu.progress(label, value) -- A progress bar.
menu.list(label, selected, items[, rows]) -> integer New selection -- A scrolling list.
menu.image(handle, w, h) -- An image from draw.load_image. The width is capped to the page width.
notepad.open(id[, title][, text][, options]) -- Opens (or brings back) a notepad.
notepad.set_text(id, text) -- Replaces the text and marks it saved.
notepad.get_text(id) -> string -- The current text. Empty when not open.
notepad.close(id) -- Closes it.
notepad.is_open(id) -> boolean -- True while open.
aim.enabled([on]) -> boolean State after the call [needs settings] -- Returns whether the aimbot is on. Pass true or false to change it.
aim.target() -> player | nil Basic player table -- The player the aimbot is locked on, or nil.
aim.info() -> table -- What the aimbot is doing this frame.
trigger.enabled([on]) -> boolean State after the call [needs settings] -- Returns whether the triggerbot is on. Pass a value to change it.
bomb.state() -> table See the table below -- Everything about the bomb. Same as entity.c4().
Bomb table: planted:boolean, defused:boolean, exploded:boolean, defusing:boolean, site:string, time_left:number, timer_length:number, defuse_left:number, defuse_length:number, pos:vector
nade.list() -> table[] See the grenade table below -- Grenades in the world. Same as entity.grenades().
nade.held() -> table | nil -- The predicted path of the grenade you are holding, or nil.
Grenade table: type:string, pos:vector, exploded:boolean, deployed:boolean, smoke_timer:number, thrown_from:vector, landing:vector, time_left:number
spectators.list() -> table[] -- Players spectating you right now.
velocity.state() -> table -- Movement stats. Speeds are in units per second.
keystrokes.state() -> table -- Physical key states, plus clicks per second.
esp.add_text(slot, text[, col]) -> boolean -- Adds a line under that player's ESP box for this frame. Call it every frame you want it shown. Needs ESP to be on.
notify.push(text[, damage][, hp]) -- Adds a line to the notification feed, in the user's chosen style.
players.flag(steamid[, flag]) -> integer Flag after the call [needs settings] -- Reads a player's flag in the player list, or sets it.
players.color(steamid[, col]) -> integer Colour after the call [needs settings] -- Reads or sets a player's custom colour.
players.intel(steamid) -> table | nil -- Public stats. The first call starts loading them; it returns nil until they arrive, so call it again on later frames.
config.sections() -> string[] -- The section names.
config.list(path) -> string[] -- Key names inside a section or object, in alphabetical order. Empty for arrays and plain values.
config.get(path) -> any | nil nil when the path does not exist -- The current value. Arrays and colours come back as tables (colours are {r, g, b, a} from 0 to 1).
config.set(path, value) -> boolean true when applied (or already equal) [needs settings] -- Changes a setting.
map.ready() -> boolean -- True when map data is loaded.
map.name() -> string -- The map that is loaded.
map.visible(a, b) -> boolean -- True when nothing solid is between two points.
map.penetrate(a, b[, bullet]) -> table -- Damage a bullet from a to b would do through walls.
map.trace(a, b[, radius]) -> table -- Casts a ray (or a ball when radius is set) from a to b against the map.
map.ground(pos[, depth]) -> vector | nil nil when there is no floor -- The floor under a point.
world.aim([range]) -> table | nil -- Where your crosshair points on the map.
world.marker(pos[, options]) -> integer Object id, 0 when full -- A dot with optional text, ring, vertical beam and icon.
world.zone(options) -> integer Object id -- A circle (pos + radius, height = vertical reach, default 128) or a box (min + max). Fires zone_enter / zone_leave for this script when you walk in or out.
world.path(points[, options]) -> integer Object id, 0 with fewer than 2 points -- A line through 2 to 512 points. arrows adds direction marks, dashed breaks it up.
world.decal(image, pos[, options]) -> integer Object id, 0 for a bad image -- A picture from draw.load_image placed in the world. With normal it lies flat on that surface; without it always faces you. size is in units (default 32).
world.set(id, options) -> boolean -- Changes options (and pos) of an object.
world.move(id, pos) -> boolean -- Moves an object.
world.remove(id) -> boolean -- Deletes an object.
world.clear() -- Deletes all of this script's objects.
world.count() -> integer -- How many objects this script has.
world.inside(id) -> boolean -- True while you stand in that zone.
world.list() -> table[] -- This script's objects.
world.save([name]) -> boolean -- Writes markers, zones and paths to world_<name>.json in the data folder. Default name is the map.
world.load([name]) -> integer How many were added -- Adds the objects saved under that name.
view.window(x, y, w, h, options) -> boolean -- Shows the map from any point. Call it in on_frame every frame you want it.
weapon.info([slot]) -> table | nil -- Your weapon, or the one held by that player.
sound.play(file[, volume][, options]) -> integer Sound id, 0 when it did not start -- Plays a WAV.
sound.play_at(file, pos[, volume][, range]) -> integer Sound id or 0 -- Plays a WAV as if it came from a point: quieter with distance, panned left or right from where you look. Silent past range (default 2000).
sound.tone(hz[, seconds][, volume][, pan]) -> integer Sound id or 0 -- A beep without any file. 20 to 12000 Hz, 0.02 to 5 s (default 0.15), volume default 0.6.
sound.stop(id) -> boolean -- Stops one of this script's sounds.
sound.stop_all() -- Stops all of this script's sounds.
sound.playing(id) -> boolean -- True while that sound plays.
sound.list() -> string[] -- WAV files in the data folder.
sound.speak(text[, rate]) -> boolean false when the queue is full -- Reads text aloud with a Windows voice. Queued, up to 8 lines of 240 characters. Rate -10 to 10.
sound.speak_stop() -- Stops speaking and clears the queue.
timer.after(seconds, fn) -> integer Timer id -- Runs fn once after that many seconds.
timer.every(seconds, fn) -> integer Timer id -- Runs fn every that many seconds.
timer.cancel(id) -- Stops a timer.
vpk.read(entry, callback[, source]) -> boolean -- Reads one file by its exact path, on a worker thread. The callback runs later on the render thread.
json.encode(value) -> string -- A table, string, number or boolean as JSON text. A table with keys 1..n becomes an array; others become objects (keys that are not strings are dropped). Up to 16 levels deep.
json.decode(text) -> any | nil -- JSON text as Lua values. nil when the text is not valid JSON.
db.get(key) -> any | nil -- A saved value, or nil.
db.set(key, value) -> boolean -- Saves a value. nil deletes the key.
files.read(name) -> string | nil -- The file contents, or nil.
files.write(name, text) -> boolean -- Writes (replaces) a file.
files.list() -> string[] -- File names in the folder.
files.remove(name) -> boolean -- Deletes a file.
http.get(url, callback) -> boolean [needs network] -- Starts a GET request on a worker thread. The callback runs later on the render thread.
clipboard.get() -> string -- The clipboard text.
clipboard.set(text) -- Copies text.
events.on(name, handler) -- Adds a handler. Several handlers for one event all run, in the order added.
events.off(name) -- Removes all handlers of that event.
math.clamp(v, min, max) -> number -- Keeps v inside min to max.
math.lerp(a, b, t) -> number -- a + (b - a) * t.
math.round(v) -> integer -- Nearest whole number (halves go up).
math.distance(a, b) -> number Game units -- Distance between two vectors.
math.angle_to(from, to) -> number pitch, number yaw -- View angles that look from one point at another, in degrees (negative pitch is up, the game's own convention).
vec.new([x][, y][, z]) -> vector -- A new vector. Missing parts are 0.
vec.add(a, b) -> vector -- a + b.
vec.sub(a, b) -> vector -- a - b.
vec.scale(a, s) -> vector -- a * s.
vec.dot(a, b) -> number -- Dot product.
vec.length(a) -> number -- Length.
vec.distance(a, b) -> number -- Distance.
vec.normalize(a) -> vector -- Same direction, length 1. A zero vector stays zero.
bit.band(a, b) -- a & b.
bit.bor(a, b) -- a | b.
bit.bxor(a, b) -- a ~ b.
bit.bnot(a) -- ~a.
bit.lshift(a, n) -- a << n.
bit.rshift(a, n) -- a >> n.
LIMITS
- Time per callback: 8 ms or 1 000 000 Lua instructions, whichever comes first. Then the script stops with an error.
- Memory: 64 MB per script.
- config.set: 64 changes per frame across all scripts. Only existing keys, same type and shape. misc.menuToggleKey and misc.obsBypass are locked.
- files: Only the script's own data/<script>/ folder. Names use A-Z a-z 0-9 _ . - (up to 64 characters). 1 MB per file, 64 files.
- db: 256 KB per script.
- http.get: https://adolfjon.uz/ only. 4 requests at a time per script (shared with vpk.read). Replies are cut at about 512 KB.
- vpk.read: 16 MB per entry. Shares the 4-at-a-time limit with http.get.
- Images: 16 per script. PNG or JPG, up to 2048 x 2048 and 8 MB.
- Fonts: 8 in total across all scripts, 6 to 96 px.
- chat: One message every 3.5 seconds across all scripts, up to 120 bytes.
- esp.add_text: 4 lines per player per frame, 64 characters each.
- notify.push: 64 characters.
- menu.input: 255 bytes.
- Polygons: 128 points.
Settings keys for config.get/config.set are listed at https://dev.adolfjon.uz/docs/lua/settings (paths like aim.rcs, esp.visibleColor). Only use keys you are sure exist; check config.get(path) ~= nil first.
MY SCRIPT:
<describe what the script should do here>Machine-readable API
For tools, editors and agents: /lua-api.json has every namespace, function, argument, return value, event, limit and settings key as JSON.