# Custom camera

CS2's experimental pawn-owned camera. `pawn.getCustomCamera()` returns `CustomPlayerCamera | null` and creates the camera on demand. Requires CS2's **September 9, 2026** engine update and a matching s2script runtime shim — an older shim cannot acquire the camera.

```ts
import { command } from '@s2script/sdk';
import { CustomCameraMode, Pawn } from '@s2script/cs2';

export function OnPluginStart(): void {
	command('third', (cmd) => {
		const pawn = Pawn.forSlot(cmd.callerSlot);
		const camera = pawn?.getCustomCamera();
		if (!camera || !pawn) return;
		camera.setFollowConfig({
			followEntity: pawn.ref,
			followEyes: true,
			cameraOffset: { x: -80, y: 0, z: 20 }
		});
		camera.setMode(CustomCameraMode.FOLLOW_POSITION);
	});
}
```

`null` if the pawn is stale or the camera bindings are unavailable.

## Modes

`getMode()` / `setMode(mode)` use `CustomCameraMode`. `setMode` returns `false` if the camera is stale, the mode is invalid, or the native rejected the call.

| Mode                  | Value | View                                                      |
| --------------------- | ----- | --------------------------------------------------------- |
| `DISABLED`            | 0     | Player eye position and angles                            |
| `CONTROLLED`          | 1     | Camera origin and angles                                  |
| `CONTROLLED_POSITION` | 2     | Camera origin, player-controlled angles                   |
| `FOLLOW_POSITION`     | 3     | Offset from a followed position, player-controlled angles |

Position a controlled camera with `camera.ref.teleport(position, angles)`.

## Follow

Configure following **before** `FOLLOW_POSITION`. `setFollowConfig` requires `followEntity`; it returns `false` on invalid or stale input.

```ts
camera.setFollowConfig({
	followEntity: pawn.ref, // required
	followEyes: true, // default false — eyes instead of origin
	followOffset: { x: 0, y: 0, z: 0 },
	cameraOffset: { x: -80, y: 0, z: 16 }, // forward / left / up, rotated by eye angles
	clipCameraOffset: true, // pull inward to avoid solids
	cameraOffsetReturnStrength: 1 // default 1 (instant)
});
```

## Ownership

The camera is **borrowed** and pawn-owned. Every script on that pawn shares the same engine entity; the latest mutation wins. Disable when finished (`setMode(CustomCameraMode.DISABLED)`). Do **not** `remove()` or delete it — the engine owns its association with the pawn. Plugin unload does not undo engine state.

`getPlayer()` returns the owning `Pawn`, or `null` if stale. `isValid()` is entity-liveness gated. Experimental, matching Valve.

## Hazards

- **Disable, don't delete** — `setMode(DISABLED)` when you are done. Removing the entity breaks the pawn's camera association.
- **Shared state** — another plugin can overwrite your mode or follow config. Treat the camera as a mutex you must release.
- **Matching runtime** — camera + [observable HUD](https://s2script.com/docs/concepts/hud) need the updated CS2 engine and the matching s2script shim.

## See also

- [Entities](https://s2script.com/docs/concepts/entities) — `EntityRef`, `Pawn`, teleport
- [HUD](https://s2script.com/docs/concepts/hud) — observable layouts from the same CS2 update
- [CS2 module](https://s2script.com/docs/api/modules/cs2) — `Pawn.getCustomCamera`, `CustomPlayerCamera`, `CustomCameraMode`
