Valve Developer Union

Entity Interactions in Source's Input/Output System

(February 11, 2018)


In Quake and GoldSrc, interaction between monsters, lights, choreography, sprites, and other entities was done through the target-targetname system. Under it, one entity would be given a name and another would be set to "target" that name; when the second entity was triggered, any entities with that name would activate.

An example of the target system
An example of the target system

Let's start with a simple example to demonstrate. When a scientist dies, an env_sprite should turn off. This is easy enough to do. The sprite is named by the mapper (say, cpu_glow1) and the scientist is set to target cpu_glow1.

The limitations of the target system

Targets works fine for basic interactions such as this, but the system is fraught with limitations. Consider the following:

  1. What action exactly "triggers" an entity is ill-defined and vague. An NPC triggers on death, but the mapper has no idea of that unless they have prior knowledge or a reference is used. What action occurs when the targeted entity is activated is also often unclear.
  2. Only one action can count as a trigger. The scientist can only trigger on death. If the mapper wants the sprite to turn off when the NPC's AI goes idle, code changes would be required, and dying would no longer trigger the sprite.
  3. An entity can only target a single name. Consider the case of two sprite entities, cpu_glow1 and cpu_glow2. The scientist can't target both directly. Quake and GoldSrc both have different ways of dealing with this, through the trigger_relay and multi_manager entities, respectively, but both solutions were incredibly clumsy. multi_manager requires the mapper to set custom keyvalues, and another trigger_relay would have to exist for each new item to be targeted.
  4. The available editors won't show what's connected to what. Nonexistent targets would appear as warnings in the compile log, but if the scientist was targeting, say, cpu_glow3 somewhere else on the map, the mapper would be left bewildered as to why the sprite wasn't turning off.

Introducing the I/O system

With Source, Valve removed the target-targetname system and replaced it with the input/output (I/O) system. Though it works under the same principle (an entity activates others when an action occurs), the I/O system is far more flexible and intuitive.

The above example, but as seen in the I/O system
The above example, but as seen in the I/O system

Adding a new output

To add a new output to an entity, go into the Object Properties dialog and switch to the Outputs tab. You should see a blank list with four dropdowns and five buttons. To add a new output, click the "Add..." button.

The Outputs tab in Object Properties
The Outputs tab in Object Properties

The first dropdown, "My output named", sets the action that will cause the entity to activate. For this trigger_multiple entity, you can cause an event to occur when the player first enters the brush volume, leaves the brush volume, when the brush volume is triggered in general, and so on. For most purposes, OnTrigger works fine.

Setting an activation condition for a trigger_multiple
Setting an activation condition for a trigger_multiple

The second dropdown, "Target entities named", displays a list of valid targetnames in the map. This field supports wildcards and keywords, which we'll discuss later. If an invalid (nonexistent) entity name is put into this field, it'll be displayed in red. If the targetname is shared by multiple entities, the field will become bold.

Targeting a nonexistent entity causes the name to display in red
Targeting a nonexistent entity causes the name to display in red

The last two dropdowns, "Via this input" and "With a parameter override of", are the values the output entity will pass to the target entity. For metrocops, they can be told to bring out their stunsticks, or to holster a weapon, or have their squad name set. A door can be told to open, close, toggle state, or lock, or a new skin can be set. Physics constraints can be broken at any time using the I/O system. This is where the true beauty of the I/O system lies; a vast, complex amount of interactions can happen between entities, without compromise and without hassle. For now, though, we'll just make a metrocop go limp.

Making a metrocop go limp with I/O
Making a metrocop go limp with I/O

On the flipside, the Inputs tab of the Object Properties dialog displays a list of all the entities which target the one currently being viewed. The metrocop would have the BecomeRagdoll output listed under his inputs. Nothing can be set here, but clicking the Mark button will bring you to the originating source. (The Mark button in the Outputs tab will also bring you to the entity being targeted.)

The Inputs tab in Object Properties
The Inputs tab in Object Properties

Note the two icons at the bottom of each screenshot. The first icon (an arrow pointing to a ball) will appear if the current entity is being targeted, while the second icon (an arrow extending out from a ball) appears if the current entity is targeting another. If an output on the current entity is broken, the second icon will appear with a red line through it.

Keywords and wildcards

Warning

The keywords are displaying red!

Hammer has its limits, and keywords are unfortunately valid targetnames that the program doesn't understand. They'll function in the engine, but display as broken I/O events in the editor. Wildcards will also confuse it. There's nothing you can do about this, but know it isn't your fault.

The I/O system allows an entity to target entities dynamically without needing to specify an actual target. This is done through keywords, which allow the entity to refer to an implicit, context-reliant target, or through wildcards, which can match multiple entities with similar names in one go. (These go in the "Target entities named" field in the Outputs tab.)

Trailing asterisks (*) can be used as wildcards to match a number of targetnames in one go. The use for this is limited to wildcards at the end of names; door1_* matches door1_trigger and door1_light, but not door2_light, and *_light doesn't work at all.

On the other hand, keywords allows you to target players or NPCs in choreography and scripted sequences. This is useful if you have a specific entity in mind, but not a specific targetname to be able to target it with.

Three in particular refer to different entities in an I/O chain. Consider the scenario of an antlion wandering into a trigger_once that's set to fire a prop_thumper. When the thumper is activated, it can refer to the antlion with the !activator keyword (refers to the entity that started the I/O chain), the trigger with the !caller keyword (the previous entity in the I/O chain), or itself with the !self keyword.

An antlion, a trigger, and a thumper in an I/O chain
An antlion, a trigger, and a thumper in an I/O chain

Other keywords exist for other functions: !player can be used to target the player in singleplayer (all players can be targeted in multiplayer using a simple player targetname), !pvsplayer picks the first player found in the same potentially visible set as the entity itself, and !speechtarget is exclusively meant for choreography and targets the entity the actor is looking at.

Tip

The ent_fire command (or, death by ent_firing squad)

Fun command to run in the console: ent_fire !picker ignite. The ent_fire command is good for debugging entity inputs and outputs (they'll work as if they were set up in Hammer), though as the !picker keyword can target what the player is looking at, it's also good for lighting things you're looking at on fire. (Ignite is a perfectly valid input to run, by the way, and it's totally possible to set up a trigger that sets all players in a game on fire.)

User inputs and outputs

The final point of discussion, user inputs and outputs, are a bit more complicated and abstract than the normal actions in the I/O system, but are good to know nonetheless. These four outputs store an action and fire when the corresponding input is fired. Complex I/O chains can be simplified down using user inputs, and multiple entities with the same name can perform different actions when the one generic user input is fired.

A good example is in d2_coast_08, where four seagulls with the same name can be told to do four different things with only one input. A logic_auto fires the FireUser1 input for the four gulls, and the corresponding OnUser1 output is fired for each, causing two to fly off to one spot of the map at different times, and another two to fly off to another spot, also at different times. Again, all done with a single input.

The user I/O routine, as seen in d2_coast_08 to cause seagulls to fly off into the distance
The user I/O routine, as seen in d2_coast_08 to cause seagulls to fly off into the distance

The I/O system makes complex choreography, scripted sequences, physics interactions, and other such functionality of the Source engine remarkably easy to carry out.