In the HL1 engine, it is safe to keep track of entities either by pointer, or by index. Entities (at least their edict_t) don't get relocated by the engine in memory. However, some slots in the entity list may be empty, because of entities that are created and destroyed dynamically. Typically, entity #0 is worldspawn, entities #1 to #maxClients are player slots (the listenserver being NOT NECESSARILY entity #1), entities from #maxClients+1 are the map's entities, beyond are the dynamically spawned ones and even beyond are the temporary entities.
Be aware that the HL1 engine can leave bad entity pointers around, i.e. edict_t pointers that are not NULL, that do point to somewhere, but where there's no more edict_t structure behind. That's why I highly recommend to check your edict_t pointers with FNullEnt() as often as possible. BE AWARE AGAIN though, that the FNullEnt() provided in the HL1 SDK is itself buggy! it does NOT make enough checks to test for an edict pointer's validity! You also have to check if the entity classname is defined, like this:
Code:
// fixed FNullEnt(). You can trust this one.
#define SAFE_FNullEnt(a) (((a) == NULL) \\
|| ((*g_engfuncs.pfnEntOffsetOfPEntity) (a) == 0) \\
|| ((a)->v.classname == NULL) \\
|| (STRING ((a)->v.classname)[0] == 0))
Use this one everywhere, but for checking the edict_t returned by pfnCreateFakeClient(), since for this one the "player" classname is not set yet (it is set during the game DLL's "player" call).