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

# Events

> Subscribe to proxy and game events

# Events

Plugins subscribe to proxy and game events via `ctx.events`. Handlers receive a typed payload depending on the event name.

## Subscribing

```ts theme={null}
ctx.events.on('game:start', (payload) => {
  // payload is GameStartPayload: { timestamp, mode, map?, gametype, server }
  ctx.client.sendChat(`§a${payload.mode} on ${payload.map ?? '?'}`);
});

// Fire once, then auto-unsubscribe
ctx.events.once('opponent:detected', (payload) => {
  ctx.client.sendChat(`§eOpponent: ${payload.username}`);
});

// Unsubscribe (same function reference as on())
const handler = (p: GameEndPayload) => { /* ... */ };
ctx.events.on('game:end', handler);
ctx.events.off('game:end', handler);
```

## Event list

| Event                 | When it fires                     | Payload                                                    |
| --------------------- | --------------------------------- | ---------------------------------------------------------- |
| `proxy:ready`         | Proxy has started                 | `BaseEventPayload` (timestamp)                             |
| `client:connected`    | Player connected to proxy         | `ClientConnectedPayload` (username, uuid)                  |
| `client:disconnected` | Player disconnected               | `ClientDisconnectedPayload` (username, reason?)            |
| `game:start`          | Game has started (mode/map known) | `GameStartPayload` (mode, map?, gametype, server)          |
| `game:end`            | Game ended (any result)           | `GameEndPayload` (mode, map, result, duration, opponents?) |
| `game:victory`        | Player won                        | `GameEndPayload`                                           |
| `game:defeat`         | Player lost                       | `GameEndPayload`                                           |
| `game:join`           | Player joined a game              | `GameJoinPayload` (mode, map?, gametype, server)           |
| `game:leave`          | Player left a game                | `GameLeavePayload` (mode, result?, duration?)              |
| `lobby:join`          | Player joined a lobby             | `LobbyJoinPayload` (gametype, lobbyname, server)           |
| `lobby:leave`         | Player left a lobby               | `BaseEventPayload`                                         |
| `locraw:update`       | /locraw data updated              | `LocrawUpdatePayload` (data, previous?)                    |
| `server:change`       | Server changed                    | `LocrawUpdatePayload`                                      |
| `mode:change`         | Game mode changed                 | `ModeChangePayload` (newMode, previousMode)                |
| `opponent:detected`   | Opponent detected in game         | `OpponentDetectedPayload` (username, uuid?, mode)          |
| `opponent:stats`      | Opponent stats fetched            | `OpponentStatsPayload` (username, uuid, mode, stats)       |

All payloads extend `BaseEventPayload` and include `timestamp: number`.

## TypeScript types

Import payload types from `@duelsplus/plugin-api` for type-safe handlers:

```ts theme={null}
import type { GameStartPayload, OpponentDetectedPayload } from '@duelsplus/plugin-api';

ctx.events.on('game:start', (payload: GameStartPayload) => {
  // payload.mode, payload.map, payload.gametype, payload.server
});
```

The package uses `PluginEventName` and `PluginEventPayloadMap` so `on()` and `once()` infer the payload type from the event name.
