Entity I/O

Sending inputs to entities, queueing delayed ones, and listening to — or blocking — outputs and inputs, for one entity, a designer name or every entity.

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

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.

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

!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

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:

pParticle->AddEntityIOEvent("Kill", nullptr, nullptr, "", 1.0f);

Both are also on ISource2ModEntities (Source2ModEntities in a script), taking the target first:

ACCEPT_INPUT(pTarget, "Open", pActivator, pCaller);        // value defaults to ""
ADD_ENTITY_IO_EVENT(pTarget, "Kill", nullptr, nullptr, "", 5.0f);

The interface versions have no HookChain and always run the chain.

Listening to outputs

Source2Mod hooks the engine's output dispatch once and hands each firing to the listeners that match it. A listener is an IEntityOutputListener (source2mod/ISource2ModEntities.h) in C++, a function in a script, and is registered for every output, for a designer name (the caller's — the entity whose output fired), for an output name, for one entity, or any mix of them.

plugin.h
class MyPlugin : public ISource2ModPlugin, public IEntityOutputListener
{
public:
    Action OnEntityOutput(CEntityInstance* pCaller, const char* pszOutput, CEntityInstance* pActivator,
                          float flDelay, bool post) override;
};
plugin.cpp
// In Load():
g_pSource2ModEntities->AddEntityOutputListener(this, "func_button", "OnPressed");   // every button's OnPressed
g_pSource2ModEntities->AddEntityOutputListener(this, nullptr, "OnStartTouch");       // any entity's OnStartTouch
g_pSource2ModEntities->AddEntityOutputListener(this, nullptr, nullptr, pButton);     // every output of one entity
virtual void AddEntityOutputListener(IEntityOutputListener* pListener,
                                     const char* pszDesignerName = nullptr,
                                     const char* pszOutput = nullptr,
                                     CEntityInstance* pEntity = nullptr) = 0;

nullptr ("", NULL_ENTITY in a script) means "any". The macros ADD_ENTITY_OUTPUT_LISTENER(this, ...) / REMOVE_ENTITY_OUTPUT_LISTENER(this) are the same calls.

The listener is called pre (post = false), before the output's targets get it, and post, after:

plugin.cpp
Action MyPlugin::OnEntityOutput(CEntityInstance* pCaller, const char* pszOutput, CEntityInstance* pActivator,
                                float flDelay, bool post)
{
    if (post)
        return Action::Ignore;

    // Nobody opens this button while a round is being set up.
    if (g_bSetupPhase && !V_strcmp(pszOutput, "OnPressed"))
        return Action::Supersede;

    return Action::Ignore;
}
Return (pre)Effect
Action::IgnoreNothing changed; the output fires.
Action::OverrideThe output still fires.
Action::SupersedeThe output does not fire — the door stays shut, the button does nothing — and there is no post call.

The strongest answer of all listeners wins. The post return value is ignored.

A listener belongs to the plugin whose library its class lives in (a script's to the script) and is dropped when that plugin unloads. The same listener may be added again with other filters; RemoveEntityOutputListener(this) drops every registration of it. A registration for one entity goes with the entity. A script keeps the id AddEntityOutputListener returns and passes it to Source2ModEntities.RemoveEntityOutputListener.

The entity events — created, spawned, deleted, parent changed — have a listener of the same shape, IEntityListener; see Watching entities come and go.

Inputs

An input an entity receives — from a map connection, another plugin or AcceptInput — is the hook point HookPoint::AcceptInput (HookPoint_AcceptInput), filtered by designer name and input name:

g_pSource2ModHooks->Hook(HookPoint::AcceptInput, HookTarget::All(), [](AcceptInputContext& ctx, bool post) -> HookReturn<bool>
{
    return { Action::Supersede, false };   // no relay triggers while the round is being set up
}, false, HookFilter{ "logic_relay", "Trigger" });

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 for the full structs.

On this page