HOOZiDocs
Skip to content

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 below
  • fn : function — callback

Event names + callback signatures

Event nameCallback signatureFields
"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_index for 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_update vs render_post z-order: frame_update writes vtx that lands before ESP / FOV / menu drawings; render_post writes vtx that lands after. Both use screen-relative coordinates (system handles viewport offset uniformly). For HUD-style mods overlaying ESP, use render_post; for underlying information display, use frame_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 in frame_update or render_post and 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.

lua
-- 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

lua
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)