Advanced Outputs

A Quick Recap

The basic output system was covered in Core Concepts. As a reminder, outputs connect one entity to another in the editor: when something happens on entity A, it calls an input on entity B, optionally with a string parameter. Most of the time that's all you need. This page covers the more advanced features that let you build complex map logic without writing any code.

Delays

Every output has a Delay field. Setting it to a value greater than zero causes the engine to wait that many seconds before actually calling the input. The entity that fired the output continues running normally in the meantime. This is useful for sequencing events, like a button that opens a door after a two second pause, or an explosion that triggers a chain of events with staggered timing.

Delayed outputs are queued on the entity that fired them, so if that entity is despawned before the delay elapses the output will not fire.

Refire

The Refire field controls how many additional times an output repeats after the initial fire. A value of 0, -1, or blank means fire once and stop. Setting it to 3 means the output fires once immediately and then three more times, each separated by the Delay interval. This makes Delay and Refire work together: a Delay of 1 and a Refire of 4 fires the output five times total, once per second.

A practical use for this is a repeating alarm that flashes a light and plays a sound five times when triggered, without needing any code or a looping entity.

The _activator Target

Normally the To Entity field targets a specific named entity in the map. There is one special target name: _activator. Instead of looking up an entity by name, this routes the output back to whichever entity caused the output chain to start.

For example, a func_trigger fires OnTriggerEnter when an entity walks into it. If you set that output's target to _activator and the input to Destroy, whatever walked into the trigger will destroy itself. This works regardless of what entity type triggered it, and without needing to know its name in advance.

The activator is tracked through the whole chain, so if entity A fires an output to entity B, and entity B fires an output to _activator, it still refers back to whatever originally kicked off the chain at entity A.

Pass Variables

Pass variables let data travel through an output chain. When an entity fires an output in code, it can attach named values alongside it. In the editor you can then reference those values in the Input Parameter field using the ! prefix, and the engine will substitute the actual value in at the moment the output fires.

Pass variables are formatted as key:value strings. For example, when func_trigger fires OnTriggerEnter, it passes from_class:Player alongside it. If you set an output's Input Parameter to !from_class, the engine strips the !, looks up the from_class variable from the chain, and uses Player as the actual parameter instead.

Without pass variables, every parameter in the editor has to be a fixed string you type in at map design time. With pass variables, parameters can be determined by what actually happens at runtime.

What Variables Are Available

Pass variables come from the engine-side code that calls CallOutput(). The built-in entities expose the following:

func_trigger

Passes from_class on all three trigger outputs, containing the class name of the entity that entered or exited.

LogicCompare

Passes value and comparison on all four comparison outputs.

LogicArithmetic

Passes value_c (the result), value_a, and value_b on OnValueComputed.

When writing your own entities you can pass any variables you like by adding them to the CallOutput call:

// Pass a damage amount and the attacker's class name
entity.CallOutput("DamageTaken", this, $"damage:{info.damage}", $"attacker:{info.from?.GetType().Name}");

Any output connected to DamageTaken in the editor can then use !damage or !attacker as its Input Parameter.

Practical Examples

A combination lock

Three buttons, each controlling one tumbler. Each func_button's OnInteracted output calls ToggleValue on its own LogicBranch, so pressing it flips the tumbler on and off. A fourth confirm button tests whether all three are true simultaneously. Wire its OnInteracted to LogicBranch A's Test. Connect OnTrue to LogicBranch B's Test. Connect that OnTrue to LogicBranch C's Test. Connect the final OnTrue to BeginMovement on a func_sliding door. If any branch is false the chain breaks before the door opens.

A weighted platform

A pressure plate that opens a door once enough entities are standing on it. Place a func_trigger covering the platform and a LogicArithmetic entity nearby. Connect OnTriggerEnter to SetB with parameter 1, then immediately call Add. Connect OnTriggerExit to SetB with parameter 1, then call Subtract. Each time the count changes, OnValueComputed fires and passes value_c into a LogicCompare's SetValue input via !value_c, then calls Compare. Set the comparison threshold to 3. Wire OnGreaterThan to open the door and OnLessThan to close it. The door responds live as entities step on and off.

Timed spawn waves

Use LogicAuto combined with Delay and Refire to spawn enemies in waves without any code. Place a LogicAuto and an EntityFactory set to your enemy class. Connect OnMapSpawn to CreateEntity with a Delay of 5 and a Refire of 4. The moment the map loads the chain starts, spawning one enemy every five seconds for a total of five. For multiple enemy types, connect the same OnMapSpawn to several factories with different delays to stagger their waves.

A one-time gate

A door or event that should only trigger once per playthrough, even across saves. Place a LogicBranch and connect a LogicAuto's OnMapSpawn to its Test input. The branch starts false, so OnFalse fires on the first load. Wire OnFalse to whatever should happen, and also to SetValue on the same branch with parameter 1. Now the branch is permanently true. Because LogicBranch saves its state, reloading the map or loading a save finds the branch already true and OnFalse never fires again.

A physics trap

A ceiling of frozen debris that drops when the player walks underneath. Place several func_physics brushes above the area with Frozen On Spawn set to 1. Place a func_trigger in the walkway below with a LogicClassFilter set to your player class. Connect OnTriggerEnter to BecomeDynamic on each physics brush, routing through a RelayEntity to fan the signal out to multiple targets. Give each connection a slightly different Delay so the pieces fall at staggered times rather than all at once.

The Logic Entities

The engine ships with a set of logic entities designed to be wired together with outputs to build complex behavior without code. Here's a summary of what's available:

LogicAuto

Fires OnMapSpawn once on the first update after the map loads. Use this to kick off any setup logic that needs to run at map start.

LogicBranch

Holds a boolean value. SetValue sets it, ToggleValue flips it, and Test fires either OnTrue or OnFalse depending on its current state.

LogicCompare

Holds two float values and compares them. Compare fires OnGreaterThan, OnLessThan, OnEqualTo, or OnNotEqualTo and passes both values as pass variables.

LogicArithmetic

Performs math on two floats. Add, Subtract, Multiply, and Divide all fire OnValueComputed with the result passed as value_c.

LogicPrint

Prints whatever string is passed as its parameter to the console. Useful for debugging output chains.

LogicClassFilter

Filters trigger volumes by entity class. Set its Classname property and assign it to a func_trigger's Filter Name to restrict which entity types can activate it.

Note LogicBranch and LogicCompare both save their current values through the save system, so a branch that was set to true before a save will still be true after a load. More logic entities are going to be added soon.