Entity index Last edited 1 day ago2024-12-21 13:36:11 UTC

An "entity index" is basically an "unique identifier number" that both User posted image GoldSrc and User posted image Source give to entities whenever they are created. Whenever an entity is "freed" (deleted/killed), its "unique identifier number" becomes available for a future entity that needs to be created.

Indexes start from 0 and worldspawn (the world/map itself) always have this index. The next "n" indexes are reserved for clients (players and bots) where "n" is the maximum amount of clients allowed on the server. This is basically the maxplayers CVAR (Console VARiable) which is always "1" on singleplayer games/mods and likely a higher number for multiplayer ones.

In the context of singleplayer games/mods, this means that index "1" is the player itself and all the map's entities might start from "2". For multiplayer mods, the first player who join will get index "1", the second will get index "2", the third one will be "3" and so on.

The word "might" in the previous sentence is important as games/mods might need to create "runtime" entities. In GoldSrc, an example of such entity is soundent which is used to store "sound cues" (combat, danger...) for the AI. In Source, an example would be the "gamerules proxy entity" mostly used by multiplayer games/mods to transfer some information between the server and clients.

Overall, the initial entity index attribution can be summed up to "world" first, then "client(s)" second and finally "runtime/map entities ordered by creation by the level designer in the editor". Once again, this is a "overall" view, not an "exact" one as some entities might self delete after creation (like info_node on GoldSrc), some entities might create other entities when they spawn, map compilers could have performed some changes during the compile process (func_detail becoming world geometry on GoldSrc with VHLT based compilers), etc...

Programming specifics

When working with entity indexes, there are several considerations to take into account: Now that these considerations have been said, time to look how both engines provides utility functions/methods to work with entity indexes.

GoldSrc

On the client side, there is only one function available and that is struct cl_entity_s* gEngfuncs.GetEntityByIndex(int idx); which returns the entity (or NULL) that uses the index in parameter. Do note that you can just use cl_entity_t* instead of struct cl_entity_s*.

On the server side, you have:
// From "dlls/util.h"
inline int ENTINDEX(edict_t* pEdict) { return (*g_engfuncs.pfnIndexOfEdict)(pEdict); }
inline edict_t* INDEXENT(int iEdictNum) { return (*g_engfuncs.pfnPEntityOfEntIndex)(iEdictNum); }
The first function returns the entity index of the entity's edict_t* you're passing as parameter. The second function is the opposite way (retrieve the edict_t* from a specific entity index).

We need to talk about pfnPEntityOfEntIndex for a moment because there is a similar but different version called pfnPEntityOfEntIndexAllEntities. Both functions returns the edict_t* of the entity that has the entity index iEntIndex (or NULL if there is no entity at the desired entity index). The difference between these two functions is that the first one (pfnPEntityOfEntIndex) has a "bug" where the engine has a bogus "client check" and the last player in a multiplayer game/mod cannot be retrieved (the result is always NULL).

When Valve fixed this "bug", Counter-Strike: Condition Zero and many other games/mods were broken because some systems relied on this "bug" in order to work properly. This is why the "fix" has been removed from the first function and the second function (pfnPEntityOfEntIndexAllEntities) is a duplicate of the first one but with the "fix" applied. This way, games/mods that relied on the "bug" works without requiring an update and new mods can use the "fixed" variant.

If you are more curious about this story, you can consult this GitHub issue on Valve's Half-Life SDK repository.

Source

Thanks to Valve Developer Community wiki for this information.

On Source, the following functions/methods can be used depending on the "scope" you're working on:
// Client
C_BaseEntity* ClientEntityList().GetBaseEntity(int);

// Server
edict_t* INDEXENT(int iEdictNum);
int ENTINDEX(edict_t* pEdict);
CEntInfo* gEntList.GetEntInfoPtrByIndex(int);
// These two below are not available on Source 2010 (aka "Alien Swarm" branch)
int engine->IndexOfEdict(edict_t*);
edict_t* engine->PEntityOfEntIndex(int); // Only works for networked entities

// Shared
int CBaseEntity::entindex();

Comments

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