Skip to content

Agent & LLM API Guide

Runtime: LuaJIT 2.1 (Lua 5.1 syntax).
Execution Context: In-process synchronous execution on the game thread inside CS:GO’s frame loop.

This guide provides autonomous AI agents and language models with the exact specifications, constraints, API signatures, and code generation patterns required to write reliable Lua scripts for Aimsense.


When writing code for Aimsense, agents must adhere to these invariants:

  1. Guarded Entity Resolution: Never assume entity lookups return valid objects. Always guard checks:
    local local_player = entity.get_local_player()
    if not local_player or not local_player:is_alive() then return end
  2. Strict Render Callback Boundary: All render.* functions are only valid inside the render callback. Calling render methods from any other callback will corrupt graphics state or fail silently.
  3. 0-Based Unbounded Netprop Arrays: Netprop arrays (such as player.m_hMyWeapons) are 0-indexed memory wrappers with no length bounds. Never use the Lua # length operator on netprop arrays. Index from 0 to max_size - 1.
  4. Explicit Returns in draw_chams:
    • return 0: Custom takeover mode. The script is responsible for calling materials.draw_chams(mat).
    • return 1: Passthrough mode. Engine draws the model normally.
    • Omitting return evaluates to 0, hiding the model.
  5. No Synchronous I/O in Per-Frame Callbacks: Never call blocking network requests (network.get without callback), file decoders, or archive decompressions inside render or createmove.
  6. No Standard io Library: Standard Lua io.* is disabled. Use the files and database namespaces.
  7. JSON Userdata Constraints: Userdata objects (vector, qangle, color) serialize as null in json.stringify. Always convert them to plain tables: { x = v.x, y = v.y, z = v.z }.

2. Event Callbacks Table (client.add_callback)

