# ADMIN JAIL

### Overview

Risk Admin Jail is a comprehensive punishment and jail management system that provides:

**Core Features:**

* **Admin UI:** Searchable panel with every online and offline player, playtime, jail status and remaining time
* **Mini-games:** Jailed players work off their sentence with 4 unique mini-games
* **Offline jailing:** Jail players even when they are not on the server
* **Routing buckets:** Isolate jailed players in private or shared dimensions
* **Timer pause:** Jail timer pauses on disconnect and resumes on reconnect
* **Discord webhooks:** Every jail and unjail action logged automatically
* **Multi-framework:** Works with ESX, QBCore and QBox (auto-detected)

**User Access:**

* ✅ You have access to: `config.lua` and `shared/minigames_config.lua`
* ❌ You do NOT have access to: `client.lua`, `server.lua`

All configuration is done through the config files. Backend logic is protected.

***

### ⚠️ Permission Setup (READ THIS FIRST!)

**YOU MUST CONFIGURE `Config.AllowedGroups` OR NO ADMIN CAN USE THE UI!**

```lua
-- In config.lua
Config.AllowedGroups = {
    'admin',
    'superadmin',
}
```

**Without correct groups:**

* The `/adminjail` command will do nothing
* No admin can open the UI or jail players
* Group names must exactly match your framework groups

**Common mistakes:**

* ❌ Wrong group names → Command does nothing
* ❌ Empty table → Nobody can open the UI
* ❌ Using ACE permission names instead of framework groups → Won't work
* ✅ Use the exact group names defined in your ESX/QBCore/QBox setup

***

### General Configuration

```lua
Config.Command     = 'adminjail' -- command to open the admin UI (no leading slash)
Config.MoveCommand = 'jailmove'  -- command for jailed players to reposition the HUD
Config.Debug       = false       -- true = verbose server-side logs
```

```lua
-- Built-in translations live in /locales/<code>.lua
-- Available: 'en', 'de', 'es', 'fr', 'pt'
-- Falls back to 'en' if the chosen code is missing or has gaps
Config.Language = 'en'
```

***

### Permissions

```lua
-- Framework groups allowed to open the admin UI
-- ESX / QBCore / QBox auto-detected
-- Empty table = nobody can open the UI
Config.AllowedGroups = {
    'admin',
    'superadmin',
}
```

**Framework group names reference:**

| Framework | Common group names           |
| --------- | ---------------------------- |
| ESX       | `admin`, `superadmin`, `mod` |
| QBCore    | `admin`, `god`               |
| QBox      | `admin`, `superadmin`        |

***

### Jail Behavior

```lua
-- Where the player is teleported when jailed (x, y, z, heading)
Config.JailCoords = vector4(-2000.5370, 3194.1423, 32.8102, 329.5341)

-- Coords used when releasing an offline-jailed player (no "before" position available)
Config.DefaultReleaseCoords = vector4(-260.0, -974.0, 31.2, 200.0)

-- Auto-teleport back if the jailed player moves further than this (meters), checked every 10s
Config.JailMaxDistance = 80.0

-- Pause timer while the player is offline?
-- false = timer keeps ticking down offline
Config.PauseOnOffline = true
```

***

### Routing Bucket Isolation

Control how jailed players are separated from the rest of the server.

```lua
-- 'alone'    = each jailed player gets a private bucket (isolated)
-- 'together' = all jailed players share one bucket (can see each other)
-- 'normal'   = stays in the normal world (visible to everyone)
Config.JailDimension         = 'alone'
Config.JailTogetherBucket    = 5000  -- bucket id used in 'together' mode
Config.JailAloneBucketOffset = 6000  -- offset + server id = bucket in 'alone' mode
```

**Mode comparison:**

| Mode       | Jailed players see each other | Use case                                 |
| ---------- | ----------------------------- | ---------------------------------------- |
| `alone`    | ❌ No                          | Maximum isolation, no player interaction |
| `together` | ✅ Yes                         | Community jail, players can talk         |
| `normal`   | ✅ Everyone                    | No dimension, visible to whole server    |

***

### Admin UI Configuration

#### Preset Reasons

```lua
-- Preset reasons shown in the REASON dropdown
Config.JailReasons = {
    'HACKING',
    'RDM / VDM',
    'COMBAT LOG',
    'EXPLOIT',
    'TOXIC BEHAVIOR',
    'CHEATING',
    'METAGAMING',
    'POWERGAMING',
}
```

#### Preset Durations

```lua
-- Preset durations shown in the TIME dropdown (label + actual seconds)
Config.JailTimes = {
    { label = '15 MIN', seconds = 900   },
    { label = '30 MIN', seconds = 1800  },
    { label = '1 H',    seconds = 3600  },
    { label = '3 H',    seconds = 10800 },
    { label = '6 H',    seconds = 21600 },
    { label = '12 H',   seconds = 43200 },
    { label = '24 H',   seconds = 86400 },
}
```

