> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spurdoverse.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Entities

> Find entities, read their schema fields, and use the built-in helpers.

Players, weapons, grenades, the bomb: every entity in the game as a read-only object.

```ts theme={null}
import entities from "@native/entities";
```

<Note>
  Values update once per tick, so they don't change between `tick` and the `render` frames after it.
</Note>

## Overview

<CardGroup cols={2}>
  <Card title="Lookup" icon="magnifying-glass" href="#lookup">
    `getByIndex`, `getByHandle`, `getEntities`, `find`, `getPlayers`, `getLocalPlayer`, `getLocalController`.
  </Card>

  <Card title="Entity objects" icon="fingerprint" href="#entity-objects">
    `alive`, `index`, `handle`, `className`, `address`.
  </Card>

  <Card title="Schema fields" icon="table-list" href="#schema-fields">
    `m_iHealth` and friends, `read`, `bytes`.
  </Card>

  <Card title="Helpers" icon="wrench" href="#helpers">
    Origin, eyes, bounds, visibility, hitboxes, bones, weapon data.
  </Card>

  <Card title="EntityQuery" icon="filter" href="#entityquery">
    `filter`, `forEach`, `toArray`, `first`, `count`, `map`, `some`, `every`.
  </Card>
</CardGroup>

Anywhere an entity is expected you can also pass its index (`EntityRef`).

***

## Lookup

### getByIndex

<br />

```ts theme={null}
entities.getByIndex(index: number): IEntityBase | null
```

The entity at `index`, or `null` when there's none.

```ts theme={null}
const ent = entities.getByIndex(64);
console.log(ent?.className ?? "empty slot");
```

### getByHandle

<br />

```ts theme={null}
entities.getByHandle(handle: number): IEntityBase | null
```

