Schema fields
Every field on a CS2 entity is generated from the game’s own schema, dumped from the live SchemaSystem rather than hand-written. Offsets resolve at runtime by name, so a game update moves them without touching plugin code.
Player and Pawn arrive with accessors already applied:
import { Player } from '@s2script/cs2';
for (const p of Player.allConnected()) {
const pawn = p.pawn;
if (!pawn) continue;
pawn.health = 100;
const where = pawn.origin; // Vector | null
} Reads return null once a ref goes stale — never garbage. Writes call notifyStateChanged for you.
Entities you create
createEntity returns a bare EntityRef with no schema fields on it. wrapEntity applies a class’s accessors to any ref:
import { wrapEntity } from '@s2script/cs2';
import { createEntity } from '@s2script/sdk/entity';
const prop = createEntity('prop_dynamic');
prop?.spawn();
const fields = wrapEntity('CBaseModelEntity', prop!);
fields.renderMode = 1; // kRenderNone The class name is checked against the generated set, and the return type follows from it — a typo is a compile error rather than an object with silently missing fields.
The wrapper is a view, not a copy: it holds the ref and reads through it on every access, so it stays correct as the entity changes and costs nothing to keep on a long-lived object.
367 entity classes are generated — everything deriving from CEntityInstance that has fields, plus CCSGameRules. Animgraph, mesh, particle and scene internals are excluded; nothing holds an EntityRef to those.
Embedded structs
A field whose type is a struct embedded in the entity becomes a nested accessor:
const glow = wrapEntity('CBaseModelEntity', ref).glow;
glow.glowing = true;
glow.glowType = 3;
glow.glowRange = 5000; Nesting works at any depth, because an embedded struct is just another (ref, base offset) pair:
const e = wrapEntity('CRagdollProp', ref);
e.collision.collisionGroup = 6; // COLLISION_GROUP_DEBRIS
e.collision.collisionAttribute.collisionGroup = 6; // struct inside a struct
e.collision.solidType = 6; // SOLID_VPHYSICS An embedded struct is only reachable through its owner — it has no entity of its own, only an offset inside one, so wrapEntity does not accept its name.
If either the owner’s offset or the field’s offset fails to resolve, the read returns null and the write is dropped. It never adds a failed lookup to a valid base and reads whatever happens to be next in memory.
Value wrappers are flattened
Some schema types exist only to name a scalar — a struct whose single field is m_Value. GameTime_t and GameTick_t are these, and they are the majority of embedded fields on a pawn. They read as plain numbers:
pawn.deathTime; // number | null — not { value: number }
pawn.createTime; A struct with one named field keeps its object form, because the name carries meaning that flattening would throw away.
Enums
Enums are integers of the width their binding declares, read unsigned — the storage is unsigned and schema enums are non-negative, so a signed read would turn high-bit flag values negative.
const e = wrapEntity('CBaseEntity', ref);
e.moveType = 5; // MOVETYPE_FLY They are typed number, not named constants — the enumerator names are not yet dumped, so keep your own constants and comment the value.
An 8-byte enum is read-only, for the same reason uint64 is: there is no narrowing 64-bit writer.
Colours are packed integers
Color fields are a little-endian uint32 with red in the low byte:
function rgba(r: number, g: number, b: number, a: number): number {
return ((r & 0xff) | ((g & 0xff) << 8) | ((b & 0xff) << 16) | ((a & 0xff) << 24)) >>> 0;
}
wrapEntity('CBaseModelEntity', ref).render = rgba(255, 0, 0, 255);
wrapEntity('CBaseModelEntity', ref).glow.glowColorOverride = rgba(255, 0, 0, 255); That covers m_clrRender (exposed as render) and m_glowColorOverride.
What is not exposed
Around 700 fields in the generated set are skipped, and the reason is recorded per field rather than guessed around:
- raw pointers and function pointers — not meaningful to hand to a plugin
CUtlVector/CUtlString/CUtlSymbolLargecontainers — these need a reader that follows the pointer- enums whose binding did not state a width — a width is never assumed
CNetworkVelocityVector/CNetworkViewOffsetVector— their components areCNetworkedQuantizedFloat, which the schema does not describe, so the layout would have to be guessed
The rule throughout: a field is exposed when the engine tells us how to read it, and skipped when it doesn’t. Nothing is derived from a constant borrowed from another framework.
For creating, spawning and removing entities, entity I/O and ray casts, see Entities and the entity module.