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

# Events

> Register callbacks for command processing, movement, rendering, votes, movement exploits, and CS2 game events.

```lua theme={"dark"}
local h = events.On(name, fn)
events.Off(h)
```

`events.On` is load-time only. A callback error stops that script's normal callbacks until it reloads. Unload handlers still run so the script can clean up.

Multiple scripts can register for the same event; all callbacks fire in registration order. Callbacks are cleaned up automatically when the script is unloaded or disabled.

## Built-in events

### createmove

```lua theme={"dark"}
events.On("createmove", function(cmd)
    cmd.forwardmove = 1
end)
```

`cmd` is a [CUserCmd](/classes/cusercmd). Writes are diffed back.

Fires once per rendered frame. The same command may appear more than once above tickrate. Movement and button changes must be written on every call; use `movement` for persistent movement changes.

### movement

```lua theme={"dark"}
events.On("movement", function(cmd)
    cmd.forwardmove = 1
end)
```

Uses the same `cmd` as `createmove`. It fires for each new command and view-angle change. Use it for movement changes that should persist across repeated frames.

Entity reads are pre-prediction state. `createmove` runs later and can overwrite these changes.

### frame\_stage

```lua theme={"dark"}
events.On("frame_stage", function(stage)
    if stage == 7 then ... end
end)
```

Fires several times per frame with the stage index.

### unload

```lua theme={"dark"}
events.On("unload", function()
    print("Script unloaded")
end)
```

Fires when the script unloads. No arguments. Runs on the game's main thread.

### paint

```lua theme={"dark"}
events.On("paint", function()
    renderer.RectFilled(10, 10, 40, 40, Color(255, 0, 0))
end)
```

Fires once per frame. Draw calls only work from here, see [Renderer](/renderer).

### vote\_setup

```lua theme={"dark"}
events.On("vote_setup", function(vote)
    if vote.type == 0 and vote.target ~= "" then
        print(vote.caller .. " started a vote to kick " .. vote.target)
    end
end)
```

Fires when a vote starts, before the HUD shows it.

| Field         |         |                                                                  |
| ------------- | ------- | ---------------------------------------------------------------- |
| `vote.type`   | integer | 0 = kick, 1 = map change, 6 = surrender, etc.                    |
| `vote.caller` | string  | who started the vote, or `"Server"`                              |
| `vote.target` | string  | target player name (kick votes only, empty otherwise)            |
| `vote.issue`  | string  | human-readable phrase (`"kick a player"`, `"change the map"`, …) |

### edgebug

```lua theme={"dark"}
events.On("edgebug", function(e)
    print(e.assisted and "assisted edgebug" or "edgebug")
end)
```

Fires when hitting an edgebug.

| Field        |                   |                                                               |
| ------------ | ----------------- | ------------------------------------------------------------- |
| `e.assisted` | boolean           | `true` when edgebug assist landed it, `false` for a legit one |
| `e.origin`   | [Vector](/vector) | player origin on the edgebug tick                             |
| `e.velocity` | [Vector](/vector) | player velocity on the edgebug tick                           |

### jumpbug

```lua theme={"dark"}
events.On("jumpbug", function()
    print("jumpbug")
end)
```

Fires on the command the jump bug lands. No arguments.

### pixelsurf

```lua theme={"dark"}
events.On("pixelsurf", function(e)
    if not e.active then return end
    print("surfing, tick " .. e.ticks)
end)
```

Fires once per command while pixel surfing, then once more with `active = false` when the surf ends.

| Field        |                   |                                                                             |
| ------------ | ----------------- | --------------------------------------------------------------------------- |
| `e.active`   | boolean           | `false` only on the final call                                              |
| `e.ticks`    | number            | confirmed contact ticks so far; on the final call, how long the surf lasted |
| `e.origin`   | [Vector](/vector) | player origin                                                               |
| `e.velocity` | [Vector](/vector) | player velocity                                                             |
| `e.normal`   | [Vector](/vector) | normal of the surface being surfed; zeroed on the final call                |

## Game events

Any CS2 game event can be registered by name. See [CS2 Game Events](https://cs2.poggu.me/dumped-data/game-events/) for the full list of events and their keys.

```lua theme={"dark"}
events.On("player_hurt", function(e)
    local dmg = e:GetInt("dmg_health")
    local victim = e:GetPawn("userid")
    print("hit for " .. dmg)
end)

events.On("round_start", function(e)
    print("round started")
end)

events.On("bullet_impact", function(e)
    local x, y, z = e:GetFloat("x"), e:GetFloat("y"), e:GetFloat("z")
end)
```

The callback receives a game event object with typed getters:

| Method                 | Returns       |                                    |
| ---------------------- | ------------- | ---------------------------------- |
| `e.name`               | string        | event name                         |
| `e:GetInt(key)`        | number        | integer field                      |
| `e:GetFloat(key)`      | number        | float field                        |
| `e:GetString(key)`     | string or nil | string field                       |
| `e:GetBool(key)`       | boolean       | boolean field                      |
| `e:GetUint64(key)`     | number        | uint64 field                       |
| `e:GetPawn(key)`       | entity or nil | player pawn for a player key       |
| `e:GetController(key)` | entity or nil | player controller for a player key |

The event object is only valid inside the callback. Don't store it.

Player fields (`userid`, `attacker`, etc.) are player controller indices internally. Use `e:GetPawn("userid")` or `e:GetController("userid")` to get the entity directly.

Names are case-sensitive. An unknown name registers successfully but never fires.

## Button constants

Available on the `events` table for use with `cmd.buttons`:

```lua theme={"dark"}
events.On("createmove", function(cmd)
    cmd.buttons = bit.bor(cmd.buttons, events.IN_ATTACK)
end)
```

| Constant              | Value |
| --------------------- | ----- |
| `events.IN_ATTACK`    | 1     |
| `events.IN_JUMP`      | 2     |
| `events.IN_DUCK`      | 4     |
| `events.IN_FORWARD`   | 8     |
| `events.IN_BACK`      | 16    |
| `events.IN_USE`       | 32    |
| `events.IN_TURNLEFT`  | 128   |
| `events.IN_TURNRIGHT` | 256   |
| `events.IN_MOVELEFT`  | 512   |
| `events.IN_MOVERIGHT` | 1024  |
| `events.IN_ATTACK2`   | 2048  |
| `events.IN_RELOAD`    | 8192  |
| `events.IN_SPEED`     | 65536 |

`cmd.buttons` is the raw button bitfield, so any other bit can be passed as a plain number. Bits above 31 (`IN_ZOOM` is one) have no constant here because `bit.bor` is 32-bit and would truncate them; add them with `+` instead.