The entity with this [`handle`](#entity-objects), or `null` once it's gone.

```ts theme={null}
const saved = pawn.handle;
// later
const same = entities.getByHandle(saved);   // null if that pawn was destroyed
```

### getEntities

<br />

```ts theme={null}
entities.getEntities(): IEntityBase[]
```

Every entity.

```ts theme={null}
const counts = new Map<string, number>();
for (const ent of entities.getEntities())
    counts.set(ent.className, (counts.get(ent.className) ?? 0) + 1);
```

### find

<br />

```ts theme={null}
entities.find(className: string): EntityQuery
entities.find(options: { className?: string; parent?: string }): EntityQuery
```

`className` matches exactly that class, `parent` matches it and every class deriving from it. Both together is an AND; an unknown name matches nothing. Returns an [EntityQuery](#entityquery) typed by the class you asked for.

```ts theme={null}
const bomb = entities.find("C_PlantedC4").first();
const grenades = entities.find({ parent: "C_BaseCSGrenadeProjectile" }).count();
const loaded = entities.find({ parent: "C_CSWeaponBase" }).filter((w) => w.m_iClip1 > 0).toArray();
```

### getPlayers

<br />

```ts theme={null}
entities.getPlayers(options?: { skipLocal?: boolean }): IPlayerPawn[]
```

The pawn of every player, in controller order. `skipLocal` leaves yours out.

```ts theme={null}
for (const pawn of entities.getPlayers({ skipLocal: true })) {
    if (pawn.className !== "C_CSPlayerPawn") continue;   // dead or spectating
    console.log(pawn.controller?.m_iszPlayerName, pawn.m_iHealth);
}
```

<Warning>
  A dead or spectating player's pawn is a `C_CSObserverPawn`, not a `C_CSPlayerPawn`.
  Check `className` before using player-only fields or `getViewAngles()`.
</Warning>

### getLocalPlayer

<br />

```ts theme={null}
entities.getLocalPlayer(): IPlayerPawn | null
```

Your pawn, `null` when you're not in a game. Same observer-pawn rule as `getPlayers`.

```ts theme={null}
const me = entities.getLocalPlayer();
if (me?.className === "C_CSPlayerPawn") console.log(me.m_iHealth, me.getViewAngles());
```

### getLocalController

<br />

```ts theme={null}
entities.getLocalController(): ICCSPlayerController | null
```

Your player controller, `null` when you're not in a game. Name, score, money and other per-player state live here.

```ts theme={null}
const ctrl = entities.getLocalController();
console.log(ctrl?.m_iszPlayerName, ctrl?.m_iPing);
```

***

## Entity objects

| Member | |
| :- | :- |
| `alive: boolean` | the entity still exists |
| `index: number` | entity index |
| `handle: number` | id for [getByHandle](#getbyhandle); `m_h*` fields hold these |
| `className: string` | schema class, e.g. `"C_CSPlayerPawn"` |
| `address: Pointer \| null` | address in game memory, see [Pointer](/api/types/pointer) |
| `toString(): string` | `"Entity(12, C_CSPlayerPawn)"`, with `", stale"` once it's gone |

The same entity is always the same object, so `===` and `Map` keys work. Once it's destroyed, `alive` is `false`
for good and fields, `address`, `read`, `bytes` and helpers return `null`; `index`, `handle` and `className` still work.

```ts theme={null}
let target: IPlayerPawn | null = null;

on("tick", () => {
    if (target && !target.alive) target = null;
    target ??= entities.getPlayers({ skipLocal: true })[0] ?? null;
});
```

<Warning>
  Field types don't include `null` (`m_iHealth` is typed `number`), but a stale entity reads `null`.
  Check `alive` on anything you keep across ticks.
</Warning>

***

## Schema fields

Every schema field is a read-only property under its schema name: `pawn.m_iHealth`, `pawn.m_ArmorValue`. Autocompletion shows what each class has; the type for class `C_CSPlayerPawn` is `IC_CSPlayerPawn`.

| Schema type | Reads as |
| :- | :- |
| integers, floats, enums | `number` (`bigint` for 64-bit) |
| `bool` | `boolean` |
| `Vector`, `QAngle`, `Quaternion`, `Vector2D`, `Color` | [Vector](/api/types/vector), [QAngle](/api/types/qangle), [Quaternion](/api/types/quaternion), [Vector2](/api/types/vector2), [Color](/api/types/color) |
| `char[N]`, `CUtlString`, `CUtlSymbolLarge` | `string` |
| `CHandle` (`m_h*`) | the entity, or `null` |
| pointer to a struct or entity (`m_p*`) | that object, or `null` |
| other pointers | [Pointer](/api/types/pointer), or `null` |
| `T[N]`, `CUtlVector` | an array (`CUtlVector` can be `null`) |
| anything else | `Uint8Array` copy of the bytes |

```ts theme={null}
const me = entities.getLocalPlayer();
if (me?.className === "C_CSPlayerPawn") {
    const weapon = me.m_pWeaponServices?.m_hActiveWeapon;   // struct pointer, then handle
    console.log(me.m_iHealth, me.m_ArmorValue, me.m_bIsScoped, weapon?.className);
}
```

Nested structs (`m_pWeaponServices` above, `getWeaponData()`) have `address`, `read`, `bytes` and `toString` as well.

### read

<br />

```ts theme={null}
entity.read<T extends ScalarType>(type: T, offset: number): ScalarValue<T> | null
```

Reads one value `offset` bytes into the object, for data the schema doesn't name. Types are the [Pointer](/api/types/pointer#scalar-types) ones. `RangeError` past the end of the object.

```ts theme={null}
const health = pawn.read("int32", 0x34C);
const origin = pawn.read("vector", 0x1224);
```

### bytes

<br />

```ts theme={null}
entity.bytes(offset?: number, size?: number): Uint8Array | null
```

A copy of `size` bytes from `offset`, defaulting to the whole object. `RangeError` past the end.

```ts theme={null}
const raw = pawn.bytes(0x300, 0x40);
```

***

## Helpers

Helpers return `null` when the data isn't there (entity gone, no model).

| Every entity | |
| :- | :- |
| `getOrigin(): Vector \| null` | world position |
| `getVelocity(): Vector \| null` | |
| `getBounds(): Bounds \| null` | world-space collision box `{ mins, maxs }` at the current origin |
| `isEnemy(other: EntityRef): boolean \| null` | on a different team, honours `mp_teammates_are_enemies` |

| Model entities (pawns, weapons, props) | |
| :- | :- |
| `getEyePosition(): Vector \| null` | origin + `m_vecViewOffset` |
| `getModelName(): string \| null` | |
| `isVisible(target: EntityRef, point?: Vec3Like): boolean \| null` | see [isVisible](#isvisible) |
| `getHitboxes()`, `getHitbox(nameOrIndex)` | see [Hitboxes and bones](#hitboxes-and-bones) |
| `getBones()`, `getBone(nameOrIndex)` | |

| Players and weapons | |
| :- | :- |
| `controller: ICCSPlayerController \| null` | the controller owning a pawn |
| `getViewAngles(): QAngle \| null` | `m_angEyeAngles`, `C_CSPlayerPawn` only |
| `getWeaponData(): ICCSWeaponBaseVData \| null` | the weapon's static data: name, damage, clip size… |

```ts theme={null}
const weapon = me.m_pWeaponServices?.m_hActiveWeapon;
const data = weapon?.getWeaponData();
console.log(data?.m_szName, data?.m_nDamage, me.getBounds()?.maxs);
```

### isVisible

<br />

```ts theme={null}
entity.isVisible(target: EntityRef, point?: Vec3Like): boolean | null
```

Line of sight through world geometry from this entity's eyes (box centre if it isn't a pawn) to `target`'s eyes or box centre. Pass `point` to test that spot instead. `null` when no map is loaded.

```ts theme={null}
if (me.isVisible(enemy)) console.log("eyes visible");

const head = enemy.getHitbox("head_0");
if (head && me.isVisible(enemy, head.start)) console.log("head visible");
```

Players never block the line. For more control use [trace](/api/trace).

### Hitboxes and bones

<br />

```ts theme={null}
entity.getHitboxes(): Hitbox[] | null
entity.getHitbox(nameOrIndex: string | number): Hitbox | null
entity.getBones(): Bone[] | null
entity.getBone(nameOrIndex: string | number): Bone | null
```

Hitboxes and bones of the current pose, in model order. Look one up by name, or by `Hitbox.index` (the model's hitbox id) / bone index. `null` when the model has none or the name doesn't exist.

| `Hitbox` | |
| :- | :- |
| `index`, `name` | model hitbox id and name |
| `bone`, `boneName` | the bone it's attached to |
| `group` | 1 head, 2 chest, 3 stomach, 4/5 arms, 6/7 legs, 8 neck |
| `radius` | capsule radius, 0 for boxes |
| `start`, `end` | world-space ends |
| `mins`, `maxs` | bone-space bounds |
| `shapeType`, `surfaceProperty` | |

`Bone` has `index`, `name` (`null` if the model has none), `position`, `rotation` and `scale`.

```ts theme={null}
const head = enemy.getHitbox("head_0");
const aimAt = head && head.start.lerp(head.end, 0.5);

for (const bone of enemy.getBones() ?? [])
    if (bone.name?.startsWith("hand")) console.log(bone.name, bone.position);
```

***

## EntityQuery

What [find](#find) returns. `filter` returns a new query and leaves the original unchanged.

| Method | Returns |
| :- | :- |
| `filter(predicate)` | a new `EntityQuery`, narrowed by type guards |
| `forEach(callback)` | `void` |
| `toArray()` | `T[]` |
| `first()` | `T \| null` |
| `count()` | `number` |
| `map(callback)` | `R[]` |
| `some(predicate)`, `every(predicate)` | `boolean` |

```ts theme={null}
const weapons = entities.find({ parent: "C_CSWeaponBase" });
const dropped = weapons.filter((w) => w.m_hOwnerEntity === null);   // weapons is unchanged

console.log(weapons.count(), dropped.count());
dropped.forEach((w) => console.log(w.className, w.getOrigin()));
```

***

## Example

Name and distance of the closest visible enemy.

```ts theme={null}
import entities from "@native/entities";
import render from "@native/render";

let closest: { pawn: IPlayerPawn; dist: number } | null = null;

on("tick", () => {
    closest = null;
    const me = entities.getLocalPlayer();
    const eye = me?.getEyePosition();
    if (!me || !eye) return;

    for (const pawn of entities.getPlayers({ skipLocal: true })) {
        if (pawn.className !== "C_CSPlayerPawn" || pawn.m_iHealth <= 0 || !pawn.isEnemy(me)) continue;
        const pos = pawn.getEyePosition();
        if (!pos || !me.isVisible(pawn)) continue;
        const dist = eye.distTo(pos);
        if (!closest || dist < closest.dist) closest = { pawn, dist };
    }
});

on("render", () => {
    if (!closest?.pawn.alive) return;
    const name = closest.pawn.controller?.m_iszPlayerName ?? "?";
    render.text([20, 200], `${name}  ${Math.round(closest.dist)}u`, "#ff4040");
});
```
