Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/twhl-community/halflife-unified-sdk/llms.txt

Use this file to discover all available pages before exploring further.

The Half-Life Unified SDK introduces an entity dictionary that stores the complete set of entities available in a mod and exposes them for programmatic creation. Unlike the original engine dispatch table, the dictionary is a runtime structure that can be queried, iterated, and specialised by entity base class — making it possible to safely create entities by name in C++ code and to restrict creation to entities of a particular type.

Relevant Headers

game/shared/entities/EntityDictionary.h

Registering an Entity

Registering an entity with the dictionary works the same way as in the original Half-Life SDK, using the LINK_ENTITY_TO_CLASS macro:
LINK_ENTITY_TO_CLASS(info_player_start, CPointEntity);
The first argument is the entity class name used by level designers in the FGD and in map files. The second argument is the name of the C++ class that implements the entity. The C++ class must:
  • Inherit from CBaseEntity or one of its derived classes.
  • Be a non-abstract class.
  • Have a public, parameterless constructor.

Creating an Entity Instance

Via the Engine (Automatic)

The engine creates entity instances automatically in two situations:
  1. When a new map starts and all entities defined in the map file are spawned.
  2. When a save game is loaded and all entities are created before restoration data is applied.

Programmatically via the Entity Dictionary

To create an entity from C++ code, use g_EntityDictionary->Create<T>():
CCrossbowBolt* pBolt = g_EntityDictionary->Create<CCrossbowBolt>("crossbow_bolt");
pBolt->Spawn();
This creates an instance of the entity registered under the name "crossbow_bolt" and returns it as a pointer to the specified type. In debug builds the dictionary validates that the requested type is compatible and emits a warning if it is not.

Via CBaseEntity::Create

Most game code creates entities through the higher-level CBaseEntity::Create helper, which sets the origin, angles, and owner automatically:
CBaseEntity::Create(STRING(m_iszSpawnObject), VecBModelOrigin(this), pev->angles, this);
Spawn must be called after all of an entity’s keyvalues have been set. CBaseEntity::Create handles this internally; if you call g_EntityDictionary->Create directly you are responsible for calling Spawn at the right time.

Destroying an Entity

Call UTIL_Remove to destroy an entity:
UTIL_Remove(entityToDestroy);
UTIL_Remove is safe to call multiple times on the same entity. After calling it, ensure any pointers to that entity are cleared.
If you need to hold a reference to an entity for any period of time, store it in an EHANDLE rather than a raw pointer. EHANDLE automatically becomes null when the referenced entity is destroyed, preventing dangling pointer access.

Additional (Typed) Dictionaries

In addition to the global g_EntityDictionary (typed as EntityDictionary<CBaseEntity>), the SDK registers specialised dictionaries for entity sub-types. This allows code to create entities while restricting the result to a specific base class. The SDK provides the following global dictionary pointers out of the box:
inline EntityDictionary<CBaseEntity>*      g_EntityDictionary = EntityDictionaryLocator<CBaseEntity>::Get();
inline EntityDictionary<CBaseItem>*        g_ItemDictionary   = EntityDictionaryLocator<CBaseItem>::Get();
inline EntityDictionary<CBasePlayerWeapon>* g_WeaponDictionary = EntityDictionaryLocator<CBasePlayerWeapon>::Get();
When registering an entity descriptor you can add it to multiple dictionaries at once:
detail::RegisterEntityDescriptorToDictionaries(descriptor,
    EntityDictionaryLocator<CBaseEntity>::Get(),
    EntityDictionaryLocator<CBaseItem>::Get(),
    EntityDictionaryLocator<CBasePlayerWeapon>::Get());
A class only registers itself to dictionaries whose template type it is derived from. Attempting to create an entity that is not derived from the dictionary’s type returns a null pointer:
auto entity = g_ItemDictionary->Create("monster_gman");

if (FNullEnt(entity))
{
    return;
}

DispatchSpawn(entity->edict());

Order of Operations: Entity Creation

Understanding the lifecycle of an entity is important when writing entity code.
StepWhat happens
ConstructorCalled first. Do not put any logic here — pev is not yet valid.
pev initialisationThe entity’s pev (entvars_t) pointer is set up.
OnCreateCalled next. Set default values here (health, model, etc.).
KeyValue callsCalled once for each keyvalue set by the level designer. Keyvalues in entvars_t are handled by DispatchKeyValue; others may be delegated to RequiredKeyValue.
SpawnCalled by DispatchSpawn or by programmatic creation code. Finishes entity setup.
ActivateCalled during ServerActivate for all non-dormant entities when a new level starts or a save game loads. Allows entities to find each other by name. Not called for programmatically created entities.
First thinkEntities that set an immediate think function complete any remaining setup in the first few frames after startup.

Order of Operations: Entity Destruction

StepWhat happens
UpdateOnRemoveCalled when UTIL_Remove is used. Run cleanup code that only applies to programmatic removal here.
OnDestroyCalled regardless of removal method. Put general cleanup code here.
DestructorCalled last. The entity is no longer valid — no logic should execute here.
Entities are also destroyed during level changes. Always test your cleanup logic with save games as well as fresh level loads to ensure it runs correctly in both cases.

Build docs developers (and LLMs) love