Commands (SourceMod Scripting)
SourceMod allows you to create console commands similar to AMX Mod X. There are two main types of console commands:
- Server Commands, which are fired from one of the following input methods:
- the server console itself;
- the remove console (RCON);
- the ServerCommand() function, either from SourceMod or the Half-Life 2 engine.
- Console Commands, which are fired from one of the following input methods:
- a client's console
- any of the server command input methods
For server commands, there is no client index. For console/client commands, there is a client index, but it may be 0 to indicate that the command is from the server.
Note that button "commands," such as +attach and +duck, are not actually console commands. They are a separate command stream sent over the network, as they need to be tied to a specific frame.
Contents
Server Commands
As noted above, server commands are fired through the server console, whether remote, local, or through the Source engine. There is no client index associated with a server command.
Server commands are registered through the RegServerCmd() function, listed in console.inc. When registering a server command, you may be hooking an already existing command, and thus whether you return Plugin_Handled or Plugin_Continue is important.
- Plugin_Continue: The original server command will be processed, if there was one. If the server command was created by a plugin, this has no effect.
- Plugin_Handled: The original server command will not be processed, if there was one. If the server command was created by a plugin, this has no effect.
- Plugin_Stop: This original server command will not be processed, if there was one. Additionally, no further hooks will be called for this command until it is fired again.
Adding Server Commands
Let's say we want to add a test command to show how Half-Life 2 breaks down command arguments. A possible implementation might be:
public OnPluginStart() { RegServerCmd("test_command", Command_Test) } public Action:Command_Test(args) { new String:arg[128] new String:full[256] GetCmdArgString(full, sizeof(full)) PrintToServer("Argument string: %s", full) PrintToServer("Argument count: %d", args) for (new i=1; i<=args; i++) { GetCmdArg(i, arg, sizeof(arg)) PrintToServer("Argument %d: %s", i, arg) } }
Blocking Server Commands
Let's say we wanted to disable the "kickid" command on the server. There's no real good reason to do this, but for example's sake:
public OnPluginStart() { RegServerCmd("kickid", Command_KickId) } public Action:Command_Kickid(args) { return Plugin_Handled }
Console Commands
Unlike server commands, console commands can be triggered by either the server or the client, so the callback function receives a client index as well as the argument count. If the server fired the command, the client index will be 0.
When returning the Action from the callback, the following effects will happen:
- Plugin_Continue: The original functionality of the command (if any) will still be processed. If there was no original functionality, the client will receive "Unknown command" in their console.
- Plugin_Handled: The original functionality of the command (if any) will be blocked. If there was no functionality originally, this prevents clients from seeing "Unknown command" in their console.
- Plugin_Stop: Same as Plugin_Handled, except that this will be the last hook called.
Note that, unlike AMX Mod X, SourceMod does not allow you to register command filters. I.e., there is no equivalent to this notation:
register_clcmd("say /ff", "Command_SayFF");
This notation was removed to make our internal code simpler and faster. Writing the same functionality is easy, and demonstrated below.
Adding Commands
Adding client commands is very simple. Let's port our earlier testing command to display information about the client as well.
public OnPluginStart() { RegConsoleCmd("test_command", Command_Test) } public Action:Command_Test(client, args) { new String:arg[128] new String:full[256] GetCmdArgString(full, sizeof(full)) if (client) { new String:name[32] GetClientName(client, name, sizeof(name)) PrintToServer("Command from client: %s", name); } else { PrintToServer("Command from server."); } PrintToServer("Argument string: %s", full) PrintToServer("Argument count: %d", args) for (new i=1; i<=args; i++) { GetCmdArg(i, arg, sizeof(arg)) PrintToServer("Argument %d: %s", i, arg) } }
Hooking Commands
A common example is hooking the say command. Let's say we want to tell players whether FF is enabled when they say '/ff' in game.
Before we implement this, a common point of confusion with the 'say' command is that Half-Life 2 (and Half-Life 1), by default, send it with the text in one big quoted string. This means if you say "I like hot dogs," your command will be broken down as such:
- Argument string: "I like hot dogs"
- Argument count: 1
- Argument #1: I like hot dogs
However, if a player types this in their console: say I like yams, it will be broken up as:
- Argument string: I like hot dogs
- Argument count: 4
- Argument #1: I
- Argument #2: like
- Argument #3: yams
Thus, to take into account both of these situations, we are going to use GetCmdArgString and manually parse the input.
public OnPluginStart() { RegConsoleCmd("say", Command_Say) } public Action:Command_Say(client, args) { new String:text[192] GetCmdArgString(text, sizeof(text)) new startidx = 0 if (text[0] == '"') { startidx = 1 /* Strip the ending quote, if there is one */ new len = strlen(text); if (text[len-1] == '"') { text[len-1] = '"' } } if (StrEqual(text[startidx], "/ff")) { new Handle:ff = FindConVar("mp_friendlyfire") if (GetConVarInt(ff)) { PrintToConsole(client, "Friendly fire is enabled.") } else { PrintToConsole(client, "Friendly fire is disabled.") } /* Block the client's messsage from broadcasting */ return Plugin_Handled } /* Let say continue normally */ return Plugin_Continue }