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

# Skater

> reskate.skater, reskate.score, reskate.trainer and reskate.hud: read and move the local skater, pick its grind exits, show its x-ray, read the score, keep a save point and hide the HUD.

These tables work on the local skater: the player's own, on their own game. Each function needs a permission, named in its section:

* `skater.read`: `reskate.skater.get`, `position`, `heading`, `speed` and `on_ground`.
* `skater.teleport`: `reskate.skater.teleport`.
* `events.skater`: `reskate.skater.state`.
* `skater.control`: `reskate.skater.noclip`, `boost`, `freeze`, `wipeout` and `grind_exit`.
* `overlay`: `reskate.skater.xray`.
* `events.tricks`: `reskate.score.current`.
* `trainer`: `reskate.trainer.set_point` and `back`.
* `hud`: `reskate.hud.hide` and `reskate.hud.corner`.

Calling a function without its permission raises an error. See [permissions](/plugins/permissions). For the world around the skater (`reskate.world.raycast` and the rest) see [world](/plugins/api/world); for its board, see [board](/plugins/api/board).

## Positions and headings

A position is a Luau `vector` in metres, in the world's frame: `y` is up. Make one with `vector.create(x, y, z)`. A position you pass must be finite and within 100 km of the map's centre, or the call raises an error.

A heading is in radians about world up, from -pi to pi. A heading of 0 faces +Z, pi/2 faces +X, and a higher heading is turned to the skater's left. Teleports take a heading the same way.

```lua theme={null}
-- The point 5 m in front of the skater.
local s = reskate.skater.get()
if s then
    local ahead = s.position + vector.create(math.sin(s.heading), 0, math.cos(s.heading)) * 5
end
```

## Reading the skater

### `reskate.skater.get()`

The local skater as the game has it this tick. Use it when you need more than one value: it reads them all at once.

<ResponseField name="skater" type="table or nil">
  `nil` when there is no skater (in a menu, during a load).

  <Expandable title="fields">
    <ResponseField name="position" type="vector">Where the skater is.</ResponseField>
    <ResponseField name="heading" type="number">Where it faces, -pi to pi.</ResponseField>
    <ResponseField name="speed" type="number">Metres per second over the ground.</ResponseField>
    <ResponseField name="vertical_speed" type="number">Metres per second, up.</ResponseField>
    <ResponseField name="airborne" type="boolean">True while it is in the air.</ResponseField>
    <ResponseField name="air_time" type="number">Seconds into the jump going on.</ResponseField>
  </Expandable>
</ResponseField>

```lua theme={null}
reskate.on("tick", function()
    local s = reskate.skater.get()
    if s and s.airborne and s.air_time > 2 then
        reskate.log("big air")
    end
end)
```

<Note>Needs the `skater.read` permission.</Note>

### `reskate.skater.position()`

The skater's position, or `nil` without a skater.

<ResponseField name="position" type="vector or nil">Where the skater is.</ResponseField>

<Note>Needs the `skater.read` permission.</Note>

### `reskate.skater.heading()`

The way the skater faces, or `nil` without a skater.

<ResponseField name="heading" type="number or nil">Radians, -pi to pi.</ResponseField>

<Note>Needs the `skater.read` permission.</Note>

### `reskate.skater.speed()`

The skater's speed over the ground, or `nil` without a skater.

<ResponseField name="speed" type="number or nil">Metres per second.</ResponseField>

<Note>Needs the `skater.read` permission.</Note>

### `reskate.skater.on_ground()`

Whether the skater is on the ground, or `nil` without a skater.

<ResponseField name="on_ground" type="boolean or nil">False while it is in the air.</ResponseField>

<Note>Needs the `skater.read` permission.</Note>

### `reskate.skater.state()`

The skater's physics state, as a name. The `skater_state` event tells you when it changes (see [events](/plugins/events)).

<ResponseField name="state" type="string or nil">One of `"riding"`, `"air"`, `"wipeout"`, `"grinding"`, `"off_board"`, `"planted"`, `"other"` or `"none"`. `nil` when there is no skater.</ResponseField>

```lua theme={null}
if reskate.skater.state() == "grinding" then
    reskate.log("on a rail")
end
```

<Note>Needs the `events.skater` permission.</Note>

## Moving the skater

