# Introduction (/docs) Source2Mod is a server modification for **Counter-Strike 2**. Plugins are written in **C++** or **SourcePawn** — both are first-class and use the same API: direct access to Source 2 internals (the schema system, entities, hooks, memory, network messages) together with the high-level pieces every server plugin needs: commands, ConVars, game events, menus, timers, translations, HTTP, SQL and more. Source2Mod grew out of [Source2Toolkit](https://github.com/Source2Toolkit/source2toolkit): the C++ core and SDK carried over, and SourcePawn was added on top of it. Who it is for [#who-it-is-for] * **C++ developers** who want native code with nothing in between, and who are comfortable reading engine code when they need to. * **SourcePawn developers** coming from SourceMod: the language they know, with an API that mirrors the C++ one name for name. * **Server owners** who run plugins built on Source2Mod: installing it is a Metamod plugin plus a folder of `.smx` files — native and script plugins alike. How it fits with Metamod:Source [#how-it-fits-with-metamodsource] Source2Mod is itself a **Metamod:Source plugin**. Metamod loads the core (`addons/source2mod/bin/...`), and the core then loads your plugins — `.smx` files from `addons/source2mod/plugins/`, native libraries and compiled SourcePawn scripts side by side — and hands each of them the Source2Mod API. That split is deliberate: * A plugin talks to one API — `ISource2ModAPI` and the `g_pSource2Mod*` interfaces in C++, the `Source2Mod*` methodmaps in SourcePawn — instead of fetching engine interfaces, scanning signatures and wiring listeners itself. * Everything a plugin registers — commands, event hooks, timers, net message hooks, menus — is owned by that plugin and removed when it unloads. * Hooks use **KHook**, the detour library inside Metamod:Source. Metamod, Source2Mod and every plugin share one KHook instance, so hooks placed by different plugins on the same function chain correctly. * C++ plugins can give scripts natives of their own, and scripts can share natives and forwards with each other. The core also resolves engine functions from a shared gamedata folder, so most plugins never ship a signature of their own. Features [#features] Engine access [#engine-access] * **Schema system** — generated classes for the game's schema (`CBaseEntity`, `CCSPlayerController`, …) with typed field access and network state updates, in C++ and as SourcePawn methodmaps. * **Entities** — find, create and spawn entities, entity listeners and entity I/O. * **Hooks** — game hooks, KHook virtual, vtable and function detours, and raw hooks from SourcePawn described at runtime. * **Addresses** — engine functions the core already resolved (spawn, take damage, set model, switch team, …), ready to call. * **GameConfig** — signatures, offsets and patches from JSON gamedata, per platform. * **Memory & modules** — pattern scans, symbol lookups and typed casts on any loaded library. * **Game systems** — register your own `IGameSystem` on the engine's factory list. Gameplay [#gameplay] * **Commands** — console and chat commands, plus listeners on commands you do not own. * **[Permissions](/docs/guides/permissions)** — admins, groups and dotted permissions from a file or granted at runtime, commands gated by them. * **[Clients & targeting](/docs/guides/clients-and-targeting)** — players by slot, SteamID or name, `@all` / `@ct` style targets with immunity. * **ConVars** — declare your own, read and write the game's, hear about changes. * **Game events** — pre/post hooks with field access, `dontBroadcast` and cancel, and firing your own. * **Network messages** — allocate, send and hook Source 2 net messages by id or by name. * **Menus** — center-screen menus with pages and submenus; keys 1–9 work without binds. * **[Translations](/docs/guides/translations)** — phrase files per plugin in every player's own language. * **Custom HUD** — Panorama layouts with click callbacks. * **Scheduler** — timers and next-frame tasks. * **Tracing** — ray and hull traces for line-of-sight and collision queries. * **Sounds** — sound events for exactly the players you pick, stop and change by guid, per-player volume channels and a hook on every sound the game sends. * **Transmit** — per-viewer entity visibility, owner-following and a CheckTransmit hook. * **[Scripts](/docs/guides/scripts)** — run cs\_script (compiled assets, raw source, hot-reloaded files), fire inputs into a script and receive its messages. Services [#services] * **[SQL](/docs/guides/sql)** — MySQL and SQLite, queries off the main thread, named connections from `databases.json`. * **HTTP** — asynchronous requests through Steam's HTTP client. * **JSON, [files and KeyValues](/docs/guides/files-and-keyvalues)** — parse and build JSON, read and write files, a streaming KeyValues parser. * **[Client preferences](/docs/guides/client-preferences), [GeoIP, map lists](/docs/guides/maps-geoip-features)** — per-player settings that persist, a player's country, the map cycle and next map. * **Paths** — the game directory, Source2Mod root, configs, gamedata, plugins and a shared data folder. * **[Crash handler](/docs/guides/crash-handler)** — minidumps processed on the server itself, plugin frames symbolized to `function @ file:line`, a suspected culprit and an optional Discord report. Where to go next [#where-to-go-next] Install Metamod:Source and Source2Mod on a CS2 server. A minimal native plugin, from class to built `.smx` . The same API as a script: a first plugin and how to compile it. Natives and forwards, entity handles, raw hooks, coming from SourceMod. The same concepts side by side, a plugin ported, and C++ for C# developers. Every Source2Mod interface, generated from the SDK headers. Generated schema classes, fields and helpers. Declare ConVars, register console and chat commands. Finding, creating and listening to entities. Network messages and the game's protobufs. Game events: hooking, reading, changing and firing. Custom HUD layouts and their callbacks. KHook detours on virtual and non-virtual functions. License [#license] Source2Mod and its SDK are licensed under the **GPLv3**, with an **MIT exception for derivative works**: plugins built against the SDK may use any license you choose, including closed-source and commercial terms. Source2Mod and the SDK themselves, and modified versions of them, stay under the GPLv3. SourcePawn and the parts of the include set that come from SourceMod are AlliedModders LLC's and keep their own license terms. See the [FAQ](/docs/resources/faq) for details. # Installation (/docs/installation) Source2Mod is a Metamod:Source plugin, so a server needs Metamod first and Source2Mod on top of it. Both are folders you copy into `game/csgo/`. Linux and Windows servers are both supported. Prerequisites [#prerequisites] * A Counter-Strike 2 dedicated server you can restart and reach the console of. * [Metamod:Source 2.x](https://www.metamodsource.net/downloads.php?branch=dev) — a current dev build, matching your OS. * The latest [Source2Mod release](https://github.com/Source2Mod/source2mod/releases) — `source2mod_-linux.tar.gz` or `source2mod_-windows.tar.gz`. CS2 updates regularly break older Metamod builds. When the server stops loading Metamod after a game update, update Metamod before looking anywhere else. Install Metamod:Source [#install-metamodsource] Download the Metamod:Source build for your operating system and extract it. Copy its `addons/` folder into your server's `game/csgo/` folder, so that you end up with `game/csgo/addons/metamod/`. Open `game/csgo/gameinfo.gi`, find the line ``` Game_LowViolence csgo_lv // Perfect World content override ``` and add this line **below** it: ``` Game csgo/addons/metamod ``` Start the server and run `meta list` in its console. If the command is known, Metamod is loaded. Game updates overwrite `gameinfo.gi`. If Metamod disappears after an update, the `Game csgo/addons/metamod` line is the first thing to check. Install Source2Mod [#install-source2mod] Download the release archive for your operating system and extract it. It contains a single `addons/` folder. Copy that `addons/` folder into `game/csgo/`, merging it with the one Metamod created. Restart the server. Source2Mod has to be loaded at server start: loading it later with `meta load` is refused (`Late load is not supported`). What the release contains [#what-the-release-contains] ``` game/csgo/addons/ ├── metamod/ │ └── source2mod.vdf # tells Metamod to load Source2Mod └── source2mod/ ├── bin/ │ └── linuxsteamrt64/ # win64/ in the Windows archive ├── configs/ │ └── core.example.json # template for core.json ├── gamedata/ │ └── gamedata.json # the core's signatures and offsets ├── plugins/ # your .smx plugins go here ├── shared/ # data more than one plugin reads └── dumps/ # crash dumps land here ``` | Folder | What it is for | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `bin/` | The s2m core binary. | | `configs/` | `core.json`, `permissions.json` (admins and groups, see [Permissions](/docs/guides/permissions)), and one `configs//` folder per plugin for its own config files (`PLUGIN_CONFIG_DIR()`). | | `gamedata/` | Every `.json` file here is loaded at start. Plugins that need signatures of their own ship an extra file next to `gamedata.json` — see [Gamedata](/docs/development/gamedata). | | `plugins/` | Every `.smx` file here is loaded at start. | | `shared/` | Data files read by more than one plugin (`SOURCE2MOD_SHARED_DIR()`). | | `dumps/` | Minidumps and crash reports written by the [crash handler](/docs/guides/crash-handler). | Missing folders are created on start, so deleting an empty one does no harm. Core configuration [#core-configuration] On its first start the core copies `configs/core.example.json` to `configs/core.json` and reads that from then on. Releases only ship the example, so updating Source2Mod never overwrites your settings. ```json title="configs/core.json" { "PublicChatTrigger": [ "!" ], "SilentChatTrigger": [ "/" ], "PluginHotReloadEnabled": true, "PluginAutoLoadEnabled": true, "UnlockConCommands": true, "UnlockConVars": true, "CrashHandlerEnabled": true, "CrashDiscordWebhook": "", "SlowCallbackWarnMs": 100, "SteamAuthMode": "auto", "AccessDeniedMessage": "You do not have access to this command." } ``` The file may contain `//` and `/* */` comments. | Key | Default | Meaning | | ------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PublicChatTrigger` | `["!"]` | Chat prefixes that run a command. `!mycommand` runs the plugin command `mycommand` and the message still shows in chat. | | `SilentChatTrigger` | `["/"]` | Same, but the chat message is suppressed. | | `PluginHotReloadEnabled` | `true` | Linux only: reload a plugin when its `.smx` is rewritten. See [Plugin lifecycle](/docs/development/plugin-lifecycle#hot-reload). | | `PluginAutoLoadEnabled` | `true` | Load every `.smx` in `plugins/` at start. When `false`, nothing loads until you run `s2m refresh` or `s2m load`. | | `UnlockConCommands` | `true` | Remove `FCVAR_HIDDEN` and `FCVAR_DEVELOPMENTONLY` from every console command, so hidden commands can be used. | | `UnlockConVars` | `true` | The same for ConVars. | | `CrashHandlerEnabled` | `true` | Install the crash handler. | | `CrashDiscordWebhook` | `""` | Discord webhook URL for crash reports. Empty disables Discord reporting; dumps are still written. | | `SlowCallbackWarnMs` | `100` | A plugin callback that takes longer than this is logged with its name and plugin — see [Compatibility](/docs/development/compatibility#when-the-server-stalls). `0` turns it off. | | `SteamAuthMode` | `"auto"` | Which SteamID a player's [permissions](/docs/guides/permissions#which-steamid-counts) are looked up under: `"auto"`, `"flexible"` or `"strict"`. | | `AccessDeniedMessage` | `"You do not have access to this command."` | What a player is told when refused a command, unless a plugin set its own answer. | The file is read once, when the core loads. Restart the server after changing it. Installing plugins [#installing-plugins] Copy a plugin's `.smx` file into `addons/source2mod/plugins/`. If it comes with configs, gamedata or shared data, those go into `configs/`, `gamedata/` and `shared/` as its instructions say. On a running server, `s2m refresh` loads every `.smx` in `plugins/` that is not loaded yet; a new gamedata file, however, is only read when the server restarts. One plugin most servers want: [**CustomhudManager\_s2t**](https://github.com/SlynxCZ/CustomhudManager_s2t/releases), the Panorama HUD — menus on the HUD, on-screen texts, toasts, timers. Plugins that draw on the HUD ask for its interface at load; without it they draw nothing. Its players need the addon described in [HUD menus, texts and prompts](/docs/panorama/hud#the-addon). Verifying the installation [#verifying-the-installation] Run `meta list` in the server console. **Source2Mod** should be in the list of Metamod plugins. Then ask Source2Mod itself. The console command is `s2m` (with the aliases `source2mod` and `sm`): ``` s2m version s2m list ``` | Command | What it does | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `s2m list` | Lists loaded plugins as `[id] name (version) by author`, with a second line per plugin: its plugin API version, the KHook commit it was built against and its tier — *stable* (no KHook hooks of its own: a game or KHook change needs only a core update) or *raw* (rebuild). See [Compatibility](/docs/development/compatibility). | | `s2m info ` | Name, version, author, description, file path, plugin API, KHook commit, how many hooks of its own it declared, and every Source2Mod interface (with revision) it asked for. | | `s2m load ` | Loads the first `.smx` in `plugins/` whose file name contains ``. | | `s2m unload ` | Unloads a plugin by the id `s2m list` shows. | | `s2m refresh` | Loads every `.smx` in `plugins/` that is not loaded yet. | | `s2m version` | Source2Mod version, plugin API version, build date and commit. | | `s2m credits` | Who made it. | `load`, `unload` and `refresh` work only from the server console (or RCON). Players can run `list`, `info`, `version` and `credits` from their own console. When something does not load [#when-something-does-not-load] * **Source2Mod is not in `meta list`** — Metamod is not loading it. Check that `addons/metamod/source2mod.vdf` exists and that the `bin/` folder for your OS is there. * **A plugin is missing from `s2m list`** — the core prints why at start: `Plugin refused to load: ` or `Failed to load : `, followed by a summary line with the number that failed. A `KHook mismatch` reason means the plugin was built against a different KHook than the core runs; see the [FAQ](/docs/resources/faq#a-plugin-refuses-to-load-with-khook-mismatch). * **The server crashes on start** — see the [crash handler guide](/docs/guides/crash-handler); the report on the next start names the module that crashed. # Command Listeners (/docs/convars-commands/command-listeners) A listener is a callback on a command that already exists: `jointeam`, `say`, `kill`, an engine command, another plugin's command. It runs *around* the command rather than replacing it, and a pre listener decides whether the command runs at all. ```cpp title="plugin.cpp" #include "source2mod/ISource2ModCommands.h" // No spectators: refuse "jointeam 1". REGISTER_CON_LISTENER("jointeam", [](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post) -> Action { if (args.ArgC() >= 2 && atoi(args.Arg(1)) == 1) { Reply(context, "Spectating is disabled on this server."); return Action::Supersede; } return Action::Ignore; }, false); ``` ```cpp title="plugin.sp" public void OnPluginStart() { Source2ModCommands.RegisterConListener("jointeam", Listen_JoinTeam, false); } // No spectators: refuse "jointeam 1". public Source2ModAction Listen_JoinTeam(Source2ModCommandContext context, Source2ModCommandArgs args, bool post) { char team[8]; args.Arg(1, team, sizeof(team)); if (args.ArgC() >= 2 && Source2ModUtils.StringToInt(team) == 1) { Reply(context, "Spectating is disabled on this server."); return Action_Supersede; } return Action_Ignore; } ``` (`Reply` is the helper from the [Commands](/docs/convars-commands/commands#the-caller) page.) The API [#the-api] ```cpp title="ISource2ModCommands.h" using CommandHandler = Source2ModCallback; virtual Source2ModHookId RegisterConListener(const char* pchName, CommandHandler handler, bool post) = 0; virtual bool UnregisterConListener(const char* pchName, const CommandHandler& handler, bool post) = 0; virtual bool UnregisterConListener(Source2ModHookId id) = 0; #define REGISTER_CON_LISTENER(pchName, handler, post) g_pSource2ModCommands->RegisterConListener(pchName, handler, post) ``` * `pchName` — the command to listen to, in **lower case** (the dispatcher lower-cases what was typed before it looks for listeners). * `post` — `false` to run before the command, `true` to run after it. Register twice if you need both. * The handler is a lambda, a function or an object and a method (`SOURCE2MOD_MEMBER(this, &MyPlugin::OnJoinTeam)`); the core reads which plugin it belongs to off the handler, so there is no plugin id to pass. It gets the same `Source2ModCommandContext` and `Source2ModCommandArgs` a command handler gets — see [arguments and the caller](/docs/convars-commands/commands#arguments). * In a script it is `Source2ModCommands.RegisterConListener(name, handler, post)` with a named `public` function that returns a `Source2ModAction`. A plugin can register several listeners on the same command. They are called in registration order. `RegisterConListener` returns a `Source2ModHookId`; `g_pSource2ModCommands->UnregisterConListener(id)` drops that one listener, and `UnregisterConListener(name, handler, post)` drops a function or object-and-method listener passed again (a lambda only goes by id). A script unregisters by id only. Unloading the plugin drops all of them. ```cpp // in Load() REGISTER_CON_LISTENER("say", OnPlayerChat, false); // later, to turn the filter off g_pSource2ModCommands->UnregisterConListener("say", OnPlayerChat, false); ``` ```cpp int g_ChatFilter; // in OnPluginStart() g_ChatFilter = Source2ModCommands.RegisterConListener("say", OnPlayerChat, false); // later, to turn the filter off Source2ModCommands.UnregisterConListener(g_ChatFilter); ``` Return values [#return-values] Listeners return an [`Action`](/docs/core-api/ISource2ModTypes) (in a script `Action_Ignore`, `Action_Override`, `Action_Supersede`): | Return | Pre (`post == false`) | Post (`post == true`) | | ------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `Action::Ignore` | Nothing changes; the next listener runs, then the command. | Nothing. | | `Action::Override` | The command still runs, but the remaining pre listeners are skipped — and for a console command the post listeners too. | Nothing. | | `Action::Supersede` | The command does **not** run. The remaining listeners — pre and post — are skipped. | The command already ran; only the remaining post listeners are skipped. | In practice: return `Ignore` when you only watch, `Supersede` when you block, and avoid `Override` — for a command there is no return value to override, and it silently cuts the other listeners off. A `Supersede` from your listener also stops every listener registered after it, including other plugins' and the handler of a command registered with `REGISTER_CON_COMMAND` on the same name. Block only what you mean to block. Pre and post together [#pre-and-post-together] ```cpp title="plugin.cpp" REGISTER_CON_LISTENER("jointeam", [](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post) -> Action { // Pre: decide. return Action::Ignore; }, false); REGISTER_CON_LISTENER("jointeam", [](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post) -> Action { // Post: the team change has been processed -- react to it. SOURCE2MOD_LOG(&g_Plugin, "Slot %d ran \"%s\"\n", context.GetPlayerSlot().Get(), args.GetCommandString()); return Action::Ignore; }, true); ``` ```cpp title="plugin.sp" public void OnPluginStart() { Source2ModCommands.RegisterConListener("jointeam", Listen_JoinTeamPre, false); Source2ModCommands.RegisterConListener("jointeam", Listen_JoinTeamPost, true); } public Source2ModAction Listen_JoinTeamPre(Source2ModCommandContext context, Source2ModCommandArgs args, bool post) { // Pre: decide. return Action_Ignore; } public Source2ModAction Listen_JoinTeamPost(Source2ModCommandContext context, Source2ModCommandArgs args, bool post) { // Post: the team change has been processed -- react to it. char command[256]; args.GetCommandString(command, sizeof(command)); Source2ModApi.Log("Slot %d ran \"%s\"", context.GetPlayerSlot().Get(), command); return Action_Ignore; } ``` Which commands can be listened to [#which-commands-can-be-listened-to] The core hooks the engine's command dispatch, so a listener sees: * every console command run from the **server console** or RCON; * console commands a **client** runs that go through the engine's command system; * **`jointeam`**, which the game handles as a raw client command — the core routes it to listeners separately; * **chat**: `say` and `say_team` (see below), and every chat command typed with a [chat trigger](/docs/convars-commands#console-commands-and-chat-commands). A client command the game handles itself, outside the console system, does not reach listeners (apart from `jointeam`). For those, hook `ISource2GameClients::ClientCommand` with `KHOOK_VIRTUAL` — the sample plugin does exactly that; see [Hooking](/docs/hooks#virtual-hooks). Filtering chat [#filtering-chat] Plain chat messages arrive as `say` (all chat) and `say_team` (team chat). A pre listener on them is a chat filter; `Supersede` drops the message. This is how FUNPLAY's Public plugin stops empty and repeated messages: ```cpp title="chat.cpp" static Action OnPlayerChat(const Source2ModCommandContext& ctx, const Source2ModCommandArgs& args, bool post) { CCSPlayerController* player = CCSPlayerController::FromSlot(ctx.GetPlayerSlot().Get()); if (!player) return Action::Ignore; // The message is everything after "say", possibly wrapped in quotes. std::string message = args.ArgS(); if (!message.empty() && message.front() == '"') message.erase(message.begin()); if (!message.empty() && message.back() == '"') message.pop_back(); if (message.empty()) return Action::Supersede; return Action::Ignore; } // in Load() REGISTER_CON_LISTENER("say", OnPlayerChat, false); REGISTER_CON_LISTENER("say_team", OnPlayerChat, false); ``` ```cpp title="chat.sp" public Source2ModAction OnPlayerChat(Source2ModCommandContext ctx, Source2ModCommandArgs args, bool post) { CPlayerSlot slot = ctx.GetPlayerSlot(); if (!slot.IsValid() || CCSPlayerController.FromSlot(slot) == NULL_ENTITY) return Action_Ignore; // The message is everything after "say", possibly wrapped in quotes. char message[256]; args.ArgS(message, sizeof(message)); int len = Source2ModUtils.Length(message); if (len > 0 && message[len - 1] == '"') message[--len] = '\0'; int start = (message[0] == '"') ? 1 : 0; if (message[start] == '\0') return Action_Supersede; return Action_Ignore; } // in OnPluginStart() Source2ModCommands.RegisterConListener("say", OnPlayerChat, false); Source2ModCommands.RegisterConListener("say_team", OnPlayerChat, false); ``` A message that starts with a chat trigger (`!`, `/`, …) does **not** reach the `say` listeners — the core dispatches it as the command it names instead. With a pre listener on that command name you can see it (and, with `Supersede`, hide the chat line); a `say` filter only ever sees ordinary chat. Listeners vs. commands [#listeners-vs-commands] | You want to… | Use | | ----------------------------------------- | ------------------------------------------------------------------------------ | | Add a new command | [`REGISTER_CON_COMMAND`](/docs/convars-commands/commands) | | Add a chat word without a console command | [`REGISTER_CHAT_LISTENER`](/docs/convars-commands/commands#chat-only-commands) | | Watch or block an existing command | `REGISTER_CON_LISTENER` | | Replace an existing command | `REGISTER_CON_LISTENER`, pre, do your thing, return `Action::Supersede` | In a script the three are `Source2ModCommands.RegisterConCommand`, `RegisterChatListener` and `RegisterConListener`. Full reference: [`ISource2ModCommands`](/docs/core-api/ISource2ModCommands). # Commands (/docs/convars-commands/commands) A plugin command is one call in `Load()` (in a script, `OnPluginStart()`). Source2Mod creates the console command, routes the chat aliases to it, and removes it again when your plugin unloads. ```cpp title="plugin.cpp" #include "source2mod/ISource2ModCommands.h" bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) { SOURCE2MOD_SAVEVARS(); REGISTER_CON_COMMAND("hello", [](const Source2ModCommandContext& ctx, const Source2ModCommandArgs& args, bool post) { SOURCE2MOD_CONPRINTF("hello from slot %d: %s\n", ctx.GetPlayerSlot().Get(), args.ArgS()); }); return true; } ``` ```cpp title="plugin.sp" public void OnPluginStart() { Source2ModCommands.RegisterConCommand("hello", Cmd_Hello); } public void Cmd_Hello(Source2ModCommandContext ctx, Source2ModCommandArgs args, bool post) { char text[256]; args.ArgS(text, sizeof(text)); Source2ModApi.ConPrintf("hello from slot %d: %s", ctx.GetPlayerSlot().Get(), text); } ``` `hello` now works from the server console, from a client's console, and from chat as `!hello` or `/hello`. The API [#the-api] ```cpp title="ISource2ModCommands.h" using ChatHandler = Source2ModCallback; using CommandHandler = Source2ModCallback; virtual Source2ModHookId RegisterChatListener(const char* pchName, ChatHandler handler, const char* pchPermission = nullptr) = 0; virtual bool UnregisterChatListener(const char* pchName, const ChatHandler& handler) = 0; virtual bool UnregisterChatListener(Source2ModHookId id) = 0; virtual Source2ModHookId RegisterConCommand(const char* pchName, ChatHandler handler, const char* pchPermission = nullptr) = 0; virtual bool UnregisterConCommand(const char* pchName, const ChatHandler& handler) = 0; virtual bool UnregisterConCommand(Source2ModHookId id) = 0; virtual Source2ModHookId RegisterConListener(const char* pchName, CommandHandler handler, bool post) = 0; virtual bool UnregisterConListener(const char* pchName, const CommandHandler& handler, bool post) = 0; virtual bool UnregisterConListener(Source2ModHookId id) = 0; virtual void ReplyToCommand(const Source2ModCommandContext& ctx, const char* pszMessage) = 0; ``` Scripts call the same methods on `Source2ModCommands` (`source2mod/commands.inc`); there the ids are plain `int`s and unregistering goes by id only. There is no plugin id argument. A handler is a `Source2ModCallback`: a lambda, a free or static function, or an object and a method, and the core reads which plugin it belongs to off the handler itself: ```cpp REGISTER_CON_COMMAND("hello", OnHello); // function REGISTER_CON_COMMAND("hello", &Commands::OnHello); // static method REGISTER_CON_COMMAND("hello", SOURCE2MOD_MEMBER(this, &MyPlugin::OnHello)); // object and method REGISTER_CON_COMMAND("hello", [](auto& ctx, auto& args, bool post) {}); // lambda ``` `SOURCE2MOD_MEMBER(inst, func)` (or `{ this, &MyPlugin::OnHello }`) and `SOURCE2MOD_STATIC(func)` are SourceHook's `SH_MEMBER` / `SH_STATIC` spelling of the same thing. Inside a macro argument use `SOURCE2MOD_MEMBER`: the comma in a bare `{ this, &MyPlugin::OnHello }` would split it into two arguments. C++ only — a script's handler is always a named `public` function: `Source2ModCommands.RegisterConCommand("hello", Cmd_Hello);`. The macros just forward: | Macro | Calls | | -------------------------------------------- | ------------------------------------------ | | `REGISTER_CON_COMMAND(name, handler)` | `RegisterConCommand(name, handler)` | | `REGISTER_CHAT_LISTENER(name, handler)` | `RegisterChatListener(name, handler)` | | `REGISTER_CON_LISTENER(name, handler, post)` | `RegisterConListener(name, handler, post)` | There are no unregister macros; call `Unregister*` on `g_pSource2ModCommands` (in a script, on `Source2ModCommands`) (see [Unregistering](#unregistering)). Commands and chat listeners take a `ChatHandler`, which returns nothing. A handler that needs to block something is a *listener*, which returns an `Action` — see [Command listeners](/docs/convars-commands/command-listeners). Full reference: [`ISource2ModCommands`](/docs/core-api/ISource2ModCommands). Console commands [#console-commands] `REGISTER_CON_COMMAND(name, handler)` does two things: 1. Creates a real engine `ConCommand` called `name`, so the console knows it (autocomplete, `find`, no "Unknown command"). 2. Registers your handler for `name` and for the chat forms, so `!name` and `/name` in chat reach the same code. If the name already exists in the engine — the game's own command or another plugin's — Source2Mod cannot take it over. It logs a warning and registers only the handler, so your code still runs whenever that command is dispatched. To *replace* someone else's command, use a pre [listener](/docs/convars-commands/command-listeners) that returns `Action::Supersede` instead. Register names in **lower case**. The dispatcher lower-cases the typed command before looking up handlers, so a handler registered as `"MyCmd"` is never found — while `!mycmd` and `!MyCmd` in chat both reach one registered as `"mycmd"`. Arguments [#arguments] `Source2ModCommandArgs` is the tokenized command line — Source2Mod's own type, built at the dispatch boundary, so neither the engine's `CCommand` nor `CCommandContext` is part of the plugin API (their layout can change without a rebuild). The calls are the ones the engine type has: | Call | Returns | | ------------------------- | -------------------------------------------------- | | `args.ArgC()` | number of tokens, **including** the command itself | | `args.Arg(0)` | the command name | | `args.Arg(i)` | token `i` (1-based for real arguments) | | `args.ArgS()` | everything after the command name, as one string | | `args.GetCommandString()` | the whole line | From chat the trigger is already stripped: `!hello a b` gives `Arg(0) == "hello"`, `Arg(1) == "a"`, `ArgC() == 3`. `Arg(i)` past the end is `""`, never null; the strings belong to the dispatch and are gone when the handler returns — copy what has to outlive it. In a script, `Arg`, `ArgS` and `GetCommandString` copy into a buffer you pass. ```cpp title="plugin.cpp" REGISTER_CON_COMMAND("sample_echo", [](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post) { if (args.ArgC() < 2) { Reply(context, "Usage: sample_echo "); return; } CCSPlayerController* pCaller = CallerOf(context); Reply(context, "%s said: %s (%d arguments)", pCaller ? pCaller->GetPlayerName() : "Console", args.ArgS(), args.ArgC() - 1); }); ``` ```cpp title="plugin.sp" // in OnPluginStart() Source2ModCommands.RegisterConCommand("sample_echo", Cmd_Echo); public void Cmd_Echo(Source2ModCommandContext context, Source2ModCommandArgs args, bool post) { if (args.ArgC() < 2) { Reply(context, "Usage: sample_echo "); return; } char text[256], name[64] = "Console"; args.ArgS(text, sizeof(text)); CCSPlayerController caller = CallerOf(context); if (caller != NULL_ENTITY) caller.GetPlayerName(name, sizeof(name)); Reply(context, "%s said: %s (%d arguments)", name, text, args.ArgC() - 1); } ``` The caller [#the-caller] `Source2ModCommandContext::GetPlayerSlot()` tells you who ran the command. A player has a valid slot; the server console (and RCON) has none — the slot is `-1` and `IsValid()` is false. ```cpp title="plugin.cpp" // A controller for a player, nullptr for the server console. static CCSPlayerController* CallerOf(const Source2ModCommandContext& context) { const CPlayerSlot slot = context.GetPlayerSlot(); return slot.IsValid() ? CCSPlayerController::FromSlot(slot) : nullptr; } ``` ```cpp title="plugin.sp" // A controller for a player, NULL_ENTITY for the server console. CCSPlayerController CallerOf(Source2ModCommandContext context) { CPlayerSlot slot = context.GetPlayerSlot(); return slot.IsValid() ? CCSPlayerController.FromSlot(slot) : NULL_ENTITY; } ``` `GetSource()` says where it was typed: | `Source2ModCommandSource` | Typed | | ------------------------- | -------------------------------------------------------------- | | `Console` | in a console — the server's, RCON, or the player's own | | `Chat` | in chat with a public trigger (`!kick`); the message shows | | `SilentChat` | in chat with a silent trigger (`/kick`); the message is hidden | `IsFromChat()` is true for both chat kinds. Replying [#replying] `ReplyToCommand(ctx, message)` answers where the command came from: the player's chat for a chat command, the player's console for one typed there, the server console for the server. `REPLY_TO_COMMAND` formats first; a script's `Source2ModCommands.ReplyToCommand(ctx, format, ...)` formats itself: ```cpp REGISTER_CON_COMMAND("hp", [](const Source2ModCommandContext& ctx, const Source2ModCommandArgs& args, bool post) { CCSPlayerController* pPlayer = CallerOf(ctx); CCSPlayerPawn* pPawn = pPlayer ? pPlayer->GetPlayerPawn() : nullptr; if (!pPawn) { REPLY_TO_COMMAND(ctx, "Players only."); return; } REPLY_TO_COMMAND(ctx, "You have %d HP.", pPawn->m_iHealth()); }); ``` ```cpp // in OnPluginStart() Source2ModCommands.RegisterConCommand("hp", Cmd_Hp); public void Cmd_Hp(Source2ModCommandContext ctx, Source2ModCommandArgs args, bool post) { CCSPlayerController player = CallerOf(ctx); CCSPlayerPawn pawn = player != NULL_ENTITY ? player.GetPlayerPawn() : NULL_ENTITY; if (pawn == NULL_ENTITY) { Source2ModCommands.ReplyToCommand(ctx, "Players only."); return; } Source2ModCommands.ReplyToCommand(ctx, "You have %d HP.", pawn.m_iHealth); } ``` For a specific place regardless of where it was typed, use the controller's `PrintToChat`, `PrintToConsole` or `PrintToCenter` on [`CBasePlayerController`](/docs/schema/entity/classes/CBasePlayerController). The `post` argument [#the-post-argument] Command handlers are registered as *pre* handlers and are called once, with `post == false`. The parameter is there because the type is shared with listeners; for commands you can ignore it. Permissions [#permissions] The ConCommands Source2Mod creates are client-executable, so a command registered without a permission runs for **any player**. Give it one and the core checks it before your handler runs: ```cpp REGISTER_CON_COMMAND("kick", OnKick, "myplugin.admin.kick"); ``` ```cpp Source2ModCommands.RegisterConCommand("kick", OnKick, "myplugin.admin.kick"); ``` A player without it does not reach the handler; they are told so where they typed the command (core.json's `AccessDeniedMessage`, or a plugin's own answer). The server console and RCON always may. The server owner can give the command another permission under `Overrides` in `permissions.json`. Who holds what — admins, groups, wildcards — is on the [Permissions](/docs/guides/permissions) page. For something only the server console should run, check the slot: ```cpp REGISTER_CON_COMMAND("myplugin_reload", [](const Source2ModCommandContext& ctx, const Source2ModCommandArgs& args, bool post) { // Server console and RCON only. if (ctx.GetPlayerSlot().IsValid()) return; // ... }); ``` ```cpp public void Cmd_Reload(Source2ModCommandContext ctx, Source2ModCommandArgs args, bool post) { // Server console and RCON only. if (ctx.GetPlayerSlot().IsValid()) return; // ... } ``` Chat-only commands [#chat-only-commands] `REGISTER_CHAT_LISTENER(name, handler)` (`Source2ModCommands.RegisterChatListener` in a script) registers the same handler without creating a console command. It fires when a player types `!name` or `/name` (or any other configured [chat trigger](/docs/convars-commands#console-commands-and-chat-commands)) in chat: ```cpp title="plugin.cpp" REGISTER_CHAT_LISTENER("noshake", [this](const Source2ModCommandContext& context, const Source2ModCommandArgs& args, bool post) { const CPlayerSlot slot = context.GetPlayerSlot(); if (!slot.IsValid()) return; m_NoShakeMask ^= (uint64_t(1) << slot.Get()); const bool bOff = (m_NoShakeMask >> slot.Get()) & 1; Reply(context, "Screen shakes are now %s for you.", bOff ? "off" : "on"); }); ``` ```cpp title="plugin.sp" int g_NoShakeMask[2]; // one bit per player slot // in OnPluginStart() Source2ModCommands.RegisterChatListener("noshake", Chat_NoShake); public void Chat_NoShake(Source2ModCommandContext context, Source2ModCommandArgs args, bool post) { CPlayerSlot slot = context.GetPlayerSlot(); if (!slot.IsValid()) return; if (PlayerMask_Has(g_NoShakeMask, slot.Get())) PlayerMask_Remove(g_NoShakeMask, slot.Get()); else PlayerMask_Add(g_NoShakeMask, slot.Get()); bool off = PlayerMask_Has(g_NoShakeMask, slot.Get()); Reply(context, "Screen shakes are now %s for you.", off ? "off" : "on"); } ``` A chat listener cannot hide the message: with the public trigger (`!`) it stays visible, with the silent trigger (`/`) the core hides it anyway. To hide `!name` messages too, register a pre [listener](/docs/convars-commands/command-listeners) on `name` and return `Action::Supersede` — *after* the command or chat listener, because a `Supersede` stops the handlers registered behind it. Unregistering [#unregistering] You rarely need to — everything is dropped when your plugin unloads. To turn a command off while the plugin keeps running (a script goes by the id the registration returned): ```cpp // By the id the registration returned -- the only way for a lambda. Source2ModHookId m_hHello = REGISTER_CON_COMMAND("hello", [](auto& ctx, auto& args, bool post) { /* ... */ }); g_pSource2ModCommands->UnregisterConCommand(m_hHello); // the handler and its chat forms // Or by the same function / object and method again. REGISTER_CHAT_LISTENER("noshake", SOURCE2MOD_MEMBER(this, &MyPlugin::OnNoShake)); g_pSource2ModCommands->UnregisterChatListener("noshake", SOURCE2MOD_MEMBER(this, &MyPlugin::OnNoShake)); ``` ```cpp // By the id the registration returned -- the only way in a script. int helloId = Source2ModCommands.RegisterConCommand("hello", Cmd_Hello); Source2ModCommands.UnregisterConCommand(helloId); // the handler and its chat forms int noshakeId = Source2ModCommands.RegisterChatListener("noshake", OnNoShake); Source2ModCommands.UnregisterChatListener(noshakeId); ``` Each returns `true` when it found the handler. Several handlers may share one command name; each runs, and unregistering removes only the one you name. `UnregisterConCommand` removes your handler. The engine `ConCommand` stays until the plugin unloads (it just does nothing in the meantime); registering the same name again reuses it. On unload Source2Mod gives the name back to the engine, so a reloaded plugin — or another one — can register it again. See also [#see-also] * [Command listeners](/docs/convars-commands/command-listeners) — hooking commands you do not own. * [`ISource2ModCommands` reference](/docs/core-api/ISource2ModCommands) * [`ISource2ModTypes`](/docs/core-api/ISource2ModTypes) — the `Action` enum. # ConVars (/docs/convars-commands/convars) ConVars in CS2 are s2sdk's `CConVar` and `CConVarRef` — Source2Mod does not wrap them in a class of its own. What it adds is [`ISource2ModConVars`](/docs/core-api/ISource2ModConVars): an index-based interface for reading, writing and creating ConVars without templates, a process-wide change callback that takes lambdas, and a few by-name helpers. Scripts get the same through the `CConVar` and `CConVarRef` methodmaps and `Source2ModConVars` (`source2mod/convars.inc`). Declaring your own [#declaring-your-own] In C++, declare a `CConVar` at file scope, then register the library's ConVars once in `Load()`. A script creates each one with `CConVar.Create*` in `OnPluginStart()`: ```cpp title="plugin.cpp" CConVar myplugin_enabled("myplugin_enabled", FCVAR_NONE, "Turns the plugin on and off", true); CConVar myplugin_bonus("myplugin_bonus", FCVAR_NONE, "Money given at round start", 1337); CConVar myplugin_scale("myplugin_scale", FCVAR_NONE, "Damage multiplier", 1.0f, true, 0.0f, true, 10.0f); // min 0, max 10 bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) { SOURCE2MOD_SAVEVARS(); g_pCVar = api->GetCVar(); ConVar_Register(FCVAR_RELEASE | FCVAR_CLIENT_CAN_EXECUTE | FCVAR_GAMEDLL); return true; } ``` ```cpp title="plugin.sp" CConVar myplugin_enabled; CConVar myplugin_bonus; CConVar myplugin_scale; public void OnPluginStart() { myplugin_enabled = CConVar.CreateBool("myplugin_enabled", FCVAR_NONE, "Turns the plugin on and off", true); myplugin_bonus = CConVar.CreateInt("myplugin_bonus", FCVAR_NONE, "Money given at round start", 1337); myplugin_scale = CConVar.CreateFloat("myplugin_scale", FCVAR_NONE, "Damage multiplier", 1.0, true, 0.0, true, 10.0); // min 0, max 10 } ``` * `g_pCVar` has to be set before `ConVar_Register()` — that is where the registration goes. * The flags passed to `ConVar_Register()` are added to every ConVar and ConCommand declared in your library. The set above is what the sample plugin and the core use. * Read and write your own ConVar with `Get()` and `Set()`: `if (myplugin_enabled.Get()) …`, `myplugin_bonus.Set(500);`. * A script needs neither `g_pCVar` nor `ConVar_Register()`, and reads and writes through the handle: `myplugin_enabled.BoolValue`, `myplugin_bonus.IntValue = 500;`, `FloatValue`, `GetString()`/`SetString()`. Constructors [#constructors] ```cpp title="tier1/convar.h (s2sdk)" CConVar(const char* name, uint64 flags, const char* help_string, const T& default_value, FnTypedChangeCallback_t cb = nullptr, ...); CConVar(const char* name, uint64 flags, const char* help_string, const T& default_value, bool min, const T& minValue, bool max, const T& maxValue, FnTypedChangeCallback_t cb = nullptr, ...); ``` Typical `T`: `bool`, `int`, `float`, `double`, `CUtlString`, `Vector`, `QAngle`, `Color`. Flags [#flags] The `FCVAR_*` flags come from s2sdk's `convar.h`. The ones you will use: | Flag | Meaning | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `FCVAR_NONE` | Nothing special. | | `FCVAR_REPLICATED` | The server's value is sent to clients and enforced there (movement, prediction). See [Replication](/docs/convars-commands/replication). | | `FCVAR_NOTIFY` | Players are told in chat when it changes. | | `FCVAR_PROTECTED` | A password or similar: the value is not sent out. | | `FCVAR_CHEAT` | Only changeable with `sv_cheats 1`. | | `FCVAR_HIDDEN` | Not listed by `find` or autocomplete. | | `FCVAR_RELEASE` | Available in release builds — part of the usual `ConVar_Register` flags. | Change callback on your own ConVar [#change-callback-on-your-own-convar] The last constructor argument is a typed callback, a plain function pointer (a lambda without captures converts to one). A script calls `AddChangeHook` on its ConVar instead and gets the old and new value as text: ```cpp title="plugin.cpp" CConVar myplugin_mode("myplugin_mode", FCVAR_NONE, "0 = off, 1 = on, 2 = debug", 1, [](CConVar* cvar, CSplitScreenSlot slot, const int* pNewValue, const int* pOldValue) { SOURCE2MOD_CONPRINTF("myplugin_mode: %d -> %d\n", *pOldValue, *pNewValue); }); ``` ```cpp title="plugin.sp" CConVar myplugin_mode; public void OnPluginStart() { myplugin_mode = CConVar.CreateInt("myplugin_mode", FCVAR_NONE, "0 = off, 1 = on, 2 = debug", 1); myplugin_mode.AddChangeHook(OnModeChanged); } public void OnModeChanged(ConVar convar, const char[] oldValue, const char[] newValue) { Source2ModApi.Log("myplugin_mode: %s -> %s", oldValue, newValue); } ``` When you need captures, or want to hear about ConVars that are not yours, use [`HOOK_CONVAR_CHANGE`](#watching-changes) instead. Unloading [#unloading] Every C++ plugin that declares a `CConVar` must call `ConVar_Unregister()` in `Unload()`. `ConVar_Register()` hands the engine pointers to the `CConVar` objects in your library. When the library is unloaded those pointers dangle, and the next thing that touches one — autocomplete, an `exec` — reads freed memory. `ConVar_Unregister()` only walks the list of the library that calls it, so the Source2Mod cannot do this for you: ```cpp title="plugin.cpp" bool MyPlugin::Unload(char* error, size_t maxlen) { KHOOK_DESTRUCT(); ConVar_Unregister(); return true; } ``` C++ only — a script has nothing to unregister: its `CConVar` belongs to the core. Reading and writing the game's ConVars [#reading-and-writing-the-games-convars] `CConVarRef` — typed, for a ConVar you keep using [#cconvarreft--typed-for-a-convar-you-keep-using] ```cpp title="plugin.cpp" CConVarRef mp_limitteams("mp_limitteams"); if (mp_limitteams.IsValidRef() && mp_limitteams.IsConVarDataAvailable()) { int current = mp_limitteams.Get(); mp_limitteams.Set(5); } ``` ```cpp title="plugin.sp" CConVarRef mp_limitteams = CConVarRef.Find("mp_limitteams"); if (mp_limitteams != null && mp_limitteams.IsValidRef() && mp_limitteams.IsConVarDataAvailable()) { int current = mp_limitteams.IntValue; mp_limitteams.IntValue = 5; } ``` In a script, `CConVarRef.Find` returns `null` for a name nobody registered, and the handle is not typed: `IntValue`, `FloatValue`, `BoolValue` and `GetString()` convert. The list below is about C++'s `CConVarRef`. Things the sample plugin spells out, all worth knowing: * A `CConVarRef` to a name that is not registered yet **pre-registers** it and reads default data until the real ConVar appears. Check `IsConVarDataAvailable()`. * The type must be right. A ref of the wrong type can keep the real ConVar from registering, or leave you with an invalid ref — check `IsValidRef()` before reading or writing. An invalid ref reads and writes shared dummy data. * `Get()`/`Set()` are the fast typed path. `GetFloat()`, `GetBool()`, `GetString()`, `SetFloat()`, … convert between types when possible. A ref to a file-scope `static CConVarRef sv_autobunnyhopping("sv_autobunnyhopping");` is a common pattern in FUNPLAY's plugins. `ConVarRefAbstract` — untyped [#convarrefabstract--untyped] ```cpp ConVarRefAbstract ref("mp_limitteams"); if (ref.IsValidRef()) { ref.SetFloat(10.0f); // converting setters float f = ref.GetFloat(); ref.SetAs(Vector(1.0f, 2.0f, 3.0f)); // for non-primitive types CConVarRef typed(ref); // "promote" to a typed ref if (typed.IsValidRef() && typed.IsConVarDataValid()) typed.Set(5); } ``` `ConVarRefAbstract` does not pre-register anything; it is a plain reference. C++ only — a script's ConVar handle is untyped to begin with: its accessors convert, and `Source2ModConVars.SetVector3`, `SetQAngle`, `SetColor`, … write the non-primitive types. One-shot helpers by name [#one-shot-helpers-by-name] `ISource2ModConVars.h` has three inline helpers for "change this and move on". They look the ConVar up themselves and return `false` if it does not exist. A script has no by-name helpers; it finds the ConVar and sets it on the handle (`ConVar.Flags` holds the low 32 flag bits, `Source2ModConVars.GetFlags` / `SetFlags` all 64): ```cpp UTIL_SetConVar("mp_freezetime", 5); UTIL_SetConVar("mp_roundtime", 1.92f); UTIL_SetConVar("hostname", "My Server"); UTIL_RemoveConVarFlags("sv_jump_impulse", FCVAR_CHEAT); // settable without sv_cheats UTIL_AddConVarFlags("myplugin_bonus", FCVAR_NOTIFY); ``` ```cpp // No by-name helpers: find the ConVar, check it, set it on the handle. CConVarRef cvar; if ((cvar = CConVarRef.Find("mp_freezetime")) != null) cvar.IntValue = 5; if ((cvar = CConVarRef.Find("mp_roundtime")) != null) cvar.FloatValue = 1.92; if ((cvar = CConVarRef.Find("hostname")) != null) cvar.SetString("My Server"); if ((cvar = CConVarRef.Find("sv_jump_impulse")) != null) cvar.Flags &= ~FCVAR_CHEAT; // settable without sv_cheats if ((cvar = CConVarRef.Find("myplugin_bonus")) != null) cvar.Flags |= FCVAR_NOTIFY; ``` For `UTIL_SetConVar` the *type* decides how the value is written, so spell it out (`UTIL_SetConVar(…)`) when a literal would deduce to the wrong one — a string literal always needs ``. These go through s2sdk directly, so they also work before Source2Mod's ConVar manager is up. `ISource2ModConVars` — by access index [#isource2modconvars--by-access-index] Every ConVar has a 16-bit access index. The interface is built around it, which suits code that does not know the type at compile time. In a script the ConVar handle takes the index's place: ```cpp uint16 idx = CVAR_IDX("mp_roundtime"); // GetConvarAccessIndexByName float roundTime = CVAR_GET_FLOAT(idx); CVAR_SET_FLOAT(idx, 2.5f); EConVarType type = g_pSource2ModConVars->GetType(idx); uint64 flags = g_pSource2ModConVars->GetFlags(idx); const char* help = g_pSource2ModConVars->GetHelpText(idx); ``` ```cpp // The ConVar handle is what the access index is in C++. ConVar mp_roundtime = CConVarRef.Find("mp_roundtime"); float roundTime = mp_roundtime.FloatValue; mp_roundtime.FloatValue = 2.5; EConVarType type = Source2ModConVars.GetType(mp_roundtime); int flags[2]; // all 64 bits, flags[0] the low half Source2ModConVars.GetFlags(mp_roundtime, flags); char help[256]; mp_roundtime.GetDescription(help, sizeof(help)); ``` | Group | Methods | | ------- | --------------------------------------------------------------------------------- | | Lookup | `GetConvarAccessIndexByName`, `GetConvarRef`, `GetName`, `GetHelpText`, `GetType` | | Flags | `GetFlags`, `SetFlags` (replaces the whole set) | | Scalars | `GetString/Bool/Int/Float/Double`, `SetString/Bool/Int/Float/Double` | | Vectors | `GetVector2/3/4`, `GetQAngle`, `GetColor` and their `Set…` | | Raw | `GetValueAddress`, `GetValue`, `SetValue` | Macros: `CVAR_IDX`, `CVAR_REF`, `CVAR_GET_STR/BOOL/INT/FLOAT/DOUBLE`, `CVAR_SET_STR/BOOL/INT/FLOAT/DOUBLE`, `CVAR_CREATE`, `CVAR_DELETE`. Every getter and setter takes an optional last `CSplitScreenSlot` (default `-1`), which you can leave alone on a server. `GetConvarAccessIndexByName` returns `SOURCE2MOD_INVALID_CONVAR_INDEX` (`0xFFFF`) when the name does not exist. Check for it before reading or writing -- `0` is a valid index, so do not test against that. `GetString` returns a pointer into a thread-local buffer: copy it before the next `GetString` call if you need to keep it. Creating ConVars at runtime [#creating-convars-at-runtime] When the name is only known at runtime — read from a config, say — create the ConVar through the interface (a script: `CConVar.Create`, the values as text — `"1 2 3"` for a vector, `"255 0 0 255"` for a colour): ```cpp title="ISource2ModConVars.h" virtual uint16 CreateConVar(const char* name, EConVarType type, const char* help, uint64 flags, bool hasMin, bool hasMax, void* defaultValue, const void* minValue, const void* maxValue) = 0; virtual void DeleteConVar(uint16 accessIndex) = 0; ``` ```cpp float def = 1.0f, min = 0.0f, max = 10.0f; uint16 idx = CVAR_CREATE("myplugin_dynamic", EConVarType_Float32, "Created at runtime", FCVAR_NONE, true, true, &def, &min, &max); // ... CVAR_DELETE(idx); // in Unload() ``` ```cpp CConVar dynamic = CConVar.Create("myplugin_dynamic", EConVarType_Float32, FCVAR_NONE, "Created at runtime", "1.0", true, "0.0", true, "10.0"); // ... Source2ModConVars.DeleteConVar(dynamic); ``` * `defaultValue`, `minValue` and `maxValue` point to a value of the matching type (`int32` for `EConVarType_Int32`, `float` for `EConVarType_Float32`, …). For `EConVarType_String` pass the string itself, `(void*)"text"`, not a pointer to it. * Pass valid `minValue`/`maxValue` pointers even when `hasMin`/`hasMax` are false — for numeric types they are read either way. * If a ConVar with that name exists already, nothing is created and its index is returned. * The ConVar is created by the core, not by your library, and is **not** removed when your plugin unloads. `DeleteConVar` in `Unload()` is on you. For ConVars known at compile time, a `CConVar` is simpler and needs no cleanup beyond `ConVar_Unregister()`. Watching changes [#watching-changes] `HOOK_CONVAR_CHANGE` registers a callback for **every** ConVar change on the server. The engine only takes one plain function pointer for this; Source2Mod installs it once and fans out, so your handler can be a capturing lambda, and it is dropped automatically when the plugin unloads. A script passes a named `public` function to `Source2ModConVars.HookConVarChange`. ```cpp title="plugin.cpp" HOOK_CONVAR_CHANGE([](ConVarRefAbstract* ref, CSplitScreenSlot slot, const char* pszNewValue, const char* pszOldValue) { if (V_strcmp(ref->GetName(), "sv_autobunnyhopping") != 0) return; SOURCE2MOD_LOG(&g_Plugin, "%s changed from %s to %s\n", ref->GetName(), pszOldValue, pszNewValue); }); ``` ```cpp title="plugin.sp" public void OnPluginStart() { Source2ModConVars.HookConVarChange(OnConVarChanged); } public void OnConVarChanged(ConVar convar, int slot, const char[] newValue, const char[] oldValue) { char name[64]; convar.GetName(name, sizeof(name)); if (!Source2ModUtils.Equal(name, "sv_autobunnyhopping")) return; Source2ModApi.Log("%s changed from %s to %s", name, oldValue, newValue); } ``` ```cpp title="ISource2ModConVars.h" using ConVarChangeHandler = Source2ModCallback; virtual Source2ModHookId HookConVarChange(ConVarChangeHandler handler) = 0; virtual bool UnhookConVarChange(const ConVarChangeHandler& handler) = 0; virtual bool UnhookConVarChange(Source2ModHookId id) = 0; ``` Filter by name first — the callback fires for all ConVars, the game's included. A plugin may register as many handlers as it likes. There is no plugin id to pass: the handler is a lambda, a function or an object and a method, and the core reads the owner off it. `HOOK_CONVAR_CHANGE(handler)` returns a `Source2ModHookId`; to drop one handler while the plugin stays loaded, pass that id to `g_pSource2ModConVars->UnhookConVarChange(id)`, or pass the same function or object and method again to `UNHOOK_CONVAR_CHANGE(handler)` (a lambda only goes by id). A script unhooks by id only, with `Source2ModConVars.UnhookConVarChange(id)`: ```cpp HOOK_CONVAR_CHANGE(SOURCE2MOD_MEMBER(this, &MyPlugin::OnConVarChanged)); // ... UNHOOK_CONVAR_CHANGE(SOURCE2MOD_MEMBER(this, &MyPlugin::OnConVarChanged)); ``` ```cpp int id = Source2ModConVars.HookConVarChange(OnConVarChanged); // ... Source2ModConVars.UnhookConVarChange(id); ``` See also [#see-also] * [Replication](/docs/convars-commands/replication) — a different value per player. * [`ISource2ModConVars` reference](/docs/core-api/ISource2ModConVars) # ConVars & Commands (/docs/convars-commands) Everything a server admin or a player types goes through the engine's console system: `mp_roundtime 5` in the server console, `jointeam 2` from a client, `!rtv` in chat. The s2m hooks that dispatch once, in the core, and hands plugins three things on top of it: * **Commands of your own** — a console command that also answers in chat. * **Listeners** — a callback on a command somebody else owns (the game, the engine, another plugin), before or after it runs, with the power to block it. * **ConVars** — your own console variables, and typed access to the game's. The interfaces are [`ISource2ModCommands`](/docs/core-api/ISource2ModCommands) and [`ISource2ModConVars`](/docs/core-api/ISource2ModConVars). Both come filled in by `SOURCE2MOD_SAVEVARS()` as `g_pSource2ModCommands` and `g_pSource2ModConVars`, and both headers ship short macros (`REGISTER_CON_COMMAND`, `CVAR_GET_INT`, …). None of the calls takes a plugin id: a handler is a lambda, a function or an object and a method (`SOURCE2MOD_MEMBER(this, &MyPlugin::OnCmd)`), and the core reads which plugin it belongs to off the handler itself. Scripts reach the same through the `Source2ModCommands`, `Source2ModConVars`, `CConVar` and `CConVarRef` methodmaps, with named `public` functions as handlers. Console commands and chat commands [#console-commands-and-chat-commands] A command registered with `REGISTER_CON_COMMAND("mycmd", …)` is reachable three ways: | Typed where | Example | Caller | | --------------------- | ---------------------------- | --------------------- | | Server console / RCON | `mycmd 1 2` | no player (slot `-1`) | | A client's console | `mycmd 1 2` | that player's slot | | Chat | `!mycmd 1 2` or `/mycmd 1 2` | that player's slot | Chat works through **chat triggers**. When a chat message starts with a trigger, the core strips the prefix, splits the rest into arguments and dispatches it exactly like a console command. The triggers come from the core config, `addons/source2mod/configs/core.json`: ```json title="core.json" { "PublicChatTrigger": [ "!" ], "SilentChatTrigger": [ "/" ] } ``` * A **public** trigger (`!` by default) runs the command and the message still shows in chat for everyone. * A **silent** trigger (`/` by default) runs the command and swallows the message — nobody sees it. Both are lists, so a server can add its own prefixes (`"."`, say). Your code never sees the prefix: `!mycmd a b` arrives as `Arg(0) == "mycmd"`, `Arg(1) == "a"`. If you only want the chat side — a word players type, with no console command behind it — use `REGISTER_CHAT_LISTENER` instead. See [Commands](/docs/convars-commands/commands). Listening to commands you do not own [#listening-to-commands-you-do-not-own] `REGISTER_CON_LISTENER` puts a callback on an **existing** command: `jointeam`, `say`, an engine command, another plugin's command. A pre listener can return `Action::Supersede` and the command never runs — that is a complete "no spectators" plugin in ten lines. See [Command listeners](/docs/convars-commands/command-listeners). ConVars [#convars] A ConVar is a named, typed value in the console: `mp_freezetime`, `sv_cheats`, or one your plugin declares (`myplugin_enabled`). Source2Mod side covers: * declaring your own with s2sdk's `CConVar` (the recommended way), or at runtime through `ISource2ModConVars::CreateConVar`; * reading and writing the game's ConVars — typed `CConVarRef`, the index-based `ISource2ModConVars` getters/setters, or the one-shot `UTIL_SetConVar` helper; * one change callback for every ConVar in the process (`HOOK_CONVAR_CHANGE`), which takes capturing lambdas. See [ConVars](/docs/convars-commands/convars). Sending one player a different value than everybody else is its own topic: [Replication](/docs/convars-commands/replication). The core unlocks hidden and development-only commands and ConVars at startup (`UnlockConCommands` and `UnlockConVars` in `core.json`, both on by default), so everything the game ships is reachable from the console and from plugins. Ownership and unloading [#ownership-and-unloading] Every command, listener and change callback belongs to the plugin whose handler it is. When the plugin unloads, Source2Mod drops all of them. You only call `Unregister*` / `Unhook*` — with the `Source2ModHookId` the registration returned, or with the same function or object and method again — to switch something off while the plugin stays loaded. The one exception is a `CConVar` you declared yourself: it lives in your library, so your `Unload()` has to call `ConVar_Unregister()`. The [ConVars](/docs/convars-commands/convars#unloading) page explains why. A script's `CConVar` belongs to the core, so a script has nothing to undo. Console and chat commands, arguments, the caller. Pre/post callbacks on existing commands, blocking them. Declaring, reading, writing and watching console variables. Per-player ConVar values and reading client ConVars. # Replication (/docs/convars-commands/replication) Some ConVars matter on the client as much as on the server. `FCVAR_REPLICATED` ConVars — `sv_autobunnyhopping`, `sv_jump_impulse`, `mp_forcecamera`, `weapon_accuracy_nospread`, … — are sent by the server to every client, and the client uses them for prediction, movement and UI. Normally everyone gets the same value. `ReplicateConVar` sends a single player a value of their own. `ReplicateConVar` [#replicateconvar] A helper on [`CBasePlayerController`](/docs/schema/entity/classes/CBasePlayerController) (so on `CCSPlayerController` too, and on the script methodmaps of the same names): ```cpp title="CBasePlayerController.h" void ReplicateConVar(const char* pszConVar, const char* pszValue); ``` It builds a `CNETMsg_SetConVar` with the one name/value pair and sends it straight down that player's net channel. The value is always a string, as the client would parse it from the console: `"1"`, `"true"`, `"301.9933"`. ```cpp title="plugin.cpp" #include "source2mod/schema/entity/classes/CCSPlayerController.h" // Auto-bunnyhop for one player only. void SetBhop(CCSPlayerController* player, bool enabled) { player->ReplicateConVar("sv_autobunnyhopping", enabled ? "true" : "false"); } ``` ```cpp title="plugin.sp" // Auto-bunnyhop for one player only. void SetBhop(CCSPlayerController player, bool enabled) { player.ReplicateConVar("sv_autobunnyhopping", enabled ? "true" : "false"); } ``` Things to keep in mind: * **Only the client's copy changes.** The server's value is untouched, and anything the server decides with it still uses the server's value. For ConVars that are simulated on both sides — movement mostly — the server has to agree, or the client mispredicts. * **The player must be connected.** Without a net channel the call does nothing. * **It does not survive a reconnect.** A client that reconnects starts from the server's values again. Keeping the value per player [#keeping-the-value-per-player] If the ConVar is `FCVAR_REPLICATED`, the engine broadcasts the server's value to every client whenever it changes — which overwrites every per-player value you sent. FUNPLAY's plugins, which toggle bunnyhop, C4-anywhere and friends per player, handle this in three places: 1. **Before sending**, drop `FCVAR_REPLICATED` from the ConVar so a later change of the server value is not pushed over the per-player ones: ```cpp UTIL_RemoveConVarFlags("sv_autobunnyhopping", FCVAR_REPLICATED); player->ReplicateConVar("sv_autobunnyhopping", state ? "true" : "false"); ``` ```cpp CConVarRef bhop = CConVarRef.Find("sv_autobunnyhopping"); bhop.Flags &= ~FCVAR_REPLICATED; player.ReplicateConVar("sv_autobunnyhopping", state ? "true" : "false"); ``` 2. **When the server value changes**, send each player their value again ([`HOOK_CONVAR_CHANGE`](/docs/convars-commands/convars#watching-changes)). 3. **When a player (re)connects**, send it again — `player_connect_full` is the usual place (see [Game events](/docs/events/game-events)). ```cpp title="bhop.cpp" static CConVarRef sv_autobunnyhopping("sv_autobunnyhopping"); static void UpdatePlayerBhop(CCSPlayerController* player, bool state) { UTIL_RemoveConVarFlags("sv_autobunnyhopping", FCVAR_REPLICATED); // With bhop on for everybody there is nothing to override. if (!sv_autobunnyhopping.Get()) player->ReplicateConVar("sv_autobunnyhopping", state ? "true" : "false"); } // in Load() HOOK_CONVAR_CHANGE([](ConVarRefAbstract* ref, CSplitScreenSlot slot, const char* pszNewValue, const char* pszOldValue) { if (V_strcmp(ref->GetName(), "sv_autobunnyhopping") != 0) return; for (int i = 0; i < 64; i++) if (auto* player = CCSPlayerController::FromSlot(i)) UpdatePlayerBhop(player, PlayerHasBhop(player)); // your own per-player state }); HOOK_GAME_EVENT("player_connect_full", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action { auto* player = static_cast(event->GetPlayerController("userid")); if (player) UpdatePlayerBhop(player, PlayerHasBhop(player)); return Action::Ignore; }, true); ``` ```cpp title="bhop.sp" CConVarRef sv_autobunnyhopping; void UpdatePlayerBhop(CCSPlayerController player, bool state) { sv_autobunnyhopping.Flags &= ~FCVAR_REPLICATED; // With bhop on for everybody there is nothing to override. if (!sv_autobunnyhopping.BoolValue) player.ReplicateConVar("sv_autobunnyhopping", state ? "true" : "false"); } public void OnPluginStart() { sv_autobunnyhopping = CConVarRef.Find("sv_autobunnyhopping"); Source2ModConVars.HookConVarChange(OnConVarChanged); Source2ModEvents.HookGameEvent("player_connect_full", Event_PlayerConnectFull, true); } public void OnConVarChanged(ConVar convar, int slot, const char[] newValue, const char[] oldValue) { char name[64]; convar.GetName(name, sizeof(name)); if (!Source2ModUtils.Equal(name, "sv_autobunnyhopping")) return; for (int i = 0; i < 64; i++) { CCSPlayerController player = CCSPlayerController.FromSlot(CPlayerSlot(i)); if (player != NULL_ENTITY) UpdatePlayerBhop(player, PlayerHasBhop(player)); // your own per-player state } } public Source2ModAction Event_PlayerConnectFull(IGameEvent event, bool post, bool &dontBroadcast) { CCSPlayerController player = event.GetPlayerController("userid"); if (player != NULL_ENTITY) UpdatePlayerBhop(player, PlayerHasBhop(player)); return Action_Ignore; } ``` `UTIL_RemoveConVarFlags` and `UTIL_AddConVarFlags` are the by-name helpers from `ISource2ModConVars.h`; see [ConVars](/docs/convars-commands/convars#one-shot-helpers-by-name). A script changes the flags on the ConVar handle's `Flags` instead. Common uses [#common-uses] | ConVar | Per-player effect | | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | `sv_autobunnyhopping` | Hold-to-jump bunnyhopping for chosen players. | | `mp_forcecamera` | What a dead player may spectate (FUNPLAY switches it per player on spawn, round start and round end). | | `sv_cheats` | Lets a single client use client-side cheat commands — FUNPLAY's Inspect plugin does this. The server stays `sv_cheats 0`. | | `sv_jump_impulse`, `sv_friction`, `weapon_accuracy_nospread`, `mp_plant_c4_anywhere` | Movement and weapon behaviour the client predicts; the server side has to match (see above). | Reading ConVars from a client [#reading-convars-from-a-client] Source2Mod has no wrapper for asking a client for one of its ConVars. There are two ways, both engine-level: * **User info.** `IVEngineServer2::GetClientConVarValue(CPlayerSlot, const char*)` returns a value from the user info the client sends to the server — no round trip, but only for what the client puts there. ```cpp IVEngineServer2* engine = g_Source2ModAPI->GetEngineServer(); const char* value = engine->GetClientConVarValue(player->GetPlayerSlot(), "name_of_userinfo_cvar"); ``` C++ only — scripts have no `GetClientConVarValue`; they use the cvar query below. * **A cvar query.** Send the client a `CSVCMsg_GetCvarValue` (fields `cookie` and `cvar_name`) and read the answer, a `CCLCMsg_RespondCvarValue` with the same cookie and a status (`EQueryCvarValueStatus`: value intact, not found, not a ConVar, protected). The reply arrives asynchronously; FUNPLAY's AntiCheat catches it with a KHook on `CServerSideClientBase::ProcessRespondCvarValue`. Sending messages is covered under [Protobuf](/docs/protobuf), hooking under [Hooking](/docs/hooks). A script sends the query and catches the answer with `Source2ModNetworkMessages` — see [Common messages](/docs/protobuf/common-messages#engine-messages). See also [#see-also] * [ConVars](/docs/convars-commands/convars) * [`CBasePlayerController`](/docs/schema/entity/classes/CBasePlayerController) * [`ISource2ModConVars` reference](/docs/core-api/ISource2ModConVars) # Creating Entities (/docs/entities/creating-entities) Creating an entity is two steps: **create** it by classname, which gives you an object that exists but has not spawned, then **spawn** it. Between the two you set whatever the entity reads on spawn. Create and spawn [#create-and-spawn] ```cpp static CBaseEntity* CBaseEntity::CreateEntityByName(const char* pszClassName); template static T* CBaseEntity::New(const char* pszClassName); void CBaseEntity::DispatchSpawn(CEntityKeyValues* pEntityKeyValues = nullptr, HookChain eChain = HookChain::Run); ``` `New` is `CreateEntityByName` with the cast done for you, and every generated class has a non-template `New(classname)` returning its own type. The cast is not checked, so the classname has to be one that really creates a `T`. Both return `nullptr` when the entity could not be created. Scripts call `CBaseEntity.CreateEntityByName` (or `Source2ModEntities.CreateEntityByName`) and `view_as` the result to the class they want; it is `NULL_ENTITY` when the entity could not be created. ```cpp title="plugin.cpp" #include "source2mod/schema/entity/classes/CBaseModelEntity.h" CBaseModelEntity* pModel = CBaseEntity::New("prop_dynamic_override"); if (!pModel) return; pModel->SetModel("models/props/de_dust/hr_dust/dust_crates/dust_crate_style_01_32x32x32.vmdl"); pModel->DispatchSpawn(); const Vector vecOrigin = pPawn->GetAbsOrigin(); pModel->Teleport(&vecOrigin, nullptr, nullptr); ``` ```cpp title="plugin.sp" CBaseModelEntity model = view_as(CBaseEntity.CreateEntityByName("prop_dynamic_override")); if (model == NULL_ENTITY) return; model.SetModel("models/props/de_dust/hr_dust/dust_crates/dust_crate_style_01_32x32x32.vmdl"); model.DispatchSpawn(); float vecOrigin[3]; pawn.GetAbsOrigin(vecOrigin); model.Teleport(vecOrigin, NULL_VECTOR, NULL_VECTOR); ``` `CREATE_ENTITY(classname)` and `g_pSource2ModEntities->CreateEntityByName()` are the same call through [ISource2ModEntities](/docs/core-api/ISource2ModEntities). Fields you set **before** `DispatchSpawn` are what the entity spawns with; some entities read their state only on spawn, others keep reading it. The FUNPLAY plugins set up a `point_worldtext` completely before spawning it: ```cpp CPointWorldText* pText = CBaseEntity::New("point_worldtext"); if (!pText) return; V_strncpy(pText->m_messageText, "Hello", 512); pText->m_messageText.NetworkStateChanged(); pText->m_bEnabled = true; pText->m_flFontSize = 48.0f; pText->m_Color = Color(255, 255, 255, 255); pText->m_bFullbright = true; pText->m_flWorldUnitsPerPx = 0.01f; pText->DispatchSpawn(); ``` ```cpp title="plugin.sp" CPointWorldText text = view_as(CBaseEntity.CreateEntityByName("point_worldtext")); if (text == NULL_ENTITY) return; text.Set_m_messageText("Hello"); // writes the field and sends the change text.m_bEnabled = true; text.m_flFontSize = 48.0; int color[4] = { 255, 255, 255, 255 }; text.Set_m_Color(color); text.m_bFullbright = true; text.m_flWorldUnitsPerPx = 0.01; text.DispatchSpawn(); ``` Keyvalues [#keyvalues] Anything a mapper would set in Hammer can be passed as keyvalues. Allocate a `CEntityKeyValues` with `new` and hand it to `DispatchSpawn`; do not delete it yourself — the SDK's own `CCSCustomHudLayout::Create` passes it the same way. Scripts pass a `StringMap` of key to string value instead; `DispatchSpawn` copies its entries, so close the map afterwards. ```cpp #include "entitykeyvalues.h" auto* pKeyValues = new CEntityKeyValues(); pKeyValues->SetString("targetname", "my_trigger"); pKeyValues->SetString("model", "models/props/de_dust/hr_dust/dust_crates/dust_crate_style_01_32x32x32.vmdl"); pKeyValues->SetInt("spawnflags", 1); pKeyValues->SetVector("origin", vecOrigin); pEntity->DispatchSpawn(pKeyValues); ``` ```cpp title="plugin.sp" StringMap keyValues = new StringMap(); keyValues.SetString("targetname", "my_trigger"); keyValues.SetString("model", "models/props/de_dust/hr_dust/dust_crates/dust_crate_style_01_32x32x32.vmdl"); keyValues.SetString("spawnflags", "1"); char origin[64]; Source2ModApi.Format(origin, sizeof(origin), "%f %f %f", vecOrigin[0], vecOrigin[1], vecOrigin[2]); keyValues.SetString("origin", origin); entity.DispatchSpawn(keyValues); delete keyValues; // DispatchSpawn copied the entries ``` `CEntityKeyValues` also has `SetBool`, `SetFloat`, `SetColor` and `SetQAngle`. In a script every value is a string, as in Hammer. Moving and orienting [#moving-and-orienting] ```cpp void CBaseEntity::Teleport(const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity, HookChain eChain = HookChain::Run); ``` Pass `nullptr` for whatever should stay as it is. This is the right way to move anything, players included — it goes through the entity's own virtual. Reading position and motion: | Helper | Returns | | --------------------------------------- | ---------------------------------- | | `GetAbsOrigin()` | World position | | `GetAbsRotation()` / `GetAngRotation()` | World / local rotation | | `GetAbsVelocity()` | Velocity | | `CBaseModelEntity::GetEyePosition()` | Origin plus `m_vecViewOffset` | | `CCSPlayerPawn::GetEyeAngles()` | Where a player is actually looking | The setters (`SetAbsOrigin`, `SetAbsRotation`, `SetAngRotation`, `SetAbsVelocity`, `SetBaseVelocity`) write the scene node or velocity fields directly. For a real move prefer `Teleport`. Parenting and following go through inputs, the way maps do it: ```cpp pText->AcceptInput("SetParent", pPawn, nullptr, "!activator"); pModel->AcceptInput("FollowEntity", pPawn, pModel, "!activator"); ``` ```cpp title="plugin.sp" text.AcceptInput("SetParent", pawn, NULL_ENTITY, "!activator"); model.AcceptInput("FollowEntity", pawn, model, "!activator"); ``` Models, bodygroups, collision [#models-bodygroups-collision] ```cpp void CBaseModelEntity::SetModel(const char* pszModel, HookChain eChain = HookChain::Run); void CBaseModelEntity::SetBodyGroup(const char* pszName, int nValue); CUtlSymbolLarge CBaseModelEntity::GetModelName(); ``` A model that no map or stock content loads has to be precached before clients can show it; that happens in the `BuildGameSessionManifest` game system event (see [Game systems](/docs/guides/game-systems)). `SetBodyGroup` sends the `SetBodyGroup` input with `"name,value"`, as the engine has no direct setter. Collision (the script includes have no `COLLISION_GROUP_*` / `MOVETYPE_*` constants, so scripts pass the numbers): ```cpp pEntity->SetCollisionGroup(COLLISION_GROUP_DEBRIS); // also calls CollisionRulesChanged() uint8 nGroup = pEntity->GetCollisionGroup(); pEntity->SetMoveType(MOVETYPE_NOCLIP); // sets m_MoveType and m_nActualMoveType ``` ```cpp title="plugin.sp" entity.SetCollisionGroup(5); // COLLISION_GROUP_DEBRIS; also calls CollisionRulesChanged() int nGroup = entity.GetCollisionGroup(); entity.SetMoveType(7); // MOVETYPE_NOCLIP; sets m_MoveType and m_nActualMoveType ``` Removing [#removing] ```cpp void CBaseEntity::Remove(HookChain eChain = HookChain::Run); // UTIL_Remove ``` Or with the `Kill` input — immediately, or delayed through the I/O queue: ```cpp pEntity->AcceptInput("Kill"); pEntity->AddEntityIOEvent("Kill", nullptr, nullptr, "", 5.0f); // in five seconds ``` ```cpp title="plugin.sp" entity.AcceptInput("Kill"); entity.AddEntityIOEvent("Kill", NULL_ENTITY, NULL_ENTITY, "", 5.0); // in five seconds ``` The delayed form is handy for anything temporary: create it, spawn it, and queue its own removal in the same place. Particles [#particles] For an entity-attached effect, `DispatchParticleEffect` sends it without creating an entity: ```cpp void CBaseEntity::DispatchParticleEffect(const char* pszParticleName, ParticleAttachment_t nAttachType = ParticleAttachment_t::PATTACH_ABSORIGIN_FOLLOW, uint8 nAttachmentPoint = 0, bool bResetAllParticlesOnEntity = false, IRecipientFilter* pFilter = nullptr, HookChain eChain = HookChain::Run); ``` A null filter sends it to everyone. To show it to one player, pass a `CSingleRecipientFilter` from `source2mod/schema/recipientfilter.h`; a script passes a player mask (`int[2]`, the `PlayerMask_*` stocks) as `recipients`: ```cpp CSingleRecipientFilter filter(pController->GetPlayerSlot()); pPawn->DispatchParticleEffect("particles/my_effect.vpcf", ParticleAttachment_t::PATTACH_ABSORIGIN_FOLLOW, 0, false, &filter); ``` ```cpp title="plugin.sp" int recipients[2]; PlayerMask_Clear(recipients); PlayerMask_Add(recipients, controller.GetSlot()); pawn.DispatchParticleEffect("particles/my_effect.vpcf", 1, 0, false, recipients); // 1 = PATTACH_ABSORIGIN_FOLLOW ``` For an effect with its own lifetime and position, create an `info_particle_system` (`CParticleSystem`) instead: ```cpp title="plugin.cpp" auto* pParticle = CBaseEntity::New("info_particle_system"); if (!pParticle) return; // CUtlSymbolLarge keeps the pointer it is given: pass a string that outlives // the entity, like this literal. pParticle->m_iszEffectName() = "particles/blood_impact/blood_pool.vpcf"; pParticle->DispatchSpawn(); pParticle->Teleport(&vecOrigin, nullptr, nullptr); pParticle->AcceptInput("Start"); pParticle->AddEntityIOEvent("Kill", pParticle, nullptr, "", 1.0f); ``` ```cpp title="plugin.sp" CParticleSystem particle = view_as(CBaseEntity.CreateEntityByName("info_particle_system")); if (particle == NULL_ENTITY) return; // m_iszEffectName has no setter in scripts; the "effect_name" keyvalue // sets it on spawn. StringMap keyValues = new StringMap(); keyValues.SetString("effect_name", "particles/blood_impact/blood_pool.vpcf"); particle.DispatchSpawn(keyValues); delete keyValues; particle.Teleport(vecOrigin, NULL_VECTOR, NULL_VECTOR); particle.AcceptInput("Start"); particle.AddEntityIOEvent("Kill", particle, NULL_ENTITY, "", 1.0); ``` Grenades [#grenades] Each grenade projectile class has `EmitGrenade`, which spawns a live, thrown grenade the way the game does when a player throws one: ```cpp static CHEGrenadeProjectile* CHEGrenadeProjectile::EmitGrenade( const Vector& vecPosition, const QAngle& angAngle, const Vector& vecVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex, HookChain eChain = HookChain::Run); ``` `CFlashbangProjectile`, `CDecoyProjectile` and `CMolotovProjectile` take the same arguments. `CSmokeGrenadeProjectile::EmitGrenade` has an extra `int nTeam` before the owner: ```cpp static CSmokeGrenadeProjectile* CSmokeGrenadeProjectile::EmitGrenade( const Vector& vecPosition, const QAngle& angAngle, const Vector& vecVelocity, int nTeam, CBaseEntity* pOwner, uint32_t nItemDefIndex, HookChain eChain = HookChain::Run); ``` `pOwner` is who gets credit for the grenade (usually a player's pawn) and `nItemDefIndex` is the weapon's item definition index. The velocity is used as both the linear and the angular velocity, as in SwiftlyS2's implementation this is ported from. ```cpp #include "source2mod/schema/entity/classes/CHEGrenadeProjectile.h" const Vector vecStart = pPawn->GetEyePosition(); const Vector vecVelocity(0.0f, 0.0f, 300.0f); CHEGrenadeProjectile::EmitGrenade(vecStart, QAngle(0, 0, 0), vecVelocity, pPawn, 44); ``` ```cpp title="plugin.sp" float vecStart[3]; pawn.GetEyePosition(vecStart); float vecVelocity[3] = { 0.0, 0.0, 300.0 }; float angles[3]; // 0 0 0 CHEGrenadeProjectile.EmitGrenade(vecStart, angles, vecVelocity, pawn, 44); ``` HookChain [#hookchain] Every helper above that calls a game function takes a `HookChain` last. The default, `HookChain::Run`, goes through other plugins' hooks on that function as if the game made the call; `HookChain::Bypass` goes straight to the original. See [Players](/docs/entities/players#hookchain) for when to use which. Scripts pass `HookChain_Run` or `HookChain_Bypass`; the default, `HookChain_Default`, keeps the C++ default. # Damage (/docs/entities/damage) All damage in CS2 — bullets, knives, grenades, falling, the world — goes through one function, `CBaseEntity::TakeDamageOld` (the game's own asserts call it that). It takes a `CTakeDamageInfo` describing the hit and a `CTakeDamageResult` it fills in. Hook it to change or block damage; call it to deal damage yourself. CTakeDamageInfo [#ctakedamageinfo] ```cpp #include "source2mod/schema/takedamageinfo.h" CTakeDamageInfo(); CTakeDamageInfo(CBaseEntity* pInflictor, CBaseEntity* pAttacker, CBaseEntity* pAbility, float flDamage, DamageTypes_t bitsDamageType); HitGroup_t GetHitGroup() const; ``` Both constructors call the game's own constructor, so every field you do not set has the game's default. The fields you will use: | Field | Type | Meaning | | ------------------------------------------------------------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------- | | `m_flDamage` | `float` | Damage to apply. Change it in a Pre hook to scale the hit. | | `m_bitsDamageType` | `int32_t` | `DMG_*` bits: `DMG_BULLET`, `DMG_FALL`, `DMG_BLAST`, `DMG_BURN`, … | | `m_hAttacker` | `CHandle` | Who is credited — for players, their **pawn**. | | `m_hInflictor` | `CHandle` | What did it — the pawn for a bullet, the projectile for a grenade. | | `m_hAbility` | `CHandle` | The weapon, when there is one. | | `m_vecDamagePosition`, `m_vecDamageForce`, `m_vecDamageDirection`, `m_vecReportedPosition` | `Vector` | Where and which way. | | `m_nDamageFlags` | `TakeDamageFlags_t` | `DFLAG_*` flags, e.g. `DFLAG_SUPPRESS_DAMAGE_MODIFICATION`, `DFLAG_PREVENT_DEATH`. | | `m_iHitGroupId` | `HitGroup_t` | Hit group field. | | `m_flOriginalDamage` | `float` | Damage before modification. | | `m_pTrace` | `CGameTrace*` | The trace that caused the hit, when there is one. | | `m_iDamageCustom`, `m_iAmmoType`, `m_bShouldBleed`, `m_bShouldSpark`, `m_AttackerInfo`, … | | See the [reference](/docs/schema/Takedamageinfo). | Scripts cannot construct one: they get it from the TakeDamage hook (`ctx.info`), with these fields as properties — the vectors and `m_nDamageFlags` as methods filling a buffer, the handles as entities. `GetHitGroup()` reads the hit group from the attached hit data through a gamedata offset; it returns `HITGROUP_INVALID` when that is unavailable and `HITGROUP_GENERIC` when the hit carries no group. Use it rather than `m_iHitGroupId` for "was this a headshot" (scripts compare to the number, `HITGROUP_HEAD` is 1): ```cpp if (pInfo->GetHitGroup() == HitGroup_t::HITGROUP_HEAD) // ... ``` ```cpp title="plugin.sp" if (info.GetHitGroup() == 1) // HITGROUP_HEAD { // ... } ``` CTakeDamageResult [#ctakedamageresult] ```cpp #include "source2mod/schema/takedamageresult.h" CTakeDamageResult(float damage); void CopyFrom(CTakeDamageInfo* pInfo); ``` What the game fills in while applying damage: `m_nHealthLost`, `m_nHealthBefore`, `m_flDamageDealt`, `m_flPreModifiedDamage`, `m_nTotalledHealthLost`, `m_flTotalledDamageDealt`, `m_bWasDamageSuppressed` and more. There is no default constructor — build it from a damage value, and call `CopyFrom(&info)` to point it at your info and copy the damage over when you call `TakeDamageOld` yourself. See the [reference](/docs/schema/Takedamageresult) for every field. Hooking TakeDamageOld [#hooking-takedamageold] `ADDR_TAKE_DAMAGE_OLD()` returns the resolved function; hook it with `KHOOK_MEMBER`, which evaluates the address when `KHOOK_INIT()` runs. Scripts do not hook it themselves: `Source2ModGameHooks.HookTakeDamage` adds a handler to the hook the core already has on it, so the class declaration is C++ only: ```cpp title="plugin.h" #include "source2mod/ISource2ModAddresses.h" #include "source2mod/ISource2ModHooks.h" #include "source2mod/schema/takedamageinfo.h" #include "source2mod/schema/takedamageresult.h" class MyPlugin : public ISource2ModPlugin { public: KHook::Return Hook_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); KHOOK_MEMBER(m_hTakeDamageOld, ADDR_TAKE_DAMAGE_OLD(), &MyPlugin::Hook_TakeDamageOld, nullptr); }; ``` ```cpp title="plugin.cpp" bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) { SOURCE2MOD_SAVEVARS(); KHOOK_INIT(); return true; } bool MyPlugin::Unload(char* error, size_t maxlen) { KHOOK_DESTRUCT(); return true; } ``` ```cpp title="plugin.sp" public void OnPluginStart() { // The core hooks CBaseEntity::TakeDamageOld from its own gamedata; // false = Pre. The hook goes when the plugin unloads. Source2ModGameHooks.HookTakeDamage(OnTakeDamage, false); } ``` The hooked entity arrives first — for player damage that is the victim's **pawn** (classname `player`), not the controller. The handler returns a `KHook::Return`; a script handler gets a `TakeDamageContext` (`ctx.entity`, `ctx.info`, `ctx.damageResult`) and returns a `Source2ModAction`, with the function's return value in `result`. The script includes have no `DMG_*` constants, so define the bits you test: ```cpp title="plugin.cpp" KHook::Return MyPlugin::Hook_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult) { if (!pInfo) return { KHook::Action::Ignore, 0 }; // No fall damage. if (pInfo->m_bitsDamageType & DMG_FALL) return { KHook::Action::Supersede, 0 }; // Halve everything else. This is a Pre hook: the original runs after // it, with whatever pInfo holds by then. pInfo->m_flDamage *= 0.5f; return { KHook::Action::Ignore, 0 }; } ``` ```cpp title="plugin.sp" #define DMG_FALL (1 << 5) // DamageTypes_t; the includes have no DMG_* constants public Source2ModAction OnTakeDamage(TakeDamageContext ctx, bool post, int &result) { CTakeDamageInfo info = ctx.info; // No fall damage. if (info.m_bitsDamageType & DMG_FALL) { result = 0; return Action_Supersede; } // Halve everything else. This is a Pre hook: the original runs after // it, with whatever the info holds by then. info.m_flDamage *= 0.5; return Action_Ignore; } ``` * `Ignore` — the original runs (with your changes to `pInfo`). * `Supersede` — the original does not run: no damage at all. * To act on the result, run the original yourself and supersede so it does not run twice: ```cpp const int64_t ret = m_hTakeDamageOld.CallOriginal(pThis, pInfo, pResult); // pResult is filled in now return { KHook::Action::Supersede, ret }; ``` ```cpp title="plugin.sp" result = ctx.CallOriginal(); // ctx.damageResult is filled in now return Action_Supersede; ``` Finding the players involved — the attacker handle points at a pawn: ```cpp CCSPlayerController* pVictim = nullptr; if (!V_strcmp(pThis->GetClassname(), "player")) pVictim = static_cast(pThis)->GetController(); CCSPlayerController* pAttacker = nullptr; if (CBaseEntity* pEnt = pInfo->m_hAttacker.Get(); pEnt && !V_strcmp(pEnt->GetClassname(), "player")) pAttacker = static_cast(pEnt)->GetController(); ``` ```cpp title="plugin.sp" CTakeDamageInfo info = ctx.info; char classname[64]; CCSPlayerController victim = NULL_ENTITY; ctx.entity.GetClassname(classname, sizeof(classname)); if (Source2ModUtils.Equal(classname, "player")) victim = view_as(ctx.entity).GetController(); CCSPlayerController attacker = NULL_ENTITY; CBaseEntity attackerEnt = info.m_hAttacker; if (attackerEnt != NULL_ENTITY) { attackerEnt.GetClassname(classname, sizeof(classname)); if (Source2ModUtils.Equal(classname, "player")) attacker = view_as(attackerEnt).GetController(); } ``` The hooks section covers handlers, `CallOriginal` and Post hooks in depth: [Hooking](/docs/hooks). Dealing damage [#dealing-damage] Player to player [#player-to-player] The simplest route is the controller helper: ```cpp void CCSPlayerController::TakeDamage(CCSPlayerController* pAttacker, int iDamage, DamageTypes_t bitsDamageType, HookChain eChain = HookChain::Bypass); ``` ```cpp pVictim->TakeDamage(pAttacker, 25, DMG_GENERIC); ``` ```cpp title="plugin.sp" victim.TakeDamage(attacker, 25, 0); // 0 = DMG_GENERIC ``` It builds the info with the attacker's pawn as inflictor and attacker, sets `DFLAG_SUPPRESS_DAMAGE_MODIFICATION` so armor and scaling leave the number alone, and calls `TakeDamageOld` on the victim's pawn. It needs both players connected with a pawn and the victim alive; otherwise it does nothing. By default it **bypasses** `TakeDamageOld` hooks — yours and other plugins' — because it is the server dealing damage, not the game. Pass `HookChain::Run` (`HookChain_Run` in a script) when it stands in for a weapon and other plugins' damage rules should apply. Anything else [#anything-else] For damage with no attacker player, a different inflictor, or fields the helper does not set, build the info yourself and call the function. The TroubleTerroristTown plugin's explosions do it like this: ```cpp title="explosions.cpp" CTakeDamageInfo info(nullptr, pAttackerPawn, nullptr, flDamage, DMG_BLAST); info.m_vecReportedPosition = vecTargetOrigin; CTakeDamageResult result(flDamage); result.CopyFrom(&info); SOURCE2MOD_ORIGINAL(ADDR_TAKE_DAMAGE_OLD())(pVictimPawn, &info, &result); ``` `SOURCE2MOD_ORIGINAL` calls the function past every hook on it — the same as `HookChain::Bypass`. Call `ADDR_TAKE_DAMAGE_OLD()(…)` without it to run the hook chain. Resolve it at the call, not once into a global: the original behind a hook only exists once the hook does. This route is C++ only: scripts cannot build a `CTakeDamageInfo`, so they deal damage through `CCSPlayerController.TakeDamage`. Damage a player through their **pawn**. Calling `TakeDamageOld` on the controller reaches an entity whose `m_iHealth` is 0. # Entity I/O (/docs/entities/entity-io) Entity I/O is how maps wire entities together: a button's `OnPressed` **output** fires a door's `Open` **input**. Plugins can take part on both ends — send inputs as if a map did, and listen to outputs as they fire, with the option to stop them. Sending inputs [#sending-inputs] ```cpp void CBaseEntity::AcceptInput(const char* pszInput, CEntityInstance* pActivator = nullptr, CEntityInstance* pCaller = nullptr, const char* pszValue = "", HookChain eChain = HookChain::Run); ``` The input runs immediately. The arguments are what a map connection passes: the input name, the entity that started the chain (**activator**), the entity whose output is firing (**caller**), and the parameter as a string. ```cpp pDoor->AcceptInput("Open"); pBreakable->AcceptInput("Break"); pEntity->AcceptInput("Kill"); pModel->AcceptInput("FollowEntity", pPawn, pModel, "!activator"); pText->AcceptInput("SetParent", pPawn, nullptr, "!activator"); // parent to the activator ``` ```cpp title="plugin.sp" door.AcceptInput("Open"); breakable.AcceptInput("Break"); entity.AcceptInput("Kill"); model.AcceptInput("FollowEntity", pawn, model, "!activator"); text.AcceptInput("SetParent", pawn, NULL_ENTITY, "!activator"); // parent to the activator ``` `!activator` in the value is resolved against the activator you pass, as it is in a map — that is how parenting and following are done. Delayed inputs [#delayed-inputs] ```cpp void CBaseEntity::AddEntityIOEvent(const char* pszInput, CEntityInstance* pActivator = nullptr, CEntityInstance* pCaller = nullptr, const char* pszValue = "", float flDelay = 0.0f, HookChain eChain = HookChain::Run); ``` Same arguments plus a delay in seconds. The event goes into the engine's I/O queue, like a connection with a delay set in Hammer. If the entity is removed first, the event is dropped with it — which makes this the easy way to give something a lifetime: ```cpp pParticle->AddEntityIOEvent("Kill", nullptr, nullptr, "", 1.0f); ``` ```cpp title="plugin.sp" particle.AddEntityIOEvent("Kill", NULL_ENTITY, NULL_ENTITY, "", 1.0); ``` Both are also on [ISource2ModEntities](/docs/core-api/ISource2ModEntities) (`Source2ModEntities` in a script), taking the target first: ```cpp ACCEPT_INPUT(pTarget, "Open", pActivator, pCaller); // value defaults to "" ADD_ENTITY_IO_EVENT(pTarget, "Kill", nullptr, nullptr, "", 5.0f); ``` ```cpp title="plugin.sp" Source2ModEntities.AcceptInput(target, "Open", activator, caller); // value defaults to "" Source2ModEntities.AddEntityIOEvent(target, "Kill", NULL_ENTITY, NULL_ENTITY, "", 5.0); ``` The interface versions have no `HookChain` and always run the chain. Listening to outputs [#listening-to-outputs] Source2Mod hooks the engine's output dispatch once and hands each firing to the listeners that match it. There are two ways to listen. For a class of entities: IEntityIOListener [#for-a-class-of-entities-ientityiolistener] Implement `IEntityIOListener` (from `source2mod/schema/entityio.h`) and register it for a classname and an output. A script registers a handler function (`Source2ModEntityOutputHandler`) instead, so the class is C++ only: ```cpp title="plugin.h" class MyPlugin : public ISource2ModPlugin, public IEntityIOListener { public: Action OnEntityOutput(const char* pchOutputName, CEntityInstance* pActivator, CEntityInstance* pCaller, float flDelay, bool post) override; }; ``` ```cpp title="plugin.cpp" // In Load(): ADD_ENTITY_IO_LISTENER(this, "func_button", "OnPressed"); // Pre ADD_ENTITY_IO_LISTENER(this, nullptr, "OnStartTouch", true); // Post, any class ``` ```cpp title="plugin.sp" // In OnPluginStart(): Source2ModEntities.AddEntityIOListener(OnEntityOutput, "func_button", "OnPressed"); // Pre Source2ModEntities.AddEntityIOListener(OnEntityOutput, "", "OnStartTouch", true); // Post, any class ``` ```cpp virtual void AddEntityIOListener(IEntityIOListener* pListener, const char* pchClassName, const char* pchOutputName, bool post = false) = 0; ``` `nullptr` (`""` in a script) for the classname or the output name means "any". The classname is matched against the **caller's** classname — the entity whose output fired. There is no plugin id argument: the listener belongs to the plugin whose library the listener object's class lives in, which the core finds through the object's vtable. In the callback: ```cpp title="plugin.cpp" Action MyPlugin::OnEntityOutput(const char* pchOutputName, CEntityInstance* pActivator, CEntityInstance* pCaller, float flDelay, bool post) { if (post) return Action::Ignore; // Nobody opens this button while a round is being set up. if (g_bSetupPhase && !V_strcmp(pchOutputName, "OnPressed")) return Action::Supersede; return Action::Ignore; } ``` ```cpp title="plugin.sp" public Source2ModAction OnEntityOutput(const char[] output, CEntityInstance activator, CEntityInstance caller, float delay, bool post) { if (post) return Action_Ignore; // Nobody opens this button while a round is being set up. if (g_bSetupPhase && Source2ModUtils.Equal(output, "OnPressed")) return Action_Supersede; return Action_Ignore; } ``` | Return (Pre) | Effect | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `Action::Ignore` | Nothing changed; the output fires. | | `Action::Override` | The output still fires. | | `Action::Supersede` | The output does **not** fire — the door stays shut, the button does nothing. Remaining Pre listeners and all Post listeners are skipped. | The Post return value is ignored. Listeners are removed for you when your plugin unloads; `REMOVE_ENTITY_IO_LISTENER(this, cls, out, post)` removes one earlier and must be given the same classname, output and `post` it was added with. A script keeps the id `Source2ModEntities.AddEntityIOListener` returns and passes it to `Source2ModEntities.RemoveEntityIOListener`; its actions are `Action_Ignore`, `Action_Override` and `Action_Supersede`. For one entity: AddSingleEntityIOListener [#for-one-entity-addsingleentityiolistener] When you care about one specific entity — a button you spawned, a trigger you created — attach a callback to it directly: ```cpp CEntityIOListenerHandle* CBaseEntity::AddSingleEntityIOListener( const char* pszOutput, std::function callback, bool post); ``` The callback has the same arguments and return value as `OnEntityOutput` and only fires when the caller is this entity: ```cpp title="plugin.cpp" std::unique_ptr g_pButtonListener; void HookButton(CBaseEntity* pButton) { g_pButtonListener.reset(pButton->AddSingleEntityIOListener("OnPressed", [](const char* pszOutput, CEntityInstance* pActivator, CEntityInstance* pCaller, float flDelay, bool post) { // pActivator is usually the player's pawn. return Action::Ignore; }, false)); } ``` ```cpp title="plugin.sp" int g_buttonListener = -1; void HookButton(CBaseEntity button) { g_buttonListener = button.AddSingleEntityIOListener("OnPressed", OnButtonPressed, false); } public Source2ModAction OnButtonPressed(const char[] output, CEntityInstance activator, CEntityInstance caller, float delay, bool post) { // activator is usually the player's pawn. return Action_Ignore; } ``` The returned handle owns the listener: **deleting it unregisters it** (or call `Unhook()`). Keep it for as long as you want the callback, and drop it when the entity goes away — the listener stays registered until you do, it just never matches again. In a script `AddSingleEntityIOListener` returns an id instead, which `Source2ModEntities.RemoveEntityIOListener` takes. Outputs on the entity [#outputs-on-the-entity] Outputs are fields too. `CBaseEntity` has `m_OnUser1` … `m_OnUser4` and `m_OnKilled`, and each class has its own (`CEntityIOOutput`). The struct in `entityio.h` exposes the output's descriptor (`m_pDesc->m_pName`) and its linked list of connections (`m_pConnections`), each with target, input, value override, delay and remaining fire count — enough to inspect how a map wired an entity. See the [entity I/O reference](/docs/schema/Entityio) for the full structs. # Finding Entities (/docs/entities/finding-entities) Most plugin code starts from something that identifies an entity — a command caller's slot, a game event's userid, a SteamID from your database, an entity index — and needs the typed object. This page lists the ways in. Players [#players] `CCSPlayerController` has static lookups for every way a player is identified: ```cpp #include "source2mod/schema/entity/classes/CCSPlayerController.h" static CCSPlayerController* FromSlot(int iSlot); static CCSPlayerController* FromSlot(CPlayerSlot slot); static CCSPlayerController* FromUserId(int iUserId); static CCSPlayerController* FromUserId(CPlayerUserId userId); static CCSPlayerController* FromSteamId(uint64 uSteamId); static CCSPlayerController* FromSteamId(CSteamID steamId); static CCSPlayerController* FromPawn(CCSPlayerPawn* pPawn); ``` All of them return `nullptr` when nothing matches. Scripts have the same statics on the `CCSPlayerController` methodmap — `FromSlot(CPlayerSlot)`, `FromUserId(int)`, `FromSteamId` (the SteamID64 as decimal text) and `FromPawn` — returning `NULL_ENTITY` instead. * `FromSlot` reads the entity at index `slot + 1` — controllers always occupy indices 1 to 64. The `CPlayerSlot` overload also returns null for an invalid slot (`-1`, which is what the server console has). * `FromUserId` and `FromSteamId` walk the slots up to `maxClients`. Bots have a SteamID of 0. * `FromPawn` follows the pawn's `m_hController` handle; the pawn must not be null. The command context gives you the caller's slot. The sample plugin's helper: ```cpp title="plugin.cpp" // A controller for a player, nullptr for the server console. static CCSPlayerController* CallerOf(const Source2ModCommandContext& context) { const CPlayerSlot slot = context.GetPlayerSlot(); return slot.IsValid() ? CCSPlayerController::FromSlot(slot) : nullptr; } ``` ```cpp title="plugin.sp" // A controller for a player, NULL_ENTITY for the server console. CCSPlayerController CallerOf(Source2ModCommandContext context) { CPlayerSlot slot = context.GetPlayerSlot(); return slot.IsValid() ? CCSPlayerController.FromSlot(slot) : NULL_ENTITY; } ``` For getting the player out of a game event, see [Events](/docs/events). Iterating players [#iterating-players] There is no player list to ask for — loop over the slots and skip the empty ones: ```cpp for (int i = 0; i < GetGlobalVars()->maxClients; ++i) { CCSPlayerController* pPlayer = CCSPlayerController::FromSlot(i); if (!pPlayer || pPlayer->m_iConnected() != PlayerConnectedState::Connected) continue; if (pPlayer->IsBot()) continue; // ... } ``` ```cpp title="plugin.sp" for (int i = 0; i < Source2ModApi.GetGlobalVars().maxClients; ++i) { CCSPlayerController player = CCSPlayerController.FromSlot(CPlayerSlot(i)); if (player == NULL_ENTITY || !Source2ModClients.IsInGame(CPlayerSlot(i))) continue; if (player.IsBot()) continue; // ... } ``` A controller exists from the moment a client starts connecting until it has fully left, so `m_iConnected` is worth checking when you are about to message or act on the player (a script can ask `Source2ModClients.IsInGame(slot)`). For "alive players", also check `m_bPawnIsAlive()` and that `GetPlayerPawn()` is not null. From controller to everything else [#from-controller-to-everything-else] | You want | Call | | ----------------- | ------------------------------------------------------------- | | The body | `GetPlayerPawn()` → `CCSPlayerPawn*` | | The observer pawn | `GetObserverPawn()` → `CCSObserverPawn*` | | Slot | `GetSlot()` (`int`) / `GetPlayerSlot()` (`CPlayerSlot`) | | Userid | `GetUserID()` (`int`) / `GetPlayerUserID()` (`CPlayerUserId`) | | SteamID | `GetSteamID()` (`uint64`) / `GetPlayerSteamID()` (`CSteamID`) | | Name | `GetPlayerName()` | | IP address | `GetIpAddress()` → `CUtlString`, without the port | | Engine client | `GetServerSideClient()` → `CServerSideClient*` | In a script, the text getters fill a buffer (`GetPlayerName(name, sizeof(name))`, `GetSteamID`, `GetIpAddress`); there is no `GetServerSideClient()`. Any entity by index [#any-entity-by-index] `CBaseEntity::FromIndex` looks an entity up by index and casts it to the type you name; every generated class also has a non-template `FromIndex` returning its own type. A script has `CBaseEntity.FromIndex(int)` and `view_as` for the type: ```cpp CBaseEntity* pEntity = CBaseEntity::FromIndex(nIndex); CCSPlayerPawn* pPawn = CCSPlayerPawn::FromIndex(nIndex); // same, typed ``` ```cpp title="plugin.sp" CBaseEntity entity = CBaseEntity.FromIndex(index); CCSPlayerPawn pawn = view_as(CBaseEntity.FromIndex(index)); // typed ``` Both take an `int` or a `CEntityIndex`. **No type check is done** — the cast is only right if the entity at that index really is that class. Check `GetClassname()` when you are not sure. By classname, name or crosshair [#by-classname-name-or-crosshair] These live on [ISource2ModEntities](/docs/core-api/ISource2ModEntities), with macros that fill in the `g_pSource2ModEntities` call; a script calls the `Source2ModEntities` statics: ```cpp #include "source2mod/ISource2ModEntities.h" FIND_ENTITY_BY_CLASSNAME(start, "func_breakable") // FindEntityByClassname FIND_ENTITY_BY_NAME(start, "door_main") // FindEntityByName (targetname) FIND_PICKER_ENTITY(pController) // FindPickerEntity: under the crosshair GET_GAME_RULES() // the live CCSGameRules, or null ``` ```cpp title="plugin.sp" Source2ModEntities.FindEntityByClassname(start, "func_breakable") // NULL_ENTITY when none Source2ModEntities.FindEntityByName(start, "door_main") // by targetname Source2ModEntities.FindPickerEntity(controller) // under the crosshair Source2ModEntities.GetGameRules() // the live CCSGameRules ``` The `Find*` functions return one match at a time. Pass `nullptr` (`NULL_ENTITY` in a script) to start and the previous match to continue: ```cpp title="plugin.cpp" int nBroken = 0; CBaseEntity* pEntity = nullptr; while ((pEntity = FIND_ENTITY_BY_CLASSNAME(pEntity, "func_breakable")) != nullptr) { pEntity->AcceptInput("Break"); nBroken++; } ``` ```cpp title="plugin.sp" int broken = 0; CBaseEntity entity = NULL_ENTITY; while ((entity = Source2ModEntities.FindEntityByClassname(entity, "func_breakable")) != NULL_ENTITY) { entity.AcceptInput("Break"); broken++; } ``` `FindEntityByName` has optional searching-entity, activator, caller and filter arguments, the same as the engine function it wraps (in a script, all but the filter). All at once [#all-at-once] `UTIL_FindAllEntitiesByDesignerName` (in `ISource2ModEntities.h`) returns a `std::vector` of every live entity whose designer name matches exactly. It walks the entity system's active list directly. A script gets the same as an `ArrayList` of entities from `Source2ModEntities.FindAllEntitiesByDesignerName`, and closes it: ```cpp for (auto* pTeam : UTIL_FindAllEntitiesByDesignerName("cs_team_manager")) { // ... } for (auto* pProp : UTIL_FindAllEntitiesByDesignerName("prop_dynamic")) pProp->AcceptInput("Kill"); ``` ```cpp title="plugin.sp" ArrayList teams = Source2ModEntities.FindAllEntitiesByDesignerName("cs_team_manager"); for (int i = 0; i < teams.Length; i++) { CTeam team = teams.Get(i); // ... } delete teams; ArrayList props = Source2ModEntities.FindAllEntitiesByDesignerName("prop_dynamic"); for (int i = 0; i < props.Length; i++) view_as(props.Get(i)).AcceptInput("Kill"); delete props; ``` Like `FromIndex`, the cast is unchecked — keep the name and the type consistent. Watching entities come and go [#watching-entities-come-and-go] Instead of searching, you can be told. Implement `IEntityListener` (from the engine's `entity2/entitysystem.h`) and register it with `ADD_ENTITY_LISTENER`. A script passes one function per callback to `Source2ModEntities.AddEntityListener` (`INVALID_FUNCTION` for the ones it does not need): ```cpp title="plugin.h" class MyPlugin : public ISource2ModPlugin, public IEntityListener { void OnEntityCreated(CEntityInstance* pEntity) override; void OnEntitySpawned(CEntityInstance* pEntity) override; void OnEntityDeleted(CEntityInstance* pEntity) override; void OnEntityParentChanged(CEntityInstance* pEntity, CEntityInstance* pNewParent) override; }; ``` ```cpp title="plugin.sp" public void OnEntityCreated(CEntityInstance entity) {} public void OnEntitySpawned(CEntityInstance entity) {} public void OnEntityDeleted(CEntityInstance entity) {} public void OnEntityParentChanged(CEntityInstance entity, CEntityInstance newParent) {} ``` ```cpp title="plugin.cpp" ADD_ENTITY_LISTENER(this); // in Load() ``` ```cpp title="plugin.sp" int listener = Source2ModEntities.AddEntityListener(OnEntityCreated, OnEntitySpawned, OnEntityDeleted, OnEntityParentChanged); // in OnPluginStart() ``` `OnEntityCreated` is early — the keyvalues have not been applied and the entity has not spawned. `OnEntitySpawned` is usually the one you want. Some entities are filled in by the game right after they spawn, so a change made there can be overwritten; the sample plugin waits one frame, through a handle: ```cpp title="plugin.cpp" void SamplePlugin::OnEntitySpawned(CEntityInstance* pEntity) { if (V_strcmp(pEntity->GetClassname(), "smokegrenade_projectile") != 0) return; CHandle hSmoke = static_cast(pEntity)->GetHandle(); NEXT_FRAME([hSmoke]() { if (CSmokeGrenadeProjectile* pSmoke = hSmoke.Get()) pSmoke->m_vSmokeColor = Vector(170.0f, 0.0f, 255.0f); }); } ``` ```cpp title="plugin.sp" public void OnEntitySpawned(CEntityInstance entity) { char classname[64]; entity.GetClassname(classname, sizeof(classname)); if (!Source2ModUtils.Equal(classname, "smokegrenade_projectile")) return; // The entity is a handle: if the smoke is gone by the next frame, // IsValid() says so. Source2ModScheduler.NextFrame(PaintSmoke, entity); } public void PaintSmoke(any data) { CSmokeGrenadeProjectile smoke = view_as(data); if (!smoke.IsValid()) return; float purple[3] = { 170.0, 0.0, 255.0 }; smoke.Set_m_vSmokeColor(purple); } ``` `AddEntityListener(IEntityListener* pListener)` takes no plugin id; the core works out which plugin the listener belongs to from the object's vtable, i.e. the library its class lives in. The listener is removed for you when that plugin unloads; `REMOVE_ENTITY_LISTENER(this)` removes it earlier. In a script, the id `AddEntityListener` returns is what `Source2ModEntities.RemoveEntityListener` takes, and the listener also goes when the plugin unloads. # Entities (/docs/entities) Almost everything on a CS2 server is an entity: players, weapons, grenades, doors, triggers, the game rules, even a Panorama HUD layout. The SDK gives you every one of them as a C++ class generated from the game's own schema, so you work with `CCSPlayerPawn*` and `pPawn->m_iHealth` instead of raw offsets. This section covers finding entities, reading and writing their fields, creating new ones, the common player helpers, entity I/O and damage. Players by slot, userid or SteamID; entities by index, classname or name; handles. Reading and writing fields, when a change is networked, and adding your own helpers. New, DispatchSpawn, keyvalues, Teleport, SetModel, particles and grenades. Respawn, teams, damage, items and weapons, printing, kicking — and HookChain. Inputs, queued events and output listeners. CTakeDamageInfo, hooking TakeDamageOld and dealing damage yourself. Controller and pawn [#controller-and-pawn] A CS2 player is **two** entities: | Entity | Class | Classname | Lives | | ---------- | --------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | Controller | `CCSPlayerController` | `cs_player_controller` | From connect to disconnect. Holds the name, SteamID, team, score, money. | | Pawn | `CCSPlayerPawn` | `player` | The body in the world. Holds health, armor, position, weapons. Replaced or reused across deaths and respawns. | The controller is the one to keep track of a *player*; the pawn is the one to act on when something happens to their *body*. They point at each other: ```cpp CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); // m_hPlayerPawn CCSPlayerController* pOwner = pPawn->GetController(); // m_hController CCSPlayerController* pSame = CCSPlayerController::FromPawn(pPawn); ``` ```cpp title="plugin.sp" CCSPlayerPawn pawn = controller.GetPlayerPawn(); // m_hPlayerPawn CCSPlayerController owner = pawn.GetController(); // m_hController CCSPlayerController same = CCSPlayerController.FromPawn(pawn); ``` A dead or spectating player also has an observer pawn (`CCSObserverPawn`, `GetObserverPawn()`). `CBasePlayerController::GetPawn()` returns `m_hPawn`, whichever pawn the controller is currently driving, cast to `CCSPlayerPawn*` — when you mean "the player's body", use `GetPlayerPawn()`. Health lives on the pawn. The controller has `m_iPawnHealth` and `m_bPawnIsAlive`, which the game mirrors for the scoreboard; read them, but change health on the pawn. Services [#services] A lot of behaviour is not on the pawn or controller itself but on **service** objects hanging off them as pointers: | Pointer | Class | For | | --------------------------------------- | ----------------------------------------- | ------------------------------------------------------------- | | `pPawn->m_pItemServices()` | `CPlayer_ItemServices` | `GiveNamedItem`, `RemoveWeapons` | | `pPawn->m_pWeaponServices()` | `CPlayer_WeaponServices` | `m_hMyWeapons`, `m_hActiveWeapon`, `DropWeapon`, `SelectItem` | | `pController->m_pInGameMoneyServices()` | `CCSPlayerController_InGameMoneyServices` | `m_iAccount` (money) | Any of these can be null on a pawn that does not have them, so check before use. In a script they are properties (`pawn.m_pItemServices`), and a missing one reads `IsNull`. Service fields are networked through the entity that owns them; writing one works exactly like writing a field on the entity (see [Schema fields](/docs/entities/schema-fields)). Indices, slots and handles [#indices-slots-and-handles] | Type | What it is | Get it | | --------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | `CEntityIndex` | Position in the entity list (`.Get()` for the int). Reused after the entity is gone. | `pEntity->GetEntityIndex()`, `GetIndex()` for the int | | `CPlayerSlot` | Player slot, 0-based. A controller's entity index is always slot + 1. | `pController->GetPlayerSlot()`, `GetSlot()` for the int | | `CPlayerUserId` | The engine's userid, as game events carry it. | `pController->GetPlayerUserID()`, `GetUserID()` for the int | | `CHandle` | Index **plus** a serial number. Resolves to null once the entity is removed, even if the index is reused. | `pEntity->GetHandle()` | The rule: a raw pointer is only good for the current call. Anything that crosses a frame — a timer, a `NEXT_FRAME` lambda, a map in your plugin — holds a `CHandle` and checks it when it comes back. A script has no raw pointers: every entity value already is a handle, kept as it is and checked with `IsValid()`: ```cpp title="plugin.cpp" CHandle hPlayer = pController->GetHandle(); g_pSource2ModScheduler->AddTimer(3.0f, [hPlayer]() { CCSPlayerController* pPlayer = hPlayer.Get(); if (!pPlayer) return; // disconnected in the meantime pPlayer->PrintToChat("Three seconds later"); }); ``` ```cpp title="plugin.sp" void RemindLater(CCSPlayerController controller) { // The entity value is a handle already: pass it on as it is. Source2ModScheduler.AddTimer(3.0, Timer_Later, controller); } public void Timer_Later(any data) { CCSPlayerController player = view_as(data); if (!player.IsValid()) return; // disconnected in the meantime player.PrintToChat("Three seconds later"); } ``` In C++, every generated entity class shadows `GetHandle()` with one returning a handle of its own type, so `pController->GetHandle()` is a `CHandle` without a template argument. How the SDK exposes schema classes [#how-the-sdk-exposes-schema-classes] The classes under `source2mod/schema/entity/classes/` are generated from a dump of the game's schema by `tools/schemagen/generator.py`, one header per class, following the game's own inheritance (`CCSPlayerPawn` → `CCSPlayerPawnBase` → `CBasePlayerPawn` → … → `CBaseEntity` → `CEntityInstance`). Each class carries: * **Fields** — one `SCHEMA_FIELD` per schema member. The offset is looked up by name in the running game's schema the first time you touch it, so a CS2 update that moves a field does not break your binary. * **Helpers** — hand-written methods (`GiveNamedItem`, `Teleport`, `PrintToChat`, …) that call into the game. They are declared by the generator so they survive regeneration and implemented in the SDK's `src/schema/entity/classes/*.cpp`. * **Static constructors** — `New(classname)`, `FromIndex(index)` and a typed `GetHandle()` on every entity class. Scripts get the same classes as methodmaps of the same names and bases (`source2mod/schema/classes.inc`, generated too): fields are properties (`pawn.m_iHealth`), helpers are methods, and `FromIndex` is on `CBaseEntity`. Include the header of the class you use; a script includes them all at once: ```cpp #include "source2mod/schema/entity/classes/CCSPlayerController.h" #include "source2mod/schema/entity/classes/CCSPlayerPawn.h" ``` ```cpp title="plugin.sp" #include // every schema class, as a methodmap of the same name ``` `dynamic_cast` works on game types: the SDK checks the cast against the engine's own RTTI and caches the answer, so a cast costs a few nanoseconds (see [Casting](/docs/development/cpp-for-csharp-developers#casting)). When you already know the type, `static_cast` is enough. In a script, check the classname (`GetClassname()`, e.g. `"cs_player_controller"`) and `view_as` the methodmap. The full list of classes, fields and helpers is in the [schema reference](/docs/schema/entity/classes/CBaseEntity); lookup and entity I/O functions are on [ISource2ModEntities](/docs/core-api/ISource2ModEntities). # Players (/docs/entities/players) The generated player classes carry helpers for the things plugins do all the time. This page groups them by task. Every signature here is copied from the SDK headers (C++); the script methodmaps have the same methods, with buffers for strings and `NULL_ENTITY` for a null entity. The [schema reference](/docs/schema/entity/classes/CCSPlayerController) has the complete list per class. HookChain [#hookchain] Most of these helpers call a game function that plugins also like to hook — `TakeDamage`, `GiveNamedItem`, `SwitchTeam`. So each one takes a last argument saying whether your call goes through those hooks: ```cpp title="ISource2ModTypes.h" enum class HookChain : uint8_t { Run, // through the Pre/Post chain, as if the game made the call Bypass, // straight to the original, past every hook }; ``` * **`HookChain::Run`** — right when your plugin stands in for the game: a custom weapon dealing damage, a round reward handing out an item. Other plugins' rules (a no-friendly-fire plugin, a weapon restriction) should apply, so they get to see and veto the call. * **`HookChain::Bypass`** — right when the server was *told* to do it: an admin command, a forced round end. Also required when you call the very function you are hooking from inside your own hook, where `Run` would recurse. Scripts pass `Source2ModHookChain`: `HookChain_Run`, `HookChain_Bypass`, or `HookChain_Default`, the default, which keeps the C++ helper's default. `Bypass` skips both inline detours and vtable hooks. The default of each helper is what it did before the argument existed, so omitting it keeps the old behaviour: | Helper | Default | | --------------------------------------------------------------------------------------------------------------- | ---------- | | `CCSPlayerController::TakeDamage` | **Bypass** | | `CCSGameRules::TerminateRound` | **Bypass** | | `CCSPlayerController::Respawn`, `SwitchTeam`, `ChangeTeam` | Run | | `CBasePlayerController::SetPawn` | Run | | `CBasePlayerPawn::CommitSuicide`, `SnapViewAngles` | Run | | `CCSPlayerPawn::PostThink` | Run | | `CPlayer_ItemServices::GiveNamedItem`, `RemoveWeapons`, `DropActivePlayerWeapon` | Run | | `CPlayer_WeaponServices::DropWeapon`, `BumpWeapon`, `SelectItem`, `Destroy` | Run | | `CAttributeList::SetOrAddAttribute`, `SetOrAddAttributeValueByName` | Run | | `CBaseEntity::AcceptInput`, `AddEntityIOEvent`, `DispatchSpawn`, `Teleport`, `Remove`, `DispatchParticleEffect` | Run | | `CBaseModelEntity::SetModel`, `CCSGameRules::GoToIntermission`, `C*Projectile::EmitGrenade` | Run | ```cpp pItems->GiveNamedItem("weapon_ak47"); // hooks run pVictim->TakeDamage(pAttacker, 50, DMG_BULLET, HookChain::Run); // opt in ``` ```cpp title="plugin.sp" items.GiveNamedItem("weapon_ak47"); // hooks run victim.TakeDamage(attacker, 50, 2 /* DMG_BULLET */, HookChain_Run); // opt in ``` For calling an `ADDR_*` function directly past its hooks there is `SOURCE2MOD_ORIGINAL(fn)`; see [Hooking](/docs/hooks). Pawn and life [#pawn-and-life] ```cpp CCSPlayerPawn* CCSPlayerController::GetPlayerPawn(); void CCSPlayerController::Respawn(HookChain eChain = HookChain::Run); void CBasePlayerPawn::CommitSuicide(bool bExplode, bool bForce, HookChain eChain = HookChain::Run); void CBasePlayerPawn::SnapViewAngles(const QAngle& angEyeAngles, HookChain eChain = HookChain::Run); ``` `Respawn` does nothing if the controller has no player pawn. It puts the player pawn back on the controller and runs the controller's respawn. `CommitSuicide(bExplode, bForce)` calls the pawn's own suicide virtual with the two flags as the game defines them; the sample plugin's slay command uses `CommitSuicide(false, true)`. `SnapViewAngles` sets where the player looks. It is a no-op on platforms where the engine function could not be resolved — currently it is only found on Linux. ```cpp title="plugin.cpp" if (CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); pPawn && pController->m_bPawnIsAlive()) { pPawn->m_iHealth = 200; pPawn->m_ArmorValue = 100; } ``` ```cpp title="plugin.sp" CCSPlayerPawn pawn = controller.GetPlayerPawn(); if (pawn != NULL_ENTITY && controller.m_bPawnIsAlive) { pawn.m_iHealth = 200; pawn.m_ArmorValue = 100; } ``` Teams [#teams] ```cpp void CCSPlayerController::SwitchTeam(int nTeam, HookChain eChain = HookChain::Run); void CCSPlayerController::ChangeTeam(int nTeam, HookChain eChain = HookChain::Run); ``` * **`SwitchTeam`** moves the player to the team without killing them. * **`ChangeTeam`** is the controller's `ChangeTeam` virtual — the `jointeam` path, the same thing as a player picking a team in the menu, with what that means for a living player. Team numbers are `CS_TEAM_NONE` (0), `CS_TEAM_SPECTATOR` (1), `CS_TEAM_T` (2) and `CS_TEAM_CT` (3), from the game's `shareddefs.h`; scripts use the numbers. The current team is `m_iTeamNum()`. Damage [#damage] ```cpp void CCSPlayerController::TakeDamage(CCSPlayerController* pAttacker, int iDamage, DamageTypes_t bitsDamageType, HookChain eChain = HookChain::Bypass); ``` Deals damage to this player's pawn as if `pAttacker`'s pawn did it. It does nothing unless both players are connected, the victim is alive and both have a pawn. The damage is flagged `DFLAG_SUPPRESS_DAMAGE_MODIFICATION`, so armor and the game's scaling do not change the number you asked for. It **bypasses** hooks by default, because damage a plugin was asked to deal is not the game dealing damage — a plugin policing hits in `TakeDamageOld` should not have to tell them apart. Pass `HookChain::Run` when you are standing in for a weapon. For damage without an attacker player, or with full control over the damage info, see [Damage](/docs/entities/damage). Items and weapons [#items-and-weapons] The item and weapon services hang off the pawn: ```cpp CPlayer_ItemServices* pItems = pPawn->m_pItemServices(); CPlayer_WeaponServices* pWeapons = pPawn->m_pWeaponServices(); ``` ```cpp title="plugin.sp" CPlayer_ItemServices items = pawn.m_pItemServices; CPlayer_WeaponServices weapons = pawn.m_pWeaponServices; ``` `CPlayer_ItemServices`: ```cpp CBasePlayerWeapon* GiveNamedItem(const char* pszItem, HookChain eChain = HookChain::Run); void RemoveWeapons(bool bRemoveSuit, HookChain eChain = HookChain::Run); void DropActivePlayerWeapon(CBasePlayerWeapon* pActiveWeapon, HookChain eChain = HookChain::Run); ``` `GiveNamedItem` takes an item classname (`"weapon_ak47"`, `"item_assaultsuit"`) and returns the weapon, or null. `DropActivePlayerWeapon` ignores its argument and drops whatever is active — prefer `DropWeapon`. `CPlayer_WeaponServices`: ```cpp bool CanUse(CBasePlayerWeapon* pWeapon); void DropWeapon(CBasePlayerWeapon* pWeapon, Vector* pVecTarget = nullptr, Vector* pVelocity = nullptr, HookChain eChain = HookChain::Run); int BumpWeapon(CBasePlayerWeapon* pWeapon, HookChain eChain = HookChain::Run); void SelectItem(CBasePlayerWeapon* pWeapon, int unk1 = 0, HookChain eChain = HookChain::Run); void Destroy(CBasePlayerWeapon* pWeapon, HookChain eChain = HookChain::Run); ``` `Destroy` removes a weapon from the player and deletes it. The inventory is `m_hMyWeapons()` and the weapon in hand `m_hActiveWeapon()`, both handles. In a script `m_hActiveWeapon` is a property, and the `m_hMyWeapons` vector is read with `Source2ModSchema.GetUtlVectorCount` / `GetUtlVectorElement` (4 bytes, one handle, per element): ```cpp title="plugin.cpp" // Strip everything except the knife. for (const auto& hWeapon : pWeapons->m_hMyWeapons()) { CBasePlayerWeapon* pWeapon = hWeapon.Get(); if (!pWeapon) continue; if (V_strstr(pWeapon->GetWeaponClassname(), "knife")) continue; pWeapons->DropWeapon(pWeapon); pWeapon->Remove(); } ``` ```cpp title="plugin.sp" // Strip everything except the knife. From the end: dropping changes the list. int count = Source2ModSchema.GetUtlVectorCount(weapons, "CPlayer_WeaponServices", "m_hMyWeapons"); for (int i = count - 1; i >= 0; i--) { SchemaObject element = Source2ModSchema.GetUtlVectorElement(weapons, "CPlayer_WeaponServices", "m_hMyWeapons", i, 4); CBasePlayerWeapon weapon = view_as(Source2ModSchema.GetEntityAt(element, 0)); if (weapon == NULL_ENTITY) continue; char classname[64]; weapon.GetWeaponClassname(classname, sizeof(classname)); if (Source2ModUtils.Find(classname, "knife") != -1) continue; weapons.DropWeapon(weapon); weapon.Remove(); } ``` `CBasePlayerWeapon::GetWeaponVData()` returns the weapon's `CCSWeaponBaseVData` (damage, clip size, price …); `CCSWeaponBaseVData::FromKey("weapon_ak47")` looks the same data up without a weapon. Money is on the controller: ```cpp if (auto* pMoney = pController->m_pInGameMoneyServices()) pMoney->m_iAccount = 16000; ``` ```cpp title="plugin.sp" CCSPlayerController_InGameMoneyServices money = controller.m_pInGameMoneyServices; if (!money.IsNull) money.m_iAccount = 16000; ``` Attributes [#attributes] Econ attributes (skins, stickers, custom values) are set on a `CAttributeList`: ```cpp void CAttributeList::SetOrAddAttribute(const char* pszAttributeName, float flValue, HookChain eChain = HookChain::Run); void CAttributeList::SetOrAddAttributeValueByName(const char* pszAttributeName, float flValue, HookChain eChain = HookChain::Run); ``` The two are the same call; the second has the engine's name. A weapon's lists are on its item: ```cpp CAttributeList& attrs = pWeapon->m_AttributeManager().m_Item().m_NetworkedDynamicAttributes(); attrs.SetOrAddAttribute("set item texture prefab", 44.0f); ``` ```cpp title="plugin.sp" CAttributeList attrs = weapon.m_AttributeManager.m_Item.m_NetworkedDynamicAttributes; attrs.SetOrAddAttribute("set item texture prefab", 44.0); ``` Messages [#messages] ```cpp void CBasePlayerController::PrintToChat(const char* pszMessage); void CBasePlayerController::PrintToCenter(const char* pszMessage); void CBasePlayerController::PrintToCenterAlert(const char* pszMessage); void CBasePlayerController::PrintToConsole(const char* pszMessage); // adds the newline void CCSPlayerController::PrintToCenterHtml(const char* pszMessage, int iDuration = 5, bool bMenu = false); ``` The first four send a `TextMsg` user message to this player only. They take a finished string — format it first (`Source2ModApi.Format` in a script): ```cpp char szBuffer[256]; V_snprintf(szBuffer, sizeof(szBuffer), "You have %d HP", pPawn->m_iHealth()); pController->PrintToChat(szBuffer); ``` ```cpp title="plugin.sp" char buffer[256]; Source2ModApi.Format(buffer, sizeof(buffer), "You have %d HP", pawn.m_iHealth); controller.PrintToChat(buffer); ``` `PrintToCenterHtml` shows HTML in the center panel for `iDuration` seconds. While the player has a [menu](/docs/guides/menus) open it does nothing unless `bMenu` is true, so it cannot overwrite the menu. Commands, convars and events on the client [#commands-convars-and-events-on-the-client] ```cpp void ExecuteClientCommand(const char* pszCommand); // runs on the client void ExecuteClientCommandFromServer(const char* pszCommand); // dispatches a server command as if the player ran it void ReplicateConVar(const char* pszConVar, const char* pszValue); void FireEventToClient(IGameEvent* pEvent); // to this player's legacy listener only ``` `ReplicateConVar` tells one client a replicated convar has a different value, without changing it on the server. Kicking [#kicking] ```cpp void CBasePlayerController::Disconnect(ENetworkDisconnectionReason eReason, const char* pszInternalReason = nullptr); ``` ```cpp pController->Disconnect(NETWORK_DISCONNECT_KICKED, "Kicked by admin"); ``` ```cpp title="plugin.sp" controller.Disconnect(39 /* NETWORK_DISCONNECT_KICKED */, "Kicked by admin"); ``` The disconnect happens on the **next frame**; the reason string is copied, so a temporary is fine. The player's open menu is closed first, and nothing happens if the player is not fully connected. Other controller helpers [#other-controller-helpers] | Helper | Does | | -------------------------------------------- | ------------------------------------------------------- | | `IsBot()` | `FL_FAKECLIENT` is set | | `SetPawn(pPawn, eChain)` | Puts a pawn on the controller | | `GetServerSideClient()` | The engine's `CServerSideClient` for this slot, or null | | `GetObserverPawn()` | The observer pawn, or null | | `CCSPlayerPawnBase::CanMove()` | False while frozen, defusing, … | | `CCSPlayerPawnBase::GetOriginalController()` | The controller the pawn was created for | # Schema Fields (/docs/entities/schema-fields) Every member of a schema class is declared with `SCHEMA_FIELD` (or `SCHEMA_FIELD_POINTER`). The macro produces a small wrapper object with no storage of its own: it finds the field's offset in the running game's schema by name the first time it is used, and from then on reads and writes the entity's memory at that offset. ```cpp title="CBaseEntity.h (generated)" class CBaseEntity : public CEntityInstance { public: DECLARE_SCHEMA_CLASS(CBaseEntity); SCHEMA_FIELD(int32_t, m_iHealth); SCHEMA_FIELD(uint8_t, m_iTeamNum); SCHEMA_FIELD(CHandle, m_hOwnerEntity); SCHEMA_FIELD(CCollisionProperty*, m_pCollision); // ... }; ``` In a script the same fields are properties of the class's methodmap (`source2mod/schema/classes.inc`). A field that cannot be a property — a vector, a color, a string — is a method of its name that fills a buffer, with `Set_()` to write it. Read with a call, write with an assignment [#read-with-a-call-write-with-an-assignment] ```cpp title="plugin.cpp" const int nOld = pPawn->m_iHealth(); // read: operator() returns a reference pPawn->m_iHealth = 150; // write: goes through Set() pPawn->m_iHealth += 25; // read-modify-write, also through Set() ``` ```cpp title="plugin.sp" int old = pawn.m_iHealth; // read: the property's getter pawn.m_iHealth = 150; // write: the setter, which marks the field changed pawn.m_iHealth += 25; // read-modify-write, also through the setter ``` Reading also works through the implicit conversion (`int n = pPawn->m_iHealth;`) and `Get()`. The call form is the clearest and the one used across the SDK. **Writing through the wrapper is what makes the change reach clients.** `=`, `Set()`, `++`, `--`, `+=`, `-=`, `*=`, `/=`, and for integers and enums `|=`, `&=`, `^=`, all call `Set()`, which marks the field as changed before writing it. The engine then includes it in the next snapshot. ```cpp pPawn->m_fFlags |= FL_FROZEN; // announced pPawn->m_fFlags() |= FL_FROZEN; // NOT announced: writes through the raw reference ``` `field()` hands you a plain reference to the memory, and anything you do to that reference is invisible to the network layer. That is useful on purpose (several writes, then one `NetworkStateChanged()`), and a bug by accident. A script has no raw reference: every property write goes through the setter and is announced. Only fields that are networked are marked; writing a server-only field through `=` just writes it. What can be assigned [#what-can-be-assigned] `=` is available for pointers, trivially copyable types (numbers, enums, `bool`, handles, `Vector`, `QAngle`, `Color`, …). The assigned value is converted to the field's own type, so `m_nByteField = 1` writes one byte, not four. For everything else — `CUtlVector`, strings, embedded classes — you take the reference with `()` and change it in place (see below). Calling NetworkStateChanged yourself [#calling-networkstatechanged-yourself] Every field wrapper has `NetworkStateChanged()`. Call it after any write that did not go through `Set()`; a script calls `Source2ModSchema.StateChanged` with the class and field name: ```cpp auto& vecWeapons = pWeaponServices->m_hMyWeapons(); // CUtlVector>& // ... modify vecWeapons ... pWeaponServices->m_hMyWeapons.NetworkStateChanged(); ``` ```cpp title="plugin.sp" // ... modify m_hMyWeapons through Source2ModSchema's raw-memory natives ... Source2ModSchema.StateChanged(weaponServices, "CPlayer_WeaponServices", "m_hMyWeapons"); ``` The cases where you need it: * **Vectors and other non-assignable types**, modified through `()`. * **Fixed arrays and char buffers**, declared with `SCHEMA_FIELD_POINTER` — it has no setter at all (below). * **Raw writes through `()`** that you batch on purpose. If you only have a class name and a field name at runtime, `schema::SetStateChanged(pEntity, "m_iHealth")` does the same, taking the class from the pointer's static type. How the change is routed [#how-the-change-is-routed] You do not have to pick; the wrapper does it from the class declaration: | Declared with | What it is | NetworkStateChanged goes to | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `DECLARE_SCHEMA_CLASS` | An entity class | The entity's own `NetworkStateChanged` | | `DECLARE_SCHEMA_CLASS_INLINE` | An object embedded in an entity or hanging off one (`CCollisionProperty`, `CGameSceneNode`, `CEconItemView`, services, `CCSGameRules`) | Through the class's `__m_pChainEntity` chainer to the owning entity when the class has one (services, game rules); otherwise the `NetworkStateChanged` virtual of the NetworkVar wrapper the object is embedded in | For the embedded case the SDK finds that virtual's vtable slot at runtime from a gamedata signature, per vtable, so no indices are kept per class. If the signature is missing, a warning is printed once and embedded fields are not networked. Embedded objects [#embedded-objects] A field whose type is a class (not a pointer) is an object stored inside the entity. `()` gives you a reference to it (in a script, the property is the object), and its own fields work as usual: ```cpp // CBaseModelEntity: SCHEMA_FIELD(CCollisionProperty, m_Collision) pModel->m_Collision().m_CollisionGroup = COLLISION_GROUP_DEBRIS; // announced ``` ```cpp title="plugin.sp" // CBaseModelEntity: property CCollisionProperty m_Collision model.m_Collision.m_CollisionGroup = 5; // COLLISION_GROUP_DEBRIS, announced ``` The same chain works as deep as it goes: ```cpp // CBasePlayerWeapon -> m_AttributeManager -> m_Item -> m_NetworkedDynamicAttributes CAttributeList& attrs = pWeapon->m_AttributeManager().m_Item().m_NetworkedDynamicAttributes(); ``` ```cpp title="plugin.sp" // CBasePlayerWeapon -> m_AttributeManager -> m_Item -> m_NetworkedDynamicAttributes CAttributeList attrs = weapon.m_AttributeManager.m_Item.m_NetworkedDynamicAttributes; ``` Pointers and handles [#pointers-and-handles] A pointer field returns the pointer; `->` on the wrapper reaches straight through it. In a script, a pointer to an object reads as a `SchemaObject` (check `IsNull`) and a vector field fills a `float[3]`: ```cpp CPlayer_ItemServices* pItems = pPawn->m_pItemServices(); if (pItems) pItems->GiveNamedItem("weapon_ak47"); Vector vecOrigin = pEntity->m_CBodyComponent->m_pSceneNode->m_vecAbsOrigin(); ``` ```cpp title="plugin.sp" CPlayer_ItemServices items = pawn.m_pItemServices; if (!items.IsNull) items.GiveNamedItem("weapon_ak47"); float origin[3]; entity.m_CBodyComponent.m_pSceneNode.m_vecAbsOrigin(origin); ``` Pointer fields can be null — check them. Handle fields are `CHandle` values; `.Get()` resolves them and returns null for a removed entity. In a script a handle field reads as the entity, `NULL_ENTITY` when empty or removed: ```cpp if (CBaseEntity* pOwner = pEntity->m_hOwnerEntity().Get()) // ... ``` ```cpp title="plugin.sp" CBaseEntity owner = entity.m_hOwnerEntity; if (owner != NULL_ENTITY) { // ... } ``` Fixed arrays and char buffers [#fixed-arrays-and-char-buffers] Fields that are C arrays in the game are declared with `SCHEMA_FIELD_POINTER`, which returns a pointer to the first element instead of a reference. It has no setter, so announce the change yourself. A script writes a char array through its `Set_()` setter, which announces it; any other array is raw memory at the field's offset, followed by `Source2ModSchema.StateChanged`: ```cpp // CPointWorldText: SCHEMA_FIELD_POINTER(char, m_messageText) V_strncpy(pText->m_messageText, "Hello", 512); pText->m_messageText.NetworkStateChanged(); // CPlayer_WeaponServices: SCHEMA_FIELD_POINTER(uint16_t, m_iAmmo) pWeaponServices->m_iAmmo()[nAmmoType] = 90; pWeaponServices->m_iAmmo.NetworkStateChanged(); ``` ```cpp title="plugin.sp" // CPointWorldText: m_messageText has a setter, which announces the change text.Set_m_messageText("Hello"); // CPlayer_WeaponServices: m_iAmmo is a uint16_t array, written as raw memory int offset = Source2ModSchema.GetOffset("CPlayer_WeaponServices", "m_iAmmo"); Source2ModSchema.SetIntAt(weaponServices, offset + ammoType * 2, 90, 2); Source2ModSchema.StateChanged(weaponServices, "CPlayer_WeaponServices", "m_iAmmo"); ``` Vectors [#vectors] `CUtlVector` fields are read and changed through the reference; a script uses `Source2ModSchema.GetUtlVectorCount` and `GetUtlVectorElement`: ```cpp for (const auto& hWeapon : pWeaponServices->m_hMyWeapons()) { if (CBasePlayerWeapon* pWeapon = hWeapon.Get()) // ... } ``` ```cpp title="plugin.sp" int count = Source2ModSchema.GetUtlVectorCount(weaponServices, "CPlayer_WeaponServices", "m_hMyWeapons"); for (int i = 0; i < count; i++) { SchemaObject element = Source2ModSchema.GetUtlVectorElement(weaponServices, "CPlayer_WeaponServices", "m_hMyWeapons", i, 4); CBasePlayerWeapon weapon = view_as(Source2ModSchema.GetEntityAt(element, 0)); if (weapon != NULL_ENTITY) { // ... } } ``` Some networked vectors are **not** laid out like a plain `CUtlVector` (`CUtlVectorEmbeddedNetworkVar`, for example), and indexing them directly reads the wrong memory. For those the schema system publishes an element accessor, available as `field.GetManipulator()` (null for every other field; C++ only). The SDK's `CCSCustomHudLayout::GetLayoutState` is an example of using it; when a helper exists for such a field, use the helper. When a field is missing [#when-a-field-is-missing] A field the running game does not have — renamed by an update, gone, a class that lost it — does not write at offset 0 and does not stop the plugin from loading. The first access logs an error naming the class and the field; reads return a zeroed value of the field's type, writes and `NetworkStateChanged()` do nothing. The rest of the plugin keeps working, and the log line says what to rename. Adding your own helpers [#adding-your-own-helpers] The generated classes carry the game's fields and the SDK's helpers, not yours. You cannot add a method to a class you do not own, and editing the generated headers is lost on the next regeneration. The pattern that works is a derived class **with no data members and no virtual functions**, plus a cast. In a script it is a methodmap deriving from the class, with methods and properties only, and `view_as`: ```cpp title="extends/CBaseEntityEx.h" #include "source2mod/schema/entity/classes/CBaseEntity.h" class CBaseEntityEx : public CBaseEntity { public: bool IsPlayerPawn() { return V_strcmp(GetClassname(), "player") == 0; } bool IsAlive() { return m_lifeState() == LIFE_ALIVE; } void AddFlags(uint32_t nMask) { m_fFlags |= nMask; } }; // The whole trick depends on this: add a member or a virtual and the cast // below silently points at the wrong memory. static_assert(sizeof(CBaseEntityEx) == sizeof(CBaseEntity)); inline CBaseEntityEx* Ex(CBaseEntity* pEntity) { return static_cast(pEntity); } ``` ```cpp title="extends/CBaseEntityEx.sp" methodmap CBaseEntityEx < CBaseEntity { public bool IsPlayerPawn() { char classname[64]; this.GetClassname(classname, sizeof(classname)); return Source2ModUtils.Equal(classname, "player"); } public bool IsAlive() { return this.m_lifeState == 0; } // LIFE_ALIVE public void AddFlags(int mask) { this.m_fFlags |= mask; } } // No cast to check: view_as(entity) is the same entity handle. ``` ```cpp if (Ex(pEntity)->IsPlayerPawn()) Ex(pEntity)->AddFlags(FL_FROZEN); ``` ```cpp title="plugin.sp" CBaseEntityEx ex = view_as(entity); if (ex.IsPlayerPawn()) ex.AddFlags(1 << 5); // FL_FROZEN ``` A schema class is only a view over engine memory, so a derived class that adds no storage has exactly the same layout, and the cast costs nothing. The FUNPLAY plugins use this for every class they extend, with one `Ex()` overload (or a trait-based template) per class. Before you add a helper, check the SDK does not already have it on the class or a base class — `CCSPlayerController` inherits a lot. Declaring fields and classes yourself [#declaring-fields-and-classes-yourself] If the game has a field or class the SDK does not generate, declare it the same way the generator does. The class name is the key into the schema, so it must match the game's class name exactly. A script declares a methodmap whose properties call `Source2ModSchema`'s generic accessors, as `classes.inc` does; an embedded class derives from `SchemaObject`: ```cpp title="my_schema.h" #include "source2mod/schema/schema.h" #include "source2mod/schema/entity/classes/CBaseModelEntity.h" class CMyGameEntity : public CBaseModelEntity { public: DECLARE_SCHEMA_CLASS(CMyGameEntity); // entity class SCHEMA_FIELD(float, m_flSomething); SCHEMA_FIELD_POINTER(char, m_szBuffer); }; class CMyEmbeddedThing { public: DECLARE_SCHEMA_CLASS_INLINE(CMyEmbeddedThing); // non-entity, embedded SCHEMA_FIELD(int32_t, m_nValue); }; ``` ```cpp title="my_schema.sp" methodmap CMyGameEntity < CBaseModelEntity { property float m_flSomething { public get() { return Source2ModSchema.GetFloat(this, "CMyGameEntity", "m_flSomething"); } public set(float value) { Source2ModSchema.SetFloat(this, "CMyGameEntity", "m_flSomething", value); } } public int m_szBuffer(char[] buffer, int maxlen) { return Source2ModSchema.GetString(this, "CMyGameEntity", "m_szBuffer", buffer, maxlen); } public void Set_m_szBuffer(const char[] value) { Source2ModSchema.SetString(this, "CMyGameEntity", "m_szBuffer", value); } } methodmap CMyEmbeddedThing < SchemaObject { property int m_nValue { public get() { return Source2ModSchema.GetInt(this, "CMyEmbeddedThing", "m_nValue"); } public set(int value) { Source2ModSchema.SetInt(this, "CMyEmbeddedThing", "m_nValue", value); } } } ``` Use `DECLARE_SCHEMA_CLASS` for entities and `DECLARE_SCHEMA_CLASS_INLINE` for objects embedded in an entity — it decides how a change is networked (table above). Fields of base classes are found through the schema's inheritance, so you only declare the new ones. For a one-off lookup without a class, `schema::GetOffset("m_field")` returns a `SchemaKey` (`offset`, `networked`), and `schema::GetServerOffset("m_field")` returns just the offset, `-1` when it does not exist. A script has `Source2ModSchema.GetOffset(className, field)`, also `-1` when it does not exist. See the [Schema API reference](/docs/core-api/Schema) for every function in `schema.h`. # Event Reference (/docs/events/event-reference) The fields below are taken from the event definitions CS2 ships in its VPKs — `resource/core.gameevents` (engine), `resource/game.gameevents` and `resource/mod.gameevents` (Counter-Strike). Where an event is defined in more than one file, the most specific definition is listed — `mod` over `game` over `core`. Names are case sensitive. For hooking, reading and firing, see [Game events](/docs/events/game-events). Field types and getters [#field-types-and-getters] | Type in the definition | Read with | | ---------------------------- | ---------------------------------------------------------------------------------- | | `player_controller_and_pawn` | `GetPlayerController(key)`, `GetPlayerPawn(key)`, `GetPlayerSlot(key)` | | `player_controller` | `GetPlayerController(key)`, `GetPlayerSlot(key)` — the pawn through the controller | | `player_pawn` | `GetPlayerPawn(key)` | | `bool` | `GetBool(key)` | | `byte`, `short`, `long` | `GetInt(key)` | | `float` | `GetFloat(key)` | | `string` | `GetString(key)` | | `uint64` | `GetUint64(key)` | Player getters return a `CEntityInstance*`; `static_cast` it to `CCSPlayerController*` / `CCSPlayerPawn*` and check for `nullptr`. In a script they return the typed handle; check it against `NULL_ENTITY`. ```cpp auto* pController = static_cast(event->GetPlayerController("userid")); CCSPlayerPawn* pPawn = pController ? pController->GetPlayerPawn() : nullptr; ``` ```cpp CCSPlayerController controller = event.GetPlayerController("userid"); CCSPlayerPawn pawn = controller != NULL_ENTITY ? controller.GetPlayerPawn() : NULL_ENTITY; ``` Players [#players] `player_connect` [#player_connect] A client connected (before it is in the game). | Field | Type | | | ----------- | ------------------ | ------------------ | | `name` | string | player name | | `userid` | player\_controller | | | `networkid` | string | network (Steam) id | | `xuid` | uint64 | SteamID64 | | `bot` | bool | | `player_connect_full` [#player_connect_full] The client finished the connection sequence — the usual "player joined" hook. | Field | Type | | -------- | ------------------ | | `userid` | player\_controller | `player_disconnect` [#player_disconnect] | Field | Type | | | ---------------------- | ------------------ | ------------------------------------------------------- | | `userid` | player\_controller | | | `reason` | short | disconnect reason (`ENetworkDisconnectionReason` value) | | `name` | string | | | `networkid` | string | | | `xuid` | uint64 | SteamID64 | | `PlayerID` | short | | | `ever_fully_connected` | bool | whether the player ever reached `player_connect_full` | `player_team` [#player_team] A player changed team. | Field | Type | | | ------------ | ----------------------------- | --------------------------------------------- | | `userid` | player\_controller\_and\_pawn | | | `team` | byte | new team (`ETeam`: 1 spectator, 2 T, 3 CT) | | `oldteam` | byte | previous team | | `disconnect` | bool | the change is because the player disconnected | | `silent` | bool | | | `isbot` | bool | | `player_spawn` [#player_spawn] | Field | Type | | -------- | ----------------------------- | | `userid` | player\_controller\_and\_pawn | `player_death` [#player_death] | Field | Type | | | --------------------------- | ----------------------------- | --------------------------------- | | `userid` | player\_controller\_and\_pawn | victim | | `attacker` | player\_controller\_and\_pawn | killer | | `assister` | player\_controller\_and\_pawn | assisting player | | `assistedflash` | bool | the assist was a flash | | `weapon` | string | weapon name the killer used | | `weapon_itemid` | string | | | `weapon_fauxitemid` | string | | | `weapon_originalowner_xuid` | string | | | `headshot` | bool | | | `dominated` | short | | | `revenge` | short | | | `wipe` | short | squad wipe | | `penetrated` | short | objects penetrated by the shot | | `noreplay` | bool | | | `noscope` | bool | | | `thrusmoke` | bool | shot through smoke | | `attackerblind` | bool | attacker was flashed | | `distance` | float | distance to the victim, in metres | | `dmg_health` | short | | | `dmg_armor` | byte | | | `hitgroup` | byte | `EHitGroup` value | | `attackerinair` | bool | | `player_hurt` [#player_hurt] | Field | Type | | | ------------ | ----------------------------- | ----------------- | | `userid` | player\_controller\_and\_pawn | victim | | `attacker` | player\_controller\_and\_pawn | | | `health` | byte | health left | | `armor` | byte | armour left | | `weapon` | string | | | `dmg_health` | short | damage to health | | `dmg_armor` | byte | damage to armour | | `hitgroup` | byte | `EHitGroup` value | `player_hurt` reports damage that was dealt. To change or prevent damage, hook the damage function instead (see [Hooking](/docs/hooks)). `player_jump` [#player_jump] | Field | Type | | -------- | ------------------ | | `userid` | player\_controller | `player_blind` [#player_blind] | Field | Type | | | ---------------- | ------------------ | ------------------- | | `userid` | player\_controller | the flashed player | | `attacker` | player\_controller | who threw the flash | | `entityid` | short | the flashbang | | `blind_duration` | float | | `player_changename` [#player_changename] | Field | Type | | --------- | ------------------ | | `userid` | player\_controller | | `oldname` | string | | `newname` | string | `player_radio` [#player_radio] | Field | Type | | -------- | ----------------------------- | | `userid` | player\_controller\_and\_pawn | | `slot` | short | Rounds and match [#rounds-and-match] `round_prestart` / `round_poststart` [#round_prestart--round_poststart] No fields. Sent before and after all other round restart actions. `round_start` [#round_start] | Field | Type | | | ----------- | ------ | ------------------------- | | `timelimit` | long | round time limit, seconds | | `fraglimit` | long | | | `objective` | string | | `round_freeze_end` [#round_freeze_end] No fields. Freeze time is over, players can move. `round_end` [#round_end] | Field | Type | | | -------------- | ------ | ------------------------------------- | | `winner` | byte | winning team (`ETeam`) | | `reason` | byte | round end reason | | `message` | string | end-of-round message | | `legacy` | byte | | | `player_count` | short | players alive at the end of the round | | `nomusic` | byte | don't play round end music | `round_mvp` [#round_mvp] | Field | Type | | -------------- | ------------------ | | `userid` | player\_controller | | `reason` | short | | `value` | long | | `musickitmvps` | long | | `nomusic` | byte | | `musickitid` | long | `round_officially_ended`, `begin_new_match`, `round_announce_match_start`, `cs_win_panel_match`, `warmup_end`, `cs_pre_restart` [#round_officially_ended-begin_new_match-round_announce_match_start-cs_win_panel_match-warmup_end-cs_pre_restart] No fields. Bomb [#bomb] | Event | Fields | | ------------------ | ----------------------------------------------------------------------------------- | | `bomb_beginplant` | `userid` (player\_controller\_and\_pawn), `site` (short) | | `bomb_planted` | `userid` (player\_controller\_and\_pawn), `site` (short), `c4` (short) | | `bomb_begindefuse` | `userid` (player\_controller\_and\_pawn), `haskit` (bool) | | `bomb_defused` | `userid` (player\_controller\_and\_pawn), `site` (short), `c4` (short) | | `bomb_exploded` | `userid` (player\_controller\_and\_pawn, the planter), `site` (short), `c4` (short) | | `bomb_dropped` | `userid` (player\_controller\_and\_pawn), `entindex` (long) | | `bomb_pickup` | `userid` (player\_pawn) | | `player_given_c4` | `userid` (player\_controller) | Weapons and items [#weapons-and-items] | Event | Fields | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `weapon_fire` | `userid` (player\_controller\_and\_pawn), `weapon` (string), `silenced` (bool) | | `weapon_reload` | `userid` (player\_controller\_and\_pawn) | | `weapon_zoom` | `userid` (player\_controller\_and\_pawn) | | `bullet_impact` | `userid` (player\_controller\_and\_pawn), `x`, `y`, `z` (float) | | `item_purchase` | `userid` (player\_controller), `team` (short), `loadout` (short), `weapon` (string) | | `item_pickup` | `userid` (player\_controller), `item` (string), `silent` (bool), `defindex` (long) | | `item_equip` | `userid` (player\_controller), `item` (string), `defindex` (long), `canzoom`, `hassilencer`, `issilenced`, `hastracers` (bool), `weptype` (short), `ispainted` (bool) | `item_equip.weptype`: -1 unknown, 0 knife, 1 pistol, 2 SMG, 3 rifle, 4 shotgun, 5 sniper rifle, 6 machine gun, 7 C4, 8 grenade. Grenades [#grenades] | Event | Fields | | ----------------------- | ----------------------------------------------------------------------------------- | | `grenade_thrown` | `userid` (player\_controller\_and\_pawn), `weapon` (string) | | `hegrenade_detonate` | `userid` (player\_controller\_and\_pawn), `entityid` (short), `x`, `y`, `z` (float) | | `flashbang_detonate` | `userid` (player\_controller\_and\_pawn), `entityid` (short), `x`, `y`, `z` (float) | | `smokegrenade_detonate` | `userid` (player\_controller\_and\_pawn), `entityid` (short), `x`, `y`, `z` (float) | Server [#server] | Event | Fields | | ------------- | ------------------------------------------------------------- | | `game_newmap` | `mapname` (string) — sent when a new map has finished loading | | `server_cvar` | `cvarname` (string), `cvarvalue` (string) | Client-only helpers [#client-only-helpers] Some events exist to drive the client UI and are useful with `FireEventToClient`: | Event | Fields | Use | | ------------------------------ | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `show_survival_respawn_status` | `loc_token` (string), `duration` (long), `userid` (player\_controller\_and\_pawn) | HTML text in the centre of the screen — what `CCSPlayerController::PrintToCenterHtml` sends. | Finding other events [#finding-other-events] The full definitions are plain text inside the game's VPKs: `game/csgo/pak01_dir.vpk` → `resource/game.gameevents` and `resource/mod.gameevents`, `game/core/pak01_dir.vpk` → `resource/core.gameevents`. Any VPK tool can open them. To see what fires in practice, hook the candidates with a post hook that logs `event->GetName()`. # Game Events (/docs/events/game-events) Game events are how CS2 announces what happens in a match: `player_death`, `round_start`, `bomb_planted`. Source2Mod hooks the engine's `IGameEventManager2::FireEvent` once and hands every event to the plugins that asked for it — before the engine processes it, after, or both. Hooking an event [#hooking-an-event] ```cpp title="plugin.cpp" #include "source2mod/ISource2ModEvents.h" #include "source2mod/schema/entity/classes/CCSPlayerController.h" bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) { SOURCE2MOD_SAVEVARS(); HOOK_GAME_EVENT("player_death", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action { auto* pVictim = static_cast(event->GetPlayerController("userid")); auto* pAttacker = static_cast(event->GetPlayerController("attacker")); SOURCE2MOD_LOG(&g_Plugin, "%s killed %s with %s%s\n", pAttacker ? pAttacker->GetPlayerName() : "world", pVictim ? pVictim->GetPlayerName() : "?", event->GetString("weapon"), event->GetBool("headshot") ? " (headshot)" : ""); return Action::Ignore; }, true); return true; } ``` ```cpp title="plugin.sp" public void OnPluginStart() { Source2ModEvents.HookGameEvent("player_death", Event_PlayerDeath, true); } public Source2ModAction Event_PlayerDeath(IGameEvent event, bool post, bool &dontBroadcast) { CCSPlayerController victim = event.GetPlayerController("userid"); CCSPlayerController attacker = event.GetPlayerController("attacker"); char victimName[64] = "?", attackerName[64] = "world", weapon[64]; if (victim != NULL_ENTITY) victim.GetPlayerName(victimName, sizeof(victimName)); if (attacker != NULL_ENTITY) attacker.GetPlayerName(attackerName, sizeof(attackerName)); event.GetString("weapon", weapon, sizeof(weapon)); Source2ModApi.Log("%s killed %s with %s%s", attackerName, victimName, weapon, event.GetBool("headshot") ? " (headshot)" : ""); return Action_Ignore; } ``` The API: ```cpp title="ISource2ModEvents.h" using GameEventHandler = Source2ModCallback; virtual Source2ModHookId HookGameEvent(const char* pchName, GameEventHandler handler, bool post) = 0; virtual bool UnhookGameEvent(const char* pchName, const GameEventHandler& handler, bool post) = 0; virtual bool UnhookGameEvent(Source2ModHookId id) = 0; #define HOOK_GAME_EVENT(passname, passfunc, post) g_pSource2ModEvents->HookGameEvent(passname, passfunc, post) #define UNHOOK_GAME_EVENT(passname, passfunc, post) g_pSource2ModEvents->UnhookGameEvent(passname, passfunc, post) ``` * `pchName` — the event name, exactly as the game defines it (case sensitive). The [event reference](/docs/events/event-reference) lists the common ones. * `handler` — a lambda, a free or static function, or an object and a method (`SOURCE2MOD_MEMBER(this, &MyPlugin::OnPlayerDeath)`). There is no plugin id argument; the core reads which plugin the hook belongs to off the handler. * `post` — `false` for a pre hook, `true` for a post hook. For both, hook twice. * The same event may be hooked as often as a plugin likes; each handler runs. * Hook in `Load()`. If the game's event manager does not exist yet, Source2Mod keeps the hook and attaches it once it does. * Hooking an event also registers a server-side listener for it, so the game creates the event even when nothing else on the server listens for it. * In a script: `Source2ModEvents.HookGameEvent(name, handler, post)` with a named `public` function; it returns `0` when the event does not exist. Everything is dropped when your plugin unloads. To remove one hook earlier, pass the `Source2ModHookId` `HOOK_GAME_EVENT` returned to `g_pSource2ModEvents->UnhookGameEvent(id)`, or hand the same function or object and method to `UNHOOK_GAME_EVENT` — a lambda has no identity and only goes by id. A script unhooks by id only: ```cpp HOOK_GAME_EVENT("round_end", SOURCE2MOD_MEMBER(this, &MyPlugin::OnRoundEnd), true); // ... UNHOOK_GAME_EVENT("round_end", SOURCE2MOD_MEMBER(this, &MyPlugin::OnRoundEnd), true); Source2ModHookId id = HOOK_GAME_EVENT("round_start", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action { return Action::Ignore; }, true); // ... g_pSource2ModEvents->UnhookGameEvent(id); ``` ```cpp int id = Source2ModEvents.HookGameEvent("round_start", Event_RoundStart, true); // ... Source2ModEvents.UnhookGameEvent(id); ``` Full reference: [`ISource2ModEvents`](/docs/core-api/ISource2ModEvents). Pre and post [#pre-and-post] | | Pre (`post == false`) | Post (`post == true`) | | ------------------- | ----------------------------------------------- | ------------------------------------- | | When | The event is filled in, nobody has seen it yet. | The engine has processed and sent it. | | The `event` | The real event. | A copy taken after the pre hooks ran. | | Change fields | Yes — the engine and clients see your values. | Pointless — it is a copy. | | `dontBroadcast` | Yes, see below. | No effect. | | `Action::Supersede` | Cancels the event. | Only skips the remaining post hooks. | Rule of thumb: **pre to change or stop, post to react.** A post hook sees the event after the game's own handling, so state the event caused — money, score, team — is already updated. Hooks for the same event run in the order they were registered, across all plugins. Reading fields [#reading-fields] `IGameEvent` is s2sdk's interface. Field names are plain strings: | Getter | For field type | | ---------------------------------------------------------- | ---------------------------------------------------- | | `GetInt(key, default = 0)` | `short`, `long`, `byte` | | `GetBool(key, default = false)` | `bool` | | `GetFloat(key, default = 0.0f)` | `float` | | `GetString(key, default = "")` | `string` | | `GetUint64(key, default = 0)` | `uint64` (SteamIDs) | | `GetPlayerController(key)` | player fields — the controller as `CEntityInstance*` | | `GetPlayerPawn(key)` | player fields — the pawn as `CEntityInstance*` | | `GetPlayerSlot(key)` | player fields — the `CPlayerSlot` | | `GetEntity(key)`, `GetEntityIndex(key)`, `GetEHandle(key)` | entity fields | Player fields (`userid`, `attacker`, `assister`) are typed `player_controller`, `player_pawn` or `player_controller_and_pawn` in the game's event files, so the type is known: `static_cast` the result (a `dynamic_cast` works too, it only checks what is already certain): ```cpp auto* pController = static_cast(event->GetPlayerController("userid")); if (!pController) return Action::Ignore; // world, a disconnected player, ... CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); ``` ```cpp CCSPlayerController controller = event.GetPlayerController("userid"); if (controller == NULL_ENTITY) return Action_Ignore; // world, a disconnected player, ... CCSPlayerPawn pawn = controller.GetPlayerPawn(); ``` `GetPlayerController` returns `nullptr` when the field names no player — an `attacker` that was the world, a player who already left — so always check. `event->GetName()` gives the event name, which helps when one handler serves several events. In a script the player getters return the typed handle (`CCSPlayerController`, `CCSPlayerPawn`) and `NULL_ENTITY` when there is no player, so there is nothing to cast; `GetString`, `GetName` and `GetUint64` copy into a buffer you pass (the 64-bit value as decimal text). Changing fields [#changing-fields] In a pre hook, set fields with the matching setter before the engine sees the event: `SetInt`, `SetBool`, `SetFloat`, `SetString`, `SetUint64`, `SetEntity`, `SetPlayer(key, CPlayerSlot)`. ```cpp title="plugin.cpp" // Every kill shows as a headshot in the killfeed. HOOK_GAME_EVENT("player_death", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action { event->SetBool("headshot", true); return Action::Override; }, false); ``` ```cpp title="plugin.sp" Source2ModEvents.HookGameEvent("player_death", Event_PlayerDeathPre, false); // Every kill shows as a headshot in the killfeed. public Source2ModAction Event_PlayerDeathPre(IGameEvent event, bool post, bool &dontBroadcast) { event.SetBool("headshot", true); return Action_Override; } ``` Return `Action::Override` when you changed something. The dispatcher treats it like `Ignore` — the event goes on — but it documents intent, and it is what the rest of Source2Mod's callbacks use. Keeping an event from the clients [#keeping-an-event-from-the-clients] Set `dontBroadcast = true` in a pre hook. The server still processes the event — the game's own handling, other plugins' post hooks — but no client receives it: ```cpp title="plugin.cpp" // Bot deaths never show up in the killfeed. HOOK_GAME_EVENT("player_death", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action { auto* pVictim = static_cast(event->GetPlayerController("userid")); if (pVictim && pVictim->IsBot()) { dontBroadcast = true; return Action::Override; } return Action::Ignore; }, false); ``` ```cpp title="plugin.sp" Source2ModEvents.HookGameEvent("player_death", Event_PlayerDeathPre, false); // Bot deaths never show up in the killfeed. public Source2ModAction Event_PlayerDeathPre(IGameEvent event, bool post, bool &dontBroadcast) { CCSPlayerController victim = event.GetPlayerController("userid"); if (victim != NULL_ENTITY && victim.IsBot()) { dontBroadcast = true; return Action_Override; } return Action_Ignore; } ``` FUNPLAY's JailBreak does the same with `player_team` and `player_radio` — the game still switches the team, the clients just never get the event: ```cpp HOOK_GAME_EVENT("player_radio", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action { dontBroadcast = true; return Action::Ignore; }, false); ``` ```cpp Source2ModEvents.HookGameEvent("player_radio", Event_PlayerRadio, false); public Source2ModAction Event_PlayerRadio(IGameEvent event, bool post, bool &dontBroadcast) { dontBroadcast = true; return Action_Ignore; } ``` The flag works both ways: setting it to `false` on an event the game fired server-only sends it to the clients after all. Cancelling an event [#cancelling-an-event] Return `Action::Supersede` from a pre hook and the event is not fired at all: the remaining pre hooks are skipped, the engine never processes it, no client receives it and no post hook runs. ```cpp HOOK_GAME_EVENT("player_jump", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action { auto* pPlayer = static_cast(event->GetPlayerController("userid")); // Nobody hears about bots jumping -- not the game, not the clients, not later hooks. if (pPlayer && pPlayer->IsBot()) return Action::Supersede; return Action::Ignore; }, false); ``` ```cpp Source2ModEvents.HookGameEvent("player_jump", Event_PlayerJump, false); public Source2ModAction Event_PlayerJump(IGameEvent event, bool post, bool &dontBroadcast) { CCSPlayerController player = event.GetPlayerController("userid"); // Nobody hears about bots jumping -- not the game, not the clients, not later hooks. if (player != NULL_ENTITY && player.IsBot()) return Action_Supersede; return Action_Ignore; } ``` Cancelling also hides the event from every other plugin that hooked it later. If all you want is to keep something off players' screens, `dontBroadcast` is the gentler tool. Firing your own event [#firing-your-own-event] Create the event through the engine's event manager (in a script, the static methods of `IGameEventManager2`), fill it in, then send it one of two ways: * **`IGameEventManager2::FireEvent(event, bDontBroadcast)`** — to the server and every client, through every plugin's hooks, exactly like a game-fired event. `FireEvent` takes ownership and frees the event. * **`CBasePlayerController::FireEventToClient(event)`** — to one player only. The server never processes it and no hooks run. The event is still yours: free it afterwards. ```cpp title="plugin.cpp" IGameEventManager2* pEventManager = g_Source2ModAPI->GetGameEventManager(); // To everybody. if (IGameEvent* pEvent = pEventManager->CreateEvent("round_announce_match_start", true)) pEventManager->FireEvent(pEvent); // freed by FireEvent // To one player (pCaller, a CCSPlayerController*): HUD text through show_survival_respawn_status. if (IGameEvent* pEvent = pEventManager->CreateEvent("show_survival_respawn_status", true)) { pEvent->SetString("loc_token", "Hello from a game event"); pEvent->SetInt("duration", 5); pEvent->SetPlayer("userid", pCaller->GetPlayerSlot()); pCaller->FireEventToClient(pEvent); pEventManager->FreeEvent(pEvent); // FireEventToClient does not free it } ``` ```cpp title="plugin.sp" // To everybody. IGameEvent event = IGameEventManager2.CreateEvent("round_announce_match_start", true); if (event != null) IGameEventManager2.FireEvent(event); // freed by FireEvent // To one player (caller, a CCSPlayerController): HUD text through show_survival_respawn_status. event = IGameEventManager2.CreateEvent("show_survival_respawn_status", true); if (event != null) { event.SetString("loc_token", "Hello from a game event"); event.SetInt("duration", 5); event.SetPlayer("userid", caller.GetPlayerSlot()); caller.FireEventToClient(event); IGameEventManager2.FreeEvent(event); // FireEventToClient does not free it } ``` * The second argument of `CreateEvent` is `bForce`: create the event even if nothing on the server listens for it. Pass `true` for events you send to clients. * `CreateEvent` returns `nullptr` (`null` in a script) for a name the game does not know. * The schema headers also declare a free function `GetGameEventManager()`, returning the same manager. `FireEventToClient` is on [`CBasePlayerController`](/docs/schema/entity/classes/CBasePlayerController); `CCSPlayerController::PrintToCenterHtml` is built on exactly this event. See also [#see-also] * [Event reference](/docs/events/event-reference) — fields of the common events. * [Listeners](/docs/events/listeners) — map start/end and plugin lifecycle. * [`ISource2ModEvents` reference](/docs/core-api/ISource2ModEvents) # Events (/docs/events) "Something happened, run my code" means different things on a CS2 server, and Source2Mod has a different tool for each. This section covers the first two; the rest are linked below. Game events [#game-events] The game announces what happens in a match through **game events** — `player_death`, `round_start`, `bomb_planted`, `player_connect_full`, some 270 of them in all. Each is a named bag of typed fields (an `IGameEvent`) that the server fires and, usually, sends to every client too (that is how the killfeed and the round timer work). ```cpp title="plugin.cpp" HOOK_GAME_EVENT("player_death", [](IGameEvent* event, bool post, bool& dontBroadcast) -> Action { auto* victim = static_cast(event->GetPlayerController("userid")); // ... return Action::Ignore; }, true); ``` ```cpp title="plugin.sp" Source2ModEvents.HookGameEvent("player_death", Event_PlayerDeath, true); public Source2ModAction Event_PlayerDeath(IGameEvent event, bool post, bool &dontBroadcast) { CCSPlayerController victim = event.GetPlayerController("userid"); // ... return Action_Ignore; } ``` With [`ISource2ModEvents`](/docs/core-api/ISource2ModEvents) you hook an event before or after the engine handles it, read and change its fields, keep it from the clients, cancel it, and fire events of your own — to everybody or to one player. See [Game events](/docs/events/game-events), and the [event reference](/docs/events/event-reference) for the fields of the ones you will use most. Source2Mod listener callbacks [#source2mod-listener-callbacks] Source2Mod itself tells your plugin about the plugin system and the map lifecycle through `ISource2ModListener`: other plugins loading and unloading, "all plugins are loaded", map start (`OnLevelInit`) and map end (`OnLevelShutdown`), plus interface queries. You implement the interface and register it once with `api->AddListener(this, this)`; a script registers nothing and defines `public` functions named after the `Source2Mod_On*` forwards instead. See [Listeners](/docs/events/listeners). Engine callbacks: clients and the frame [#engine-callbacks-clients-and-the-frame] Player connection and the server frame are **not** listener callbacks. They are engine interface functions you hook with KHook, the way the sample plugin does: | You want | Hook | | -------------------------------- | -------------------------------------------------------- | | A client connecting (can reject) | `ISource2GameClients::ClientConnect` | | Client fully in the game | `ISource2GameClients::ClientPutInServer`, `ClientActive` | | A client leaving | `ISource2GameClients::ClientDisconnect` | | Every server tick | `ISource2Server::GameFrame` | | A raw client command | `ISource2GameClients::ClientCommand` | ```cpp title="plugin.h" KHOOK_VIRTUAL(m_hGameFrame, &ISource2Server::GameFrame, &g_pSource2Server, nullptr, &MyPlugin::Hook_GameFrame); KHOOK_VIRTUAL(m_hClientDisconnect, &ISource2GameClients::ClientDisconnect, &g_pSource2GameClients, nullptr, &MyPlugin::Hook_ClientDisconnect); ``` C++ only — scripts get client connection and the frame as forwards (`Source2Mod_OnClientConnect`, `Source2Mod_OnClientDisconnect`, `Source2Mod_OnGameFrame`, …); see [Listeners](/docs/events/listeners). See [Hooking](/docs/hooks) for how these work. Many of these moments also have a game event — `player_connect_full` and `player_disconnect` are often all you need. Entities and entity outputs [#entities-and-entity-outputs] Entities being created, spawned and deleted (`IEntityListener`, `ADD_ENTITY_LISTENER`) and map logic firing outputs such as `OnPressed` or `OnStartTouch` (`IEntityIOListener`, `ADD_ENTITY_IO_LISTENER`) belong to [`ISource2ModEntities`](/docs/core-api/ISource2ModEntities). See [Entities](/docs/entities). Net messages, sounds, commands, ConVars [#net-messages-sounds-commands-convars] * Protobuf messages between server and clients (user messages, net messages) — [Protobuf](/docs/protobuf). * Sounds being emitted — [`ISource2ModSounds`](/docs/core-api/ISource2ModSounds). * Commands and chat — [ConVars & Commands](/docs/convars-commands). * ConVar changes — [`HOOK_CONVAR_CHANGE`](/docs/convars-commands/convars#watching-changes). Hook, read, modify, block and fire IGameEvents. Every ISource2ModListener callback and when it fires. Common CS2 events and their fields. # Listeners (/docs/events/listeners) `ISource2ModListener` is how Source2Mod tells your plugin about the plugin system and the map lifecycle. Every method has an empty default, so you override only what you need, and you register the listener once in `Load()`. A script gets the same callbacks as forwards from `source2mod/listener.inc`, named `Source2Mod_On…`: no class and no registration, it just defines the `public` functions it wants. ```cpp title="plugin.h" #include "source2mod/ISource2ModPlugin.h" class MyPlugin final : public ISource2ModPlugin, public ISource2ModListener { public: bool Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) override; bool Unload(char* error, size_t maxlen) override; // ISource2ModListener void OnAllSource2ModPluginsLoaded() override; void OnLevelInit(const char* mapName, const char* mapEntities, const char* oldLevel, const char* landmarkName, bool loadGame, bool background) override; void OnLevelShutdown() override; // GetAuthor(), GetName(), ... as usual }; ``` ```cpp title="plugin.sp" // No class and no registration: a public function named after a // source2mod/listener.inc forward is called. public void Source2Mod_OnAllSource2ModPluginsLoaded() { } public void Source2Mod_OnLevelInit(const char[] mapName, const char[] mapEntities, const char[] oldLevel, const char[] landmarkName, bool loadGame, bool background) { } public void Source2Mod_OnLevelShutdown() { } ``` ```cpp title="plugin.cpp" bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) { SOURCE2MOD_SAVEVARS(); api->AddListener(this, this); return true; } ``` Registering [#registering] ```cpp title="ISource2ModApi.h" virtual void AddListener(ISource2ModPlugin* plugin, ISource2ModListener* pListener) = 0; ``` * `plugin` — your plugin object; the listener is filed under it. * `pListener` — the object that implements the callbacks. Usually the plugin class itself, hence `AddListener(this, this)`, but it can be any object that lives as long as the plugin. A plugin can add several. Call it from `Load()`. There is no `RemoveListener`: Source2Mod forgets a plugin's listeners when the plugin unloads (or when its `Load()` fails). C++ only — a script has nothing to register; its `Source2Mod_On*` functions are called as long as it is loaded. The callbacks [#the-callbacks] Plugin lifecycle [#plugin-lifecycle] | Callback | Fires | | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `OnPluginLoad(PluginId id, const char* name)` | After a s2m plugin (`.smx`) loaded successfully, on every plugin's listeners — **including the new plugin's own**, so you see your own `g_PluginID` once. | | `OnPluginUnload(PluginId id, const char* name)` | When a Source2Mod plugin is about to unload — before its `Unload()` runs — on every plugin's listeners, including the one going away. | | `OnAllSource2ModPluginsLoaded()` | After Source2Mod loaded the plugins folder at startup, and again after `s2m refresh` loaded at least one new plugin. | | `OnMetamodPluginLoad(PluginId id)` | A Metamod:Source plugin was loaded. | | `OnMetamodPluginUnload(PluginId id)` | A Metamod:Source plugin was unloaded. | | `OnAllMetamodPluginsLoaded()` | Metamod:Source finished loading all of its plugins. | The ids in `OnPluginLoad`/`OnPluginUnload` are **Source2Mod** plugin ids, the ones `s2m list` shows. The ids in the two `OnMetamod…` callbacks are Metamod's own, from a different numbering — which is why they are separate callbacks. `OnAllSource2ModPluginsLoaded` is the place for anything that depends on other plugins: fetching another plugin's interface with `GET_SOURCE2MOD_IFACE`, for example. Note what does **not** trigger it: loading one plugin by hand with `s2m load`, or a hot reload. A plugin that may be loaded that way should also try its setup from `Load()` or from `OnPluginLoad`. A script has no interfaces to fetch; it checks for the other plugin's library with `Source2ModNatives.LibraryExists` and then calls its natives. ```cpp title="plugin.cpp" void MyPlugin::OnAllSource2ModPluginsLoaded() { // IOtherPluginApi / OTHER_PLUGIN_API_NAME: the header the other plugin ships. int ret = SOURCE2MOD_IFACE_FAILED; GET_SOURCE2MOD_IFACE(m_pOtherApi, IOtherPluginApi, OTHER_PLUGIN_API_NAME, ret); if (ret != SOURCE2MOD_IFACE_OK) SOURCE2MOD_LOG(this, "Other plugin not found, running without it.\n"); } ``` ```cpp title="plugin.sp" public void Source2Mod_OnAllSource2ModPluginsLoaded() { // "other_plugin": the library name the other plugin registers. if (!Source2ModNatives.LibraryExists("other_plugin")) Source2ModApi.Log("Other plugin not found, running without it."); } ``` Map lifecycle [#map-lifecycle] | Callback | Fires | | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `OnLevelInit(mapName, mapEntities, oldLevel, landmarkName, loadGame, background)` | A map is initialising — Metamod's `OnLevelInit`, passed through. `mapName` is the map being loaded. | | `OnLevelShutdown()` | The current map is shutting down. | Map start is the place to reset per-map state; map shutdown is the last moment to let go of entity pointers, handles to per-map objects, and timers that must not survive the map. ```cpp title="plugin.cpp" void MyPlugin::OnLevelInit(const char* pMapName, const char* pMapEntities, const char* pOldLevel, const char* pLandmarkName, bool loadGame, bool background) { SOURCE2MOD_LOG(this, "OnLevelInit(%s)\n", pMapName); } void MyPlugin::OnLevelShutdown() { // Anything map-scoped goes here. } ``` ```cpp title="plugin.sp" public void Source2Mod_OnLevelInit(const char[] mapName, const char[] mapEntities, const char[] oldLevel, const char[] landmarkName, bool loadGame, bool background) { Source2ModApi.Log("OnLevelInit(%s)", mapName); } public void Source2Mod_OnLevelShutdown() { // Anything map-scoped goes here. } ``` A plugin loaded while a map is already running has missed that map's `OnLevelInit`. If you need the current map at load time, read it from the engine rather than waiting for the next map change. Engine callbacks [#engine-callbacks] Plugin API 2 (core v1.0.26) fans the engine calls most plugins used to hook themselves out of the core's own hooks, so a plugin needs no `KHOOK_VIRTUAL` for any of these — and stays on the [stable tier](/docs/development/compatibility): | Callback | Fires | | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `OnGameFrame(bool simulating, bool firstTick, bool lastTick)` | Once a frame, after `ISource2Server::GameFrame` and Source2Mod's own per-frame work (timers, menus). | | `OnStartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession* session, const char* mapName)` | After `INetworkServerService::StartupServer`: a new server session, the new map's entity system is Source2Mod's. | | `OnClientPutInServer(CPlayerSlot slot, const char* name, int type, uint64 xuid)` | After `IServerGameClients::ClientPutInServer`. | | `OnClientVoice(CPlayerSlot slot)` | After `ClientVoice`. | | `OnClientSettingsChanged(CPlayerSlot slot)` | After `ClientSettingsChanged`. | | `OnClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* name, uint64 xuid, const char* networkId)` | After `ClientDisconnect`; the controller still exists. | | `OnGameServerSteamAPIActivated()` | After the Steam API came up. | | `OnGameServerSteamAPIDeactivated()` | Before it goes. | | `OnLoadEventsFromFile(IGameEventManager2* manager, const char* filename, bool searchAll)` | After `IGameEventManager2::LoadEventsFromFile`, with the manager it was called on — the place to keep it, or to register a `.gameevents` file of your own. | They are appended after the older callbacks, so a plugin built against API 1 keeps loading; the core just never calls them on it. Scripts get them as forwards of the same names prefixed `Source2Mod_`. There a SteamID (`xuid`) is decimal text, the disconnect reason an `int`, `Source2Mod_OnStartupServer` gets only the map name and `Source2Mod_OnLoadEventsFromFile` no manager. ```cpp title="plugin.cpp" void MyPlugin::OnClientPutInServer(CPlayerSlot slot, const char* name, int type, uint64 xuid) { SOURCE2MOD_LOG(this, "%s is in (slot %d)\n", name, slot.Get()); } ``` ```cpp title="plugin.sp" public void Source2Mod_OnClientPutInServer(CPlayerSlot slot, const char[] name, int type, const char[] xuid) { Source2ModApi.Log("%s is in (slot %d)", name, slot.Get()); } ``` Interface queries [#interface-queries] These let your plugin **expose** interfaces to other plugins: | Callback | Called when | | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | | `void* OnSource2ModQuery(const char* iface, int* ret)` | Another s2m plugin asks for an interface (`g_Source2ModAPI->Source2ModFactory`, `GET_SOURCE2MOD_IFACE`). | | `void* OnMetamodQuery(const char* iface, int* ret)` | A Metamod:Source plugin asks for one through Metamod's factory. By default it answers exactly like `OnSource2ModQuery`. | Return your interface and set `*ret` to `SOURCE2MOD_IFACE_OK` when `iface` is a name you serve; otherwise return `nullptr`. The default implementation sets `SOURCE2MOD_IFACE_FAILED`. Override `OnMetamodQuery` to answer Metamod plugins differently, or to return `nullptr` and keep an interface Source2Mod-only. C++ only — a script offers natives instead: `Source2ModNatives.CreateNative` and `Source2ModNatives.RegisterLibrary` in `AskPluginLoad2`. ```cpp title="plugin.cpp" // MY_PLUGIN_API_NAME and g_MyPluginApi: your own interface name and object. void* MyPlugin::OnSource2ModQuery(const char* iface, int* ret) { if (strcmp(iface, MY_PLUGIN_API_NAME) == 0) { if (ret) *ret = SOURCE2MOD_IFACE_OK; return &g_MyPluginApi; } if (ret) *ret = SOURCE2MOD_IFACE_FAILED; return nullptr; } ``` What is not a listener callback [#what-is-not-a-listener-callback] `ISource2ModListener` is deliberately small. For everything else: | Event | Where | | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Client connect (`ClientConnect`, `OnClientConnected`, `ClientActive`, `ClientCommand`) | KHook on `ISource2GameClients` — see [Hooking](/docs/hooks), or the `player_connect_full` [game event](/docs/events/game-events); put in server and disconnect are listener callbacks above | | Damage, movement, item pickup, entity inputs | [Game hooks](/docs/hooks/game-hooks) | | Entity created / spawned / deleted | `IEntityListener` via `ADD_ENTITY_LISTENER` — see [Entities](/docs/entities) | | Entity outputs | `IEntityIOListener` via `ADD_ENTITY_IO_LISTENER` — see [Entities](/docs/entities) | | Round start, deaths, bomb, … | [Game events](/docs/events/game-events) | The full interface is documented in the [`ISource2ModPlugin` reference](/docs/core-api/ISource2ModPlugin); `AddListener` in [`ISource2ModApi`](/docs/core-api/ISource2ModApi). # Addresses (/docs/hooks/addresses) Source2Mod scans a set of game functions once at startup and hands them out, typed, through `ISource2ModAddresses`. Each one has a shorthand macro, `ADDR_*()`, that returns a function pointer ready to call — or to hook. ```cpp #include "source2mod/ISource2ModAddresses.h" if (auto pfnSwitchTeam = ADDR_SWITCH_TEAM()) pfnSwitchTeam(pController, 3); ``` The macros read `g_pSource2ModAddresses`, which `SOURCE2MOD_SAVEVARS()` fills in `Load()` (and `SOURCE2MOD_GLOBALVARS()` declares in your other files). Module inspection, pattern scanning and pointer arithmetic are not here — they are on `ISource2ModModule` and `ISource2ModMemory`. Reference: [ISource2ModAddresses](/docs/core-api/ISource2ModAddresses). Three ways to reach a game function [#three-ways-to-reach-a-game-function] | you want to… | use | | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | do something the SDK already wraps (give an item, switch team, end the round) | the schema helper — `pItemServices->GiveNamedItem("weapon_ak47")` | | call a resolved function the helpers do not wrap, or with arguments they do not expose | `ADDR_*()` | | call it **past** every plugin's hooks | `SOURCE2MOD_ORIGINAL(ADDR_*())`, or the helper with `HookChain::Bypass` | | hook it | `KHOOK_MEMBER(m_hX, ADDR_*(), ...)` — see [Function hooks](/docs/hooks/function-hooks) | | reach a function Source2Mod does not resolve | your own gamedata entry and `GAMECONFIG_RESOLVE(name)` — see [Gamedata](/docs/hooks/gamedata) | The sample plugin shows the first two side by side: ```cpp title="plugin.cpp" // Way one: Source2Mod resolved it and knows the type. if (auto pfnSwitchTeam = ADDR_SWITCH_TEAM()) { pfnSwitchTeam(pCaller, nNewTeam); return; } // Way two: any gamedata entry, with the type spelled out by you. using SwitchTeam_t = void (FASTCALL *)(CCSPlayerController *, unsigned char); if (auto pfnSwitchTeam = GAMECONFIG_RESOLVE("CCSPlayerController::SwitchTeam").RCast()) pfnSwitchTeam(pCaller, nNewTeam); // Way three: pCaller->SwitchTeam(nNewTeam) does exactly the above. ``` Calling past the hooks [#calling-past-the-hooks] An `ADDR_*` macro hands back the address the signature scan found. That is what you want to **hook**, but not always what you want to **call**: once any plugin inline-hooks the function, its first bytes are a jump into the detour, so calling it runs the whole Pre/Post chain. That is right when you stand in for the game — a plugin dealing damage as if a weapon fired wants the other plugins' rules to apply. It is wrong when you are asking for the function itself — an admin command, a forced round end — where another plugin's rules have no business vetoing what the server was told to do. For that, wrap the call in `SOURCE2MOD_ORIGINAL`: ```cpp SOURCE2MOD_ORIGINAL(ADDR_TAKE_DAMAGE_OLD())(pPawn, &info, &result); ``` `SOURCE2MOD_ORIGINAL(fn)` casts `KHook::FindOriginal(fn)` back to `fn`'s type. It is signature-blind, so it works whatever prototype you hold and however the plugin that hooked the function declared its hook. Unhooked functions come back unchanged, so it is always safe to apply. Resolve it at the call; do not cache the result in a global. The address is only the original from the moment a hook exists, and hooks are installed long after these addresses are scanned. HookChain on schema helpers [#hookchain-on-schema-helpers] The schema helpers make the same choice through their last argument, a `HookChain` (`ISource2ModTypes.h`): | value | the call goes | | ------------------- | --------------------------------------------------------------------------------- | | `HookChain::Run` | through the Pre/Post chain, as if the game made the call | | `HookChain::Bypass` | straight to the original, past every hook — inline detours and vtable hooks alike | The default of each helper is what it always did, so existing calls keep their behaviour. `CCSGameRules::TerminateRound` and `CCSPlayerController::TakeDamage` default to `Bypass` (the server was told to end the round or deal the damage); every other helper defaults to `Run`. ```cpp pItemServices->GiveNamedItem("weapon_ak47"); // hooks run pController->TakeDamage(pAttacker, 50, DMG_BULLET, HookChain::Run); // opt in to the hooks pGameRules->TerminateRound(5.0f, nReason); // bypasses by default ``` Helpers that take a `HookChain`: | class | helpers | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `CBaseEntity` | `AcceptInput`, `AddEntityIOEvent`, `DispatchSpawn`, `Teleport`, `Remove`, `DispatchParticleEffect` | | `CBaseModelEntity` | `SetModel` | | `CBasePlayerController` | `SetPawn` | | `CBasePlayerPawn` | `CommitSuicide`, `SnapViewAngles` | | `CCSPlayerPawn` | `PostThink` | | `CCSPlayerController` | `TakeDamage` (Bypass), `Respawn`, `SwitchTeam`, `ChangeTeam` | | `CCSGameRules` | `TerminateRound` (Bypass), `GoToIntermission` | | `CPlayer_ItemServices` | `GiveNamedItem`, `DropActivePlayerWeapon`, `RemoveWeapons` | | `CPlayer_WeaponServices` | `DropWeapon`, `BumpWeapon`, `SelectItem`, `Destroy` | | `CAttributeList` | `SetOrAddAttribute`, `SetOrAddAttributeValueByName` | | grenade projectiles | `CHEGrenadeProjectile`, `CFlashbangProjectile`, `CSmokeGrenadeProjectile`, `CDecoyProjectile`, `CMolotovProjectile` — `EmitGrenade` | When to pick which [#when-to-pick-which] * **Standing in for the game** (a custom weapon dealing damage, a round reward handing out an item): `Run`. Other plugins' damage rules, item restrictions and so on should apply. * **Doing what the server was told** (admin command, forced round end, a gamemode's own rule): `Bypass`. * **Inside a hook on the same function**: `Bypass`, always — `Run` would re-enter your own handler. See [Recursion](/docs/hooks/handlers#recursion). For your own calls through a vtable index, `CALL_VIRTUAL_CHAIN(retType, index, eChain, this, args...)` (`utils/virtual.h`) does the same for virtual functions; `ResolveHookChain(fn, eChain)` does it for a function pointer. The ADDR_* macros [#the-addr_-macros] Every macro calls the `ISource2ModAddresses` method of the same name and returns the typedef declared above it in `ISource2ModAddresses.h` — read that typedef before you call or hook the function, it carries the checked prototype and the platform differences. **Required** functions must resolve for Source2Mod to start, so they are never null while it runs. **Optional** ones are plugin-only hook targets resolved leniently: a pattern that stops matching after a game update leaves them **null** instead of blocking startup — check before you call or hook them (a hook on a null target is logged and skipped). Entities and players (required) [#entities-and-players-required] | macro | function | gamedata key | | ------------------------------------ | ----------------------------------------------------------------- | ---------------------------------------------- | | `ADDR_CREATE_ENTITY_BY_NAME()` | `CreateEntityByName` | `UTIL::CreateEntityByName` | | `ADDR_DISPATCH_SPAWN()` | `CBaseEntity::DispatchSpawn` | `CBaseEntity::DispatchSpawn` | | `ADDR_TAKE_DAMAGE_OLD()` | `CBaseEntity::TakeDamageOld` | `CBaseEntity::TakeDamageOld` | | `ADDR_TAKE_DAMAGE_INFO()` | `CTakeDamageInfo` constructor | `CTakeDamageInfo::Constructor` | | `ADDR_SET_MODEL()` | `CBaseModelEntity::SetModel` | `CBaseModelEntity::SetModel` | | `ADDR_SET_PAWN()` | `CBasePlayerController::SetPawn` | `CBasePlayerController::SetPawn` | | `ADDR_SNAP_VIEW_ANGLES()` | `CBasePlayerPawn::SnapViewAngles` | `CBasePlayerPawn::SnapViewAngles` | | `ADDR_SWITCH_TEAM()` | `CCSPlayerController::SwitchTeam` | `CCSPlayerController::SwitchTeam` | | `ADDR_WEAPON_SERVICES_DESTROY()` | `CPlayer_WeaponServices::Destroy` | `CCSPlayer_WeaponServices::Destroy` | | `ADDR_GIVE_NAMED_ITEM()` | `CCSPlayer_ItemServices::GiveNamedItem` (the full implementation) | `CCSPlayer_ItemServices::GiveNamedItem` | | `ADDR_POST_THINK()` | `CCSPlayerPawn::PostThink` | `CCSPlayerPawn::PostThink` | | `ADDR_CAN_MOVE()` | `CCSPlayerPawnBase::CanMove` | `CCSPlayerPawnBase::CanMove` | | `ADDR_UTIL_REMOVE()` | `UTIL_Remove` | `UTIL::Remove` | | `ADDR_SET_OR_ADD_ATTRIBUTE()` | `CAttributeList::SetOrAddAttributeValueByName` | `CAttributeList::SetOrAddAttributeValueByName` | | `ADDR_GET_WEAPON_CS_DATA_FROM_KEY()` | `GetWeaponCSDataFromKey` | `GetWeaponCSDataFromKey` | | `ADDR_DISPATCH_PARTICLE_EFFECT()` | `DispatchParticleEffect` | `DispatchParticleEffect` | | `ADDR_EMIT_HEGRENADE()` | `CHEGrenadeProjectile::EmitGrenade` | `CHEGrenadeProjectile::EmitGrenade` | | `ADDR_EMIT_FLASHBANG()` | `CFlashbangProjectile::EmitGrenade` | `CFlashbangProjectile::EmitGrenade` | | `ADDR_EMIT_SMOKE()` | `CSmokeGrenadeProjectile::EmitGrenade` | `CSmokeGrenadeProjectile::EmitGrenade` | | `ADDR_EMIT_DECOY()` | `CDecoyProjectile::EmitGrenade` | `CDecoyProjectile::EmitGrenade` | | `ADDR_EMIT_MOLOTOV()` | `CMolotovProjectile::EmitGrenade` | `CMolotovProjectile::EmitGrenade` | Game rules, entity I/O and networking (required) [#game-rules-entity-io-and-networking-required] | macro | function | gamedata key | | ----------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------- | | `ADDR_TERMINATE_ROUND()` | `CCSGameRules::TerminateRound` — argument order differs per platform | `CCSGameRules::TerminateRound` | | `ADDR_ACCEPT_INPUT()` | `CEntityInstance::AcceptInput` | `CEntityInstance::AcceptInput` | | `ADDR_FIRE_OUTPUT_INTERNAL()` | `CEntityIOOutput::FireOutputInternal` | `CEntityIOOutput::FireOutputInternal` | | `ADDR_ADD_ENTITY_IO_EVENT()` | `CEntitySystem::AddEntityIOEvent` | `CEntitySystem::AddEntityIOEvent` | | `ADDR_FIND_ENTITY_BY_CLASSNAME()` | `CGameEntitySystem::FindEntityByClassName` | `CGameEntitySystem::FindEntityByClassName` | | `ADDR_FIND_ENTITY_BY_NAME()` | `CGameEntitySystem::FindEntityByName` | `CGameEntitySystem::FindEntityByName` | | `ADDR_LEGACY_GAME_EVENT_LISTENER()` | `LegacyGameEventListener` | `LegacyGameEventListener` | | `ADDR_FILTER_MESSAGE()` | `INetworkMessageProcessingPreFilter::FilterMessage` | `INetworkMessageProcessingPreFilter::FilterMessage` | Optional — may be null [#optional--may-be-null] | macro | function | | ---------------------------------- | ----------------------------------------------- | | `ADDR_IDENTITY_ACCEPT_INPUT()` | `CEntityIdentity::AcceptInput` | | `ADDR_CAN_ACQUIRE()` | `CCSPlayer_ItemServices::CanAcquire` | | `ADDR_PROCESS_USER_CMD()` | `CCSPlayerController::ProcessUserCmd` | | `ADDR_ON_SIMULATE_USER_COMMANDS()` | `CBasePlayerController::OnSimulateUserCommands` | | `ADDR_GET_NAV_MESH_DATA()` | `CSource2Server::GetNavMeshData` | | `ADDR_LOG_DIRECT()` | `CLoggingSystem::LogDirect` (tier0) | | `ADDR_CMD_EXECUTE_COMMAND()` | `Cmd_ExecuteCommand` (engine2) | Movement (`CCSPlayer_MovementServices::*`, all optional): | macro | function | | ---------------------------- | -------------------------------------------------------- | | `ADDR_AIR_ACCELERATE()` | `AirAccelerate` | | `ADDR_AIR_MOVE()` | `AirMove` | | `ADDR_CAN_UNDUCK()` | `CanUnduck` | | `ADDR_CATEGORIZE_POSITION()` | `CategorizePosition` | | `ADDR_CHECK_FALLING()` | `CheckFalling` | | `ADDR_CHECK_PARAMETERS()` | `CheckParameters` | | `ADDR_CHECK_VELOCITY()` | `CheckVelocity` | | `ADDR_CHECK_WATER()` | `CheckWater` | | `ADDR_DUCK()` | `Duck` | | `ADDR_FRICTION()` | `Friction` | | `ADDR_FULL_WALK_MOVE()` | `FullWalkMove` | | `ADDR_GROUND_ACCELERATE()` | `GroundAccelerate` — argument order differs per platform | | `ADDR_LADDER_MOVE()` | `LadderMove` | | `ADDR_MOVE_INIT()` | `MoveInit` | | `ADDR_PLAYER_MOVE()` | `PlayerMove` | | `ADDR_PROCESS_MOVEMENT()` | `ProcessMovement` | | `ADDR_SETUP_MOVE()` | `SetupMove` | | `ADDR_TRY_PLAYER_MOVE()` | `TryPlayerMove` | | `ADDR_WALK_MOVE()` | `WalkMove` | | `ADDR_WATER_MOVE()` | `WaterMove` | Jumping (members of the jump components, not of the movement services — all optional): | macro | function | | --------------------------------- | -------------------------------------- | | `ADDR_ON_JUMP_LEGACY()` | `CCSPlayerLegacyJump::OnJump` | | `ADDR_ON_JUMP_MODERN()` | `CCSPlayerModernJump::OnJump` | | `ADDR_CHECK_JUMP_BUTTON_LEGACY()` | `CCSPlayerLegacyJump::CheckJumpButton` | | `ADDR_CHECK_JUMP_BUTTON_MODERN()` | `CCSPlayerModernJump::CheckJumpButton` | The gamedata key of each optional function is its function name as listed (`CCSPlayer_MovementServices::AirAccelerate`, `CCSPlayerLegacyJump::OnJump`, …). ```cpp title="Hooks.h" // Optional target: if the signature broke, KHOOK_INIT() logs it and skips this hook. KHook::Return OnCheckJumpButtonLegacy(CCSPlayerLegacyJump* pThis, CMoveData* pMove); KHOOK_MEMBER(m_hCheckJumpButtonLegacy, ADDR_CHECK_JUMP_BUTTON_LEGACY(), &CHooks::OnCheckJumpButtonLegacy, nullptr); ``` # Function Hooks (/docs/hooks/function-hooks) Anything a signature scan can find is hookable — no vtable, no interface. A function hook patches the first bytes of the function itself, so it catches **every** call: through a vtable, direct, from any library. ```cpp KHOOK_MEMBER(member, target, pre, post) // a function with a `this` KHOOK_FUNCTION(member, target, pre, post) // a free function ``` * `KHOOK_MEMBER` — the handler's first parameter is the object (`this`), the rest are the function's own arguments. * `KHOOK_FUNCTION` — the handler takes only the function's own arguments. Where the address comes from [#where-the-address-comes-from] The **target** is one of: | target | example | resolved by | | --------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | a gamedata entry name | `"CCSPlayerPawn::PostThink"` | `ISource2ModGameConfig::ResolveSignature` — the entry's `library`, then its symbol or pattern | | an `ADDR_*` getter | `ADDR_TAKE_DAMAGE_OLD()` | Source2Mod, which scanned it at startup — nothing is scanned twice | | an address | an `ISource2ModMemory`, `void*` or typed function pointer | you | | `nullptr` | | you, later, with `m_hX.Init(address)` | ```cpp title="Hooks.h" class CHooks { public: KHook::Return OnTakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); KHook::Return OnPostThink(CCSPlayerPawn* pThis); // What Source2Mod already resolved. KHOOK_MEMBER(m_hTakeDamageOld, ADDR_TAKE_DAMAGE_OLD(), &CHooks::OnTakeDamageOld, nullptr); // A gamedata entry. KHOOK_MEMBER(m_hPostThink, "CCSPlayerPawn::PostThink", &CHooks::OnPostThink, nullptr); }; ``` Prefer an `ADDR_*` getter when one exists: it is already resolved, and its typedef in `ISource2ModAddresses.h` documents the real prototype — including platform differences. The full list is on [Addresses](/docs/hooks/addresses). For a function Source2Mod does not know, add an entry to your gamedata (see [Gamedata](/docs/hooks/gamedata)) and name it. A pattern you keep in the plugin instead is scanned yourself and passed as an address. `WIN_LINUX(win, lin)` from `ISource2ModModule.h` picks the platform's pattern at compile time: ```cpp ISource2ModMemory addr = pServer->FindPattern(WIN_LINUX("48 89 7C 24 ? 55", "55 48 89 E5 41 57")); ``` See [Memory & modules](/docs/guides/memory-and-modules#pattern-scanning). A gamedata entry is still the better choice for anything a game update moves: it is fixed without a rebuild. Deferred targets [#deferred-targets] The target is **not** evaluated when your object is constructed. The macro wraps it in a capture-less lambda that runs at `KHOOK_INIT()` (or at `m_hX.Init()`). That is what lets `ADDR_TAKE_DAMAGE_OLD()` or `GAMECONFIG_RESOLVE("...")` sit right in the macro even though your hooks object is usually a global, constructed long before `Load()` when `g_pSource2ModAddresses` is still null. Two consequences: * the target expression must not capture anything — it cannot name a local or `this`; globals and function calls are fine; * the translation unit needs Source2Mod globals in scope. A header that declares hooks outside the plugin's main file uses `SOURCE2MOD_GLOBALVARS()` so `g_pSource2ModAddresses` is known: ```cpp title="hooks/Hooks.h" #include "source2mod/ISource2ModAddresses.h" // The ADDR_* targets below read g_pSource2ModAddresses at KHOOK_INIT(). SOURCE2MOD_GLOBALVARS(); ``` A target that resolves to null at init — a missing gamedata entry, an optional `ADDR_*` that was not found — is logged and the hook is skipped. Installing and removing everything [#installing-and-removing-everything] `KHOOK_INIT()` in `Load()` resolves and installs every hook the plugin declared with the macros; `KHOOK_DESTRUCT()` in `Unload()` takes them all down: ```cpp title="plugin.cpp" bool Plugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) { SOURCE2MOD_SAVEVARS(); g_pSource2Server = api->GetSource2Server(); // anything a hook reads has to be set first // The KHook commit check inside returns false out of Load() on a mismatch. KHOOK_INIT(); return true; } bool Plugin::Unload(char* error, size_t maxlen) { KHOOK_DESTRUCT(); return true; } ``` A hook whose target cannot be resolved — a gamedata entry that is missing, a vtable not found, an instance still null — is logged and skipped; the rest still go in. (`ISource2ModHook::InitAll()` is the call that returns `false` in that case, if you want to know.) A hook declared with a `nullptr` target is left alone and counts as success. `KHOOK_INIT()` is also where the [KHook commit check](/docs/development/compatibility#the-khook-check) happens: a plugin built against a different KHook than the core runs gets `false` returned out of `Load()`, with the reason in `error`. A plugin that never calls `KHOOK_INIT()` — no hooks of its own — is not checked. Order matters: `KHOOK_INIT()` reads the instance pointers and calls the Source2Mod, so it goes after `SOURCE2MOD_SAVEVARS()` and after you have set the globals your virtual hooks name. The list `KHOOK_INIT()` walks is per plugin binary: every hook wrapper registers itself as it is constructed, whatever file it is in, and `KHOOK_DESTRUCT()` takes them down last-first. Hooks installed later [#hooks-installed-later] `KHOOK_INIT()` installs the hooks that exist **when it runs**. A hook that is a member of an object you create after `Load()` is registered but not installed — install it yourself with `m_hX.Init()`, typically in that object's constructor (the members are built before the constructor body runs): ```cpp class CChatFilter { public: KHook::Return Hook_ClientCommand(ISource2GameClients* pThis, CPlayerSlot slot, const CCommand& args); CChatFilter() { m_hClientCommand.Init(); // KHOOK_INIT() ran long ago } private: KHOOK_VIRTUAL(m_hClientCommand, &ISource2GameClients::ClientCommand, &g_pSource2GameClients, &CChatFilter::Hook_ClientCommand, nullptr); }; auto* pFilter = new CChatFilter(); // the hook goes in here ... delete pFilter; // and comes out here ``` `Init()` resolves the target the macro was given exactly as `KHOOK_INIT()` would: it reads `&pInstance` now, looks the vtable up, resolves the gamedata entry or calls the `ADDR_*` getter. It returns `false` and logs when that fails, and it can simply be called again — a hook waiting for an instance that does not exist yet goes in with the first call that finds it. On a hook that is already installed it does nothing; `m_hX.IsInstalled()` tells you which is which. A hook declared with a `nullptr` target is placed the explicit way: ```cpp m_hX.Init(address); // KHOOK_MEMBER / KHOOK_FUNCTION: ISource2ModMemory or typed function pointer m_hX.Init(pInstance); // KHOOK_VIRTUAL, one object m_hX.InitGlobal(vtable); // KHOOK_VIRTUAL, a whole vtable ``` Removing hooks [#removing-hooks] `KHOOK_DESTRUCT()` detaches every hook and deletes it — deleting is what takes the detour down. One hook can also go on its own: `m_hX.Destruct()`. That is final: the wrapper no longer owns a KHook object, so it cannot be installed again — to hook the same thing later, create a new owner. Destroying the owner does the same. Each wrapper's destructor calls `Destruct()` and takes itself off the plugin's hook list, so deleting an object removes its hooks, and a later `KHOOK_DESTRUCT()` never touches a hook that is already gone. `Destruct()` is safe to call twice: an object still alive at `Unload()` is covered by `KHOOK_DESTRUCT()`, and deleting it afterwards is harmless. Install and remove hooks on the game thread, and never destroy an owner from inside one of its own handlers — removing a hook synchronously under its own callback can deadlock or crash. Defer it to the next frame. Take every hook down before your plugin unloads. A live hook pointing into an unmapped library crashes the server on the next call. The wrapper's members [#the-wrappers-members] What `KHOOK_MEMBER` / `KHOOK_FUNCTION` / `KHOOK_VIRTUAL` declare is a `CSource2ModMemberHook`, `CSource2ModFunctionHook` or `CSource2ModVirtualHook`: | member | does | | ------------------------------------------------------- | ----------------------------------------------------------------------- | | `Init()` | resolves the macro's target and installs; `false` on failure, retryable | | `Init(address)` / `Init(pInstance)` / `InitGlobal(...)` | installs on an explicit target | | `Destruct()` | detaches and deletes the KHook object; final | | `IsInstalled()` | whether it is installed | | `CallOriginal(...)` | runs the original past every hook | | `Get()` / `operator->` | the KHook object underneath (`KHook::Member`, `Function` or `Virtual`) | See the [ISource2ModHooks reference](/docs/core-api/ISource2ModHooks). # Game Hooks (/docs/hooks/game-hooks) Most plugins hook the same few dozen game functions: `TakeDamage`, `CanAcquire`, `PostThink`, the movement services, the jump code. Every one of them written as a `KHOOK_MEMBER` ties the plugin to that function's exact prototype and to a signature of its own — and when Valve moves an argument or changes the order between Windows and Linux, every plugin has to be rebuilt. `ISource2ModGameHooks` holds those hooks in the core. You register a handler; the core resolves the function from **its** gamedata, places the detour the first time anybody listens and takes it out again once the last listener is gone. A changed prototype is fixed in the core's one call site, and the plugins keep running. This is what puts a plugin on the **stable tier** — see [Compatibility](/docs/development/compatibility). ```cpp title="plugin.cpp" #include "source2mod/ISource2ModGameHooks.h" bool MyPlugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) { SOURCE2MOD_SAVEVARS(); // Pre: halve every hit on a player. m_hDamage = g_pSource2ModGameHooks->HookTakeDamage([](TakeDamageContext& ctx, bool post) -> Action { if (ctx.entity && ctx.entity->IsPlayerPawn()) ctx.info->m_flDamage = ctx.info->m_flDamage() * 0.5f; // by pointer: the original sees it return Action::Ignore; }, false); // Post: react to what CanAcquire decided -- a method as the handler. g_pSource2ModGameHooks->HookCanAcquire(SOURCE2MOD_MEMBER(this, &MyPlugin::OnCanAcquire), true); return true; } ``` There is no `KHOOK_INIT()` in this: the plugin declared no hook of its own. No plugin id either — whose a handler is, the core reads off the handler itself (see [Handlers and owners](#handlers-and-owners)). The handler [#the-handler] Every hook hands its handler a **context** — a plain struct with the hooked object, the function's arguments and, where the function returns something, `result` — plus `post`, which pass this is. The handler answers the way a KHook handler does: an action, and for a function with a return value the value that goes with it (`GameHookReturn`). | Answer | Pre | Post | | ------------------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------ | | `Action::Ignore` | nothing changed | nothing changed | | `{ Action::Override, value }` | the original still runs, the function returns `value` | the function returns `value` instead of the original's | | `{ Action::Supersede, value }` | the original does not run, the function returns `value`; the remaining pre handlers are skipped | — | ```cpp // Nobody moves while frozen: CanMove returns false. g_pSource2ModGameHooks->HookCanMove([](CanMoveContext& ctx, bool post) -> GameHookReturn { if (IsFrozen(ctx.pawn)) return { Action::Supersede, false }; return Action::Ignore; }, false); ``` An action alone takes its value from `ctx.result`, so setting the field and returning `Action::Supersede` works too — but keep the two in one statement: an `if` without braces around `ctx.result = x; return Action::Supersede;` guards only the first of them. A function without a return value (PostThink, the movement functions) answers the action alone. Arguments passed **by pointer** (the damage info, the move data, the weapon) can be changed in place and the original sees the change. Arguments passed **by value** (a speed, a flag) cannot; a handler that needs to change one keeps a raw `KHOOK_MEMBER` of its own and uses [`KHook::Recall`](/docs/hooks/handlers#changing-the-arguments). Handlers run in registration order, pre before the original, post after it. In post, `ctx.result` holds what the original returned. A value a handler returns lands in `ctx.result` right away, so the next handler sees it. A `Supersede` stops the remaining pre handlers — the same rule as [command listeners](/docs/convars-commands/command-listeners). What the game would do: CallOriginal [#what-the-game-would-do-calloriginal] `ctx.CallOriginal()` runs the game's function now, with the arguments the game passed, past every hook on it — SourceHook's `SH_CALL` — and returns what it returned. From a pre handler that is how you learn what the game would answer: ```cpp g_pSource2ModGameHooks->HookCanAcquire([](CanAcquireContext& ctx, bool post) -> GameHookReturn { const AcquireResult game = ctx.CallOriginal(); if (game == AcquireResult::Allowed && IsBlockedHere(ctx)) return { Action::Supersede, AcquireResult::NotAllowedByMode }; return { Action::Supersede, game }; // it has run already -- do not let it run twice }, false); ``` In a pre handler the function **really runs**, side effects and all, so a handler that calls it answers `Supersede` with the value; with `Ignore` the game would run it a second time. A changed pointed-to object (the damage info, the move data) counts in that call; a scalar field changed in the context does not. In a post handler `ctx.result` already holds the game's answer. The same trick without a value — the game's own code, run with a setting flipped for one call: ```cpp CConVarRef sv_autobunnyhopping("sv_autobunnyhopping"); g_pSource2ModGameHooks->HookCheckJumpButtonLegacy([](LegacyJumpContext& ctx, bool post) -> Action { if (sv_autobunnyhopping.Get()) return Action::Ignore; sv_autobunnyhopping.Set(true); ctx.CallOriginal(); sv_autobunnyhopping.Set(false); return Action::Supersede; }, false); ``` Which functions [#which-functions] The set is the one Swiftly hooks, which is what most plugins reach for: | Group | Hooks | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Damage and items | `TakeDamage`, `CanAcquire`, `CanUse`, `DropWeapon` | | Player | `CanMove`, `PostThink`, `ProcessUsercmds`, `SimulateUserCommands`, `RunCommand` | | Entities | `AcceptInput`, `Touch` | | Movement services | `AirAccelerate`, `AirMove`, `CanUnduck`, `CategorizePosition`, `CheckFalling`, `CheckParameters`, `CheckVelocity`, `CheckWater`, `Duck`, `Friction`, `FullWalkMove`, `GroundAccelerate`, `LadderMove`, `MoveInit`, `PlayerMove`, `ProcessMovement`, `SetupMove`, `TryPlayerMove`, `WalkMove`, `WaterMove` | | Jump | `OnJumpLegacy`, `OnJumpModern`, `CheckJumpButtonLegacy`, `CheckJumpButtonModern` | Every context is listed in the [ISource2ModGameHooks reference](/docs/core-api/ISource2ModGameHooks). Two things to know: * `GroundAccelerate`'s arguments come in a different order on Windows and Linux. The context always has them in one order; that swap is the core's. * `Touch` is a vtable hook on `CBaseEntity`'s own table, so an entity class that overrides `Touch` is not seen. `CanUse` and `DropWeapon` are vtable hooks on the weapon services; everything else is found by signature. `IsAvailable(GameHook::X)` says whether the core's gamedata has the function on this platform. Handlers can be registered either way; on a missing entry they never run. Handlers and owners [#handlers-and-owners] A handler is a `Source2ModCallback`: a lambda, a free or static function, or an object and one of its methods — SourceHook's `SH_STATIC` / `SH_MEMBER`, spelt `SOURCE2MOD_STATIC(func)` / `SOURCE2MOD_MEMBER(this, &Class::Method)` (or just `func` and `{ this, &Class::Method }`). Whose it is, the core reads off the handler: its code lies in the plugin's library, and that is the owner. No call takes a plugin id, and what a plugin still holds when it unloads, the core drops for it — nothing to do in `Unload()`. `HookX()` returns a `GameHookId`. A plugin may put several handlers on one function — a pre and a post, two features that both care about damage — and drop any one of them with `UnhookX(id)`, or with the same function or method it hooked with (a lambda has no such identity; it goes by its id): ```cpp GameHookId m_hDamage = 0; m_hDamage = g_pSource2ModGameHooks->HookTakeDamage(OnTakeDamage, false); g_pSource2ModGameHooks->HookPostThink(SOURCE2MOD_MEMBER(this, &MyPlugin::OnPostThink), true); ... g_pSource2ModGameHooks->UnhookTakeDamage(m_hDamage); g_pSource2ModGameHooks->UnhookPostThink(SOURCE2MOD_MEMBER(this, &MyPlugin::OnPostThink), true); ``` Unhooking from inside the handler is fine: the detour itself comes out on the next frame, never from inside its own dispatch. Debugging [#debugging] `s2m hookdebug ` in the server console logs every call of the matching game hooks: each plugin's answer, the outcome and the return value; `TakeDamage` adds the victim, the damage and the damage bits. ``` Source2Mod hookdebug TakeDamage ... hookdebug CBaseEntity::TakeDamageOld pre: JailBreak.smx -> Supersede ... hookdebug CBaseEntity::TakeDamageOld pre: Supersede (return 0) victim=func_breakable damage=25.000000 bits=4 Source2Mod hookdebug off ``` When to still hook yourself [#when-to-still-hook-yourself] * The function is not in the set. * You need to change an argument passed by value, or call the original with different arguments (`KHook::Recall`). Those are raw hooks — [`KHOOK_MEMBER`](/docs/hooks/function-hooks), [`KHOOK_VIRTUAL`](/docs/hooks/virtual-hooks) — and they put the plugin on the raw tier: it hooks through KHook itself and is rebuilt when the function changes. `s2m list` shows which tier each plugin is on. Reference [#reference] * [`ISource2ModGameHooks`](/docs/core-api/ISource2ModGameHooks) — every hook, context and enum * [`ISource2ModTypes`](/docs/core-api/ISource2ModTypes) — `Source2ModCallback`, `SOURCE2MOD_STATIC`, `SOURCE2MOD_MEMBER` * [`ISource2ModApi::GameHooks()`](/docs/core-api/ISource2ModApi) — the same interface through the API # Gamedata for Hooks (/docs/hooks/gamedata) A hook needs to know **where** the function is. For an engine interface the compiler knows (`&ISource2Server::GameFrame`), but most of the game is not exported: functions are found by a byte **signature**, and virtuals of game classes by a vtable **offset**. Both live in gamedata, one value per platform, so a game update means editing a JSON file instead of rebuilding plugins. This page covers gamedata as far as hooking needs it. The file format itself is documented in [Gamedata](/docs/development/gamedata). Where gamedata comes from [#where-gamedata-comes-from] Source2Mod reads **every** `.json` file in its gamedata folder (`addons/source2mod/gamedata/`, `SOURCE2MOD_GAMEDATA_DIR()` at runtime) into one table, `gamedata.json` first and the rest in name order. The first definition of a key wins, so a key in `gamedata.json` cannot be redefined by another file. Ship your plugin's entries as a file of their own with keys of their own (`myplugin.json`), and they resolve exactly like Source2Mod's. Each entry is keyed by name and carries per-platform values; only the current platform's value is loaded. ```json title="gamedata/myplugin.json" { "CCSGameRules::TerminateRound": { "signatures": { "library": "server", "windows": "48 8B C4 4C 89 48 ? 48 89 48 ? 55 41 54", "linux": "55 48 89 E5 41 57 41 56 49 89 FE 41 55 41 54 53 48 81 EC ? ? ? ? 48 8D 05 ? ? ? ? F3 0F 11 85 ? ? ? ? 89 B5" } }, "CCSPlayerController::Respawn": { "offsets": { "windows": 274, "linux": 276 } } } ``` (Both entries are real, from Source2Mod's own `gamedata.json`; they are shown here only as examples of the shape. Use your own key names.) How hooks use it [#how-hooks-use-it] | you write | gamedata used | resolved through | | ------------------------------------------------------------------ | --------------------------------------------- | ----------------------------------------- | | `KHOOK_MEMBER(m_hX, "Name", ...)` / `KHOOK_FUNCTION` | `signatures` | `ISource2ModGameConfig::ResolveSignature` | | `KHOOK_VIRTUAL(m_hX, "Name", KHOOK_VTABLE(...), ...)` | `offsets` | `ISource2ModGameConfig::GetOffset` | | `KHOOK_VIRTUAL(m_hX, KHOOK_GAMEDATA(&Class::Method, "Name"), ...)` | `offsets`, checked against the header's index | the same | | [`ISource2ModGameHooks`](/docs/hooks/game-hooks) | the core's own `signatures` and `offsets` | the core | | `GAMECONFIG_RESOLVE("Name")` | `signatures` | the same, for calling | | `GAMECONFIG_OFFSET("Name")` | `offsets` | the same, for `CALL_VIRTUAL` | `ResolveSignature` looks the entry's `library` up among the loaded modules (`server`, `engine2` — `engine` is accepted as an alias — `tier0`, `vscript`…), then finds the function by exported symbol when the value starts with `@`, or by pattern scan otherwise. It returns a null `ISource2ModMemory` when the module, the symbol or the pattern is missing. `GetOffset` returns `-1` for a missing key. See [ISource2ModGameConfig](/docs/core-api/ISource2ModGameConfig). Writing a signature [#writing-a-signature] A signature is the first bytes of the function, written as hex, with `?` for every byte that is not stable: ```text 48 89 5C 24 10 57 48 83 EC 30 48 8B DA 48 8B F9 48 85 C9 55 48 89 E5 41 57 41 56 49 89 FE 41 55 41 54 53 48 81 EC ? ? ? ? ``` * **Start at the function's first byte.** The address the scan finds is where the detour goes; a pattern that starts in the middle of a function places the hook in the middle of it. * **Wildcard what the compiler or linker will move**: relative call and jump targets, RIP-relative addresses (`48 8D 05 ? ? ? ?`), stack frame sizes and field displacements that change when a class grows (`48 81 EC ? ? ? ?`). * **Keep it unique and short.** The scan returns the first match; a pattern that also matches another function silently resolves to the wrong one. Add bytes until exactly one function matches, then stop — every extra byte is one more that can change in the next update. * **Exported functions** do not need a pattern: write the symbol with a leading `@` (`"@SymbolName"`) and it is looked up by name in the module's exports. Tools such as IDA with a signature-maker plugin produce these, but always check the result is unique in the current binary. Windows and Linux are different binaries [#windows-and-linux-are-different-binaries] Every entry has a value per platform because nothing carries over between them: * **Signatures** — MSVC and Clang/GCC generate different code for the same source. Write and verify each platform's pattern against its own binary (`server.dll` / `libserver.so`). * **Vtable indices** — the two ABIs lay vtables out differently (the Itanium ABI used on Linux has two destructor entries where MSVC has one, and MSVC merges overloads differently). The same virtual can sit a few slots apart: `CCSPlayerController::Respawn` is 274 on Windows and 276 on Linux, while `CBaseEntity::Teleport` is 165 and 164. Never derive one from the other. * **Argument order** — the prototype itself can differ, as it does for `TerminateRound` and `GroundAccelerate`. The handler must follow; see [Platform differences](/docs/hooks/handlers#platform-differences). * **Field offsets** — the layout of non-schema structs and of padding can differ between the platforms, so a raw offset into an object read in a hook needs a per-platform value too. Networked and schema fields do not: the SDK's schema accessors look their offsets up at runtime from the schema system. Finding a vtable index [#finding-a-vtable-index] * From the declaration: when a header's class matches the game slot for slot, `&Class::Method` in `KHOOK_VIRTUAL` gives the index with nothing written down. * From the binary: find the vtable (by its RTTI name — `ISource2ModModule::GetVirtualTableByName` does this at runtime) and count the entries up to the function. Remember the table starts after the RTTI pointer: index 0 is the first function. * Put the number in gamedata under `offsets`, per platform, and name that key in `KHOOK_VIRTUAL`. Verifying [#verifying] * **Read the log.** A hook whose signature did not resolve logs `KHook: 'Name' was not resolved; hook not installed`, one whose offset is missing logs `KHook: offset 'Name' is missing from gamedata; hook not installed`, and `KHOOK_INIT()` returns `false`. * **Check the hook fires** — a log line in the handler, once, on both platforms. * **Check the arguments make sense** — print a pointer's class name or a float argument. Garbage means the prototype or the platform order is wrong, not the signature. * **After every game update**, recheck your own entries. Source2Mod refuses to start when one of its required signatures breaks, but your plugin's entries are yours to watch. A signature that matches the wrong function does not fail — it hooks the wrong function, with your handler reading its arguments as if they were the ones you expected. Uniqueness is the one check you cannot skip. # Writing Handlers (/docs/hooks/handlers) A handler is an ordinary member function of the class that declared the hook. It receives the hooked call's arguments and returns a `KHook::Return` that tells KHook what to do with the original. The signature [#the-signature] The object that was actually called arrives as the first parameter; the rest are the function's own arguments, in the order the **machine code** takes them. A `KHOOK_FUNCTION` handler has no object, only the arguments. ```cpp // void CCSPlayer_WeaponServices::DropWeapon(CBasePlayerWeapon*, Vector*, Vector*) KHook::Return OnDropWeapon(CCSPlayer_WeaponServices* pThis, CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity); ``` The macros derive the hook's type from this declaration, so the declaration **is** the prototype KHook uses to read the registers and the stack. Nothing checks it against the game. A wrong parameter list compiles fine and then reads garbage — or corrupts the call — at runtime. * **Match the real function**, not an older port or a Source 1 header. Where Source2Mod resolves the function, its typedef in `ISource2ModAddresses.h` is the checked prototype: `CCSPlayerPawn_PostThink_t` takes nothing but the pawn, `CCSPlayer_MovementServices_TryPlayerMove_t` is declared `void` because no caller reads what is left in `al`, and so on. * **Widths matter.** A `bool` where the game passes an `int`, or a `float` where it passes a `double`, reads the wrong part of the register. * **Returning a struct or a non-trivial class by value** changes the calling convention (a hidden return pointer). Stick to what the typedef says. Platform differences [#platform-differences] The same function does not always take its arguments in the same order on Windows and Linux: the compilers are free to lay the parameters out differently, and they do. `CCSGameRules::TerminateRound` is the standing example — the delay and the reason swap places: ```cpp title="ISource2ModAddresses.h" #ifdef _WIN32 using CCSGameRules_TerminateRound_t = void (FASTCALL*)(CCSGameRules* pThis, float flDelay, uint32 nReason, uint32* pnTeamId); #else using CCSGameRules_TerminateRound_t = void (FASTCALL*)(CCSGameRules* pThis, uint32 nReason, uint32* pnTeamId, float flDelay); #endif ``` The handler has to follow suit, with an `#ifdef` on the declaration **and** the definition. This is TroubleTerroristTown's round-end hook (the plugin spells the reason as its own 32-bit enum; any type of the same width works): ```cpp title="Hooks.h" class CHooks { public: // Same argument order as CCSGameRules_TerminateRound_t: the delay and // the reason swap places between platforms, the team goes by pointer. #ifdef _WIN32 KHook::Return OnTerminateRound(CCSGameRules* pThis, float flDelay, uint32 nReason, uint32* pnTeamId); #else KHook::Return OnTerminateRound(CCSGameRules* pThis, uint32 nReason, uint32* pnTeamId, float flDelay); #endif KHOOK_MEMBER(m_hTerminateRound, ADDR_TERMINATE_ROUND(), &CHooks::OnTerminateRound, nullptr); }; ``` ```cpp title="OnTerminateRound.cpp" #ifdef _WIN32 KHook::Return CHooks::OnTerminateRound(CCSGameRules* pThis, float flDelay, uint32 nReason, uint32* pnTeamId) #else KHook::Return CHooks::OnTerminateRound(CCSGameRules* pThis, uint32 nReason, uint32* pnTeamId, float flDelay) #endif { if (g_bRoundEnding) return { KHook::Action::Supersede }; // a round end is already in flight: swallow this one return { KHook::Action::Ignore }; } ``` The winning team goes in as a pointer, null when there is none. Other known cases are marked in `ISource2ModAddresses.h` (`GroundAccelerate` swaps the wish direction and the frame time). When you write a signature of your own, check the prototype on **both** binaries — see [Gamedata](/docs/hooks/gamedata). Returning an action [#returning-an-action] A handler returns `KHook::Return`: the action, plus the value when the hooked function returns one. ```cpp return { KHook::Action::Ignore }; // void handlers return { KHook::Action::Ignore, true }; // the value is not used with Ignore return { KHook::Action::Override, false }; // the original runs, false is returned return { KHook::Action::Supersede, false }; // the original is skipped, false is returned ``` What KHook does with each is in [How a hooked call runs](/docs/hooks#how-a-hooked-call-runs). For example, turning a connection away before the game sees it: ```cpp KHook::Return Plugin::Hook_ClientConnect(ISource2GameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, bool unk1, CBufferString* pRejectReason) { if (IsBanned(xuid)) return { KHook::Action::Supersede, false }; // the game never sees this connection return { KHook::Action::Ignore, true }; } ``` Changing what the original sees [#changing-what-the-original-sees] Through pointers [#through-pointers] Arguments passed by pointer or reference can simply be changed in a **Pre** handler: the original runs afterwards and reads what you left there. This is the sample plugin's damage scaling: ```cpp KHook::Return SamplePlugin::Hook_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult) { // A Pre hook: the original runs after it, with whatever is in pInfo by then. if (pInfo && sample_damage_scale.Get() != 1.0f) pInfo->m_flDamage *= sample_damage_scale.Get(); return { KHook::Action::Ignore, 0 }; } ``` Changing the arguments [#changing-the-arguments] Arguments passed by value cannot be changed that way. `KHook::Recall` carries the call on with **new arguments**: the hooks after yours, the original and every Post handler see them instead of the ones the caller passed. Your own handler is not called again — the call picks up right after it. The action you pass is your handler's action as usual, so `Ignore` lets the original run with the new arguments. `Recall` hands that return back, so it is the whole of the handler's `return`: ```cpp // Hooked with KHOOK_MEMBER(m_hSetModel, ADDR_SET_MODEL(), &Plugin::Hook_SetModel, nullptr) KHook::Return Plugin::Hook_SetModel(CBaseModelEntity* pThis, const char* pszModel) { if (V_strcmp(pszModel, "models/weapons/old.vmdl")) return { KHook::Action::Ignore }; const char* pszReplacement = "models/weapons/new.vmdl"; return KHook::Recall(ADDR_SET_MODEL(), KHook::Return{ KHook::Action::Ignore }, pThis, pszReplacement); } ``` The first argument of `Recall` only supplies the function's **type** — KHook calls the chain it is already in, not that pointer. For a detour, pass the typed function pointer (an `ADDR_*` getter or your own typedef); for a virtual hook, the member function pointer (`&ISource2GameClients::ClientCommand`). Do not pass a pointer to an SDK schema helper such as `&CBaseModelEntity::SetModel` — its parameter list is the helper's (it has a `HookChain` argument), not the game function's. This is SourceHook's `RETURN_META_NEWPARAMS`. Only call it from inside a handler — KHook aborts on a recall made anywhere else. Calling the original [#calling-the-original] From a Pre handler, `CallOriginal` runs the real function with the arguments you give it, past every hook on it — then supersede, so KHook does not run it a second time. The member declared by the macro has `CallOriginal` itself and forwards `->` to the KHook object underneath: ```cpp // Public plugin: auto-bunnyhop for a whitelisted player, for this one call. sv_autobunnyhopping.Set(true); m_hCheckJumpButtonLegacy->CallOriginal(pThis, mv); sv_autobunnyhopping.Set(false); return { KHook::Action::Supersede }; ``` Use it to act on the original's result from inside the Pre handler, to run it with changed global state around it, or to run it more than once: ```cpp const int64_t ret = m_hTakeDamageOld.CallOriginal(pThis, pInfo, pResult); // ... pResult is filled in now ... return { KHook::Action::Supersede, ret }; ``` Outside a handler, `KHook::CallOriginal(function, args...)` does the same for any function — hooked by you or by someone else. For a member function pointer it finds the original behind a vtable hook or a detour; for a plain function pointer it finds the original behind a detour. For Source2Mod's `ADDR_*` functions there is the shorter [`SOURCE2MOD_ORIGINAL`](/docs/hooks/addresses#calling-past-the-hooks). `Supersede` skips the original **for every remaining Pre handler too**. If you called the original yourself and only want to stop KHook calling it a second time, supersede is still the right answer — but hooks registered after yours will not see it run. Reading the return value in Post [#reading-the-return-value-in-post] In a Post handler the original has already run. These are only valid inside a Post handler: ```cpp bool original = KHook::GetOriginalReturn(); // what the original returned bool overriden = KHook::GetOverrideReturn(); // what a Pre handler overrode it with bool current = KHook::GetCurrentReturn(); // what the call is going to return bool skipped = KHook::WasOriginalFunctionSkipped(); // did a Pre handler supersede it ``` `GetOriginalReturn` is meaningless when the original was superseded — check `WasOriginalFunctionSkipped()` first. Recursion [#recursion] A handler that calls the function it hooks — through the game, a schema helper or an `ADDR_*` pointer — runs straight back into its own hook. Unless the handler guards against it, that is endless recursion. Call the original instead: | from inside the hook of… | call it as | | -------------------------------- | -------------------------------------- | | your own `m_hX` | `m_hX.CallOriginal(...)` | | an `ADDR_*` function | `SOURCE2MOD_ORIGINAL(ADDR_...())(...)` | | a function a schema helper wraps | the helper with `HookChain::Bypass` | ```cpp // Inside a TerminateRound hook: end the round now, without re-entering the hook. pGameRules->TerminateRound(5.0f, static_cast(nReason), 0, HookChain::Bypass); ``` `CCSGameRules::TerminateRound` and `CCSPlayerController::TakeDamage` already default to `HookChain::Bypass`; every other helper defaults to `HookChain::Run`. See [Addresses](/docs/hooks/addresses#hookchain-on-schema-helpers). The opposite case is fine: calling **another** hooked function from a handler runs that function's hooks as usual. Threads and context [#threads-and-context] The handler's context is the object that declared the hook, so handlers are ordinary member functions with access to your state. KHook itself is thread-safe, but your handler runs on whatever thread called the hooked function — engine code does not always run on the game thread. Keep a handler that can be reached from elsewhere free of anything that is not thread-safe, or hand the work to the game thread. Keep hot handlers cheap. `PostThink`, the movement functions and `PostEventAbstract` run for every player every tick; return early on the cases you do not care about. # Hooking (/docs/hooks) Source2Mod hooks with **KHook** — Metamod:Source's own detour library, on the one engine Metamod runs for the whole server. If you have written a Metamod 2.0 plugin, you already know the API. The SDK wraps it in a set of `KHOOK_*` macros so a hook is one line in your class, and Source2Mod does the resolving, attaching and tearing down for you. This section goes from the ground up. This page covers what KHook is, how a hooked call runs and which macro fits which target; the subpages go through each part in detail. ISource2ModGameHooks: the game functions plugins hook most, detoured once by the core — a handler and a context, no KHook of your own. KHOOK_VIRTUAL on one instance or a whole vtable. KHOOK_MEMBER / KHOOK_FUNCTION by gamedata name, ADDR_* getter or address; KHOOK_INIT and KHOOK_DESTRUCT. Signatures, KHook::Return, CallOriginal, changing arguments and return values. ADDR_* functions, SOURCE2MOD_ORIGINAL and HookChain. Signatures and offsets from a hooking point of view. KHook objects without the macros, and the C-style API underneath. KHook in one minute [#khook-in-one-minute] A detour library lets you run your own code before and after a function of the game or the engine — or instead of it. Metamod ships one and hands it to every plugin, so that all plugins on a server patch functions through the **same** backend. Two libraries patching the same function independently cannot see each other, and that ends in crashes that are very hard to trace. For twenty years that library was **SourceHook**. It was built to solve conflicts between virtual table hooks, and its whole API revolves around virtual functions. Source 2 plugins need much more than that — plenty of what is worth hooking is a plain function found by signature — so plugins started bringing their own detour libraries, and the old conflicts came back. **KHook** is the successor, written for Metamod 2.0. It detours both vtable entries and plain function addresses, it is thread-safe, and the goal is for every Metamod 2.0 plugin to use it so Metamod owns detouring again. The KHook library itself is documented in its [README](https://github.com/Kenzzer/KHook/blob/master/README.md); this section covers it as far as a Source2Mod plugin needs it. One engine for everyone [#one-engine-for-everyone] Source2Mod does **not** run a hook engine of its own. It takes KHook from Metamod when it loads and hands that same engine to every plugin through `SOURCE2MOD_KHOOK_INTERFACE`. This is deliberate. `CallOriginal` and `Supersede` walk the handler chain of the engine that owns the patch. Two independent engines patching the same address cannot see through each other — one of them silently calls the other's trampoline instead of the real original. One engine means one chain per address, so the core's hooks, your plugin's hooks and the hooks of the Metamod plugins next to you compose correctly. You never fetch it. `SOURCE2MOD_EXPOSE` declares the globals and `SOURCE2MOD_SAVEVARS` fills them: ```cpp title="plugin.cpp" Plugin g_Plugin; SOURCE2MOD_EXPOSE(my_plugin, g_Plugin); bool Plugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) { SOURCE2MOD_SAVEVARS(); // KHook is live from here on ... } ``` The KHook headers come from the SDK's `vendor/khook` submodule. The SDK stamps that commit into your binary and `KHOOK_INIT()` refuses the load when it differs from the one the core was built with, so a plugin built against one KHook never hooks through another. A plugin with no hooks of its own is never checked — it does not hook, so it does not care. How a hooked call runs [#how-a-hooked-call-runs] Once a function is detoured, every call to it goes through the same loop: 1. Every **Pre** callback runs, with the function's own arguments. 2. Each callback reports an **action**. KHook settles on one for the call. 3. Unless a Pre callback **superseded** it, the original function runs. 4. Every **Post** callback runs — the original has already returned by now. 5. The call returns: the original's value, or the one a callback supplied. | `KHook::Action` | meaning | | --------------- | ---------------------------------------------------------------- | | `Ignore` | the callback did nothing | | `Override` | the original still runs, but the callback's return value is used | | `Supersede` | the original is skipped, the callback's return value is used | Two things follow from that loop: * `Supersede` in a **Post** callback cannot stop the original — it has already run. * When several hooks want to decide the return value, the **first** `Override` or `Supersede` wins. KHook makes no attempt to resolve such conflicts, so do not rely on being the one that wins. Source2Mod's own listeners — `REGISTER_CON_LISTENER`, `HOOK_GAME_EVENT`, the net message hooks, entity output listeners — return `Action`, which is the same type: `ISource2ModTypes.h` aliases it to `KHook::Action`. Three layers [#three-layers] | layer | what it is | use it when | | ---------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------- | | `KHOOK_*` macros (`ISource2ModHooks.h`) | one line per hook, resolved and installed by Source2Mod | almost always | | `KHook::Virtual` / `Member` / `Function` | KHook's own class templates | a hook the macros do not fit — see [Raw KHook](/docs/hooks/raw-khook) | | `KHook::SetupHook` / `SetupVirtualHook` | the raw C-style API the classes are built on | practically never | A hook is one line [#a-hook-is-one-line] Every hook is a member of your class, declared with a macro from `ISource2ModHooks.h` (pulled in by `ISource2ModPlugin.h`). The macro takes the hook's type from the **handler** it names — the handler's first parameter is the hooked class, the rest are the function's own arguments — so nothing is spelled twice. Declare the handler above the macro. ```cpp title="plugin.h" class Plugin final : public ISource2ModPlugin { public: bool Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) override; bool Unload(char* error, size_t maxlen) override; KHook::Return Hook_GameFrame(ISource2Server* pThis, bool simulating, bool bFirstTick, bool bLastTick); KHook::Return Hook_LoadEventsFromFile(IGameEventManager2* pThis, const char* pszFile, bool bSearchAll); KHook::Return Hook_PostThink(CCSPlayerPawn* pThis); KHook::Return Hook_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); KHook::Return Hook_SetModel(CBaseModelEntity* pThis, const char* pszModel); private: KHOOK_VIRTUAL(m_hGameFrame, &ISource2Server::GameFrame, &g_pSource2Server, nullptr, &Plugin::Hook_GameFrame); // Post only KHOOK_VIRTUAL(m_hLoadEventsFromFile, &IGameEventManager2::LoadEventsFromFile, KHOOK_VTABLE("server", "CGameEventManager"), &Plugin::Hook_LoadEventsFromFile, nullptr); // Pre only KHOOK_MEMBER(m_hPostThink, "CCSPlayerPawn::PostThink", &Plugin::Hook_PostThink, nullptr); // gamedata entry name KHOOK_MEMBER(m_hTakeDamageOld, ADDR_TAKE_DAMAGE_OLD(), &Plugin::Hook_TakeDamageOld, nullptr); // address Source2Mod already resolved KHOOK_MEMBER(m_hSetModel, ADDR_SET_MODEL(), &Plugin::Hook_SetModel, nullptr); }; ``` The last two arguments are always the Pre and the Post callback; `nullptr` leaves a side empty. A function Source2Mod resolves itself has an `ADDR_*()` getter in `ISource2ModAddresses.h`; pass it instead of a gamedata name and your plugin needs no gamedata entry, no signature of its own, and follows Source2Mod's gamedata through game updates. The getter is read when `KHOOK_INIT()` runs, after `SOURCE2MOD_SAVEVARS()` has filled `g_pSource2ModAddresses`. The full list is on [Addresses](/docs/hooks/addresses). `KHOOK_INIT()` in `Load()` installs every hook declared this way and `KHOOK_DESTRUCT()` in `Unload()` takes them all down — see [Function hooks](/docs/hooks/function-hooks#installing-and-removing-everything). Which macro to pick [#which-macro-to-pick] | you want to hook | use | target | | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | a game function in the core's set (`TakeDamage`, `CanAcquire`, `PostThink`, the movement services…) | [`ISource2ModGameHooks`](/docs/hooks/game-hooks) | a handler — no hook of your own | | an engine call the core already fans out (`GameFrame`, `ClientPutInServer`, `ClientDisconnect`, `StartupServer`…) | an [`ISource2ModListener` callback](/docs/events/listeners#engine-callbacks) | — | | a method of an engine interface you hold a pointer to (`ISource2Server::GameFrame`) | `KHOOK_VIRTUAL` | `&g_pInstance`, the function as `KHOOK_GAMEDATA(&Class::Method, "name")` to follow the core's gamedata | | a virtual of a game class, for every object of that class (`CCSPlayerController::Respawn`) | `KHOOK_VIRTUAL` | `KHOOK_VTABLE(module, class)`, function as a gamedata offset name or `&Class::Method` | | an interface that is not the class's first base (`IHLTVDirector` in `CCSHLTVDirector`) | `KHOOK_VIRTUAL` | `KHOOK_VTABLE_BASE(module, class, base)` | | a function Source2Mod already resolves (`TakeDamageOld`, `TerminateRound`, movement…) | `KHOOK_MEMBER` | an `ADDR_*()` getter | | a member function found by signature | `KHOOK_MEMBER` | a gamedata entry name | | a free function (no `this`) | `KHOOK_FUNCTION` | a gamedata entry name, `ADDR_*()` or an address | | something the macros cannot express | `KHook::Virtual` / `Member` / `Function` | see [Raw KHook](/docs/hooks/raw-khook) | Before you hook anything, check whether Source2Mod already dispatches it. [Game hooks](/docs/hooks/game-hooks) and the listener's engine callbacks cover most of what plugins detour; game events ([Events](/docs/events)), net and user messages ([Protobuf](/docs/protobuf/hooking)), console commands and chat ([ConVars & Commands](/docs/convars-commands)), entity outputs, sounds and transmit all have listener APIs that are already hooked once in the core — using those is cheaper and survives game updates without gamedata of your own. Coming from SourceHook [#coming-from-sourcehook] Earlier Source2Mod builds ran a private SourceHook engine. The mapping is: | SourceHook | KHook | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `SH_DECL_HOOK*` + `SH_ADD_HOOK` | `KHOOK_VIRTUAL(..., &pInstance, ...)` | | `SH_ADD_DVPHOOK` / `SH_ADD_MANUALDVPHOOK` | `KHOOK_VIRTUAL(..., KHOOK_VTABLE(module, class), ...)`, with a gamedata offset name or an index as the function | | `SH_DECL_MANUALHOOK*` + `SH_MANUALHOOK_RECONFIGURE` | `KHOOK_VIRTUAL` with an index or a gamedata offset name; raw: `KHook::Virtual` + `Configure(index)` | | `SH_DECL_INLINEHOOK*` + `SH_ADD_INLINEHOOK` | `KHOOK_MEMBER` / `KHOOK_FUNCTION` with a gamedata name, an address or an `ADDR_*` getter | | `SH_MEMBER(this, &Class::Handler)` | the context — the macros always use the enclosing object | | `META_IFACEPTR` / `SH_IFACEPTR` | the first handler parameter | | `RETURN_META` / `RETURN_META_VALUE` | `return { action }` / `return { action, value }` | | `RETURN_META_NEWPARAMS` | `return KHook::Recall(...)` — see [Changing the arguments](/docs/hooks/handlers#changing-the-arguments) | | `MRES_IGNORED` / `MRES_HANDLED` | `KHook::Action::Ignore` | | `MRES_OVERRIDE` | `KHook::Action::Override` | | `MRES_SUPERCEDE` | `KHook::Action::Supersede` | | `SH_CALL(...)` | `m_hX->CallOriginal(pThis, ...)` / `KHook::CallOriginal(&Class::Method, pThis, ...)` | | `META_RESULT_ORIG_RET(T)` | `KHook::GetOriginalReturn()` | | `META_RESULT_OVERRIDE_RET(T)` | `KHook::GetOverrideReturn()` | | `SH_REMOVE_HOOK` / `SH_REMOVE_HOOK_ID` | `KHOOK_DESTRUCT()` / `m_hX.Destruct()` / deleting the owner | `KHook::Action` and `META_RES` are **not** numerically compatible — `META_RES` has an extra `MRES_HANDLED` at 1, which shifts everything after it. There is nothing left to cast between: Source2Mod's `Action` *is* `KHook::Action`. Checklist [#checklist] * Set every global a virtual hook names **before** `KHOOK_INIT()`. * Declare the handler **above** the macro that names it. * The handler's parameters must match the real function — on **both** platforms. * A missing gamedata offset is refused, not installed at `-1` — read the log. * `KHOOK_VTABLE` finds the primary vtable only; for a base further in use `KHOOK_VTABLE_BASE`. * A silent virtual hook is often a direct call that never goes through the vtable. * Hooks on objects created after `Load()` need `m_hX.Init()`. * `Destruct()` is final; never destroy a hook's owner from its own handler. * Every hook comes down before the plugin unloads. See the [ISource2ModHooks reference](/docs/core-api/ISource2ModHooks) for every class and macro. # Raw KHook (/docs/hooks/raw-khook) The `KHOOK_*` macros cover almost every hook a plugin needs. Underneath they are plain KHook objects, and you can hold those directly when the wrapper does not fit: a hook moved between instances at runtime, attached to several objects at once, configured from something the macros cannot express, or used outside a plugin class. What the macros wrap [#what-the-macros-wrap] The macros wrap `KHook::Virtual`, `KHook::Member` and `KHook::Function` in `CSource2ModVirtualHook`, `CSource2ModMemberHook` and `CSource2ModFunctionHook`. Each wrapper owns its KHook object, registers itself in a per-plugin list that `KHOOK_INIT()` / `KHOOK_DESTRUCT()` walk, and remembers its target until the Source2Mod is ready to resolve it. You do not have to leave the wrapper to reach KHook: `m_hX.Get()` and `m_hX->` hand you the object underneath, so `m_hX->AddGlobal(...)` or `m_hX->Configure(...)` are one step away. Go fully raw only when you need to own the object's lifetime yourself. KHOOK_NEW [#khook_new] A raw KHook object carries the hooked function and the callbacks in its type. CTAD would derive that from the constructor arguments, but MSVC cannot, so `KHOOK_NEW` (`ISource2ModTypes.h`) takes the type from the member it is stored in instead — for a constructor's initialiser list: ```cpp title="plugin.h" class Plugin final : public ISource2ModPlugin { public: Plugin(); KHook::Return Hook_GameFrame(ISource2Server* pThis, bool simulating, bool bFirstTick, bool bLastTick); private: KHook::Virtual* m_hGameFrame = nullptr; }; ``` ```cpp title="plugin.cpp" Plugin::Plugin() : KHOOK_NEW(m_hGameFrame, &ISource2Server::GameFrame, this, nullptr, &Plugin::Hook_GameFrame) { } bool Plugin::Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) { SOURCE2MOD_SAVEVARS(); m_hGameFrame->Add(api->GetSource2Server()); // attach return true; } bool Plugin::Unload(char* error, size_t maxlen) { m_hGameFrame->Remove(g_pSource2Server); // detach delete m_hGameFrame; // deleting takes the hook down m_hGameFrame = nullptr; return true; } ``` The address lookup, `Add` / `Configure` and `Remove` / `delete` are all yours. `KHOOK_INIT()` and `KHOOK_DESTRUCT()` do not see raw objects. This is how the s2m core itself hooks — for example the overloaded `IGameEventSystem::PostEventAbstract`, disambiguated with a `static_cast`: ```cpp KHOOK_NEW(m_hPostEventAbstract, static_cast(&IGameEventSystem::PostEventAbstract), this, &Virtuals::Hook_PostEventAbstract, nullptr) ``` The three classes [#the-three-classes] | class | where it goes | attach / detach | | ------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `KHook::Virtual` | `Configure(&CLASS::Method)` or `Configure(index)` | `Add(pThis)` / `Remove(pThis)` for one object, `AddGlobal(pThis)` / `RemoveGlobal(pThis)` for every object sharing `pThis`'s vtable | | `KHook::Member` | `Configure(address)` or `Configure(&CLASS::Method)` | configuring places the detour | | `KHook::Function` | `Configure(address)` or `Configure(&Function)` | configuring places the detour | The constructors take the function (or nothing, and you `Configure` later), an optional context object, and the Pre and Post callbacks. With a context the callbacks are members of the context's class — what the macros always use; without one they are free functions. Deleting the object removes the detour. Every class has `CallOriginal(...)` to run the original past its hooks. A detour by gamedata name [#a-detour-by-gamedata-name] ```cpp KHook::Member* m_hPostThink = nullptr; // constructor KHOOK_NEW(m_hPostThink, this, &Plugin::Hook_PostThink, nullptr) // Load(), after SOURCE2MOD_SAVEVARS() if (ISource2ModMemory address = GAMECONFIG_RESOLVE("CCSPlayerPawn::PostThink")) m_hPostThink->Configure(address.RCast()); // Unload() delete m_hPostThink; ``` A whole vtable without an instance [#a-whole-vtable-without-an-instance] `AddGlobal` only reads the first pointer of what it is handed — the vtable pointer — so a local holding a vtable address stands in for an object. This is what `KHOOK_VTABLE` does for you: ```cpp void* m_pVTable = nullptr; ISource2ModModule* pServer = ISource2ModModule::New("server"); m_pVTable = pServer->GetVirtualTableByName("CCSPlayerController").GetPtr(); FREE_MODULE(pServer); if (m_pVTable) m_hRespawn->AddGlobal(reinterpret_cast(&m_pVTable)); // Unload(): RemoveGlobal with the same stand-in, then delete m_hRespawn->RemoveGlobal(reinterpret_cast(&m_pVTable)); ``` Keep `m_pVTable` alive (a member, not a local) — the same pointer is needed to remove the hook. Finding originals [#finding-originals] Two free functions answer "what would run without the hooks": ```cpp void* KHook::FindOriginal(void* function); // behind a detour void* KHook::FindOriginalVirtual(void** vtable, int index); // behind a vtable hook ``` Both hand the input back unchanged when nothing is hooked. `SOURCE2MOD_ORIGINAL` and `HookChain::Bypass` are built on them — see [Addresses](/docs/hooks/addresses#calling-past-the-hooks). The low-level API [#the-low-level-api] Everything above is built on a handful of C-style functions. You will very likely never call them, but knowing what they do explains how the classes behave. ```cpp HookID_t KHook::SetupHook(void* function, void* context, void* removed_function, void* pre, void* post, void* make_return, void* make_call_original, unsigned int stack_size, bool async = false); HookID_t KHook::SetupVirtualHook(void** vtable, int index, void* context, void* removed_function, void* pre, void* post, void* make_return, void* make_call_original, unsigned int stack_size, bool async = false); void KHook::RemoveHook(HookID_t id, bool async = false, void (*hook_removal_fn)(HookID_t, void*) = nullptr, void* context = nullptr); ``` | parameter | meaning | | ------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `function` / `vtable` + `index` | what to detour: an address, or a vtable slot | | `context` | any pointer; inside the callbacks `KHook::GetContext()` returns it | | `removed_function` | called when the hook has been removed — the place to clean up | | `pre`, `post` | the callbacks, with exactly the detoured function's signature | | `make_call_original` | may be called to run the original and store its return value | | `make_return` | called on the hook chosen to produce the final return value | | `stack_size` | the stack size in bytes for the detoured function — the classes compute it from the argument types | | `async` | add (or remove) the hook asynchronously; done synchronously from inside the hooked function it can deadlock | A callback at this level does not *return* its result — KHook restores the registers around every callback and ignores what it returns. It reports through `KHook::SaveReturnValue(action, ptr_to_return, return_size, init_op, deinit_op, original)` instead: the action, a pointer to the value to copy, its size, and two functions KHook may use to construct and destruct its own copy. `make_call_original` fetches the real function with `KHook::GetOriginalFunction()`, calls it and saves the result with `original = true`; `make_return` reads the chosen value with `KHook::GetCurrentValuePtr(true)` and must end with `KHook::DestroyReturnValue()`, which frees that copy and tells KHook the call is over. The raw API is full of footguns — a missed `DestroyReturnValue` leaks and leaves KHook mid-call, a wrong `stack_size` corrupts the arguments. The classes and macros exist so nobody has to write this by hand. The KHook [README](https://github.com/Kenzzer/KHook/blob/master/README.md) and `vendor/khook/include/khook.hpp` in the SDK are the reference for everything on this page. # Virtual Hooks (/docs/hooks/virtual-hooks) A virtual hook replaces one slot of a vtable. It is the right tool for engine interfaces (`ISource2Server`, `ISource2GameClients`, `IGameEventSystem`…) and for virtual functions of game classes. ```cpp KHOOK_VIRTUAL(member, function, target, pre, post) ``` * **member** — the name of the hook member in your class. * **function** — which slot: see [Which slot](#which-slot). * **target** — which object or vtable: see [What it attaches to](#what-it-attaches-to). * **pre**, **post** — `&Self::Handler` or `nullptr`. The handler's first parameter is the hooked class, the rest are the function's own arguments. Write the handlers first — the macro takes the hook's type from them. [Writing handlers](/docs/hooks/handlers) covers the body. Which slot [#which-slot] The **function** argument says where in the vtable the hook goes: * `&Class::Method` — the index is read out of the member function pointer, nothing written down by hand. The class declaration has to match the game's vtable **slot for slot**; headers carried over from older games often do not, so check the index against the binary before you trust one. * an integer — a hardcoded index. * a gamedata offset name (`"CCSPlayerController::Respawn"`) — read from gamedata at `KHOOK_INIT()`. Offsets differ between Windows and Linux, and gamedata keeps one per platform — see [Gamedata](/docs/hooks/gamedata). * `KHOOK_GAMEDATA(&Class::Method, "name")` — both at once. Gamedata wins; a difference between the two is logged (one of them is stale), and a missing entry for this platform leaves the header's index in place. The core's gamedata carries the engine interfaces' slots (`ISource2Server::GameFrame`, `ISource2GameClients::ClientPutInServer`, …), so a hook written this way follows a core update instead of a rebuild. The last two keep the plugin on the [stable tier](/docs/development/compatibility): a bare `&Class::Method` compiles the slot in from the SDK's header and needs a rebuild when the slot moves. Before writing one at all, check whether the engine call is already an [`ISource2ModListener` callback](/docs/events/listeners#engine-callbacks) (`OnGameFrame`, `OnClientPutInServer`, `OnClientDisconnect`, …). When the method is overloaded, the member function pointer has to be disambiguated with a `static_cast` to the exact signature. `PostEventAbstract` is the usual example — this is how the AntiCheat plugin hooks it: ```cpp title="Listeners.h" KHook::Return OnPostEventAbstract(IGameEventSystem* pThis, CSplitScreenSlot slot, bool bLocalOnly, int nClientCount, const uint64* clients, INetworkMessageInternal* event, const CNetMessage* data, unsigned long size, NetChannelBufType_t bufType); // PostEventAbstract is overloaded, so the member function pointer has // to be disambiguated explicitly. KHOOK_VIRTUAL(m_hPostEventAbstract, (static_cast(&IGameEventSystem::PostEventAbstract)), &globals::gameEventSystem, &CListeners::OnPostEventAbstract, nullptr); ``` Note the extra parentheses around the `static_cast`: it contains commas, and without them the preprocessor splits it into several macro arguments. A gamedata offset that is missing reads as `-1`, and a hook configured at `-1` lands one slot **before** the vtable — on the typeinfo pointer, which corrupts every later by-name vtable lookup on that class. The wrapper refuses a negative index and logs the key instead of installing the hook. What it attaches to [#what-it-attaches-to] The **target** says what the hook is attached to: | target | hooks | notes | | ---------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------- | | `&pInstance` | that one object | a pointer to the instance pointer, read at `KHOOK_INIT()` | | `KHOOK_VTABLE(module, class)` | every object sharing the class's vtable | the vtable is found by RTTI name | | `KHOOK_VTABLE_BASE(module, class, base)` | every object, through the vtable of a base further in | see [Secondary vtables](#secondary-vtables) | | `nullptr` | nothing yet | you attach it yourself with `Init(pInstance)` or `InitGlobal(vtable)` | One instance [#one-instance] `&pInstance` is a pointer to the variable holding the instance (`&g_pSource2Server`). It is read at `KHOOK_INIT()`, not when the hook is constructed, so the global may still be null at that point — it only has to be set by the time `KHOOK_INIT()` runs. The variable may be of a type derived from the hooked class — an `ISource2Server*` for a method declared on its base `IServerGameDLL`, say. The hook fires for **that object only**: KHook patches the vtable the object points at and checks `this` on every call. ```cpp title="plugin.h" KHook::Return Hook_ClientCommand(ISource2GameClients* pThis, CPlayerSlot slot, const CCommand& args); KHOOK_VIRTUAL(m_hClientCommand, &ISource2GameClients::ClientCommand, &g_pSource2GameClients, &Plugin::Hook_ClientCommand, nullptr); ``` A whole vtable [#a-whole-vtable] `KHOOK_VTABLE(module, class)` looks a vtable up by its RTTI class name and hooks it for every object that uses it — every client, every player controller. `pThis` tells you which one was called. The module is either a name (`"server"`, `"engine2"`, `"tier0"`…), loaded through `ISource2ModModule::New` at init, or a pointer to an `ISource2ModModule*` your plugin fills in during `Load()` (`&modules::server`), read at init as well. ```cpp title="plugin.h" KHook::Return Hook_SendNetMessage(CServerSideClientBase* pThis, const CNetMessage* pData, NetChannelBufType_t bufType); KHook::Return Hook_Respawn(CCSPlayerController* pThis); // Every client: CServerSideClient's vtable in engine2. KHOOK_VIRTUAL(m_hSendNetMessage, &CServerSideClientBase::SendNetMessage, KHOOK_VTABLE("engine2", "CServerSideClient"), &Plugin::Hook_SendNetMessage, nullptr); // Every controller, with the slot from gamedata. KHOOK_VIRTUAL(m_hRespawn, "CCSPlayerController::Respawn", KHOOK_VTABLE("server", "CCSPlayerController"), &Plugin::Hook_Respawn, nullptr); ``` A real one from FUNPLAY's plugins, with the module held by the plugin: ```cpp title="Listeners.h" KHook::Return OnDropWeapon(CCSPlayer_WeaponServices* pThis, CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity); KHOOK_VIRTUAL(m_hDropWeapon, "CCSPlayer_WeaponServices::DropWeapon", KHOOK_VTABLE(&modules::server, "CCSPlayer_WeaponServices"), nullptr, &CListeners::OnDropWeapon); ``` A vtable hook only sees calls that **go through the vtable**. Inside one library the compiler often knows the exact class and calls the function directly, and such a call never reaches your hook. Calls from another library — the engine calling into `libserver`, say — always go through the vtable. If a virtual hook stays silent, look at how the function is actually called before blaming the index — or hook the implementation by signature with [KHOOK\_MEMBER](/docs/hooks/function-hooks). Secondary vtables [#secondary-vtables] A class with several bases has several vtables: the **primary** one, shared with the first base, and one more for every base further in. `KHOOK_VTABLE` finds the primary one. For an interface that is not the first base, name the base too — `KHOOK_VTABLE_BASE(module, class, base)` reads where the base sits out of the RTTI and hooks that table: ```cpp title="Listeners.h" // IHLTVDirector is CCSHLTVDirector's third base. KHOOK_VIRTUAL(m_hHLTVRecordingStarted, &IHLTVDirector::OnRecordingStarted, KHOOK_VTABLE_BASE(&modules::server, "CCSHLTVDirector", "IHLTVDirector"), nullptr, &CListeners::OnHLTVRecordingStarted); ``` The same lookups are on `ISource2ModModule` for your own use: `GetVirtualTableByBase(name, baseName)`, and `GetVirtualTableByOffset(name, offset)` when you know the byte offset instead. An offset moves whenever the class is laid out anew, so prefer the base. Only non-virtual bases can be looked up. Two other ways around it: * a **function hook** on the implementation, by signature — that is how the s2m hooks `FilterMessage`; * a **virtual hook on the instance**, when you can get the interface pointer itself. That pointer already points at the secondary table, and `&pInstance` hooks exactly the table the object uses. Attaching by hand [#attaching-by-hand] A hook declared with a `nullptr` target is not touched by `KHOOK_INIT()`. You attach it when you have the target: ```cpp m_hX.Init(pInstance); // one object m_hX.InitGlobal(vtable); // an ISource2ModMemory holding a vtable m_hX.InitGlobal(pModule, "CCSPlayerController"); // by RTTI name m_hX.InitGlobal("server", "CCSHLTVDirector", "IHLTVDirector"); // by name and base ``` Each returns `false` (and logs) when the target could not be resolved. The rest of the lifecycle — `Init()` for hooks created after `Load()`, `Destruct()`, `IsInstalled()` — is the same for every hook kind and is covered in [Function hooks](/docs/hooks/function-hooks#hooks-installed-later). Calling the original [#calling-the-original] The wrapper has `CallOriginal(pThis, args...)`, which runs the function past every hook on it, and forwards `->` to the `KHook::Virtual` underneath: ```cpp m_hClientCommand.CallOriginal(pThis, slot, args); ``` See [Writing handlers](/docs/hooks/handlers#calling-the-original) for when and how to use it. # ISource2ModAddresses (/docs/core-api/ISource2ModAddresses) Exposes pre-resolved function pointers for common engine operations. Module inspection, pattern scanning, symbol lookup, and pointer arithmetic are available via ISource2ModModule and ISource2ModMemory. Provides access to pre-resolved engine function pointers. For module inspection, pattern scanning, and pointer arithmetic use ISource2ModModule and ISource2ModMemory instead. **Interface id** `ISource2ModAddresses001` (`SOURCE2MOD_ADDRESSES_INTERFACE`) · **Global** `g_pSource2ModAddresses` · **Header** [`source2mod/ISource2ModAddresses.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModAddresses.h) Types [#types] `CBaseEntity_CreateEntityByName_t` [#cbaseentity_createentitybyname_t] ```cpp using CBaseEntity_CreateEntityByName_t = CBaseEntity* (FASTCALL*)(const char*, int); ``` Creates an entity by classname. `CBaseEntity_DispatchSpawn_t` [#cbaseentity_dispatchspawn_t] ```cpp using CBaseEntity_DispatchSpawn_t = void (FASTCALL*)(CBaseEntity*, CEntityKeyValues*); ``` Dispatches spawn on an entity. `CBaseEntity_TakeDamageOld_t` [#cbaseentity_takedamageold_t] ```cpp using CBaseEntity_TakeDamageOld_t = int64_t (FASTCALL*)(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); ``` CBaseEntity::TakeDamageOld -- the game's own asserts name it so. `CBaseModelEntity_SetModel_t` [#cbasemodelentity_setmodel_t] ```cpp using CBaseModelEntity_SetModel_t = void (FASTCALL*)(CBaseModelEntity*, const char*); ``` Sets model on entity. `CBasePlayerController_SetPawn_t` [#cbaseplayercontroller_setpawn_t] ```cpp using CBasePlayerController_SetPawn_t = void (FASTCALL*)(CBasePlayerController*, CBasePlayerPawn*, bool, bool, bool, bool); ``` Assigns pawn to player controller. `CBasePlayerPawn_SnapViewAngles_t` [#cbaseplayerpawn_snapviewangles_t] ```cpp using CBasePlayerPawn_SnapViewAngles_t = void (FASTCALL*)(CBasePlayerPawn*, QAngle*); ``` Snaps a pawn's view angles, as the engine does on teleport. `CCSGameRules_TerminateRound_t` [#ccsgamerules_terminateround_t] ```cpp using CCSGameRules_TerminateRound_t = void (FASTCALL*)(CCSGameRules* pThis, float flDelay, uint32 nReason, uint32* pnTeamId); using CCSGameRules_TerminateRound_t = void (FASTCALL*)(CCSGameRules* pThis, uint32 nReason, uint32* pnTeamId, float flDelay); ``` CCSGameRules::TerminateRound The delay and the reason swap places between platforms, and the winning team goes in as a pointer -- null when there is none, which is the ordinary case. SwiftlyS2 splits it the same way; getting this wrong on Linux means the game reads the reason as the delay. Declared differently per platform ( `#ifdef _WIN32` ); the first line is the Windows form, the second the Linux one. `CPlayer_WeaponServices_Destroy_t` [#cplayer_weaponservices_destroy_t] ```cpp using CPlayer_WeaponServices_Destroy_t = void (FASTCALL*)(CPlayer_WeaponServices*, CBasePlayerWeapon*); ``` Destroys weapon from player pawn. `LegacyGameEventListener_t` [#legacygameeventlistener_t] ```cpp using LegacyGameEventListener_t = IGameEventListener2* (FASTCALL*)(CPlayerSlot); ``` Retrieves legacy game event listener. `CCSPlayerController_SwitchTeam_t` [#ccsplayercontroller_switchteam_t] ```cpp using CCSPlayerController_SwitchTeam_t = void (FASTCALL*)(CCSPlayerController*, int nTeam); ``` Switches player team. The team is read as a full int. `CEntityInstance_AcceptInput_t` [#centityinstance_acceptinput_t] ```cpp using CEntityInstance_AcceptInput_t = bool (FASTCALL*)(CEntityInstance*, const char*, CEntityInstance*, CEntityInstance*, const variant_t&); ``` Accepts entity input. Interns the name and forwards to CEntityIdentity::AcceptInput, returning its result. `CEntityIOOutput_FireOutputInternal_t` [#centityiooutput_fireoutputinternal_t] ```cpp using CEntityIOOutput_FireOutputInternal_t = void (FASTCALL*)(CEntityIOOutput*, CEntityInstance*, CEntityInstance*, void*, float, void*, void*); ``` Fires entity output internally. `CEntitySystem_AddEntityIOEvent_t` [#centitysystem_addentityioevent_t] ```cpp using CEntitySystem_AddEntityIOEvent_t = void (FASTCALL*)(CEntitySystem*, CEntityInstance*, const char*, CEntityInstance*, CEntityInstance*, const variant_t&, float, void*, void*); ``` Adds entity IO event. `CGameEntitySystem_FindEntityByClassName_t` [#cgameentitysystem_findentitybyclassname_t] ```cpp using CGameEntitySystem_FindEntityByClassName_t = CBaseEntity* (FASTCALL*)(CEntitySystem*, CEntityInstance*, const char*); ``` Finds entity by classname. `CGameEntitySystem_FindEntityByName_t` [#cgameentitysystem_findentitybyname_t] ```cpp using CGameEntitySystem_FindEntityByName_t = CBaseEntity* (FASTCALL*)(CEntitySystem*, CEntityInstance*, const char*, CEntityInstance*, CEntityInstance*, CEntityInstance*, IEntityFindFilter*); ``` Finds entity by name. `CTakeDamageInfo_CTakeDamageInfo_t` [#ctakedamageinfo_ctakedamageinfo_t] ```cpp using CTakeDamageInfo_CTakeDamageInfo_t = void (FASTCALL*)(CTakeDamageInfo* pThis, CBaseEntity* pInflictor, CBaseEntity* pAttacker, CBaseEntity* pAbility, const Vector* vecDamageForce, const Vector* vecDamagePosition, float flDamage, int bitsDamageType, int iCustomDamage, void* a10); ``` Constructs CTakeDamageInfo with valid members. `INetworkMessageProcessingPreFilter_FilterMessage_t` [#inetworkmessageprocessingprefilter_filtermessage_t] ```cpp using INetworkMessageProcessingPreFilter_FilterMessage_t = bool (THISCALL*)(INetworkMessageProcessingPreFilter* pThis, const CNetMessage* pData, INetChannel* pChannel); ``` `CEntityIdentity_AcceptInput_t` [#centityidentity_acceptinput_t] ```cpp using CEntityIdentity_AcceptInput_t = bool (FASTCALL*)(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* pValue, void* a6, void* a7); ``` CEntityIdentity::AcceptInput -- the identity-side entry point. Takes the input name already interned: CEntityInstance::AcceptInput looks it up and passes a pointer to the symbol, not the string. a6/a7 point at objects the caller builds on its stack. `CCSPlayer_ItemServices_CanAcquire_t` [#ccsplayer_itemservices_canacquire_t] ```cpp using CCSPlayer_ItemServices_CanAcquire_t = int64_t (FASTCALL*)(CCSPlayer_ItemServices* pThis, CEconItemView* pItem, int nAcquireMethod, void* a4); ``` CCSPlayer\_ItemServices::CanAcquire -- returns an AcquireResult. `CCSPlayerPawnBase_CanMove_t` [#ccsplayerpawnbase_canmove_t] ```cpp using CCSPlayerPawnBase_CanMove_t = bool (FASTCALL*)(CCSPlayerPawnBase* pThis); ``` CCSPlayerPawnBase::CanMove -- a CCSPlayerPawnBase virtual; false while frozen, defusing, etc. `CCSPlayerController_ProcessUserCmd_t` [#ccsplayercontroller_processusercmd_t] ```cpp using CCSPlayerController_ProcessUserCmd_t = void* (FASTCALL*)(CCSPlayerController* pThis, void* pUserCmds, int nNumCmds, bool bPaused, float flMargin); ``` CCSPlayerController::ProcessUserCmd `CBasePlayerController_OnSimulateUserCommands_t` [#cbaseplayercontroller_onsimulateusercommands_t] ```cpp using CBasePlayerController_OnSimulateUserCommands_t = void (FASTCALL*)(CBasePlayerController* pThis); ``` CBasePlayerController::OnSimulateUserCommands `CCSPlayer_MovementServices_AirAccelerate_t` [#ccsplayer_movementservices_airaccelerate_t] ```cpp using CCSPlayer_MovementServices_AirAccelerate_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, Vector* pWishDirection, float flWishSpeed, float flAcceleration); ``` `CCSPlayer_MovementServices_AirMove_t` [#ccsplayer_movementservices_airmove_t] ```cpp using CCSPlayer_MovementServices_AirMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_CanUnduck_t` [#ccsplayer_movementservices_canunduck_t] ```cpp using CCSPlayer_MovementServices_CanUnduck_t = bool (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_CategorizePosition_t` [#ccsplayer_movementservices_categorizeposition_t] ```cpp using CCSPlayer_MovementServices_CategorizePosition_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, bool bStayOnGround); ``` `CCSPlayer_MovementServices_CheckFalling_t` [#ccsplayer_movementservices_checkfalling_t] ```cpp using CCSPlayer_MovementServices_CheckFalling_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_CheckParameters_t` [#ccsplayer_movementservices_checkparameters_t] ```cpp using CCSPlayer_MovementServices_CheckParameters_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_CheckVelocity_t` [#ccsplayer_movementservices_checkvelocity_t] ```cpp using CCSPlayer_MovementServices_CheckVelocity_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, void* a3); ``` `CCSPlayer_MovementServices_CheckWater_t` [#ccsplayer_movementservices_checkwater_t] ```cpp using CCSPlayer_MovementServices_CheckWater_t = bool (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_Duck_t` [#ccsplayer_movementservices_duck_t] ```cpp using CCSPlayer_MovementServices_Duck_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_Friction_t` [#ccsplayer_movementservices_friction_t] ```cpp using CCSPlayer_MovementServices_Friction_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_FullWalkMove_t` [#ccsplayer_movementservices_fullwalkmove_t] ```cpp using CCSPlayer_MovementServices_FullWalkMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, bool bGround); ``` `CCSPlayer_MovementServices_LadderMove_t` [#ccsplayer_movementservices_laddermove_t] ```cpp using CCSPlayer_MovementServices_LadderMove_t = bool (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_MoveInit_t` [#ccsplayer_movementservices_moveinit_t] ```cpp using CCSPlayer_MovementServices_MoveInit_t = bool (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_PlayerMove_t` [#ccsplayer_movementservices_playermove_t] ```cpp using CCSPlayer_MovementServices_PlayerMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_ProcessMovement_t` [#ccsplayer_movementservices_processmovement_t] ```cpp using CCSPlayer_MovementServices_ProcessMovement_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_SetupMove_t` [#ccsplayer_movementservices_setupmove_t] ```cpp using CCSPlayer_MovementServices_SetupMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CUserCmd* pCmd, CMoveData* pMove); ``` `CCSPlayer_MovementServices_TryPlayerMove_t` [#ccsplayer_movementservices_tryplayermove_t] ```cpp using CCSPlayer_MovementServices_TryPlayerMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, Vector* pFirstDest, CGameTrace* pFirstTrace, bool* pbIsSurfing); ``` `CCSPlayer_MovementServices_WalkMove_t` [#ccsplayer_movementservices_walkmove_t] ```cpp using CCSPlayer_MovementServices_WalkMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_WaterMove_t` [#ccsplayer_movementservices_watermove_t] ```cpp using CCSPlayer_MovementServices_WaterMove_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove); ``` `CCSPlayer_MovementServices_GroundAccelerate_t` [#ccsplayer_movementservices_groundaccelerate_t] ```cpp using CCSPlayer_MovementServices_GroundAccelerate_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, float flFrameTime, Vector* pWishDirection, float flWishSpeed, float flAcceleration); using CCSPlayer_MovementServices_GroundAccelerate_t = void (FASTCALL*)(CCSPlayer_MovementServices* pThis, CMoveData* pMove, Vector* pWishDirection, float flFrameTime, float flWishSpeed, float flAcceleration); ``` CCSPlayer\_MovementServices::GroundAccelerate The wish direction and the frame time swap places between platforms. Declared differently per platform ( `#ifdef _WIN32` ); the first line is the Windows form, the second the Linux one. `CCSPlayerLegacyJump_OnJump_t` [#ccsplayerlegacyjump_onjump_t] ```cpp using CCSPlayerLegacyJump_OnJump_t = void (FASTCALL*)(CCSPlayerLegacyJump* pThis, CMoveData* pMove); ``` Members of the jump components, not of the movement services: the movement services call them on m\_LegacyJump / m\_ModernJump. Which pair is live depends on the sv\_jump\_impulse-era cvars; the modern and legacy paths are separate functions in the binary. `CCSPlayerModernJump_OnJump_t` [#ccsplayermodernjump_onjump_t] ```cpp using CCSPlayerModernJump_OnJump_t = void (FASTCALL*)(CCSPlayerModernJump* pThis, CMoveData* pMove); ``` `CCSPlayerLegacyJump_CheckJumpButton_t` [#ccsplayerlegacyjump_checkjumpbutton_t] ```cpp using CCSPlayerLegacyJump_CheckJumpButton_t = void (FASTCALL*)(CCSPlayerLegacyJump* pThis, CMoveData* pMove); ``` `CCSPlayerModernJump_CheckJumpButton_t` [#ccsplayermodernjump_checkjumpbutton_t] ```cpp using CCSPlayerModernJump_CheckJumpButton_t = void (FASTCALL*)(CCSPlayerModernJump* pThis, CMoveData* pMove); ``` `CAttributeList_SetOrAddAttributeValueByName_t` [#cattributelist_setoraddattributevaluebyname_t] ```cpp using CAttributeList_SetOrAddAttributeValueByName_t = void (FASTCALL*)(CAttributeList* pThis, const char* pszAttributeName, float flValue); ``` CAttributeList::SetOrAddAttributeValueByName `CDecoyProjectile_EmitGrenade_t` [#cdecoyprojectile_emitgrenade_t] ```cpp using CDecoyProjectile_EmitGrenade_t = CDecoyProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex); ``` C\Projectile::EmitGrenade -- spawns a live grenade. The fourth argument (pAngVelocity) is never read on either platform -- every variant overwrites that register before touching it -- but it keeps its slot, so pass anything (the helpers pass the velocity again). Smoke takes one extra int the others do not have. `CFlashbangProjectile_EmitGrenade_t` [#cflashbangprojectile_emitgrenade_t] ```cpp using CFlashbangProjectile_EmitGrenade_t = CFlashbangProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex); ``` `CHEGrenadeProjectile_EmitGrenade_t` [#chegrenadeprojectile_emitgrenade_t] ```cpp using CHEGrenadeProjectile_EmitGrenade_t = CHEGrenadeProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex); ``` `CMolotovProjectile_EmitGrenade_t` [#cmolotovprojectile_emitgrenade_t] ```cpp using CMolotovProjectile_EmitGrenade_t = CMolotovProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex); ``` `CSmokeGrenadeProjectile_EmitGrenade_t` [#csmokegrenadeprojectile_emitgrenade_t] ```cpp using CSmokeGrenadeProjectile_EmitGrenade_t = CSmokeGrenadeProjectile* (FASTCALL*)(Vector* pPosition, QAngle* pAngle, Vector* pVelocity, Vector* pAngVelocity, CBaseEntity* pOwner, uint32_t nItemDefIndex, int a7); ``` `CCSPlayer_ItemServices_GiveNamedItem_t` [#ccsplayer_itemservices_givenameditem_t] ```cpp using CCSPlayer_ItemServices_GiveNamedItem_t = CBasePlayerWeapon* (FASTCALL*)(CCSPlayer_ItemServices* pThis, const char* pszItem, int nSubType, CEconItemView* pScriptItem, bool bForce, void* a6); ``` CCSPlayer\_ItemServices::GiveNamedItem -- the full implementation. The GiveNamedItem vtable slots are thunks into this one: the plain one passes (name, 0, nullptr, false, nullptr), the bool-returning one forwards its own bool as bForce and tests the result. Hooking this catches every give, virtual or not. `CCSPlayerPawn_PostThink_t` [#ccsplayerpawn_postthink_t] ```cpp using CCSPlayerPawn_PostThink_t = void (FASTCALL*)(CCSPlayerPawn* pThis); ``` CCSPlayerPawn::PostThink -- virtual, takes nothing but the pawn. `UTIL_Remove_t` [#util_remove_t] ```cpp using UTIL_Remove_t = void (FASTCALL*)(CEntityInstance* pEntity); ``` UTIL\_Remove -- queues the entity for deletion; null is ignored. `DispatchParticleEffect_t` [#dispatchparticleeffect_t] ```cpp using DispatchParticleEffect_t = void (FASTCALL*)(const char* pszParticleName, int nAttachType, CBaseEntity* pEntity, uint8 nAttachmentPoint, uint32 nAttachmentNameToken, bool bResetAllParticlesOnEntity, int nSplitScreenPlayerSlot, IRecipientFilter* pFilter, uint8 nFlags); ``` DispatchParticleEffect The attachment name is a 32-bit string token (it arrives in r8d), not the CUtlSymbolLarge older ports declare. A null filter sends to everyone. The last byte seeds the effect's flag byte; pass 0. `GetWeaponCSDataFromKey_t` [#getweaponcsdatafromkey_t] ```cpp using GetWeaponCSDataFromKey_t = CCSWeaponBaseVData* (FASTCALL*)(int nWeaponType, const char* pszKey); ``` GetWeaponCSDataFromKey -- looks a weapon's VData up by its key ("weapon\_ak47"). nWeaponType -1 accepts any type; any other value returns null when the found data is of a different type. `CSource2Server_GetNavMeshData_t` [#csource2server_getnavmeshdata_t] ```cpp using CSource2Server_GetNavMeshData_t = bool (FASTCALL*)(ISource2Server* pThis, CNavData* pNavMeshData); ``` CSource2Server::GetNavMeshData -- ISource2Server vtable slot 32. The game's implementation ignores the out argument and only reports whether a nav mesh is loaded (g\_pNavMesh != null); CCSNavArea reads that global out of its body. `CLoggingSystem_LogDirect_t` [#cloggingsystem_logdirect_t] ```cpp using CLoggingSystem_LogDirect_t = LoggingResponse_t (FASTCALL*)(CLoggingSystem* pThis, LoggingChannelID_t nChannelID, LoggingSeverity_t nSeverity, const LoggingRareOptions_t* pRareOptions, const LoggingMetaData_t* pMetaData, Color color, const char* pszMessage); ``` CLoggingSystem::LogDirect (tier0) -- the member behind LoggingSystem\_LogDirect. `Cmd_ExecuteCommand_t` [#cmd_executecommand_t] ```cpp using Cmd_ExecuteCommand_t = void (FASTCALL*)(void* pThis, int nTarget, CPlayerSlot nSlot, ConCommandRef hCommand, const CCommand* pArgs); ``` Cmd\_ExecuteCommand (engine2) -- runs one parsed command. The first argument is the engine's command-buffer owner, not a CCommand: the deferred-command loop passes itself there. The target is a CommandTarget\_t and the command goes in by value, as ICvar::DispatchConCommand takes it. Methods [#methods] `CBaseEntity_CreateEntityByName` [#cbaseentity_createentitybyname] ```cpp virtual CBaseEntity_CreateEntityByName_t CBaseEntity_CreateEntityByName() = 0; ``` Creates an entity by classname. `CBaseEntity_DispatchSpawn` [#cbaseentity_dispatchspawn] ```cpp virtual CBaseEntity_DispatchSpawn_t CBaseEntity_DispatchSpawn() = 0; ``` Dispatches spawn on an entity. `CBaseEntity_TakeDamageOld` [#cbaseentity_takedamageold] ```cpp virtual CBaseEntity_TakeDamageOld_t CBaseEntity_TakeDamageOld() = 0; ``` CBaseEntity::TakeDamageOld -- the game's own asserts name it so. `CBaseModelEntity_SetModel` [#cbasemodelentity_setmodel] ```cpp virtual CBaseModelEntity_SetModel_t CBaseModelEntity_SetModel() = 0; ``` Sets model on entity. `CBasePlayerController_SetPawn` [#cbaseplayercontroller_setpawn] ```cpp virtual CBasePlayerController_SetPawn_t CBasePlayerController_SetPawn() = 0; ``` Assigns pawn to player controller. `CBasePlayerPawn_SnapViewAngles` [#cbaseplayerpawn_snapviewangles] ```cpp virtual CBasePlayerPawn_SnapViewAngles_t CBasePlayerPawn_SnapViewAngles() = 0; ``` Snaps a pawn's view angles, as the engine does on teleport. `CCSGameRules_TerminateRound` [#ccsgamerules_terminateround] ```cpp virtual CCSGameRules_TerminateRound_t CCSGameRules_TerminateRound() = 0; ``` CCSGameRules::TerminateRound `CPlayer_WeaponServices_Destroy` [#cplayer_weaponservices_destroy] ```cpp virtual CPlayer_WeaponServices_Destroy_t CPlayer_WeaponServices_Destroy() = 0; ``` Destroys weapon from player pawn. `LegacyGameEventListener` [#legacygameeventlistener] ```cpp virtual LegacyGameEventListener_t LegacyGameEventListener() = 0; ``` Retrieves legacy game event listener. `CCSPlayerController_SwitchTeam` [#ccsplayercontroller_switchteam] ```cpp virtual CCSPlayerController_SwitchTeam_t CCSPlayerController_SwitchTeam() = 0; ``` Switches player team. The team is read as a full int. `CEntityInstance_AcceptInput` [#centityinstance_acceptinput] ```cpp virtual CEntityInstance_AcceptInput_t CEntityInstance_AcceptInput() = 0; ``` Accepts entity input. Interns the name and forwards to CEntityIdentity::AcceptInput, returning its result. `CEntityIOOutput_FireOutputInternal` [#centityiooutput_fireoutputinternal] ```cpp virtual CEntityIOOutput_FireOutputInternal_t CEntityIOOutput_FireOutputInternal() = 0; ``` Fires entity output internally. `CEntitySystem_AddEntityIOEvent` [#centitysystem_addentityioevent] ```cpp virtual CEntitySystem_AddEntityIOEvent_t CEntitySystem_AddEntityIOEvent() = 0; ``` Adds entity IO event. `CGameEntitySystem_FindEntityByClassName` [#cgameentitysystem_findentitybyclassname] ```cpp virtual CGameEntitySystem_FindEntityByClassName_t CGameEntitySystem_FindEntityByClassName() = 0; ``` Finds entity by classname. `CGameEntitySystem_FindEntityByName` [#cgameentitysystem_findentitybyname] ```cpp virtual CGameEntitySystem_FindEntityByName_t CGameEntitySystem_FindEntityByName() = 0; ``` Finds entity by name. `CTakeDamageInfo_CTakeDamageInfo` [#ctakedamageinfo_ctakedamageinfo] ```cpp virtual CTakeDamageInfo_CTakeDamageInfo_t CTakeDamageInfo_CTakeDamageInfo() = 0; ``` Constructs CTakeDamageInfo with valid members. `INetworkMessageProcessingPreFilter_FilterMessage` [#inetworkmessageprocessingprefilter_filtermessage] ```cpp virtual INetworkMessageProcessingPreFilter_FilterMessage_t INetworkMessageProcessingPreFilter_FilterMessage() = 0; ``` `CEntityIdentity_AcceptInput` [#centityidentity_acceptinput] ```cpp virtual CEntityIdentity_AcceptInput_t CEntityIdentity_AcceptInput() = 0; ``` CEntityIdentity::AcceptInput -- the identity-side entry point. `CCSPlayer_ItemServices_CanAcquire` [#ccsplayer_itemservices_canacquire] ```cpp virtual CCSPlayer_ItemServices_CanAcquire_t CCSPlayer_ItemServices_CanAcquire() = 0; ``` CCSPlayer\_ItemServices::CanAcquire -- returns an AcquireResult. `CCSPlayerPawnBase_CanMove` [#ccsplayerpawnbase_canmove] ```cpp virtual CCSPlayerPawnBase_CanMove_t CCSPlayerPawnBase_CanMove() = 0; ``` CCSPlayerPawnBase::CanMove -- a CCSPlayerPawnBase virtual; false while frozen, defusing, etc. `CCSPlayerController_ProcessUserCmd` [#ccsplayercontroller_processusercmd] ```cpp virtual CCSPlayerController_ProcessUserCmd_t CCSPlayerController_ProcessUserCmd() = 0; ``` CCSPlayerController::ProcessUserCmd `CBasePlayerController_OnSimulateUserCommands` [#cbaseplayercontroller_onsimulateusercommands] ```cpp virtual CBasePlayerController_OnSimulateUserCommands_t CBasePlayerController_OnSimulateUserCommands() = 0; ``` CBasePlayerController::OnSimulateUserCommands `CCSPlayer_MovementServices_AirAccelerate` [#ccsplayer_movementservices_airaccelerate] ```cpp virtual CCSPlayer_MovementServices_AirAccelerate_t CCSPlayer_MovementServices_AirAccelerate() = 0; ``` `CCSPlayer_MovementServices_AirMove` [#ccsplayer_movementservices_airmove] ```cpp virtual CCSPlayer_MovementServices_AirMove_t CCSPlayer_MovementServices_AirMove() = 0; ``` `CCSPlayer_MovementServices_CanUnduck` [#ccsplayer_movementservices_canunduck] ```cpp virtual CCSPlayer_MovementServices_CanUnduck_t CCSPlayer_MovementServices_CanUnduck() = 0; ``` `CCSPlayer_MovementServices_CategorizePosition` [#ccsplayer_movementservices_categorizeposition] ```cpp virtual CCSPlayer_MovementServices_CategorizePosition_t CCSPlayer_MovementServices_CategorizePosition() = 0; ``` `CCSPlayer_MovementServices_CheckFalling` [#ccsplayer_movementservices_checkfalling] ```cpp virtual CCSPlayer_MovementServices_CheckFalling_t CCSPlayer_MovementServices_CheckFalling() = 0; ``` `CCSPlayer_MovementServices_CheckParameters` [#ccsplayer_movementservices_checkparameters] ```cpp virtual CCSPlayer_MovementServices_CheckParameters_t CCSPlayer_MovementServices_CheckParameters() = 0; ``` `CCSPlayer_MovementServices_CheckVelocity` [#ccsplayer_movementservices_checkvelocity] ```cpp virtual CCSPlayer_MovementServices_CheckVelocity_t CCSPlayer_MovementServices_CheckVelocity() = 0; ``` `CCSPlayer_MovementServices_CheckWater` [#ccsplayer_movementservices_checkwater] ```cpp virtual CCSPlayer_MovementServices_CheckWater_t CCSPlayer_MovementServices_CheckWater() = 0; ``` `CCSPlayer_MovementServices_Duck` [#ccsplayer_movementservices_duck] ```cpp virtual CCSPlayer_MovementServices_Duck_t CCSPlayer_MovementServices_Duck() = 0; ``` `CCSPlayer_MovementServices_Friction` [#ccsplayer_movementservices_friction] ```cpp virtual CCSPlayer_MovementServices_Friction_t CCSPlayer_MovementServices_Friction() = 0; ``` `CCSPlayer_MovementServices_FullWalkMove` [#ccsplayer_movementservices_fullwalkmove] ```cpp virtual CCSPlayer_MovementServices_FullWalkMove_t CCSPlayer_MovementServices_FullWalkMove() = 0; ``` `CCSPlayer_MovementServices_GroundAccelerate` [#ccsplayer_movementservices_groundaccelerate] ```cpp virtual CCSPlayer_MovementServices_GroundAccelerate_t CCSPlayer_MovementServices_GroundAccelerate() = 0; ``` CCSPlayer\_MovementServices::GroundAccelerate `CCSPlayer_MovementServices_LadderMove` [#ccsplayer_movementservices_laddermove] ```cpp virtual CCSPlayer_MovementServices_LadderMove_t CCSPlayer_MovementServices_LadderMove() = 0; ``` `CCSPlayer_MovementServices_MoveInit` [#ccsplayer_movementservices_moveinit] ```cpp virtual CCSPlayer_MovementServices_MoveInit_t CCSPlayer_MovementServices_MoveInit() = 0; ``` `CCSPlayer_MovementServices_PlayerMove` [#ccsplayer_movementservices_playermove] ```cpp virtual CCSPlayer_MovementServices_PlayerMove_t CCSPlayer_MovementServices_PlayerMove() = 0; ``` `CCSPlayer_MovementServices_ProcessMovement` [#ccsplayer_movementservices_processmovement] ```cpp virtual CCSPlayer_MovementServices_ProcessMovement_t CCSPlayer_MovementServices_ProcessMovement() = 0; ``` `CCSPlayer_MovementServices_SetupMove` [#ccsplayer_movementservices_setupmove] ```cpp virtual CCSPlayer_MovementServices_SetupMove_t CCSPlayer_MovementServices_SetupMove() = 0; ``` `CCSPlayer_MovementServices_TryPlayerMove` [#ccsplayer_movementservices_tryplayermove] ```cpp virtual CCSPlayer_MovementServices_TryPlayerMove_t CCSPlayer_MovementServices_TryPlayerMove() = 0; ``` `CCSPlayer_MovementServices_WalkMove` [#ccsplayer_movementservices_walkmove] ```cpp virtual CCSPlayer_MovementServices_WalkMove_t CCSPlayer_MovementServices_WalkMove() = 0; ``` `CCSPlayer_MovementServices_WaterMove` [#ccsplayer_movementservices_watermove] ```cpp virtual CCSPlayer_MovementServices_WaterMove_t CCSPlayer_MovementServices_WaterMove() = 0; ``` `CCSPlayerLegacyJump_OnJump` [#ccsplayerlegacyjump_onjump] ```cpp virtual CCSPlayerLegacyJump_OnJump_t CCSPlayerLegacyJump_OnJump() = 0; ``` Members of the jump components, not of the movement services: the movement services call them on m\_LegacyJump / m\_ModernJump. `CCSPlayerModernJump_OnJump` [#ccsplayermodernjump_onjump] ```cpp virtual CCSPlayerModernJump_OnJump_t CCSPlayerModernJump_OnJump() = 0; ``` `CCSPlayerLegacyJump_CheckJumpButton` [#ccsplayerlegacyjump_checkjumpbutton] ```cpp virtual CCSPlayerLegacyJump_CheckJumpButton_t CCSPlayerLegacyJump_CheckJumpButton() = 0; ``` `CCSPlayerModernJump_CheckJumpButton` [#ccsplayermodernjump_checkjumpbutton] ```cpp virtual CCSPlayerModernJump_CheckJumpButton_t CCSPlayerModernJump_CheckJumpButton() = 0; ``` `CAttributeList_SetOrAddAttributeValueByName` [#cattributelist_setoraddattributevaluebyname] ```cpp virtual CAttributeList_SetOrAddAttributeValueByName_t CAttributeList_SetOrAddAttributeValueByName() = 0; ``` CAttributeList::SetOrAddAttributeValueByName `CDecoyProjectile_EmitGrenade` [#cdecoyprojectile_emitgrenade] ```cpp virtual CDecoyProjectile_EmitGrenade_t CDecoyProjectile_EmitGrenade() = 0; ``` C\Projectile::EmitGrenade -- spawns a live grenade. `CFlashbangProjectile_EmitGrenade` [#cflashbangprojectile_emitgrenade] ```cpp virtual CFlashbangProjectile_EmitGrenade_t CFlashbangProjectile_EmitGrenade() = 0; ``` `CHEGrenadeProjectile_EmitGrenade` [#chegrenadeprojectile_emitgrenade] ```cpp virtual CHEGrenadeProjectile_EmitGrenade_t CHEGrenadeProjectile_EmitGrenade() = 0; ``` `CMolotovProjectile_EmitGrenade` [#cmolotovprojectile_emitgrenade] ```cpp virtual CMolotovProjectile_EmitGrenade_t CMolotovProjectile_EmitGrenade() = 0; ``` `CSmokeGrenadeProjectile_EmitGrenade` [#csmokegrenadeprojectile_emitgrenade] ```cpp virtual CSmokeGrenadeProjectile_EmitGrenade_t CSmokeGrenadeProjectile_EmitGrenade() = 0; ``` `CCSPlayerPawn_PostThink` [#ccsplayerpawn_postthink] ```cpp virtual CCSPlayerPawn_PostThink_t CCSPlayerPawn_PostThink() = 0; ``` CCSPlayerPawn::PostThink -- virtual, takes nothing but the pawn. `UTIL_Remove` [#util_remove] ```cpp virtual UTIL_Remove_t UTIL_Remove() = 0; ``` UTIL\_Remove -- queues the entity for deletion; null is ignored. `DispatchParticleEffect` [#dispatchparticleeffect] ```cpp virtual DispatchParticleEffect_t DispatchParticleEffect() = 0; ``` DispatchParticleEffect `GetWeaponCSDataFromKey` [#getweaponcsdatafromkey] ```cpp virtual GetWeaponCSDataFromKey_t GetWeaponCSDataFromKey() = 0; ``` GetWeaponCSDataFromKey -- looks a weapon's VData up by its key ("weapon\_ak47"). nWeaponType -1 accepts any type; any other value returns null when the found data is of a different type. `CSource2Server_GetNavMeshData` [#csource2server_getnavmeshdata] ```cpp virtual CSource2Server_GetNavMeshData_t CSource2Server_GetNavMeshData() = 0; ``` CSource2Server::GetNavMeshData -- ISource2Server vtable slot 32. `CLoggingSystem_LogDirect` [#cloggingsystem_logdirect] ```cpp virtual CLoggingSystem_LogDirect_t CLoggingSystem_LogDirect() = 0; ``` CLoggingSystem::LogDirect (tier0) -- the member behind LoggingSystem\_LogDirect. `Cmd_ExecuteCommand` [#cmd_executecommand] ```cpp virtual Cmd_ExecuteCommand_t Cmd_ExecuteCommand() = 0; ``` Cmd\_ExecuteCommand (engine2) -- runs one parsed command. `CCSPlayer_ItemServices_GiveNamedItem` [#ccsplayer_itemservices_givenameditem] ```cpp virtual CCSPlayer_ItemServices_GiveNamedItem_t CCSPlayer_ItemServices_GiveNamedItem() = 0; ``` CCSPlayer\_ItemServices::GiveNamedItem -- the full implementation. Macros [#macros] `SOURCE2MOD_ORIGINAL` [#source2mod_original] ```cpp #define SOURCE2MOD_ORIGINAL(fn) reinterpret_cast(KHook::FindOriginal(reinterpret_cast(fn))) ``` Calls a function from this interface, bypassing any inline hooks on it. The SDK's schema helpers do the same through their HookChain argument (ISource2ModTypes.h); this is for calling an ADDR\_\* function directly. The ADDR\_\* macros below hand back the address the signature scan found, which is what you want to hook. It is not always what you want to *call*: once any plugin inline-hooks one of these functions, its first bytes are a jump into the detour, so calling it runs the whole Pre/Post chain. That is right when you are standing in for the game -- a plugin dealing damage as if a weapon fired wants the chain -- and wrong when you are asking for the function itself, e.g. an admin command, where another plugin's rules have no business vetoing what the server was told to do. Wrap the call in this when you mean the latter: ```cpp SOURCE2MOD_ORIGINAL(ADDR_TAKE_DAMAGE_OLD())(pPawn, &info, &result); ``` Signature-blind, so it works whatever prototype you hold and however the plugin that hooked it declared its hook -- the two routinely disagree, and TerminateRound is the standing example: the hook declares (CCSGameRules, RoundEndReason, float, void\*, uint8\_t) where this interface's typedef says (CCSGameRules\*, uint32, uint32\*, float). Unhooked functions come back unchanged (KHook::FindOriginal hands the address back as is), so this is always safe to apply. Resolve it at the call, do not cache it in a global. The address is only the original from the moment the hook exists, and hooks are installed long after these addresses are scanned. Other macros [#other-macros] | Macro | Expands to | | ------------------------------------ | ---------------------------------------------------------------------------- | | `ADDR_CREATE_ENTITY_BY_NAME()` | `g_pSource2ModAddresses->CBaseEntity_CreateEntityByName()` | | `ADDR_DISPATCH_SPAWN()` | `g_pSource2ModAddresses->CBaseEntity_DispatchSpawn()` | | `ADDR_TAKE_DAMAGE_OLD()` | `g_pSource2ModAddresses->CBaseEntity_TakeDamageOld()` | | `ADDR_SET_MODEL()` | `g_pSource2ModAddresses->CBaseModelEntity_SetModel()` | | `ADDR_SET_PAWN()` | `g_pSource2ModAddresses->CBasePlayerController_SetPawn()` | | `ADDR_SNAP_VIEW_ANGLES()` | `g_pSource2ModAddresses->CBasePlayerPawn_SnapViewAngles()` | | `ADDR_TERMINATE_ROUND()` | `g_pSource2ModAddresses->CCSGameRules_TerminateRound()` | | `ADDR_WEAPON_SERVICES_DESTROY()` | `g_pSource2ModAddresses->CPlayer_WeaponServices_Destroy()` | | `ADDR_LEGACY_GAME_EVENT_LISTENER()` | `g_pSource2ModAddresses->LegacyGameEventListener()` | | `ADDR_SWITCH_TEAM()` | `g_pSource2ModAddresses->CCSPlayerController_SwitchTeam()` | | `ADDR_ACCEPT_INPUT()` | `g_pSource2ModAddresses->CEntityInstance_AcceptInput()` | | `ADDR_FIRE_OUTPUT_INTERNAL()` | `g_pSource2ModAddresses->CEntityIOOutput_FireOutputInternal()` | | `ADDR_ADD_ENTITY_IO_EVENT()` | `g_pSource2ModAddresses->CEntitySystem_AddEntityIOEvent()` | | `ADDR_FIND_ENTITY_BY_CLASSNAME()` | `g_pSource2ModAddresses->CGameEntitySystem_FindEntityByClassName()` | | `ADDR_FIND_ENTITY_BY_NAME()` | `g_pSource2ModAddresses->CGameEntitySystem_FindEntityByName()` | | `ADDR_TAKE_DAMAGE_INFO()` | `g_pSource2ModAddresses->CTakeDamageInfo_CTakeDamageInfo()` | | `ADDR_FILTER_MESSAGE()` | `g_pSource2ModAddresses->INetworkMessageProcessingPreFilter_FilterMessage()` | | `ADDR_IDENTITY_ACCEPT_INPUT()` | `g_pSource2ModAddresses->CEntityIdentity_AcceptInput()` | | `ADDR_CAN_ACQUIRE()` | `g_pSource2ModAddresses->CCSPlayer_ItemServices_CanAcquire()` | | `ADDR_CAN_MOVE()` | `g_pSource2ModAddresses->CCSPlayerPawnBase_CanMove()` | | `ADDR_PROCESS_USER_CMD()` | `g_pSource2ModAddresses->CCSPlayerController_ProcessUserCmd()` | | `ADDR_ON_SIMULATE_USER_COMMANDS()` | `g_pSource2ModAddresses->CBasePlayerController_OnSimulateUserCommands()` | | `ADDR_AIR_ACCELERATE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_AirAccelerate()` | | `ADDR_AIR_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_AirMove()` | | `ADDR_CAN_UNDUCK()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CanUnduck()` | | `ADDR_CATEGORIZE_POSITION()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CategorizePosition()` | | `ADDR_CHECK_FALLING()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CheckFalling()` | | `ADDR_CHECK_PARAMETERS()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CheckParameters()` | | `ADDR_CHECK_VELOCITY()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CheckVelocity()` | | `ADDR_CHECK_WATER()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_CheckWater()` | | `ADDR_DUCK()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_Duck()` | | `ADDR_FRICTION()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_Friction()` | | `ADDR_FULL_WALK_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_FullWalkMove()` | | `ADDR_GROUND_ACCELERATE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_GroundAccelerate()` | | `ADDR_LADDER_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_LadderMove()` | | `ADDR_MOVE_INIT()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_MoveInit()` | | `ADDR_PLAYER_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_PlayerMove()` | | `ADDR_PROCESS_MOVEMENT()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_ProcessMovement()` | | `ADDR_SETUP_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_SetupMove()` | | `ADDR_TRY_PLAYER_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_TryPlayerMove()` | | `ADDR_WALK_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_WalkMove()` | | `ADDR_WATER_MOVE()` | `g_pSource2ModAddresses->CCSPlayer_MovementServices_WaterMove()` | | `ADDR_ON_JUMP_LEGACY()` | `g_pSource2ModAddresses->CCSPlayerLegacyJump_OnJump()` | | `ADDR_ON_JUMP_MODERN()` | `g_pSource2ModAddresses->CCSPlayerModernJump_OnJump()` | | `ADDR_CHECK_JUMP_BUTTON_LEGACY()` | `g_pSource2ModAddresses->CCSPlayerLegacyJump_CheckJumpButton()` | | `ADDR_CHECK_JUMP_BUTTON_MODERN()` | `g_pSource2ModAddresses->CCSPlayerModernJump_CheckJumpButton()` | | `ADDR_SET_OR_ADD_ATTRIBUTE()` | `g_pSource2ModAddresses->CAttributeList_SetOrAddAttributeValueByName()` | | `ADDR_EMIT_DECOY()` | `g_pSource2ModAddresses->CDecoyProjectile_EmitGrenade()` | | `ADDR_EMIT_FLASHBANG()` | `g_pSource2ModAddresses->CFlashbangProjectile_EmitGrenade()` | | `ADDR_EMIT_HEGRENADE()` | `g_pSource2ModAddresses->CHEGrenadeProjectile_EmitGrenade()` | | `ADDR_EMIT_MOLOTOV()` | `g_pSource2ModAddresses->CMolotovProjectile_EmitGrenade()` | | `ADDR_EMIT_SMOKE()` | `g_pSource2ModAddresses->CSmokeGrenadeProjectile_EmitGrenade()` | | `ADDR_POST_THINK()` | `g_pSource2ModAddresses->CCSPlayerPawn_PostThink()` | | `ADDR_UTIL_REMOVE()` | `g_pSource2ModAddresses->UTIL_Remove()` | | `ADDR_DISPATCH_PARTICLE_EFFECT()` | `g_pSource2ModAddresses->DispatchParticleEffect()` | | `ADDR_GET_WEAPON_CS_DATA_FROM_KEY()` | `g_pSource2ModAddresses->GetWeaponCSDataFromKey()` | | `ADDR_GET_NAV_MESH_DATA()` | `g_pSource2ModAddresses->CSource2Server_GetNavMeshData()` | | `ADDR_LOG_DIRECT()` | `g_pSource2ModAddresses->CLoggingSystem_LogDirect()` | | `ADDR_CMD_EXECUTE_COMMAND()` | `g_pSource2ModAddresses->Cmd_ExecuteCommand()` | | `ADDR_GIVE_NAMED_ITEM()` | `g_pSource2ModAddresses->CCSPlayer_ItemServices_GiveNamedItem()` | # ISource2ModApi (/docs/core-api/ISource2ModApi) This interface provides access to: * Core engine interfaces * Source2Mod subsystems (Commands, Events, ConVars, etc.) * Logging and console output * Interface querying and factory access This is the primary interface passed to plugins during initialization. Main API interface for Source2Mod. Provides access to all source2mod subsystems and engine interfaces. ```cpp api->Commands()->RegisterConCommand(...); api->Events()->HookGameEvent(...); ``` **Global** `g_Source2ModAPI` · **Header** [`source2mod/ISource2ModApi.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModApi.h) Types [#types] `PluginId` [#pluginid] ```cpp using PluginId = int; ``` `CreateInterfaceFn` [#createinterfacefn] ```cpp typedef void* (*CreateInterfaceFn)(const char* pName, int* pReturnCode); ``` Methods [#methods] `Log` [#log] ```cpp virtual void Log(ISource2ModPlugin* plugin, const char* msg, ...) = 0; ``` Logs a formatted message with plugin context. | Parameter | Type | Description | | --------- | -------------------------------------------------------- | --------------- | | `plugin` | [`ISource2ModPlugin*`](/docs/core-api/ISource2ModPlugin) | Plugin instance | | `msg` | `const char*` | Format string | | `—` | `...` | | `ConPrint` [#conprint] ```cpp virtual void ConPrint(const char* msg) = 0; ``` Prints message to console. | Parameter | Type | | --------- | ------------- | | `msg` | `const char*` | `ConPrintf` [#conprintf] ```cpp virtual void ConPrintf(const char* fmt, ...) = 0; ``` Prints formatted message to console. | Parameter | Type | | --------- | ------------- | | `fmt` | `const char*` | | `—` | `...` | `AddListener` [#addlistener] ```cpp virtual void AddListener(ISource2ModPlugin* plugin, ISource2ModListener* pListener) = 0; ``` Registers a source2mod listener. | Parameter | Type | Description | | ----------- | ------------------------------------------------------------------------------ | ----------------- | | `plugin` | [`ISource2ModPlugin*`](/docs/core-api/ISource2ModPlugin) | Owner plugin | | `pListener` | [`ISource2ModListener*`](/docs/core-api/ISource2ModPlugin#isource2modlistener) | Listener instance | `QueryInterface` [#queryinterface] ```cpp virtual void* QueryInterface(CreateInterfaceFn fn, const char* iface, int min = -1) = 0; ``` Queries interface from factory. | Parameter | Type | Default | Description | | --------- | ---------------------------------------------------------------------- | ------- | -------------------------- | | `fn` | [`CreateInterfaceFn`](/docs/core-api/ISource2ModApi#createinterfacefn) | | Factory function | | `iface` | `const char*` | | Interface name | | `min` | `int` | `-1` | Minimum version (optional) | `InterfaceSearch` [#interfacesearch] ```cpp virtual void* InterfaceSearch(CreateInterfaceFn fn, const char* iface, int max, int* ret) = 0; ``` Searches for interface by iterating versions. | Parameter | Type | | --------- | ---------------------------------------------------------------------- | | `fn` | [`CreateInterfaceFn`](/docs/core-api/ISource2ModApi#createinterfacefn) | | `iface` | `const char*` | | `max` | `int` | | `ret` | `int*` | `FormatIface` [#formatiface] ```cpp virtual int FormatIface(char iface[], size_t maxlength) = 0; ``` Formats interface name with version. | Parameter | Type | | ----------- | --------- | | `iface` | `char []` | | `maxlength` | `size_t` | `GetEngineFactory` [#getenginefactory] ```cpp virtual CreateInterfaceFn GetEngineFactory(bool syn = true) = 0; ``` | Parameter | Type | Default | | --------- | ------ | ------- | | `syn` | `bool` | `true` | `GetPhysicsFactory` [#getphysicsfactory] ```cpp virtual CreateInterfaceFn GetPhysicsFactory(bool syn = true) = 0; ``` | Parameter | Type | Default | | --------- | ------ | ------- | | `syn` | `bool` | `true` | `GetFileSystemFactory` [#getfilesystemfactory] ```cpp virtual CreateInterfaceFn GetFileSystemFactory(bool syn = true) = 0; ``` | Parameter | Type | Default | | --------- | ------ | ------- | | `syn` | `bool` | `true` | `GetServerFactory` [#getserverfactory] ```cpp virtual CreateInterfaceFn GetServerFactory(bool syn = true) = 0; ``` | Parameter | Type | Default | | --------- | ------ | ------- | | `syn` | `bool` | `true` | `Source2ModFactory` [#source2modfactory] ```cpp virtual void* Source2ModFactory(const char* iface, int* ret, PluginId* id) = 0; ``` Source2Mod interface factory. | Parameter | Type | | --------- | ----------------------------------------------------- | | `iface` | `const char*` | | `ret` | `int*` | | `id` | [`PluginId*`](/docs/core-api/ISource2ModApi#pluginid) | `MetaFactory` [#metafactory] ```cpp virtual void* MetaFactory(const char* iface, int* ret, PluginId* id) = 0; ``` Metamod interface factory. | Parameter | Type | | --------- | ----------------------------------------------------- | | `iface` | `const char*` | | `ret` | `int*` | | `id` | [`PluginId*`](/docs/core-api/ISource2ModApi#pluginid) | `Addresses` [#addresses] ```cpp virtual ISource2ModAddresses* Addresses() = 0; ``` Access address/pattern system. `Commands` [#commands] ```cpp virtual ISource2ModCommands* Commands() = 0; ``` Access command system. `ConVars` [#convars] ```cpp virtual ISource2ModConVars* ConVars() = 0; ``` Access ConVar system. `CustomHud` [#customhud] ```cpp virtual ISource2ModCustomHud* CustomHud() = 0; ``` Access custom HUD layout system. `Entities` [#entities] ```cpp virtual ISource2ModEntities* Entities() = 0; ``` Access entities and Entity I/O system. `Events` [#events] ```cpp virtual ISource2ModEvents* Events() = 0; ``` Access event system. `GameConfig` [#gameconfig] ```cpp virtual ISource2ModGameConfig* GameConfig() = 0; ``` Access game configuration system. `GameSystems` [#gamesystems] ```cpp virtual ISource2ModGameSystems* GameSystems() = 0; ``` Access game system registry. `HTTP` [#http] ```cpp virtual ISource2ModHTTP* HTTP() = 0; ``` Access HTTP system. `Menus` [#menus] ```cpp virtual ISource2ModMenus* Menus() = 0; ``` Access menu system. `SQL` [#sql] ```cpp virtual ISource2ModSQL* SQL() = 0; ``` Access the SQL system (MySQL and SQLite). `NetworkMessages` [#networkmessages] ```cpp virtual ISource2ModNetworkMessages* NetworkMessages() = 0; ``` Access network messages system. `Paths` [#paths] ```cpp virtual ISource2ModPaths* Paths() = 0; ``` Where Source2Mod keeps things on disk. `Scheduler` [#scheduler] ```cpp virtual ISource2ModScheduler* Scheduler() = 0; ``` Access scheduler system. `Sounds` [#sounds] ```cpp virtual ISource2ModSounds* Sounds() = 0; ``` Access sound system. `Trace` [#trace] ```cpp virtual ISource2ModTrace* Trace() = 0; ``` Access tracing system. `Transmit` [#transmit] ```cpp virtual ISource2ModTransmit* Transmit() = 0; ``` Access transmit system (per-viewer entity visibility). `LoadModule` [#loadmodule] ```cpp virtual ISource2ModModule* LoadModule(const char* name) = 0; ``` Creates and initialises a module wrapper by name. The returned object must be released with FreeModule(). | Parameter | Type | Description | | --------- | ------------- | ---------------------------------------------- | | `name` | `const char*` | Module name without extension (e.g. "server"). | **Returns** [`ISource2ModModule*`](/docs/core-api/ISource2ModModule) — Heap-allocated ISource2ModModule, or nullptr on failure. `LoadModuleFromMemory` [#loadmodulefrommemory] ```cpp virtual ISource2ModModule* LoadModuleFromMemory(uintptr_t ptr) = 0; ``` Creates and initialises a module wrapper from an address inside it. The returned object must be released with FreeModule(). | Parameter | Type | Description | | --------- | ----------- | ---------------------------------------------- | | `ptr` | `uintptr_t` | Any address that belongs to the target module. | **Returns** [`ISource2ModModule*`](/docs/core-api/ISource2ModModule) — Heap-allocated ISource2ModModule, or nullptr on failure. `FreeModule` [#freemodule] ```cpp virtual void FreeModule(ISource2ModModule* module) = 0; ``` Releases a module previously obtained from LoadModule() / LoadModuleFromMemory(). | Parameter | Type | | --------- | -------------------------------------------------------- | | `module` | [`ISource2ModModule*`](/docs/core-api/ISource2ModModule) | `GetSource2Server` [#getsource2server] ```cpp virtual ISource2Server* GetSource2Server() = 0; ``` `GetSource2ServerConfig` [#getsource2serverconfig] ```cpp virtual ISource2ServerConfig* GetSource2ServerConfig() = 0; ``` `GetSource2GameClients` [#getsource2gameclients] ```cpp virtual ISource2GameClients* GetSource2GameClients() = 0; ``` `GetSource2GameEntities` [#getsource2gameentities] ```cpp virtual ISource2GameEntities* GetSource2GameEntities() = 0; ``` `GetEngineServer` [#getengineserver] ```cpp virtual IVEngineServer2* GetEngineServer() = 0; ``` `GetCVar` [#getcvar] ```cpp virtual ICvar* GetCVar() = 0; ``` `GetSchemaSystem` [#getschemasystem] ```cpp virtual CSchemaSystem* GetSchemaSystem() = 0; ``` `GetGameResourceService` [#getgameresourceservice] ```cpp virtual IGameResourceService* GetGameResourceService() = 0; ``` `GetNetworkServerService` [#getnetworkserverservice] ```cpp virtual INetworkServerService* GetNetworkServerService() = 0; ``` `GetNetworkSystem` [#getnetworksystem] ```cpp virtual INetworkSystem* GetNetworkSystem() = 0; ``` `GetNetworkMessages` [#getnetworkmessages] ```cpp virtual INetworkMessages* GetNetworkMessages() = 0; ``` `GetNetworkStringTableServer` [#getnetworkstringtableserver] ```cpp virtual INetworkStringTableContainer* GetNetworkStringTableServer() = 0; ``` `GetEngineServiceMgr` [#getengineservicemgr] ```cpp virtual IEngineServiceMgr* GetEngineServiceMgr() = 0; ``` `GetHostStateMgr` [#gethoststatemgr] ```cpp virtual IHostStateMgr* GetHostStateMgr() = 0; ``` `GetLocalize` [#getlocalize] ```cpp virtual ILocalize* GetLocalize() = 0; ``` `GetFileSystem` [#getfilesystem] ```cpp virtual IFileSystem* GetFileSystem() = 0; ``` `GetGameEventSystem` [#getgameeventsystem] ```cpp virtual IGameEventSystem* GetGameEventSystem() = 0; ``` `GetGameEventManager` [#getgameeventmanager] ```cpp virtual IGameEventManager2* GetGameEventManager() = 0; ``` `GetEntitySystem` [#getentitysystem] ```cpp virtual CGameEntitySystem* GetEntitySystem() = 0; ``` `GetGlobalVars` [#getglobalvars] ```cpp virtual CGlobalVars* GetGlobalVars() = 0; ``` `GetBaseDir` [#getbasedir] ```cpp virtual const char* GetBaseDir() = 0; ``` Returns base directory of the server. `Format` [#format] ```cpp virtual size_t Format(char* buffer, size_t maxlength, const char* format, ...) = 0; ``` Formats string into buffer. | Parameter | Type | | ----------- | ------------- | | `buffer` | `char*` | | `maxlength` | `size_t` | | `format` | `const char*` | | `—` | `...` | `FormatArgs` [#formatargs] ```cpp virtual size_t FormatArgs(char* buffer, size_t maxlength, const char* format, va_list ap) = 0; ``` Formats string using va\_list. | Parameter | Type | | ----------- | ------------- | | `buffer` | `char*` | | `maxlength` | `size_t` | | `format` | `const char*` | | `ap` | `va_list` | `GameHooks` [#gamehooks] ```cpp virtual ISource2ModGameHooks* GameHooks() = 0; ``` Access the core's hooks on common game functions. # ISource2ModClients (/docs/core-api/ISource2ModClients) What a CBasePlayerController already does (name, IP, SteamID64, user id, console commands, team changes, Disconnect) stays there. **Interface id** `ISource2ModClients001` (`SOURCE2MOD_CLIENTS_INTERFACE`) · **Global** `g_pSource2ModClients` · **Header** [`source2mod/ISource2ModClients.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModClients.h) Methods [#methods] `IsConnected` [#isconnected] ```cpp virtual bool IsConnected(CPlayerSlot slot) = 0; ``` A slot has a client (connecting or in game). | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | `IsInGame` [#isingame] ```cpp virtual bool IsInGame(CPlayerSlot slot) = 0; ``` A slot's client is fully in game. | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | `IsFakeClient` [#isfakeclient] ```cpp virtual bool IsFakeClient(CPlayerSlot slot) = 0; ``` | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | `IsAuthorized` [#isauthorized] ```cpp virtual bool IsAuthorized(CPlayerSlot slot) = 0; ``` Steam has confirmed the client's SteamID. | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | `GetAuthId` [#getauthid] ```cpp virtual std::string GetAuthId(CPlayerSlot slot, ESource2ModAuthId form, bool validated = true) = 0; ``` A client's SteamID as text. | Parameter | Type | Default | Description | | ----------- | -------------------------------------------------------------------------- | ------- | ---------------------------------------------- | | `slot` | `CPlayerSlot` | | | | `form` | [`ESource2ModAuthId`](/docs/core-api/ISource2ModClients#esource2modauthid) | | | | `validated` | `bool` | `true` | Only once Steam has confirmed it (else empty). | **Returns** `std::string` — Empty for a bot or no client. `GetLatency` [#getlatency] ```cpp virtual float GetLatency(CPlayerSlot slot) = 0; ``` Average round trip in seconds (0 for a bot or no client). | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | `GetConnectionTime` [#getconnectiontime] ```cpp virtual float GetConnectionTime(CPlayerSlot slot) = 0; ``` Seconds connected (0 for a bot or no client). | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | `GetClientConVar` [#getclientconvar] ```cpp virtual std::string GetClientConVar(CPlayerSlot slot, const char* name) = 0; ``` A cvar the client sent (cl\_language, ...); empty when it did not. | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | | `name` | `const char*` | `Kick` [#kick] ```cpp virtual void Kick(CPlayerSlot slot, const char* message) = 0; ``` Disconnects a client. CS2 shows them the kick reason's own text; @p message is the reason the server logs. | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | | `message` | `const char*` | `GetClientCount` [#getclientcount] ```cpp virtual int GetClientCount(bool inGameOnly = true) = 0; ``` Clients on the server, bots included. | Parameter | Type | Default | | ------------ | ------ | ------- | | `inGameOnly` | `bool` | `true` | Enums [#enums] `ESource2ModAuthId` [#esource2modauthid] ```cpp enum ESource2ModAuthId ``` SteamID forms (SourceMod's AuthIdType values). | Name | Value | Description | | ----------------------------- | ----- | ---------------------- | | `SOURCE2MOD_AUTHID_ENGINE` | `0` | as the engine shows it | | `SOURCE2MOD_AUTHID_STEAM2` | | STEAM\_1:0:12345 | | `SOURCE2MOD_AUTHID_STEAM3` | | \[U:1:24690] | | `SOURCE2MOD_AUTHID_STEAMID64` | | 76561197960290418 | # ISource2ModCommands (/docs/core-api/ISource2ModCommands) Provides functionality for: * Registering console commands * Listening to console command execution * Handling in-game chat messages This system allows plugins to hook into player input via chat or console. Interface for registering and handling commands. Supports: * Chat-based commands (player messages) * Console commands (server or client) * Command listeners with pre/post execution hooks Every registration belongs to the plugin whose handler it is. **Interface id** `ISource2ModCommands003` (`SOURCE2MOD_COMMANDS_INTERFACE`) · **Global** `g_pSource2ModCommands` · **Header** [`source2mod/ISource2ModCommands.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModCommands.h) Types [#types] `Source2ModCommandId` [#source2modcommandid] ```cpp using Source2ModCommandId = Source2ModHookId; ``` A console command of the plugin's own (RegisterConCommand). `Source2ModCommandHookId` [#source2modcommandhookid] ```cpp using Source2ModCommandHookId = Source2ModHookId; ``` A listener on a command somebody else owns (RegisterConListener). `Source2ModChatListenerId` [#source2modchatlistenerid] ```cpp using Source2ModChatListenerId = Source2ModHookId; ``` A chat-only listener (RegisterChatListener). `ChatHandler` [#chathandler] ```cpp using ChatHandler = Source2ModCallback; ``` A chat command or console command handler: a function, an object and a method, or a lambda (Source2ModCallback). `CommandHandler` [#commandhandler] ```cpp using CommandHandler = Source2ModCallback; ``` Callback for console commands. Methods [#methods] `RegisterChatListener` [#registerchatlistener] ```cpp virtual Source2ModChatListenerId RegisterChatListener(const char* pchName, ChatHandler handler, const char* pchPermission = nullptr) = 0; ``` Registers a chat command. Only fires for a chat message that starts with one of the configured chat triggers (public or silent, "!" and "/" by default), e.g. "!kick" or "/kick"; plain "kick" typed into chat does not fire it. The trigger is stripped before matching, so args.Arg(0) is the bare name. A silent trigger hides the message; a public one lets it show. Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it. | Parameter | Type | Default | Description | | --------------- | --------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `pchName` | `const char*` | | Command name without a trigger (e.g. "kick") | | `handler` | [`ChatHandler`](/docs/core-api/ISource2ModCommands#chathandler) | | A function, an object and a method, or a lambda | | `pchPermission` | `const char*` | `nullptr` | What a player needs to run it (see ISource2ModPermissions); nullptr or "" for everybody. A player without it gets the access-denied answer instead. | **Returns** [`Source2ModChatListenerId`](/docs/core-api/ISource2ModCommands#source2modchatlistenerid) — The id UnregisterChatListener(id) takes `UnregisterChatListener` [#unregisterchatlistener] ```cpp virtual bool UnregisterChatListener(const char* pchName, const ChatHandler& handler) = 0; virtual bool UnregisterChatListener(Source2ModChatListenerId id) = 0; ``` Drops the chat command with this handler -- a function or an object and a method; a lambda goes by its id. Drops the chat command RegisterChatListener() returned the id for. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `pchName` | `const char*` | | `handler` | [`const ChatHandler&`](/docs/core-api/ISource2ModCommands#chathandler) | | `id` | [`Source2ModChatListenerId`](/docs/core-api/ISource2ModCommands#source2modchatlistenerid) | **Returns** `bool` — true when one was found `RegisterConCommand` [#registerconcommand] ```cpp virtual Source2ModCommandId RegisterConCommand(const char* pchName, ChatHandler handler, const char* pchPermission = nullptr) = 0; ``` Registers a console command, reachable from chat as "!name" and "/name" too. Several handlers may share a name; each runs. Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it. | Parameter | Type | Default | Description | | --------------- | --------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `pchName` | `const char*` | | Command name (e.g. "sv\_test") | | `handler` | [`ChatHandler`](/docs/core-api/ISource2ModCommands#chathandler) | | A function, an object and a method, or a lambda | | `pchPermission` | `const char*` | `nullptr` | What a player needs to run it (see ISource2ModPermissions); nullptr or "" for everybody. The server console always may. The owner can re-assign it under "Overrides" in permissions.json. | **Returns** [`Source2ModCommandId`](/docs/core-api/ISource2ModCommands#source2modcommandid) — The id UnregisterConCommand(id) takes `UnregisterConCommand` [#unregisterconcommand] ```cpp virtual bool UnregisterConCommand(const char* pchName, const ChatHandler& handler) = 0; virtual bool UnregisterConCommand(Source2ModCommandId id) = 0; ``` Drops the console command handler -- a function or an object and a method; a lambda goes by its id. The command name stays claimed until the plugin unloads. Drops the console command handler RegisterConCommand() returned the id for. | Parameter | Type | | --------- | ------------------------------------------------------------------------------- | | `pchName` | `const char*` | | `handler` | [`const ChatHandler&`](/docs/core-api/ISource2ModCommands#chathandler) | | `id` | [`Source2ModCommandId`](/docs/core-api/ISource2ModCommands#source2modcommandid) | **Returns** `bool` — true when one was found `RegisterConListener` [#registerconlistener] ```cpp virtual Source2ModCommandHookId RegisterConListener(const char* pchName, CommandHandler handler, bool post) = 0; ``` Registers a listener on an existing console command. The handler's Action: * Ignore: do nothing * Override: the original still runs; commands return nothing, so this has no further effect. Later pre listeners and all post listeners still fire. * Supersede: block the original (pre only); the remaining pre listeners and all post listeners are skipped Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it. | Parameter | Type | Description | | --------- | --------------------------------------------------------------------- | --------------------------------------------------- | | `pchName` | `const char*` | Existing command name to listen for | | `handler` | [`CommandHandler`](/docs/core-api/ISource2ModCommands#commandhandler) | A function, an object and a method, or a lambda | | `post` | `bool` | false to run before the original, true to run after | **Returns** [`Source2ModCommandHookId`](/docs/core-api/ISource2ModCommands#source2modcommandhookid) — The id UnregisterConListener(id) takes `UnregisterConListener` [#unregisterconlistener] ```cpp virtual bool UnregisterConListener(const char* pchName, const CommandHandler& handler, bool post) = 0; virtual bool UnregisterConListener(Source2ModCommandHookId id) = 0; ``` Drops the console listener with this handler -- a function or an object and a method; a lambda goes by its id. Drops the console listener RegisterConListener() returned the id for. | Parameter | Type | | --------- | --------------------------------------------------------------------------------------- | | `pchName` | `const char*` | | `handler` | [`const CommandHandler&`](/docs/core-api/ISource2ModCommands#commandhandler) | | `post` | `bool` | | `id` | [`Source2ModCommandHookId`](/docs/core-api/ISource2ModCommands#source2modcommandhookid) | **Returns** `bool` — true when one was found `ReplyToCommand` [#replytocommand] ```cpp virtual void ReplyToCommand(const Source2ModCommandContext& ctx, const char* pszMessage) = 0; ``` Answers a command where it was typed: to the player's chat when it came from chat, to the player's console when it came from their console, to the server console when it came from there. One line; the newline is added. | Parameter | Type | | ------------ | ------------------------------------------------------------------------------------------------ | | `ctx` | [`const Source2ModCommandContext&`](/docs/core-api/ISource2ModCommands#source2modcommandcontext) | | `pszMessage` | `const char*` | `Source2ModCommandContext` [#source2modcommandcontext] Constructors [#constructors] ```cpp Source2ModCommandContext(CPlayerSlot slot, int target, Source2ModCommandSource source = Source2ModCommandSource::Console); ``` `GetPlayerSlot` [#getplayerslot] ```cpp CPlayerSlot GetPlayerSlot() const; ``` The player who issued the command; invalid for the server console. `GetCommandTarget` [#getcommandtarget] ```cpp int GetCommandTarget() const; ``` The engine's CommandTarget\_t (CT\_NO\_TARGET, CT\_FIRST\_SPLITSCREEN\_CLIENT, ...). `GetSource` [#getsource] ```cpp Source2ModCommandSource GetSource() const; ``` Where it was typed. `IsFromChat` [#isfromchat] ```cpp bool IsFromChat() const; ``` Typed in chat, with either trigger. `Source2ModCommandArgs` [#source2modcommandargs] A command's arguments, in Source2Mod's own layout; same accessors as the engine's CCommand. The strings belong to the dispatch and are gone when the handler returns -- copy what has to outlive it. Fields [#fields] | Field | Type | | ---------- | ----- | | `kMaxArgs` | `int` | Constructors [#constructors-1] ```cpp Source2ModCommandArgs() = default; Source2ModCommandArgs(int argc, const char* const* argv, const char* argS, const char* command); ``` `ArgC` [#argc] ```cpp int ArgC() const; ``` Number of arguments, the command name included. `Arg` [#arg] ```cpp const char* Arg(int i) const; ``` Argument i, the command name at 0; "" past the end. | Parameter | Type | | --------- | ----- | | `i` | `int` | `ArgS` [#args] ```cpp const char* ArgS() const; ``` Everything after the command name, as typed. `GetCommandString` [#getcommandstring] ```cpp const char* GetCommandString() const; ``` The whole command line, as typed. Enums [#enums] `Source2ModCommandSource` [#source2modcommandsource] ```cpp enum class Source2ModCommandSource : uint8_t ``` Where a command was typed: what ReplyToCommand() answers into. | Name | Description | | ------------ | ------------------------------------------------------------ | | `Console` | A console -- the server's or the player's own | | `Chat` | Chat, with a public trigger ("!kick"); the message shows | | `SilentChat` | Chat, with a silent trigger ("/kick"); the message is hidden | Macros [#macros] `REGISTER_CHAT_LISTENER` [#register_chat_listener] ```cpp #define REGISTER_CHAT_LISTENER(pchName, ...) g_pSource2ModCommands->RegisterChatListener(pchName, __VA_ARGS__) ``` RegisterConCommand(name, handler) or RegisterConCommand(name, handler, permission). `REPLY_TO_COMMAND` [#reply_to_command] ```cpp #define REPLY_TO_COMMAND(ctx, fmt, ...) \ do { char _source2modReply[512]; snprintf(_source2modReply, sizeof(_source2modReply), fmt, ##__VA_ARGS__); g_pSource2ModCommands->ReplyToCommand(ctx, _source2modReply); } while (0) ``` ReplyToCommand() with printf formatting. Other macros [#other-macros] | Macro | Expands to | | ----------------------------------------------- | -------------------------------------------------------------------- | | `REGISTER_CON_COMMAND(pchName, ...)` | `g_pSource2ModCommands->RegisterConCommand(pchName, __VA_ARGS__)` | | `REGISTER_CON_LISTENER(pchName, handler, post)` | `g_pSource2ModCommands->RegisterConListener(pchName, handler, post)` | # ISource2ModConVars (/docs/core-api/ISource2ModConVars) Provides functionality for: * Querying ConVars by name * Reading and writing values * Accessing metadata (type, flags, help text) * Creating and deleting ConVars ConVars are identified by an internal access index. **Interface id** `ISource2ModConVars002` (`SOURCE2MOD_CONVARS_INTERFACE`) · **Global** `g_pSource2ModConVars` · **Header** [`source2mod/ISource2ModConVars.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModConVars.h) Types [#types] `Source2ModConVarHookId` [#source2modconvarhookid] ```cpp using Source2ModConVarHookId = Source2ModHookId; ``` A ConVar change hook (HookConVarChange). `ConVarChangeHandler` [#convarchangehandler] ```cpp using ConVarChangeHandler = Source2ModCallback; ``` Callback type for ConVar value changes. Methods [#methods] `GetConvarAccessIndexByName` [#getconvaraccessindexbyname] ```cpp virtual uint16 GetConvarAccessIndexByName(const char* name) = 0; ``` Retrieves access index of a ConVar by name. | Parameter | Type | Description | | --------- | ------------- | ----------- | | `name` | `const char*` | ConVar name | **Returns** `uint16` — Access index, or SOURCE2MOD\_INVALID\_CONVAR\_INDEX if no ConVar has that name. 0 is a valid index, so compare against the sentinel rather than testing for zero. `GetConvarRef` [#getconvarref] ```cpp virtual ConVarRefAbstract GetConvarRef(uint16 accessIndex) = 0; ``` Gets a ConVar reference wrapper. | Parameter | Type | | ------------- | -------- | | `accessIndex` | `uint16` | `GetName` [#getname] ```cpp virtual const char* GetName(uint16 accessIndex) = 0; ``` Gets ConVar name. | Parameter | Type | | ------------- | -------- | | `accessIndex` | `uint16` | `GetHelpText` [#gethelptext] ```cpp virtual const char* GetHelpText(uint16 accessIndex) = 0; ``` Gets help/description text. | Parameter | Type | | ------------- | -------- | | `accessIndex` | `uint16` | `GetType` [#gettype] ```cpp virtual EConVarType GetType(uint16 accessIndex) = 0; ``` Gets ConVar type. | Parameter | Type | | ------------- | -------- | | `accessIndex` | `uint16` | `GetFlags` [#getflags] ```cpp virtual uint64 GetFlags(uint16 accessIndex) = 0; ``` Gets flags (FCVAR\_\*). | Parameter | Type | | ------------- | -------- | | `accessIndex` | `uint16` | `SetFlags` [#setflags] ```cpp virtual void SetFlags(uint16 accessIndex, uint64 flags) = 0; ``` Sets flags (FCVAR\_\*). | Parameter | Type | | ------------- | -------- | | `accessIndex` | `uint16` | | `flags` | `uint64` | `GetValueAddress` [#getvalueaddress] ```cpp virtual void* GetValueAddress(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` Gets pointer to underlying value. Type depends on ConVar type. | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetString` [#getstring] ```cpp virtual const char* GetString(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetBool` [#getbool] ```cpp virtual bool GetBool(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetInt` [#getint] ```cpp virtual int32 GetInt(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetFloat` [#getfloat] ```cpp virtual float GetFloat(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetDouble` [#getdouble] ```cpp virtual double GetDouble(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetString` [#setstring] ```cpp virtual void SetString(uint16 accessIndex, const char* value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `const char*` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetBool` [#setbool] ```cpp virtual void SetBool(uint16 accessIndex, bool value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `bool` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetInt` [#setint] ```cpp virtual void SetInt(uint16 accessIndex, int32 value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `int32` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetFloat` [#setfloat] ```cpp virtual void SetFloat(uint16 accessIndex, float value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `float` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetDouble` [#setdouble] ```cpp virtual void SetDouble(uint16 accessIndex, double value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `double` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetVector2` [#getvector2] ```cpp virtual Vector2D GetVector2(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetVector3` [#getvector3] ```cpp virtual Vector GetVector3(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetVector4` [#getvector4] ```cpp virtual Vector4D GetVector4(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetQAngle` [#getqangle] ```cpp virtual QAngle GetQAngle(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetColor` [#getcolor] ```cpp virtual Color GetColor(uint16 accessIndex, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetVector2` [#setvector2] ```cpp virtual void SetVector2(uint16 accessIndex, const Vector2D& value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `const Vector2D&` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetVector3` [#setvector3] ```cpp virtual void SetVector3(uint16 accessIndex, const Vector& value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `const Vector&` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetVector4` [#setvector4] ```cpp virtual void SetVector4(uint16 accessIndex, const Vector4D& value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `const Vector4D&` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetQAngle` [#setqangle] ```cpp virtual void SetQAngle(uint16 accessIndex, const QAngle& value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `const QAngle&` | | | `slot` | `CSplitScreenSlot` | `-1` | `SetColor` [#setcolor] ```cpp virtual void SetColor(uint16 accessIndex, const Color& value, CSplitScreenSlot slot = -1) = 0; ``` | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `const Color&` | | | `slot` | `CSplitScreenSlot` | `-1` | `GetValue` [#getvalue] ```cpp virtual void GetValue(uint16 accessIndex, void* outValue, CSplitScreenSlot slot = -1) = 0; ``` Gets value into user-provided buffer. | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `outValue` | `void*` | | | `slot` | `CSplitScreenSlot` | `-1` | `HookConVarChange` [#hookconvarchange] ```cpp virtual Source2ModConVarHookId HookConVarChange(ConVarChangeHandler handler) = 0; ``` Registers a listener for every ConVar value change. The engine only takes plain function pointers here, and only keeps one list of them for the whole process. Source2Mod installs a single one of those and fans out to the handlers registered through this, so a plugin can use a capturing lambda and does not have to unregister on unload. A plugin may register as many as it likes. Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it. | Parameter | Type | Description | | --------- | ------------------------------------------------------------------------------ | ----------------------------------------------- | | `handler` | [`ConVarChangeHandler`](/docs/core-api/ISource2ModConVars#convarchangehandler) | A function, an object and a method, or a lambda | **Returns** [`Source2ModConVarHookId`](/docs/core-api/ISource2ModConVars#source2modconvarhookid) — The id UnhookConVarChange(id) takes `UnhookConVarChange` [#unhookconvarchange] ```cpp virtual bool UnhookConVarChange(const ConVarChangeHandler& handler) = 0; virtual bool UnhookConVarChange(Source2ModConVarHookId id) = 0; ``` Drops the change listener with this handler -- a function or an object and a method; a lambda goes by its id. Drops the change listener HookConVarChange() returned the id for. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------- | | `handler` | [`const ConVarChangeHandler&`](/docs/core-api/ISource2ModConVars#convarchangehandler) | | `id` | [`Source2ModConVarHookId`](/docs/core-api/ISource2ModConVars#source2modconvarhookid) | **Returns** `bool` — true when one was found `SetValue` [#setvalue] ```cpp virtual void SetValue(uint16 accessIndex, const void* value, CSplitScreenSlot slot = -1) = 0; ``` Sets value from user-provided buffer. | Parameter | Type | Default | | ------------- | ------------------ | ------- | | `accessIndex` | `uint16` | | | `value` | `const void*` | | | `slot` | `CSplitScreenSlot` | `-1` | `CreateConVar` [#createconvar] ```cpp virtual uint16 CreateConVar(const char* name, EConVarType type, const char* help, uint64 flags, bool hasMin, bool hasMax, void* defaultValue, const void* minValue, const void* maxValue) = 0; ``` Creates a new ConVar. The engine has no way to unregister a ConVar, so one created here outlives the plugin that created it. When the plugin loads again, the same call returns the existing ConVar with its current value. | Parameter | Type | Description | | -------------- | ------------- | ------------------------------------------------------------------------------ | | `name` | `const char*` | ConVar name | | `type` | `EConVarType` | Variable type | | `help` | `const char*` | Description text | | `flags` | `uint64` | FCVAR flags | | `hasMin` | `bool` | Whether min value is enforced | | `hasMax` | `bool` | Whether max value is enforced | | `defaultValue` | `void*` | Default value pointer (const char\* for EConVarType\_String) | | `minValue` | `const void*` | Minimum value pointer; only read when hasMin is true, may be nullptr otherwise | | `maxValue` | `const void*` | Maximum value pointer; only read when hasMax is true, may be nullptr otherwise | **Returns** `uint16` — Access index of the created ConVar. If a ConVar with that name already exists, its index is returned and the arguments are ignored. SOURCE2MOD\_INVALID\_CONVAR\_INDEX on bad arguments or an unsupported type. `DeleteConVar` [#deleteconvar] ```cpp virtual void DeleteConVar(uint16 accessIndex) = 0; ``` Deletes a ConVar. | Parameter | Type | | ------------- | -------- | | `accessIndex` | `uint16` | Functions [#functions] `UTIL_RemoveConVarFlags` [#util_removeconvarflags] ```cpp inline bool UTIL_RemoveConVarFlags(const char* pszName, uint64 nFlags); ``` Clears flags on a ConVar found by name, e.g. FCVAR\_CHEAT. | Parameter | Type | | --------- | ------------- | | `pszName` | `const char*` | | `nFlags` | `uint64` | **Returns** `bool` — false when there is no such ConVar, or its data is not available. `UTIL_AddConVarFlags` [#util_addconvarflags] ```cpp inline bool UTIL_AddConVarFlags(const char* pszName, uint64 nFlags); ``` Sets flags on a ConVar found by name. | Parameter | Type | | --------- | ------------- | | `pszName` | `const char*` | | `nFlags` | `uint64` | `UTIL_SetConVar` [#util_setconvar] ```cpp template inline bool UTIL_SetConVar(const char* pszName, const T& value, CSplitScreenSlot slot = -1); inline bool UTIL_SetConVar(const char* pszName, double v, CSplitScreenSlot slot = -1); inline bool UTIL_SetConVar(const char* pszName, float v, CSplitScreenSlot slot = -1); inline bool UTIL_SetConVar(const char* pszName, int v, CSplitScreenSlot slot = -1); inline bool UTIL_SetConVar(const char* pszName, unsigned v, CSplitScreenSlot slot = -1); ``` Sets a ConVar by name. The type is what decides how the value is written, so call it as UTIL\_SetConVar\("mp\_freezetime", 5) when the literal would deduce to something else. | Parameter | Type | Default | | --------- | ------------------ | ------- | | `pszName` | `const char*` | | | `value` | `const T&` | | | `slot` | `CSplitScreenSlot` | `-1` | | `v` | `double` | | | `v` | `float` | | | `v` | `int` | | | `—` | `unsigned v` | | **Returns** `bool` — false when there is no such ConVar, or its data is not available. Macros [#macros] `HOOK_CONVAR_CHANGE` [#hook_convar_change] ```cpp #define HOOK_CONVAR_CHANGE(passfunc) g_pSource2ModConVars->HookConVarChange(passfunc) ``` Macro for listening to ConVar value changes. `UNHOOK_CONVAR_CHANGE` [#unhook_convar_change] ```cpp #define UNHOOK_CONVAR_CHANGE(passfunc) g_pSource2ModConVars->UnhookConVarChange(passfunc) ``` Macro for dropping a ConVar change hook (a function or an object and a method; a lambda goes by the id HOOK\_CONVAR\_CHANGE returned). Other macros [#other-macros] | Macro | Expands to | | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `CVAR_IDX(name)` | `g_pSource2ModConVars->GetConvarAccessIndexByName(name)` | | `CVAR_REF(idx)` | `g_pSource2ModConVars->GetConvarRef(idx)` | | `CVAR_GET_STR(idx, ...)` | `g_pSource2ModConVars->GetString(idx, ##__VA_ARGS__)` | | `CVAR_GET_BOOL(idx, ...)` | `g_pSource2ModConVars->GetBool(idx, ##__VA_ARGS__)` | | `CVAR_GET_INT(idx, ...)` | `g_pSource2ModConVars->GetInt(idx, ##__VA_ARGS__)` | | `CVAR_GET_FLOAT(idx, ...)` | `g_pSource2ModConVars->GetFloat(idx, ##__VA_ARGS__)` | | `CVAR_GET_DOUBLE(idx, ...)` | `g_pSource2ModConVars->GetDouble(idx, ##__VA_ARGS__)` | | `CVAR_SET_STR(idx, v, ...)` | `g_pSource2ModConVars->SetString(idx, v, ##__VA_ARGS__)` | | `CVAR_SET_BOOL(idx, v, ...)` | `g_pSource2ModConVars->SetBool(idx, v, ##__VA_ARGS__)` | | `CVAR_SET_INT(idx, v, ...)` | `g_pSource2ModConVars->SetInt(idx, v, ##__VA_ARGS__)` | | `CVAR_SET_FLOAT(idx, v, ...)` | `g_pSource2ModConVars->SetFloat(idx, v, ##__VA_ARGS__)` | | `CVAR_SET_DOUBLE(idx, v, ...)` | `g_pSource2ModConVars->SetDouble(idx, v, ##__VA_ARGS__)` | | `CVAR_CREATE(name, type, help, flags, hasMin, hasMax, def, minVal, maxVal)` | `g_pSource2ModConVars->CreateConVar(name, type, help, flags, hasMin, hasMax, def, minVal, maxVal)` | | `CVAR_DELETE(idx)` | `g_pSource2ModConVars->DeleteConVar(idx)` | # ISource2ModCookies (/docs/core-api/ISource2ModCookies) SourceMod's clientprefs. A cookie is a named string setting ("quake\_sounds", "hud\_color"); every player has their own value of it, kept in a database under their SteamID, and loaded into memory when the player is authorized. Until then -- AreCookiesCached() false -- reads return "" and writes are kept and saved once the player's values arrived. The database is an SQLite file, addons/source2mod/data/clientprefs.sq3 unless core.json's "ClientPrefsDatabase" names another. The tables are SourceMod's (sm\_cookies, sm\_cookie\_cache) and players are keyed by "STEAM\_1:Y:Z", so a SourceMod clientprefs database can be carried over. Values are saved when they change (a write goes to the database right away, asynchronously) -- nothing is lost on a crash or a map change. **Interface id** `ISource2ModCookies001` (`SOURCE2MOD_COOKIES_INTERFACE`) · **Global** `g_pSource2ModCookies` · **Header** [`source2mod/ISource2ModCookies.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModCookies.h) Types [#types] `Source2ModCookiesListenerId` [#source2modcookieslistenerid] ```cpp using Source2ModCookiesListenerId = Source2ModHookId; ``` A cookies-cached listener (AddCookiesCachedListener). `Source2ModCookieId` [#source2modcookieid] ```cpp using Source2ModCookieId = int; ``` A registered cookie; 0 is none. `CookiesCachedCallback` [#cookiescachedcallback] ```cpp using CookiesCachedCallback = Source2ModCallback; ``` A player's cookies were loaded (SourceMod's OnClientCookiesCached). Runs on the main thread, once per connection, after OnClientAuthorized. Methods [#methods] `IsConnected` [#isconnected] ```cpp virtual bool IsConnected() = 0; ``` Whether the cookie database is connected. Everything else works without it, only nothing is loaded or saved. `RegisterCookie` [#registercookie] ```cpp virtual Source2ModCookieId RegisterCookie(const char* name, const char* description, Source2ModCookieAccess access) = 0; ``` Registers a cookie, or finds it when it already is registered (by any plugin -- cookies are shared, by name). | Parameter | Type | | ------------- | ------------------------------------------------------------------------------------ | | `name` | `const char*` | | `description` | `const char*` | | `access` | [`Source2ModCookieAccess`](/docs/core-api/ISource2ModCookies#source2modcookieaccess) | **Returns** [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) — The cookie, 0 for an empty name or one longer than SOURCE2MOD\_COOKIE\_MAX\_NAME\_LENGTH. `FindCookie` [#findcookie] ```cpp virtual Source2ModCookieId FindCookie(const char* name) = 0; ``` A registered cookie by name; 0 when there is none. | Parameter | Type | | --------- | ------------- | | `name` | `const char*` | `GetCookieName` [#getcookiename] ```cpp virtual const char* GetCookieName(Source2ModCookieId cookie) = 0; ``` Its name / description; nullptr for a bad id. | Parameter | Type | | --------- | ---------------------------------------------------------------------------- | | `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) | `GetCookieDescription` [#getcookiedescription] ```cpp virtual const char* GetCookieDescription(Source2ModCookieId cookie) = 0; ``` | Parameter | Type | | --------- | ---------------------------------------------------------------------------- | | `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) | `GetCookieAccess` [#getcookieaccess] ```cpp virtual Source2ModCookieAccess GetCookieAccess(Source2ModCookieId cookie) = 0; ``` | Parameter | Type | | --------- | ---------------------------------------------------------------------------- | | `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) | `GetCookieCount` [#getcookiecount] ```cpp virtual int GetCookieCount() = 0; ``` How many cookies are registered; ids run from 1 to that. `AreCookiesCached` [#arecookiescached] ```cpp virtual bool AreCookiesCached(CPlayerSlot slot) = 0; ``` Whether the player's values were loaded (or there is no database to load them from). | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | `GetCookie` [#getcookie] ```cpp virtual bool GetCookie(CPlayerSlot slot, Source2ModCookieId cookie, char* out, size_t maxlen) = 0; ``` The player's value. | Parameter | Type | | --------- | ---------------------------------------------------------------------------- | | `slot` | `CPlayerSlot` | | `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) | | `out` | `char*` | | `maxlen` | `size_t` | **Returns** `bool` — false (and "") when the player has never had one, or the values are not loaded yet. `SetCookie` [#setcookie] ```cpp virtual bool SetCookie(CPlayerSlot slot, Source2ModCookieId cookie, const char* value) = 0; ``` Sets the player's value and saves it. | Parameter | Type | | --------- | ---------------------------------------------------------------------------- | | `slot` | `CPlayerSlot` | | `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) | | `value` | `const char*` | **Returns** `bool` — false for an empty slot, a bot, a bad id or a value longer than SOURCE2MOD\_COOKIE\_MAX\_VALUE\_LENGTH. `GetCookieTime` [#getcookietime] ```cpp virtual int64_t GetCookieTime(CPlayerSlot slot, Source2ModCookieId cookie) = 0; ``` When the value was last set, Unix time; 0 when it never was. | Parameter | Type | | --------- | ---------------------------------------------------------------------------- | | `slot` | `CPlayerSlot` | | `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) | `SetCookieBySteamID` [#setcookiebysteamid] ```cpp virtual bool SetCookieBySteamID(uint64 steamId, Source2ModCookieId cookie, const char* value) = 0; ``` Sets the value of a player who need not be connected. | Parameter | Type | Description | | --------- | ---------------------------------------------------------------------------- | ------------ | | `steamId` | `uint64` | A SteamID64. | | `cookie` | [`Source2ModCookieId`](/docs/core-api/ISource2ModCookies#source2modcookieid) | | | `value` | `const char*` | | `AddCookiesCachedListener` [#addcookiescachedlistener] ```cpp virtual Source2ModCookiesListenerId AddCookiesCachedListener(CookiesCachedCallback callback) = 0; ``` Runs `callback` whenever a player's cookies were loaded. Goes with the plugin. | Parameter | Type | | ---------- | ---------------------------------------------------------------------------------- | | `callback` | [`CookiesCachedCallback`](/docs/core-api/ISource2ModCookies#cookiescachedcallback) | `RemoveCookiesCachedListener` [#removecookiescachedlistener] ```cpp virtual bool RemoveCookiesCachedListener(Source2ModCookiesListenerId id) = 0; ``` | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------- | | `id` | [`Source2ModCookiesListenerId`](/docs/core-api/ISource2ModCookies#source2modcookieslistenerid) | Enums [#enums] `Source2ModCookieAccess` [#source2modcookieaccess] ```cpp enum Source2ModCookieAccess ``` Who may see and change a cookie from the cookie commands. SourceMod's CookieAccess. | Name | Value | Description | | ----------------------------- | ----- | -------------------------------------------- | | `SOURCE2MOD_COOKIE_PUBLIC` | `0` | Players can read and change it. | | `SOURCE2MOD_COOKIE_PROTECTED` | `1` | Players can read it, only plugins change it. | | `SOURCE2MOD_COOKIE_PRIVATE` | `2` | Hidden from players. | Macros [#macros] `SOURCE2MOD_COOKIE_MAX_NAME_LENGTH` [#source2mod_cookie_max_name_length] ```cpp #define SOURCE2MOD_COOKIE_MAX_NAME_LENGTH 30 ``` Longest cookie name and value, in bytes without the terminator (SourceMod's). # ISource2ModCustomHud (/docs/core-api/ISource2ModCustomHud) `custom_hud_layout` entities render a panorama layout on top of the game HUD. The entity itself is driven through CCSCustomHudLayout; this interface only owns the click callbacks, because the button press arrives as a user message on the core's own hook and has to be routed back to whichever plugin created the layout. Plugins do not normally touch this interface -- CCSCustomHudLayout's HookClick() forwards here for them. Registry of custom HUD click callbacks. Callbacks are keyed by layout entity, so several plugins may listen on their own layouts at once. Every callback a plugin registers is dropped when that plugin unloads, and the whole registry is cleared on level shutdown along with the entities it points at. **Interface id** `ISource2ModCustomHud002` (`SOURCE2MOD_CUSTOMHUD_INTERFACE`) · **Global** `g_pSource2ModCustomHud` · **Header** [`source2mod/ISource2ModCustomHud.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModCustomHud.h) Types [#types] `Source2ModCustomHudHookId` [#source2modcustomhudhookid] ```cpp using Source2ModCustomHudHookId = Source2ModHookId; ``` A custom HUD click hook (HookCustomHudClick). `CustomHudClickHandler` [#customhudclickhandler] ```cpp using CustomHudClickHandler = Source2ModCallback; ``` Callback type for custom HUD button clicks. Methods [#methods] `HookCustomHudClick` [#hookcustomhudclick] ```cpp virtual Source2ModCustomHudHookId HookCustomHudClick(CCSCustomHudLayout* pLayout, CustomHudClickHandler handler) = 0; ``` Registers a click callback for one layout entity. Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it. | Parameter | Type | Description | | --------- | ------------------------------------------------------------------------------------ | ----------------------------------------------- | | `pLayout` | [`CCSCustomHudLayout*`](/docs/schema/entity/classes/CCSCustomHudLayout) | Layout to listen on | | `handler` | [`CustomHudClickHandler`](/docs/core-api/ISource2ModCustomHud#customhudclickhandler) | A function, an object and a method, or a lambda | **Returns** [`Source2ModCustomHudHookId`](/docs/core-api/ISource2ModCustomHud#source2modcustomhudhookid) — The id UnhookCustomHudClick(id) takes `UnhookCustomHudClick` [#unhookcustomhudclick] ```cpp virtual bool UnhookCustomHudClick(CCSCustomHudLayout* pLayout, const CustomHudClickHandler& handler) = 0; virtual bool UnhookCustomHudClick(Source2ModCustomHudHookId id) = 0; virtual void UnhookCustomHudClick(CCSCustomHudLayout* pLayout) = 0; ``` Drops the hook with this handler -- a function or an object and a method; a lambda goes by its id. Drops the hook HookCustomHudClick() returned the id for. Drops every callback registered for a layout. | Parameter | Type | Description | | --------- | -------------------------------------------------------------------------------------------- | --------------------------- | | `pLayout` | [`CCSCustomHudLayout*`](/docs/schema/entity/classes/CCSCustomHudLayout) | Layout to stop listening on | | `handler` | [`const CustomHudClickHandler&`](/docs/core-api/ISource2ModCustomHud#customhudclickhandler) | | | `id` | [`Source2ModCustomHudHookId`](/docs/core-api/ISource2ModCustomHud#source2modcustomhudhookid) | | **Returns** `bool` — true when one was found Macros [#macros] `HOOK_CUSTOM_HUD_CLICK` [#hook_custom_hud_click] ```cpp #define HOOK_CUSTOM_HUD_CLICK(pLayout, handler) g_pSource2ModCustomHud->HookCustomHudClick(pLayout, handler) ``` Hook macros, matching HOOK\_GAME\_EVENT. Other macros [#other-macros] | Macro | Expands to | | ---------------------------------- | ------------------------------------------------------- | | `UNHOOK_CUSTOM_HUD_CLICK(pLayout)` | `g_pSource2ModCustomHud->UnhookCustomHudClick(pLayout)` | # ISource2ModEntities (/docs/core-api/ISource2ModEntities) Provides: * Entity searching (classname, name, picker) * Entity creation * Input triggering * Output listening Entity I/O is the system used by maps to connect entities together. Interface for interacting with entities and Entity I/O system. **Interface id** `ISource2ModEntities002` (`SOURCE2MOD_ENTITIES_INTERFACE`) · **Global** `g_pSource2ModEntities` · **Header** [`source2mod/ISource2ModEntities.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModEntities.h) Methods [#methods] `GetGameRules` [#getgamerules] ```cpp virtual CCSGameRules* GetGameRules() = 0; ``` The active CCSGameRules, or null before the entity exists. Source2Mod captures this from cs\_gamerules as the entity is created, so it is live for the whole map and a plugin does not have to scan for the proxy itself. `FindPickerEntity` [#findpickerentity] ```cpp virtual CBaseEntity* FindPickerEntity(CBasePlayerController* pPlayer, CCSGameRules* pGameRules = nullptr) = 0; ``` Finds entity under player's crosshair. | Parameter | Type | Default | | ------------ | ----------------------------------------------------------------------------- | --------- | | `pPlayer` | [`CBasePlayerController*`](/docs/schema/entity/classes/CBasePlayerController) | | | `pGameRules` | [`CCSGameRules*`](/docs/schema/entity/classes/CCSGameRules) | `nullptr` | `FindEntityByClassname` [#findentitybyclassname] ```cpp virtual CBaseEntity* FindEntityByClassname(CEntityInstance* pStart, const char* name) = 0; ``` Finds entity by classname. | Parameter | Type | | --------- | ------------------ | | `pStart` | `CEntityInstance*` | | `name` | `const char*` | `FindEntityByName` [#findentitybyname] ```cpp virtual CBaseEntity* FindEntityByName(CEntityInstance* pStartEntity, const char* szName, CEntityInstance* pSearchingEntity = nullptr, CEntityInstance* pActivator = nullptr, CEntityInstance* pCaller = nullptr, IEntityFindFilter* pFilter = nullptr) = 0; ``` Finds entity by name. | Parameter | Type | Default | | ------------------ | -------------------- | --------- | | `pStartEntity` | `CEntityInstance*` | | | `szName` | `const char*` | | | `pSearchingEntity` | `CEntityInstance*` | `nullptr` | | `pActivator` | `CEntityInstance*` | `nullptr` | | `pCaller` | `CEntityInstance*` | `nullptr` | | `pFilter` | `IEntityFindFilter*` | `nullptr` | `CreateEntityByName` [#createentitybyname] ```cpp virtual CBaseEntity* CreateEntityByName(const char* pszClassName) = 0; ``` Creates entity by classname. | Parameter | Type | | -------------- | ------------- | | `pszClassName` | `const char*` | `AddEntityListener` [#addentitylistener] ```cpp virtual void AddEntityListener(IEntityListener* pListener) = 0; ``` Adds listener for entity spawn, creation, deletion and parent change. Removed for you if the owning plugin unloads. The listener is an object inside that plugin's library and the engine holds it directly, so one left behind is a call into unmapped memory. | Parameter | Type | Description | | ----------- | ------------------ | ----------------------------------------------------- | | `pListener` | `IEntityListener*` | Listener object; the plugin whose class it is owns it | `RemoveEntityListener` [#removeentitylistener] ```cpp virtual void RemoveEntityListener(IEntityListener* pListener) = 0; ``` Removes listener for entity spawn, creation, deletion and parent change. | Parameter | Type | | ----------- | ------------------ | | `pListener` | `IEntityListener*` | `AcceptInput` [#acceptinput] ```cpp virtual void AcceptInput(CEntityInstance* pTarget, const char* pszInput, CEntityInstance* pActivator, CEntityInstance* pCaller, const char* pszValue = "") = 0; ``` Sends input to entity. Equivalent to Source engine AcceptInput() | Parameter | Type | Default | | ------------ | ------------------ | ------- | | `pTarget` | `CEntityInstance*` | | | `pszInput` | `const char*` | | | `pActivator` | `CEntityInstance*` | | | `pCaller` | `CEntityInstance*` | | | `pszValue` | `const char*` | `""` | `AddEntityIOEvent` [#addentityioevent] ```cpp virtual void AddEntityIOEvent(CEntityInstance* pTarget, const char* pszInput, CEntityInstance* pActivator = nullptr, CEntityInstance* pCaller = nullptr, const char* pszValue = "", float flDelay = 0.0f) = 0; ``` Queues an entity I/O event. | Parameter | Type | Default | | ------------ | ------------------ | --------- | | `pTarget` | `CEntityInstance*` | | | `pszInput` | `const char*` | | | `pActivator` | `CEntityInstance*` | `nullptr` | | `pCaller` | `CEntityInstance*` | `nullptr` | | `pszValue` | `const char*` | `""` | | `flDelay` | `float` | `0.0f` | `AddEntityIOListener` [#addentityiolistener] ```cpp virtual void AddEntityIOListener(IEntityIOListener* pListener, const char* pchClassName, const char* pchOutputName, bool post = false) = 0; ``` Adds listener for entity outputs; the plugin whose class the listener is owns it. Removed for you if the owning plugin unloads, for the same reason as AddEntityListener(). | Parameter | Type | Default | | --------------- | --------------------------------------------------------------- | ------- | | `pListener` | [`IEntityIOListener*`](/docs/schema/Entityio#ientityiolistener) | | | `pchClassName` | `const char*` | | | `pchOutputName` | `const char*` | | | `post` | `bool` | `false` | `RemoveEntityIOListener` [#removeentityiolistener] ```cpp virtual void RemoveEntityIOListener(IEntityIOListener* pListener, const char* pchClassName, const char* pchOutputName, bool post = false) = 0; ``` Removes entity output listener. | Parameter | Type | Default | | --------------- | --------------------------------------------------------------- | ------- | | `pListener` | [`IEntityIOListener*`](/docs/schema/Entityio#ientityiolistener) | | | `pchClassName` | `const char*` | | | `pchOutputName` | `const char*` | | | `post` | `bool` | `false` | Functions [#functions] `UTIL_FindAllEntitiesByDesignerName` [#util_findallentitiesbydesignername] ```cpp template inline std::vector UTIL_FindAllEntitiesByDesignerName(const char* pszDesignerName); ``` Every entity whose designer name matches, cast to T. Walks the entity system's active list directly rather than going through the engine's finder, so it sees exactly the entities that are live right now and matches on m\_designerName -- the name the map gave the entity, which is not always the classname the engine reports. ```cpp for (auto* pProp : UTIL_FindAllEntitiesByDesignerName("prop_dynamic")) pProp->AcceptInput("Kill"); ``` | Parameter | Type | Description | | ----------------- | ------------- | -------------------------------- | | `pszDesignerName` | `const char*` | Designer name to match, exactly. | **Returns** `std::vector` — Matches in list order; empty if the entity system is not up yet. Macros [#macros] | Macro | Expands to | | --------------------------------------------- | --------------------------------------------------------------------------- | | `GET_GAME_RULES()` | `g_pSource2ModEntities->GetGameRules()` | | `FIND_PICKER_ENTITY(player, ...)` | `g_pSource2ModEntities->FindPickerEntity(player, ##__VA_ARGS__)` | | `FIND_ENTITY_BY_CLASSNAME(start, name)` | `g_pSource2ModEntities->FindEntityByClassname(start, name)` | | `FIND_ENTITY_BY_NAME(start, name, ...)` | `g_pSource2ModEntities->FindEntityByName(start, name, ##__VA_ARGS__)` | | `CREATE_ENTITY(classname)` | `g_pSource2ModEntities->CreateEntityByName(classname)` | | `ADD_ENTITY_LISTENER(l)` | `g_pSource2ModEntities->AddEntityListener(l)` | | `REMOVE_ENTITY_LISTENER(l)` | `g_pSource2ModEntities->RemoveEntityListener(l)` | | `ACCEPT_INPUT(target, input, ...)` | `g_pSource2ModEntities->AcceptInput(target, input, ##__VA_ARGS__)` | | `ADD_ENTITY_IO_EVENT(target, input, ...)` | `g_pSource2ModEntities->AddEntityIOEvent(target, input, ##__VA_ARGS__)` | | `ADD_ENTITY_IO_LISTENER(l, cls, out, ...)` | `g_pSource2ModEntities->AddEntityIOListener(l, cls, out, ##__VA_ARGS__)` | | `REMOVE_ENTITY_IO_LISTENER(l, cls, out, ...)` | `g_pSource2ModEntities->RemoveEntityIOListener(l, cls, out, ##__VA_ARGS__)` | # ISource2ModEvents (/docs/core-api/ISource2ModEvents) Provides functionality for hooking into Source2 game events, allowing plugins to inspect, modify, or block events. Events correspond to engine-defined events (e.g. "player_death"). Interface for registering game event listeners. Allows plugins to: * Listen to engine events * Modify event data * Block event propagation **Interface id** `ISource2ModEvents002` (`SOURCE2MOD_EVENTS_INTERFACE`) · **Global** `g_pSource2ModEvents` · **Header** [`source2mod/ISource2ModEvents.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModEvents.h) Types [#types] `Source2ModEventHookId` [#source2modeventhookid] ```cpp using Source2ModEventHookId = Source2ModHookId; ``` A game event hook (HookGameEvent). `GameEventHandler` [#gameeventhandler] ```cpp using GameEventHandler = Source2ModCallback; ``` Callback type for game events. Methods [#methods] `HookGameEvent` [#hookgameevent] ```cpp virtual Source2ModEventHookId HookGameEvent(const char* pchName, GameEventHandler handler, bool post) = 0; ``` Registers a listener for a game event. The same event may be hooked as often as a plugin likes; each handler runs. Whose it is, the core reads off the handler (see Source2ModCallback); what a plugin still holds at unload the core drops for it. | Parameter | Type | Description | | --------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `pchName` | `const char*` | Event name (e.g. "player\_death") | | `handler` | [`GameEventHandler`](/docs/core-api/ISource2ModEvents#gameeventhandler) | A function, an object and a method, or a lambda | | `post` | `bool` | false to be called before the engine processes the event, true to be called after | **Returns** [`Source2ModEventHookId`](/docs/core-api/ISource2ModEvents#source2modeventhookid) — The id UnhookGameEvent(id) takes; 0 for an event the game does not have `UnhookGameEvent` [#unhookgameevent] ```cpp virtual bool UnhookGameEvent(const char* pchName, const GameEventHandler& handler, bool post) = 0; virtual bool UnhookGameEvent(Source2ModEventHookId id) = 0; ``` Drops the hook with this handler -- a function or an object and a method; a lambda goes by its id. Drops the hook HookGameEvent() returned the id for. | Parameter | Type | | --------- | --------------------------------------------------------------------------------- | | `pchName` | `const char*` | | `handler` | [`const GameEventHandler&`](/docs/core-api/ISource2ModEvents#gameeventhandler) | | `post` | `bool` | | `id` | [`Source2ModEventHookId`](/docs/core-api/ISource2ModEvents#source2modeventhookid) | **Returns** `bool` — true when one was found Macros [#macros] `HOOK_GAME_EVENT` [#hook_game_event] ```cpp #define HOOK_GAME_EVENT(passname, passfunc, post) g_pSource2ModEvents->HookGameEvent(passname, passfunc, post) ``` Macro for hooking a game event. `UNHOOK_GAME_EVENT` [#unhook_game_event] ```cpp #define UNHOOK_GAME_EVENT(passname, passfunc, post) g_pSource2ModEvents->UnhookGameEvent(passname, passfunc, post) ``` Macro for dropping a game event hook (a function or an object and a method; a lambda goes by the id HOOK\_GAME\_EVENT returned). # ISource2ModFeatures (/docs/core-api/ISource2ModFeatures) SourceMod's GetFeatureStatus, for both kinds of plugin: a native C++ plugin asks before it calls into another plugin's interface or relies on a part of the core that may be switched off (GeoIP without a database), a SourcePawn plugin asks the same through GetFeatureStatus() / Source2ModFeatures. What can be asked about: * a native -- a SourcePawn native, the core's own or one a plugin registered (CreateNative, ISource2ModNatives). A plugin native is Unavailable while its plugin is unloaded. * a capability -- a named part of the core that can be missing at runtime; the names are the SOURCE2MOD\_CAPABILITY\_\* below. * an interface -- an interface Source2ModFactory() would hand out: the core's (at the exact revision asked for) or one a plugin serves through OnSource2ModQuery. * a library -- a name a plugin registered (RegPluginLibrary, ISource2ModNatives::RegisterLibrary). **Interface id** `ISource2ModFeatures001` (`SOURCE2MOD_FEATURES_INTERFACE`) · **Global** `g_pSource2ModFeatures` · **Header** [`source2mod/ISource2ModFeatures.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModFeatures.h) Methods [#methods] `GetFeatureStatus` [#getfeaturestatus] ```cpp virtual Source2ModFeatureStatus GetFeatureStatus(Source2ModFeatureType type, const char* name) = 0; ``` The status of a native, capability, interface or library. | Parameter | Type | Description | | --------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | `type` | [`Source2ModFeatureType`](/docs/core-api/ISource2ModFeatures#source2modfeaturetype) | What `name` names. | | `name` | `const char*` | The native, capability, interface ("ISource2ModMenus005") or library name. | `IsNativeAvailable` [#isnativeavailable] ```cpp virtual bool IsNativeAvailable(const char* name) = 0; ``` GetFeatureStatus(SOURCE2MOD\_FEATURE\_NATIVE, name) == AVAILABLE. | Parameter | Type | | --------- | ------------- | | `name` | `const char*` | `IsInterfaceAvailable` [#isinterfaceavailable] ```cpp virtual bool IsInterfaceAvailable(const char* iface) = 0; ``` Whether Source2ModFactory() would hand this interface out right now. Unlike asking the factory, a miss is not logged. | Parameter | Type | | --------- | ------------- | | `iface` | `const char*` | `IsLibraryAvailable` [#islibraryavailable] ```cpp virtual bool IsLibraryAvailable(const char* name) = 0; ``` Whether some plugin registered this library and is still loaded. | Parameter | Type | | --------- | ------------- | | `name` | `const char*` | `HasCapability` [#hascapability] ```cpp virtual bool HasCapability(const char* name) = 0; ``` Whether a SOURCE2MOD\_CAPABILITY\_\* is usable. | Parameter | Type | | --------- | ------------- | | `name` | `const char*` | Enums [#enums] `Source2ModFeatureType` [#source2modfeaturetype] ```cpp enum Source2ModFeatureType ``` The same numbers as SourceMod's FeatureType, plus the two Source2Mod adds. | Name | Value | | ------------------------------- | ----- | | `SOURCE2MOD_FEATURE_NATIVE` | `0` | | `SOURCE2MOD_FEATURE_CAPABILITY` | `1` | | `SOURCE2MOD_FEATURE_INTERFACE` | `2` | | `SOURCE2MOD_FEATURE_LIBRARY` | `3` | `Source2ModFeatureStatus` [#source2modfeaturestatus] ```cpp enum Source2ModFeatureStatus ``` The same numbers as SourceMod's FeatureStatus. | Name | Value | Description | | -------------------------------- | ----- | ------------------------------------------------------------------------ | | `SOURCE2MOD_FEATURE_AVAILABLE` | `0` | There, and usable. | | `SOURCE2MOD_FEATURE_UNAVAILABLE` | `1` | Known, but not usable right now (its plugin unloaded, no database, ...). | | `SOURCE2MOD_FEATURE_UNKNOWN` | `2` | Not known at all. | Macros [#macros] `SOURCE2MOD_CAPABILITY_SOURCEPAWN` [#source2mod_capability_sourcepawn] ```cpp #define SOURCE2MOD_CAPABILITY_SOURCEPAWN "sourcepawn" ``` The SourcePawn VM: always there in this core. `SOURCE2MOD_CAPABILITY_GEOIP` [#source2mod_capability_geoip] ```cpp #define SOURCE2MOD_CAPABILITY_GEOIP "geoip" ``` GeoIP lookups: a GeoLite2 database was found and opened. `SOURCE2MOD_CAPABILITY_CLIENTPREFS` [#source2mod_capability_clientprefs] ```cpp #define SOURCE2MOD_CAPABILITY_CLIENTPREFS "clientprefs" ``` Client preferences: the cookie database is connected. `SOURCE2MOD_CAPABILITY_GAMESYSTEMS` [#source2mod_capability_gamesystems] ```cpp #define SOURCE2MOD_CAPABILITY_GAMESYSTEMS "gamesystems" ``` Registering game systems (ISource2ModGameSystems::IsAvailable()). # ISource2ModFiles (/docs/core-api/ISource2ModFiles) Paths that are not absolute are under the game folder (csgo), as in SourceMod; ResolvePath says where one lands. A plugin that only needs to read and write its own files can as well use the C++ library -- these exist so plugins and SourcePawn scripts share one notion of where things are and one KeyValues. KeyValues3 (the format CS2's own files use) is not here: the engine's KV3 (tier1/keyvalues3.h in the SDK) reads and writes it. **Interface id** `ISource2ModFiles001` (`SOURCE2MOD_FILES_INTERFACE`) · **Global** `g_pSource2ModFiles` · **Header** [`source2mod/ISource2ModFiles.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModFiles.h) Types [#types] `Source2ModDirCallback` [#source2moddircallback] ```cpp using Source2ModDirCallback = std::function; ``` Called per directory entry; return false to stop. Methods [#methods] `ResolvePath` [#resolvepath] ```cpp virtual std::string ResolvePath(const char* path) = 0; ``` A path as the other calls use it: absolute as given, else under the game folder. | Parameter | Type | | --------- | ------------- | | `path` | `const char*` | `FileExists` [#fileexists] ```cpp virtual bool FileExists(const char* path) = 0; ``` | Parameter | Type | | --------- | ------------- | | `path` | `const char*` | `DirExists` [#direxists] ```cpp virtual bool DirExists(const char* path) = 0; ``` | Parameter | Type | | --------- | ------------- | | `path` | `const char*` | `FileSize` [#filesize] ```cpp virtual int64_t FileSize(const char* path) = 0; ``` -1 when there is no such file. | Parameter | Type | | --------- | ------------- | | `path` | `const char*` | `FileTime` [#filetime] ```cpp virtual int64_t FileTime(const char* path) = 0; ``` Seconds since 1970 of the last change; -1 when there is no such file. | Parameter | Type | | --------- | ------------- | | `path` | `const char*` | `ReadFile` [#readfile] ```cpp virtual bool ReadFile(const char* path, std::string& contents) = 0; ``` The whole file. @return false when it cannot be read. | Parameter | Type | | ---------- | -------------- | | `path` | `const char*` | | `contents` | `std::string&` | `WriteFile` [#writefile] ```cpp virtual bool WriteFile(const char* path, const void* data, size_t size, bool append = false) = 0; ``` Creates or replaces (or appends to) a file; missing folders are made. | Parameter | Type | Default | | --------- | ------------- | ------- | | `path` | `const char*` | | | `data` | `const void*` | | | `size` | `size_t` | | | `append` | `bool` | `false` | `DeleteFile` [#deletefile] ```cpp virtual bool DeleteFile(const char* path) = 0; ``` | Parameter | Type | | --------- | ------------- | | `path` | `const char*` | `RenameFile` [#renamefile] ```cpp virtual bool RenameFile(const char* from, const char* to) = 0; ``` | Parameter | Type | | --------- | ------------- | | `from` | `const char*` | | `to` | `const char*` | `CreateDirectory` [#createdirectory] ```cpp virtual bool CreateDirectory(const char* path) = 0; ``` Makes a folder and those above it. | Parameter | Type | | --------- | ------------- | | `path` | `const char*` | `RemoveDirectory` [#removedirectory] ```cpp virtual bool RemoveDirectory(const char* path) = 0; ``` Removes an empty folder. | Parameter | Type | | --------- | ------------- | | `path` | `const char*` | `ListDirectory` [#listdirectory] ```cpp virtual bool ListDirectory(const char* path, Source2ModDirCallback callback) = 0; ``` The entries of a folder, without "." and "..". @return false when it cannot be read. | Parameter | Type | | ---------- | -------------------------------------------------------------------------------- | | `path` | `const char*` | | `callback` | [`Source2ModDirCallback`](/docs/core-api/ISource2ModFiles#source2moddircallback) | `CreateKeyValues` [#createkeyvalues] ```cpp virtual ISource2ModKeyValues* CreateKeyValues(const char* name) = 0; ``` A new KeyValues tree with an empty root named @p name; Release() it. | Parameter | Type | | --------- | ------------- | | `name` | `const char*` | `ParseKeyValues` [#parsekeyvalues] ```cpp virtual ESource2ModKvError ParseKeyValues(const char* text, ISource2ModKvListener* listener, int* line = nullptr, int* col = nullptr, bool escapes = true) = 0; ``` Reads KeyValues text, calling @p listener per event. | Parameter | Type | Default | | ---------- | --------------------------------------------------------------------------------- | --------- | | `text` | `const char*` | | | `listener` | [`ISource2ModKvListener*`](/docs/core-api/ISource2ModFiles#isource2modkvlistener) | | | `line` | `int*` | `nullptr` | | `col` | `int*` | `nullptr` | | `escapes` | `bool` | `true` | `ParseKeyValuesFile` [#parsekeyvaluesfile] ```cpp virtual ESource2ModKvError ParseKeyValuesFile(const char* path, ISource2ModKvListener* listener, int* line = nullptr, int* col = nullptr, bool escapes = true) = 0; ``` | Parameter | Type | Default | | ---------- | --------------------------------------------------------------------------------- | --------- | | `path` | `const char*` | | | `listener` | [`ISource2ModKvListener*`](/docs/core-api/ISource2ModFiles#isource2modkvlistener) | | | `line` | `int*` | `nullptr` | | `col` | `int*` | `nullptr` | | `escapes` | `bool` | `true` | `KvErrorString` [#kverrorstring] ```cpp virtual const char* KvErrorString(ESource2ModKvError error) = 0; ``` What an error means. | Parameter | Type | | --------- | -------------------------------------------------------------------------- | | `error` | [`ESource2ModKvError`](/docs/core-api/ISource2ModFiles#esource2modkverror) | `ISource2ModKeyValues` [#isource2modkeyvalues] One key of a KeyValues tree: a section with subkeys, or a value. Key arguments may be paths ("a/b/c"); names compare case-insensitively. A null or empty key means this key itself. Values convert on read (a string "5" reads as 5, an int as "5"). Pointers to keys stay valid until that key or one above it is removed, or the root is released. `GetName` [#getname] ```cpp virtual const char* GetName() = 0; ``` `SetName` [#setname] ```cpp virtual void SetName(const char* name) = 0; ``` | Parameter | Type | | --------- | ------------- | | `name` | `const char*` | `FindKey` [#findkey] ```cpp virtual ISource2ModKeyValues* FindKey(const char* key, bool create = false) = 0; ``` A key under this one; nullptr when it is not there and @p create is false. | Parameter | Type | Default | | --------- | ------------- | ------- | | `key` | `const char*` | | | `create` | `bool` | `false` | `GetFirstSubKey` [#getfirstsubkey] ```cpp virtual ISource2ModKeyValues* GetFirstSubKey() = 0; ``` The first / next key of any kind. `GetNextKey` [#getnextkey] ```cpp virtual ISource2ModKeyValues* GetNextKey() = 0; ``` `GetFirstTrueSubKey` [#getfirsttruesubkey] ```cpp virtual ISource2ModKeyValues* GetFirstTrueSubKey() = 0; ``` The first / next key that is a section. `GetNextTrueSubKey` [#getnexttruesubkey] ```cpp virtual ISource2ModKeyValues* GetNextTrueSubKey() = 0; ``` `GetFirstValue` [#getfirstvalue] ```cpp virtual ISource2ModKeyValues* GetFirstValue() = 0; ``` The first / next key that is a value. `GetNextValue` [#getnextvalue] ```cpp virtual ISource2ModKeyValues* GetNextValue() = 0; ``` `GetDataType` [#getdatatype] ```cpp virtual ESource2ModKvType GetDataType(const char* key = nullptr) = 0; ``` | Parameter | Type | Default | | --------- | ------------- | --------- | | `key` | `const char*` | `nullptr` | `GetString` [#getstring] ```cpp virtual const char* GetString(const char* key = nullptr, const char* def = "") = 0; ``` The value as text; valid until the key changes. @p def when there is none. | Parameter | Type | Default | | --------- | ------------- | --------- | | `key` | `const char*` | `nullptr` | | `def` | `const char*` | `""` | `GetInt` [#getint] ```cpp virtual int GetInt(const char* key = nullptr, int def = 0) = 0; ``` | Parameter | Type | Default | | --------- | ------------- | --------- | | `key` | `const char*` | `nullptr` | | `def` | `int` | `0` | `GetFloat` [#getfloat] ```cpp virtual float GetFloat(const char* key = nullptr, float def = 0.0f) = 0; ``` | Parameter | Type | Default | | --------- | ------------- | --------- | | `key` | `const char*` | `nullptr` | | `def` | `float` | `0.0f` | `GetUint64` [#getuint64] ```cpp virtual uint64_t GetUint64(const char* key = nullptr, uint64_t def = 0) = 0; ``` | Parameter | Type | Default | | --------- | ------------- | --------- | | `key` | `const char*` | `nullptr` | | `def` | `uint64_t` | `0` | `GetColor` [#getcolor] ```cpp virtual uint32_t GetColor(const char* key = nullptr, uint32_t def = 0) = 0; ``` r | g \<\< 8 | b \<\< 16 | a \<\< 24 (a "r g b a" string reads too). | Parameter | Type | Default | | --------- | ------------- | --------- | | `key` | `const char*` | `nullptr` | | `def` | `uint32_t` | `0` | `SetString` [#setstring] ```cpp virtual void SetString(const char* key, const char* value) = 0; ``` | Parameter | Type | | --------- | ------------- | | `key` | `const char*` | | `value` | `const char*` | `SetInt` [#setint] ```cpp virtual void SetInt(const char* key, int value) = 0; ``` | Parameter | Type | | --------- | ------------- | | `key` | `const char*` | | `value` | `int` | `SetFloat` [#setfloat] ```cpp virtual void SetFloat(const char* key, float value) = 0; ``` | Parameter | Type | | --------- | ------------- | | `key` | `const char*` | | `value` | `float` | `SetUint64` [#setuint64] ```cpp virtual void SetUint64(const char* key, uint64_t value) = 0; ``` | Parameter | Type | | --------- | ------------- | | `key` | `const char*` | | `value` | `uint64_t` | `SetColor` [#setcolor] ```cpp virtual void SetColor(const char* key, uint32_t rgba) = 0; ``` | Parameter | Type | | --------- | ------------- | | `key` | `const char*` | | `rgba` | `uint32_t` | `RemoveKey` [#removekey] ```cpp virtual bool RemoveKey(const char* key) = 0; ``` Removes a key under this one. @return false when it was not there. | Parameter | Type | | --------- | ------------- | | `key` | `const char*` | `CopySubkeys` [#copysubkeys] ```cpp virtual void CopySubkeys(ISource2ModKeyValues* from) = 0; ``` Copies @p from's subkeys into this one; Merge replaces keys of the same name. | Parameter | Type | | --------- | ------------------------------------------------------------------------------- | | `from` | [`ISource2ModKeyValues*`](/docs/core-api/ISource2ModFiles#isource2modkeyvalues) | `Merge` [#merge] ```cpp virtual void Merge(ISource2ModKeyValues* from) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------- | | `from` | [`ISource2ModKeyValues*`](/docs/core-api/ISource2ModFiles#isource2modkeyvalues) | `Clone` [#clone] ```cpp virtual ISource2ModKeyValues* Clone() = 0; ``` A detached copy of this key and everything under it; Release() it. `LoadFromFile` [#loadfromfile] ```cpp virtual bool LoadFromFile(const char* path, bool escapes = false) = 0; ``` Replaces this key with the first section of a file / text (its name and subkeys). @return false on a missing file or bad text. | Parameter | Type | Default | | --------- | ------------- | ------- | | `path` | `const char*` | | | `escapes` | `bool` | `false` | `LoadFromString` [#loadfromstring] ```cpp virtual bool LoadFromString(const char* text, bool escapes = false) = 0; ``` | Parameter | Type | Default | | --------- | ------------- | ------- | | `text` | `const char*` | | | `escapes` | `bool` | `false` | `SaveToFile` [#savetofile] ```cpp virtual bool SaveToFile(const char* path, bool escapes = false) = 0; ``` Writes this key and everything under it in Valve's layout. | Parameter | Type | Default | | --------- | ------------- | ------- | | `path` | `const char*` | | | `escapes` | `bool` | `false` | `SaveToString` [#savetostring] ```cpp virtual size_t SaveToString(char* buffer, size_t maxlen, bool escapes = false) = 0; ``` | Parameter | Type | Default | | --------- | -------- | ------- | | `buffer` | `char*` | | | `maxlen` | `size_t` | | | `escapes` | `bool` | `false` | **Returns** `size_t` — The text's length; at most @p maxlen - 1 bytes are written. `Release` [#release] ```cpp virtual void Release() = 0; ``` Frees a tree from CreateKeyValues or Clone (only a root). `ISource2ModKvListener` [#isource2modkvlistener] Gets the reader's events in order; return HALT to stop early. `OnStart` [#onstart] ```cpp virtual void OnStart(); ``` `OnEnterSection` [#onentersection] ```cpp virtual ESource2ModKvParseResult OnEnterSection(const char* name, bool quoted); ``` | Parameter | Type | | --------- | ------------- | | `name` | `const char*` | | `quoted` | `bool` | `OnKeyValue` [#onkeyvalue] ```cpp virtual ESource2ModKvParseResult OnKeyValue(const char* key, const char* value, bool keyQuoted, bool valueQuoted); ``` | Parameter | Type | | ------------- | ------------- | | `key` | `const char*` | | `value` | `const char*` | | `keyQuoted` | `bool` | | `valueQuoted` | `bool` | `OnLeaveSection` [#onleavesection] ```cpp virtual ESource2ModKvParseResult OnLeaveSection(); ``` `OnRawLine` [#onrawline] ```cpp virtual ESource2ModKvParseResult OnRawLine(const char* line, int lineNumber); ``` | Parameter | Type | | ------------ | ------------- | | `line` | `const char*` | | `lineNumber` | `int` | `OnEnd` [#onend] ```cpp virtual void OnEnd(bool halted, bool failed); ``` | Parameter | Type | | --------- | ------ | | `halted` | `bool` | | `failed` | `bool` | Enums [#enums] `ESource2ModKvType` [#esource2modkvtype] ```cpp enum ESource2ModKvType ``` What a key holds (SourceMod's KvDataTypes numbering). | Name | Value | Description | | ---------------------- | ----- | ---------------------------- | | `SOURCE2MOD_KV_NONE` | `0` | a section: subkeys, no value | | `SOURCE2MOD_KV_STRING` | `1` | | | `SOURCE2MOD_KV_INT` | `2` | | | `SOURCE2MOD_KV_FLOAT` | `3` | | | `SOURCE2MOD_KV_COLOR` | `6` | | | `SOURCE2MOD_KV_UINT64` | `7` | | `ESource2ModKvParseResult` [#esource2modkvparseresult] ```cpp enum ESource2ModKvParseResult ``` | Name | Value | Description | | ------------------------------ | ----- | ------------------------------------- | | `SOURCE2MOD_KVPARSE_CONTINUE` | `0` | | | `SOURCE2MOD_KVPARSE_HALT` | | stop, not an error | | `SOURCE2MOD_KVPARSE_HALT_FAIL` | | stop with SOURCE2MOD\_KVERROR\_CUSTOM | `ESource2ModKvError` [#esource2modkverror] ```cpp enum ESource2ModKvError ``` SourceMod's SMCError numbering. | Name | Value | Description | | -------------------------------------- | ----- | ------------------------------------ | | `SOURCE2MOD_KVERROR_OK` | `0` | | | `SOURCE2MOD_KVERROR_STREAM_OPEN` | | | | `SOURCE2MOD_KVERROR_STREAM_ERROR` | | | | `SOURCE2MOD_KVERROR_CUSTOM` | | | | `SOURCE2MOD_KVERROR_INVALID_SECTION1` | | | | `SOURCE2MOD_KVERROR_INVALID_SECTION2` | | \{ without a name | | `SOURCE2MOD_KVERROR_INVALID_SECTION3` | | a lone token before } | | `SOURCE2MOD_KVERROR_INVALID_SECTION4` | | } without a section | | `SOURCE2MOD_KVERROR_INVALID_SECTION5` | | a section not closed | | `SOURCE2MOD_KVERROR_INVALID_TOKENS` | | an unterminated string, a lone token | | `SOURCE2MOD_KVERROR_TOKEN_OVERFLOW` | | | | `SOURCE2MOD_KVERROR_INVALID_PROPERTY1` | | a key/value outside any section | # ISource2ModGameConfig (/docs/core-api/ISource2ModGameConfig) Provides access to external configuration data used for: * Signature scanning * Symbol resolution * Memory patching * Offset lookup Typically backed by external config files (JSON, KV, etc.). Interface for accessing game configuration entries. **Interface id** `ISource2ModGameConfig002` (`SOURCE2MOD_GAMECONFIG_INTERFACE`) · **Global** `g_pSource2ModGameConfig` · **Header** [`source2mod/ISource2ModGameConfig.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModGameConfig.h) Methods [#methods] `GetLibrary` [#getlibrary] ```cpp virtual const char* GetLibrary(const char* pchName) = 0; ``` Gets library/module name. | Parameter | Type | Description | | --------- | ------------- | ----------------- | | `pchName` | `const char*` | Config entry name | **Returns** `const char*` — Library name `GetSignature` [#getsignature] ```cpp virtual const char* GetSignature(const char* pchName) = 0; ``` Gets signature (pattern). | Parameter | Type | Description | | --------- | ------------- | ----------------- | | `pchName` | `const char*` | Config entry name | **Returns** `const char*` — Pattern string `GetSymbol` [#getsymbol] ```cpp virtual const char* GetSymbol(const char* pchName) = 0; ``` Gets symbol name. | Parameter | Type | Description | | --------- | ------------- | ----------------- | | `pchName` | `const char*` | Config entry name | **Returns** `const char*` — Symbol string `GetPatch` [#getpatch] ```cpp virtual const char* GetPatch(const char* pchName) = 0; ``` Gets patch definition. | Parameter | Type | Description | | --------- | ------------- | ----------------- | | `pchName` | `const char*` | Config entry name | **Returns** `const char*` — Patch data `GetOffset` [#getoffset] ```cpp virtual int GetOffset(const char* pchName) = 0; ``` Gets integer offset. | Parameter | Type | Description | | --------- | ------------- | ----------------- | | `pchName` | `const char*` | Config entry name | **Returns** `int` — Offset value `IsSymbol` [#issymbol] ```cpp virtual bool IsSymbol(const char* pchName) = 0; ``` Whether the entry is a symbol name rather than a byte pattern. Symbol entries are written into the signature field with a leading "@". | Parameter | Type | | --------- | ------------- | | `pchName` | `const char*` | `ResolveSignature` [#resolvesignature] ```cpp virtual ISource2ModMemory ResolveSignature(const char* pchName) = 0; ``` Resolves a gamedata entry to an address. Looks the entry's "library" up among the loaded modules -- engine2, server, vscript, tier0 -- then finds it by exported symbol when the entry is "@"-prefixed, or by pattern scan otherwise. Saves a plugin repeating the module lookup and the symbol/pattern branch for every signature. | Parameter | Type | Description | | --------- | ------------- | -------------------- | | `pchName` | `const char*` | Gamedata entry name. | **Returns** [`ISource2ModMemory`](/docs/core-api/ISource2ModMemory) — The address, or a null ISource2ModMemory when the module, symbol or pattern is missing. Converts to void\* on its own; use RCast\() for a typed function pointer. Macros [#macros] | Macro | Expands to | | ---------------------------- | ------------------------------------------------- | | `GAMECONFIG_LIBRARY(name)` | `g_pSource2ModGameConfig->GetLibrary(name)` | | `GAMECONFIG_SIGNATURE(name)` | `g_pSource2ModGameConfig->GetSignature(name)` | | `GAMECONFIG_SYMBOL(name)` | `g_pSource2ModGameConfig->GetSymbol(name)` | | `GAMECONFIG_PATCH(name)` | `g_pSource2ModGameConfig->GetPatch(name)` | | `GAMECONFIG_OFFSET(name)` | `g_pSource2ModGameConfig->GetOffset(name)` | | `GAMECONFIG_RESOLVE(name)` | `g_pSource2ModGameConfig->ResolveSignature(name)` | # ISource2ModGameHooks (/docs/core-api/ISource2ModGameHooks) One detour per function, placed by the core from its gamedata the first time a plugin asks for it and taken out again once the last listener is gone -- a function nobody listens to is not hooked at all. A plugin registers a handler and never touches KHook, a signature or a prototype. When Valve changes one -- an argument added, the order swapped between platforms, as GroundAccelerate's is -- the core's gamedata and its one call site change, and the plugins keep running. Anything not in this set is still a KHOOK\_MEMBER / KHOOK\_VIRTUAL of the plugin's own. Every hook hands its handler a context: the hooked object, the arguments, and `result` where the function returns something. What the handler returns is the usual Action: * Ignore -- nothing changed. * Override -- pre: the original still runs but the function returns `ctx.result`; post: the function returns `ctx.result` instead of what the original produced. * Supersede -- pre only: the original does not run, the function returns `ctx.result`. Stops the remaining pre handlers. Arguments passed by pointer (the damage info, the move data, the weapon) can be changed in place and the original sees the change; arguments passed by value (a speed, a flag) cannot -- a handler that needs that keeps a raw KHOOK\_MEMBER of its own. Handlers run in registration order, pre before the original and post after it; in post, `result` holds what the original returned. HookX() returns a Source2ModGameHookId for UnhookX(); a plugin may have several handlers on one function. The handlers are the plugin's until it unhooks them or unloads, whichever comes first; nothing to undo in Unload(). Whose a handler is, the core reads off the handler (see Source2ModCallback): no call takes a plugin id. What a plugin still holds at unload the core drops for it. **Global** `g_pSource2ModGameHooks` · **Header** [`source2mod/ISource2ModGameHooks.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModGameHooks.h) Types [#types] `GameHookHandler` [#gamehookhandler] ```cpp template using GameHookHandler = Source2ModCallback(CONTEXT& ctx, bool post)>; ``` A handler: the context, and whether this is the post pass. A function, an object and a method, or a lambda (Source2ModCallback). `Source2ModGameHookId` [#source2modgamehookid] ```cpp using Source2ModGameHookId = Source2ModHookId; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. Methods [#methods] `HookTakeDamage` [#hooktakedamage] ```cpp virtual Source2ModGameHookId HookTakeDamage(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCanAcquire` [#hookcanacquire] ```cpp virtual Source2ModGameHookId HookCanAcquire(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCanMove` [#hookcanmove] ```cpp virtual Source2ModGameHookId HookCanMove(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCanUse` [#hookcanuse] ```cpp virtual Source2ModGameHookId HookCanUse(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | --------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookPostThink` [#hookpostthink] ```cpp virtual Source2ModGameHookId HookPostThink(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------ | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookProcessUsercmds` [#hookprocessusercmds] ```cpp virtual Source2ModGameHookId HookProcessUsercmds(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookSimulateUserCommands` [#hooksimulateusercommands] ```cpp virtual Source2ModGameHookId HookSimulateUserCommands(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookRunCommand` [#hookruncommand] ```cpp virtual Source2ModGameHookId HookRunCommand(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookAcceptInput` [#hookacceptinput] ```cpp virtual Source2ModGameHookId HookAcceptInput(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookTouch` [#hooktouch] ```cpp virtual Source2ModGameHookId HookTouch(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | -------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookDropWeapon` [#hookdropweapon] ```cpp virtual Source2ModGameHookId HookDropWeapon(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookAirAccelerate` [#hookairaccelerate] ```cpp virtual Source2ModGameHookId HookAirAccelerate(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookAirMove` [#hookairmove] ```cpp virtual Source2ModGameHookId HookAirMove(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCanUnduck` [#hookcanunduck] ```cpp virtual Source2ModGameHookId HookCanUnduck(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCategorizePosition` [#hookcategorizeposition] ```cpp virtual Source2ModGameHookId HookCategorizePosition(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCheckFalling` [#hookcheckfalling] ```cpp virtual Source2ModGameHookId HookCheckFalling(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCheckParameters` [#hookcheckparameters] ```cpp virtual Source2ModGameHookId HookCheckParameters(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCheckVelocity` [#hookcheckvelocity] ```cpp virtual Source2ModGameHookId HookCheckVelocity(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCheckWater` [#hookcheckwater] ```cpp virtual Source2ModGameHookId HookCheckWater(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookDuck` [#hookduck] ```cpp virtual Source2ModGameHookId HookDuck(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookFriction` [#hookfriction] ```cpp virtual Source2ModGameHookId HookFriction(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookFullWalkMove` [#hookfullwalkmove] ```cpp virtual Source2ModGameHookId HookFullWalkMove(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookGroundAccelerate` [#hookgroundaccelerate] ```cpp virtual Source2ModGameHookId HookGroundAccelerate(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookLadderMove` [#hookladdermove] ```cpp virtual Source2ModGameHookId HookLadderMove(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookMoveInit` [#hookmoveinit] ```cpp virtual Source2ModGameHookId HookMoveInit(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookPlayerMove` [#hookplayermove] ```cpp virtual Source2ModGameHookId HookPlayerMove(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookProcessMovement` [#hookprocessmovement] ```cpp virtual Source2ModGameHookId HookProcessMovement(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookSetupMove` [#hooksetupmove] ```cpp virtual Source2ModGameHookId HookSetupMove(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------ | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookTryPlayerMove` [#hooktryplayermove] ```cpp virtual Source2ModGameHookId HookTryPlayerMove(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookWalkMove` [#hookwalkmove] ```cpp virtual Source2ModGameHookId HookWalkMove(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookWaterMove` [#hookwatermove] ```cpp virtual Source2ModGameHookId HookWaterMove(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookOnJumpLegacy` [#hookonjumplegacy] ```cpp virtual Source2ModGameHookId HookOnJumpLegacy(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookOnJumpModern` [#hookonjumpmodern] ```cpp virtual Source2ModGameHookId HookOnJumpModern(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCheckJumpButtonLegacy` [#hookcheckjumpbuttonlegacy] ```cpp virtual Source2ModGameHookId HookCheckJumpButtonLegacy(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `HookCheckJumpButtonModern` [#hookcheckjumpbuttonmodern] ```cpp virtual Source2ModGameHookId HookCheckJumpButtonModern(GameHookHandler handler, bool post) = 0; ``` What HookX() returns and UnhookX() takes: one registered handler. A plugin may register several handlers on the same function and drop any one of them; whatever it still holds at unload the core drops for it. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------- | | `handler` | [`GameHookHandler`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookTakeDamage` [#unhooktakedamage] ```cpp virtual bool UnhookTakeDamage(Source2ModGameHookId id) = 0; virtual bool UnhookTakeDamage(const GameHookHandler& handler, bool post) = 0; ``` Drops a handler: the one HookX() returned the id for, or the one that is this function or object and method (a lambda goes by its id). True when it was found. | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCanAcquire` [#unhookcanacquire] ```cpp virtual bool UnhookCanAcquire(Source2ModGameHookId id) = 0; virtual bool UnhookCanAcquire(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCanMove` [#unhookcanmove] ```cpp virtual bool UnhookCanMove(Source2ModGameHookId id) = 0; virtual bool UnhookCanMove(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCanUse` [#unhookcanuse] ```cpp virtual bool UnhookCanUse(Source2ModGameHookId id) = 0; virtual bool UnhookCanUse(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookPostThink` [#unhookpostthink] ```cpp virtual bool UnhookPostThink(Source2ModGameHookId id) = 0; virtual bool UnhookPostThink(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookProcessUsercmds` [#unhookprocessusercmds] ```cpp virtual bool UnhookProcessUsercmds(Source2ModGameHookId id) = 0; virtual bool UnhookProcessUsercmds(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookSimulateUserCommands` [#unhooksimulateusercommands] ```cpp virtual bool UnhookSimulateUserCommands(Source2ModGameHookId id) = 0; virtual bool UnhookSimulateUserCommands(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookRunCommand` [#unhookruncommand] ```cpp virtual bool UnhookRunCommand(Source2ModGameHookId id) = 0; virtual bool UnhookRunCommand(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookAcceptInput` [#unhookacceptinput] ```cpp virtual bool UnhookAcceptInput(Source2ModGameHookId id) = 0; virtual bool UnhookAcceptInput(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookTouch` [#unhooktouch] ```cpp virtual bool UnhookTouch(Source2ModGameHookId id) = 0; virtual bool UnhookTouch(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | --------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookDropWeapon` [#unhookdropweapon] ```cpp virtual bool UnhookDropWeapon(Source2ModGameHookId id) = 0; virtual bool UnhookDropWeapon(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookAirAccelerate` [#unhookairaccelerate] ```cpp virtual bool UnhookAirAccelerate(Source2ModGameHookId id) = 0; virtual bool UnhookAirAccelerate(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookAirMove` [#unhookairmove] ```cpp virtual bool UnhookAirMove(Source2ModGameHookId id) = 0; virtual bool UnhookAirMove(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCanUnduck` [#unhookcanunduck] ```cpp virtual bool UnhookCanUnduck(Source2ModGameHookId id) = 0; virtual bool UnhookCanUnduck(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCategorizePosition` [#unhookcategorizeposition] ```cpp virtual bool UnhookCategorizePosition(Source2ModGameHookId id) = 0; virtual bool UnhookCategorizePosition(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCheckFalling` [#unhookcheckfalling] ```cpp virtual bool UnhookCheckFalling(Source2ModGameHookId id) = 0; virtual bool UnhookCheckFalling(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCheckParameters` [#unhookcheckparameters] ```cpp virtual bool UnhookCheckParameters(Source2ModGameHookId id) = 0; virtual bool UnhookCheckParameters(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCheckVelocity` [#unhookcheckvelocity] ```cpp virtual bool UnhookCheckVelocity(Source2ModGameHookId id) = 0; virtual bool UnhookCheckVelocity(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCheckWater` [#unhookcheckwater] ```cpp virtual bool UnhookCheckWater(Source2ModGameHookId id) = 0; virtual bool UnhookCheckWater(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookDuck` [#unhookduck] ```cpp virtual bool UnhookDuck(Source2ModGameHookId id) = 0; virtual bool UnhookDuck(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookFriction` [#unhookfriction] ```cpp virtual bool UnhookFriction(Source2ModGameHookId id) = 0; virtual bool UnhookFriction(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookFullWalkMove` [#unhookfullwalkmove] ```cpp virtual bool UnhookFullWalkMove(Source2ModGameHookId id) = 0; virtual bool UnhookFullWalkMove(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ---------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookGroundAccelerate` [#unhookgroundaccelerate] ```cpp virtual bool UnhookGroundAccelerate(Source2ModGameHookId id) = 0; virtual bool UnhookGroundAccelerate(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookLadderMove` [#unhookladdermove] ```cpp virtual bool UnhookLadderMove(Source2ModGameHookId id) = 0; virtual bool UnhookLadderMove(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookMoveInit` [#unhookmoveinit] ```cpp virtual bool UnhookMoveInit(Source2ModGameHookId id) = 0; virtual bool UnhookMoveInit(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookPlayerMove` [#unhookplayermove] ```cpp virtual bool UnhookPlayerMove(Source2ModGameHookId id) = 0; virtual bool UnhookPlayerMove(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookProcessMovement` [#unhookprocessmovement] ```cpp virtual bool UnhookProcessMovement(Source2ModGameHookId id) = 0; virtual bool UnhookProcessMovement(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookSetupMove` [#unhooksetupmove] ```cpp virtual bool UnhookSetupMove(Source2ModGameHookId id) = 0; virtual bool UnhookSetupMove(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookTryPlayerMove` [#unhooktryplayermove] ```cpp virtual bool UnhookTryPlayerMove(Source2ModGameHookId id) = 0; virtual bool UnhookTryPlayerMove(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ----------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookWalkMove` [#unhookwalkmove] ```cpp virtual bool UnhookWalkMove(Source2ModGameHookId id) = 0; virtual bool UnhookWalkMove(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookWaterMove` [#unhookwatermove] ```cpp virtual bool UnhookWaterMove(Source2ModGameHookId id) = 0; virtual bool UnhookWaterMove(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------ | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookOnJumpLegacy` [#unhookonjumplegacy] ```cpp virtual bool UnhookOnJumpLegacy(Source2ModGameHookId id) = 0; virtual bool UnhookOnJumpLegacy(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookOnJumpModern` [#unhookonjumpmodern] ```cpp virtual bool UnhookOnJumpModern(Source2ModGameHookId id) = 0; virtual bool UnhookOnJumpModern(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCheckJumpButtonLegacy` [#unhookcheckjumpbuttonlegacy] ```cpp virtual bool UnhookCheckJumpButtonLegacy(Source2ModGameHookId id) = 0; virtual bool UnhookCheckJumpButtonLegacy(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `UnhookCheckJumpButtonModern` [#unhookcheckjumpbuttonmodern] ```cpp virtual bool UnhookCheckJumpButtonModern(Source2ModGameHookId id) = 0; virtual bool UnhookCheckJumpButtonModern(const GameHookHandler& handler, bool post) = 0; ``` | Parameter | Type | | --------- | -------------------------------------------------------------------------------------------------- | | `id` | [`Source2ModGameHookId`](/docs/core-api/ISource2ModGameHooks#source2modgamehookid) | | `handler` | [`const GameHookHandler&`](/docs/core-api/ISource2ModGameHooks#gamehookhandler) | | `post` | `bool` | `IsAvailable` [#isavailable] ```cpp virtual bool IsAvailable(GameHook hook) = 0; ``` False when the core's gamedata has no entry for the function on this platform -- handlers can still be registered, they just never run. | Parameter | Type | | --------- | ---------------------------------------------------------- | | `hook` | [`GameHook`](/docs/core-api/ISource2ModGameHooks#gamehook) | `TakeDamageContext` [#takedamagecontext] CBaseEntity::TakeDamageOld(info, result) -> int64 Fields [#fields] | Field | Type | | -------------- | --------------------------------------------------------- | | `entity` | [`CBaseEntity*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/CCSPlayer_ItemServices) | | `item` | [`CEconItemView*`](/docs/schema/entity/classes/CEconItemView) | | `method` | [`AcquireMethod`](/docs/core-api/ISource2ModGameHooks#acquiremethod) | | `unk` | `void*` | | `result` | [`AcquireResult`](/docs/core-api/ISource2ModGameHooks#acquireresult) | `CanMoveContext` [#canmovecontext] CCSPlayerPawnBase::CanMove() -> bool; false while frozen, defusing, ... Fields [#fields-2] | Field | Type | | -------- | --------------------------------------------------------------------- | | `pawn` | [`CCSPlayerPawnBase*`](/docs/schema/entity/classes/CCSPlayerPawnBase) | | `result` | `bool` | `CanUseContext` [#canusecontext] CCSPlayer\_WeaponServices::CanUse(weapon) -> bool Fields [#fields-3] | Field | Type | | ---------- | ----------------------------------------------------------------------------------- | | `services` | [`CCSPlayer_WeaponServices*`](/docs/schema/entity/classes/CCSPlayer_WeaponServices) | | `weapon` | [`CBasePlayerWeapon*`](/docs/schema/entity/classes/CBasePlayerWeapon) | | `result` | `bool` | `PostThinkContext` [#postthinkcontext] CCSPlayerPawn::PostThink() Fields [#fields-4] | Field | Type | | ------ | --------------------------------------------------------------------- | | `pawn` | [`CCSPlayerPawnBase*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/CCSPlayerController) | | `cmds` | `void*` | | `count` | `int` | | `paused` | `bool` | | `margin` | `float` | | `result` | `void*` | `SimulateUserCommandsContext` [#simulateusercommandscontext] CBasePlayerController::OnSimulateUserCommands() Fields [#fields-6] | Field | Type | | ------------ | ----------------------------------------------------------------------------- | | `controller` | [`CBasePlayerController*`](/docs/schema/entity/classes/CBasePlayerController) | `RunCommandContext` [#runcommandcontext] CPlayer\_MovementServices::RunCommand(cmd), on CCSPlayer\_MovementServices' vtable; the command is a CUserCmd. Fields [#fields-7] | Field | Type | | ---------- | --------------------------------------------------------------------------------------- | | `services` | [`CCSPlayer_MovementServices*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/CBaseEntity) | | `other` | [`CBaseEntity*`](/docs/schema/entity/classes/CBaseEntity) | `DropWeaponContext` [#dropweaponcontext] CCSPlayer\_WeaponServices::DropWeapon(weapon, target, velocity) Fields [#fields-10] | Field | Type | | ---------- | ----------------------------------------------------------------------------------- | | `services` | [`CCSPlayer_WeaponServices*`](/docs/schema/entity/classes/CCSPlayer_WeaponServices) | | `weapon` | [`CBasePlayerWeapon*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/CCSPlayerLegacyJump) | | `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) | `ModernJumpContext` [#modernjumpcontext] CCSPlayerModernJump::OnJump / CheckJumpButton(move) Fields [#fields-20] | Field | Type | | ---------- | ------------------------------------------------------------------------- | | `jump` | [`CCSPlayerModernJump*`](/docs/schema/entity/classes/CCSPlayerModernJump) | | `moveData` | [`CMoveData*`](/docs/schema/Movedata#cmovedata) | `GameHookReturn` [#gamehookreturn] What a game hook handler answers -- 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. For a function without a return value there is only the action. Several handlers on one function: Supersede stops the chain, otherwise the strongest action wins; each value a handler returns lands in ctx.result right away, so the last one to return a value decides it and the next handler sees it there. Fields [#fields-21] | Field | Type | | ---------- | -------------------------------------------------- | | `action` | [`Action`](/docs/core-api/ISource2ModTypes#action) | | `hasValue` | `bool` | Constructors [#constructors] ```cpp GameHookReturn(Action a); GameHookReturn(Action a, RETURN v); ``` 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` | | `GameHook` [#gamehook] ```cpp enum class GameHook : int ``` The hooks, for IsAvailable(). | Name | | ----------------------- | | `TakeDamage` | | `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` | | `Count` | Macros [#macros] `SOURCE2MOD_GAMEHOOK_CALL_ORIGINAL` [#source2mod_gamehook_call_original] ```cpp #define SOURCE2MOD_GAMEHOOK_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. # 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__)` | # ISource2ModHooks (/docs/core-api/ISource2ModHooks) A raw KHook hook needs four places: the type in the header, `new` in the constructor, the address lookup plus Configure/Add in Init, and Remove/delete in Unload. The wrappers here fold all of that into one member declared with a macro in the class body: ```cpp class CHooks { public: KHook::Return OnPostThink(CCSPlayerPawn* pThis); KHook::Return OnGameFrame(ISource2Server* pThis, bool a, bool b, bool c); KHook::Return OnLoadEventsFromFile(IGameEventManager2* pThis, const char* pszFile, bool bSearchAll); KHook::Return OnRespawn(CCSPlayerController* pThis); KHOOK_MEMBER(m_hPostThink, "CCSPlayerPawn::PostThink", &CHooks::OnPostThink, nullptr); KHOOK_VIRTUAL(m_hGameFrame, &ISource2Server::GameFrame, &g_pSource2Server, nullptr, &CHooks::OnGameFrame); KHOOK_VIRTUAL(m_hLoadEventsFromFile, &IGameEventManager2::LoadEventsFromFile, KHOOK_VTABLE("server", "CGameEventManager"), &CHooks::OnLoadEventsFromFile, nullptr); KHOOK_VIRTUAL(m_hRespawn, "CCSPlayerController::Respawn", KHOOK_VTABLE("server", "CCSPlayerController"), &CHooks::OnRespawn, nullptr); }; ``` The hook's type is taken from the handler's signature (the first parameter is the hooked class, the rest are the function's own arguments), so nothing is spelled twice and MSVC needs no CTAD. The handler has to be declared above the macro that names it. The context is the enclosing object. `KHOOK_INIT()` in Load() (after SOURCE2MOD\_SAVEVARS()) resolves and installs every hook declared this way; `KHOOK_DESTRUCT()` in Unload() takes them all down. A hook whose target is `nullptr` is left to the caller: `m_hX.Init(address)`, `m_hX.Init(pInstance)` or `m_hX.InitGlobal(vtable)`. KHOOK\_INIT() only sees the hooks that exist when it runs. A hook that is a member of an object created later (`new CFeature()` after Load()) registers itself but stays uninstalled until `m_hX.Init()` -- in that object's constructor, say -- resolves the target the macro was given. `m_hX.Destruct()` takes one hook down for good; destroying the owner does the same and unregisters it, so KHOOK\_DESTRUCT() never touches a hook that is already gone. Targets: * KHOOK\_MEMBER / KHOOK\_FUNCTION -- a gamedata entry name (resolved through ISource2ModGameConfig::ResolveSignature), an ISource2ModMemory / void\* / typed function pointer (`ADDR_TAKE_DAMAGE_OLD()` -- what Source2Mod already resolved, evaluated at KHOOK\_INIT()), or `nullptr`. * KHOOK\_VIRTUAL -- the function is a member function pointer (index read from the vtable), an integer index, or a gamedata offset name; the target is a pointer to the instance pointer (`&g_pSource2Server`, read at Init so it may still be null when the hook is constructed), `KHOOK_VTABLE(module, class)` for a whole vtable by RTTI name -- the module as a name or as `&pModule` (an ISource2ModModule\* filled in at load) -- or `nullptr`. `KHOOK_NEW` (ISource2ModTypes.h) is still there for a raw KHook object with none of this on top. **Header** [`source2mod/ISource2ModHooks.h`](https://github.com/Source2Mod/source2mod-sdk/blob/main/public/source2mod/ISource2ModHooks.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; ``` `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/ISource2ModHooks#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/ISource2ModHooks#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/ISource2ModHooks#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_Source2ModAPI->Log(g_PluginAPI, "%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)` | # 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) **Inherits** `KHook::Virtual` Methods [#methods] `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. # 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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/CCSPlayerController) | Target player | `OnKeyPress` [#onkeypress] ```cpp virtual void OnKeyPress(CCSPlayerController* player, int key) = 0; ``` Handles key press input. | Parameter | Type | Description | | --------- | ------------------------------------------------------------------------- | ---------------------- | | `player` | [`CCSPlayerController*`](/docs/schema/entity/classes/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. This is how the CustomhudManager\_s2t plugin serves its HudMenu (ICustomhudManager::OpenMenu); a plugin with a screen of its own can do the same. | Parameter | Type | Description | | ---------- | ------------------------------------------------------------------------- | -------------------------------------------------------- | | `owner` | [`PluginId`](/docs/core-api/ISource2ModApi#pluginid) | Plugin the menu belongs to (whose unload closes it) | | `player` | [`CCSPlayerController*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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`](/docs/schema/entity/classes/CCSPlayerController) | Callback executed on select | | `DisabledEvaluator` | [`std::function`](/docs/schema/entity/classes/CCSPlayerController) | Optional dynamic disabled evaluator, asked for the player it is shown to | | `TextEvaluator` | [`std::function`](/docs/schema/entity/classes/CCSPlayerController) | Text worked out for the player it is shown to (replaces Text) | | `VisibleEvaluator` | [`std::function`](/docs/schema/entity/classes/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`](/docs/schema/entity/classes/CCSPlayerController) | | `s_onSelect` | [`std::function`](/docs/schema/entity/classes/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`](/docs/schema/entity/classes/CCSPlayerController) | `SetEndHandler` [#setendhandler] ```cpp void SetEndHandler(std::function handler); ``` What the default OnMenuEnd calls. | Parameter | Type | | --------- | ------------------------------------------------------------------------------------------------------------- | | `handler` | [`std::function`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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`](/docs/schema/entity/classes/CCSPlayerController) | | | `onSelect` | [`std::function`](/docs/schema/entity/classes/CCSPlayerController) | | | `disabledEvaluator` | [`std::function`](/docs/schema/entity/classes/CCSPlayerController) | `nullptr` | | `visibleEvaluator` | [`std::function`](/docs/schema/entity/classes/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`](/docs/schema/entity/classes/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`](/docs/schema/entity/classes/CCSPlayerController) | | Callback executed on selection | | `disabled` | `bool` | `false` | Whether option is disabled | | `close` | `bool` | `true` | Whether menu closes after select | | `disabledEvaluator` | [`std::function`](/docs/schema/entity/classes/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`](/docs/schema/entity/classes/CCSPlayerController) | Function that checks if player can select | | `onSelect` | [`std::function`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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_Source2ModAPI->LoadModule(name)`. Release with `delete` or `g_Source2ModAPI->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_Source2ModAPI->LoadModule(name)` | | `LOAD_MODULE_MEM(ptr)` | `g_Source2ModAPI->LoadModuleFromMemory(ptr)` | | `FREE_MODULE(mod)` | `g_Source2ModAPI->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). Not part of SOURCE2MOD\_FILLVARS(): a plugin that wants it asks for it, ```cpp auto* natives = (ISource2ModNatives*)g_Source2ModAPI->Source2ModFactory(SOURCE2MOD_NATIVES_INTERFACE, nullptr, nullptr); natives->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. **Interface id** `ISource2ModNatives001` (`SOURCE2MOD_NATIVES_INTERFACE`) · **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. 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` | # 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\_Source2ModAPI. `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** `ISource2ModGameHooks003` (`SOURCE2MOD_GAMEHOOKS_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] Listener for source2mod and engine events. Allows plugins to react to lifecycle and map events. `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 `OnGameFrame` [#ongameframe] ```cpp virtual void OnGameFrame(bool simulating, bool firstTick, bool lastTick); ``` Post ISource2Server::GameFrame: once a frame, after Source2Mod's own per-frame work (timers, menus). | Parameter | Type | | ------------ | ------ | | `simulating` | `bool` | | `firstTick` | `bool` | | `lastTick` | `bool` | `OnStartupServer` [#onstartupserver] ```cpp virtual void OnStartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession* session, const char* mapName); ``` Post INetworkServerService::StartupServer: a new server session; the entity system of the new map is already Source2Mod's. | Parameter | Type | | --------- | ----------------------------------- | | `config` | `const GameSessionConfiguration_t&` | | `session` | `ISource2WorldSession*` | | `mapName` | `const char*` | `OnClientConnect` [#onclientconnect] ```cpp virtual bool OnClientConnect(CPlayerSlot slot, const char* name, uint64 xuid, const char* networkId, bool unk1, CBufferString* rejectReason); ``` Pre ISource2GameClients::ClientConnect: a player asks to join (the engine does not call it for bots), right after OnClientConnected. `networkId` is the player's "ip:port" here. Return false to refuse; the reason goes into `rejectReason` (the CS2 client shows its own "rejected by game" text whatever it says). The first plugin to refuse ends it -- the ones after it are not asked. | Parameter | Type | | -------------- | ---------------- | | `slot` | `CPlayerSlot` | | `name` | `const char*` | | `xuid` | `uint64` | | `networkId` | `const char*` | | `unk1` | `bool` | | `rejectReason` | `CBufferString*` | `OnClientConnected` [#onclientconnected] ```cpp virtual void OnClientConnected(CPlayerSlot slot, const char* name, uint64 xuid, const char* networkId, const char* address, bool fakePlayer); ``` Post ISource2GameClients::OnClientConnected: a player or a bot (`fakePlayer`) got a slot. For a player the engine calls this before ClientConnect, so it also comes for a connection that is then refused. `networkId` is the SteamID ("\[U:1:...]"), `address` the player's "ip:port". | Parameter | Type | | ------------ | ------------- | | `slot` | `CPlayerSlot` | | `name` | `const char*` | | `xuid` | `uint64` | | `networkId` | `const char*` | | `address` | `const char*` | | `fakePlayer` | `bool` | `OnClientPutInServer` [#onclientputinserver] ```cpp virtual void OnClientPutInServer(CPlayerSlot slot, const char* name, int type, uint64 xuid); ``` Post ISource2GameClients::ClientPutInServer: the player (type 0) or bot (type 1) is in the game and has a controller. | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | | `name` | `const char*` | | `type` | `int` | | `xuid` | `uint64` | `OnClientDisconnect` [#onclientdisconnect] ```cpp virtual void OnClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* name, uint64 xuid, const char* networkId); ``` Pre ISource2GameClients::ClientDisconnect: the player is leaving and everything of theirs is still there. | Parameter | Type | | ----------- | ----------------------------- | | `slot` | `CPlayerSlot` | | `reason` | `ENetworkDisconnectionReason` | | `name` | `const char*` | | `xuid` | `uint64` | | `networkId` | `const char*` | `OnClientDisconnectPost` [#onclientdisconnectpost] ```cpp virtual void OnClientDisconnectPost(CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* name, uint64 xuid, const char* networkId); ``` Post ISource2GameClients::ClientDisconnect: the game is done with the player; Source2Mod forgets the slot's permissions and cookies only after this. | Parameter | Type | | ----------- | ----------------------------- | | `slot` | `CPlayerSlot` | | `reason` | `ENetworkDisconnectionReason` | | `name` | `const char*` | | `xuid` | `uint64` | | `networkId` | `const char*` | `OnClientVoice` [#onclientvoice] ```cpp virtual void OnClientVoice(CPlayerSlot slot); ``` Post ISource2GameClients::ClientVoice: the player in this slot is talking. | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | `OnClientSettingsChanged` [#onclientsettingschanged] ```cpp virtual void OnClientSettingsChanged(CPlayerSlot slot); ``` Post ISource2GameClients::ClientSettingsChanged: the player's convars (name, language, ...) changed. | Parameter | Type | | --------- | ------------- | | `slot` | `CPlayerSlot` | `OnGameServerSteamAPIActivated` [#ongameserversteamapiactivated] ```cpp virtual void OnGameServerSteamAPIActivated(); ``` Post ISource2Server::GameServerSteamAPIActivated: the Steam API is up. `OnGameServerSteamAPIDeactivated` [#ongameserversteamapideactivated] ```cpp virtual void OnGameServerSteamAPIDeactivated(); ``` Pre ISource2Server::GameServerSteamAPIDeactivated: the Steam API is about to go. `OnLoadEventsFromFile` [#onloadeventsfromfile] ```cpp virtual void OnLoadEventsFromFile(IGameEventManager2* manager, const char* filename, bool searchAll); ``` Post IGameEventManager2::LoadEventsFromFile, with the manager it was called on: the place to keep it, or to register a .gameevents file of your own. | Parameter | Type | | ----------- | --------------------- | | `manager` | `IGameEventManager2*` | | `filename` | `const char*` | | `searchAll` | `bool` | `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 4 ``` 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_Source2ModAPI = nullptr; ISource2ModPlugin* g_PluginAPI = 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_Source2ModAPI; extern ISource2ModPlugin* g_PluginAPI; 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_SAVEVARS` [#source2mod_savevars] ```cpp #define SOURCE2MOD_SAVEVARS() \ g_Source2ModAPI = api; /* s2sdk's GameEntitySystem() (entity2/entitysystem.cpp) reads it */ g_pGameResourceServiceServer = api->GetGameResourceService(); g_PluginAPI = 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_Source2ModAPI->Log ``` Shortcut for logging `SOURCE2MOD_CONPRINT` [#source2mod_conprint] ```cpp #define SOURCE2MOD_CONPRINT g_Source2ModAPI->ConPrint ``` Console print `SOURCE2MOD_CONPRINTF` [#source2mod_conprintf] ```cpp #define SOURCE2MOD_CONPRINTF g_Source2ModAPI->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_Source2ModAPI->QueryInterface(g_Source2ModAPI->v_factory(), v_name); if (!v_var) { if (error && maxlen) { g_Source2ModAPI->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_Source2ModAPI->QueryInterface(g_Source2ModAPI->v_factory(), v_name, 0); if (!v_var) { if (error && maxlen) { g_Source2ModAPI->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_Source2ModAPI->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_Source2ModAPI->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)` | # 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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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*`](/docs/schema/entity/classes/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`](/docs/schema/entity/classes/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`](/docs/schema/entity/classes/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** `ISource2ModTranslations001` (`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. 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 \ - \