-
Notifications
You must be signed in to change notification settings - Fork 3
Writing Plugins
A plugin is a class inheriting minqlxtended.Plugin, in a .py file under
qlx_pluginsPath, named after the file.
import minqlxtended
class my_plugin(minqlxtended.Plugin):
@minqlxtended.hook("chat")
def handle_chat(self, player, msg, channel, recipient):
...
@minqlxtended.command(("sv_fps", "svfps"), permission=5, usage="<integer>")
def cmd_svfps(self, player, msg, channel):
...Decorators register when the plugin is constructed, and take the same arguments as
add_hook and add_command:
| Decorator | Registers |
|---|---|
@minqlxtended.hook(event, priority=Priority.NORMAL) |
An event handler |
@minqlxtended.command(name, permission=0, channels=None, usage="") |
A chat/console command |
@minqlxtended.vote(name, usage="", description="") |
A custom vote |
minqlxtended.setting(name, default, type=str) |
A qlx_* cvar, as a class attribute |
add_hook, add_command and add_vote still work and are interchangeable with the
decorators. Use them from __init__ when the registration depends on something only known at
runtime, like a cvar or the gametype. infectedmm.py in the plugins repository attaches and
detaches its hooks as the gametype changes.
Stacking with @thread, @delay and @next_frame is supported; put the registration
decorator outermost. A handler whose signature doesn't fit its event is refused at
registration, not at first dispatch.
Anything on the game thread comes out of the frame budget, so blocking there makes players lag.
| Decorator | Runs |
|---|---|
@minqlxtended.thread |
On a fresh thread. For I/O and anything slow |
@minqlxtended.next_frame |
On the game thread, at the start of the next frame |
@minqlxtended.delay(seconds) |
On the game thread, after the delay |
Engine views are game-thread-only. Do the slow work in a @thread worker, then hand the
engine part to @next_frame.
Never put
@threadon a command handler. ItsReturn.USAGEnever reaches the command invoker. Validate arguments on the calling thread and move the slow part into an inner threaded helper.
Work scheduled with @next_frame or @delay is dropped once the plugin is unloaded, the
unload half of a !reload included. A @thread worker cannot be cancelled, so a loop that
shouldn't outlive its plugin must check self.is_loaded.
Use Plugin.resolve_player() and Plugin.resolve_identifier() to take a <id> argument.
Hand-rolling int() plus Plugin.player() answers None for an empty slot rather than
raising, and an int outside [0, MAX_CLIENTS) falls through to a by-name search, so
!slap 999 looks for a player called "999".
resolve_identifier() returns a named tuple of (steam_id, name, player), where player is
None for someone not connected.
Each client has a 64-slot reliable command ring, and overflow is dropped rather than delayed.
Use Plugin.reply_lines(recipient, lines) to send several lines as one command, and
Plugin.tell_many(players, msg), which broadcasts when the recipients are everyone
connected. A loop of tell() is what fills the ring.
| Return | Effect |
|---|---|
None / Return.NONE
|
Carry on to the next handler |
Return.STOP |
Stop calling handlers |
Return.STOP_EVENT |
Let the remaining handlers run, cancel the event |
Return.STOP_ALL |
Stop the handlers and cancel the event |
Return.USAGE |
Commands only: print the usage string |
Return is not an IntEnum, so a bare False, True, 0 or 1 matches no member and is
reported as a value the event didn't understand.