#### Custom Input

```lua
-- Allow admins to type a custom value instead of picking a preset
Config.AllowCustomReason = true
Config.AllowCustomTime   = true

-- Hard caps on custom values to prevent oversized payloads / SQL bloat
Config.MaxReasonLength = 128
Config.MaxJailSeconds  = 60 * 60 * 24 * 30 -- 30 days
```

***

### Jail Items

```lua
-- Items handed to a player on jail (online targets only)
-- Wrapper auto-detects ox / qb / codem inventories and ESX / QBCore / QBox native APIs
Config.JailItems = {
    Enabled = true,
    Items = {
        { name = 'bread', count = 1 },
        { name = 'water', count = 1 },
    },
}
```

**Notes:**

* Items are only given to online players at the moment of jailing
* Offline jails skip item distribution
* Item names must match your inventory system exactly

***

### Disabled Controls

```lua
-- Controls disabled every frame while jailed
-- Reference: https://docs.fivem.net/docs/game-references/controls/
-- Comment a line out to allow that input. Empty table = nothing disabled.
Config.DisabledControls = {
    24,  -- ATTACK         (LMB / punch)
    25,  -- AIM            (RMB / aim weapon)
    47,  -- DETONATE       (G, sticky bombs)
    58,  -- THROW_GRENADE
    140, -- MELEE_ATTACK1
    141, -- MELEE_ATTACK2
    142, -- MELEE_ATTACK_ALTERNATE
    263, -- MELEE_ATTACK_HEAVY
    264, -- MELEE_BLOCK
    257, -- ATTACK_2
    23,  -- ENTER          (enter vehicle, F)
    75,  -- VEH_EXIT
    -- 21,  -- SPRINT
    -- 22,  -- JUMP
    -- 37,  -- WEAPON_SELECT (TAB)
    -- 244, -- INTERACTION_MENU (M)
}
```

**Control reference:** <https://docs.fivem.net/docs/game-references/controls/>

***

### Admin Notifications

```lua
-- Broadcast slide-in notifies to all online admins when someone jails / unjails a player
-- false = no slide-in notify shown to admins (webhook + UI list still update normally)
Config.AdminNotifyEnabled = true

-- How long (seconds) the in-game notify slides in for before auto-hiding
Config.JailNotifyDuration = 20
```

***

### Performance & Data

```lua
-- Playtime tracking — values are accumulated in RAM and only written to the database
-- on FlushInterval to avoid spamming SQL every second
Config.Playtime = {
    IncrementInterval = 60,  -- seconds between playtime ticks (RAM only)
    FlushInterval     = 300, -- seconds between database saves
}

-- Max offline players shown in the admin UI search list (sorted by most recently seen)
Config.MaxOfflinePlayers = 50

-- How often (seconds) the server pushes a fresh player list to open admin UIs
Config.PlayerList = {
    PushInterval = 5,
}

-- Discord avatar lookup (bot token lives in shared/secrets.lua, never here)
Config.Discord = {
    CacheTTL = 3600, -- seconds before a fetched avatar URL is refetched
}
```

***

### Mini-Games

Mini-game settings live in `shared/minigames_config.lua`.

#### Global Settings

```lua
Config.MiniGames = {
    -- How long (seconds) a spot stays on cooldown after being completed
    -- Applies to ALL mini-games
    MarkerCooldown = 60,

    -- Visual marker shown at each interaction spot
    Marker = {
        Type   = 1,      -- 1 = cylinder, 27 = vertical chevron, 28 = chevron down
        ScaleX = 0.8,
        ScaleY = 0.8,
        ScaleZ = 0.5,
        ZOffset = 0.0,
        Color  = { r = 19, g = 255, b = 224, a = 140 },
        Bobbing = true,
    },
}
```

#### Window Cleaning

```lua
Windows = {
    Enabled        = true,   -- true = active, false = markers hidden and spots disabled
    TimeReduction  = 60,     -- seconds removed from jail per completion
    CleanThreshold = 0.95,   -- fraction of dirt that must be cleaned to win (0.0-1.0)
    MinPlayDuration = 4,     -- minimum seconds before completion can register (anti-cheese)
    Radius         = 1.8,    -- interaction radius in meters

    -- Each entry is one cleanable spot. x/y/z = exact position.
    Spots = {
        { x = -2000.5370, y = 3194.1423, z = 32.8102 },
        { x = -2001.8000, y = 3194.1423, z = 32.8102 },
    },
}
```

#### Yard Sweeping

