on() connects a script to the cheat and to the game. Every event, the object each one hands over, and timers.
A script does nothing by itself after it loads. Everything it does later is a reaction: to a frame being drawn, to a command being built, to a key press, to a player being hurt, to a timer running out. on( name, handler ) is how a script says call me when that happens, and it is the only way in.
Calls handler every time the event name happens. name is one of the cheat's events below, or "game:" followed by the name of an event of the game itself (see game events). Returns a subscription you can keep to cancel it later; most scripts never do.
name - string. An unknown name raises unknown event 'x' at once, so a typo cannot hide.
handler - function. What it receives depends on the event.
Several handlers for one event are called in the order they were added; handlers of different scripts are called in the alphabetical order of the scripts. A handler may cancel itself, or add another handler, while it runs.
Once per rendered frame, on the render thread, after the cheat's own overlays and before the menu - so the menu is always on top of what you draw, unless you ask for the front layer with c:layer( "front" ).
The canvas is what you draw with. It is only valid while the handler runs; calling a method on one you kept raises this canvas is only valid inside the callback that received it.
Once per command build, on the game thread, before the cheat's own features run. That ordering is the point. The usual shape of a script here is read the situation, then change a setting for this tick - adapt a hitchance to the distance, force a shot while airborne - and a change made in this handler is what the features see on this very tick. Dispatching after them would apply every such change a tick late.
cmd is the command of the tick; see Editing the command. It is only valid inside the handler.
move only runs while the local player is alive and there is a camera - the same conditions the cheat's own features need. It does not run while you are dead or spectating; frame still does.
The same command, after every feature has had its say and right before it is finalised. The last word on buttons, angles and movement. The input history has already been written by then, so do not rely on changing it from here.
Called around the game's frame-stage callback - stage just before the game's own handler for that stage, stage_post just after - with the stage number, once for every stage the game goes through, several times per rendered frame. Three stages have names in game.stage: net_commit (6), pre_render (7) and render_start (12).
on( "stage", function( stage )
if stage == game.stage.pre_render then
-- once per frame, right before the world is rendered
end
end )
This is also where the runtime does the engine work that scripts queue from other threads, so it is the event to use for anything that has to run on the game's thread every frame.
The camera as it is about to be rendered, after the cheat's own camera work. view reads and writes the camera directly - see Camera & projection. It is only valid inside the handler.
on( "view", function( view )
view:set_fov( 110 )
end )
Window messages: key presses, typed text, mouse buttons, wheel and movement. The handler runs on the window's own thread, before the menu and before the game see the message. Return true and the message goes to neither.
msg is a table whose kind field says what it is - the message kinds are listed below.
-- swallow the middle mouse button while a script wants it
on( "input", function( msg )
if msg.kind == "mouse_down" and msg.button == 3 then
return true
end
end )
Once for every player the overlay draws, right after the built-in elements - box, bars, name, weapon, flags. player is the player's controller as an entity; esp is an object that adds text, bars and icons around the player's box and keeps them out of the way of the overlay's own elements. See esp.
on( "esp", function( player, esp )
if esp:enemy( ) then
esp:text( "bottom", player:name( ), color( 255, 255, 255 ) )
end
end )
The last call before the script's state is closed - when the script is reloaded or switched off, or the cheat shuts down. Put things back here: a convar the script changed, a setting, a file that should be flushed. Particles a script created are destroyed for you; controls, handlers, timers, fonts, textures and shaders disappear with the state.
The handler has half a second. An error in it is reported but does not stop the unload.
local cvar = game.cvar( "cl_showfps" )
local before = cvar and cvar:int( )
on( "unload", function( )
if cvar and before then
cvar:set_int( before )
end
end )
The game announces things as it happens: a player was hurt, a round started, a bomb was planted. A script listens to any of them by putting game: in front of the event's name:
on( "game:round_start", function( ev )
ui.notify( "new round" )
end )
The runtime registers the listener with the game when the first handler for a name appears and removes it when the last one is cancelled. If the game has no event of that name - or refuses to listen - a line says so in the Scripts tab output, and the handler never runs. The registration happens on the game's thread at the next frame stage, so an event fired in the very instant after on() is missed; a script that subscribes while it loads does not notice.
The handler receives an event object. It reads the fields of the event by name, and it is only valid inside the handler.
A field read as an integer and compared with zero. A field the game stores as a real boolean may read as false whatever it holds; if that happens, use ev:int.
The controller of the player a user-id field points to - "userid", "attacker", "assister". nil when there is none, for instance when the attacker was the world.
The fields x, y and z of the event as a point - what bullet_impact carries. With a prefix it reads <prefix>x, <prefix>y and <prefix>z.
on( "game:bullet_impact", function( ev )
local shooter = ev:player( "userid" )
local where = ev:position( )
if shooter and shooter == ents.local_player( ) then
print( string.format( "my bullet hit %.0f %.0f %.0f", where.x, where.y, where.z ) )
end
end )
These are the game's own events, with the fields they typically carry. The list is a starting point, not a catalogue: any event the game defines can be subscribed to, and a field that does not exist simply reads as the default.
Event
Fields
player_hurt
userid and attacker (players), dmg_health, dmg_armor, health, hitgroup
A timer calls a function later, once or repeatedly. Timers are checked once per rendered frame on the render thread, so they are as precise as a frame is long - good for a flash that fades after two seconds, not for anything that needs a millisecond.
Calls handler repeatedly, every seconds. The period is at least a millisecond. The next call is scheduled before the handler runs, so a slow handler does not stretch the period, and cancelling the timer from inside the handler works.
Whether the timer is still pending. A one-shot timer is not active any more once it has fired.
-- flash a message for two seconds
local shown_until = 0
on( "game:bomb_planted", function( ev )
shown_until = render.time( ) + 2
end )
on( "frame", function( canvas )
if render.time( ) < shown_until then
canvas:text( 20, 60, "BOMB PLANTED", color( 244, 63, 94 ) )
end
end )
The example above does the same job without a timer: comparing against render.time() in the frame handler is often simpler than scheduling something that undoes itself.