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

# Events

> Every event a plugin can listen for, what it hands you, when it runs and how to cancel one.

`events.on(name, callback)` adds a listener and returns an id. `events.off(id)` removes it again.

```js theme={null}
declareModule({ name: "greeter", permissions: ["chat"] });

const listening = events.on("playerJoin", (data) => {
    chat.addChatMessage("§7welcome " + data.name);
});
```

A script's listeners are cleared for it every time the file reloads, so there is nothing to tidy
up by hand. A module stays loaded for the life of the client, so anything it registers
conditionally is its own to remove in `disable`. See
[Scripts and modules](/docs/features/plugin-api/modules).

Listening needs no permission, but the plugin has to be enabled: a listener belonging to a
disabled plugin is skipped when the event fires. An unrecognised event name is accepted and
warned about in the log, together with the list of names that do exist, since nothing would ever
fire it.

## The catalogue

| Event                 | Your callback receives    | Fires when                                                           |
| --------------------- | ------------------------- | -------------------------------------------------------------------- |
| `chat`                | `ChatData`                | A chat message arrives from the server                               |
| `actionBar`           | `ActionBarData`           | The server writes a line above your hotbar                           |
| `serverMod`           | `ServerModData`           | The server sends Lunar or Badlion data for one of that client's mods |
| `packet`              | The packet itself         | Any packet arrives from the server                                   |
| `tick`                | —                         | Every client tick, while you are in a world                          |
| `renderOverlay`       | —                         | Every frame, while no screen is open                                 |
| `worldUnload`         | —                         | You leave a world                                                    |
| `playerMove`          | `MovementData`            | The server moves you, teleports you or corrects your position        |
| `blockUpdate`         | `BlockUpdateData`         | A block changes, once per block in a bulk change                     |
| `playerJoin`          | `PlayerConnectionData`    | Somebody is added to the tab list                                    |
| `playerLeave`         | `PlayerConnectionData`    | Somebody is removed from the tab list                                |
| `playerDeath`         | A UUID string             | A player dies                                                        |
| `playerRespawn`       | A UUID string             | You respawn or change dimension                                      |
| `playerInteract`      | `InteractionData`         | A player gets into a bed                                             |
| `entityVelocity`      | `EntityVelocityData`      | The server pushes an entity, including knockback on you              |
| `soundPlay`           | `SoundData`               | The server plays a sound                                             |
| `particleSpawn`       | `ParticleData`            | The server spawns particles                                          |
| `windowOpen`          | `WindowData`              | A chest, menu or other container opens                               |
| `windowClose`         | —                         | The server closes the open container                                 |
| `scoreboardObjective` | `ScoreboardObjectiveData` | A scoreboard objective is created, removed or renamed                |
| `title`               | `(type, message)`         | The server sends a title, subtitle or title command                  |
| `anticheatFlag`       | `(uuid, checkId, name)`   | Any anti-cheat check flags a player                                  |

<Note>
  `title` and `anticheatFlag` hand your callback **two and three separate arguments** rather than
  one object. Every other event passes a single value.
</Note>

## Payload shapes

| Payload                   | Fields                                      |
| ------------------------- | ------------------------------------------- |
| `ChatData`                | `message`, `formatted`, `isSystem`          |
| `ActionBarData`           | `message`, `formatted`                      |
| `ServerModData`           | `client`, `mod`, `payload`                  |
| `MovementData`            | `x`, `y`, `z`, `yaw`, `pitch`               |
| `BlockUpdateData`         | `x`, `y`, `z`, `blockRegistryName`          |
| `PlayerConnectionData`    | `uuid`, `name`                              |
| `InteractionData`         | `entityId`, `action`                        |
| `EntityVelocityData`      | `entityId`, `motionX`, `motionY`, `motionZ` |
| `SoundData`               | `name`, `x`, `y`, `z`, `volume`, `pitch`    |
| `ParticleData`            | `name`, `x`, `y`, `z`, `count`              |
| `WindowData`              | `windowId`, `title`, `slots`                |
| `ScoreboardObjectiveData` | `name`, `value`, `type`, `mode`             |

A few of them are easier to misread than they look.

`ChatData` gives you the line three ways round: `message` has the colour codes stripped,
`formatted` is the line exactly as it was sent, and `isSystem` is true for a message shown above
the hotbar rather than in chat.

`ActionBarData` is the text above your hotbar, which on Hypixel carries timers, counters and pickup
messages: it fires many times a second in a game, so keep the listener cheap. Returning `false`
hides that line from you.

`ServerModData` is what a server pushed to Lunar or Badlion for one of **that client's** own mods.
`mod` is the wire name the client uses (`waypoints`, `tntTime`, `teamMarker` on Badlion;
`waypoint`, `server_rule`, `mod_setting` on Lunar) and `payload` is the JSON it carried, as text.
Only the channel belonging to the client you are running is read, so a plugin on Forge never sees
this event, and there is nothing to send back: it is what the server said, not a way to say
anything.

```js theme={null}
declareModule({ name: "mod-watch" });

events.on("serverMod", (data) => {
    if (data.mod !== "waypoints") return;
    const payload = JSON.parse(data.payload);
    console.info(data.client + " sent " + payload.waypoints.length + " waypoints");
});
```

`MovementData` is the position the **server** has put you at, not the one you walked to, so it
fires on teleports and rubber-banding rather than on every step.