```lua
Yard = {
    Enabled        = true,
    TimeReduction  = 50,     -- seconds removed from jail per completion
    CleanThreshold = 1.0,    -- fraction of leaves required inside pile to win (1.0 = every leaf)
    MinPlayDuration = 5,
    LeafCount      = 28,     -- how many leaves spawn at game start
    SweepRadius    = 0.08,   -- broom reach as fraction of min(stageW, stageH)
    SweepForce     = 1.5,    -- how strongly leaves are pushed (multiplier on cursor velocity)
    LeafFriction   = 0.86,   -- per-frame velocity decay (lower = more drag, higher = more glide)

    -- Pile target zone — leaves whose centers fall inside this ellipse count as swept
    PileArea = {
        cx      = 0.80,  -- center x as fraction of stage width
        cy      = 0.78,  -- center y as fraction of stage height
        radiusX = 0.16,
        radiusY = 0.14,
    },

    Spots = {
        { x = -1995.0000, y = 3192.5000, z = 32.8102 },
    },
}
```

#### Toilet Scrubbing

```lua
Toilets = {
    Enabled        = true,
    TimeReduction  = 45,     -- seconds removed from jail per completion
    CleanThreshold = 0.98,
    MinPlayDuration = 4,
    BrushScale     = 0.38,   -- 1.0 = default size, 0.5 = half, 1.5 = 50% bigger
    BrushAnchor    = 'bottom', -- 'top' / 'center' / 'bottom' — where the cursor sits on the brush

    -- Dirt is only painted inside this ellipse
    DirtArea = {
        cx      = 0.5,
        cy      = 0.68,
        radiusX = 0.17,
        radiusY = 0.18,
    },

    Spots = {
        { x = -1987.9838, y = 3202.9543, z = 32.8102 },
    },
}
```

#### Trash Sorting

```lua
Trash = {
    Enabled        = true,
    TimeReduction  = 55,     -- seconds removed from jail per completion
    MinPlayDuration = 4,
    Items          = 8,      -- how many items to sort per round

    -- Bins shown at the bottom of the stage. id must match item types in Catalog.
    Bins = {
        { id = 'bio',      label = 'BIO',      color = '#5a8c2a' },
        { id = 'plastic',  label = 'PLASTIC',  color = '#2a6db3' },
        { id = 'residual', label = 'RESIDUAL', color = '#7a7a7a' },
    },

    -- Add more items by dropping a PNG into web/assets/img/trash/ and adding a row below
    Catalog = {
        { name = 'APPLE CORE',   type = 'bio',      icon = 'assets/img/trash/apple-core.png'   },
        { name = 'WATER BOTTLE', type = 'plastic',  icon = 'assets/img/trash/water-bottle.png' },
        { name = 'PLASTIC BAG',  type = 'plastic',  icon = 'assets/img/trash/plastic-bag.png'  },
    },

    Spots = {
        { x = -1985.0000, y = 3196.0000, z = 32.8102 },
    },
}
```

**Adding custom trash items:**

1. Drop a transparent PNG into `web/assets/img/trash/`
2. Add a new row to `Catalog` with `name`, `type` and `icon`
3. `type` must match an existing bin `id`
4. If the icon file is missing the item falls back to text-only automatically

***

### Setting Up Mini-Game Spots

#### Step 1: Find Coordinates

Go to each location in-game where you want a mini-game spot and note the exact `x`, `y`, `z` coordinates.

#### Step 2: Add Spots to Config

Open `shared/minigames_config.lua` and add your coordinates to the matching mini-game `Spots` table.

#### Step 3: Set Time Reductions

Adjust `TimeReduction` per mini-game based on difficulty:

| Mini-game        | Suggested reduction | Difficulty |
| ---------------- | ------------------- | ---------- |
| Window Cleaning  | 45-60 seconds       | Easy       |
| Yard Sweeping    | 40-55 seconds       | Medium     |
| Toilet Scrubbing | 35-50 seconds       | Medium     |
| Trash Sorting    | 50-65 seconds       | Hard       |

#### Step 4: Set Cooldowns

```lua
-- Global cooldown applies to all mini-games
Config.MiniGames.MarkerCooldown = 60 -- seconds
```

Lower = players can replay spots faster. Higher = more time between replays.

***

### Jailing Players (Admin Guide)

#### Step 1: Open Admin UI

Type `/adminjail` in chat. Only players in `Config.AllowedGroups` can open the panel.

#### Step 2: Find the Player

Use the search bar to find any online or offline player by name. Offline players appear with their last seen time and are sorted by most recently seen.

#### Step 3: Select Reason and Duration

* Click a preset reason or type a custom one (if `Config.AllowCustomReason = true`)
* Click a preset duration or type a custom one (if `Config.AllowCustomTime = true`)

#### Step 4: Click PUNISH

* **Online player:** Teleported to jail instantly, jail HUD appears, items distributed
* **Offline player:** Record saved to database, applied the moment they next join

#### Unjailing a Player

