# Introduction (/docs)
Source2Mod is a server modification for **Counter-Strike 2**. Plugins are
written in **C++** or **SourcePawn** — both are first-class and use the same
API: direct access to Source 2 internals (the schema system, entities, hooks,
memory, network messages) together with the high-level pieces every server
plugin needs: commands, ConVars, game events, menus, timers, translations,
HTTP, SQL and more.
Source2Mod grew out of [Source2Toolkit](https://github.com/Source2Toolkit/source2toolkit):
the C++ core and SDK carried over, and SourcePawn was added on top of it.
Who it is for [#who-it-is-for]
* **C++ developers** who want native code with nothing in between, and who
are comfortable reading engine code when they need to.
* **SourcePawn developers** coming from SourceMod: the language they know,
with an API that mirrors the C++ one name for name.
* **Server owners** who run plugins built on Source2Mod: installing it is a
Metamod plugin plus a folder of `.smx` files — native and script plugins
alike.
How it fits with Metamod:Source [#how-it-fits-with-metamodsource]
Source2Mod is itself a **Metamod:Source plugin**. Metamod loads the core
(`addons/source2mod/bin/...`), and the core then loads your plugins — `.smx`
files from `addons/source2mod/plugins/`, native libraries and compiled
SourcePawn scripts side by side — and hands each of them the Source2Mod API.
That split is deliberate:
* A plugin talks to one API — `ISource2ModAPI` and the `g_pSource2Mod*`
interfaces in C++, the `Source2Mod*` methodmaps in SourcePawn — instead of
fetching engine interfaces, scanning signatures and wiring listeners itself.
* Everything a plugin registers — commands, event hooks, timers, net message
hooks, menus — is owned by that plugin and removed when it unloads.
* Hooks use **KHook**, the detour library inside Metamod:Source. Metamod,
Source2Mod and every plugin share one KHook instance, so hooks placed by
different plugins on the same function chain correctly.
* C++ plugins can give scripts natives of their own, and scripts can share
natives and forwards with each other.
The core also resolves engine functions from a shared gamedata folder, so most
plugins never ship a signature of their own.
Features [#features]
Engine access [#engine-access]
* **Schema system** — generated classes for the game's schema (`CBaseEntity`, `CCSPlayerController`, …) with typed field access and network state updates, in C++ and as SourcePawn methodmaps.
* **Entities** — find, create and spawn entities, entity listeners and entity I/O.
* **Hooks** — game hooks, KHook virtual, vtable and function detours, and raw hooks from SourcePawn described at runtime.
* **Addresses** — engine functions the core already resolved (spawn, take damage, set model, switch team, …), ready to call.
* **GameConfig** — signatures, offsets and patches from JSON gamedata, per platform.
* **Memory & modules** — pattern scans, symbol lookups and typed casts on any loaded library.
* **Game systems** — register your own `IGameSystem` on the engine's factory list.
Gameplay [#gameplay]
* **Commands** — console and chat commands, plus listeners on commands you do not own.
* **[Permissions](/docs/guides/permissions)** — admins, groups and dotted permissions from a file or granted at runtime, commands gated by them.
* **[Clients & targeting](/docs/guides/clients-and-targeting)** — players by slot, SteamID or name, `@all` / `@ct` style targets with immunity.
* **ConVars** — declare your own, read and write the game's, hear about changes.
* **Game events** — pre/post hooks with field access, `dontBroadcast` and cancel, and firing your own.
* **Network messages** — allocate, send and hook Source 2 net messages by id or by name.
* **Menus** — center-screen menus with pages and submenus; keys 1–9 work without binds.
* **[Translations](/docs/guides/translations)** — phrase files per plugin in every player's own language.
* **Custom HUD** — Panorama layouts with click callbacks.
* **Scheduler** — timers and next-frame tasks.
* **Tracing** — ray and hull traces for line-of-sight and collision queries.
* **Sounds** — sound events for exactly the players you pick, stop and change by guid, per-player volume channels and a hook on every sound the game sends.
* **Transmit** — per-viewer entity visibility, owner-following and a CheckTransmit hook.
* **[Scripts](/docs/guides/scripts)** — run cs\_script (compiled assets, raw source, hot-reloaded files), fire inputs into a script and receive its messages.
Services [#services]
* **[SQL](/docs/guides/sql)** — MySQL and SQLite, queries off the main thread, named connections from `databases.json`.
* **HTTP** — asynchronous requests through Steam's HTTP client.
* **JSON, [files and KeyValues](/docs/guides/files-and-keyvalues)** — parse and build JSON, read and write files, a streaming KeyValues parser.
* **[Client preferences](/docs/guides/client-preferences), [GeoIP, map lists](/docs/guides/maps-geoip-features)** — per-player settings that persist, a player's country, the map cycle and next map.
* **Paths** — the game directory, Source2Mod root, configs, gamedata, plugins and a shared data folder.
* **[Crash handler](/docs/guides/crash-handler)** — minidumps processed on the server itself, plugin frames symbolized to `function @ file:line`, a suspected culprit and an optional Discord report.
Where to go next [#where-to-go-next]
Install Metamod:Source and Source2Mod on a CS2 server.
A minimal native plugin, from class to built
`.smx`
.
The same API as a script: a first plugin and how to compile it.
Natives and forwards, entity handles, raw hooks, coming from SourceMod.
The same concepts side by side, a plugin ported, and C++ for C# developers.
Every Source2Mod interface, generated from the SDK headers.
Generated schema classes, fields and helpers.
Declare ConVars, register console and chat commands.
Finding, creating and listening to entities.
Network messages and the game's protobufs.
Game events: hooking, reading, changing and firing.
Custom HUD layouts and their callbacks.
KHook detours on virtual and non-virtual functions.
License [#license]
Source2Mod and its SDK are licensed under the **GPLv3**, with an **MIT exception for derivative works**: plugins built against the SDK may use any license you choose, including closed-source and commercial terms. Source2Mod and the SDK themselves, and modified versions of them, stay under the GPLv3. SourcePawn and the parts of the include set that come from SourceMod are AlliedModders LLC's and keep their own license terms. See the [FAQ](/docs/resources/faq) for details.
# Installation (/docs/installation)
Source2Mod is a Metamod:Source plugin, so a server needs Metamod first and Source2Mod on top of it. Both are folders you copy into `game/csgo/`. Linux and Windows servers are both supported.
Prerequisites [#prerequisites]
* A Counter-Strike 2 dedicated server you can restart and reach the console of.
* [Metamod:Source 2.x](https://www.metamodsource.net/downloads.php?branch=dev) — a current dev build, matching your OS.
* The latest [Source2Mod release](https://github.com/Source2Mod/source2mod/releases) — `source2mod_-linux.tar.gz` or `source2mod_-windows.tar.gz`.
CS2 updates regularly break older Metamod builds. When the server stops loading Metamod after a game update, update Metamod before looking anywhere else.
Install Metamod:Source [#install-metamodsource]
Download the Metamod:Source build for your operating system and extract it.
Copy its `addons/` folder into your server's `game/csgo/` folder, so that you end up with `game/csgo/addons/metamod/`.
Open `game/csgo/gameinfo.gi`, find the line
```
Game_LowViolence csgo_lv // Perfect World content override
```
and add this line **below** it:
```
Game csgo/addons/metamod
```
Start the server and run `meta list` in its console. If the command is known, Metamod is loaded.
Game updates overwrite `gameinfo.gi`. If Metamod disappears after an update, the `Game csgo/addons/metamod` line is the first thing to check.
Install Source2Mod [#install-source2mod]
Download the release archive for your operating system and extract it. It contains a single `addons/` folder.
Copy that `addons/` folder into `game/csgo/`, merging it with the one Metamod created.
Restart the server. Source2Mod has to be loaded at server start: loading it later with `meta load` is refused (`Late load is not supported`).
What the release contains [#what-the-release-contains]
```
game/csgo/addons/
├── metamod/
│ └── source2mod.vdf # tells Metamod to load Source2Mod
└── source2mod/
├── bin/
│ └── linuxsteamrt64/ # win64/ in the Windows archive
├── configs/
│ └── core.example.json # template for core.json
├── gamedata/
│ └── gamedata.json # the core's signatures and offsets
├── plugins/ # your .smx plugins go here
├── shared/ # data more than one plugin reads
└── dumps/ # crash dumps land here
```
| Folder | What it is for |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bin/` | The s2m core binary. |
| `configs/` | `core.json`, `permissions.json` (admins and groups, see [Permissions](/docs/guides/permissions)), and one `configs//` folder per plugin for its own config files (`PLUGIN_CONFIG_DIR()`). |
| `gamedata/` | Every `.json` file here is loaded at start. Plugins that need signatures of their own ship an extra file next to `gamedata.json` — see [Gamedata](/docs/development/gamedata). |
| `plugins/` | Every `.smx` file here is loaded at start. |
| `shared/` | Data files read by more than one plugin (`SOURCE2MOD_SHARED_DIR()`). |
| `dumps/` | Minidumps and crash reports written by the [crash handler](/docs/guides/crash-handler). |
Missing folders are created on start, so deleting an empty one does no harm.
Core configuration [#core-configuration]
On its first start the core copies `configs/core.example.json` to `configs/core.json` and reads that from then on. Releases only ship the example, so updating Source2Mod never overwrites your settings.
```json title="configs/core.json"
{
"PublicChatTrigger": [ "!" ],
"SilentChatTrigger": [ "/" ],
"PluginHotReloadEnabled": true,
"PluginAutoLoadEnabled": true,
"UnlockConCommands": true,
"UnlockConVars": true,
"CrashHandlerEnabled": true,
"CrashDiscordWebhook": "",
"SlowCallbackWarnMs": 100,
"SteamAuthMode": "auto",
"AccessDeniedMessage": "You do not have access to this command."
}
```
The file may contain `//` and `/* */` comments.
| Key | Default | Meaning |
| ------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PublicChatTrigger` | `["!"]` | Chat prefixes that run a command. `!mycommand` runs the plugin command `mycommand` and the message still shows in chat. |
| `SilentChatTrigger` | `["/"]` | Same, but the chat message is suppressed. |
| `PluginHotReloadEnabled` | `true` | Linux only: reload a plugin when its `.smx` is rewritten. See [Plugin lifecycle](/docs/development/plugin-lifecycle#hot-reload). |
| `PluginAutoLoadEnabled` | `true` | Load every `.smx` in `plugins/` at start. When `false`, nothing loads until you run `s2m refresh` or `s2m load`. |
| `UnlockConCommands` | `true` | Remove `FCVAR_HIDDEN` and `FCVAR_DEVELOPMENTONLY` from every console command, so hidden commands can be used. |
| `UnlockConVars` | `true` | The same for ConVars. |
| `CrashHandlerEnabled` | `true` | Install the crash handler. |
| `CrashDiscordWebhook` | `""` | Discord webhook URL for crash reports. Empty disables Discord reporting; dumps are still written. |
| `SlowCallbackWarnMs` | `100` | A plugin callback that takes longer than this is logged with its name and plugin — see [Compatibility](/docs/development/compatibility#when-the-server-stalls). `0` turns it off. |
| `SteamAuthMode` | `"auto"` | Which SteamID a player's [permissions](/docs/guides/permissions#which-steamid-counts) are looked up under: `"auto"`, `"flexible"` or `"strict"`. |
| `AccessDeniedMessage` | `"You do not have access to this command."` | What a player is told when refused a command, unless a plugin set its own answer. |
The file is read once, when the core loads. Restart the server after changing it.
Installing plugins [#installing-plugins]
Copy a plugin's `.smx` file into `addons/source2mod/plugins/`. If it comes with configs, gamedata or shared data, those go into `configs/`, `gamedata/` and `shared/` as its instructions say.
On a running server, `s2m refresh` loads every `.smx` in `plugins/` that is not loaded yet; a new gamedata file, however, is only read when the server restarts.
One plugin most servers want: [**CustomhudManager\_s2t**](https://github.com/SlynxCZ/CustomhudManager_s2t/releases), the Panorama HUD — menus on the HUD, on-screen texts, toasts, timers. Plugins that draw on the HUD ask for its interface at load; without it they draw nothing. Its players need the addon described in [HUD menus, texts and prompts](/docs/panorama/hud#the-addon).
Verifying the installation [#verifying-the-installation]
Run `meta list` in the server console. **Source2Mod** should be in the list of Metamod plugins.
Then ask Source2Mod itself. The console command is `s2m` (with the aliases `source2mod` and `sm`):
```
s2m version
s2m list
```
| Command | What it does |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `s2m list` | Lists loaded plugins as `[id] name (version) by author`, with a second line per plugin: its plugin API version, the KHook commit it was built against and its tier — *stable* (no KHook hooks of its own: a game or KHook change needs only a core update) or *raw* (rebuild). See [Compatibility](/docs/development/compatibility). |
| `s2m info ` | Name, version, author, description, file path, plugin API, KHook commit, how many hooks of its own it declared, and every Source2Mod interface (with revision) it asked for. |
| `s2m load ` | Loads the first `.smx` in `plugins/` whose file name contains ``. |
| `s2m unload ` | Unloads a plugin by the id `s2m list` shows. |
| `s2m refresh` | Loads every `.smx` in `plugins/` that is not loaded yet. |
| `s2m version` | Source2Mod version, plugin API version, build date and commit. |
| `s2m credits` | Who made it. |
`load`, `unload` and `refresh` work only from the server console (or RCON). Players can run `list`, `info`, `version` and `credits` from their own console.
When something does not load [#when-something-does-not-load]
* **Source2Mod is not in `meta list`** — Metamod is not loading it. Check that `addons/metamod/source2mod.vdf` exists and that the `bin/` folder for your OS is there.
* **A plugin is missing from `s2m list`** — the core prints why at start: `Plugin refused to load: ` or `Failed to load : `, followed by a summary line with the number that failed. A `KHook mismatch` reason means the plugin was built against a different KHook than the core runs; see the [FAQ](/docs/resources/faq#a-plugin-refuses-to-load-with-khook-mismatch).
* **The server crashes on start** — see the [crash handler guide](/docs/guides/crash-handler); the report on the next start names the module that crashed.
# Command Listeners (/docs/convars-commands/command-listeners)
A listener is a callback on a command that already exists: `jointeam`, `say`,
`kill`, an engine command, another plugin's command. It runs *around* the
command rather than replacing it, and a pre listener decides whether the
command runs at all.
```cpp title="plugin.cpp"
#include "source2mod/ISource2ModCommands.h"
// No spectators: refuse "jointeam 1".
REGISTER_CON_LISTENER("jointeam", [](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post) -> Action
{
if (args.ArgC() >= 2 && atoi(args.Arg(1)) == 1)
{
Reply(context, "Spectating is disabled on this server.");
return Action::Supersede;
}
return Action::Ignore;
}, false);
```
```cpp title="plugin.sp"
public void OnPluginStart()
{
Source2ModCommands.RegisterConListener("jointeam", Listen_JoinTeam, false);
}
// No spectators: refuse "jointeam 1".
public Source2ModAction Listen_JoinTeam(Source2ModCommandContext context, Source2ModCommandArgs args, bool post)
{
char team[8];
args.Arg(1, team, sizeof(team));
if (args.ArgC() >= 2 && Source2ModUtils.StringToInt(team) == 1)
{
Reply(context, "Spectating is disabled on this server.");
return Action_Supersede;
}
return Action_Ignore;
}
```
(`Reply` is the helper from the [Commands](/docs/convars-commands/commands#the-caller) page.)
The API [#the-api]
```cpp title="ISource2ModCommands.h"
using CommandHandler = Source2ModCallback;
virtual Source2ModHookId RegisterConListener(const char* pchName, CommandHandler handler, bool post) = 0;
virtual bool UnregisterConListener(const char* pchName, const CommandHandler& handler, bool post) = 0;
virtual bool UnregisterConListener(Source2ModHookId id) = 0;
#define REGISTER_CON_LISTENER(pchName, handler, post) g_pSource2ModCommands->RegisterConListener(pchName, handler, post)
```
* `pchName` — the command to listen to, in **lower case** (the dispatcher
lower-cases what was typed before it looks for listeners).
* `post` — `false` to run before the command, `true` to run after it. Register
twice if you need both.
* The handler is a lambda, a function or an object and a method
(`SOURCE2MOD_MEMBER(this, &MyPlugin::OnJoinTeam)`); the core reads which plugin
it belongs to off the handler, so there is no plugin id to pass. It gets the
same `Source2ModCommandContext` and `Source2ModCommandArgs` a command handler
gets — see [arguments and the caller](/docs/convars-commands/commands#arguments).
* In a script it is `Source2ModCommands.RegisterConListener(name, handler, post)`
with a named `public` function that returns a `Source2ModAction`.
A plugin can register several listeners on the same command. They are called in
registration order. `RegisterConListener` returns a `Source2ModHookId`;
`g_pSource2ModCommands->UnregisterConListener(id)` drops that one listener, and
`UnregisterConListener(name, handler, post)` drops a function or
object-and-method listener passed again (a lambda only goes by id). A script
unregisters by id only. Unloading the plugin drops all of them.
```cpp
// in Load()
REGISTER_CON_LISTENER("say", OnPlayerChat, false);
// later, to turn the filter off
g_pSource2ModCommands->UnregisterConListener("say", OnPlayerChat, false);
```
```cpp
int g_ChatFilter;
// in OnPluginStart()
g_ChatFilter = Source2ModCommands.RegisterConListener("say", OnPlayerChat, false);
// later, to turn the filter off
Source2ModCommands.UnregisterConListener(g_ChatFilter);
```
Return values [#return-values]
Listeners return an [`Action`](/docs/core-api/ISource2ModTypes) (in a script
`Action_Ignore`, `Action_Override`, `Action_Supersede`):
| Return | Pre (`post == false`) | Post (`post == true`) |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `Action::Ignore` | Nothing changes; the next listener runs, then the command. | Nothing. |
| `Action::Override` | The command still runs, but the remaining pre listeners are skipped — and for a console command the post listeners too. | Nothing. |
| `Action::Supersede` | The command does **not** run. The remaining listeners — pre and post — are skipped. | The command already ran; only the remaining post listeners are skipped. |
In practice: return `Ignore` when you only watch, `Supersede` when you block,
and avoid `Override` — for a command there is no return value to override, and
it silently cuts the other listeners off.
A `Supersede` from your listener also stops every listener registered after it,
including other plugins' and the handler of a command registered with
`REGISTER_CON_COMMAND` on the same name. Block only what you mean to block.
Pre and post together [#pre-and-post-together]
```cpp title="plugin.cpp"
REGISTER_CON_LISTENER("jointeam", [](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post) -> Action
{
// Pre: decide.
return Action::Ignore;
}, false);
REGISTER_CON_LISTENER("jointeam", [](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post) -> Action
{
// Post: the team change has been processed -- react to it.
SOURCE2MOD_LOG(&g_Plugin, "Slot %d ran \"%s\"\n", context.GetPlayerSlot().Get(), args.GetCommandString());
return Action::Ignore;
}, true);
```
```cpp title="plugin.sp"
public void OnPluginStart()
{
Source2ModCommands.RegisterConListener("jointeam", Listen_JoinTeamPre, false);
Source2ModCommands.RegisterConListener("jointeam", Listen_JoinTeamPost, true);
}
public Source2ModAction Listen_JoinTeamPre(Source2ModCommandContext context, Source2ModCommandArgs args, bool post)
{
// Pre: decide.
return Action_Ignore;
}
public Source2ModAction Listen_JoinTeamPost(Source2ModCommandContext context, Source2ModCommandArgs args, bool post)
{
// Post: the team change has been processed -- react to it.
char command[256];
args.GetCommandString(command, sizeof(command));
Source2ModApi.Log("Slot %d ran \"%s\"", context.GetPlayerSlot().Get(), command);
return Action_Ignore;
}
```
Which commands can be listened to [#which-commands-can-be-listened-to]
The core hooks the engine's command dispatch, so a listener sees:
* every console command run from the **server console** or RCON;
* console commands a **client** runs that go through the engine's command
system;
* **`jointeam`**, which the game handles as a raw client command — the core
routes it to listeners separately;
* **chat**: `say` and `say_team` (see below), and every chat command typed with
a [chat trigger](/docs/convars-commands#console-commands-and-chat-commands).
A client command the game handles itself, outside the console system, does not
reach listeners (apart from `jointeam`). For those, hook
`ISource2GameClients::ClientCommand` with `KHOOK_VIRTUAL` — the sample plugin
does exactly that; see [Hooking](/docs/hooks#virtual-hooks).
Filtering chat [#filtering-chat]
Plain chat messages arrive as `say` (all chat) and `say_team` (team chat). A
pre listener on them is a chat filter; `Supersede` drops the message. This is
how FUNPLAY's Public plugin stops empty and repeated messages:
```cpp title="chat.cpp"
static Action OnPlayerChat(const Source2ModCommandContext& ctx, const Source2ModCommandArgs& args, bool post)
{
CCSPlayerController* player = CCSPlayerController::FromSlot(ctx.GetPlayerSlot().Get());
if (!player)
return Action::Ignore;
// The message is everything after "say", possibly wrapped in quotes.
std::string message = args.ArgS();
if (!message.empty() && message.front() == '"') message.erase(message.begin());
if (!message.empty() && message.back() == '"') message.pop_back();
if (message.empty())
return Action::Supersede;
return Action::Ignore;
}
// in Load()
REGISTER_CON_LISTENER("say", OnPlayerChat, false);
REGISTER_CON_LISTENER("say_team", OnPlayerChat, false);
```
```cpp title="chat.sp"
public Source2ModAction OnPlayerChat(Source2ModCommandContext ctx, Source2ModCommandArgs args, bool post)
{
CPlayerSlot slot = ctx.GetPlayerSlot();
if (!slot.IsValid() || CCSPlayerController.FromSlot(slot) == NULL_ENTITY)
return Action_Ignore;
// The message is everything after "say", possibly wrapped in quotes.
char message[256];
args.ArgS(message, sizeof(message));
int len = Source2ModUtils.Length(message);
if (len > 0 && message[len - 1] == '"')
message[--len] = '\0';
int start = (message[0] == '"') ? 1 : 0;
if (message[start] == '\0')
return Action_Supersede;
return Action_Ignore;
}
// in OnPluginStart()
Source2ModCommands.RegisterConListener("say", OnPlayerChat, false);
Source2ModCommands.RegisterConListener("say_team", OnPlayerChat, false);
```
A message that starts with a chat trigger (`!`, `/`, …) does **not** reach the
`say` listeners — the core dispatches it as the command it names instead. With a
pre listener on that command name you can see it (and, with `Supersede`, hide
the chat line); a `say` filter only ever sees ordinary chat.
Listeners vs. commands [#listeners-vs-commands]
| You want to… | Use |
| ----------------------------------------- | ------------------------------------------------------------------------------ |
| Add a new command | [`REGISTER_CON_COMMAND`](/docs/convars-commands/commands) |
| Add a chat word without a console command | [`REGISTER_CHAT_LISTENER`](/docs/convars-commands/commands#chat-only-commands) |
| Watch or block an existing command | `REGISTER_CON_LISTENER` |
| Replace an existing command | `REGISTER_CON_LISTENER`, pre, do your thing, return `Action::Supersede` |
In a script the three are `Source2ModCommands.RegisterConCommand`,
`RegisterChatListener` and `RegisterConListener`.
Full reference: [`ISource2ModCommands`](/docs/core-api/ISource2ModCommands).
# Commands (/docs/convars-commands/commands)
A plugin command is one call in `Load()` (in a script, `OnPluginStart()`). Source2Mod creates the console
command, routes the chat aliases to it, and removes it again when your plugin
unloads.
```cpp title="plugin.cpp"
#include "source2mod/ISource2ModCommands.h"
bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS();
REGISTER_CON_COMMAND("hello", [](const Source2ModCommandContext& ctx, const Source2ModCommandArgs& args, bool post)
{
SOURCE2MOD_CONPRINTF("hello from slot %d: %s\n", ctx.GetPlayerSlot().Get(), args.ArgS());
});
return true;
}
```
```cpp title="plugin.sp"
public void OnPluginStart()
{
Source2ModCommands.RegisterConCommand("hello", Cmd_Hello);
}
public void Cmd_Hello(Source2ModCommandContext ctx, Source2ModCommandArgs args, bool post)
{
char text[256];
args.ArgS(text, sizeof(text));
Source2ModApi.ConPrintf("hello from slot %d: %s", ctx.GetPlayerSlot().Get(), text);
}
```
`hello` now works from the server console, from a client's console, and from
chat as `!hello` or `/hello`.
The API [#the-api]
```cpp title="ISource2ModCommands.h"
using ChatHandler = Source2ModCallback;
using CommandHandler = Source2ModCallback;
virtual Source2ModHookId RegisterChatListener(const char* pchName, ChatHandler handler, const char* pchPermission = nullptr) = 0;
virtual bool UnregisterChatListener(const char* pchName, const ChatHandler& handler) = 0;
virtual bool UnregisterChatListener(Source2ModHookId id) = 0;
virtual Source2ModHookId RegisterConCommand(const char* pchName, ChatHandler handler, const char* pchPermission = nullptr) = 0;
virtual bool UnregisterConCommand(const char* pchName, const ChatHandler& handler) = 0;
virtual bool UnregisterConCommand(Source2ModHookId id) = 0;
virtual Source2ModHookId RegisterConListener(const char* pchName, CommandHandler handler, bool post) = 0;
virtual bool UnregisterConListener(const char* pchName, const CommandHandler& handler, bool post) = 0;
virtual bool UnregisterConListener(Source2ModHookId id) = 0;
virtual void ReplyToCommand(const Source2ModCommandContext& ctx, const char* pszMessage) = 0;
```
Scripts call the same methods on `Source2ModCommands`
(`source2mod/commands.inc`); there the ids are plain `int`s and unregistering
goes by id only.
There is no plugin id argument. A handler is a `Source2ModCallback`: a lambda, a
free or static function, or an object and a method, and the core reads which
plugin it belongs to off the handler itself:
```cpp
REGISTER_CON_COMMAND("hello", OnHello); // function
REGISTER_CON_COMMAND("hello", &Commands::OnHello); // static method
REGISTER_CON_COMMAND("hello", SOURCE2MOD_MEMBER(this, &MyPlugin::OnHello)); // object and method
REGISTER_CON_COMMAND("hello", [](auto& ctx, auto& args, bool post) {}); // lambda
```
`SOURCE2MOD_MEMBER(inst, func)` (or `{ this, &MyPlugin::OnHello }`) and
`SOURCE2MOD_STATIC(func)` are SourceHook's `SH_MEMBER` / `SH_STATIC` spelling of
the same thing. Inside a macro argument use `SOURCE2MOD_MEMBER`: the comma in a
bare `{ this, &MyPlugin::OnHello }` would split it into two arguments.
C++ only — a script's handler is always a named `public` function:
`Source2ModCommands.RegisterConCommand("hello", Cmd_Hello);`.
The macros just forward:
| Macro | Calls |
| -------------------------------------------- | ------------------------------------------ |
| `REGISTER_CON_COMMAND(name, handler)` | `RegisterConCommand(name, handler)` |
| `REGISTER_CHAT_LISTENER(name, handler)` | `RegisterChatListener(name, handler)` |
| `REGISTER_CON_LISTENER(name, handler, post)` | `RegisterConListener(name, handler, post)` |
There are no unregister macros; call `Unregister*` on `g_pSource2ModCommands`
(in a script, on `Source2ModCommands`)
(see [Unregistering](#unregistering)).
Commands and chat listeners take a `ChatHandler`, which returns nothing. A
handler that needs to block something is a *listener*, which returns an
`Action` — see [Command listeners](/docs/convars-commands/command-listeners).
Full reference: [`ISource2ModCommands`](/docs/core-api/ISource2ModCommands).
Console commands [#console-commands]
`REGISTER_CON_COMMAND(name, handler)` does two things:
1. Creates a real engine `ConCommand` called `name`, so the console knows it
(autocomplete, `find`, no "Unknown command").
2. Registers your handler for `name` and for the chat forms, so `!name` and
`/name` in chat reach the same code.
If the name already exists in the engine — the game's own command or another
plugin's — Source2Mod cannot take it over. It logs a warning and registers
only the handler, so your code still runs whenever that command is dispatched.
To *replace* someone else's command, use a pre
[listener](/docs/convars-commands/command-listeners) that returns
`Action::Supersede` instead.
Register names in **lower case**. The dispatcher lower-cases the typed command
before looking up handlers, so a handler registered as `"MyCmd"` is never
found — while `!mycmd` and `!MyCmd` in chat both reach one registered as `"mycmd"`.
Arguments [#arguments]
`Source2ModCommandArgs` is the tokenized command line — Source2Mod's own type,
built at the dispatch boundary, so neither the engine's `CCommand` nor
`CCommandContext` is part of the plugin API (their layout can change without a
rebuild). The calls are the ones the engine type has:
| Call | Returns |
| ------------------------- | -------------------------------------------------- |
| `args.ArgC()` | number of tokens, **including** the command itself |
| `args.Arg(0)` | the command name |
| `args.Arg(i)` | token `i` (1-based for real arguments) |
| `args.ArgS()` | everything after the command name, as one string |
| `args.GetCommandString()` | the whole line |
From chat the trigger is already stripped: `!hello a b` gives
`Arg(0) == "hello"`, `Arg(1) == "a"`, `ArgC() == 3`. `Arg(i)` past the end is
`""`, never null; the strings belong to the dispatch and are gone when the
handler returns — copy what has to outlive it. In a script, `Arg`, `ArgS` and
`GetCommandString` copy into a buffer you pass.
```cpp title="plugin.cpp"
REGISTER_CON_COMMAND("sample_echo", [](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post)
{
if (args.ArgC() < 2)
{
Reply(context, "Usage: sample_echo ");
return;
}
CCSPlayerController* pCaller = CallerOf(context);
Reply(context, "%s said: %s (%d arguments)",
pCaller ? pCaller->GetPlayerName() : "Console", args.ArgS(), args.ArgC() - 1);
});
```
```cpp title="plugin.sp"
// in OnPluginStart()
Source2ModCommands.RegisterConCommand("sample_echo", Cmd_Echo);
public void Cmd_Echo(Source2ModCommandContext context, Source2ModCommandArgs args, bool post)
{
if (args.ArgC() < 2)
{
Reply(context, "Usage: sample_echo ");
return;
}
char text[256], name[64] = "Console";
args.ArgS(text, sizeof(text));
CCSPlayerController caller = CallerOf(context);
if (caller != NULL_ENTITY)
caller.GetPlayerName(name, sizeof(name));
Reply(context, "%s said: %s (%d arguments)", name, text, args.ArgC() - 1);
}
```
The caller [#the-caller]
`Source2ModCommandContext::GetPlayerSlot()` tells you who ran the command. A player has
a valid slot; the server console (and RCON) has none — the slot is `-1` and
`IsValid()` is false.
```cpp title="plugin.cpp"
// A controller for a player, nullptr for the server console.
static CCSPlayerController* CallerOf(const Source2ModCommandContext& context)
{
const CPlayerSlot slot = context.GetPlayerSlot();
return slot.IsValid() ? CCSPlayerController::FromSlot(slot) : nullptr;
}
```
```cpp title="plugin.sp"
// A controller for a player, NULL_ENTITY for the server console.
CCSPlayerController CallerOf(Source2ModCommandContext context)
{
CPlayerSlot slot = context.GetPlayerSlot();
return slot.IsValid() ? CCSPlayerController.FromSlot(slot) : NULL_ENTITY;
}
```
`GetSource()` says where it was typed:
| `Source2ModCommandSource` | Typed |
| ------------------------- | -------------------------------------------------------------- |
| `Console` | in a console — the server's, RCON, or the player's own |
| `Chat` | in chat with a public trigger (`!kick`); the message shows |
| `SilentChat` | in chat with a silent trigger (`/kick`); the message is hidden |
`IsFromChat()` is true for both chat kinds.
Replying [#replying]
`ReplyToCommand(ctx, message)` answers where the command came from: the
player's chat for a chat command, the player's console for one typed there,
the server console for the server. `REPLY_TO_COMMAND` formats first; a
script's `Source2ModCommands.ReplyToCommand(ctx, format, ...)` formats itself:
```cpp
REGISTER_CON_COMMAND("hp", [](const Source2ModCommandContext& ctx, const Source2ModCommandArgs& args, bool post)
{
CCSPlayerController* pPlayer = CallerOf(ctx);
CCSPlayerPawn* pPawn = pPlayer ? pPlayer->GetPlayerPawn() : nullptr;
if (!pPawn)
{
REPLY_TO_COMMAND(ctx, "Players only.");
return;
}
REPLY_TO_COMMAND(ctx, "You have %d HP.", pPawn->m_iHealth());
});
```
```cpp
// in OnPluginStart()
Source2ModCommands.RegisterConCommand("hp", Cmd_Hp);
public void Cmd_Hp(Source2ModCommandContext ctx, Source2ModCommandArgs args, bool post)
{
CCSPlayerController player = CallerOf(ctx);
CCSPlayerPawn pawn = player != NULL_ENTITY ? player.GetPlayerPawn() : NULL_ENTITY;
if (pawn == NULL_ENTITY)
{
Source2ModCommands.ReplyToCommand(ctx, "Players only.");
return;
}
Source2ModCommands.ReplyToCommand(ctx, "You have %d HP.", pawn.m_iHealth);
}
```
For a specific place regardless of where it was typed, use the controller's
`PrintToChat`, `PrintToConsole` or `PrintToCenter` on
[`CBasePlayerController`](/docs/schema/entity/classes/CBasePlayerController).
The `post` argument [#the-post-argument]
Command handlers are registered as *pre* handlers and are called once, with
`post == false`. The parameter is there because the type is shared with
listeners; for commands you can ignore it.
Permissions [#permissions]
The ConCommands Source2Mod creates are client-executable, so a command
registered without a permission runs for **any player**. Give it one and the
core checks it before your handler runs:
```cpp
REGISTER_CON_COMMAND("kick", OnKick, "myplugin.admin.kick");
```
```cpp
Source2ModCommands.RegisterConCommand("kick", OnKick, "myplugin.admin.kick");
```
A player without it does not reach the handler; they are told so where they
typed the command (core.json's `AccessDeniedMessage`, or a plugin's own
answer). The server console and RCON always may. The server owner can give
the command another permission under `Overrides` in `permissions.json`.
Who holds what — admins, groups, wildcards — is on the
[Permissions](/docs/guides/permissions) page.
For something only the server console should run, check the slot:
```cpp
REGISTER_CON_COMMAND("myplugin_reload", [](const Source2ModCommandContext& ctx, const Source2ModCommandArgs& args, bool post)
{
// Server console and RCON only.
if (ctx.GetPlayerSlot().IsValid())
return;
// ...
});
```
```cpp
public void Cmd_Reload(Source2ModCommandContext ctx, Source2ModCommandArgs args, bool post)
{
// Server console and RCON only.
if (ctx.GetPlayerSlot().IsValid())
return;
// ...
}
```
Chat-only commands [#chat-only-commands]
`REGISTER_CHAT_LISTENER(name, handler)` (`Source2ModCommands.RegisterChatListener`
in a script) registers the same handler without
creating a console command. It fires when a player types `!name` or `/name`
(or any other configured [chat trigger](/docs/convars-commands#console-commands-and-chat-commands))
in chat:
```cpp title="plugin.cpp"
REGISTER_CHAT_LISTENER("noshake", [this](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post)
{
const CPlayerSlot slot = context.GetPlayerSlot();
if (!slot.IsValid())
return;
m_NoShakeMask ^= (uint64_t(1) << slot.Get());
const bool bOff = (m_NoShakeMask >> slot.Get()) & 1;
Reply(context, "Screen shakes are now %s for you.", bOff ? "off" : "on");
});
```
```cpp title="plugin.sp"
int g_NoShakeMask[2]; // one bit per player slot
// in OnPluginStart()
Source2ModCommands.RegisterChatListener("noshake", Chat_NoShake);
public void Chat_NoShake(Source2ModCommandContext context, Source2ModCommandArgs args, bool post)
{
CPlayerSlot slot = context.GetPlayerSlot();
if (!slot.IsValid())
return;
if (PlayerMask_Has(g_NoShakeMask, slot.Get()))
PlayerMask_Remove(g_NoShakeMask, slot.Get());
else
PlayerMask_Add(g_NoShakeMask, slot.Get());
bool off = PlayerMask_Has(g_NoShakeMask, slot.Get());
Reply(context, "Screen shakes are now %s for you.", off ? "off" : "on");
}
```
A chat listener cannot hide the message: with the public trigger (`!`) it
stays visible, with the silent trigger (`/`) the core hides it anyway. To hide
`!name` messages too, register a pre [listener](/docs/convars-commands/command-listeners)
on `name` and return `Action::Supersede` — *after* the command or chat
listener, because a `Supersede` stops the handlers registered behind it.
Unregistering [#unregistering]
You rarely need to — everything is dropped when your plugin unloads. To turn a
command off while the plugin keeps running (a script goes by the id the
registration returned):
```cpp
// By the id the registration returned -- the only way for a lambda.
Source2ModHookId m_hHello = REGISTER_CON_COMMAND("hello", [](auto& ctx, auto& args, bool post) { /* ... */ });
g_pSource2ModCommands->UnregisterConCommand(m_hHello); // the handler and its chat forms
// Or by the same function / object and method again.
REGISTER_CHAT_LISTENER("noshake", SOURCE2MOD_MEMBER(this, &MyPlugin::OnNoShake));
g_pSource2ModCommands->UnregisterChatListener("noshake", SOURCE2MOD_MEMBER(this, &MyPlugin::OnNoShake));
```
```cpp
// By the id the registration returned -- the only way in a script.
int helloId = Source2ModCommands.RegisterConCommand("hello", Cmd_Hello);
Source2ModCommands.UnregisterConCommand(helloId); // the handler and its chat forms
int noshakeId = Source2ModCommands.RegisterChatListener("noshake", OnNoShake);
Source2ModCommands.UnregisterChatListener(noshakeId);
```
Each returns `true` when it found the handler. Several handlers may share one
command name; each runs, and unregistering removes only the one you name.
`UnregisterConCommand` removes your handler. The engine `ConCommand` stays
until the plugin unloads (it just does nothing in the meantime); registering
the same name again reuses it. On unload Source2Mod gives the name back to the
engine, so a reloaded plugin — or another one — can register it again.
See also [#see-also]
* [Command listeners](/docs/convars-commands/command-listeners) — hooking commands you do not own.
* [`ISource2ModCommands` reference](/docs/core-api/ISource2ModCommands)
* [`ISource2ModTypes`](/docs/core-api/ISource2ModTypes) — the `Action` enum.
# ConVars (/docs/convars-commands/convars)
ConVars in CS2 are s2sdk's `CConVar` and `CConVarRef` — Source2Mod does
not wrap them in a class of its own. What it adds is
[`ISource2ModConVars`](/docs/core-api/ISource2ModConVars): an index-based interface for
reading, writing and creating ConVars without templates, a process-wide change
callback that takes lambdas, and a few by-name helpers. Scripts get the same
through the `CConVar` and `CConVarRef` methodmaps and `Source2ModConVars`
(`source2mod/convars.inc`).
Declaring your own [#declaring-your-own]
In C++, declare a `CConVar` at file scope, then register the library's
ConVars once in `Load()`. A script creates each one with `CConVar.Create*` in
`OnPluginStart()`:
```cpp title="plugin.cpp"
CConVar myplugin_enabled("myplugin_enabled", FCVAR_NONE, "Turns the plugin on and off", true);
CConVar myplugin_bonus("myplugin_bonus", FCVAR_NONE, "Money given at round start", 1337);
CConVar myplugin_scale("myplugin_scale", FCVAR_NONE, "Damage multiplier",
1.0f, true, 0.0f, true, 10.0f); // min 0, max 10
bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS();
g_pCVar = api->GetCVar();
ConVar_Register(FCVAR_RELEASE | FCVAR_CLIENT_CAN_EXECUTE | FCVAR_GAMEDLL);
return true;
}
```
```cpp title="plugin.sp"
CConVar myplugin_enabled;
CConVar myplugin_bonus;
CConVar myplugin_scale;
public void OnPluginStart()
{
myplugin_enabled = CConVar.CreateBool("myplugin_enabled", FCVAR_NONE, "Turns the plugin on and off", true);
myplugin_bonus = CConVar.CreateInt("myplugin_bonus", FCVAR_NONE, "Money given at round start", 1337);
myplugin_scale = CConVar.CreateFloat("myplugin_scale", FCVAR_NONE, "Damage multiplier",
1.0, true, 0.0, true, 10.0); // min 0, max 10
}
```
* `g_pCVar` has to be set before `ConVar_Register()` — that is where the
registration goes.
* The flags passed to `ConVar_Register()` are added to every ConVar and
ConCommand declared in your library. The set above is what the sample plugin
and the core use.
* Read and write your own ConVar with `Get()` and `Set()`:
`if (myplugin_enabled.Get()) …`, `myplugin_bonus.Set(500);`.
* A script needs neither `g_pCVar` nor `ConVar_Register()`, and reads and
writes through the handle: `myplugin_enabled.BoolValue`,
`myplugin_bonus.IntValue = 500;`, `FloatValue`, `GetString()`/`SetString()`.
Constructors [#constructors]
```cpp title="tier1/convar.h (s2sdk)"
CConVar(const char* name, uint64 flags, const char* help_string, const T& default_value,
FnTypedChangeCallback_t cb = nullptr, ...);
CConVar(const char* name, uint64 flags, const char* help_string, const T& default_value,
bool min, const T& minValue, bool max, const T& maxValue,
FnTypedChangeCallback_t cb = nullptr, ...);
```
Typical `T`: `bool`, `int`, `float`, `double`, `CUtlString`, `Vector`,
`QAngle`, `Color`.
Flags [#flags]
The `FCVAR_*` flags come from s2sdk's `convar.h`. The ones you will use:
| Flag | Meaning |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `FCVAR_NONE` | Nothing special. |
| `FCVAR_REPLICATED` | The server's value is sent to clients and enforced there (movement, prediction). See [Replication](/docs/convars-commands/replication). |
| `FCVAR_NOTIFY` | Players are told in chat when it changes. |
| `FCVAR_PROTECTED` | A password or similar: the value is not sent out. |
| `FCVAR_CHEAT` | Only changeable with `sv_cheats 1`. |
| `FCVAR_HIDDEN` | Not listed by `find` or autocomplete. |
| `FCVAR_RELEASE` | Available in release builds — part of the usual `ConVar_Register` flags. |
Change callback on your own ConVar [#change-callback-on-your-own-convar]
The last constructor argument is a typed callback, a plain function pointer
(a lambda without captures converts to one). A script calls `AddChangeHook` on
its ConVar instead and gets the old and new value as text:
```cpp title="plugin.cpp"
CConVar myplugin_mode("myplugin_mode", FCVAR_NONE, "0 = off, 1 = on, 2 = debug", 1,
[](CConVar* cvar, CSplitScreenSlot slot, const int* pNewValue, const int* pOldValue)
{
SOURCE2MOD_CONPRINTF("myplugin_mode: %d -> %d\n", *pOldValue, *pNewValue);
});
```
```cpp title="plugin.sp"
CConVar myplugin_mode;
public void OnPluginStart()
{
myplugin_mode = CConVar.CreateInt("myplugin_mode", FCVAR_NONE, "0 = off, 1 = on, 2 = debug", 1);
myplugin_mode.AddChangeHook(OnModeChanged);
}
public void OnModeChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
Source2ModApi.Log("myplugin_mode: %s -> %s", oldValue, newValue);
}
```
When you need captures, or want to hear about ConVars that are not yours, use
[`HOOK_CONVAR_CHANGE`](#watching-changes) instead.
Unloading [#unloading]
Every C++ plugin that declares a `CConVar` must call `ConVar_Unregister()` in `Unload()`.
`ConVar_Register()` hands the engine pointers to the `CConVar` objects in your
library. When the library is unloaded those pointers dangle, and the next thing
that touches one — autocomplete, an `exec` — reads freed memory.
`ConVar_Unregister()` only walks the list of the library that calls it, so the
Source2Mod cannot do this for you:
```cpp title="plugin.cpp"
bool MyPlugin::Unload(char* error, size_t maxlen)
{
KHOOK_DESTRUCT();
ConVar_Unregister();
return true;
}
```
C++ only — a script has nothing to unregister: its `CConVar` belongs to the
core.
Reading and writing the game's ConVars [#reading-and-writing-the-games-convars]
`CConVarRef` — typed, for a ConVar you keep using [#cconvarreft--typed-for-a-convar-you-keep-using]
```cpp title="plugin.cpp"
CConVarRef mp_limitteams("mp_limitteams");
if (mp_limitteams.IsValidRef() && mp_limitteams.IsConVarDataAvailable())
{
int current = mp_limitteams.Get();
mp_limitteams.Set(5);
}
```
```cpp title="plugin.sp"
CConVarRef mp_limitteams = CConVarRef.Find("mp_limitteams");
if (mp_limitteams != null && mp_limitteams.IsValidRef() && mp_limitteams.IsConVarDataAvailable())
{
int current = mp_limitteams.IntValue;
mp_limitteams.IntValue = 5;
}
```
In a script, `CConVarRef.Find` returns `null` for a name nobody registered,
and the handle is not typed: `IntValue`, `FloatValue`, `BoolValue` and
`GetString()` convert. The list below is about C++'s `CConVarRef`.
Things the sample plugin spells out, all worth knowing:
* A `CConVarRef` to a name that is not registered yet **pre-registers** it
and reads default data until the real ConVar appears. Check
`IsConVarDataAvailable()`.
* The type must be right. A ref of the wrong type can keep the real ConVar
from registering, or leave you with an invalid ref — check `IsValidRef()`
before reading or writing. An invalid ref reads and writes shared dummy data.
* `Get()`/`Set()` are the fast typed path. `GetFloat()`, `GetBool()`,
`GetString()`, `SetFloat()`, … convert between types when possible.
A ref to a file-scope `static CConVarRef sv_autobunnyhopping("sv_autobunnyhopping");`
is a common pattern in FUNPLAY's plugins.
`ConVarRefAbstract` — untyped [#convarrefabstract--untyped]
```cpp
ConVarRefAbstract ref("mp_limitteams");
if (ref.IsValidRef())
{
ref.SetFloat(10.0f); // converting setters
float f = ref.GetFloat();
ref.SetAs(Vector(1.0f, 2.0f, 3.0f)); // for non-primitive types
CConVarRef typed(ref); // "promote" to a typed ref
if (typed.IsValidRef() && typed.IsConVarDataValid())
typed.Set(5);
}
```
`ConVarRefAbstract` does not pre-register anything; it is a plain reference.
C++ only — a script's ConVar handle is untyped to begin with: its accessors
convert, and `Source2ModConVars.SetVector3`, `SetQAngle`, `SetColor`, … write
the non-primitive types.
One-shot helpers by name [#one-shot-helpers-by-name]
`ISource2ModConVars.h` has three inline helpers for "change this and move on".
They look the ConVar up themselves and return `false` if it does not exist. A
script has no by-name helpers; it finds the ConVar and sets it on the handle
(`ConVar.Flags` holds the low 32 flag bits, `Source2ModConVars.GetFlags` /
`SetFlags` all 64):
```cpp
UTIL_SetConVar("mp_freezetime", 5);
UTIL_SetConVar("mp_roundtime", 1.92f);
UTIL_SetConVar("hostname", "My Server");
UTIL_RemoveConVarFlags("sv_jump_impulse", FCVAR_CHEAT); // settable without sv_cheats
UTIL_AddConVarFlags("myplugin_bonus", FCVAR_NOTIFY);
```
```cpp
// No by-name helpers: find the ConVar, check it, set it on the handle.
CConVarRef cvar;
if ((cvar = CConVarRef.Find("mp_freezetime")) != null)
cvar.IntValue = 5;
if ((cvar = CConVarRef.Find("mp_roundtime")) != null)
cvar.FloatValue = 1.92;
if ((cvar = CConVarRef.Find("hostname")) != null)
cvar.SetString("My Server");
if ((cvar = CConVarRef.Find("sv_jump_impulse")) != null)
cvar.Flags &= ~FCVAR_CHEAT; // settable without sv_cheats
if ((cvar = CConVarRef.Find("myplugin_bonus")) != null)
cvar.Flags |= FCVAR_NOTIFY;
```
For `UTIL_SetConVar` the *type* decides how the value is written, so spell it
out (`UTIL_SetConVar(…)`) when a literal would deduce to the wrong one — a
string literal always needs ``.
These go through s2sdk directly, so they also work before Source2Mod's ConVar
manager is up.
`ISource2ModConVars` — by access index [#isource2modconvars--by-access-index]
Every ConVar has a 16-bit access index. The interface is built around it, which
suits code that does not know the type at compile time. In a script the
ConVar handle takes the index's place:
```cpp
uint16 idx = CVAR_IDX("mp_roundtime"); // GetConvarAccessIndexByName
float roundTime = CVAR_GET_FLOAT(idx);
CVAR_SET_FLOAT(idx, 2.5f);
EConVarType type = g_pSource2ModConVars->GetType(idx);
uint64 flags = g_pSource2ModConVars->GetFlags(idx);
const char* help = g_pSource2ModConVars->GetHelpText(idx);
```
```cpp
// The ConVar handle is what the access index is in C++.
ConVar mp_roundtime = CConVarRef.Find("mp_roundtime");
float roundTime = mp_roundtime.FloatValue;
mp_roundtime.FloatValue = 2.5;
EConVarType type = Source2ModConVars.GetType(mp_roundtime);
int flags[2]; // all 64 bits, flags[0] the low half
Source2ModConVars.GetFlags(mp_roundtime, flags);
char help[256];
mp_roundtime.GetDescription(help, sizeof(help));
```
| Group | Methods |
| ------- | --------------------------------------------------------------------------------- |
| Lookup | `GetConvarAccessIndexByName`, `GetConvarRef`, `GetName`, `GetHelpText`, `GetType` |
| Flags | `GetFlags`, `SetFlags` (replaces the whole set) |
| Scalars | `GetString/Bool/Int/Float/Double`, `SetString/Bool/Int/Float/Double` |
| Vectors | `GetVector2/3/4`, `GetQAngle`, `GetColor` and their `Set…` |
| Raw | `GetValueAddress`, `GetValue`, `SetValue` |
Macros: `CVAR_IDX`, `CVAR_REF`, `CVAR_GET_STR/BOOL/INT/FLOAT/DOUBLE`,
`CVAR_SET_STR/BOOL/INT/FLOAT/DOUBLE`, `CVAR_CREATE`, `CVAR_DELETE`. Every
getter and setter takes an optional last `CSplitScreenSlot` (default `-1`),
which you can leave alone on a server.
`GetConvarAccessIndexByName` returns `SOURCE2MOD_INVALID_CONVAR_INDEX` (`0xFFFF`)
when the name does not exist. Check for it before reading or writing -- `0` is
a valid index, so do not test against that.
`GetString` returns a pointer into a thread-local buffer: copy it before the
next `GetString` call if you need to keep it.
Creating ConVars at runtime [#creating-convars-at-runtime]
When the name is only known at runtime — read from a config, say — create the
ConVar through the interface (a script: `CConVar.Create`, the values as text —
`"1 2 3"` for a vector, `"255 0 0 255"` for a colour):
```cpp title="ISource2ModConVars.h"
virtual uint16 CreateConVar(const char* name, EConVarType type, const char* help, uint64 flags,
bool hasMin, bool hasMax,
void* defaultValue, const void* minValue, const void* maxValue) = 0;
virtual void DeleteConVar(uint16 accessIndex) = 0;
```
```cpp
float def = 1.0f, min = 0.0f, max = 10.0f;
uint16 idx = CVAR_CREATE("myplugin_dynamic", EConVarType_Float32, "Created at runtime",
FCVAR_NONE, true, true, &def, &min, &max);
// ...
CVAR_DELETE(idx); // in Unload()
```
```cpp
CConVar dynamic = CConVar.Create("myplugin_dynamic", EConVarType_Float32, FCVAR_NONE, "Created at runtime",
"1.0", true, "0.0", true, "10.0");
// ...
Source2ModConVars.DeleteConVar(dynamic);
```
* `defaultValue`, `minValue` and `maxValue` point to a value of the matching
type (`int32` for `EConVarType_Int32`, `float` for `EConVarType_Float32`, …).
For `EConVarType_String` pass the string itself, `(void*)"text"`, not a
pointer to it.
* Pass valid `minValue`/`maxValue` pointers even when `hasMin`/`hasMax` are
false — for numeric types they are read either way.
* If a ConVar with that name exists already, nothing is created and its index
is returned.
* The ConVar is created by the core, not by your library, and is **not**
removed when your plugin unloads. `DeleteConVar` in `Unload()` is on you.
For ConVars known at compile time, a `CConVar` is simpler and needs no cleanup
beyond `ConVar_Unregister()`.
Watching changes [#watching-changes]
`HOOK_CONVAR_CHANGE` registers a callback for **every** ConVar change on the
server. The engine only takes one plain function pointer for this; Source2Mod
installs it once and fans out, so your handler can be a capturing lambda, and it
is dropped automatically when the plugin unloads. A script passes a named
`public` function to `Source2ModConVars.HookConVarChange`.
```cpp title="plugin.cpp"
HOOK_CONVAR_CHANGE([](ConVarRefAbstract* ref, CSplitScreenSlot slot, const char* pszNewValue, const char* pszOldValue)
{
if (V_strcmp(ref->GetName(), "sv_autobunnyhopping") != 0)
return;
SOURCE2MOD_LOG(&g_Plugin, "%s changed from %s to %s\n", ref->GetName(), pszOldValue, pszNewValue);
});
```
```cpp title="plugin.sp"
public void OnPluginStart()
{
Source2ModConVars.HookConVarChange(OnConVarChanged);
}
public void OnConVarChanged(ConVar convar, int slot, const char[] newValue, const char[] oldValue)
{
char name[64];
convar.GetName(name, sizeof(name));
if (!Source2ModUtils.Equal(name, "sv_autobunnyhopping"))
return;
Source2ModApi.Log("%s changed from %s to %s", name, oldValue, newValue);
}
```
```cpp title="ISource2ModConVars.h"
using ConVarChangeHandler = Source2ModCallback;
virtual Source2ModHookId HookConVarChange(ConVarChangeHandler handler) = 0;
virtual bool UnhookConVarChange(const ConVarChangeHandler& handler) = 0;
virtual bool UnhookConVarChange(Source2ModHookId id) = 0;
```
Filter by name first — the callback fires for all ConVars, the game's included.
A plugin may register as many handlers as it likes. There is no plugin id to
pass: the handler is a lambda, a function or an object and a method, and the
core reads the owner off it. `HOOK_CONVAR_CHANGE(handler)` returns a
`Source2ModHookId`; to drop one handler while the plugin stays loaded, pass that id
to `g_pSource2ModConVars->UnhookConVarChange(id)`, or pass the same function or
object and method again to `UNHOOK_CONVAR_CHANGE(handler)` (a lambda only goes
by id). A script unhooks by id only, with `Source2ModConVars.UnhookConVarChange(id)`:
```cpp
HOOK_CONVAR_CHANGE(SOURCE2MOD_MEMBER(this, &MyPlugin::OnConVarChanged));
// ...
UNHOOK_CONVAR_CHANGE(SOURCE2MOD_MEMBER(this, &MyPlugin::OnConVarChanged));
```
```cpp
int id = Source2ModConVars.HookConVarChange(OnConVarChanged);
// ...
Source2ModConVars.UnhookConVarChange(id);
```
See also [#see-also]
* [Replication](/docs/convars-commands/replication) — a different value per player.
* [`ISource2ModConVars` reference](/docs/core-api/ISource2ModConVars)
# ConVars & Commands (/docs/convars-commands)
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`](/docs/core-api/ISource2ModCommands) and
[`ISource2ModConVars`](/docs/core-api/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 [#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`:
```json title="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](/docs/convars-commands/commands).
Listening to commands you do not own [#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](/docs/convars-commands/command-listeners).
ConVars [#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` (the recommended way), or at
runtime through `ISource2ModConVars::CreateConVar`;
* reading and writing the game's ConVars — typed `CConVarRef`, 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](/docs/convars-commands/convars). Sending one player a different
value than everybody else is its own topic:
[Replication](/docs/convars-commands/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 [#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](/docs/convars-commands/convars#unloading) page explains why. A
script's `CConVar` belongs to the core, so a script has nothing to undo.
Console and chat commands, arguments, the caller.
Pre/post callbacks on existing commands, blocking them.
Declaring, reading, writing and watching console variables.
Per-player ConVar values and reading client ConVars.
# Replication (/docs/convars-commands/replication)
Some ConVars matter on the client as much as on the server. `FCVAR_REPLICATED`
ConVars — `sv_autobunnyhopping`, `sv_jump_impulse`, `mp_forcecamera`,
`weapon_accuracy_nospread`, … — are sent by the server to every client, and the
client uses them for prediction, movement and UI. Normally everyone gets the
same value. `ReplicateConVar` sends a single player a value of their own.
`ReplicateConVar` [#replicateconvar]
A helper on [`CBasePlayerController`](/docs/schema/entity/classes/CBasePlayerController)
(so on `CCSPlayerController` too, and on the script methodmaps of the same
names):
```cpp title="CBasePlayerController.h"
void ReplicateConVar(const char* pszConVar, const char* pszValue);
```
It builds a `CNETMsg_SetConVar` with the one name/value pair and sends it
straight down that player's net channel. The value is always a string, as the
client would parse it from the console: `"1"`, `"true"`, `"301.9933"`.
```cpp title="plugin.cpp"
#include "source2mod/schema/entity/classes/CCSPlayerController.h"
// Auto-bunnyhop for one player only.
void SetBhop(CCSPlayerController* player, bool enabled)
{
player->ReplicateConVar("sv_autobunnyhopping", enabled ? "true" : "false");
}
```
```cpp title="plugin.sp"
// Auto-bunnyhop for one player only.
void SetBhop(CCSPlayerController player, bool enabled)
{
player.ReplicateConVar("sv_autobunnyhopping", enabled ? "true" : "false");
}
```
Things to keep in mind:
* **Only the client's copy changes.** The server's value is untouched, and
anything the server decides with it still uses the server's value. For
ConVars that are simulated on both sides — movement mostly — the server has
to agree, or the client mispredicts.
* **The player must be connected.** Without a net channel the call does
nothing.
* **It does not survive a reconnect.** A client that reconnects starts from the
server's values again.
Keeping the value per player [#keeping-the-value-per-player]
If the ConVar is `FCVAR_REPLICATED`, the engine broadcasts the server's value to
every client whenever it changes — which overwrites every per-player value you
sent. FUNPLAY's plugins, which toggle bunnyhop, C4-anywhere and friends per
player, handle this in three places:
1. **Before sending**, drop `FCVAR_REPLICATED` from the ConVar so a later change
of the server value is not pushed over the per-player ones:
```cpp
UTIL_RemoveConVarFlags("sv_autobunnyhopping", FCVAR_REPLICATED);
player->ReplicateConVar("sv_autobunnyhopping", state ? "true" : "false");
```
```cpp
CConVarRef bhop = CConVarRef.Find("sv_autobunnyhopping");
bhop.Flags &= ~FCVAR_REPLICATED;
player.ReplicateConVar("sv_autobunnyhopping", state ? "true" : "false");
```
2. **When the server value changes**, send each player their value again
([`HOOK_CONVAR_CHANGE`](/docs/convars-commands/convars#watching-changes)).
3. **When a player (re)connects**, send it again — `player_connect_full` is the
usual place (see [Game events](/docs/events/game-events)).
```cpp title="bhop.cpp"
static CConVarRef sv_autobunnyhopping("sv_autobunnyhopping");
static void UpdatePlayerBhop(CCSPlayerController* player, bool state)
{
UTIL_RemoveConVarFlags("sv_autobunnyhopping", FCVAR_REPLICATED);
// With bhop on for everybody there is nothing to override.
if (!sv_autobunnyhopping.Get())
player->ReplicateConVar("sv_autobunnyhopping", state ? "true" : "false");
}
// in Load()
HOOK_CONVAR_CHANGE([](ConVarRefAbstract* ref, CSplitScreenSlot slot, const char* pszNewValue, const char* pszOldValue)
{
if (V_strcmp(ref->GetName(), "sv_autobunnyhopping") != 0)
return;
for (int i = 0; i < 64; i++)
if (auto* player = CCSPlayerController::FromSlot(i))
UpdatePlayerBhop(player, PlayerHasBhop(player)); // your own per-player state
});
HOOK_GAME_EVENT("player_connect_full", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action
{
auto* player = static_cast(event->GetPlayerController("userid"));
if (player)
UpdatePlayerBhop(player, PlayerHasBhop(player));
return Action::Ignore;
}, true);
```
```cpp title="bhop.sp"
CConVarRef sv_autobunnyhopping;
void UpdatePlayerBhop(CCSPlayerController player, bool state)
{
sv_autobunnyhopping.Flags &= ~FCVAR_REPLICATED;
// With bhop on for everybody there is nothing to override.
if (!sv_autobunnyhopping.BoolValue)
player.ReplicateConVar("sv_autobunnyhopping", state ? "true" : "false");
}
public void OnPluginStart()
{
sv_autobunnyhopping = CConVarRef.Find("sv_autobunnyhopping");
Source2ModConVars.HookConVarChange(OnConVarChanged);
Source2ModEvents.HookGameEvent("player_connect_full", Event_PlayerConnectFull, true);
}
public void OnConVarChanged(ConVar convar, int slot, const char[] newValue, const char[] oldValue)
{
char name[64];
convar.GetName(name, sizeof(name));
if (!Source2ModUtils.Equal(name, "sv_autobunnyhopping"))
return;
for (int i = 0; i < 64; i++)
{
CCSPlayerController player = CCSPlayerController.FromSlot(CPlayerSlot(i));
if (player != NULL_ENTITY)
UpdatePlayerBhop(player, PlayerHasBhop(player)); // your own per-player state
}
}
public Source2ModAction Event_PlayerConnectFull(IGameEvent event, bool post, bool &dontBroadcast)
{
CCSPlayerController player = event.GetPlayerController("userid");
if (player != NULL_ENTITY)
UpdatePlayerBhop(player, PlayerHasBhop(player));
return Action_Ignore;
}
```
`UTIL_RemoveConVarFlags` and `UTIL_AddConVarFlags` are the by-name helpers from
`ISource2ModConVars.h`; see [ConVars](/docs/convars-commands/convars#one-shot-helpers-by-name).
A script changes the flags on the ConVar handle's `Flags` instead.
Common uses [#common-uses]
| ConVar | Per-player effect |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `sv_autobunnyhopping` | Hold-to-jump bunnyhopping for chosen players. |
| `mp_forcecamera` | What a dead player may spectate (FUNPLAY switches it per player on spawn, round start and round end). |
| `sv_cheats` | Lets a single client use client-side cheat commands — FUNPLAY's Inspect plugin does this. The server stays `sv_cheats 0`. |
| `sv_jump_impulse`, `sv_friction`, `weapon_accuracy_nospread`, `mp_plant_c4_anywhere` | Movement and weapon behaviour the client predicts; the server side has to match (see above). |
Reading ConVars from a client [#reading-convars-from-a-client]
Source2Mod has no wrapper for asking a client for one of its ConVars. There
are two ways, both engine-level:
* **User info.** `IVEngineServer2::GetClientConVarValue(CPlayerSlot, const char*)`
returns a value from the user info the client sends to the server — no round
trip, but only for what the client puts there.
```cpp
IVEngineServer2* engine = g_Source2ModAPI->GetEngineServer();
const char* value = engine->GetClientConVarValue(player->GetPlayerSlot(), "name_of_userinfo_cvar");
```
C++ only — scripts have no `GetClientConVarValue`; they use the cvar query
below.
* **A cvar query.** Send the client a `CSVCMsg_GetCvarValue` (fields `cookie`
and `cvar_name`) and read the answer, a `CCLCMsg_RespondCvarValue` with the
same cookie and a status (`EQueryCvarValueStatus`: value intact, not found,
not a ConVar, protected). The reply arrives asynchronously; FUNPLAY's
AntiCheat catches it with a KHook on
`CServerSideClientBase::ProcessRespondCvarValue`. Sending messages is covered
under [Protobuf](/docs/protobuf), hooking under [Hooking](/docs/hooks). A
script sends the query and catches the answer with `Source2ModNetworkMessages`
— see [Common messages](/docs/protobuf/common-messages#engine-messages).
See also [#see-also]
* [ConVars](/docs/convars-commands/convars)
* [`CBasePlayerController`](/docs/schema/entity/classes/CBasePlayerController)
* [`ISource2ModConVars` reference](/docs/core-api/ISource2ModConVars)
# Creating Entities (/docs/entities/creating-entities)
Creating an entity is two steps: **create** it by classname, which gives you an
object that exists but has not spawned, then **spawn** it. Between the two you
set whatever the entity reads on spawn.
Create and spawn [#create-and-spawn]
```cpp
static CBaseEntity* CBaseEntity::CreateEntityByName(const char* pszClassName);
template
static T* CBaseEntity::New(const char* pszClassName);
void CBaseEntity::DispatchSpawn(CEntityKeyValues* pEntityKeyValues = nullptr,
HookChain eChain = HookChain::Run);
```
`New` is `CreateEntityByName` with the cast done for you, and every
generated class has a non-template `New(classname)` returning its own type.
The cast is not checked, so the classname has to be one that really creates a
`T`. Both return `nullptr` when the entity could not be created. Scripts call
`CBaseEntity.CreateEntityByName` (or `Source2ModEntities.CreateEntityByName`)
and `view_as` the result to the class they want; it is `NULL_ENTITY` when the
entity could not be created.
```cpp title="plugin.cpp"
#include "source2mod/schema/entity/classes/CBaseModelEntity.h"
CBaseModelEntity* pModel = CBaseEntity::New("prop_dynamic_override");
if (!pModel)
return;
pModel->SetModel("models/props/de_dust/hr_dust/dust_crates/dust_crate_style_01_32x32x32.vmdl");
pModel->DispatchSpawn();
const Vector vecOrigin = pPawn->GetAbsOrigin();
pModel->Teleport(&vecOrigin, nullptr, nullptr);
```
```cpp title="plugin.sp"
CBaseModelEntity model = view_as(CBaseEntity.CreateEntityByName("prop_dynamic_override"));
if (model == NULL_ENTITY)
return;
model.SetModel("models/props/de_dust/hr_dust/dust_crates/dust_crate_style_01_32x32x32.vmdl");
model.DispatchSpawn();
float vecOrigin[3];
pawn.GetAbsOrigin(vecOrigin);
model.Teleport(vecOrigin, NULL_VECTOR, NULL_VECTOR);
```
`CREATE_ENTITY(classname)` and `g_pSource2ModEntities->CreateEntityByName()` are
the same call through [ISource2ModEntities](/docs/core-api/ISource2ModEntities).
Fields you set **before** `DispatchSpawn` are what the entity spawns with;
some entities read their state only on spawn, others keep reading it. The FUNPLAY
plugins set up a `point_worldtext` completely before spawning it:
```cpp
CPointWorldText* pText = CBaseEntity::New("point_worldtext");
if (!pText)
return;
V_strncpy(pText->m_messageText, "Hello", 512);
pText->m_messageText.NetworkStateChanged();
pText->m_bEnabled = true;
pText->m_flFontSize = 48.0f;
pText->m_Color = Color(255, 255, 255, 255);
pText->m_bFullbright = true;
pText->m_flWorldUnitsPerPx = 0.01f;
pText->DispatchSpawn();
```
```cpp title="plugin.sp"
CPointWorldText text = view_as(CBaseEntity.CreateEntityByName("point_worldtext"));
if (text == NULL_ENTITY)
return;
text.Set_m_messageText("Hello"); // writes the field and sends the change
text.m_bEnabled = true;
text.m_flFontSize = 48.0;
int color[4] = { 255, 255, 255, 255 };
text.Set_m_Color(color);
text.m_bFullbright = true;
text.m_flWorldUnitsPerPx = 0.01;
text.DispatchSpawn();
```
Keyvalues [#keyvalues]
Anything a mapper would set in Hammer can be passed as keyvalues. Allocate a
`CEntityKeyValues` with `new` and hand it to `DispatchSpawn`; do not delete it
yourself — the SDK's own `CCSCustomHudLayout::Create` passes it the same way.
Scripts pass a `StringMap` of key to string value instead; `DispatchSpawn`
copies its entries, so close the map afterwards.
```cpp
#include "entitykeyvalues.h"
auto* pKeyValues = new CEntityKeyValues();
pKeyValues->SetString("targetname", "my_trigger");
pKeyValues->SetString("model", "models/props/de_dust/hr_dust/dust_crates/dust_crate_style_01_32x32x32.vmdl");
pKeyValues->SetInt("spawnflags", 1);
pKeyValues->SetVector("origin", vecOrigin);
pEntity->DispatchSpawn(pKeyValues);
```
```cpp title="plugin.sp"
StringMap keyValues = new StringMap();
keyValues.SetString("targetname", "my_trigger");
keyValues.SetString("model", "models/props/de_dust/hr_dust/dust_crates/dust_crate_style_01_32x32x32.vmdl");
keyValues.SetString("spawnflags", "1");
char origin[64];
Source2ModApi.Format(origin, sizeof(origin), "%f %f %f", vecOrigin[0], vecOrigin[1], vecOrigin[2]);
keyValues.SetString("origin", origin);
entity.DispatchSpawn(keyValues);
delete keyValues; // DispatchSpawn copied the entries
```
`CEntityKeyValues` also has `SetBool`, `SetFloat`, `SetColor` and `SetQAngle`.
In a script every value is a string, as in Hammer.
Moving and orienting [#moving-and-orienting]
```cpp
void CBaseEntity::Teleport(const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity,
HookChain eChain = HookChain::Run);
```
Pass `nullptr` for whatever should stay as it is. This is the right way to move
anything, players included — it goes through the entity's own virtual.
Reading position and motion:
| Helper | Returns |
| --------------------------------------- | ---------------------------------- |
| `GetAbsOrigin()` | World position |
| `GetAbsRotation()` / `GetAngRotation()` | World / local rotation |
| `GetAbsVelocity()` | Velocity |
| `CBaseModelEntity::GetEyePosition()` | Origin plus `m_vecViewOffset` |
| `CCSPlayerPawn::GetEyeAngles()` | Where a player is actually looking |
The setters (`SetAbsOrigin`, `SetAbsRotation`, `SetAngRotation`,
`SetAbsVelocity`, `SetBaseVelocity`) write the scene node or velocity fields
directly. For a real move prefer `Teleport`.
Parenting and following go through inputs, the way maps do it:
```cpp
pText->AcceptInput("SetParent", pPawn, nullptr, "!activator");
pModel->AcceptInput("FollowEntity", pPawn, pModel, "!activator");
```
```cpp title="plugin.sp"
text.AcceptInput("SetParent", pawn, NULL_ENTITY, "!activator");
model.AcceptInput("FollowEntity", pawn, model, "!activator");
```
Models, bodygroups, collision [#models-bodygroups-collision]
```cpp
void CBaseModelEntity::SetModel(const char* pszModel, HookChain eChain = HookChain::Run);
void CBaseModelEntity::SetBodyGroup(const char* pszName, int nValue);
CUtlSymbolLarge CBaseModelEntity::GetModelName();
```
A model that no map or stock content loads has to be precached before clients
can show it; that happens in the `BuildGameSessionManifest` game system event
(see [Game systems](/docs/guides/game-systems)). `SetBodyGroup` sends the
`SetBodyGroup` input with `"name,value"`, as the engine has no direct setter.
Collision (the script includes have no `COLLISION_GROUP_*` / `MOVETYPE_*`
constants, so scripts pass the numbers):
```cpp
pEntity->SetCollisionGroup(COLLISION_GROUP_DEBRIS); // also calls CollisionRulesChanged()
uint8 nGroup = pEntity->GetCollisionGroup();
pEntity->SetMoveType(MOVETYPE_NOCLIP); // sets m_MoveType and m_nActualMoveType
```
```cpp title="plugin.sp"
entity.SetCollisionGroup(5); // COLLISION_GROUP_DEBRIS; also calls CollisionRulesChanged()
int nGroup = entity.GetCollisionGroup();
entity.SetMoveType(7); // MOVETYPE_NOCLIP; sets m_MoveType and m_nActualMoveType
```
Removing [#removing]
```cpp
void CBaseEntity::Remove(HookChain eChain = HookChain::Run); // UTIL_Remove
```
Or with the `Kill` input — immediately, or delayed through the I/O queue:
```cpp
pEntity->AcceptInput("Kill");
pEntity->AddEntityIOEvent("Kill", nullptr, nullptr, "", 5.0f); // in five seconds
```
```cpp title="plugin.sp"
entity.AcceptInput("Kill");
entity.AddEntityIOEvent("Kill", NULL_ENTITY, NULL_ENTITY, "", 5.0); // in five seconds
```
The delayed form is handy for anything temporary: create it, spawn it, and
queue its own removal in the same place.
Particles [#particles]
For an entity-attached effect, `DispatchParticleEffect` sends it without
creating an entity:
```cpp
void CBaseEntity::DispatchParticleEffect(const char* pszParticleName,
ParticleAttachment_t nAttachType = ParticleAttachment_t::PATTACH_ABSORIGIN_FOLLOW,
uint8 nAttachmentPoint = 0,
bool bResetAllParticlesOnEntity = false,
IRecipientFilter* pFilter = nullptr,
HookChain eChain = HookChain::Run);
```
A null filter sends it to everyone. To show it to one player, pass a
`CSingleRecipientFilter` from `source2mod/schema/recipientfilter.h`; a script
passes a player mask (`int[2]`, the `PlayerMask_*` stocks) as `recipients`:
```cpp
CSingleRecipientFilter filter(pController->GetPlayerSlot());
pPawn->DispatchParticleEffect("particles/my_effect.vpcf",
ParticleAttachment_t::PATTACH_ABSORIGIN_FOLLOW, 0, false, &filter);
```
```cpp title="plugin.sp"
int recipients[2];
PlayerMask_Clear(recipients);
PlayerMask_Add(recipients, controller.GetSlot());
pawn.DispatchParticleEffect("particles/my_effect.vpcf", 1, 0, false, recipients); // 1 = PATTACH_ABSORIGIN_FOLLOW
```
For an effect with its own lifetime and position, create an
`info_particle_system` (`CParticleSystem`) instead:
```cpp title="plugin.cpp"
auto* pParticle = CBaseEntity::New("info_particle_system");
if (!pParticle)
return;
// CUtlSymbolLarge keeps the pointer it is given: pass a string that outlives
// the entity, like this literal.
pParticle->m_iszEffectName() = "particles/blood_impact/blood_pool.vpcf";
pParticle->DispatchSpawn();
pParticle->Teleport(&vecOrigin, nullptr, nullptr);
pParticle->AcceptInput("Start");
pParticle->AddEntityIOEvent("Kill", pParticle, nullptr, "", 1.0f);
```
```cpp title="plugin.sp"
CParticleSystem particle = view_as(CBaseEntity.CreateEntityByName("info_particle_system"));
if (particle == NULL_ENTITY)
return;
// m_iszEffectName has no setter in scripts; the "effect_name" keyvalue
// sets it on spawn.
StringMap keyValues = new StringMap();
keyValues.SetString("effect_name", "particles/blood_impact/blood_pool.vpcf");
particle.DispatchSpawn(keyValues);
delete keyValues;
particle.Teleport(vecOrigin, NULL_VECTOR, NULL_VECTOR);
particle.AcceptInput("Start");
particle.AddEntityIOEvent("Kill", particle, NULL_ENTITY, "", 1.0);
```
Grenades [#grenades]
Each grenade projectile class has `EmitGrenade`, which spawns a live, thrown
grenade the way the game does when a player throws one:
```cpp
static CHEGrenadeProjectile* CHEGrenadeProjectile::EmitGrenade(
const Vector& vecPosition, const QAngle& angAngle, const Vector& vecVelocity,
CBaseEntity* pOwner, uint32_t nItemDefIndex, HookChain eChain = HookChain::Run);
```
`CFlashbangProjectile`, `CDecoyProjectile` and `CMolotovProjectile` take the same
arguments. `CSmokeGrenadeProjectile::EmitGrenade` has an extra `int nTeam`
before the owner:
```cpp
static CSmokeGrenadeProjectile* CSmokeGrenadeProjectile::EmitGrenade(
const Vector& vecPosition, const QAngle& angAngle, const Vector& vecVelocity,
int nTeam, CBaseEntity* pOwner, uint32_t nItemDefIndex, HookChain eChain = HookChain::Run);
```
`pOwner` is who gets credit for the grenade (usually a player's pawn) and
`nItemDefIndex` is the weapon's item definition index. The velocity is used as
both the linear and the angular velocity, as in SwiftlyS2's implementation this
is ported from.
```cpp
#include "source2mod/schema/entity/classes/CHEGrenadeProjectile.h"
const Vector vecStart = pPawn->GetEyePosition();
const Vector vecVelocity(0.0f, 0.0f, 300.0f);
CHEGrenadeProjectile::EmitGrenade(vecStart, QAngle(0, 0, 0), vecVelocity, pPawn, 44);
```
```cpp title="plugin.sp"
float vecStart[3];
pawn.GetEyePosition(vecStart);
float vecVelocity[3] = { 0.0, 0.0, 300.0 };
float angles[3]; // 0 0 0
CHEGrenadeProjectile.EmitGrenade(vecStart, angles, vecVelocity, pawn, 44);
```
HookChain [#hookchain]
Every helper above that calls a game function takes a `HookChain` last. The
default, `HookChain::Run`, goes through other plugins' hooks on that function
as if the game made the call; `HookChain::Bypass` goes straight to the
original. See [Players](/docs/entities/players#hookchain) for when to use which.
Scripts pass `HookChain_Run` or `HookChain_Bypass`; the default,
`HookChain_Default`, keeps the C++ default.
# Damage (/docs/entities/damage)
All damage in CS2 — bullets, knives, grenades, falling, the world — goes
through one function, `CBaseEntity::TakeDamageOld` (the game's own asserts call
it that). It takes a `CTakeDamageInfo` describing the hit and a
`CTakeDamageResult` it fills in. Hook it to change or block damage; call it to
deal damage yourself.
CTakeDamageInfo [#ctakedamageinfo]
```cpp
#include "source2mod/schema/takedamageinfo.h"
CTakeDamageInfo();
CTakeDamageInfo(CBaseEntity* pInflictor, CBaseEntity* pAttacker, CBaseEntity* pAbility,
float flDamage, DamageTypes_t bitsDamageType);
HitGroup_t GetHitGroup() const;
```
Both constructors call the game's own constructor, so every field you do not
set has the game's default. The fields you will use:
| Field | Type | Meaning |
| ------------------------------------------------------------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------- |
| `m_flDamage` | `float` | Damage to apply. Change it in a Pre hook to scale the hit. |
| `m_bitsDamageType` | `int32_t` | `DMG_*` bits: `DMG_BULLET`, `DMG_FALL`, `DMG_BLAST`, `DMG_BURN`, … |
| `m_hAttacker` | `CHandle` | Who is credited — for players, their **pawn**. |
| `m_hInflictor` | `CHandle` | What did it — the pawn for a bullet, the projectile for a grenade. |
| `m_hAbility` | `CHandle` | The weapon, when there is one. |
| `m_vecDamagePosition`, `m_vecDamageForce`, `m_vecDamageDirection`, `m_vecReportedPosition` | `Vector` | Where and which way. |
| `m_nDamageFlags` | `TakeDamageFlags_t` | `DFLAG_*` flags, e.g. `DFLAG_SUPPRESS_DAMAGE_MODIFICATION`, `DFLAG_PREVENT_DEATH`. |
| `m_iHitGroupId` | `HitGroup_t` | Hit group field. |
| `m_flOriginalDamage` | `float` | Damage before modification. |
| `m_pTrace` | `CGameTrace*` | The trace that caused the hit, when there is one. |
| `m_iDamageCustom`, `m_iAmmoType`, `m_bShouldBleed`, `m_bShouldSpark`, `m_AttackerInfo`, … | | See the [reference](/docs/schema/Takedamageinfo). |
Scripts cannot construct one: they get it from the TakeDamage hook (`ctx.info`),
with these fields as properties — the vectors and `m_nDamageFlags` as methods
filling a buffer, the handles as entities.
`GetHitGroup()` reads the hit group from the attached hit data through a
gamedata offset; it returns `HITGROUP_INVALID` when that is unavailable and
`HITGROUP_GENERIC` when the hit carries no group. Use it rather than
`m_iHitGroupId` for "was this a headshot" (scripts compare to the number,
`HITGROUP_HEAD` is 1):
```cpp
if (pInfo->GetHitGroup() == HitGroup_t::HITGROUP_HEAD)
// ...
```
```cpp title="plugin.sp"
if (info.GetHitGroup() == 1) // HITGROUP_HEAD
{
// ...
}
```
CTakeDamageResult [#ctakedamageresult]
```cpp
#include "source2mod/schema/takedamageresult.h"
CTakeDamageResult(float damage);
void CopyFrom(CTakeDamageInfo* pInfo);
```
What the game fills in while applying damage: `m_nHealthLost`,
`m_nHealthBefore`, `m_flDamageDealt`, `m_flPreModifiedDamage`,
`m_nTotalledHealthLost`, `m_flTotalledDamageDealt`, `m_bWasDamageSuppressed`
and more. There is no default constructor — build it from a damage value, and
call `CopyFrom(&info)` to point it at your info and copy the damage over when
you call `TakeDamageOld` yourself.
See the [reference](/docs/schema/Takedamageresult) for every field.
Hooking TakeDamageOld [#hooking-takedamageold]
`ADDR_TAKE_DAMAGE_OLD()` returns the resolved function; hook it with
`KHOOK_MEMBER`, which evaluates the address when `KHOOK_INIT()` runs. Scripts do
not hook it themselves: `Source2ModGameHooks.HookTakeDamage` adds a handler to
the hook the core already has on it, so the class declaration is C++ only:
```cpp title="plugin.h"
#include "source2mod/ISource2ModAddresses.h"
#include "source2mod/ISource2ModHooks.h"
#include "source2mod/schema/takedamageinfo.h"
#include "source2mod/schema/takedamageresult.h"
class MyPlugin : public ISource2ModPlugin
{
public:
KHook::Return Hook_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult);
KHOOK_MEMBER(m_hTakeDamageOld, ADDR_TAKE_DAMAGE_OLD(), &MyPlugin::Hook_TakeDamageOld, nullptr);
};
```
```cpp title="plugin.cpp"
bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS();
KHOOK_INIT();
return true;
}
bool MyPlugin::Unload(char* error, size_t maxlen)
{
KHOOK_DESTRUCT();
return true;
}
```
```cpp title="plugin.sp"
public void OnPluginStart()
{
// The core hooks CBaseEntity::TakeDamageOld from its own gamedata;
// false = Pre. The hook goes when the plugin unloads.
Source2ModGameHooks.HookTakeDamage(OnTakeDamage, false);
}
```
The hooked entity arrives first — for player damage that is the victim's
**pawn** (classname `player`), not the controller. The handler returns a
`KHook::Return`; a script handler gets a `TakeDamageContext`
(`ctx.entity`, `ctx.info`, `ctx.damageResult`) and returns a `Source2ModAction`,
with the function's return value in `result`. The script includes have no
`DMG_*` constants, so define the bits you test:
```cpp title="plugin.cpp"
KHook::Return MyPlugin::Hook_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult)
{
if (!pInfo)
return { KHook::Action::Ignore, 0 };
// No fall damage.
if (pInfo->m_bitsDamageType & DMG_FALL)
return { KHook::Action::Supersede, 0 };
// Halve everything else. This is a Pre hook: the original runs after
// it, with whatever pInfo holds by then.
pInfo->m_flDamage *= 0.5f;
return { KHook::Action::Ignore, 0 };
}
```
```cpp title="plugin.sp"
#define DMG_FALL (1 << 5) // DamageTypes_t; the includes have no DMG_* constants
public Source2ModAction OnTakeDamage(TakeDamageContext ctx, bool post, int &result)
{
CTakeDamageInfo info = ctx.info;
// No fall damage.
if (info.m_bitsDamageType & DMG_FALL)
{
result = 0;
return Action_Supersede;
}
// Halve everything else. This is a Pre hook: the original runs after
// it, with whatever the info holds by then.
info.m_flDamage *= 0.5;
return Action_Ignore;
}
```
* `Ignore` — the original runs (with your changes to `pInfo`).
* `Supersede` — the original does not run: no damage at all.
* To act on the result, run the original yourself and supersede so it does not
run twice:
```cpp
const int64_t ret = m_hTakeDamageOld.CallOriginal(pThis, pInfo, pResult);
// pResult is filled in now
return { KHook::Action::Supersede, ret };
```
```cpp title="plugin.sp"
result = ctx.CallOriginal();
// ctx.damageResult is filled in now
return Action_Supersede;
```
Finding the players involved — the attacker handle points at a pawn:
```cpp
CCSPlayerController* pVictim = nullptr;
if (!V_strcmp(pThis->GetClassname(), "player"))
pVictim = static_cast(pThis)->GetController();
CCSPlayerController* pAttacker = nullptr;
if (CBaseEntity* pEnt = pInfo->m_hAttacker.Get(); pEnt && !V_strcmp(pEnt->GetClassname(), "player"))
pAttacker = static_cast(pEnt)->GetController();
```
```cpp title="plugin.sp"
CTakeDamageInfo info = ctx.info;
char classname[64];
CCSPlayerController victim = NULL_ENTITY;
ctx.entity.GetClassname(classname, sizeof(classname));
if (Source2ModUtils.Equal(classname, "player"))
victim = view_as(ctx.entity).GetController();
CCSPlayerController attacker = NULL_ENTITY;
CBaseEntity attackerEnt = info.m_hAttacker;
if (attackerEnt != NULL_ENTITY)
{
attackerEnt.GetClassname(classname, sizeof(classname));
if (Source2ModUtils.Equal(classname, "player"))
attacker = view_as(attackerEnt).GetController();
}
```
The hooks section covers handlers, `CallOriginal` and Post hooks in depth:
[Hooking](/docs/hooks).
Dealing damage [#dealing-damage]
Player to player [#player-to-player]
The simplest route is the controller helper:
```cpp
void CCSPlayerController::TakeDamage(CCSPlayerController* pAttacker, int iDamage,
DamageTypes_t bitsDamageType,
HookChain eChain = HookChain::Bypass);
```
```cpp
pVictim->TakeDamage(pAttacker, 25, DMG_GENERIC);
```
```cpp title="plugin.sp"
victim.TakeDamage(attacker, 25, 0); // 0 = DMG_GENERIC
```
It builds the info with the attacker's pawn as inflictor and attacker, sets
`DFLAG_SUPPRESS_DAMAGE_MODIFICATION` so armor and scaling leave the number
alone, and calls `TakeDamageOld` on the victim's pawn. It needs both players
connected with a pawn and the victim alive; otherwise it does nothing.
By default it **bypasses** `TakeDamageOld` hooks — yours and other plugins' —
because it is the server dealing damage, not the game. Pass `HookChain::Run`
(`HookChain_Run` in a script)
when it stands in for a weapon and other plugins' damage rules should apply.
Anything else [#anything-else]
For damage with no attacker player, a different inflictor, or fields the
helper does not set, build the info yourself and call the function. The
TroubleTerroristTown plugin's explosions do it like this:
```cpp title="explosions.cpp"
CTakeDamageInfo info(nullptr, pAttackerPawn, nullptr, flDamage, DMG_BLAST);
info.m_vecReportedPosition = vecTargetOrigin;
CTakeDamageResult result(flDamage);
result.CopyFrom(&info);
SOURCE2MOD_ORIGINAL(ADDR_TAKE_DAMAGE_OLD())(pVictimPawn, &info, &result);
```
`SOURCE2MOD_ORIGINAL` calls the function past every hook on it — the same as
`HookChain::Bypass`. Call `ADDR_TAKE_DAMAGE_OLD()(…)` without it to run the
hook chain. Resolve it at the call, not once into a global: the original
behind a hook only exists once the hook does.
This route is C++ only: scripts cannot build a `CTakeDamageInfo`, so they deal
damage through `CCSPlayerController.TakeDamage`.
Damage a player through their **pawn**. Calling `TakeDamageOld` on the
controller reaches an entity whose `m_iHealth` is 0.
# Entity I/O (/docs/entities/entity-io)
Entity I/O is how maps wire entities together: a button's `OnPressed`
**output** fires a door's `Open` **input**. Plugins can take part on both ends —
send inputs as if a map did, and listen to outputs as they fire, with the option
to stop them.
Sending inputs [#sending-inputs]
```cpp
void CBaseEntity::AcceptInput(const char* pszInput,
CEntityInstance* pActivator = nullptr,
CEntityInstance* pCaller = nullptr,
const char* pszValue = "",
HookChain eChain = HookChain::Run);
```
The input runs immediately. The arguments are what a map connection passes:
the input name, the entity that started the chain (**activator**), the entity
whose output is firing (**caller**), and the parameter as a string.
```cpp
pDoor->AcceptInput("Open");
pBreakable->AcceptInput("Break");
pEntity->AcceptInput("Kill");
pModel->AcceptInput("FollowEntity", pPawn, pModel, "!activator");
pText->AcceptInput("SetParent", pPawn, nullptr, "!activator"); // parent to the activator
```
```cpp title="plugin.sp"
door.AcceptInput("Open");
breakable.AcceptInput("Break");
entity.AcceptInput("Kill");
model.AcceptInput("FollowEntity", pawn, model, "!activator");
text.AcceptInput("SetParent", pawn, NULL_ENTITY, "!activator"); // parent to the activator
```
`!activator` in the value is resolved against the activator you pass, as it is
in a map — that is how parenting and following are done.
Delayed inputs [#delayed-inputs]
```cpp
void CBaseEntity::AddEntityIOEvent(const char* pszInput,
CEntityInstance* pActivator = nullptr,
CEntityInstance* pCaller = nullptr,
const char* pszValue = "",
float flDelay = 0.0f,
HookChain eChain = HookChain::Run);
```
Same arguments plus a delay in seconds. The event goes into the engine's I/O
queue, like a connection with a delay set in Hammer. If the entity is removed
first, the event is dropped with it — which makes this the easy way to give
something a lifetime:
```cpp
pParticle->AddEntityIOEvent("Kill", nullptr, nullptr, "", 1.0f);
```
```cpp title="plugin.sp"
particle.AddEntityIOEvent("Kill", NULL_ENTITY, NULL_ENTITY, "", 1.0);
```
Both are also on [ISource2ModEntities](/docs/core-api/ISource2ModEntities)
(`Source2ModEntities` in a script), taking the target first:
```cpp
ACCEPT_INPUT(pTarget, "Open", pActivator, pCaller); // value defaults to ""
ADD_ENTITY_IO_EVENT(pTarget, "Kill", nullptr, nullptr, "", 5.0f);
```
```cpp title="plugin.sp"
Source2ModEntities.AcceptInput(target, "Open", activator, caller); // value defaults to ""
Source2ModEntities.AddEntityIOEvent(target, "Kill", NULL_ENTITY, NULL_ENTITY, "", 5.0);
```
The interface versions have no `HookChain` and always run the chain.
Listening to outputs [#listening-to-outputs]
Source2Mod hooks the engine's output dispatch once and hands each firing to
the listeners that match it. There are two ways to listen.
For a class of entities: IEntityIOListener [#for-a-class-of-entities-ientityiolistener]
Implement `IEntityIOListener` (from `source2mod/schema/entityio.h`) and
register it for a classname and an output. A script registers a handler
function (`Source2ModEntityOutputHandler`) instead, so the class is C++ only:
```cpp title="plugin.h"
class MyPlugin : public ISource2ModPlugin, public IEntityIOListener
{
public:
Action OnEntityOutput(const char* pchOutputName, CEntityInstance* pActivator,
CEntityInstance* pCaller, float flDelay, bool post) override;
};
```
```cpp title="plugin.cpp"
// In Load():
ADD_ENTITY_IO_LISTENER(this, "func_button", "OnPressed"); // Pre
ADD_ENTITY_IO_LISTENER(this, nullptr, "OnStartTouch", true); // Post, any class
```
```cpp title="plugin.sp"
// In OnPluginStart():
Source2ModEntities.AddEntityIOListener(OnEntityOutput, "func_button", "OnPressed"); // Pre
Source2ModEntities.AddEntityIOListener(OnEntityOutput, "", "OnStartTouch", true); // Post, any class
```
```cpp
virtual void AddEntityIOListener(IEntityIOListener* pListener,
const char* pchClassName,
const char* pchOutputName,
bool post = false) = 0;
```
`nullptr` (`""` in a script) for the classname or the output name means "any". The classname is
matched against the **caller's** classname — the entity whose output fired.
There is no plugin id argument: the listener belongs to the plugin whose
library the listener object's class lives in, which the core finds through the
object's vtable.
In the callback:
```cpp title="plugin.cpp"
Action MyPlugin::OnEntityOutput(const char* pchOutputName, CEntityInstance* pActivator,
CEntityInstance* pCaller, float flDelay, bool post)
{
if (post)
return Action::Ignore;
// Nobody opens this button while a round is being set up.
if (g_bSetupPhase && !V_strcmp(pchOutputName, "OnPressed"))
return Action::Supersede;
return Action::Ignore;
}
```
```cpp title="plugin.sp"
public Source2ModAction OnEntityOutput(const char[] output, CEntityInstance activator,
CEntityInstance caller, float delay, bool post)
{
if (post)
return Action_Ignore;
// Nobody opens this button while a round is being set up.
if (g_bSetupPhase && Source2ModUtils.Equal(output, "OnPressed"))
return Action_Supersede;
return Action_Ignore;
}
```
| Return (Pre) | Effect |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `Action::Ignore` | Nothing changed; the output fires. |
| `Action::Override` | The output still fires. |
| `Action::Supersede` | The output does **not** fire — the door stays shut, the button does nothing. Remaining Pre listeners and all Post listeners are skipped. |
The Post return value is ignored. Listeners are removed for you when your
plugin unloads; `REMOVE_ENTITY_IO_LISTENER(this, cls, out, post)` removes one
earlier and must be given the same classname, output and `post` it was added
with. A script keeps the id `Source2ModEntities.AddEntityIOListener` returns
and passes it to `Source2ModEntities.RemoveEntityIOListener`; its actions are
`Action_Ignore`, `Action_Override` and `Action_Supersede`.
For one entity: AddSingleEntityIOListener [#for-one-entity-addsingleentityiolistener]
When you care about one specific entity — a button you spawned, a trigger you
created — attach a callback to it directly:
```cpp
CEntityIOListenerHandle* CBaseEntity::AddSingleEntityIOListener(
const char* pszOutput,
std::function callback,
bool post);
```
The callback has the same arguments and return value as `OnEntityOutput` and
only fires when the caller is this entity:
```cpp title="plugin.cpp"
std::unique_ptr g_pButtonListener;
void HookButton(CBaseEntity* pButton)
{
g_pButtonListener.reset(pButton->AddSingleEntityIOListener("OnPressed",
[](const char* pszOutput, CEntityInstance* pActivator, CEntityInstance* pCaller, float flDelay, bool post)
{
// pActivator is usually the player's pawn.
return Action::Ignore;
},
false));
}
```
```cpp title="plugin.sp"
int g_buttonListener = -1;
void HookButton(CBaseEntity button)
{
g_buttonListener = button.AddSingleEntityIOListener("OnPressed", OnButtonPressed, false);
}
public Source2ModAction OnButtonPressed(const char[] output, CEntityInstance activator,
CEntityInstance caller, float delay, bool post)
{
// activator is usually the player's pawn.
return Action_Ignore;
}
```
The returned handle owns the listener: **deleting it unregisters it** (or call
`Unhook()`). Keep it for as long as you want the callback, and drop it when the
entity goes away — the listener stays registered until you do, it just never
matches again.
In a script `AddSingleEntityIOListener` returns an id instead, which
`Source2ModEntities.RemoveEntityIOListener` takes.
Outputs on the entity [#outputs-on-the-entity]
Outputs are fields too. `CBaseEntity` has `m_OnUser1` … `m_OnUser4` and
`m_OnKilled`, and each class has its own (`CEntityIOOutput`). The struct in
`entityio.h` exposes the output's descriptor (`m_pDesc->m_pName`) and its
linked list of connections (`m_pConnections`), each with target, input, value
override, delay and remaining fire count — enough to inspect how a map wired
an entity.
See the [entity I/O reference](/docs/schema/Entityio) for the full structs.
# Finding Entities (/docs/entities/finding-entities)
Most plugin code starts from something that identifies an entity — a command
caller's slot, a game event's userid, a SteamID from your database, an entity
index — and needs the typed object. This page lists the ways in.
Players [#players]
`CCSPlayerController` has static lookups for every way a player is identified:
```cpp
#include "source2mod/schema/entity/classes/CCSPlayerController.h"
static CCSPlayerController* FromSlot(int iSlot);
static CCSPlayerController* FromSlot(CPlayerSlot slot);
static CCSPlayerController* FromUserId(int iUserId);
static CCSPlayerController* FromUserId(CPlayerUserId userId);
static CCSPlayerController* FromSteamId(uint64 uSteamId);
static CCSPlayerController* FromSteamId(CSteamID steamId);
static CCSPlayerController* FromPawn(CCSPlayerPawn* pPawn);
```
All of them return `nullptr` when nothing matches. Scripts have the same
statics on the `CCSPlayerController` methodmap — `FromSlot(CPlayerSlot)`,
`FromUserId(int)`, `FromSteamId` (the SteamID64 as decimal text) and
`FromPawn` — returning `NULL_ENTITY` instead.
* `FromSlot` reads the entity at index `slot + 1` — controllers always occupy
indices 1 to 64. The `CPlayerSlot` overload also returns null for an invalid
slot (`-1`, which is what the server console has).
* `FromUserId` and `FromSteamId` walk the slots up to `maxClients`. Bots have
a SteamID of 0.
* `FromPawn` follows the pawn's `m_hController` handle; the pawn must not be null.
The command context gives you the caller's slot. The sample plugin's helper:
```cpp title="plugin.cpp"
// A controller for a player, nullptr for the server console.
static CCSPlayerController* CallerOf(const Source2ModCommandContext& context)
{
const CPlayerSlot slot = context.GetPlayerSlot();
return slot.IsValid() ? CCSPlayerController::FromSlot(slot) : nullptr;
}
```
```cpp title="plugin.sp"
// A controller for a player, NULL_ENTITY for the server console.
CCSPlayerController CallerOf(Source2ModCommandContext context)
{
CPlayerSlot slot = context.GetPlayerSlot();
return slot.IsValid() ? CCSPlayerController.FromSlot(slot) : NULL_ENTITY;
}
```
For getting the player out of a game event, see [Events](/docs/events).
Iterating players [#iterating-players]
There is no player list to ask for — loop over the slots and skip the empty
ones:
```cpp
for (int i = 0; i < GetGlobalVars()->maxClients; ++i)
{
CCSPlayerController* pPlayer = CCSPlayerController::FromSlot(i);
if (!pPlayer || pPlayer->m_iConnected() != PlayerConnectedState::Connected)
continue;
if (pPlayer->IsBot())
continue;
// ...
}
```
```cpp title="plugin.sp"
for (int i = 0; i < Source2ModApi.GetGlobalVars().maxClients; ++i)
{
CCSPlayerController player = CCSPlayerController.FromSlot(CPlayerSlot(i));
if (player == NULL_ENTITY || !Source2ModClients.IsInGame(CPlayerSlot(i)))
continue;
if (player.IsBot())
continue;
// ...
}
```
A controller exists from the moment a client starts connecting until it has
fully left, so `m_iConnected` is worth checking when you are about to message
or act on the player (a script can ask `Source2ModClients.IsInGame(slot)`). For
"alive players", also check `m_bPawnIsAlive()` and that `GetPlayerPawn()` is
not null.
From controller to everything else [#from-controller-to-everything-else]
| You want | Call |
| ----------------- | ------------------------------------------------------------- |
| The body | `GetPlayerPawn()` → `CCSPlayerPawn*` |
| The observer pawn | `GetObserverPawn()` → `CCSObserverPawn*` |
| Slot | `GetSlot()` (`int`) / `GetPlayerSlot()` (`CPlayerSlot`) |
| Userid | `GetUserID()` (`int`) / `GetPlayerUserID()` (`CPlayerUserId`) |
| SteamID | `GetSteamID()` (`uint64`) / `GetPlayerSteamID()` (`CSteamID`) |
| Name | `GetPlayerName()` |
| IP address | `GetIpAddress()` → `CUtlString`, without the port |
| Engine client | `GetServerSideClient()` → `CServerSideClient*` |
In a script, the text getters fill a buffer (`GetPlayerName(name, sizeof(name))`,
`GetSteamID`, `GetIpAddress`); there is no `GetServerSideClient()`.
Any entity by index [#any-entity-by-index]
`CBaseEntity::FromIndex` looks an entity up by index and casts it to the
type you name; every generated class also has a non-template `FromIndex`
returning its own type. A script has `CBaseEntity.FromIndex(int)` and `view_as`
for the type:
```cpp
CBaseEntity* pEntity = CBaseEntity::FromIndex(nIndex);
CCSPlayerPawn* pPawn = CCSPlayerPawn::FromIndex(nIndex); // same, typed
```
```cpp title="plugin.sp"
CBaseEntity entity = CBaseEntity.FromIndex(index);
CCSPlayerPawn pawn = view_as(CBaseEntity.FromIndex(index)); // typed
```
Both take an `int` or a `CEntityIndex`. **No type check is done** — the cast
is only right if the entity at that index really is that class. Check
`GetClassname()` when you are not sure.
By classname, name or crosshair [#by-classname-name-or-crosshair]
These live on [ISource2ModEntities](/docs/core-api/ISource2ModEntities), with macros
that fill in the `g_pSource2ModEntities` call; a script calls the
`Source2ModEntities` statics:
```cpp
#include "source2mod/ISource2ModEntities.h"
FIND_ENTITY_BY_CLASSNAME(start, "func_breakable") // FindEntityByClassname
FIND_ENTITY_BY_NAME(start, "door_main") // FindEntityByName (targetname)
FIND_PICKER_ENTITY(pController) // FindPickerEntity: under the crosshair
GET_GAME_RULES() // the live CCSGameRules, or null
```
```cpp title="plugin.sp"
Source2ModEntities.FindEntityByClassname(start, "func_breakable") // NULL_ENTITY when none
Source2ModEntities.FindEntityByName(start, "door_main") // by targetname
Source2ModEntities.FindPickerEntity(controller) // under the crosshair
Source2ModEntities.GetGameRules() // the live CCSGameRules
```
The `Find*` functions return one match at a time. Pass `nullptr` (`NULL_ENTITY`
in a script) to start and the previous match to continue:
```cpp title="plugin.cpp"
int nBroken = 0;
CBaseEntity* pEntity = nullptr;
while ((pEntity = FIND_ENTITY_BY_CLASSNAME(pEntity, "func_breakable")) != nullptr)
{
pEntity->AcceptInput("Break");
nBroken++;
}
```
```cpp title="plugin.sp"
int broken = 0;
CBaseEntity entity = NULL_ENTITY;
while ((entity = Source2ModEntities.FindEntityByClassname(entity, "func_breakable")) != NULL_ENTITY)
{
entity.AcceptInput("Break");
broken++;
}
```
`FindEntityByName` has optional searching-entity, activator, caller and filter
arguments, the same as the engine function it wraps (in a script, all but the
filter).
All at once [#all-at-once]
`UTIL_FindAllEntitiesByDesignerName` (in `ISource2ModEntities.h`) returns a
`std::vector` of every live entity whose designer name matches exactly. It
walks the entity system's active list directly. A script gets the same as an
`ArrayList` of entities from `Source2ModEntities.FindAllEntitiesByDesignerName`,
and closes it:
```cpp
for (auto* pTeam : UTIL_FindAllEntitiesByDesignerName("cs_team_manager"))
{
// ...
}
for (auto* pProp : UTIL_FindAllEntitiesByDesignerName("prop_dynamic"))
pProp->AcceptInput("Kill");
```
```cpp title="plugin.sp"
ArrayList teams = Source2ModEntities.FindAllEntitiesByDesignerName("cs_team_manager");
for (int i = 0; i < teams.Length; i++)
{
CTeam team = teams.Get(i);
// ...
}
delete teams;
ArrayList props = Source2ModEntities.FindAllEntitiesByDesignerName("prop_dynamic");
for (int i = 0; i < props.Length; i++)
view_as(props.Get(i)).AcceptInput("Kill");
delete props;
```
Like `FromIndex`, the cast is unchecked — keep the name and the type consistent.
Watching entities come and go [#watching-entities-come-and-go]
Instead of searching, you can be told. Implement `IEntityListener` (from the
engine's `entity2/entitysystem.h`) and register it with `ADD_ENTITY_LISTENER`.
A script passes one function per callback to `Source2ModEntities.AddEntityListener`
(`INVALID_FUNCTION` for the ones it does not need):
```cpp title="plugin.h"
class MyPlugin : public ISource2ModPlugin, public IEntityListener
{
void OnEntityCreated(CEntityInstance* pEntity) override;
void OnEntitySpawned(CEntityInstance* pEntity) override;
void OnEntityDeleted(CEntityInstance* pEntity) override;
void OnEntityParentChanged(CEntityInstance* pEntity, CEntityInstance* pNewParent) override;
};
```
```cpp title="plugin.sp"
public void OnEntityCreated(CEntityInstance entity) {}
public void OnEntitySpawned(CEntityInstance entity) {}
public void OnEntityDeleted(CEntityInstance entity) {}
public void OnEntityParentChanged(CEntityInstance entity, CEntityInstance newParent) {}
```
```cpp title="plugin.cpp"
ADD_ENTITY_LISTENER(this); // in Load()
```
```cpp title="plugin.sp"
int listener = Source2ModEntities.AddEntityListener(OnEntityCreated, OnEntitySpawned,
OnEntityDeleted, OnEntityParentChanged); // in OnPluginStart()
```
`OnEntityCreated` is early — the keyvalues have not been applied and the entity
has not spawned. `OnEntitySpawned` is usually the one you want. Some entities
are filled in by the game right after they spawn, so a change made there can be
overwritten; the sample plugin waits one frame, through a handle:
```cpp title="plugin.cpp"
void SamplePlugin::OnEntitySpawned(CEntityInstance* pEntity)
{
if (V_strcmp(pEntity->GetClassname(), "smokegrenade_projectile") != 0)
return;
CHandle hSmoke = static_cast(pEntity)->GetHandle();
NEXT_FRAME([hSmoke]()
{
if (CSmokeGrenadeProjectile* pSmoke = hSmoke.Get())
pSmoke->m_vSmokeColor = Vector(170.0f, 0.0f, 255.0f);
});
}
```
```cpp title="plugin.sp"
public void OnEntitySpawned(CEntityInstance entity)
{
char classname[64];
entity.GetClassname(classname, sizeof(classname));
if (!Source2ModUtils.Equal(classname, "smokegrenade_projectile"))
return;
// The entity is a handle: if the smoke is gone by the next frame,
// IsValid() says so.
Source2ModScheduler.NextFrame(PaintSmoke, entity);
}
public void PaintSmoke(any data)
{
CSmokeGrenadeProjectile smoke = view_as(data);
if (!smoke.IsValid())
return;
float purple[3] = { 170.0, 0.0, 255.0 };
smoke.Set_m_vSmokeColor(purple);
}
```
`AddEntityListener(IEntityListener* pListener)` takes no plugin id; the core
works out which plugin the listener belongs to from the object's vtable, i.e.
the library its class lives in. The listener is removed for you when that
plugin unloads; `REMOVE_ENTITY_LISTENER(this)` removes it earlier. In a script,
the id `AddEntityListener` returns is what `Source2ModEntities.RemoveEntityListener`
takes, and the listener also goes when the plugin unloads.
# Entities (/docs/entities)
Almost everything on a CS2 server is an entity: players, weapons, grenades,
doors, triggers, the game rules, even a Panorama HUD layout. The SDK gives you
every one of them as a C++ class generated from the game's own schema, so you
work with `CCSPlayerPawn*` and `pPawn->m_iHealth` instead of raw offsets.
This section covers finding entities, reading and writing their fields,
creating new ones, the common player helpers, entity I/O and damage.
Players by slot, userid or SteamID; entities by index, classname or name; handles.
Reading and writing fields, when a change is networked, and adding your own helpers.
New, DispatchSpawn, keyvalues, Teleport, SetModel, particles and grenades.
Respawn, teams, damage, items and weapons, printing, kicking — and HookChain.
Inputs, queued events and output listeners.
CTakeDamageInfo, hooking TakeDamageOld and dealing damage yourself.
Controller and pawn [#controller-and-pawn]
A CS2 player is **two** entities:
| Entity | Class | Classname | Lives |
| ---------- | --------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| Controller | `CCSPlayerController` | `cs_player_controller` | From connect to disconnect. Holds the name, SteamID, team, score, money. |
| Pawn | `CCSPlayerPawn` | `player` | The body in the world. Holds health, armor, position, weapons. Replaced or reused across deaths and respawns. |
The controller is the one to keep track of a *player*; the pawn is the one to
act on when something happens to their *body*. They point at each other:
```cpp
CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); // m_hPlayerPawn
CCSPlayerController* pOwner = pPawn->GetController(); // m_hController
CCSPlayerController* pSame = CCSPlayerController::FromPawn(pPawn);
```
```cpp title="plugin.sp"
CCSPlayerPawn pawn = controller.GetPlayerPawn(); // m_hPlayerPawn
CCSPlayerController owner = pawn.GetController(); // m_hController
CCSPlayerController same = CCSPlayerController.FromPawn(pawn);
```
A dead or spectating player also has an observer pawn (`CCSObserverPawn`,
`GetObserverPawn()`). `CBasePlayerController::GetPawn()` returns `m_hPawn`,
whichever pawn the controller is currently driving, cast to `CCSPlayerPawn*` —
when you mean "the player's body", use `GetPlayerPawn()`.
Health lives on the pawn. The controller has `m_iPawnHealth` and
`m_bPawnIsAlive`, which the game mirrors for the scoreboard; read them, but
change health on the pawn.
Services [#services]
A lot of behaviour is not on the pawn or controller itself but on **service**
objects hanging off them as pointers:
| Pointer | Class | For |
| --------------------------------------- | ----------------------------------------- | ------------------------------------------------------------- |
| `pPawn->m_pItemServices()` | `CPlayer_ItemServices` | `GiveNamedItem`, `RemoveWeapons` |
| `pPawn->m_pWeaponServices()` | `CPlayer_WeaponServices` | `m_hMyWeapons`, `m_hActiveWeapon`, `DropWeapon`, `SelectItem` |
| `pController->m_pInGameMoneyServices()` | `CCSPlayerController_InGameMoneyServices` | `m_iAccount` (money) |
Any of these can be null on a pawn that does not have them, so check before use.
In a script they are properties (`pawn.m_pItemServices`), and a missing one
reads `IsNull`.
Service fields are networked through the entity that owns them; writing one
works exactly like writing a field on the entity (see
[Schema fields](/docs/entities/schema-fields)).
Indices, slots and handles [#indices-slots-and-handles]
| Type | What it is | Get it |
| --------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `CEntityIndex` | Position in the entity list (`.Get()` for the int). Reused after the entity is gone. | `pEntity->GetEntityIndex()`, `GetIndex()` for the int |
| `CPlayerSlot` | Player slot, 0-based. A controller's entity index is always slot + 1. | `pController->GetPlayerSlot()`, `GetSlot()` for the int |
| `CPlayerUserId` | The engine's userid, as game events carry it. | `pController->GetPlayerUserID()`, `GetUserID()` for the int |
| `CHandle` | Index **plus** a serial number. Resolves to null once the entity is removed, even if the index is reused. | `pEntity->GetHandle()` |
The rule: a raw pointer is only good for the current call. Anything that
crosses a frame — a timer, a `NEXT_FRAME` lambda, a map in your plugin — holds a
`CHandle` and checks it when it comes back. A script has no raw pointers: every
entity value already is a handle, kept as it is and checked with `IsValid()`:
```cpp title="plugin.cpp"
CHandle hPlayer = pController->GetHandle();
g_pSource2ModScheduler->AddTimer(3.0f, [hPlayer]()
{
CCSPlayerController* pPlayer = hPlayer.Get();
if (!pPlayer)
return; // disconnected in the meantime
pPlayer->PrintToChat("Three seconds later");
});
```
```cpp title="plugin.sp"
void RemindLater(CCSPlayerController controller)
{
// The entity value is a handle already: pass it on as it is.
Source2ModScheduler.AddTimer(3.0, Timer_Later, controller);
}
public void Timer_Later(any data)
{
CCSPlayerController player = view_as(data);
if (!player.IsValid())
return; // disconnected in the meantime
player.PrintToChat("Three seconds later");
}
```
In C++, every generated entity class shadows `GetHandle()` with one returning a handle
of its own type, so `pController->GetHandle()` is a
`CHandle` without a template argument.
How the SDK exposes schema classes [#how-the-sdk-exposes-schema-classes]
The classes under `source2mod/schema/entity/classes/` are generated from a
dump of the game's schema by `tools/schemagen/generator.py`, one header per
class, following the game's own inheritance
(`CCSPlayerPawn` → `CCSPlayerPawnBase` → `CBasePlayerPawn` → … → `CBaseEntity`
→ `CEntityInstance`). Each class carries:
* **Fields** — one `SCHEMA_FIELD` per schema member. The offset is looked up by
name in the running game's schema the first time you touch it, so a CS2
update that moves a field does not break your binary.
* **Helpers** — hand-written methods (`GiveNamedItem`, `Teleport`,
`PrintToChat`, …) that call into the game. They are declared by the
generator so they survive regeneration and implemented in the SDK's
`src/schema/entity/classes/*.cpp`.
* **Static constructors** — `New(classname)`, `FromIndex(index)` and a typed
`GetHandle()` on every entity class.
Scripts get the same classes as methodmaps of the same names and bases
(`source2mod/schema/classes.inc`, generated too): fields are properties
(`pawn.m_iHealth`), helpers are methods, and `FromIndex` is on `CBaseEntity`.
Include the header of the class you use; a script includes them all at once:
```cpp
#include "source2mod/schema/entity/classes/CCSPlayerController.h"
#include "source2mod/schema/entity/classes/CCSPlayerPawn.h"
```
```cpp title="plugin.sp"
#include // every schema class, as a methodmap of the same name
```
`dynamic_cast` works on game types: the SDK checks the cast against the
engine's own RTTI and caches the answer, so a cast costs a few nanoseconds
(see [Casting](/docs/development/cpp-for-csharp-developers#casting)). When you
already know the type, `static_cast` is enough. In a script, check the
classname (`GetClassname()`, e.g. `"cs_player_controller"`) and `view_as` the
methodmap.
The full list of classes, fields and helpers is in the
[schema reference](/docs/schema/entity/classes/CBaseEntity); lookup and entity
I/O functions are on [ISource2ModEntities](/docs/core-api/ISource2ModEntities).
# Players (/docs/entities/players)
The generated player classes carry helpers for the things plugins do all the
time. This page groups them by task. Every signature here is copied from the
SDK headers (C++); the script methodmaps have the same methods, with buffers for
strings and `NULL_ENTITY` for a null entity. The [schema reference](/docs/schema/entity/classes/CCSPlayerController)
has the complete list per class.
HookChain [#hookchain]
Most of these helpers call a game function that plugins also like to hook —
`TakeDamage`, `GiveNamedItem`, `SwitchTeam`. So each one takes a last argument
saying whether your call goes through those hooks:
```cpp title="ISource2ModTypes.h"
enum class HookChain : uint8_t
{
Run, // through the Pre/Post chain, as if the game made the call
Bypass, // straight to the original, past every hook
};
```
* **`HookChain::Run`** — right when your plugin stands in for the game: a
custom weapon dealing damage, a round reward handing out an item. Other
plugins' rules (a no-friendly-fire plugin, a weapon restriction) should
apply, so they get to see and veto the call.
* **`HookChain::Bypass`** — right when the server was *told* to do it: an admin
command, a forced round end. Also required when you call the very function
you are hooking from inside your own hook, where `Run` would recurse.
Scripts pass `Source2ModHookChain`: `HookChain_Run`, `HookChain_Bypass`, or
`HookChain_Default`, the default, which keeps the C++ helper's default.
`Bypass` skips both inline detours and vtable hooks. The default of each
helper is what it did before the argument existed, so omitting it keeps the
old behaviour:
| Helper | Default |
| --------------------------------------------------------------------------------------------------------------- | ---------- |
| `CCSPlayerController::TakeDamage` | **Bypass** |
| `CCSGameRules::TerminateRound` | **Bypass** |
| `CCSPlayerController::Respawn`, `SwitchTeam`, `ChangeTeam` | Run |
| `CBasePlayerController::SetPawn` | Run |
| `CBasePlayerPawn::CommitSuicide`, `SnapViewAngles` | Run |
| `CCSPlayerPawn::PostThink` | Run |
| `CPlayer_ItemServices::GiveNamedItem`, `RemoveWeapons`, `DropActivePlayerWeapon` | Run |
| `CPlayer_WeaponServices::DropWeapon`, `BumpWeapon`, `SelectItem`, `Destroy` | Run |
| `CAttributeList::SetOrAddAttribute`, `SetOrAddAttributeValueByName` | Run |
| `CBaseEntity::AcceptInput`, `AddEntityIOEvent`, `DispatchSpawn`, `Teleport`, `Remove`, `DispatchParticleEffect` | Run |
| `CBaseModelEntity::SetModel`, `CCSGameRules::GoToIntermission`, `C*Projectile::EmitGrenade` | Run |
```cpp
pItems->GiveNamedItem("weapon_ak47"); // hooks run
pVictim->TakeDamage(pAttacker, 50, DMG_BULLET, HookChain::Run); // opt in
```
```cpp title="plugin.sp"
items.GiveNamedItem("weapon_ak47"); // hooks run
victim.TakeDamage(attacker, 50, 2 /* DMG_BULLET */, HookChain_Run); // opt in
```
For calling an `ADDR_*` function directly past its hooks there is
`SOURCE2MOD_ORIGINAL(fn)`; see [Hooking](/docs/hooks).
Pawn and life [#pawn-and-life]
```cpp
CCSPlayerPawn* CCSPlayerController::GetPlayerPawn();
void CCSPlayerController::Respawn(HookChain eChain = HookChain::Run);
void CBasePlayerPawn::CommitSuicide(bool bExplode, bool bForce, HookChain eChain = HookChain::Run);
void CBasePlayerPawn::SnapViewAngles(const QAngle& angEyeAngles, HookChain eChain = HookChain::Run);
```
`Respawn` does nothing if the controller has no player pawn. It puts the
player pawn back on the controller and runs the controller's respawn.
`CommitSuicide(bExplode, bForce)` calls the pawn's own suicide virtual with
the two flags as the game defines them; the sample plugin's slay command uses
`CommitSuicide(false, true)`.
`SnapViewAngles` sets where the player looks. It is a no-op on platforms where
the engine function could not be resolved — currently it is only found on Linux.
```cpp title="plugin.cpp"
if (CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); pPawn && pController->m_bPawnIsAlive())
{
pPawn->m_iHealth = 200;
pPawn->m_ArmorValue = 100;
}
```
```cpp title="plugin.sp"
CCSPlayerPawn pawn = controller.GetPlayerPawn();
if (pawn != NULL_ENTITY && controller.m_bPawnIsAlive)
{
pawn.m_iHealth = 200;
pawn.m_ArmorValue = 100;
}
```
Teams [#teams]
```cpp
void CCSPlayerController::SwitchTeam(int nTeam, HookChain eChain = HookChain::Run);
void CCSPlayerController::ChangeTeam(int nTeam, HookChain eChain = HookChain::Run);
```
* **`SwitchTeam`** moves the player to the team without killing them.
* **`ChangeTeam`** is the controller's `ChangeTeam` virtual — the `jointeam`
path, the same thing as a player picking a team in the menu, with what that
means for a living player.
Team numbers are `CS_TEAM_NONE` (0), `CS_TEAM_SPECTATOR` (1), `CS_TEAM_T` (2)
and `CS_TEAM_CT` (3), from the game's `shareddefs.h`; scripts use the numbers.
The current team is `m_iTeamNum()`.
Damage [#damage]
```cpp
void CCSPlayerController::TakeDamage(CCSPlayerController* pAttacker, int iDamage,
DamageTypes_t bitsDamageType,
HookChain eChain = HookChain::Bypass);
```
Deals damage to this player's pawn as if `pAttacker`'s pawn did it. It does
nothing unless both players are connected, the victim is alive and both have a
pawn. The damage is flagged `DFLAG_SUPPRESS_DAMAGE_MODIFICATION`, so armor and
the game's scaling do not change the number you asked for.
It **bypasses** hooks by default, because damage a plugin was asked to deal
is not the game dealing damage — a plugin policing hits in `TakeDamageOld`
should not have to tell them apart. Pass `HookChain::Run` when you are standing
in for a weapon. For damage without an attacker player, or with full control
over the damage info, see [Damage](/docs/entities/damage).
Items and weapons [#items-and-weapons]
The item and weapon services hang off the pawn:
```cpp
CPlayer_ItemServices* pItems = pPawn->m_pItemServices();
CPlayer_WeaponServices* pWeapons = pPawn->m_pWeaponServices();
```
```cpp title="plugin.sp"
CPlayer_ItemServices items = pawn.m_pItemServices;
CPlayer_WeaponServices weapons = pawn.m_pWeaponServices;
```
`CPlayer_ItemServices`:
```cpp
CBasePlayerWeapon* GiveNamedItem(const char* pszItem, HookChain eChain = HookChain::Run);
void RemoveWeapons(bool bRemoveSuit, HookChain eChain = HookChain::Run);
void DropActivePlayerWeapon(CBasePlayerWeapon* pActiveWeapon, HookChain eChain = HookChain::Run);
```
`GiveNamedItem` takes an item classname (`"weapon_ak47"`, `"item_assaultsuit"`)
and returns the weapon, or null. `DropActivePlayerWeapon` ignores its argument
and drops whatever is active — prefer `DropWeapon`.
`CPlayer_WeaponServices`:
```cpp
bool CanUse(CBasePlayerWeapon* pWeapon);
void DropWeapon(CBasePlayerWeapon* pWeapon, Vector* pVecTarget = nullptr, Vector* pVelocity = nullptr, HookChain eChain = HookChain::Run);
int BumpWeapon(CBasePlayerWeapon* pWeapon, HookChain eChain = HookChain::Run);
void SelectItem(CBasePlayerWeapon* pWeapon, int unk1 = 0, HookChain eChain = HookChain::Run);
void Destroy(CBasePlayerWeapon* pWeapon, HookChain eChain = HookChain::Run);
```
`Destroy` removes a weapon from the player and deletes it. The inventory is
`m_hMyWeapons()` and the weapon in hand `m_hActiveWeapon()`, both handles. In a
script `m_hActiveWeapon` is a property, and the `m_hMyWeapons` vector is read
with `Source2ModSchema.GetUtlVectorCount` / `GetUtlVectorElement` (4 bytes, one
handle, per element):
```cpp title="plugin.cpp"
// Strip everything except the knife.
for (const auto& hWeapon : pWeapons->m_hMyWeapons())
{
CBasePlayerWeapon* pWeapon = hWeapon.Get();
if (!pWeapon)
continue;
if (V_strstr(pWeapon->GetWeaponClassname(), "knife"))
continue;
pWeapons->DropWeapon(pWeapon);
pWeapon->Remove();
}
```
```cpp title="plugin.sp"
// Strip everything except the knife. From the end: dropping changes the list.
int count = Source2ModSchema.GetUtlVectorCount(weapons, "CPlayer_WeaponServices", "m_hMyWeapons");
for (int i = count - 1; i >= 0; i--)
{
SchemaObject element = Source2ModSchema.GetUtlVectorElement(weapons, "CPlayer_WeaponServices", "m_hMyWeapons", i, 4);
CBasePlayerWeapon weapon = view_as(Source2ModSchema.GetEntityAt(element, 0));
if (weapon == NULL_ENTITY)
continue;
char classname[64];
weapon.GetWeaponClassname(classname, sizeof(classname));
if (Source2ModUtils.Find(classname, "knife") != -1)
continue;
weapons.DropWeapon(weapon);
weapon.Remove();
}
```
`CBasePlayerWeapon::GetWeaponVData()` returns the weapon's `CCSWeaponBaseVData`
(damage, clip size, price …); `CCSWeaponBaseVData::FromKey("weapon_ak47")`
looks the same data up without a weapon.
Money is on the controller:
```cpp
if (auto* pMoney = pController->m_pInGameMoneyServices())
pMoney->m_iAccount = 16000;
```
```cpp title="plugin.sp"
CCSPlayerController_InGameMoneyServices money = controller.m_pInGameMoneyServices;
if (!money.IsNull)
money.m_iAccount = 16000;
```
Attributes [#attributes]
Econ attributes (skins, stickers, custom values) are set on a `CAttributeList`:
```cpp
void CAttributeList::SetOrAddAttribute(const char* pszAttributeName, float flValue, HookChain eChain = HookChain::Run);
void CAttributeList::SetOrAddAttributeValueByName(const char* pszAttributeName, float flValue, HookChain eChain = HookChain::Run);
```
The two are the same call; the second has the engine's name. A weapon's
lists are on its item:
```cpp
CAttributeList& attrs = pWeapon->m_AttributeManager().m_Item().m_NetworkedDynamicAttributes();
attrs.SetOrAddAttribute("set item texture prefab", 44.0f);
```
```cpp title="plugin.sp"
CAttributeList attrs = weapon.m_AttributeManager.m_Item.m_NetworkedDynamicAttributes;
attrs.SetOrAddAttribute("set item texture prefab", 44.0);
```
Messages [#messages]
```cpp
void CBasePlayerController::PrintToChat(const char* pszMessage);
void CBasePlayerController::PrintToCenter(const char* pszMessage);
void CBasePlayerController::PrintToCenterAlert(const char* pszMessage);
void CBasePlayerController::PrintToConsole(const char* pszMessage); // adds the newline
void CCSPlayerController::PrintToCenterHtml(const char* pszMessage, int iDuration = 5, bool bMenu = false);
```
The first four send a `TextMsg` user message to this player only. They take a
finished string — format it first (`Source2ModApi.Format` in a script):
```cpp
char szBuffer[256];
V_snprintf(szBuffer, sizeof(szBuffer), "You have %d HP", pPawn->m_iHealth());
pController->PrintToChat(szBuffer);
```
```cpp title="plugin.sp"
char buffer[256];
Source2ModApi.Format(buffer, sizeof(buffer), "You have %d HP", pawn.m_iHealth);
controller.PrintToChat(buffer);
```
`PrintToCenterHtml` shows HTML in the center panel for `iDuration` seconds.
While the player has a [menu](/docs/guides/menus) open it does nothing unless
`bMenu` is true, so it cannot overwrite the menu.
Commands, convars and events on the client [#commands-convars-and-events-on-the-client]
```cpp
void ExecuteClientCommand(const char* pszCommand); // runs on the client
void ExecuteClientCommandFromServer(const char* pszCommand); // dispatches a server command as if the player ran it
void ReplicateConVar(const char* pszConVar, const char* pszValue);
void FireEventToClient(IGameEvent* pEvent); // to this player's legacy listener only
```
`ReplicateConVar` tells one client a replicated convar has a different value,
without changing it on the server.
Kicking [#kicking]
```cpp
void CBasePlayerController::Disconnect(ENetworkDisconnectionReason eReason,
const char* pszInternalReason = nullptr);
```
```cpp
pController->Disconnect(NETWORK_DISCONNECT_KICKED, "Kicked by admin");
```
```cpp title="plugin.sp"
controller.Disconnect(39 /* NETWORK_DISCONNECT_KICKED */, "Kicked by admin");
```
The disconnect happens on the **next frame**; the reason string is copied, so a
temporary is fine. The player's open menu is closed first, and nothing happens
if the player is not fully connected.
Other controller helpers [#other-controller-helpers]
| Helper | Does |
| -------------------------------------------- | ------------------------------------------------------- |
| `IsBot()` | `FL_FAKECLIENT` is set |
| `SetPawn(pPawn, eChain)` | Puts a pawn on the controller |
| `GetServerSideClient()` | The engine's `CServerSideClient` for this slot, or null |
| `GetObserverPawn()` | The observer pawn, or null |
| `CCSPlayerPawnBase::CanMove()` | False while frozen, defusing, … |
| `CCSPlayerPawnBase::GetOriginalController()` | The controller the pawn was created for |
# Schema Fields (/docs/entities/schema-fields)
Every member of a schema class is declared with `SCHEMA_FIELD` (or
`SCHEMA_FIELD_POINTER`). The macro produces a small wrapper object with no
storage of its own: it finds the field's offset in the running game's schema
by name the first time it is used, and from then on reads and writes the
entity's memory at that offset.
```cpp title="CBaseEntity.h (generated)"
class CBaseEntity : public CEntityInstance
{
public:
DECLARE_SCHEMA_CLASS(CBaseEntity);
SCHEMA_FIELD(int32_t, m_iHealth);
SCHEMA_FIELD(uint8_t, m_iTeamNum);
SCHEMA_FIELD(CHandle, m_hOwnerEntity);
SCHEMA_FIELD(CCollisionProperty*, m_pCollision);
// ...
};
```
In a script the same fields are properties of the class's methodmap
(`source2mod/schema/classes.inc`). A field that cannot be a property — a
vector, a color, a string — is a method of its name that fills a buffer, with
`Set_()` to write it.
Read with a call, write with an assignment [#read-with-a-call-write-with-an-assignment]
```cpp title="plugin.cpp"
const int nOld = pPawn->m_iHealth(); // read: operator() returns a reference
pPawn->m_iHealth = 150; // write: goes through Set()
pPawn->m_iHealth += 25; // read-modify-write, also through Set()
```
```cpp title="plugin.sp"
int old = pawn.m_iHealth; // read: the property's getter
pawn.m_iHealth = 150; // write: the setter, which marks the field changed
pawn.m_iHealth += 25; // read-modify-write, also through the setter
```
Reading also works through the implicit conversion
(`int n = pPawn->m_iHealth;`) and `Get()`. The call form is the clearest and
the one used across the SDK.
**Writing through the wrapper is what makes the change reach clients.** `=`,
`Set()`, `++`, `--`, `+=`, `-=`, `*=`, `/=`, and for integers and enums `|=`,
`&=`, `^=`, all call `Set()`, which marks the field as changed before writing
it. The engine then includes it in the next snapshot.
```cpp
pPawn->m_fFlags |= FL_FROZEN; // announced
pPawn->m_fFlags() |= FL_FROZEN; // NOT announced: writes through the raw reference
```
`field()` hands you a plain reference to the memory, and anything you do to
that reference is invisible to the network layer. That is useful on purpose
(several writes, then one `NetworkStateChanged()`), and a bug by accident.
A script has no raw reference: every property write goes through the setter and
is announced.
Only fields that are networked are marked; writing a server-only field through
`=` just writes it.
What can be assigned [#what-can-be-assigned]
`=` is available for pointers, trivially copyable types (numbers, enums, `bool`,
handles, `Vector`, `QAngle`, `Color`, …). The assigned value is converted to the
field's own type, so `m_nByteField = 1` writes one byte, not four.
For everything else — `CUtlVector`, strings, embedded classes — you take the
reference with `()` and change it in place (see below).
Calling NetworkStateChanged yourself [#calling-networkstatechanged-yourself]
Every field wrapper has `NetworkStateChanged()`. Call it after any write that
did not go through `Set()`; a script calls `Source2ModSchema.StateChanged` with
the class and field name:
```cpp
auto& vecWeapons = pWeaponServices->m_hMyWeapons(); // CUtlVector>&
// ... modify vecWeapons ...
pWeaponServices->m_hMyWeapons.NetworkStateChanged();
```
```cpp title="plugin.sp"
// ... modify m_hMyWeapons through Source2ModSchema's raw-memory natives ...
Source2ModSchema.StateChanged(weaponServices, "CPlayer_WeaponServices", "m_hMyWeapons");
```
The cases where you need it:
* **Vectors and other non-assignable types**, modified through `()`.
* **Fixed arrays and char buffers**, declared with `SCHEMA_FIELD_POINTER` —
it has no setter at all (below).
* **Raw writes through `()`** that you batch on purpose.
If you only have a class name and a field name at runtime,
`schema::SetStateChanged(pEntity, "m_iHealth")` does the same, taking the
class from the pointer's static type.
How the change is routed [#how-the-change-is-routed]
You do not have to pick; the wrapper does it from the class declaration:
| Declared with | What it is | NetworkStateChanged goes to |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `DECLARE_SCHEMA_CLASS` | An entity class | The entity's own `NetworkStateChanged` |
| `DECLARE_SCHEMA_CLASS_INLINE` | An object embedded in an entity or hanging off one (`CCollisionProperty`, `CGameSceneNode`, `CEconItemView`, services, `CCSGameRules`) | Through the class's `__m_pChainEntity` chainer to the owning entity when the class has one (services, game rules); otherwise the `NetworkStateChanged` virtual of the NetworkVar wrapper the object is embedded in |
For the embedded case the SDK finds that virtual's vtable slot at runtime from
a gamedata signature, per vtable, so no indices are kept per class. If the
signature is missing, a warning is printed once and embedded fields are not
networked.
Embedded objects [#embedded-objects]
A field whose type is a class (not a pointer) is an object stored inside the
entity. `()` gives you a reference to it (in a script, the property is the
object), and its own fields work as usual:
```cpp
// CBaseModelEntity: SCHEMA_FIELD(CCollisionProperty, m_Collision)
pModel->m_Collision().m_CollisionGroup = COLLISION_GROUP_DEBRIS; // announced
```
```cpp title="plugin.sp"
// CBaseModelEntity: property CCollisionProperty m_Collision
model.m_Collision.m_CollisionGroup = 5; // COLLISION_GROUP_DEBRIS, announced
```
The same chain works as deep as it goes:
```cpp
// CBasePlayerWeapon -> m_AttributeManager -> m_Item -> m_NetworkedDynamicAttributes
CAttributeList& attrs = pWeapon->m_AttributeManager().m_Item().m_NetworkedDynamicAttributes();
```
```cpp title="plugin.sp"
// CBasePlayerWeapon -> m_AttributeManager -> m_Item -> m_NetworkedDynamicAttributes
CAttributeList attrs = weapon.m_AttributeManager.m_Item.m_NetworkedDynamicAttributes;
```
Pointers and handles [#pointers-and-handles]
A pointer field returns the pointer; `->` on the wrapper reaches straight
through it. In a script, a pointer to an object reads as a `SchemaObject`
(check `IsNull`) and a vector field fills a `float[3]`:
```cpp
CPlayer_ItemServices* pItems = pPawn->m_pItemServices();
if (pItems)
pItems->GiveNamedItem("weapon_ak47");
Vector vecOrigin = pEntity->m_CBodyComponent->m_pSceneNode->m_vecAbsOrigin();
```
```cpp title="plugin.sp"
CPlayer_ItemServices items = pawn.m_pItemServices;
if (!items.IsNull)
items.GiveNamedItem("weapon_ak47");
float origin[3];
entity.m_CBodyComponent.m_pSceneNode.m_vecAbsOrigin(origin);
```
Pointer fields can be null — check them. Handle fields are `CHandle` values;
`.Get()` resolves them and returns null for a removed entity. In a script a
handle field reads as the entity, `NULL_ENTITY` when empty or removed:
```cpp
if (CBaseEntity* pOwner = pEntity->m_hOwnerEntity().Get())
// ...
```
```cpp title="plugin.sp"
CBaseEntity owner = entity.m_hOwnerEntity;
if (owner != NULL_ENTITY)
{
// ...
}
```
Fixed arrays and char buffers [#fixed-arrays-and-char-buffers]
Fields that are C arrays in the game are declared with `SCHEMA_FIELD_POINTER`,
which returns a pointer to the first element instead of a reference. It has
no setter, so announce the change yourself. A script writes a char array
through its `Set_()` setter, which announces it; any other array is raw
memory at the field's offset, followed by `Source2ModSchema.StateChanged`:
```cpp
// CPointWorldText: SCHEMA_FIELD_POINTER(char, m_messageText)
V_strncpy(pText->m_messageText, "Hello", 512);
pText->m_messageText.NetworkStateChanged();
// CPlayer_WeaponServices: SCHEMA_FIELD_POINTER(uint16_t, m_iAmmo)
pWeaponServices->m_iAmmo()[nAmmoType] = 90;
pWeaponServices->m_iAmmo.NetworkStateChanged();
```
```cpp title="plugin.sp"
// CPointWorldText: m_messageText has a setter, which announces the change
text.Set_m_messageText("Hello");
// CPlayer_WeaponServices: m_iAmmo is a uint16_t array, written as raw memory
int offset = Source2ModSchema.GetOffset("CPlayer_WeaponServices", "m_iAmmo");
Source2ModSchema.SetIntAt(weaponServices, offset + ammoType * 2, 90, 2);
Source2ModSchema.StateChanged(weaponServices, "CPlayer_WeaponServices", "m_iAmmo");
```
Vectors [#vectors]
`CUtlVector` fields are read and changed through the reference; a script uses
`Source2ModSchema.GetUtlVectorCount` and `GetUtlVectorElement`:
```cpp
for (const auto& hWeapon : pWeaponServices->m_hMyWeapons())
{
if (CBasePlayerWeapon* pWeapon = hWeapon.Get())
// ...
}
```
```cpp title="plugin.sp"
int count = Source2ModSchema.GetUtlVectorCount(weaponServices, "CPlayer_WeaponServices", "m_hMyWeapons");
for (int i = 0; i < count; i++)
{
SchemaObject element = Source2ModSchema.GetUtlVectorElement(weaponServices, "CPlayer_WeaponServices", "m_hMyWeapons", i, 4);
CBasePlayerWeapon weapon = view_as(Source2ModSchema.GetEntityAt(element, 0));
if (weapon != NULL_ENTITY)
{
// ...
}
}
```
Some networked vectors are **not** laid out like a plain `CUtlVector`
(`CUtlVectorEmbeddedNetworkVar`, for example), and indexing them directly reads
the wrong memory. For those the schema system publishes an element accessor,
available as `field.GetManipulator()` (null for every other field; C++ only). The SDK's
`CCSCustomHudLayout::GetLayoutState` is an example of using it; when a helper
exists for such a field, use the helper.
When a field is missing [#when-a-field-is-missing]
A field the running game does not have — renamed by an update, gone, a class
that lost it — does not write at offset 0 and does not stop the plugin from
loading. The first access logs an error naming the class and the field; reads
return a zeroed value of the field's type, writes and `NetworkStateChanged()`
do nothing. The rest of the plugin keeps working, and the log line says what
to rename.
Adding your own helpers [#adding-your-own-helpers]
The generated classes carry the game's fields and the SDK's helpers, not yours.
You cannot add a method to a class you do not own, and editing the generated
headers is lost on the next regeneration. The pattern that works is a derived
class **with no data members and no virtual functions**, plus a cast. In a
script it is a methodmap deriving from the class, with methods and properties
only, and `view_as`:
```cpp title="extends/CBaseEntityEx.h"
#include "source2mod/schema/entity/classes/CBaseEntity.h"
class CBaseEntityEx : public CBaseEntity
{
public:
bool IsPlayerPawn() { return V_strcmp(GetClassname(), "player") == 0; }
bool IsAlive() { return m_lifeState() == LIFE_ALIVE; }
void AddFlags(uint32_t nMask) { m_fFlags |= nMask; }
};
// The whole trick depends on this: add a member or a virtual and the cast
// below silently points at the wrong memory.
static_assert(sizeof(CBaseEntityEx) == sizeof(CBaseEntity));
inline CBaseEntityEx* Ex(CBaseEntity* pEntity)
{
return static_cast(pEntity);
}
```
```cpp title="extends/CBaseEntityEx.sp"
methodmap CBaseEntityEx < CBaseEntity
{
public bool IsPlayerPawn()
{
char classname[64];
this.GetClassname(classname, sizeof(classname));
return Source2ModUtils.Equal(classname, "player");
}
public bool IsAlive() { return this.m_lifeState == 0; } // LIFE_ALIVE
public void AddFlags(int mask) { this.m_fFlags |= mask; }
}
// No cast to check: view_as(entity) is the same entity handle.
```
```cpp
if (Ex(pEntity)->IsPlayerPawn())
Ex(pEntity)->AddFlags(FL_FROZEN);
```
```cpp title="plugin.sp"
CBaseEntityEx ex = view_as(entity);
if (ex.IsPlayerPawn())
ex.AddFlags(1 << 5); // FL_FROZEN
```
A schema class is only a view over engine memory, so a derived class that adds
no storage has exactly the same layout, and the cast costs nothing. The
FUNPLAY plugins use this for every class they extend, with one `Ex()` overload
(or a trait-based template) per class. Before you add a helper, check the SDK
does not already have it on the class or a base class —
`CCSPlayerController` inherits a lot.
Declaring fields and classes yourself [#declaring-fields-and-classes-yourself]
If the game has a field or class the SDK does not generate, declare it the same
way the generator does. The class name is the key into the schema, so it must
match the game's class name exactly. A script declares a methodmap whose
properties call `Source2ModSchema`'s generic accessors, as `classes.inc` does;
an embedded class derives from `SchemaObject`:
```cpp title="my_schema.h"
#include "source2mod/schema/schema.h"
#include "source2mod/schema/entity/classes/CBaseModelEntity.h"
class CMyGameEntity : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CMyGameEntity); // entity class
SCHEMA_FIELD(float, m_flSomething);
SCHEMA_FIELD_POINTER(char, m_szBuffer);
};
class CMyEmbeddedThing
{
public:
DECLARE_SCHEMA_CLASS_INLINE(CMyEmbeddedThing); // non-entity, embedded
SCHEMA_FIELD(int32_t, m_nValue);
};
```
```cpp title="my_schema.sp"
methodmap CMyGameEntity < CBaseModelEntity
{
property float m_flSomething
{
public get() { return Source2ModSchema.GetFloat(this, "CMyGameEntity", "m_flSomething"); }
public set(float value) { Source2ModSchema.SetFloat(this, "CMyGameEntity", "m_flSomething", value); }
}
public int m_szBuffer(char[] buffer, int maxlen) { return Source2ModSchema.GetString(this, "CMyGameEntity", "m_szBuffer", buffer, maxlen); }
public void Set_m_szBuffer(const char[] value) { Source2ModSchema.SetString(this, "CMyGameEntity", "m_szBuffer", value); }
}
methodmap CMyEmbeddedThing < SchemaObject
{
property int m_nValue
{
public get() { return Source2ModSchema.GetInt(this, "CMyEmbeddedThing", "m_nValue"); }
public set(int value) { Source2ModSchema.SetInt(this, "CMyEmbeddedThing", "m_nValue", value); }
}
}
```
Use `DECLARE_SCHEMA_CLASS` for entities and `DECLARE_SCHEMA_CLASS_INLINE` for
objects embedded in an entity — it decides how a change is networked (table
above). Fields of base classes are found through the schema's inheritance, so
you only declare the new ones.
For a one-off lookup without a class, `schema::GetOffset("m_field")`
returns a `SchemaKey` (`offset`, `networked`), and
`schema::GetServerOffset("m_field")` returns just the offset, `-1` when it
does not exist. A script has `Source2ModSchema.GetOffset(className, field)`,
also `-1` when it does not exist.
See the [Schema API reference](/docs/core-api/Schema) for every function in
`schema.h`.
# Event Reference (/docs/events/event-reference)
The fields below are taken from the event definitions CS2 ships in its VPKs —
`resource/core.gameevents` (engine), `resource/game.gameevents` and
`resource/mod.gameevents` (Counter-Strike). Where an event is defined in more
than one file, the most specific definition is listed — `mod` over `game` over
`core`. Names are case sensitive.
For hooking, reading and firing, see [Game events](/docs/events/game-events).
Field types and getters [#field-types-and-getters]
| Type in the definition | Read with |
| ---------------------------- | ---------------------------------------------------------------------------------- |
| `player_controller_and_pawn` | `GetPlayerController(key)`, `GetPlayerPawn(key)`, `GetPlayerSlot(key)` |
| `player_controller` | `GetPlayerController(key)`, `GetPlayerSlot(key)` — the pawn through the controller |
| `player_pawn` | `GetPlayerPawn(key)` |
| `bool` | `GetBool(key)` |
| `byte`, `short`, `long` | `GetInt(key)` |
| `float` | `GetFloat(key)` |
| `string` | `GetString(key)` |
| `uint64` | `GetUint64(key)` |
Player getters return a `CEntityInstance*`; `static_cast` it to
`CCSPlayerController*` / `CCSPlayerPawn*` and check for `nullptr`. In a script
they return the typed handle; check it against `NULL_ENTITY`.
```cpp
auto* pController = static_cast(event->GetPlayerController("userid"));
CCSPlayerPawn* pPawn = pController ? pController->GetPlayerPawn() : nullptr;
```
```cpp
CCSPlayerController controller = event.GetPlayerController("userid");
CCSPlayerPawn pawn = controller != NULL_ENTITY ? controller.GetPlayerPawn() : NULL_ENTITY;
```
Players [#players]
`player_connect` [#player_connect]
A client connected (before it is in the game).
| Field | Type | |
| ----------- | ------------------ | ------------------ |
| `name` | string | player name |
| `userid` | player\_controller | |
| `networkid` | string | network (Steam) id |
| `xuid` | uint64 | SteamID64 |
| `bot` | bool | |
`player_connect_full` [#player_connect_full]
The client finished the connection sequence — the usual "player joined" hook.
| Field | Type |
| -------- | ------------------ |
| `userid` | player\_controller |
`player_disconnect` [#player_disconnect]
| Field | Type | |
| ---------------------- | ------------------ | ------------------------------------------------------- |
| `userid` | player\_controller | |
| `reason` | short | disconnect reason (`ENetworkDisconnectionReason` value) |
| `name` | string | |
| `networkid` | string | |
| `xuid` | uint64 | SteamID64 |
| `PlayerID` | short | |
| `ever_fully_connected` | bool | whether the player ever reached `player_connect_full` |
`player_team` [#player_team]
A player changed team.
| Field | Type | |
| ------------ | ----------------------------- | --------------------------------------------- |
| `userid` | player\_controller\_and\_pawn | |
| `team` | byte | new team (`ETeam`: 1 spectator, 2 T, 3 CT) |
| `oldteam` | byte | previous team |
| `disconnect` | bool | the change is because the player disconnected |
| `silent` | bool | |
| `isbot` | bool | |
`player_spawn` [#player_spawn]
| Field | Type |
| -------- | ----------------------------- |
| `userid` | player\_controller\_and\_pawn |
`player_death` [#player_death]
| Field | Type | |
| --------------------------- | ----------------------------- | --------------------------------- |
| `userid` | player\_controller\_and\_pawn | victim |
| `attacker` | player\_controller\_and\_pawn | killer |
| `assister` | player\_controller\_and\_pawn | assisting player |
| `assistedflash` | bool | the assist was a flash |
| `weapon` | string | weapon name the killer used |
| `weapon_itemid` | string | |
| `weapon_fauxitemid` | string | |
| `weapon_originalowner_xuid` | string | |
| `headshot` | bool | |
| `dominated` | short | |
| `revenge` | short | |
| `wipe` | short | squad wipe |
| `penetrated` | short | objects penetrated by the shot |
| `noreplay` | bool | |
| `noscope` | bool | |
| `thrusmoke` | bool | shot through smoke |
| `attackerblind` | bool | attacker was flashed |
| `distance` | float | distance to the victim, in metres |
| `dmg_health` | short | |
| `dmg_armor` | byte | |
| `hitgroup` | byte | `EHitGroup` value |
| `attackerinair` | bool | |
`player_hurt` [#player_hurt]
| Field | Type | |
| ------------ | ----------------------------- | ----------------- |
| `userid` | player\_controller\_and\_pawn | victim |
| `attacker` | player\_controller\_and\_pawn | |
| `health` | byte | health left |
| `armor` | byte | armour left |
| `weapon` | string | |
| `dmg_health` | short | damage to health |
| `dmg_armor` | byte | damage to armour |
| `hitgroup` | byte | `EHitGroup` value |
`player_hurt` reports damage that was dealt. To change or prevent damage, hook
the damage function instead (see [Hooking](/docs/hooks)).
`player_jump` [#player_jump]
| Field | Type |
| -------- | ------------------ |
| `userid` | player\_controller |
`player_blind` [#player_blind]
| Field | Type | |
| ---------------- | ------------------ | ------------------- |
| `userid` | player\_controller | the flashed player |
| `attacker` | player\_controller | who threw the flash |
| `entityid` | short | the flashbang |
| `blind_duration` | float | |
`player_changename` [#player_changename]
| Field | Type |
| --------- | ------------------ |
| `userid` | player\_controller |
| `oldname` | string |
| `newname` | string |
`player_radio` [#player_radio]
| Field | Type |
| -------- | ----------------------------- |
| `userid` | player\_controller\_and\_pawn |
| `slot` | short |
Rounds and match [#rounds-and-match]
`round_prestart` / `round_poststart` [#round_prestart--round_poststart]
No fields. Sent before and after all other round restart actions.
`round_start` [#round_start]
| Field | Type | |
| ----------- | ------ | ------------------------- |
| `timelimit` | long | round time limit, seconds |
| `fraglimit` | long | |
| `objective` | string | |
`round_freeze_end` [#round_freeze_end]
No fields. Freeze time is over, players can move.
`round_end` [#round_end]
| Field | Type | |
| -------------- | ------ | ------------------------------------- |
| `winner` | byte | winning team (`ETeam`) |
| `reason` | byte | round end reason |
| `message` | string | end-of-round message |
| `legacy` | byte | |
| `player_count` | short | players alive at the end of the round |
| `nomusic` | byte | don't play round end music |
`round_mvp` [#round_mvp]
| Field | Type |
| -------------- | ------------------ |
| `userid` | player\_controller |
| `reason` | short |
| `value` | long |
| `musickitmvps` | long |
| `nomusic` | byte |
| `musickitid` | long |
`round_officially_ended`, `begin_new_match`, `round_announce_match_start`, `cs_win_panel_match`, `warmup_end`, `cs_pre_restart` [#round_officially_ended-begin_new_match-round_announce_match_start-cs_win_panel_match-warmup_end-cs_pre_restart]
No fields.
Bomb [#bomb]
| Event | Fields |
| ------------------ | ----------------------------------------------------------------------------------- |
| `bomb_beginplant` | `userid` (player\_controller\_and\_pawn), `site` (short) |
| `bomb_planted` | `userid` (player\_controller\_and\_pawn), `site` (short), `c4` (short) |
| `bomb_begindefuse` | `userid` (player\_controller\_and\_pawn), `haskit` (bool) |
| `bomb_defused` | `userid` (player\_controller\_and\_pawn), `site` (short), `c4` (short) |
| `bomb_exploded` | `userid` (player\_controller\_and\_pawn, the planter), `site` (short), `c4` (short) |
| `bomb_dropped` | `userid` (player\_controller\_and\_pawn), `entindex` (long) |
| `bomb_pickup` | `userid` (player\_pawn) |
| `player_given_c4` | `userid` (player\_controller) |
Weapons and items [#weapons-and-items]
| Event | Fields |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `weapon_fire` | `userid` (player\_controller\_and\_pawn), `weapon` (string), `silenced` (bool) |
| `weapon_reload` | `userid` (player\_controller\_and\_pawn) |
| `weapon_zoom` | `userid` (player\_controller\_and\_pawn) |
| `bullet_impact` | `userid` (player\_controller\_and\_pawn), `x`, `y`, `z` (float) |
| `item_purchase` | `userid` (player\_controller), `team` (short), `loadout` (short), `weapon` (string) |
| `item_pickup` | `userid` (player\_controller), `item` (string), `silent` (bool), `defindex` (long) |
| `item_equip` | `userid` (player\_controller), `item` (string), `defindex` (long), `canzoom`, `hassilencer`, `issilenced`, `hastracers` (bool), `weptype` (short), `ispainted` (bool) |
`item_equip.weptype`: -1 unknown, 0 knife, 1 pistol, 2 SMG, 3 rifle, 4 shotgun,
5 sniper rifle, 6 machine gun, 7 C4, 8 grenade.
Grenades [#grenades]
| Event | Fields |
| ----------------------- | ----------------------------------------------------------------------------------- |
| `grenade_thrown` | `userid` (player\_controller\_and\_pawn), `weapon` (string) |
| `hegrenade_detonate` | `userid` (player\_controller\_and\_pawn), `entityid` (short), `x`, `y`, `z` (float) |
| `flashbang_detonate` | `userid` (player\_controller\_and\_pawn), `entityid` (short), `x`, `y`, `z` (float) |
| `smokegrenade_detonate` | `userid` (player\_controller\_and\_pawn), `entityid` (short), `x`, `y`, `z` (float) |
Server [#server]
| Event | Fields |
| ------------- | ------------------------------------------------------------- |
| `game_newmap` | `mapname` (string) — sent when a new map has finished loading |
| `server_cvar` | `cvarname` (string), `cvarvalue` (string) |
Client-only helpers [#client-only-helpers]
Some events exist to drive the client UI and are useful with
`FireEventToClient`:
| Event | Fields | Use |
| ------------------------------ | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `show_survival_respawn_status` | `loc_token` (string), `duration` (long), `userid` (player\_controller\_and\_pawn) | HTML text in the centre of the screen — what `CCSPlayerController::PrintToCenterHtml` sends. |
Finding other events [#finding-other-events]
The full definitions are plain text inside the game's VPKs:
`game/csgo/pak01_dir.vpk` → `resource/game.gameevents` and
`resource/mod.gameevents`, `game/core/pak01_dir.vpk` →
`resource/core.gameevents`. Any VPK tool can open them. To see what fires in
practice, hook the candidates with a post hook that logs `event->GetName()`.
# Game Events (/docs/events/game-events)
Game events are how CS2 announces what happens in a match: `player_death`,
`round_start`, `bomb_planted`. Source2Mod hooks the engine's
`IGameEventManager2::FireEvent` once and hands every event to the plugins that
asked for it — before the engine processes it, after, or both.
Hooking an event [#hooking-an-event]
```cpp title="plugin.cpp"
#include "source2mod/ISource2ModEvents.h"
#include "source2mod/schema/entity/classes/CCSPlayerController.h"
bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS();
HOOK_GAME_EVENT("player_death", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action
{
auto* pVictim = static_cast(event->GetPlayerController("userid"));
auto* pAttacker = static_cast(event->GetPlayerController("attacker"));
SOURCE2MOD_LOG(&g_Plugin, "%s killed %s with %s%s\n",
pAttacker ? pAttacker->GetPlayerName() : "world",
pVictim ? pVictim->GetPlayerName() : "?",
event->GetString("weapon"),
event->GetBool("headshot") ? " (headshot)" : "");
return Action::Ignore;
}, true);
return true;
}
```
```cpp title="plugin.sp"
public void OnPluginStart()
{
Source2ModEvents.HookGameEvent("player_death", Event_PlayerDeath, true);
}
public Source2ModAction Event_PlayerDeath(IGameEvent event, bool post, bool &dontBroadcast)
{
CCSPlayerController victim = event.GetPlayerController("userid");
CCSPlayerController attacker = event.GetPlayerController("attacker");
char victimName[64] = "?", attackerName[64] = "world", weapon[64];
if (victim != NULL_ENTITY)
victim.GetPlayerName(victimName, sizeof(victimName));
if (attacker != NULL_ENTITY)
attacker.GetPlayerName(attackerName, sizeof(attackerName));
event.GetString("weapon", weapon, sizeof(weapon));
Source2ModApi.Log("%s killed %s with %s%s", attackerName, victimName, weapon,
event.GetBool("headshot") ? " (headshot)" : "");
return Action_Ignore;
}
```
The API:
```cpp title="ISource2ModEvents.h"
using GameEventHandler = Source2ModCallback;
virtual Source2ModHookId HookGameEvent(const char* pchName, GameEventHandler handler, bool post) = 0;
virtual bool UnhookGameEvent(const char* pchName, const GameEventHandler& handler, bool post) = 0;
virtual bool UnhookGameEvent(Source2ModHookId id) = 0;
#define HOOK_GAME_EVENT(passname, passfunc, post) g_pSource2ModEvents->HookGameEvent(passname, passfunc, post)
#define UNHOOK_GAME_EVENT(passname, passfunc, post) g_pSource2ModEvents->UnhookGameEvent(passname, passfunc, post)
```
* `pchName` — the event name, exactly as the game defines it (case sensitive).
The [event reference](/docs/events/event-reference) lists the common ones.
* `handler` — a lambda, a free or static function, or an object and a method
(`SOURCE2MOD_MEMBER(this, &MyPlugin::OnPlayerDeath)`). There is no plugin id
argument; the core reads which plugin the hook belongs to off the handler.
* `post` — `false` for a pre hook, `true` for a post hook. For both, hook twice.
* The same event may be hooked as often as a plugin likes; each handler runs.
* Hook in `Load()`. If the game's event manager does not exist yet, Source2Mod
keeps the hook and attaches it once it does.
* Hooking an event also registers a server-side listener for it, so the game
creates the event even when nothing else on the server listens for it.
* In a script: `Source2ModEvents.HookGameEvent(name, handler, post)` with a
named `public` function; it returns `0` when the event does not exist.
Everything is dropped when your plugin unloads. To remove one hook earlier,
pass the `Source2ModHookId` `HOOK_GAME_EVENT` returned to
`g_pSource2ModEvents->UnhookGameEvent(id)`, or hand the same function or object
and method to `UNHOOK_GAME_EVENT` — a lambda has no identity and only goes by
id. A script unhooks by id only:
```cpp
HOOK_GAME_EVENT("round_end", SOURCE2MOD_MEMBER(this, &MyPlugin::OnRoundEnd), true);
// ...
UNHOOK_GAME_EVENT("round_end", SOURCE2MOD_MEMBER(this, &MyPlugin::OnRoundEnd), true);
Source2ModHookId id = HOOK_GAME_EVENT("round_start", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action
{
return Action::Ignore;
}, true);
// ...
g_pSource2ModEvents->UnhookGameEvent(id);
```
```cpp
int id = Source2ModEvents.HookGameEvent("round_start", Event_RoundStart, true);
// ...
Source2ModEvents.UnhookGameEvent(id);
```
Full reference: [`ISource2ModEvents`](/docs/core-api/ISource2ModEvents).
Pre and post [#pre-and-post]
| | Pre (`post == false`) | Post (`post == true`) |
| ------------------- | ----------------------------------------------- | ------------------------------------- |
| When | The event is filled in, nobody has seen it yet. | The engine has processed and sent it. |
| The `event` | The real event. | A copy taken after the pre hooks ran. |
| Change fields | Yes — the engine and clients see your values. | Pointless — it is a copy. |
| `dontBroadcast` | Yes, see below. | No effect. |
| `Action::Supersede` | Cancels the event. | Only skips the remaining post hooks. |
Rule of thumb: **pre to change or stop, post to react.** A post hook sees the
event after the game's own handling, so state the event caused — money, score,
team — is already updated.
Hooks for the same event run in the order they were registered, across all
plugins.
Reading fields [#reading-fields]
`IGameEvent` is s2sdk's interface. Field names are plain strings:
| Getter | For field type |
| ---------------------------------------------------------- | ---------------------------------------------------- |
| `GetInt(key, default = 0)` | `short`, `long`, `byte` |
| `GetBool(key, default = false)` | `bool` |
| `GetFloat(key, default = 0.0f)` | `float` |
| `GetString(key, default = "")` | `string` |
| `GetUint64(key, default = 0)` | `uint64` (SteamIDs) |
| `GetPlayerController(key)` | player fields — the controller as `CEntityInstance*` |
| `GetPlayerPawn(key)` | player fields — the pawn as `CEntityInstance*` |
| `GetPlayerSlot(key)` | player fields — the `CPlayerSlot` |
| `GetEntity(key)`, `GetEntityIndex(key)`, `GetEHandle(key)` | entity fields |
Player fields (`userid`, `attacker`, `assister`) are typed
`player_controller`, `player_pawn` or `player_controller_and_pawn` in the
game's event files, so the type is known: `static_cast` the result (a
`dynamic_cast` works too, it only checks what is already certain):
```cpp
auto* pController = static_cast(event->GetPlayerController("userid"));
if (!pController)
return Action::Ignore; // world, a disconnected player, ...
CCSPlayerPawn* pPawn = pController->GetPlayerPawn();
```
```cpp
CCSPlayerController controller = event.GetPlayerController("userid");
if (controller == NULL_ENTITY)
return Action_Ignore; // world, a disconnected player, ...
CCSPlayerPawn pawn = controller.GetPlayerPawn();
```
`GetPlayerController` returns `nullptr` when the field names no player — an
`attacker` that was the world, a player who already left — so always check. `event->GetName()` gives the event name, which helps when one handler
serves several events.
In a script the player getters return the typed handle (`CCSPlayerController`,
`CCSPlayerPawn`) and `NULL_ENTITY` when there is no player, so there is nothing
to cast; `GetString`, `GetName` and `GetUint64` copy into a buffer you pass (the
64-bit value as decimal text).
Changing fields [#changing-fields]
In a pre hook, set fields with the matching setter before the engine sees the
event: `SetInt`, `SetBool`, `SetFloat`, `SetString`, `SetUint64`, `SetEntity`,
`SetPlayer(key, CPlayerSlot)`.
```cpp title="plugin.cpp"
// Every kill shows as a headshot in the killfeed.
HOOK_GAME_EVENT("player_death", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action
{
event->SetBool("headshot", true);
return Action::Override;
}, false);
```
```cpp title="plugin.sp"
Source2ModEvents.HookGameEvent("player_death", Event_PlayerDeathPre, false);
// Every kill shows as a headshot in the killfeed.
public Source2ModAction Event_PlayerDeathPre(IGameEvent event, bool post, bool &dontBroadcast)
{
event.SetBool("headshot", true);
return Action_Override;
}
```
Return `Action::Override` when you changed something. The dispatcher treats it
like `Ignore` — the event goes on — but it documents intent, and it is what the
rest of Source2Mod's callbacks use.
Keeping an event from the clients [#keeping-an-event-from-the-clients]
Set `dontBroadcast = true` in a pre hook. The server still processes the event —
the game's own handling, other plugins' post hooks — but no client receives it:
```cpp title="plugin.cpp"
// Bot deaths never show up in the killfeed.
HOOK_GAME_EVENT("player_death", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action
{
auto* pVictim = static_cast(event->GetPlayerController("userid"));
if (pVictim && pVictim->IsBot())
{
dontBroadcast = true;
return Action::Override;
}
return Action::Ignore;
}, false);
```
```cpp title="plugin.sp"
Source2ModEvents.HookGameEvent("player_death", Event_PlayerDeathPre, false);
// Bot deaths never show up in the killfeed.
public Source2ModAction Event_PlayerDeathPre(IGameEvent event, bool post, bool &dontBroadcast)
{
CCSPlayerController victim = event.GetPlayerController("userid");
if (victim != NULL_ENTITY && victim.IsBot())
{
dontBroadcast = true;
return Action_Override;
}
return Action_Ignore;
}
```
FUNPLAY's JailBreak does the same with `player_team` and `player_radio` — the
game still switches the team, the clients just never get the event:
```cpp
HOOK_GAME_EVENT("player_radio", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action
{
dontBroadcast = true;
return Action::Ignore;
}, false);
```
```cpp
Source2ModEvents.HookGameEvent("player_radio", Event_PlayerRadio, false);
public Source2ModAction Event_PlayerRadio(IGameEvent event, bool post, bool &dontBroadcast)
{
dontBroadcast = true;
return Action_Ignore;
}
```
The flag works both ways: setting it to `false` on an event the game fired
server-only sends it to the clients after all.
Cancelling an event [#cancelling-an-event]
Return `Action::Supersede` from a pre hook and the event is not fired at all:
the remaining pre hooks are skipped, the engine never processes it, no client
receives it and no post hook runs.
```cpp
HOOK_GAME_EVENT("player_jump", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action
{
auto* pPlayer = static_cast(event->GetPlayerController("userid"));
// Nobody hears about bots jumping -- not the game, not the clients, not later hooks.
if (pPlayer && pPlayer->IsBot())
return Action::Supersede;
return Action::Ignore;
}, false);
```
```cpp
Source2ModEvents.HookGameEvent("player_jump", Event_PlayerJump, false);
public Source2ModAction Event_PlayerJump(IGameEvent event, bool post, bool &dontBroadcast)
{
CCSPlayerController player = event.GetPlayerController("userid");
// Nobody hears about bots jumping -- not the game, not the clients, not later hooks.
if (player != NULL_ENTITY && player.IsBot())
return Action_Supersede;
return Action_Ignore;
}
```
Cancelling also hides the event from every other plugin that hooked it later.
If all you want is to keep something off players' screens, `dontBroadcast` is
the gentler tool.
Firing your own event [#firing-your-own-event]
Create the event through the engine's event manager (in a script, the static
methods of `IGameEventManager2`), fill it in, then send it one of two ways:
* **`IGameEventManager2::FireEvent(event, bDontBroadcast)`** — to the server and
every client, through every plugin's hooks, exactly like a game-fired event.
`FireEvent` takes ownership and frees the event.
* **`CBasePlayerController::FireEventToClient(event)`** — to one player only.
The server never processes it and no hooks run. The event is still yours:
free it afterwards.
```cpp title="plugin.cpp"
IGameEventManager2* pEventManager = g_Source2ModAPI->GetGameEventManager();
// To everybody.
if (IGameEvent* pEvent = pEventManager->CreateEvent("round_announce_match_start", true))
pEventManager->FireEvent(pEvent); // freed by FireEvent
// To one player (pCaller, a CCSPlayerController*): HUD text through show_survival_respawn_status.
if (IGameEvent* pEvent = pEventManager->CreateEvent("show_survival_respawn_status", true))
{
pEvent->SetString("loc_token", "Hello from a game event");
pEvent->SetInt("duration", 5);
pEvent->SetPlayer("userid", pCaller->GetPlayerSlot());
pCaller->FireEventToClient(pEvent);
pEventManager->FreeEvent(pEvent); // FireEventToClient does not free it
}
```
```cpp title="plugin.sp"
// To everybody.
IGameEvent event = IGameEventManager2.CreateEvent("round_announce_match_start", true);
if (event != null)
IGameEventManager2.FireEvent(event); // freed by FireEvent
// To one player (caller, a CCSPlayerController): HUD text through show_survival_respawn_status.
event = IGameEventManager2.CreateEvent("show_survival_respawn_status", true);
if (event != null)
{
event.SetString("loc_token", "Hello from a game event");
event.SetInt("duration", 5);
event.SetPlayer("userid", caller.GetPlayerSlot());
caller.FireEventToClient(event);
IGameEventManager2.FreeEvent(event); // FireEventToClient does not free it
}
```
* The second argument of `CreateEvent` is `bForce`: create the event even if
nothing on the server listens for it. Pass `true` for events you send to
clients.
* `CreateEvent` returns `nullptr` (`null` in a script) for a name the game does
not know.
* The schema headers also declare a free function `GetGameEventManager()`,
returning the same manager.
`FireEventToClient` is on
[`CBasePlayerController`](/docs/schema/entity/classes/CBasePlayerController);
`CCSPlayerController::PrintToCenterHtml` is built on exactly this event.
See also [#see-also]
* [Event reference](/docs/events/event-reference) — fields of the common events.
* [Listeners](/docs/events/listeners) — map start/end and plugin lifecycle.
* [`ISource2ModEvents` reference](/docs/core-api/ISource2ModEvents)
# Events (/docs/events)
"Something happened, run my code" means different things on a CS2 server,
and Source2Mod has a different tool for each. This section covers the first
two; the rest are linked below.
Game events [#game-events]
The game announces what happens in a match through **game events** —
`player_death`, `round_start`, `bomb_planted`, `player_connect_full`, some 270
of them in all. Each is a named bag of typed fields (an `IGameEvent`) that the
server fires and, usually, sends to every client too (that is how the killfeed
and the round timer work).
```cpp title="plugin.cpp"
HOOK_GAME_EVENT("player_death", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action
{
auto* victim = static_cast(event->GetPlayerController("userid"));
// ...
return Action::Ignore;
}, true);
```
```cpp title="plugin.sp"
Source2ModEvents.HookGameEvent("player_death", Event_PlayerDeath, true);
public Source2ModAction Event_PlayerDeath(IGameEvent event, bool post, bool &dontBroadcast)
{
CCSPlayerController victim = event.GetPlayerController("userid");
// ...
return Action_Ignore;
}
```
With [`ISource2ModEvents`](/docs/core-api/ISource2ModEvents) you hook an event before
or after the engine handles it, read and change its fields, keep it from the
clients, cancel it, and fire events of your own — to everybody or to one
player. See [Game events](/docs/events/game-events), and the
[event reference](/docs/events/event-reference) for the fields of the ones you
will use most.
Source2Mod listener callbacks [#source2mod-listener-callbacks]
Source2Mod itself tells your plugin about the plugin system and the map
lifecycle through `ISource2ModListener`: other plugins loading and unloading,
"all plugins are loaded", map start (`OnLevelInit`) and map end
(`OnLevelShutdown`), plus interface queries. You implement the interface and
register it once with `api->AddListener(this, this)`; a script registers
nothing and defines `public` functions named after the `Source2Mod_On*`
forwards instead. See
[Listeners](/docs/events/listeners).
Engine callbacks: clients and the frame [#engine-callbacks-clients-and-the-frame]
Player connection and the server frame are **not** listener callbacks. They
are engine interface functions you hook with KHook, the way the sample plugin
does:
| You want | Hook |
| -------------------------------- | -------------------------------------------------------- |
| A client connecting (can reject) | `ISource2GameClients::ClientConnect` |
| Client fully in the game | `ISource2GameClients::ClientPutInServer`, `ClientActive` |
| A client leaving | `ISource2GameClients::ClientDisconnect` |
| Every server tick | `ISource2Server::GameFrame` |
| A raw client command | `ISource2GameClients::ClientCommand` |
```cpp title="plugin.h"
KHOOK_VIRTUAL(m_hGameFrame, &ISource2Server::GameFrame, &g_pSource2Server, nullptr, &MyPlugin::Hook_GameFrame);
KHOOK_VIRTUAL(m_hClientDisconnect, &ISource2GameClients::ClientDisconnect, &g_pSource2GameClients, nullptr, &MyPlugin::Hook_ClientDisconnect);
```
C++ only — scripts get client connection and the frame as forwards
(`Source2Mod_OnClientConnect`, `Source2Mod_OnClientDisconnect`,
`Source2Mod_OnGameFrame`, …); see [Listeners](/docs/events/listeners).
See [Hooking](/docs/hooks) for how these work. Many of these moments also have
a game event — `player_connect_full` and `player_disconnect` are often all you
need.
Entities and entity outputs [#entities-and-entity-outputs]
Entities being created, spawned and deleted (`IEntityListener`, `ADD_ENTITY_LISTENER`)
and map logic firing outputs such as `OnPressed` or `OnStartTouch`
(`IEntityIOListener`, `ADD_ENTITY_IO_LISTENER`) belong to
[`ISource2ModEntities`](/docs/core-api/ISource2ModEntities). See [Entities](/docs/entities).
Net messages, sounds, commands, ConVars [#net-messages-sounds-commands-convars]
* Protobuf messages between server and clients (user messages, net messages) —
[Protobuf](/docs/protobuf).
* Sounds being emitted — [`ISource2ModSounds`](/docs/core-api/ISource2ModSounds).
* Commands and chat — [ConVars & Commands](/docs/convars-commands).
* ConVar changes — [`HOOK_CONVAR_CHANGE`](/docs/convars-commands/convars#watching-changes).
Hook, read, modify, block and fire IGameEvents.
Every ISource2ModListener callback and when it fires.
Common CS2 events and their fields.
# Listeners (/docs/events/listeners)
`ISource2ModListener` is how Source2Mod tells your plugin about the plugin system
and the map lifecycle. Every method has an empty default, so you override only
what you need, and you register the listener once in `Load()`. A script gets
the same callbacks as forwards from `source2mod/listener.inc`, named
`Source2Mod_On…`: no class and no registration, it just defines the `public`
functions it wants.
```cpp title="plugin.h"
#include "source2mod/ISource2ModPlugin.h"
class MyPlugin final : public ISource2ModPlugin, public ISource2ModListener
{
public:
bool Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) override;
bool Unload(char* error, size_t maxlen) override;
// ISource2ModListener
void OnAllSource2ModPluginsLoaded() override;
void OnLevelInit(const char* mapName, const char* mapEntities, const char* oldLevel,
const char* landmarkName, bool loadGame, bool background) override;
void OnLevelShutdown() override;
// GetAuthor(), GetName(), ... as usual
};
```
```cpp title="plugin.sp"
// No class and no registration: a public function named after a
// source2mod/listener.inc forward is called.
public void Source2Mod_OnAllSource2ModPluginsLoaded()
{
}
public void Source2Mod_OnLevelInit(const char[] mapName, const char[] mapEntities, const char[] oldLevel,
const char[] landmarkName, bool loadGame, bool background)
{
}
public void Source2Mod_OnLevelShutdown()
{
}
```
```cpp title="plugin.cpp"
bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS();
api->AddListener(this, this);
return true;
}
```
Registering [#registering]
```cpp title="ISource2ModApi.h"
virtual void AddListener(ISource2ModPlugin* plugin, ISource2ModListener* pListener) = 0;
```
* `plugin` — your plugin object; the listener is filed under it.
* `pListener` — the object that implements the callbacks. Usually the plugin
class itself, hence `AddListener(this, this)`, but it can be any object that
lives as long as the plugin. A plugin can add several.
Call it from `Load()`. There is no `RemoveListener`: Source2Mod forgets a
plugin's listeners when the plugin unloads (or when its `Load()` fails).
C++ only — a script has nothing to register; its `Source2Mod_On*` functions
are called as long as it is loaded.
The callbacks [#the-callbacks]
Plugin lifecycle [#plugin-lifecycle]
| Callback | Fires |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OnPluginLoad(PluginId id, const char* name)` | After a s2m plugin (`.smx`) loaded successfully, on every plugin's listeners — **including the new plugin's own**, so you see your own `g_PluginID` once. |
| `OnPluginUnload(PluginId id, const char* name)` | When a Source2Mod plugin is about to unload — before its `Unload()` runs — on every plugin's listeners, including the one going away. |
| `OnAllSource2ModPluginsLoaded()` | After Source2Mod loaded the plugins folder at startup, and again after `s2m refresh` loaded at least one new plugin. |
| `OnMetamodPluginLoad(PluginId id)` | A Metamod:Source plugin was loaded. |
| `OnMetamodPluginUnload(PluginId id)` | A Metamod:Source plugin was unloaded. |
| `OnAllMetamodPluginsLoaded()` | Metamod:Source finished loading all of its plugins. |
The ids in `OnPluginLoad`/`OnPluginUnload` are **Source2Mod** plugin ids, the
ones `s2m list` shows. The ids in the two `OnMetamod…` callbacks are
Metamod's own, from a different numbering — which is why they are separate
callbacks.
`OnAllSource2ModPluginsLoaded` is the place for anything that depends on other
plugins: fetching another plugin's interface with `GET_SOURCE2MOD_IFACE`, for
example. Note what does **not** trigger it: loading one plugin by hand with
`s2m load`, or a hot reload. A plugin that may be loaded that way should
also try its setup from `Load()` or from `OnPluginLoad`. A script has no
interfaces to fetch; it checks for the other plugin's library with
`Source2ModNatives.LibraryExists` and then calls its natives.
```cpp title="plugin.cpp"
void MyPlugin::OnAllSource2ModPluginsLoaded()
{
// IOtherPluginApi / OTHER_PLUGIN_API_NAME: the header the other plugin ships.
int ret = SOURCE2MOD_IFACE_FAILED;
GET_SOURCE2MOD_IFACE(m_pOtherApi, IOtherPluginApi, OTHER_PLUGIN_API_NAME, ret);
if (ret != SOURCE2MOD_IFACE_OK)
SOURCE2MOD_LOG(this, "Other plugin not found, running without it.\n");
}
```
```cpp title="plugin.sp"
public void Source2Mod_OnAllSource2ModPluginsLoaded()
{
// "other_plugin": the library name the other plugin registers.
if (!Source2ModNatives.LibraryExists("other_plugin"))
Source2ModApi.Log("Other plugin not found, running without it.");
}
```
Map lifecycle [#map-lifecycle]
| Callback | Fires |
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `OnLevelInit(mapName, mapEntities, oldLevel, landmarkName, loadGame, background)` | A map is initialising — Metamod's `OnLevelInit`, passed through. `mapName` is the map being loaded. |
| `OnLevelShutdown()` | The current map is shutting down. |
Map start is the place to reset per-map state; map shutdown is
the last moment to let go of entity pointers, handles to per-map objects, and
timers that must not survive the map.
```cpp title="plugin.cpp"
void MyPlugin::OnLevelInit(const char* pMapName, const char* pMapEntities, const char* pOldLevel,
const char* pLandmarkName, bool loadGame, bool background)
{
SOURCE2MOD_LOG(this, "OnLevelInit(%s)\n", pMapName);
}
void MyPlugin::OnLevelShutdown()
{
// Anything map-scoped goes here.
}
```
```cpp title="plugin.sp"
public void Source2Mod_OnLevelInit(const char[] mapName, const char[] mapEntities, const char[] oldLevel,
const char[] landmarkName, bool loadGame, bool background)
{
Source2ModApi.Log("OnLevelInit(%s)", mapName);
}
public void Source2Mod_OnLevelShutdown()
{
// Anything map-scoped goes here.
}
```
A plugin loaded while a map is already running has missed that map's
`OnLevelInit`. If you need the current map at load time, read it from the
engine rather than waiting for the next map change.
Engine callbacks [#engine-callbacks]
Plugin API 2 (core v1.0.26) fans the engine calls most plugins used to hook
themselves out of the core's own hooks, so a plugin needs no `KHOOK_VIRTUAL`
for any of these — and stays on the [stable tier](/docs/development/compatibility):
| Callback | Fires |
| -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OnGameFrame(bool simulating, bool firstTick, bool lastTick)` | Once a frame, after `ISource2Server::GameFrame` and Source2Mod's own per-frame work (timers, menus). |
| `OnStartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession* session, const char* mapName)` | After `INetworkServerService::StartupServer`: a new server session, the new map's entity system is Source2Mod's. |
| `OnClientPutInServer(CPlayerSlot slot, const char* name, int type, uint64 xuid)` | After `IServerGameClients::ClientPutInServer`. |
| `OnClientVoice(CPlayerSlot slot)` | After `ClientVoice`. |
| `OnClientSettingsChanged(CPlayerSlot slot)` | After `ClientSettingsChanged`. |
| `OnClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* name, uint64 xuid, const char* networkId)` | After `ClientDisconnect`; the controller still exists. |
| `OnGameServerSteamAPIActivated()` | After the Steam API came up. |
| `OnGameServerSteamAPIDeactivated()` | Before it goes. |
| `OnLoadEventsFromFile(IGameEventManager2* manager, const char* filename, bool searchAll)` | After `IGameEventManager2::LoadEventsFromFile`, with the manager it was called on — the place to keep it, or to register a `.gameevents` file of your own. |
They are appended after the older callbacks, so a plugin built against API 1
keeps loading; the core just never calls them on it.
Scripts get them as forwards of the same names prefixed `Source2Mod_`. There
a SteamID (`xuid`) is decimal text, the disconnect reason an `int`,
`Source2Mod_OnStartupServer` gets only the map name and
`Source2Mod_OnLoadEventsFromFile` no manager.
```cpp title="plugin.cpp"
void MyPlugin::OnClientPutInServer(CPlayerSlot slot, const char* name, int type, uint64 xuid)
{
SOURCE2MOD_LOG(this, "%s is in (slot %d)\n", name, slot.Get());
}
```
```cpp title="plugin.sp"
public void Source2Mod_OnClientPutInServer(CPlayerSlot slot, const char[] name, int type, const char[] xuid)
{
Source2ModApi.Log("%s is in (slot %d)", name, slot.Get());
}
```
Interface queries [#interface-queries]
These let your plugin **expose** interfaces to other plugins:
| Callback | Called when |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `void* OnSource2ModQuery(const char* iface, int* ret)` | Another s2m plugin asks for an interface (`g_Source2ModAPI->Source2ModFactory`, `GET_SOURCE2MOD_IFACE`). |
| `void* OnMetamodQuery(const char* iface, int* ret)` | A Metamod:Source plugin asks for one through Metamod's factory. By default it answers exactly like `OnSource2ModQuery`. |
Return your interface and set `*ret` to `SOURCE2MOD_IFACE_OK` when `iface` is a
name you serve; otherwise return `nullptr`. The default implementation sets
`SOURCE2MOD_IFACE_FAILED`. Override `OnMetamodQuery` to answer Metamod plugins
differently, or to return `nullptr` and keep an interface Source2Mod-only.
C++ only — a script offers natives instead: `Source2ModNatives.CreateNative`
and `Source2ModNatives.RegisterLibrary` in `AskPluginLoad2`.
```cpp title="plugin.cpp"
// MY_PLUGIN_API_NAME and g_MyPluginApi: your own interface name and object.
void* MyPlugin::OnSource2ModQuery(const char* iface, int* ret)
{
if (strcmp(iface, MY_PLUGIN_API_NAME) == 0)
{
if (ret) *ret = SOURCE2MOD_IFACE_OK;
return &g_MyPluginApi;
}
if (ret) *ret = SOURCE2MOD_IFACE_FAILED;
return nullptr;
}
```
What is not a listener callback [#what-is-not-a-listener-callback]
`ISource2ModListener` is deliberately small. For everything else:
| Event | Where |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Client connect (`ClientConnect`, `OnClientConnected`, `ClientActive`, `ClientCommand`) | KHook on `ISource2GameClients` — see [Hooking](/docs/hooks), or the `player_connect_full` [game event](/docs/events/game-events); put in server and disconnect are listener callbacks above |
| Damage, movement, item pickup, entity inputs | [Game hooks](/docs/hooks/game-hooks) |
| Entity created / spawned / deleted | `IEntityListener` via `ADD_ENTITY_LISTENER` — see [Entities](/docs/entities) |
| Entity outputs | `IEntityIOListener` via `ADD_ENTITY_IO_LISTENER` — see [Entities](/docs/entities) |
| Round start, deaths, bomb, … | [Game events](/docs/events/game-events) |
The full interface is documented in the
[`ISource2ModPlugin` reference](/docs/core-api/ISource2ModPlugin); `AddListener` in
[`ISource2ModApi`](/docs/core-api/ISource2ModApi).
# Addresses (/docs/hooks/addresses)
Source2Mod scans a set of game functions once at startup and hands them out,
typed, through `ISource2ModAddresses`. Each one has a shorthand macro,
`ADDR_*()`, that returns a function pointer ready to call — or to hook.
```cpp
#include "source2mod/ISource2ModAddresses.h"
if (auto pfnSwitchTeam = ADDR_SWITCH_TEAM())
pfnSwitchTeam(pController, 3);
```
The macros read `g_pSource2ModAddresses`, which `SOURCE2MOD_SAVEVARS()` fills in
`Load()` (and `SOURCE2MOD_GLOBALVARS()` declares in your other files). Module
inspection, pattern scanning and pointer arithmetic are not here — they are on
`ISource2ModModule` and `ISource2ModMemory`. Reference:
[ISource2ModAddresses](/docs/core-api/ISource2ModAddresses).
Three ways to reach a game function [#three-ways-to-reach-a-game-function]
| you want to… | use |
| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| do something the SDK already wraps (give an item, switch team, end the round) | the schema helper — `pItemServices->GiveNamedItem("weapon_ak47")` |
| call a resolved function the helpers do not wrap, or with arguments they do not expose | `ADDR_*()` |
| call it **past** every plugin's hooks | `SOURCE2MOD_ORIGINAL(ADDR_*())`, or the helper with `HookChain::Bypass` |
| hook it | `KHOOK_MEMBER(m_hX, ADDR_*(), ...)` — see [Function hooks](/docs/hooks/function-hooks) |
| reach a function Source2Mod does not resolve | your own gamedata entry and `GAMECONFIG_RESOLVE(name)` — see [Gamedata](/docs/hooks/gamedata) |
The sample plugin shows the first two side by side:
```cpp title="plugin.cpp"
// Way one: Source2Mod resolved it and knows the type.
if (auto pfnSwitchTeam = ADDR_SWITCH_TEAM())
{
pfnSwitchTeam(pCaller, nNewTeam);
return;
}
// Way two: any gamedata entry, with the type spelled out by you.
using SwitchTeam_t = void (FASTCALL *)(CCSPlayerController *, unsigned char);
if (auto pfnSwitchTeam = GAMECONFIG_RESOLVE("CCSPlayerController::SwitchTeam").RCast())
pfnSwitchTeam(pCaller, nNewTeam);
// Way three: pCaller->SwitchTeam(nNewTeam) does exactly the above.
```
Calling past the hooks [#calling-past-the-hooks]
An `ADDR_*` macro hands back the address the signature scan found. That is
what you want to **hook**, but not always what you want to **call**: once any
plugin inline-hooks the function, its first bytes are a jump into the detour,
so calling it runs the whole Pre/Post chain.
That is right when you stand in for the game — a plugin dealing damage as if a
weapon fired wants the other plugins' rules to apply. It is wrong when you are
asking for the function itself — an admin command, a forced round end — where
another plugin's rules have no business vetoing what the server was told to
do. For that, wrap the call in `SOURCE2MOD_ORIGINAL`:
```cpp
SOURCE2MOD_ORIGINAL(ADDR_TAKE_DAMAGE_OLD())(pPawn, &info, &result);
```
`SOURCE2MOD_ORIGINAL(fn)` casts `KHook::FindOriginal(fn)` back to `fn`'s type. It
is signature-blind, so it works whatever prototype you hold and however the
plugin that hooked the function declared its hook. Unhooked functions come back
unchanged, so it is always safe to apply.
Resolve it at the call; do not cache the result in a global. The address is
only the original from the moment a hook exists, and hooks are installed long
after these addresses are scanned.
HookChain on schema helpers [#hookchain-on-schema-helpers]
The schema helpers make the same choice through their last argument, a
`HookChain` (`ISource2ModTypes.h`):
| value | the call goes |
| ------------------- | --------------------------------------------------------------------------------- |
| `HookChain::Run` | through the Pre/Post chain, as if the game made the call |
| `HookChain::Bypass` | straight to the original, past every hook — inline detours and vtable hooks alike |
The default of each helper is what it always did, so existing calls keep their
behaviour. `CCSGameRules::TerminateRound` and `CCSPlayerController::TakeDamage`
default to `Bypass` (the server was told to end the round or deal the damage);
every other helper defaults to `Run`.
```cpp
pItemServices->GiveNamedItem("weapon_ak47"); // hooks run
pController->TakeDamage(pAttacker, 50, DMG_BULLET, HookChain::Run); // opt in to the hooks
pGameRules->TerminateRound(5.0f, nReason); // bypasses by default
```
Helpers that take a `HookChain`:
| class | helpers |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `CBaseEntity` | `AcceptInput`, `AddEntityIOEvent`, `DispatchSpawn`, `Teleport`, `Remove`, `DispatchParticleEffect` |
| `CBaseModelEntity` | `SetModel` |
| `CBasePlayerController` | `SetPawn` |
| `CBasePlayerPawn` | `CommitSuicide`, `SnapViewAngles` |
| `CCSPlayerPawn` | `PostThink` |
| `CCSPlayerController` | `TakeDamage` (Bypass), `Respawn`, `SwitchTeam`, `ChangeTeam` |
| `CCSGameRules` | `TerminateRound` (Bypass), `GoToIntermission` |
| `CPlayer_ItemServices` | `GiveNamedItem`, `DropActivePlayerWeapon`, `RemoveWeapons` |
| `CPlayer_WeaponServices` | `DropWeapon`, `BumpWeapon`, `SelectItem`, `Destroy` |
| `CAttributeList` | `SetOrAddAttribute`, `SetOrAddAttributeValueByName` |
| grenade projectiles | `CHEGrenadeProjectile`, `CFlashbangProjectile`, `CSmokeGrenadeProjectile`, `CDecoyProjectile`, `CMolotovProjectile` — `EmitGrenade` |
When to pick which [#when-to-pick-which]
* **Standing in for the game** (a custom weapon dealing damage, a round reward
handing out an item): `Run`. Other plugins' damage rules, item restrictions
and so on should apply.
* **Doing what the server was told** (admin command, forced round end, a
gamemode's own rule): `Bypass`.
* **Inside a hook on the same function**: `Bypass`, always — `Run` would
re-enter your own handler. See [Recursion](/docs/hooks/handlers#recursion).
For your own calls through a vtable index, `CALL_VIRTUAL_CHAIN(retType, index,
eChain, this, args...)` (`utils/virtual.h`) does the same for virtual
functions; `ResolveHookChain(fn, eChain)` does it for a function pointer.
The ADDR_* macros [#the-addr_-macros]
Every macro calls the `ISource2ModAddresses` method of the same name and returns
the typedef declared above it in `ISource2ModAddresses.h` — read that typedef
before you call or hook the function, it carries the checked prototype and the
platform differences.
**Required** functions must resolve for Source2Mod to start, so they are never
null while it runs. **Optional** ones are plugin-only hook targets resolved
leniently: a pattern that stops matching after a game update leaves them
**null** instead of blocking startup — check before you call or hook them (a
hook on a null target is logged and skipped).
Entities and players (required) [#entities-and-players-required]
| macro | function | gamedata key |
| ------------------------------------ | ----------------------------------------------------------------- | ---------------------------------------------- |
| `ADDR_CREATE_ENTITY_BY_NAME()` | `CreateEntityByName` | `UTIL::CreateEntityByName` |
| `ADDR_DISPATCH_SPAWN()` | `CBaseEntity::DispatchSpawn` | `CBaseEntity::DispatchSpawn` |
| `ADDR_TAKE_DAMAGE_OLD()` | `CBaseEntity::TakeDamageOld` | `CBaseEntity::TakeDamageOld` |
| `ADDR_TAKE_DAMAGE_INFO()` | `CTakeDamageInfo` constructor | `CTakeDamageInfo::Constructor` |
| `ADDR_SET_MODEL()` | `CBaseModelEntity::SetModel` | `CBaseModelEntity::SetModel` |
| `ADDR_SET_PAWN()` | `CBasePlayerController::SetPawn` | `CBasePlayerController::SetPawn` |
| `ADDR_SNAP_VIEW_ANGLES()` | `CBasePlayerPawn::SnapViewAngles` | `CBasePlayerPawn::SnapViewAngles` |
| `ADDR_SWITCH_TEAM()` | `CCSPlayerController::SwitchTeam` | `CCSPlayerController::SwitchTeam` |
| `ADDR_WEAPON_SERVICES_DESTROY()` | `CPlayer_WeaponServices::Destroy` | `CCSPlayer_WeaponServices::Destroy` |
| `ADDR_GIVE_NAMED_ITEM()` | `CCSPlayer_ItemServices::GiveNamedItem` (the full implementation) | `CCSPlayer_ItemServices::GiveNamedItem` |
| `ADDR_POST_THINK()` | `CCSPlayerPawn::PostThink` | `CCSPlayerPawn::PostThink` |
| `ADDR_CAN_MOVE()` | `CCSPlayerPawnBase::CanMove` | `CCSPlayerPawnBase::CanMove` |
| `ADDR_UTIL_REMOVE()` | `UTIL_Remove` | `UTIL::Remove` |
| `ADDR_SET_OR_ADD_ATTRIBUTE()` | `CAttributeList::SetOrAddAttributeValueByName` | `CAttributeList::SetOrAddAttributeValueByName` |
| `ADDR_GET_WEAPON_CS_DATA_FROM_KEY()` | `GetWeaponCSDataFromKey` | `GetWeaponCSDataFromKey` |
| `ADDR_DISPATCH_PARTICLE_EFFECT()` | `DispatchParticleEffect` | `DispatchParticleEffect` |
| `ADDR_EMIT_HEGRENADE()` | `CHEGrenadeProjectile::EmitGrenade` | `CHEGrenadeProjectile::EmitGrenade` |
| `ADDR_EMIT_FLASHBANG()` | `CFlashbangProjectile::EmitGrenade` | `CFlashbangProjectile::EmitGrenade` |
| `ADDR_EMIT_SMOKE()` | `CSmokeGrenadeProjectile::EmitGrenade` | `CSmokeGrenadeProjectile::EmitGrenade` |
| `ADDR_EMIT_DECOY()` | `CDecoyProjectile::EmitGrenade` | `CDecoyProjectile::EmitGrenade` |
| `ADDR_EMIT_MOLOTOV()` | `CMolotovProjectile::EmitGrenade` | `CMolotovProjectile::EmitGrenade` |
Game rules, entity I/O and networking (required) [#game-rules-entity-io-and-networking-required]
| macro | function | gamedata key |
| ----------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------- |
| `ADDR_TERMINATE_ROUND()` | `CCSGameRules::TerminateRound` — argument order differs per platform | `CCSGameRules::TerminateRound` |
| `ADDR_ACCEPT_INPUT()` | `CEntityInstance::AcceptInput` | `CEntityInstance::AcceptInput` |
| `ADDR_FIRE_OUTPUT_INTERNAL()` | `CEntityIOOutput::FireOutputInternal` | `CEntityIOOutput::FireOutputInternal` |
| `ADDR_ADD_ENTITY_IO_EVENT()` | `CEntitySystem::AddEntityIOEvent` | `CEntitySystem::AddEntityIOEvent` |
| `ADDR_FIND_ENTITY_BY_CLASSNAME()` | `CGameEntitySystem::FindEntityByClassName` | `CGameEntitySystem::FindEntityByClassName` |
| `ADDR_FIND_ENTITY_BY_NAME()` | `CGameEntitySystem::FindEntityByName` | `CGameEntitySystem::FindEntityByName` |
| `ADDR_LEGACY_GAME_EVENT_LISTENER()` | `LegacyGameEventListener` | `LegacyGameEventListener` |
| `ADDR_FILTER_MESSAGE()` | `INetworkMessageProcessingPreFilter::FilterMessage` | `INetworkMessageProcessingPreFilter::FilterMessage` |
Optional — may be null [#optional--may-be-null]
| macro | function |
| ---------------------------------- | ----------------------------------------------- |
| `ADDR_IDENTITY_ACCEPT_INPUT()` | `CEntityIdentity::AcceptInput` |
| `ADDR_CAN_ACQUIRE()` | `CCSPlayer_ItemServices::CanAcquire` |
| `ADDR_PROCESS_USER_CMD()` | `CCSPlayerController::ProcessUserCmd` |
| `ADDR_ON_SIMULATE_USER_COMMANDS()` | `CBasePlayerController::OnSimulateUserCommands` |
| `ADDR_GET_NAV_MESH_DATA()` | `CSource2Server::GetNavMeshData` |
| `ADDR_LOG_DIRECT()` | `CLoggingSystem::LogDirect` (tier0) |
| `ADDR_CMD_EXECUTE_COMMAND()` | `Cmd_ExecuteCommand` (engine2) |
Movement (`CCSPlayer_MovementServices::*`, all optional):
| macro | function |
| ---------------------------- | -------------------------------------------------------- |
| `ADDR_AIR_ACCELERATE()` | `AirAccelerate` |
| `ADDR_AIR_MOVE()` | `AirMove` |
| `ADDR_CAN_UNDUCK()` | `CanUnduck` |
| `ADDR_CATEGORIZE_POSITION()` | `CategorizePosition` |
| `ADDR_CHECK_FALLING()` | `CheckFalling` |
| `ADDR_CHECK_PARAMETERS()` | `CheckParameters` |
| `ADDR_CHECK_VELOCITY()` | `CheckVelocity` |
| `ADDR_CHECK_WATER()` | `CheckWater` |
| `ADDR_DUCK()` | `Duck` |
| `ADDR_FRICTION()` | `Friction` |
| `ADDR_FULL_WALK_MOVE()` | `FullWalkMove` |
| `ADDR_GROUND_ACCELERATE()` | `GroundAccelerate` — argument order differs per platform |
| `ADDR_LADDER_MOVE()` | `LadderMove` |
| `ADDR_MOVE_INIT()` | `MoveInit` |
| `ADDR_PLAYER_MOVE()` | `PlayerMove` |
| `ADDR_PROCESS_MOVEMENT()` | `ProcessMovement` |
| `ADDR_SETUP_MOVE()` | `SetupMove` |
| `ADDR_TRY_PLAYER_MOVE()` | `TryPlayerMove` |
| `ADDR_WALK_MOVE()` | `WalkMove` |
| `ADDR_WATER_MOVE()` | `WaterMove` |
Jumping (members of the jump components, not of the movement services — all
optional):
| macro | function |
| --------------------------------- | -------------------------------------- |
| `ADDR_ON_JUMP_LEGACY()` | `CCSPlayerLegacyJump::OnJump` |
| `ADDR_ON_JUMP_MODERN()` | `CCSPlayerModernJump::OnJump` |
| `ADDR_CHECK_JUMP_BUTTON_LEGACY()` | `CCSPlayerLegacyJump::CheckJumpButton` |
| `ADDR_CHECK_JUMP_BUTTON_MODERN()` | `CCSPlayerModernJump::CheckJumpButton` |
The gamedata key of each optional function is its function name as listed
(`CCSPlayer_MovementServices::AirAccelerate`, `CCSPlayerLegacyJump::OnJump`, …).
```cpp title="Hooks.h"
// Optional target: if the signature broke, KHOOK_INIT() logs it and skips this hook.
KHook::Return OnCheckJumpButtonLegacy(CCSPlayerLegacyJump* pThis, CMoveData* pMove);
KHOOK_MEMBER(m_hCheckJumpButtonLegacy, ADDR_CHECK_JUMP_BUTTON_LEGACY(), &CHooks::OnCheckJumpButtonLegacy, nullptr);
```
# Function Hooks (/docs/hooks/function-hooks)
Anything a signature scan can find is hookable — no vtable, no interface. A
function hook patches the first bytes of the function itself, so it catches
**every** call: through a vtable, direct, from any library.
```cpp
KHOOK_MEMBER(member, target, pre, post) // a function with a `this`
KHOOK_FUNCTION(member, target, pre, post) // a free function
```
* `KHOOK_MEMBER` — the handler's first parameter is the object (`this`), the
rest are the function's own arguments.
* `KHOOK_FUNCTION` — the handler takes only the function's own arguments.
Where the address comes from [#where-the-address-comes-from]
The **target** is one of:
| target | example | resolved by |
| --------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| a gamedata entry name | `"CCSPlayerPawn::PostThink"` | `ISource2ModGameConfig::ResolveSignature` — the entry's `library`, then its symbol or pattern |
| an `ADDR_*` getter | `ADDR_TAKE_DAMAGE_OLD()` | Source2Mod, which scanned it at startup — nothing is scanned twice |
| an address | an `ISource2ModMemory`, `void*` or typed function pointer | you |
| `nullptr` | | you, later, with `m_hX.Init(address)` |
```cpp title="Hooks.h"
class CHooks
{
public:
KHook::Return OnTakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult);
KHook::Return OnPostThink(CCSPlayerPawn* pThis);
// What Source2Mod already resolved.
KHOOK_MEMBER(m_hTakeDamageOld, ADDR_TAKE_DAMAGE_OLD(), &CHooks::OnTakeDamageOld, nullptr);
// A gamedata entry.
KHOOK_MEMBER(m_hPostThink, "CCSPlayerPawn::PostThink", &CHooks::OnPostThink, nullptr);
};
```
Prefer an `ADDR_*` getter when one exists: it is already resolved, and its
typedef in `ISource2ModAddresses.h` documents the real prototype — including
platform differences. The full list is on [Addresses](/docs/hooks/addresses).
For a function Source2Mod does not know, add an entry to your gamedata (see
[Gamedata](/docs/hooks/gamedata)) and name it.
A pattern you keep in the plugin instead is scanned yourself and passed as an
address. `WIN_LINUX(win, lin)` from `ISource2ModModule.h` picks the platform's
pattern at compile time:
```cpp
ISource2ModMemory addr = pServer->FindPattern(WIN_LINUX("48 89 7C 24 ? 55", "55 48 89 E5 41 57"));
```
See [Memory & modules](/docs/guides/memory-and-modules#pattern-scanning). A
gamedata entry is still the better choice for anything a game update moves: it
is fixed without a rebuild.
Deferred targets [#deferred-targets]
The target is **not** evaluated when your object is constructed. The macro
wraps it in a capture-less lambda that runs at `KHOOK_INIT()` (or at
`m_hX.Init()`). That is what lets `ADDR_TAKE_DAMAGE_OLD()` or
`GAMECONFIG_RESOLVE("...")` sit right in the macro even though your hooks
object is usually a global, constructed long before `Load()` when
`g_pSource2ModAddresses` is still null.
Two consequences:
* the target expression must not capture anything — it cannot name a local or
`this`; globals and function calls are fine;
* the translation unit needs Source2Mod globals in scope. A header that
declares hooks outside the plugin's main file uses `SOURCE2MOD_GLOBALVARS()` so
`g_pSource2ModAddresses` is known:
```cpp title="hooks/Hooks.h"
#include "source2mod/ISource2ModAddresses.h"
// The ADDR_* targets below read g_pSource2ModAddresses at KHOOK_INIT().
SOURCE2MOD_GLOBALVARS();
```
A target that resolves to null at init — a missing gamedata entry, an optional
`ADDR_*` that was not found — is logged and the hook is skipped.
Installing and removing everything [#installing-and-removing-everything]
`KHOOK_INIT()` in `Load()` resolves and installs every hook the plugin
declared with the macros; `KHOOK_DESTRUCT()` in `Unload()` takes them all down:
```cpp title="plugin.cpp"
bool Plugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS();
g_pSource2Server = api->GetSource2Server(); // anything a hook reads has to be set first
// The KHook commit check inside returns false out of Load() on a mismatch.
KHOOK_INIT();
return true;
}
bool Plugin::Unload(char* error, size_t maxlen)
{
KHOOK_DESTRUCT();
return true;
}
```
A hook whose target cannot be resolved — a gamedata entry that is missing, a
vtable not found, an instance still null — is logged and skipped; the rest
still go in. (`ISource2ModHook::InitAll()` is the call that returns `false` in
that case, if you want to know.) A hook declared with a `nullptr` target is
left alone and counts as success.
`KHOOK_INIT()` is also where the [KHook commit check](/docs/development/compatibility#the-khook-check)
happens: a plugin built against a different KHook than the core runs gets
`false` returned out of `Load()`, with the reason in `error`. A plugin that
never calls `KHOOK_INIT()` — no hooks of its own — is not checked.
Order matters: `KHOOK_INIT()` reads the instance pointers and calls the
Source2Mod, so it goes after `SOURCE2MOD_SAVEVARS()` and after you have set the
globals your virtual hooks name.
The list `KHOOK_INIT()` walks is per plugin binary: every hook wrapper
registers itself as it is constructed, whatever file it is in, and
`KHOOK_DESTRUCT()` takes them down last-first.
Hooks installed later [#hooks-installed-later]
`KHOOK_INIT()` installs the hooks that exist **when it runs**. A hook that is a
member of an object you create after `Load()` is registered but not installed —
install it yourself with `m_hX.Init()`, typically in that object's constructor
(the members are built before the constructor body runs):
```cpp
class CChatFilter
{
public:
KHook::Return Hook_ClientCommand(ISource2GameClients* pThis, CPlayerSlot slot, const CCommand& args);
CChatFilter()
{
m_hClientCommand.Init(); // KHOOK_INIT() ran long ago
}
private:
KHOOK_VIRTUAL(m_hClientCommand, &ISource2GameClients::ClientCommand, &g_pSource2GameClients, &CChatFilter::Hook_ClientCommand, nullptr);
};
auto* pFilter = new CChatFilter(); // the hook goes in here
...
delete pFilter; // and comes out here
```
`Init()` resolves the target the macro was given exactly as `KHOOK_INIT()`
would: it reads `&pInstance` now, looks the vtable up, resolves the gamedata
entry or calls the `ADDR_*` getter. It returns `false` and logs when that
fails, and it can simply be called again — a hook waiting for an instance that
does not exist yet goes in with the first call that finds it. On a hook that
is already installed it does nothing; `m_hX.IsInstalled()` tells you which is
which.
A hook declared with a `nullptr` target is placed the explicit way:
```cpp
m_hX.Init(address); // KHOOK_MEMBER / KHOOK_FUNCTION: ISource2ModMemory or typed function pointer
m_hX.Init(pInstance); // KHOOK_VIRTUAL, one object
m_hX.InitGlobal(vtable); // KHOOK_VIRTUAL, a whole vtable
```
Removing hooks [#removing-hooks]
`KHOOK_DESTRUCT()` detaches every hook and deletes it — deleting is what takes
the detour down. One hook can also go on its own: `m_hX.Destruct()`. That is
final: the wrapper no longer owns a KHook object, so it cannot be installed
again — to hook the same thing later, create a new owner.
Destroying the owner does the same. Each wrapper's destructor calls
`Destruct()` and takes itself off the plugin's hook list, so deleting an object
removes its hooks, and a later `KHOOK_DESTRUCT()` never touches a hook that is
already gone. `Destruct()` is safe to call twice: an object still alive at
`Unload()` is covered by `KHOOK_DESTRUCT()`, and deleting it afterwards is
harmless.
Install and remove hooks on the game thread, and never destroy an owner from
inside one of its own handlers — removing a hook synchronously under its own
callback can deadlock or crash. Defer it to the next frame.
Take every hook down before your plugin unloads. A live hook pointing into an
unmapped library crashes the server on the next call.
The wrapper's members [#the-wrappers-members]
What `KHOOK_MEMBER` / `KHOOK_FUNCTION` / `KHOOK_VIRTUAL` declare is a
`CSource2ModMemberHook`, `CSource2ModFunctionHook` or `CSource2ModVirtualHook`:
| member | does |
| ------------------------------------------------------- | ----------------------------------------------------------------------- |
| `Init()` | resolves the macro's target and installs; `false` on failure, retryable |
| `Init(address)` / `Init(pInstance)` / `InitGlobal(...)` | installs on an explicit target |
| `Destruct()` | detaches and deletes the KHook object; final |
| `IsInstalled()` | whether it is installed |
| `CallOriginal(...)` | runs the original past every hook |
| `Get()` / `operator->` | the KHook object underneath (`KHook::Member`, `Function` or `Virtual`) |
See the [ISource2ModHooks reference](/docs/core-api/ISource2ModHooks).
# Game Hooks (/docs/hooks/game-hooks)
Most plugins hook the same few dozen game functions: `TakeDamage`,
`CanAcquire`, `PostThink`, the movement services, the jump code. Every one of
them written as a `KHOOK_MEMBER` ties the plugin to that function's exact
prototype and to a signature of its own — and when Valve moves an argument or
changes the order between Windows and Linux, every plugin has to be rebuilt.
`ISource2ModGameHooks` holds those hooks in the core. You register a handler; the
core resolves the function from **its** gamedata, places the detour the first
time anybody listens and takes it out again once the last listener is gone. A
changed prototype is fixed in the core's one call site, and the plugins keep
running. This is what puts a plugin on the **stable tier** — see
[Compatibility](/docs/development/compatibility).
```cpp title="plugin.cpp"
#include "source2mod/ISource2ModGameHooks.h"
bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS();
// Pre: halve every hit on a player.
m_hDamage = g_pSource2ModGameHooks->HookTakeDamage([](TakeDamageContext& ctx, bool post) -> Action
{
if (ctx.entity && ctx.entity->IsPlayerPawn())
ctx.info->m_flDamage = ctx.info->m_flDamage() * 0.5f; // by pointer: the original sees it
return Action::Ignore;
}, false);
// Post: react to what CanAcquire decided -- a method as the handler.
g_pSource2ModGameHooks->HookCanAcquire(SOURCE2MOD_MEMBER(this, &MyPlugin::OnCanAcquire), true);
return true;
}
```
There is no `KHOOK_INIT()` in this: the plugin declared no hook of its own. No
plugin id either — whose a handler is, the core reads off the handler itself
(see [Handlers and owners](#handlers-and-owners)).
The handler [#the-handler]
Every hook hands its handler a **context** — a plain struct with the hooked
object, the function's arguments and, where the function returns something,
`result` — plus `post`, which pass this is. The handler answers the way a
KHook handler does: an action, and for a function with a return value the
value that goes with it (`GameHookReturn`).
| Answer | Pre | Post |
| ------------------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `Action::Ignore` | nothing changed | nothing changed |
| `{ Action::Override, value }` | the original still runs, the function returns `value` | the function returns `value` instead of the original's |
| `{ Action::Supersede, value }` | the original does not run, the function returns `value`; the remaining pre handlers are skipped | — |
```cpp
// Nobody moves while frozen: CanMove returns false.
g_pSource2ModGameHooks->HookCanMove([](CanMoveContext& ctx, bool post) -> GameHookReturn
{
if (IsFrozen(ctx.pawn))
return { Action::Supersede, false };
return Action::Ignore;
}, false);
```
An action alone takes its value from `ctx.result`, so setting the field and
returning `Action::Supersede` works too — but keep the two in one statement:
an `if` without braces around `ctx.result = x; return Action::Supersede;`
guards only the first of them. A function without a return value (PostThink,
the movement functions) answers the action alone.
Arguments passed **by pointer** (the damage info, the move data, the weapon)
can be changed in place and the original sees the change. Arguments passed
**by value** (a speed, a flag) cannot; a handler that needs to change one keeps
a raw `KHOOK_MEMBER` of its own and uses [`KHook::Recall`](/docs/hooks/handlers#changing-the-arguments).
Handlers run in registration order, pre before the original, post after it. In
post, `ctx.result` holds what the original returned. A value a handler returns
lands in `ctx.result` right away, so the next handler sees it. A `Supersede`
stops the remaining pre handlers — the same rule as
[command listeners](/docs/convars-commands/command-listeners).
What the game would do: CallOriginal [#what-the-game-would-do-calloriginal]
`ctx.CallOriginal()` runs the game's function now, with the arguments the game
passed, past every hook on it — SourceHook's `SH_CALL` — and returns what it
returned. From a pre handler that is how you learn what the game would answer:
```cpp
g_pSource2ModGameHooks->HookCanAcquire([](CanAcquireContext& ctx, bool post) -> GameHookReturn
{
const AcquireResult game = ctx.CallOriginal();
if (game == AcquireResult::Allowed && IsBlockedHere(ctx))
return { Action::Supersede, AcquireResult::NotAllowedByMode };
return { Action::Supersede, game }; // it has run already -- do not let it run twice
}, false);
```
In a pre handler the function **really runs**, side effects and all, so a
handler that calls it answers `Supersede` with the value; with `Ignore` the
game would run it a second time. A changed pointed-to object (the damage info,
the move data) counts in that call; a scalar field changed in the context does
not. In a post handler `ctx.result` already holds the game's answer.
The same trick without a value — the game's own code, run with a setting
flipped for one call:
```cpp
CConVarRef sv_autobunnyhopping("sv_autobunnyhopping");
g_pSource2ModGameHooks->HookCheckJumpButtonLegacy([](LegacyJumpContext& ctx, bool post) -> Action
{
if (sv_autobunnyhopping.Get())
return Action::Ignore;
sv_autobunnyhopping.Set(true);
ctx.CallOriginal();
sv_autobunnyhopping.Set(false);
return Action::Supersede;
}, false);
```
Which functions [#which-functions]
The set is the one Swiftly hooks, which is what most plugins reach for:
| Group | Hooks |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Damage and items | `TakeDamage`, `CanAcquire`, `CanUse`, `DropWeapon` |
| Player | `CanMove`, `PostThink`, `ProcessUsercmds`, `SimulateUserCommands`, `RunCommand` |
| Entities | `AcceptInput`, `Touch` |
| Movement services | `AirAccelerate`, `AirMove`, `CanUnduck`, `CategorizePosition`, `CheckFalling`, `CheckParameters`, `CheckVelocity`, `CheckWater`, `Duck`, `Friction`, `FullWalkMove`, `GroundAccelerate`, `LadderMove`, `MoveInit`, `PlayerMove`, `ProcessMovement`, `SetupMove`, `TryPlayerMove`, `WalkMove`, `WaterMove` |
| Jump | `OnJumpLegacy`, `OnJumpModern`, `CheckJumpButtonLegacy`, `CheckJumpButtonModern` |
Every context is listed in the [ISource2ModGameHooks reference](/docs/core-api/ISource2ModGameHooks).
Two things to know:
* `GroundAccelerate`'s arguments come in a different order on Windows and
Linux. The context always has them in one order; that swap is the core's.
* `Touch` is a vtable hook on `CBaseEntity`'s own table, so an entity class
that overrides `Touch` is not seen. `CanUse` and `DropWeapon` are vtable hooks
on the weapon services; everything else is found by signature.
`IsAvailable(GameHook::X)` says whether the core's gamedata has the function on
this platform. Handlers can be registered either way; on a missing entry they
never run.
Handlers and owners [#handlers-and-owners]
A handler is a `Source2ModCallback`: a lambda, a free or static function, or an
object and one of its methods — SourceHook's `SH_STATIC` / `SH_MEMBER`, spelt
`SOURCE2MOD_STATIC(func)` / `SOURCE2MOD_MEMBER(this, &Class::Method)` (or just `func`
and `{ this, &Class::Method }`). Whose it is, the core reads off the handler:
its code lies in the plugin's library, and that is the owner. No call takes a
plugin id, and what a plugin still holds when it unloads, the core drops for
it — nothing to do in `Unload()`.
`HookX()` returns a `GameHookId`. A plugin may put several handlers on one
function — a pre and a post, two features that both care about damage — and
drop any one of them with `UnhookX(id)`, or with the same function or method
it hooked with (a lambda has no such identity; it goes by its id):
```cpp
GameHookId m_hDamage = 0;
m_hDamage = g_pSource2ModGameHooks->HookTakeDamage(OnTakeDamage, false);
g_pSource2ModGameHooks->HookPostThink(SOURCE2MOD_MEMBER(this, &MyPlugin::OnPostThink), true);
...
g_pSource2ModGameHooks->UnhookTakeDamage(m_hDamage);
g_pSource2ModGameHooks->UnhookPostThink(SOURCE2MOD_MEMBER(this, &MyPlugin::OnPostThink), true);
```
Unhooking from inside the handler is fine: the detour itself comes out on the
next frame, never from inside its own dispatch.
Debugging [#debugging]
`s2m hookdebug ` in the server console logs
every call of the matching game hooks: each plugin's answer, the outcome and
the return value; `TakeDamage` adds the victim, the damage and the damage bits.
```
Source2Mod hookdebug TakeDamage
... hookdebug CBaseEntity::TakeDamageOld pre: JailBreak.smx -> Supersede
... hookdebug CBaseEntity::TakeDamageOld pre: Supersede (return 0) victim=func_breakable damage=25.000000 bits=4
Source2Mod hookdebug off
```
When to still hook yourself [#when-to-still-hook-yourself]
* The function is not in the set.
* You need to change an argument passed by value, or call the original with
different arguments (`KHook::Recall`).
Those are raw hooks — [`KHOOK_MEMBER`](/docs/hooks/function-hooks),
[`KHOOK_VIRTUAL`](/docs/hooks/virtual-hooks) — and they put the plugin on the
raw tier: it hooks through KHook itself and is rebuilt when the function
changes. `s2m list` shows which tier each plugin is on.
Reference [#reference]
* [`ISource2ModGameHooks`](/docs/core-api/ISource2ModGameHooks) — every hook, context and enum
* [`ISource2ModTypes`](/docs/core-api/ISource2ModTypes) — `Source2ModCallback`, `SOURCE2MOD_STATIC`, `SOURCE2MOD_MEMBER`
* [`ISource2ModApi::GameHooks()`](/docs/core-api/ISource2ModApi) — the same interface through the API
# Gamedata for Hooks (/docs/hooks/gamedata)
A hook needs to know **where** the function is. For an engine interface the
compiler knows (`&ISource2Server::GameFrame`), but most of the game is not
exported: functions are found by a byte **signature**, and virtuals of game
classes by a vtable **offset**. Both live in gamedata, one value per platform,
so a game update means editing a JSON file instead of rebuilding plugins.
This page covers gamedata as far as hooking needs it. The file format itself is
documented in [Gamedata](/docs/development/gamedata).
Where gamedata comes from [#where-gamedata-comes-from]
Source2Mod reads **every** `.json` file in its gamedata folder
(`addons/source2mod/gamedata/`, `SOURCE2MOD_GAMEDATA_DIR()` at runtime) into
one table, `gamedata.json` first and the rest in name order. The first
definition of a key wins, so a key in `gamedata.json` cannot be redefined by
another file. Ship your plugin's entries as a file of their own with keys of
their own (`myplugin.json`), and they resolve exactly like Source2Mod's.
Each entry is keyed by name and carries per-platform values; only the current
platform's value is loaded.
```json title="gamedata/myplugin.json"
{
"CCSGameRules::TerminateRound": {
"signatures": {
"library": "server",
"windows": "48 8B C4 4C 89 48 ? 48 89 48 ? 55 41 54",
"linux": "55 48 89 E5 41 57 41 56 49 89 FE 41 55 41 54 53 48 81 EC ? ? ? ? 48 8D 05 ? ? ? ? F3 0F 11 85 ? ? ? ? 89 B5"
}
},
"CCSPlayerController::Respawn": {
"offsets": {
"windows": 274,
"linux": 276
}
}
}
```
(Both entries are real, from Source2Mod's own `gamedata.json`; they are shown
here only as examples of the shape. Use your own key names.)
How hooks use it [#how-hooks-use-it]
| you write | gamedata used | resolved through |
| ------------------------------------------------------------------ | --------------------------------------------- | ----------------------------------------- |
| `KHOOK_MEMBER(m_hX, "Name", ...)` / `KHOOK_FUNCTION` | `signatures` | `ISource2ModGameConfig::ResolveSignature` |
| `KHOOK_VIRTUAL(m_hX, "Name", KHOOK_VTABLE(...), ...)` | `offsets` | `ISource2ModGameConfig::GetOffset` |
| `KHOOK_VIRTUAL(m_hX, KHOOK_GAMEDATA(&Class::Method, "Name"), ...)` | `offsets`, checked against the header's index | the same |
| [`ISource2ModGameHooks`](/docs/hooks/game-hooks) | the core's own `signatures` and `offsets` | the core |
| `GAMECONFIG_RESOLVE("Name")` | `signatures` | the same, for calling |
| `GAMECONFIG_OFFSET("Name")` | `offsets` | the same, for `CALL_VIRTUAL` |
`ResolveSignature` looks the entry's `library` up among the loaded modules
(`server`, `engine2` — `engine` is accepted as an alias — `tier0`, `vscript`…),
then finds the function by exported symbol when the value starts with `@`, or by
pattern scan otherwise. It returns a null `ISource2ModMemory` when the module, the
symbol or the pattern is missing. `GetOffset` returns `-1` for a missing key.
See [ISource2ModGameConfig](/docs/core-api/ISource2ModGameConfig).
Writing a signature [#writing-a-signature]
A signature is the first bytes of the function, written as hex, with `?` for
every byte that is not stable:
```text
48 89 5C 24 10 57 48 83 EC 30 48 8B DA 48 8B F9 48 85 C9
55 48 89 E5 41 57 41 56 49 89 FE 41 55 41 54 53 48 81 EC ? ? ? ?
```
* **Start at the function's first byte.** The address the scan finds is where
the detour goes; a pattern that starts in the middle of a function places the
hook in the middle of it.
* **Wildcard what the compiler or linker will move**: relative call and jump
targets, RIP-relative addresses (`48 8D 05 ? ? ? ?`), stack frame sizes and
field displacements that change when a class grows (`48 81 EC ? ? ? ?`).
* **Keep it unique and short.** The scan returns the first match; a pattern
that also matches another function silently resolves to the wrong one. Add
bytes until exactly one function matches, then stop — every extra byte is one
more that can change in the next update.
* **Exported functions** do not need a pattern: write the symbol with a leading
`@` (`"@SymbolName"`) and it is looked up by name in the module's exports.
Tools such as IDA with a signature-maker plugin produce these, but always check
the result is unique in the current binary.
Windows and Linux are different binaries [#windows-and-linux-are-different-binaries]
Every entry has a value per platform because nothing carries over between them:
* **Signatures** — MSVC and Clang/GCC generate different code for the same
source. Write and verify each platform's pattern against its own binary
(`server.dll` / `libserver.so`).
* **Vtable indices** — the two ABIs lay vtables out differently (the Itanium
ABI used on Linux has two destructor entries where MSVC has one, and MSVC
merges overloads differently). The same virtual can sit a few slots apart:
`CCSPlayerController::Respawn` is 274 on Windows and 276 on Linux, while
`CBaseEntity::Teleport` is 165 and 164. Never derive one from the other.
* **Argument order** — the prototype itself can differ, as it does for
`TerminateRound` and `GroundAccelerate`. The handler must follow; see
[Platform differences](/docs/hooks/handlers#platform-differences).
* **Field offsets** — the layout of non-schema structs and of padding can
differ between the platforms, so a raw offset into an object read in a hook
needs a per-platform value too. Networked and schema fields do not: the SDK's
schema accessors look their offsets up at runtime from the schema system.
Finding a vtable index [#finding-a-vtable-index]
* From the declaration: when a header's class matches the game slot for slot,
`&Class::Method` in `KHOOK_VIRTUAL` gives the index with nothing written down.
* From the binary: find the vtable (by its RTTI name — `ISource2ModModule::GetVirtualTableByName`
does this at runtime) and count the entries up to the function. Remember the
table starts after the RTTI pointer: index 0 is the first function.
* Put the number in gamedata under `offsets`, per platform, and name that key in
`KHOOK_VIRTUAL`.
Verifying [#verifying]
* **Read the log.** A hook whose signature did not resolve logs
`KHook: 'Name' was not resolved; hook not installed`, one whose offset is
missing logs `KHook: offset 'Name' is missing from gamedata; hook not installed`,
and `KHOOK_INIT()` returns `false`.
* **Check the hook fires** — a log line in the handler, once, on both
platforms.
* **Check the arguments make sense** — print a pointer's class name or a
float argument. Garbage means the prototype or the platform order is wrong,
not the signature.
* **After every game update**, recheck your own entries. Source2Mod refuses
to start when one of its required signatures breaks, but your plugin's
entries are yours to watch.
A signature that matches the wrong function does not fail — it hooks the wrong
function, with your handler reading its arguments as if they were the ones you
expected. Uniqueness is the one check you cannot skip.
# Writing Handlers (/docs/hooks/handlers)
A handler is an ordinary member function of the class that declared the hook.
It receives the hooked call's arguments and returns a `KHook::Return` that
tells KHook what to do with the original.
The signature [#the-signature]
The object that was actually called arrives as the first parameter; the rest
are the function's own arguments, in the order the **machine code** takes
them. A `KHOOK_FUNCTION` handler has no object, only the arguments.
```cpp
// void CCSPlayer_WeaponServices::DropWeapon(CBasePlayerWeapon*, Vector*, Vector*)
KHook::Return OnDropWeapon(CCSPlayer_WeaponServices* pThis, CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity);
```
The macros derive the hook's type from this declaration, so the declaration
**is** the prototype KHook uses to read the registers and the stack. Nothing
checks it against the game. A wrong parameter list compiles fine and then reads
garbage — or corrupts the call — at runtime.
* **Match the real function**, not an older port or a Source 1 header. Where
Source2Mod resolves the function, its typedef in `ISource2ModAddresses.h` is
the checked prototype: `CCSPlayerPawn_PostThink_t` takes nothing but the
pawn, `CCSPlayer_MovementServices_TryPlayerMove_t` is declared `void`
because no caller reads what is left in `al`, and so on.
* **Widths matter.** A `bool` where the game passes an `int`, or a `float`
where it passes a `double`, reads the wrong part of the register.
* **Returning a struct or a non-trivial class by value** changes the calling
convention (a hidden return pointer). Stick to what the typedef says.
Platform differences [#platform-differences]
The same function does not always take its arguments in the same order on
Windows and Linux: the compilers are free to lay the parameters out
differently, and they do. `CCSGameRules::TerminateRound` is the standing
example — the delay and the reason swap places:
```cpp title="ISource2ModAddresses.h"
#ifdef _WIN32
using CCSGameRules_TerminateRound_t = void (FASTCALL*)(CCSGameRules* pThis, float flDelay,
uint32 nReason, uint32* pnTeamId);
#else
using CCSGameRules_TerminateRound_t = void (FASTCALL*)(CCSGameRules* pThis, uint32 nReason,
uint32* pnTeamId, float flDelay);
#endif
```
The handler has to follow suit, with an `#ifdef` on the declaration **and** the
definition. This is TroubleTerroristTown's round-end hook (the plugin spells
the reason as its own 32-bit enum; any type of the same width works):
```cpp title="Hooks.h"
class CHooks
{
public:
// Same argument order as CCSGameRules_TerminateRound_t: the delay and
// the reason swap places between platforms, the team goes by pointer.
#ifdef _WIN32
KHook::Return OnTerminateRound(CCSGameRules* pThis, float flDelay, uint32 nReason, uint32* pnTeamId);
#else
KHook::Return OnTerminateRound(CCSGameRules* pThis, uint32 nReason, uint32* pnTeamId, float flDelay);
#endif
KHOOK_MEMBER(m_hTerminateRound, ADDR_TERMINATE_ROUND(), &CHooks::OnTerminateRound, nullptr);
};
```
```cpp title="OnTerminateRound.cpp"
#ifdef _WIN32
KHook::Return CHooks::OnTerminateRound(CCSGameRules* pThis, float flDelay, uint32 nReason, uint32* pnTeamId)
#else
KHook::Return CHooks::OnTerminateRound(CCSGameRules* pThis, uint32 nReason, uint32* pnTeamId, float flDelay)
#endif
{
if (g_bRoundEnding)
return { KHook::Action::Supersede }; // a round end is already in flight: swallow this one
return { KHook::Action::Ignore };
}
```
The winning team goes in as a pointer, null when there is none. Other known
cases are marked in `ISource2ModAddresses.h` (`GroundAccelerate` swaps the wish
direction and the frame time). When you write a signature of your own, check
the prototype on **both** binaries — see [Gamedata](/docs/hooks/gamedata).
Returning an action [#returning-an-action]
A handler returns `KHook::Return`: the action, plus the value when the
hooked function returns one.
```cpp
return { KHook::Action::Ignore }; // void handlers
return { KHook::Action::Ignore, true }; // the value is not used with Ignore
return { KHook::Action::Override, false }; // the original runs, false is returned
return { KHook::Action::Supersede, false }; // the original is skipped, false is returned
```
What KHook does with each is in
[How a hooked call runs](/docs/hooks#how-a-hooked-call-runs). For example,
turning a connection away before the game sees it:
```cpp
KHook::Return Plugin::Hook_ClientConnect(ISource2GameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, bool unk1, CBufferString* pRejectReason)
{
if (IsBanned(xuid))
return { KHook::Action::Supersede, false }; // the game never sees this connection
return { KHook::Action::Ignore, true };
}
```
Changing what the original sees [#changing-what-the-original-sees]
Through pointers [#through-pointers]
Arguments passed by pointer or reference can simply be changed in a **Pre**
handler: the original runs afterwards and reads what you left there. This is
the sample plugin's damage scaling:
```cpp
KHook::Return SamplePlugin::Hook_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult)
{
// A Pre hook: the original runs after it, with whatever is in pInfo by then.
if (pInfo && sample_damage_scale.Get() != 1.0f)
pInfo->m_flDamage *= sample_damage_scale.Get();
return { KHook::Action::Ignore, 0 };
}
```
Changing the arguments [#changing-the-arguments]
Arguments passed by value cannot be changed that way. `KHook::Recall` carries
the call on with **new arguments**: the hooks after yours, the original and
every Post handler see them instead of the ones the caller passed. Your own
handler is not called again — the call picks up right after it. The action you
pass is your handler's action as usual, so `Ignore` lets the original run with
the new arguments. `Recall` hands that return back, so it is the whole of the
handler's `return`:
```cpp
// Hooked with KHOOK_MEMBER(m_hSetModel, ADDR_SET_MODEL(), &Plugin::Hook_SetModel, nullptr)
KHook::Return Plugin::Hook_SetModel(CBaseModelEntity* pThis, const char* pszModel)
{
if (V_strcmp(pszModel, "models/weapons/old.vmdl"))
return { KHook::Action::Ignore };
const char* pszReplacement = "models/weapons/new.vmdl";
return KHook::Recall(ADDR_SET_MODEL(), KHook::Return{ KHook::Action::Ignore }, pThis, pszReplacement);
}
```
The first argument of `Recall` only supplies the function's **type** — KHook
calls the chain it is already in, not that pointer. For a detour, pass the
typed function pointer (an `ADDR_*` getter or your own typedef); for a virtual
hook, the member function pointer (`&ISource2GameClients::ClientCommand`).
Do not pass a pointer to an SDK schema helper such as
`&CBaseModelEntity::SetModel` — its parameter list is the helper's (it has a
`HookChain` argument), not the game function's.
This is SourceHook's `RETURN_META_NEWPARAMS`. Only call it from inside a
handler — KHook aborts on a recall made anywhere else.
Calling the original [#calling-the-original]
From a Pre handler, `CallOriginal` runs the real function with the arguments
you give it, past every hook on it — then supersede, so KHook does not run it
a second time. The member declared by the macro has `CallOriginal` itself and
forwards `->` to the KHook object underneath:
```cpp
// Public plugin: auto-bunnyhop for a whitelisted player, for this one call.
sv_autobunnyhopping.Set(true);
m_hCheckJumpButtonLegacy->CallOriginal(pThis, mv);
sv_autobunnyhopping.Set(false);
return { KHook::Action::Supersede };
```
Use it to act on the original's result from inside the Pre handler, to run it
with changed global state around it, or to run it more than once:
```cpp
const int64_t ret = m_hTakeDamageOld.CallOriginal(pThis, pInfo, pResult);
// ... pResult is filled in now ...
return { KHook::Action::Supersede, ret };
```
Outside a handler, `KHook::CallOriginal(function, args...)` does the same for
any function — hooked by you or by someone else. For a member function pointer
it finds the original behind a vtable hook or a detour; for a plain function
pointer it finds the original behind a detour. For Source2Mod's `ADDR_*`
functions there is the shorter [`SOURCE2MOD_ORIGINAL`](/docs/hooks/addresses#calling-past-the-hooks).
`Supersede` skips the original **for every remaining Pre handler too**. If
you called the original yourself and only want to stop KHook calling it a
second time, supersede is still the right answer — but hooks registered
after yours will not see it run.
Reading the return value in Post [#reading-the-return-value-in-post]
In a Post handler the original has already run. These are only valid inside a
Post handler:
```cpp
bool original = KHook::GetOriginalReturn(); // what the original returned
bool overriden = KHook::GetOverrideReturn(); // what a Pre handler overrode it with
bool current = KHook::GetCurrentReturn(); // what the call is going to return
bool skipped = KHook::WasOriginalFunctionSkipped(); // did a Pre handler supersede it
```
`GetOriginalReturn` is meaningless when the original was superseded — check
`WasOriginalFunctionSkipped()` first.
Recursion [#recursion]
A handler that calls the function it hooks — through the game, a schema helper
or an `ADDR_*` pointer — runs straight back into its own hook. Unless the
handler guards against it, that is endless recursion. Call the original instead:
| from inside the hook of… | call it as |
| -------------------------------- | -------------------------------------- |
| your own `m_hX` | `m_hX.CallOriginal(...)` |
| an `ADDR_*` function | `SOURCE2MOD_ORIGINAL(ADDR_...())(...)` |
| a function a schema helper wraps | the helper with `HookChain::Bypass` |
```cpp
// Inside a TerminateRound hook: end the round now, without re-entering the hook.
pGameRules->TerminateRound(5.0f, static_cast(nReason), 0, HookChain::Bypass);
```
`CCSGameRules::TerminateRound` and `CCSPlayerController::TakeDamage` already
default to `HookChain::Bypass`; every other helper defaults to
`HookChain::Run`. See [Addresses](/docs/hooks/addresses#hookchain-on-schema-helpers).
The opposite case is fine: calling **another** hooked function from a handler
runs that function's hooks as usual.
Threads and context [#threads-and-context]
The handler's context is the object that declared the hook, so handlers are
ordinary member functions with access to your state. KHook itself is
thread-safe, but your handler runs on whatever thread called the hooked
function — engine code does not always run on the game thread. Keep a handler
that can be reached from elsewhere free of anything that is not thread-safe,
or hand the work to the game thread.
Keep hot handlers cheap. `PostThink`, the movement functions and
`PostEventAbstract` run for every player every tick; return early on the cases
you do not care about.
# Hooking (/docs/hooks)
Source2Mod hooks with **KHook** — Metamod:Source's own detour library, on
the one engine Metamod runs for the whole server. If you have written a
Metamod 2.0 plugin, you already know the API. The SDK wraps it in a set of
`KHOOK_*` macros so a hook is one line in your class, and Source2Mod does the
resolving, attaching and tearing down for you.
This section goes from the ground up. This page covers what KHook is, how a
hooked call runs and which macro fits which target; the subpages go through
each part in detail.
ISource2ModGameHooks: the game functions plugins hook most, detoured once by the core — a handler and a context, no KHook of your own.
KHOOK_VIRTUAL on one instance or a whole vtable.
KHOOK_MEMBER / KHOOK_FUNCTION by gamedata name, ADDR_* getter or address; KHOOK_INIT and KHOOK_DESTRUCT.
Signatures, KHook::Return, CallOriginal, changing arguments and return values.
ADDR_* functions, SOURCE2MOD_ORIGINAL and HookChain.
Signatures and offsets from a hooking point of view.
KHook objects without the macros, and the C-style API underneath.
KHook in one minute [#khook-in-one-minute]
A detour library lets you run your own code before and after a function of the
game or the engine — or instead of it. Metamod ships one and hands it to every
plugin, so that all plugins on a server patch functions through the **same**
backend. Two libraries patching the same function independently cannot see
each other, and that ends in crashes that are very hard to trace.
For twenty years that library was **SourceHook**. It was built to solve
conflicts between virtual table hooks, and its whole API revolves around
virtual functions. Source 2 plugins need much more than that — plenty of what
is worth hooking is a plain function found by signature — so plugins started
bringing their own detour libraries, and the old conflicts came back.
**KHook** is the successor, written for Metamod 2.0. It detours both vtable
entries and plain function addresses, it is thread-safe, and the goal is for
every Metamod 2.0 plugin to use it so Metamod owns detouring again.
The KHook library itself is documented in its
[README](https://github.com/Kenzzer/KHook/blob/master/README.md); this section
covers it as far as a Source2Mod plugin needs it.
One engine for everyone [#one-engine-for-everyone]
Source2Mod does **not** run a hook engine of its own. It takes KHook from
Metamod when it loads and hands that same engine to every plugin through
`SOURCE2MOD_KHOOK_INTERFACE`.
This is deliberate. `CallOriginal` and `Supersede` walk the handler chain of
the engine that owns the patch. Two independent engines patching the same
address cannot see through each other — one of them silently calls the other's
trampoline instead of the real original. One engine means one chain per
address, so the core's hooks, your plugin's hooks and the hooks of the Metamod
plugins next to you compose correctly.
You never fetch it. `SOURCE2MOD_EXPOSE` declares the globals and
`SOURCE2MOD_SAVEVARS` fills them:
```cpp title="plugin.cpp"
Plugin g_Plugin;
SOURCE2MOD_EXPOSE(my_plugin, g_Plugin);
bool Plugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS(); // KHook is live from here on
...
}
```
The KHook headers come from the SDK's `vendor/khook` submodule. The SDK stamps
that commit into your binary and `KHOOK_INIT()` refuses the load when it
differs from the one the core was built with, so a plugin built against one
KHook never hooks through another. A plugin with no hooks of its own is never
checked — it does not hook, so it does not care.
How a hooked call runs [#how-a-hooked-call-runs]
Once a function is detoured, every call to it goes through the same loop:
1. Every **Pre** callback runs, with the function's own arguments.
2. Each callback reports an **action**. KHook settles on one for the call.
3. Unless a Pre callback **superseded** it, the original function runs.
4. Every **Post** callback runs — the original has already returned by now.
5. The call returns: the original's value, or the one a callback supplied.
| `KHook::Action` | meaning |
| --------------- | ---------------------------------------------------------------- |
| `Ignore` | the callback did nothing |
| `Override` | the original still runs, but the callback's return value is used |
| `Supersede` | the original is skipped, the callback's return value is used |
Two things follow from that loop:
* `Supersede` in a **Post** callback cannot stop the original — it has already
run.
* When several hooks want to decide the return value, the **first**
`Override` or `Supersede` wins. KHook makes no attempt to resolve such
conflicts, so do not rely on being the one that wins.
Source2Mod's own listeners — `REGISTER_CON_LISTENER`, `HOOK_GAME_EVENT`, the
net message hooks, entity output listeners — return `Action`, which is the
same type: `ISource2ModTypes.h` aliases it to `KHook::Action`.
Three layers [#three-layers]
| layer | what it is | use it when |
| ---------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------- |
| `KHOOK_*` macros (`ISource2ModHooks.h`) | one line per hook, resolved and installed by Source2Mod | almost always |
| `KHook::Virtual` / `Member` / `Function` | KHook's own class templates | a hook the macros do not fit — see [Raw KHook](/docs/hooks/raw-khook) |
| `KHook::SetupHook` / `SetupVirtualHook` | the raw C-style API the classes are built on | practically never |
A hook is one line [#a-hook-is-one-line]
Every hook is a member of your class, declared with a macro from
`ISource2ModHooks.h` (pulled in by `ISource2ModPlugin.h`). The macro takes the
hook's type from the **handler** it names — the handler's first parameter is
the hooked class, the rest are the function's own arguments — so nothing is
spelled twice. Declare the handler above the macro.
```cpp title="plugin.h"
class Plugin final : public ISource2ModPlugin
{
public:
bool Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) override;
bool Unload(char* error, size_t maxlen) override;
KHook::Return Hook_GameFrame(ISource2Server* pThis, bool simulating, bool bFirstTick, bool bLastTick);
KHook::Return Hook_LoadEventsFromFile(IGameEventManager2* pThis, const char* pszFile, bool bSearchAll);
KHook::Return Hook_PostThink(CCSPlayerPawn* pThis);
KHook::Return Hook_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult);
KHook::Return Hook_SetModel(CBaseModelEntity* pThis, const char* pszModel);
private:
KHOOK_VIRTUAL(m_hGameFrame, &ISource2Server::GameFrame, &g_pSource2Server, nullptr, &Plugin::Hook_GameFrame); // Post only
KHOOK_VIRTUAL(m_hLoadEventsFromFile, &IGameEventManager2::LoadEventsFromFile, KHOOK_VTABLE("server", "CGameEventManager"), &Plugin::Hook_LoadEventsFromFile, nullptr); // Pre only
KHOOK_MEMBER(m_hPostThink, "CCSPlayerPawn::PostThink", &Plugin::Hook_PostThink, nullptr); // gamedata entry name
KHOOK_MEMBER(m_hTakeDamageOld, ADDR_TAKE_DAMAGE_OLD(), &Plugin::Hook_TakeDamageOld, nullptr); // address Source2Mod already resolved
KHOOK_MEMBER(m_hSetModel, ADDR_SET_MODEL(), &Plugin::Hook_SetModel, nullptr);
};
```
The last two arguments are always the Pre and the Post callback; `nullptr`
leaves a side empty.
A function Source2Mod resolves itself has an `ADDR_*()` getter in
`ISource2ModAddresses.h`; pass it instead of a gamedata name and your plugin needs
no gamedata entry, no signature of its own, and follows Source2Mod's gamedata
through game updates. The getter is read when `KHOOK_INIT()` runs, after
`SOURCE2MOD_SAVEVARS()` has filled `g_pSource2ModAddresses`. The full list is on
[Addresses](/docs/hooks/addresses). `KHOOK_INIT()` in `Load()` installs every hook declared
this way and `KHOOK_DESTRUCT()` in `Unload()` takes them all down — see
[Function hooks](/docs/hooks/function-hooks#installing-and-removing-everything).
Which macro to pick [#which-macro-to-pick]
| you want to hook | use | target |
| ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| a game function in the core's set (`TakeDamage`, `CanAcquire`, `PostThink`, the movement services…) | [`ISource2ModGameHooks`](/docs/hooks/game-hooks) | a handler — no hook of your own |
| an engine call the core already fans out (`GameFrame`, `ClientPutInServer`, `ClientDisconnect`, `StartupServer`…) | an [`ISource2ModListener` callback](/docs/events/listeners#engine-callbacks) | — |
| a method of an engine interface you hold a pointer to (`ISource2Server::GameFrame`) | `KHOOK_VIRTUAL` | `&g_pInstance`, the function as `KHOOK_GAMEDATA(&Class::Method, "name")` to follow the core's gamedata |
| a virtual of a game class, for every object of that class (`CCSPlayerController::Respawn`) | `KHOOK_VIRTUAL` | `KHOOK_VTABLE(module, class)`, function as a gamedata offset name or `&Class::Method` |
| an interface that is not the class's first base (`IHLTVDirector` in `CCSHLTVDirector`) | `KHOOK_VIRTUAL` | `KHOOK_VTABLE_BASE(module, class, base)` |
| a function Source2Mod already resolves (`TakeDamageOld`, `TerminateRound`, movement…) | `KHOOK_MEMBER` | an `ADDR_*()` getter |
| a member function found by signature | `KHOOK_MEMBER` | a gamedata entry name |
| a free function (no `this`) | `KHOOK_FUNCTION` | a gamedata entry name, `ADDR_*()` or an address |
| something the macros cannot express | `KHook::Virtual` / `Member` / `Function` | see [Raw KHook](/docs/hooks/raw-khook) |
Before you hook anything, check whether Source2Mod already dispatches it.
[Game hooks](/docs/hooks/game-hooks) and the listener's engine callbacks cover
most of what plugins detour; game events ([Events](/docs/events)), net and user messages
([Protobuf](/docs/protobuf/hooking)), console commands and chat
([ConVars & Commands](/docs/convars-commands)), entity outputs, sounds and
transmit all have listener APIs that are already hooked once in the core —
using those is cheaper and survives game updates without gamedata of your own.
Coming from SourceHook [#coming-from-sourcehook]
Earlier Source2Mod builds ran a private SourceHook engine. The mapping is:
| SourceHook | KHook |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `SH_DECL_HOOK*` + `SH_ADD_HOOK` | `KHOOK_VIRTUAL(..., &pInstance, ...)` |
| `SH_ADD_DVPHOOK` / `SH_ADD_MANUALDVPHOOK` | `KHOOK_VIRTUAL(..., KHOOK_VTABLE(module, class), ...)`, with a gamedata offset name or an index as the function |
| `SH_DECL_MANUALHOOK*` + `SH_MANUALHOOK_RECONFIGURE` | `KHOOK_VIRTUAL` with an index or a gamedata offset name; raw: `KHook::Virtual` + `Configure(index)` |
| `SH_DECL_INLINEHOOK*` + `SH_ADD_INLINEHOOK` | `KHOOK_MEMBER` / `KHOOK_FUNCTION` with a gamedata name, an address or an `ADDR_*` getter |
| `SH_MEMBER(this, &Class::Handler)` | the context — the macros always use the enclosing object |
| `META_IFACEPTR` / `SH_IFACEPTR` | the first handler parameter |
| `RETURN_META` / `RETURN_META_VALUE` | `return { action }` / `return { action, value }` |
| `RETURN_META_NEWPARAMS` | `return KHook::Recall(...)` — see [Changing the arguments](/docs/hooks/handlers#changing-the-arguments) |
| `MRES_IGNORED` / `MRES_HANDLED` | `KHook::Action::Ignore` |
| `MRES_OVERRIDE` | `KHook::Action::Override` |
| `MRES_SUPERCEDE` | `KHook::Action::Supersede` |
| `SH_CALL(...)` | `m_hX->CallOriginal(pThis, ...)` / `KHook::CallOriginal(&Class::Method, pThis, ...)` |
| `META_RESULT_ORIG_RET(T)` | `KHook::GetOriginalReturn()` |
| `META_RESULT_OVERRIDE_RET(T)` | `KHook::GetOverrideReturn()` |
| `SH_REMOVE_HOOK` / `SH_REMOVE_HOOK_ID` | `KHOOK_DESTRUCT()` / `m_hX.Destruct()` / deleting the owner |
`KHook::Action` and `META_RES` are **not** numerically compatible — `META_RES`
has an extra `MRES_HANDLED` at 1, which shifts everything after it. There is
nothing left to cast between: Source2Mod's `Action` *is* `KHook::Action`.
Checklist [#checklist]
* Set every global a virtual hook names **before** `KHOOK_INIT()`.
* Declare the handler **above** the macro that names it.
* The handler's parameters must match the real function — on **both**
platforms.
* A missing gamedata offset is refused, not installed at `-1` — read the log.
* `KHOOK_VTABLE` finds the primary vtable only; for a base further in use
`KHOOK_VTABLE_BASE`.
* A silent virtual hook is often a direct call that never goes through the
vtable.
* Hooks on objects created after `Load()` need `m_hX.Init()`.
* `Destruct()` is final; never destroy a hook's owner from its own handler.
* Every hook comes down before the plugin unloads.
See the [ISource2ModHooks reference](/docs/core-api/ISource2ModHooks) for every
class and macro.
# Raw KHook (/docs/hooks/raw-khook)
The `KHOOK_*` macros cover almost every hook a plugin needs. Underneath they are
plain KHook objects, and you can hold those directly when the wrapper does not
fit: a hook moved between instances at runtime, attached to several objects at
once, configured from something the macros cannot express, or used outside a
plugin class.
What the macros wrap [#what-the-macros-wrap]
The macros wrap `KHook::Virtual`, `KHook::Member` and `KHook::Function` in
`CSource2ModVirtualHook`, `CSource2ModMemberHook` and `CSource2ModFunctionHook`. Each
wrapper owns its KHook object, registers itself in a per-plugin list that
`KHOOK_INIT()` / `KHOOK_DESTRUCT()` walk, and remembers its target until the
Source2Mod is ready to resolve it.
You do not have to leave the wrapper to reach KHook: `m_hX.Get()` and
`m_hX->` hand you the object underneath, so `m_hX->AddGlobal(...)` or
`m_hX->Configure(...)` are one step away. Go fully raw only when you need to
own the object's lifetime yourself.
KHOOK_NEW [#khook_new]
A raw KHook object carries the hooked function and the callbacks in its type.
CTAD would derive that from the constructor arguments, but MSVC cannot, so
`KHOOK_NEW` (`ISource2ModTypes.h`) takes the type from the member it is stored in
instead — for a constructor's initialiser list:
```cpp title="plugin.h"
class Plugin final : public ISource2ModPlugin
{
public:
Plugin();
KHook::Return Hook_GameFrame(ISource2Server* pThis, bool simulating, bool bFirstTick, bool bLastTick);
private:
KHook::Virtual* m_hGameFrame = nullptr;
};
```
```cpp title="plugin.cpp"
Plugin::Plugin() :
KHOOK_NEW(m_hGameFrame, &ISource2Server::GameFrame, this, nullptr, &Plugin::Hook_GameFrame)
{
}
bool Plugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS();
m_hGameFrame->Add(api->GetSource2Server()); // attach
return true;
}
bool Plugin::Unload(char* error, size_t maxlen)
{
m_hGameFrame->Remove(g_pSource2Server); // detach
delete m_hGameFrame; // deleting takes the hook down
m_hGameFrame = nullptr;
return true;
}
```
The address lookup, `Add` / `Configure` and `Remove` / `delete` are all yours.
`KHOOK_INIT()` and `KHOOK_DESTRUCT()` do not see raw objects.
This is how the s2m core itself hooks — for example the overloaded
`IGameEventSystem::PostEventAbstract`, disambiguated with a `static_cast`:
```cpp
KHOOK_NEW(m_hPostEventAbstract,
static_cast(&IGameEventSystem::PostEventAbstract),
this, &Virtuals::Hook_PostEventAbstract, nullptr)
```
The three classes [#the-three-classes]
| class | where it goes | attach / detach |
| ------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `KHook::Virtual` | `Configure(&CLASS::Method)` or `Configure(index)` | `Add(pThis)` / `Remove(pThis)` for one object, `AddGlobal(pThis)` / `RemoveGlobal(pThis)` for every object sharing `pThis`'s vtable |
| `KHook::Member` | `Configure(address)` or `Configure(&CLASS::Method)` | configuring places the detour |
| `KHook::Function` | `Configure(address)` or `Configure(&Function)` | configuring places the detour |
The constructors take the function (or nothing, and you `Configure` later), an
optional context object, and the Pre and Post callbacks. With a context the
callbacks are members of the context's class — what the macros always use;
without one they are free functions. Deleting the object removes the detour.
Every class has `CallOriginal(...)` to run the original past its hooks.
A detour by gamedata name [#a-detour-by-gamedata-name]
```cpp
KHook::Member* m_hPostThink = nullptr;
// constructor
KHOOK_NEW(m_hPostThink, this, &Plugin::Hook_PostThink, nullptr)
// Load(), after SOURCE2MOD_SAVEVARS()
if (ISource2ModMemory address = GAMECONFIG_RESOLVE("CCSPlayerPawn::PostThink"))
m_hPostThink->Configure(address.RCast());
// Unload()
delete m_hPostThink;
```
A whole vtable without an instance [#a-whole-vtable-without-an-instance]
`AddGlobal` only reads the first pointer of what it is handed — the vtable
pointer — so a local holding a vtable address stands in for an object. This is
what `KHOOK_VTABLE` does for you:
```cpp
void* m_pVTable = nullptr;
ISource2ModModule* pServer = ISource2ModModule::New("server");
m_pVTable = pServer->GetVirtualTableByName("CCSPlayerController").GetPtr();
FREE_MODULE(pServer);
if (m_pVTable)
m_hRespawn->AddGlobal(reinterpret_cast(&m_pVTable));
// Unload(): RemoveGlobal with the same stand-in, then delete
m_hRespawn->RemoveGlobal(reinterpret_cast(&m_pVTable));
```
Keep `m_pVTable` alive (a member, not a local) — the same pointer is needed to
remove the hook.
Finding originals [#finding-originals]
Two free functions answer "what would run without the hooks":
```cpp
void* KHook::FindOriginal(void* function); // behind a detour
void* KHook::FindOriginalVirtual(void** vtable, int index); // behind a vtable hook
```
Both hand the input back unchanged when nothing is hooked. `SOURCE2MOD_ORIGINAL`
and `HookChain::Bypass` are built on them — see
[Addresses](/docs/hooks/addresses#calling-past-the-hooks).
The low-level API [#the-low-level-api]
Everything above is built on a handful of C-style functions. You will very
likely never call them, but knowing what they do explains how the classes
behave.
```cpp
HookID_t KHook::SetupHook(void* function, void* context, void* removed_function, void* pre, void* post, void* make_return, void* make_call_original, unsigned int stack_size, bool async = false);
HookID_t KHook::SetupVirtualHook(void** vtable, int index, void* context, void* removed_function, void* pre, void* post, void* make_return, void* make_call_original, unsigned int stack_size, bool async = false);
void KHook::RemoveHook(HookID_t id, bool async = false, void (*hook_removal_fn)(HookID_t, void*) = nullptr, void* context = nullptr);
```
| parameter | meaning |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `function` / `vtable` + `index` | what to detour: an address, or a vtable slot |
| `context` | any pointer; inside the callbacks `KHook::GetContext()` returns it |
| `removed_function` | called when the hook has been removed — the place to clean up |
| `pre`, `post` | the callbacks, with exactly the detoured function's signature |
| `make_call_original` | may be called to run the original and store its return value |
| `make_return` | called on the hook chosen to produce the final return value |
| `stack_size` | the stack size in bytes for the detoured function — the classes compute it from the argument types |
| `async` | add (or remove) the hook asynchronously; done synchronously from inside the hooked function it can deadlock |
A callback at this level does not *return* its result — KHook restores the
registers around every callback and ignores what it returns. It reports
through `KHook::SaveReturnValue(action, ptr_to_return, return_size, init_op, deinit_op, original)`
instead: the action, a pointer to the value to copy, its size, and two
functions KHook may use to construct and destruct its own copy.
`make_call_original` fetches the real function with
`KHook::GetOriginalFunction()`, calls it and saves the result with
`original = true`; `make_return` reads the chosen value with
`KHook::GetCurrentValuePtr(true)` and must end with
`KHook::DestroyReturnValue()`, which frees that copy and tells KHook the call
is over.
The raw API is full of footguns — a missed `DestroyReturnValue` leaks and
leaves KHook mid-call, a wrong `stack_size` corrupts the arguments. The
classes and macros exist so nobody has to write this by hand.
The KHook [README](https://github.com/Kenzzer/KHook/blob/master/README.md) and
`vendor/khook/include/khook.hpp` in the SDK are the reference for everything on
this page.
# Virtual Hooks (/docs/hooks/virtual-hooks)
A virtual hook replaces one slot of a vtable. It is the right tool for engine
interfaces (`ISource2Server`, `ISource2GameClients`, `IGameEventSystem`…) and for
virtual functions of game classes.
```cpp
KHOOK_VIRTUAL(member, function, target, pre, post)
```
* **member** — the name of the hook member in your class.
* **function** — which slot: see [Which slot](#which-slot).
* **target** — which object or vtable: see [What it attaches to](#what-it-attaches-to).
* **pre**, **post** — `&Self::Handler` or `nullptr`.
The handler's first parameter is the hooked class, the rest are the function's
own arguments. Write the handlers first — the macro takes the hook's type from
them. [Writing handlers](/docs/hooks/handlers) covers the body.
Which slot [#which-slot]
The **function** argument says where in the vtable the hook goes:
* `&Class::Method` — the index is read out of the member function pointer,
nothing written down by hand. The class declaration has to match the game's
vtable **slot for slot**; headers carried over from older games often do not,
so check the index against the binary before you trust one.
* an integer — a hardcoded index.
* a gamedata offset name (`"CCSPlayerController::Respawn"`) — read from
gamedata at `KHOOK_INIT()`. Offsets differ between Windows and Linux, and
gamedata keeps one per platform — see [Gamedata](/docs/hooks/gamedata).
* `KHOOK_GAMEDATA(&Class::Method, "name")` — both at once. Gamedata wins; a
difference between the two is logged (one of them is stale), and a missing
entry for this platform leaves the header's index in place. The core's
gamedata carries the engine interfaces' slots (`ISource2Server::GameFrame`,
`ISource2GameClients::ClientPutInServer`, …), so a hook written this way
follows a core update instead of a rebuild.
The last two keep the plugin on the [stable tier](/docs/development/compatibility):
a bare `&Class::Method` compiles the slot in from the SDK's header and needs a
rebuild when the slot moves. Before writing one at all, check whether the
engine call is already an [`ISource2ModListener` callback](/docs/events/listeners#engine-callbacks)
(`OnGameFrame`, `OnClientPutInServer`, `OnClientDisconnect`, …).
When the method is overloaded, the member function pointer has to be
disambiguated with a `static_cast` to the exact signature. `PostEventAbstract`
is the usual example — this is how the AntiCheat plugin hooks it:
```cpp title="Listeners.h"
KHook::Return OnPostEventAbstract(IGameEventSystem* pThis, CSplitScreenSlot slot, bool bLocalOnly, int nClientCount, const uint64* clients, INetworkMessageInternal* event, const CNetMessage* data, unsigned long size, NetChannelBufType_t bufType);
// PostEventAbstract is overloaded, so the member function pointer has
// to be disambiguated explicitly.
KHOOK_VIRTUAL(m_hPostEventAbstract, (static_cast(&IGameEventSystem::PostEventAbstract)), &globals::gameEventSystem, &CListeners::OnPostEventAbstract, nullptr);
```
Note the extra parentheses around the `static_cast`: it contains commas, and
without them the preprocessor splits it into several macro arguments.
A gamedata offset that is missing reads as `-1`, and a hook configured at
`-1` lands one slot **before** the vtable — on the typeinfo pointer, which
corrupts every later by-name vtable lookup on that class. The wrapper refuses
a negative index and logs the key instead of installing the hook.
What it attaches to [#what-it-attaches-to]
The **target** says what the hook is attached to:
| target | hooks | notes |
| ---------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------- |
| `&pInstance` | that one object | a pointer to the instance pointer, read at `KHOOK_INIT()` |
| `KHOOK_VTABLE(module, class)` | every object sharing the class's vtable | the vtable is found by RTTI name |
| `KHOOK_VTABLE_BASE(module, class, base)` | every object, through the vtable of a base further in | see [Secondary vtables](#secondary-vtables) |
| `nullptr` | nothing yet | you attach it yourself with `Init(pInstance)` or `InitGlobal(vtable)` |
One instance [#one-instance]
`&pInstance` is a pointer to the variable holding the instance
(`&g_pSource2Server`). It is read at `KHOOK_INIT()`, not when the hook is
constructed, so the global may still be null at that point — it only has to be
set by the time `KHOOK_INIT()` runs. The variable may be of a type derived
from the hooked class — an `ISource2Server*` for a method declared on its base
`IServerGameDLL`, say.
The hook fires for **that object only**: KHook patches the vtable the object
points at and checks `this` on every call.
```cpp title="plugin.h"
KHook::Return Hook_ClientCommand(ISource2GameClients* pThis, CPlayerSlot slot, const CCommand& args);
KHOOK_VIRTUAL(m_hClientCommand, &ISource2GameClients::ClientCommand, &g_pSource2GameClients, &Plugin::Hook_ClientCommand, nullptr);
```
A whole vtable [#a-whole-vtable]
`KHOOK_VTABLE(module, class)` looks a vtable up by its RTTI class name and hooks
it for every object that uses it — every client, every player controller.
`pThis` tells you which one was called.
The module is either a name (`"server"`, `"engine2"`, `"tier0"`…), loaded
through `ISource2ModModule::New` at init, or a pointer to an `ISource2ModModule*` your
plugin fills in during `Load()` (`&modules::server`), read at init as well.
```cpp title="plugin.h"
KHook::Return Hook_SendNetMessage(CServerSideClientBase* pThis, const CNetMessage* pData, NetChannelBufType_t bufType);
KHook::Return Hook_Respawn(CCSPlayerController* pThis);
// Every client: CServerSideClient's vtable in engine2.
KHOOK_VIRTUAL(m_hSendNetMessage, &CServerSideClientBase::SendNetMessage, KHOOK_VTABLE("engine2", "CServerSideClient"), &Plugin::Hook_SendNetMessage, nullptr);
// Every controller, with the slot from gamedata.
KHOOK_VIRTUAL(m_hRespawn, "CCSPlayerController::Respawn", KHOOK_VTABLE("server", "CCSPlayerController"), &Plugin::Hook_Respawn, nullptr);
```
A real one from FUNPLAY's plugins, with the module held by the plugin:
```cpp title="Listeners.h"
KHook::Return OnDropWeapon(CCSPlayer_WeaponServices* pThis, CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity);
KHOOK_VIRTUAL(m_hDropWeapon, "CCSPlayer_WeaponServices::DropWeapon", KHOOK_VTABLE(&modules::server, "CCSPlayer_WeaponServices"), nullptr, &CListeners::OnDropWeapon);
```
A vtable hook only sees calls that **go through the vtable**. Inside one
library the compiler often knows the exact class and calls the function
directly, and such a call never reaches your hook. Calls from another
library — the engine calling into `libserver`, say — always go through the
vtable. If a virtual hook stays silent, look at how the function is actually
called before blaming the index — or hook the implementation by signature
with [KHOOK\_MEMBER](/docs/hooks/function-hooks).
Secondary vtables [#secondary-vtables]
A class with several bases has several vtables: the **primary** one, shared
with the first base, and one more for every base further in. `KHOOK_VTABLE`
finds the primary one. For an interface that is not the first base, name the
base too — `KHOOK_VTABLE_BASE(module, class, base)` reads where the base sits
out of the RTTI and hooks that table:
```cpp title="Listeners.h"
// IHLTVDirector is CCSHLTVDirector's third base.
KHOOK_VIRTUAL(m_hHLTVRecordingStarted, &IHLTVDirector::OnRecordingStarted, KHOOK_VTABLE_BASE(&modules::server, "CCSHLTVDirector", "IHLTVDirector"), nullptr, &CListeners::OnHLTVRecordingStarted);
```
The same lookups are on `ISource2ModModule` for your own use:
`GetVirtualTableByBase(name, baseName)`, and
`GetVirtualTableByOffset(name, offset)` when you know the byte offset instead.
An offset moves whenever the class is laid out anew, so prefer the base. Only
non-virtual bases can be looked up.
Two other ways around it:
* a **function hook** on the implementation, by signature — that is how the
s2m hooks `FilterMessage`;
* a **virtual hook on the instance**, when you can get the interface pointer
itself. That pointer already points at the secondary table, and `&pInstance`
hooks exactly the table the object uses.
Attaching by hand [#attaching-by-hand]
A hook declared with a `nullptr` target is not touched by `KHOOK_INIT()`. You
attach it when you have the target:
```cpp
m_hX.Init(pInstance); // one object
m_hX.InitGlobal(vtable); // an ISource2ModMemory holding a vtable
m_hX.InitGlobal(pModule, "CCSPlayerController"); // by RTTI name
m_hX.InitGlobal("server", "CCSHLTVDirector", "IHLTVDirector"); // by name and base
```
Each returns `false` (and logs) when the target could not be resolved. The
rest of the lifecycle — `Init()` for hooks created after `Load()`,
`Destruct()`, `IsInstalled()` — is the same for every hook kind and is covered
in [Function hooks](/docs/hooks/function-hooks#hooks-installed-later).
Calling the original [#calling-the-original]
The wrapper has `CallOriginal(pThis, args...)`, which runs the function past
every hook on it, and forwards `->` to the `KHook::Virtual` underneath:
```cpp
m_hClientCommand.CallOriginal(pThis, slot, args);
```
See [Writing handlers](/docs/hooks/handlers#calling-the-original) for when and
how to use it.
# ISource2ModAddresses (/docs/core-api/ISource2ModAddresses)
Exposes pre-resolved function pointers for common engine operations. Module inspection, pattern scanning, symbol lookup, and pointer arithmetic are available via ISource2ModModule and ISource2ModMemory.
Provides access to pre-resolved engine function pointers.
For module inspection, pattern scanning, and pointer arithmetic use ISource2ModModule and ISource2ModMemory instead.
**Interface id** `ISource2ModAddresses001` (`SOURCE2MOD_ADDRESSES_INTERFACE`) · **Global** `g_pSource2ModAddresses` · **Header** [`source2mod/ISource2ModAddresses.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModAddresses.h)
Types [#types]
`CBaseEntity_CreateEntityByName_t` [#cbaseentity_createentitybyname_t]
```cpp
using CBaseEntity_CreateEntityByName_t = CBaseEntity* (FASTCALL*)(const char*, int);
```
Creates an entity by classname.
`CBaseEntity_DispatchSpawn_t` [#cbaseentity_dispatchspawn_t]
```cpp
using CBaseEntity_DispatchSpawn_t = void (FASTCALL*)(CBaseEntity*, CEntityKeyValues*);
```
Dispatches spawn on an entity.
`CBaseEntity_TakeDamageOld_t` [#cbaseentity_takedamageold_t]
```cpp
using CBaseEntity_TakeDamageOld_t = int64_t (FASTCALL*)(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult);
```
CBaseEntity::TakeDamageOld -- the game's own asserts name it so.
`CBaseModelEntity_SetModel_t` [#cbasemodelentity_setmodel_t]
```cpp
using CBaseModelEntity_SetModel_t = void (FASTCALL*)(CBaseModelEntity*, const char*);
```
Sets model on entity.
`CBasePlayerController_SetPawn_t` [#cbaseplayercontroller_setpawn_t]
```cpp
using CBasePlayerController_SetPawn_t = void (FASTCALL*)(CBasePlayerController*, CBasePlayerPawn*, bool, bool, bool, bool);
```
Assigns pawn to player controller.
`CBasePlayerPawn_SnapViewAngles_t` [#cbaseplayerpawn_snapviewangles_t]
```cpp
using CBasePlayerPawn_SnapViewAngles_t = void (FASTCALL*)(CBasePlayerPawn*, QAngle*);
```
Snaps a pawn's view angles, as the engine does on teleport.
`CCSGameRules_TerminateRound_t` [#ccsgamerules_terminateround_t]
```cpp
using CCSGameRules_TerminateRound_t = void (FASTCALL*)(CCSGameRules* pThis, float flDelay, uint32 nReason, uint32* pnTeamId);
using CCSGameRules_TerminateRound_t = void (FASTCALL*)(CCSGameRules* pThis, uint32 nReason, uint32* pnTeamId, float flDelay);
```
CCSGameRules::TerminateRound
The delay and the reason swap places between platforms, and the winning team goes in as a pointer -- null when there is none, which is the ordinary case. SwiftlyS2 splits it the same way; getting this wrong on Linux means the game reads the reason as the delay.
Declared differently per platform (
`#ifdef _WIN32`
); the first line is the Windows form, the second the Linux one.
`CPlayer_WeaponServices_Destroy_t` [#cplayer_weaponservices_destroy_t]
```cpp
using CPlayer_WeaponServices_Destroy_t = void (FASTCALL*)(CPlayer_WeaponServices*, CBasePlayerWeapon*);
```
Destroys weapon from player pawn.
`LegacyGameEventListener_t` [#legacygameeventlistener_t]
```cpp
using LegacyGameEventListener_t = IGameEventListener2* (FASTCALL*)(CPlayerSlot);
```
Retrieves legacy game event listener.
`CCSPlayerController_SwitchTeam_t` [#ccsplayercontroller_switchteam_t]
```cpp
using CCSPlayerController_SwitchTeam_t = void (FASTCALL*)(CCSPlayerController*, int nTeam);
```
Switches player team. The team is read as a full int.
`CEntityInstance_AcceptInput_t` [#centityinstance_acceptinput_t]
```cpp
using CEntityInstance_AcceptInput_t = bool (FASTCALL*)(CEntityInstance*, const char*, CEntityInstance*, CEntityInstance*, const variant_t&);
```
Accepts entity input. Interns the name and forwards to CEntityIdentity::AcceptInput, returning its result.
`CEntityIOOutput_FireOutputInternal_t` [#centityiooutput_fireoutputinternal_t]
```cpp
using CEntityIOOutput_FireOutputInternal_t = void (FASTCALL*)(CEntityIOOutput*, CEntityInstance*, CEntityInstance*, void*, float, void*, void*);
```
Fires entity output internally.
`CEntitySystem_AddEntityIOEvent_t` [#centitysystem_addentityioevent_t]
```cpp
using CEntitySystem_AddEntityIOEvent_t = void (FASTCALL*)(CEntitySystem*, CEntityInstance*, const char*, CEntityInstance*, CEntityInstance*, const variant_t&, float, void*, void*);
```
Adds entity IO event.
`CGameEntitySystem_FindEntityByClassName_t` [#cgameentitysystem_findentitybyclassname_t]
```cpp
using CGameEntitySystem_FindEntityByClassName_t = CBaseEntity* (FASTCALL*)(CEntitySystem*, CEntityInstance*, const char*);
```
Finds entity by classname.
`CGameEntitySystem_FindEntityByName_t` [#cgameentitysystem_findentitybyname_t]
```cpp
using CGameEntitySystem_FindEntityByName_t = CBaseEntity* (FASTCALL*)(CEntitySystem*, CEntityInstance*, const char*, CEntityInstance*, CEntityInstance*, CEntityInstance*, IEntityFindFilter*);
```
Finds entity by name.
`CTakeDamageInfo_CTakeDamageInfo_t` [#ctakedamageinfo_ctakedamageinfo_t]
```cpp
using CTakeDamageInfo_CTakeDamageInfo_t = void (FASTCALL*)(CTakeDamageInfo* pThis, CBaseEntity* pInflictor, CBaseEntity* pAttacker, CBaseEntity* pAbility, const Vector* vecDamageForce, const Vector* vecDamagePosition, float flDamage, int bitsDamageType, int iCustomDamage, void* a10);
```
Constructs CTakeDamageInfo with valid members.
`INetworkMessageProcessingPreFilter_FilterMessage_t` [#inetworkmessageprocessingprefilter_filtermessage_t]
```cpp
using INetworkMessageProcessingPreFilter_FilterMessage_t = bool (THISCALL*)(INetworkMessageProcessingPreFilter* pThis, const CNetMessage* pData, INetChannel* pChannel);
```
`CEntityIdentity_AcceptInput_t` [#centityidentity_acceptinput_t]
```cpp
using CEntityIdentity_AcceptInput_t = bool (FASTCALL*)(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* pValue, void* a6, void* a7);
```
CEntityIdentity::AcceptInput -- the identity-side entry point.
Takes the input name already interned: CEntityInstance::AcceptInput looks it up and passes a pointer to the symbol, not the string. a6/a7 point at objects the caller builds on its stack.
`CCSPlayer_ItemServices_CanAcquire_t` [#ccsplayer_itemservices_canacquire_t]
```cpp
using CCSPlayer_ItemServices_CanAcquire_t = int64_t (FASTCALL*)(CCSPlayer_ItemServices* pThis, CEconItemView* pItem, int nAcquireMethod, void* a4);
```
CCSPlayer\_ItemServices::CanAcquire -- returns an AcquireResult.
`CCSPlayerPawnBase_CanMove_t` [#ccsplayerpawnbase_canmove_t]
```cpp
using CCSPlayerPawnBase_CanMove_t = bool (FASTCALL*)(CCSPlayerPawnBase* pThis);
```
CCSPlayerPawnBase::CanMove -- a CCSPlayerPawnBase virtual; false while frozen, defusing, etc.
`CCSPlayerController_ProcessUserCmd_t` [#ccsplayercontroller_processusercmd_t]
```cpp
using CCSPlayerController_ProcessUserCmd_t = void* (FASTCALL*)(CCSPlayerController* pThis, void* pUserCmds, int nNumCmds, bool bPaused, float flMargin);
```
CCSPlayerController::ProcessUserCmd
`CBasePlayerController_OnSimulateUserCommands_t` [#cbaseplayercontroller_onsimulateusercommands_t]
```cpp
using CBasePlayerController_OnSimulateUserCommands_t = void (FASTCALL*)(CBasePlayerController* pThis);
```
CBasePlayerController::OnSimulateUserCommands
`CCSPlayer_MovementServices_AirAccelerate_t` [#ccsplayer_movementservices_airaccelerate_t]
```cpp
using CCSPlayer_MovementServices_AirAccelerate_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, Vector* pWishDirection, float flWishSpeed, float flAcceleration);
```
`CCSPlayer_MovementServices_AirMove_t` [#ccsplayer_movementservices_airmove_t]
```cpp
using CCSPlayer_MovementServices_AirMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_CanUnduck_t` [#ccsplayer_movementservices_canunduck_t]
```cpp
using CCSPlayer_MovementServices_CanUnduck_t = bool (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_CategorizePosition_t` [#ccsplayer_movementservices_categorizeposition_t]
```cpp
using CCSPlayer_MovementServices_CategorizePosition_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, bool bStayOnGround);
```
`CCSPlayer_MovementServices_CheckFalling_t` [#ccsplayer_movementservices_checkfalling_t]
```cpp
using CCSPlayer_MovementServices_CheckFalling_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_CheckParameters_t` [#ccsplayer_movementservices_checkparameters_t]
```cpp
using CCSPlayer_MovementServices_CheckParameters_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_CheckVelocity_t` [#ccsplayer_movementservices_checkvelocity_t]
```cpp
using CCSPlayer_MovementServices_CheckVelocity_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, void* a3);
```
`CCSPlayer_MovementServices_CheckWater_t` [#ccsplayer_movementservices_checkwater_t]
```cpp
using CCSPlayer_MovementServices_CheckWater_t = bool (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_Duck_t` [#ccsplayer_movementservices_duck_t]
```cpp
using CCSPlayer_MovementServices_Duck_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_Friction_t` [#ccsplayer_movementservices_friction_t]
```cpp
using CCSPlayer_MovementServices_Friction_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_FullWalkMove_t` [#ccsplayer_movementservices_fullwalkmove_t]
```cpp
using CCSPlayer_MovementServices_FullWalkMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, bool bGround);
```
`CCSPlayer_MovementServices_LadderMove_t` [#ccsplayer_movementservices_laddermove_t]
```cpp
using CCSPlayer_MovementServices_LadderMove_t = bool (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_MoveInit_t` [#ccsplayer_movementservices_moveinit_t]
```cpp
using CCSPlayer_MovementServices_MoveInit_t = bool (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_PlayerMove_t` [#ccsplayer_movementservices_playermove_t]
```cpp
using CCSPlayer_MovementServices_PlayerMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_ProcessMovement_t` [#ccsplayer_movementservices_processmovement_t]
```cpp
using CCSPlayer_MovementServices_ProcessMovement_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_SetupMove_t` [#ccsplayer_movementservices_setupmove_t]
```cpp
using CCSPlayer_MovementServices_SetupMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CUserCmd* pCmd, CMoveData* pMove);
```
`CCSPlayer_MovementServices_TryPlayerMove_t` [#ccsplayer_movementservices_tryplayermove_t]
```cpp
using CCSPlayer_MovementServices_TryPlayerMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, Vector* pFirstDest, CGameTrace* pFirstTrace, bool* pbIsSurfing);
```
`CCSPlayer_MovementServices_WalkMove_t` [#ccsplayer_movementservices_walkmove_t]
```cpp
using CCSPlayer_MovementServices_WalkMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_WaterMove_t` [#ccsplayer_movementservices_watermove_t]
```cpp
using CCSPlayer_MovementServices_WaterMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove);
```
`CCSPlayer_MovementServices_GroundAccelerate_t` [#ccsplayer_movementservices_groundaccelerate_t]
```cpp
using CCSPlayer_MovementServices_GroundAccelerate_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, float flFrameTime, Vector* pWishDirection, float flWishSpeed, float flAcceleration);
using CCSPlayer_MovementServices_GroundAccelerate_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, Vector* pWishDirection, float flFrameTime, float flWishSpeed, float flAcceleration);
```
CCSPlayer\_MovementServices::GroundAccelerate
The wish direction and the frame time swap places between platforms.
Declared differently per platform (
`#ifdef _WIN32`
); the first line is the Windows form, the second the Linux one.
`CCSPlayerLegacyJump_OnJump_t` [#ccsplayerlegacyjump_onjump_t]
```cpp
using CCSPlayerLegacyJump_OnJump_t = void (FASTCALL*)(CCSPlayerLegacyJump* pThis, CMoveData* pMove);
```
Members of the jump components, not of the movement services: the movement services call them on m\_LegacyJump / m\_ModernJump.
Which pair is live depends on the sv\_jump\_impulse-era cvars; the modern and legacy paths are separate functions in the binary.
`CCSPlayerModernJump_OnJump_t` [#ccsplayermodernjump_onjump_t]
```cpp
using CCSPlayerModernJump_OnJump_t = void (FASTCALL*)(CCSPlayerModernJump* pThis, CMoveData* pMove);
```
`CCSPlayerLegacyJump_CheckJumpButton_t` [#ccsplayerlegacyjump_checkjumpbutton_t]
```cpp
using CCSPlayerLegacyJump_CheckJumpButton_t = void (FASTCALL*)(CCSPlayerLegacyJump* pThis, CMoveData* pMove);
```
`CCSPlayerModernJump_CheckJumpButton_t` [#ccsplayermodernjump_checkjumpbutton_t]
```cpp
using CCSPlayerModernJump_CheckJumpButton_t = void (FASTCALL*)(CCSPlayerModernJump* pThis, CMoveData* pMove);
```
`CAttributeList_SetOrAddAttributeValueByName_t` [#cattributelist_setoraddattributevaluebyname_t]
```cpp
using CAttributeList_SetOrAddAttributeValueByName_t = void (FASTCALL*)(CAttributeList* pThis, const char* pszAttributeName, float flValue);
```
CAttributeList::SetOrAddAttributeValueByName
`CDecoyProjectile_EmitGrenade_t` [#cdecoyprojectile_emitgrenade_t]
```cpp
using CDecoyProjectile_EmitGrenade_t = CDecoyProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex);
```
C\Projectile::EmitGrenade -- spawns a live grenade.
The fourth argument (pAngVelocity) is never read on either platform -- every variant overwrites that register before touching it -- but it keeps its slot, so pass anything (the helpers pass the velocity again). Smoke takes one extra int the others do not have.
`CFlashbangProjectile_EmitGrenade_t` [#cflashbangprojectile_emitgrenade_t]
```cpp
using CFlashbangProjectile_EmitGrenade_t = CFlashbangProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex);
```
`CHEGrenadeProjectile_EmitGrenade_t` [#chegrenadeprojectile_emitgrenade_t]
```cpp
using CHEGrenadeProjectile_EmitGrenade_t = CHEGrenadeProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex);
```
`CMolotovProjectile_EmitGrenade_t` [#cmolotovprojectile_emitgrenade_t]
```cpp
using CMolotovProjectile_EmitGrenade_t = CMolotovProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex);
```
`CSmokeGrenadeProjectile_EmitGrenade_t` [#csmokegrenadeprojectile_emitgrenade_t]
```cpp
using CSmokeGrenadeProjectile_EmitGrenade_t = CSmokeGrenadeProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex, int a7);
```
`CCSPlayer_ItemServices_GiveNamedItem_t` [#ccsplayer_itemservices_givenameditem_t]
```cpp
using CCSPlayer_ItemServices_GiveNamedItem_t = CBasePlayerWeapon* (FASTCALL*)(CCSPlayer_ItemServices* pThis, const char* pszItem, int nSubType, CEconItemView* pScriptItem, bool bForce, void* a6);
```
CCSPlayer\_ItemServices::GiveNamedItem -- the full implementation.
The GiveNamedItem vtable slots are thunks into this one: the plain one passes (name, 0, nullptr, false, nullptr), the bool-returning one forwards its own bool as bForce and tests the result. Hooking this catches every give, virtual or not.
`CCSPlayerPawn_PostThink_t` [#ccsplayerpawn_postthink_t]
```cpp
using CCSPlayerPawn_PostThink_t = void (FASTCALL*)(CCSPlayerPawn* pThis);
```
CCSPlayerPawn::PostThink -- virtual, takes nothing but the pawn.
`UTIL_Remove_t` [#util_remove_t]
```cpp
using UTIL_Remove_t = void (FASTCALL*)(CEntityInstance* pEntity);
```
UTIL\_Remove -- queues the entity for deletion; null is ignored.
`DispatchParticleEffect_t` [#dispatchparticleeffect_t]
```cpp
using DispatchParticleEffect_t = void (FASTCALL*)(const char* pszParticleName, int nAttachType, CBaseEntity* pEntity, uint8 nAttachmentPoint, uint32 nAttachmentNameToken, bool bResetAllParticlesOnEntity, int nSplitScreenPlayerSlot, IRecipientFilter* pFilter, uint8 nFlags);
```
DispatchParticleEffect
The attachment name is a 32-bit string token (it arrives in r8d), not the CUtlSymbolLarge older ports declare. A null filter sends to everyone. The last byte seeds the effect's flag byte; pass 0.
`GetWeaponCSDataFromKey_t` [#getweaponcsdatafromkey_t]
```cpp
using GetWeaponCSDataFromKey_t = CCSWeaponBaseVData* (FASTCALL*)(int nWeaponType, const char* pszKey);
```
GetWeaponCSDataFromKey -- looks a weapon's VData up by its key ("weapon\_ak47"). nWeaponType -1 accepts any type; any other value returns null when the found data is of a different type.
`CSource2Server_GetNavMeshData_t` [#csource2server_getnavmeshdata_t]
```cpp
using CSource2Server_GetNavMeshData_t = bool (FASTCALL*)(ISource2Server* pThis, CNavData* pNavMeshData);
```
CSource2Server::GetNavMeshData -- ISource2Server vtable slot 32.
The game's implementation ignores the out argument and only reports whether a nav mesh is loaded (g\_pNavMesh != null); CCSNavArea reads that global out of its body.
`CLoggingSystem_LogDirect_t` [#cloggingsystem_logdirect_t]
```cpp
using CLoggingSystem_LogDirect_t = LoggingResponse_t (FASTCALL*)(CLoggingSystem* pThis, LoggingChannelID_t nChannelID, LoggingSeverity_t nSeverity, const LoggingRareOptions_t* pRareOptions, const LoggingMetaData_t* pMetaData, Color color, const char* pszMessage);
```
CLoggingSystem::LogDirect (tier0) -- the member behind LoggingSystem\_LogDirect.
`Cmd_ExecuteCommand_t` [#cmd_executecommand_t]
```cpp
using Cmd_ExecuteCommand_t = void (FASTCALL*)(void* pThis, int nTarget, CPlayerSlot nSlot, ConCommandRef hCommand, const CCommand* pArgs);
```
Cmd\_ExecuteCommand (engine2) -- runs one parsed command.
The first argument is the engine's command-buffer owner, not a CCommand: the deferred-command loop passes itself there. The target is a CommandTarget\_t and the command goes in by value, as ICvar::DispatchConCommand takes it.
Methods [#methods]
`CBaseEntity_CreateEntityByName` [#cbaseentity_createentitybyname]
```cpp
virtual CBaseEntity_CreateEntityByName_t CBaseEntity_CreateEntityByName() = 0;
```
Creates an entity by classname.
`CBaseEntity_DispatchSpawn` [#cbaseentity_dispatchspawn]
```cpp
virtual CBaseEntity_DispatchSpawn_t CBaseEntity_DispatchSpawn() = 0;
```
Dispatches spawn on an entity.
`CBaseEntity_TakeDamageOld` [#cbaseentity_takedamageold]
```cpp
virtual CBaseEntity_TakeDamageOld_t CBaseEntity_TakeDamageOld() = 0;
```
CBaseEntity::TakeDamageOld -- the game's own asserts name it so.
`CBaseModelEntity_SetModel` [#cbasemodelentity_setmodel]
```cpp
virtual CBaseModelEntity_SetModel_t CBaseModelEntity_SetModel() = 0;
```
Sets model on entity.
`CBasePlayerController_SetPawn` [#cbaseplayercontroller_setpawn]
```cpp
virtual CBasePlayerController_SetPawn_t CBasePlayerController_SetPawn() = 0;
```
Assigns pawn to player controller.
`CBasePlayerPawn_SnapViewAngles` [#cbaseplayerpawn_snapviewangles]
```cpp
virtual CBasePlayerPawn_SnapViewAngles_t CBasePlayerPawn_SnapViewAngles() = 0;
```
Snaps a pawn's view angles, as the engine does on teleport.
`CCSGameRules_TerminateRound` [#ccsgamerules_terminateround]
```cpp
virtual CCSGameRules_TerminateRound_t CCSGameRules_TerminateRound() = 0;
```
CCSGameRules::TerminateRound
`CPlayer_WeaponServices_Destroy` [#cplayer_weaponservices_destroy]
```cpp
virtual CPlayer_WeaponServices_Destroy_t CPlayer_WeaponServices_Destroy() = 0;
```
Destroys weapon from player pawn.
`LegacyGameEventListener` [#legacygameeventlistener]
```cpp
virtual LegacyGameEventListener_t LegacyGameEventListener() = 0;
```
Retrieves legacy game event listener.
`CCSPlayerController_SwitchTeam` [#ccsplayercontroller_switchteam]
```cpp
virtual CCSPlayerController_SwitchTeam_t CCSPlayerController_SwitchTeam() = 0;
```
Switches player team. The team is read as a full int.
`CEntityInstance_AcceptInput` [#centityinstance_acceptinput]
```cpp
virtual CEntityInstance_AcceptInput_t CEntityInstance_AcceptInput() = 0;
```
Accepts entity input. Interns the name and forwards to CEntityIdentity::AcceptInput, returning its result.
`CEntityIOOutput_FireOutputInternal` [#centityiooutput_fireoutputinternal]
```cpp
virtual CEntityIOOutput_FireOutputInternal_t CEntityIOOutput_FireOutputInternal() = 0;
```
Fires entity output internally.
`CEntitySystem_AddEntityIOEvent` [#centitysystem_addentityioevent]
```cpp
virtual CEntitySystem_AddEntityIOEvent_t CEntitySystem_AddEntityIOEvent() = 0;
```
Adds entity IO event.
`CGameEntitySystem_FindEntityByClassName` [#cgameentitysystem_findentitybyclassname]
```cpp
virtual CGameEntitySystem_FindEntityByClassName_t CGameEntitySystem_FindEntityByClassName() = 0;
```
Finds entity by classname.
`CGameEntitySystem_FindEntityByName` [#cgameentitysystem_findentitybyname]
```cpp
virtual CGameEntitySystem_FindEntityByName_t CGameEntitySystem_FindEntityByName() = 0;
```
Finds entity by name.
`CTakeDamageInfo_CTakeDamageInfo` [#ctakedamageinfo_ctakedamageinfo]
```cpp
virtual CTakeDamageInfo_CTakeDamageInfo_t CTakeDamageInfo_CTakeDamageInfo() = 0;
```
Constructs CTakeDamageInfo with valid members.
`INetworkMessageProcessingPreFilter_FilterMessage` [#inetworkmessageprocessingprefilter_filtermessage]
```cpp
virtual INetworkMessageProcessingPreFilter_FilterMessage_t INetworkMessageProcessingPreFilter_FilterMessage() = 0;
```
`CEntityIdentity_AcceptInput` [#centityidentity_acceptinput]
```cpp
virtual CEntityIdentity_AcceptInput_t CEntityIdentity_AcceptInput() = 0;
```
CEntityIdentity::AcceptInput -- the identity-side entry point.
`CCSPlayer_ItemServices_CanAcquire` [#ccsplayer_itemservices_canacquire]
```cpp
virtual CCSPlayer_ItemServices_CanAcquire_t CCSPlayer_ItemServices_CanAcquire() = 0;
```
CCSPlayer\_ItemServices::CanAcquire -- returns an AcquireResult.
`CCSPlayerPawnBase_CanMove` [#ccsplayerpawnbase_canmove]
```cpp
virtual CCSPlayerPawnBase_CanMove_t CCSPlayerPawnBase_CanMove() = 0;
```
CCSPlayerPawnBase::CanMove -- a CCSPlayerPawnBase virtual; false while frozen, defusing, etc.
`CCSPlayerController_ProcessUserCmd` [#ccsplayercontroller_processusercmd]
```cpp
virtual CCSPlayerController_ProcessUserCmd_t CCSPlayerController_ProcessUserCmd() = 0;
```
CCSPlayerController::ProcessUserCmd
`CBasePlayerController_OnSimulateUserCommands` [#cbaseplayercontroller_onsimulateusercommands]
```cpp
virtual CBasePlayerController_OnSimulateUserCommands_t CBasePlayerController_OnSimulateUserCommands() = 0;
```
CBasePlayerController::OnSimulateUserCommands
`CCSPlayer_MovementServices_AirAccelerate` [#ccsplayer_movementservices_airaccelerate]
```cpp
virtual CCSPlayer_MovementServices_AirAccelerate_t CCSPlayer_MovementServices_AirAccelerate() = 0;
```
`CCSPlayer_MovementServices_AirMove` [#ccsplayer_movementservices_airmove]
```cpp
virtual CCSPlayer_MovementServices_AirMove_t CCSPlayer_MovementServices_AirMove() = 0;
```
`CCSPlayer_MovementServices_CanUnduck` [#ccsplayer_movementservices_canunduck]
```cpp
virtual CCSPlayer_MovementServices_CanUnduck_t CCSPlayer_MovementServices_CanUnduck() = 0;
```
`CCSPlayer_MovementServices_CategorizePosition` [#ccsplayer_movementservices_categorizeposition]
```cpp
virtual CCSPlayer_MovementServices_CategorizePosition_t CCSPlayer_MovementServices_CategorizePosition() = 0;
```
`CCSPlayer_MovementServices_CheckFalling` [#ccsplayer_movementservices_checkfalling]
```cpp
virtual CCSPlayer_MovementServices_CheckFalling_t CCSPlayer_MovementServices_CheckFalling() = 0;
```
`CCSPlayer_MovementServices_CheckParameters` [#ccsplayer_movementservices_checkparameters]
```cpp
virtual CCSPlayer_MovementServices_CheckParameters_t CCSPlayer_MovementServices_CheckParameters() = 0;
```
`CCSPlayer_MovementServices_CheckVelocity` [#ccsplayer_movementservices_checkvelocity]
```cpp
virtual CCSPlayer_MovementServices_CheckVelocity_t CCSPlayer_MovementServices_CheckVelocity() = 0;
```
`CCSPlayer_MovementServices_CheckWater` [#ccsplayer_movementservices_checkwater]
```cpp
virtual CCSPlayer_MovementServices_CheckWater_t CCSPlayer_MovementServices_CheckWater() = 0;
```
`CCSPlayer_MovementServices_Duck` [#ccsplayer_movementservices_duck]
```cpp
virtual CCSPlayer_MovementServices_Duck_t CCSPlayer_MovementServices_Duck() = 0;
```
`CCSPlayer_MovementServices_Friction` [#ccsplayer_movementservices_friction]
```cpp
virtual CCSPlayer_MovementServices_Friction_t CCSPlayer_MovementServices_Friction() = 0;
```
`CCSPlayer_MovementServices_FullWalkMove` [#ccsplayer_movementservices_fullwalkmove]
```cpp
virtual CCSPlayer_MovementServices_FullWalkMove_t CCSPlayer_MovementServices_FullWalkMove() = 0;
```
`CCSPlayer_MovementServices_GroundAccelerate` [#ccsplayer_movementservices_groundaccelerate]
```cpp
virtual CCSPlayer_MovementServices_GroundAccelerate_t CCSPlayer_MovementServices_GroundAccelerate() = 0;
```
CCSPlayer\_MovementServices::GroundAccelerate
`CCSPlayer_MovementServices_LadderMove` [#ccsplayer_movementservices_laddermove]
```cpp
virtual CCSPlayer_MovementServices_LadderMove_t CCSPlayer_MovementServices_LadderMove() = 0;
```
`CCSPlayer_MovementServices_MoveInit` [#ccsplayer_movementservices_moveinit]
```cpp
virtual CCSPlayer_MovementServices_MoveInit_t CCSPlayer_MovementServices_MoveInit() = 0;
```
`CCSPlayer_MovementServices_PlayerMove` [#ccsplayer_movementservices_playermove]
```cpp
virtual CCSPlayer_MovementServices_PlayerMove_t CCSPlayer_MovementServices_PlayerMove() = 0;
```
`CCSPlayer_MovementServices_ProcessMovement` [#ccsplayer_movementservices_processmovement]
```cpp
virtual CCSPlayer_MovementServices_ProcessMovement_t CCSPlayer_MovementServices_ProcessMovement() = 0;
```
`CCSPlayer_MovementServices_SetupMove` [#ccsplayer_movementservices_setupmove]
```cpp
virtual CCSPlayer_MovementServices_SetupMove_t CCSPlayer_MovementServices_SetupMove() = 0;
```
`CCSPlayer_MovementServices_TryPlayerMove` [#ccsplayer_movementservices_tryplayermove]
```cpp
virtual CCSPlayer_MovementServices_TryPlayerMove_t CCSPlayer_MovementServices_TryPlayerMove() = 0;
```
`CCSPlayer_MovementServices_WalkMove` [#ccsplayer_movementservices_walkmove]
```cpp
virtual CCSPlayer_MovementServices_WalkMove_t CCSPlayer_MovementServices_WalkMove() = 0;
```
`CCSPlayer_MovementServices_WaterMove` [#ccsplayer_movementservices_watermove]
```cpp
virtual CCSPlayer_MovementServices_WaterMove_t CCSPlayer_MovementServices_WaterMove() = 0;
```
`CCSPlayerLegacyJump_OnJump` [#ccsplayerlegacyjump_onjump]
```cpp
virtual CCSPlayerLegacyJump_OnJump_t CCSPlayerLegacyJump_OnJump() = 0;
```
Members of the jump components, not of the movement services: the movement services call them on m\_LegacyJump / m\_ModernJump.
`CCSPlayerModernJump_OnJump` [#ccsplayermodernjump_onjump]
```cpp
virtual CCSPlayerModernJump_OnJump_t CCSPlayerModernJump_OnJump() = 0;
```
`CCSPlayerLegacyJump_CheckJumpButton` [#ccsplayerlegacyjump_checkjumpbutton]
```cpp
virtual CCSPlayerLegacyJump_CheckJumpButton_t CCSPlayerLegacyJump_CheckJumpButton() = 0;
```
`CCSPlayerModernJump_CheckJumpButton` [#ccsplayermodernjump_checkjumpbutton]
```cpp
virtual CCSPlayerModernJump_CheckJumpButton_t CCSPlayerModernJump_CheckJumpButton() = 0;
```
`CAttributeList_SetOrAddAttributeValueByName` [#cattributelist_setoraddattributevaluebyname]
```cpp
virtual CAttributeList_SetOrAddAttributeValueByName_t CAttributeList_SetOrAddAttributeValueByName() = 0;
```
CAttributeList::SetOrAddAttributeValueByName
`CDecoyProjectile_EmitGrenade` [#cdecoyprojectile_emitgrenade]
```cpp
virtual CDecoyProjectile_EmitGrenade_t CDecoyProjectile_EmitGrenade() = 0;
```
C\Projectile::EmitGrenade -- spawns a live grenade.
`CFlashbangProjectile_EmitGrenade` [#cflashbangprojectile_emitgrenade]
```cpp
virtual CFlashbangProjectile_EmitGrenade_t CFlashbangProjectile_EmitGrenade() = 0;
```
`CHEGrenadeProjectile_EmitGrenade` [#chegrenadeprojectile_emitgrenade]
```cpp
virtual CHEGrenadeProjectile_EmitGrenade_t CHEGrenadeProjectile_EmitGrenade() = 0;
```
`CMolotovProjectile_EmitGrenade` [#cmolotovprojectile_emitgrenade]
```cpp
virtual CMolotovProjectile_EmitGrenade_t CMolotovProjectile_EmitGrenade() = 0;
```
`CSmokeGrenadeProjectile_EmitGrenade` [#csmokegrenadeprojectile_emitgrenade]
```cpp
virtual CSmokeGrenadeProjectile_EmitGrenade_t CSmokeGrenadeProjectile_EmitGrenade() = 0;
```
`CCSPlayerPawn_PostThink` [#ccsplayerpawn_postthink]
```cpp
virtual CCSPlayerPawn_PostThink_t CCSPlayerPawn_PostThink() = 0;
```
CCSPlayerPawn::PostThink -- virtual, takes nothing but the pawn.
`UTIL_Remove` [#util_remove]
```cpp
virtual UTIL_Remove_t UTIL_Remove() = 0;
```
UTIL\_Remove -- queues the entity for deletion; null is ignored.
`DispatchParticleEffect` [#dispatchparticleeffect]
```cpp
virtual DispatchParticleEffect_t DispatchParticleEffect() = 0;
```
DispatchParticleEffect
`GetWeaponCSDataFromKey` [#getweaponcsdatafromkey]
```cpp
virtual GetWeaponCSDataFromKey_t GetWeaponCSDataFromKey() = 0;
```
GetWeaponCSDataFromKey -- looks a weapon's VData up by its key ("weapon\_ak47"). nWeaponType -1 accepts any type; any other value returns null when the found data is of a different type.
`CSource2Server_GetNavMeshData` [#csource2server_getnavmeshdata]
```cpp
virtual CSource2Server_GetNavMeshData_t CSource2Server_GetNavMeshData() = 0;
```
CSource2Server::GetNavMeshData -- ISource2Server vtable slot 32.
`CLoggingSystem_LogDirect` [#cloggingsystem_logdirect]
```cpp
virtual CLoggingSystem_LogDirect_t CLoggingSystem_LogDirect() = 0;
```
CLoggingSystem::LogDirect (tier0) -- the member behind LoggingSystem\_LogDirect.
`Cmd_ExecuteCommand` [#cmd_executecommand]
```cpp
virtual Cmd_ExecuteCommand_t Cmd_ExecuteCommand() = 0;
```
Cmd\_ExecuteCommand (engine2) -- runs one parsed command.
`CCSPlayer_ItemServices_GiveNamedItem` [#ccsplayer_itemservices_givenameditem]
```cpp
virtual CCSPlayer_ItemServices_GiveNamedItem_t CCSPlayer_ItemServices_GiveNamedItem() = 0;
```
CCSPlayer\_ItemServices::GiveNamedItem -- the full implementation.
Macros [#macros]
`SOURCE2MOD_ORIGINAL` [#source2mod_original]
```cpp
#define SOURCE2MOD_ORIGINAL(fn) reinterpret_cast(KHook::FindOriginal(reinterpret_cast(fn)))
```
Calls a function from this interface, bypassing any inline hooks on it.
The SDK's schema helpers do the same through their HookChain argument (ISource2ModTypes.h); this is for calling an ADDR\_\* function directly.
The ADDR\_\* macros below hand back the address the signature scan found, which is what you want to hook. It is not always what you want to *call*: once any plugin inline-hooks one of these functions, its first bytes are a jump into the detour, so calling it runs the whole Pre/Post chain. That is right when you are standing in for the game -- a plugin dealing damage as if a weapon fired wants the chain -- and wrong when you are asking for the function itself, e.g. an admin command, where another plugin's rules have no business vetoing what the server was told to do.
Wrap the call in this when you mean the latter:
```cpp
SOURCE2MOD_ORIGINAL(ADDR_TAKE_DAMAGE_OLD())(pPawn, &info, &result);
```
Signature-blind, so it works whatever prototype you hold and however the plugin that hooked it declared its hook -- the two routinely disagree, and TerminateRound is the standing example: the hook declares (CCSGameRules, RoundEndReason, float, void\*, uint8\_t) where this interface's typedef says (CCSGameRules\*, uint32, uint32\*, float). Unhooked functions come back unchanged (KHook::FindOriginal hands the address back as is), so this is always safe to apply.
Resolve it at the call, do not cache it in a global. The address is only the original from the moment the hook exists, and hooks are installed long after these addresses are scanned.
Other macros [#other-macros]
| Macro | Expands to |
| ------------------------------------ | ---------------------------------------------------------------------------- |
| `ADDR_CREATE_ENTITY_BY_NAME()` | `g_pSource2ModAddresses->CBaseEntity_CreateEntityByName()` |
| `ADDR_DISPATCH_SPAWN()` | `g_pSource2ModAddresses->CBaseEntity_DispatchSpawn()` |
| `ADDR_TAKE_DAMAGE_OLD()` | `g_pSource2ModAddresses->CBaseEntity_TakeDamageOld()` |
| `ADDR_SET_MODEL()` | `g_pSource2ModAddresses->CBaseModelEntity_SetModel()` |
| `ADDR_SET_PAWN()` | `g_pSource2ModAddresses->CBasePlayerController_SetPawn()` |
| `ADDR_SNAP_VIEW_ANGLES()` | `g_pSource2ModAddresses->CBasePlayerPawn_SnapViewAngles()` |
| `ADDR_TERMINATE_ROUND()` | `g_pSource2ModAddresses->CCSGameRules_TerminateRound()` |
| `ADDR_WEAPON_SERVICES_DESTROY()` | `g_pSource2ModAddresses->CPlayer_WeaponServices_Destroy()` |
| `ADDR_LEGACY_GAME_EVENT_LISTENER()` | `g_pSource2ModAddresses->LegacyGameEventListener()` |
| `ADDR_SWITCH_TEAM()` | `g_pSource2ModAddresses->CCSPlayerController_SwitchTeam()` |
| `ADDR_ACCEPT_INPUT()` | `g_pSource2ModAddresses->CEntityInstance_AcceptInput()` |
| `ADDR_FIRE_OUTPUT_INTERNAL()` | `g_pSource2ModAddresses->CEntityIOOutput_FireOutputInternal()` |
| `ADDR_ADD_ENTITY_IO_EVENT()` | `g_pSource2ModAddresses->CEntitySystem_AddEntityIOEvent()` |
| `ADDR_FIND_ENTITY_BY_CLASSNAME()` | `g_pSource2ModAddresses->CGameEntitySystem_FindEntityByClassName()` |
| `ADDR_FIND_ENTITY_BY_NAME()` | `g_pSource2ModAddresses->CGameEntitySystem_FindEntityByName()` |
| `ADDR_TAKE_DAMAGE_INFO()` | `g_pSource2ModAddresses->CTakeDamageInfo_CTakeDamageInfo()` |
| `ADDR_FILTER_MESSAGE()` | `g_pSource2ModAddresses->INetworkMessageProcessingPreFilter_FilterMessage()` |
| `ADDR_IDENTITY_ACCEPT_INPUT()` | `g_pSource2ModAddresses->CEntityIdentity_AcceptInput()` |
| `ADDR_CAN_ACQUIRE()` | `g_pSource2ModAddresses->CCSPlayer_ItemServices_CanAcquire()` |
| `ADDR_CAN_MOVE()` | `g_pSource2ModAddresses->CCSPlayerPawnBase_CanMove()` |
| `ADDR_PROCESS_USER_CMD()` | `g_pSource2ModAddresses->CCSPlayerController_ProcessUserCmd()` |
| `ADDR_ON_SIMULATE_USER_COMMANDS()` | `g_pSource2ModAddresses->CBasePlayerController_OnSimulateUserCommands()` |
| `ADDR_AIR_ACCELERATE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_AirAccelerate()` |
| `ADDR_AIR_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_AirMove()` |
| `ADDR_CAN_UNDUCK()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CanUnduck()` |
| `ADDR_CATEGORIZE_POSITION()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CategorizePosition()` |
| `ADDR_CHECK_FALLING()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CheckFalling()` |
| `ADDR_CHECK_PARAMETERS()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CheckParameters()` |
| `ADDR_CHECK_VELOCITY()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CheckVelocity()` |
| `ADDR_CHECK_WATER()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CheckWater()` |
| `ADDR_DUCK()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_Duck()` |
| `ADDR_FRICTION()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_Friction()` |
| `ADDR_FULL_WALK_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_FullWalkMove()` |
| `ADDR_GROUND_ACCELERATE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_GroundAccelerate()` |
| `ADDR_LADDER_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_LadderMove()` |
| `ADDR_MOVE_INIT()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_MoveInit()` |
| `ADDR_PLAYER_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_PlayerMove()` |
| `ADDR_PROCESS_MOVEMENT()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_ProcessMovement()` |
| `ADDR_SETUP_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_SetupMove()` |
| `ADDR_TRY_PLAYER_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_TryPlayerMove()` |
| `ADDR_WALK_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_WalkMove()` |
| `ADDR_WATER_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_WaterMove()` |
| `ADDR_ON_JUMP_LEGACY()` | `g_pSource2ModAddresses->CCSPlayerLegacyJump_OnJump()` |
| `ADDR_ON_JUMP_MODERN()` | `g_pSource2ModAddresses->CCSPlayerModernJump_OnJump()` |
| `ADDR_CHECK_JUMP_BUTTON_LEGACY()` | `g_pSource2ModAddresses->CCSPlayerLegacyJump_CheckJumpButton()` |
| `ADDR_CHECK_JUMP_BUTTON_MODERN()` | `g_pSource2ModAddresses->CCSPlayerModernJump_CheckJumpButton()` |
| `ADDR_SET_OR_ADD_ATTRIBUTE()` | `g_pSource2ModAddresses->CAttributeList_SetOrAddAttributeValueByName()` |
| `ADDR_EMIT_DECOY()` | `g_pSource2ModAddresses->CDecoyProjectile_EmitGrenade()` |
| `ADDR_EMIT_FLASHBANG()` | `g_pSource2ModAddresses->CFlashbangProjectile_EmitGrenade()` |
| `ADDR_EMIT_HEGRENADE()` | `g_pSource2ModAddresses->CHEGrenadeProjectile_EmitGrenade()` |
| `ADDR_EMIT_MOLOTOV()` | `g_pSource2ModAddresses->CMolotovProjectile_EmitGrenade()` |
| `ADDR_EMIT_SMOKE()` | `g_pSource2ModAddresses->CSmokeGrenadeProjectile_EmitGrenade()` |
| `ADDR_POST_THINK()` | `g_pSource2ModAddresses->CCSPlayerPawn_PostThink()` |
| `ADDR_UTIL_REMOVE()` | `g_pSource2ModAddresses->UTIL_Remove()` |
| `ADDR_DISPATCH_PARTICLE_EFFECT()` | `g_pSource2ModAddresses->DispatchParticleEffect()` |
| `ADDR_GET_WEAPON_CS_DATA_FROM_KEY()` | `g_pSource2ModAddresses->GetWeaponCSDataFromKey()` |
| `ADDR_GET_NAV_MESH_DATA()` | `g_pSource2ModAddresses->CSource2Server_GetNavMeshData()` |
| `ADDR_LOG_DIRECT()` | `g_pSource2ModAddresses->CLoggingSystem_LogDirect()` |
| `ADDR_CMD_EXECUTE_COMMAND()` | `g_pSource2ModAddresses->Cmd_ExecuteCommand()` |
| `ADDR_GIVE_NAMED_ITEM()` | `g_pSource2ModAddresses->CCSPlayer_ItemServices_GiveNamedItem()` |
# ISource2ModApi (/docs/core-api/ISource2ModApi)
This interface provides access to:
* Core engine interfaces
* Source2Mod subsystems (Commands, Events, ConVars, etc.)
* Logging and console output
* Interface querying and factory access
This is the primary interface passed to plugins during initialization.
Main API interface for Source2Mod.
Provides access to all source2mod subsystems and engine interfaces.
```cpp
api->Commands()->RegisterConCommand(...);
api->Events()->HookGameEvent(...);
```
**Global** `g_Source2ModAPI` · **Header** [`source2mod/ISource2ModApi.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModApi.h)
Types [#types]
`PluginId` [#pluginid]
```cpp
using PluginId = int;
```
`CreateInterfaceFn` [#createinterfacefn]
```cpp
typedef void* (*CreateInterfaceFn)(const char* pName, int* pReturnCode);
```
Methods [#methods]
`Log` [#log]
```cpp
virtual void Log(ISource2ModPlugin* plugin, const char* msg, ...) = 0;
```
Logs a formatted message with plugin context.
| Parameter | Type | Description |
| --------- | -------------------------------------------------------- | --------------- |
| `plugin` | [`ISource2ModPlugin*`](/docs/core-api/ISource2ModPlugin) | Plugin instance |
| `msg` | `const char*` | Format string |
| `—` | `...` | |
`ConPrint` [#conprint]
```cpp
virtual void ConPrint(const char* msg) = 0;
```
Prints message to console.
| Parameter | Type |
| --------- | ------------- |
| `msg` | `const char*` |
`ConPrintf` [#conprintf]
```cpp
virtual void ConPrintf(const char* fmt, ...) = 0;
```
Prints formatted message to console.
| Parameter | Type |
| --------- | ------------- |
| `fmt` | `const char*` |
| `—` | `...` |
`AddListener` [#addlistener]
```cpp
virtual void AddListener(ISource2ModPlugin* plugin, ISource2ModListener* pListener) = 0;
```
Registers a source2mod listener.
| Parameter | Type | Description |
| ----------- | ------------------------------------------------------------------------------ | ----------------- |
| `plugin` | [`ISource2ModPlugin*`](/docs/core-api/ISource2ModPlugin) | Owner plugin |
| `pListener` | [`ISource2ModListener*`](/docs/core-api/ISource2ModPlugin#isource2modlistener) | Listener instance |
`QueryInterface` [#queryinterface]
```cpp
virtual void* QueryInterface(CreateInterfaceFn fn, const char* iface, int min = -1) = 0;
```
Queries interface from factory.
| Parameter | Type | Default | Description |
| --------- | ---------------------------------------------------------------------- | ------- | -------------------------- |
| `fn` | [`CreateInterfaceFn`](/docs/core-api/ISource2ModApi#createinterfacefn) | | Factory function |
| `iface` | `const char*` | | Interface name |
| `min` | `int` | `-1` | Minimum version (optional) |
`InterfaceSearch` [#interfacesearch]
```cpp
virtual void* InterfaceSearch(CreateInterfaceFn fn, const char* iface, int max, int* ret) = 0;
```
Searches for interface by iterating versions.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------- |
| `fn` | [`CreateInterfaceFn`](/docs/core-api/ISource2ModApi#createinterfacefn) |
| `iface` | `const char*` |
| `max` | `int` |
| `ret` | `int*` |
`FormatIface` [#formatiface]
```cpp
virtual int FormatIface(char iface[], size_t maxlength) = 0;
```
Formats interface name with version.
| Parameter | Type |
| ----------- | --------- |
| `iface` | `char []` |
| `maxlength` | `size_t` |
`GetEngineFactory` [#getenginefactory]
```cpp
virtual CreateInterfaceFn GetEngineFactory(bool syn = true) = 0;
```
| Parameter | Type | Default |
| --------- | ------ | ------- |
| `syn` | `bool` | `true` |
`GetPhysicsFactory` [#getphysicsfactory]
```cpp
virtual CreateInterfaceFn GetPhysicsFactory(bool syn = true) = 0;
```
| Parameter | Type | Default |
| --------- | ------ | ------- |
| `syn` | `bool` | `true` |
`GetFileSystemFactory` [#getfilesystemfactory]
```cpp
virtual CreateInterfaceFn GetFileSystemFactory(bool syn = true) = 0;
```
| Parameter | Type | Default |
| --------- | ------ | ------- |
| `syn` | `bool` | `true` |
`GetServerFactory` [#getserverfactory]
```cpp
virtual CreateInterfaceFn GetServerFactory(bool syn = true) = 0;
```
| Parameter | Type | Default |
| --------- | ------ | ------- |
| `syn` | `bool` | `true` |
`Source2ModFactory` [#source2modfactory]
```cpp
virtual void* Source2ModFactory(const char* iface, int* ret, PluginId* id) = 0;
```
Source2Mod interface factory.
| Parameter | Type |
| --------- | ----------------------------------------------------- |
| `iface` | `const char*` |
| `ret` | `int*` |
| `id` | [`PluginId*`](/docs/core-api/ISource2ModApi#pluginid) |
`MetaFactory` [#metafactory]
```cpp
virtual void* MetaFactory(const char* iface, int* ret, PluginId* id) = 0;
```
Metamod interface factory.
| Parameter | Type |
| --------- | ----------------------------------------------------- |
| `iface` | `const char*` |
| `ret` | `int*` |
| `id` | [`PluginId*`](/docs/core-api/ISource2ModApi#pluginid) |
`Addresses` [#addresses]
```cpp
virtual ISource2ModAddresses* Addresses() = 0;
```
Access address/pattern system.
`Commands` [#commands]
```cpp
virtual ISource2ModCommands* Commands() = 0;
```
Access command system.
`ConVars` [#convars]
```cpp
virtual ISource2ModConVars* ConVars() = 0;
```
Access ConVar system.
`CustomHud` [#customhud]
```cpp
virtual ISource2ModCustomHud* CustomHud() = 0;
```
Access custom HUD layout system.
`Entities` [#entities]
```cpp
virtual ISource2ModEntities* Entities() = 0;
```
Access entities and Entity I/O system.
`Events` [#events]
```cpp
virtual ISource2ModEvents* Events() = 0;
```
Access event system.
`GameConfig` [#gameconfig]
```cpp
virtual ISource2ModGameConfig* GameConfig() = 0;
```
Access game configuration system.
`GameSystems` [#gamesystems]
```cpp
virtual ISource2ModGameSystems* GameSystems() = 0;
```
Access game system registry.
`HTTP` [#http]
```cpp
virtual ISource2ModHTTP* HTTP() = 0;
```
Access HTTP system.
`Menus` [#menus]
```cpp
virtual ISource2ModMenus* Menus() = 0;
```
Access menu system.
`SQL` [#sql]
```cpp
virtual ISource2ModSQL* SQL() = 0;
```
Access the SQL system (MySQL and SQLite).
`NetworkMessages` [#networkmessages]
```cpp
virtual ISource2ModNetworkMessages* NetworkMessages() = 0;
```
Access network messages system.
`Paths` [#paths]
```cpp
virtual ISource2ModPaths* Paths() = 0;
```
Where Source2Mod keeps things on disk.
`Scheduler` [#scheduler]
```cpp
virtual ISource2ModScheduler* Scheduler() = 0;
```
Access scheduler system.
`Sounds` [#sounds]
```cpp
virtual ISource2ModSounds* Sounds() = 0;
```
Access sound system.
`Trace` [#trace]
```cpp
virtual ISource2ModTrace* Trace() = 0;
```
Access tracing system.
`Transmit` [#transmit]
```cpp
virtual ISource2ModTransmit* Transmit() = 0;
```
Access transmit system (per-viewer entity visibility).
`LoadModule` [#loadmodule]
```cpp
virtual ISource2ModModule* LoadModule(const char* name) = 0;
```
Creates and initialises a module wrapper by name.
The returned object must be released with FreeModule().
| Parameter | Type | Description |
| --------- | ------------- | ---------------------------------------------- |
| `name` | `const char*` | Module name without extension (e.g. "server"). |
**Returns** [`ISource2ModModule*`](/docs/core-api/ISource2ModModule) — Heap-allocated ISource2ModModule, or nullptr on failure.
`LoadModuleFromMemory` [#loadmodulefrommemory]
```cpp
virtual ISource2ModModule* LoadModuleFromMemory(uintptr_t ptr) = 0;
```
Creates and initialises a module wrapper from an address inside it.
The returned object must be released with FreeModule().
| Parameter | Type | Description |
| --------- | ----------- | ---------------------------------------------- |
| `ptr` | `uintptr_t` | Any address that belongs to the target module. |
**Returns** [`ISource2ModModule*`](/docs/core-api/ISource2ModModule) — Heap-allocated ISource2ModModule, or nullptr on failure.
`FreeModule` [#freemodule]
```cpp
virtual void FreeModule(ISource2ModModule* module) = 0;
```
Releases a module previously obtained from LoadModule() / LoadModuleFromMemory().
| Parameter | Type |
| --------- | -------------------------------------------------------- |
| `module` | [`ISource2ModModule*`](/docs/core-api/ISource2ModModule) |
`GetSource2Server` [#getsource2server]
```cpp
virtual ISource2Server* GetSource2Server() = 0;
```
`GetSource2ServerConfig` [#getsource2serverconfig]
```cpp
virtual ISource2ServerConfig* GetSource2ServerConfig() = 0;
```
`GetSource2GameClients` [#getsource2gameclients]
```cpp
virtual ISource2GameClients* GetSource2GameClients() = 0;
```
`GetSource2GameEntities` [#getsource2gameentities]
```cpp
virtual ISource2GameEntities* GetSource2GameEntities() = 0;
```
`GetEngineServer` [#getengineserver]
```cpp
virtual IVEngineServer2* GetEngineServer() = 0;
```
`GetCVar` [#getcvar]
```cpp
virtual ICvar* GetCVar() = 0;
```
`GetSchemaSystem` [#getschemasystem]
```cpp
virtual CSchemaSystem* GetSchemaSystem() = 0;
```
`GetGameResourceService` [#getgameresourceservice]
```cpp
virtual IGameResourceService* GetGameResourceService() = 0;
```
`GetNetworkServerService` [#getnetworkserverservice]
```cpp
virtual INetworkServerService* GetNetworkServerService() = 0;
```
`GetNetworkSystem` [#getnetworksystem]
```cpp
virtual INetworkSystem* GetNetworkSystem() = 0;
```
`GetNetworkMessages` [#getnetworkmessages]
```cpp
virtual INetworkMessages* GetNetworkMessages() = 0;
```
`GetNetworkStringTableServer` [#getnetworkstringtableserver]
```cpp
virtual INetworkStringTableContainer* GetNetworkStringTableServer() = 0;
```
`GetEngineServiceMgr` [#getengineservicemgr]
```cpp
virtual IEngineServiceMgr* GetEngineServiceMgr() = 0;
```
`GetHostStateMgr` [#gethoststatemgr]
```cpp
virtual IHostStateMgr* GetHostStateMgr() = 0;
```
`GetLocalize` [#getlocalize]
```cpp
virtual ILocalize* GetLocalize() = 0;
```
`GetFileSystem` [#getfilesystem]
```cpp
virtual IFileSystem* GetFileSystem() = 0;
```
`GetGameEventSystem` [#getgameeventsystem]
```cpp
virtual IGameEventSystem* GetGameEventSystem() = 0;
```
`GetGameEventManager` [#getgameeventmanager]
```cpp
virtual IGameEventManager2* GetGameEventManager() = 0;
```
`GetEntitySystem` [#getentitysystem]
```cpp
virtual CGameEntitySystem* GetEntitySystem() = 0;
```
`GetGlobalVars` [#getglobalvars]
```cpp
virtual CGlobalVars* GetGlobalVars() = 0;
```
`GetBaseDir` [#getbasedir]
```cpp
virtual const char* GetBaseDir() = 0;
```
Returns base directory of the server.
`Format` [#format]
```cpp
virtual size_t Format(char* buffer, size_t maxlength, const char* format, ...) = 0;
```
Formats string into buffer.
| Parameter | Type |
| ----------- | ------------- |
| `buffer` | `char*` |
| `maxlength` | `size_t` |
| `format` | `const char*` |
| `—` | `...` |
`FormatArgs` [#formatargs]
```cpp
virtual size_t FormatArgs(char* buffer, size_t maxlength, const char* format, va_list ap) = 0;
```
Formats string using va\_list.
| Parameter | Type |
| ----------- | ------------- |
| `buffer` | `char*` |
| `maxlength` | `size_t` |
| `format` | `const char*` |
| `ap` | `va_list` |
`GameHooks` [#gamehooks]
```cpp
virtual ISource2ModGameHooks* GameHooks() = 0;
```
Access the core's hooks on common game functions.
# ISource2ModClients (/docs/core-api/ISource2ModClients)
What a CBasePlayerController already does (name, IP, SteamID64, user id, console commands, team changes, Disconnect) stays there.
**Interface id** `ISource2ModClients001` (`SOURCE2MOD_CLIENTS_INTERFACE`) · **Global** `g_pSource2ModClients` · **Header** [`source2mod/ISource2ModClients.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModClients.h)
Methods [#methods]
`IsConnected` [#isconnected]
```cpp
virtual bool IsConnected(CPlayerSlot slot) = 0;
```
A slot has a client (connecting or in game).
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`IsInGame` [#isingame]
```cpp
virtual bool IsInGame(CPlayerSlot slot) = 0;
```
A slot's client is fully in game.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`IsFakeClient` [#isfakeclient]
```cpp
virtual bool IsFakeClient(CPlayerSlot slot) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`IsAuthorized` [#isauthorized]
```cpp
virtual bool IsAuthorized(CPlayerSlot slot) = 0;
```
Steam has confirmed the client's SteamID.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`GetAuthId` [#getauthid]
```cpp
virtual std::string GetAuthId(CPlayerSlot slot, ESource2ModAuthId form, bool validated = true) = 0;
```
A client's SteamID as text.
| Parameter | Type | Default | Description |
| ----------- | -------------------------------------------------------------------------- | ------- | ---------------------------------------------- |
| `slot` | `CPlayerSlot` | | |
| `form` | [`ESource2ModAuthId`](/docs/core-api/ISource2ModClients#esource2modauthid) | | |
| `validated` | `bool` | `true` | Only once Steam has confirmed it (else empty). |
**Returns** `std::string` — Empty for a bot or no client.
`GetLatency` [#getlatency]
```cpp
virtual float GetLatency(CPlayerSlot slot) = 0;
```
Average round trip in seconds (0 for a bot or no client).
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`GetConnectionTime` [#getconnectiontime]
```cpp
virtual float GetConnectionTime(CPlayerSlot slot) = 0;
```
Seconds connected (0 for a bot or no client).
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`GetClientConVar` [#getclientconvar]
```cpp
virtual std::string GetClientConVar(CPlayerSlot slot, const char* name) = 0;
```
A cvar the client sent (cl\_language, ...); empty when it did not.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
| `name` | `const char*` |
`Kick` [#kick]
```cpp
virtual void Kick(CPlayerSlot slot, const char* message) = 0;
```
Disconnects a client. CS2 shows them the kick reason's own text; @p message is the reason the server logs.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
| `message` | `const char*` |
`GetClientCount` [#getclientcount]
```cpp
virtual int GetClientCount(bool inGameOnly = true) = 0;
```
Clients on the server, bots included.
| Parameter | Type | Default |
| ------------ | ------ | ------- |
| `inGameOnly` | `bool` | `true` |
Enums [#enums]
`ESource2ModAuthId` [#esource2modauthid]
```cpp
enum ESource2ModAuthId
```
SteamID forms (SourceMod's AuthIdType values).
| Name | Value | Description |
| ----------------------------- | ----- | ---------------------- |
| `SOURCE2MOD_AUTHID_ENGINE` | `0` | as the engine shows it |
| `SOURCE2MOD_AUTHID_STEAM2` | | STEAM\_1:0:12345 |
| `SOURCE2MOD_AUTHID_STEAM3` | | \[U:1:24690] |
| `SOURCE2MOD_AUTHID_STEAMID64` | | 76561197960290418 |
# ISource2ModCommands (/docs/core-api/ISource2ModCommands)
Provides functionality for:
* Registering console commands
* Listening to console command execution
* Handling in-game chat messages
This system allows plugins to hook into player input via chat or console.
Interface for registering and handling commands.
Supports:
* Chat-based commands (player messages)
* Console commands (server or client)
* Command listeners with pre/post execution hooks
Every registration belongs to the plugin whose handler it is.
**Interface id** `ISource2ModCommands003` (`SOURCE2MOD_COMMANDS_INTERFACE`) · **Global** `g_pSource2ModCommands` · **Header** [`source2mod/ISource2ModCommands.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModCommands.h)
Types [#types]
`Source2ModCommandId` [#source2modcommandid]
```cpp
using Source2ModCommandId = Source2ModHookId;
```
A console command of the plugin's own (RegisterConCommand).
`Source2ModCommandHookId` [#source2modcommandhookid]
```cpp
using Source2ModCommandHookId = Source2ModHookId;
```
A listener on a command somebody else owns (RegisterConListener).
`Source2ModChatListenerId` [#source2modchatlistenerid]
```cpp
using Source2ModChatListenerId = Source2ModHookId;
```
A chat-only listener (RegisterChatListener).
`ChatHandler` [#chathandler]
```cpp
using ChatHandler = Source2ModCallback;
```
A chat command or console command handler: a function, an object and a method, or a lambda (Source2ModCallback).
`CommandHandler` [#commandhandler]
```cpp
using CommandHandler = Source2ModCallback;
```
Callback for console commands.
Methods [#methods]
`RegisterChatListener` [#registerchatlistener]
```cpp
virtual Source2ModChatListenerId RegisterChatListener(const char* pchName, ChatHandler handler, const char* pchPermission = nullptr) = 0;
```
Registers a chat command.
Only fires for a chat message that starts with one of the configured chat triggers (public or silent, "!" and "/" by default), e.g. "!kick" or "/kick"; plain "kick" typed into chat does not fire it. The trigger is stripped before matching, so args.Arg(0) is the bare name. A silent trigger hides the message; a public one lets it show.
Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it.
| Parameter | Type | Default | Description |
| --------------- | --------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pchName` | `const char*` | | Command name without a trigger (e.g. "kick") |
| `handler` | [`ChatHandler`](/docs/core-api/ISource2ModCommands#chathandler) | | A function, an object and a method, or a lambda |
| `pchPermission` | `const char*` | `nullptr` | What a player needs to run it (see ISource2ModPermissions); nullptr or "" for everybody. A player without it gets the access-denied answer instead. |
**Returns** [`Source2ModChatListenerId`](/docs/core-api/ISource2ModCommands#source2modchatlistenerid) — The id UnregisterChatListener(id) takes
`UnregisterChatListener` [#unregisterchatlistener]
```cpp
virtual bool UnregisterChatListener(const char* pchName, const ChatHandler& handler) = 0;
virtual bool UnregisterChatListener(Source2ModChatListenerId id) = 0;
```
Drops the chat command with this handler -- a function or an object and a method; a lambda goes by its id.
Drops the chat command RegisterChatListener() returned the id for.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `pchName` | `const char*` |
| `handler` | [`const ChatHandler&`](/docs/core-api/ISource2ModCommands#chathandler) |
| `id` | [`Source2ModChatListenerId`](/docs/core-api/ISource2ModCommands#source2modchatlistenerid) |
**Returns** `bool` — true when one was found
`RegisterConCommand` [#registerconcommand]
```cpp
virtual Source2ModCommandId RegisterConCommand(const char* pchName, ChatHandler handler, const char* pchPermission = nullptr) = 0;
```
Registers a console command, reachable from chat as "!name" and "/name" too. Several handlers may share a name; each runs.
Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it.
| Parameter | Type | Default | Description |
| --------------- | --------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pchName` | `const char*` | | Command name (e.g. "sv\_test") |
| `handler` | [`ChatHandler`](/docs/core-api/ISource2ModCommands#chathandler) | | A function, an object and a method, or a lambda |
| `pchPermission` | `const char*` | `nullptr` | What a player needs to run it (see ISource2ModPermissions); nullptr or "" for everybody. The server console always may. The owner can re-assign it under "Overrides" in permissions.json. |
**Returns** [`Source2ModCommandId`](/docs/core-api/ISource2ModCommands#source2modcommandid) — The id UnregisterConCommand(id) takes
`UnregisterConCommand` [#unregisterconcommand]
```cpp
virtual bool UnregisterConCommand(const char* pchName, const ChatHandler& handler) = 0;
virtual bool UnregisterConCommand(Source2ModCommandId id) = 0;
```
Drops the console command handler -- a function or an object and a method; a lambda goes by its id. The command name stays claimed until the plugin unloads.
Drops the console command handler RegisterConCommand() returned the id for.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------- |
| `pchName` | `const char*` |
| `handler` | [`const ChatHandler&`](/docs/core-api/ISource2ModCommands#chathandler) |
| `id` | [`Source2ModCommandId`](/docs/core-api/ISource2ModCommands#source2modcommandid) |
**Returns** `bool` — true when one was found
`RegisterConListener` [#registerconlistener]
```cpp
virtual Source2ModCommandHookId RegisterConListener(const char* pchName, CommandHandler handler, bool post) = 0;
```
Registers a listener on an existing console command.
The handler's Action:
* Ignore: do nothing
* Override: the original still runs; commands return nothing, so this has no further effect. Later pre listeners and all post listeners still fire.
* Supersede: block the original (pre only); the remaining pre listeners and all post listeners are skipped
Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it.
| Parameter | Type | Description |
| --------- | --------------------------------------------------------------------- | --------------------------------------------------- |
| `pchName` | `const char*` | Existing command name to listen for |
| `handler` | [`CommandHandler`](/docs/core-api/ISource2ModCommands#commandhandler) | A function, an object and a method, or a lambda |
| `post` | `bool` | false to run before the original, true to run after |
**Returns** [`Source2ModCommandHookId`](/docs/core-api/ISource2ModCommands#source2modcommandhookid) — The id UnregisterConListener(id) takes
`UnregisterConListener` [#unregisterconlistener]
```cpp
virtual bool UnregisterConListener(const char* pchName, const CommandHandler& handler, bool post) = 0;
virtual bool UnregisterConListener(Source2ModCommandHookId id) = 0;
```
Drops the console listener with this handler -- a function or an object and a method; a lambda goes by its id.
Drops the console listener RegisterConListener() returned the id for.
| Parameter | Type |
| --------- | --------------------------------------------------------------------------------------- |
| `pchName` | `const char*` |
| `handler` | [`const CommandHandler&`](/docs/core-api/ISource2ModCommands#commandhandler) |
| `post` | `bool` |
| `id` | [`Source2ModCommandHookId`](/docs/core-api/ISource2ModCommands#source2modcommandhookid) |
**Returns** `bool` — true when one was found
`ReplyToCommand` [#replytocommand]
```cpp
virtual void ReplyToCommand(const Source2ModCommandContext& ctx, const char* pszMessage) = 0;
```
Answers a command where it was typed: to the player's chat when it came from chat, to the player's console when it came from their console, to the server console when it came from there. One line; the newline is added.
| Parameter | Type |
| ------------ | ------------------------------------------------------------------------------------------------ |
| `ctx` | [`const Source2ModCommandContext&`](/docs/core-api/ISource2ModCommands#source2modcommandcontext) |
| `pszMessage` | `const char*` |
`Source2ModCommandContext` [#source2modcommandcontext]
Constructors [#constructors]
```cpp
Source2ModCommandContext(CPlayerSlot slot, int target, Source2ModCommandSource source = Source2ModCommandSource::Console);
```
`GetPlayerSlot` [#getplayerslot]
```cpp
CPlayerSlot GetPlayerSlot() const;
```
The player who issued the command; invalid for the server console.
`GetCommandTarget` [#getcommandtarget]
```cpp
int GetCommandTarget() const;
```
The engine's CommandTarget\_t (CT\_NO\_TARGET, CT\_FIRST\_SPLITSCREEN\_CLIENT, ...).
`GetSource` [#getsource]
```cpp
Source2ModCommandSource GetSource() const;
```
Where it was typed.
`IsFromChat` [#isfromchat]
```cpp
bool IsFromChat() const;
```
Typed in chat, with either trigger.
`Source2ModCommandArgs` [#source2modcommandargs]
A command's arguments, in Source2Mod's own layout; same accessors as the engine's CCommand. The strings belong to the dispatch and are gone when the handler returns -- copy what has to outlive it.
Fields [#fields]
| Field | Type |
| ---------- | ----- |
| `kMaxArgs` | `int` |
Constructors [#constructors-1]
```cpp
Source2ModCommandArgs() = default;
Source2ModCommandArgs(int argc, const char* const* argv, const char* argS, const char* command);
```
`ArgC` [#argc]
```cpp
int ArgC() const;
```
Number of arguments, the command name included.
`Arg` [#arg]
```cpp
const char* Arg(int i) const;
```
Argument i, the command name at 0; "" past the end.
| Parameter | Type |
| --------- | ----- |
| `i` | `int` |
`ArgS` [#args]
```cpp
const char* ArgS() const;
```
Everything after the command name, as typed.
`GetCommandString` [#getcommandstring]
```cpp
const char* GetCommandString() const;
```
The whole command line, as typed.
Enums [#enums]
`Source2ModCommandSource` [#source2modcommandsource]
```cpp
enum class Source2ModCommandSource : uint8_t
```
Where a command was typed: what ReplyToCommand() answers into.
| Name | Description |
| ------------ | ------------------------------------------------------------ |
| `Console` | A console -- the server's or the player's own |
| `Chat` | Chat, with a public trigger ("!kick"); the message shows |
| `SilentChat` | Chat, with a silent trigger ("/kick"); the message is hidden |
Macros [#macros]
`REGISTER_CHAT_LISTENER` [#register_chat_listener]
```cpp
#define REGISTER_CHAT_LISTENER(pchName, ...) g_pSource2ModCommands->RegisterChatListener(pchName, __VA_ARGS__)
```
RegisterConCommand(name, handler) or RegisterConCommand(name, handler, permission).
`REPLY_TO_COMMAND` [#reply_to_command]
```cpp
#define REPLY_TO_COMMAND(ctx, fmt, ...) \
do {
char _source2modReply[512];
snprintf(_source2modReply, sizeof(_source2modReply), fmt, ##__VA_ARGS__);
g_pSource2ModCommands->ReplyToCommand(ctx, _source2modReply);
} while (0)
```
ReplyToCommand() with printf formatting.
Other macros [#other-macros]
| Macro | Expands to |
| ----------------------------------------------- | -------------------------------------------------------------------- |
| `REGISTER_CON_COMMAND(pchName, ...)` | `g_pSource2ModCommands->RegisterConCommand(pchName, __VA_ARGS__)` |
| `REGISTER_CON_LISTENER(pchName, handler, post)` | `g_pSource2ModCommands->RegisterConListener(pchName, handler, post)` |
# ISource2ModConVars (/docs/core-api/ISource2ModConVars)
Provides functionality for:
* Querying ConVars by name
* Reading and writing values
* Accessing metadata (type, flags, help text)
* Creating and deleting ConVars
ConVars are identified by an internal access index.
**Interface id** `ISource2ModConVars002` (`SOURCE2MOD_CONVARS_INTERFACE`) · **Global** `g_pSource2ModConVars` · **Header** [`source2mod/ISource2ModConVars.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModConVars.h)
Types [#types]
`Source2ModConVarHookId` [#source2modconvarhookid]
```cpp
using Source2ModConVarHookId = Source2ModHookId;
```
A ConVar change hook (HookConVarChange).
`ConVarChangeHandler` [#convarchangehandler]
```cpp
using ConVarChangeHandler = Source2ModCallback;
```
Callback type for ConVar value changes.
Methods [#methods]
`GetConvarAccessIndexByName` [#getconvaraccessindexbyname]
```cpp
virtual uint16 GetConvarAccessIndexByName(const char* name) = 0;
```
Retrieves access index of a ConVar by name.
| Parameter | Type | Description |
| --------- | ------------- | ----------- |
| `name` | `const char*` | ConVar name |
**Returns** `uint16` — Access index, or SOURCE2MOD\_INVALID\_CONVAR\_INDEX if no ConVar has that name. 0 is a valid index, so compare against the sentinel rather than testing for zero.
`GetConvarRef` [#getconvarref]
```cpp
virtual ConVarRefAbstract GetConvarRef(uint16 accessIndex) = 0;
```
Gets a ConVar reference wrapper.
| Parameter | Type |
| ------------- | -------- |
| `accessIndex` | `uint16` |
`GetName` [#getname]
```cpp
virtual const char* GetName(uint16 accessIndex) = 0;
```
Gets ConVar name.
| Parameter | Type |
| ------------- | -------- |
| `accessIndex` | `uint16` |
`GetHelpText` [#gethelptext]
```cpp
virtual const char* GetHelpText(uint16 accessIndex) = 0;
```
Gets help/description text.
| Parameter | Type |
| ------------- | -------- |
| `accessIndex` | `uint16` |
`GetType` [#gettype]
```cpp
virtual EConVarType GetType(uint16 accessIndex) = 0;
```
Gets ConVar type.
| Parameter | Type |
| ------------- | -------- |
| `accessIndex` | `uint16` |
`GetFlags` [#getflags]
```cpp
virtual uint64 GetFlags(uint16 accessIndex) = 0;
```
Gets flags (FCVAR\_\*).
| Parameter | Type |
| ------------- | -------- |
| `accessIndex` | `uint16` |
`SetFlags` [#setflags]
```cpp
virtual void SetFlags(uint16 accessIndex, uint64 flags) = 0;
```
Sets flags (FCVAR\_\*).
| Parameter | Type |
| ------------- | -------- |
| `accessIndex` | `uint16` |
| `flags` | `uint64` |
`GetValueAddress` [#getvalueaddress]
```cpp
virtual void* GetValueAddress(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
Gets pointer to underlying value.
Type depends on ConVar type.
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetString` [#getstring]
```cpp
virtual const char* GetString(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetBool` [#getbool]
```cpp
virtual bool GetBool(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetInt` [#getint]
```cpp
virtual int32 GetInt(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetFloat` [#getfloat]
```cpp
virtual float GetFloat(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetDouble` [#getdouble]
```cpp
virtual double GetDouble(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetString` [#setstring]
```cpp
virtual void SetString(uint16 accessIndex, const char* value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `const char*` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetBool` [#setbool]
```cpp
virtual void SetBool(uint16 accessIndex, bool value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `bool` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetInt` [#setint]
```cpp
virtual void SetInt(uint16 accessIndex, int32 value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `int32` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetFloat` [#setfloat]
```cpp
virtual void SetFloat(uint16 accessIndex, float value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `float` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetDouble` [#setdouble]
```cpp
virtual void SetDouble(uint16 accessIndex, double value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `double` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetVector2` [#getvector2]
```cpp
virtual Vector2D GetVector2(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetVector3` [#getvector3]
```cpp
virtual Vector GetVector3(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetVector4` [#getvector4]
```cpp
virtual Vector4D GetVector4(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetQAngle` [#getqangle]
```cpp
virtual QAngle GetQAngle(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetColor` [#getcolor]
```cpp
virtual Color GetColor(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetVector2` [#setvector2]
```cpp
virtual void SetVector2(uint16 accessIndex, const Vector2D& value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `const Vector2D&` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetVector3` [#setvector3]
```cpp
virtual void SetVector3(uint16 accessIndex, const Vector& value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `const Vector&` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetVector4` [#setvector4]
```cpp
virtual void SetVector4(uint16 accessIndex, const Vector4D& value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `const Vector4D&` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetQAngle` [#setqangle]
```cpp
virtual void SetQAngle(uint16 accessIndex, const QAngle& value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `const QAngle&` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`SetColor` [#setcolor]
```cpp
virtual void SetColor(uint16 accessIndex, const Color& value, CSplitScreenSlot slot = -1) = 0;
```
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `const Color&` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`GetValue` [#getvalue]
```cpp
virtual void GetValue(uint16 accessIndex, void* outValue, CSplitScreenSlot slot = -1) = 0;
```
Gets value into user-provided buffer.
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `outValue` | `void*` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`HookConVarChange` [#hookconvarchange]
```cpp
virtual Source2ModConVarHookId HookConVarChange(ConVarChangeHandler handler) = 0;
```
Registers a listener for every ConVar value change.
The engine only takes plain function pointers here, and only keeps one list of them for the whole process. Source2Mod installs a single one of those and fans out to the handlers registered through this, so a plugin can use a capturing lambda and does not have to unregister on unload.
A plugin may register as many as it likes.
Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------------------------ | ----------------------------------------------- |
| `handler` | [`ConVarChangeHandler`](/docs/core-api/ISource2ModConVars#convarchangehandler) | A function, an object and a method, or a lambda |
**Returns** [`Source2ModConVarHookId`](/docs/core-api/ISource2ModConVars#source2modconvarhookid) — The id UnhookConVarChange(id) takes
`UnhookConVarChange` [#unhookconvarchange]
```cpp
virtual bool UnhookConVarChange(const ConVarChangeHandler& handler) = 0;
virtual bool UnhookConVarChange(Source2ModConVarHookId id) = 0;
```
Drops the change listener with this handler -- a function or an object and a method; a lambda goes by its id.
Drops the change listener HookConVarChange() returned the id for.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------- |
| `handler` | [`const ConVarChangeHandler&`](/docs/core-api/ISource2ModConVars#convarchangehandler) |
| `id` | [`Source2ModConVarHookId`](/docs/core-api/ISource2ModConVars#source2modconvarhookid) |
**Returns** `bool` — true when one was found
`SetValue` [#setvalue]
```cpp
virtual void SetValue(uint16 accessIndex, const void* value, CSplitScreenSlot slot = -1) = 0;
```
Sets value from user-provided buffer.
| Parameter | Type | Default |
| ------------- | ------------------ | ------- |
| `accessIndex` | `uint16` | |
| `value` | `const void*` | |
| `slot` | `CSplitScreenSlot` | `-1` |
`CreateConVar` [#createconvar]
```cpp
virtual uint16 CreateConVar(const char* name, EConVarType type, const char* help, uint64 flags, bool hasMin, bool hasMax, void* defaultValue, const void* minValue, const void* maxValue) = 0;
```
Creates a new ConVar.
The engine has no way to unregister a ConVar, so one created here outlives the plugin that created it. When the plugin loads again, the same call returns the existing ConVar with its current value.
| Parameter | Type | Description |
| -------------- | ------------- | ------------------------------------------------------------------------------ |
| `name` | `const char*` | ConVar name |
| `type` | `EConVarType` | Variable type |
| `help` | `const char*` | Description text |
| `flags` | `uint64` | FCVAR flags |
| `hasMin` | `bool` | Whether min value is enforced |
| `hasMax` | `bool` | Whether max value is enforced |
| `defaultValue` | `void*` | Default value pointer (const char\* for EConVarType\_String) |
| `minValue` | `const void*` | Minimum value pointer; only read when hasMin is true, may be nullptr otherwise |
| `maxValue` | `const void*` | Maximum value pointer; only read when hasMax is true, may be nullptr otherwise |
**Returns** `uint16` — Access index of the created ConVar. If a ConVar with that name already exists, its index is returned and the arguments are ignored. SOURCE2MOD\_INVALID\_CONVAR\_INDEX on bad arguments or an unsupported type.
`DeleteConVar` [#deleteconvar]
```cpp
virtual void DeleteConVar(uint16 accessIndex) = 0;
```
Deletes a ConVar.
| Parameter | Type |
| ------------- | -------- |
| `accessIndex` | `uint16` |
Functions [#functions]
`UTIL_RemoveConVarFlags` [#util_removeconvarflags]
```cpp
inline bool UTIL_RemoveConVarFlags(const char* pszName, uint64 nFlags);
```
Clears flags on a ConVar found by name, e.g. FCVAR\_CHEAT.
| Parameter | Type |
| --------- | ------------- |
| `pszName` | `const char*` |
| `nFlags` | `uint64` |
**Returns** `bool` — false when there is no such ConVar, or its data is not available.
`UTIL_AddConVarFlags` [#util_addconvarflags]
```cpp
inline bool UTIL_AddConVarFlags(const char* pszName, uint64 nFlags);
```
Sets flags on a ConVar found by name.
| Parameter | Type |
| --------- | ------------- |
| `pszName` | `const char*` |
| `nFlags` | `uint64` |
`UTIL_SetConVar` [#util_setconvar]
```cpp
template inline bool UTIL_SetConVar(const char* pszName, const T& value, CSplitScreenSlot slot = -1);
inline bool UTIL_SetConVar(const char* pszName, double v, CSplitScreenSlot slot = -1);
inline bool UTIL_SetConVar(const char* pszName, float v, CSplitScreenSlot slot = -1);
inline bool UTIL_SetConVar(const char* pszName, int v, CSplitScreenSlot slot = -1);
inline bool UTIL_SetConVar(const char* pszName, unsigned v, CSplitScreenSlot slot = -1);
```
Sets a ConVar by name.
The type is what decides how the value is written, so call it as UTIL\_SetConVar\("mp\_freezetime", 5) when the literal would deduce to something else.
| Parameter | Type | Default |
| --------- | ------------------ | ------- |
| `pszName` | `const char*` | |
| `value` | `const T&` | |
| `slot` | `CSplitScreenSlot` | `-1` |
| `v` | `double` | |
| `v` | `float` | |
| `v` | `int` | |
| `—` | `unsigned v` | |
**Returns** `bool` — false when there is no such ConVar, or its data is not available.
Macros [#macros]
`HOOK_CONVAR_CHANGE` [#hook_convar_change]
```cpp
#define HOOK_CONVAR_CHANGE(passfunc) g_pSource2ModConVars->HookConVarChange(passfunc)
```
Macro for listening to ConVar value changes.
`UNHOOK_CONVAR_CHANGE` [#unhook_convar_change]
```cpp
#define UNHOOK_CONVAR_CHANGE(passfunc) g_pSource2ModConVars->UnhookConVarChange(passfunc)
```
Macro for dropping a ConVar change hook (a function or an object and a method; a lambda goes by the id HOOK\_CONVAR\_CHANGE returned).
Other macros [#other-macros]
| Macro | Expands to |
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `CVAR_IDX(name)` | `g_pSource2ModConVars->GetConvarAccessIndexByName(name)` |
| `CVAR_REF(idx)` | `g_pSource2ModConVars->GetConvarRef(idx)` |
| `CVAR_GET_STR(idx, ...)` | `g_pSource2ModConVars->GetString(idx, ##__VA_ARGS__)` |
| `CVAR_GET_BOOL(idx, ...)` | `g_pSource2ModConVars->GetBool(idx, ##__VA_ARGS__)` |
| `CVAR_GET_INT(idx, ...)` | `g_pSource2ModConVars->GetInt(idx, ##__VA_ARGS__)` |
| `CVAR_GET_FLOAT(idx, ...)` | `g_pSource2ModConVars->GetFloat(idx, ##__VA_ARGS__)` |
| `CVAR_GET_DOUBLE(idx, ...)` | `g_pSource2ModConVars->GetDouble(idx, ##__VA_ARGS__)` |
| `CVAR_SET_STR(idx, v, ...)` | `g_pSource2ModConVars->SetString(idx, v, ##__VA_ARGS__)` |
| `CVAR_SET_BOOL(idx, v, ...)` | `g_pSource2ModConVars->SetBool(idx, v, ##__VA_ARGS__)` |
| `CVAR_SET_INT(idx, v, ...)` | `g_pSource2ModConVars->SetInt(idx, v, ##__VA_ARGS__)` |
| `CVAR_SET_FLOAT(idx, v, ...)` | `g_pSource2ModConVars->SetFloat(idx, v, ##__VA_ARGS__)` |
| `CVAR_SET_DOUBLE(idx, v, ...)` | `g_pSource2ModConVars->SetDouble(idx, v, ##__VA_ARGS__)` |
| `CVAR_CREATE(name, type, help, flags, hasMin, hasMax, def, minVal, maxVal)` | `g_pSource2ModConVars->CreateConVar(name, type, help, flags, hasMin, hasMax, def, minVal, maxVal)` |
| `CVAR_DELETE(idx)` | `g_pSource2ModConVars->DeleteConVar(idx)` |
# ISource2ModCookies (/docs/core-api/ISource2ModCookies)
SourceMod's clientprefs. A cookie is a named string setting ("quake\_sounds", "hud\_color"); every player has their own value of it, kept in a database under their SteamID, and loaded into memory when the player is authorized. Until then -- AreCookiesCached() false -- reads return "" and writes are kept and saved once the player's values arrived.
The database is an SQLite file, addons/source2mod/data/clientprefs.sq3 unless core.json's "ClientPrefsDatabase" names another. The tables are SourceMod's (sm\_cookies, sm\_cookie\_cache) and players are keyed by "STEAM\_1:Y:Z", so a SourceMod clientprefs database can be carried over.
Values are saved when they change (a write goes to the database right away, asynchronously) -- nothing is lost on a crash or a map change.
**Interface id** `ISource2ModCookies001` (`SOURCE2MOD_COOKIES_INTERFACE`) · **Global** `g_pSource2ModCookies` · **Header** [`source2mod/ISource2ModCookies.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModCookies.h)
Types [#types]
`Source2ModCookiesListenerId` [#source2modcookieslistenerid]
```cpp
using Source2ModCookiesListenerId = Source2ModHookId;
```
A cookies-cached listener (AddCookiesCachedListener).
`Source2ModCookieId` [#source2modcookieid]
```cpp
using Source2ModCookieId = int;
```
A registered cookie; 0 is none.
`CookiesCachedCallback` [#cookiescachedcallback]
```cpp
using CookiesCachedCallback = Source2ModCallback;
```
A player's cookies were loaded (SourceMod's OnClientCookiesCached).
Runs on the main thread, once per connection, after OnClientAuthorized.
Methods [#methods]
`IsConnected` [#isconnected]
```cpp
virtual bool IsConnected() = 0;
```
Whether the cookie database is connected. Everything else works without it, only nothing is loaded or saved.
`RegisterCookie` [#registercookie]
```cpp
virtual Source2ModCookieId RegisterCookie(const char* name, const char* description, Source2ModCookieAccess access) = 0;
```
Registers a cookie, or finds it when it already is registered (by any plugin -- cookies are shared, by name).
| Parameter | Type |
| ------------- | ------------------------------------------------------------------------------------ |
| `name` | `const char*` |
| `description` | `const char*` |
| `access` | [`Source2ModCookieAccess`](/docs/core-api/ISource2ModCookies#source2modcookieaccess) |
**Returns** [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) — The cookie, 0 for an empty name or one longer than SOURCE2MOD\_COOKIE\_MAX\_NAME\_LENGTH.
`FindCookie` [#findcookie]
```cpp
virtual Source2ModCookieId FindCookie(const char* name) = 0;
```
A registered cookie by name; 0 when there is none.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`GetCookieName` [#getcookiename]
```cpp
virtual const char* GetCookieName(Source2ModCookieId cookie) = 0;
```
Its name / description; nullptr for a bad id.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------- |
| `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) |
`GetCookieDescription` [#getcookiedescription]
```cpp
virtual const char* GetCookieDescription(Source2ModCookieId cookie) = 0;
```
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------- |
| `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) |
`GetCookieAccess` [#getcookieaccess]
```cpp
virtual Source2ModCookieAccess GetCookieAccess(Source2ModCookieId cookie) = 0;
```
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------- |
| `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) |
`GetCookieCount` [#getcookiecount]
```cpp
virtual int GetCookieCount() = 0;
```
How many cookies are registered; ids run from 1 to that.
`AreCookiesCached` [#arecookiescached]
```cpp
virtual bool AreCookiesCached(CPlayerSlot slot) = 0;
```
Whether the player's values were loaded (or there is no database to load them from).
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`GetCookie` [#getcookie]
```cpp
virtual bool GetCookie(CPlayerSlot slot, Source2ModCookieId cookie, char* out, size_t maxlen) = 0;
```
The player's value.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------- |
| `slot` | `CPlayerSlot` |
| `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) |
| `out` | `char*` |
| `maxlen` | `size_t` |
**Returns** `bool` — false (and "") when the player has never had one, or the values are not loaded yet.
`SetCookie` [#setcookie]
```cpp
virtual bool SetCookie(CPlayerSlot slot, Source2ModCookieId cookie, const char* value) = 0;
```
Sets the player's value and saves it.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------- |
| `slot` | `CPlayerSlot` |
| `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) |
| `value` | `const char*` |
**Returns** `bool` — false for an empty slot, a bot, a bad id or a value longer than SOURCE2MOD\_COOKIE\_MAX\_VALUE\_LENGTH.
`GetCookieTime` [#getcookietime]
```cpp
virtual int64_t GetCookieTime(CPlayerSlot slot, Source2ModCookieId cookie) = 0;
```
When the value was last set, Unix time; 0 when it never was.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------- |
| `slot` | `CPlayerSlot` |
| `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) |
`SetCookieBySteamID` [#setcookiebysteamid]
```cpp
virtual bool SetCookieBySteamID(uint64 steamId, Source2ModCookieId cookie, const char* value) = 0;
```
Sets the value of a player who need not be connected.
| Parameter | Type | Description |
| --------- | ---------------------------------------------------------------------------- | ------------ |
| `steamId` | `uint64` | A SteamID64. |
| `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) | |
| `value` | `const char*` | |
`AddCookiesCachedListener` [#addcookiescachedlistener]
```cpp
virtual Source2ModCookiesListenerId AddCookiesCachedListener(CookiesCachedCallback callback) = 0;
```
Runs `callback` whenever a player's cookies were loaded. Goes with the plugin.
| Parameter | Type |
| ---------- | ---------------------------------------------------------------------------------- |
| `callback` | [`CookiesCachedCallback`](/docs/core-api/ISource2ModCookies#cookiescachedcallback) |
`RemoveCookiesCachedListener` [#removecookiescachedlistener]
```cpp
virtual bool RemoveCookiesCachedListener(Source2ModCookiesListenerId id) = 0;
```
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModCookiesListenerId`](/docs/core-api/ISource2ModCookies#source2modcookieslistenerid) |
Enums [#enums]
`Source2ModCookieAccess` [#source2modcookieaccess]
```cpp
enum Source2ModCookieAccess
```
Who may see and change a cookie from the cookie commands. SourceMod's CookieAccess.
| Name | Value | Description |
| ----------------------------- | ----- | -------------------------------------------- |
| `SOURCE2MOD_COOKIE_PUBLIC` | `0` | Players can read and change it. |
| `SOURCE2MOD_COOKIE_PROTECTED` | `1` | Players can read it, only plugins change it. |
| `SOURCE2MOD_COOKIE_PRIVATE` | `2` | Hidden from players. |
Macros [#macros]
`SOURCE2MOD_COOKIE_MAX_NAME_LENGTH` [#source2mod_cookie_max_name_length]
```cpp
#define SOURCE2MOD_COOKIE_MAX_NAME_LENGTH 30
```
Longest cookie name and value, in bytes without the terminator (SourceMod's).
# ISource2ModCustomHud (/docs/core-api/ISource2ModCustomHud)
`custom_hud_layout` entities render a panorama layout on top of the game HUD. The entity itself is driven through CCSCustomHudLayout; this interface only owns the click callbacks, because the button press arrives as a user message on the core's own hook and has to be routed back to whichever plugin created the layout.
Plugins do not normally touch this interface -- CCSCustomHudLayout's HookClick() forwards here for them.
Registry of custom HUD click callbacks.
Callbacks are keyed by layout entity, so several plugins may listen on their own layouts at once. Every callback a plugin registers is dropped when that plugin unloads, and the whole registry is cleared on level shutdown along with the entities it points at.
**Interface id** `ISource2ModCustomHud002` (`SOURCE2MOD_CUSTOMHUD_INTERFACE`) · **Global** `g_pSource2ModCustomHud` · **Header** [`source2mod/ISource2ModCustomHud.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModCustomHud.h)
Types [#types]
`Source2ModCustomHudHookId` [#source2modcustomhudhookid]
```cpp
using Source2ModCustomHudHookId = Source2ModHookId;
```
A custom HUD click hook (HookCustomHudClick).
`CustomHudClickHandler` [#customhudclickhandler]
```cpp
using CustomHudClickHandler = Source2ModCallback;
```
Callback type for custom HUD button clicks.
Methods [#methods]
`HookCustomHudClick` [#hookcustomhudclick]
```cpp
virtual Source2ModCustomHudHookId HookCustomHudClick(CCSCustomHudLayout* pLayout, CustomHudClickHandler handler) = 0;
```
Registers a click callback for one layout entity.
Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------------------------------ | ----------------------------------------------- |
| `pLayout` | [`CCSCustomHudLayout*`](/docs/schema/entity/classes/CCSCustomHudLayout) | Layout to listen on |
| `handler` | [`CustomHudClickHandler`](/docs/core-api/ISource2ModCustomHud#customhudclickhandler) | A function, an object and a method, or a lambda |
**Returns** [`Source2ModCustomHudHookId`](/docs/core-api/ISource2ModCustomHud#source2modcustomhudhookid) — The id UnhookCustomHudClick(id) takes
`UnhookCustomHudClick` [#unhookcustomhudclick]
```cpp
virtual bool UnhookCustomHudClick(CCSCustomHudLayout* pLayout, const CustomHudClickHandler& handler) = 0;
virtual bool UnhookCustomHudClick(Source2ModCustomHudHookId id) = 0;
virtual void UnhookCustomHudClick(CCSCustomHudLayout* pLayout) = 0;
```
Drops the hook with this handler -- a function or an object and a method; a lambda goes by its id.
Drops the hook HookCustomHudClick() returned the id for.
Drops every callback registered for a layout.
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------------------------- | --------------------------- |
| `pLayout` | [`CCSCustomHudLayout*`](/docs/schema/entity/classes/CCSCustomHudLayout) | Layout to stop listening on |
| `handler` | [`const CustomHudClickHandler&`](/docs/core-api/ISource2ModCustomHud#customhudclickhandler) | |
| `id` | [`Source2ModCustomHudHookId`](/docs/core-api/ISource2ModCustomHud#source2modcustomhudhookid) | |
**Returns** `bool` — true when one was found
Macros [#macros]
`HOOK_CUSTOM_HUD_CLICK` [#hook_custom_hud_click]
```cpp
#define HOOK_CUSTOM_HUD_CLICK(pLayout, handler) g_pSource2ModCustomHud->HookCustomHudClick(pLayout, handler)
```
Hook macros, matching HOOK\_GAME\_EVENT.
Other macros [#other-macros]
| Macro | Expands to |
| ---------------------------------- | ------------------------------------------------------- |
| `UNHOOK_CUSTOM_HUD_CLICK(pLayout)` | `g_pSource2ModCustomHud->UnhookCustomHudClick(pLayout)` |
# ISource2ModEntities (/docs/core-api/ISource2ModEntities)
Provides:
* Entity searching (classname, name, picker)
* Entity creation
* Input triggering
* Output listening
Entity I/O is the system used by maps to connect entities together.
Interface for interacting with entities and Entity I/O system.
**Interface id** `ISource2ModEntities002` (`SOURCE2MOD_ENTITIES_INTERFACE`) · **Global** `g_pSource2ModEntities` · **Header** [`source2mod/ISource2ModEntities.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModEntities.h)
Methods [#methods]
`GetGameRules` [#getgamerules]
```cpp
virtual CCSGameRules* GetGameRules() = 0;
```
The active CCSGameRules, or null before the entity exists.
Source2Mod captures this from cs\_gamerules as the entity is created, so it is live for the whole map and a plugin does not have to scan for the proxy itself.
`FindPickerEntity` [#findpickerentity]
```cpp
virtual CBaseEntity* FindPickerEntity(CBasePlayerController* pPlayer, CCSGameRules* pGameRules = nullptr) = 0;
```
Finds entity under player's crosshair.
| Parameter | Type | Default |
| ------------ | ----------------------------------------------------------------------------- | --------- |
| `pPlayer` | [`CBasePlayerController*`](/docs/schema/entity/classes/CBasePlayerController) | |
| `pGameRules` | [`CCSGameRules*`](/docs/schema/entity/classes/CCSGameRules) | `nullptr` |
`FindEntityByClassname` [#findentitybyclassname]
```cpp
virtual CBaseEntity* FindEntityByClassname(CEntityInstance* pStart, const char* name) = 0;
```
Finds entity by classname.
| Parameter | Type |
| --------- | ------------------ |
| `pStart` | `CEntityInstance*` |
| `name` | `const char*` |
`FindEntityByName` [#findentitybyname]
```cpp
virtual CBaseEntity* FindEntityByName(CEntityInstance* pStartEntity, const char* szName, CEntityInstance* pSearchingEntity = nullptr, CEntityInstance* pActivator = nullptr, CEntityInstance* pCaller = nullptr, IEntityFindFilter* pFilter = nullptr) = 0;
```
Finds entity by name.
| Parameter | Type | Default |
| ------------------ | -------------------- | --------- |
| `pStartEntity` | `CEntityInstance*` | |
| `szName` | `const char*` | |
| `pSearchingEntity` | `CEntityInstance*` | `nullptr` |
| `pActivator` | `CEntityInstance*` | `nullptr` |
| `pCaller` | `CEntityInstance*` | `nullptr` |
| `pFilter` | `IEntityFindFilter*` | `nullptr` |
`CreateEntityByName` [#createentitybyname]
```cpp
virtual CBaseEntity* CreateEntityByName(const char* pszClassName) = 0;
```
Creates entity by classname.
| Parameter | Type |
| -------------- | ------------- |
| `pszClassName` | `const char*` |
`AddEntityListener` [#addentitylistener]
```cpp
virtual void AddEntityListener(IEntityListener* pListener) = 0;
```
Adds listener for entity spawn, creation, deletion and parent change.
Removed for you if the owning plugin unloads. The listener is an object inside that plugin's library and the engine holds it directly, so one left behind is a call into unmapped memory.
| Parameter | Type | Description |
| ----------- | ------------------ | ----------------------------------------------------- |
| `pListener` | `IEntityListener*` | Listener object; the plugin whose class it is owns it |
`RemoveEntityListener` [#removeentitylistener]
```cpp
virtual void RemoveEntityListener(IEntityListener* pListener) = 0;
```
Removes listener for entity spawn, creation, deletion and parent change.
| Parameter | Type |
| ----------- | ------------------ |
| `pListener` | `IEntityListener*` |
`AcceptInput` [#acceptinput]
```cpp
virtual void AcceptInput(CEntityInstance* pTarget, const char* pszInput, CEntityInstance* pActivator, CEntityInstance* pCaller, const char* pszValue = "") = 0;
```
Sends input to entity.
Equivalent to Source engine AcceptInput()
| Parameter | Type | Default |
| ------------ | ------------------ | ------- |
| `pTarget` | `CEntityInstance*` | |
| `pszInput` | `const char*` | |
| `pActivator` | `CEntityInstance*` | |
| `pCaller` | `CEntityInstance*` | |
| `pszValue` | `const char*` | `""` |
`AddEntityIOEvent` [#addentityioevent]
```cpp
virtual void AddEntityIOEvent(CEntityInstance* pTarget, const char* pszInput, CEntityInstance* pActivator = nullptr, CEntityInstance* pCaller = nullptr, const char* pszValue = "", float flDelay = 0.0f) = 0;
```
Queues an entity I/O event.
| Parameter | Type | Default |
| ------------ | ------------------ | --------- |
| `pTarget` | `CEntityInstance*` | |
| `pszInput` | `const char*` | |
| `pActivator` | `CEntityInstance*` | `nullptr` |
| `pCaller` | `CEntityInstance*` | `nullptr` |
| `pszValue` | `const char*` | `""` |
| `flDelay` | `float` | `0.0f` |
`AddEntityIOListener` [#addentityiolistener]
```cpp
virtual void AddEntityIOListener(IEntityIOListener* pListener, const char* pchClassName, const char* pchOutputName, bool post = false) = 0;
```
Adds listener for entity outputs; the plugin whose class the listener is owns it.
Removed for you if the owning plugin unloads, for the same reason as AddEntityListener().
| Parameter | Type | Default |
| --------------- | --------------------------------------------------------------- | ------- |
| `pListener` | [`IEntityIOListener*`](/docs/schema/Entityio#ientityiolistener) | |
| `pchClassName` | `const char*` | |
| `pchOutputName` | `const char*` | |
| `post` | `bool` | `false` |
`RemoveEntityIOListener` [#removeentityiolistener]
```cpp
virtual void RemoveEntityIOListener(IEntityIOListener* pListener, const char* pchClassName, const char* pchOutputName, bool post = false) = 0;
```
Removes entity output listener.
| Parameter | Type | Default |
| --------------- | --------------------------------------------------------------- | ------- |
| `pListener` | [`IEntityIOListener*`](/docs/schema/Entityio#ientityiolistener) | |
| `pchClassName` | `const char*` | |
| `pchOutputName` | `const char*` | |
| `post` | `bool` | `false` |
Functions [#functions]
`UTIL_FindAllEntitiesByDesignerName` [#util_findallentitiesbydesignername]
```cpp
template inline std::vector UTIL_FindAllEntitiesByDesignerName(const char* pszDesignerName);
```
Every entity whose designer name matches, cast to T.
Walks the entity system's active list directly rather than going through the engine's finder, so it sees exactly the entities that are live right now and matches on m\_designerName -- the name the map gave the entity, which is not always the classname the engine reports.
```cpp
for (auto* pProp : UTIL_FindAllEntitiesByDesignerName("prop_dynamic"))
pProp->AcceptInput("Kill");
```
| Parameter | Type | Description |
| ----------------- | ------------- | -------------------------------- |
| `pszDesignerName` | `const char*` | Designer name to match, exactly. |
**Returns** `std::vector` — Matches in list order; empty if the entity system is not up yet.
Macros [#macros]
| Macro | Expands to |
| --------------------------------------------- | --------------------------------------------------------------------------- |
| `GET_GAME_RULES()` | `g_pSource2ModEntities->GetGameRules()` |
| `FIND_PICKER_ENTITY(player, ...)` | `g_pSource2ModEntities->FindPickerEntity(player, ##__VA_ARGS__)` |
| `FIND_ENTITY_BY_CLASSNAME(start, name)` | `g_pSource2ModEntities->FindEntityByClassname(start, name)` |
| `FIND_ENTITY_BY_NAME(start, name, ...)` | `g_pSource2ModEntities->FindEntityByName(start, name, ##__VA_ARGS__)` |
| `CREATE_ENTITY(classname)` | `g_pSource2ModEntities->CreateEntityByName(classname)` |
| `ADD_ENTITY_LISTENER(l)` | `g_pSource2ModEntities->AddEntityListener(l)` |
| `REMOVE_ENTITY_LISTENER(l)` | `g_pSource2ModEntities->RemoveEntityListener(l)` |
| `ACCEPT_INPUT(target, input, ...)` | `g_pSource2ModEntities->AcceptInput(target, input, ##__VA_ARGS__)` |
| `ADD_ENTITY_IO_EVENT(target, input, ...)` | `g_pSource2ModEntities->AddEntityIOEvent(target, input, ##__VA_ARGS__)` |
| `ADD_ENTITY_IO_LISTENER(l, cls, out, ...)` | `g_pSource2ModEntities->AddEntityIOListener(l, cls, out, ##__VA_ARGS__)` |
| `REMOVE_ENTITY_IO_LISTENER(l, cls, out, ...)` | `g_pSource2ModEntities->RemoveEntityIOListener(l, cls, out, ##__VA_ARGS__)` |
# ISource2ModEvents (/docs/core-api/ISource2ModEvents)
Provides functionality for hooking into Source2 game events, allowing plugins to inspect, modify, or block events.
Events correspond to engine-defined events (e.g. "player_death").
Interface for registering game event listeners.
Allows plugins to:
* Listen to engine events
* Modify event data
* Block event propagation
**Interface id** `ISource2ModEvents002` (`SOURCE2MOD_EVENTS_INTERFACE`) · **Global** `g_pSource2ModEvents` · **Header** [`source2mod/ISource2ModEvents.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModEvents.h)
Types [#types]
`Source2ModEventHookId` [#source2modeventhookid]
```cpp
using Source2ModEventHookId = Source2ModHookId;
```
A game event hook (HookGameEvent).
`GameEventHandler` [#gameeventhandler]
```cpp
using GameEventHandler = Source2ModCallback;
```
Callback type for game events.
Methods [#methods]
`HookGameEvent` [#hookgameevent]
```cpp
virtual Source2ModEventHookId HookGameEvent(const char* pchName, GameEventHandler handler, bool post) = 0;
```
Registers a listener for a game event. The same event may be hooked as often as a plugin likes; each handler runs.
Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it.
| Parameter | Type | Description |
| --------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `pchName` | `const char*` | Event name (e.g. "player\_death") |
| `handler` | [`GameEventHandler`](/docs/core-api/ISource2ModEvents#gameeventhandler) | A function, an object and a method, or a lambda |
| `post` | `bool` | false to be called before the engine processes the event, true to be called after |
**Returns** [`Source2ModEventHookId`](/docs/core-api/ISource2ModEvents#source2modeventhookid) — The id UnhookGameEvent(id) takes; 0 for an event the game does not have
`UnhookGameEvent` [#unhookgameevent]
```cpp
virtual bool UnhookGameEvent(const char* pchName, const GameEventHandler& handler, bool post) = 0;
virtual bool UnhookGameEvent(Source2ModEventHookId id) = 0;
```
Drops the hook with this handler -- a function or an object and a method; a lambda goes by its id.
Drops the hook HookGameEvent() returned the id for.
| Parameter | Type |
| --------- | --------------------------------------------------------------------------------- |
| `pchName` | `const char*` |
| `handler` | [`const GameEventHandler&`](/docs/core-api/ISource2ModEvents#gameeventhandler) |
| `post` | `bool` |
| `id` | [`Source2ModEventHookId`](/docs/core-api/ISource2ModEvents#source2modeventhookid) |
**Returns** `bool` — true when one was found
Macros [#macros]
`HOOK_GAME_EVENT` [#hook_game_event]
```cpp
#define HOOK_GAME_EVENT(passname, passfunc, post) g_pSource2ModEvents->HookGameEvent(passname, passfunc, post)
```
Macro for hooking a game event.
`UNHOOK_GAME_EVENT` [#unhook_game_event]
```cpp
#define UNHOOK_GAME_EVENT(passname, passfunc, post) g_pSource2ModEvents->UnhookGameEvent(passname, passfunc, post)
```
Macro for dropping a game event hook (a function or an object and a method; a lambda goes by the id HOOK\_GAME\_EVENT returned).
# ISource2ModFeatures (/docs/core-api/ISource2ModFeatures)
SourceMod's GetFeatureStatus, for both kinds of plugin: a native C++ plugin asks before it calls into another plugin's interface or relies on a part of the core that may be switched off (GeoIP without a database), a SourcePawn plugin asks the same through GetFeatureStatus() / Source2ModFeatures.
What can be asked about:
* a native -- a SourcePawn native, the core's own or one a plugin registered (CreateNative, ISource2ModNatives). A plugin native is Unavailable while its plugin is unloaded.
* a capability -- a named part of the core that can be missing at runtime; the names are the SOURCE2MOD\_CAPABILITY\_\* below.
* an interface -- an interface Source2ModFactory() would hand out: the core's (at the exact revision asked for) or one a plugin serves through OnSource2ModQuery.
* a library -- a name a plugin registered (RegPluginLibrary, ISource2ModNatives::RegisterLibrary).
**Interface id** `ISource2ModFeatures001` (`SOURCE2MOD_FEATURES_INTERFACE`) · **Global** `g_pSource2ModFeatures` · **Header** [`source2mod/ISource2ModFeatures.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModFeatures.h)
Methods [#methods]
`GetFeatureStatus` [#getfeaturestatus]
```cpp
virtual Source2ModFeatureStatus GetFeatureStatus(Source2ModFeatureType type, const char* name) = 0;
```
The status of a native, capability, interface or library.
| Parameter | Type | Description |
| --------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `type` | [`Source2ModFeatureType`](/docs/core-api/ISource2ModFeatures#source2modfeaturetype) | What `name` names. |
| `name` | `const char*` | The native, capability, interface ("ISource2ModMenus005") or library name. |
`IsNativeAvailable` [#isnativeavailable]
```cpp
virtual bool IsNativeAvailable(const char* name) = 0;
```
GetFeatureStatus(SOURCE2MOD\_FEATURE\_NATIVE, name) == AVAILABLE.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`IsInterfaceAvailable` [#isinterfaceavailable]
```cpp
virtual bool IsInterfaceAvailable(const char* iface) = 0;
```
Whether Source2ModFactory() would hand this interface out right now. Unlike asking the factory, a miss is not logged.
| Parameter | Type |
| --------- | ------------- |
| `iface` | `const char*` |
`IsLibraryAvailable` [#islibraryavailable]
```cpp
virtual bool IsLibraryAvailable(const char* name) = 0;
```
Whether some plugin registered this library and is still loaded.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`HasCapability` [#hascapability]
```cpp
virtual bool HasCapability(const char* name) = 0;
```
Whether a SOURCE2MOD\_CAPABILITY\_\* is usable.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
Enums [#enums]
`Source2ModFeatureType` [#source2modfeaturetype]
```cpp
enum Source2ModFeatureType
```
The same numbers as SourceMod's FeatureType, plus the two Source2Mod adds.
| Name | Value |
| ------------------------------- | ----- |
| `SOURCE2MOD_FEATURE_NATIVE` | `0` |
| `SOURCE2MOD_FEATURE_CAPABILITY` | `1` |
| `SOURCE2MOD_FEATURE_INTERFACE` | `2` |
| `SOURCE2MOD_FEATURE_LIBRARY` | `3` |
`Source2ModFeatureStatus` [#source2modfeaturestatus]
```cpp
enum Source2ModFeatureStatus
```
The same numbers as SourceMod's FeatureStatus.
| Name | Value | Description |
| -------------------------------- | ----- | ------------------------------------------------------------------------ |
| `SOURCE2MOD_FEATURE_AVAILABLE` | `0` | There, and usable. |
| `SOURCE2MOD_FEATURE_UNAVAILABLE` | `1` | Known, but not usable right now (its plugin unloaded, no database, ...). |
| `SOURCE2MOD_FEATURE_UNKNOWN` | `2` | Not known at all. |
Macros [#macros]
`SOURCE2MOD_CAPABILITY_SOURCEPAWN` [#source2mod_capability_sourcepawn]
```cpp
#define SOURCE2MOD_CAPABILITY_SOURCEPAWN "sourcepawn"
```
The SourcePawn VM: always there in this core.
`SOURCE2MOD_CAPABILITY_GEOIP` [#source2mod_capability_geoip]
```cpp
#define SOURCE2MOD_CAPABILITY_GEOIP "geoip"
```
GeoIP lookups: a GeoLite2 database was found and opened.
`SOURCE2MOD_CAPABILITY_CLIENTPREFS` [#source2mod_capability_clientprefs]
```cpp
#define SOURCE2MOD_CAPABILITY_CLIENTPREFS "clientprefs"
```
Client preferences: the cookie database is connected.
`SOURCE2MOD_CAPABILITY_GAMESYSTEMS` [#source2mod_capability_gamesystems]
```cpp
#define SOURCE2MOD_CAPABILITY_GAMESYSTEMS "gamesystems"
```
Registering game systems (ISource2ModGameSystems::IsAvailable()).
# ISource2ModFiles (/docs/core-api/ISource2ModFiles)
Paths that are not absolute are under the game folder (csgo), as in SourceMod; ResolvePath says where one lands. A plugin that only needs to read and write its own files can as well use the C++ library -- these exist so plugins and SourcePawn scripts share one notion of where things are and one KeyValues.
KeyValues3 (the format CS2's own files use) is not here: the engine's KV3 (tier1/keyvalues3.h in the SDK) reads and writes it.
**Interface id** `ISource2ModFiles001` (`SOURCE2MOD_FILES_INTERFACE`) · **Global** `g_pSource2ModFiles` · **Header** [`source2mod/ISource2ModFiles.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModFiles.h)
Types [#types]
`Source2ModDirCallback` [#source2moddircallback]
```cpp
using Source2ModDirCallback = std::function;
```
Called per directory entry; return false to stop.
Methods [#methods]
`ResolvePath` [#resolvepath]
```cpp
virtual std::string ResolvePath(const char* path) = 0;
```
A path as the other calls use it: absolute as given, else under the game folder.
| Parameter | Type |
| --------- | ------------- |
| `path` | `const char*` |
`FileExists` [#fileexists]
```cpp
virtual bool FileExists(const char* path) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `path` | `const char*` |
`DirExists` [#direxists]
```cpp
virtual bool DirExists(const char* path) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `path` | `const char*` |
`FileSize` [#filesize]
```cpp
virtual int64_t FileSize(const char* path) = 0;
```
-1 when there is no such file.
| Parameter | Type |
| --------- | ------------- |
| `path` | `const char*` |
`FileTime` [#filetime]
```cpp
virtual int64_t FileTime(const char* path) = 0;
```
Seconds since 1970 of the last change; -1 when there is no such file.
| Parameter | Type |
| --------- | ------------- |
| `path` | `const char*` |
`ReadFile` [#readfile]
```cpp
virtual bool ReadFile(const char* path, std::string& contents) = 0;
```
The whole file. @return false when it cannot be read.
| Parameter | Type |
| ---------- | -------------- |
| `path` | `const char*` |
| `contents` | `std::string&` |
`WriteFile` [#writefile]
```cpp
virtual bool WriteFile(const char* path, const void* data, size_t size, bool append = false) = 0;
```
Creates or replaces (or appends to) a file; missing folders are made.
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `path` | `const char*` | |
| `data` | `const void*` | |
| `size` | `size_t` | |
| `append` | `bool` | `false` |
`DeleteFile` [#deletefile]
```cpp
virtual bool DeleteFile(const char* path) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `path` | `const char*` |
`RenameFile` [#renamefile]
```cpp
virtual bool RenameFile(const char* from, const char* to) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `from` | `const char*` |
| `to` | `const char*` |
`CreateDirectory` [#createdirectory]
```cpp
virtual bool CreateDirectory(const char* path) = 0;
```
Makes a folder and those above it.
| Parameter | Type |
| --------- | ------------- |
| `path` | `const char*` |
`RemoveDirectory` [#removedirectory]
```cpp
virtual bool RemoveDirectory(const char* path) = 0;
```
Removes an empty folder.
| Parameter | Type |
| --------- | ------------- |
| `path` | `const char*` |
`ListDirectory` [#listdirectory]
```cpp
virtual bool ListDirectory(const char* path, Source2ModDirCallback callback) = 0;
```
The entries of a folder, without "." and "..". @return false when it cannot be read.
| Parameter | Type |
| ---------- | -------------------------------------------------------------------------------- |
| `path` | `const char*` |
| `callback` | [`Source2ModDirCallback`](/docs/core-api/ISource2ModFiles#source2moddircallback) |
`CreateKeyValues` [#createkeyvalues]
```cpp
virtual ISource2ModKeyValues* CreateKeyValues(const char* name) = 0;
```
A new KeyValues tree with an empty root named @p name; Release() it.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`ParseKeyValues` [#parsekeyvalues]
```cpp
virtual ESource2ModKvError ParseKeyValues(const char* text, ISource2ModKvListener* listener, int* line = nullptr, int* col = nullptr, bool escapes = true) = 0;
```
Reads KeyValues text, calling @p listener per event.
| Parameter | Type | Default |
| ---------- | --------------------------------------------------------------------------------- | --------- |
| `text` | `const char*` | |
| `listener` | [`ISource2ModKvListener*`](/docs/core-api/ISource2ModFiles#isource2modkvlistener) | |
| `line` | `int*` | `nullptr` |
| `col` | `int*` | `nullptr` |
| `escapes` | `bool` | `true` |
`ParseKeyValuesFile` [#parsekeyvaluesfile]
```cpp
virtual ESource2ModKvError ParseKeyValuesFile(const char* path, ISource2ModKvListener* listener, int* line = nullptr, int* col = nullptr, bool escapes = true) = 0;
```
| Parameter | Type | Default |
| ---------- | --------------------------------------------------------------------------------- | --------- |
| `path` | `const char*` | |
| `listener` | [`ISource2ModKvListener*`](/docs/core-api/ISource2ModFiles#isource2modkvlistener) | |
| `line` | `int*` | `nullptr` |
| `col` | `int*` | `nullptr` |
| `escapes` | `bool` | `true` |
`KvErrorString` [#kverrorstring]
```cpp
virtual const char* KvErrorString(ESource2ModKvError error) = 0;
```
What an error means.
| Parameter | Type |
| --------- | -------------------------------------------------------------------------- |
| `error` | [`ESource2ModKvError`](/docs/core-api/ISource2ModFiles#esource2modkverror) |
`ISource2ModKeyValues` [#isource2modkeyvalues]
One key of a KeyValues tree: a section with subkeys, or a value.
Key arguments may be paths ("a/b/c"); names compare case-insensitively. A null or empty key means this key itself. Values convert on read (a string "5" reads as 5, an int as "5"). Pointers to keys stay valid until that key or one above it is removed, or the root is released.
`GetName` [#getname]
```cpp
virtual const char* GetName() = 0;
```
`SetName` [#setname]
```cpp
virtual void SetName(const char* name) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`FindKey` [#findkey]
```cpp
virtual ISource2ModKeyValues* FindKey(const char* key, bool create = false) = 0;
```
A key under this one; nullptr when it is not there and @p create is false.
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `key` | `const char*` | |
| `create` | `bool` | `false` |
`GetFirstSubKey` [#getfirstsubkey]
```cpp
virtual ISource2ModKeyValues* GetFirstSubKey() = 0;
```
The first / next key of any kind.
`GetNextKey` [#getnextkey]
```cpp
virtual ISource2ModKeyValues* GetNextKey() = 0;
```
`GetFirstTrueSubKey` [#getfirsttruesubkey]
```cpp
virtual ISource2ModKeyValues* GetFirstTrueSubKey() = 0;
```
The first / next key that is a section.
`GetNextTrueSubKey` [#getnexttruesubkey]
```cpp
virtual ISource2ModKeyValues* GetNextTrueSubKey() = 0;
```
`GetFirstValue` [#getfirstvalue]
```cpp
virtual ISource2ModKeyValues* GetFirstValue() = 0;
```
The first / next key that is a value.
`GetNextValue` [#getnextvalue]
```cpp
virtual ISource2ModKeyValues* GetNextValue() = 0;
```
`GetDataType` [#getdatatype]
```cpp
virtual ESource2ModKvType GetDataType(const char* key = nullptr) = 0;
```
| Parameter | Type | Default |
| --------- | ------------- | --------- |
| `key` | `const char*` | `nullptr` |
`GetString` [#getstring]
```cpp
virtual const char* GetString(const char* key = nullptr, const char* def = "") = 0;
```
The value as text; valid until the key changes. @p def when there is none.
| Parameter | Type | Default |
| --------- | ------------- | --------- |
| `key` | `const char*` | `nullptr` |
| `def` | `const char*` | `""` |
`GetInt` [#getint]
```cpp
virtual int GetInt(const char* key = nullptr, int def = 0) = 0;
```
| Parameter | Type | Default |
| --------- | ------------- | --------- |
| `key` | `const char*` | `nullptr` |
| `def` | `int` | `0` |
`GetFloat` [#getfloat]
```cpp
virtual float GetFloat(const char* key = nullptr, float def = 0.0f) = 0;
```
| Parameter | Type | Default |
| --------- | ------------- | --------- |
| `key` | `const char*` | `nullptr` |
| `def` | `float` | `0.0f` |
`GetUint64` [#getuint64]
```cpp
virtual uint64_t GetUint64(const char* key = nullptr, uint64_t def = 0) = 0;
```
| Parameter | Type | Default |
| --------- | ------------- | --------- |
| `key` | `const char*` | `nullptr` |
| `def` | `uint64_t` | `0` |
`GetColor` [#getcolor]
```cpp
virtual uint32_t GetColor(const char* key = nullptr, uint32_t def = 0) = 0;
```
r | g \<\< 8 | b \<\< 16 | a \<\< 24 (a "r g b a" string reads too).
| Parameter | Type | Default |
| --------- | ------------- | --------- |
| `key` | `const char*` | `nullptr` |
| `def` | `uint32_t` | `0` |
`SetString` [#setstring]
```cpp
virtual void SetString(const char* key, const char* value) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `key` | `const char*` |
| `value` | `const char*` |
`SetInt` [#setint]
```cpp
virtual void SetInt(const char* key, int value) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `key` | `const char*` |
| `value` | `int` |
`SetFloat` [#setfloat]
```cpp
virtual void SetFloat(const char* key, float value) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `key` | `const char*` |
| `value` | `float` |
`SetUint64` [#setuint64]
```cpp
virtual void SetUint64(const char* key, uint64_t value) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `key` | `const char*` |
| `value` | `uint64_t` |
`SetColor` [#setcolor]
```cpp
virtual void SetColor(const char* key, uint32_t rgba) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `key` | `const char*` |
| `rgba` | `uint32_t` |
`RemoveKey` [#removekey]
```cpp
virtual bool RemoveKey(const char* key) = 0;
```
Removes a key under this one. @return false when it was not there.
| Parameter | Type |
| --------- | ------------- |
| `key` | `const char*` |
`CopySubkeys` [#copysubkeys]
```cpp
virtual void CopySubkeys(ISource2ModKeyValues* from) = 0;
```
Copies @p from's subkeys into this one; Merge replaces keys of the same name.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------- |
| `from` | [`ISource2ModKeyValues*`](/docs/core-api/ISource2ModFiles#isource2modkeyvalues) |
`Merge` [#merge]
```cpp
virtual void Merge(ISource2ModKeyValues* from) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------- |
| `from` | [`ISource2ModKeyValues*`](/docs/core-api/ISource2ModFiles#isource2modkeyvalues) |
`Clone` [#clone]
```cpp
virtual ISource2ModKeyValues* Clone() = 0;
```
A detached copy of this key and everything under it; Release() it.
`LoadFromFile` [#loadfromfile]
```cpp
virtual bool LoadFromFile(const char* path, bool escapes = false) = 0;
```
Replaces this key with the first section of a file / text (its name and subkeys). @return false on a missing file or bad text.
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `path` | `const char*` | |
| `escapes` | `bool` | `false` |
`LoadFromString` [#loadfromstring]
```cpp
virtual bool LoadFromString(const char* text, bool escapes = false) = 0;
```
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `text` | `const char*` | |
| `escapes` | `bool` | `false` |
`SaveToFile` [#savetofile]
```cpp
virtual bool SaveToFile(const char* path, bool escapes = false) = 0;
```
Writes this key and everything under it in Valve's layout.
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `path` | `const char*` | |
| `escapes` | `bool` | `false` |
`SaveToString` [#savetostring]
```cpp
virtual size_t SaveToString(char* buffer, size_t maxlen, bool escapes = false) = 0;
```
| Parameter | Type | Default |
| --------- | -------- | ------- |
| `buffer` | `char*` | |
| `maxlen` | `size_t` | |
| `escapes` | `bool` | `false` |
**Returns** `size_t` — The text's length; at most @p maxlen - 1 bytes are written.
`Release` [#release]
```cpp
virtual void Release() = 0;
```
Frees a tree from CreateKeyValues or Clone (only a root).
`ISource2ModKvListener` [#isource2modkvlistener]
Gets the reader's events in order; return HALT to stop early.
`OnStart` [#onstart]
```cpp
virtual void OnStart();
```
`OnEnterSection` [#onentersection]
```cpp
virtual ESource2ModKvParseResult OnEnterSection(const char* name, bool quoted);
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
| `quoted` | `bool` |
`OnKeyValue` [#onkeyvalue]
```cpp
virtual ESource2ModKvParseResult OnKeyValue(const char* key, const char* value, bool keyQuoted, bool valueQuoted);
```
| Parameter | Type |
| ------------- | ------------- |
| `key` | `const char*` |
| `value` | `const char*` |
| `keyQuoted` | `bool` |
| `valueQuoted` | `bool` |
`OnLeaveSection` [#onleavesection]
```cpp
virtual ESource2ModKvParseResult OnLeaveSection();
```
`OnRawLine` [#onrawline]
```cpp
virtual ESource2ModKvParseResult OnRawLine(const char* line, int lineNumber);
```
| Parameter | Type |
| ------------ | ------------- |
| `line` | `const char*` |
| `lineNumber` | `int` |
`OnEnd` [#onend]
```cpp
virtual void OnEnd(bool halted, bool failed);
```
| Parameter | Type |
| --------- | ------ |
| `halted` | `bool` |
| `failed` | `bool` |
Enums [#enums]
`ESource2ModKvType` [#esource2modkvtype]
```cpp
enum ESource2ModKvType
```
What a key holds (SourceMod's KvDataTypes numbering).
| Name | Value | Description |
| ---------------------- | ----- | ---------------------------- |
| `SOURCE2MOD_KV_NONE` | `0` | a section: subkeys, no value |
| `SOURCE2MOD_KV_STRING` | `1` | |
| `SOURCE2MOD_KV_INT` | `2` | |
| `SOURCE2MOD_KV_FLOAT` | `3` | |
| `SOURCE2MOD_KV_COLOR` | `6` | |
| `SOURCE2MOD_KV_UINT64` | `7` | |
`ESource2ModKvParseResult` [#esource2modkvparseresult]
```cpp
enum ESource2ModKvParseResult
```
| Name | Value | Description |
| ------------------------------ | ----- | ------------------------------------- |
| `SOURCE2MOD_KVPARSE_CONTINUE` | `0` | |
| `SOURCE2MOD_KVPARSE_HALT` | | stop, not an error |
| `SOURCE2MOD_KVPARSE_HALT_FAIL` | | stop with SOURCE2MOD\_KVERROR\_CUSTOM |
`ESource2ModKvError` [#esource2modkverror]
```cpp
enum ESource2ModKvError
```
SourceMod's SMCError numbering.
| Name | Value | Description |
| -------------------------------------- | ----- | ------------------------------------ |
| `SOURCE2MOD_KVERROR_OK` | `0` | |
| `SOURCE2MOD_KVERROR_STREAM_OPEN` | | |
| `SOURCE2MOD_KVERROR_STREAM_ERROR` | | |
| `SOURCE2MOD_KVERROR_CUSTOM` | | |
| `SOURCE2MOD_KVERROR_INVALID_SECTION1` | | |
| `SOURCE2MOD_KVERROR_INVALID_SECTION2` | | \{ without a name |
| `SOURCE2MOD_KVERROR_INVALID_SECTION3` | | a lone token before } |
| `SOURCE2MOD_KVERROR_INVALID_SECTION4` | | } without a section |
| `SOURCE2MOD_KVERROR_INVALID_SECTION5` | | a section not closed |
| `SOURCE2MOD_KVERROR_INVALID_TOKENS` | | an unterminated string, a lone token |
| `SOURCE2MOD_KVERROR_TOKEN_OVERFLOW` | | |
| `SOURCE2MOD_KVERROR_INVALID_PROPERTY1` | | a key/value outside any section |
# ISource2ModGameConfig (/docs/core-api/ISource2ModGameConfig)
Provides access to external configuration data used for:
* Signature scanning
* Symbol resolution
* Memory patching
* Offset lookup
Typically backed by external config files (JSON, KV, etc.).
Interface for accessing game configuration entries.
**Interface id** `ISource2ModGameConfig002` (`SOURCE2MOD_GAMECONFIG_INTERFACE`) · **Global** `g_pSource2ModGameConfig` · **Header** [`source2mod/ISource2ModGameConfig.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModGameConfig.h)
Methods [#methods]
`GetLibrary` [#getlibrary]
```cpp
virtual const char* GetLibrary(const char* pchName) = 0;
```
Gets library/module name.
| Parameter | Type | Description |
| --------- | ------------- | ----------------- |
| `pchName` | `const char*` | Config entry name |
**Returns** `const char*` — Library name
`GetSignature` [#getsignature]
```cpp
virtual const char* GetSignature(const char* pchName) = 0;
```
Gets signature (pattern).
| Parameter | Type | Description |
| --------- | ------------- | ----------------- |
| `pchName` | `const char*` | Config entry name |
**Returns** `const char*` — Pattern string
`GetSymbol` [#getsymbol]
```cpp
virtual const char* GetSymbol(const char* pchName) = 0;
```
Gets symbol name.
| Parameter | Type | Description |
| --------- | ------------- | ----------------- |
| `pchName` | `const char*` | Config entry name |
**Returns** `const char*` — Symbol string
`GetPatch` [#getpatch]
```cpp
virtual const char* GetPatch(const char* pchName) = 0;
```
Gets patch definition.
| Parameter | Type | Description |
| --------- | ------------- | ----------------- |
| `pchName` | `const char*` | Config entry name |
**Returns** `const char*` — Patch data
`GetOffset` [#getoffset]
```cpp
virtual int GetOffset(const char* pchName) = 0;
```
Gets integer offset.
| Parameter | Type | Description |
| --------- | ------------- | ----------------- |
| `pchName` | `const char*` | Config entry name |
**Returns** `int` — Offset value
`IsSymbol` [#issymbol]
```cpp
virtual bool IsSymbol(const char* pchName) = 0;
```
Whether the entry is a symbol name rather than a byte pattern.
Symbol entries are written into the signature field with a leading "@".
| Parameter | Type |
| --------- | ------------- |
| `pchName` | `const char*` |
`ResolveSignature` [#resolvesignature]
```cpp
virtual ISource2ModMemory ResolveSignature(const char* pchName) = 0;
```
Resolves a gamedata entry to an address.
Looks the entry's "library" up among the loaded modules -- engine2, server, vscript, tier0 -- then finds it by exported symbol when the entry is "@"-prefixed, or by pattern scan otherwise. Saves a plugin repeating the module lookup and the symbol/pattern branch for every signature.
| Parameter | Type | Description |
| --------- | ------------- | -------------------- |
| `pchName` | `const char*` | Gamedata entry name. |
**Returns** [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) — The address, or a null ISource2ModMemory when the module, symbol or pattern is missing. Converts to void\* on its own; use RCast\() for a typed function pointer.
Macros [#macros]
| Macro | Expands to |
| ---------------------------- | ------------------------------------------------- |
| `GAMECONFIG_LIBRARY(name)` | `g_pSource2ModGameConfig->GetLibrary(name)` |
| `GAMECONFIG_SIGNATURE(name)` | `g_pSource2ModGameConfig->GetSignature(name)` |
| `GAMECONFIG_SYMBOL(name)` | `g_pSource2ModGameConfig->GetSymbol(name)` |
| `GAMECONFIG_PATCH(name)` | `g_pSource2ModGameConfig->GetPatch(name)` |
| `GAMECONFIG_OFFSET(name)` | `g_pSource2ModGameConfig->GetOffset(name)` |
| `GAMECONFIG_RESOLVE(name)` | `g_pSource2ModGameConfig->ResolveSignature(name)` |
# ISource2ModGameHooks (/docs/core-api/ISource2ModGameHooks)
One detour per function, placed by the core from its gamedata the first time a plugin asks for it and taken out again once the last listener is gone -- a function nobody listens to is not hooked at all. A plugin registers a handler and never touches KHook, a signature or a prototype. When Valve changes one -- an argument added, the order swapped between platforms, as GroundAccelerate's is -- the core's gamedata and its one call site change, and the plugins keep running. Anything not in this set is still a KHOOK\_MEMBER / KHOOK\_VIRTUAL of the plugin's own.
Every hook hands its handler a context: the hooked object, the arguments, and `result` where the function returns something. What the handler returns is the usual Action:
* Ignore -- nothing changed.
* Override -- pre: the original still runs but the function returns `ctx.result`; post: the function returns `ctx.result` instead of what the original produced.
* Supersede -- pre only: the original does not run, the function returns `ctx.result`. Stops the remaining pre handlers.
Arguments passed by pointer (the damage info, the move data, the weapon) can be changed in place and the original sees the change; arguments passed by value (a speed, a flag) cannot -- a handler that needs that keeps a raw KHOOK\_MEMBER of its own. Handlers run in registration order, pre before the original and post after it; in post, `result` holds what the original returned.
HookX() returns a Source2ModGameHookId for UnhookX(); a plugin may have several handlers on one function. The handlers are the plugin's until it unhooks them or unloads, whichever comes first; nothing to undo in Unload().
Whose a handler is, the core reads off the handler (see Source2ModCallback): no call takes a plugin id. What a plugin still holds at unload the core drops for it.
**Global** `g_pSource2ModGameHooks` · **Header** [`source2mod/ISource2ModGameHooks.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModGameHooks.h)
Types [#types]
`GameHookHandler` [#gamehookhandler]
```cpp
template using GameHookHandler = Source2ModCallback(CONTEXT& ctx, bool post)>;
```
A handler: the context, and whether this is the post pass. A function, an object and a method, or a lambda (Source2ModCallback).
`Source2ModGameHookId` [#source2modgamehookid]
```cpp
using Source2ModGameHookId = Source2ModHookId;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
Methods [#methods]
`HookTakeDamage` [#hooktakedamage]
```cpp
virtual Source2ModGameHookId HookTakeDamage(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCanAcquire` [#hookcanacquire]
```cpp
virtual Source2ModGameHookId HookCanAcquire(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCanMove` [#hookcanmove]
```cpp
virtual Source2ModGameHookId HookCanMove(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCanUse` [#hookcanuse]
```cpp
virtual Source2ModGameHookId HookCanUse(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | --------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookPostThink` [#hookpostthink]
```cpp
virtual Source2ModGameHookId HookPostThink(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------ |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookProcessUsercmds` [#hookprocessusercmds]
```cpp
virtual Source2ModGameHookId HookProcessUsercmds(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookSimulateUserCommands` [#hooksimulateusercommands]
```cpp
virtual Source2ModGameHookId HookSimulateUserCommands(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookRunCommand` [#hookruncommand]
```cpp
virtual Source2ModGameHookId HookRunCommand(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookAcceptInput` [#hookacceptinput]
```cpp
virtual Source2ModGameHookId HookAcceptInput(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookTouch` [#hooktouch]
```cpp
virtual Source2ModGameHookId HookTouch(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookDropWeapon` [#hookdropweapon]
```cpp
virtual Source2ModGameHookId HookDropWeapon(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookAirAccelerate` [#hookairaccelerate]
```cpp
virtual Source2ModGameHookId HookAirAccelerate(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookAirMove` [#hookairmove]
```cpp
virtual Source2ModGameHookId HookAirMove(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCanUnduck` [#hookcanunduck]
```cpp
virtual Source2ModGameHookId HookCanUnduck(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCategorizePosition` [#hookcategorizeposition]
```cpp
virtual Source2ModGameHookId HookCategorizePosition(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | --------------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCheckFalling` [#hookcheckfalling]
```cpp
virtual Source2ModGameHookId HookCheckFalling(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCheckParameters` [#hookcheckparameters]
```cpp
virtual Source2ModGameHookId HookCheckParameters(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCheckVelocity` [#hookcheckvelocity]
```cpp
virtual Source2ModGameHookId HookCheckVelocity(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCheckWater` [#hookcheckwater]
```cpp
virtual Source2ModGameHookId HookCheckWater(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookDuck` [#hookduck]
```cpp
virtual Source2ModGameHookId HookDuck(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookFriction` [#hookfriction]
```cpp
virtual Source2ModGameHookId HookFriction(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookFullWalkMove` [#hookfullwalkmove]
```cpp
virtual Source2ModGameHookId HookFullWalkMove(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | --------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookGroundAccelerate` [#hookgroundaccelerate]
```cpp
virtual Source2ModGameHookId HookGroundAccelerate(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookLadderMove` [#hookladdermove]
```cpp
virtual Source2ModGameHookId HookLadderMove(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookMoveInit` [#hookmoveinit]
```cpp
virtual Source2ModGameHookId HookMoveInit(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookPlayerMove` [#hookplayermove]
```cpp
virtual Source2ModGameHookId HookPlayerMove(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookProcessMovement` [#hookprocessmovement]
```cpp
virtual Source2ModGameHookId HookProcessMovement(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookSetupMove` [#hooksetupmove]
```cpp
virtual Source2ModGameHookId HookSetupMove(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------ |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookTryPlayerMove` [#hooktryplayermove]
```cpp
virtual Source2ModGameHookId HookTryPlayerMove(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookWalkMove` [#hookwalkmove]
```cpp
virtual Source2ModGameHookId HookWalkMove(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookWaterMove` [#hookwatermove]
```cpp
virtual Source2ModGameHookId HookWaterMove(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookOnJumpLegacy` [#hookonjumplegacy]
```cpp
virtual Source2ModGameHookId HookOnJumpLegacy(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookOnJumpModern` [#hookonjumpmodern]
```cpp
virtual Source2ModGameHookId HookOnJumpModern(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCheckJumpButtonLegacy` [#hookcheckjumpbuttonlegacy]
```cpp
virtual Source2ModGameHookId HookCheckJumpButtonLegacy(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`HookCheckJumpButtonModern` [#hookcheckjumpbuttonmodern]
```cpp
virtual Source2ModGameHookId HookCheckJumpButtonModern(GameHookHandler handler, bool post) = 0;
```
What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------- |
| `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookTakeDamage` [#unhooktakedamage]
```cpp
virtual bool UnhookTakeDamage(Source2ModGameHookId id) = 0;
virtual bool UnhookTakeDamage(const GameHookHandler& handler, bool post) = 0;
```
Drops a handler: the one HookX() returned the id for, or the one that is this function or object and method (a lambda goes by its id). True when it was found.
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookCanAcquire` [#unhookcanacquire]
```cpp
virtual bool UnhookCanAcquire(Source2ModGameHookId id) = 0;
virtual bool UnhookCanAcquire(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookCanMove` [#unhookcanmove]
```cpp
virtual bool UnhookCanMove(Source2ModGameHookId id) = 0;
virtual bool UnhookCanMove(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookCanUse` [#unhookcanuse]
```cpp
virtual bool UnhookCanUse(Source2ModGameHookId id) = 0;
virtual bool UnhookCanUse(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookPostThink` [#unhookpostthink]
```cpp
virtual bool UnhookPostThink(Source2ModGameHookId id) = 0;
virtual bool UnhookPostThink(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookProcessUsercmds` [#unhookprocessusercmds]
```cpp
virtual bool UnhookProcessUsercmds(Source2ModGameHookId id) = 0;
virtual bool UnhookProcessUsercmds(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookSimulateUserCommands` [#unhooksimulateusercommands]
```cpp
virtual bool UnhookSimulateUserCommands(Source2ModGameHookId id) = 0;
virtual bool UnhookSimulateUserCommands(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookRunCommand` [#unhookruncommand]
```cpp
virtual bool UnhookRunCommand(Source2ModGameHookId id) = 0;
virtual bool UnhookRunCommand(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookAcceptInput` [#unhookacceptinput]
```cpp
virtual bool UnhookAcceptInput(Source2ModGameHookId id) = 0;
virtual bool UnhookAcceptInput(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | --------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookTouch` [#unhooktouch]
```cpp
virtual bool UnhookTouch(Source2ModGameHookId id) = 0;
virtual bool UnhookTouch(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | --------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookDropWeapon` [#unhookdropweapon]
```cpp
virtual bool UnhookDropWeapon(Source2ModGameHookId id) = 0;
virtual bool UnhookDropWeapon(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookAirAccelerate` [#unhookairaccelerate]
```cpp
virtual bool UnhookAirAccelerate(Source2ModGameHookId id) = 0;
virtual bool UnhookAirAccelerate(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookAirMove` [#unhookairmove]
```cpp
virtual bool UnhookAirMove(Source2ModGameHookId id) = 0;
virtual bool UnhookAirMove(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookCanUnduck` [#unhookcanunduck]
```cpp
virtual bool UnhookCanUnduck(Source2ModGameHookId id) = 0;
virtual bool UnhookCanUnduck(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookCategorizePosition` [#unhookcategorizeposition]
```cpp
virtual bool UnhookCategorizePosition(Source2ModGameHookId id) = 0;
virtual bool UnhookCategorizePosition(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookCheckFalling` [#unhookcheckfalling]
```cpp
virtual bool UnhookCheckFalling(Source2ModGameHookId id) = 0;
virtual bool UnhookCheckFalling(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookCheckParameters` [#unhookcheckparameters]
```cpp
virtual bool UnhookCheckParameters(Source2ModGameHookId id) = 0;
virtual bool UnhookCheckParameters(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookCheckVelocity` [#unhookcheckvelocity]
```cpp
virtual bool UnhookCheckVelocity(Source2ModGameHookId id) = 0;
virtual bool UnhookCheckVelocity(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookCheckWater` [#unhookcheckwater]
```cpp
virtual bool UnhookCheckWater(Source2ModGameHookId id) = 0;
virtual bool UnhookCheckWater(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookDuck` [#unhookduck]
```cpp
virtual bool UnhookDuck(Source2ModGameHookId id) = 0;
virtual bool UnhookDuck(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookFriction` [#unhookfriction]
```cpp
virtual bool UnhookFriction(Source2ModGameHookId id) = 0;
virtual bool UnhookFriction(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookFullWalkMove` [#unhookfullwalkmove]
```cpp
virtual bool UnhookFullWalkMove(Source2ModGameHookId id) = 0;
virtual bool UnhookFullWalkMove(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookGroundAccelerate` [#unhookgroundaccelerate]
```cpp
virtual bool UnhookGroundAccelerate(Source2ModGameHookId id) = 0;
virtual bool UnhookGroundAccelerate(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookLadderMove` [#unhookladdermove]
```cpp
virtual bool UnhookLadderMove(Source2ModGameHookId id) = 0;
virtual bool UnhookLadderMove(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookMoveInit` [#unhookmoveinit]
```cpp
virtual bool UnhookMoveInit(Source2ModGameHookId id) = 0;
virtual bool UnhookMoveInit(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookPlayerMove` [#unhookplayermove]
```cpp
virtual bool UnhookPlayerMove(Source2ModGameHookId id) = 0;
virtual bool UnhookPlayerMove(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookProcessMovement` [#unhookprocessmovement]
```cpp
virtual bool UnhookProcessMovement(Source2ModGameHookId id) = 0;
virtual bool UnhookProcessMovement(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookSetupMove` [#unhooksetupmove]
```cpp
virtual bool UnhookSetupMove(Source2ModGameHookId id) = 0;
virtual bool UnhookSetupMove(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookTryPlayerMove` [#unhooktryplayermove]
```cpp
virtual bool UnhookTryPlayerMove(Source2ModGameHookId id) = 0;
virtual bool UnhookTryPlayerMove(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookWalkMove` [#unhookwalkmove]
```cpp
virtual bool UnhookWalkMove(Source2ModGameHookId id) = 0;
virtual bool UnhookWalkMove(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookWaterMove` [#unhookwatermove]
```cpp
virtual bool UnhookWaterMove(Source2ModGameHookId id) = 0;
virtual bool UnhookWaterMove(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookOnJumpLegacy` [#unhookonjumplegacy]
```cpp
virtual bool UnhookOnJumpLegacy(Source2ModGameHookId id) = 0;
virtual bool UnhookOnJumpLegacy(const GameHookHandler& handler, bool post) = 0;
```
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) |
| `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) |
| `post` | `bool` |
`UnhookOnJumpModern` [#unhookonjumpmodern]
```cpp
virtual bool UnhookOnJumpModern(Source2ModGameHookId id) = 0;
virtual bool UnhookOnJumpModern(const GameHookHandler