Skip to content
Thomas Jones edited this page Aug 17, 2026 · 1 revision

Hook one with @minqlxtended.hook("name"). See Writing Plugins for registration and return values.

game_start, game_end, round_end, team_switch, kill and death are read out of the game module, so zmq_stats_enable 1 is not required. The ZMQ listener is only needed by plugins hooking the raw stats event.

Players

Event Arguments
player_connect (player, is_bot)
player_loaded (player)
player_disconnect (player, reason)
player_spawn (player)
team_switch_attempt (player, old_team, new_team, target)
team_switch (player, old_team, new_team)
userinfo (player, changed, infostring)
kill (victim, killer, mod)
death (victim, killer, mod)
damage (target, attacker, damage, dflags, mod)
weapon_fired (player, weapon)
item_pickup (player, item_name)
objective (player, kind, count)
kamikaze_use (player)
kamikaze_explode (player, is_used_on_demand)

kill fires only when another player was responsible; death always fires, with killer None when nobody was. Changing team does not raise death.

team_switch_attempt comes from SetTeam, so cancelling prevents the switch outright. It fires for admin puts, duel-queue promotion, follow-cycling and level exit as well as a player typing team X. target is the raw argument, which tells a follow1 from an ordinary spectate. Cancelling it to enforce a team lock blocks admins too.

damage fires after the damage is applied, so the target's health already reflects it. attacker is None for lava, a trigger_hurt or a fall, and is the target on self-damage. It cannot be cancelled: G_Damage applies armour, powerups and scoring internally and takes its direction and impact point by pointer. Damage to shootable world entities is filtered out.

Match

Event Arguments
game_countdown ()
game_start ()
game_end (aborted)
round_countdown (round_number)
round_start (round_number)
round_end (round_number, winning_team, time)

round_end reports the winner by watching which team's score went up, so it behaves the same in Clan Arena, Freeze Tag, Red Rover and Attack & Defend, and answers None on a draw. time is the round's duration in milliseconds. Its round number is the one round_start gave.

aborted is true when the match ended without a result.

Server

Event Arguments
frame ()
map (map, factory)
new_game ()
spawn_server ()
chat (player, msg, channel, recipient)
client_command (player, command)
server_command (player, command)
console_print (text)
set_configstring (index, value)
cvar_changed (name, old_value, new_value)
demo_finished (client_id, path, size, discarded, failed)
unload (plugin)

chat comes from G_Say, so the message arrives exactly as the engine has it and cancelling stops it being said. Private messages (/tell) raise it too, with recipient set to the player who was told and None for anything said openly. The channel addresses the speaker, so a !command typed into a tell answers whoever typed it.

cvar_changed fires only for a write that changed the live value.

demo_finished carries a client id rather than a Player, because the player it recorded has usually left by then. discarded means the segment held nothing but a gamestate and was removed, so there is no file at path.

Votes

Event Arguments
vote_called (player, vote, args)
vote_started (player, vote, args)
vote (player, yes)
vote_ended (votes, vote, args, passed)

vote_called fires while another vote is already running, and for votes the engine goes on to reject. Hook vote_started for the votes that actually run. vote fires whether or not a vote is running. vote_ended also fires for votes abandoned by intermission or a level change, reported as not passed.

A passing vote's tally is one short: the frame that tips it over the line never records the deciding vote. An expiry reports the true count.

What a map change looks like

A map change is not one moment. The order is: spawn_server, then the engine tears the old level down and builds the new one, then map and new_game.

Everything in between still reaches your handlers. SV_SpawnServer drops every bot through the same path a real disconnect takes, so a devmap raises player_disconnect for each of them. It writes ten cvars, so cvar_changed fires repeatedly. It prints throughout, so does console_print. It runs the game module's frame export a few times with the entity hooks live.

Plugin.game is None for the whole of that window. The configstring cache is dropped when spawn_server fires and CS_SERVERINFO is not rewritten until the end.

A handler touching minqlxtended.level, an Entity or a GameClient during that window raises EngineStateError rather than reading freed memory. The game module is genuinely unmapped for part of it. Hook map or new_game, which are the first events for which the new world is complete.

Clone this wiki locally