event
Event subscriptions. Subscriptions are revoked automatically when the script is unloaded; no need to hand-write event.off.
event.on(name, fn) → handle
Parameters
name : string— event name, one of the table belowfn : function— callback
Event names + callback signatures
| Event name | Callback signature | Fields |
|---|---|---|
"frame_update" | fn(e) | e.delta_time : number — z-order below ESP |
"render_post" | fn(e) | e.delta_time : number — z-order above ESP / FOV / menu |
"config_changed" | fn(e) | e.module_name : string — fires on config modify |
"script_loaded" | fn(e) | e.script_path : string, e.is_reload : bool |
"script_unloaded" | fn(e) | e.script_path : string |
"menu_toggled" | fn(e) | e.visible : bool — fires on MenuOpen hotkey edges |
"weapon_changed" | fn(e) | e.prev_weapon : int, e.curr_weapon : int — local held-weapon edge (sdk::ItemId) |
"map_changed" | fn(e) | e.prev_map : string, e.curr_map : string — map switch (level_name edge) |
"player_knocked" | fn(e) | Knocked down (is_down false→true). Shares player lifecycle fields (below) |
"player_revived" | fn(e) | Revived (is_down true→false, still alive) |
"player_died" | fn(e) | Killed (is_dead false→true) |
"player_respawned" | fn(e) | Respawned (is_dead true→false) |
All events share these base fields: e.timestamp : int (millis unix ts, lazy-computed), e.source : string.
Player lifecycle events share these fields: e.player_index : int, e.name : string, e.team_num : int, e.is_local : bool, e.is_teammate : bool.
Why 4 separate player event names (not a single state field):
- Handlers skip the
if state == "knocked"branching boilerplate. - Subscribing to only what you care about avoids unrelated dispatches — EventBus matches by
type_indexfor zero waste.
The render thread diffs every player against last frame's state and only publishes on edges — no per-frame spam. A player seen for the first time does not produce an event (avoids false reports on initialization). weapon_changed / map_changed use the same edge semantics.
frame_updatevsrender_postz-order:frame_updatewrites vtx that lands before ESP / FOV / menu drawings;render_postwrites vtx that lands after. Both use screen-relative coordinates (system handles viewport offset uniformly). For HUD-style mods overlaying ESP, userender_post; for underlying information display, useframe_update.Both fire once per render frame (bound to the screen render cadence — at 144Hz / 240Hz, 144/240 times per second).
draw.*must be hooked inframe_updateorrender_postand called every frame — the foreground draw list is reset every frame; missing a frame produces a flicker.Heavy work (data computation, networking, etc.) should be throttled by the script itself by accumulating
e.delta_time. See Getting Started.
Returns: handle : number — used for event.off.
event.off(handle)
Explicitly unsubscribe (usually not needed manually).
Custom Events (cross-script)
Beyond the built-in events above, any other event name goes through the Lua custom-event channel.
event.on(name, fn) → handle
Any name not in the built-in event list automatically uses the custom channel. The fn signature is function(...) — it receives all arguments forwarded from event.emit(name, ...).
event.emit(name, ...)
Fires a custom event, dispatching synchronously to all handlers registered under name. Emitting a built-in event name is not allowed (to prevent scripts from forging system events); attempting to emit "frame_update" etc. is rejected.
Handler exceptions are caught and do not interrupt subsequent dispatch. When a script is unloaded, all custom-event subscriptions registered by that script are revoked automatically.
-- Script A: listener
event.on("hp_changed", function(player_name, new_hp, old_hp)
print(player_name, "hp:", old_hp, "→", new_hp)
end)
-- Script B: broadcast
event.emit("hp_changed", "alice", 30, 100)Example
event.on("frame_update", function(e)
-- Called every render frame; dt is equivalent to time.delta()
do_something(e.delta_time)
end)
event.on("script_unloaded", function(ue)
if ue.script_path ~= _SCRIPT_PATH then return end
-- Only handle our own unload (rare cases: flush buffers)
end)
-- Menu visibility: toast follows menu
event.on("menu_toggled", function(e)
if e.visible then gui.notify:info("menu opened") end
end)
-- Player lifecycle: teammate knocked alert
event.on("player_knocked", function(e)
if e.is_teammate then gui.notify:warn(e.name .. " knocked!", 3) end
end)
event.on("player_died", function(e)
if e.is_local then gui.notify:error("You died", 2) end
end)
-- HUD overlays ESP: use render_post, not frame_update
event.on("render_post", function(e)
draw.text(draw.font("default"), 24, 100, 100, 0xFFFFFFFF, "On top of ESP")
end)
-- Weapon swap: reset per-weapon state
event.on("weapon_changed", function(e)
print(string.format("weapon: %d → %d", e.prev_weapon, e.curr_weapon))
end)
-- Map change: reset round-local stats
event.on("map_changed", function(e)
print("map:", e.prev_map, "→", e.curr_map)
end)