> ## 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.

# Effects

> reskate.effects: particle effects and decals your mod ships, placed, moved and faded in the world.

`reskate.effects` plays particle effects and places decals that your mod ships: sparks off a grind, smoke behind a board, a stain where a skater bails. With one more permission it also plays [the game's own particle effects](#the-games-own-effects). Every function needs the `effects` [permission](https://docs.reskate.dev/plugins/permissions.md); calling one without it raises an error.

> [!WARNING]
>
> Effects are local: only your game shows them. Another player sees them only when their copy of your plugin makes them too. A dedicated server has no renderer: `state()` returns `false` there, and `particle` and `decal` return `nil`.

## Your mod's own assets

Without the `effects.game` permission a plugin plays only effects its mod ships, never the game's own. Every asset of your own that you play is named in the manifest twice:

* in `effects`, a list of at most 128 exact asset names, which needs the `effects` permission;
* under a `resident` entry for the bundle `win32/levels/game/dingolevel_root/dingolevel_root`, whose `roots` cover each name: the exact name, or a prefix ending in `/`. See [`resident`](https://docs.reskate.dev/plugins/manifest#resident).

```json theme={null}
{
    "api": 1,
    "side": "client",
    "entry": "plugin/main.lua",
    "permissions": ["effects"],
    "effects": ["effects/reskate/example/ebp_spark", "world/materials/decal/reskate_example/stain_dv"],
    "resident": [{
        "bundle": "win32/levels/game/dingolevel_root/dingolevel_root",
        "roots": ["effects/reskate/example/", "world/materials/decal/reskate_example/"]
    }]
}
```

A particle effect's name starts with `effects/reskate/`, and a decal's with `world/materials/decal/reskate_`. Names are at most 240 characters of lowercase letters, digits, `/`, `_` and `-`, with no empty folder and no `/` at the end. A manifest that lists an effect without the permission, breaks these rules, or names one no `resident` root covers is refused, and the plugin does not start.

Naming an asset does not make it. Build your effects and what they use into that shared bundle with your mod. A particle asset must be an effect blueprint and a decal asset a decal volume template; effects in a part of the map that streams in and out are refused.

## The game's own effects

With the `effects.game` permission as well as `effects`, `particle` also takes the name of one of the game's own effect blueprints, such as `"effects/environments/effectblueprints/ebp_env_thick_smoke_popup"`. Such a name is not listed in the manifest. Decals stay your mod's own.

The game's effects include the ones its store sells with gestures and items, which is why this is a permission of its own: a player sees it when they approve your plugin. Playing an effect shows a picture. It does not equip, grant or unlock anything.

> [!WARNING]
>
> An effect can be played only while the game has it loaded for the whole level. The game loads effects with what uses them: a map's own effects come with the map, and a gesture's effect comes in a bundle of its own when the gesture is used. On a map that does not have an effect, `particle` returns `nil, "effect asset or renderer unavailable"`. Ask for a gesture's bundle with `load`, below, and always be ready for an effect that is not there.

### `reskate.effects.load(bundle, asset)`

Asks the game for one of its effect bundles, so an effect in it can be played. The bundle stays for the rest of the level and goes with it: ask again after each level change.

**bundle** `string` *required*

The bundle's name as the game's data has it, in lower case, under `win32/effects/`: `"win32/effects/gestures/effectblueprints/ebp_gesture_confetti_burst_bundle"`. At most 240 characters. A bundle anywhere else (a level's, an item's) raises an error.

**asset** `string` *required*

The effect in it that you want: `"effects/gestures/effectblueprints/ebp_gesture_confetti_burst"`. When the level already has it, nothing is loaded.

**state** `string`

* `"ready"`: the effect is there to play.
* `"loading"`: the bundle was asked for. Call again on a later tick.
* `"failed"`: the game has no bundle of that name, or could not load it. It is not tried again this level.
* `"unavailable"`: effects cannot be made now (see [when effects show](#when-effects-show)), this game build cannot load bundles this way, or 16 bundles have been asked for this level.

```lua theme={null}
local BUNDLE = "win32/effects/gestures/effectblueprints/ebp_gesture_confetti_burst_bundle"
local EFFECT = "effects/gestures/effectblueprints/ebp_gesture_confetti_burst"

reskate.on("tick", function()
    if reskate.effects.load(BUNDLE, EFFECT) == "ready" and want_confetti then
        want_confetti = false
        reskate.effects.particle(EFFECT, at(reskate.skater.position()))
    end
end)
```

> [!NOTE]
>
> Needs the `effects` and `effects.game` permissions. A name that is not a bundle under `win32/effects/`, or an `asset` that is one of your mod's own, raises an error. Every plugin together asks for at most 16 bundles a level.

## Transforms

A transform is a list of 16 numbers: the effect's X, Y and Z axes in the world, each followed by 0, then its position followed by 1.

```lua theme={null}
-- Upright at a point, 1 m across.
local function at(p)
    return {1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, 0,  p.x, p.y, p.z, 1}
end
```

Every number is finite. The axes are at right angles to each other (within 1%), each 0.001 to 20 m long, which scales the effect. The position is within 100 km of the map's centre. Any other transform raises an error. A particle effect emits along its Y axis. A decal projects along its Y axis, which sets how deep it reaches; X and Z set its size.

## When effects show

Effects are made only while the game can show them: not without a local skater, while a level loads, in a replay, in Noclip or in the park editor. A skater your plugin is holding still with `reskate.skater.freeze` does not count as Noclip: effects are made as usual, so a vehicle a plugin flies can have them. `state()` says whether they can be made now, and which world they belong to.

Whenever effects stop being able to show (a level change, a world reset, Noclip, the park editor, a replay, or the local skater going), every effect is let go, and `state()`'s epoch changes. Watch the epoch: when it changes, call `clear()`, forget your handles and start again once `ready` is true.

```lua theme={null}
local epoch
reskate.on("tick", function()
    local ready, now = reskate.effects.state()
    if now ~= epoch then
        reskate.effects.clear()
        epoch = now
        -- forget every handle here
    end
    if not ready then return end
    -- make and move effects
end)
```

An effect lasts at most 10 minutes, then the game lets go of it. Let go of yours sooner. Handles of effects that ended, expired or were let go by the world are dropped from your plugin for you, and give back their room. When your plugin stops, reloads or fails, all its effects go.

## Making

### `reskate.effects.particle(asset, transform)`

Starts a particle effect.

**asset** `string` *required*

A particle effect named in the manifest's `effects`.

**transform** `table` *required*

Where it plays: 16 numbers, as above.

**handle** `number or nil`

The effect's handle, a whole number from 1. Only your plugin can use it.

**why** `string`

`"effect limit reached"` (your plugin holds 24 particle effects), or `"effect asset or renderer unavailable"` (the asset is not loaded, effects cannot show now, or every plugin together holds 64).

```lua theme={null}
local spark = reskate.effects.particle("effects/reskate/example/ebp_spark", at(reskate.skater.position()))
```

> [!NOTE]
>
> Needs the `effects` permission. An asset that is neither in the manifest nor, with `effects.game`, one of the game's particle effects raises an error; so does one of the game's without that permission.

### `reskate.effects.decal(asset, transform, options)`

Places a decal.

**asset** `string` *required*

A decal named in the manifest's `effects`.

**transform** `table` *required*

Where it goes: 16 numbers, as above.

**options** `table`

**fields**

**attach** `boolean` (default: false)

True looks for the surface around the decal's centre, along its Y axis, and sticks the decal to it. A decal on a moving surface follows it, and goes when that surface goes. When no surface is found, `decal` returns `nil`.

**lift** `number` (default: 0)

With `attach`, how far the decal's centre sits off the surface: 0 to 1 m.

**handle** `number or nil`

The decal's handle.

**why** `string`

`"effect limit reached"` (your plugin holds 2048 decals), or `"effect asset or renderer unavailable"` (also when every plugin together holds 4096).

The game makes room to draw the decals over the next second, so a burst of new decals may take up to a second to all show.

> [!NOTE]
>
> Needs the `effects` permission. A `lift` outside 0 to 1 raises an error.

## Changing

Each of these takes a handle your plugin made. A handle that is not a whole number from 1, or that is not your plugin's (another plugin's, released or dropped), raises an error. They return nothing.

### `reskate.effects.move(handle, transform)`

Moves a particle effect, or a decal that is not attached, to a new transform.

> [!NOTE]
>
> Needs the `effects` permission.

### `reskate.effects.stop(handle)`

Stops a particle effect emitting. The particles already out finish as they would.

> [!NOTE]
>
> Needs the `effects` permission.

### `reskate.effects.opacity(handle, alpha)`

Fades a decal: `alpha` is 0 (gone) to 1. Outside that raises an error.

> [!NOTE]
>
> Needs the `effects` permission.

### `reskate.effects.release(handle)`

Stops the effect and lets go of it. Its handle is no longer yours.

> [!NOTE]
>
> Needs the `effects` permission.

### `reskate.effects.clear()`

Lets go of every effect your plugin made. Safe to call any time, and again.

> [!NOTE]
>
> Needs the `effects` permission.

## Reading

### `reskate.effects.state()`

**ready** `boolean`

Whether effects can be made now.

**epoch** `number`

A number for the current world. It changes when the world does: then clear your effects and start again.

> [!NOTE]
>
> Needs the `effects` permission. Every `reskate.effects` call counts towards 8192 effect calls a tick, past which a call raises an error.


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