Find the player in the admin UI, click the unjail button. Player is teleported back to their position before jailing. Works for both online and offline players.

***

### Discord Webhook Setup

```lua
-- Webhook token lives in shared/secrets.lua — never put it in config.lua
```

Open `shared/secrets.lua` and add your webhook URL:

```lua
Config.Secrets = {
    DiscordWebhook = 'https://discord.com/api/webhooks/YOUR_WEBHOOK_URL',
    DiscordBotToken = 'YOUR_BOT_TOKEN', -- optional, used for avatar lookups
}
```

**What gets logged:**

* Player name, license and server ID
* Admin name and server ID
* Reason and duration
* Timestamp
* Whether the player was online or offline at the time

***

### Troubleshooting

#### `/adminjail` command does nothing

**Cause:** Group not in `Config.AllowedGroups` **Solution:** Add your exact framework group name to the list and restart

#### Jailed player can leave the jail area

**Cause:** `Config.JailMaxDistance` too high or position check not triggering **Solution:** Lower `Config.JailMaxDistance` — default is `80.0` meters, check every 10 seconds

#### Timer keeps running while player is offline

**Cause:** `Config.PauseOnOffline` is set to `false` **Solution:** Set `Config.PauseOnOffline = true`

#### Mini-game spots not showing

**Cause 1:** `Enabled = false` for that mini-game **Solution:** Set `Enabled = true` in `shared/minigames_config.lua`

**Cause 2:** Player is not jailed **Solution:** Mini-game markers only appear for jailed players — working as intended

**Cause 3:** Spot is on cooldown **Solution:** Wait for `Config.MiniGames.MarkerCooldown` seconds to expire

#### Mini-game completes instantly

**Cause:** `MinPlayDuration` too low or set to `0` **Solution:** Set `MinPlayDuration` to at least `4` seconds per mini-game

#### Offline jail not applying on join

**Cause:** Player identifier mismatch between session and database **Solution:** Enable `Config.Debug = true` and check server console for identifier logs on player join

#### Items not given on jail

**Cause 1:** `Config.JailItems.Enabled = false` **Solution:** Set `Enabled = true`

**Cause 2:** Item name doesn't match inventory system **Solution:** Check exact item name in your inventory database and match it in `Config.JailItems.Items`

***

### Best Practices

**Jail setup:**

* ✅ Place jail coords inside a proper interior or fenced area
* ✅ Set `JailMaxDistance` to match the size of your jail area
* ✅ Use `alone` bucket mode for serious RP servers
* ✅ Use `together` bucket mode if you want jailed players to interact
* ❌ Don't set `JailMaxDistance` too low — players may be teleported back constantly

**Mini-games:**

* ✅ Place spots in logical locations inside the jail (windows on walls, toilets in cells, yard outside)
* ✅ Keep `MinPlayDuration` at 4-5 seconds minimum to prevent abuse
* ✅ Set `MarkerCooldown` to at least 60 seconds to prevent farming
* ✅ Balance `TimeReduction` so players must complete multiple games to clear long sentences
* ❌ Don't place spots outside the jail area
* ❌ Don't set `CleanThreshold` below `0.85` — games become too easy

**Permissions:**

* ✅ Keep `AllowCustomReason` and `AllowCustomTime` enabled for flexibility
* ✅ Set `MaxJailSeconds` to a reasonable cap (default 30 days)
* ✅ Use `AdminNotifyEnabled = true` so all admins see jail actions in realtime
* ❌ Don't add too many groups — keep admin access tight

***

### Quick Start Checklist

**Server setup:**

* \[ ] Configure `Config.AllowedGroups` with your admin group names
* \[ ] Set `Config.JailCoords` to your jail location
* \[ ] Set `Config.DefaultReleaseCoords` to your release location
* \[ ] Configure `Config.Language` if not using English
* \[ ] Add webhook URL to `shared/secrets.lua`

**Mini-games:**

* \[ ] Open `shared/minigames_config.lua`
* \[ ] Set coordinates for each mini-game `Spots` table
* \[ ] Adjust `TimeReduction` per mini-game
* \[ ] Set `MarkerCooldown` to your preferred cooldown
* \[ ] Enable or disable individual mini-games with `Enabled = true/false`

**Test everything:**

* \[ ] Open admin UI with `/adminjail`
* \[ ] Jail an online player — verify teleport and HUD appear
* \[ ] Jail an offline player — verify record saves and applies on join
* \[ ] Walk to a mini-game spot as a jailed player — verify marker appears
* \[ ] Complete a mini-game — verify time reduces
* \[ ] Verify timer pauses on disconnect and resumes on reconnect
* \[ ] Check Discord webhook for jail log entry
* \[ ] Unjail a player — verify teleport back and HUD disappears


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://risk-scripts.gitbook.io/risk-scripts/scripts/admin-jail.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