Section titled “2. Event Callbacks Table (client.add_callback)”
Event Name Signature Description
render fn() Primary visual drawing phase.
createmove fn(cmd: user_cmd_t) Pre-aimbot tick processing. Writes to cmd are active.
post_createmove fn(cmd: user_cmd_t) Post-engine tick processing. Read-only snapshot.
antiaim fn(ctx: antiaim_context_t) Override anti-aim pitch, yaw, desync, and fake lag.
frame_stage fn(stage: number) Stages: 0=START, 1=NET_START, 4=NET_END, 5=RENDER_START.
game_events fn(event: game_event_t) CS:GO gameplay events (player_death, player_hurt, etc.).
aim_shot fn(shot: shot_t) Ragebot client firing intent.
aim_ack fn(shot: shot_t) Server shot acknowledgement (hit/miss/spread).
draw_chams fn(class: number) -> number Custom chams override. Classes: 0=Enemy, 1=Shot, 2=Backtrack, 3=Local, 4=ViewModel. Return 0 or 1.
unload fn() Cleanup hook (restore ConVars, release materials).

  • globals.curtime: Current simulation time in seconds.
  • globals.realtime: Wall-clock time since game start.
  • globals.frametime: Duration of last frame in seconds.
  • globals.tickinterval: Seconds per tick (1/64 or 1/128).
  • globals.tickcount: Current tick count integer.
  • globals.is_in_game: true if connected and spawned in a server.
  • globals.max_players: Maximum player slots on server.
  • print(val): Log string to cheat console with script prefix.
  • error(msg): Log red error to console (non-terminating).
  • print_raw(text, color?): Unprefixed console output with \aRRGGBB support.
  • to_ticks(sec) / to_time(ticks): Time/tick conversions.
  • math.angle_diff(a, b): Shortest signed angle difference in degrees.
  • math.normalize_yaw(yaw): Wraps yaw into [-180, 180].
  • client.add_callback(event: string, fn: function)
  • client.remove_callback(event: string, fn: function) -> boolean
  • client.delay_call(seconds: number, fn: function, ...args)
  • client.unload_script() / client.reload_script()
  • client.color_log(r: number, g: number, b: number, ...)
  • entity.get(index: number, is_userid?: boolean) -> entity_t | player_t | weapon_t | nil
  • entity.from_handle(handle: number) -> entity_t | player_t | weapon_t | nil
  • entity.get_local_player() -> player_t | nil
  • entity.get_players(enemies_only?: boolean) -> table of player_t (1-based list)
  • entity.get_player_resource() -> player_resource_t | nil
  • ui.find(tab: string, groupbox: string, widget?: string, type?: number) -> ui_element_t | nil
  • ui.tab(name: string, icon?: string)
  • ui.groupbox(tab: string, name: string) -> groupbox_t
  • Widget creation:
    • gb:checkbox(name, default?)
    • gb:slider_int(name, min, max, default, format?)
    • gb:slider_float(name, min, max, default, format?)
    • gb:combo(name, ...options)
    • gb:multicombo(name, ...options)
    • gb:listbox(name, ...options)
    • gb:listbox_multi(name, ...options)
    • gb:color_picker(name, default?)
    • gb:keybind(name)
    • gb:input(name)
    • gb:button(name)
    • gb:label(name)
  • ui_element_t methods:
    • el:get() (Single value) / el:get(index: number) (Multicombo option boolean)
    • el:set(value) / el:set(value, index: number)
    • el:override(value?) / el:override(value?, index?: number)
    • el:set_callback(fn)
    • el:visible(shown: boolean)
    • el:add_gear_item(child_element) -> el (Chainable)
    • el:add_switch(checkbox_element) -> el
  • render.line(from: vector, to: vector, clr: color)
  • render.rect(from: vector, to: vector, clr: color, rounding?: number)
  • render.rect_outline(from: vector, to: vector, clr: color, rounding?: number)
  • render.gradient(from, to, tl: color, tr: color, bl: color, br: color)
  • render.circle(center: vector, clr: color, radius: number, start_deg?, fraction?)
  • render.circle_outline(center, clr, radius, start_deg?, fraction?, thickness?)
  • render.text(font: number | font_t, pos: vector, clr: color, flags: string, text: string)
    • Fonts: 1 (Verdana), 2 (Small Fonts), 3 (Verdana Bold), 4 (Calibri Bold)
    • Flags: "c" (center), "o" (outline), "d" (drop shadow)
  • render.measure_text(font, text) -> vector(width, height)
  • render.load_font(name, size, flags?) -> font_t
  • render.load_image(bytes, size?) -> image_t / render.load_svg(svg_markup, size?)
  • render.texture(image: image_t, pos: vector, clr?: color, size?: vector)
  • render.screen_size() -> vector(w, h)
  • render.world_to_screen(world_pos: vector) -> vector (Returns -1, -1 if behind camera)
  • render.project(world_pos: vector) -> vector, boolean (screen_pos, behind_camera)
  • utils.console_exec(cmd: string)
  • utils.is_key_pressed(vk: number) -> boolean
  • utils.get_mouse_position() -> vector
  • utils.trace_line(from: vector, to: vector, mask: number, filter?: entity | fn) -> trace_t
  • utils.trace_bullet(attacker: player_t, from: vector, to: vector, target?: player_t) -> fire_bullet_t
  • utils.create_interface(module, name) -> number / utils.pattern_scan(module, pattern) -> number
  • cvar.<name> or cvar:find(name) -> convar | nil
  • cv:int(val?) / cv:float(val?) / cv:string(val?) (Returns previous value on write)
  • materials.create(name: string, shader: string, keyvalues: table) -> material_t
  • materials.find(name: string) -> material_t | nil
  • materials.draw_chams(mat: material_t) (Only inside draw_chams callback)
  • rage.get_antiaim_target() -> player_t | nil
  • rage.get_antiaim_yaw() -> number
  • rage.get_exploit_charge() -> number (0.0..1.0)
  • rage.is_shifting() -> boolean
  • rage.force_charge() / rage.force_teleport()
  • files.read(path) -> string / files.write(path, data) -> boolean / files.exists(path)
  • files.create_folder(path) / files.list(path) / files.delete(path)
  • database.read(key) / database.write(key, val) / database.flush()
  • json.parse(str) -> table / json.stringify(tbl, indent?) -> string
  • network.get(url, headers?, callback?)
  • http.get(url, options?, callback?) / http.post(url, options?, callback?)
    • Callback signature: function(success: boolean, response: table)
    • Response table: response.status, response.body, response.headers, response.timed_out

local group = ui.groupbox("Visuals", "Enemy Box")
local enabled = group:checkbox("Enabled", true)
local box_clr = group:color_picker("Box Color", color(255, 255, 255, 255))
client.add_callback("render", function()
if not enabled:get() or not globals.is_in_game then return end
local enemies = entity.get_players(true)
for i = 1, #enemies do
local enemy = enemies[i]
if enemy:is_alive() and not enemy:is_dormant() then
local mins, maxs = enemy:get_bounding_box()
if mins.x ~= 0 or maxs.x ~= 0 then
render.rect_outline(mins, maxs, box_clr:get())
render.text(1, vector(mins.x, mins.y - 14), color(255, 255, 255), "o", enemy:get_name())
end
end
end
end)
local IN_JUMP = bit32.lshift(1, 1)
client.add_callback("createmove", function(cmd)
local me = entity.get_local_player()
if not me or not me:is_alive() then return end
local on_ground = bit32.band(me.m_fFlags, 1) ~= 0
if not on_ground then
cmd.buttons = bit32.band(cmd.buttons, bit32.bnot(IN_JUMP))
end
end)
client.add_callback("antiaim", function(ctx)
ctx:pitch(89)
ctx:yaw_offset(180)
ctx:desync(true)
ctx:desync_angle(58)
end)