ISource2ModHooks
One way to hook anything, the same from C++ and SourcePawn:
Hook = Point (what) + Target (on which object) + Handler (with its Context)
Points (HookPoint::*):
- game functions with a typed context: TakeDamage, Touch, PostThink, AcceptInput, the movement functions, PlayerChat (Host_Say), ... (ISource2ModHookContexts.h);
- entity virtuals with a typed context, hooked on the target's own class (SourceMod's SDKHooks): Spawn, Use, StartTouch, EndTouch, Blocked, Think, Killed, WeaponPickup, WeaponSwitch;
- engine callbacks: GameFrame, StartupServer, ClientConnect, ClientConnected, ClientPutInServer, ClientDisconnect, ClientVoice, ClientSettingsChanged, the Steam API, LoadEventsFromFile;
- any function: HookPoint::Function(fn), fn an ISource2ModFunction -- a gamedata entry (Find), a signature, a symbol, a vtable slot, an engine interface's virtual, a core address (ISource2ModFunctions::From*). SourceMod's DHooks.
Targets (HookTarget):
- All() every call: a detour, every instance;
- Instance(object) one object: an entity, a CServerSideClient, a services object, the game rules, ...;
- Class("CCSPlayerPawn") every object of that C++ class or one derived from it (RTTI). The object is the function's
this(for a free function its first argument). A virtual is hooked once, on the vtable, and the core compares the object before a handler runs. Engine callbacks take All() only.
Filters (HookFilter, optional): a point whose object is an entity compares its designer name ("logic_relay", "player"); AcceptInput also the input's name.
Entity events (created, spawned, deleted, parent changed) and entity outputs are listeners of ISource2ModEntities, the way commands, convars and game events have theirs: this is for functions.
Every Hook() returns a Source2ModHookId; Unhook(id) drops it. What a plugin still holds when it unloads, the core drops for it.
KHook (ISource2ModKHook.h: KHOOK_VIRTUAL, KHOOK_MEMBER, KHOOK_FUNCTION) stays available to C++ plugins, but prefer this: a plugin with KHook hooks of its own is "raw tier" in s2m list and needs a rebuild whenever KHook or the engine changes, a plugin on ISource2ModHooks only a core update.
// Half damage for one pawn.
g_pSource2ModHooks->Hook(HookPoint::TakeDamage, HookTarget::Instance(pawn),
[](TakeDamageContext& ctx, bool post) -> HookReturn<std::int64_t>
{
ctx.info->m_flDamage /= 2; // the original sees it: the info is passed by pointer
return Action::Ignore;
});
// Every tick, after the game's own work.
g_pSource2ModHooks->Hook(HookPoint::GameFrame, HookTarget::All(), SOURCE2MOD_MEMBER(this, &Plugin::OnGameFrame), true);
// One input of one kind of entity.
g_pSource2ModHooks->Hook(HookPoint::AcceptInput, HookTarget::All(), OnRelayInput, false, HookFilter{ "logic_relay", "Trigger" });
// Any function, here one client's string commands.
ISource2ModFunction* fn = g_pSource2ModFunctions->Find("CServerSideClient::ExecuteStringCommand");
g_pSource2ModHooks->Hook(HookPoint::Function(fn), HookTarget::Instance(client), OnStringCommand);Interface id ISource2ModHooks001 (SOURCE2MOD_HOOKS_INTERFACE) · Global g_pSource2ModHooks · Header source2mod/ISource2ModHooks.h
Types
HookHandler
template <typename CONTEXT> using HookHandler = Source2ModCallback<HookReturn<typename CONTEXT::Return>(CONTEXT& ctx, bool post)>;A handler: the context, and whether this is the post pass. A function, an object and a method (SOURCE2MOD_MEMBER), or a lambda.
Methods
Hook
template <typename CONTEXT> Source2ModHookId Hook(HookPointOf<CONTEXT> point, const HookTarget& target, typename HookNonDeduced<HookHandler<CONTEXT>>::type handler, bool post = false, const HookFilter& filter = {});Hooks @p point on @p target.
| Parameter | Type | Default | Description |
|---|---|---|---|
point | HookPointOf<CONTEXT> | ||
target | const HookTarget& | ||
handler | typename HookNonDeduced<HookHandler<CONTEXT>>::type | ||
post | bool | false | false: before the original (may change arguments, Override / Supersede); true: after it (sees the result). |
filter | const HookFilter& | {} | Designer / output / input name, for the points that take one. |
Returns Source2ModHookId — The hook's id for Unhook(); 0 when the point does not exist on this game, the target does not fit the point, or the function of a Function point is null.
Unhook
virtual bool Unhook(Source2ModHookId id) = 0;Drops one hook. @return false when there is no such hook.
| Parameter | Type |
|---|---|
id | Source2ModHookId |
IsAvailable
virtual bool IsAvailable(HookPointId point) = 0;Whether the point resolves on the running game (its gamedata matched).
| Parameter | Type |
|---|---|
point | HookPointId |
AddHook
virtual Source2ModHookId AddHook(HookPointId point, ISource2ModFunction* function, const HookTarget& target, const HookFilter& filter, const void* handler, bool post) = 0;Type-erased Hook(): @p handler is a HookHandler<CONTEXT>* for @p point's context, copied by the core. Hook() is the way in.
| Parameter | Type |
|---|---|
point | HookPointId |
function | ISource2ModFunction* |
target | const HookTarget& |
filter | const HookFilter& |
handler | const void* |
post | bool |
HookTarget
One way to hook anything, the same from C++ and SourcePawn:
Hook = Point (what) + Target (on which object) + Handler (with its Context)
Points (HookPoint::*):
- game functions with a typed context: TakeDamage, Touch, PostThink, AcceptInput, the movement functions, PlayerChat (Host_Say), ... (ISource2ModHookContexts.h);
- entity virtuals with a typed context, hooked on the target's own class (SourceMod's SDKHooks): Spawn, Use, StartTouch, EndTouch, Blocked, Think, Killed, WeaponPickup, WeaponSwitch;
- engine callbacks: GameFrame, StartupServer, ClientConnect, ClientConnected, ClientPutInServer, ClientDisconnect, ClientVoice, ClientSettingsChanged, the Steam API, LoadEventsFromFile;
- any function: HookPoint::Function(fn), fn an ISource2ModFunction -- a gamedata entry (Find), a signature, a symbol, a vtable slot, an engine interface's virtual, a core address (ISource2ModFunctions::From*). SourceMod's DHooks.
Targets (HookTarget):
- All() every call: a detour, every instance;
- Instance(object) one object: an entity, a CServerSideClient, a services object, the game rules, ...;
- Class("CCSPlayerPawn") every object of that C++ class or one derived from it (RTTI). The object is the function's
this(for a free function its first argument). A virtual is hooked once, on the vtable, and the core compares the object before a handler runs. Engine callbacks take All() only.
Filters (HookFilter, optional): a point whose object is an entity compares its designer name ("logic_relay", "player"); AcceptInput also the input's name.
Entity events (created, spawned, deleted, parent changed) and entity outputs are listeners of ISource2ModEntities, the way commands, convars and game events have theirs: this is for functions.
Every Hook() returns a Source2ModHookId; Unhook(id) drops it. What a plugin still holds when it unloads, the core drops for it.
KHook (ISource2ModKHook.h: KHOOK_VIRTUAL, KHOOK_MEMBER, KHOOK_FUNCTION) stays available to C++ plugins, but prefer this: a plugin with KHook hooks of its own is "raw tier" in s2m list and needs a rebuild whenever KHook or the engine changes, a plugin on ISource2ModHooks only a core update.
// Half damage for one pawn.
g_pSource2ModHooks->Hook(HookPoint::TakeDamage, HookTarget::Instance(pawn),
[](TakeDamageContext& ctx, bool post) -> HookReturn<std::int64_t>
{
ctx.info->m_flDamage /= 2; // the original sees it: the info is passed by pointer
return Action::Ignore;
});
// Every tick, after the game's own work.
g_pSource2ModHooks->Hook(HookPoint::GameFrame, HookTarget::All(), SOURCE2MOD_MEMBER(this, &Plugin::OnGameFrame), true);
// One input of one kind of entity.
g_pSource2ModHooks->Hook(HookPoint::AcceptInput, HookTarget::All(), OnRelayInput, false, HookFilter{ "logic_relay", "Trigger" });
// Any function, here one client's string commands.
ISource2ModFunction* fn = g_pSource2ModFunctions->Find("CServerSideClient::ExecuteStringCommand");
g_pSource2ModHooks->Hook(HookPoint::Function(fn), HookTarget::Instance(client), OnStringCommand);Fields
| Field | Type | Description |
|---|---|---|
kind | Kind | |
instance | void* | |
className | const char* | Kind::Class: the RTTI name ("CCSPlayerPawn") |
All
static HookTarget All();Instance
static HookTarget Instance(const void* object);| Parameter | Type |
|---|---|
object | const void* |
Class
static HookTarget Class(const char* name);| Parameter | Type |
|---|---|
name | const char* |
Kind
enum class Kind : int32_t| Name | Value |
|---|---|
All | 0 |
Instance | |
Class |
HookFilter
Compared before a handler runs; nullptr or "" matches anything.
Fields
| Field | Type | Description |
|---|---|---|
designerName | const char* | the object is an entity: its designer name |
name | const char* | AcceptInput: the input |
HookPointOf
A point and the context its handlers get, so Hook() checks the handler's signature at compile time.
Fields
| Field | Type | Description |
|---|---|---|
id | HookPointId | |
function | ISource2ModFunction* | HookPointId::Function only |
FunctionContext
The context of HookPoint::Function: the arguments by index, as the function describes them (this of a member function is index 0), and the return value.
SetArg in a pre handler changes what the original (and the later handlers) get. SetReturn with Override / Supersede is what the caller gets; in a post handler GetReturn is what the original returned. A Vector / string argument is read as a pointer (GetArg<Vector*>).
Function
ISource2ModFunction* Function() const;This
void* This() const;this of a member function, the first argument otherwise.
GetArg
template <typename T> T GetArg(int index) const;| Parameter | Type |
|---|---|
index | int |
SetArg
template <typename T> void SetArg(int index, T value);| Parameter | Type |
|---|---|
index | int |
value | T |
GetReturn
template <typename T> T GetReturn() const;SetReturn
template <typename T> void SetReturn(T value);| Parameter | Type |
|---|---|
value | T |
CallOriginal
template <typename T = void> T CallOriginal();The function now, with the current arguments, past every hook on it. In a pre handler answer Supersede afterwards (with SetReturn), or the game calls it a second time.
Return
using Return = void;HookReturn
What a handler answers: an Action, and for a function with a return value optionally the value -- KHook's { action, value }.
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 aloneAn action alone takes its value from ctx.result, so a handler that sets the field and returns Override / Supersede keeps working. Several handlers on one point: Supersede stops the chain, otherwise the strongest action wins; each value a handler returns lands in ctx.result right away.
Fields
| Field | Type |
|---|---|
action | Action |
hasValue | bool |
Constructors
HookReturn(Action a);
HookReturn(Action a, RETURN v);Enums
HookPointId
enum class HookPointId : int32_tEvery point. The numbers are SourcePawn's HookPoint_* too.
| Name | Value | Description |
|---|---|---|
TakeDamage | 0 | Game functions (ISource2ModHookContexts.h). |
CanAcquire | ||
CanMove | ||
CanUse | ||
PostThink | ||
ProcessUsercmds | ||
SimulateUserCommands | ||
RunCommand | ||
AcceptInput | ||
Touch | ||
DropWeapon | ||
AirAccelerate | ||
AirMove | ||
CanUnduck | ||
CategorizePosition | ||
CheckFalling | ||
CheckParameters | ||
CheckVelocity | ||
CheckWater | ||
Duck | ||
Friction | ||
FullWalkMove | ||
GroundAccelerate | ||
LadderMove | ||
MoveInit | ||
PlayerMove | ||
ProcessMovement | ||
SetupMove | ||
TryPlayerMove | ||
WalkMove | ||
WaterMove | ||
OnJumpLegacy | ||
OnJumpModern | ||
CheckJumpButtonLegacy | ||
CheckJumpButtonModern | ||
PlayerChat | ||
Spawn | 50 | Entity virtuals, on the target's class vtable (SDKHooks). |
Use | ||
StartTouch | ||
EndTouch | ||
Blocked | ||
Think | ||
Killed | ||
WeaponPickup | ||
WeaponSwitch | ||
GameFrame | 100 | Engine callbacks. |
StartupServer | ||
ClientConnect | ||
ClientConnected | ||
ClientPutInServer | ||
ClientDisconnect | ||
ClientVoice | ||
ClientSettingsChanged | ||
GameServerSteamAPIActivated | ||
GameServerSteamAPIDeactivated | ||
LoadEventsFromFile | ||
Function | 300 | HookPoint::Function. |
Functions
Function
inline HookPointOf<FunctionContext> Function(ISource2ModFunction* fn);Any function from ISource2ModFunctions.
| Parameter | Type |
|---|---|
fn | ISource2ModFunction* |