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 andrender.world_to_screenwork only inside apaintcallback 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")readsname.luafromscripts\orscripts\libraries\and from nowhere else. Dots are folders:require("a.b")readsa\b.lua. There is no other search path (package.pathandpackage.cpathare empty) and no native (C) modules. - Libraries you get:
base,package,string,math,tableandbit32(LuaJIT has noutf8). The Lua is LuaJIT 2.1 (Lua 5.1 with the JIT compiler and extensions):gotoand labels,0xand1LLnumber literals,table.move,coroutine.isyieldable, andtable.new(narray, nhash)/table.clear(t)throughrequire("table.new")andrequire("table.clear"). Lua 5.0 leftovers are gone:string.gfind(usegmatch) andmath.mod(usemath.fmod).dofile,loadfile,package.loadlibandpackage.searchpathare removed, andloadandloadstringtake source text only (see Security).ffiandstring.bufferare only there with unsafe scripts. - Output:
print(...)writes<script>.lua: <text>tologs\nihilo.logand 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.
requirereads modules fromnihilo\scripts\andnihilo\scripts\libraries\only.package.pathandpackage.cpathare 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) andpackage.loadlib(loads a DLL) are removed from every script, also with unsafe scripts (thereffi.loadcovers DLLs).load,loadstring,requireand 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(itsrefandreservereturn raw pointers and load ffi),client.find_sigandclient.create_interfaceexist only in scripts that were loaded while CONFIG > LUA > Allow unsafe scripts was on. Using a1LLnumber literal does not makerequire("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>inlogs\nihilo.logand 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
requirereads; 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.