Game events
You listen, you never subscribe. aqua already shadows the engine’s event
dispatcher (CGameEventManager::FireEventClientSide), so every networked event
passes through it with AddListener called zero times. aqua.events only
tells aqua which fields to copy out - the engine never learns a listener
exists, and there is no script-reachable way to register one. That is deliberate:
a registration sits in a game-owned, enumerable list, and one that outlives an
unload is a call into unmapped memory.
aqua.events.listen(name, fields) -> bool
Section titled “aqua.events.listen(name, fields) -> bool”Declare an event and the field keys you want off it. You must name the keys: the engine’s getters are by-name and there is no key enumeration we can walk, so aqua cannot guess what you came for.
Two forms, and you can mix them:
aqua.events.listen("player_hurt", { "userid", "dmg_health" }) -- array: read as intaqua.events.listen("player_death", { weapon = "string", userid = "int" }) -- map: explicit type- Types are
"int"(default),"float","bool","string"and"player".playerreturns the identity table described below; other types retain their existing number/string/boolean results. - A conflicting type for a key already requested by another script rejects your
entire update and returns false. Its previous subscription is retained.
intandplayercan coexist: each script receives its requested representation. - Unknown event names and unknown keys are not errors: you get
0/"". Nothing validates against the game’s event schema. - Calling it again for the same event replaces your script’s keys and discards
its pending deliveries from the old subscription.
{}listens without fields. aqua.events.unlisten(name)removes this script’s subscription to that event.aqua.events.clear()removes this script’s event subscriptions. Other scripts and automatic shot callbacks retain their subscriptions.- Subscriptions and pending delivery are removed on stop, error or unload.
Examples
Section titled “Examples”Resolve player fields directly, including the local player on the first event:
aqua.events.listen("player_hurt", { userid = "player", attacker = "player", dmg_health = "int",})
function on_event(name, data) if name == "player_hurt" and data.userid and data.userid.isLocalPlayer then print("Local damage: " .. data.dmg_health) endendA player field always includes userid (the original integer, default 0) and
resolved. When resolved it also includes index, handle, pawnHandle,
steamId, name, team and isLocalPlayer. Match handle with the local or
other get_players() entries when needed. Full handles include serial numbers;
SteamIDs stay strings. Bots can resolve with steamId="0", so SteamID alone
cannot distinguish bots.
Resolution uses the engine’s event-player getter, not an assumed userid/index
formula. Identity is copied at event capture and remains the captured identity
even if the player leaves before delivery. A world attacker, unknown player,
new player awaiting its first snapshot or recycled identity produces
resolved=false with no guessed player fields. Fields unavailable because the
native getter is unresolved can be absent entirely. Existing array/int fields
continue returning the raw number.
player_hurt - the fields the built-in hitmarker uses:
aqua.events.listen("player_hurt", { "userid", "attacker", "health", "armor", "dmg_health", "dmg_armor", "hitgroup"})player_death:
aqua.events.listen("player_death", { userid = "int", attacker = "int", assister = "int", weapon = "string", headshot = "bool", noscope = "bool",})These two are examples, not a supported list - any event the server sends
works the same way. Name it, name its keys, read them in on_event.
Limits worth knowing
Section titled “Limits worth knowing”- No
uint64getter bound.int,float,boolandstringare; a 64-bit field (e.g. an xuid) will not read correctly through any of them. userid="int"is the raw event number. Requestuserid="player"(and likewise attacker/assister) for resolved identity; do not infer an entity index.- The queue holds
256events between frames. Past that, events are dropped and aWARNINGnaming the count goes to the log rather than the queue growing. - Delivery is once per frame, batched - not the instant the engine fires.