In the previous chapter, we covered weapons. Weapons are a special type of entity, but an entity nonetheless.
In this chapter, we will look at entities more generally, and see how they work. Later on, we'll dive into writing some useful entities for mappers.
*edict_t* vs. entity classes
Let's make this clear. The engine doesn't know the difference between a
. Instead, the engine only knows one form of entity. And that is
link_t area; // linked to a division node or leaf
int headnode; // -1 to use normal leaf check
int num_leafs; // How many leaves the entity occupies, shouldn't be more than MAX_ENT_LEAFS
float freetime; // sv.time when the object was freed
void* pvPrivateData; // Alloced and freed by engine, used by DLLs; pointer to a HL SDK entity
entvars_t v; // Common entity variables
It's basically an entity dictionary, containing data such as whether the entity's memory is free, its "serial number", its link to a BSP leaf, a pointer to the game entity (an instance of an HL SDK entity class), and an entvars_t variable to hold common entity variables.
Generally, you won't use
, there's a reference to this
"pev" stands for "Pointer to Entity Variables", and it is essentially a group of variables that is common to all entities.
is defined in
, and constants that can be used with variables from entvars_t are in
Some of the most important variables from
classname - entity's classname
targetname - entity's name
target - target entity's name
origin - entity's current position
velocity - entity's current velocity
movedir - entity's current movement direction
angles - entity's current angles
avelocity - angular velocity
model - path to the model
nextthink - next time the entity will call its think callback
movetype - movement type of the entity, which can be:
MOVETYPE_NONE - entity won't move
MOVETYPE_WALK - entity will use walk physics (players only)
MOVETYPE_STEP - entity will use walk physics for monsters
MOVETYPE_FLY - entity not be affected by gravity
MOVETYPE_TOSS - affected by gravity
MOVETYPE_PUSH - entity can push other entities
MOVETYPE_NOCLIP - not affected by gravity, passes through everything
MOVETYPE_FLYMISSILE - same as
MOVETYPE_FLY but with extra size so it can hit monsters
MOVETYPE_BOUNCE - affected by gravity, bounces from the surface depending on
MOVETYPE_BOUNCEMISSILE - combination of FLYMISSILE and BOUNCE
MOVETYPE_FOLLOW - follows pev->aiment
MOVETYPE_PUSHSTEP - a combination of PUSH and STEP for brush entities, only works with
solid - solidity mode of the entity, which can be:
SOLID_NOT - non-solid, everything passes through it
SOLID_TRIGGER - calls Touch, otherwise non-solid
SOLID_BBOX - uses its axis-aligned bounding box for collisions (this collision box cannot rotate, keep in mind), calls Touch when an entity touches one of its edges, can call Blocked
SOLID_SLIDEBOX - can only call Touch when on the ground, calls it when an entity touches one of its edges
SOLID_BSP - uses BSP cliphulls of the brush model for collisions, Touch on edge and can call Blocked
rendermode - render mode of this entity, can be:
kRenderNormal - Normal
kRenderTransColor - Color
kRenderTransTexture - Texture
kRenderGlow - Glow
kRenderTransAlpha - Solid
kRenderTransAdd - Additive
renderamt - render amount of this entity (typically wrongly named in FGDs as "FX Amount")
rendercolor - render colour
renderfx - render effect (look at
const.h, it's got many of them)
spawnflags - flags set in the map editor (in the Flags tab)
flags - temporary entity flags. The constants are defined in const.h, and here are some of the more important ones:
FL_SKIPLOCALHOST - Tells the engine not to transmit the entity to the local host
FL_ONGROUND - Indicates whether the entity is on the ground or not; can be overridden
FL_FAKECLIENT - Indicates that this is a bot; set this while writing your own bots
FL_FLOAT - Tells the engine to apply buoyancy to the entity when in water
FL_ALWAYSTHINK - Calls Think every frame
FL_MONSTERCLIP - Only collide with and block entities that have this flag set
FL_ONTRAIN - Ignore the player's movement commands; useful to set this when controlling a vehicle
FL_WORLDBRUSH - Tells the engine that this is an entirely static brush entity, almost as if it were worldspawn
FL_KILLME - Marks the entity to be deleted from memory; this is set by
FL_DORMANT - Doesn't send updates to the client, thus saving on bandwidth; the entity cannot call Think if this is set
Uninitialised values will be 0, as this structure is memset'ed by the engine
while spawning the entity.
Certain utility functions will accept
as a parameter, so you'll need to convert your
or any entity class you're using into
. While there's no direct conversion, you can use the
utility function to 'convert' an
. For example:
We'll discuss utility functions later on.
You may have noticed several variables of the
type. This is not
Beginners get very confused by
, since it's essentially an
, so I'll briefly explain it here.
The engine has a string allocator, which is essentially a very large string.
You can imagine it this way:
H e l l o w o r l d \0 f u n c _ w a l l \0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
If we wanted to allocate a new string there via
, like so:
string_t something = ALLOC_STRING( "something" );
...it'd allocate a string and return some number. If we had a
variable with a value of 11,
would return "func_wall" in the example above. In reality, you'd get garbage at low string_t values, and usable strings usually show up around 250 million.
The mother of all entities.
As mentioned previously, every entity in the HL SDK inherits from
. We won't cover all of its methods and members, but we will go through the most important ones.
Called for every entity in the map by the engine.
Entities override this so they can load model, sound and other files that they may use.
Files can be precached with the following macros:
- precaches studio models
- precaches audio files
- precaches any file, you can even precache a whole .bsp if you wanted to
Handles map keyvalues and assigns values from them to member variables. The engine calls this multiple times until it covers all keyvalues of every entity.Parameters:
*pkvd* - the data of one keyvalue
This is how keyvalues are typically handled:
if (FStrEq(pkvd->szKeyName, "myKeyvalue"))
m_myVariable = atoi(pkvd->szValue);
pkvd->fHandled = TRUE; // This line is NOT needed if using Solokiller's Half-Life: Updated SDK!
to all possible keyvalues this entity may have, and then convert the
string into a value of the type we need.
If no match is found, then go back up to the KeyValue method of our superclass.
are the key methods for transferring custom data fields across save files. If an entity has its own members, but doesn't save/restore, then it is most likely to break while loading a save file, or across level transitions.
A save/restore is fully set up when an entity class has defined and implemented a save-restore table, as well as the save-restore methods.
Inside the entity class, you override the
methods as well as declaring the save-restore table (
virtual bool Save( CSave &save );
virtual bool Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData;
Outside of the entity class, you define the save-restore table like this:
Note: Valve generally placed this near either the class declaration or the
TYPEDESCRIPTION CPendulum::m_SaveData =
DEFINE_FIELD( CPendulum, m_accel, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_distance, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_time, FIELD_TIME ),
DEFINE_FIELD( CPendulum, m_damp, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_maxSpeed, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_dampSpeed, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_center, FIELD_VECTOR ),
DEFINE_FIELD( CPendulum, m_start, FIELD_VECTOR ),
IMPLEMENT_SAVERESTORE( CPendulum, CBaseEntity );
/LINK_ENTITY_TO_CLASS/ line, but it can be placed anywhere.
requires 3 parameters: the class being saved/restored, the attribute being saved/restored and its type. The following table contains all available types of variables you can save and restore. The types with an asterisk (
) are the ones you will see and likely use very often in the HL SDK :
|FIELD_FLOAT*||A floating point value (|
75.5) that is not related to time (use
FIELD_TIME for that purpose)
|FIELD_STRING*||A string ID (|
string_t - often the result of
|FIELD_ENTITY||An entity offset (|
|FIELD_CLASSPTR*||An entity class pointer (like |
|FIELD_EHANDLE*||An entity handle (|
|FIELD_EVARS||An entity variables pointer (|
|FIELD_EDICT||An entity dictionary pointer (|
|FIELD_VECTOR*||A vector (|
vec3_t, array of 3
|FIELD_POSITION_VECTOR||A world coordinate (fixed up across level transitions "automagically")|
|FIELD_POINTER||Arbitrary data pointer (scheduled to be removed by Valve but seems that isn't the case)|
|FIELD_INTEGER*||An integer (|
|FIELD_FUNCTION||A class function pointer (like |
|FIELD_BOOLEAN*||A boolean value.|
|FIELD_SHORT||A 2 byte integer value.|
|FIELD_CHARACTER||A single byte value.|
|FIELD_TIME*||Same as |
FIELD_FLOAT but for variables related to time (like weapons reload time), usually a floating point value that has a relation to the game time (
|FIELD_MODELNAME||An engine string that is a model's name (requires precaching).|
|FIELD_SOUNDNAME||Same as previous one but for sounds.|
The last line,
requires 2 parameters: the class being saved and its parent. It tells the save/restore code to take care of your entity variables as well as the parent's ones. This is handy because it avoids having to redefine every variable from the parent(s).
A way of storing "capability flags" for entities. Entities can override this to return certain constants that will make the entity usable by the player, or held down continuously, transferable between levels etc.
The constants can be:
- unknown and unused
- the entity will transfer across level transitions
- calls Spawn right after Restore, i.e. after loading a savefile
- don't write into the savefile
- can be used by the player
- can be held by the player (such as levers and valves)
- can be toggled by the player
- receives +/- from the player, only used by func_tracktrain
- entity can be used as a master (multisource has this cap)
- entity always goes across transitions
You can add your own cap flags as well.
Called on every entity after the server is activated. It is used, for example, by CFuncTrain/func_train to teleport itself to the first path_corner as soon as the server starts.
has a lot of overrideable methods, but some of these are modular - they can be changed dynamically at runtime.
One of those is Think:
virtual void Think( void )
if ( m_pfnThink )
These methods actually call callbacks.
void (CBaseEntity ::*m_pfnThink)(void);
void (CBaseEntity ::*m_pfnTouch)( CBaseEntity *pOther );
void (CBaseEntity ::*m_pfnUse)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void (CBaseEntity ::*m_pfnBlocked)( CBaseEntity *pOther );
Keep in mind, however, that you don't really have to use these, and for simple entities, you can just override these methods without consequences.
Called every time period, or every tick, this method is often used by entities to perform real-time or otherwise periodic logic, e.g. check if a certain entity is within radius every 0.5 seconds.
To set a think callback, use the
To set the next time an entity will "think", set the
pev->nextthink = gpGlobals->time + 0.5; // think every 0.5 seconds
Called every time the entity is touched by another entity. Entities such as trigger_once absolutely rely on this to work.
To set a touch callback, use the
*pOther* - entity that touched this entity
Called either by players when pressing the use button aiming at an entity (if the respective object cap is enabled), or by other entities that have the ability to trigger other entities. For example, when a func_button triggers a func_door, it is calling the func_door's Use method.
To set the use callback, use the
pActivator - entity that started the trigger sequence
pCaller - entity that actually fired this entity
useType - type of usage: USE_TOGGLE, USE_ON, USE_OFF and USE_SET
value - the value sent from pCaller, typically used only in func_tracktrain
Called every time an entity blocks this entity.
This is essentially how doors and trains damage and gib things. They apply damage to any entity that blocked them.Parameters:
pOther - entity that blocked this entity
These methods are useful in certain situations.
Allocates a new entity of any classname and spawns it at given coordinates. Returns a CBaseEntity pointer to the newly spawned entity.Parameters:
szName - the classname itself, e.g. env_sprite, monster_zombie etc.
vecOrigin - the position at which it'll spawn
vecAngles - the angles it'll have after spawning
pentOwner - owner of the newly created entity (NULL by default)
Retrieves a class pointer from an existing entity. It is used to 'convert'
or entity indices into
pent - entity dictionary/edict to convert from (
pev - entity variables to convert from (
eoffset - entity index to retrieve from (
If you wonder how the engine actually calls these functions, it is done through the various Dispatch functions:
The game exports these to the engine when
is loaded, after which the engine calls them for each
in its internal array of entities.
Other base classes and important entity classes
- worldspawn entity
- base item class
- generic delay entity, can be spawned when an entity tries to trigger another entity with a delay
CBaseAnimating - entity that supports studio model animations, provides utilities for submodels, setting controllers etc.
CBasePlayerItem - generic player item
CBasePlayerWeapon - generic player weapon
CBaseToggle - generic toggleable entity, implements linear movement for trains, opening and closing mechanism for doors etc.
CBaseMonster - base monster class
CBasePlayer - player class
On the next page, you'll learn how to create a very basic entity that will print a mapper-defined message to the console.