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_triggerPasses from_class on all three trigger outputs, containing the class name of the entity that entered or exited.
LogicComparePasses value and comparison on all four comparison outputs.
LogicArithmeticPasses 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:
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:
LogicAutoFires 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.
LogicBranchHolds a boolean value. SetValue sets it, ToggleValue flips it, and Test fires either OnTrue or OnFalse depending on its current state.
LogicCompareHolds two float values and compares them. Compare fires OnGreaterThan, OnLessThan, OnEqualTo, or OnNotEqualTo and passes both values as pass variables.
LogicArithmeticPerforms math on two floats. Add, Subtract, Multiply, and Divide all fire OnValueComputed with the result passed as value_c.
LogicPrintPrints whatever string is passed as its parameter to the console. Useful for debugging output chains.
LogicClassFilterFilters 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.
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.