15. Triggers¶
Triggers are one of the most important entities in Half-Life. They are hidden in plain sight, literally. A trigger typically has a BSP model associated with it, though some map or modding tools such as the Vluzacn’s map compile tools removes the associated BSP model. If the associated model is present, then they can be made visible in a speedrunning mod to make planning and routing easier.
15.1. Mechanism¶
A trigger entity usually has solid type SOLID_TRIGGER
. Solids of this type
do not get “touched” by conventional means where a line tracing of some sort is
done to check if the line intersects with them. Instead, these trigger entities
are maintained in a BSP tree called “area nodes”, pointed to by the
sv_areanodes
global variable in the engine library. They are placed there by
calling SV_LinkEdict
with each of them as the first argument somewhere at
the beginning of a level load. In every subsequent frame, SV_LinkEdict
is
called with the player entity as the argument at some point, which causes
SV_TouchLinks
to get called if the second argument is set to true.
The SV_TouchLinks
function walks through each of the triggers stored in the
area nodes, and checks if the player intersects with the model using hull
information. If the player and the trigger intersects, then DispatchTouch
in
the game library will get called, which in turn calls the Touch
member
function of the trigger in question. The Touch
function then runs whatever
logic specific to the trigger type.
Some triggers, such as trigger_once, may get removed after touching, though not
immediately. Typically a trigger is scheduled to be removed after 0.1 seconds.
The reason for this delay may be illustrated by this comment in
CBaseTrigger::ActivateMultiTrigger
:
// we can't just remove (self) here, because this is a touch function
// called while C code is looping through area links...
SetTouch( NULL );
pev->nextthink = gpGlobals->time + 0.1;
SetThink( &CBaseTrigger::SUB_Remove );
In other words, the delay is likely to prevent a crash as SV_TouchLinks
is
traversing the area nodes and holding a reference to the trigger.
15.2. Base trigger¶
The base trigger refers to the base class from which all other trigger types are
inherited. Two important properties associated with a trigger that are not so
obvious are the delay on activation and the delay after use. The property
names of these values are delay
and wait
respectively. The delay
property is read by the superclass CBaseDelay
and wait
is read from
CBaseToggle
.
15.3. trigger_multiple¶
This type of trigger is typically used for actions that can be triggered
multiple times. When triggered, the assigned target will be fired using
SUB_UseTargets
. For this trigger, the delays on activation or after use
properties are important to know if hitting the trigger is important in
progressing in the run. For example, sometimes a trigger_multiple in front of a
door is activated or enabled only after something other action has been done.
This means that standing in the trigger area can result in premature firing even
before the trigger has been activated. When the trigger finally activates, the
delay after use can cause the trigger is not fire immediately, thus wasting
crucial time. To avoid this issue, we should avoid touching trigger_multiple
prematurely until we are sure they have been activated. And of course, this
requires knowing precisely when the trigger activates.
15.4. trigger_changelevel¶
15.5. trigger_push¶
A push trigger or a push field is associated with a basevelocity set by the map designer. It imparts onto the velocity of each of the entities that touches it, provided the entities in question satisfy certain conditions written in CTriggerPush::Touch
in the Half-Life SDK. The push trigger also sets the FL_BASEVELOCITY
flag in the entities it touches.
If the touching entities satisfy the necessary conditions, the behaviour differs slightly depending on the spawn flags of the trigger. Let be the velocity of a touching entity. If SF_TRIG_PUSH_ONCE
is set, then we have
Then, the push trigger will remove itself.
On the other hand, if SF_TRIG_PUSH_ONCE
is not set, then if FL_BASEVELOCITY
is set in the flags of the entity, the new velocity is given by
where is the current basevelocity of the entity. If FL_BASEVELOCITY
is not set for the entity, then we instead have
Subsequently, the FL_BASEVELOCITY
flag will be set for the entity, which is important for the player entity.
15.5.1. Boosting by rapid touching¶
There exists a function in the closed source engine code, SV_CheckMovingGround
, which is called unconditionally before all of player physics. At a high level, this function modifies the player velocity and/or basevelocity depending on various conditions. One such condition is when the player stands on a conveyor belt, which is described in func_conveyor. Another condition is when the FL_BASEVELOCITY
flag is not set in the player entity. In this case, the player velocity and basevelocity will be modified to be
Equation (15.1) may be referred to as the basevelocity exit equation. One frame after the player exits a push field (or any entity that imparts a basevelocity), the FL_BASEVELOCITY
flag will no longer be set by the trigger_push
. Since this flag is always reset every frame, (15.1) will be invoked in the beginning of the next frame. If the player is able to repeatedly enter and leave a push trigger, the basevelocity will be added to the player velocity rapidly, resulting in a massive acceleration. This has been used to great effect in many Half-Life speedruns, typically achieved by ducking and unducking rapidly above a trigger_push
, which changes the player hull repeatedly (see Ducking). If the ducking and unducking sequence is done at an extremely high frame rate, the resulting acceleration is one of the highest possible in the game.
15.6. trigger_hurt¶
The hurt trigger applies damage to entities that touch it. A hurt trigger always has a delay of half a second after it damages some entity.