Entity Programming - Overview Last edited 4 years ago2020-04-07 18:07:12 UTC

You are viewing an older revision of this wiki page. The current revision may be more detailed and up-to-date. Click here to see the current revision of this page.

Half-Life Programming

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, we'll create two point entities (trigger_timer and trigger_conprint - prints to the console) and a brush entity (a "counting impulse" button - the more the player presses E, the more it'll progress).

entvars_t vs. edict_t vs. entity classes

Let's make this clear. The engine doesn't know the difference between a CBaseEntity and a CFuncWall. Instead, the engine only knows one form of entity. And that is edict_t.
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, if it's a brush entity how many leaves it has, private entity data (to store member variables specific to the C++ classes of entities), and an entvars_t variable.
Generally, you won't use edict_t often.

In CBaseEntity, there's an entvars_t as well.
entvars_t *pev;
"pev" stands for "Pointer to Entity Variables", and it is essentially a set of variables that is common to all entities. entvars_t is defined in progdefs.h, and constants that can be used with variables from entvars_t are in const.h.
Some of the most important variables from entvars_t are: Certain utility functions will accept edict_t as a parameter, so you'll need to convert your CBaseEntity or CFuncWall or any entity class you're using into edict_t. While there's no direct conversion, you can use the ENT utility function to 'convert' an entvars_t into an edict_t. For example:
SET_MODEL(ENT(pev), STRING(pev->model));
We'll discuss utility functions later on.

string_t

You may have noticed several variables of the string_t type. This is not a C-string.

Beginners get very confused by string_t, since it's essentially an unsigned int, so I'll briefly explain it here.
The engine has a giant internal string. string_t is in fact just an offset into that giant 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 ALLOC_STRING, like so:
string_t something = ALLOC_STRING( "something" );
...it'd return 22. If we had a string_t variable with a value of 11, STRING would return 11. That is essentially what string_t is. Just an offset into the internal string.

CBaseEntity

The mother of all entities.
As mentioned previously, every entity in the HL SDK inherits from CBaseEntity. We won't cover all of its methods and members, but we will go through the most important ones.

Spawn

Called for every entity in the map by the engine.

Precache

Entities override this so they can load model, sound and other files that they may use.
Files can be precached with the following macros:
PRECACHE_MODEL - precaches studio models
PRECACHE_SOUND - precaches audio files
PRECACHE_GENERIC - precaches any file, you can even precache a whole .bsp if you wanted to

KeyValue

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: This is how keyvalues are typically handled:
if (FStrEq(pkvd->szKeyName, "myKeyvalue"))
{
    m_myVariable = atoi(pkvd->szValue);
    pkvd->fHandled = TRUE;
}

else
    CBaseEntity::KeyValue(pkvd);
We compare szKeyName to all possible keyvalues this entity may have, and then convert the szValue 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.

Save & Restore

Key methods for transferring custom data fields across savefiles. If an entity has its own members, but doesn't save/restore, then it is most likely to break while loading a savefile and 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:
virtual int        Save( CSave &save );
virtual int        Restore( CRestore &restore );
static    TYPEDESCRIPTION m_SaveData[];
Outside the entity class:
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 );

ObjectCaps

A way of storing read-only 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:
FCAP_CUSTOMSAVE - unknown and unused
FCAP_ACROSS_TRANSITION - the entity will transfer across level transitions
FCAP_MUST_SPAWN - calls Spawn right after Restore, i.e. after loading a savefile
FCAP_DONT_SAVE - don't write into the savefile
FCAP_IMPULSE_USE - can be used by the player
FCAP_CONTINUOUS_USE - can be held by the player (such as levers and valves)
FCAP_ONOFF_USE - can be toggled by the player
FCAP_DIRECTIONAL_USE - receives +/- from the player, only used by func_tracktrain
FCAP_MASTER - entity can be used as a master (multisource has this cap)
FCAP_FORCE_TRANSITION - entity always goes across transitions

Active

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. It is rarely used.

Modular methods

These methods are actually 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 );
This means that each can be changed at any time.

Think

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 SetThink macro.

To set the next time an entity will "think", set the pev->nextthink variable.
Example:
pev->nextthink = gpGlobals->time + 0.5; // think every 0.5 seconds

Touch

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 SetTouch macro.

Parameters:

Use

Called either by players when USE-ing an entity, 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 SetUse macro.

Parameters:

Blocked

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:

Static methods

These methods are useful in certain situations.

Create

Allocates a new entity of any classname and spawns it at given coordinates. Returns a CBaseEntity pointer to the newly spawned entity.

Parameters:

Instance

Retrieves private data from an existing entity. It is used to 'convert' edict_t*, entvars_t* or entity indices into CBaseEntity*.

Parameters:

Dispatch functions

If you wonder how the engine actually calls these functions, it is done through the Dispatch functions:
DispatchSpawn
DispatchKeyValue
DispatchTouch
DispatchUse
DispatchThink
DispatchBlocked
DispatchSave
DispatchRestore
DispatchObjectCollsionBox
It locates their addresses when it loads hl.dll, and then calls them for each edict_t in its internal array of entities.

Other base classes and important entity classes

CWorld - worldspawn entity
CItem - base item class
CBaseDelay - generic delay entity, can be spawned when an entity tries to trigger another entity with a delay On the next page, you'll learn how to create a very basic entity that will print a mapper-defined message to the console.
Note: The next page isn't actually written yet.

Comments

You must log in to post a comment. You can login or register a new account.