# Events

Game events are the engine's named notifications: `player_spawn`, `player_death`, `round_start`, and about 270 more. Subscribe with `hook.on` (after the event fires) or `hook.onPre` (before it is broadcast to clients, so you can change or suppress it).

```ts
import { hook, HookResult, Chat } from '@s2script/sdk';
import { Player } from '@s2script/cs2';

export function OnPluginStart(): void {
	hook.on('player_spawn', (ev) => {
		const player = Player.fromSlot(ev.getPlayerSlot('userid'));
		if (player) Chat.toSlot(player.slot, 'Welcome back!');
	});

	hook.onPre('player_death', (ev) => {
		ev.setString('weapon', 'knife'); // rewrite the killfeed icon
		return HookResult.Changed;
	});
}
```

Register subscriptions inside `OnPluginStart`. `hook.on` and `hook.onPre` are load-window APIs: they throw after the plugin finishes starting, the same as `command`. Subscriptions are removed automatically when the plugin unloads.

## Pre and post

|            | `hook.on` (post)                     | `hook.onPre` (pre)                        |
| ---------- | ------------------------------------ | ----------------------------------------- |
| Runs       | after the server processed the event | before the event is broadcast to clients  |
| Can modify | no                                   | yes, with `setInt` / `setString` / …      |
| Return     | nothing                              | a `HookResult`, or nothing for `Continue` |
| SourceMod  | `HookEvent(..., EventHookMode_Post)` | `HookEvent(..., EventHookMode_Pre)`       |

## Return values

Return a `HookResult` from an `onPre` handler. When several plugins handle the same event, the highest result wins.

| Result               | Effect                                                           |
| -------------------- | ---------------------------------------------------------------- |
| `Continue` (or none) | No change.                                                       |
| `Changed`            | You modified fields; the modified event is broadcast.            |
| `Handled`            | Clients do not receive the event. The server still processes it. |
| `Stop`               | Like `Handled`, and lower-priority handlers do not run.          |

> [!NOTE]
> `Handled` and `Stop` hide the event from clients (no killfeed line, no sound cue). They do not undo the game logic that caused it. To prevent damage, use `OnTakeDamage` via [SDKHooks](https://s2script.com/docs/concepts/sdkhooks).

## Reading fields

`GameEvent` has typed accessors. Missing fields return a default rather than throwing.

| Accessor             | Returns                                                         |
| -------------------- | --------------------------------------------------------------- |
| `getInt(key)`        | number, `0` if absent                                           |
| `getFloat(key)`      | number, `0` if absent                                           |
| `getBool(key)`       | boolean, `false` if absent                                      |
| `getString(key)`     | string, `""` if absent                                          |
| `getUint64(key)`     | a 64-bit value as a decimal string                              |
| `getPlayerSlot(key)` | a 0-based player slot for `userid` / `attacker` fields, or `-1` |

Each getter has a matching setter (`setInt`, `setString`, …) for use in `onPre`.

> [!WARNING]
> A `GameEvent` is valid only while your handler runs synchronously. Read what you need before any `await`. A stashed event reads defaults later.

Field names for every event are listed in the [game events reference](https://s2script.com/docs/reference/events).

## Typed events on CS2

`@s2script/cs2` exports `Events`, a typed view of the same catalog. `Events.on('player_death', …)` checks the event name and field keys at compile time.

```ts
import { Events } from '@s2script/cs2';

export function OnPluginStart(): void {
	Events.on('player_death', (ev) => {
		const attacker = ev.getPlayerSlot('attacker');
		const headshot = ev.getBool('headshot');
	});
}
```

## Firing events

Fire an event with `Events.fire` from `@s2script/sdk` (SourceMod `FireEvent`). Field keys must match the event's declared fields. Other plugins' `hook.on` / `hook.onPre` handlers run before `fire` returns. It returns `false` for an unknown event name.

```ts
import { Events } from '@s2script/sdk';

Events.fire('game_message', { target: 1, text: 'Map changes in 60 seconds' });
```

Pass `dontBroadcast = true` as the third argument to keep an event server-side. `Events.fireToClient(slot, name, fields)` sends an event to one client and skips `onPre` handlers.

## Showing an event to some players

From an `onPre` handler, call `Events.setRecipients(slots)` and return `HookResult.Handled`. The event then reaches exactly those players instead of nobody.

```ts
import { hook, Events, HookResult } from '@s2script/sdk';
import { Player } from '@s2script/cs2';

export function OnPluginStart(): void {
	// Only the victim sees their own death in the killfeed.
	hook.onPre('player_death', (ev) => {
		const victim = ev.getPlayerSlot('userid');
		if (victim < 0 || !Player.fromSlot(victim)) return;
		Events.setRecipients([victim]);
		return HookResult.Handled;
	});
}
```

## What is not a game event

`hook` covers the game-event catalog only. There is no `hook.client`, `hook.entity`, `hook.server`, or `hook.events`.

- Map, frame, client and entity lifecycle callbacks are [named publics](https://s2script.com/docs/concepts/lifecycle) such as `OnMapStart` and `OnClientPutInServer`.
- Per-entity virtuals and damage (`OnTakeDamage`, `SetTransmit`, Spawn/Think/Use, weapon hooks) are [SDKHooks](https://s2script.com/docs/concepts/sdkhooks).
- Entity outputs such as `OnStartTouch` use `onOutput` (SourceMod `HookEntityOutput`), described in [Authoring plugins](https://s2script.com/docs/authoring).

See the [events module](https://s2script.com/docs/api/modules/events), the [plugin module](https://s2script.com/docs/api/modules/plugin) (`hook`), and the [game events reference](https://s2script.com/docs/reference/events).
