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.


Fig. 15.1. trigger_once and trigger_hurt in c3a2c displayed using a custom mod.

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.