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¶
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
On the other hand, the player velocity is computed as
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.
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.