### `reskate.skater.teleport(position, heading)`

Moves the skater to `position`, facing `heading`. The teleport is sent at once and happens within a few seconds.

<ParamField path="position" type="vector" required>Where the skater goes. Finite, within 100 km.</ParamField>
<ParamField path="heading" type="number" default="the skater's heading now">Radians. Any finite number; it is wrapped into -pi to pi.</ParamField>

<ResponseField name="ok" type="boolean">True when the teleport was sent.</ResponseField>
<ResponseField name="why" type="string">Why not, when `ok` is false: `"asked more than twice in a second"`, `"the host has turned teleporting off"` or `"teleporting is unavailable now (no skater, or a load)"`.</ResponseField>

```lua theme={null}
local spawn = vector.create(120, 4, -36)
local ok, why = reskate.skater.teleport(spawn, 0)
if not ok then reskate.log("no teleport: " .. why, "warn") end
```

A position or heading that is not finite raises an error. The game's refusals return `false` and why.

<Note>
  Needs the `skater.teleport` permission. At most 2 teleports a second, counted together with `reskate.trainer.back`. In a session whose host has turned teleporting (and noclip) off, every teleport is refused.
</Note>

### `reskate.skater.noclip(on)`

Turns noclip on or off, as the console's own noclip does: the skater flies through the world.

<ParamField path="on" type="boolean" required>True for on, false (or nothing) for off.</ParamField>

<ResponseField name="ok" type="boolean">True when the change was queued. It takes effect at the game's next tick.</ResponseField>
<ResponseField name="why" type="string">Why not: `"the host has turned noclip off"`, `"asked more than 4 times in a second"`, or the game's own reason (`"the game is busy; try again"`).</ResponseField>

```lua theme={null}
reskate.input.bind("noclip", "F6")
local flying = false
reskate.on("action", function(name)
    if name == "noclip" then
        flying = not flying
        reskate.skater.noclip(flying)
    end
end)
```

<Note>
  Needs the `skater.control` permission. At most 4 calls a second, a budget shared with the plugin's other control calls (`noclip`, `boost`, `freeze`, `camera.follow`, `ai.spawn`, `animation.play`, `board.toss`, `outfit.set`).
</Note>

### `reskate.skater.boost(kind)`

Gives the skater a push, as the console's boosts do.

<ParamField path="kind" type="string" required>`"forward"`, `"up"` or `"offboard"` (up, off the board). Anything else raises an error.</ParamField>

<ResponseField name="ok" type="boolean">True when the boost was queued.</ResponseField>
<ResponseField name="why" type="string">Why not: `"the host has turned boosts off"`, `"asked more than 4 times in a second"`, or the game's own reason.</ResponseField>

```lua theme={null}
local ok, why = reskate.skater.boost("up")
```

<Note>Needs the `skater.control` permission. Shares the budget of 4 control calls a second (see `noclip`).</Note>

### `reskate.skater.freeze(on)`

Holds the skater where it is: noclip with no input, so it hovers in place. The skater stays held while any plugin asks for it, and is let go when the last one stops asking.

<ParamField path="on" type="boolean" required>True to hold the skater, false (or nothing) to let it go.</ParamField>

<ResponseField name="ok" type="boolean">True when this plugin's ask was taken.</ResponseField>
<ResponseField name="why" type="string">Why not: `"the host has turned noclip off"`, `"asked more than 4 times in a second"`, or the game's own reason.</ResponseField>

```lua theme={null}
-- Hold the skater for a countdown, then let go.
reskate.skater.freeze(true)
reskate.time.after(3000, function() reskate.skater.freeze(false) end)
```

<Note>
  Needs the `skater.control` permission. Shares the budget of 4 control calls a second. When the plugin stops, its hold is let go.
</Note>

### `reskate.skater.wipeout(force)`

Makes the skater bail, as a slam of that force would.

<ParamField path="force" type="number" default="2.5">0.5 to 3. Out of range raises an error.</ParamField>

<ResponseField name="ok" type="boolean">True when the wipeout was asked for.</ResponseField>
<ResponseField name="why" type="string">Why not: `"one wipeout every 2 s"` or `"not while a throwdown runs"`.</ResponseField>

```lua theme={null}
reskate.on("trick_landed", function(trick)
    if trick.points < 100 then reskate.skater.wipeout(1) end
end)
```

