Listeners

The ISource2ModListener callbacks — plugin lifecycle, map start and end, Steam authorisation, permissions, interface queries — what each one reports and exactly when it fires.

ISource2ModListener is how Source2Mod tells your plugin about the plugin system, the map lifecycle, Steam authorisation and permissions. The engine's own callbacks — the frame, clients connecting and leaving — are hook points instead. 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.

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
};
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

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

Plugin lifecycle

CallbackFires
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.

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");
}

Map lifecycle

CallbackFires
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.

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.
}

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

The engine calls a plugin used to override here — OnGameFrame, OnStartupServer, OnClientConnect, OnClientConnected, OnClientPutInServer, OnClientDisconnect / OnClientDisconnectPost, OnClientVoice, OnClientSettingsChanged, the Steam API pair and OnLoadEventsFromFile — are no longer on the listener. Since plugin API 5 they are hook points of ISource2ModHooks, hooked with HookTarget::All(), pre or post, with a context instead of a parameter list. In SourcePawn the matching Source2Mod_On… forwards are gone the same way.

WasNow
OnGameFrame(simulating, firstTick, lastTick)HookPoint::GameFrame, post
OnStartupServer(config, session, mapName)HookPoint::StartupServer
OnClientConnect(...) returning falseHookPoint::ClientConnect, pre: { Action::Supersede, false }
OnClientConnected, OnClientPutInServerHookPoint::ClientConnected, HookPoint::ClientPutInServer
OnClientDisconnect / OnClientDisconnectPostHookPoint::ClientDisconnect, pre / post
OnClientVoice, OnClientSettingsChangedHookPoint::ClientVoice, HookPoint::ClientSettingsChanged
OnGameServerSteamAPIActivated / DeactivatedHookPoint::GameServerSteamAPIActivated / Deactivated
OnLoadEventsFromFile(manager, filename, searchAll)HookPoint::LoadEventsFromFile
plugin.cpp
g_pSource2ModHooks->Hook(HookPoint::ClientPutInServer, HookTarget::All(), SOURCE2MOD_MEMBER(this, &MyPlugin::OnClientPutInServer), true);

HookReturn<void> MyPlugin::OnClientPutInServer(ClientPutInServerContext& ctx, bool post)
{
    SOURCE2MOD_LOG(this, "%s is in (slot %d)
", ctx.name, ctx.slot.Get());
    return Action::Ignore;
}

Clients and permissions

CallbackFires
OnClientAuthorized(CPlayerSlot slot, uint64 steamId)Steam validated the player in this slot: from now on they are checked under steamId. Not called for bots.
OnClientAuthorizeFailed(CPlayerSlot slot, uint64 steamId)Steam refused the ticket; steamId is the one they claimed.
OnPermissionsChanged(uint64 steamId)What this SteamID64 holds may have changed — a grant, a group, a reload. 0 when a change can touch anybody.

Scripts get them as Source2Mod_OnClientAuthorized, Source2Mod_OnClientAuthorizeFailed and Source2Mod_OnPermissionsChanged, with the SteamID as decimal text.

Interface queries

These let your plugin expose interfaces to other plugins:

CallbackCalled when
void* OnSource2ModQuery(const char* iface, int* ret)Another s2m plugin asks for an interface (g_pSource2ModAPI->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.

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

ISource2ModListener is deliberately small. For everything else:

EventWhere
The frame, clients connecting, put in server, disconnectingHook points (HookPoint::GameFrame, ClientConnect, ClientPutInServer, ClientDisconnect, …)
ClientActive, ClientCommand and other engine callsA Function hook, or the player_connect_full game event
Damage, movement, item pickup, entity inputsHook points
Entity created / spawned / deletedIEntityListener via AddEntityListener — see Entity listeners
Entity outputsIEntityOutputListener via AddEntityOutputListener — see Entity listeners
Round start, deaths, bomb, …Game events

The full interface is documented in the ISource2ModPlugin reference; AddListener in ISource2ModApi.

On this page