
# Events

`module:event(name, fn)` subscribes. The callback gets one table of fields, or nothing when
the event has none. Names are exact snake_case strings.

Three rules cover the whole list:

**Cancellable** events carry `:cancel()`.

```lua
module:event("chat", function(e)
    if e.text:find("spam") then
        e:cancel()   -- drop the message
    end
end)
```

**Writable** fields are read back after the callback. Assign one and the game uses your value.

```lua
module:event("input", function(e)
    e.jump = true    -- jump this tick
end)
```

**The table is dead after the callback returns.** Copy out what you need; do not store it.

Numbers are plain Lua numbers (`integer` marks the whole ones), colors are `0xAARRGGBB`, and
positions arrive as raw `x, y, z` scalars rather than [vec3](vec3.md). Callbacks run only
while the module is enabled.

## Lifecycle

| Event | Fires |
|-------|-------|
| `enable` | Module toggled on |
| `disable` | Module toggled off |

## Ticks

| Event | Fields | Notes |
|-------|--------|-------|
| `tick` | none | Before the player tick. Cancellable |
| `post_tick` | none | After the player tick |
| `pre_interaction` | none | Before attacks and item use are processed |

## Input

| Event | Fields | Notes |
|-------|--------|-------|
| `input` | `forward, backward, left, right, jump, sneak, sprint` — `boolean`, all writable | Cancellable. Drives player movement |
| `key` | `key, action, mods` — `integer` | Cancellable. GLFW key codes; mouse buttons arrive too as `0` left, `1` right, `2` middle |
| `char` | `char` — `string`; `codepoint, mods` — `integer` | Cancellable. Typed character |
| `mouse_move` | `x, y` — `number` | Cancellable |
| `mouse_scroll` | `horizontal, vertical` — `number` | Cancellable |
| `mouse_look` | `delta_x, delta_y` — `number` | Cancellable. Camera rotation by mouse |
| `cursor_lock` | `state` — `"lock"` / `"unlock"` | Cancellable |
| `slot_drag` | `slot` — `integer` (`-1` outside a slot); `button` — `integer`; `shift` — `boolean` | Cancellable |

## Screens

| Event | Fields | Notes |
|-------|--------|-------|
| `screen_open` | `type` — `string`; `title` — [text](text.md); `handled` — `boolean` | Cancellable. A screen is about to open, same fields as [`screen.current()`](screen.md). It is not current yet and gets its widgets on the next tick |
| `screen_close` | none | Cancellable. The open screen is about to close. A container screen has already told the server by then, so cancelling only keeps a dead screen up |

Cancelling `screen_open` for a server's container menu leaves the menu open without a screen:
[`container`](screen.md#container) still reads it and `interaction.click_slot` still clicks it.

## Chat and text

| Event | Fields | Notes |
|-------|--------|-------|
| `chat` | `text` — `string` | Cancellable. A message arrived |
| `chat_send` | `text` — `string` | Cancellable. You are about to send |
| `title` | `text` — `string`; `kind` — `"title"` / `"subtitle"` / `"actionbar"` | A title appeared |
| `boss_bar` | `text` — `string` | Boss bar text changed |

## Combat and interaction

| Event | Fields | Notes |
|-------|--------|-------|
| `attack` | `target` — [entity](entity.md) | Cancellable. Before you hit |
| `post_attack` | `target` — [entity](entity.md) | After the hit |
| `auto_attack_target` | `target` — [entity](entity.md) or `nil`, writable | Cancellable. Auto Attack's target for this tick |
| `auto_attack_rotation` | `target` — [entity](entity.md); `yaw, pitch` — `number`, writable | Cancellable. Auto Attack's aim before the rotation service sees it |
| `item_use` | `cooldown` — `integer`, writable | Cancellable. A right click; cancelling drops it before the game uses an item or a block |
| `block_interact` | `hand` — `"main"` / `"off"`; `x, y, z` — `integer`; `side` — `string` | Cancellable |
| `block_place` | `hand`; `x, y, z`; `side`; `item` — [item](item.md) | |
| `block_breaking` | `state` — `"start"` / `"stop"`; `block` — block id; `x, y, z` | |

The two `auto_attack_*` events fire each interaction tick while Auto Attack is on, so you can
steer it instead of replacing it:

```lua
module:event("auto_attack_target", function(e)
    if e.target and e.target:name() == "Bot" then
        e:cancel()
    end
end)

module:event("auto_attack_rotation", function(e)
    e.pitch = e.pitch - 2
end)
```

`rotation` below sits lower: it fires for every rotation the service applies, including the
one Auto Attack requests here.

## Movement

| Event | Fields | Notes |
|-------|--------|-------|
| `jump` | `yaw` — `number`; `cooldown` — `integer`; both writable | Cancellable |
| `land` | `x, y, z, fall_distance` — `number` | Touched the ground |
| `slowdown` | `multiplier` — `number`, writable | Cancellable. Slowdown while using an item |
| `rotation` | `yaw, pitch` — `number`, writable; `priority` — `integer`, read-only | Cancellable. Every rotation the service applies |

## Network

| Event | Fields | Notes |
|-------|--------|-------|
| `packet_send` | `name` — `string`, plus decoded fields | Cancellable |
| `packet_receive` | `name` — `string`, plus decoded fields | Cancellable |
| `packet_process` | none | Received packets applied to the world |
| `movement_packets` | none | After movement packets are sent |
| `player_packets` | none | After player packets are sent |

`name` is the stable snake_case packet name that `packets.send` also accepts. Common packets
carry decoded fields too; the full list is on [Packets](packets.md).

```lua
module:event("packet_receive", function(e)
    if e.name == "entity_velocity" and e.entity_id == player:id() then
        e:cancel()   -- ignore knockback aimed at us
    end
end)
```

:::caution
`packet_send` and `packet_receive` run on network threads. Collect data there and act in
`tick`; never draw or call `projection` from them.
:::

## World and modules

| Event | Fields |
|-------|--------|
| `chunk_load` | `x, z` — `integer`, chunk coordinates |
| `chunk_unload` | `x, z` — `integer` |
| `world_change` | `dimension` — `string`. Fires on every join, reconnect, server switch and dimension change |
| `disconnect` | none. Fires as you leave a server or singleplayer world |
| `module_state` | `module` — `string`; `enabled` — `boolean` |

## Rendering

`render_2d`, `render_gui` and `render_3d` receive a drawing object instead of a field table.

| Event | Fields | Notes |
|-------|--------|-------|
| `frame` | none | Every frame |
| `render_2d` | [2D canvas](canvas.md) | Draw over the game |
| `render_gui` | [2D canvas](canvas.md) | Draw during GUI screens |
| `render_3d` | [3D renderer](world-render.md) | Draw in the world |
| `camera` | `x, y, z, yaw, pitch` — `number`, all writable; `tick_progress` — `number` | Camera pose each frame, after it is positioned. Free Camera moves through this event, so writing the fields overrides it too |
| `sky_color` | `color` — writable; `original` — the value without you | |
| `time_of_day` | `time` — daytime ticks, writable; `original` — `integer` | Visual only |
| `skybox` | `rendered` — `boolean`, writable; `true` hides the vanilla sky | |
| `fog` | `red, green, blue, alpha, environmental_start, environmental_end, render_distance_start, render_distance_end` — `number`, all writable | Color channels are `0..1`, the rest are world distances |
| `window_resize` | `width, height` — `integer` | |
