Finding Entities

Getting a player controller from a slot, userid, SteamID or pawn, and finding other entities by index, classname, name or crosshair.

Most plugin code starts from something that identifies an entity — a command caller's slot, a game event's userid, a SteamID from your database, an entity index — and needs the typed object. This page lists the ways in.

Players

CCSPlayerController has static lookups for every way a player is identified:

#include "source2mod/schema/entity/classes/CCSPlayerController.h"

static CCSPlayerController* FromSlot(int iSlot);
static CCSPlayerController* FromSlot(CPlayerSlot slot);
static CCSPlayerController* FromUserId(int iUserId);
static CCSPlayerController* FromUserId(CPlayerUserId userId);
static CCSPlayerController* FromSteamId(uint64 uSteamId);
static CCSPlayerController* FromSteamId(CSteamID steamId);
static CCSPlayerController* FromPawn(CCSPlayerPawn* pPawn);

All of them return nullptr when nothing matches. Scripts have the same statics on the CCSPlayerController methodmap — FromSlot(CPlayerSlot), FromUserId(int), FromSteamId (the SteamID64 as decimal text) and FromPawn — returning NULL_ENTITY instead.

  • FromSlot reads the entity at index slot + 1 — controllers always occupy indices 1 to 64. The CPlayerSlot overload also returns null for an invalid slot (-1, which is what the server console has).
  • FromUserId and FromSteamId walk the slots up to maxClients. Bots have a SteamID of 0.
  • FromPawn follows the pawn's m_hController handle; the pawn must not be null.

The command context gives you the caller's slot. The sample plugin's helper:

plugin.cpp
// A controller for a player, nullptr for the server console.
static CCSPlayerController* CallerOf(const Source2ModCommandContext& context)
{
    const CPlayerSlot slot = context.GetPlayerSlot();
    return slot.IsValid() ? CCSPlayerController::FromSlot(slot) : nullptr;
}

For getting the player out of a game event, see Events.

Iterating players

There is no player list to ask for — loop over the slots and skip the empty ones:

for (int i = 0; i < GetGlobalVars()->maxClients; ++i)
{
    CCSPlayerController* pPlayer = CCSPlayerController::FromSlot(i);
    if (!pPlayer || pPlayer->m_iConnected() != PlayerConnectedState::Connected)
        continue;

    if (pPlayer->IsBot())
        continue;

    // ...
}

A controller exists from the moment a client starts connecting until it has fully left, so m_iConnected is worth checking when you are about to message or act on the player (a script can ask Source2ModClients.IsInGame(slot)). For "alive players", also check m_bPawnIsAlive() and that GetPlayerPawn() is not null.

From controller to everything else

You wantCall
The bodyGetPlayerPawn() → CCSPlayerPawn*
The observer pawnGetObserverPawn() → CCSObserverPawn*
SlotGetSlot() (int) / GetPlayerSlot() (CPlayerSlot)
UseridGetUserID() (int) / GetPlayerUserID() (CPlayerUserId)
SteamIDGetSteamID() (uint64) / GetPlayerSteamID() (CSteamID)
NameGetPlayerName()
IP addressGetIpAddress() → CUtlString, without the port
Engine clientGetServerSideClient() → CServerSideClient*

In a script, the text getters fill a buffer (GetPlayerName(name, sizeof(name)), GetSteamID, GetIpAddress); there is no GetServerSideClient().

Any entity by index

CBaseEntity::FromIndex<T> looks an entity up by index and casts it to the type you name; every generated class also has a non-template FromIndex returning its own type. A script has CBaseEntity.FromIndex(int) and view_as for the type:

CBaseEntity* pEntity = CBaseEntity::FromIndex<CBaseEntity>(nIndex);
CCSPlayerPawn* pPawn = CCSPlayerPawn::FromIndex(nIndex);   // same, typed

Both take an int or a CEntityIndex. No type check is done — the cast is only right if the entity at that index really is that class. Check GetClassname() when you are not sure.

