Entities

How CS2 models players and other entities, and how the SDK exposes them as typed schema classes you read, write and call helpers on.

Almost everything on a CS2 server is an entity: players, weapons, grenades, doors, triggers, the game rules, even a Panorama HUD layout. The SDK gives you every one of them as a C++ class generated from the game's own schema, so you work with CCSPlayerPawn* and pPawn->m_iHealth instead of raw offsets.

This section covers finding entities, reading and writing their fields, creating new ones, the common player helpers, entity I/O and damage.

Controller and pawn

A CS2 player is two entities:

EntityClassClassnameLives
ControllerCCSPlayerControllercs_player_controllerFrom connect to disconnect. Holds the name, SteamID, team, score, money.
PawnCCSPlayerPawnplayerThe body in the world. Holds health, armor, position, weapons. Replaced or reused across deaths and respawns.

The controller is the one to keep track of a player; the pawn is the one to act on when something happens to their body. They point at each other:

CCSPlayerPawn* pPawn = pController->GetPlayerPawn();          // m_hPlayerPawn
CCSPlayerController* pOwner = pPawn->GetController();         // m_hController
CCSPlayerController* pSame = CCSPlayerController::FromPawn(pPawn);

A dead or spectating player also has an observer pawn (CCSObserverPawn, GetObserverPawn()). CBasePlayerController::GetPawn() returns m_hPawn, whichever pawn the controller is currently driving, cast to CCSPlayerPawn* — when you mean "the player's body", use GetPlayerPawn().

Health lives on the pawn. The controller has m_iPawnHealth and m_bPawnIsAlive, which the game mirrors for the scoreboard; read them, but change health on the pawn.

Services

A lot of behaviour is not on the pawn or controller itself but on service objects hanging off them as pointers:

PointerClassFor
pPawn->m_pItemServices()CPlayer_ItemServicesGiveNamedItem, RemoveWeapons
pPawn->m_pWeaponServices()CPlayer_WeaponServicesm_hMyWeapons, m_hActiveWeapon, DropWeapon, SelectItem
pController->m_pInGameMoneyServices()CCSPlayerController_InGameMoneyServicesm_iAccount (money)

Any of these can be null on a pawn that does not have them, so check before use. In a script they are properties (pawn.m_pItemServices), and a missing one reads IsNull. Service fields are networked through the entity that owns them; writing one works exactly like writing a field on the entity (see Schema fields).

Indices, slots and handles

TypeWhat it isGet it
CEntityIndexPosition in the entity list (.Get() for the int). Reused after the entity is gone.pEntity->GetEntityIndex(), GetIndex() for the int
CPlayerSlotPlayer slot, 0-based. A controller's entity index is always slot + 1.pController->GetPlayerSlot(), GetSlot() for the int
CPlayerUserIdThe engine's userid, as game events carry it.pController->GetPlayerUserID(), GetUserID() for the int
CHandle<T>Index plus a serial number. Resolves to null once the entity is removed, even if the index is reused.pEntity->GetHandle()

The rule: a raw pointer is only good for the current call. Anything that crosses a frame — a timer, a NEXT_FRAME lambda, a map in your plugin — holds a CHandle and checks it when it comes back. A script has no raw pointers: every entity value already is a handle, kept as it is and checked with IsValid():

plugin.cpp
CHandle<CCSPlayerController> hPlayer = pController->GetHandle();

g_pSource2ModScheduler->AddTimer(3.0f, [hPlayer]()
{
    CCSPlayerController* pPlayer = hPlayer.Get();
    if (!pPlayer)
        return; // disconnected in the meantime

    pPlayer->PrintToChat("Three seconds later");
});

In C++, every generated entity class shadows GetHandle() with one returning a handle of its own type, so pController->GetHandle() is a CHandle<CCSPlayerController> without a template argument.

How the SDK exposes schema classes

The classes under source2mod/schema/entity/classes/ are generated from a dump of the game's schema by tools/schemagen/generator.py, one header per class, following the game's own inheritance (CCSPlayerPawn → CCSPlayerPawnBase → CBasePlayerPawn → … → CBaseEntity → CEntityInstance). Each class carries:

  • Fields — one SCHEMA_FIELD per schema member. The offset is looked up by name in the running game's schema the first time you touch it, so a CS2 update that moves a field does not break your binary.
  • Helpers — hand-written methods (GiveNamedItem, Teleport, PrintToChat, …) that call into the game. They are declared by the generator so they survive regeneration and implemented in the SDK's src/schema/entity/classes/*.cpp.
  • Static constructors — New(classname), FromIndex(index) and a typed GetHandle() on every entity class.

Scripts get the same classes as methodmaps of the same names and bases (source2mod/schema/classes.inc, generated too): fields are properties (pawn.m_iHealth), helpers are methods, and FromIndex is on CBaseEntity.

Include the header of the class you use; a script includes them all at once:

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

dynamic_cast works on game types: the SDK checks the cast against the engine's own RTTI and caches the answer, so a cast costs a few nanoseconds (see Casting). When you already know the type, static_cast is enough. In a script, check the classname (GetClassname(), e.g. "cs_player_controller") and view_as the methodmap.

The full list of classes, fields and helpers is in the schema reference; lookup and entity I/O functions are on ISource2ModEntities.

On this page