16. Func entities

We will refer to entities with class name prefixed by func_ as func entities. There are a few func entities in Half-Life that are of interest to speedrunners. A complete description of all entities in Half-Life is beyond the scope of this documentation. The reader is invited to study the Half-Life SDK code and reverse engineer for further investigations.

16.1. func_pushable

_images/movable-box.jpg

Fig. 16.1. A movable box (func_pushable).

func_pushable is a rather common entity type in Half-Life. They are characterised by their ability to be moved by the player. Whenever a pushable entity is spotted, it means the potential to go extraordinarily fast (see Object manoeuvre). It is perhaps for this reason that later Half-Life mappers and modders tend to avoid pushable entities as much as possible, seeing the exploitations speedrunners have done with these innocuous objects. Indeed, in modern Half-Life maps, movable objects tend to be much harder to come by than the vanilla Half-Life maps.

16.1.1. Object manoeuvre

Object manoeuvring is the act of boosting around by means of +use on a movable entity. Before describing the specific tricks to perform object manoeuvring, we must first write down the general equation for the player and object velocities during a +use operation on a movable entity.

Recall that is the velocity clip function that limits each component of some vector to the value of sv_maxvelocity. Define the movable entity’s velocity in the horizontal plane that is used to update the entity position. Namely, if is the movable entity’s position, then in the next frame . Define the player velocity in the horizontal plane that is used to update the player horizontal position.

When +use is held, the initial velocities may be denoted as and . At any frame , and are defined to be the final velocities in the frame used for position update. For example, is the final velocity in the first frame at which +use is issued. Then for all , we have

(16.1)

On the other hand, the player velocity is computed as

(16.2)

Note that these equations are as general as they reasonably can, but they did not account for other phenomena that can affect the velocities, such as collision events. is a scale factor that depends on a few circumstances. If the player is standing on the ground and not in water, then .

Suppose -use is issued in the current frame . Then the final velocity in the current frame is

Observe that if we ignore , and assume (which is true the vast majority of the time), then , and the factor of is gone from the usual case. In other words, by releasing the use key, the player velocity is “recovered” to equal the object velocity.

To observe the behaviour of , we first ignore the velocity clip for a moment, and assume , and . Then the equations may be written as

Here we immediately see that the growth of the object speed is exponential, and so is the growth of the player speed, albeit at a slower rate and much lower speed than the corresponding object speed. This is why when one holds +use on an object for too long, the object will quickly move past the player until it is out of the use radius, due to it very high speed relative to the player.

16.2. func_breakable

16.2.1. Item duplication

Box item duplication is a trick useful for duplicating items dropped by crates. To perform this trick, we simply fire a shotgun simultaneously at two adjacent crates, one of which is explosive and the other will spawn the desired item upon destruction. When the explosive crate explodes and the target crate is destroyed simultaneously, the desired item will be duplicated. Most people would be able to conjure up a guess on how the trick might have worked. Namely, the crate in question breaks twice due to damage inflicted simultanously by the explosive crate and the shotgun pellets. Such an explanation would imply that any type of simultaneous damage inflicted in the same frame can trigger the glitch. Unfortunately, this intuitive guess is false. For instance, if we shoot the crate while a hand grenade explodes in the same frame, the box items will never be duplicated. The actual reason for why this trick works more complicated, and requires many different moving parts to work together in just the right way.

The core of the trick lies in the multidamage mechanism, described in Damage system previously. When an explosive crate explodes, a radius damage is inflicted onto surrounding entities. The function responsible for this is RadiusDamage. It searches for entities within a radius. For each entity, it usually calls ClearMultiDamage, followed by TraceAttack which just calls AddMultiDamage to the entity, followed by ApplyMultiDamage.

When a shotgun fires, FireBulletsPlayer is called. At the very beginning of this function, ClearMultiDamage is called, followed by a loop in which each pellet is randomly assigned a direction to simulate spread, then a tracing function is called for each pellet to determine what entity has been hit. Then, this entity’s TraceAttack is called. After the loop ends, the function concludes with a call to ApplyMultiDamage.

We can now make use of the knowledge we learnt above to understand how the trick works. Suppose we have two crates, one explosive and the other carrying the desired item. To perform the trick we fire the shotgun so that both crates are simultaneously broken. First of all, FireBulletsPlayer will be called. The ClearMultiDamage at the beginning of the function ensures that any leftover multidamage will not interfere with our current situation. Suppose the first few pellets strike the explosive crate. For each of these pellets, TraceAttack is being called on the explosive crate, which in turns call AddMultiDamage, which accumulates the damage dealt to the explosive crate. Suppose now the loop reaches a pellet that is set to deal damage on the desired crate instead of the explosive crate. As a result, TraceAttack and so AddMultiDamage is called on the desired crate, which is a different entity than the explosive crate. Since the desired crate is not the same as gMultiDamage->pEntity, AddMultiDamage will call ApplyMultiDamage to inflict the accumulated damage on the explosive crate. This is the moment where the explosive crate explodes.

