register_event is the oldest and still the most widely used hook in AMX Mod X. It listens for network messages the game DLL sends to clients — DeathMsg, CurWeapon, Health, DamageEnd, and dozens more — and fires your function when one arrives. It hooks nothing inside the engine and needs no external module beyond core AMXX, which is exactly why it survives on every stack from HLDS to ReHLDS. This guide covers the flag string, the condition arguments that trip up most people, how to read message arguments, and where register_event_ex improves on it.
1. The signature and where to register
Register events once, in plugin_init(). The call takes the event name, your callback, a flag string, and any number of condition strings:
#include <amxmodx>
public plugin_init()
{
register_plugin("Event Demo", "1.0", "CSB")
register_event("DeathMsg", "on_death", "a")
register_event("CurWeapon", "on_curweapon", "be", "1=1")
}
The event names are the message names the game DLL emits. You can list them live with message_begin logging, but the common ones are stable across CS 1.6.
2. The flag string
The third argument is a string of single-character flags. The letters you will actually use:
| Flag | Meaning |
|---|---|
a | Fire only when the message is broadcast globally (sent to all players) |
b | Fire per receiving client — the message is directed at one player, and the first parameter of your handler is that player's index |
d | Only fire when the receiving player is dead |
e | Only fire when the receiving player is alive |
A broadcast event like DeathMsg uses a. A per-client event like CurWeapon (the engine tells each player about their own weapon) uses b, usually with e so it only runs for living players. That is why the classic speed/weapon idiom is "be".
3. Condition strings
Everything after the flag string is a condition on a numbered message argument. The number is the argument index (1-based), followed by a comparison operator and a value:
// only fire when arg 1 equals 1
register_event("CurWeapon", "on_curweapon", "be", "1=1")
// only fire when arg 2 (weapon id) is the AWP
register_event("CurWeapon", "on_awp", "be", "1=1", "2=19")
// fire when arg 1 is greater than 0
register_event("Health", "on_low_hp", "be", "1>0")
Multiple conditions are ANDed together. Operators are =, ! (not equal), <, >, and & (bitwise-and non-zero). For CurWeapon, arg 1 is whether the weapon is active and arg 2 is the weapon id — filtering in the condition string is far cheaper than filtering with if inside a handler that fires on every weapon switch.
4. Reading message arguments in the handler
Inside the callback, pull argument values with read_data. Integer args use read_data(n), floats use read_data(n, Float:...) via the float form, and strings use the buffer form:
public on_death()
{
new killer = read_data(1) // attacker index
new victim = read_data(2) // victim index
new headshot = read_data(3) // 1 if headshot
if (killer == victim || !killer)
return
new name[32]
get_user_name(killer, name, charsmax(name))
client_print(0, print_chat, "[CSB] %s got a kill%s",
name, headshot ? " (headshot)" : "")
}
With flag b, the affected player is passed as the handler's first parameter, so declare it: public on_curweapon(id). With a, there is no per-player id — you read indices out of the message data instead, as above.
5. register_event_ex
The letter-flag string is compact but easy to get wrong, and a typo in it is silent. register_event_ex is the newer form: it takes the same event name and callback, but the flags argument is a bitmask built from named RegisterEvent_* constants instead of a cryptic string, so a mistake is a compile error rather than a runtime surprise:
register_event_ex("CurWeapon", "on_curweapon",
RegisterEvent_OnePlayer | RegisterEvent_Alive)
The exact constant names are defined in amxmodx.inc — open the include and read them rather than guessing, because they are the authoritative list. Functionally it is the same hook; prefer it in new code for readability. Condition strings still work the same way as trailing arguments.
Common errors
- Handler never fires — wrong flag. A per-client event registered with
a(or vice versa) simply never matches. Try registering with no flags first to confirm the event name is right, then add filters. - Handler fires but
idis 0 — you used flagaand declaredpublic handler(id). Global events do not pass a player id; read indices from the data instead. - Condition never matches — argument indices are 1-based and per-event. Log
read_data(0)(the argument count) and each argument to discover the real layout before hard-coding a condition. - Reads return garbage — you called
read_datawith the wrong type. Use the float form for float args; an integer read of a float argument returns its bit pattern.
Verification
Add a temporary server_print("event fired, args=%d", read_data(0)) to the top of the handler, load the plugin, and trigger the event on the server. The console line proves the registration matches. Once it fires reliably, tighten it with condition strings and remove the print. If you are new to the language used here, start with Pawn basics for AMXX; to understand when an event hook is the wrong tool, read register_event vs Hamsandwich vs ReAPI.









