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 whereExampleCaller
Server console / RCONmycmd 1 2no player (slot -1)
A client's consolemycmd 1 2that player's slot
Chat!mycmd 1 2 or /mycmd 1 2that 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:

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 through ISource2ModConVars::CreateConVar;
  • reading and writing the game's ConVars — typed CConVarRef<T>, the index-based ISource2ModConVars getters/setters, or the one-shot UTIL_SetConVar helper;
  • 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.

On this page