Lua API

Scripting referenceWebsite

Getting started

Scripts are plain .lua files. Put them in <csgo dir>\nihilo\scripts\. Shared code goes in <csgo dir>\nihilo\scripts\libraries\.

  • Load and unload: open the menu, then CONFIG > LUA. Pick a file in Script List and press Load Script (the button reads Unload Script while it runs). Refresh List reads the folder again, Open Script Folder shows it in Explorer, Reload Scripts loads every running script again (menu items keep their values). client.load_script(name) loads another script; it is queued and runs in the next frame.
  • Threads: all Lua runs on the game thread. The script's top-level code and every callback can call any function, except that render.* draw calls and render.world_to_screen work only inside a paint callback and raise a Lua error anywhere else. The badge on each card shows where a function can be called: any, paint only, or event only (the event object of a game event callback).
  • require: require("name") reads name.lua from scripts\ or scripts\libraries\ and from nowhere else. Dots are folders: require("a.b") reads a\b.lua. There is no other search path (package.path and package.cpath are empty) and no native (C) modules.
  • Libraries you get: base, package, string, math, table and bit32 (LuaJIT has no utf8). The Lua is LuaJIT 2.1 (Lua 5.1 with the JIT compiler and extensions): goto and labels, 0x and 1LL number literals, table.move, coroutine.isyieldable, and table.new(narray, nhash) / table.clear(t) through require("table.new") and require("table.clear"). Lua 5.0 leftovers are gone: string.gfind (use gmatch) and math.mod (use math.fmod). dofile, loadfile, package.loadlib and package.searchpath are removed, and load and loadstring take source text only (see Security). ffi and string.buffer are only there with unsafe scripts.
  • Output: print(...) writes <script>.lua: <text> to logs\nihilo.log and the game console.
  • Errors: a Lua error unloads the script. An error in the top-level code stops the load; an error in a callback or a button callback unloads the script in the next frame. The error is written to the log and in red to the game console, and the script name turns red in the Lua panel (see Security).
  • Menu items: every ui.add_* item shows in the Lua panel under the script while it runs. Values are stored in the preset's [lua] section and come back the next time the script loads.

A full example. Save it as nihilo\scripts\phase_b_test.lua and load it from CONFIG > LUA: it draws a box with text on screen and prints each player_hurt event.

-- nihilo\scripts\phase_b_test.lua
local font = render.create_font("Verdana", 14, 700, font_flags.antialias + font_flags.dropshadow)
local health_bar = ui.get("VISUALS", "ENEMY ESP", "Health bar")
print("Health bar row:", health_bar ~= nil, "on:", health_bar and health_bar:get())

callbacks.register("paint", function()
    render.rectangle_filled(20, 200, 190, 40, color(20, 20, 20, 180))
    render.rectangle(20, 200, 190, 40, color(160, 157, 251, 255))
    render.text(30, 211, "nihilo lua ok", color(255, 255, 255, 255), font)
end)

callbacks.register("player_hurt", function(e)
    print("player_hurt userid", e:get_int("userid"), "attacker", e:get_int("attacker"), "dmg", e:get_int("dmg_health"))
end)

A smaller one, with a menu checkbox:

-- scripts\hello.lua
local enable = ui.add_checkbox("Say hello")
callbacks.register("paint", function()
	if enable:get() then
		render.text(10, 10, "hello " .. client.username, color(255, 255, 255))
	end
end)

Callbacks

Register a function with callbacks.register(name, fn). An unknown name is a Lua error. When the game raises that callback, every function registered for it in every running script is called, one after the other. Callbacks that receive an argument show it in the signature.

Security

  • require stays in two folders. require reads modules from nihilo\scripts\ and nihilo\scripts\libraries\ only. package.path and package.cpath are empty, so there is no search relative to the game folder and no native modules.
  • No other way to files or native code. dofile, loadfile (read any path), package.searchpath (tests any path) and package.loadlib (loads a DLL) are removed from every script, also with unsafe scripts (there ffi.load covers DLLs). load, loadstring, require and the script file take Lua source text only: LuaJIT bytecode is refused, because LuaJIT (2.0 and 2.1) does not check bytecode and a crafted chunk can read and write any memory.
  • ffi is off by default. ffi, string.buffer (its ref and reserve return raw pointers and load ffi), client.find_sig and client.create_interface exist only in scripts that were loaded while CONFIG > LUA > Allow unsafe scripts was on. Using a 1LL number literal does not make require("ffi") work. The option is never saved: it is off every time the game starts. Turning it on or off does not change scripts that are already running; reload them.
  • Presets never run scripts. A preset can mark scripts (Save in Preset) and stores their item values, but loading a preset never runs code. Nothing runs until you press Load Script or Load Marked Scripts yourself.
  • A script error unloads that script. A Lua error in the top-level code, a callback (paint, frame stage, game event), or a button callback is logged as [Scripting] [WARN] <script>.lua: <error> in logs\nihilo.log and printed in red in the game console as [lua] <script>.lua: <error>. All callbacks and buttons of that script stop at once, and the script is unloaded in the next frame. In the Lua panel its name turns red and stays red until you load it again (a running script's name is in the menu accent color). Other scripts keep running.
  • This is not a sandbox. A script runs with the rights of the game process. The folder rules only limit where require reads; they do not limit what native code can do. Read a script before you load it, and turn unsafe scripts on only for code you trust.