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 activatordoor.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
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);particle.AddEntityIOEvent("Kill", NULL_ENTITY, NULL_ENTITY, "", 1.0);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);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
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.
class MyPlugin : public ISource2ModPlugin, public IEntityOutputListener
{
public:
Action OnEntityOutput(CEntityInstance* pCaller, const char* pszOutput, CEntityInstance* pActivator,
float flDelay, bool post) override;
};public Source2ModAction OnEntityOutput(CEntityInstance caller, const char[] output,
CEntityInstance activator, float delay, bool post)
{
return Action_Ignore;
}// 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// In OnPluginStart():
Source2ModEntities.AddEntityOutputListener(OnEntityOutput, "func_button", "OnPressed"); // every button's OnPressed
Source2ModEntities.AddEntityOutputListener(OnEntityOutput, "", "OnStartTouch"); // any entity's OnStartTouch
Source2ModEntities.AddEntityOutputListener(OnEntityOutput, "", "", button); // every output of one entityvirtual 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:
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;
}public Source2ModAction OnEntityOutput(CEntityInstance caller, const char[] output,
CEntityInstance activator, float delay, bool post)
{
if (post)
return Action_Ignore;
// Nobody opens this button while a round is being set up.
if (g_bSetupPhase && Source2ModUtils.Equal(output, "OnPressed"))
return Action_Supersede;
return Action_Ignore;
}| Return (pre) | Effect |
|---|---|
Action::Ignore | Nothing changed; the output fires. |
Action::Override | The output still fires. |
Action::Supersede | The output does not fire — the door stays shut, the button does nothing — and there is no post call. |
The strongest answer of all listeners wins. The post return value is ignored.
A listener belongs to the plugin whose library its class lives in (a script's
to the script) and is dropped when that plugin unloads. The same listener may
be added again with other filters; RemoveEntityOutputListener(this) drops
every registration of it. A registration for one entity goes with the entity.
A script keeps the id AddEntityOutputListener returns and passes it to
Source2ModEntities.RemoveEntityOutputListener.
The entity events — created, spawned, deleted, parent changed — have a
listener of the same shape, IEntityListener; see
Watching entities come and go.
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" });Source2ModHooks.Hook(HookPoint_AcceptInput, OnRelayTrigger, false, "logic_relay", "Trigger");
public Source2ModAction OnRelayTrigger(AcceptInputContext ctx, bool post, bool &result)
{
result = false; // no relay triggers while the round is being set up
return Action_Supersede;
}Outputs on the entity
Outputs 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.
Players
The everyday player helpers — respawning, teams, killing and damaging, items and weapons, attributes, messages, kicking — and the HookChain argument they take.
Damage
CTakeDamageInfo and CTakeDamageResult, hooking CBaseEntity::TakeDamageOld to change or block damage, and dealing damage yourself.