# 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** — one API for C++ and SourcePawn: typed hook points (game functions and engine callbacks), and any function by gamedata entry, address, signature or vtable slot, on one entity, a class or every call; raw KHook for C++ plugins that need it.
* **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, function 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
```
The answers are tables. `s2m list` on a server with two native plugins and a
script:
```
+----------------------------------------------------+
| 3 plugin(s) loaded |
+----+---------------+---------+--------+------------+
| Id | Name | Version | Author | Type |
+----+---------------+---------+--------+------------+
| 1 | Admin | 1.0.0 | Slynx | Native |
| 2 | Sample Plugin | 1.0.0 | Slynx | Native |
| 3 | Hello | 1.0.0 | You | SourcePawn |
+----+---------------+---------+--------+------------+
```
`s2m` on its own prints the banner and the list of subcommands:
```
+----------------------------------------------------------------------------------------------+
| Source2Mod commands |
+---------------+-----------------------------------+------------------------------------------+
| Command | Arguments | Description |
+---------------+-----------------------------------+------------------------------------------+
| s2m list | | Loaded plugins |
| s2m load | | Load a plugin from plugins/ |
| s2m unload | | Unload a plugin |
| s2m info | | Everything about one plugin |
| s2m refresh | | Load the plugins that are not loaded yet |
| s2m hookdebug | | Log every call of matching game hooks |
| s2m admins | > | Players' permissions |
| s2m version | | Build, API and Metamod versions |
| s2m credits | | Who made Source2Mod |
+---------------+-----------------------------------+------------------------------------------+
```
| Command | What it does |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `s2m list` | Loaded plugins: id, name, version, author and type — *Native* (C++) or *SourcePawn*. |
| `s2m info ` | Name, version, author, description, type, file path, plugin API, KHook commit, how many KHook hooks of its own it declared — with its tier, *stable* (none: a game or KHook change needs only a core update) or *raw* (rebuild) — and every Source2Mod interface (with revision) it asked for. See [Compatibility](/docs/development/compatibility). |
| `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 hookdebug ` | Logs every call of the hook points whose name contains `` (`TakeDamage`, ...): each plugin's answer, the outcome and the return value. `off` stops it; without an argument it says what is on. |
| `s2m admins [list\|reload\|info ]` | The connected players' permissions (`list`, the default), `permissions.json` read again (`reload`; what plugins granted stays), or everything about one player or SteamID (`info`). |
| `s2m version` | Source2Mod version and commit, plugin API version, the KHook interface served to plugins, Metamod's plugin interface, the binary's path, build date and build id. |
| `s2m credits` | Who made it. |
`load`, `unload`, `refresh`, `hookdebug` and `admins` work only from the server console (or RCON). Players can run `list`, `info`, `version` and `credits` from their own console, and see only those in the help.
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".
HOOK_CON_COMMAND("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.HookConCommand("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 HookConCommand(const char* pchName, CommandHandler handler, bool post) = 0;
virtual bool UnhookConCommand(const char* pchName, const CommandHandler& handler, bool post) = 0;
virtual bool UnhookConCommand(Source2ModHookId id) = 0;
#define HOOK_CON_COMMAND(pchName, handler, post) g_pSource2ModCommands->HookConCommand(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.HookConCommand(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. `HookConCommand` returns a `Source2ModHookId`;
`g_pSource2ModCommands->UnhookConCommand(id)` drops that one listener, and
`UnhookConCommand(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()
HOOK_CON_COMMAND("say", OnPlayerChat, false);
// later, to turn the filter off
g_pSource2ModCommands->UnhookConCommand("say", OnPlayerChat, false);
```
```cpp
int g_ChatFilter;
// in OnPluginStart()
g_ChatFilter = Source2ModCommands.HookConCommand("say", OnPlayerChat, false);
// later, to turn the filter off
Source2ModCommands.UnhookConCommand(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"
HOOK_CON_COMMAND("jointeam", [](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post) -> Action
{
// Pre: decide.
return Action::Ignore;
}, false);
HOOK_CON_COMMAND("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.HookConCommand("jointeam", Listen_JoinTeamPre, false);
Source2ModCommands.HookConCommand("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; [three ways to see chat](#three-ways-to-see-chat)), 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` as a Function point — the sample plugin
does exactly that; see [Virtual functions](/docs/hooks/virtual-hooks#on-one-object).
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. A filter
that stops empty 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()
HOOK_CON_COMMAND("say", OnPlayerChat, false);
HOOK_CON_COMMAND("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.HookConCommand("say", OnPlayerChat, false);
Source2ModCommands.HookConCommand("say_team", OnPlayerChat, false);
```
A message that starts with a chat trigger (`!`, `/`, …) and names a Source2Mod
command does **not** reach the `say` listeners: the core dispatches it as that
command instead. With a pre listener on the command name you can see it (and,
with `Supersede`, hide the chat line). A `/` line that names no Source2Mod
command is ordinary chat, as in SourceMod, so another framework on the same
server (CounterStrikeSharp's `/css_x` commands) still gets it.
Three ways to see chat [#three-ways-to-see-chat]
| Where | What it sees |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HOOK_CON_COMMAND("say" / "say_team")` | The say command as it is dispatched, before the game handles it: ordinary chat only (see above). |
| [`HookPoint::PlayerChat`](/docs/hooks/game-hooks#game-functions) | `Host_Say`, where the game prints the line: the controller, the line, `teamOnly`. Pre may `Supersede` to drop it. `!command` lines come here too. |
| The `player_chat` game event | Fired once per line that went out, after the say command. The game defines it but never fires it; Source2Mod fires it unless another framework already did (CounterStrikeSharp fires its own), so it never arrives twice. |
A chat formatter (print the line yourself, drop the original) belongs on
`PlayerChat`; FUNPLAY's mode plugins work that way. Logging and statistics
fit the `player_chat` event.
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 | `HOOK_CON_COMMAND` |
| Replace an existing command | `HOOK_CON_COMMAND`, pre, do your thing, return `Action::Supersede` |
In a script the three are `Source2ModCommands.RegisterConCommand`,
`RegisterChatListener` and `HookConCommand`.
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 HookConCommand(const char* pchName, CommandHandler handler, bool post) = 0;
virtual bool UnhookConCommand(const char* pchName, const CommandHandler& handler, bool post) = 0;
virtual bool UnhookConCommand(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)` |
| `HOOK_CON_COMMAND(name, handler, post)` | `HookConCommand(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`](/viewers/schema#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(); // also sets g_pCVar, which ConVar_Register() uses
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)
{
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`](/viewers/schema#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_pSource2ModAPI->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`](/viewers/schema#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 TakeDamage [#hooking-takedamage]
`CBaseEntity::TakeDamageOld` is the [hook point](/docs/hooks/game-hooks)
`HookPoint::TakeDamage` (`HookPoint_TakeDamage`): the core hooks it from its
own gamedata and hands every handler a `TakeDamageContext` — `entity`, `info`,
`damageResult` and, for the return value, `result`. Hook every entity, one
entity (SourceMod's `SDKHook(entity, SDKHook_OnTakeDamage, …)`), or a class:
```cpp title="plugin.cpp"
#include "source2mod/ISource2ModHooks.h"
#include "source2mod/schema/takedamageinfo.h"
#include "source2mod/schema/takedamageresult.h"
bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late)
{
SOURCE2MOD_SAVEVARS();
// Pre, every entity. HookTarget::Instance(pPawn) would be one pawn,
// HookTarget::Class("CCSPlayerPawn") every player pawn.
g_pSource2ModHooks->Hook(HookPoint::TakeDamage, HookTarget::All(), SOURCE2MOD_MEMBER(this, &MyPlugin::OnTakeDamage));
return true;
}
```
```cpp title="plugin.sp"
public void OnPluginStart()
{
// Pre, every entity. HookInstance(HookPoint_TakeDamage, pawn, …) would be
// one pawn, HookClass(HookPoint_TakeDamage, "CCSPlayerPawn", …) every
// player pawn. The hook goes when the plugin unloads.
Source2ModHooks.Hook(HookPoint_TakeDamage, OnTakeDamage);
}
```
The hooked entity is `ctx.entity` — for player damage that is the victim's
**pawn** (designer name `player`), not the controller. The script includes
have no `DMG_*` constants, so define the bits you test:
```cpp title="plugin.cpp"
HookReturn MyPlugin::OnTakeDamage(TakeDamageContext& ctx, bool post)
{
if (!ctx.info)
return Action::Ignore;
// No fall damage.
if (ctx.info->m_bitsDamageType & DMG_FALL)
return { Action::Supersede, 0 };
// Halve everything else. This is a pre hook: the original runs after
// it, with whatever the info holds by then.
ctx.info->m_flDamage *= 0.5f;
return Action::Ignore;
}
```
```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 the info).
* `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 std::int64_t dealt = ctx.CallOriginal();
// ctx.damageResult is filled in now
return { Action::Supersede, dealt };
```
```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(ctx.entity->GetClassname(), "player"))
pVictim = static_cast(ctx.entity)->GetController();
CCSPlayerController* pAttacker = nullptr;
if (CBaseEntity* pEnt = ctx.info->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. A listener is an `IEntityOutputListener`
(`source2mod/ISource2ModEntities.h`) in C++, a function in a script, and is
registered for every output, for a **designer name** (the caller's — the
entity whose output fired), for an **output name**, for **one entity**, or
any mix of them.
```cpp title="plugin.h"
class MyPlugin : public ISource2ModPlugin, public IEntityOutputListener
{
public:
Action OnEntityOutput(CEntityInstance* pCaller, const char* pszOutput, CEntityInstance* pActivator,
float flDelay, bool post) override;
};
```
```cpp title="plugin.sp"
public Source2ModAction OnEntityOutput(CEntityInstance caller, const char[] output,
CEntityInstance activator, float delay, bool post)
{
return Action_Ignore;
}
```
```cpp title="plugin.cpp"
// In Load():
g_pSource2ModEntities->AddEntityOutputListener(this, "func_button", "OnPressed"); // every button's OnPressed
g_pSource2ModEntities->AddEntityOutputListener(this, nullptr, "OnStartTouch"); // any entity's OnStartTouch
g_pSource2ModEntities->AddEntityOutputListener(this, nullptr, nullptr, pButton); // every output of one entity
```
```cpp title="plugin.sp"
// In OnPluginStart():
Source2ModEntities.AddEntityOutputListener(OnEntityOutput, "func_button", "OnPressed"); // every button's OnPressed
Source2ModEntities.AddEntityOutputListener(OnEntityOutput, "", "OnStartTouch"); // any entity's OnStartTouch
Source2ModEntities.AddEntityOutputListener(OnEntityOutput, "", "", button); // every output of one entity
```
```cpp
virtual void AddEntityOutputListener(IEntityOutputListener* pListener,
const char* pszDesignerName = nullptr,
const char* pszOutput = nullptr,
CEntityInstance* pEntity = nullptr) = 0;
```
`nullptr` (`""`, `NULL_ENTITY` in a script) means "any". The macros
`ADD_ENTITY_OUTPUT_LISTENER(this, ...)` / `REMOVE_ENTITY_OUTPUT_LISTENER(this)`
are the same calls.
The listener is called **pre** (`post = false`), before the output's targets
get it, and **post**, after:
```cpp title="plugin.cpp"
Action MyPlugin::OnEntityOutput(CEntityInstance* pCaller, const char* pszOutput, CEntityInstance* pActivator,
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(pszOutput, "OnPressed"))
return Action::Supersede;
return Action::Ignore;
}
```
```cpp title="plugin.sp"
public Source2ModAction OnEntityOutput(CEntityInstance caller, const char[] output,
CEntityInstance activator, 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 — and there is no post call. |
The strongest answer of all listeners wins. The post return value is
ignored.
A listener belongs to the plugin whose library its class lives in (a script's
to the script) and is dropped when that plugin unloads. The same listener may
be added again with other filters; `RemoveEntityOutputListener(this)` drops
every registration of it. A registration for one entity goes with the entity.
A script keeps the id `AddEntityOutputListener` returns and passes it to
`Source2ModEntities.RemoveEntityOutputListener`.
The entity events — created, spawned, deleted, parent changed — have a
listener of the same shape, `IEntityListener`; see
[Watching entities come and go](/docs/entities/finding-entities#watching-entities-come-and-go).
Inputs [#inputs]
An input an entity receives — from a map connection, another plugin or
`AcceptInput` — is the [hook point](/docs/hooks/game-hooks)
`HookPoint::AcceptInput` (`HookPoint_AcceptInput`), filtered by designer name
and input name:
```cpp
g_pSource2ModHooks->Hook(HookPoint::AcceptInput, HookTarget::All(), [](AcceptInputContext& ctx, bool post) -> HookReturn
{
return { Action::Supersede, false }; // no relay triggers while the round is being set up
}, false, HookFilter{ "logic_relay", "Trigger" });
```
```cpp
Source2ModHooks.Hook(HookPoint_AcceptInput, OnRelayTrigger, false, "logic_relay", "Trigger");
public Source2ModAction OnRelayTrigger(AcceptInputContext ctx, bool post, bool &result)
{
result = false; // no relay triggers while the round is being set up
return Action_Supersede;
}
```
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:
`g_pSource2ModEntities->FindAllEntitiesByDesignerName` (the core walks the
entity list and returns `std::vector`), cast to `T`. 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`) — override what you need — and register
it with `g_pSource2ModEntities->AddEntityListener`. A script passes one
function per callback to `Source2ModEntities.AddEntityListener`
(`INVALID_FUNCTION`, or `_`, for the ones it does not need).
| Callback | Called |
| ----------------------- | --------------------------------------------------------- |
| `OnEntityCreated` | the entity exists; keyvalues not applied, not spawned yet |
| `OnEntitySpawned` | spawned — usually the one you want |
| `OnEntityDeleted` | the last call for it |
| `OnEntityParentChanged` | its parent changed |
A registration can be for every entity, for one **designer name**
(`"player"`, `"smokegrenade_projectile"`), or for **one entity**:
```cpp title="plugin.cpp"
class SamplePlugin : public ISource2ModPlugin, public IEntityListener
{
void OnEntitySpawned(CEntityInstance* pEntity) override;
};
// In Load(): only smoke grenades.
g_pSource2ModEntities->AddEntityListener(this, "smokegrenade_projectile");
// Every entity: AddEntityListener(this);
// One entity: AddEntityListener(this, nullptr, pEntity);
```
```cpp title="plugin.sp"
// In OnPluginStart(): only smoke grenades.
Source2ModEntities.AddEntityListener(_, OnSmokeSpawned, _, _, "smokegrenade_projectile");
// Every entity: AddEntityListener(OnCreated, OnSpawned, OnDeleted, OnParentChanged);
// One entity: AddEntityListener(_, _, OnDeleted, _, "", entity);
```
Some entities are filled in by the game right after they spawn, so a change
made in `OnEntitySpawned` can be overwritten; the sample plugin waits one
frame, through a handle:
```cpp title="plugin.cpp"
void SamplePlugin::OnEntitySpawned(CEntityInstance* pEntity)
{
// The designer name above lets only smoke grenades through.
CHandle hSmoke = static_cast(pEntity)->GetHandle();
g_pSource2ModScheduler->NextFrame([hSmoke]()
{
if (CSmokeGrenadeProjectile* pSmoke = hSmoke.Get())
pSmoke->m_vSmokeColor = Vector(170.0f, 0.0f, 255.0f);
});
}
```
```cpp title="plugin.sp"
public void OnSmokeSpawned(CEntityInstance entity)
{
// 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` takes no plugin id: the listener belongs to the plugin
whose library its class lives in, and it is dropped for you when that plugin
unloads. The same listener may be added again with other filters; it is then
called once per matching registration. `RemoveEntityListener(this)`
(`REMOVE_ENTITY_LISTENER(this)`) drops every registration of it. A
registration for one entity goes with the entity, after its
`OnEntityDeleted`. In a script, the id `AddEntityListener` returns is what
`Source2ModEntities.RemoveEntityListener` takes.
Entity outputs have a listener of the same shape — see
[Entity I/O](/docs/entities/entity-io#listening-to-outputs).
# 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 viewer](/viewers/schema#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 viewer](/viewers/schema#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. Every class with its fields, their offsets and sizes, and the
SDK's helpers is in the [schema viewer](/viewers/schema).
```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`.
# 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.
`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.
`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_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_pSource2ModAPI` · **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` |
`Hooks` [#hooks]
```cpp
virtual ISource2ModHooks* Hooks() = 0;
```
The hooks (ISource2ModHooks.h): game functions, entity virtuals, engine callbacks, any function. Entity events and outputs are listeners of ISource2ModEntities.
# 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** `ISource2ModCommands004` (`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 (HookConCommand).
`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
`HookConCommand` [#hookconcommand]
```cpp
virtual Source2ModCommandHookId HookConCommand(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 UnhookConCommand(id) takes
`UnhookConCommand` [#unhookconcommand]
```cpp
virtual bool UnhookConCommand(const char* pchName, const CommandHandler& handler, bool post) = 0;
virtual bool UnhookConCommand(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 HookConCommand() 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__)` |
| `HOOK_CON_COMMAND(pchName, handler, post)` | `g_pSource2ModCommands->HookConCommand(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*`](/viewers/schema#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*`](/viewers/schema#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** `ISource2ModEntities004` (`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*`](/viewers/schema#CBasePlayerController) | |
| `pGameRules` | [`CCSGameRules*`](/viewers/schema#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` |
`FindAllEntitiesByDesignerName` [#findallentitiesbydesignername]
```cpp
virtual std::vector FindAllEntitiesByDesignerName(const char* pszDesignerName) = 0;
```
Every entity with this designer name ("prop\_dynamic", "cs\_player\_controller"), in entity-list order; empty when the entity system is not up. UTIL\_FindAllEntitiesByDesignerName\ is the typed form.
| Parameter | Type |
| ----------------- | ------------- |
| `pszDesignerName` | `const char*` |
`CreateEntityByName` [#createentitybyname]
```cpp
virtual CBaseEntity* CreateEntityByName(const char* pszClassName) = 0;
```
Creates entity by classname.
| Parameter | Type |
| -------------- | ------------- |
| `pszClassName` | `const char*` |
`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` |
`AddEntityListener` [#addentitylistener]
```cpp
virtual void AddEntityListener(IEntityListener* pListener, const char* pszDesignerName = nullptr, CEntityInstance* pEntity = nullptr) = 0;
```
Entity events: the IEntityListener's OnEntityCreated (keyvalues not applied, not spawned yet), OnEntitySpawned, OnEntityDeleted (the last call for it), OnEntityParentChanged. Override what you need.
The same listener may be added again with other filters; it is then called once per matching registration. Remove drops them all.
| Parameter | Type | Default | Description |
| ----------------- | ------------------ | --------- | -------------------------------------------------------------------------------------------------- |
| `pListener` | `IEntityListener*` | | The plugin whose class it is owns it; dropped for you when that plugin unloads. |
| `pszDesignerName` | `const char*` | `nullptr` | Only entities of this designer name ("player", "smokegrenade\_projectile"); nullptr for every one. |
| `pEntity` | `CEntityInstance*` | `nullptr` | Only this one entity; nullptr for every one. Gone with the entity, after its OnEntityDeleted. |
`RemoveEntityListener` [#removeentitylistener]
```cpp
virtual void RemoveEntityListener(IEntityListener* pListener) = 0;
```
Drops every registration of the listener.
| Parameter | Type |
| ----------- | ------------------ |
| `pListener` | `IEntityListener*` |
`AddEntityOutputListener` [#addentityoutputlistener]
```cpp
virtual void AddEntityOutputListener(IEntityOutputListener* pListener, const char* pszDesignerName = nullptr, const char* pszOutput = nullptr, CEntityInstance* pEntity = nullptr) = 0;
```
Entity outputs: IEntityOutputListener::OnEntityOutput, pre and post.
The same listener may be added again with other filters. Remove drops them all.
| Parameter | Type | Default | Description |
| ----------------- | ------------------------------------------------------------------------------------ | --------- | ------------------------------------------------------------------------------------- |
| `pListener` | [`IEntityOutputListener*`](/docs/core-api/ISource2ModEntities#ientityoutputlistener) | | The plugin whose class it is owns it; dropped for you when that plugin unloads. |
| `pszDesignerName` | `const char*` | `nullptr` | Only outputs of entities of this designer name (the caller's); nullptr for every one. |
| `pszOutput` | `const char*` | `nullptr` | Only this output ("OnTrigger", "OnStartTouch"); nullptr for every one. |
| `pEntity` | `CEntityInstance*` | `nullptr` | Only outputs of this one entity; nullptr for every one. Gone with the entity. |
`RemoveEntityOutputListener` [#removeentityoutputlistener]
```cpp
virtual void RemoveEntityOutputListener(IEntityOutputListener* pListener) = 0;
```
Drops every registration of the listener.
| Parameter | Type |
| ----------- | ------------------------------------------------------------------------------------ |
| `pListener` | [`IEntityOutputListener*`](/docs/core-api/ISource2ModEntities#ientityoutputlistener) |
`IEntityOutputListener` [#ientityoutputlistener]
Entity outputs firing (CEntityIOOutput::FireOutputInternal), for ISource2ModEntities::AddEntityOutputListener. Called pre (post = false), before the output's targets get it, and post, after; pre may answer Action::Supersede and the output does not fire. Override and Ignore both let it fire. The strongest answer of all listeners wins.
`OnEntityOutput` [#onentityoutput]
```cpp
virtual Action OnEntityOutput(CEntityInstance* pCaller, const char* pszOutput, CEntityInstance* pActivator, float flDelay, bool post) = 0;
```
| Parameter | Type |
| ------------ | ------------------ |
| `pCaller` | `CEntityInstance*` |
| `pszOutput` | `const char*` |
| `pActivator` | `CEntityInstance*` |
| `flDelay` | `float` |
| `post` | `bool` |
Functions [#functions]
`UTIL_FindAllEntitiesByDesignerName` [#util_findallentitiesbydesignername]
```cpp
template inline std::vector UTIL_FindAllEntitiesByDesignerName(const char* pszDesignerName);
```
| Parameter | Type |
| ----------------- | ------------- |
| `pszDesignerName` | `const char*` |
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, ##__VA_ARGS__)` |
| `REMOVE_ENTITY_LISTENER(l)` | `g_pSource2ModEntities->RemoveEntityListener(l)` |
| `ADD_ENTITY_OUTPUT_LISTENER(l, ...)` | `g_pSource2ModEntities->AddEntityOutputListener(l, ##__VA_ARGS__)` |
| `REMOVE_ENTITY_OUTPUT_LISTENER(l)` | `g_pSource2ModEntities->RemoveEntityOutputListener(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__)` |
# 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 |
# ISource2ModFunctions (/docs/core-api/ISource2ModFunctions)
**Interface id** `ISource2ModFunctions002` (`SOURCE2MOD_FUNCTIONS_INTERFACE`) · **Global** `g_pSource2ModFunctions` · **Header** [`source2mod/ISource2ModFunctions.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModFunctions.h)
Methods [#methods]
`Find` [#find]
```cpp
virtual ISource2ModFunction* Find(const char* name) = 0;
```
The function a gamedata entry with a "function" object describes.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
**Returns** [`ISource2ModFunction*`](/docs/core-api/ISource2ModFunctions#isource2modfunction) — nullptr when there is no such entry, it has no "function", or its signature does not match the running game (the reason is on the console).
`Create` [#create]
```cpp
virtual ISource2ModFunction* Create(PluginId owner, void* address, void** vtable, int vtableIndex, bool isMember, Source2ModType returnType, const Source2ModType* params, int count) = 0;
```
A function described here instead of in gamedata.
| Parameter | Type | Description |
| ------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | The plugin it belongs to (g\_PluginID); freed when it unloads. |
| `address` | `void*` | The function; nullptr for a virtual. |
| `vtable` | `void**` | For a virtual called through one class's vtable whatever the object's class; nullptr to use the object's own. |
| `vtableIndex` | `int` | The slot of a virtual; -1 for a function at @p address. |
| `isMember` | `bool` | Whether the first parameter is `this` (a virtual always is). |
| `returnType` | [`Source2ModType`](/docs/core-api/ISource2ModFunctions#source2modtype) | |
| `params` | [`const Source2ModType*`](/docs/core-api/ISource2ModFunctions#source2modtype) | The parameter types, `this` included. |
| `count` | `int` | |
`Destroy` [#destroy]
```cpp
virtual void Destroy(ISource2ModFunction* function) = 0;
```
Frees one from Create() / From\*(). One from Find() is left alone.
| Parameter | Type |
| ---------- | --------------------------------------------------------------------------------- |
| `function` | [`ISource2ModFunction*`](/docs/core-api/ISource2ModFunctions#isource2modfunction) |
`FromGameConfig` [#fromgameconfig]
```cpp
virtual ISource2ModFunction* FromGameConfig(PluginId owner, const char* name, const FunctionTypes& types) = 0;
```
A gamedata entry (its signature, symbol or -- a virtual -- offset), with the types given here instead of in a "function" object.
| Parameter | Type |
| --------- | --------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `name` | `const char*` |
| `types` | [`const FunctionTypes&`](/docs/core-api/ISource2ModFunctions#functiontypes) |
`FromSignature` [#fromsignature]
```cpp
virtual ISource2ModFunction* FromSignature(PluginId owner, const char* module, const char* signature, const FunctionTypes& types, const char* section = "") = 0;
```
A signature of the plugin's own in a module ("server"), optionally only in one section (".text").
| Parameter | Type | Default |
| ----------- | --------------------------------------------------------------------------- | ------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | |
| `module` | `const char*` | |
| `signature` | `const char*` | |
| `types` | [`const FunctionTypes&`](/docs/core-api/ISource2ModFunctions#functiontypes) | |
| `section` | `const char*` | `""` |
`FromSignatureRelative` [#fromsignaturerelative]
```cpp
virtual ISource2ModFunction* FromSignatureRelative(PluginId owner, const char* module, const char* signature, int offset, int nextInstructionOffset, const FunctionTypes& types, const char* section = "") = 0;
```
The function a matched call / jmp / lea refers to: the rel32 at @p offset from the match, relative to the next instruction at @p nextInstructionOffset (a call or jmp at the start: 1, 5).
| Parameter | Type | Default |
| ----------------------- | --------------------------------------------------------------------------- | ------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | |
| `module` | `const char*` | |
| `signature` | `const char*` | |
| `offset` | `int` | |
| `nextInstructionOffset` | `int` | |
| `types` | [`const FunctionTypes&`](/docs/core-api/ISource2ModFunctions#functiontypes) | |
| `section` | `const char*` | `""` |
`FromSymbol` [#fromsymbol]
```cpp
virtual ISource2ModFunction* FromSymbol(PluginId owner, const char* module, const char* symbol, const FunctionTypes& types) = 0;
```
An exported or debug symbol in a module.
| Parameter | Type |
| --------- | --------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `module` | `const char*` |
| `symbol` | `const char*` |
| `types` | [`const FunctionTypes&`](/docs/core-api/ISource2ModFunctions#functiontypes) |
`FromVirtualTable` [#fromvirtualtable]
```cpp
virtual ISource2ModFunction* FromVirtualTable(PluginId owner, const char* module, const char* className, int index, const FunctionTypes& types, const char* baseName = "") = 0;
```
Slot @p index of one class's vtable (an RTTI name in a module; @p baseName picks a secondary base's vtable): called on any object, hooked on that class.
| Parameter | Type | Default |
| ----------- | --------------------------------------------------------------------------- | ------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | |
| `module` | `const char*` | |
| `className` | `const char*` | |
| `index` | `int` | |
| `types` | [`const FunctionTypes&`](/docs/core-api/ISource2ModFunctions#functiontypes) | |
| `baseName` | `const char*` | `""` |
`FromVirtual` [#fromvirtual]
```cpp
virtual ISource2ModFunction* FromVirtual(PluginId owner, int index, const FunctionTypes& types) = 0;
```
Slot @p index of whatever object it is called / hooked on (an engine interface: FromVirtual(g\_PluginID, KHook::GetVtableIndex(\&ISource2Server::GameFrame), ...) with HookTarget::Instance(g\_pSource2Server)).
| Parameter | Type |
| --------- | --------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `index` | `int` |
| `types` | [`const FunctionTypes&`](/docs/core-api/ISource2ModFunctions#functiontypes) |
`ISource2ModFunction` [#isource2modfunction]
A game function: where it is and what it takes. From Find() (owned by the core, valid until the server shuts down) or Create() (the plugin's, freed with Destroy() or when the plugin unloads).
`GetName` [#getname]
```cpp
virtual const char* GetName() const = 0;
```
The gamedata name, "" for one from Create().
`GetReturnType` [#getreturntype]
```cpp
virtual Source2ModType GetReturnType() const = 0;
```
`GetParamCount` [#getparamcount]
```cpp
virtual int GetParamCount() const = 0;
```
Parameters, `this` of a member function included (it is the first).
`GetParamType` [#getparamtype]
```cpp
virtual Source2ModType GetParamType(int index) const = 0;
```
| Parameter | Type |
| --------- | ----- |
| `index` | `int` |
`IsVirtual` [#isvirtual]
```cpp
virtual bool IsVirtual() const = 0;
```
A virtual: the address comes from the vtable of the object it is called on.
`GetAddress` [#getaddress]
```cpp
virtual void* GetAddress(void* pThis = nullptr, bool bypassHooks = false) const = 0;
```
Where a call goes. A virtual reads it off @p pThis's vtable (or off the fixed vtable it was created with). @p bypassHooks: past every hook on the function (KHook::FindOriginal), as HookChain::Bypass.
| Parameter | Type | Default |
| ------------- | ------- | --------- |
| `pThis` | `void*` | `nullptr` |
| `bypassHooks` | `bool` | `false` |
**Returns** `void*` — nullptr when there is no address (a virtual without an object).
`CheckCall` [#checkcall]
```cpp
virtual bool CheckCall(Source2ModType returnType, const Source2ModType* params, int count) const = 0;
```
Whether a call of this shape matches the gamedata; a mismatch is reported on the console (once per function and shape).
| Parameter | Type |
| ------------ | ----------------------------------------------------------------------------- |
| `returnType` | [`Source2ModType`](/docs/core-api/ISource2ModFunctions#source2modtype) |
| `params` | [`const Source2ModType*`](/docs/core-api/ISource2ModFunctions#source2modtype) |
| `count` | `int` |
`Call` [#call]
```cpp
template typename source2mod_functions_detail::Signature::Return Call(A&&... args) const;
```
Calls it. @p Sig is the function's C++ type with `this` as the first parameter of a member function: Call\(ent, true). Nothing is called when it does not match the gamedata; the result is then value-initialised.
| Parameter | Type |
| --------- | -------- |
| `args` | `A&&...` |
`CallOriginal` [#calloriginal]
```cpp
template typename source2mod_functions_detail::Signature::Return CallOriginal(A&&... args) const;
```
Call past every hook on the function (all plugins', raw and game hooks).
| Parameter | Type |
| --------- | -------- |
| `args` | `A&&...` |
`IsMember` [#ismember]
```cpp
virtual bool IsMember() const = 0;
```
A member function ("this" in the gamedata, or a virtual).
`FunctionTypes` [#functiontypes]
What a function takes and returns, for one found without a gamedata "function" object (ISource2ModFunctions::From\*).
```cpp
const Source2ModType params[] = { Source2ModType::Entity, Source2ModType::Bool };
FunctionTypes types{ Source2ModType::Void, params, 2, true }; // void (CBaseEntity* this, bool)
```
Fields [#fields]
| Field | Type | Description |
| ------------ | ----------------------------------------------------------------------------- | ------------------------------------- |
| `returnType` | [`Source2ModType`](/docs/core-api/ISource2ModFunctions#source2modtype) | |
| `params` | [`const Source2ModType*`](/docs/core-api/ISource2ModFunctions#source2modtype) | `this` included for a member function |
| `count` | `int` | |
| `isMember` | `bool` | the first parameter is `this` |
Enums [#enums]
`Source2ModType` [#source2modtype]
```cpp
enum class Source2ModType : int32_t
```
How a value crosses into a game function and back. The numbers are SourcePawn's Source2ModType\_\*.
| Name | Value |
| -------------- | ----- |
| `Void` | `0` |
| `Bool` | |
| `Int8` | |
| `Int16` | |
| `Int32` | |
| `Int64` | |
| `Float` | |
| `Double` | |
| `Entity` | |
| `Object` | |
| `String` | |
| `Vector` | |
| `VectorValue` | |
| `Pointer` | |
| `BufferString` | |
| `Command` | |
| `IntRef` | |
| `BoolRef` | |
| `FloatRef` | |
Functions [#functions]
`TypeOf` [#typeof]
```cpp
template constexpr Source2ModType TypeOf();
```
`AsThis` [#asthis]
```cpp
template void* AsThis(A&& arg);
```
| Parameter | Type |
| --------- | ----- |
| `arg` | `A&&` |
`Source2ModCallFunction` [#source2modcallfunction]
```cpp
template inline typename source2mod_functions_detail::Signature::Return Source2ModCallFunction(ISource2ModFunctions* functions, ISource2ModFunction*& cache, const char* name, HookChain chain, A&&... args);
```
A gamedata function by name (its "function" object), called through the hooks (HookChain::Run) or straight to the original (HookChain::Bypass) -- how the SDK's schema helpers (Teleport, SwitchTeam, GiveNamedItem, ...) make their calls. Found once per call site. When the entry is missing or does not match @p Sig nothing is called and the result is value-initialised.
| Parameter | Type |
| ----------- | ---------------------------------------------------------------------------------- |
| `functions` | `ISource2ModFunctions*` |
| `cache` | [`ISource2ModFunction*&`](/docs/core-api/ISource2ModFunctions#isource2modfunction) |
| `name` | `const char*` |
| `chain` | [`HookChain`](/docs/core-api/ISource2ModTypes#hookchain) |
| `args` | `A&&...` |
Macros [#macros]
`SOURCE2MOD_CALL_FUNCTION` [#source2mod_call_function]
```cpp
#define SOURCE2MOD_CALL_FUNCTION(SIG, NAME, CHAIN, ...) \
([&]() -> typename source2mod_functions_detail::Signature::Return {
static ISource2ModFunction* s_function = nullptr;
return Source2ModCallFunction(g_pSource2ModFunctions, s_function, NAME, CHAIN, __VA_ARGS__);
}())
```
SOURCE2MOD\_CALL\_FUNCTION(void(CCSPlayerController\*, int), "CCSPlayerController::SwitchTeam", eChain, this, nTeam)
# 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)` |
# ISource2ModGameSystems (/docs/core-api/ISource2ModGameSystems)
A game system is how the engine lets code run at fixed points of a session -- BuildGameSessionManifest for precaching, ServerGamePostSimulate per frame, and the rest of the IGameSystem event set. Registering one means getting a factory into the engine's own linked list, whose head lives in a static the engine never exports.
Source2Mod finds that head once, by signature, and hands it over here. It has to be handed over rather than used directly, because CBaseGameSystemFactory::sm\_pFirst is a static member: every binary that includes the s2sdk header gets its own copy, so a plugin's copy is null until it is pointed at the engine's.
Access to the engine's game system registry.
Registering a game system of your own is three steps:
```cpp
// 1. Declare it, the way s2sdk expects.
class MyGameSystem : public CAutoGameSystem
{
public:
DECLARE_GAME_SYSTEM();
GS_EVENT(BuildGameSessionManifest);
void SetGameSystemGlobalPtrs(void* pValue) override
{
if (sm_Factory) sm_Factory->SetGlobalPtr(pValue);
}
bool DoesGameSystemReallocate() override { return sm_Factory->ShouldAutoAdd(); }
// Not IGameSystemFactory*: that is abstract and has no virtual
// destructor, so deleting your factory through it is undefined. Hold a
// final type of your own instead.
static MyGameSystemFactory* sm_Factory;
};
class MyGameSystemFactory final : public CGameSystemStaticFactory
{
public:
using CGameSystemStaticFactory::CGameSystemStaticFactory;
};
MyGameSystem g_MyGameSystem;
MyGameSystemFactory* MyGameSystem::sm_Factory = nullptr;
CBaseGameSystemFactory** CBaseGameSystemFactory::sm_pFirst = nullptr;
// 2. Point this binary's copy of sm_pFirst at the engine's list, in Load().
CBaseGameSystemFactory::sm_pFirst = api->GameSystems()->GetFactoryListHead();
// 3. Register. The factory adds itself to the list its constructor sees.
MyGameSystem::sm_Factory =
new MyGameSystemFactory("MyPlugin_GameSystem", &g_MyGameSystem);
```
Registration only takes effect for sessions started afterwards; the engine walks the list when it initialises all systems.
**Interface id** `ISource2ModGameSystems001` (`SOURCE2MOD_GAMESYSTEMS_INTERFACE`) · **Global** `g_pSource2ModGameSystems` · **Header** [`source2mod/ISource2ModGameSystems.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModGameSystems.h)
Methods [#methods]
`IsAvailable` [#isavailable]
```cpp
virtual bool IsAvailable() const = 0;
```
Whether the factory list head was found.
False means the signature scan failed -- usually a game update -- and registering a game system cannot work. Check before registering.
`GetFactoryListHead` [#getfactorylisthead]
```cpp
virtual CBaseGameSystemFactory** GetFactoryListHead() = 0;
```
The engine's game system factory list head.
Assign this to your own binary's CBaseGameSystemFactory::sm\_pFirst before constructing any factory. Returns nullptr when IsAvailable() is false.
`FindByName` [#findbyname]
```cpp
virtual IGameSystem* FindByName(const char* name) = 0;
```
Finds an already-registered game system by the name it was registered under.
| Parameter | Type | Description |
| --------- | ------------- | ----------------------------------------------- |
| `name` | `const char*` | Registration name, e.g. "MyPlugin\_GameSystem". |
**Returns** `IGameSystem*` — The system, or nullptr if nothing is registered under that name.
# ISource2ModGeoIP (/docs/core-api/ISource2ModGeoIP)
Lookups go to a MaxMind GeoLite2 database (.mmdb) through libmaxminddb. The database is not shipped: download GeoLite2-City.mmdb (or the smaller GeoLite2-Country.mmdb, which has no city, region, time zone or location) from MaxMind and put it where core.json's "GeoIPDatabase" says -- addons/source2mod/geoip/GeoLite2-City.mmdb by default. Without it every lookup returns false and the core says so once in its log.
An address may carry a port ("1.2.3.4:27005"); it is cut off.
**Interface id** `ISource2ModGeoIP001` (`SOURCE2MOD_GEOIP_INTERFACE`) · **Global** `g_pSource2ModGeoIP` · **Header** [`source2mod/ISource2ModGeoIP.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModGeoIP.h)
Methods [#methods]
`IsLoaded` [#isloaded]
```cpp
virtual bool IsLoaded() = 0;
```
Whether a database is open.
`GetDatabaseType` [#getdatabasetype]
```cpp
virtual const char* GetDatabaseType() = 0;
```
The database's type, "GeoLite2-City" / "GeoLite2-Country"; "" when none is open.
`Lookup` [#lookup]
```cpp
virtual bool Lookup(const char* ip, Source2ModGeoIPField field, char* out, size_t maxlen, const char* language = nullptr) = 0;
```
Looks one field of an address up.
| Parameter | Type | Default | Description |
| ---------- | ------------------------------------------------------------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ip` | `const char*` | | IPv4 or IPv6, a port is allowed. |
| `field` | [`Source2ModGeoIPField`](/docs/core-api/ISource2ModGeoIP#source2modgeoipfield) | | What to read. |
| `out` | `char*` | | Receives it, "" on failure. |
| `maxlen` | `size_t` | | Size of `out`. |
| `language` | `const char*` | `nullptr` | For the names: "en", "de", "es", "fr", "ja", "pt-BR", "ru", "zh-CN"; nullptr = "en". Falls back to English when the database has no name in that language. |
**Returns** `bool` — false when there is no database, the address is not in it, or the database has no such field for it.
`LookupPlayer` [#lookupplayer]
```cpp
virtual bool LookupPlayer(CPlayerSlot slot, Source2ModGeoIPField field, char* out, size_t maxlen, const char* language = nullptr) = 0;
```
Lookup() for the address of the player in this slot.
| Parameter | Type | Default |
| ---------- | ------------------------------------------------------------------------------ | --------- |
| `slot` | `CPlayerSlot` | |
| `field` | [`Source2ModGeoIPField`](/docs/core-api/ISource2ModGeoIP#source2modgeoipfield) | |
| `out` | `char*` | |
| `maxlen` | `size_t` | |
| `language` | `const char*` | `nullptr` |
`GetLocation` [#getlocation]
```cpp
virtual bool GetLocation(const char* ip, double& latitude, double& longitude) = 0;
```
Latitude and longitude. City database only.
| Parameter | Type |
| ----------- | ------------- |
| `ip` | `const char*` |
| `latitude` | `double&` |
| `longitude` | `double&` |
`LookupPath` [#lookuppath]
```cpp
virtual bool LookupPath(const char* ip, const char* path, char* out, size_t maxlen) = 0;
```
Any string, number or bool of the record, by its path.
```cpp
g_pSource2ModGeoIP->LookupPath("8.8.8.8", "country/names/de", buf, sizeof(buf));
g_pSource2ModGeoIP->LookupPath("8.8.8.8", "subdivisions/0/iso_code", buf, sizeof(buf));
```
| Parameter | Type | Description |
| --------- | ------------- | ---------------------------------------- |
| `ip` | `const char*` | |
| `path` | `const char*` | Keys and array indices separated by '/'. |
| `out` | `char*` | |
| `maxlen` | `size_t` | |
`GetPlayerIP` [#getplayerip]
```cpp
virtual bool GetPlayerIP(CPlayerSlot slot, char* out, size_t maxlen) = 0;
```
The player's IP address without the port; false for an empty slot or a bot.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
| `out` | `char*` |
| `maxlen` | `size_t` |
Enums [#enums]
`Source2ModGeoIPField` [#source2modgeoipfield]
```cpp
enum Source2ModGeoIPField
```
What Lookup() reads.
| Name | Value | Description |
| --------------------------------- | ----- | ----------------------------------------------------------- |
| `SOURCE2MOD_GEOIP_COUNTRY_CODE` | `0` | ISO 3166-1 alpha-2, "US". |
| `SOURCE2MOD_GEOIP_COUNTRY` | `1` | "United States". |
| `SOURCE2MOD_GEOIP_CONTINENT_CODE` | `2` | Two letters, "NA". |
| `SOURCE2MOD_GEOIP_CONTINENT` | `3` | "North America". |
| `SOURCE2MOD_GEOIP_REGION_CODE` | `4` | The first subdivision's ISO code, "CA". City database only. |
| `SOURCE2MOD_GEOIP_REGION` | `5` | "California". City database only. |
| `SOURCE2MOD_GEOIP_CITY` | `6` | "Mountain View". City database only. |
| `SOURCE2MOD_GEOIP_TIMEZONE` | `7` | IANA time zone, "America/Los\_Angeles". City database only. |
| `SOURCE2MOD_GEOIP_POSTAL_CODE` | `8` | Postal code. City database only. |
# ISource2ModHTTP (/docs/core-api/ISource2ModHTTP)
Requests go out through Steam's own HTTP client, which the game server already has, so there is no curl to link and nothing to pump. Steam only hands that client over once the API has activated, which happens after level startup -- requests made before then are queued and sent when it does, so a plugin does not have to care whether it is early.
Responses come back on the main thread.
Issues HTTP requests through Steam's HTTP client.
```cpp
HTTP_GET("https://example.com/api", [](const Source2ModHTTPResponse& r)
{
if (!r.m_bSuccess || r.m_nStatusCode != 200)
return;
// r.m_sBody -- parse with whatever your plugin already uses.
});
```
**Interface id** `ISource2ModHTTP002` (`SOURCE2MOD_HTTP_INTERFACE`) · **Global** `g_pSource2ModHTTP` · **Header** [`source2mod/ISource2ModHTTP.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModHTTP.h)
Types [#types]
`Source2ModHTTPCallback` [#source2modhttpcallback]
```cpp
using Source2ModHTTPCallback = Source2ModCallback;
```
Called with the response on the main thread: a function, an object and a method, or a lambda (Source2ModCallback) -- whose it is, the core reads off it.
Methods [#methods]
`IsAvailable` [#isavailable]
```cpp
virtual bool IsAvailable() const = 0;
```
Whether Steam's HTTP client is available yet.
Requests made while this is false are queued, not dropped, so checking is only worth it to avoid building a body you cannot send.
`PendingCount` [#pendingcount]
```cpp
virtual int PendingCount() const = 0;
```
Number of requests waiting for Steam's HTTP client.
`Request` [#request]
```cpp
virtual void Request(ESource2ModHTTPMethod method, const char* pszUrl, const char* pszBody, Source2ModHTTPCallback callback, const std::vector* pHeaders = nullptr) = 0;
```
Issues a request.
The plugin the callback's code lies in owns the request.
Cancelled, callback and all, if the owning plugin unloads first -- the callback holds code inside that plugin's library.
| Parameter | Type | Default | Description |
| ---------- | ------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------- |
| `method` | [`ESource2ModHTTPMethod`](/docs/core-api/ISource2ModHTTP#esource2modhttpmethod) | | HTTP method |
| `pszUrl` | `const char*` | | Absolute URL |
| `pszBody` | `const char*` | | Request body, or nullptr for methods that take none |
| `callback` | [`Source2ModHTTPCallback`](/docs/core-api/ISource2ModHTTP#source2modhttpcallback) | | Called once the request completes; may be null |
| `pHeaders` | [`const std::vector*`](/docs/core-api/ISource2ModHTTP#source2modhttpheader) | `nullptr` | Extra request headers, or nullptr |
`Get` [#get]
```cpp
virtual void Get(const char* pszUrl, Source2ModHTTPCallback callback, const std::vector* pHeaders = nullptr) = 0;
```
| Parameter | Type | Default |
| ---------- | ------------------------------------------------------------------------------------------------- | --------- |
| `pszUrl` | `const char*` | |
| `callback` | [`Source2ModHTTPCallback`](/docs/core-api/ISource2ModHTTP#source2modhttpcallback) | |
| `pHeaders` | [`const std::vector*`](/docs/core-api/ISource2ModHTTP#source2modhttpheader) | `nullptr` |
`Post` [#post]
```cpp
virtual void Post(const char* pszUrl, const char* pszBody, Source2ModHTTPCallback callback, const std::vector* pHeaders = nullptr) = 0;
```
| Parameter | Type | Default |
| ---------- | ------------------------------------------------------------------------------------------------- | --------- |
| `pszUrl` | `const char*` | |
| `pszBody` | `const char*` | |
| `callback` | [`Source2ModHTTPCallback`](/docs/core-api/ISource2ModHTTP#source2modhttpcallback) | |
| `pHeaders` | [`const std::vector*`](/docs/core-api/ISource2ModHTTP#source2modhttpheader) | `nullptr` |
`Put` [#put]
```cpp
virtual void Put(const char* pszUrl, const char* pszBody, Source2ModHTTPCallback callback, const std::vector* pHeaders = nullptr) = 0;
```
| Parameter | Type | Default |
| ---------- | ------------------------------------------------------------------------------------------------- | --------- |
| `pszUrl` | `const char*` | |
| `pszBody` | `const char*` | |
| `callback` | [`Source2ModHTTPCallback`](/docs/core-api/ISource2ModHTTP#source2modhttpcallback) | |
| `pHeaders` | [`const std::vector*`](/docs/core-api/ISource2ModHTTP#source2modhttpheader) | `nullptr` |
`Patch` [#patch]
```cpp
virtual void Patch(const char* pszUrl, const char* pszBody, Source2ModHTTPCallback callback, const std::vector* pHeaders = nullptr) = 0;
```
| Parameter | Type | Default |
| ---------- | ------------------------------------------------------------------------------------------------- | --------- |
| `pszUrl` | `const char*` | |
| `pszBody` | `const char*` | |
| `callback` | [`Source2ModHTTPCallback`](/docs/core-api/ISource2ModHTTP#source2modhttpcallback) | |
| `pHeaders` | [`const std::vector*`](/docs/core-api/ISource2ModHTTP#source2modhttpheader) | `nullptr` |
`Delete` [#delete]
```cpp
virtual void Delete(const char* pszUrl, const char* pszBody, Source2ModHTTPCallback callback, const std::vector* pHeaders = nullptr) = 0;
```
| Parameter | Type | Default |
| ---------- | ------------------------------------------------------------------------------------------------- | --------- |
| `pszUrl` | `const char*` | |
| `pszBody` | `const char*` | |
| `callback` | [`Source2ModHTTPCallback`](/docs/core-api/ISource2ModHTTP#source2modhttpcallback) | |
| `pHeaders` | [`const std::vector*`](/docs/core-api/ISource2ModHTTP#source2modhttpheader) | `nullptr` |
`Source2ModHTTPHeader` [#source2modhttpheader]
One request or response header.
Fields [#fields]
| Field | Type |
| ---------- | ------------- |
| `m_sName` | `std::string` |
| `m_sValue` | `std::string` |
Constructors [#constructors]
```cpp
Source2ModHTTPHeader() = default;
Source2ModHTTPHeader(std::string name, std::string value);
```
`Source2ModHTTPResponse` [#source2modhttpresponse]
What a completed request came back with.
m_bSuccess is about the transport, not the status code -- a 404 that arrived is a success with m_nStatusCode == 404. A request that never completed has m_bSuccess false and m_nStatusCode 0.
Fields [#fields-1]
| Field | Type | Description |
| --------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `m_bSuccess` | `bool` | |
| `m_nStatusCode` | `int` | |
| `m_sBody` | `std::string` | |
| `m_pJson` | [`const ISource2ModJSONValue*`](/docs/core-api/ISource2ModJSON#isource2modjsonvalue) | The body parsed as JSON, when it was JSON. Owned by Source2Mod and valid only for the duration of the callback -- copy anything you keep. Null when the body was empty or did not parse. |
Enums [#enums]
`ESource2ModHTTPMethod` [#esource2modhttpmethod]
```cpp
enum class ESource2ModHTTPMethod : int
```
HTTP method of a request.
| Name | Value |
| -------- | ----- |
| `Get` | `0` |
| `Post` | |
| `Put` | |
| `Patch` | |
| `Delete` | |
| `Head` | |
Macros [#macros]
| Macro | Expands to |
| ------------------------------------------------ | ------------------------------------------------------------------------ |
| `HTTP_REQUEST(method, pszUrl, pszBody, cb, ...)` | `g_pSource2ModHTTP->Request(method, pszUrl, pszBody, cb, ##__VA_ARGS__)` |
| `HTTP_GET(pszUrl, cb, ...)` | `g_pSource2ModHTTP->Get(pszUrl, cb, ##__VA_ARGS__)` |
| `HTTP_POST(pszUrl, pszBody, cb, ...)` | `g_pSource2ModHTTP->Post(pszUrl, pszBody, cb, ##__VA_ARGS__)` |
| `HTTP_PUT(pszUrl, pszBody, cb, ...)` | `g_pSource2ModHTTP->Put(pszUrl, pszBody, cb, ##__VA_ARGS__)` |
| `HTTP_PATCH(pszUrl, pszBody, cb, ...)` | `g_pSource2ModHTTP->Patch(pszUrl, pszBody, cb, ##__VA_ARGS__)` |
| `HTTP_DELETE(pszUrl, pszBody, cb, ...)` | `g_pSource2ModHTTP->Delete(pszUrl, pszBody, cb, ##__VA_ARGS__)` |
# ISource2ModHookContexts (/docs/core-api/ISource2ModHookContexts)
Every point 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 -- HookPoint::Function (FunctionContext) can. Handlers run in registration order, pre before the original and post after it; in post, `result` holds what the original returned.
The game-function points are one detour per function, placed by the core from its gamedata the first time a plugin asks for it and kept until shutdown (a detour taken out and put back would miss calls while KHook inserts it again). 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.
**Header** [`source2mod/ISource2ModHookContexts.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModHookContexts.h)
`TakeDamageContext` [#takedamagecontext]
CBaseEntity::TakeDamageOld(info, result) -> int64
Fields [#fields]
| Field | Type |
| -------------- | ----------------------------------------------------- |
| `entity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `info` | [`CTakeDamageInfo*`](/docs/schema/Takedamageinfo) |
| `damageResult` | [`CTakeDamageResult*`](/docs/schema/Takedamageresult) |
| `result` | `std::int64_t` |
`CanAcquireContext` [#canacquirecontext]
CCSPlayer\_ItemServices::CanAcquire(item, method, unk) -> AcquireResult
Fields [#fields-1]
| Field | Type |
| ---------- | ----------------------------------------------------------------------- |
| `services` | [`CCSPlayer_ItemServices*`](/viewers/schema#CCSPlayer_ItemServices) |
| `item` | [`CEconItemView*`](/viewers/schema#CEconItemView) |
| `method` | [`AcquireMethod`](/docs/core-api/ISource2ModHookContexts#acquiremethod) |
| `unk` | `void*` |
| `result` | [`AcquireResult`](/docs/core-api/ISource2ModHookContexts#acquireresult) |
`CanMoveContext` [#canmovecontext]
CCSPlayerPawnBase::CanMove() -> bool; false while frozen, defusing, ...
Fields [#fields-2]
| Field | Type |
| -------- | --------------------------------------------------------- |
| `pawn` | [`CCSPlayerPawnBase*`](/viewers/schema#CCSPlayerPawnBase) |
| `result` | `bool` |
`CanUseContext` [#canusecontext]
CCSPlayer\_WeaponServices::CanUse(weapon) -> bool
Fields [#fields-3]
| Field | Type |
| ---------- | ----------------------------------------------------------------------- |
| `services` | [`CCSPlayer_WeaponServices*`](/viewers/schema#CCSPlayer_WeaponServices) |
| `weapon` | [`CBasePlayerWeapon*`](/viewers/schema#CBasePlayerWeapon) |
| `result` | `bool` |
`PostThinkContext` [#postthinkcontext]
CCSPlayerPawn::PostThink()
Fields [#fields-4]
| Field | Type |
| ------ | --------------------------------------------------------- |
| `pawn` | [`CCSPlayerPawnBase*`](/viewers/schema#CCSPlayerPawnBase) |
`ProcessUsercmdsContext` [#processusercmdscontext]
CCSPlayerController::ProcessUserCmd(cmds, count, paused, margin) -> void\*. The commands are CUserCmd (a protobuf-backed type), left untyped here.
Fields [#fields-5]
| Field | Type |
| ------------ | ------------------------------------------------------------- |
| `controller` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) |
| `cmds` | `void*` |
| `count` | `int` |
| `paused` | `bool` |
| `margin` | `float` |
| `result` | `void*` |
`SimulateUserCommandsContext` [#simulateusercommandscontext]
CBasePlayerController::OnSimulateUserCommands()
Fields [#fields-6]
| Field | Type |
| ------------ | ----------------------------------------------------------------- |
| `controller` | [`CBasePlayerController*`](/viewers/schema#CBasePlayerController) |
`RunCommandContext` [#runcommandcontext]
CPlayer\_MovementServices::RunCommand(cmd), on CCSPlayer\_MovementServices' vtable; the command is a CUserCmd.
Fields [#fields-7]
| Field | Type |
| ---------- | --------------------------------------------------------------------------- |
| `services` | [`CCSPlayer_MovementServices*`](/viewers/schema#CCSPlayer_MovementServices) |
| `cmd` | `void*` |
`AcceptInputContext` [#acceptinputcontext]
CEntityIdentity::AcceptInput(name, activator, caller, value, ...) -> bool
Fields [#fields-8]
| Field | Type |
| ----------- | ------------------ |
| `identity` | `CEntityIdentity*` |
| `inputName` | `CUtlSymbolLarge*` |
| `activator` | `CEntityInstance*` |
| `caller` | `CEntityInstance*` |
| `value` | `variant_t*` |
| `result` | `bool` |
`TouchContext` [#touchcontext]
CBaseEntity::Touch(other), on CBaseEntity's own vtable -- an entity class that overrides Touch is not seen here.
Fields [#fields-9]
| Field | Type |
| -------- | --------------------------------------------- |
| `entity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `other` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
`DropWeaponContext` [#dropweaponcontext]
CCSPlayer\_WeaponServices::DropWeapon(weapon, target, velocity)
Fields [#fields-10]
| Field | Type |
| ---------- | ----------------------------------------------------------------------- |
| `services` | [`CCSPlayer_WeaponServices*`](/viewers/schema#CCSPlayer_WeaponServices) |
| `weapon` | [`CBasePlayerWeapon*`](/viewers/schema#CBasePlayerWeapon) |
| `target` | `Vector*` |
| `velocity` | `Vector*` |
`MovementContext` [#movementcontext]
The CCSPlayer\_MovementServices functions that take the move data alone: AirMove, CheckFalling, CheckParameters, Duck, Friction, PlayerMove, ProcessMovement, WalkMove, WaterMove (void), and CanUnduck, CheckWater, LadderMove, MoveInit (bool, in `result`).
Fields [#fields-11]
| Field | Type |
| ---------- | --------------------------------------------------------------------------- |
| `services` | [`CCSPlayer_MovementServices*`](/viewers/schema#CCSPlayer_MovementServices) |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
| `result` | `bool` |
`AirAccelerateContext` [#airacceleratecontext]
CCSPlayer\_MovementServices::AirAccelerate(move, wishDirection, wishSpeed, acceleration)
Fields [#fields-12]
| Field | Type |
| --------------- | --------------------------------------------------------------------------- |
| `services` | [`CCSPlayer_MovementServices*`](/viewers/schema#CCSPlayer_MovementServices) |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
| `wishDirection` | `Vector*` |
| `wishSpeed` | `float` |
| `acceleration` | `float` |
`GroundAccelerateContext` [#groundacceleratecontext]
CCSPlayer\_MovementServices::GroundAccelerate(move, wishDirection, frameTime, wishSpeed, acceleration). The engine's argument order differs between Linux and Windows; here it is always this one.
Fields [#fields-13]
| Field | Type |
| --------------- | --------------------------------------------------------------------------- |
| `services` | [`CCSPlayer_MovementServices*`](/viewers/schema#CCSPlayer_MovementServices) |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
| `wishDirection` | `Vector*` |
| `frameTime` | `float` |
| `wishSpeed` | `float` |
| `acceleration` | `float` |
`CategorizePositionContext` [#categorizepositioncontext]
CCSPlayer\_MovementServices::CategorizePosition(move, stayOnGround)
Fields [#fields-14]
| Field | Type |
| -------------- | --------------------------------------------------------------------------- |
| `services` | [`CCSPlayer_MovementServices*`](/viewers/schema#CCSPlayer_MovementServices) |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
| `stayOnGround` | `bool` |
`CheckVelocityContext` [#checkvelocitycontext]
CCSPlayer\_MovementServices::CheckVelocity(move, unk)
Fields [#fields-15]
| Field | Type |
| ---------- | --------------------------------------------------------------------------- |
| `services` | [`CCSPlayer_MovementServices*`](/viewers/schema#CCSPlayer_MovementServices) |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
| `unk` | `void*` |
`FullWalkMoveContext` [#fullwalkmovecontext]
CCSPlayer\_MovementServices::FullWalkMove(move, onGround)
Fields [#fields-16]
| Field | Type |
| ---------- | --------------------------------------------------------------------------- |
| `services` | [`CCSPlayer_MovementServices*`](/viewers/schema#CCSPlayer_MovementServices) |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
| `onGround` | `bool` |
`SetupMoveContext` [#setupmovecontext]
CCSPlayer\_MovementServices::SetupMove(cmd, move); the command is a CUserCmd.
Fields [#fields-17]
| Field | Type |
| ---------- | --------------------------------------------------------------------------- |
| `services` | [`CCSPlayer_MovementServices*`](/viewers/schema#CCSPlayer_MovementServices) |
| `cmd` | `CUserCmd*` |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
`TryPlayerMoveContext` [#tryplayermovecontext]
CCSPlayer\_MovementServices::TryPlayerMove(move, firstDest, firstTrace, isSurfing)
Fields [#fields-18]
| Field | Type |
| ------------ | --------------------------------------------------------------------------- |
| `services` | [`CCSPlayer_MovementServices*`](/viewers/schema#CCSPlayer_MovementServices) |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
| `firstDest` | `Vector*` |
| `firstTrace` | `CGameTrace*` |
| `isSurfing` | `bool*` |
`LegacyJumpContext` [#legacyjumpcontext]
CCSPlayerLegacyJump::OnJump / CheckJumpButton(move)
Fields [#fields-19]
| Field | Type |
| ---------- | ------------------------------------------------------------- |
| `jump` | [`CCSPlayerLegacyJump*`](/viewers/schema#CCSPlayerLegacyJump) |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
`ModernJumpContext` [#modernjumpcontext]
CCSPlayerModernJump::OnJump / CheckJumpButton(move)
Fields [#fields-20]
| Field | Type |
| ---------- | ------------------------------------------------------------- |
| `jump` | [`CCSPlayerModernJump*`](/viewers/schema#CCSPlayerModernJump) |
| `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) |
`PlayerChatContext` [#playerchatcontext]
Host\_Say(controller, args, teamOnly, ...): a say / say\_team line the game is about to print and send. Lines a silent chat trigger hid for a Source2Mod command never get here; "!command" lines do (they stay in chat). Pre: Supersede drops the line (no chat, no player\_say event). Source2Mod does not fire a player\_chat game event (CounterStrikeSharp does, from its own Host\_Say hook): this point is the way to see chat.
Fields [#fields-21]
| Field | Type | Description |
| ------------ | ------------------------------------------------------------- | --------------------------------------------------------------- |
| `controller` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) | nullptr: the server console |
| `args` | `CCommand*` | the say command; Arg(1) is the line |
| `message` | `const char*` | the line (Arg(1), or ArgS() for an unquoted one), never nullptr |
| `teamOnly` | `bool` | say\_team |
| `unk1` | `int` | |
| `unk2` | `const char*` | |
`EntityUseInfo` [#entityuseinfo]
CBaseEntity::Use's argument (activator, caller, USE\_TYPE, value).
Fields [#fields-22]
| Field | Type |
| ----------- | --------------------------------------------- |
| `activator` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `caller` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `useType` | `std::int32_t` |
| `value` | `float` |
`SpawnContext` [#spawncontext]
CBaseEntity::Spawn(keyValues) -- the entity's keyvalues are applied here.
Fields [#fields-23]
| Field | Type |
| ----------- | --------------------------------------------- |
| `entity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `keyValues` | `const CEntityKeyValues*` |
`UseContext` [#usecontext]
CBaseEntity::Use(info) -- a player's +use, a map's Use input. Changing \*info in pre changes what the entity gets.
Fields [#fields-24]
| Field | Type |
| -------- | ------------------------------------------------------------------------ |
| `entity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `info` | [`EntityUseInfo*`](/docs/core-api/ISource2ModHookContexts#entityuseinfo) |
`EntityOtherContext` [#entityothercontext]
CBaseEntity::StartTouch / EndTouch / Blocked(other)
Fields [#fields-25]
| Field | Type |
| -------- | --------------------------------------------- |
| `entity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `other` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
`ThinkContext` [#thinkcontext]
CBaseEntity::Think() -- the entity's think function, every time it runs.
Fields [#fields-26]
| Field | Type |
| -------- | --------------------------------------------- |
| `entity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
`KilledContext` [#killedcontext]
CBaseEntity::Event\_Killed(damage) -> bool -- health reached 0 and m\_lifeState is already "dying". Supersede skips the class's death handling (a player's ragdoll and death, a breakable's gibs).
Fields [#fields-27]
| Field | Type |
| -------- | ----------------------------------------------------------- |
| `entity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `damage` | [`const CTakeDamageResult*`](/docs/schema/Takedamageresult) |
| `result` | `bool` |
`WeaponPickupContext` [#weaponpickupcontext]
CCSPlayer\_WeaponServices::PickupWeapon(weapon) -> 0 refused, 1 taken, 2 taken as ammo -- touching or +using a weapon (SDKHooks' WeaponEquip / WeaponCanUse). Every player: HookTarget::All() works.
Fields [#fields-28]
| Field | Type |
| ---------- | ----------------------------------------------------------------------- |
| `services` | [`CCSPlayer_WeaponServices*`](/viewers/schema#CCSPlayer_WeaponServices) |
| `weapon` | [`CBasePlayerWeapon*`](/viewers/schema#CBasePlayerWeapon) |
| `result` | `std::int32_t` |
`WeaponSwitchContext` [#weaponswitchcontext]
CCSPlayer\_WeaponServices::SwitchToWeapon(weapon, viewModelIndex) -> bool (SDKHooks' WeaponSwitch). Every player: HookTarget::All() works.
Fields [#fields-29]
| Field | Type |
| ---------------- | ----------------------------------------------------------------------- |
| `services` | [`CCSPlayer_WeaponServices*`](/viewers/schema#CCSPlayer_WeaponServices) |
| `weapon` | [`CBasePlayerWeapon*`](/viewers/schema#CBasePlayerWeapon) |
| `viewModelIndex` | `std::int32_t` |
| `result` | `bool` |
`GameFrameContext` [#gameframecontext]
ISource2Server::GameFrame -- every tick.
Fields [#fields-30]
| Field | Type |
| ------------ | ------ |
| `simulating` | `bool` |
| `firstTick` | `bool` |
| `lastTick` | `bool` |
`StartupServerContext` [#startupservercontext]
INetworkServerService::StartupServer -- a new map's server is up.
Fields [#fields-31]
| Field | Type |
| --------- | ----------------------------------- |
| `config` | `const GameSessionConfiguration_t*` |
| `session` | `ISource2WorldSession*` |
| `mapName` | `const char*` |
`ClientConnectContext` [#clientconnectcontext]
ISource2GameClients::ClientConnect. Pre only means anything: answer \{ Action::Supersede, false } (or set ctx.result = false and Supersede) to turn the client away, with the reason in ctx.rejectReason. The first handler that refuses ends it. Not called for bots.
Fields [#fields-32]
| Field | Type |
| -------------- | ---------------- |
| `slot` | `CPlayerSlot` |
| `name` | `const char*` |
| `xuid` | `uint64` |
| `networkId` | `const char*` |
| `unk1` | `bool` |
| `rejectReason` | `CBufferString*` |
| `result` | `bool` |
`Return` [#return]
```cpp
using Return = bool;
```
`ClientConnectedContext` [#clientconnectedcontext]
ISource2GameClients::OnClientConnected -- bots too (fakePlayer).
Fields [#fields-33]
| Field | Type |
| ------------ | ------------- |
| `slot` | `CPlayerSlot` |
| `name` | `const char*` |
| `xuid` | `uint64` |
| `networkId` | `const char*` |
| `address` | `const char*` |
| `fakePlayer` | `bool` |
`ClientPutInServerContext` [#clientputinservercontext]
ISource2GameClients::ClientPutInServer.
Fields [#fields-34]
| Field | Type |
| ------ | ------------- |
| `slot` | `CPlayerSlot` |
| `name` | `const char*` |
| `type` | `int` |
| `xuid` | `uint64` |
`ClientDisconnectContext` [#clientdisconnectcontext]
ISource2GameClients::ClientDisconnect: pre while the client is still in game, post once it is gone.
Fields [#fields-35]
| Field | Type |
| ----------- | ----------------------------- |
| `slot` | `CPlayerSlot` |
| `reason` | `ENetworkDisconnectionReason` |
| `name` | `const char*` |
| `xuid` | `uint64` |
| `networkId` | `const char*` |
`ClientSlotContext` [#clientslotcontext]
ClientVoice, ClientSettingsChanged: which client.
Fields [#fields-36]
| Field | Type |
| ------ | ------------- |
| `slot` | `CPlayerSlot` |
`EmptyContext` [#emptycontext]
GameServerSteamAPIActivated / Deactivated: nothing to pass.
`LoadEventsFromFileContext` [#loadeventsfromfilecontext]
IGameEventManager2::LoadEventsFromFile -- a resource file of game events.
Fields [#fields-37]
| Field | Type |
| ----------- | --------------------- |
| `manager` | `IGameEventManager2*` |
| `filename` | `const char*` |
| `searchAll` | `bool` |
Enums [#enums]
`AcquireResult` [#acquireresult]
```cpp
enum AcquireResult : std::uint32_t
```
What CCSPlayer\_ItemServices::CanAcquire answers.
| Name | Value |
| -------------------------- | ----- |
| `Allowed` | `0` |
| `InvalidItem` | |
| `AlreadyOwned` | |
| `AlreadyPurchased` | |
| `ReachedGrenadeTypeLimit` | |
| `ReachedGrenadeTotalLimit` | |
| `NotAllowedByTeam` | |
| `NotAllowedByMap` | |
| `NotAllowedByMode` | |
| `NotAllowedForPurchase` | |
| `NotAllowedByProhibition` | |
`AcquireMethod` [#acquiremethod]
```cpp
enum AcquireMethod : std::uint32_t
```
How an item is being acquired in CCSPlayer\_ItemServices::CanAcquire.
| Name | Value |
| ------------- | ----- |
| `PickUp` | `0` |
| `Buy` | |
| `BuyWithCtrl` | |
Macros [#macros]
`SOURCE2MOD_HOOK_CALL_ORIGINAL` [#source2mod_hook_call_original]
```cpp
#define SOURCE2MOD_HOOK_CALL_ORIGINAL(RET) \
using Return = RET;
RET CallOriginal() const { return callOriginal_(callOriginalData_); }
RET (*callOriginal_)(const void*) = nullptr;
const void* callOriginalData_ = nullptr;
```
Every context ends with CallOriginal(): the game's function, run now with the arguments the game passed, past every hook on it -- SourceHook's SH\_CALL. What it returns is what the game would have answered.
In a pre handler the function really runs (with all its side effects), so a handler that calls it should answer Supersede with ctx.result set to the value, or the game runs it a second time after the handlers:
const AcquireResult game = ctx.CallOriginal(); ctx.result = game == AcquireResult::Allowed && Blocked(ctx) ? AcquireResult::NotAllowedByMode : game; return Action::Supersede;
A changed pointed-to object (the damage info, the move data) counts; a scalar field changed in the context does not, the call takes what the game passed. In a post handler ctx.result already holds the game's answer.
The two members after it belong to the core; the movement contexts shared by void and bool functions answer false for the void ones.
# ISource2ModHooks (/docs/core-api/ISource2ModHooks)
Hook = Point (what) + Target (on which object) + Handler (with its Context)
Points (HookPoint::\*):
* game functions with a typed context: TakeDamage, Touch, PostThink, AcceptInput, the movement functions, PlayerChat (Host\_Say), ... (ISource2ModHookContexts.h);
* entity virtuals with a typed context, hooked on the target's own class (SourceMod's SDKHooks): Spawn, Use, StartTouch, EndTouch, Blocked, Think, Killed, WeaponPickup, WeaponSwitch;
* engine callbacks: GameFrame, StartupServer, ClientConnect, ClientConnected, ClientPutInServer, ClientDisconnect, ClientVoice, ClientSettingsChanged, the Steam API, LoadEventsFromFile;
* any function: HookPoint::Function(fn), fn an ISource2ModFunction -- a gamedata entry (Find), a signature, a symbol, a vtable slot, an engine interface's virtual, a core address (ISource2ModFunctions::From\*). SourceMod's DHooks.
Targets (HookTarget):
* All() every call: a detour, every instance;
* Instance(object) one object: an entity, a CServerSideClient, a services object, the game rules, ...;
* Class("CCSPlayerPawn") every object of that C++ class or one derived from it (RTTI). The object is the function's `this` (for a free function its first argument). A virtual is hooked once, on the vtable, and the core compares the object before a handler runs. Engine callbacks take All() only.
Filters (HookFilter, optional): a point whose object is an entity compares its designer name ("logic\_relay", "player"); AcceptInput also the input's name.
Entity events (created, spawned, deleted, parent changed) and entity outputs are listeners of ISource2ModEntities, the way commands, convars and game events have theirs: this is for functions.
Every Hook() returns a Source2ModHookId; Unhook(id) drops it. What a plugin still holds when it unloads, the core drops for it.
KHook (ISource2ModKHook.h: KHOOK\_VIRTUAL, KHOOK\_MEMBER, KHOOK\_FUNCTION) stays available to C++ plugins, but prefer this: a plugin with KHook hooks of its own is "raw tier" in `s2m list` and needs a rebuild whenever KHook or the engine changes, a plugin on ISource2ModHooks only a core update.
```cpp
// Half damage for one pawn.
g_pSource2ModHooks->Hook(HookPoint::TakeDamage, HookTarget::Instance(pawn),
[](TakeDamageContext& ctx, bool post) -> HookReturn
{
ctx.info->m_flDamage /= 2; // the original sees it: the info is passed by pointer
return Action::Ignore;
});
// Every tick, after the game's own work.
g_pSource2ModHooks->Hook(HookPoint::GameFrame, HookTarget::All(), SOURCE2MOD_MEMBER(this, &Plugin::OnGameFrame), true);
// One input of one kind of entity.
g_pSource2ModHooks->Hook(HookPoint::AcceptInput, HookTarget::All(), OnRelayInput, false, HookFilter{ "logic_relay", "Trigger" });
// Any function, here one client's string commands.
ISource2ModFunction* fn = g_pSource2ModFunctions->Find("CServerSideClient::ExecuteStringCommand");
g_pSource2ModHooks->Hook(HookPoint::Function(fn), HookTarget::Instance(client), OnStringCommand);
```
**Interface id** `ISource2ModHooks001` (`SOURCE2MOD_HOOKS_INTERFACE`) · **Global** `g_pSource2ModHooks` · **Header** [`source2mod/ISource2ModHooks.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModHooks.h)
Types [#types]
`HookHandler` [#hookhandler]
```cpp
template using HookHandler = Source2ModCallback(CONTEXT& ctx, bool post)>;
```
A handler: the context, and whether this is the post pass. A function, an object and a method (SOURCE2MOD\_MEMBER), or a lambda.
Methods [#methods]
`Hook` [#hook]
```cpp
template Source2ModHookId Hook(HookPointOf point, const HookTarget& target, typename HookNonDeduced>::type handler, bool post = false, const HookFilter& filter = {});
```
Hooks @p point on @p target.
| Parameter | Type | Default | Description |
| --------- | ------------------------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------- |
| `point` | [`HookPointOf`](/docs/core-api/ISource2ModHooks#hookpointof) | | |
| `target` | [`const HookTarget&`](/docs/core-api/ISource2ModHooks#hooktarget) | | |
| `handler` | [`typename HookNonDeduced>::type`](/docs/core-api/ISource2ModHooks#hooknondeduced) | | |
| `post` | `bool` | `false` | false: before the original (may change arguments, Override / Supersede); true: after it (sees the result). |
| `filter` | [`const HookFilter&`](/docs/core-api/ISource2ModHooks#hookfilter) | `{}` | Designer / output / input name, for the points that take one. |
**Returns** [`Source2ModHookId`](/docs/core-api/ISource2ModTypes#source2modhookid) — The hook's id for Unhook(); 0 when the point does not exist on this game, the target does not fit the point, or the function of a Function point is null.
`Unhook` [#unhook]
```cpp
virtual bool Unhook(Source2ModHookId id) = 0;
```
Drops one hook. @return false when there is no such hook.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------- |
| `id` | [`Source2ModHookId`](/docs/core-api/ISource2ModTypes#source2modhookid) |
`IsAvailable` [#isavailable]
```cpp
virtual bool IsAvailable(HookPointId point) = 0;
```
Whether the point resolves on the running game (its gamedata matched).
| Parameter | Type |
| --------- | ------------------------------------------------------------ |
| `point` | [`HookPointId`](/docs/core-api/ISource2ModHooks#hookpointid) |
`AddHook` [#addhook]
```cpp
virtual Source2ModHookId AddHook(HookPointId point, ISource2ModFunction* function, const HookTarget& target, const HookFilter& filter, const void* handler, bool post) = 0;
```
Type-erased Hook(): @p handler is a HookHandler\\* for @p point's context, copied by the core. Hook() is the way in.
| Parameter | Type |
| ---------- | --------------------------------------------------------------------------------- |
| `point` | [`HookPointId`](/docs/core-api/ISource2ModHooks#hookpointid) |
| `function` | [`ISource2ModFunction*`](/docs/core-api/ISource2ModFunctions#isource2modfunction) |
| `target` | [`const HookTarget&`](/docs/core-api/ISource2ModHooks#hooktarget) |
| `filter` | [`const HookFilter&`](/docs/core-api/ISource2ModHooks#hookfilter) |
| `handler` | `const void*` |
| `post` | `bool` |
`HookTarget` [#hooktarget]
One way to hook anything, the same from C++ and SourcePawn:
Hook = Point (what) + Target (on which object) + Handler (with its Context)
Points (HookPoint::\*):
* game functions with a typed context: TakeDamage, Touch, PostThink, AcceptInput, the movement functions, PlayerChat (Host\_Say), ... (ISource2ModHookContexts.h);
* entity virtuals with a typed context, hooked on the target's own class (SourceMod's SDKHooks): Spawn, Use, StartTouch, EndTouch, Blocked, Think, Killed, WeaponPickup, WeaponSwitch;
* engine callbacks: GameFrame, StartupServer, ClientConnect, ClientConnected, ClientPutInServer, ClientDisconnect, ClientVoice, ClientSettingsChanged, the Steam API, LoadEventsFromFile;
* any function: HookPoint::Function(fn), fn an ISource2ModFunction -- a gamedata entry (Find), a signature, a symbol, a vtable slot, an engine interface's virtual, a core address (ISource2ModFunctions::From\*). SourceMod's DHooks.
Targets (HookTarget):
* All() every call: a detour, every instance;
* Instance(object) one object: an entity, a CServerSideClient, a services object, the game rules, ...;
* Class("CCSPlayerPawn") every object of that C++ class or one derived from it (RTTI). The object is the function's `this` (for a free function its first argument). A virtual is hooked once, on the vtable, and the core compares the object before a handler runs. Engine callbacks take All() only.
Filters (HookFilter, optional): a point whose object is an entity compares its designer name ("logic\_relay", "player"); AcceptInput also the input's name.
Entity events (created, spawned, deleted, parent changed) and entity outputs are listeners of ISource2ModEntities, the way commands, convars and game events have theirs: this is for functions.
Every Hook() returns a Source2ModHookId; Unhook(id) drops it. What a plugin still holds when it unloads, the core drops for it.
KHook (ISource2ModKHook.h: KHOOK\_VIRTUAL, KHOOK\_MEMBER, KHOOK\_FUNCTION) stays available to C++ plugins, but prefer this: a plugin with KHook hooks of its own is "raw tier" in `s2m list` and needs a rebuild whenever KHook or the engine changes, a plugin on ISource2ModHooks only a core update.
```cpp
// Half damage for one pawn.
g_pSource2ModHooks->Hook(HookPoint::TakeDamage, HookTarget::Instance(pawn),
[](TakeDamageContext& ctx, bool post) -> HookReturn
{
ctx.info->m_flDamage /= 2; // the original sees it: the info is passed by pointer
return Action::Ignore;
});
// Every tick, after the game's own work.
g_pSource2ModHooks->Hook(HookPoint::GameFrame, HookTarget::All(), SOURCE2MOD_MEMBER(this, &Plugin::OnGameFrame), true);
// One input of one kind of entity.
g_pSource2ModHooks->Hook(HookPoint::AcceptInput, HookTarget::All(), OnRelayInput, false, HookFilter{ "logic_relay", "Trigger" });
// Any function, here one client's string commands.
ISource2ModFunction* fn = g_pSource2ModFunctions->Find("CServerSideClient::ExecuteStringCommand");
g_pSource2ModHooks->Hook(HookPoint::Function(fn), HookTarget::Instance(client), OnStringCommand);
```
Fields [#fields]
| Field | Type | Description |
| ----------- | ------------- | -------------------------------------------- |
| `kind` | `Kind` | |
| `instance` | `void*` | |
| `className` | `const char*` | Kind::Class: the RTTI name ("CCSPlayerPawn") |
`All` [#all]
```cpp
static HookTarget All();
```
`Instance` [#instance]
```cpp
static HookTarget Instance(const void* object);
```
| Parameter | Type |
| --------- | ------------- |
| `object` | `const void*` |
`Class` [#class]
```cpp
static HookTarget Class(const char* name);
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`Kind` [#kind]
```cpp
enum class Kind : int32_t
```
| Name | Value |
| ---------- | ----- |
| `All` | `0` |
| `Instance` | |
| `Class` | |
`HookFilter` [#hookfilter]
Compared before a handler runs; nullptr or "" matches anything.
Fields [#fields-1]
| Field | Type | Description |
| -------------- | ------------- | ------------------------------------------ |
| `designerName` | `const char*` | the object is an entity: its designer name |
| `name` | `const char*` | AcceptInput: the input |
`HookPointOf` [#hookpointof]
A point and the context its handlers get, so Hook() checks the handler's signature at compile time.
Fields [#fields-2]
| Field | Type | Description |
| ---------- | --------------------------------------------------------------------------------- | -------------------------- |
| `id` | [`HookPointId`](/docs/core-api/ISource2ModHooks#hookpointid) | |
| `function` | [`ISource2ModFunction*`](/docs/core-api/ISource2ModFunctions#isource2modfunction) | HookPointId::Function only |
`FunctionContext` [#functioncontext]
The context of HookPoint::Function: the arguments by index, as the function describes them (`this` of a member function is index 0), and the return value.
SetArg in a pre handler changes what the original (and the later handlers) get. SetReturn with Override / Supersede is what the caller gets; in a post handler GetReturn is what the original returned. A Vector / string argument is read as a pointer (GetArg\).
`Function` [#function]
```cpp
ISource2ModFunction* Function() const;
```
`This` [#this]
```cpp
void* This() const;
```
`this` of a member function, the first argument otherwise.
`GetArg` [#getarg]
```cpp
template T GetArg(int index) const;
```
| Parameter | Type |
| --------- | ----- |
| `index` | `int` |
`SetArg` [#setarg]
```cpp
template void SetArg(int index, T value);
```
| Parameter | Type |
| --------- | ----- |
| `index` | `int` |
| `value` | `T` |
`GetReturn` [#getreturn]
```cpp
template T GetReturn() const;
```
`SetReturn` [#setreturn]
```cpp
template void SetReturn(T value);
```
| Parameter | Type |
| --------- | ---- |
| `value` | `T` |
`CallOriginal` [#calloriginal]
```cpp
template T CallOriginal();
```
The function now, with the current arguments, past every hook on it. In a pre handler answer Supersede afterwards (with SetReturn), or the game calls it a second time.
`Return` [#return]
```cpp
using Return = void;
```
`HookReturn` [#hookreturn]
What a handler answers: an Action, and for a function with a return value optionally the value -- KHook's \{ action, value }.
```cpp
return { Action::Supersede, 0 }; // skip the original, it returns 0
return { Action::Override, AcquireResult::NotAllowedByMode };
return { Action::Supersede, ctx.CallOriginal() };
return Action::Ignore; // an action alone
```
An action alone takes its value from ctx.result, so a handler that sets the field and returns Override / Supersede keeps working. Several handlers on one point: Supersede stops the chain, otherwise the strongest action wins; each value a handler returns lands in ctx.result right away.
Fields [#fields-3]
| Field | Type |
| ---------- | -------------------------------------------------- |
| `action` | [`Action`](/docs/core-api/ISource2ModTypes#action) |
| `hasValue` | `bool` |
Constructors [#constructors]
```cpp
HookReturn(Action a);
HookReturn(Action a, RETURN v);
```
Enums [#enums]
`HookPointId` [#hookpointid]
```cpp
enum class HookPointId : int32_t
```
Every point. The numbers are SourcePawn's HookPoint\_\* too.
| Name | Value | Description |
| ------------------------------- | ----- | --------------------------------------------------------- |
| `TakeDamage` | `0` | Game functions (ISource2ModHookContexts.h). |
| `CanAcquire` | | |
| `CanMove` | | |
| `CanUse` | | |
| `PostThink` | | |
| `ProcessUsercmds` | | |
| `SimulateUserCommands` | | |
| `RunCommand` | | |
| `AcceptInput` | | |
| `Touch` | | |
| `DropWeapon` | | |
| `AirAccelerate` | | |
| `AirMove` | | |
| `CanUnduck` | | |
| `CategorizePosition` | | |
| `CheckFalling` | | |
| `CheckParameters` | | |
| `CheckVelocity` | | |
| `CheckWater` | | |
| `Duck` | | |
| `Friction` | | |
| `FullWalkMove` | | |
| `GroundAccelerate` | | |
| `LadderMove` | | |
| `MoveInit` | | |
| `PlayerMove` | | |
| `ProcessMovement` | | |
| `SetupMove` | | |
| `TryPlayerMove` | | |
| `WalkMove` | | |
| `WaterMove` | | |
| `OnJumpLegacy` | | |
| `OnJumpModern` | | |
| `CheckJumpButtonLegacy` | | |
| `CheckJumpButtonModern` | | |
| `PlayerChat` | | |
| `Spawn` | `50` | Entity virtuals, on the target's class vtable (SDKHooks). |
| `Use` | | |
| `StartTouch` | | |
| `EndTouch` | | |
| `Blocked` | | |
| `Think` | | |
| `Killed` | | |
| `WeaponPickup` | | |
| `WeaponSwitch` | | |
| `GameFrame` | `100` | Engine callbacks. |
| `StartupServer` | | |
| `ClientConnect` | | |
| `ClientConnected` | | |
| `ClientPutInServer` | | |
| `ClientDisconnect` | | |
| `ClientVoice` | | |
| `ClientSettingsChanged` | | |
| `GameServerSteamAPIActivated` | | |
| `GameServerSteamAPIDeactivated` | | |
| `LoadEventsFromFile` | | |
| `Function` | `300` | HookPoint::Function. |
Functions [#functions]
`Function` [#function-1]
```cpp
inline HookPointOf Function(ISource2ModFunction* fn);
```
Any function from ISource2ModFunctions.
| Parameter | Type |
| --------- | --------------------------------------------------------------------------------- |
| `fn` | [`ISource2ModFunction*`](/docs/core-api/ISource2ModFunctions#isource2modfunction) |
# ISource2ModJSON (/docs/core-api/ISource2ModJSON)
Source2Mod parses; a plugin walks the result through this interface. Nothing library-specific crosses the boundary, so a plugin does not have to vendor a matching JSON implementation or agree with Source2Mod on its ABI.
**Interface id** `ISource2ModJSON001` (`SOURCE2MOD_JSON_INTERFACE`) · **Global** `g_pSource2ModJSON` · **Header** [`source2mod/ISource2ModJSON.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModJSON.h)
Methods [#methods]
`Parse` [#parse]
```cpp
virtual ISource2ModJSONDocument* Parse(const char* pszText, int nLength = -1) = 0;
```
Parses text into a document the caller then owns.
Never returns null: on a parse error the document reports IsValid() false and its root reads as null, so walking it is safe without checking.
| Parameter | Type | Default | Description |
| --------- | ------------- | ------- | ------------------------------------------- |
| `pszText` | `const char*` | | JSON text. |
| `nLength` | `int` | `-1` | Length, or -1 to read up to the terminator. |
**Returns** [`ISource2ModJSONDocument*`](/docs/core-api/ISource2ModJSON#isource2modjsondocument) — A document; call Release() on it when done.
`ISource2ModJSONValue` [#isource2modjsonvalue]
One node of a parsed document: an object, array, or scalar.
Nodes are owned by the document they came from and stay valid until it is released. Every accessor is total -- asking a string for its integer value, or an object for element 3, returns the default you passed rather than throwing.
`IsNull` [#isnull]
```cpp
virtual bool IsNull() const = 0;
```
`IsObject` [#isobject]
```cpp
virtual bool IsObject() const = 0;
```
`IsArray` [#isarray]
```cpp
virtual bool IsArray() const = 0;
```
`IsString` [#isstring]
```cpp
virtual bool IsString() const = 0;
```
`IsNumber` [#isnumber]
```cpp
virtual bool IsNumber() const = 0;
```
`IsBool` [#isbool]
```cpp
virtual bool IsBool() const = 0;
```
`Has` [#has]
```cpp
virtual bool Has(const char* pszKey) const = 0;
```
Whether this object has the key. False for anything but an object.
| Parameter | Type |
| --------- | ------------- |
| `pszKey` | `const char*` |
`Get` [#get]
```cpp
virtual const ISource2ModJSONValue* Get(const char* pszKey) const = 0;
```
The value at a key, or null when absent or not an object.
| Parameter | Type |
| --------- | ------------- |
| `pszKey` | `const char*` |
`Count` [#count]
```cpp
virtual int Count() const = 0;
```
Elements in an array, or members in an object. Zero otherwise.
`At` [#at]
```cpp
virtual const ISource2ModJSONValue* At(int nIndex) const = 0;
```
The element at an index, or null when out of range or not an array.
| Parameter | Type |
| --------- | ----- |
| `nIndex` | `int` |
`KeyAt` [#keyat]
```cpp
virtual const char* KeyAt(int nIndex) const = 0;
```
The key of the member at an index, for walking an object. Null when out of range or not an object.
| Parameter | Type |
| --------- | ----- |
| `nIndex` | `int` |
`AsString` [#asstring]
```cpp
virtual const char* AsString(const char* pszDefault = "") const = 0;
```
This node's value, or pszDefault when it is not a string.
The pointer stays valid as long as the document does.
| Parameter | Type | Default |
| ------------ | ------------- | ------- |
| `pszDefault` | `const char*` | `""` |
`AsInt64` [#asint64]
```cpp
virtual int64_t AsInt64(int64_t nDefault = 0) const = 0;
```
| Parameter | Type | Default |
| ---------- | --------- | ------- |
| `nDefault` | `int64_t` | `0` |
`AsInt` [#asint]
```cpp
virtual int AsInt(int nDefault = 0) const = 0;
```
| Parameter | Type | Default |
| ---------- | ----- | ------- |
| `nDefault` | `int` | `0` |
`AsDouble` [#asdouble]
```cpp
virtual double AsDouble(double flDefault = 0.0) const = 0;
```
| Parameter | Type | Default |
| ----------- | -------- | ------- |
| `flDefault` | `double` | `0.0` |
`AsFloat` [#asfloat]
```cpp
virtual float AsFloat(float flDefault = 0.0f) const = 0;
```
| Parameter | Type | Default |
| ----------- | ------- | ------- |
| `flDefault` | `float` | `0.0f` |
`AsBool` [#asbool]
```cpp
virtual bool AsBool(bool bDefault = false) const = 0;
```
| Parameter | Type | Default |
| ---------- | ------ | ------- |
| `bDefault` | `bool` | `false` |
`GetString` [#getstring]
```cpp
virtual const char* GetString(const char* pszKey, const char* pszDefault = "") const = 0;
```
Shorthand for Get(key) then the matching As\*, with the default when the key is missing or the wrong type.
| Parameter | Type | Default |
| ------------ | ------------- | ------- |
| `pszKey` | `const char*` | |
| `pszDefault` | `const char*` | `""` |
`GetInt64` [#getint64]
```cpp
virtual int64_t GetInt64(const char* pszKey, int64_t nDefault = 0) const = 0;
```
| Parameter | Type | Default |
| ---------- | ------------- | ------- |
| `pszKey` | `const char*` | |
| `nDefault` | `int64_t` | `0` |
`GetInt` [#getint]
```cpp
virtual int GetInt(const char* pszKey, int nDefault = 0) const = 0;
```
| Parameter | Type | Default |
| ---------- | ------------- | ------- |
| `pszKey` | `const char*` | |
| `nDefault` | `int` | `0` |
`GetDouble` [#getdouble]
```cpp
virtual double GetDouble(const char* pszKey, double flDefault = 0.0) const = 0;
```
| Parameter | Type | Default |
| ----------- | ------------- | ------- |
| `pszKey` | `const char*` | |
| `flDefault` | `double` | `0.0` |
`GetFloat` [#getfloat]
```cpp
virtual float GetFloat(const char* pszKey, float flDefault = 0.0f) const = 0;
```
| Parameter | Type | Default |
| ----------- | ------------- | ------- |
| `pszKey` | `const char*` | |
| `flDefault` | `float` | `0.0f` |
`GetBool` [#getbool]
```cpp
virtual bool GetBool(const char* pszKey, bool bDefault = false) const = 0;
```
| Parameter | Type | Default |
| ---------- | ------------- | ------- |
| `pszKey` | `const char*` | |
| `bDefault` | `bool` | `false` |
`Dump` [#dump]
```cpp
virtual const char* Dump(int nIndent = -1) const = 0;
```
This node serialised back to text.
| Parameter | Type | Default | Description |
| --------- | ----- | ------- | --------------------------------------------- |
| `nIndent` | `int` | `-1` | Spaces per level, or -1 for the compact form. |
**Returns** `const char*` — Text owned by the document; valid until it is released.
`ISource2ModJSONDocument` [#isource2modjsondocument]
A parsed document, and the root node it owns.
Release() invalidates every node reached through it.
`IsValid` [#isvalid]
```cpp
virtual bool IsValid() const = 0;
```
Whether the text parsed. A failed parse still returns a document, whose root reads as null, so a caller can skip the check.
`GetError` [#geterror]
```cpp
virtual const char* GetError() const = 0;
```
The parser's complaint, or an empty string when IsValid().
`Root` [#root]
```cpp
virtual const ISource2ModJSONValue* Root() const = 0;
```
`Release` [#release]
```cpp
virtual void Release() = 0;
```
Frees the document and everything reached through it.
# ISource2ModKHook (/docs/core-api/ISource2ModKHook)
**Header** [`source2mod/ISource2ModKHook.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModKHook.h)
Types [#types]
`ResolvedType` [#resolvedtype]
```cpp
template using ResolvedType = std::decay_t;
```
`VirtualHookFor` [#virtualhookfor]
```cpp
template using VirtualHookFor = typename MemberTraits::type>::Virtual;
```
`MemberHookFor` [#memberhookfor]
```cpp
template using MemberHookFor = typename MemberTraits::type>::Member;
```
`FunctionHookFor` [#functionhookfor]
```cpp
template using FunctionHookFor = typename FunctionTraits::type>::Function;
```
`Virtual` [#virtual]
**Inherits** [`KHook::Virtual`](/docs/core-api/ISource2ModKHook#virtual)
`GetIndex` [#getindex]
```cpp
std::int32_t GetIndex() const;
```
The vtable slot this hook is on: what the member function pointer or the index it was made with came to, or what Configure() set.
`ISource2ModHook` [#isource2modhook]
A hook the plugin declared through one of the KHOOK\_\* macros.
Every instance registers itself on construction; KHOOK\_INIT() and KHOOK\_DESTRUCT() walk that list.
`Init` [#init]
```cpp
virtual bool Init() = 0;
```
Resolves the target given at construction and installs the hook.
KHOOK\_INIT() calls this on every hook that exists at that point; a hook constructed later is installed by calling it directly. Once installed it does nothing, and after a failure (an instance still null, say) it can simply be called again.
**Returns** `bool` — false when the target could not be resolved. A hook with no target (nullptr) is left alone and counts as success.
`Destruct` [#destruct]
```cpp
virtual void Destruct() = 0;
```
Detaches and deletes the underlying KHook object.
Deleting is what takes the detour down. Safe to call more than once, and final: the hook cannot be installed again afterwards. The wrapper's destructor calls it too, so destroying the owning object takes its hooks down.
`IsInstalled` [#isinstalled]
```cpp
virtual bool IsInstalled() const = 0;
```
Whether the hook is currently installed.
`InitAll` [#initall]
```cpp
static bool InitAll();
```
Init() on every hook declared in this plugin.
**Returns** `bool` — false when any of them failed; the rest are installed anyway.
`DestructAll` [#destructall]
```cpp
static void DestructAll();
```
Destruct() on every hook declared in this plugin, last first.
`Count` [#count]
```cpp
static int Count();
```
How many hooks this plugin declared -- ISource2ModPlugin::GetRawHookCount().
`CSource2ModVTableName` [#csource2modvtablename]
The vtable a global virtual hook attaches to: an RTTI class name inside a module ("server", "engine2", ...).
Fields [#fields]
| Field | Type |
| ----------- | --------------------------------------------------------- |
| `pszModule` | `const char*` |
| `ppModule` | [`ISource2ModModule**`](/docs/core-api/ISource2ModModule) |
| `pszClass` | `const char*` |
| `pszBase` | `const char*` |
Constructors [#constructors]
```cpp
CSource2ModVTableName() = default;
CSource2ModVTableName(const char* module, const char* name);
CSource2ModVTableName(ISource2ModModule** module, const char* name);
CSource2ModVTableName(const char* module, const char* name, const char* base);
CSource2ModVTableName(ISource2ModModule** module, const char* name, const char* base);
```
The module by name -- loaded through ISource2ModModule::New at Init.
The module through a pointer to an ISource2ModModule\* the plugin fills in at load (read at Init, so it may still be null now).
The table of a base class sub-object inside the class -- for an interface that is not its first base (ISource2ModModule::GetVirtualTableByBase).
`CSource2ModVirtualIndex` [#csource2modvirtualindex]
A virtual hook's slot given both ways: the member function pointer the interface header compiles to, and the name of a gamedata offset for the same function. Gamedata wins at KHOOK\_INIT(); a difference between the two is reported (one of them is stale), and a missing entry for this platform leaves the interface's index in place. KHOOK\_GAMEDATA() makes one.
Fields [#fields-1]
| Field | Type |
| --------- | ------------- |
| `mfp` | `MFP` |
| `pszName` | `const char*` |
`CSource2ModVirtualHook` [#csource2modvirtualhook]
**Inherits** [`ISource2ModHook`](/docs/core-api/ISource2ModKHook#isource2modhook)
Constructors [#constructors-1]
```cpp
template CSource2ModVirtualHook(FUNCTION function, TARGET target, CONTEXT* pContext, PRE pre, POST post);
```
`Init` [#init-1]
```cpp
bool Init() override;
bool Init(CLASS* pInstance);
```
Installs the hook on one instance.
| Parameter | Type |
| ----------- | -------- |
| `pInstance` | `CLASS*` |
`InitGlobal` [#initglobal]
```cpp
bool InitGlobal(ISource2ModMemory vtable);
bool InitGlobal(ISource2ModModule* pModule, const char* pszClass, const char* pszBase = nullptr);
bool InitGlobal(const char* pszModule, const char* pszClass, const char* pszBase = nullptr);
```
Installs the hook on a whole vtable -- every object sharing it.
Installs the hook on the vtable of an RTTI class name in a module, or of its base pszBase.
Same, with the module loaded by name for the lookup.
| Parameter | Type | Default |
| ----------- | -------------------------------------------------------- | --------- |
| `vtable` | [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) | |
| `pModule` | [`ISource2ModModule*`](/docs/core-api/ISource2ModModule) | |
| `pszClass` | `const char*` | |
| `pszBase` | `const char*` | `nullptr` |
| `pszModule` | `const char*` | |
`Destruct` [#destruct-1]
```cpp
void Destruct() override;
```
`IsInstalled` [#isinstalled-1]
```cpp
bool IsInstalled() const override;
```
`Get` [#get]
```cpp
HookType* Get() const;
```
The KHook object itself, for anything the wrapper does not cover.
`CallOriginal` [#calloriginal]
```cpp
RETURN CallOriginal(CLASS* pThis, ARGS... args);
```
Runs the original on pThis, bypassing the hook chain.
| Parameter | Type |
| --------- | --------- |
| `pThis` | `CLASS*` |
| `args` | `ARGS...` |
`HookType` [#hooktype]
```cpp
using HookType = Source2ModKHook::Virtual;
```
`CSource2ModMemberHook` [#csource2modmemberhook]
A KHook::Member (a function with a `this`, found by address) plus where its address comes from.
**Inherits** [`ISource2ModHook`](/docs/core-api/ISource2ModKHook#isource2modhook)
Constructors [#constructors-2]
```cpp
template CSource2ModMemberHook(TARGET target, CONTEXT* pContext, PRE pre, POST post);
```
`Init` [#init-2]
```cpp
bool Init() override;
bool Init(ISource2ModMemory address);
template bool Init(R (*pfnFunction)(A...));
```
Places the detour at an address.
Same, from a typed function pointer (an ADDR\_\* getter, say).
| Parameter | Type |
| ------------- | ------------------------------------------------------- |
| `address` | [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) |
| `pfnFunction` | `R (*)(A...)` |
`Destruct` [#destruct-2]
```cpp
void Destruct() override;
```
`IsInstalled` [#isinstalled-2]
```cpp
bool IsInstalled() const override;
```
`Get` [#get-1]
```cpp
HookType* Get() const;
```
`CallOriginal` [#calloriginal-1]
```cpp
RETURN CallOriginal(CLASS* pThis, ARGS... args);
```
| Parameter | Type |
| --------- | --------- |
| `pThis` | `CLASS*` |
| `args` | `ARGS...` |
`HookType` [#hooktype-1]
```cpp
using HookType = KHook::Member;
```
`CSource2ModFunctionHook` [#csource2modfunctionhook]
A KHook::Function (a free function, found by address) plus where its address comes from.
**Inherits** [`ISource2ModHook`](/docs/core-api/ISource2ModKHook#isource2modhook)
Constructors [#constructors-3]
```cpp
template CSource2ModFunctionHook(TARGET target, CONTEXT* pContext, PRE pre, POST post);
```
`Init` [#init-3]
```cpp
bool Init() override;
bool Init(ISource2ModMemory address);
template bool Init(R (*pfnFunction)(A...));
```
| Parameter | Type |
| ------------- | ------------------------------------------------------- |
| `address` | [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) |
| `pfnFunction` | `R (*)(A...)` |
`Destruct` [#destruct-3]
```cpp
void Destruct() override;
```
`IsInstalled` [#isinstalled-3]
```cpp
bool IsInstalled() const override;
```
`Get` [#get-2]
```cpp
HookType* Get() const;
```
`CallOriginal` [#calloriginal-2]
```cpp
RETURN CallOriginal(ARGS... args);
```
| Parameter | Type |
| --------- | --------- |
| `args` | `ARGS...` |
`HookType` [#hooktype-2]
```cpp
using HookType = KHook::Function;
```
Functions [#functions]
`ToMemory` [#tomemory]
```cpp
inline ISource2ModMemory ToMemory(ISource2ModMemory address);
template inline ISource2ModMemory ToMemory(R (*pfnFunction)(A...));
```
| Parameter | Type |
| ------------- | ------------------------------------------------------- |
| `address` | [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) |
| `pfnFunction` | `R (*)(A...)` |
`Source2ModVirtualIndex` [#source2modvirtualindex]
```cpp
template constexpr CSource2ModVirtualIndex Source2ModVirtualIndex(MFP mfp, const char* pszName);
```
| Parameter | Type |
| --------- | ------------- |
| `mfp` | `MFP` |
| `pszName` | `const char*` |
`Source2ModRawHookCount` [#source2modrawhookcount]
```cpp
inline int Source2ModRawHookCount();
```
See ISource2ModPlugin::GetRawHookCount().
Macros [#macros]
`KHOOK_VTABLE` [#khook_vtable]
```cpp
#define KHOOK_VTABLE(module, name) ::CSource2ModVTableName{ module, name }
```
A whole vtable as a virtual hook's target: an RTTI class name in a module -- the module's name ("server", "engine2", "tier0", ...) or a pointer to an ISource2ModModule\* the plugin fills in at load (`&modules::server`).
`KHOOK_VTABLE_BASE` [#khook_vtable_base]
```cpp
#define KHOOK_VTABLE_BASE(module, name, base) ::CSource2ModVTableName{ module, name, base }
```
The vtable of a base class sub-object inside an RTTI class: for an interface that is not the class's first base, whose table KHOOK\_VTABLE cannot reach. The base's position comes from the RTTI. Needs a core with ISource2ModModule::GetVirtualTableByBase.
`KHOOK_VIRTUAL` [#khook_virtual]
```cpp
#define KHOOK_VIRTUAL(member, function, target, pre, post) ::source2modhook::VirtualHookFor member { function, target, this, pre, post }
```
Declares a virtual hook as a class member.
Which form to pick: a gamedata name (alone or with KHOOK\_GAMEDATA) follows the core's gamedata, so an engine update that moves the slot is fixed by updating the core; a bare `&Class::Method` compiles the slot in from the SDK's header and needs a rebuild when it moves.
```cpp
KHOOK_VIRTUAL(m_hGameFrame, &ISource2Server::GameFrame, &g_pSource2Server, nullptr, &Plugin::Hook_GameFrame);
KHOOK_VIRTUAL(m_hGameFrame, KHOOK_GAMEDATA(&ISource2Server::GameFrame, "ISource2Server::GameFrame"), &g_pSource2Server, nullptr, &Plugin::Hook_GameFrame);
```
`KHOOK_GAMEDATA` [#khook_gamedata]
```cpp
#define KHOOK_GAMEDATA(function, name) ::Source2ModVirtualIndex(function, name)
```
A virtual hook's slot from the interface header and from gamedata at once, for KHOOK\_VIRTUAL's `function`. See there.
`KHOOK_MEMBER` [#khook_member]
```cpp
#define KHOOK_MEMBER(member, target, pre, post) ::source2modhook::MemberHookFor member { [] { return (target); }, this, pre, post }
```
Declares a hook on a member function found by address.
The target is evaluated at KHOOK\_INIT(), not here, so an expression that needs Source2Mod -- ADDR\_TAKE\_DAMAGE\_OLD(), a GAMECONFIG\_RESOLVE() -- is fine even though the owner is usually a global constructed long before Load().
```cpp
KHOOK_MEMBER(m_hPostThink, "CCSPlayerPawn::PostThink", &Plugin::Hook_PostThink, nullptr);
KHOOK_MEMBER(m_hTakeDamageOld, ADDR_TAKE_DAMAGE_OLD(), &Plugin::Hook_TakeDamageOld, nullptr);
```
`KHOOK_FUNCTION` [#khook_function]
```cpp
#define KHOOK_FUNCTION(member, target, pre, post) ::source2modhook::FunctionHookFor member { [] { return (target); }, this, pre, post }
```
Declares a hook on a free function found by address. Same parameters as KHOOK\_MEMBER; the handler takes no hooked object.
`KHOOK_CHECK` [#khook_check]
```cpp
#define KHOOK_CHECK() \
do {
const char* source2modKHookNote = nullptr;
if (!::Source2ModKHookVersionMatches(g_Source2ModCoreKHookHeader, g_Source2ModCoreKHookCommit, error, maxlen, &source2modKHookNote))
return false;
if (source2modKHookNote)
g_pSource2ModAPI->Log(g_pPluginAPI, "%s\n", source2modKHookNote);
} while (0)
```
Installs every hook declared with the macros above. Call in Load(), after SOURCE2MOD\_SAVEVARS() and once the instance pointers hooks read are set.
This is also where the KHook check happens: a plugin that hooks through KHook shares the detour engine with the core and must have been built against the same khook.hpp (IKHook and the template layout; a KHook update confined to its src/ does not count), so a mismatch returns false out of Load() with the reason in `error`. A plugin that never calls this is not checked -- it does not hook, so it does not care. A plugin using raw KHook objects (KHOOK\_NEW) without any KHOOK\_\* macro must call KHOOK\_CHECK() itself before the first one.
Each hook that could not be resolved is logged and skipped.
`KHOOK_DESTRUCT` [#khook_destruct]
```cpp
#define KHOOK_DESTRUCT() ::ISource2ModHook::DestructAll()
```
Takes every hook down. Call in Unload().
Other macros [#other-macros]
| Macro | Expands to |
| -------------- | --------------------------------------------------------------- |
| `KHOOK_INIT()` | `do { KHOOK_CHECK(); ::ISource2ModHook::InitAll(); } while (0)` |
# ISource2ModMaps (/docs/core-api/ISource2ModMaps)
What a map vote, a map chooser or an admin "map" command builds on; none of those are in the core.
A map is named the way the console takes it:
* "de\_dust2" -- a map the server has (IsMapValid()), changed to with `changelevel`;
* "workshop/3070212801" or "3070212801" -- a Workshop map by its ID, changed to with `host_workshop_map`;
* any other name -- assumed to be a map of the server's Workshop collection, changed to with `ds_workshop_changelevel`.
Map lists come from configs/maplists.json, SourceMod's maplists.cfg as JSON:
```cpp
{
"mapcyclefile": { "file": "mapcycle.txt" }, // relative to the game directory
"default": { "target": "mapcyclefile" }, // another list's name
"mapchooser": { "maps": [ "de_dust2", "workshop/3070212801" ] }
}
```
A list that is not in the file falls back to "default", "default" to "mapcyclefile", and that to mapcycle.txt in the game directory. In a list file every line is a map; empty lines and lines starting with //, # or ; are skipped.
**Interface id** `ISource2ModMaps001` (`SOURCE2MOD_MAPS_INTERFACE`) · **Global** `g_pSource2ModMaps` · **Header** [`source2mod/ISource2ModMaps.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModMaps.h)
Methods [#methods]
`GetCurrentMap` [#getcurrentmap]
```cpp
virtual const char* GetCurrentMap() = 0;
```
The map running now ("de\_dust2"); "" before the first one.
`IsMapValid` [#ismapvalid]
```cpp
virtual bool IsMapValid(const char* map) = 0;
```
Whether the server has this map (the engine's IsMapValid). Workshop maps that are not downloaded yet are not valid.
| Parameter | Type |
| --------- | ------------- |
| `map` | `const char*` |
`FindMap` [#findmap]
```cpp
virtual Source2ModFindMapResult FindMap(const char* map, std::string& found) = 0;
```
SourceMod's FindMap: the map a typed name means.
| Parameter | Type | Description |
| --------- | -------------- | ----------------------------------------------- |
| `map` | `const char*` | What was typed. |
| `found` | `std::string&` | Receives the map's full name (or `map` itself). |
`SetNextMap` [#setnextmap]
```cpp
virtual bool SetNextMap(const char* map) = 0;
```
The map the match moves to when it ends.
Sets the game's `nextlevel` as well; for a Workshop map the core changes level itself when the match ends (cs\_win\_panel\_match, after mp\_match\_restart\_delay).
| Parameter | Type |
| --------- | ------------- |
| `map` | `const char*` |
**Returns** `bool` — false for a name FindMap() does not find at all.
`GetNextMap` [#getnextmap]
```cpp
virtual const char* GetNextMap() = 0;
```
The next map: what SetNextMap() set, else the game's `nextlevel`, else the one after the current map in "mapcyclefile".
**Returns** `const char*` — "" when none of those gives one.
`ChangeLevel` [#changelevel]
```cpp
virtual bool ChangeLevel(const char* map, const char* reason) = 0;
```
Changes the map now (SourceMod's ForceChangeLevel).
| Parameter | Type | Description |
| --------- | ------------- | -------------------------------- |
| `map` | `const char*` | A map in any of the forms above. |
| `reason` | `const char*` | Kept in the map history. |
**Returns** `bool` — false for an empty name.
`GetMapHistorySize` [#getmaphistorysize]
```cpp
virtual int GetMapHistorySize() = 0;
```
How many earlier maps the history holds (up to 32).
`GetMapHistory` [#getmaphistory]
```cpp
virtual bool GetMapHistory(int index, Source2ModMapHistoryEntry& entry) = 0;
```
An earlier map, 0 = the one before this one. false when out of range.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------- |
| `index` | `int` |
| `entry` | [`Source2ModMapHistoryEntry&`](/docs/core-api/ISource2ModMaps#source2modmaphistoryentry) |
`ReadMapList` [#readmaplist]
```cpp
virtual bool ReadMapList(const char* name, std::vector& maps, int flags) = 0;
```
Reads a map list (see the file comment).
| Parameter | Type | Description |
| --------- | --------------------------- | ------------------------------------------------------- |
| `name` | `const char*` | The list: "default", "mapcyclefile", or a plugin's own. |
| `maps` | `std::vector&` | The maps are appended here. |
| `flags` | `int` | Source2ModMapListFlags. |
**Returns** `bool` — false when no list could be read (`maps` is left as it was).
`Source2ModMapHistoryEntry` [#source2modmaphistoryentry]
One map that was played, newest first in GetMapHistory().
Fields [#fields]
| Field | Type | Description |
| ----------- | ------------- | ------------------------------------------------------------------------------------ |
| `map` | `std::string` | |
| `reason` | `std::string` | Why the server left it: the reason given to ChangeLevel(), or "Normal level change". |
| `startTime` | `int64_t` | When it started, Unix time. |
Enums [#enums]
`Source2ModFindMapResult` [#source2modfindmapresult]
```cpp
enum Source2ModFindMapResult
```
SourceMod's FindMapResult, the same numbers.
| Name | Value | Description |
| --------------------------------------- | ----- | ---------------------------------------------------------------------------------- |
| `SOURCE2MOD_FINDMAP_FOUND` | `0` | The server has a map by exactly this name. |
| `SOURCE2MOD_FINDMAP_NOT_FOUND` | `1` | Nothing by this name. |
| `SOURCE2MOD_FINDMAP_FUZZY_MATCH` | `2` | One map of the map lists starts with or contains this name; `found` holds it. |
| `SOURCE2MOD_FINDMAP_NON_CANONICAL` | `3` | Not used by this core (SourceMod's workshop name normalisation). |
| `SOURCE2MOD_FINDMAP_POSSIBLY_AVAILABLE` | `4` | A Workshop ID or Workshop name: may well exist, cannot be checked before changing. |
`Source2ModMapListFlags` [#source2modmaplistflags]
```cpp
enum Source2ModMapListFlags
```
ReadMapList() flags, SourceMod's MAPLIST\_FLAG\_\* values.
| Name | Value | Description |
| ------------------------------- | -------- | ------------------------------------------------------------------- |
| `SOURCE2MOD_MAPLIST_MAPSFOLDER` | `1 << 0` | When the list cannot be read, every map in the maps folder instead. |
| `SOURCE2MOD_MAPLIST_NO_DEFAULT` | `1 << 2` | Do not fall back to "default" / "mapcyclefile". |
# ISource2ModMemory (/docs/core-api/ISource2ModMemory)
Plugins use this value type instead of DynLibUtils::CMemory directly so they remain decoupled from the DynLibUtils headers. The implementation is identical: every method delegates 1:1 to the underlying arithmetic.
**Header** [`source2mod/ISource2ModMemory.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModMemory.h)
Constructors [#constructors]
```cpp
ISource2ModMemory();
ISource2ModMemory(const ISource2ModMemory&) noexcept = default;
ISource2ModMemory(ISource2ModMemory&& other) noexcept;
ISource2ModMemory(const uintptr_t ptr);
ISource2ModMemory(const void* ptr);
```
Methods [#methods]
`GetPtr` [#getptr]
```cpp
inline void* GetPtr() const noexcept;
```
The address as a pointer.
`GetAddr` [#getaddr]
```cpp
inline uintptr_t GetAddr() const noexcept;
```
The same address as an integer.
`GetValue` [#getvalue]
```cpp
template inline T GetValue() const noexcept;
```
`CCast` [#ccast]
```cpp
template inline T CCast() const noexcept;
```
`RCast` [#rcast]
```cpp
template inline T RCast() const noexcept;
```
`UCast` [#ucast]
```cpp
template inline T UCast() const noexcept;
```
`Offset` [#offset]
```cpp
inline ISource2ModMemory Offset(ptrdiff_t offset) const noexcept;
```
| Parameter | Type |
| --------- | ----------- |
| `offset` | `ptrdiff_t` |
`OffsetSelf` [#offsetself]
```cpp
inline ISource2ModMemory& OffsetSelf(ptrdiff_t offset) noexcept;
```
| Parameter | Type |
| --------- | ----------- |
| `offset` | `ptrdiff_t` |
`Deref` [#deref]
```cpp
inline ISource2ModMemory Deref(int deref = 1) const;
```
| Parameter | Type | Default |
| --------- | ----- | ------- |
| `deref` | `int` | `1` |
`DerefSelf` [#derefself]
```cpp
inline ISource2ModMemory& DerefSelf(int deref = 1);
```
| Parameter | Type | Default |
| --------- | ----- | ------- |
| `deref` | `int` | `1` |
`FollowNearCall` [#follownearcall]
```cpp
inline ISource2ModMemory FollowNearCall(const ptrdiff_t opcodeOffset = 0x1, const ptrdiff_t nextInstructionOffset = 0x5) const;
```
| Parameter | Type | Default |
| ----------------------- | ----------------- | ------- |
| `opcodeOffset` | `const ptrdiff_t` | `0x1` |
| `nextInstructionOffset` | `const ptrdiff_t` | `0x5` |
`FollowNearCallSelf` [#follownearcallself]
```cpp
inline ISource2ModMemory& FollowNearCallSelf(const ptrdiff_t opcodeOffset = 0x1, const ptrdiff_t nextInstructionOffset = 0x5);
```
| Parameter | Type | Default |
| ----------------------- | ----------------- | ------- |
| `opcodeOffset` | `const ptrdiff_t` | `0x1` |
| `nextInstructionOffset` | `const ptrdiff_t` | `0x5` |
`ResolveRelativeAddress` [#resolverelativeaddress]
```cpp
inline ISource2ModMemory ResolveRelativeAddress(const ptrdiff_t registerOffset = 0x0, const ptrdiff_t nextInstructionOffset = 0x4) const;
```
| Parameter | Type | Default |
| ----------------------- | ----------------- | ------- |
| `registerOffset` | `const ptrdiff_t` | `0x0` |
| `nextInstructionOffset` | `const ptrdiff_t` | `0x4` |
`ResolveRelativeAddressSelf` [#resolverelativeaddressself]
```cpp
inline ISource2ModMemory& ResolveRelativeAddressSelf(const ptrdiff_t registerOffset = 0x0, const ptrdiff_t nextInstructionOffset = 0x4);
```
| Parameter | Type | Default |
| ----------------------- | ----------------- | ------- |
| `registerOffset` | `const ptrdiff_t` | `0x0` |
| `nextInstructionOffset` | `const ptrdiff_t` | `0x4` |
# ISource2ModMenus (/docs/core-api/ISource2ModMenus)
Provides functionality for:
* Defining menus and menu options
* Handling per-player menu instances
* Processing menu navigation and input
A menu is an object of the plugin's: a CenterHtmlMenu filled with options, or a class derived from it that overrides the handlers (DisplayTitle, OnOptionSelected, OnMenuEnd). Options can be fixed or worked out per player (text, disabled, visible), open a submenu (Back returns), or be plain text lines. Keys: the numbered options of the page, 7 Back (previous page, else the parent menu), 8 Next, 9 Exit -- so 1-6 options a page.
```cpp
class ShopMenu : public CenterHtmlMenu
{
public:
ShopMenu() : CenterHtmlMenu("Shop")
{
AddSubmenu("Weapons", &m_weapons);
AddDynamicOption([](CCSPlayerController* p) { return "Credits: " + std::to_string(Credits(p)); }, {})
.Disabled = true;
}
void OnMenuEnd(CCSPlayerController* player, MenuEndReason reason) override { ... }
private:
CenterHtmlMenu m_weapons{"Weapons"};
};
```
Interface for menu system management.
**Interface id** `ISource2ModMenus007` (`SOURCE2MOD_MENUS_INTERFACE`) · **Global** `g_pSource2ModMenus` · **Header** [`source2mod/ISource2ModMenus.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModMenus.h)
Types [#types]
`PluginId` [#pluginid]
```cpp
using PluginId = int;
```
Methods [#methods]
`OpenCenterHtmlMenu` [#opencenterhtmlmenu]
```cpp
virtual void OpenCenterHtmlMenu(PluginId owner, CCSPlayerController* player, CenterHtmlMenu* menu) = 0;
```
Opens a CenterHtmlMenu for a player.
Closed for you if the owning plugin unloads while it is open. The menu object and its option handlers live inside that plugin's library, and the next key press would go looking for them there.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------------- | -------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Plugin the menu belongs to |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) | Target player |
| `menu` | [`CenterHtmlMenu*`](/docs/core-api/ISource2ModMenus#centerhtmlmenu) | Menu to open |
`GetActiveMenu` [#getactivemenu]
```cpp
virtual IMenuInstance* GetActiveMenu(CCSPlayerController* player) = 0;
```
Gets active menu instance for player.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------- | ------------- |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) | Target player |
**Returns** [`IMenuInstance*`](/docs/core-api/ISource2ModMenus#imenuinstance) — Pointer to active menu instance or nullptr
`CloseActiveMenu` [#closeactivemenu]
```cpp
virtual void CloseActiveMenu(CCSPlayerController* player) = 0;
```
Closes active menu for player.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------- | ------------- |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) | Target player |
`OnKeyPress` [#onkeypress]
```cpp
virtual void OnKeyPress(CCSPlayerController* player, int key) = 0;
```
Handles key press input.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------- | ---------------------- |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) | Player who pressed key |
| `key` | `int` | Pressed key (1-9) |
`OpenMenu` [#openmenu]
```cpp
virtual void OpenMenu(PluginId owner, CCSPlayerController* player, IMenuInstance* instance) = 0;
```
Opens a menu instance another library built.
The instance is drawn, refreshed (OnFrame) and fed the keys by the core like its own, and Destroy()ed when closed, replaced, the player leaves or `owner` unloads. A plugin that draws menus somewhere of its own (a HUD menu, say) serves them this way.
| Parameter | Type | Description |
| ---------- | ----------------------------------------------------------------- | -------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Plugin the menu belongs to (whose unload closes it) |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) | Target player |
| `instance` | [`IMenuInstance*`](/docs/core-api/ISource2ModMenus#imenuinstance) | Built with new by the caller; the core owns it from here |
`CloseMenu` [#closemenu]
```cpp
virtual void CloseMenu(IMenu* menu) = 0;
```
Closes a menu for everyone who has it open, also where it is a submenu they are in (MenuEndReason::Closed). Call it before destroying a menu that may be open.
| Parameter | Type |
| --------- | ------------------------------------------------- |
| `menu` | [`IMenu*`](/docs/core-api/ISource2ModMenus#imenu) |
`IsMenuOpen` [#ismenuopen]
```cpp
virtual bool IsMenuOpen(CCSPlayerController* player, IMenu* menu) = 0;
```
Whether @p player has @p menu open, as the root or as the submenu they are in or came through.
| Parameter | Type |
| --------- | ------------------------------------------------------------- |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) |
| `menu` | [`IMenu*`](/docs/core-api/ISource2ModMenus#imenu) |
`GetOpenMenu` [#getopenmenu]
```cpp
virtual IMenu* GetOpenMenu(CCSPlayerController* player) = 0;
```
The menu @p player is looking at (the submenu they are in), or nullptr.
| Parameter | Type |
| --------- | ------------------------------------------------------------- |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) |
`ChatMenuOption` [#chatmenuoption]
Represents a single menu option.
Contains display text, state and callback logic.
Fields [#fields]
| Field | Type | Description |
| ------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `Text` | `std::string` | Display text |
| `OnSelect` | [`std::function`](/viewers/schema#CCSPlayerController) | Callback executed on select |
| `DisabledEvaluator` | [`std::function`](/viewers/schema#CCSPlayerController) | Optional dynamic disabled evaluator, asked for the player it is shown to |
| `TextEvaluator` | [`std::function`](/viewers/schema#CCSPlayerController) | Text worked out for the player it is shown to (replaces Text) |
| `VisibleEvaluator` | [`std::function`](/viewers/schema#CCSPlayerController) | Whether it shows for the player; not shown (and takes no number) when false |
`IMenu` [#imenu]
Base interface for menu definitions.
`Title` [#title]
```cpp
virtual const std::string& Title() const = 0;
```
Gets menu title.
**Returns** `const std::string&` — Reference to menu title string
`SetTitle` [#settitle]
```cpp
virtual void SetTitle(std::string t) = 0;
```
Sets the menu title.
| Parameter | Type | Description |
| --------- | ------------- | ----------- |
| `t` | `std::string` | New title |
`Options` [#options]
```cpp
virtual std::vector& Options() = 0;
virtual const std::vector& Options() const = 0;
```
Gets menu options.
Gets menu options (const).
**Returns** [`std::vector&`](/docs/core-api/ISource2ModMenus#chatmenuoption) — Reference to options vector
`GetPostSelectAction` [#getpostselectaction]
```cpp
virtual PostSelectAction GetPostSelectAction() const = 0;
```
Gets post-select behavior.
**Returns** [`PostSelectAction`](/docs/core-api/ISource2ModMenus#postselectaction) — PostSelectAction value
`SetPostSelectAction` [#setpostselectaction]
```cpp
virtual void SetPostSelectAction(PostSelectAction a) = 0;
```
Sets post-select behavior.
| Parameter | Type | Description |
| --------- | ---------------------------------------------------------------------- | --------------------------------- |
| `a` | [`PostSelectAction`](/docs/core-api/ISource2ModMenus#postselectaction) | Behavior to apply after selection |
`GetExitButton` [#getexitbutton]
```cpp
virtual bool GetExitButton() const = 0;
```
Gets exit button state.
**Returns** `bool` — True if exit button is enabled
`SetExitButton` [#setexitbutton]
```cpp
virtual void SetExitButton(bool v) = 0;
```
Sets exit button state.
| Parameter | Type | Description |
| --------- | ------ | -------------------------- |
| `v` | `bool` | True to enable exit button |
`IBaseMenu` [#ibasemenu]
Base implementation of IMenu.
**Inherits** [`IMenu`](/docs/core-api/ISource2ModMenus#imenu)
Fields [#fields-1]
| Field | Type |
| ------------- | ---------------------------------------------------------------------------------- |
| `s_canSelect` | [`std::function`](/viewers/schema#CCSPlayerController) |
| `s_onSelect` | [`std::function`](/viewers/schema#CCSPlayerController) |
Constructors [#constructors]
```cpp
explicit IBaseMenu(std::string title);
```
Constructs a menu.
`Title` [#title-1]
```cpp
const std::string& Title() const override;
```
`SetTitle` [#settitle-1]
```cpp
void SetTitle(std::string t) override;
```
| Parameter | Type |
| --------- | ------------- |
| `t` | `std::string` |
`Options` [#options-1]
```cpp
std::vector& Options() override;
const std::vector& Options() const override;
```
`ClearOptions` [#clearoptions]
```cpp
void ClearOptions();
```
Drops every option, for a menu that is rebuilt in place.
`GetPostSelectAction` [#getpostselectaction-1]
```cpp
PostSelectAction GetPostSelectAction() const override;
```
`SetPostSelectAction` [#setpostselectaction-1]
```cpp
void SetPostSelectAction(PostSelectAction a) override;
```
Sets post-select behavior.
| Parameter | Type | Description |
| --------- | ---------------------------------------------------------------------- | ----------------- |
| `a` | [`PostSelectAction`](/docs/core-api/ISource2ModMenus#postselectaction) | Behavior to apply |
`GetExitButton` [#getexitbutton-1]
```cpp
bool GetExitButton() const override;
```
`SetExitButton` [#setexitbutton-1]
```cpp
void SetExitButton(bool v) override;
```
Sets exit button state.
| Parameter | Type | Description |
| --------- | ------ | -------------------------- |
| `v` | `bool` | True to enable exit button |
`GetItemsPerPage` [#getitemsperpage]
```cpp
int GetItemsPerPage() const;
```
Options a page, 1-6 (keys 7, 8, 9 are Back, Next, Exit); default 5.
`SetItemsPerPage` [#setitemsperpage]
```cpp
void SetItemsPerPage(int count);
```
| Parameter | Type |
| --------- | ----- |
| `count` | `int` |
`GetTimeout` [#gettimeout]
```cpp
int GetTimeout() const;
```
Seconds after which the menu closes (MenuEndReason::Timeout); 0 = never.
`SetTimeout` [#settimeout]
```cpp
void SetTimeout(int seconds);
```
| Parameter | Type |
| --------- | ----- |
| `seconds` | `int` |
`SetDynamicTitle` [#setdynamictitle]
```cpp
void SetDynamicTitle(std::function title);
```
A title worked out per player (what the default DisplayTitle shows).
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `title` | [`std::function`](/viewers/schema#CCSPlayerController) |
`SetEndHandler` [#setendhandler]
```cpp
void SetEndHandler(std::function handler);
```
What the default OnMenuEnd calls.
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------------- |
| `handler` | [`std::function`](/viewers/schema#CCSPlayerController) |
`DisplayTitle` [#displaytitle]
```cpp
virtual std::string DisplayTitle(CCSPlayerController* player);
```
The title shown to a player. Override for a title of your own; the default is the dynamic title if set, else Title().
| Parameter | Type |
| --------- | ------------------------------------------------------------- |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) |
`OnOptionSelected` [#onoptionselected]
```cpp
virtual void OnOptionSelected(CCSPlayerController* player, ChatMenuOption& option, int index);
```
An option was picked (not a submenu, not text). The default runs its OnSelect. @p index is its index in Options().
| Parameter | Type |
| --------- | ------------------------------------------------------------------- |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) |
| `option` | [`ChatMenuOption&`](/docs/core-api/ISource2ModMenus#chatmenuoption) |
| `index` | `int` |
`OnMenuEnd` [#onmenuend]
```cpp
virtual void OnMenuEnd(CCSPlayerController* player, MenuEndReason reason);
```
The menu (opened as the root) went away for a player. The default calls the end handler. Not called when the owning plugin unloads.
| Parameter | Type |
| --------- | ---------------------------------------------------------------- |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) |
| `reason` | [`MenuEndReason`](/docs/core-api/ISource2ModMenus#menuendreason) |
`AddSubmenu` [#addsubmenu]
```cpp
ChatMenuOption& AddSubmenu(std::string display, IBaseMenu* submenu, bool disabled = false);
```
An option that opens @p submenu.
| Parameter | Type | Default |
| ---------- | --------------------------------------------------------- | ------- |
| `display` | `std::string` | |
| `submenu` | [`IBaseMenu*`](/docs/core-api/ISource2ModMenus#ibasemenu) | |
| `disabled` | `bool` | `false` |
`AddText` [#addtext]
```cpp
ChatMenuOption& AddText(std::string text);
```
A line of text that cannot be picked and takes no number.
| Parameter | Type |
| --------- | ------------- |
| `text` | `std::string` |
`AddDynamicOption` [#adddynamicoption]
```cpp
ChatMenuOption& AddDynamicOption(std::function text, std::function onSelect, std::function disabledEvaluator = nullptr, std::function visibleEvaluator = nullptr);
```
An option whose text, and optionally disabled / visible state, is worked out per player.
| Parameter | Type | Default |
| ------------------- | --------------------------------------------------------------------------------------------------- | --------- |
| `text` | [`std::function`](/viewers/schema#CCSPlayerController) | |
| `onSelect` | [`std::function`](/viewers/schema#CCSPlayerController) | |
| `disabledEvaluator` | [`std::function`](/viewers/schema#CCSPlayerController) | `nullptr` |
| `visibleEvaluator` | [`std::function`](/viewers/schema#CCSPlayerController) | `nullptr` |
`AddMenuOption` [#addmenuoption]
```cpp
ChatMenuOption& AddMenuOption(std::string display, std::function onSelect, bool disabled = false);
```
Adds a menu option.
| Parameter | Type | Default | Description |
| ---------- | --------------------------------------------------------------------------------------------------- | ------- | ------------------------------ |
| `display` | `std::string` | | Display text |
| `onSelect` | [`std::function`](/viewers/schema#CCSPlayerController) | | Callback executed on selection |
| `disabled` | `bool` | `false` | Whether option is disabled |
**Returns** [`ChatMenuOption&`](/docs/core-api/ISource2ModMenus#chatmenuoption) — Reference to created option
`AddMenuOptionWithCooldown` [#addmenuoptionwithcooldown]
```cpp
ChatMenuOption& AddMenuOptionWithCooldown(std::string optionText, std::function action, bool disabled = false, bool close = true, std::function disabledEvaluator = nullptr);
```
Adds a menu option with cooldown support.
| Parameter | Type | Default | Description |
| ------------------- | --------------------------------------------------------------------------------------------------- | --------- | ----------------------------------- |
| `optionText` | `std::string` | | Display text |
| `action` | [`std::function`](/viewers/schema#CCSPlayerController) | | Callback executed on selection |
| `disabled` | `bool` | `false` | Whether option is disabled |
| `close` | `bool` | `true` | Whether menu closes after select |
| `disabledEvaluator` | [`std::function`](/viewers/schema#CCSPlayerController) | `nullptr` | Optional dynamic disabled evaluator |
**Returns** [`ChatMenuOption&`](/docs/core-api/ISource2ModMenus#chatmenuoption) — Reference to created option
`SetCooldownHandlers` [#setcooldownhandlers]
```cpp
static void SetCooldownHandlers(std::function canSelect, std::function onSelect);
```
Sets global cooldown handlers.
| Parameter | Type | Description |
| ----------- | ---------------------------------------------------------------------------------- | ----------------------------------------- |
| `canSelect` | [`std::function`](/viewers/schema#CCSPlayerController) | Function that checks if player can select |
| `onSelect` | [`std::function`](/viewers/schema#CCSPlayerController) | Function called after selection |
`IMenuInstance` [#imenuinstance]
Runtime instance of a menu bound to a player.
`Display` [#display]
```cpp
virtual void Display() = 0;
```
Displays the menu.
`OnKeyPress` [#onkeypress-1]
```cpp
virtual void OnKeyPress(CCSPlayerController* player, int key) = 0;
```
Handles key press input.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------- | -------------------------- |
| `player` | [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) | Player who triggered input |
| `key` | `int` | Pressed key (1-9) |
`Reset` [#reset]
```cpp
virtual void Reset();
```
Resets menu state.
`Close` [#close]
```cpp
virtual void Close() = 0;
```
Closes the menu.
`OnFrame` [#onframe]
```cpp
virtual void OnFrame();
```
Once a frame while this is the player's open menu.
The default redraws, which is what a center HTML menu needs (it fades). A menu drawn on a HUD layout overrides it to refresh only when something changed.
`AcceptsKeys` [#acceptskeys]
```cpp
virtual bool AcceptsKeys() const;
```
Whether the number keys (chat triggers, binds) pick options.
A menu the player clicks instead returns false and the core drops the key presses while it is open.
`Destroy` [#destroy]
```cpp
virtual void Destroy();
```
Destroys the instance.
The core calls this, never delete, when the menu is closed, replaced or the player leaves -- so an instance a plugin built (OpenMenu) is freed by the library that allocated it.
`OnEnd` [#onend]
```cpp
virtual void OnEnd(MenuEndReason reason);
```
The menu is going away for this player, just before Destroy(); not called when the owning plugin unloads. (ISource2ModMenus006)
| Parameter | Type |
| --------- | ---------------------------------------------------------------- |
| `reason` | [`MenuEndReason`](/docs/core-api/ISource2ModMenus#menuendreason) |
`KeyMask` [#keymask]
```cpp
virtual uint32_t KeyMask() const;
```
The number keys the menu does something with right now, as a bit mask (bit 1 = key 1 ... bit 9 = key 9). A key outside it is left to the game: with no binds, Source2Mod reads the keys from the weapon slots, and only the keys in the mask are taken. The default takes 1-9. (ISource2ModMenus007)
`Menu` [#menu]
```cpp
IMenu* Menu() const;
```
Gets underlying menu.
**Returns** [`IMenu*`](/docs/core-api/ISource2ModMenus#imenu) — Pointer to menu
`Player` [#player]
```cpp
CCSPlayerController* Player();
```
Gets owning player.
**Returns** [`CCSPlayerController*`](/viewers/schema#CCSPlayerController) — Pointer to player
`CenterHtmlMenu` [#centerhtmlmenu]
Styled center HTML menu.
**Inherits** [`IBaseMenu`](/docs/core-api/ISource2ModMenus#ibasemenu)
Fields [#fields-2]
| Field | Type |
| --------------- | ------------- |
| `TitleColor` | `std::string` |
| `EnabledColor` | `std::string` |
| `DisabledColor` | `std::string` |
| `PrevPageColor` | `std::string` |
| `NextPageColor` | `std::string` |
| `CloseColor` | `std::string` |
Constructors [#constructors-1]
```cpp
explicit CenterHtmlMenu(std::string title);
```
Constructs center HTML menu.
Enums [#enums]
`PostSelectAction` [#postselectaction]
```cpp
enum class PostSelectAction
```
Action executed after selecting a menu option.
| Name | Description |
| --------- | -------------------------- |
| `Close` | Close menu after selection |
| `Reset` | Reset menu to first page |
| `Nothing` | Do nothing |
`MenuEndReason` [#menuendreason]
```cpp
enum class MenuEndReason
```
Why a menu went away for a player (IBaseMenu::OnMenuEnd).
| Name | Description |
| -------------- | --------------------------------------------------------------- |
| `Selected` | An option closed it (CloseOnSelect, or PostSelectAction::Close) |
| `Exit` | The player pressed Exit |
| `Replaced` | Another menu was opened over it |
| `Closed` | CloseActiveMenu / CloseMenu |
| `Disconnected` | The player left |
| `Timeout` | The menu's timeout ran out |
Macros [#macros]
| Macro | Expands to |
| ------------------------------------- | ------------------------------------------------------------------ |
| `OPEN_CENTER_HTML_MENU(player, menu)` | `g_pSource2ModMenus->OpenCenterHtmlMenu(g_PluginID, player, menu)` |
| `GET_ACTIVE_MENU(player)` | `g_pSource2ModMenus->GetActiveMenu(player)` |
| `CLOSE_ACTIVE_MENU(player)` | `g_pSource2ModMenus->CloseActiveMenu(player)` |
| `MENU_ON_KEY_PRESS(player, key)` | `g_pSource2ModMenus->OnKeyPress(player, key)` |
# ISource2ModModule (/docs/core-api/ISource2ModModule)
Plugins obtain an ISource2ModModule via ISource2ModAPI::LoadModule() and release it with ISource2ModAPI::FreeModule(). The implementation inside Source2Mod core delegates every call 1:1 to DynLibUtils::CModule so plugins are fully decoupled from the DynLibUtils headers.
**Header** [`source2mod/ISource2ModModule.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModModule.h)
Methods [#methods]
`InitFromName` [#initfromname]
```cpp
virtual bool InitFromName(const char* name, bool extension = false) = 0;
```
Initialises the module from its name (without extension).
| Parameter | Type | Default | Description |
| ----------- | ------------- | ------- | ----------------------------------------------------------- |
| `name` | `const char*` | | Module name, e.g. "server" or "engine2". |
| `extension` | `bool` | `false` | When true the platform extension is appended automatically. |
**Returns** `bool` — True on success.
`InitFromMemory` [#initfrommemory]
```cpp
virtual bool InitFromMemory(uintptr_t ptr) = 0;
```
Initialises the module from an address inside it.
| Parameter | Type | Description |
| --------- | ----------- | ---------------------------------------------- |
| `ptr` | `uintptr_t` | Any address that belongs to the target module. |
**Returns** `bool` — True on success.
`FindPattern` [#findpattern]
```cpp
virtual ISource2ModMemory FindPattern(const char* pattern, ISource2ModMemory startAddress = ISource2ModMemory()) const = 0;
```
Scans the executable section for a byte-string pattern.
Pattern format: "AB CD ?? EF" (space-separated hex bytes, ?? = wildcard).
| Parameter | Type | Default | Description |
| -------------- | ------------------------------------------------------- | --------------------- | ---------------------------------------- |
| `pattern` | `const char*` | | IDA-style pattern string. |
| `startAddress` | [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) | `ISource2ModMemory()` | Optional address to start scanning from. |
**Returns** [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) — Address of the first match, or an invalid (null) ISource2ModMemory.
`FindPatternInSection` [#findpatterninsection]
```cpp
virtual ISource2ModMemory FindPatternInSection(const char* pattern, const char* section, ISource2ModMemory startAddress = ISource2ModMemory()) const = 0;
```
Scans a named section for a byte-string pattern.
| Parameter | Type | Default | Description |
| -------------- | ------------------------------------------------------- | --------------------- | ---------------------------------------- |
| `pattern` | `const char*` | | IDA-style pattern string. |
| `section` | `const char*` | | Section name, e.g. ".text" or ".rdata". |
| `startAddress` | [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) | `ISource2ModMemory()` | Optional address to start scanning from. |
**Returns** [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) — Address of the first match, or an invalid (null) ISource2ModMemory.
`GetVirtualTableByName` [#getvirtualtablebyname]
```cpp
virtual ISource2ModMemory GetVirtualTableByName(const char* name, bool decorated = false) const = 0;
```
Returns the address of a virtual table by mangled class name.
| Parameter | Type | Default | Description |
| ----------- | ------------- | ------- | -------------------------------------------------- |
| `name` | `const char*` | | Class name (undecorated unless decorated is true). |
| `decorated` | `bool` | `false` | Pass true when name is already a decorated symbol. |
`GetFunctionByName` [#getfunctionbyname]
```cpp
virtual ISource2ModMemory GetFunctionByName(const char* name) const = 0;
```
Returns the address of an exported symbol by name.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`GetSectionByName` [#getsectionbyname]
```cpp
virtual SectionInfo GetSectionByName(const char* name) const = 0;
```
Returns information about a named section.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`GetModuleHandle` [#getmodulehandle]
```cpp
virtual void* GetModuleHandle() const = 0;
```
Returns the OS-native module handle (HMODULE / void\*).
`GetModuleBase` [#getmodulebase]
```cpp
virtual ISource2ModMemory GetModuleBase() const = 0;
```
Returns the base load address of the module.
`GetModulePath` [#getmodulepath]
```cpp
virtual const char* GetModulePath() const = 0;
```
Returns the absolute filesystem path of the module.
The pointer is valid for the lifetime of this ISource2ModModule.
`GetModuleName` [#getmodulename]
```cpp
virtual const char* GetModuleName() const = 0;
```
Returns the filename component of the module path.
The pointer is valid for the lifetime of this ISource2ModModule.
`GetVirtualTableByOffset` [#getvirtualtablebyoffset]
```cpp
virtual ISource2ModMemory GetVirtualTableByOffset(const char* name, std::ptrdiff_t offset, bool decorated = false) const = 0;
```
Returns the virtual table of the sub-object at an offset inside a class.
A class with several bases has one table per base that is not at offset 0; offset 0 is the primary table, the one GetVirtualTableByName returns. The offset moves whenever the class is laid out anew -- prefer GetVirtualTableByBase.
| Parameter | Type | Default | Description |
| ----------- | ---------------- | ------- | -------------------------------------------------- |
| `name` | `const char*` | | Class name (undecorated unless decorated is true). |
| `offset` | `std::ptrdiff_t` | | Offset of the sub-object in bytes. |
| `decorated` | `bool` | `false` | Pass true when name is already a decorated symbol. |
`GetVirtualTableByBase` [#getvirtualtablebybase]
```cpp
virtual ISource2ModMemory GetVirtualTableByBase(const char* name, const char* baseName, bool decorated = false) const = 0;
```
Returns the virtual table of a base class sub-object, located through the RTTI.
For an interface that is not a class's first base, whose table GetVirtualTableByName cannot reach. Non-virtual bases only.
| Parameter | Type | Default | Description |
| ----------- | ------------- | ------- | -------------------------------------------------------- |
| `name` | `const char*` | | Class name (undecorated unless decorated is true). |
| `baseName` | `const char*` | | Base class name. |
| `decorated` | `bool` | `false` | Pass true when both names are already decorated symbols. |
`New` [#new]
```cpp
static ISource2ModModule* New(const char* name);
static ISource2ModModule* New(uintptr_t ptr);
static ISource2ModModule* New(void* ptr);
static ISource2ModModule* New(ISource2ModMemory mem);
```
Creates a module wrapper by name (without extension).
Equivalent to `g_pSource2ModAPI->LoadModule(name)`. Release with `delete` or `g_pSource2ModAPI->FreeModule()`.
Creates a module wrapper from an address that belongs to the module.
Creates a module wrapper from a void pointer inside the module.
Creates a module wrapper from an ISource2ModMemory address inside the module.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------- | -------------------------------------- |
| `name` | `const char*` | Module name, e.g. "server" or "tier0". |
| `ptr` | `uintptr_t` | Any address inside the target module. |
| `ptr` | `void*` | Any address inside the target module. |
| `mem` | [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) | |
Macros [#macros]
`WIN_LINUX` [#win_linux]
```cpp
#define WIN_LINUX(win, lin) win
```
The Windows or the Linux one of two things -- a pattern, an offset, a library name -- picked at compile time.
```cpp
pServer->FindPattern(WIN_LINUX("48 89 7C 24 ? 55", "55 48 89 E5 41 57"));
```
Prefer a gamedata entry for anything the game moves: that is updated without a rebuild. This is for a pattern a plugin keeps to itself.
Other macros [#other-macros]
| Macro | Expands to |
| ---------------------- | --------------------------------------------- |
| `WIN_LINUX(win, lin)` | `lin` |
| `LOAD_MODULE(name)` | `g_pSource2ModAPI->LoadModule(name)` |
| `LOAD_MODULE_MEM(ptr)` | `g_pSource2ModAPI->LoadModuleFromMemory(ptr)` |
| `FREE_MODULE(mod)` | `g_pSource2ModAPI->FreeModule(mod)` |
# ISource2ModNatives (/docs/core-api/ISource2ModNatives)
What a SourceMod extension does for its scripts: register natives a .sp declares and calls, a library its scripts can test for, and handles of a type of its own. The natives are SourcePawn's -- SPVM\_NATIVE\_FUNC, called with the script's IPluginContext and its parameters -- so the SourcePawn headers come with this one (public/sourcepawn, the core's VM version).
SOURCE2MOD\_SAVEVARS() fills g\_pSource2ModNatives like every other global (null on a core without script support); including this header is only needed to call it.
```cpp
g_pSource2ModNatives->AddNatives(g_PluginID, g_MyNatives);
```
Everything registered here goes when the plugin unloads: its natives become unbound (a script calling one gets an error), its libraries are removed (OnLibraryRemoved) and its handle types are destroyed together with every handle of them.
The other way round (002): a script's API, called from C++. A script exposes it the way it does for other scripts -- natives (Source2ModNatives.CreateNative), a library (RegisterLibrary) and global forwards (GlobalForward) -- and a C++ plugin reaches them by name:
```cpp
ISource2ModScriptNative* give = g_pSource2ModNatives->FindScriptNative("Shop_GiveCredits");
int total = give->Call(pController, 100, "kill bonus"); // 0 when no script serves it
if (!give->TryCall(&total, pController, 100, "kill bonus"))
Msg("%s\n", give->GetLastError());
g_pSource2ModNatives->HookScriptForward("Shop_OnBuy", [](const char*, Source2ModScriptArg* args, int count) -> cell_t
{
return args[1].AsInt() > 1000 ? 2 : 0; // Action_Supersede : Action_Ignore
});
g_pSource2ModNatives->AddLibraryListener([](const char* name, bool added) { ... });
```
The objects Find\* returns belong to the core and stay valid until it unloads: they name the native or forward, not the script, so one found before the script loads works once it does, and a call after the script unloaded is refused (false / a value-initialised result) instead of reaching freed code.
**Interface id** `ISource2ModNatives002` (`SOURCE2MOD_NATIVES_INTERFACE`) · **Global** `g_pSource2ModNatives` · **Header** [`source2mod/ISource2ModNatives.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModNatives.h)
Types [#types]
`Source2ModHandleType` [#source2modhandletype]
```cpp
using Source2ModHandleType = uint32_t;
```
A handle type registered through CreateHandleType(); 0 is none.
`Source2ModHandleDestructor` [#source2modhandledestructor]
```cpp
using Source2ModHandleDestructor = void (*)(void* object);
```
Takes a handle's object when its last handle is closed.
`Source2ModScriptForwardHandler` [#source2modscriptforwardhandler]
```cpp
using Source2ModScriptForwardHandler = Source2ModCallback;
```
A C++ hook on a script global forward. @p args are the forward's parameters; a reference, an array or a string buffer is written back to the script that fired it. Returns what a script function of the forward would (an Action for most).
`Source2ModLibraryHandler` [#source2modlibraryhandler]
```cpp
using Source2ModLibraryHandler = Source2ModCallback;
```
A library came (added) or went, as OnLibraryAdded / OnLibraryRemoved.
Methods [#methods]
`AddNatives` [#addnatives]
```cpp
virtual bool AddNatives(PluginId owner, const sp_nativeinfo_t* natives) = 0;
```
Registers natives for scripts.
Scripts already loaded get the ones they were missing; a script that failed to load for want of one of them is loaded again. A name that a live plugin already registered is refused (and logged), the rest of the list still goes in.
| Parameter | Type | Description |
| --------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | The plugin's id (g\_PluginID) |
| `natives` | `const sp_nativeinfo_t*` | Name and function pairs, ending in \{ nullptr, nullptr }; copied, the array need not outlive the call |
**Returns** `bool` — false when any of them was refused
`RegisterLibrary` [#registerlibrary]
```cpp
virtual bool RegisterLibrary(PluginId owner, const char* name) = 0;
```
Registers a library, what LibraryExists() and OnLibraryAdded() in a script see. Scripts that are running get OnLibraryAdded now.
Several plugins may register the same library; it is removed (OnLibraryRemoved) when the last of them unloads.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `name` | `const char*` |
**Returns** `bool` — false on an empty name
`LibraryExists` [#libraryexists]
```cpp
virtual bool LibraryExists(const char* name) = 0;
```
Whether a plugin (script or native) registered this library.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`GetScriptPlugin` [#getscriptplugin]
```cpp
virtual PluginId GetScriptPlugin(SourcePawn::IPluginContext* ctx) = 0;
```
The script plugin a native was called from.
| Parameter | Type |
| --------- | ----------------------------- |
| `ctx` | `SourcePawn::IPluginContext*` |
**Returns** [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) — Its plugin id, 0 when the context is no script's
`FormatString` [#formatstring]
```cpp
virtual bool FormatString(SourcePawn::IPluginContext* ctx, const cell_t* params, int fmtParam, char* buffer, size_t maxlen, size_t* written = nullptr) = 0;
```
SourceMod's format, as Format() and PrintToServer() do it: the format at params\[fmtParam], its arguments after it.
| Parameter | Type | Default |
| ---------- | ----------------------------- | --------- |
| `ctx` | `SourcePawn::IPluginContext*` | |
| `params` | `const cell_t*` | |
| `fmtParam` | `int` | |
| `buffer` | `char*` | |
| `maxlen` | `size_t` | |
| `written` | `size_t*` | `nullptr` |
**Returns** `bool` — false after reporting the error to the script
`CreateHandleType` [#createhandletype]
```cpp
virtual Source2ModHandleType CreateHandleType(PluginId owner, const char* name, Source2ModHandleDestructor destroy) = 0;
```
Registers a handle type for the objects this plugin hands out.
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | |
| `name` | `const char*` | |
| `destroy` | [`Source2ModHandleDestructor`](/docs/core-api/ISource2ModNatives#source2modhandledestructor) | Called with the object when its last handle is closed (CloseHandle / delete, the owning script unloading); may be nullptr |
`CreateHandle` [#createhandle]
```cpp
virtual cell_t CreateHandle(Source2ModHandleType type, void* object, SourcePawn::IPluginContext* ctx) = 0;
```
A new handle to `object`, owned by the script that `ctx` is -- closed when that script unloads.
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------- |
| `type` | [`Source2ModHandleType`](/docs/core-api/ISource2ModNatives#source2modhandletype) |
| `object` | `void*` |
| `ctx` | `SourcePawn::IPluginContext*` |
**Returns** `cell_t` — The handle, 0 on an unknown type or a full handle table
`ReadHandle` [#readhandle]
```cpp
virtual void* ReadHandle(SourcePawn::IPluginContext* ctx, cell_t handle, Source2ModHandleType type) = 0;
```
The object behind a handle of this type.
| Parameter | Type |
| --------- | -------------------------------------------------------------------------------- |
| `ctx` | `SourcePawn::IPluginContext*` |
| `handle` | `cell_t` |
| `type` | [`Source2ModHandleType`](/docs/core-api/ISource2ModNatives#source2modhandletype) |
**Returns** `void*` — The object, or nullptr after reporting SourceMod's "Invalid Handle" error to the script
`CloseHandle` [#closehandle]
```cpp
virtual bool CloseHandle(cell_t handle) = 0;
```
Closes a handle whoever owns it.
| Parameter | Type |
| --------- | -------- |
| `handle` | `cell_t` |
`FindScriptNative` [#findscriptnative]
```cpp
virtual ISource2ModScriptNative* FindScriptNative(const char* name) = 0;
```
A native a script made (CreateNative), by name.
Never nullptr for a non-empty name: the object stands for the name, so it can be found before the script registers the native and keeps working across the script's reloads. IsAvailable() says whether a script serves it now; a native a C++ plugin registered is not available here (call that plugin's code directly).
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`FindScriptForward` [#findscriptforward]
```cpp
virtual ISource2ModScriptForward* FindScriptForward(const char* name) = 0;
```
A global forward by name (see ISource2ModScriptForward); never nullptr for a non-empty name.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`HookScriptForward` [#hookscriptforward]
```cpp
virtual Source2ModHookId HookScriptForward(const char* name, Source2ModScriptForwardHandler handler) = 0;
```
Hooks a script global forward: the handler runs whenever a script fires it (after the scripts' own functions), and when C++ calls it through FindScriptForward(). Goes when the plugin that made the handler unloads.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------------- |
| `name` | `const char*` |
| `handler` | [`Source2ModScriptForwardHandler`](/docs/core-api/ISource2ModNatives#source2modscriptforwardhandler) |
**Returns** [`Source2ModHookId`](/docs/core-api/ISource2ModTypes#source2modhookid) — The hook's id, 0 on an empty name or handler
`UnhookScriptForward` [#unhookscriptforward]
```cpp
virtual bool UnhookScriptForward(Source2ModHookId id) = 0;
```
| Parameter | Type |
| --------- | ---------------------------------------------------------------------- |
| `id` | [`Source2ModHookId`](/docs/core-api/ISource2ModTypes#source2modhookid) |
`AddLibraryListener` [#addlibrarylistener]
```cpp
virtual Source2ModHookId AddLibraryListener(Source2ModLibraryHandler handler, bool replay = true) = 0;
```
Tells about libraries coming and going -- a script's RegisterLibrary or a C++ RegisterLibrary(), what scripts get as OnLibraryAdded / OnLibraryRemoved. A library goes when the last plugin that registered it unloads; by then that plugin's natives no longer answer (a call from the handler is refused).
| Parameter | Type | Default | Description |
| --------- | ---------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------- |
| `handler` | [`Source2ModLibraryHandler`](/docs/core-api/ISource2ModNatives#source2modlibraryhandler) | | |
| `replay` | `bool` | `true` | Call the handler now for every library already there |
**Returns** [`Source2ModHookId`](/docs/core-api/ISource2ModTypes#source2modhookid) — The listener's id, 0 on an empty handler
`RemoveLibraryListener` [#removelibrarylistener]
```cpp
virtual bool RemoveLibraryListener(Source2ModHookId id) = 0;
```
| Parameter | Type |
| --------- | ---------------------------------------------------------------------- |
| `id` | [`Source2ModHookId`](/docs/core-api/ISource2ModTypes#source2modhookid) |
`EntityFromScriptCell` [#entityfromscriptcell]
```cpp
virtual CEntityInstance* EntityFromScriptCell(cell_t value) = 0;
```
The live entity a script's entity cell names (natives, forward arguments); nullptr for NULL\_ENTITY or a removed one.
| Parameter | Type |
| --------- | -------- |
| `value` | `cell_t` |
`ScriptCellFromEntity` [#scriptcellfromentity]
```cpp
virtual cell_t ScriptCellFromEntity(CEntityInstance* entity) = 0;
```
An entity as a script's entity methodmap value; -1 (NULL\_ENTITY) for nullptr.
| Parameter | Type |
| --------- | ------------------ |
| `entity` | `CEntityInstance*` |
`Source2ModScriptBuffer` [#source2modscriptbuffer]
A string a script writes into: Call(..., Source2ModScriptBuffer\{ buf, sizeof(buf) }).
Fields [#fields]
| Field | Type |
| ------ | -------- |
| `data` | `char*` |
| `size` | `size_t` |
`Source2ModScriptArray` [#source2modscriptarray]
A cell array: Call(..., Source2ModScriptArray\{ values, 3 }); copyback takes the script's changes.
Fields [#fields-1]
| Field | Type |
| ---------- | --------- |
| `data` | `cell_t*` |
| `count` | `size_t` |
| `copyback` | `bool` |
`Source2ModScriptArg` [#source2modscriptarg]
One argument of a call into a script, or of a script forward a C++ hook receives. The pointers are the caller's: a script writing a reference, an array or a string buffer writes through them.
Fields [#fields-2]
| Field | Type | Description |
| ---------- | -------------------------------------------------------------------------------------- | --------------------------------- |
| `type` | [`Source2ModScriptArgType`](/docs/core-api/ISource2ModNatives#source2modscriptargtype) | |
| `size` | `uint32_t` | StringBuffer: bytes, Array: cells |
| `copyback` | `bool` | Array |
Constructors [#constructors]
```cpp
Source2ModScriptArg();
```
`Int` [#int]
```cpp
static Source2ModScriptArg Int(cell_t value);
```
| Parameter | Type |
| --------- | -------- |
| `value` | `cell_t` |
`Float` [#float]
```cpp
static Source2ModScriptArg Float(float value);
```
| Parameter | Type |
| --------- | ------- |
| `value` | `float` |
`Bool` [#bool]
```cpp
static Source2ModScriptArg Bool(bool value);
```
| Parameter | Type |
| --------- | ------ |
| `value` | `bool` |
`Entity` [#entity]
```cpp
static Source2ModScriptArg Entity(CEntityInstance* value);
```
| Parameter | Type |
| --------- | ------------------ |
| `value` | `CEntityInstance*` |
`String` [#string]
```cpp
static Source2ModScriptArg String(const char* value);
```
| Parameter | Type |
| --------- | ------------- |
| `value` | `const char*` |
`Buffer` [#buffer]
```cpp
static Source2ModScriptArg Buffer(char* data, size_t size);
```
| Parameter | Type |
| --------- | -------- |
| `data` | `char*` |
| `size` | `size_t` |
`Array` [#array]
```cpp
static Source2ModScriptArg Array(cell_t* data, size_t count, bool copyback = false);
```
| Parameter | Type | Default |
| ---------- | --------- | ------- |
| `data` | `cell_t*` | |
| `count` | `size_t` | |
| `copyback` | `bool` | `false` |
`CellRef` [#cellref]
```cpp
static Source2ModScriptArg CellRef(cell_t* value);
```
| Parameter | Type |
| --------- | --------- |
| `value` | `cell_t*` |
`FloatRef` [#floatref]
```cpp
static Source2ModScriptArg FloatRef(float* value);
```
| Parameter | Type |
| --------- | -------- |
| `value` | `float*` |
`Null` [#null]
```cpp
static Source2ModScriptArg Null(Source2ModScriptArgType nullType);
```
| Parameter | Type |
| ---------- | -------------------------------------------------------------------------------------- |
| `nullType` | [`Source2ModScriptArgType`](/docs/core-api/ISource2ModNatives#source2modscriptargtype) |
`AsInt` [#asint]
```cpp
cell_t AsInt() const;
```
The value as a cell: a Cell / Bool / Entity forward argument (an entity as the script's cell, see EntityFromScriptCell), a reference's value.
`AsFloat` [#asfloat]
```cpp
float AsFloat() const;
```
`AsBool` [#asbool]
```cpp
bool AsBool() const;
```
`AsString` [#asstring]
```cpp
const char* AsString() const;
```
A String or StringBuffer argument; "" for anything else.
`ISource2ModScriptCallable` [#isource2modscriptcallable]
Something in a script C++ can call: a native a script made, a global forward. Owned by the core, valid until it unloads.
`GetName` [#getname]
```cpp
virtual const char* GetName() const = 0;
```
`IsAvailable` [#isavailable]
```cpp
virtual bool IsAvailable() const = 0;
```
Whether a call would reach a script right now.
`Invoke` [#invoke]
```cpp
virtual bool Invoke(const Source2ModScriptArg* args, int count, cell_t* result) = 0;
```
Calls it with arguments as they are.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `args` | [`const Source2ModScriptArg*`](/docs/core-api/ISource2ModNatives#source2modscriptarg) | |
| `count` | `int` | |
| `result` | `cell_t*` | The return value (a forward's, combined by its ExecType); may be nullptr |
**Returns** `bool` — false when refused (not available, a bad argument) or the script raised an error; GetLastError() says which
`GetLastError` [#getlasterror]
```cpp
virtual const char* GetLastError() const = 0;
```
Why the last Invoke() failed; "" after one that went through.
`EntityFromCell` [#entityfromcell]
```cpp
virtual CEntityInstance* EntityFromCell(cell_t value) const = 0;
```
The live entity a script's entity cell names, nullptr for NULL\_ENTITY or a removed one.
| Parameter | Type |
| --------- | -------- |
| `value` | `cell_t` |
`Call` [#call]
```cpp
template R Call(A&&... args);
```
A typed call. Arguments: integers / enums / a Handle (cell), bool, float / double, const char\* / std::string / a string literal (const char\[]), char buf\[N] or Source2ModScriptBuffer (a string the script writes), Source2ModScriptArray, cell\_t\* / float\* (int& / float&), an entity pointer (CEntityInstance or derived; nullptr is NULL\_ENTITY), Source2ModScriptNullString / NullVector, or a ready Source2ModScriptArg. R: void, bool, an integer or enum, float, or an entity pointer.
| Parameter | Type |
| --------- | -------- |
| `args` | `A&&...` |
**Returns** `R` — The result, value-initialised when the call failed
`TryCall` [#trycall]
```cpp
template bool TryCall(R* out, A&&... args);
```
Call() that says whether it went through; @p out may be nullptr.
| Parameter | Type |
| --------- | -------- |
| `out` | `R*` |
| `args` | `A&&...` |
`ISource2ModScriptNative` [#isource2modscriptnative]
A native a script made with Source2ModNatives.CreateNative, by name. The script reads the arguments with Source2ModNatives.GetCell / GetString / GetCellRef / GetArray... as for a script caller; its `plugin` parameter is INVALID\_HANDLE (a C++ caller has none). Source2ModNatives.FormatString and GetFunction have nothing to read from a C++ caller and fail.
**Inherits** [`ISource2ModScriptCallable`](/docs/core-api/ISource2ModNatives#isource2modscriptcallable)
`GetOwner` [#getowner]
```cpp
virtual PluginId GetOwner() const = 0;
```
The plugin that registered it now, 0 while none has.
`ISource2ModScriptForward` [#isource2modscriptforward]
A global forward by name: every running script's public function of that name (GlobalForward semantics) and every C++ HookScriptForward() on it, in that order. The result -- a Source2ModAction value, 0 Ignore, 1 Override, 2 Supersede -- is combined by the ExecType of the script's GlobalForward of that name, ET\_Event (the highest) when no script made one.
**Inherits** [`ISource2ModScriptCallable`](/docs/core-api/ISource2ModNatives#isource2modscriptcallable)
`GetFunctionCount` [#getfunctioncount]
```cpp
virtual int GetFunctionCount() const = 0;
```
Script functions plus C++ hooks a call would reach.
Enums [#enums]
`Source2ModScriptArgType` [#source2modscriptargtype]
```cpp
enum class Source2ModScriptArgType : int32_t
```
What an argument of a call into a script is.
| Name | Value | Description |
| -------------- | ----- | ------------------------------------------------------------------------------ |
| `Cell` | `0` | any, int, a Handle: cell |
| `Float` | | float: flt |
| `Bool` | | bool: cell (0 / 1) |
| `Entity` | | an entity methodmap value: entity (nullptr is NULL\_ENTITY) |
| `String` | | const char\[]: string |
| `StringBuffer` | | char\[] the script may write (Source2ModNatives.SetString): buffer, size bytes |
| `Array` | | any\[]: array, size cells; written back when copyback |
| `CellRef` | | int &: cellRef |
| `FloatRef` | | float &: floatRef |
| `NullString` | | NULL\_STRING |
| `NullVector` | | NULL\_VECTOR |
Functions [#functions]
`ToArg` [#toarg]
```cpp
template Source2ModScriptArg ToArg(T&& value);
```
| Parameter | Type |
| --------- | ----- |
| `value` | `T&&` |
# ISource2ModNetworkMessages (/docs/core-api/ISource2ModNetworkMessages)
Provides functionality for:
* Allocating and deallocating network messages by ID or partial name
* Reading and writing protobuf fields via reflection (scalar, repeated, nested)
* Sending messages to individual players or a set of players
* Hooking outgoing server/client network messages
Interface for working with Source2 network messages.
All field access is done via protobuf reflection using field names. The @p msg pointer is an opaque handle to an allocated protobuf message; always deallocate with DeallocateNetMessage() when done.
**Interface id** `ISource2ModNetworkMessages002` (`SOURCE2MOD_NETWORKMESSAGES_INTERFACE`) · **Global** `g_pSource2ModNetworkMessages` · **Header** [`source2mod/ISource2ModNetworkMessages.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModNetworkMessages.h)
Types [#types]
`Source2ModNetMessageHookId` [#source2modnetmessagehookid]
```cpp
using Source2ModNetMessageHookId = Source2ModHookId;
```
A net message hook (HookServerMessage / HookClientMessage / HookServerInternalMessage).
`NetMessageServerHook` [#netmessageserverhook]
```cpp
using NetMessageServerHook = Source2ModCallback;
```
Hook called when the server sends a message to a set of clients.
`NetMessageClientHook` [#netmessageclienthook]
```cpp
using NetMessageClientHook = Source2ModCallback;
```
Hook called when a client sends a message to the server, or for internal server sends.
Methods [#methods]
`AllocateNetMessageByID` [#allocatenetmessagebyid]
```cpp
virtual void* AllocateNetMessageByID(int msgid) = 0;
```
Allocates a network message by its numeric ID.
| Parameter | Type | Description |
| --------- | ----- | ----------- |
| `msgid` | `int` | Message ID. |
**Returns** `void*` — Opaque message pointer, or nullptr on failure.
`AllocateNetMessageByPartialName` [#allocatenetmessagebypartialname]
```cpp
virtual void* AllocateNetMessageByPartialName(const char* name) = 0;
```
Allocates a network message by a partial name match.
| Parameter | Type | Description |
| --------- | ------------- | -------------------------------------------------- |
| `name` | `const char*` | Partial name string (e.g. "CSVCMsg\_UserMessage"). |
**Returns** `void*` — Opaque message pointer, or nullptr on failure.
`DeallocateNetMessage` [#deallocatenetmessage]
```cpp
virtual void DeallocateNetMessage(void* msg) = 0;
```
Deallocates a previously allocated network message.
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| `msg` | `void*` | Message pointer returned by Allocate\*. |
`HasField` [#hasfield]
```cpp
virtual bool HasField(void* msg, const char* fieldName) = 0;
```
Returns true if a singular field is set on the message.
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetInt32` [#getint32]
```cpp
virtual int GetInt32(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedInt32` [#getrepeatedint32]
```cpp
virtual int GetRepeatedInt32(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetInt32` [#setint32]
```cpp
virtual void SetInt32(void* msg, const char* fieldName, int value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `int` |
`SetRepeatedInt32` [#setrepeatedint32]
```cpp
virtual void SetRepeatedInt32(void* msg, const char* fieldName, int index, int value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `int` |
`AddInt32` [#addint32]
```cpp
virtual void AddInt32(void* msg, const char* fieldName, int value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `int` |
`GetInt64` [#getint64]
```cpp
virtual int64_t GetInt64(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedInt64` [#getrepeatedint64]
```cpp
virtual int64_t GetRepeatedInt64(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetInt64` [#setint64]
```cpp
virtual void SetInt64(void* msg, const char* fieldName, int64_t value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `int64_t` |
`SetRepeatedInt64` [#setrepeatedint64]
```cpp
virtual void SetRepeatedInt64(void* msg, const char* fieldName, int index, int64_t value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `int64_t` |
`AddInt64` [#addint64]
```cpp
virtual void AddInt64(void* msg, const char* fieldName, int64_t value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `int64_t` |
`GetUInt32` [#getuint32]
```cpp
virtual uint32_t GetUInt32(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedUInt32` [#getrepeateduint32]
```cpp
virtual uint32_t GetRepeatedUInt32(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetUInt32` [#setuint32]
```cpp
virtual void SetUInt32(void* msg, const char* fieldName, uint32_t value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `uint32_t` |
`SetRepeatedUInt32` [#setrepeateduint32]
```cpp
virtual void SetRepeatedUInt32(void* msg, const char* fieldName, int index, uint32_t value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `uint32_t` |
`AddUInt32` [#adduint32]
```cpp
virtual void AddUInt32(void* msg, const char* fieldName, uint32_t value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `uint32_t` |
`GetUInt64` [#getuint64]
```cpp
virtual uint64_t GetUInt64(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedUInt64` [#getrepeateduint64]
```cpp
virtual uint64_t GetRepeatedUInt64(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetUInt64` [#setuint64]
```cpp
virtual void SetUInt64(void* msg, const char* fieldName, uint64_t value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `uint64_t` |
`SetRepeatedUInt64` [#setrepeateduint64]
```cpp
virtual void SetRepeatedUInt64(void* msg, const char* fieldName, int index, uint64_t value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `uint64_t` |
`AddUInt64` [#adduint64]
```cpp
virtual void AddUInt64(void* msg, const char* fieldName, uint64_t value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `uint64_t` |
`GetBool` [#getbool]
```cpp
virtual bool GetBool(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedBool` [#getrepeatedbool]
```cpp
virtual bool GetRepeatedBool(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetBool` [#setbool]
```cpp
virtual void SetBool(void* msg, const char* fieldName, bool value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `bool` |
`SetRepeatedBool` [#setrepeatedbool]
```cpp
virtual void SetRepeatedBool(void* msg, const char* fieldName, int index, bool value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `bool` |
`AddBool` [#addbool]
```cpp
virtual void AddBool(void* msg, const char* fieldName, bool value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `bool` |
`GetFloat` [#getfloat]
```cpp
virtual float GetFloat(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedFloat` [#getrepeatedfloat]
```cpp
virtual float GetRepeatedFloat(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetFloat` [#setfloat]
```cpp
virtual void SetFloat(void* msg, const char* fieldName, float value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `float` |
`SetRepeatedFloat` [#setrepeatedfloat]
```cpp
virtual void SetRepeatedFloat(void* msg, const char* fieldName, int index, float value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `float` |
`AddFloat` [#addfloat]
```cpp
virtual void AddFloat(void* msg, const char* fieldName, float value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `float` |
`GetDouble` [#getdouble]
```cpp
virtual double GetDouble(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedDouble` [#getrepeateddouble]
```cpp
virtual double GetRepeatedDouble(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetDouble` [#setdouble]
```cpp
virtual void SetDouble(void* msg, const char* fieldName, double value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `double` |
`SetRepeatedDouble` [#setrepeateddouble]
```cpp
virtual void SetRepeatedDouble(void* msg, const char* fieldName, int index, double value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `double` |
`AddDouble` [#adddouble]
```cpp
virtual void AddDouble(void* msg, const char* fieldName, double value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `double` |
`GetString` [#getstring]
```cpp
virtual std::string GetString(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedString` [#getrepeatedstring]
```cpp
virtual std::string GetRepeatedString(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetString` [#setstring]
```cpp
virtual void SetString(void* msg, const char* fieldName, const char* value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `const char*` |
`SetRepeatedString` [#setrepeatedstring]
```cpp
virtual void SetRepeatedString(void* msg, const char* fieldName, int index, const char* value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `const char*` |
`AddString` [#addstring]
```cpp
virtual void AddString(void* msg, const char* fieldName, const char* value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `const char*` |
`GetVector2D` [#getvector2d]
```cpp
virtual Vector2D GetVector2D(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedVector2D` [#getrepeatedvector2d]
```cpp
virtual Vector2D GetRepeatedVector2D(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetVector2D` [#setvector2d]
```cpp
virtual void SetVector2D(void* msg, const char* fieldName, Vector2D value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `Vector2D` |
`SetRepeatedVector2D` [#setrepeatedvector2d]
```cpp
virtual void SetRepeatedVector2D(void* msg, const char* fieldName, int index, Vector2D value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `Vector2D` |
`AddVector2D` [#addvector2d]
```cpp
virtual void AddVector2D(void* msg, const char* fieldName, Vector2D value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `Vector2D` |
`GetVector` [#getvector]
```cpp
virtual Vector GetVector(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedVector` [#getrepeatedvector]
```cpp
virtual Vector GetRepeatedVector(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetVector` [#setvector]
```cpp
virtual void SetVector(void* msg, const char* fieldName, Vector value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `Vector` |
`SetRepeatedVector` [#setrepeatedvector]
```cpp
virtual void SetRepeatedVector(void* msg, const char* fieldName, int index, Vector value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `Vector` |
`AddVector` [#addvector]
```cpp
virtual void AddVector(void* msg, const char* fieldName, Vector value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `Vector` |
`GetColor` [#getcolor]
```cpp
virtual Color GetColor(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedColor` [#getrepeatedcolor]
```cpp
virtual Color GetRepeatedColor(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetColor` [#setcolor]
```cpp
virtual void SetColor(void* msg, const char* fieldName, Color value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `Color` |
`SetRepeatedColor` [#setrepeatedcolor]
```cpp
virtual void SetRepeatedColor(void* msg, const char* fieldName, int index, Color value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `Color` |
`AddColor` [#addcolor]
```cpp
virtual void AddColor(void* msg, const char* fieldName, Color value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `Color` |
`GetQAngle` [#getqangle]
```cpp
virtual QAngle GetQAngle(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedQAngle` [#getrepeatedqangle]
```cpp
virtual QAngle GetRepeatedQAngle(void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetQAngle` [#setqangle]
```cpp
virtual void SetQAngle(void* msg, const char* fieldName, QAngle value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `QAngle` |
`SetRepeatedQAngle` [#setrepeatedqangle]
```cpp
virtual void SetRepeatedQAngle(void* msg, const char* fieldName, int index, QAngle value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `QAngle` |
`AddQAngle` [#addqangle]
```cpp
virtual void AddQAngle(void* msg, const char* fieldName, QAngle value) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `QAngle` |
`GetBytes` [#getbytes]
```cpp
virtual int GetBytes(uint8_t* out, void* msg, const char* fieldName) = 0;
```
Copies a bytes field into @p out.
Pass @p out as nullptr to query the size without copying.
| Parameter | Type |
| ----------- | ------------- |
| `out` | `uint8_t*` |
| `msg` | `void*` |
| `fieldName` | `const char*` |
**Returns** `int` — Number of bytes in the field.
`GetRepeatedBytes` [#getrepeatedbytes]
```cpp
virtual int GetRepeatedBytes(uint8_t* out, void* msg, const char* fieldName, int index) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `out` | `uint8_t*` |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`SetBytes` [#setbytes]
```cpp
virtual void SetBytes(void* msg, const char* fieldName, const char* value, int valueLength) = 0;
```
| Parameter | Type |
| ------------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `const char*` |
| `valueLength` | `int` |
`SetRepeatedBytes` [#setrepeatedbytes]
```cpp
virtual void SetRepeatedBytes(void* msg, const char* fieldName, int index, const char* value, int valueLength) = 0;
```
| Parameter | Type |
| ------------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
| `value` | `const char*` |
| `valueLength` | `int` |
`AddBytes` [#addbytes]
```cpp
virtual void AddBytes(void* msg, const char* fieldName, const char* value, int valueLength) = 0;
```
| Parameter | Type |
| ------------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `value` | `const char*` |
| `valueLength` | `int` |
`GetNestedMessage` [#getnestedmessage]
```cpp
virtual void* GetNestedMessage(void* msg, const char* fieldName) = 0;
```
Returns a mutable pointer to a singular nested message field.
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedNestedMessage` [#getrepeatednestedmessage]
```cpp
virtual void* GetRepeatedNestedMessage(void* msg, const char* fieldName, int index) = 0;
```
Returns a mutable pointer to a repeated nested message element.
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
| `index` | `int` |
`AddNestedMessage` [#addnestedmessage]
```cpp
virtual void* AddNestedMessage(void* msg, const char* fieldName) = 0;
```
Appends a new element to a repeated nested message field and returns it.
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`GetRepeatedFieldSize` [#getrepeatedfieldsize]
```cpp
virtual int GetRepeatedFieldSize(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`ClearRepeatedField` [#clearrepeatedfield]
```cpp
virtual void ClearRepeatedField(void* msg, const char* fieldName) = 0;
```
| Parameter | Type |
| ----------- | ------------- |
| `msg` | `void*` |
| `fieldName` | `const char*` |
`Clear` [#clear]
```cpp
virtual void Clear(void* msg) = 0;
```
Clears all fields on the message.
| Parameter | Type |
| --------- | ------- |
| `msg` | `void*` |
`SendMessage` [#sendmessage]
```cpp
virtual void SendMessage(void* msg, int msgid, CPlayerSlot slot) = 0;
```
Sends a network message to a single player.
| Parameter | Type | Description |
| --------- | ------------- | --------------------------------------------------- |
| `msg` | `void*` | Allocated message to send (not freed by this call). |
| `msgid` | `int` | Message ID matching the allocation. |
| `slot` | `CPlayerSlot` | |
`SendMessageToPlayers` [#sendmessagetoplayers]
```cpp
virtual void SendMessageToPlayers(void* msg, int msgid, uint64_t playermask) = 0;
```
Sends a network message to a set of players.
| Parameter | Type | Description |
| ------------ | ---------- | --------------------------------------------------- |
| `msg` | `void*` | Allocated message to send (not freed by this call). |
| `msgid` | `int` | Message ID matching the allocation. |
| `playermask` | `uint64_t` | 64-bit bitmask; bit N = slot N. |
`HookServerMessage` [#hookservermessage]
```cpp
virtual Source2ModNetMessageHookId HookServerMessage(NetMessageServerHook handler) = 0;
```
Hooks outgoing server-to-client messages.
A plugin may hook as often as it likes; each handler runs. The source2mod drops what a plugin still holds when it unloads, so a plugin that forgets to unhook does not leave the engine calling into a closed library. Whose a hook is, the core reads off the handler (see Source2ModCallback).
| Parameter | Type | Description |
| --------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `handler` | [`NetMessageServerHook`](/docs/core-api/ISource2ModNetworkMessages#netmessageserverhook) | A function, an object and a method, or a lambda |
**Returns** [`Source2ModNetMessageHookId`](/docs/core-api/ISource2ModNetworkMessages#source2modnetmessagehookid) — The id UnhookServerMessage(id) takes
`UnhookServerMessage` [#unhookservermessage]
```cpp
virtual bool UnhookServerMessage(const NetMessageServerHook& handler) = 0;
virtual bool UnhookServerMessage(Source2ModNetMessageHookId 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 HookServerMessage() returned the id for.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------------- |
| `handler` | [`const NetMessageServerHook&`](/docs/core-api/ISource2ModNetworkMessages#netmessageserverhook) |
| `id` | [`Source2ModNetMessageHookId`](/docs/core-api/ISource2ModNetworkMessages#source2modnetmessagehookid) |
**Returns** `bool` — true when one was found
`HookClientMessage` [#hookclientmessage]
```cpp
virtual Source2ModNetMessageHookId HookClientMessage(NetMessageClientHook handler) = 0;
```
Hooks incoming client-to-server messages (as HookServerMessage()).
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------- |
| `handler` | [`NetMessageClientHook`](/docs/core-api/ISource2ModNetworkMessages#netmessageclienthook) |
**Returns** [`Source2ModNetMessageHookId`](/docs/core-api/ISource2ModNetworkMessages#source2modnetmessagehookid) — The id UnhookClientMessage(id) takes
`UnhookClientMessage` [#unhookclientmessage]
```cpp
virtual bool UnhookClientMessage(const NetMessageClientHook& handler) = 0;
virtual bool UnhookClientMessage(Source2ModNetMessageHookId 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 HookClientMessage() returned the id for.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------------- |
| `handler` | [`const NetMessageClientHook&`](/docs/core-api/ISource2ModNetworkMessages#netmessageclienthook) |
| `id` | [`Source2ModNetMessageHookId`](/docs/core-api/ISource2ModNetworkMessages#source2modnetmessagehookid) |
**Returns** `bool` — true when one was found
`HookServerInternalMessage` [#hookserverinternalmessage]
```cpp
virtual Source2ModNetMessageHookId HookServerInternalMessage(NetMessageClientHook handler) = 0;
```
Hooks internal server-side message sends, per client (as HookServerMessage()).
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------- |
| `handler` | [`NetMessageClientHook`](/docs/core-api/ISource2ModNetworkMessages#netmessageclienthook) |
**Returns** [`Source2ModNetMessageHookId`](/docs/core-api/ISource2ModNetworkMessages#source2modnetmessagehookid) — The id UnhookServerInternalMessage(id) takes
`UnhookServerInternalMessage` [#unhookserverinternalmessage]
```cpp
virtual bool UnhookServerInternalMessage(const NetMessageClientHook& handler) = 0;
virtual bool UnhookServerInternalMessage(Source2ModNetMessageHookId 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 HookServerInternalMessage() returned the id for.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------------- |
| `handler` | [`const NetMessageClientHook&`](/docs/core-api/ISource2ModNetworkMessages#netmessageclienthook) |
| `id` | [`Source2ModNetMessageHookId`](/docs/core-api/ISource2ModNetworkMessages#source2modnetmessagehookid) |
**Returns** `bool` — true when one was found
Macros [#macros]
`NET_MSG_ALLOC_BY_ID` [#net_msg_alloc_by_id]
```cpp
#define NET_MSG_ALLOC_BY_ID(id) g_pSource2ModNetworkMessages->AllocateNetMessageByID(id)
```
Shorthand accessors via g\_pSource2ModAPI.
`HOOK_SERVER_MESSAGE` [#hook_server_message]
```cpp
#define HOOK_SERVER_MESSAGE(handler) g_pSource2ModNetworkMessages->HookServerMessage(handler)
```
Hook macros, matching HOOK\_GAME\_EVENT and HOOK\_CONVAR\_CHANGE.
Other macros [#other-macros]
| Macro | Expands to |
| ----------------------------------------- | --------------------------------------------------------------------- |
| `NET_MSG_ALLOC_BY_NAME(name)` | `g_pSource2ModNetworkMessages->AllocateNetMessageByPartialName(name)` |
| `NET_MSG_FREE(msg)` | `g_pSource2ModNetworkMessages->DeallocateNetMessage(msg)` |
| `NET_MSG_SEND(msg, id, slot)` | `g_pSource2ModNetworkMessages->SendMessage(msg, id, slot)` |
| `NET_MSG_SEND_PLAYERS(msg, id, mask)` | `g_pSource2ModNetworkMessages->SendMessageToPlayers(msg, id, mask)` |
| `UNHOOK_SERVER_MESSAGE(handler)` | `g_pSource2ModNetworkMessages->UnhookServerMessage(handler)` |
| `HOOK_CLIENT_MESSAGE(handler)` | `g_pSource2ModNetworkMessages->HookClientMessage(handler)` |
| `UNHOOK_CLIENT_MESSAGE(handler)` | `g_pSource2ModNetworkMessages->UnhookClientMessage(handler)` |
| `HOOK_SERVER_INTERNAL_MESSAGE(handler)` | `g_pSource2ModNetworkMessages->HookServerInternalMessage(handler)` |
| `UNHOOK_SERVER_INTERNAL_MESSAGE(handler)` | `g_pSource2ModNetworkMessages->UnhookServerInternalMessage(handler)` |
# ISource2ModPaths (/docs/core-api/ISource2ModPaths)
Every path a plugin needs comes from here rather than being spelled out against the game directory, so the layout can move without every plugin having to be found and edited.
Source2Mod's on-disk layout.
Under the game directory:
```cpp
addons/source2mod/
configs/ core.json, and one directory per plugin
/ this plugin's own configs
gamedata/ every .json here is read into the game config
plugins/ the .smx files themselves
shared/ data more than one plugin reads
```
The returned strings are owned by Source2Mod and stay valid for as long as it is loaded; copy them if you need to keep one past that.
**Interface id** `ISource2ModPaths001` (`SOURCE2MOD_PATHS_INTERFACE`) · **Global** `g_pSource2ModPaths` · **Header** [`source2mod/ISource2ModPaths.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModPaths.h)
Methods [#methods]
`GameDirectory` [#gamedirectory]
```cpp
virtual const char* GameDirectory() = 0;
```
The game directory, e.g. ".../game/csgo".
`RootDirectory` [#rootdirectory]
```cpp
virtual const char* RootDirectory() = 0;
```
addons/source2mod.
`ConfigsDirectory` [#configsdirectory]
```cpp
virtual const char* ConfigsDirectory() = 0;
```
addons/source2mod/configs.
`GamedataDirectory` [#gamedatadirectory]
```cpp
virtual const char* GamedataDirectory() = 0;
```
addons/source2mod/gamedata.
`PluginsDirectory` [#pluginsdirectory]
```cpp
virtual const char* PluginsDirectory() = 0;
```
addons/source2mod/plugins, where the .smx files live.
`SharedDirectory` [#shareddirectory]
```cpp
virtual const char* SharedDirectory() = 0;
```
addons/source2mod/shared, for data more than one plugin reads.
`PluginConfigDirectory` [#pluginconfigdirectory]
```cpp
virtual const char* PluginConfigDirectory(PluginId owner) = 0;
```
This plugin's own config directory, configs/\.
The directory is not created for you; it is where the plugin's files are expected to have been installed.
| Parameter | Type | Description |
| --------- | ---------------------------------------------------- | -------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Plugin ID to look up |
**Returns** `const char*` — The path, or nullptr when there is no such plugin
Macros [#macros]
`PLUGIN_CONFIG_DIR` [#plugin_config_dir]
```cpp
#define PLUGIN_CONFIG_DIR() g_pSource2ModPaths->PluginConfigDirectory(g_PluginID)
```
This plugin's config directory; the plugin ID is filled in for you.
Other macros [#other-macros]
| Macro | Expands to |
| --------------------------- | ----------------------------------------- |
| `SOURCE2MOD_GAME_DIR()` | `g_pSource2ModPaths->GameDirectory()` |
| `SOURCE2MOD_ROOT_DIR()` | `g_pSource2ModPaths->RootDirectory()` |
| `SOURCE2MOD_CONFIGS_DIR()` | `g_pSource2ModPaths->ConfigsDirectory()` |
| `SOURCE2MOD_GAMEDATA_DIR()` | `g_pSource2ModPaths->GamedataDirectory()` |
| `SOURCE2MOD_PLUGINS_DIR()` | `g_pSource2ModPaths->PluginsDirectory()` |
| `SOURCE2MOD_SHARED_DIR()` | `g_pSource2ModPaths->SharedDirectory()` |
# ISource2ModPermissions (/docs/core-api/ISource2ModPermissions)
A permission is a lower-case dotted string a plugin makes up for itself: "funplay.admin.kick", "vip.trail". What a player holds may end in ".*", which covers that node and everything below it ("funplay.admin.*" covers "funplay.admin" and "funplay.admin.kick", not "funplay.adminx"), and "\*" covers everything -- the root permission.
A group is a name with permissions, an immunity and groups it inherits from. The group "default" applies to every player, also to one whose SteamID is not known yet.
Players are looked up by SteamID64, so a SteamID can be granted things before its player connects. Which SteamID a connected player is checked under depends on SteamAuthMode in core.json:
* "auto" -- the SteamID the client claimed, right away, when the server does not validate Steam tickets (-insecure, sv\_lan 1, no Steam connection); otherwise only once Steam validated it.
* "flexible" -- always the claimed SteamID right away.
* "strict" -- always only once Steam validated it.
Until then the player has the "default" group and nothing else. ISource2ModListener::OnClientAuthorized() says when the validated one arrives.
What a player holds comes in layers: configs/permissions.json (admins, groups and command overrides an owner writes by hand), and one layer per plugin that grants something at runtime. A player has the union of every layer. A plugin's layer is dropped when it unloads, and "s2m admins reload" re-reads the file without touching the plugins' layers -- a plugin that loads admins from a database grants them again from its Load() and keeps them current itself.
**Interface id** `ISource2ModPermissions001` (`SOURCE2MOD_PERMISSIONS_INTERFACE`) · **Global** `g_pSource2ModPermissions` · **Header** [`source2mod/ISource2ModPermissions.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModPermissions.h)
Types [#types]
`Source2ModAccessDeniedHandlerId` [#source2modaccessdeniedhandlerid]
```cpp
using Source2ModAccessDeniedHandlerId = Source2ModHookId;
```
An access-denied handler (SetAccessDeniedHandler).
`AccessDeniedHandler` [#accessdeniedhandler]
```cpp
using AccessDeniedHandler = Source2ModCallback;
```
What runs when a player is refused a command.
Methods [#methods]
`HasPermission` [#haspermission]
```cpp
virtual bool HasPermission(uint64 steamId, const char* permission) = 0;
```
Whether this SteamID64 holds the permission, through any layer, group or wildcard. 0 (an unknown player) has the "default" group only.
| Parameter | Type |
| ------------ | ------------- |
| `steamId` | `uint64` |
| `permission` | `const char*` |
`PlayerHasPermission` [#playerhaspermission]
```cpp
virtual bool PlayerHasPermission(CPlayerSlot slot, const char* permission) = 0;
```
Whether the player in this slot holds the permission, under the SteamID they are checked by (see GetPlayerSteamID()). The server console -- an invalid slot -- holds everything.
| Parameter | Type |
| ------------ | ------------- |
| `slot` | `CPlayerSlot` |
| `permission` | `const char*` |
`CheckAccess` [#checkaccess]
```cpp
virtual bool CheckAccess(CPlayerSlot slot, const char* name, const char* defaultPermission) = 0;
```
SourceMod's CheckCommandAccess: a feature gate the owner can re-assign in permissions.json.
The permission checked is the "Overrides" entry for `name` when there is one, otherwise `defaultPermission`. An empty permission lets everybody through.
| Parameter | Type |
| ------------------- | ------------- |
| `slot` | `CPlayerSlot` |
| `name` | `const char*` |
| `defaultPermission` | `const char*` |
`InGroup` [#ingroup]
```cpp
virtual bool InGroup(uint64 steamId, const char* group) = 0;
```
Whether this SteamID64 is in the group, directly or by inheritance.
| Parameter | Type |
| --------- | ------------- |
| `steamId` | `uint64` |
| `group` | `const char*` |
`GetImmunity` [#getimmunity]
```cpp
virtual int GetImmunity(uint64 steamId) = 0;
```
The highest immunity among the player's own and their groups'.
| Parameter | Type |
| --------- | -------- |
| `steamId` | `uint64` |
`CanTarget` [#cantarget]
```cpp
virtual bool CanTarget(CPlayerSlot caller, CPlayerSlot target) = 0;
```
Whether `caller` may act on `target`: the server console and a root ("\*") caller always may, anybody may act on themselves, and otherwise the caller's immunity has to be at least the target's.
| Parameter | Type |
| --------- | ------------- |
| `caller` | `CPlayerSlot` |
| `target` | `CPlayerSlot` |
`GetPlayerSteamID` [#getplayersteamid]
```cpp
virtual uint64 GetPlayerSteamID(CPlayerSlot slot) = 0;
```
The SteamID64 the player in this slot is checked under: 0 for an empty slot, a bot, or a player Steam has not validated yet while SteamAuthMode wants it to.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`IsPlayerAuthorized` [#isplayerauthorized]
```cpp
virtual bool IsPlayerAuthorized(CPlayerSlot slot) = 0;
```
Whether Steam validated the player in this slot.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`GrantPermission` [#grantpermission]
```cpp
virtual void GrantPermission(PluginId owner, uint64 steamId, const char* permission) = 0;
```
Grants a permission (wildcards allowed) to a SteamID64, in the owner's layer. Gone when the owner unloads, or by RevokePermission().
| Parameter | Type |
| ------------ | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `steamId` | `uint64` |
| `permission` | `const char*` |
`RevokePermission` [#revokepermission]
```cpp
virtual void RevokePermission(PluginId owner, uint64 steamId, const char* permission) = 0;
```
Takes back what GrantPermission() gave in the owner's layer; other layers are untouched.
| Parameter | Type |
| ------------ | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `steamId` | `uint64` |
| `permission` | `const char*` |
`AddToGroup` [#addtogroup]
```cpp
virtual void AddToGroup(PluginId owner, uint64 steamId, const char* group) = 0;
```
Puts a SteamID64 in a group, in the owner's layer.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `steamId` | `uint64` |
| `group` | `const char*` |
`RemoveFromGroup` [#removefromgroup]
```cpp
virtual void RemoveFromGroup(PluginId owner, uint64 steamId, const char* group) = 0;
```
Takes a SteamID64 out of a group in the owner's layer.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `steamId` | `uint64` |
| `group` | `const char*` |
`SetImmunity` [#setimmunity]
```cpp
virtual void SetImmunity(PluginId owner, uint64 steamId, int immunity) = 0;
```
The player's own immunity in the owner's layer; the highest of all layers and groups counts.
| Parameter | Type |
| ---------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `steamId` | `uint64` |
| `immunity` | `int` |
`ClearPlayer` [#clearplayer]
```cpp
virtual void ClearPlayer(PluginId owner, uint64 steamId) = 0;
```
Drops everything the owner's layer holds for this SteamID64.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `steamId` | `uint64` |
`CreateGroup` [#creategroup]
```cpp
virtual void CreateGroup(PluginId owner, const char* group, int immunity) = 0;
```
Defines a group in the owner's layer, or sets its immunity when it is already there. A group defined in several layers (the file and a plugin, two plugins) has all of their permissions and parents and the highest immunity.
| Parameter | Type |
| ---------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `group` | `const char*` |
| `immunity` | `int` |
`DeleteGroup` [#deletegroup]
```cpp
virtual void DeleteGroup(PluginId owner, const char* group) = 0;
```
Drops the owner's definition of the group; other layers' stay.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `group` | `const char*` |
`AddGroupPermission` [#addgrouppermission]
```cpp
virtual void AddGroupPermission(PluginId owner, const char* group, const char* permission) = 0;
```
Adds a permission to the owner's definition of the group (made if missing).
| Parameter | Type |
| ------------ | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `group` | `const char*` |
| `permission` | `const char*` |
`RemoveGroupPermission` [#removegrouppermission]
```cpp
virtual void RemoveGroupPermission(PluginId owner, const char* group, const char* permission) = 0;
```
Takes a permission back out of the owner's definition of the group.
| Parameter | Type |
| ------------ | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `group` | `const char*` |
| `permission` | `const char*` |
`AddGroupParent` [#addgroupparent]
```cpp
virtual void AddGroupParent(PluginId owner, const char* group, const char* parent) = 0;
```
Makes the group inherit everything `parent` has (made if missing).
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `group` | `const char*` |
| `parent` | `const char*` |
`RemoveGroupParent` [#removegroupparent]
```cpp
virtual void RemoveGroupParent(PluginId owner, const char* group, const char* parent) = 0;
```
Undoes AddGroupParent() in the owner's layer.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `group` | `const char*` |
| `parent` | `const char*` |
`SetAccessDeniedHandler` [#setaccessdeniedhandler]
```cpp
virtual Source2ModAccessDeniedHandlerId SetAccessDeniedHandler(AccessDeniedHandler handler) = 0;
```
Replaces what a player sees when they are refused a command.
The core answers "You do not have access to this command." (the AccessDeniedMessage of core.json) through ReplyToCommand() -- to chat when they typed it in chat, to their console otherwise. A plugin that wants its own wording (a translation, a prefix) sets a handler; the one set last is used, and when its plugin unloads the one before it takes over again.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------- |
| `handler` | [`AccessDeniedHandler`](/docs/core-api/ISource2ModPermissions#accessdeniedhandler) |
**Returns** [`Source2ModAccessDeniedHandlerId`](/docs/core-api/ISource2ModPermissions#source2modaccessdeniedhandlerid) — The id ClearAccessDeniedHandler() takes
`ClearAccessDeniedHandler` [#clearaccessdeniedhandler]
```cpp
virtual bool ClearAccessDeniedHandler(Source2ModAccessDeniedHandlerId id) = 0;
```
Drops a handler SetAccessDeniedHandler() returned the id for.
| Parameter | Type |
| --------- | ---------------------------------------------------------------------------------------------------------- |
| `id` | [`Source2ModAccessDeniedHandlerId`](/docs/core-api/ISource2ModPermissions#source2modaccessdeniedhandlerid) |
Macros [#macros]
| Macro | Expands to |
| -------------------------------------------------- | ----------------------------------------------------------------------------- |
| `PERMISSIONS_HAS(steamId, permission)` | `g_pSource2ModPermissions->HasPermission(steamId, permission)` |
| `PERMISSIONS_PLAYER_HAS(slot, permission)` | `g_pSource2ModPermissions->PlayerHasPermission(slot, permission)` |
| `PERMISSIONS_CHECK_ACCESS(slot, name, permission)` | `g_pSource2ModPermissions->CheckAccess(slot, name, permission)` |
| `PERMISSIONS_GRANT(steamId, permission)` | `g_pSource2ModPermissions->GrantPermission(g_PluginID, steamId, permission)` |
| `PERMISSIONS_REVOKE(steamId, permission)` | `g_pSource2ModPermissions->RevokePermission(g_PluginID, steamId, permission)` |
| `PERMISSIONS_ADD_TO_GROUP(steamId, group)` | `g_pSource2ModPermissions->AddToGroup(g_PluginID, steamId, group)` |
| `PERMISSIONS_REMOVE_FROM_GROUP(steamId, group)` | `g_pSource2ModPermissions->RemoveFromGroup(g_PluginID, steamId, group)` |
# ISource2ModPlugin (/docs/core-api/ISource2ModPlugin)
This file defines:
* Plugin lifecycle interface (ISource2ModPlugin)
* Listener system (ISource2ModListener)
* Global variables and exposure macros
* Interface helper macros
This is the main entry point for plugin developers.
**Interface id** `ISource2ModNatives002` (`SOURCE2MOD_NATIVES_INTERFACE`) · **Interface id** `ISource2ModSchemaMethods001` (`SOURCE2MOD_SCHEMAMETHODS_INTERFACE`) · **Interface id** `Source2ModKHook001` (`SOURCE2MOD_KHOOK_INTERFACE`) · **Interface id** `Source2ModKHookVersion001` (`SOURCE2MOD_KHOOK_VERSION_INTERFACE`) · **Interface id** `Source2ModKHookHeader001` (`SOURCE2MOD_KHOOK_HEADER_INTERFACE`) · **Header** [`source2mod/ISource2ModPlugin.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModPlugin.h)
Types [#types]
`PluginId` [#pluginid]
```cpp
using PluginId = int;
```
Methods [#methods]
`GetApiVersion` [#getapiversion]
```cpp
virtual int GetApiVersion();
```
`Load` [#load]
```cpp
virtual bool Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) = 0;
```
Called when plugin is loaded.
This is the main initialization function.
| Parameter | Type | Description |
| --------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Plugin identifier |
| `api` | [`ISource2ModAPI*`](/docs/core-api/ISource2ModApi) | Source2Mod API instance |
| `error` | `char*` | Buffer for error message |
| `maxlen` | `size_t` | Error buffer size |
| `late` | `bool` | False only when Source2Mod's startup pass loads the plugin. True for every load after that: "s2m load", "source2mod refresh" and the file watcher's hot reload. Players and entities may already exist. |
**Returns** `bool` — True on success, false on failure
`Unload` [#unload]
```cpp
virtual bool Unload(char* error, size_t maxlen);
```
Called when plugin is unloaded.
| Parameter | Type |
| --------- | -------- |
| `error` | `char*` |
| `maxlen` | `size_t` |
**Returns** `bool` — True on success
`GetAuthor` [#getauthor]
```cpp
virtual const char* GetAuthor() = 0;
```
Plugin author name
`GetName` [#getname]
```cpp
virtual const char* GetName() = 0;
```
Plugin name
`GetDescription` [#getdescription]
```cpp
virtual const char* GetDescription() = 0;
```
Plugin description
`GetVersion` [#getversion]
```cpp
virtual const char* GetVersion() = 0;
```
Plugin version
`GetKHookCommit` [#getkhookcommit]
```cpp
virtual const char* GetKHookCommit();
```
The KHook commit this plugin was built against (the SDK's vendor/khook), "" when built without git.
`GetRawHookCount` [#getrawhookcount]
```cpp
virtual int GetRawHookCount();
```
How many KHook hooks the plugin declared through the KHOOK\_\* macros: none means the plugin is on the stable tier and an engine update is the core's problem alone.
`ISource2ModListener` [#isource2modlistener]
Source2Mod and Metamod events: plugins coming and going, the map, interface queries, the permission system.
The engine's own callbacks (GameFrame, client connect / put in server / disconnect, ...) are hook points: g\_pSource2ModHooks->Hook(HookPoint::GameFrame, HookTarget::All(), ...) -- ISource2ModHooks.h.
`OnPluginLoad` [#onpluginload]
```cpp
virtual void OnPluginLoad(PluginId id, const char* name);
```
Called when a source2mod plugin (.smx) has loaded, after its Load() returned. The id is a source2mod plugin id -- the one "s2m list" shows. Fires for your own plugin too (a listener added in Load() gets it right after Load() returns); compare against your own id to skip it. The name is what the plugin's GetName() returns.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `id` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `name` | `const char*` |
`OnPluginUnload` [#onpluginunload]
```cpp
virtual void OnPluginUnload(PluginId id, const char* name);
```
Called when a source2mod plugin (.smx) is about to unload, before its Unload() runs. Fires for your own plugin too, like OnPluginLoad().
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `id` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `name` | `const char*` |
`OnMetamodPluginLoad` [#onmetamodpluginload]
```cpp
virtual void OnMetamodPluginLoad(PluginId id);
```
Called when a Metamod:Source plugin is loaded.
The id is Metamod's, from a different numbering than Source2Mod's -- which is why this is not the same callback as OnPluginLoad().
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `id` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
`OnMetamodPluginUnload` [#onmetamodpluginunload]
```cpp
virtual void OnMetamodPluginUnload(PluginId id);
```
Called when a Metamod:Source plugin is unloaded.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `id` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
`OnAllSource2ModPluginsLoaded` [#onallsource2modpluginsloaded]
```cpp
virtual void OnAllSource2ModPluginsLoaded();
```
Called when all source2mod plugins are loaded
`OnAllMetamodPluginsLoaded` [#onallmetamodpluginsloaded]
```cpp
virtual void OnAllMetamodPluginsLoaded();
```
Called when all Metamod plugins are loaded
`OnLevelInit` [#onlevelinit]
```cpp
virtual void OnLevelInit(const char* mapName, const char* mapEntities, const char* oldLevel, const char* landmarkName, bool loadGame, bool background);
```
Called when a map is initialized.
| Parameter | Type |
| -------------- | ------------- |
| `mapName` | `const char*` |
| `mapEntities` | `const char*` |
| `oldLevel` | `const char*` |
| `landmarkName` | `const char*` |
| `loadGame` | `bool` |
| `background` | `bool` |
`OnLevelShutdown` [#onlevelshutdown]
```cpp
virtual void OnLevelShutdown();
```
Called when a map is shutting down
`OnSource2ModQuery` [#onsource2modquery]
```cpp
virtual void* OnSource2ModQuery(const char* iface, int* ret);
```
Called when an interface is requested from source2mod.
| Parameter | Type |
| --------- | ------------- |
| `iface` | `const char*` |
| `ret` | `int*` |
**Returns** `void*` — Pointer to interface or nullptr
`OnMetamodQuery` [#onmetamodquery]
```cpp
virtual void* OnMetamodQuery(const char* iface, int* ret);
```
Called when a Metamod:Source plugin asks for an interface.
A Metamod plugin reaching for something through g\_SMAPI->MetaFactory() ends up here, so a source2mod plugin can hand its own interfaces to code that did not load through Source2Mod.
By default this answers exactly like OnSource2ModQuery(), which is what a plugin usually wants -- an interface worth exposing is worth exposing to either caller. Override it to answer a Metamod plugin differently, or return nullptr to keep an interface for source2mod plugins only. The two are separate callbacks precisely so that choice exists: OnSource2ModQuery() cannot tell who is asking.
| Parameter | Type |
| --------- | ------------- |
| `iface` | `const char*` |
| `ret` | `int*` |
**Returns** `void*` — Pointer to interface or nullptr
`OnClientAuthorized` [#onclientauthorized]
```cpp
virtual void OnClientAuthorized(CPlayerSlot slot, uint64 steamId);
```
Steam validated the player in this slot: from now on they are checked under `steamId` whatever SteamAuthMode says. Not called for bots.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
| `steamId` | `uint64` |
`OnClientAuthorizeFailed` [#onclientauthorizefailed]
```cpp
virtual void OnClientAuthorizeFailed(CPlayerSlot slot, uint64 steamId);
```
Steam refused the ticket of the player in this slot; `steamId` is the one they claimed.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
| `steamId` | `uint64` |
`OnPermissionsChanged` [#onpermissionschanged]
```cpp
virtual void OnPermissionsChanged(uint64 steamId);
```
What this SteamID64 holds may have changed -- a grant, a group, a reload. 0 when a change can touch anybody (a group was edited, permissions.json was re-read).
| Parameter | Type |
| --------- | -------- |
| `steamId` | `uint64` |
Functions [#functions]
`Source2ModKHookVersionMatches` [#source2modkhookversionmatches]
```cpp
inline bool Source2ModKHookVersionMatches(const char* coreHeader, const char* coreCommit, char* error, size_t maxlen, const char** note);
```
The load-time half of the KHook check; KHOOK\_INIT() calls it.
Compares the khook.hpp hashes when both sides have one, else the commits (a core older than the header hash). Returns false and fills `error` only when the compared values are known and differ. A side that cannot say -- a core too old to answer, a plugin built without git -- makes the check impossible, not failed: that goes out through `note` and the load goes on.
| Parameter | Type |
| ------------ | -------------- |
| `coreHeader` | `const char*` |
| `coreCommit` | `const char*` |
| `error` | `char*` |
| `maxlen` | `size_t` |
| `note` | `const char**` |
`Source2ModRawHookCount` [#source2modrawhookcount]
```cpp
int Source2ModRawHookCount();
inline int Source2ModRawHookCount();
```
Defined by ISource2ModHooks.h for a plugin; the core never asks itself.
Macros [#macros]
`SOURCE2MOD_KHOOK_COMMIT` [#source2mod_khook_commit]
```cpp
#define SOURCE2MOD_KHOOK_COMMIT "unknown"
```
Set by the SDK's CMakeLists.txt / AMBuildScript from vendor/khook (the core: from metamod's copy). A build that bypassed both gets "unknown", which the check reports and lets through.
`SOURCE2MOD_KHOOK_HEADER` [#source2mod_khook_header]
```cpp
#define SOURCE2MOD_KHOOK_HEADER "unknown"
```
Set by the SDK's CMakeLists.txt / AMBuildScript: the first 16 hex digits of the SHA-256 of khook.hpp with CRs dropped. A build that bypassed both gets "unknown", and the check falls back to the commit.
`SOURCE2MOD_INTERFACE_NAME` [#source2mod_interface_name]
```cpp
#define SOURCE2MOD_INTERFACE_NAME "Source2ModPlugin001"
```
Plugin interface name
`SOURCE2MOD_PLAPI_VERSION` [#source2mod_plapi_version]
```cpp
#define SOURCE2MOD_PLAPI_VERSION 5
```
Plugin API version
`SOURCE2MOD_PLAPI_NAME` [#source2mod_plapi_name]
```cpp
#define SOURCE2MOD_PLAPI_NAME "ISource2ModPlugin"
```
Plugin API interface name
`SOURCE2MOD_EXPOSURE_FUNC` [#source2mod_exposure_func]
```cpp
#define SOURCE2MOD_EXPOSURE_FUNC(name, var) EXPOSE_SINGLE_INTERFACE_GLOBALVAR(ISource2ModPlugin, ISource2ModPlugin, SOURCE2MOD_PLAPI_NAME, var);
```
Exposes plugin interface to engine.
`SOURCE2MOD_DEFINE_GLOBALVARS` [#source2mod_define_globalvars]
```cpp
#define SOURCE2MOD_DEFINE_GLOBALVARS() \
ISource2ModAPI* g_pSource2ModAPI = nullptr;
ISource2ModPlugin* g_pPluginAPI = nullptr;
PluginId g_PluginID = 0;
const char* g_Source2ModCoreKHookCommit = nullptr;
const char* g_Source2ModCoreKHookHeader = nullptr;
...
```
Defines Source2Mod globals -- the API, the plugin identity and one pointer per source2mod interface.
Part of SOURCE2MOD\_EXPOSE for a plugin. The core expands it on its own too and fills the pointers with SOURCE2MOD\_FILLVARS(), so SDK code reads the same globals (ADDR\_\*, g\_pSource2ModGameConfig, ...) whichever binary it is linked into.
`SOURCE2MOD_EXPOSE` [#source2mod_expose]
```cpp
#define SOURCE2MOD_EXPOSE(name, var) \
SOURCE2MOD_DEFINE_GLOBALVARS()
namespace KHook { KHook::IKHook* __exported__khook = nullptr; }
SOURCE2MOD_EXPOSURE_FUNC(name, var)
```
Defines global plugin variables.
Creates:
* everything SOURCE2MOD\_DEFINE\_GLOBALVARS() does
* KHook::\_\_exported\_\_khook, the detour engine (see SOURCE2MOD\_KHOOK\_INTERFACE)
`SOURCE2MOD_GLOBALVARS` [#source2mod_globalvars]
```cpp
#define SOURCE2MOD_GLOBALVARS() \
extern ISource2ModAPI* g_pSource2ModAPI;
extern ISource2ModPlugin* g_pPluginAPI;
extern PluginId g_PluginID;
extern const char* g_Source2ModCoreKHookCommit;
extern const char* g_Source2ModCoreKHookHeader;
extern ISource2ModAddresses* g_pSource2ModAddresses;
...
```
Declares external globals.
`SOURCE2MOD_FILLVARS` [#source2mod_fillvars]
```cpp
#define SOURCE2MOD_FILLVARS(api) \
g_pSource2ModAddresses = (ISource2ModAddresses*) (api)->Source2ModFactory(SOURCE2MOD_ADDRESSES_INTERFACE, nullptr, nullptr);
g_pSource2ModCommands = (ISource2ModCommands*) (api)->Source2ModFactory(SOURCE2MOD_COMMANDS_INTERFACE, nullptr, nullptr);
...
```
Points every source2mod interface global at what the API hands out.
Part of SOURCE2MOD\_SAVEVARS() for a plugin; the core calls it once its interfaces exist (see SOURCE2MOD\_DEFINE\_GLOBALVARS()).
`SOURCE2MOD_FILLGAMEVARS` [#source2mod_fillgamevars]
```cpp
#define SOURCE2MOD_FILLGAMEVARS(api) \
g_pSource2Server = (api)->GetSource2Server();
g_pSource2ServerConfig = (api)->GetSource2ServerConfig();
g_pSource2GameClients = (api)->GetSource2GameClients();
g_pSource2GameEntities = (api)->GetSource2GameEntities();
g_pEngineServer = (api)->GetEngineServer();
g_pCVar = (api)->GetCVar();
...
```
Points s2sdk's engine globals at the engine's interfaces.
The globals s2sdk declares in interfaces/interfaces.h and defines in its interfaces library, which every plugin links: the ones SDK code and s2sdk's own sources read (g\_pCVar for ConVars and commands, g\_pGameResourceServiceServer for GameEntitySystem()) and the ones a plugin usually wants (g\_pSource2Server for a hook, g\_pNetworkMessages, g\_pSchemaSystem, ...), all from what the core already holds -- a plugin connects nothing itself.
Part of SOURCE2MOD\_SAVEVARS(); the core sets its own.
`SOURCE2MOD_SAVEVARS` [#source2mod_savevars]
```cpp
#define SOURCE2MOD_SAVEVARS() \
g_pSource2ModAPI = api;
SOURCE2MOD_FILLGAMEVARS(api)
g_pPluginAPI = static_cast(this);
g_PluginID = id;
KHook::__exported__khook = static_cast(api->Source2ModFactory(SOURCE2MOD_KHOOK_INTERFACE, nullptr, nullptr));
...
```
Initializes global variables inside Load().
Must be called in plugin Load(), with its parameters under their declared names (id, api, error, maxlen).
`SOURCE2MOD_LOG` [#source2mod_log]
```cpp
#define SOURCE2MOD_LOG g_pSource2ModAPI->Log
```
Shortcut for logging
`SOURCE2MOD_CONPRINT` [#source2mod_conprint]
```cpp
#define SOURCE2MOD_CONPRINT g_pSource2ModAPI->ConPrint
```
Console print
`SOURCE2MOD_CONPRINTF` [#source2mod_conprintf]
```cpp
#define SOURCE2MOD_CONPRINTF g_pSource2ModAPI->ConPrintf
```
Console printf
`GET_VALVE_IFACE_CURRENT` [#get_valve_iface_current]
```cpp
#define GET_VALVE_IFACE_CURRENT(v_factory, v_var, v_type, v_name) \
v_var = (v_type *)g_pSource2ModAPI->QueryInterface(g_pSource2ModAPI->v_factory(), v_name);
if (!v_var)
{
if (error && maxlen)
{
g_pSource2ModAPI->Format(error, maxlen, "Could not find interface: %s", v_name);
}
return false;
}
```
Macro for automatically getting a current or newer Valve interface.
`GET_VALVE_IFACE_ANY` [#get_valve_iface_any]
```cpp
#define GET_VALVE_IFACE_ANY(v_factory, v_var, v_type, v_name) \
v_var = (v_type *)g_pSource2ModAPI->QueryInterface(g_pSource2ModAPI->v_factory(), v_name, 0);
if (!v_var)
{
if (error && maxlen)
{
g_pSource2ModAPI->Format(error, maxlen, "Could not find interface: %s", v_name);
}
return false;
}
```
Same as GET\_IFACE, except searches for any.
`GET_SOURCE2MOD_IFACE` [#get_source2mod_iface]
```cpp
#define GET_SOURCE2MOD_IFACE(v_var, v_type, v_name, v_ret) \
v_var = (v_type *)g_pSource2ModAPI->Source2ModFactory(v_name, &(v_ret), nullptr);
if (!v_var)
{
(v_ret) = SOURCE2MOD_IFACE_FAILED;
}
```
Macro for automatically getting a Source2Mod plugin exposed interface.
Unlike the GET\_VALVE\_IFACE\_\* macros this one never returns on its own, so it is usable outside Load() -- in OnAllSource2ModPluginsLoaded(), say, where the interface belongs to another plugin and its absence is an ordinary outcome rather than a fatal one. Test v\_ret against SOURCE2MOD\_IFACE\_OK and decide.
`GET_METAMOD_IFACE` [#get_metamod_iface]
```cpp
#define GET_METAMOD_IFACE(v_var, v_type, v_name, v_ret) \
v_var = (v_type *)g_pSource2ModAPI->MetaFactory(v_name, &(v_ret), nullptr);
if (!v_var)
{
(v_ret) = SOURCE2MOD_IFACE_FAILED;
}
```
Macro for automatically getting a Metamod:Source plugin exposed interface.
Same contract as GET\_SOURCE2MOD\_IFACE: it never returns on its own, it only reports through v\_ret. Metamod fills v\_ret with META\_IFACE\_OK/META\_IFACE\_FAILED, which are binary-compatible with the SOURCE2MOD\_IFACE\_\* values, so compare against those rather than pulling a metamod header in here.
# ISource2ModSQL (/docs/core-api/ISource2ModSQL)
This module provides functionality for:
* MySQL and SQLite connections (ESource2ModSQLDriver), or ones named in configs/databases.json
* Query execution
* Transactions
* Result set handling
* Data escaping
Commonly used for persistent storage and asynchronous database operations.
**Interface id** `ISource2ModSQL001` (`SOURCE2MOD_SQL_INTERFACE`) · **Global** `g_pSource2ModSQL` · **Header** [`source2mod/ISource2ModSQL.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModSQL.h)
Types [#types]
`Source2ModSQLConnectCallbackFunc` [#source2modsqlconnectcallbackfunc]
```cpp
typedef std::function Source2ModSQLConnectCallbackFunc;
```
`Source2ModSQLQueryCallbackFunc` [#source2modsqlquerycallbackfunc]
```cpp
typedef std::function Source2ModSQLQueryCallbackFunc;
```
`Source2ModSQLTransactionSuccessCallbackFunc` [#source2modsqltransactionsuccesscallbackfunc]
```cpp
typedef std::function)> Source2ModSQLTransactionSuccessCallbackFunc;
```
`Source2ModSQLTransactionFailureCallbackFunc` [#source2modsqltransactionfailurecallbackfunc]
```cpp
typedef std::function Source2ModSQLTransactionFailureCallbackFunc;
```
`Source2ModSQLServerlessCallbackFunc` [#source2modsqlserverlesscallbackfunc]
```cpp
typedef std::function Source2ModSQLServerlessCallbackFunc;
```
Callback for a completed serverless query. Runs on the main thread.
Methods [#methods]
`CreateConnection` [#createconnection]
```cpp
virtual ISource2ModSQLConnection* CreateConnection(PluginId owner, Source2ModSQLConnectionInfo info) = 0;
```
Creates a new connection instance (MySQL or SQLite, by info.driver).
Destroyed for you if the owning plugin unloads without calling Destroy(): the worker thread runs code, and the query callbacks are std::functions holding code, inside that plugin's library.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------------------------------------ | --------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Plugin the connection belongs to. |
| `info` | [`Source2ModSQLConnectionInfo`](/docs/core-api/ISource2ModSQL#source2modsqlconnectioninfo) | Connection configuration data. |
**Returns** [`ISource2ModSQLConnection*`](/docs/core-api/ISource2ModSQL#isource2modsqlconnection) — Newly created connection.
`CreateServerless` [#createserverless]
```cpp
virtual ISource2ModSQLServerless* CreateServerless(PluginId owner, Source2ModSQLConnectionInfo info) = 0;
```
Creates a handle that runs each of its queries on a connection of its own, opened and closed around that one query.
Destroyed for you if the owning plugin unloads without calling Destroy(), together with every query of its still in the air: a pending callback is a std::function holding code inside that plugin's library.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Plugin the handle belongs to. |
| `info` | [`Source2ModSQLConnectionInfo`](/docs/core-api/ISource2ModSQL#source2modsqlconnectioninfo) | Connection configuration data. Copied, strings included -- it does not have to outlive this call. |
**Returns** [`ISource2ModSQLServerless*`](/docs/core-api/ISource2ModSQL#isource2modsqlserverless) — Newly created serverless handle.
`GetNamedConnection` [#getnamedconnection]
```cpp
virtual bool GetNamedConnection(const char* name, Source2ModSQLConnectionInfo& info) = 0;
```
The connection details named @p name in configs/databases.json (\{"name": \{"driver": "mysql"|"sqlite", "host", "user", "pass", "database", "port", "timeout"}}), or "default" for an empty name.
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `name` | `const char*` | |
| `info` | [`Source2ModSQLConnectionInfo&`](/docs/core-api/ISource2ModSQL#source2modsqlconnectioninfo) | Filled in when found; its strings stay valid until the config is read again (map change). |
**Returns** `bool` — false when there is no such entry.
`Source2ModSQLTransaction` [#source2modsqltransaction]
Fields [#fields]
| Field | Type |
| --------- | -------------------------- |
| `queries` | `std::vector` |
`Source2ModSQLConnectionInfo` [#source2modsqlconnectioninfo]
Fields [#fields-1]
| Field | Type | Description |
| ---------- | ---------------------------------------------------------------------------- | -------------------------------------------------- |
| `host` | `const char*` | |
| `user` | `const char*` | |
| `pass` | `const char*` | |
| `database` | `const char*` | |
| `port` | `int` | |
| `timeout` | `int` | |
| `driver` | [`ESource2ModSQLDriver`](/docs/core-api/ISource2ModSQL#esource2modsqldriver) | Which server this is; MySQL unless said otherwise. |
`ISource2ModSQLRow` [#isource2modsqlrow]
`ISource2ModSQLResult` [#isource2modsqlresult]
`GetRowCount` [#getrowcount]
```cpp
virtual int GetRowCount() = 0;
```
Returns the total number of rows in the result set.
`GetFieldCount` [#getfieldcount]
```cpp
virtual int GetFieldCount() = 0;
```
Returns the number of columns in the result set.
`FieldNameToNum` [#fieldnametonum]
```cpp
virtual bool FieldNameToNum(const char* name, unsigned int* columnId) = 0;
```
Converts a field name into a column index.
| Parameter | Type | Description |
| ---------- | --------------- | ------------------------- |
| `name` | `const char*` | Field name to search for. |
| `columnId` | `unsigned int*` | Output column index. |
**Returns** `bool` — True if the field exists, false otherwise.
`FieldNumToName` [#fieldnumtoname]
```cpp
virtual const char* FieldNumToName(unsigned int colId) = 0;
```
Returns the field name for a column index.
| Parameter | Type | Description |
| --------- | -------------- | ------------- |
| `colId` | `unsigned int` | Column index. |
**Returns** `const char*` — Field name string.
`MoreRows` [#morerows]
```cpp
virtual bool MoreRows() = 0;
```
Checks whether additional rows are available.
**Returns** `bool` — True if more rows can be fetched.
`FetchRow` [#fetchrow]
```cpp
virtual ISource2ModSQLRow* FetchRow() = 0;
```
Fetches the next row from the result set.
**Returns** [`ISource2ModSQLRow*`](/docs/core-api/ISource2ModSQL#isource2modsqlrow) — Pointer to the fetched row.
`CurrentRow` [#currentrow]
```cpp
virtual ISource2ModSQLRow* CurrentRow() = 0;
```
Returns the currently active row.
**Returns** [`ISource2ModSQLRow*`](/docs/core-api/ISource2ModSQL#isource2modsqlrow) — Pointer to the current row.
`Rewind` [#rewind]
```cpp
virtual bool Rewind() = 0;
```
Rewinds the internal row cursor to the beginning.
**Returns** `bool` — True on success.
`GetFieldType` [#getfieldtype]
```cpp
virtual int GetFieldType(unsigned int field) = 0;
```
Returns the field type of a column (ESource2ModSQLType numbering).
| Parameter | Type | Description |
| --------- | -------------- | ------------- |
| `field` | `unsigned int` | Column index. |
**Returns** `int` — Field type identifier.
`GetString` [#getstring]
```cpp
virtual const char* GetString(unsigned int columnId, size_t* length = nullptr) = 0;
```
Retrieves a string value from a column.
| Parameter | Type | Default | Description |
| ---------- | -------------- | --------- | ------------------------------ |
| `columnId` | `unsigned int` | | Column index. |
| `length` | `size_t*` | `nullptr` | Optional output string length. |
**Returns** `const char*` — Pointer to string data.
`GetDataSize` [#getdatasize]
```cpp
virtual size_t GetDataSize(unsigned int columnId) = 0;
```
Returns the raw data size of a column value.
| Parameter | Type | Description |
| ---------- | -------------- | ------------- |
| `columnId` | `unsigned int` | Column index. |
**Returns** `size_t` — Size in bytes.
`GetFloat` [#getfloat]
```cpp
virtual float GetFloat(unsigned int columnId) = 0;
```
Retrieves a floating-point value from a column.
| Parameter | Type | Description |
| ---------- | -------------- | ------------- |
| `columnId` | `unsigned int` | Column index. |
**Returns** `float` — Float value.
`GetInt` [#getint]
```cpp
virtual int GetInt(unsigned int columnId) = 0;
```
Retrieves an integer value from a column.
| Parameter | Type | Description |
| ---------- | -------------- | ------------- |
| `columnId` | `unsigned int` | Column index. |
**Returns** `int` — Integer value.
`GetInt64` [#getint64]
```cpp
virtual int64_t GetInt64(unsigned int columnId) = 0;
```
Retrieves a 64-bit integer value from a column.
| Parameter | Type | Description |
| ---------- | -------------- | ------------- |
| `columnId` | `unsigned int` | Column index. |
**Returns** `int64_t` — 64-bit integer value.
`IsNull` [#isnull]
```cpp
virtual bool IsNull(unsigned int columnId) = 0;
```
Checks whether a column contains a NULL value.
| Parameter | Type | Description |
| ---------- | -------------- | ------------- |
| `columnId` | `unsigned int` | Column index. |
**Returns** `bool` — True if the value is NULL.
`ISource2ModSQLQuery` [#isource2modsqlquery]
`GetResultSet` [#getresultset]
```cpp
virtual ISource2ModSQLResult* GetResultSet() = 0;
```
Returns the current result set.
**Returns** [`ISource2ModSQLResult*`](/docs/core-api/ISource2ModSQL#isource2modsqlresult) — Pointer to the result set.
`FetchMoreResults` [#fetchmoreresults]
```cpp
virtual bool FetchMoreResults() = 0;
```
Advances to the next available result set.
**Returns** `bool` — True if another result set exists.
`GetInsertId` [#getinsertid]
```cpp
virtual unsigned int GetInsertId() = 0;
```
Returns the last inserted row ID.
**Returns** `unsigned int` — Insert ID value.
`GetAffectedRows` [#getaffectedrows]
```cpp
virtual unsigned int GetAffectedRows() = 0;
```
Returns the number of affected rows.
**Returns** `unsigned int` — Affected row count.
`ISource2ModSQLConnection` [#isource2modsqlconnection]
`Connect` [#connect]
```cpp
virtual void Connect(Source2ModSQLConnectCallbackFunc callback) = 0;
```
Establishes the connection (opens the file, for SQLite).
| Parameter | Type | Description |
| ---------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| `callback` | [`Source2ModSQLConnectCallbackFunc`](/docs/core-api/ISource2ModSQL#source2modsqlconnectcallbackfunc) | Callback invoked after connection attempt. |
`Query` [#query]
```cpp
virtual void Query(char* query, Source2ModSQLQueryCallbackFunc callback) = 0;
virtual void Query(const char* query, Source2ModSQLQueryCallbackFunc callback, ...) = 0;
```
Executes a raw SQL query.
The callback always runs, once. On success it gets the query, owned by Source2Mod and valid only for the duration of the callback -- copy anything you keep, do not delete it. On failure it gets nullptr: the connection is not up (Connect() has not reported true yet, or it failed) or the server rejected the query. The reason is written to the server log.
Executes a formatted SQL query.
Same callback contract as the overload above: nullptr on failure, otherwise a source2mod-owned query valid only during the callback.
| Parameter | Type | Description |
| ---------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
| `query` | `char*` | SQL query string. |
| `callback` | [`Source2ModSQLQueryCallbackFunc`](/docs/core-api/ISource2ModSQL#source2modsqlquerycallbackfunc) | Callback invoked with query result, on the main thread. |
| `query` | `const char*` | SQL query string. |
| `—` | `...` | |
`ExecuteTransaction` [#executetransaction]
```cpp
virtual void ExecuteTransaction(Source2ModSQLTransaction txn, Source2ModSQLTransactionSuccessCallbackFunc success, Source2ModSQLTransactionFailureCallbackFunc failure) = 0;
```
Executes multiple queries as a transaction.
Exactly one of the two callbacks runs, on the main thread.
@p txn, in order. They are owned by Source2Mod and freed once the callback returns -- copy anything you keep, do not delete them.
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `txn` | [`Source2ModSQLTransaction`](/docs/core-api/ISource2ModSQL#source2modsqltransaction) | Transaction query batch. |
| `success` | [`Source2ModSQLTransactionSuccessCallbackFunc`](/docs/core-api/ISource2ModSQL#source2modsqltransactionsuccesscallbackfunc) | Callback invoked on success, with one query per entry of |
| `failure` | [`Source2ModSQLTransactionFailureCallbackFunc`](/docs/core-api/ISource2ModSQL#source2modsqltransactionfailurecallbackfunc) | Callback invoked on failure, after the transaction has been rolled back, with the error and the index of the query that failed -- or -1 when it was not one of them (not connected, BEGIN or COMMIT failed). |
`Destroy` [#destroy]
```cpp
virtual void Destroy() = 0;
```
Destroys and releases the connection instance.
`GetDriver` [#getdriver]
```cpp
virtual ESource2ModSQLDriver GetDriver() = 0;
```
The driver this connection talks to.
`Escape` [#escape]
```cpp
virtual std::string Escape(char* string) = 0;
virtual std::string Escape(const char* string) = 0;
```
Escapes a mutable string for safe SQL usage.
Escapes a constant string for safe SQL usage.
| Parameter | Type | Description |
| --------- | ------------- | ------------- |
| `string` | `char*` | Input string. |
| `string` | `const char*` | Input string. |
**Returns** `std::string` — Escaped string.
`Source2ModSQLServerlessResult` [#source2modsqlserverlessresult]
What one serverless query came back with.
m_bSuccess is about the query, not about rows -- a SELECT that matched nothing is a success with an empty result set. A query that failed has m_bSuccess false, m_sError filled in and m_pQuery null.
Fields [#fields-2]
| Field | Type | Description |
| -------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `m_bSuccess` | `bool` | |
| `m_nErrorCode` | `unsigned int` | The driver's own error number (MySQL's, or SQLite's result code), or 0 when the query never reached the database. |
| `m_sError` | `std::string` | |
| `m_pQuery` | [`ISource2ModSQLQuery*`](/docs/core-api/ISource2ModSQL#isource2modsqlquery) | Rows, insert id and affected rows, read off the connection before it was closed again. Owned by Source2Mod and valid only for the duration of the callback -- copy anything you keep. Null when the query failed. |
`ISource2ModSQLServerless` [#isource2modsqlserverless]
A set of connection details that queries are run against one at a time.
Every query opens its own connection, runs, reads its rows out and closes again -- there is nothing to keep alive, nothing to reconnect, and nothing that can go stale over a map change. None of it happens on the main thread: queries go through Source2Mod's shared SQL worker, one thread for all of them however many plugins are using it, and land back in a callback on the main thread.
Meant for the occasional query -- loading a player on connect, writing their stats on disconnect. A connection costs a handshake and a login every single time, so for anything that runs per tick or per shot, keep an ISource2ModSQLConnection open instead.
```cpp
// once, when your plugin loads
m_pDb = api->SQL()->CreateServerless(g_PLID, { "127.0.0.1", "user", "pass", "db" });
// wherever you need it
m_pDb->Query("SELECT name, kills FROM players WHERE steamid = ?", { steamid },
[](const Source2ModSQLServerlessResult& r)
{
if (!r.m_bSuccess)
return; // r.m_sError says what went wrong
ISource2ModSQLResult* pSet = r.m_pQuery->GetResultSet();
while (pSet && pSet->MoreRows() && pSet->FetchRow())
{
// pSet->GetString(0), pSet->GetInt(1) -- copy anything you keep.
}
});
```
`Query` [#query-1]
```cpp
virtual void Query(const char* query, Source2ModSQLServerlessCallbackFunc callback) = 0;
virtual void Query(const char* query, std::vector params, Source2ModSQLServerlessCallbackFunc callback) = 0;
```
Runs one query on a connection of its own.
Runs one parameterised query on a connection of its own.
Placeholders are filled in on the worker against the live connection: `?` becomes the escaped value in quotes, `??` becomes a quoted identifier -- a table or column name, which cannot be a `?`. A `?` inside a string literal is left alone, and a placeholder count that does not match @p params fails the query instead of running it.
| Parameter | Type | Description |
| ---------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `query` | `const char*` | SQL to run. |
| `callback` | [`Source2ModSQLServerlessCallbackFunc`](/docs/core-api/ISource2ModSQL#source2modsqlserverlesscallbackfunc) | Callback invoked with the result, on the main thread. |
| `params` | `std::vector` | Values for the placeholders, in order. |
`Destroy` [#destroy-1]
```cpp
virtual void Destroy() = 0;
```
Drops these details and cancels what they still have queued. A query already on the wire is left to finish and its result thrown away -- its callback does not run.
Enums [#enums]
`ESource2ModSQLDriver` [#esource2modsqldriver]
```cpp
enum ESource2ModSQLDriver
```
| Name | Value | Description |
| ------------------------------ | ----- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `SOURCE2MOD_SQL_DRIVER_MYSQL` | `0` | A MySQL / MariaDB server: host, user, pass, database, port. |
| `SOURCE2MOD_SQL_DRIVER_SQLITE` | `1` | An SQLite file: database is its path (a relative one is under addons/source2mod/data, made when missing); host, user and pass unused. |
Macros [#macros]
| Macro | Expands to |
| ----------------------------- | ------------------------------------------------------ |
| `SQL_CREATE_CONNECTION(info)` | `g_pSource2ModSQL->CreateConnection(g_PluginID, info)` |
# ISource2ModScheduler (/docs/core-api/ISource2ModScheduler)
Provides functionality for:
* Scheduling callbacks for next frame
* Creating repeating or delayed timers
* Managing timer lifecycle
Interface for scheduling tasks and timers.
**Interface id** `ISource2ModScheduler002` (`SOURCE2MOD_SCHEDULER_INTERFACE`) · **Global** `g_pSource2ModScheduler` · **Header** [`source2mod/ISource2ModScheduler.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModScheduler.h)
Types [#types]
`TimerCallback` [#timercallback]
```cpp
using TimerCallback = Source2ModCallback;
```
Callback executed by timers A timer's or a next-frame task's callback: a function, an object and a method, or a lambda (Source2ModCallback) -- whose it is, the core reads off it.
Methods [#methods]
`NextFrame` [#nextframe]
```cpp
virtual void NextFrame(TimerCallback task) = 0;
```
Executes a task on the next frame.
Runs once on the next game frame
Dropped if the owning plugin unloads before the frame arrives
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------- | ------------------------------------------------------ |
| `task` | [`TimerCallback`](/docs/core-api/ISource2ModScheduler#timercallback) | Callback function; the plugin whose code it is owns it |
`AddTimer` [#addtimer]
```cpp
virtual Timer* AddTimer(float interval, TimerCallback callback, int flags = 0) = 0;
```
Adds a timer.
Killed automatically when the owning plugin unloads
| Parameter | Type | Default | Description |
| ---------- | -------------------------------------------------------------------- | ------- | ---------------------------------- |
| `interval` | `float` | | Time in seconds between executions |
| `callback` | [`TimerCallback`](/docs/core-api/ISource2ModScheduler#timercallback) | | Function to execute |
| `flags` | `int` | `0` | Timer behavior flags |
**Returns** [`Timer*`](/docs/core-api/ISource2ModScheduler#timer) — Pointer to created Timer
`KillTimer` [#killtimer]
```cpp
virtual void KillTimer(Timer* timer) = 0;
```
Removes a timer.
| Parameter | Type | Description |
| --------- | ----------------------------------------------------- | -------------- |
| `timer` | [`Timer*`](/docs/core-api/ISource2ModScheduler#timer) | Timer instance |
`Timer` [#timer]
Represents a scheduled timer.
Contains execution data and state for scheduled callbacks.
Fields [#fields]
| Field | Type | Description |
| ---------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Plugin that created the timer, 0 for Source2Mod itself. The callback holds code inside that plugin's library, so the scheduler has to drop the timer before the library is closed -- destroying the std::function afterwards calls into unmapped memory. |
| `Interval` | `float` | Interval between executions (seconds) |
| `ExecTime` | `double` | Next execution time (engine time) |
| `Callback` | [`TimerCallback`](/docs/core-api/ISource2ModScheduler#timercallback) | Callback function |
| `Flags` | `int` | Timer flags (TimerFlags) |
| `InExec` | `bool` | True while callback is executing |
| `KillMe` | `bool` | Marks timer for deletion |
Constructors [#constructors]
```cpp
Timer(PluginId owner, float interval, double execTime, TimerCallback callback, int flags);
```
Constructs a timer.
Enums [#enums]
`TimerFlags` [#timerflags]
```cpp
enum TimerFlags
```
Timer behavior flags.
| Name | Value | Description |
| ------------------------- | -------- | --------------------------------------- |
| `TIMER_FLAG_REPEAT` | `1 << 0` | Repeats timer indefinitely |
| `TIMER_FLAG_NO_MAPCHANGE` | `1 << 1` | Automatically stops timer on map change |
Macros [#macros]
| Macro | Expands to |
| ------------------------------ | --------------------------------------------------------------- |
| `NEXT_FRAME(task)` | `g_pSource2ModScheduler->NextFrame(task)` |
| `ADD_TIMER(interval, cb, ...)` | `g_pSource2ModScheduler->AddTimer(interval, cb, ##__VA_ARGS__)` |
| `KILL_TIMER(timer)` | `g_pSource2ModScheduler->KillTimer(timer)` |
# ISource2ModSchemaMethods (/docs/core-api/ISource2ModSchemaMethods)
The code behind CBaseEntity::Teleport, CCSPlayerController::TakeDamage, CPlayer\_ItemServices::GiveNamedItem, the schema offset lookups, the nav areas, ... lives in the core (the SDK's src/impl). A plugin carries only forwarders, generated by tools/schemamethods/generate.py, that call the core's copy through this interface -- so a fix in a helper ships with a core update, and plugins keep running unchanged.
What still needs a plugin rebuild: a changed schema class member (its type), a changed helper signature, the header-only templates and inlines.
Each function is found by its signature ("CBaseEntity::Teleport(const Vector\*,const QAngle\*,const Vector\*,HookChain)"). A plugin built against a newer SDK than the core gets a warning for a function the core does not have, and the call does nothing (the return value is value-initialised).
**Interface id** `ISource2ModSchemaMethods001` (`SOURCE2MOD_SCHEMAMETHODS_INTERFACE`) · **Global** `g_pSource2ModSchemaMethods` · **Header** [`source2mod/ISource2ModSchemaMethods.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModSchemaMethods.h)
Methods [#methods]
`Find` [#find]
```cpp
virtual void* Find(const char* key) = 0;
```
The core's thunk for a function by its signature key; null when this core does not have it.
| Parameter | Type |
| --------- | ------------- |
| `key` | `const char*` |
`GetCount` [#getcount]
```cpp
virtual int GetCount() = 0;
```
`GetKey` [#getkey]
```cpp
virtual const char* GetKey(int index) = 0;
```
| Parameter | Type |
| --------- | ----- |
| `index` | `int` |
`Source2ModSchemaMethodEntry` [#source2modschemamethodentry]
Fields [#fields]
| Field | Type |
| ---------- | ------------- |
| `key` | `const char*` |
| `function` | `void*` |
Functions [#functions]
`Source2ModSchemaMethod` [#source2modschemamethod]
```cpp
void* Source2ModSchemaMethod(const char* key, bool& resolved);
```
Plugin side: Find() through g\_pSource2ModSchemaMethods (src/forward). @p resolved becomes true once the answer is final -- the interface is there, whether or not it has the key (a missing one is warned about then); before SOURCE2MOD\_SAVEVARS() it stays false and the caller asks again.
| Parameter | Type |
| ---------- | ------------- |
| `key` | `const char*` |
| `resolved` | `bool&` |
`Source2ModSchemaMissing` [#source2modschemamissing]
```cpp
template inline R Source2ModSchemaMissing();
```
What a forwarder returns when the core does not have the function.
`Source2ModSchemaMethodsImpl` [#source2modschemamethodsimpl]
```cpp
ISource2ModSchemaMethods* Source2ModSchemaMethodsImpl();
```
The core's instance (src/impl/schema\_methods.cpp).
# ISource2ModScripts (/docs/core-api/ISource2ModScripts)
CS2 runs map and mode logic in JavaScript through `point_script` entities (`import { Instance } from "cs_script/point_script"`). This interface lets a plugin run scripts of its own and talk to them:
* run a compiled script asset (`.vjs`) the way a map does -- this needs no signature and keeps working through game updates;
* run raw JavaScript source, or a `.js` file straight from the server's disk, and run it again after an edit (hot reload);
* fire an input a script listens for with `Instance.OnScriptInput(...)` (native -> script);
* receive what a script sends with `Instance.ServerCommand(...)` (script -> native);
* keep a script alive across round restarts and map changes.
A script sends a message with
```cpp
Instance.ServerCommand("source2mod_script mychannel " + encodeURIComponent(JSON.stringify(data)));
```
and every handler registered for `mychannel` receives everything after the channel name. The line goes through the server's command buffer: it arrives on a later tick, a `;` or a newline in it would split it into two commands, and it is cut at the buffer's line limit -- encode the payload (as above) and keep it short. Only the server itself can run `source2mod_script`; a client typing it is ignored.
Everything here belongs on the main thread.
Runs cs\_script scripts for plugins and carries messages both ways.
A script is known by a name the plugin picks, which is also the targetname of its `point_script` entity; running a script under a name already in use replaces the old one. Every script and every message handler belongs to the plugin that created it and goes away when that plugin unloads.
**Interface id** `ISource2ModScripts002` (`SOURCE2MOD_SCRIPTS_INTERFACE`) · **Global** `g_pSource2ModScripts` · **Header** [`source2mod/ISource2ModScripts.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModScripts.h)
Types [#types]
`Source2ModScriptMessageHookId` [#source2modscriptmessagehookid]
```cpp
using Source2ModScriptMessageHookId = Source2ModHookId;
```
A cs\_script message hook (HookScriptMessage).
`ScriptMessageHandler` [#scriptmessagehandler]
```cpp
using ScriptMessageHandler = Source2ModCallback;
```
Called with a message a script sent through `source2mod_script`.
Methods [#methods]
`CanRunSource` [#canrunsource]
```cpp
virtual bool CanRunSource() = 0;
```
Whether raw source can be run (RunSource, RunFile).
Needs the game's script loader (gamedata "CSScript::RunScript") and the script component inside `point_script`. LoadAsset() works without both.
`LoadAsset` [#loadasset]
```cpp
virtual CEntityHandle LoadAsset(PluginId owner, const char* pszName, const char* pszAsset, bool bPersistent) = 0;
```
Runs a compiled script asset, exactly as a map's `point_script` does.
| Parameter | Type | Description |
| ------------- | ---------------------------------------------------- | ---------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Owning plugin |
| `pszName` | `const char*` | Name to refer to the script by |
| `pszAsset` | `const char*` | Resource path of the compiled script, e.g. "scripts/myplugin/main.vjs" |
| `bPersistent` | `bool` | Spawn it again after a round restart or a map change |
**Returns** `CEntityHandle` — The `point_script` entity, or an invalid handle on failure
`RunSource` [#runsource]
```cpp
virtual CEntityHandle RunSource(PluginId owner, const char* pszName, const char* pszVirtualPath, const char* pszSource, bool bPersistent) = 0;
```
Runs raw JavaScript source in a new `point_script`.
A script that does not compile is reported by the game in the server console; the entity exists regardless.
| Parameter | Type | Description |
| ---------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------ |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Owning plugin |
| `pszName` | `const char*` | Name to refer to the script by |
| `pszVirtualPath` | `const char*` | Path the script goes by in errors and stack traces, e.g. "scripts/myplugin.js" |
| `pszSource` | `const char*` | The source, an ES module importing "cs\_script/point\_script" |
| `bPersistent` | `bool` | Run it again after a round restart or a map change |
**Returns** `CEntityHandle` — The `point_script` entity, or an invalid handle when the source cannot be run
`RunFile` [#runfile]
```cpp
virtual CEntityHandle RunFile(PluginId owner, const char* pszName, const char* pszFilePath, bool bPersistent) = 0;
```
Runs a JavaScript file from the server's disk.
| Parameter | Type | Description |
| ------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Owning plugin |
| `pszName` | `const char*` | Name to refer to the script by |
| `pszFilePath` | `const char*` | Absolute path, or relative to the game directory (`game/csgo`) |
| `bPersistent` | `bool` | Run it again after a round restart or a map change; the file is read again every time, so edits are picked up |
**Returns** `CEntityHandle` — The `point_script` entity, or an invalid handle when the file cannot be read or run
`Reload` [#reload]
```cpp
virtual CEntityHandle Reload(const char* pszName) = 0;
```
Throws the script's entity away and runs the script again.
A file is read again, which makes this the hot reload after an edit; source and assets run as they were given.
| Parameter | Type |
| --------- | ------------- |
| `pszName` | `const char*` |
**Returns** `CEntityHandle` — The new entity, or an invalid handle when there is no script by that name or it cannot be run
`Remove` [#remove]
```cpp
virtual void Remove(const char* pszName) = 0;
```
Removes the script and its entity. Unknown names are ignored.
| Parameter | Type |
| --------- | ------------- |
| `pszName` | `const char*` |
`Find` [#find]
```cpp
virtual CEntityHandle Find(const char* pszName) = 0;
```
The script's `point_script` entity, or an invalid handle.
| Parameter | Type |
| --------- | ------------- |
| `pszName` | `const char*` |
`FireInput` [#fireinput]
```cpp
virtual bool FireInput(const char* pszName, const char* pszInput) = 0;
```
Fires the callback the script registered with `Instance.OnScriptInput(pszInput, ...)`.
This is the game's `RunScriptInput` entity input. Only the input name reaches the script; data travels the other way (see HookScriptMessage()), or stays on the script side.
| Parameter | Type |
| ---------- | ------------- |
| `pszName` | `const char*` |
| `pszInput` | `const char*` |
**Returns** `bool` — false when there is no live script by that name
`HookScriptMessage` [#hookscriptmessage]
```cpp
virtual Source2ModScriptMessageHookId HookScriptMessage(const char* pszChannel, ScriptMessageHandler handler) = 0;
```
Receives what scripts send on a channel through `source2mod_script`.
Several handlers may hook one channel, from one plugin or more; each gets every message.
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 |
| ------------ | -------------------------------------------------------------------------------- |
| `pszChannel` | `const char*` |
| `handler` | [`ScriptMessageHandler`](/docs/core-api/ISource2ModScripts#scriptmessagehandler) |
**Returns** [`Source2ModScriptMessageHookId`](/docs/core-api/ISource2ModScripts#source2modscriptmessagehookid) — The id UnhookScriptMessage(id) takes
`UnhookScriptMessage` [#unhookscriptmessage]
```cpp
virtual bool UnhookScriptMessage(const char* pszChannel, const ScriptMessageHandler& handler) = 0;
virtual bool UnhookScriptMessage(Source2ModScriptMessageHookId 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 HookScriptMessage() returned the id for.
| Parameter | Type |
| ------------ | -------------------------------------------------------------------------------------------------- |
| `pszChannel` | `const char*` |
| `handler` | [`const ScriptMessageHandler&`](/docs/core-api/ISource2ModScripts#scriptmessagehandler) |
| `id` | [`Source2ModScriptMessageHookId`](/docs/core-api/ISource2ModScripts#source2modscriptmessagehookid) |
**Returns** `bool` — true when one was found
Macros [#macros]
`SCRIPTS_LOAD_ASSET` [#scripts_load_asset]
```cpp
#define SCRIPTS_LOAD_ASSET(name, asset, persistent) g_pSource2ModScripts->LoadAsset(g_PluginID, name, asset, persistent)
```
Shorthand accessors via g\_pSource2ModScripts.
Other macros [#other-macros]
| Macro | Expands to |
| ---------------------------------------------------- | ----------------------------------------------------------------------------- |
| `SCRIPTS_RUN_SOURCE(name, path, source, persistent)` | `g_pSource2ModScripts->RunSource(g_PluginID, name, path, source, persistent)` |
| `SCRIPTS_RUN_FILE(name, file, persistent)` | `g_pSource2ModScripts->RunFile(g_PluginID, name, file, persistent)` |
| `SCRIPTS_FIRE_INPUT(name, input)` | `g_pSource2ModScripts->FireInput(name, input)` |
| `HOOK_SCRIPT_MESSAGE(channel, handler)` | `g_pSource2ModScripts->HookScriptMessage(channel, handler)` |
| `UNHOOK_SCRIPT_MESSAGE(channel, handler)` | `g_pSource2ModScripts->UnhookScriptMessage(channel, handler)` |
# ISource2ModSounds (/docs/core-api/ISource2ModSounds)
A sound event reaches a client as one network message (CMsgSosStartSoundEvent): the hash of its name, the entity it plays from, a guid and a packed block of parameters. This interface builds and sends that message itself instead of going through an entity on the server, which is what makes the rest possible:
* a sound goes to exactly the players you pick -- nobody else receives it, so there is nothing for them to hear;
* Emit() hands back the guid, and the guid stops the sound at once or changes its parameters while it plays;
* volume channels: a player's own volume for "music", "effects" or whatever a plugin names is applied per recipient, so a player turns the server's music down in a menu instead of fighting snd\_musicvolume;
* every sound the game itself starts passes through HookSound(), where it can be blocked, renamed, re-parametrised or re-addressed.
Everything here belongs on the main thread.
**Interface id** `ISource2ModSounds002` (`SOURCE2MOD_SOUNDS_INTERFACE`) · **Global** `g_pSource2ModSounds` · **Header** [`source2mod/ISource2ModSounds.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModSounds.h)
Types [#types]
`Source2ModSoundHookId` [#source2modsoundhookid]
```cpp
using Source2ModSoundHookId = Source2ModHookId;
```
A sound hook (HookSound).
`SoundGuid` [#soundguid]
```cpp
using SoundGuid = uint32_t;
```
Identifies one playing sound on the clients it was sent to.
Taken from the engine's own counter, so it never collides with a sound the game started. 0 means "nothing was sent".
`SoundRecipients` [#soundrecipients]
```cpp
using SoundRecipients = uint64_t;
```
Recipient set, one bit per player slot (bit N = slot N).
`SoundHook` [#soundhook]
```cpp
using SoundHook = Source2ModCallback;
```
Hook called for every sound the game is about to send to clients.
Sounds sent through this interface do not come back through the hook.
The server only knows about the sounds it sends. What a client predicts for itself -- its own weapon fire, its own knife swing, its own footsteps -- is played locally and never passes through here; the copy of those sounds that goes to everybody else does.
Methods [#methods]
`CreateSound` [#createsound]
```cpp
virtual ISource2ModSound* CreateSound(PluginId owner, const char* name = nullptr) = 0;
```
Creates a sound: listener-sourced, volume and pitch 1, no recipients.
ISource2ModSound::New() is the usual way in. Release with `delete` or DestroySound(); whatever a plugin leaves behind goes when it unloads.
| Parameter | Type | Default | Description |
| --------- | ---------------------------------------------------- | --------- | ---------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | | Plugin ID that owns the sound |
| `name` | `const char*` | `nullptr` | Sound event name, may be set later |
`DestroySound` [#destroysound]
```cpp
virtual void DestroySound(ISource2ModSound* sound) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------ |
| `sound` | [`ISource2ModSound*`](/docs/core-api/ISource2ModSounds#isource2modsound) |
`EmitSoundToPlayer` [#emitsoundtoplayer]
```cpp
virtual SoundGuid EmitSoundToPlayer(CPlayerSlot slot, const char* name, float volume = 1.0f, float pitch = 1.0f, const char* channel = nullptr) = 0;
```
Plays a sound for one player only, at the listener.
| Parameter | Type | Default |
| --------- | ------------- | --------- |
| `slot` | `CPlayerSlot` | |
| `name` | `const char*` | |
| `volume` | `float` | `1.0f` |
| `pitch` | `float` | `1.0f` |
| `channel` | `const char*` | `nullptr` |
`EmitSoundToPlayers` [#emitsoundtoplayers]
```cpp
virtual SoundGuid EmitSoundToPlayers(SoundRecipients recipients, const char* name, float volume = 1.0f, float pitch = 1.0f, const char* channel = nullptr) = 0;
```
Plays a sound for a set of players, at each listener.
| Parameter | Type | Default |
| ------------ | --------------------------------------------------------------------- | --------- |
| `recipients` | [`SoundRecipients`](/docs/core-api/ISource2ModSounds#soundrecipients) | |
| `name` | `const char*` | |
| `volume` | `float` | `1.0f` |
| `pitch` | `float` | `1.0f` |
| `channel` | `const char*` | `nullptr` |
`EmitSoundToAll` [#emitsoundtoall]
```cpp
virtual SoundGuid EmitSoundToAll(const char* name, float volume = 1.0f, float pitch = 1.0f, const char* channel = nullptr) = 0;
```
Plays a sound for everybody, at each listener.
| Parameter | Type | Default |
| --------- | ------------- | --------- |
| `name` | `const char*` | |
| `volume` | `float` | `1.0f` |
| `pitch` | `float` | `1.0f` |
| `channel` | `const char*` | `nullptr` |
`EmitSoundFromEntity` [#emitsoundfromentity]
```cpp
virtual SoundGuid EmitSoundFromEntity(CEntityInstance* entity, const char* name, float volume = 1.0f, float pitch = 1.0f, SoundRecipients recipients = ~SoundRecipients(0), const char* channel = nullptr) = 0;
```
Plays a sound from an entity, for @p recipients (everybody by default).
| Parameter | Type | Default |
| ------------ | --------------------------------------------------------------------- | --------------------- |
| `entity` | `CEntityInstance*` | |
| `name` | `const char*` | |
| `volume` | `float` | `1.0f` |
| `pitch` | `float` | `1.0f` |
| `recipients` | [`SoundRecipients`](/docs/core-api/ISource2ModSounds#soundrecipients) | `~SoundRecipients(0)` |
| `channel` | `const char*` | `nullptr` |
`StopSound` [#stopsound]
```cpp
virtual void StopSound(SoundGuid guid) = 0;
```
Stops a sound at once, for everybody it was sent to.
| Parameter | Type |
| --------- | --------------------------------------------------------- |
| `guid` | [`SoundGuid`](/docs/core-api/ISource2ModSounds#soundguid) |
`StopSoundForPlayers` [#stopsoundforplayers]
```cpp
virtual void StopSoundForPlayers(SoundGuid guid, SoundRecipients recipients) = 0;
```
Stops a sound for some of its recipients; it keeps playing for the rest.
| Parameter | Type |
| ------------ | --------------------------------------------------------------------- |
| `guid` | [`SoundGuid`](/docs/core-api/ISource2ModSounds#soundguid) |
| `recipients` | [`SoundRecipients`](/docs/core-api/ISource2ModSounds#soundrecipients) |
`StopSoundByName` [#stopsoundbyname]
```cpp
virtual void StopSoundByName(const char* name, int sourceEntityIndex = SOUND_SOURCE_LISTENER, SoundRecipients recipients = ~SoundRecipients(0)) = 0;
```
Stops a sound by name, including one the game started.
| Parameter | Type | Default | Description |
| ------------------- | --------------------------------------------------------------------- | ----------------------- | --------------------------------------------------------------------------- |
| `name` | `const char*` | | Sound event name |
| `sourceEntityIndex` | `int` | `SOUND_SOURCE_LISTENER` | Entity the sound plays from, SOUND\_SOURCE\_LISTENER for sounds without one |
| `recipients` | [`SoundRecipients`](/docs/core-api/ISource2ModSounds#soundrecipients) | `~SoundRecipients(0)` | Who to stop it for, everybody by default |
`SetSoundVolume` [#setsoundvolume]
```cpp
virtual void SetSoundVolume(SoundGuid guid, float volume) = 0;
```
Changes the volume of a playing sound. Channel volumes still apply.
| Parameter | Type |
| --------- | --------------------------------------------------------- |
| `guid` | [`SoundGuid`](/docs/core-api/ISource2ModSounds#soundguid) |
| `volume` | `float` |
`UpdateSoundParams` [#updatesoundparams]
```cpp
virtual void UpdateSoundParams(SoundGuid guid, const ISource2ModSound* params, SoundRecipients recipients = SOUND_RECIPIENTS_NONE) = 0;
```
Sends the parameters of @p params to a playing sound.
Only the parameters set on @p params are sent; its name, source and recipients are ignored.
| Parameter | Type | Default | Description |
| ------------ | ------------------------------------------------------------------------------ | ----------------------- | ------------------------------------------------- |
| `guid` | [`SoundGuid`](/docs/core-api/ISource2ModSounds#soundguid) | | |
| `params` | [`const ISource2ModSound*`](/docs/core-api/ISource2ModSounds#isource2modsound) | | |
| `recipients` | [`SoundRecipients`](/docs/core-api/ISource2ModSounds#soundrecipients) | `SOUND_RECIPIENTS_NONE` | Who to send to, 0 for everybody the sound went to |
`SetPlayerVolume` [#setplayervolume]
```cpp
virtual void SetPlayerVolume(CPlayerSlot slot, const char* channel, float volume) = 0;
```
Sets one player's volume for a channel (0 mutes, 1 is unchanged).
A channel is just a name sounds are emitted under ("music", "effects", ...). Applies to every later sound on the channel, and straight away to the ones this player is still hearing: they get louder, quieter, or stop. Forgotten when the player disconnects -- Source2Mod keeps nothing on disk, load it from wherever the plugin stores preferences.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
| `channel` | `const char*` |
| `volume` | `float` |
`GetPlayerVolume` [#getplayervolume]
```cpp
virtual float GetPlayerVolume(CPlayerSlot slot, const char* channel) = 0;
```
A player's volume for a channel, 1.0 when never set.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
| `channel` | `const char*` |
`ResetPlayerVolumes` [#resetplayervolumes]
```cpp
virtual void ResetPlayerVolumes(CPlayerSlot slot) = 0;
```
Puts all of a player's channels back to 1.0.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`HashSoundName` [#hashsoundname]
```cpp
virtual uint32_t HashSoundName(const char* name) = 0;
```
Hash a sound event name travels as, for matching a hooked sound:
static const uint32\_t knifeHit = g\_pSource2ModSounds->HashSoundName("Weapon\_Knife.Hit"); if (sound->GetNameHash() == knifeHit) ...
The name is remembered, so ISource2ModSound::GetName() and LookupSoundName() resolve it from then on.
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`LookupSoundName` [#lookupsoundname]
```cpp
virtual const char* LookupSoundName(uint32_t hash) = 0;
```
Name for a hash, nullptr when Source2Mod has not seen it.
| Parameter | Type |
| --------- | ---------- |
| `hash` | `uint32_t` |
`HookSound` [#hooksound]
```cpp
virtual Source2ModSoundHookId HookSound(SoundHook handler) = 0;
```
Hooks the sounds the game sends to clients.
A plugin may hook as often as it 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 |
| --------- | --------------------------------------------------------- | ----------------------------------------------- |
| `handler` | [`SoundHook`](/docs/core-api/ISource2ModSounds#soundhook) | A function, an object and a method, or a lambda |
**Returns** [`Source2ModSoundHookId`](/docs/core-api/ISource2ModSounds#source2modsoundhookid) — The id UnhookSound(id) takes
`UnhookSound` [#unhooksound]
```cpp
virtual bool UnhookSound(const SoundHook& handler) = 0;
virtual bool UnhookSound(Source2ModSoundHookId 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 HookSound() returned the id for.
| Parameter | Type |
| --------- | --------------------------------------------------------------------------------- |
| `handler` | [`const SoundHook&`](/docs/core-api/ISource2ModSounds#soundhook) |
| `id` | [`Source2ModSoundHookId`](/docs/core-api/ISource2ModSounds#source2modsoundhookid) |
**Returns** `bool` — true when one was found
`ISource2ModSound` [#isource2modsound]
One sound event: what to play, from where, how, and for whom.
Made with ISource2ModSound::New(), released with `delete`, and reusable in between -- Emit() may be called any number of times, each call is a new sound with a new guid. The same type is what a HookSound() handler receives for a sound the game is about to send.
Parameter names are the sound event's public fields ("public.volume", "public.pitch", "public.position", or whatever a custom .vsndevts declares). They travel as hashes, so a name cannot be listed back, only asked for.
`SetName` [#setname]
```cpp
virtual void SetName(const char* name) = 0;
```
Sets the sound event to play, e.g. "Weapon\_AK47.Single".
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`GetName` [#getname]
```cpp
virtual const char* GetName() const = 0;
```
Sound event name, or "" when only its hash is known.
A hooked game sound arrives as a hash, and a hash cannot be turned back into a name. Source2Mod resolves it against the names it has been given so far -- everything emitted through this interface and everything passed to ISource2ModSounds::HashSoundName() -- so the sounds a plugin looks for come out named, the rest as "".
`SetNameHash` [#setnamehash]
```cpp
virtual void SetNameHash(uint32_t hash) = 0;
```
| Parameter | Type |
| --------- | ---------- |
| `hash` | `uint32_t` |
`GetNameHash` [#getnamehash]
```cpp
virtual uint32_t GetNameHash() const = 0;
```
`GetGuid` [#getguid]
```cpp
virtual SoundGuid GetGuid() const = 0;
```
Guid of the sound. Assigned by the engine for a hooked sound, by the last Emit() otherwise (0 before the first one).
`SetSourceEntityIndex` [#setsourceentityindex]
```cpp
virtual void SetSourceEntityIndex(int index) = 0;
```
Entity index the sound plays from and follows, or SOUND\_SOURCE\_LISTENER (default) / SOUND\_SOURCE\_WORLD.
| Parameter | Type |
| --------- | ----- |
| `index` | `int` |
`GetSourceEntityIndex` [#getsourceentityindex]
```cpp
virtual int GetSourceEntityIndex() const = 0;
```
`SetSourceEntity` [#setsourceentity]
```cpp
virtual void SetSourceEntity(CEntityInstance* entity) = 0;
```
Plays the sound from an entity. nullptr is SOUND\_SOURCE\_LISTENER.
| Parameter | Type |
| --------- | ------------------ |
| `entity` | `CEntityInstance*` |
`SetPosition` [#setposition]
```cpp
virtual void SetPosition(const Vector& position) = 0;
```
Plays the sound at a point in the world: SOUND\_SOURCE\_WORLD plus "public.position".
| Parameter | Type |
| ---------- | --------------- |
| `position` | `const Vector&` |
`SetVolume` [#setvolume]
```cpp
virtual void SetVolume(float volume) = 0;
```
"public.volume". 1.0 is the sound event's own volume.
With a channel set, each recipient gets this multiplied by their own volume for that channel.
| Parameter | Type |
| --------- | ------- |
| `volume` | `float` |
`GetVolume` [#getvolume]
```cpp
virtual float GetVolume() const = 0;
```
`SetPitch` [#setpitch]
```cpp
virtual void SetPitch(float pitch) = 0;
```
"public.pitch". 1.0 is unchanged.
| Parameter | Type |
| --------- | ------- |
| `pitch` | `float` |
`GetPitch` [#getpitch]
```cpp
virtual float GetPitch() const = 0;
```
`SetChannel` [#setchannel]
```cpp
virtual void SetChannel(const char* channel) = 0;
```
Volume channel this sound belongs to, or nullptr/"" for none.
See ISource2ModSounds::SetPlayerVolume(). A recipient whose volume for the channel is 0 is not sent the sound at all.
| Parameter | Type |
| --------- | ------------- |
| `channel` | `const char*` |
`GetChannel` [#getchannel]
```cpp
virtual const char* GetChannel() const = 0;
```
`HasParam` [#hasparam]
```cpp
virtual bool HasParam(const char* name) const = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`GetParamType` [#getparamtype]
```cpp
virtual SoundParamType GetParamType(const char* name) const = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`RemoveParam` [#removeparam]
```cpp
virtual void RemoveParam(const char* name) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
`ClearParams` [#clearparams]
```cpp
virtual void ClearParams() = 0;
```
`SetBool` [#setbool]
```cpp
virtual void SetBool(const char* name, bool value) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
| `value` | `bool` |
`SetInt32` [#setint32]
```cpp
virtual void SetInt32(const char* name, int32_t value) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
| `value` | `int32_t` |
`SetUInt32` [#setuint32]
```cpp
virtual void SetUInt32(const char* name, uint32_t value) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
| `value` | `uint32_t` |
`SetUInt64` [#setuint64]
```cpp
virtual void SetUInt64(const char* name, uint64_t value) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
| `value` | `uint64_t` |
`SetFloat` [#setfloat]
```cpp
virtual void SetFloat(const char* name, float value) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `name` | `const char*` |
| `value` | `float` |
`SetFloat3` [#setfloat3]
```cpp
virtual void SetFloat3(const char* name, const Vector& value) = 0;
```
| Parameter | Type |
| --------- | --------------- |
| `name` | `const char*` |
| `value` | `const Vector&` |
`GetBool` [#getbool]
```cpp
virtual bool GetBool(const char* name, bool def = false) const = 0;
```
Typed getters. @p def comes back when the parameter is missing or of another type.
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `name` | `const char*` | |
| `def` | `bool` | `false` |
`GetInt32` [#getint32]
```cpp
virtual int32_t GetInt32(const char* name, int32_t def = 0) const = 0;
```
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `name` | `const char*` | |
| `def` | `int32_t` | `0` |
`GetUInt32` [#getuint32]
```cpp
virtual uint32_t GetUInt32(const char* name, uint32_t def = 0) const = 0;
```
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `name` | `const char*` | |
| `def` | `uint32_t` | `0` |
`GetUInt64` [#getuint64]
```cpp
virtual uint64_t GetUInt64(const char* name, uint64_t def = 0) const = 0;
```
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `name` | `const char*` | |
| `def` | `uint64_t` | `0` |
`GetFloat` [#getfloat]
```cpp
virtual float GetFloat(const char* name, float def = 0.0f) const = 0;
```
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `name` | `const char*` | |
| `def` | `float` | `0.0f` |
`GetFloat3` [#getfloat3]
```cpp
virtual Vector GetFloat3(const char* name, const Vector& def = Vector(0, 0, 0)) const = 0;
```
| Parameter | Type | Default |
| --------- | --------------- | ----------------- |
| `name` | `const char*` | |
| `def` | `const Vector&` | `Vector(0, 0, 0)` |
`AddRecipient` [#addrecipient]
```cpp
virtual void AddRecipient(CPlayerSlot slot) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`RemoveRecipient` [#removerecipient]
```cpp
virtual void RemoveRecipient(CPlayerSlot slot) = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`HasRecipient` [#hasrecipient]
```cpp
virtual bool HasRecipient(CPlayerSlot slot) const = 0;
```
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`AddAllRecipients` [#addallrecipients]
```cpp
virtual void AddAllRecipients() = 0;
```
Every player connected at the moment of the call -- the ones with a net channel, so bots are left out (a sound cannot reach them).
`ClearRecipients` [#clearrecipients]
```cpp
virtual void ClearRecipients() = 0;
```
`SetRecipients` [#setrecipients]
```cpp
virtual void SetRecipients(SoundRecipients mask) = 0;
```
| Parameter | Type |
| --------- | --------------------------------------------------------------------- |
| `mask` | [`SoundRecipients`](/docs/core-api/ISource2ModSounds#soundrecipients) |
`GetRecipients` [#getrecipients]
```cpp
virtual SoundRecipients GetRecipients() const = 0;
```
Recipient set, one bit per player slot (bit N = slot N).
`Emit` [#emit]
```cpp
virtual SoundGuid Emit() = 0;
```
Sends the sound to its recipients.
Not available inside a HookSound() handler -- the sound there is already on its way; change it and return Action::Override.
**Returns** [`SoundGuid`](/docs/core-api/ISource2ModSounds#soundguid) — Guid of the new sound, 0 when there was nobody to send it to.
`New` [#new]
```cpp
static ISource2ModSound* New(const char* name = nullptr);
```
Creates a sound: listener-sourced, volume and pitch 1, no recipients.
Equivalent to `g_pSource2ModSounds->CreateSound(g_PluginID, name)`. Release with `delete` or `g_pSource2ModSounds->DestroySound()`.
ISource2ModSound\* music = ISource2ModSound::New("jb.duel"); music->SetChannel("music"); music->AddAllRecipients(); SoundGuid guid = music->Emit(); delete music;
| Parameter | Type | Default | Description |
| --------- | ------------- | --------- | -------------------------------------------------- |
| `name` | `const char*` | `nullptr` | Sound event name, may be set later with SetName(). |
Enums [#enums]
`SoundParamType` [#soundparamtype]
```cpp
enum class SoundParamType : uint8_t
```
Wire type of a packed sound parameter.
The engine knows more than these; they are the ones whose layout is known, and every public parameter of the stock sound events is one of them. Parameters of any other type on a hooked sound are carried through untouched.
| Name | Value |
| --------- | ----- |
| `Invalid` | `0` |
| `Bool` | `1` |
| `Int32` | `2` |
| `UInt32` | `3` |
| `UInt64` | `4` |
| `Float` | `8` |
| `Float3` | `10` |
Functions [#functions]
`SoundRecipientOf` [#soundrecipientof]
```cpp
inline SoundRecipients SoundRecipientOf(CPlayerSlot slot);
```
Recipient mask of one slot.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
Macros [#macros]
`SOUND_EMIT_TO_PLAYER` [#sound_emit_to_player]
```cpp
#define SOUND_EMIT_TO_PLAYER(slot, ...) g_pSource2ModSounds->EmitSoundToPlayer(slot, __VA_ARGS__)
```
Shorthand accessors via g\_pSource2ModSounds.
Other macros [#other-macros]
| Macro | Expands to |
| ------------------------------------- | --------------------------------------------------------------- |
| `SOUND_EMIT_TO_PLAYERS(mask, ...)` | `g_pSource2ModSounds->EmitSoundToPlayers(mask, __VA_ARGS__)` |
| `SOUND_EMIT_TO_ALL(...)` | `g_pSource2ModSounds->EmitSoundToAll(__VA_ARGS__)` |
| `SOUND_EMIT_FROM_ENTITY(entity, ...)` | `g_pSource2ModSounds->EmitSoundFromEntity(entity, __VA_ARGS__)` |
| `SOUND_STOP(guid)` | `g_pSource2ModSounds->StopSound(guid)` |
| `HOOK_SOUND(handler)` | `g_pSource2ModSounds->HookSound(handler)` |
| `UNHOOK_SOUND(handler)` | `g_pSource2ModSounds->UnhookSound(handler)` |
# ISource2ModTargeting (/docs/core-api/ISource2ModTargeting)
Patterns:
* @all, @ct, @t, @spec, @alive, @dead, @bots, @humans, @me, @!me, @aim (the player under the caller's crosshair), and filters plugins add;
* \#\, a SteamID in any form (#STEAM\_1:0:1, \[U:1:2], 7656119...), #\;
* otherwise part of a name (case-insensitive); a name that matches exactly wins over partial matches.
The caller's immunity is checked through ISource2ModPermissions (CanTarget) unless SOURCE2MOD\_TARGET\_NO\_IMMUNITY; the console (slot -1) targets anyone.
```cpp
std::vector targets;
Source2ModTargetName name;
int n = g_pSource2ModTargeting->ProcessTargetString(arg, caller, SOURCE2MOD_TARGET_ALIVE, targets, &name);
if (n <= 0)
return ctx.Reply(g_pSource2ModTargeting->GetTargetErrorText(n));
```
**Interface id** `ISource2ModTargeting001` (`SOURCE2MOD_TARGETING_INTERFACE`) · **Global** `g_pSource2ModTargeting` · **Header** [`source2mod/ISource2ModTargeting.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModTargeting.h)
Types [#types]
`Source2ModTargetFilterId` [#source2modtargetfilterid]
```cpp
using Source2ModTargetFilterId = Source2ModHookId;
```
A target filter (AddTargetFilter).
`Source2ModTargetFilter` [#source2modtargetfilter]
```cpp
using Source2ModTargetFilter = Source2ModCallback& targets)>;
```
A filter a plugin adds ("@admins"): fills @p targets with whom it means.
Methods [#methods]
`ProcessTargetString` [#processtargetstring]
```cpp
virtual int ProcessTargetString(const char* pattern, CPlayerSlot caller, int flags, std::vector& targets, Source2ModTargetName* name = nullptr) = 0;
```
Finds the players a pattern means.
| Parameter | Type | Default | Description |
| --------- | ----------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------- |
| `pattern` | `const char*` | | |
| `caller` | `CPlayerSlot` | | Who is targeting (immunity, @me, @aim); -1 for the console. |
| `flags` | `int` | | SOURCE2MOD\_TARGET\_\* filters. |
| `targets` | `std::vector&` | | Filled with the players, in slot order. |
| `name` | [`Source2ModTargetName*`](/docs/core-api/ISource2ModTargeting#source2modtargetname) | `nullptr` | How to name them (optional). |
**Returns** `int` — The number of players, or a SOURCE2MOD\_TARGET\_\* error (\<= 0).
`FindTarget` [#findtarget]
```cpp
virtual int FindTarget(const char* pattern, CPlayerSlot caller, int flags, CPlayerSlot& target) = 0;
```
One player, or an error: ProcessTargetString with NO\_MULTI.
| Parameter | Type |
| --------- | -------------- |
| `pattern` | `const char*` |
| `caller` | `CPlayerSlot` |
| `flags` | `int` |
| `target` | `CPlayerSlot&` |
`GetTargetErrorPhrase` [#gettargeterrorphrase]
```cpp
virtual const char* GetTargetErrorPhrase(int error) = 0;
```
The phrase (in common.phrases) for an error: "No matching client", ...
| Parameter | Type |
| --------- | ----- |
| `error` | `int` |
`GetTargetErrorText` [#gettargeterrortext]
```cpp
virtual const char* GetTargetErrorText(int error) = 0;
```
The same in English, for a reply that is not translated.
| Parameter | Type |
| --------- | ----- |
| `error` | `int` |
`AddTargetFilter` [#addtargetfilter]
```cpp
virtual Source2ModTargetFilterId AddTargetFilter(const char* name, Source2ModTargetFilter filter, const char* phrase, bool phraseIsTranslation) = 0;
```
Adds "@\". @p phrase names the targets ("all admins").
| Parameter | Type |
| --------------------- | -------------------------------------------------------------------------------------- |
| `name` | `const char*` |
| `filter` | [`Source2ModTargetFilter`](/docs/core-api/ISource2ModTargeting#source2modtargetfilter) |
| `phrase` | `const char*` |
| `phraseIsTranslation` | `bool` |
**Returns** [`Source2ModTargetFilterId`](/docs/core-api/ISource2ModTargeting#source2modtargetfilterid) — An id for RemoveTargetFilter, 0 when the name is taken.
`RemoveTargetFilter` [#removetargetfilter]
```cpp
virtual bool RemoveTargetFilter(Source2ModTargetFilterId id) = 0;
```
| Parameter | Type |
| --------- | ------------------------------------------------------------------------------------------ |
| `id` | [`Source2ModTargetFilterId`](/docs/core-api/ISource2ModTargeting#source2modtargetfilterid) |
`Source2ModTargetName` [#source2modtargetname]
How to name what was targeted ("all terrorists", or the one player's name).
Fields [#fields]
| Field | Type | Description |
| ---------- | ------------- | ------------------------------------------------------------------------------ |
| `text` | `std::string` | |
| `isPhrase` | `bool` | text is a phrase name in common.phrases ("all terrorists") rather than a name. |
# ISource2ModTrace (/docs/core-api/ISource2ModTrace)
This module provides functionality for:
* Raycasting (line traces)
* Hull traces (box collision)
* Physics queries
* Visibility and hit detection
Commonly used for shooting, line-of-sight checks and movement.
Interface for performing trace operations.
**Interface id** `ISource2ModTrace001` (`SOURCE2MOD_TRACE_INTERFACE`) · **Global** `g_pSource2ModTrace` · **Header** [`source2mod/ISource2ModTrace.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModTrace.h)
Methods [#methods]
`TraceShape` [#traceshape]
```cpp
virtual TraceResult TraceShape(const Vector& vecStart, const QAngle& angAngles, CBaseEntity* pIgnoreEntity, TraceOptions* pTraceOptions) = 0;
```
Performs directional trace using angles.
| Parameter | Type |
| --------------- | --------------------------------------------------------------- |
| `vecStart` | `const Vector&` |
| `angAngles` | `const QAngle&` |
| `pIgnoreEntity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `pTraceOptions` | [`TraceOptions*`](/docs/core-api/ISource2ModTrace#traceoptions) |
`TraceEndShape` [#traceendshape]
```cpp
virtual TraceResult TraceEndShape(const Vector& vecStart, const Vector& vecEnd, CBaseEntity* pIgnoreEntity, TraceOptions* pTraceOptions) = 0;
```
Performs trace between two points.
| Parameter | Type |
| --------------- | --------------------------------------------------------------- |
| `vecStart` | `const Vector&` |
| `vecEnd` | `const Vector&` |
| `pIgnoreEntity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `pTraceOptions` | [`TraceOptions*`](/docs/core-api/ISource2ModTrace#traceoptions) |
`TraceHullShape` [#tracehullshape]
```cpp
virtual TraceResult TraceHullShape(const Vector& vecStart, const Vector& vecEnd, const Vector& vecMins, const Vector& vecMaxs, CBaseEntity* pIgnoreEntity, TraceOptions* pTraceOptions) = 0;
```
Performs hull (box) trace.
| Parameter | Type |
| --------------- | --------------------------------------------------------------- |
| `vecStart` | `const Vector&` |
| `vecEnd` | `const Vector&` |
| `vecMins` | `const Vector&` |
| `vecMaxs` | `const Vector&` |
| `pIgnoreEntity` | [`CBaseEntity*`](/viewers/schema#CBaseEntity) |
| `pTraceOptions` | [`TraceOptions*`](/docs/core-api/ISource2ModTrace#traceoptions) |
`TraceShapeEx` [#traceshapeex]
```cpp
virtual TraceResult TraceShapeEx(const Vector& vecStart, const Vector& vecEnd, CTraceFilter* pTraceFilter, Ray_t* pRay) = 0;
```
Advanced trace with custom filter.
| Parameter | Type |
| -------------- | --------------- |
| `vecStart` | `const Vector&` |
| `vecEnd` | `const Vector&` |
| `pTraceFilter` | `CTraceFilter*` |
| `pRay` | `Ray_t*` |
`PointContents` [#pointcontents]
```cpp
virtual uint64 PointContents(const Vector* const vTestPos, uint64 nContentsMask) = 0;
```
Returns the contents bitmask at a world position.
| Parameter | Type |
| --------------- | --------------------- |
| `vTestPos` | `const Vector* const` |
| `nContentsMask` | `uint64` |
`CheckAreaOverlappingEntity` [#checkareaoverlappingentity]
```cpp
virtual bool CheckAreaOverlappingEntity(const CCSNavArea* const rArea, const CBaseEntity* const rEntity, bool bExtrudeHullHeight) = 0;
```
Checks whether a nav area overlaps with an entity.
| Parameter | Type |
| -------------------- | --------------------------------------------------------- |
| `rArea` | [`const CCSNavArea* const`](/docs/schema/Navarea) |
| `rEntity` | [`const CBaseEntity* const`](/viewers/schema#CBaseEntity) |
| `bExtrudeHullHeight` | `bool` |
`GetEntityWorldSpaceAABB` [#getentityworldspaceaabb]
```cpp
virtual void GetEntityWorldSpaceAABB(const CBaseEntity* const rEntity, Vector* pMinsOut, Vector* pMaxsOut) = 0;
```
Retrieves the world-space AABB of an entity.
| Parameter | Type |
| ---------- | --------------------------------------------------------- |
| `rEntity` | [`const CBaseEntity* const`](/viewers/schema#CBaseEntity) |
| `pMinsOut` | `Vector*` |
| `pMaxsOut` | `Vector*` |
`CTraceFilterEx` [#ctracefilterex]
The filter TraceShapeEx expects when you have an entity to ignore.
Source2Mod uses this one internally; it is here because a plugin calling TraceShapeEx needs a CTraceFilter of its own and this covers the ordinary case -- ignore an entity, its owner and everything sharing its hierarchy.
**Inherits** `CTraceFilter`
Constructors [#constructors]
```cpp
explicit CTraceFilterEx(CBaseEntity* pEntityToIgnore);
CTraceFilterEx();
```
`TraceOptions` [#traceoptions]
Options controlling trace behavior.
Fields [#fields]
| Field | Type |
| ------------------ | ---------- |
| `InteractsAs` | `uint64_t` |
| `InteractsWith` | `uint64_t` |
| `InteractsExclude` | `uint64_t` |
`TraceResult` [#traceresult]
Result of a trace query.
Contains information about collision, hit entity, hit position, surface data, and more.
Constructors [#constructors-1]
```cpp
TraceResult();
explicit TraceResult(const CGameTrace* pTrace);
TraceResult(const TraceResult&) = delete;
TraceResult(TraceResult&&) = default;
```
`StartPos` [#startpos]
```cpp
const Vector& StartPos() const;
```
`EndPos` [#endpos]
```cpp
const Vector& EndPos() const;
```
`HitPoint` [#hitpoint]
```cpp
const Vector& HitPoint() const;
```
`Normal` [#normal]
```cpp
const Vector& Normal() const;
```
`Fraction` [#fraction]
```cpp
float Fraction() const;
```
`HitOffset` [#hitoffset]
```cpp
float HitOffset() const;
```
`DidHit` [#didhit]
```cpp
bool DidHit() const;
```
`IsAllSolid` [#isallsolid]
```cpp
bool IsAllSolid() const;
```
`HasExactHit` [#hasexacthit]
```cpp
bool HasExactHit() const;
```
`HitEntity` [#hitentity]
```cpp
CEntityInstance* HitEntity() const;
```
`TriangleIndex` [#triangleindex]
```cpp
int TriangleIndex() const;
```
`HitboxBoneIndex` [#hitboxboneindex]
```cpp
int HitboxBoneIndex() const;
```
`Hitbox` [#hitbox]
```cpp
CHitBox* Hitbox() const;
```
`Contents` [#contents]
```cpp
int Contents() const;
```
`RayType` [#raytype]
```cpp
RayType_t RayType() const;
```
`Body` [#body]
```cpp
IPhysicsBody* Body() const;
```
`Shape` [#shape]
```cpp
IPhysicsShape* Shape() const;
```
`BodyTransform` [#bodytransform]
```cpp
const CTransform& BodyTransform() const;
```
`ShapeAttributes` [#shapeattributes]
```cpp
const RnCollisionAttr_t& ShapeAttributes() const;
```
`Surface` [#surface]
```cpp
CPhysSurfaceProperties* Surface() const;
```
Macros [#macros]
| Macro | Expands to |
| ------------------------------------------------------- | ------------------------------------------------------------------------ |
| `TRACE_SHAPE(start, angles, ent, opts)` | `g_pSource2ModTrace->TraceShape(start, angles, ent, opts)` |
| `TRACE_END_SHAPE(start, end, ent, opts)` | `g_pSource2ModTrace->TraceEndShape(start, end, ent, opts)` |
| `TRACE_HULL_SHAPE(start, end, mins, maxs, ent, opts)` | `g_pSource2ModTrace->TraceHullShape(start, end, mins, maxs, ent, opts)` |
| `TRACE_SHAPE_EX(start, end, filter, ray)` | `g_pSource2ModTrace->TraceShapeEx(start, end, filter, ray)` |
| `POINT_CONTENTS(pos, mask)` | `g_pSource2ModTrace->PointContents(pos, mask)` |
| `ENTITY_WORLD_AABB(ent, mins, maxs)` | `g_pSource2ModTrace->GetEntityWorldSpaceAABB(ent, mins, maxs)` |
| `CHECK_AREA_OVERLAPPING_ENTITY(area, ent, extrudeHull)` | `g_pSource2ModTrace->CheckAreaOverlappingEntity(area, ent, extrudeHull)` |
# ISource2ModTranslations (/docs/core-api/ISource2ModTranslations)
A phrase is a name ("Welcome") with a text per language and, optionally, a format that says what its \{1}, \{2}, ... placeholders are ("\{1:s},\{2:d}"). Phrases come from wherever the plugin has them: a phrase file (translations/\.txt, SourceMod's layout), JSON, or one call per text at runtime -- a plugin that downloads its texts (HTTP, SQL) pushes them when they arrive and can change or remove them later. Files are only one source.
Lookups never fail: a phrase without the asked language falls back to the server's language, then English, then the phrase name itself.
```cpp
auto* tr = g_pSource2ModTranslations;
tr->AddPhrase(g_PluginID, "Welcome", "en", "Welcome, {1}!", "{1:s}");
tr->AddPhrase(g_PluginID, "Welcome", "cze", "Vítej, {1}!");
const char* args[] = { player->GetPlayerName() };
char text[256];
tr->FormatPhrase(g_PluginID, "Welcome", player->GetPlayerSlot(), args, 1, text, sizeof(text));
```
**Interface id** `ISource2ModTranslations002` (`SOURCE2MOD_TRANSLATIONS_INTERFACE`) · **Global** `g_pSource2ModTranslations` · **Header** [`source2mod/ISource2ModTranslations.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModTranslations.h)
Methods [#methods]
`GetLanguageCount` [#getlanguagecount]
```cpp
virtual int GetLanguageCount() = 0;
```
Languages known: configs/languages.cfg plus those added at runtime.
`GetLanguageInfo` [#getlanguageinfo]
```cpp
virtual bool GetLanguageInfo(int language, const char** code, const char** name) = 0;
```
A language's code ("en", "cze") and name ("English").
| Parameter | Type |
| ---------- | -------------- |
| `language` | `int` |
| `code` | `const char**` |
| `name` | `const char**` |
**Returns** `bool` — false for an index out of range.
`FindLanguage` [#findlanguage]
```cpp
virtual int FindLanguage(const char* codeOrName) = 0;
```
A language by its code or its name (either, case-insensitive); -1 when unknown.
| Parameter | Type |
| ------------ | ------------- |
| `codeOrName` | `const char*` |
`AddLanguage` [#addlanguage]
```cpp
virtual int AddLanguage(const char* code, const char* name) = 0;
```
Adds a language at runtime (or finds it when the code is known).
| Parameter | Type |
| --------- | ------------- |
| `code` | `const char*` |
| `name` | `const char*` |
**Returns** `int` — Its index.
`GetServerLanguage` [#getserverlanguage]
```cpp
virtual int GetServerLanguage() = 0;
```
The server's language (ServerLang in core.json).
`GetPlayerLanguage` [#getplayerlanguage]
```cpp
virtual int GetPlayerLanguage(CPlayerSlot slot) = 0;
```
A player's language: one set with SetPlayerLanguage, else the client's own (its cl\_language), else the server's.
| Parameter | Type |
| --------- | ------------- |
| `slot` | `CPlayerSlot` |
`SetPlayerLanguage` [#setplayerlanguage]
```cpp
virtual void SetPlayerLanguage(CPlayerSlot slot, int language) = 0;
```
Overrides a player's language until they disconnect; -1 goes back to theirs.
| Parameter | Type |
| ---------- | ------------- |
| `slot` | `CPlayerSlot` |
| `language` | `int` |
`LoadPhraseFile` [#loadphrasefile]
```cpp
virtual bool LoadPhraseFile(PluginId owner, const char* file, char* error, size_t maxlen) = 0;
```
Loads translations/\.txt and translations/\/\.txt for every language (SourceMod's phrase files) into @p owner's phrases.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `file` | `const char*` |
| `error` | `char*` |
| `maxlen` | `size_t` |
**Returns** `bool` — false (and why in @p error) when the base file is missing or broken.
`LoadPhrasesJson` [#loadphrasesjson]
```cpp
virtual bool LoadPhrasesJson(PluginId owner, const char* json, char* error, size_t maxlen) = 0;
```
Loads phrases from JSON: \{"\": \{"#format": "\{1:s}", "\": "\", ...}, ...}. Unknown language codes are added.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `json` | `const char*` |
| `error` | `char*` |
| `maxlen` | `size_t` |
**Returns** `bool` — false (and why in @p error) for text that is not such an object.
`AddPhrase` [#addphrase]
```cpp
virtual void AddPhrase(PluginId owner, const char* phrase, const char* language, const char* text, const char* format = nullptr) = 0;
```
Adds or replaces one text of a phrase.
| Parameter | Type | Default | Description |
| ---------- | ---------------------------------------------------- | --------- | -------------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | | |
| `phrase` | `const char*` | | |
| `language` | `const char*` | | A language code; one not known yet is added. |
| `text` | `const char*` | | |
| `format` | `const char*` | `nullptr` | The phrase's placeholder format ("\{1:s},\{2:d}"); nullptr keeps the one it has. |
`RemovePhrase` [#removephrase]
```cpp
virtual bool RemovePhrase(PluginId owner, const char* phrase, const char* language = nullptr) = 0;
```
Removes a phrase's text in one language, or the whole phrase for a null / empty @p language.
| Parameter | Type | Default |
| ---------- | ---------------------------------------------------- | --------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | |
| `phrase` | `const char*` | |
| `language` | `const char*` | `nullptr` |
**Returns** `bool` — false when there was nothing to remove.
`ClearPhrases` [#clearphrases]
```cpp
virtual void ClearPhrases(PluginId owner) = 0;
```
Drops every phrase of @p owner (a plugin's go when it unloads anyway).
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
`PhraseExists` [#phraseexists]
```cpp
virtual bool PhraseExists(PluginId owner, const char* phrase) = 0;
```
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `phrase` | `const char*` |
`IsTranslatedFor` [#istranslatedfor]
```cpp
virtual bool IsTranslatedFor(PluginId owner, const char* phrase, int language) = 0;
```
| Parameter | Type |
| ---------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `phrase` | `const char*` |
| `language` | `int` |
`Translate` [#translate]
```cpp
virtual size_t Translate(PluginId owner, const char* phrase, CPlayerSlot target, char* buffer, size_t maxlen) = 0;
```
The raw text of a phrase for a target (\{n} placeholders left in).
| Parameter | Type | Description |
| --------- | ---------------------------------------------------- | -------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | |
| `phrase` | `const char*` | |
| `target` | `CPlayerSlot` | A player, or SOURCE2MOD\_LANG\_SERVER. |
| `buffer` | `char*` | |
| `maxlen` | `size_t` | |
**Returns** `size_t` — Bytes written; the fallbacks (server language, English, the phrase name) mean there always is a text.
`FormatPhrase` [#formatphrase]
```cpp
virtual size_t FormatPhrase(PluginId owner, const char* phrase, CPlayerSlot target, const char* const* args, int numArgs, char* buffer, size_t maxlen) = 0;
```
The text for a target with its \{1}..\{n} replaced by @p args. The values are text already; the phrase's format is not applied.
| Parameter | Type |
| --------- | ---------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) |
| `phrase` | `const char*` |
| `target` | `CPlayerSlot` |
| `args` | `const char* const*` |
| `numArgs` | `int` |
| `buffer` | `char*` |
| `maxlen` | `size_t` |
**Returns** `size_t` — Bytes written.
`TranslateLanguage` [#translatelanguage]
```cpp
virtual size_t TranslateLanguage(PluginId owner, const char* phrase, int language, char* buffer, size_t maxlen) = 0;
```
The raw text of a phrase in a language (\{n} placeholders left in), for a plugin that keeps a language itself rather than asking for a player's.
| Parameter | Type | Description |
| ---------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | |
| `phrase` | `const char*` | |
| `language` | `int` | A language index (FindLanguage); -1 for the server's. Falls back like Translate: the server's language, English, the phrase name. |
| `buffer` | `char*` | |
| `maxlen` | `size_t` | |
**Returns** `size_t` — Bytes written.
Macros [#macros]
`SOURCE2MOD_LANG_SERVER` [#source2mod_lang_server]
```cpp
#define SOURCE2MOD_LANG_SERVER CPlayerSlot(-1)
```
The server, as a translation target (FormatPhrase / Translate).
# ISource2ModTransmit (/docs/core-api/ISource2ModTransmit)
Every tick the server works out, per player, the set of entities to send (CheckTransmit). This interface sits after that decision and takes entities back out of it:
* an entity is hooked once, and from then on has a visibility per viewer -- hidden from some players, shown to others, with up to eight independent "channels" so two features (a !hide command, a spectator mode, a team-only marker) can each hide the same entity without stepping on each other's toes: the entity shows only when no channel hides it;
* a player is hidden through their controller: hiding a controller from a viewer hides the pawn that controller is driving, the observer pawn and a dead player pawn, while the controller itself stays -- the scoreboard keeps working;
* an entity can be tied to an owner entity, and then disappears wherever its owner is hidden (a player's weapons, a beam attached to them);
* BlockAll takes an entity away from everybody, the one-line "server-side only" switch;
* and for anything that does not fit the model, HookCheckTransmit() hands over each viewer's transmit set, wrapped, so a plugin never has to lay out CCheckTransmitInfo itself.
Hiding is done the way the client expects: the entity leaves the transmit set and is put on the "non-transmitted" list at the same time, so the client keeps a record of it while it is hidden and takes it back without the "missing client entity" crash when it comes back. Both of those bits are what ISource2ModTransmitInfo::BlockTransmit() does.
Everything here belongs on the main thread.
**Interface id** `ISource2ModTransmit002` (`SOURCE2MOD_TRANSMIT_INTERFACE`) · **Global** `g_pSource2ModTransmit` · **Header** [`source2mod/ISource2ModTransmit.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModTransmit.h)
Types [#types]
`Source2ModTransmitHookId` [#source2modtransmithookid]
```cpp
using Source2ModTransmitHookId = Source2ModHookId;
```
A CheckTransmit hook (HookCheckTransmit).
`CheckTransmitHook` [#checktransmithook]
```cpp
using CheckTransmitHook = Source2ModCallback;
```
Hook called once per CheckTransmit, after Source2Mod has applied the hooked entities' visibility.
Runs every tick for every viewer, so do the expensive part (who owns what, who is on which team) once at the top and only the bit work per viewer.
Methods [#methods]
`HookEntity` [#hookentity]
```cpp
virtual bool HookEntity(PluginId owner, CEntityInstance* entity, bool defaultVisible = true) = 0;
```
Puts an entity under this interface's control.
Until an entity is hooked, the setters below refuse it and it is sent as the engine decides. The hook goes away with the entity, with the plugin, or with UnhookEntity().
Hook a player's controller, not their pawn: a controller's visibility is applied to whatever pawn it is driving. Pawns are refused.
| Parameter | Type | Default | Description |
| ---------------- | ---------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | | Plugin ID that owns the hook |
| `entity` | `CEntityInstance*` | | Entity to hook |
| `defaultVisible` | `bool` | `true` | Whether viewers see it until told otherwise. false is for "personal" entities: hidden from everybody, then SetVisible(entity, ownerSlot, true). |
**Returns** `bool` — false when the entity is a pawn, already hooked, or about to be deleted.
`UnhookEntity` [#unhookentity]
```cpp
virtual void UnhookEntity(CEntityInstance* entity) = 0;
```
Forgets an entity: sent as the engine decides from now on.
| Parameter | Type |
| --------- | ------------------ |
| `entity` | `CEntityInstance*` |
`IsEntityHooked` [#isentityhooked]
```cpp
virtual bool IsEntityHooked(CEntityInstance* entity) = 0;
```
| Parameter | Type |
| --------- | ------------------ |
| `entity` | `CEntityInstance*` |
`SetVisible` [#setvisible]
```cpp
virtual bool SetVisible(CEntityInstance* entity, CPlayerSlot viewer, bool visible, int channel = 0) = 0;
```
Shows or hides a hooked entity for one viewer.
| Parameter | Type | Default | Description |
| --------- | ------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `entity` | `CEntityInstance*` | | Hooked entity |
| `viewer` | `CPlayerSlot` | | The player it is shown to / hidden from |
| `visible` | `bool` | | false hides it on @p channel; true withdraws that channel's objection, and for an entity hidden by default also marks the viewer as allowed |
| `channel` | `int` | `0` | Channel 0..TRANSMIT\_MAX\_CHANNELS-1, or TRANSMIT\_CHANNEL\_ALL for every channel at once |
**Returns** `bool` — false when the entity is not hooked.
`SetVisibleToAll` [#setvisibletoall]
```cpp
virtual bool SetVisibleToAll(CEntityInstance* entity, bool visible, int channel = 0) = 0;
```
SetVisible() for every player slot at once.
| Parameter | Type | Default |
| --------- | ------------------ | ------- |
| `entity` | `CEntityInstance*` | |
| `visible` | `bool` | |
| `channel` | `int` | `0` |
`IsVisible` [#isvisible]
```cpp
virtual bool IsVisible(CEntityInstance* entity, CPlayerSlot viewer) = 0;
```
Whether a viewer would receive the entity, the channels, the default and BlockAll all taken into account. An unhooked entity is visible. The owner is not consulted -- ask about it directly.
| Parameter | Type |
| --------- | ------------------ |
| `entity` | `CEntityInstance*` |
| `viewer` | `CPlayerSlot` |
`IsHiddenOnChannel` [#ishiddenonchannel]
```cpp
virtual bool IsHiddenOnChannel(CEntityInstance* entity, CPlayerSlot viewer, int channel) = 0;
```
Whether one channel hides the entity from the viewer.
| Parameter | Type |
| --------- | ------------------ |
| `entity` | `CEntityInstance*` |
| `viewer` | `CPlayerSlot` |
| `channel` | `int` |
`SetBlockAll` [#setblockall]
```cpp
virtual bool SetBlockAll(CEntityInstance* entity, bool block) = 0;
```
Hides a hooked entity from everybody, whatever the channels say.
| Parameter | Type |
| --------- | ------------------ |
| `entity` | `CEntityInstance*` |
| `block` | `bool` |
`GetBlockAll` [#getblockall]
```cpp
virtual bool GetBlockAll(CEntityInstance* entity) = 0;
```
| Parameter | Type |
| --------- | ------------------ |
| `entity` | `CEntityInstance*` |
`SetOwner` [#setowner]
```cpp
virtual bool SetOwner(CEntityInstance* entity, CEntityInstance* owner) = 0;
```
Ties a hooked entity to an owner: wherever the owner is hidden, so is the entity. The owner has to be hooked for that to apply.
| Parameter | Type | Description |
| --------- | ------------------ | ------------------------------ |
| `entity` | `CEntityInstance*` | |
| `owner` | `CEntityInstance*` | Owner entity, nullptr to untie |
`GetOwner` [#getowner]
```cpp
virtual CEntityInstance* GetOwner(CEntityInstance* entity) = 0;
```
| Parameter | Type |
| --------- | ------------------ |
| `entity` | `CEntityInstance*` |
`ResetViewer` [#resetviewer]
```cpp
virtual void ResetViewer(CPlayerSlot viewer) = 0;
```
Puts every hooked entity back to its default for one viewer.
Done by Source2Mod when the player disconnects.
| Parameter | Type |
| --------- | ------------- |
| `viewer` | `CPlayerSlot` |
`HookCheckTransmit` [#hookchecktransmit]
```cpp
virtual Source2ModTransmitHookId HookCheckTransmit(CheckTransmitHook handler) = 0;
```
Hooks CheckTransmit itself.
Handlers run after Source2Mod's own pass, in the order they were added; a plugin may add 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` | [`CheckTransmitHook`](/docs/core-api/ISource2ModTransmit#checktransmithook) | A function, an object and a method, or a lambda |
**Returns** [`Source2ModTransmitHookId`](/docs/core-api/ISource2ModTransmit#source2modtransmithookid) — The id UnhookCheckTransmit(id) takes
`UnhookCheckTransmit` [#unhookchecktransmit]
```cpp
virtual bool UnhookCheckTransmit(const CheckTransmitHook& handler) = 0;
virtual bool UnhookCheckTransmit(Source2ModTransmitHookId 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 HookCheckTransmit() returned the id for.
| Parameter | Type |
| --------- | ----------------------------------------------------------------------------------------- |
| `handler` | [`const CheckTransmitHook&`](/docs/core-api/ISource2ModTransmit#checktransmithook) |
| `id` | [`Source2ModTransmitHookId`](/docs/core-api/ISource2ModTransmit#source2modtransmithookid) |
**Returns** `bool` — true when one was found
`ISource2ModTransmitInfo` [#isource2modtransmitinfo]
One viewer's slice of a CheckTransmit call: who it is for, and which entities are on their way to them.
Handed to a CheckTransmitHook, one per viewer, valid for the duration of the callback only.
`GetPlayerSlot` [#getplayerslot]
```cpp
virtual CPlayerSlot GetPlayerSlot() const = 0;
```
The player this transmit set belongs to.
`IsFullUpdate` [#isfullupdate]
```cpp
virtual bool IsFullUpdate() const = 0;
```
True when the player is being sent everything from scratch (first snapshot, reconnect), false for an ordinary delta.
`IsTransmitting` [#istransmitting]
```cpp
virtual bool IsTransmitting(int entityIndex) const = 0;
```
Whether the entity is currently in the set.
| Parameter | Type |
| ------------- | ----- |
| `entityIndex` | `int` |
`BlockTransmit` [#blocktransmit]
```cpp
virtual void BlockTransmit(int entityIndex) = 0;
virtual void BlockTransmit(CEntityInstance* entity) = 0;
```
Takes an entity out of the set, and lets the client keep track of it while it is gone, so it can come back later.
This is the right way to hide an entity. Clearing the transmit bit alone makes the client forget the entity, and retransmitting it afterwards crashes the client on "missing client entity".
| Parameter | Type |
| ------------- | ------------------ |
| `entityIndex` | `int` |
| `entity` | `CEntityInstance*` |
`AllowTransmit` [#allowtransmit]
```cpp
virtual void AllowTransmit(int entityIndex) = 0;
virtual void AllowTransmit(CEntityInstance* entity) = 0;
```
Puts an entity (back) into the set.
| Parameter | Type |
| ------------- | ------------------ |
| `entityIndex` | `int` |
| `entity` | `CEntityInstance*` |
`GetTransmitEntity` [#gettransmitentity]
```cpp
virtual CBitVec* GetTransmitEntity() = 0;
```
The four bit sets of CCheckTransmitInfo, for whatever the methods above do not cover. Entity index = bit index.
TransmitEntity -- entities sent this tick. TransmitNonPlayers -- entities the client keeps a record of without receiving them (what BlockTransmit() sets). TransmitOutOfPVS -- entities that left the PVS but still get a delta. TransmitAlways -- HLTV/replay only, may be nullptr.
`GetTransmitNonPlayers` [#gettransmitnonplayers]
```cpp
virtual CBitVec* GetTransmitNonPlayers() = 0;
```
`GetTransmitOutOfPVS` [#gettransmitoutofpvs]
```cpp
virtual CBitVec* GetTransmitOutOfPVS() = 0;
```
`GetTransmitAlways` [#gettransmitalways]
```cpp
virtual CBitVec* GetTransmitAlways() = 0;
```
Macros [#macros]
`TRANSMIT_HOOK_ENTITY` [#transmit_hook_entity]
```cpp
#define TRANSMIT_HOOK_ENTITY(entity, ...) g_pSource2ModTransmit->HookEntity(g_PluginID, entity, ##__VA_ARGS__)
```
Shorthand accessors via g\_pSource2ModTransmit.
Other macros [#other-macros]
| Macro | Expands to |
| ------------------------------------ | -------------------------------------------------------- |
| `TRANSMIT_UNHOOK_ENTITY(entity)` | `g_pSource2ModTransmit->UnhookEntity(entity)` |
| `TRANSMIT_SET_VISIBLE(entity, ...)` | `g_pSource2ModTransmit->SetVisible(entity, __VA_ARGS__)` |
| `TRANSMIT_SET_BLOCK_ALL(entity, on)` | `g_pSource2ModTransmit->SetBlockAll(entity, on)` |
| `HOOK_CHECK_TRANSMIT(handler)` | `g_pSource2ModTransmit->HookCheckTransmit(handler)` |
| `UNHOOK_CHECK_TRANSMIT(handler)` | `g_pSource2ModTransmit->UnhookCheckTransmit(handler)` |
# ISource2ModTypes (/docs/core-api/ISource2ModTypes)
Defines:
* Action -- what a callback tells Source2Mod to do with the original (KHook::Action)
* HookChain -- whether an SDK helper's call into the game runs the hooks on it
**Header** [`source2mod/ISource2ModTypes.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModTypes.h)
Types [#types]
`PluginId` [#pluginid]
```cpp
using PluginId = int;
```
A plugin's id; the core hands it to Load().
`Source2ModHookId` [#source2modhookid]
```cpp
using Source2ModHookId = int;
```
What a Register\* / Hook\* call returns and the matching Unregister\* / Unhook\* takes: the one handler it registered. 0 is never handed out.
`Action` [#action]
```cpp
using Action = KHook::Action;
```
What a source2mod callback (command listener, game event hook, net message hook, entity output listener) did about the original.
KHook's own result type, so a KHook handler hands a listener's answer straight through and a listener returns what a hook would:
* Ignore -- did nothing; the original runs as it would have.
* Override -- changed something (the event, the return value) but the original still runs. Pre only.
* Supersede -- block the original entirely. Pre only.
Enums [#enums]
`HookChain` [#hookchain]
```cpp
enum class HookChain : uint8_t
```
Whether an SDK helper's call into the game goes through the hooks plugins placed on the function it calls.
Every schema helper that calls a hookable game function (TakeDamage, GiveNamedItem, TerminateRound, SetModel, ...) takes one as its last argument:
* Run -- through the Pre/Post chain, as if the game made the call. Right when the plugin stands in for the game -- a weapon dealing damage, a round reward handing out an item -- and other plugins' rules should apply.
* Bypass -- straight to the original, past every hook (inline detours and vtable hooks alike). Right when the server was told to do it -- an admin command, a forced round end -- or when calling the very function you are hooking, where Run would recurse.
The default of each helper is what it always did, so existing calls keep their behaviour; see the helper's declaration.
```cpp
pItemServices->GiveNamedItem("weapon_ak47"); // hooks run
pController->TakeDamage(pAttacker, 50, DMG_BULLET, HookChain::Run); // opt in
```
| Name |
| -------- |
| `Run` |
| `Bypass` |
Functions [#functions]
`defined` [#defined]
```cpp
template #if !defined(_MSC_VER) #endif void CallbackOrigin();
```
One per callable type, instantiated where the callable is: its address is a code address inside the plugin that made the callable, which is how the core tells whose a handler is. Hidden, so the dynamic linker never hands another module's copy out for it.
| Parameter | Type |
| --------- | ---------- |
| `—` | `_MSC_VER` |
`MethodOrigin` [#methodorigin]
```cpp
template const void* MethodOrigin(const void* object, M method);
```
A code address of a method: its entry, or for a virtual method the object's vtable (both live in the module that defined the class).
| Parameter | Type |
| --------- | ------------- |
| `object` | `const void*` |
| `method` | `M` |
`ResolveHookChain` [#resolvehookchain]
```cpp
template inline FN ResolveHookChain(FN fn, HookChain eChain);
```
The function to call for a resolved address: the address itself, or, for HookChain::Bypass, the original behind any detour on it.
Resolve at the call, not once: a hook installed later changes the answer.
| Parameter | Type |
| --------- | -------------------------------------------------------- |
| `fn` | `FN` |
| `eChain` | [`HookChain`](/docs/core-api/ISource2ModTypes#hookchain) |
`ResolveHookChainVirtual` [#resolvehookchainvirtual]
```cpp
inline void* ResolveHookChainVirtual(void* pThis, int nIndex, HookChain eChain);
```
The function to call for vtable slot nIndex of pThis: whatever the slot holds, or, for HookChain::Bypass, the original behind both a vtable hook on the slot and a detour on the function it points at.
| Parameter | Type |
| --------- | -------------------------------------------------------- |
| `pThis` | `void*` |
| `nIndex` | `int` |
| `eChain` | [`HookChain`](/docs/core-api/ISource2ModTypes#hookchain) |
Macros [#macros]
`SOURCE2MOD_STATIC` [#source2mod_static]
```cpp
#define SOURCE2MOD_STATIC(func) (func)
```
SourceHook's SH\_STATIC / SH\_MEMBER, for a source2mod handler argument.
```cpp
g_pSource2ModEvents->HookGameEvent("round_end", SOURCE2MOD_STATIC(OnRoundEnd), true);
g_pSource2ModEvents->HookGameEvent("round_end", SOURCE2MOD_MEMBER(this, &Plugin::OnRoundEnd), true);
// ... and later the same delegate takes it off again:
g_pSource2ModEvents->UnhookGameEvent("round_end", SOURCE2MOD_MEMBER(this, &Plugin::OnRoundEnd), true);
```
Plain `OnRoundEnd` / `{ this, &Plugin::OnRoundEnd }` do the same; these only spell it the SourceHook way. The owner is the plugin whose code the function or method is, as for every handler.
Other macros [#other-macros]
| Macro | Expands to |
| ------------------------------- | ------------------------------------------------------------------ |
| `SOURCE2MOD_MEMBER(inst, func)` | `{ inst, func }` |
| `KHOOK_NEW(member, ...)` | `member(new std::remove_pointer_t(__VA_ARGS__))` |
# ISource2ModUtils (/docs/core-api/ISource2ModUtils)
The same helpers SourcePawn scripts get (source2mod/utils.inc), so a C++ plugin and a script that both write to a log, format a time or match a pattern get the same result.
Regex is ECMAScript (std::regex), not PCRE: SOURCE2MOD\_REGEX\_CASELESS and \_DOTALL always apply, \_MULTILINE where the C++ library has it.
**Interface id** `ISource2ModUtils001` (`SOURCE2MOD_UTILS_INTERFACE`) · **Global** `g_pSource2ModUtils` · **Header** [`source2mod/ISource2ModUtils.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModUtils.h)
Methods [#methods]
`Explode` [#explode]
```cpp
virtual std::vector Explode(const char* text, const char* separator, int maxParts = 0) = 0;
```
Splits on @p separator (not on an empty one); @p maxParts > 0 caps the parts, the last keeps the rest.
| Parameter | Type | Default |
| ----------- | ------------- | ------- |
| `text` | `const char*` | |
| `separator` | `const char*` | |
| `maxParts` | `int` | `0` |
`Implode` [#implode]
```cpp
virtual std::string Implode(const std::vector& parts, const char* separator) = 0;
```
| Parameter | Type |
| ----------- | --------------------------------- |
| `parts` | `const std::vector&` |
| `separator` | `const char*` |
`Replace` [#replace]
```cpp
virtual int Replace(std::string& text, const char* search, const char* replace, bool caseSensitive = true) = 0;
```
Replaces every @p search. @return How many.
| Parameter | Type | Default |
| --------------- | -------------- | ------- |
| `text` | `std::string&` | |
| `search` | `const char*` | |
| `replace` | `const char*` | |
| `caseSensitive` | `bool` | `true` |
`Trim` [#trim]
```cpp
virtual std::string Trim(const char* text) = 0;
```
Without the whitespace at both ends.
| Parameter | Type |
| --------- | ------------- |
| `text` | `const char*` |
`Find` [#find]
```cpp
virtual int Find(const char* text, const char* search, bool caseSensitive = true) = 0;
```
Where @p search starts in @p text; -1 when it is not there.
| Parameter | Type | Default |
| --------------- | ------------- | ------- |
| `text` | `const char*` | |
| `search` | `const char*` | |
| `caseSensitive` | `bool` | `true` |
`StrEqual` [#strequal]
```cpp
virtual bool StrEqual(const char* a, const char* b, bool caseSensitive = true) = 0;
```
| Parameter | Type | Default |
| --------------- | ------------- | ------- |
| `a` | `const char*` | |
| `b` | `const char*` | |
| `caseSensitive` | `bool` | `true` |
`GetTime` [#gettime]
```cpp
virtual int64_t GetTime() = 0;
```
Seconds since 1970.
`FormatTime` [#formattime]
```cpp
virtual std::string FormatTime(const char* format, int64_t stamp = -1) = 0;
```
strftime in the server's local time; @p stamp -1 is now.
| Parameter | Type | Default |
| --------- | ------------- | ------- |
| `format` | `const char*` | |
| `stamp` | `int64_t` | `-1` |
`ParseTime` [#parsetime]
```cpp
virtual int64_t ParseTime(const char* text, const char* format) = 0;
```
The opposite, as local time. @return -1 when @p text does not fit @p format.
| Parameter | Type |
| --------- | ------------- |
| `text` | `const char*` |
| `format` | `const char*` |
`GetTickCount` [#gettickcount]
```cpp
virtual int64_t GetTickCount() = 0;
```
Milliseconds on a steady clock (only differences mean anything).
`CompileRegex` [#compileregex]
```cpp
virtual ISource2ModRegex* CompileRegex(const char* pattern, int flags = 0, std::string* error = nullptr) = 0;
```
nullptr (and why in @p error) for a bad pattern; Release() it.
| Parameter | Type | Default |
| --------- | -------------- | --------- |
| `pattern` | `const char*` | |
| `flags` | `int` | `0` |
| `error` | `std::string*` | `nullptr` |
`LogToFile` [#logtofile]
```cpp
virtual bool LogToFile(PluginId owner, const char* path, const char* message) = 0;
```
Appends "L \ - \