<Note>
  Needs the `skater.control` permission. One wipeout every 2 seconds; a refused call counts too. While No Bail protects the skater, the wipeout is asked for but dropped, as any other wipeout is: `ok` is still true, and the skater rides on.
</Note>

## Grind exits

### `reskate.skater.grind_exit(wish)`

Picks the side the skater pops out of a grind to, whatever the trick and the body's rotation. The wish stands for every pop out of a grind until you change it. Each pop is told to your plugin as the [`grind_exit` event](/plugins/events).

At the pop only the launch's sideways part, across the rail, is changed: the game's own sideways speed when it had one (1 metre per second or more), turned to the chosen side, else 1.8 metres per second (the game's usual), times `strength`. The speed along the rail, the pop's height, the trick, the flip and the rotation stay the game's. A pop slower than 0.5 metres per second along the rail is left alone.

<ParamField path="wish" type="table or nil" required>
  What the pops do. `nil` gives the exits back to the game, while your plugin keeps holding them.

  <Expandable title="fields">
    <ParamField path="side" type="string" required>`"toe"` or `"heel"` (the skater's toe or heel side), or `"left"` or `"right"` of the direction of travel.</ParamField>
    <ParamField path="stance" type="string">The player's stance, `"regular"` or `"goofy"`. Toe and heel need it: with it and the way the skater faces they follow the foot that leads, riding normal, fakie or switch. Without it, and on slides (where the toes point along the rail), toe is the left of travel and heel the right.</ParamField>
    <ParamField path="strength" type="number" default="1">0.1 to 1: how much of the full sideways speed the pop gets. At 1, a pop the game already sends to that side is kept as it is.</ParamField>
  </Expandable>
</ParamField>

<ResponseField name="ok" type="boolean">True when your plugin holds the grind exits with this wish.</ResponseField>
<ResponseField name="why" type="string">Why not: `"<plugin> picks the grind exits"` (another plugin holds them) or `"grind exits are unavailable for this game build"`.</ResponseField>

```lua theme={null}
-- LT pops out to the toe side, RT to the heel side.
local held = false
reskate.on("load", function()
    local ok, why = reskate.skater.grind_exit(nil) -- hold the exits, the game's own for now
    held = ok
    if not ok then reskate.log("no grind exits: " .. why, "warn") end
end)

reskate.on("tick", function()
    if not held then return end
    local pad = reskate.input.gamepad() -- needs input
    local wish = nil
    if pad and pad.left_trigger > 0.5 then
        wish = {side = "toe", stance = "goofy", strength = 0.8}
    elseif pad and pad.right_trigger > 0.5 then
        wish = {side = "heel", stance = "goofy", strength = 0.8}
    end
    reskate.skater.grind_exit(wish)
end)
```

A wish that is not a table, a `side` missing or not one of the four, another `stance`, or a `strength` out of range raises an error.

<Note>
  Needs the `skater.control` permission (and `events.skater` to hear the `grind_exit` event). One plugin holds the grind exits at a time: the first to call this, until it stops. Calling with `nil` does not let go of them. It works in any session; other players see the pop as your skater makes it. [`reskate.input.hide`](/plugins/api/input) keeps the triggers you pick with from the game.
</Note>

## X-ray

### `reskate.skater.xray(on)`

Keeps the local skater's x-ray ready for [`d:skeleton`](/plugins/api/overlay), which draws the game's own skeleton through the skater. The x-ray is ready while any plugin asks for it. The first time, the skeleton is read in the background, so `d:skeleton` draws nothing for a moment.

<ParamField path="on" type="boolean" required>True to keep it ready, false (or nothing) to stop asking.</ParamField>

<ResponseField name="ok" type="boolean">True when the ask was taken.</ResponseField>
<ResponseField name="why" type="string">`"the x-ray is unavailable for this game build"` when `ok` is false.</ResponseField>

```lua theme={null}
reskate.on("load", function()
    local ok, why = reskate.skater.xray(true)
    if not ok then reskate.log(why, "warn") end
end)

reskate.on("draw", function(d)
    d:skeleton({Head = "#ff4040", Chest = "#ffc400"}, 0.8)
end)
```

<Note>
  Needs the `overlay` permission. While the x-ray is ready ReSkate follows the skater's rendering every frame, so ask only while you may draw it. When the plugin stops, its ask is dropped.
</Note>

## Score

### `reskate.score.current()`

The score HUD as it is now: the line's score, its multiplier and the trick going on. The `trick_started`, `trick_landed` and `line_broken` events tell you when these change (see [events](/plugins/events)).

<ResponseField name="score" type="table or nil">
  `nil` while the game shows no score HUD.

  <Expandable title="fields">
    <ResponseField name="line" type="number">The line's score.</ResponseField>
    <ResponseField name="multiplier" type="number">The line's multiplier.</ResponseField>
    <ResponseField name="in_trick" type="boolean">True while a trick is going on.</ResponseField>
    <ResponseField name="trick" type="string">The HUD's sequence header: the trick it names. Empty when it shows none.</ResponseField>
    <ResponseField name="detail" type="string">The HUD's descriptor line, such as `"Manual"` or `"Kickflip"`. Empty when it shows none.</ResponseField>
    <ResponseField name="manual" type="boolean">True while a manual is going on, as the HUD names it.</ResponseField>
  </Expandable>
</ResponseField>

```lua theme={null}
reskate.on("tick", function()
    local score = reskate.score.current()
    if score and score.line * score.multiplier > 50000 then
        reskate.log("a huge line is going")
    end
end)
```

<Note>Needs the `events.tricks` permission.</Note>

## Trainer

A plugin has one save point of its own, kept in memory: it is gone when the plugin reloads. It is separate from the player's own trainer save points.

### `reskate.trainer.set_point()`

Saves where the skater is and the way it faces.

<ResponseField name="ok" type="boolean">True when the point was saved.</ResponseField>
<ResponseField name="why" type="string">`"there is no skater"` when `ok` is false.</ResponseField>

<Note>Needs the `trainer` permission.</Note>

### `reskate.trainer.back()`

Teleports the skater to the plugin's save point, facing the way it faced there.

<ResponseField name="ok" type="boolean">True when the teleport was sent.</ResponseField>
<ResponseField name="why" type="string">Why not: `"no save point is set"`, `"asked more than twice in a second"`, `"the host has turned teleporting off"` or `"teleporting is unavailable now"`.</ResponseField>

```lua theme={null}
reskate.input.bind("save", "F5")
reskate.input.bind("load", "F8")
reskate.on("action", function(name)
    if name == "save" then reskate.trainer.set_point()
    elseif name == "load" then reskate.trainer.back() end
end)
```

<Note>
  Needs the `trainer` permission (not `skater.teleport`). At most 2 teleports a second, counted together with `reskate.skater.teleport`.
</Note>

## HUD

### `reskate.hud.hide(hidden)`

Hides the game's whole HUD, as the `hideui` console command does. The HUD stays hidden while any plugin asks, and comes back when the last one stops asking. If the player had hidden the HUD already, it stays as the player left it.

<ParamField path="hidden" type="boolean" required>True to hide the HUD, false (or nothing) to stop asking.</ParamField>

Returns nothing.

```lua theme={null}
reskate.on("load", function() reskate.hud.hide(true) end)
reskate.on("unload", function() reskate.hud.hide(false) end)
```

<Note>Needs the `hud` permission. When the plugin stops, its ask is dropped.</Note>

### `reskate.hud.corner(hidden)`

Hides skate.'s HUD in the bottom left corner, for a plugin that shows something of its own there: the d-pad menu, and the score HUD while a line goes on. The rest of the HUD stays. The corner stays hidden while any plugin asks, and comes back when the last one stops asking.

<ParamField path="hidden" type="boolean" required>True to hide the corner. False (or nothing) shows it again, and keeps your plugin ready to hide it: a later `true` hides it at once.</ParamField>

<ResponseField name="ok" type="boolean">True.</ResponseField>

```lua theme={null}
reskate.on("load", function() reskate.hud.corner(false) end) -- ready to hide it

-- Your own card in the corner for 5 s after each bail (bail_end needs events.skater).
reskate.on("bail_end", function()
    reskate.hud.corner(true)
    reskate.time.after(5000, function() reskate.hud.corner(false) end)
end)
```

<Note>Needs the `hud` permission. When the plugin stops, its ask is dropped and the corner is the game's again.</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.