By classname, name or crosshair

These live on ISource2ModEntities, with macros that fill in the g_pSource2ModEntities call; a script calls the Source2ModEntities statics:

#include "source2mod/ISource2ModEntities.h"

FIND_ENTITY_BY_CLASSNAME(start, "func_breakable")  // FindEntityByClassname
FIND_ENTITY_BY_NAME(start, "door_main")            // FindEntityByName (targetname)
FIND_PICKER_ENTITY(pController)                    // FindPickerEntity: under the crosshair
GET_GAME_RULES()                                   // the live CCSGameRules, or null

The Find* functions return one match at a time. Pass nullptr (NULL_ENTITY in a script) to start and the previous match to continue:

plugin.cpp
int nBroken = 0;

CBaseEntity* pEntity = nullptr;
while ((pEntity = FIND_ENTITY_BY_CLASSNAME(pEntity, "func_breakable")) != nullptr)
{
    pEntity->AcceptInput("Break");
    nBroken++;
}

FindEntityByName has optional searching-entity, activator, caller and filter arguments, the same as the engine function it wraps (in a script, all but the filter).

All at once

UTIL_FindAllEntitiesByDesignerName<T> (in ISource2ModEntities.h) returns a std::vector<T*> of every live entity whose designer name matches exactly. It walks the entity system's active list directly. A script gets the same as an ArrayList of entities from Source2ModEntities.FindAllEntitiesByDesignerName, and closes it:

for (auto* pTeam : UTIL_FindAllEntitiesByDesignerName<CTeam>("cs_team_manager"))
{
    // ...
}

for (auto* pProp : UTIL_FindAllEntitiesByDesignerName<CDynamicProp>("prop_dynamic"))
    pProp->AcceptInput("Kill");

Like FromIndex, the cast is unchecked — keep the name and the type consistent.

Watching entities come and go

Instead of searching, you can be told. Implement IEntityListener (from the engine's entity2/entitysystem.h) and register it with ADD_ENTITY_LISTENER. A script passes one function per callback to Source2ModEntities.AddEntityListener (INVALID_FUNCTION for the ones it does not need):

plugin.h
class MyPlugin : public ISource2ModPlugin, public IEntityListener
{
    void OnEntityCreated(CEntityInstance* pEntity) override;
    void OnEntitySpawned(CEntityInstance* pEntity) override;
    void OnEntityDeleted(CEntityInstance* pEntity) override;
    void OnEntityParentChanged(CEntityInstance* pEntity, CEntityInstance* pNewParent) override;
};
plugin.cpp
ADD_ENTITY_LISTENER(this);   // in Load()

OnEntityCreated is early — the keyvalues have not been applied and the entity has not spawned. OnEntitySpawned is usually the one you want. Some entities are filled in by the game right after they spawn, so a change made there can be overwritten; the sample plugin waits one frame, through a handle:

plugin.cpp
void SamplePlugin::OnEntitySpawned(CEntityInstance* pEntity)
{
    if (V_strcmp(pEntity->GetClassname(), "smokegrenade_projectile") != 0)
        return;

    CHandle<CSmokeGrenadeProjectile> hSmoke = static_cast<CSmokeGrenadeProjectile*>(pEntity)->GetHandle();

    NEXT_FRAME([hSmoke]()
    {
        if (CSmokeGrenadeProjectile* pSmoke = hSmoke.Get())
            pSmoke->m_vSmokeColor = Vector(170.0f, 0.0f, 255.0f);
    });
}

AddEntityListener(IEntityListener* pListener) takes no plugin id; the core works out which plugin the listener belongs to from the object's vtable, i.e. the library its class lives in. The listener is removed for you when that plugin unloads; REMOVE_ENTITY_LISTENER(this) removes it earlier. In a script, the id AddEntityListener returns is what Source2ModEntities.RemoveEntityListener takes, and the listener also goes when the plugin unloads.

On this page