# Direct Effects

#### Overview

Direct effects let you apply temporary client-side effects to a player on-demand from any **server-side** script. Unlike status-driven effects (which activate automatically based on thresholds), direct effects are triggered explicitly by you through an export.

Use cases include:

* A drug script applying screen effects and stumbling for 60 seconds
* A flashbang applying blurry vision and camera shake
* An injury system slowing the player down
* A food item giving a temporary strength boost

Direct effects use the **same effect system** as statuses. They share the same queue, the same dominance resolution, and the same effect keys. If a status and a direct effect both try to control `walkingStyle`, the system picks the most severe one automatically.

***

### Table of Contents

* [Adding Effects](#adding-effects)
* [Real-World Examples](#real-world-examples)
* [Effect Keys](#effect-keys)
* [Simple vs Rich Values](#simple-vs-rich-values)
* [Reactions](#reactions)
* [Duration & Stacking](#duration-and-stacking)
* [Activation Threshold](#activation-threshold)
* [FAQ](#faq)

***

### Adding Effects

Use the `AddDirectEffect` export from **any server-side script**:

```lua
exports["zyke_status"]:AddDirectEffect(playerId, effects, activationThreshold)
```

| Parameter             | Type      | Required | Description                                      |
| --------------------- | --------- | -------- | ------------------------------------------------ |
| `playerId`            | `integer` | Yes      | The server-side player ID                        |
| `effects`             | `table[]` | Yes      | Array of effect entries (see below)              |
| `activationThreshold` | `integer` | No       | Seconds of total duration before effects kick in |

Each entry in the `effects` array has three fields:

| Field      | Type                                      | Description                      |
| ---------- | ----------------------------------------- | -------------------------------- |
| `name`     | `string`                                  | The effect key (see table below) |
| `value`    | `string`, `number`, `boolean`, or `table` | The effect value                 |
| `duration` | `number`                                  | How long it lasts in seconds     |

**Basic Example**

```lua
-- Slow the player and shake their camera for 30 seconds
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "movementSpeed", value = 0.7, duration = 30},
    {name = "cameraShaking", value = {value = "DRUNK_SHAKE", intensity = 0.3}, duration = 30},
})
```

***

### Real-World Examples

These examples show how you'd call `AddDirectEffect` from your own server scripts. All of them go inside a server-side event, callback, or item use handler.

**Energy Drink — Temporary Speed Boost**

A player drinks an energy drink and gets a short burst of speed and extra punch strength.

```lua
-- Inside your item use handler (server side)
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "movementSpeed", value = 1.2, duration = 120},
    {name = "strength", value = 1.5, duration = 120},
})
```

The player runs faster and hits harder for 2 minutes, then everything returns to normal automatically.

**Smoking Weed — Trippy Visuals**

A player smokes a joint. They get a screen overlay, wobbly walking, and a slight stumble for a few minutes.

```lua
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "screenEffect", value = {value = "Dax_TripBlend01", intensity = 0.4}, duration = 180},
    {name = "walkingStyle", value = "move_m@drunk@slightlydrunk", duration = 180},
    {name = "stumble", value = 1.0, duration = 180},
})
```

If they smoke another one while the first is still active, the durations stack. They'll be stumbling around for even longer.

**Flashbang — Intense Short Burst**

A player gets hit by a flashbang. Heavy screen effect, blurry vision, and camera shake — but it only lasts a few seconds.

```lua
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "screenEffect", value = {value = "BarryFadeOut", intensity = 1.5}, duration = 8},
    {name = "blurryVision", value = true, duration = 8},
    {name = "cameraShaking", value = {value = "DRUNK_SHAKE", intensity = 1.0}, duration = 8},
    {name = "blockSprinting", value = true, duration = 5},
})
```

**Leg Injury — Slowed Down**

A player gets injured and can't run properly. Combine reduced speed with blocked sprinting and jumping.

```lua
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "movementSpeed", value = 0.6, duration = 300},
    {name = "blockSprinting", value = true, duration = 300},
    {name = "blockJumping", value = true, duration = 300},
    {name = "walkingStyle", value = "move_m@drunk@a", duration = 300},
})
```

The player limps around for 5 minutes. If they receive medical treatment, the effects expire naturally (or you can avoid adding them in the first place based on your logic).

**Poisoned Food — Coughing Reaction**

A player eats something dodgy. They start coughing with a looping animation and sound, plus a subtle screen tint.

```lua
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "screenEffect", value = {value = "InchPurple02", intensity = 0.3}, duration = 90},
    {
        name = "reaction",
        value = {
            animation = {
                dict = "timetable@gardener@smoking_joint",
                clip = "idle_cough",
                flag = 49,
                start = 800,
                stop = 2000,
            },
            sound = {
                name = {"cough_m1.wav", "cough_m2.ogg"},
                volume = 0.2,
                distance = 5.0,
            },
            loop = {
                delay = {8000, 15000},
            },
        },
        duration = 90,
    },
})
```

The player coughs every 8–15 seconds with a random sound pick, while a subtle purple tint sits on their screen for 90 seconds.

***

### Effect Keys

These are the same keys used in status configs. All of them work with direct effects:

| Key              | Value Type                       | Description                                              |
| ---------------- | -------------------------------- | -------------------------------------------------------- |
| `screenEffect`   | `string` or `{value, intensity}` | Applies a timecycle modifier as a screen overlay         |
| `cameraShaking`  | `string` or `{value, intensity}` | Shakes the gameplay camera                               |
| `walkingStyle`   | `string`                         | Changes the player's movement clipset (walk animation)   |
| `blurryVision`   | `boolean`                        | Periodically fades a blur overlay in and out             |
| `blockJumping`   | `boolean`                        | Prevents the player from jumping                         |
| `blockSprinting` | `boolean`                        | Prevents the player from sprinting                       |
| `movementSpeed`  | `number`                         | Scales run/sprint speed (`1.0` = normal, lower = slower) |
| `strength`       | `number`                         | Scales unarmed melee damage (`1.0` = normal)             |
| `stumble`        | `number`                         | Chance-based stumbling/ragdoll (higher = more frequent)  |
| `reaction`       | `table`                          | Synchronized animation + sound (see below)               |

***

### Simple vs Rich Values

Some effect keys accept either a simple value or a table with extra options. Both forms are valid:

```lua
-- Simple: just the effect name, uses default intensity of 1.0
{name = "screenEffect", value = "BarryFadeOut", duration = 30}

-- Rich: with explicit intensity
{name = "screenEffect", value = {value = "BarryFadeOut", intensity = 0.3}, duration = 30}
```

```lua
-- Simple
{name = "cameraShaking", value = "DRUNK_SHAKE", duration = 30}

-- Rich
{name = "cameraShaking", value = {value = "DRUNK_SHAKE", intensity = 0.5}, duration = 30}
```

For `walkingStyle`, `blurryVision`, `blockJumping`, `blockSprinting`, `movementSpeed`, `strength`, and `stumble`, the simple value is all you need.

***

### Reactions

Reactions combine animations and/or sounds. They work the same way as in status configs, but you pass the full reaction table as the `value`:

```lua
exports["zyke_status"]:AddDirectEffect(playerId, {
    {
        name = "reaction",
        value = {
            animation = {
                dict = "timetable@gardener@smoking_joint",
                clip = "idle_cough",
                flag = 49,
                start = 800,
                stop = 2000,
            },
            sound = {
                name = "stomach_growl.ogg",
                volume = 0.2,
                distance = 2.0,
            },
            loop = {
                delay = {5000, 10000},
            },
        },
        duration = 60,
    }
})
```

You don't need both `animation` and `sound`, either one on its own works fine.

**Animation Fields**

| Field           | Type      | Default | Description                                      |
| --------------- | --------- | ------- | ------------------------------------------------ |
| `dict`          | `string`  |         | Animation dictionary                             |
| `clip`          | `string`  |         | Animation clip name                              |
| `flag`          | `integer` | `49`    | Animation flag (49 = upper body, doesn't freeze) |
| `start`         | `integer` | `nil`   | Start time in ms (skip the beginning)            |
| `stop`          | `integer` | `nil`   | Stop time in ms (cut the animation short)        |
| `blendInSpeed`  | `number`  | `1.0`   | How fast the animation blends in                 |
| `blendOutSpeed` | `number`  | `1.0`   | How fast the animation blends out                |
| `forceAnim`     | `boolean` | `false` | Force replay even if already playing             |
| `speed`         | `number`  | `1.0`   | Playback speed                                   |

**Sound Fields**

| Field      | Type                             | Default | Description                                                             |
| ---------- | -------------------------------- | ------- | ----------------------------------------------------------------------- |
| `name`     | `string`, `string[]`, or `table` |         | Sound file(s). Supports `{male = ..., female = ...}` for gendered audio |
| `volume`   | `number`                         | `0.3`   | Playback volume                                                         |
| `distance` | `number`                         | `10.0`  | How far other players can hear it                                       |

> Sounds require `zyke_sounds` to be running. If it's not available, the sound part is silently skipped.

**Loop Settings**

| Field   | Type                      | Description                                                  |
| ------- | ------------------------- | ------------------------------------------------------------ |
| `delay` | `integer` or `{min, max}` | Delay in ms before repeating. Use a table for a random range |

If `loop` is not defined, the reaction plays once and does not repeat.

***

### Duration & Stacking

**Duration** is in seconds. The server counts down each effect's duration automatically. When it reaches zero, the effect is removed and the client is updated.

**Stacking** works intelligently:

* **Same effect key, same value**: Durations are merged (added together). Calling `AddDirectEffect` twice with `movementSpeed = 0.7` for 30 seconds gives you 60 seconds total.
* **Same effect key, different value**: Both are tracked separately. The system uses the most dominant value. When the stronger one expires, the weaker one takes over automatically.
* **Number values close together**: If two numeric values are within `0.1` of each other (configurable via `Config.Settings.directEffects.accuracyMerge`), they are merged to keep things efficient.

```lua
-- First call: speed 0.7 for 30s
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "movementSpeed", value = 0.7, duration = 30},
})

-- Second call: same speed, durations add up (now 60s total)
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "movementSpeed", value = 0.7, duration = 30},
})

-- Third call: different speed, tracked separately
-- 0.5 is more severe, so it plays first. When it expires, 0.7 takes over.
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "movementSpeed", value = 0.5, duration = 10},
})
```

**Boolean effects** (like `blockJumping`) always merge into a single entry with the combined duration.

***

### Activation Threshold

The optional `activationThreshold` parameter delays when effects actually start playing. The value is in **seconds of total accumulated duration** across all direct effects.

```lua
-- Effects won't kick in until the player has at least 15 seconds of total direct effect duration
exports["zyke_status"]:AddDirectEffect(playerId, {
    {name = "screenEffect", value = "BikerFilter", duration = 30},
}, 15)
```

This is useful for gradual build-up effects, where you don't want a single small dose to trigger visuals, but repeated doses should. The timer ticks in the background even before the threshold is met.

Most of the time you can ignore this parameter and effects will start immediately.

***

### FAQ

**Q: Is this a server-side or client-side export?**\
Server-side only. The system handles syncing to the client automatically.

**Q: Are direct effects saved to the database?**\
Yes. They persist across reconnects and restarts. The remaining duration picks up where it left off.

**Q: Do direct effects conflict with status effects?**\
They share the same queue. If both a status and a direct effect control the same key, the most severe value wins. They don't stack (except `damage`).

**Q: Can I apply multiple effects in one call?**\
Yes. Pass multiple entries in the `effects` array and they are all applied at once.

**Q: What happens when duration runs out?**\
The effect is automatically removed and the client is updated. If there is another effect of the same type still active (from stacking), it takes over seamlessly.

**Q: Can I remove a direct effect early?**\
There is currently no dedicated export for early removal. The duration-based expiry handles cleanup automatically.

**Q: Can I use effect keys that aren't listed here?**\
Only the keys listed above are supported. Custom keys require registering a new queue key in the effect manager.
