Nonagon Lua API
Full reference for the Nonagon scripting interface. Scripts live in C:\nonagon\scripts\ and are hot-reloaded from the UI.
on_pre_move (every command), scripted movement (cmd.set_button, cmd.add_button_step, silent cmd.set_view_angles, movement.block_features), grenade simulation on the CS2 server model (utils.simulate_grenade, utils.simulate_throw), json, clipboard, input, more draw / entity / ui functions, ragebot and anti-aim state. New items are marked new.callbacks
Register event handlers. A script can register one handler per event; registering again replaces it. All handlers are dropped when the script is unloaded or reloaded.
false for an unknown event name.Available Events
callbacks.register("on_render", function()
draw.text(10, 10, "Hello Nonagon", 255, 255, 255, 255)
end)
callbacks.register("on_player_hurt", function(attacker, victim, dmg, hp, hg)
client.log(attacker .. " hit " .. victim .. " for " .. dmg .. " dmg")
end)client
Core client functions — screen, time, view, player info, system info, console, notifications, deferred calls.
type: 0 default, 1 info (default), 2 success, 3 warning, 4 error.true when it is on screen.Time & State
client.get_tickcount.client.frametime.View & Local Player
System & Info
"de_mirage"), "" outside a map.C:\nonagon\scripts\ — for the script's own data files.YYYY-MM-DD.Console & Networking
Deferred Execution
func after the delay (checked every frame).client.execute_after(2.0, function()
client.notify("Two seconds passed", 2)
end)draw
Rendering primitives. Only inside on_render. Colors are RGBA 0–255 and default to 255.
segments 8–128, default 48.input
Keyboard and mouse. Keys are Windows virtual-key codes (the same as the menu's keybinds).
input.get_mouse_pos.entity
Player functions take a controller index; the generic ones any entity index.
Players
FL_ONGROUND = 1, FL_DUCKING = 2).Weapons
"weapon_smokegrenade").Generic Entities
"CPlantedC4").local bombs = entity.get_by_class("CPlantedC4")
if #bombs > 0 then
local x, y, z = entity.get_ent_origin(bombs[1])
local sx, sy, ok = client.world_to_screen(x, y, z)
if ok then
draw.rect_filled(sx - 10, sy - 10, 20, 20, 255, 60, 60, 200)
draw.text(sx + 14, sy - 6, "BOMB", 255, 60, 60, 255)
end
endutils
Tracing, grenade simulation, clipboard, memory and random numbers.
Trace
0x1C3003.Grenades
{ {x, y, z}, … }, a point per tick), whether it detonated and the flight time. weapon picks the detonation rule ("weapon_smokegrenade", "weapon_molotov" …).run ticks of running along yaw + run_yaw (walking with walk), the jump, the release delay ticks later — through the game's own movement — then the flight as simulate_grenade. Uses the eye and velocity the server throws with (the tick after the release, or the jump's for a jump throw).Clipboard
Low-level & Memory
Random
-- where would a smoke thrown right now from here land?
callbacks.register("on_render", function()
local me = entity.get_local_player()
if not me then return end
local ex, ey, ez = client.get_eye_pos()
local pitch, yaw = client.get_view_angles()
local vx, vy, vz = entity.get_velocity(me)
local path, detonated, t = utils.simulate_grenade(ex, ey, ez, pitch, yaw, 1, vx, vy, vz, "weapon_smokegrenade")
local last = path[#path]
if last then draw.circle_3d(last[1], last[2], last[3], 16, 120, 200, 255, 255, 1.5) end
end)json
JSON for script data (location lists, settings, exchange through the clipboard).
nil on a parse error.local path = client.get_scripts_dir() .. "my_data.json"
local f = io.open(path, "r")
local data = f and json.decode(f:read("a")) or {}
if f then f:close() end
data.runs = (data.runs or 0) + 1
f = io.open(path, "w") f:write(json.encode(data)) f:close()cmd
The user command being built. Only inside on_pre_move and on_createmove (elsewhere it raises an error).
Movement
forwardmove, leftmove, upmove (−1 … 1).Angles
pitch, yaw, roll.Buttons
set_button).when (0–1 of the tick, default 0.5) — what a real key press sends next to the button state. Attack and jump are taken at their step's time: a jump throw is a jump press and an attack release at the same when.Button constants
| IN_ATTACK | 1 | left click |
| IN_JUMP | 2 | |
| IN_DUCK | 4 | |
| IN_FORWARD | 8 | W |
| IN_BACK | 16 | S |
| IN_USE | 32 | E |
| IN_MOVELEFT | 512 | A |
| IN_MOVERIGHT | 1024 | D |
| IN_ATTACK2 | 2048 | right click |
| IN_RELOAD | 8192 | R |
| IN_SPEED | 65536 | Shift (walk) |
local IN_ATTACK, IN_JUMP = 1, 2.local IN_ATTACK, IN_JUMP = 1, 2
ui.add_hotkey("Jump throw")
-- a jump throw bind: jump and release the grenade at the same subtick
callbacks.register("on_pre_move", function()
if not input.is_key_down(ui.get_hotkey("Jump throw")) then return end
cmd.set_button(IN_JUMP, true)
cmd.add_button_step(IN_JUMP, true, 0.5)
cmd.set_button(IN_ATTACK, false)
cmd.add_button_step(IN_ATTACK, false, 0.5)
end)movement
Take the movement over from the cheat for a command. Call every command it should hold (in on_pre_move).
ui
Controls on the script's settings page. Add them at the top level; values are saved with the config.
Controls
ui.get_bool / set_bool.ui.get_int / set_int.ui.get_float / set_float.ui.get_combo / set_combo. Alias add_combobox.ui.get_color / set_color.ui.get_hotkey / set_hotkey.ui.get_string / set_string.ui.is_clicked.add_text.end_group.Values
set_* functions return false for an unknown name.set_combo.Visibility
ui.add_checkbox("Enable ESP", true)
ui.add_slider_int("ESP Distance", 100, 5000, 2000)
ui.add_combo("Style", { "Box", "Corners", "Off" }, 0)
ui.add_color_picker("Color", 1, 0.3, 0.3, 1)
ui.add_input("Tag", "nonagon")
ui.add_button("Reset")
callbacks.register("on_render", function()
if ui.is_clicked("Reset") then ui.set_int("ESP Distance", 2000) end
ui.set_visible("Color", ui.get_combo("Style") ~= 2)
if not ui.get_bool("Enable ESP") then return end
local r, g, b, a = ui.get_color("Color")
end)cfg
Key-value store shared between scripts. Resets on script reload.
antiaim
Control anti-aim from Lua (degrees). Only inside on_createmove; the values reset every tick.
180 = backwards).callbacks.register("on_createmove", function()
antiaim.set_yaw_offset(math.sin(client.curtime() * 8) * 60)
antiaim.override_pitch(89)
end)ragebot
Read the ragebot's state.
Hit Groups
Returned by utils.trace_bullet and on_player_hurt.
Trace Masks
Optional mask for utils.trace_line / trace_hull.
Examples
Damage Predictor
callbacks.register("on_render", function()
local y = 40
local lx, ly, lz = client.get_eye_pos()
for _, idx in ipairs(entity.get_players()) do
local ex, ey, ez = entity.get_eye_pos(idx)
local hit, dmg = utils.trace_bullet(lx, ly, lz, ex, ey, ez)
if hit then
draw.text(10, y, string.format("[%s] %d dmg | %d hp", entity.get_name(idx), dmg, entity.get_health(idx)), 255, 200, 0, 255)
y = y + 16
end
end
end)Grenade Spot (walk in, aim, throw)
local IN_ATTACK, IN_JUMP = 1, 2
local spot = { pos = { -300.0, -1163.0, -167.97 }, pitch = -30.0, yaw = 120.0, jump = true }
ui.add_hotkey("Throw")
callbacks.register("on_pre_move", function()
if not input.is_key_down(ui.get_hotkey("Throw")) then return end
local me = entity.get_local_player()
local is_nade, pin = entity.get_grenade_state(me)
if not is_nade then return end
movement.block_features()
cmd.set_view_angles(spot.pitch, spot.yaw, true) -- silent: the camera stays
local x, y = entity.get_origin(me)
local dx, dy = spot.pos[1] - x, spot.pos[2] - y
if dx * dx + dy * dy > 0.25 then
local _, cam_yaw = client.get_view_angles()
local rel = math.rad(math.deg(math.atan(dy, dx)) - cam_yaw)
cmd.set_move(math.cos(rel), math.sin(rel)) -- walk onto the spot
cmd.set_button(IN_ATTACK, true) -- keep the pin pulled
elseif pin then
if spot.jump then cmd.set_button(IN_JUMP, true) cmd.add_button_step(IN_JUMP, true, 0.5) end
cmd.set_button(IN_ATTACK, false)
cmd.add_button_step(IN_ATTACK, false, 0.5)
end
end)
callbacks.register("on_render", function()
local path = utils.simulate_throw({ x = spot.pos[1], y = spot.pos[2], z = spot.pos[3],
pitch = spot.pitch, yaw = spot.yaw, jump = spot.jump, weapon = "weapon_smokegrenade" })
local px, py, pok
for _, p in ipairs(path) do
local sx, sy, ok = client.world_to_screen(p[1], p[2], p[3])
if ok and pok then draw.line(px, py, sx, sy, 120, 200, 255, 255, 1.5) end
px, py, pok = sx, sy, ok
end
end)Find Smoke Grenades
callbacks.register("on_render", function()
for _, ent in ipairs(entity.get_by_class("CSmokeGrenadeProjectile")) do
local x, y, z = entity.get_ent_origin(ent)
draw.circle_3d(x, y, z, 144, 180, 180, 180, 200, 1, 64)
end
end)