The explosive crate calls RadiusDamage which in turn inflicts damage onto the desired crate. When this happens, the TakeDamage associated with the desired crate will be called, which causes the associated item to spawn. The desired crate now turns into SOLID_NOT. Once RadiusDamage returns, we go back to the last AddMultiDamage call mentioned in the previous paragraph. Here, gMultiDamage->pEntity will be made to point to the desired crate, and the damage for the current pellet will be assigned to gMultiDamage->amount.

Remember the FireBulletsPlayer at the beginning of this series of events? The loop in this function will continue to iterate. However, since the desired crate is of SOLID_NOT type, the tracing functions will completely miss the crate. In other words, the rest of the shotgun pellets will not hit the desired crate, and that overall only one pellet hits the crate. When the loop finally completes, the final ApplyMultiDamage then inflicts the damage dealt by the one pellet onto the desired crate. Since ApplyMultiDamage does not rely on tracing functions to determine the target entity, but rather, it uses gMultiDamage->pEntity set a moment ago, the damage will be successfully inflicted, which triggers the second TakeDamage call for the desired crate. This causes it to spawn the associated item again. This concludes the box item duplication trick.

One assumption we made in the description above is that the loop in FireBulletsPlayer breaks the explosion crate first. If this is not the case, then the item will not be duplicated. To see this, notice that the desired crate becomes SOLID_NOT as soon as the first set of pellets breaks it, which causes the later explosion to miss the crate in the RadiusDamage.

So why does shooting the target crate when a grenade explodes not work? To see this, suppose the grenade explodes first. The grenade will call RadiusDamage to inflict blast damage onto the target crate. After that, the crate becomes SOLID_NOT. All of the bullets will therefore miss the crate. On the other hand, suppose the bullets hit the crate first. The crate will then break and becomes SOLID_NOT again. When the grenade later calls RadiusDamage, the tracing functions within RadiusDamage will again miss the crate.

To put it simply, this trick does not work in cases like this because usually there is no way for the second damage to find the crate, since they depend on tracing functions and they do not save the pointer to the desired crate before the crate becomes SOLID_NOT.

16.3. func_rotating

16.4. func_friction

The func_friction entity is associated with the CFrictionModifier class defined in triggers.cpp. However, it is not a typical trigger because it inherits from CBaseEntity and not CBaseTrigger or CBaseToggle. This entity sets the friction modifier of any entity that touches it to a value specified by the map designer. This entity ignores entities of MOVETYPE_BOUNCEMISSILE and MOVETYPE_BOUNCE, however. As a result, the friction modifier of many common entities is left unchanged on touch, including all grenades, snarks, and gibs.

The func_friction entity serves two purposes. The obvious one is to change the friction value of entities touching it. For example, a slippery wet floor may be simulated by placing a func_friction on the floor with a fractional friction modifier. The second less obvious purpose is to make the player bounce off other solid entities when touching it. Indeed, recall from Collision that the bounce coefficient is affected by the player friction modifier. Sometimes, the increased bounce may be an unintended side effect.

16.5. func_conveyor

The conveyor entity is a common sight in the Residue Processing chapter. Behaviourally it is reminiscent of the push trigger (see trigger_push), in that it imparts some base velocity to entities that intersect with it and meet certain necessary conditions. The conveyor entity is the only entity that has the FL_CONVEYOR flag set. When the player stands on another entity with that flag set, SV_CheckMovingGround in the engine will modify the player’s basevelocity.

_images/conveyor-residue-processing.jpg

Fig. 16.2. A famous scene of the conveyor belts in the Residue Processing chapter.

Let the player’s basevelocity and the “velocity” of the conveyor. If the player already has FL_BASEVELOCITY set, the new player basevelocity is given by

If FL_BASEVELOCITY is not set for the player, however, the new basevelocity will be

In either of these cases, the FL_BASEVELOCITY flag will be set for the player.

Similar to the push trigger, whenever the player leaves a conveyor belt, the player velocity and basevelocity will be modified by (15.1). This can result in some additional acceleration at least, and potentially massive acceleration. One way to achieve this is to ducktap repeatedly off a conveyor belt. If there is very small clearance above the player, the player could also rapidly jump off the conveyor belt to leave and touch the conveyor belt repeatedly.