`PlayerConnectionData` follows the tab list rather than the world, which on Hypixel is what you
want: it fires as players enter and leave your lobby. `name` may be `null` for an entry the server
sent without a profile.

`EntityVelocityData` is already converted to blocks per tick, so you can compare it against
`movement.getMotionX()` directly.

`InteractionData` currently only ever reports `"SLEEP"`, and `ScoreboardObjectiveData` uses `mode`
`0` for created, `1` for removed and `2` for updated.

`title` is the odd one out and hands your callback two arguments. `type` is one of `TITLE`,
`SUBTITLE`, `TIMES`, `CLEAR` or `RESET`, and `message` has its colour codes stripped. The last
three carry no text, so expect an empty string for those:

```js theme={null}
events.on("title", (type, message) => {
    if (type === "TITLE") console.info("title: " + message);
});
```

`anticheatFlag` hands you three: the flagged player's UUID, the check's id and its display name.
`anticheat.onFlag` is the supported way to receive the same thing, and reads better alongside a
check you registered yourself.

## Cancelling

Returning `false` from a `chat`, `actionBar` or `packet` listener stops that message, line or packet
reaching the rest of the client, which is how a plugin hides a line or drops a packet:

```js theme={null}
events.on("chat", (event) => {
    if (event.message.includes("has joined the lobby")) return false;
});
```

Every other event ignores what a listener returns, because by the time it runs the packet behind it
has already been handled. Returning `false` from a `tick` or `serverMod` listener does nothing.

<Warning>
  Cancelling `chat` hides the line from you, not from the server. Cancelling `packet` drops a packet
  the client was about to process, so dropping the wrong one desynchronises you from the server.
  Narrow with `events.isPacket` before you return `false`.
</Warning>

## When your listener runs

`chat`, `packet` and `renderOverlay` run **inline**, on the thread that produced them, because each
of them can be acted on before the client sees it. `chat` and `packet` therefore run on the network
thread, and `renderOverlay` on the render thread.

Everything else is scheduled onto the client thread and arrives a moment later, which is why those
events cannot be cancelled.

`tick` only fires while you are actually in a world, and `renderOverlay` only while no screen is
open, so an overlay disappears while the inventory or a menu is up.

Whatever the event, your listener has **two seconds**. A listener that passes that budget stops
that plugin and leaves the rest of the client running, so keep the body short and hand anything
slow to a timer.

## Packets

`packet` fires for every packet the server sends and hands you the object Minecraft built rather
than a copy of it, so read it through its Java accessors: `getX()`, not `.x`.

`events.isPacket(packet, name)` narrows one by its simple class name, which is also what tells your
editor the real signatures:

```js theme={null}
declareModule({ name: "sniffer" });

events.on("packet", (packet) => {
    if (events.isPacket(packet, "S29PacketSoundEffect")) {
        console.info(packet.getSoundName() + " at " + packet.getVolume());
    }
});
```

Every packet Seraph types is listed under `ServerPacketMap` in `seraph.d.ts`, from
`S00PacketKeepAlive` through to `S49PacketUpdateEntityNBT`. A packet Seraph has no type for still
arrives; it simply has no completion behind it.

This is the busiest event there is, several hundred times a second in a full lobby, so check the
type first and do as little as possible in the branch. If a named event above covers what you want,
use that instead: `windowOpen` is cheaper than watching for `S2DPacketOpenWindow` yourself.

## Java values you will meet

A packet's accessors return Java objects, not plain JavaScript ones. They are typed in
`seraph.d.ts` and behave the way Java does.

| Type             | Read it with                                                           |
| ---------------- | ---------------------------------------------------------------------- |
| `JavaList<T>`    | `size()`, `get(i)`, `isEmpty()`, `contains(v)`, `toArray()`            |
| `JavaMap<K, V>`  | `size()`, `get(k)`, `containsKey(k)`, `keySet()`, `values()`           |
| `JavaEnum`       | `name()` for the constant, `ordinal()` for its index                   |
| `JavaUUID`       | `toString()`                                                           |
| `IChatComponent` | `getUnformattedText()`, `getFormattedText()`, `getSiblings()`          |
| `ItemStack`      | `getDisplayName()`, `stackSize`, `getItemDamage()`, `getTagCompound()` |
| `NBTTagCompound` | `hasKey(k)`, `getString(k)`, `getInteger(k)`, `getBoolean(k)`          |
| `BlockPos`       | `getX()`, `getY()`, `getZ()`                                           |
| `GameProfile`    | `getId()`, `getName()`                                                 |

`toArray()` is the quickest way out of Java and into ordinary JavaScript:

```js theme={null}
events.on("packet", (packet) => {
    if (!events.isPacket(packet, "S02PacketChat")) return;

    const component = packet.getChatComponent();
    const siblings = component.getSiblings().toArray();

    console.info(component.getUnformattedText() + " (" + siblings.length + " parts)");
});
```

Compare an enum with `name()` rather than against the value itself, since two wrappers around the
same constant are not `===` equal:

```js theme={null}
if (packet.getGameType().name() === "SPECTATOR") {
    hud.actionBar("§7spectating");
}
```

## Next

<CardGroup cols={2}>
  <Card title="API reference" icon="book" href="/docs/features/plugin-api/reference">
    Every global object a plugin can reach.
  </Card>

  <Card title="Examples" icon="code" href="/docs/features/plugin-api/examples">
    Complete plugins you can drop straight into the folder.
  </Card>
</CardGroup>
