ConVars & Commands
Console and chat commands, listening in on other commands, and console variables — what Source2Mod gives you and where each piece lives.
Everything a server admin or a player types goes through the engine's console
system: mp_roundtime 5 in the server console, jointeam 2 from a client,
!rtv in chat. The s2m hooks that dispatch once, in the core, and hands
plugins three things on top of it:
- Commands of your own — a console command that also answers in chat.
- Listeners — a callback on a command somebody else owns (the game, the engine, another plugin), before or after it runs, with the power to block it.
- ConVars — your own console variables, and typed access to the game's.
The interfaces are ISource2ModCommands and
ISource2ModConVars. Both come filled in by
SOURCE2MOD_SAVEVARS() as g_pSource2ModCommands and g_pSource2ModConVars, and both
headers ship short macros (REGISTER_CON_COMMAND, CVAR_GET_INT, …). None of
the calls takes a plugin id: a handler is a lambda, a function or an object and
a method (SOURCE2MOD_MEMBER(this, &MyPlugin::OnCmd)), and the core reads which
plugin it belongs to off the handler itself. Scripts reach the same through the
Source2ModCommands, Source2ModConVars, CConVar and CConVarRef
methodmaps, with named public functions as handlers.
Console commands and chat commands
A command registered with REGISTER_CON_COMMAND("mycmd", …) is reachable three ways:
| Typed where | Example | Caller |
|---|---|---|
| Server console / RCON | mycmd 1 2 | no player (slot -1) |
| A client's console | mycmd 1 2 | that player's slot |
| Chat | !mycmd 1 2 or /mycmd 1 2 | that player's slot |
Chat works through chat triggers. When a chat message starts with a
trigger, the core strips the prefix, splits the rest into arguments and
dispatches it exactly like a console command. The triggers come from the core
config, addons/source2mod/configs/core.json:
{
"PublicChatTrigger": [ "!" ],
"SilentChatTrigger": [ "/" ]
}- A public trigger (
!by default) runs the command and the message still shows in chat for everyone. - A silent trigger (
/by default) runs the command and swallows the message — nobody sees it.
Both are lists, so a server can add its own prefixes (".", say). Your code
never sees the prefix: !mycmd a b arrives as Arg(0) == "mycmd", Arg(1) == "a".
If you only want the chat side — a word players type, with no console command
behind it — use REGISTER_CHAT_LISTENER instead. See Commands.
Listening to commands you do not own
REGISTER_CON_LISTENER puts a callback on an existing command: jointeam,
say, an engine command, another plugin's command. A pre listener can return
Action::Supersede and the command never runs — that is a complete "no
spectators" plugin in ten lines. See Command listeners.
ConVars
A ConVar is a named, typed value in the console: mp_freezetime,
sv_cheats, or one your plugin declares (myplugin_enabled). Source2Mod side
covers:
- declaring your own with s2sdk's
CConVar<T>(the recommended way), or at runtime throughISource2ModConVars::CreateConVar; - reading and writing the game's ConVars — typed
CConVarRef<T>, the index-basedISource2ModConVarsgetters/setters, or the one-shotUTIL_SetConVarhelper; - one change callback for every ConVar in the process (
HOOK_CONVAR_CHANGE), which takes capturing lambdas.
See ConVars. Sending one player a different value than everybody else is its own topic: Replication.
The core unlocks hidden and development-only commands and ConVars at startup
(UnlockConCommands and UnlockConVars in core.json, both on by default), so
everything the game ships is reachable from the console and from plugins.
Ownership and unloading
Every command, listener and change callback belongs to the plugin whose
handler it is. When the plugin unloads, Source2Mod drops all of them. You only
call Unregister* / Unhook* — with the Source2ModHookId the registration
returned, or with the same function or object and method again — to switch
something off while the plugin stays loaded.
The one exception is a CConVar you declared yourself: it lives in your
library, so your Unload() has to call ConVar_Unregister(). The
ConVars page explains why. A
script's CConVar belongs to the core, so a script has nothing to undo.