Skip to content

Console Commands

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

minqlxtended.console_command(cmd) runs the command, and it has finished by the time the call returns.

minqlxtended.console_command("set g_gravity 200")
minqlxtended.get_cvar("g_gravity")     # 200

Two kinds of call can't work that way and go to the engine's command buffer instead, taking effect a frame or so later.

Commands that reload the game module

Changing the map unloads the game module from inside SV_SpawnServer, and plugins reach Python through hooks that sit under its vmMain: chat through G_Say, @next_frame and @delay through G_RunFrame, a vote handler through Cmd_CallVote_f. Running one of these where it was asked for means returning into code that has just been unloaded. The engine's own header says as much: EXEC_NOW is "a VM should NEVER use this, because some commands might cause the VM to be unloaded".

There are seven, taken from the callers of SV_SpawnServer, SV_RestartGameProgs and SV_Shutdown:

Command Handler Releases the module by
map, devmap SV_Map_f SV_SpawnServer
arena SV_Arena_f SV_SpawnServer
map_restart SV_MapRestart_f SV_RestartGameProgs, or SV_SpawnServer after a latched cvar change
startRandomMap SV_StartRandomMap_f formats a map command and runs it with EXEC_NOW
killserver SV_KillServer_f SV_Shutdown
quit Com_Quit_f SV_Shutdown

These go to the command buffer and run from Com_Frame, where nothing of ours is on the stack. A passed callvote map has always taken that route, which is why votes never crashed when !map did.

vstr and exec are not on the list despite carrying arbitrary text: Cmd_Vstr_f and Cmd_Exec_f push it through Cbuf_InsertText and return, so it runs from Com_Frame whoever asked. That is how the game module changes the map: it sets nextmap and sends vstr nextmap.

Calls from a thread that isn't the game's

The engine's command text belongs to the game thread, so a @minqlxtended.thread worker's command is queued for the game thread to hand over on the next frame.

What this means for a plugin

  • Don't read back what a map did. Game.change_map() returns before the map has loaded. Hook map or new_game to act on the new one. Everything else, cvar writes included, is readable straight after.
  • minqlxtended.redirect_print() captures whatever prints inside the block, and nests. It won't see the output of a map, which runs after the block has closed.
  • A command longer than 1023 characters raises ValueError, that being what Cmd_TokenizeString accepts. A worker thread's command arriving while the queue is full raises EngineStateError.

Game.change_map() is the single place the map command is built, so Plugin.change_map(), the Game.map and Game.factory setters and a raw console_command("map ...") all get this.

Diagnostic commands

Run these from the server console.

Command Prints
qlx_prof Frame cost per probe. See Internals
qlx_pyperf CPython perf trampoline state. See Internals
qlx_reliable How far behind each client is on its 64-slot reliable ring
qlx_scoreboard What the team scoreboard trim is holding back

qlx_reliable reset and qlx_scoreboard reset zero their counters.

Clone this wiki locally