# About US

Otherplanet is a project by Bagieta which is created to provide high quality fivem resources. Everyday we're constantly working on new Paid and Free Releases.

{% content-ref url="/pages/fFr0LYvbF8lchhW2ORTL" %}
[OP Clothing](/scripts/op-clothing)
{% endcontent-ref %}

{% content-ref url="/pages/tGalw0Rm021r39HFFZgY" %}
[OP Mechanic V2](/scripts/op-mechanic-v2)
{% endcontent-ref %}

{% content-ref url="/pages/oqjCGZnEvngSMHIm7gqu" %}
[OP Missions Creator](/scripts/op-missions-creator)
{% endcontent-ref %}

{% content-ref url="/pages/KLXmN6G6z75UvmNDst99" %}
[OP Multicharacter](/scripts/op-multicharacter)
{% endcontent-ref %}

{% content-ref url="/pages/3v8X5ZS7zLd0jBiOynct" %}
[OP Gangs](/scripts/op-gangs)
{% endcontent-ref %}

{% content-ref url="/pages/OY0IyMM0kaz6irXazFVD" %}
[OP Garages V3](/scripts/op-garages-v3)
{% endcontent-ref %}

{% content-ref url="/pages/uA9FeILknHcz892mEV1d" %}
[OP Courier Job](/scripts/op-courier-job)
{% endcontent-ref %}

{% content-ref url="/pages/a9OByAdSTwujihXlqgSv" %}
[OP Container Wars](/scripts/op-container-wars)
{% endcontent-ref %}

{% content-ref url="/pages/FYz9gxUwf0H6Iuvkb6hz" %}
[OP HUD](/scripts/op-hud)
{% endcontent-ref %}

{% content-ref url="/pages/H8v4u28YCszpbSb1ajIk" %}
[OP Vehicle Theft](/scripts/op-vehicle-theft)
{% endcontent-ref %}

{% content-ref url="/pages/gRzIgdO4ORuHNuIPYi99" %}
[OP Drug Selling](/scripts/op-drug-selling)
{% endcontent-ref %}


# You lack the required entitlement

### ❌ Missing entitlement

#### Error example

```
You lack the required entitlement to use op-garages
```

#### What does this mean?

All OTHERPLANET scripts use the **FiveM escrow system**, which means each script is linked to a specific **FiveM account** (the one used during purchase).

To make the script work, your server must be using a **server key created on the same FiveM account** that owns the script.

You can create or manage your server key in **FiveM Keymaster**.

After creating the key, add it to your `server.cfg`:

```
sv_licenseKey "YOUR_SERVER_KEY_HERE"
```

#### The script is on another FiveM account

If the script was purchased on a **different FiveM account**, you need to transfer it to the account used for your server.

You can do this here:\
**FiveM Keymaster → Purchased assets → Transfer to another account**

⚠️ **Important:**\
cfx.re allows **only one transfer per asset**, so make sure you transfer it to the correct account.


# Failed to verify protected resource

### ❌ Failed to verify protected resource

#### Error example

```
Failed to verify protected resource op-garages
```

#### What does this mean?

FiveM failed to verify the protected (escrow) resource.\
This is usually caused by an **incorrect upload or corrupted files**.

#### How to fix it

* Restart your server
* **Do not upload the script file-by-file**\
  Upload the **.zip file as-is**, then extract it **directly on your VPS**
* Make sure the script folder contains a **.fxap file**\
  If it’s missing, reinstall the script
* If you’re using **FileZilla**, files may have been corrupted during transfer\
  Try using **WinSCP** or another FTP client instead


# Error parsing script (syntax error)

### ❌ Error parsing script (syntax error)

#### Error example

```
[script:op-garages] Error parsing script @op-crime/server/bossMenu.lua
[c-scripting-core] Failed to load script server/bossMenu.lua
```

#### What does this mean?

FiveM failed to load the script because the file is **corrupted, incomplete, or incompatible** with your server version.\
This is **not** a script bug — it’s almost always an environment or upload issue.

#### How to fix it

* If you’re using **FileZilla**, files may have been corrupted during transfer\
  → Use **WinSCP** or another FTP client
* **Do not upload the script file-by-file**\
  Upload the **.zip file as-is**, then extract it **directly on your VPS**
* Make sure your **server artifacts are up to date**\
  Minimum supported version: **4752**
  * Download updated artifacts
  * Or follow the official FiveM server update guide
* If the issue persists, **reinstall the script from scratch**


# Database export

How to export your database (HeidiSQL / phpMyAdmin)

### 📦 How to export your database (HeidiSQL / phpMyAdmin)

Database export is required when:

* we ask you to **send a database dump** for debugging
* you are **moving your server**
* you want to **create a backup before changes**

Below are two supported methods.

***

### ✅ Method 1: Export using phpMyAdmin

#### Steps

1. Open **phpMyAdmin**
2. Select your **database** from the left sidebar
3. Click the **Export** tab
4. Choose **Quick** export method
5. Format: **SQL**
6. Click **Export** and download the `.sql` file

📺 Video tutorial:

{% embed url="<https://www.youtube.com/watch?v=KmlyhiqoUtk>" %}

***

### ✅ Method 2: Export using HeidiSQL (recommended)

#### Steps

1. Open **HeidiSQL** and connect to your database
2. Right-click your **database name**
3. Click **Export database as SQL**
4. Select:
   * Output: **SQL file**
   * Structure + Data
5. Start the export and save the `.sql` file

📺 Video tutorial:

{% embed url="<https://www.youtube.com/watch?v=P_DwadCOUno>" %}

***

### ⚠️ Important notes

* **Do not modify** the exported `.sql` file
* **Do not send screenshots** — always send the `.sql` file
* If the database is large, **compress it to `.zip`** before uploading
* Never share database files publicly — send them **only in tickets**

***

### 📩 Sending the file to support

Upload the exported `.sql` (or `.zip`) file **directly in your ticket** so we can analyze the issue properly.


# OP Clothing

OP Clothing is the most complete clothing, barber and tattoo system ever released for FiveM.

{% embed url="<https://otherplanet.dev/product/clothing>" %}


# Installation

## Downloading Dependencies

Dependencies are very important — you need to install them before moving on to the installation of our script.

{% stepper %}
{% step %}

### ox\_lib

{% embed url="<https://github.com/overextended/ox_lib>" %}
{% endstep %}

{% step %}

### screencapture (optional)

Install this dependency only on your Test Server if you want to complete Clothing Screenshots!

{% embed url="<https://github.com/itschip/screencapture>" %}

{% endstep %}
{% endstepper %}

***

## Server.cfg

All dependencies you’ve installed must be started before our script. Make sure to follow the correct start-up order in your `server.cfg`.

```
# REQUIRED:

ensure ox_lib
ensure screencapture #optional
ensure op-clothing
```

***

## Migration

Our script have built-in migration commands for many resources.

{% hint style="danger" %}
**Back up your database before running any migration.** Migrations only read from the source tables, but a backup is always the safe move.
{% endhint %}

<table><thead><tr><th width="247">Script</th><th width="149">Framework</th><th>Command</th></tr></thead><tbody><tr><td>Illenium Appearance</td><td>ESX</td><td>op_clothing_migrate_illenium_esx</td></tr><tr><td>Illenium Appearance</td><td>QB/QBOX</td><td>op_clothing_migrate_illenium_qb</td></tr><tr><td>Skinchanger</td><td>ESX</td><td>op_clothing_migrate_skinchanger_esx</td></tr><tr><td>Fivem Appearance</td><td>ESX</td><td>op_clothing_migrate_fivem_appearance_esx</td></tr><tr><td>RCore Clothing</td><td>ESX/QB/QBOX</td><td>op_clothing_migrate_rcore</td></tr><tr><td>RCore Clothing (only outfits)</td><td></td><td>op_clothing_migrate_rcore_outfits_only</td></tr></tbody></table>

{% hint style="warning" %}
THESE COMMANDS ARE ONLY TRIGGERABLE FROM TX ADMIN CONSOLE! DO NOT PERFORM ON LIVE SERVER WITH PLAYERS.
{% endhint %}


# Tutorials


# Screenshotting Clothes

How to generate thumbnails for addon (streamed) clothing.

## Addon Clothing Screenshots

{% hint style="info" %}
Vanilla GTA clothing already has thumbnails served from the OTHERPLANET CDN, so you only need to capture **addon / streamed** items. The menu works without any images - missing items just show a numbered placeholder.
{% endhint %}

The thumbnail generator spawns a ped in a hidden sky studio, applies one item at a time against a green backdrop, takes a screenshot, then chroma-keys it into a transparent PNG.

## Requirements

* **screenshot-basic** must be started (set in `server.cfg` before `op-clothing`).
* `Config.Thumbnails.Enabled = true`.
* You must pass the admin permission gate (or run it on a player you control).
* Your addon clothing must be **streamed and working** in-game first.

{% hint style="warning" %}
Run the capture on a **test / dev server** with no other players around. The ped is teleported to a sky studio for each shot, so do it on a character you don't mind moving.
{% endhint %}

## Step 1 - Quick test first

Validate framing and the green-screen key on a few items before a full run:

```
/op_clothing_thumbs_test male 3
/op_clothing_thumbs_test female 3
```

This captures only the first 3 drawables per category. Open the clothing menu and check the tiles look correct.

## Step 2 - Full capture

Run a complete batch per gender:

```
/op_clothing_thumbs male
/op_clothing_thumbs female
```

{% hint style="info" %}
With `Config.Thumbnails.SkipCdnCapture = true` (default) the batch **skips every item already on the CDN** and only shoots what's missing - i.e. your addon items. This keeps the run short even on a full wardrobe.
{% endhint %}

## Step 3 - Targeted captures (optional)

| Command                                           | What it captures               |
| ------------------------------------------------- | ------------------------------ |
| `/op_clothing_thumbs_hair [male\|female] [count]` | Hairstyles only (head visible) |
| `/op_clothing_thumbs_wrist [male\|female]`        | Watches + bracelets only       |
| `/op_clothing_thumbs_test [male\|female] [count]` | First N drawables per category |

You can also **re-shoot a single item** from the in-game admin menu (`/clothingadmin` → item → re-capture) when one thumbnail needs a redo.

## Step 4 - Build the hash map

Addon clothing shifts the raw drawable indices whenever packs are added or removed. The hash map keeps existing thumbnails resolving to the right item:

```
/op_clothing_thumbs_map male
/op_clothing_thumbs_map female
```

{% hint style="success" %}
With `Config.Thumbnails.AutoHashMapAfterBatch = true` (default) this runs automatically after every full batch, so you usually don't need to call it by hand.
{% endhint %}

## Step 5 - Serve the new images

Captured PNGs are written to `web/thumbnails` inside the `Config.Thumbnails.StoreResource` (default `op-clothing`). To serve the fresh `nui://` URLs:

```
restart op-clothing
```

If you moved the store to a **separate assets resource**, that resource is remounted automatically and no restart of op-clothing is needed.

## Where files go

| Setting                           | Default                                   | Meaning                         |
| --------------------------------- | ----------------------------------------- | ------------------------------- |
| `Config.Thumbnails.StoreResource` | `op-clothing`                             | Resource the PNGs live in       |
| `Config.Thumbnails.StorePath`     | `web/thumbnails`                          | Folder inside that resource     |
| `Config.Thumbnails.LocalBaseUrl`  | `nui://op-clothing/web/thumbnails/%s.png` | URL template for captured items |
| `Config.Thumbnails.BaseUrl`       | CDN                                       | URL template for vanilla items  |

## Troubleshooting

* **"screenshot-basic is not started"** - add it to `server.cfg` above op-clothing.
* **Green edges on items** - lower `Config.Thumbnails.KeyHard` (more aggressive key).
* **Wrong framing for a slot** - tweak `Config.Thumbnails.CameraSlots['component:X']` (`fov`, `distance`, `zPos`).
* **Thumbnails don't update** - `restart op-clothing` (or reopen the menu) so the new `nui://` files are mounted.
* **Access denied** - check your permissions.


# Permissions

How to grant admin access to op-clothing features.

{% hint style="info" %}
Every admin feature - the thumbnail generator, `/clothingadmin`, the shop editor, the job locker editor and custom ped management - shares **one** permission gate configured in `config/MainConfig.lua` under `Config.Permissions`.
{% endhint %}

## The gate

```lua
Config.Permissions = {
    AcePermission = 'op_clothing.admin',            -- ACE permission object
    AllowedGroups = { 'admin', 'superadmin', 'god' }, -- framework groups
}
```

A player passes the gate when **any** of these is true:

* They hold the `AcePermission` ACE, **or**
* Their framework group is in `AllowedGroups`, **or**
* The command is run from the **server console**.

## Option A - ACE permission (recommended)

ACE works on every framework and is independent of in-game jobs/groups. Add this to your `server.cfg`:

```
# allow a whole group
add_ace group.admin op_clothing.admin allow

# or allow a single identifier (license, steam, etc.)
add_principal identifier.license:xxxxxxxxxxxxxxxx group.admin
```

{% hint style="success" %}
If your admins are already in `group.admin` (txAdmin assigns this by default), the single `add_ace group.admin op_clothing.admin allow` line is all you need.
{% endhint %}

## Option B - framework groups

If you'd rather gate by your framework's group/permission, leave `AcePermission` as-is and edit `AllowedGroups` to match your setup:

```lua
AllowedGroups = { 'admin', 'superadmin', 'god' },
```

* **ESX** - matches the player's `group` (`admin`, `superadmin`, ...).
* **QBCore / Qbox** - matches the player's permission level (`admin`, `god`).

## Custom ped permission (separate)

Granting players access to **custom peds** is intentionally a different, narrower permission so you can hand it out without full admin rights. It lives in `config/CustomPeds.lua`:

```lua
AllowAce = 'op-clothing.customped.allow',
```

Holders of this ACE (or anyone who passes the main admin gate) can pick custom peds. Per-player grants are stored in the database and managed through these exports:

```lua
exports['op-clothing']:GrantCustomPedPermission(src)
exports['op-clothing']:RevokeCustomPedPermission(src)
local has = exports['op-clothing']:HasCustomPedPermission(src)
```

## What each feature checks

| Feature             | Command                  | Gate                                              |
| ------------------- | ------------------------ | ------------------------------------------------- |
| Clothing admin hub  | `/clothingadmin`         | `Config.Permissions`                              |
| Thumbnail generator | `/op_clothing_thumbs`    | `Config.Permissions`                              |
| Shop editor         | `/clothingadmin shops`   | `Config.Permissions`                              |
| Job locker editor   | `/clothingadmin lockers` | `Config.Permissions`                              |
| Custom peds         | `/charmenu`              | `Config.Permissions` **or** `CustomPeds.AllowAce` |

## Troubleshooting

* **"No access" in-game** - confirm the ACE line is in `server.cfg` and the player is in the right group, then `refresh` / reconnect.
* **Works in console but not in-game** - the console always passes; your in-game player is missing the ACE or group.
* **Changed the config but nothing changed** - restart op-clothing so the new `Config.Permissions` is loaded.


# Customization

Our script is fully configurable, allowing you to customize it however you like. You can also modify many functions located in the so-called "Editable" zone!

## With the Editable files, you can:

* Add your own FRAMEWORK
* Modify functions, such as:
  * Modify notifications
  * Change the targeting system


# Exports / Events


# Client Side

## Exports & Events

{% hint style="warning" %}
If you're looking for other data than listed here - feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

These are the native **op-clothing** exports and events. The optional `ped` argument defaults to the local player when omitted.

### OpenClothingShop

Opens a clothing shop by id.

```lua
exports['op-clothing']:OpenClothingShop(shopId)
```

***

### OpenBarber

Opens a barber shop by id (alias: `OpenBarberShop`).

```lua
exports['op-clothing']:OpenBarber(shopId)
```

***

### OpenTattooShop

Opens a tattoo shop by id.

```lua
exports['op-clothing']:OpenTattooShop(shopId)
```

***

### OpenCharacterCreator

Opens the character creator. `opts` is optional.

```lua
exports['op-clothing']:OpenCharacterCreator({
    allowCancel = true,
    allowModelChange = true,
    onComplete = function(appearance)
        print('character created')
    end,
})
```

***

### OpenWardrobe

Opens the free wardrobe (owned outfits + full catalog, no prices). `opts` is optional.

```lua
exports['op-clothing']:OpenWardrobe()
```

***

### GetAppearance

Returns the ped's full appearance table.

```lua
local appearance = exports['op-clothing']:GetAppearance()
```

***

### SetAppearance

Applies a full appearance table to the ped.

```lua
exports['op-clothing']:SetAppearance(appearance)
```

***

### SetPedSkin

Applies a full appearance table (ped-first argument).

```lua
exports['op-clothing']:SetPedSkin(ped, appearance)
```

***

### SetClothing

Applies only the clothing part of an appearance, keeping the face/hair.

```lua
exports['op-clothing']:SetClothing(appearance)
```

***

### GetPlayerClothing

Returns a clothing-only payload (components + props), re-applicable through `ApplyOutfit`.

```lua
local outfit = exports['op-clothing']:GetPlayerClothing()
```

***

### SetPlayerClothing

Merges a clothing-only payload onto the ped's current look.

```lua
exports['op-clothing']:SetPlayerClothing(clothing)
```

***

### ApplyOutfit

Merges an outfit onto the ped's current look (keeps face, hair, heritage).

```lua
exports['op-clothing']:ApplyOutfit(outfit)
```

***

### SaveAppearance

Persists the ped's current look to the database.

```lua
exports['op-clothing']:SaveAppearance()
```

***

### ReloadAppearance

Re-pulls the saved look from the server and re-applies it (re-attaches tattoos/decorations).

```lua
exports['op-clothing']:ReloadAppearance()
```

***

### IsMenuOpen

Returns a `boolean` - whether any op-clothing menu is currently open.

```lua
local isOpen = exports['op-clothing']:IsMenuOpen()
```

***

### OpenJobLocker

Opens a job locker by its id.

```lua
exports['op-clothing']:OpenJobLocker(lockerId)
```

***

### OpenJobLockerByJob

Opens the nearest accessible locker for the given job. Returns a `boolean`.

```lua
local opened = exports['op-clothing']:OpenJobLockerByJob('police')
```

***

## Client Events

### op-clothing:client:menuOpened

Fired whenever any clothing/barber/tattoo/creator menu becomes visible.

```lua
AddEventHandler('op-clothing:client:menuOpened', function(data)
    -- data = { mode, shopId, free }
end)
```

***

### op-clothing:client:menuClosed

Fired whenever the menu is dismissed (confirm or cancel).

```lua
AddEventHandler('op-clothing:client:menuClosed', function(data)
    -- data = { mode }
end)
```

***

### op-clothing:client:appearanceLoaded

Fired after the saved look is (re)applied on spawn or `ReloadAppearance`.

```lua
AddEventHandler('op-clothing:client:appearanceLoaded', function(appearance)
    -- Your Code
end)
```

***

### op-clothing:client:characterCreated

Fired after the character creator finishes.

```lua
AddEventHandler('op-clothing:client:characterCreated', function(appearance)
    -- Your Code
end)
```

***

### op-clothing:client:purchased

Fired after a successful clothing purchase.

```lua
AddEventHandler('op-clothing:client:purchased', function(data)
    -- data = { shopId, price }
end)
```

***

### Triggerable client events

Drive op-clothing from another resource without grabbing the export reference.

```lua
TriggerEvent('op-clothing:client:openShop', shopId)
TriggerEvent('op-clothing:client:openWardrobe', opts)
TriggerEvent('op-clothing:client:openCharacterCreator', opts)
TriggerEvent('op-clothing:client:openJobLocker', lockerIdOrJob)
TriggerEvent('op-clothing:client:reloadAppearance')
TriggerEvent('op-clothing:client:saveAppearance')
```


# Server Side

## Exports & Events

{% hint style="warning" %}
If you're looking for other data than listed here - feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

### GetAppearance (server)

Returns the player's saved appearance from the database.

```lua
local appearance = exports['op-clothing']:GetAppearance(src)
```

***

### SetAppearance (server)

Saves and applies a full appearance to the player. Pass a `pedNetId` to target a specific ped.

```lua
exports['op-clothing']:SetAppearance(src, appearance, pedNetId)
```

***

### SetClothing (server)

Applies only the clothing part of an appearance to the player.

```lua
exports['op-clothing']:SetClothing(src, appearance, pedNetId)
```

***

### SaveAppearance (server)

Persists an appearance for the player without re-applying it.

```lua
exports['op-clothing']:SaveAppearance(src, appearance)
```

***

### GetOutfits

Returns the player's saved outfits.

```lua
local outfits = exports['op-clothing']:GetOutfits(src)
```

***

### OpenShop

Opens a clothing shop on the player's client (aliases: `OpenClothingShop`, `OpenBarberShop`, `OpenTattooShop`).

```lua
exports['op-clothing']:OpenShop(src, shopId)
```

***

### OpenCharacterCreator (server)

Opens the character creator on the player's client.

```lua
exports['op-clothing']:OpenCharacterCreator(src, opts)
```

***

### OpenJobLocker (server)

Opens a job locker on the player's client.

```lua
exports['op-clothing']:OpenJobLocker(src, lockerId)
```

***

### CanOpenJobLocker

Returns a `boolean` - whether the player may open the given locker.

```lua
local canOpen = exports['op-clothing']:CanOpenJobLocker(src, lockerId)
```

***

### GetShop / GetAllShops

Returns a single shop config, or all shops.

```lua
local shop = exports['op-clothing']:GetShop(shopId)
local shops = exports['op-clothing']:GetAllShops()
```

***

### GetJobLocker / GetAllJobLockers

Returns a single locker config, or all lockers.

```lua
local locker = exports['op-clothing']:GetJobLocker(lockerId)
local lockers = exports['op-clothing']:GetAllJobLockers()
```

***

### Custom ped permissions

Grant, revoke or check a player's permission to use custom peds.

```lua
exports['op-clothing']:GrantCustomPedPermission(src)
exports['op-clothing']:RevokeCustomPedPermission(src)
local has = exports['op-clothing']:HasCustomPedPermission(src)
```

## Server Events

### op-clothing:server:characterCreated

Fired server-side when a player finishes the character creator.

```lua
AddEventHandler('op-clothing:server:characterCreated', function(src, appearance)
    -- Your Code
end)
```

***

### op-clothing:server:purchased

Fired server-side after a successful purchase.

```lua
AddEventHandler('op-clothing:server:purchased', function(src, shopId, price)
    -- Your Code
end)
```


# Migrations

Integrations & Migrations

{% hint style="danger" %}
**Back up your database before running any migration.** Migrations only read from the source tables, but a backup is always the safe move.
{% endhint %}

op-clothing is a drop-in replacement for the most popular clothing scripts. It **provides** their resource names and forwards their exports/events, so other resources keep working without changes - and it ships console commands to migrate existing player skins and outfits into op-clothing.

## Supported integrations

These resource names are provided by op-clothing. Do **not** start the original resource alongside it.

| Script                  | Skins | Outfits | Notes        |
| ----------------------- | :---: | :-----: | ------------ |
| illenium-appearance     |  yes  |   yes   | ESX + QBCore |
| fivem-appearance        |  yes  |   yes   | ESX          |
| skinchanger / esx\_skin |  yes  |    -    | ESX          |
| qb-clothing             |  yes  |   yes   | QBCore       |
| rcore\_clothing         |  yes  |   yes   | ESX + QBCore |

Compatibility is controlled in `config/MainConfig.lua` under `Config.Compat` (`Enabled`, `LegacyProvides`, `IlleniumExports`, `SkinchangerEvents`, `QbClothingEvents`, `RcoreEvents`, `NormalizeOnLoad`).

{% hint style="info" %}
Migration commands are **server-console only** (admin-restricted) and take no arguments unless noted. Run them once, then remove the old resource.
{% endhint %}

## op\_clothing\_migrate\_illenium\_esx

Migrates illenium-appearance on **ESX**: skins from `users.skin` and outfits from `player_outfits`.

```
op_clothing_migrate_illenium_esx
```

***

## op\_clothing\_migrate\_illenium\_qb

Migrates illenium-appearance on **QBCore**: skins from `playerskins` and outfits from `player_outfits`.

```
op_clothing_migrate_illenium_qb
```

***

## op\_clothing\_migrate\_skinchanger\_esx

Migrates skinchanger / esx\_skin skins from the ESX `users.skin` column.

```
op_clothing_migrate_skinchanger_esx
```

***

## op\_clothing\_migrate\_fivem\_appearance\_esx

Migrates fivem-appearance on **ESX**: skins from `users.skin` and outfits from the `outfits` table.

```
op_clothing_migrate_fivem_appearance_esx
```

***

## op\_clothing\_migrate\_rcore

Migrates rcore\_clothing: active skins from `rcore_clothing_current` and saved outfits from `rcore_clothing_outfits`.

```
op_clothing_migrate_rcore
```

***

## op\_clothing\_migrate\_rcore\_outfits\_only

Migrates only the saved outfits from `rcore_clothing_outfits` (skips active skins).

```
op_clothing_migrate_rcore_outfits_only
```

***

## op\_clothing\_migrate\_outfits\_only

Migrates only saved outfits. Pass `illenium` (default) or `fivem` to pick the source table.

```
op_clothing_migrate_outfits_only illenium
op_clothing_migrate_outfits_only fivem
```

***

## Where migrated data goes

| Data          | Destination                                    |
| ------------- | ---------------------------------------------- |
| Player skins  | Framework table (`users.skin` / `playerskins`) |
| Saved outfits | `op_clothing_outfits`                          |

## Notes & limitations

* Migrations are **read-only** on the source tables - your old data is untouched.
* Tattoos are not carried over from formats that don't store them (e.g. rcore\_clothing).
* Non-freemode ped models fall back to a freemode model when the source only stores a model hash.
* Re-running a migration may create duplicate outfits - run each command once.


# OP Ambulance Job

Advanced ambulance and medical system for FiveM. Hospital admin, injury treatment, death flow, stretchers, CK / DNA lab, pharmacy and insurance. Built for ESX, QBCore and Qbox.


# Installation

Before you start, make sure you're logged into the CFX portal. This ensures you can download the asset and receive future updates.

#### Downloading Dependencies

Dependencies are required before you start `op-ambulancejob`.

{% stepper %}
{% step %}
**ox\_lib**

{% embed url="<https://github.com/overextended/ox_lib>" %}
{% endstep %}

{% step %}
**oxmysql**

{% embed url="<https://github.com/overextended/oxmysql>" %}
{% endstep %}

{% step %}
**fivem-freecam**

Used by Hospital Admin prop placer.

{% embed url="<https://github.com/Deltanic/fivem-freecam>" %}
{% endstep %}

{% step %}
**op-ambulancejob-props**

Shipped with the package. Streams stretcher, wheelchair and CK skeleton models. Start it before `op-ambulancejob`.
{% endstep %}

{% step %}

### op-ambulancemdt

Shipped with the package
{% endstep %}
{% endstepper %}

***

#### Server.cfg

Start dependencies before our script. Framework and inventory must also be running.

```
ensure oxmysql
ensure ox_lib
ensure fivem-freecam
ensure op-ambulancejob-props
ensure op-ambulancejob
ensure op-ambulancemdt
```

***

#### Database

On first start the script creates its own tables automatically.

| Table                   | Purpose                               |
| ----------------------- | ------------------------------------- |
| `opa_hospitals`         | Hospitals created in `/hospitaladmin` |
| `opa_hospital_clothing` | Saved wardrobe outfits                |
| `opa_medical`           | Persisted medical state               |
| `opa_ck_permits`        | CK permits                            |
| `opa_ck_characters`     | CK character records                  |
| `opa_ck_corpses`        | World corpses / body bags             |

It also adds `insuranceDate` on `users` / `players` when missing, and optional `job` / `configName` columns on owned vehicle tables for job garage.

***

#### First Setup

{% stepper %}
{% step %}
**Create items**

{% content-ref url="/pages/GOmLXUl6V2wO4gEFQ8cC" %}
[Create Items](/scripts/op-ambulance-job/create-items)
{% endcontent-ref %}
{% endstep %}

{% step %}
**Grant Hospital Admin**

Follow How to add player to Hospital Admin, then open `/hospitaladmin`.
{% endstep %}

{% step %}
**Create a hospital**

Follow How to setup the first hospital. Link real framework job names (for example `ambulance`).
{% endstep %}

{% step %}
**Configure locale / currency**

Edit `config/MainConfig.lua` (`Config.Locale`, `Config.Currency`) and check other files listed in Config Files.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
EMS jobs come from hospitals you create in Hospital Admin - there is no static job name list to maintain for medic access.
{% endhint %}


# Tutorials


# How to add player to Hospital Admin

/hospitaladmin is gated the same way as other OTHERPLANET admin panels: ACE -> identifier whitelist -> framework rank.

#### Option 1 - ACE (recommended)

In `server.cfg`:

```
add_ace group.admin opambulance.admin allow
```

Restart server, then run `/hospitaladmin` in-game.

#### Option 2 - Identifier whitelist

Edit `config/MainConfig.lua`:

```lua
Config.AdminPanelPlayers = {
    ['license:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'] = true,
    -- ['discord:000000000000000000'] = true,
}
```

Use license / discord / character / citizenid identifiers from txAdmin.

#### Option 3 - Framework rank

```lua
Config.RanksPermissions = {
    AdminPanelPlayers = {
        ['esx'] = { 'superadmin', 'admin', 'mod' },
        ['qbcore'] = { 'god', 'admin' },
        ['qbox'] = { 'god', 'admin' },
    },
}
```

{% hint style="info" %}
Command name defaults to `hospitaladmin` (`config/AdminConfig.lua` -> `Config.HospitalAdmin.command`). ACE name defaults to `opambulance.admin`.
{% endhint %}

Same ACL is used for `/fckmenu` (CK admin).


# How to setup the first hospital

Hospitals are stored in the database and managed from Hospital Admin. There is no static hospital seed list in config - medic jobs sync from hospitals you create.

{% stepper %}
{% step %}
**Open Hospital Admin**

After you have permission, run:

```
/hospitaladmin
```

{% endstep %}

{% step %}
**Create hospital**

Set at least:

* Label - display name
* Allowed jobs - must match real framework job names (example: `ambulance`)
* Spawn / beds - respawn and local-help bed points
* Blip - optional map blip
* Duty point - clock in / out
* Stashes / wardrobe / garage - staff points
* Pharmacy / insurance / local help - civilian services (optional)
* DNA lab / printer - forensics / documents (optional)
* Permissions tab - min grade per job for duty, stash, wardrobe, dna, printer, garage
  {% endstep %}

{% step %}
**Framework job**

Create the job in your framework (ESX jobs / QB shared jobs) with the same name as hospital `allowedJobs`. Grades should match Hospital Admin permission grades.
{% endstep %}

{% step %}
**Duty / access**

Players need an allowed hospital job. Street EMS features (examine, stretcher, GPS, pharmacy) use job membership from hospitals. Point features (duty, stash, wardrobe, garage, DNA, printer) also check the Permissions tab min grade for that hospital.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
If the hospital job name does not exist on the framework, duty and garage will not work correctly.
{% endhint %}

{% hint style="info" %}
Dispatch jobs for distress (`Config.Dispatch.jobs`) are separate - they must match keys in op-ambulancemdt `Config.Jobs` when MDT is used.
{% endhint %}


# Create Items

{% hint style="warning" %}
If your inventory is not on this list, create the same item names using your inventory format.
{% endhint %}

{% hint style="info" %}
Item icons ship with the package - check `[itemsicons]` / `web/items`.
{% endhint %}

#### Treatment

| Item           | Purpose                                     |
| -------------- | ------------------------------------------- |
| `bandage`      | Self-stop bleeding. EMS: blunt / burn dress |
| `quickclot`    | Heavy bleed / GSW (use first when bleeding) |
| `suturekit`    | Close deep wounds                           |
| `tourniquet`   | Limb bleed                                  |
| `splint`       | Fracture                                    |
| `saline`       | Stabilize after bleed control               |
| `bagvalvemask` | CPR after stabilize                         |

#### Civilian

| Item          | Purpose                                           |
| ------------- | ------------------------------------------------- |
| `medkit`      | Full health + stop bleeding (not GSW / fractures) |
| `painkillers` | Temporary limp relief                             |

#### Equipment

| Item             | Purpose                                         |
| ---------------- | ----------------------------------------------- |
| `ems_gps`        | Toggle unit GPS blips                           |
| `ems_stretcher`  | Place stretcher (needs `op-ambulancejob-props`) |
| `ems_wheelchair` | Place wheelchair                                |
| `medbag_small`   | Small med bag - 8 slots / 10kg                  |
| `medbag_big`     | Big med bag - 16 slots / 25kg                   |

#### CK / documents

| Item                    | Purpose                                             |
| ----------------------- | --------------------------------------------------- |
| `body_bag_empty`        | Required to collect a CK corpse                     |
| `body_bag`              | Filled bag with corpse metadata - use to place back |
| `dna_sample`            | Unanalyzed swab (`metadata.dna`)                    |
| `dna_print_data`        | Analyzed profile for hospital printer               |
| `death_certificate`     | Printed death certificate                           |
| `incident_report`       | Printed incident report                             |
| `call_report`           | Printed call report                                 |
| `insurance_certificate` | Printed insurance proof                             |

#### Placeables

Defined in `config/PlaceablesConfig.lua` (example: `ems_cone`, `ems_barrier`, `ems_medbag_prop`). Create matching inventory items if you enable them.

***

{% tabs %}
{% tab title="ox\_inventory" %}
Paste into `ox_inventory/data/items.lua`.

Also merge the containers block into `ox_inventory/data/containers.lua` (do not replace the whole file).

```lua
['medkit'] = {
    label = 'Medkit',
    weight = 800,
    stack = true,
    close = true,
    description = 'Restore full health and stop bleeding. Does not treat gunshots or fractures.',
    server = { export = 'op-ambulancejob.useMedkit' },
    client = { export = 'op-ambulancejob.useMedkit' },
},

['painkillers'] = {
    label = 'Painkillers',
    weight = 80,
    stack = true,
    close = true,
    description = 'Temporarily stop limping. Does not heal fractures.',
    server = { export = 'op-ambulancejob.usePainkillers' },
    client = { export = 'op-ambulancejob.usePainkillers' },
},

['bandage'] = {
    label = 'Bandage',
    weight = 100,
    stack = true,
    close = true,
    description = 'Stop your own bleeding, or dress bruises/burns (EMS).',
    server = { export = 'op-ambulancejob.useBandage' },
    client = { export = 'op-ambulancejob.useBandage' },
},

['quickclot'] = {
    label = 'QuickClot',
    weight = 120,
    stack = true,
    close = true,
    description = 'Stop heavy bleeding / gunshot hemorrhage.',
},

['tourniquet'] = {
    label = 'Tourniquet',
    weight = 180,
    stack = true,
    close = true,
    description = 'Catastrophic limb bleeding.',
},

['suturekit'] = {
    label = 'Suture Kit',
    weight = 250,
    stack = true,
    close = true,
    description = 'Close deep cuts and entry wounds before bandaging.',
},

['splint'] = {
    label = 'Splint',
    weight = 400,
    stack = true,
    close = true,
    description = 'Stabilize fractured limbs.',
},

['saline'] = {
    label = 'Saline Solution',
    weight = 300,
    stack = true,
    close = true,
    description = 'Stabilize after bleeding control.',
},

['bagvalvemask'] = {
    label = 'Bag Valve Mask',
    weight = 600,
    stack = true,
    close = true,
    description = 'CPR after stabilize.',
},

['ems_gps'] = {
    label = 'EMS GPS',
    weight = 200,
    stack = false,
    close = true,
    description = 'Toggle unit GPS - other medics with GPS on see you on the map.',
    server = { export = 'op-ambulancejob.useEmsGps' },
},

['ems_stretcher'] = {
    label = 'EMS Stretcher',
    weight = 5000,
    stack = false,
    close = true,
    description = 'Place a stretcher - push, lower/fold, load patients into an ambulance.',
    server = { export = 'op-ambulancejob.useEmsStretcher' },
    client = { export = 'op-ambulancejob.useEmsStretcher' },
},

['ems_wheelchair'] = {
    label = 'Wheelchair',
    weight = 8000,
    stack = false,
    close = true,
    description = 'Place a wheelchair. Sit to ride, pick up to put it back in inventory.',
    server = { export = 'op-ambulancejob.useEmsWheelchair' },
},

['body_bag_empty'] = {
    label = 'Empty Body Bag',
    weight = 1500,
    stack = true,
    close = true,
    description = 'Unused body bag. Required to collect a corpse.',
},

['body_bag'] = {
    label = 'Body Bag',
    weight = 5000,
    stack = false,
    close = true,
    description = 'Contains collected remains. Use to place the body back in the world.',
    server = { export = 'op-ambulancejob.useBodyBag' },
},

['dna_sample'] = {
    label = 'DNA Sample',
    weight = 40,
    stack = false,
    close = true,
    description = 'Unanalyzed biological sample.',
},

['dna_print_data'] = {
    label = 'DNA Data to print',
    weight = 10,
    stack = false,
    close = true,
    description = 'Analyzed DNA profile. Use a hospital printer later.',
},

['death_certificate'] = {
    label = 'Death Certificate',
    weight = 20,
    stack = false,
    close = true,
    consume = 0,
    description = 'Official death certificate. Use to read the document.',
    server = { export = 'op-ambulancejob.useDeathCertificate' },
},

['incident_report'] = {
    label = 'Incident Report',
    weight = 20,
    stack = false,
    close = true,
    consume = 0,
    description = 'Printed hospital incident report. Use to read the document.',
    server = { export = 'op-ambulancejob.useHospitalDocument' },
},

['call_report'] = {
    label = 'Call Report',
    weight = 20,
    stack = false,
    close = true,
    consume = 0,
    description = 'Printed hospital call report. Use to read the document.',
    server = { export = 'op-ambulancejob.useHospitalDocument' },
},

['insurance_certificate'] = {
    label = 'Insurance Certificate',
    weight = 20,
    stack = false,
    close = true,
    consume = 0,
    description = 'Printed proof of hospital insurance. Use to read the document.',
    server = { export = 'op-ambulancejob.useHospitalDocument' },
},

['medbag_small'] = {
    label = 'Small Med Bag',
    weight = 1500,
    stack = false,
    close = false,
    description = 'Compact EMS bag. 8 slots / 10kg.',
},

['medbag_big'] = {
    label = 'Big Med Bag',
    weight = 3500,
    stack = false,
    close = false,
    description = 'Large EMS bag. 16 slots / 25kg.',
},
```

`ox_inventory/data/containers.lua`:

```lua
['medbag_small'] = {
    slots = 8,
    maxWeight = 10000,
},

['medbag_big'] = {
    slots = 16,
    maxWeight = 25000,
},
```

{% endtab %}

{% tab title="qb-core / qbox" %}
Paste into `qb-core/shared/items.lua` (or your Qbox shared items).

```lua
medkit = {
    name = 'medkit',
    label = 'Medkit',
    weight = 800,
    type = 'item',
    image = 'medkit.png',
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Restore full health and stop bleeding. Does not treat gunshots or fractures.',
},
painkillers = {
    name = 'painkillers',
    label = 'Painkillers',
    weight = 80,
    type = 'item',
    image = 'painkillers.png',
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Temporarily stop limping. Does not heal fractures.',
},
bandage = {
    name = 'bandage',
    label = 'Bandage',
    weight = 100,
    type = 'item',
    image = 'bandage.png',
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Stop your own bleeding, or dress bruises/burns (EMS).',
},
quickclot = {
    name = 'quickclot',
    label = 'QuickClot',
    weight = 120,
    type = 'item',
    image = 'quickclot.png',
    unique = false,
    useable = false,
    shouldClose = true,
    combinable = nil,
    description = 'Stop heavy bleeding / gunshot hemorrhage.',
},
tourniquet = {
    name = 'tourniquet',
    label = 'Tourniquet',
    weight = 180,
    type = 'item',
    image = 'tourniquet.png',
    unique = false,
    useable = false,
    shouldClose = true,
    combinable = nil,
    description = 'Catastrophic limb bleeding.',
},
suturekit = {
    name = 'suturekit',
    label = 'Suture Kit',
    weight = 250,
    type = 'item',
    image = 'suturekit.png',
    unique = false,
    useable = false,
    shouldClose = true,
    combinable = nil,
    description = 'Close deep cuts and entry wounds.',
},
splint = {
    name = 'splint',
    label = 'Splint',
    weight = 400,
    type = 'item',
    image = 'splint.png',
    unique = false,
    useable = false,
    shouldClose = true,
    combinable = nil,
    description = 'Stabilize fractured limbs.',
},
saline = {
    name = 'saline',
    label = 'Saline Solution',
    weight = 300,
    type = 'item',
    image = 'saline.png',
    unique = false,
    useable = false,
    shouldClose = true,
    combinable = nil,
    description = 'Stabilize after bleeding control.',
},
bagvalvemask = {
    name = 'bagvalvemask',
    label = 'Bag Valve Mask',
    weight = 600,
    type = 'item',
    image = 'bagvalvemask.png',
    unique = false,
    useable = false,
    shouldClose = true,
    combinable = nil,
    description = 'CPR after stabilize.',
},
ems_gps = {
    name = 'ems_gps',
    label = 'EMS GPS',
    weight = 200,
    type = 'item',
    image = 'ems_gps.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Toggle unit GPS - other medics with GPS on see you on the map.',
},
ems_stretcher = {
    name = 'ems_stretcher',
    label = 'EMS Stretcher',
    weight = 5000,
    type = 'item',
    image = 'ems_stretcher.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Place a stretcher - push, lower/fold, load patients into an ambulance.',
},
ems_wheelchair = {
    name = 'ems_wheelchair',
    label = 'Wheelchair',
    weight = 8000,
    type = 'item',
    image = 'ems_wheelchair.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Place a wheelchair. Sit to ride, pick up to put it back in inventory.',
},
body_bag_empty = {
    name = 'body_bag_empty',
    label = 'Empty Body Bag',
    weight = 1500,
    type = 'item',
    image = 'body_bag_empty.png',
    unique = false,
    useable = false,
    shouldClose = true,
    combinable = nil,
    description = 'Unused body bag. Required to collect a corpse.',
},
body_bag = {
    name = 'body_bag',
    label = 'Body Bag',
    weight = 5000,
    type = 'item',
    image = 'body_bag.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Contains a collected CK corpse. Use to place the body back in the world.',
},
dna_sample = {
    name = 'dna_sample',
    label = 'DNA Sample',
    weight = 40,
    type = 'item',
    image = 'dna_sample.png',
    unique = true,
    useable = false,
    shouldClose = true,
    combinable = nil,
    description = 'Unanalyzed biological sample.',
},
dna_print_data = {
    name = 'dna_print_data',
    label = 'DNA Data to print',
    weight = 10,
    type = 'item',
    image = 'dna_print_data.png',
    unique = true,
    useable = false,
    shouldClose = true,
    combinable = nil,
    description = 'Analyzed DNA profile. Use a hospital printer later.',
},
death_certificate = {
    name = 'death_certificate',
    label = 'Death Certificate',
    weight = 20,
    type = 'item',
    image = 'death_certificate.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Official death certificate. Use to read the document.',
},
incident_report = {
    name = 'incident_report',
    label = 'Incident Report',
    weight = 20,
    type = 'item',
    image = 'incident_report.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Printed hospital incident report. Use to read the document.',
},
call_report = {
    name = 'call_report',
    label = 'Call Report',
    weight = 20,
    type = 'item',
    image = 'call_report.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Printed hospital call report. Use to read the document.',
},
insurance_certificate = {
    name = 'insurance_certificate',
    label = 'Insurance Certificate',
    weight = 20,
    type = 'item',
    image = 'insurance_certificate.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Printed proof of hospital insurance. Use to read the document.',
},
medbag_small = {
    name = 'medbag_small',
    label = 'Small Med Bag',
    weight = 1500,
    type = 'item',
    image = 'medbag_small.png',
    unique = true,
    useable = true,
    shouldClose = false,
    combinable = nil,
    description = 'Compact EMS bag. 8 slots / 10kg.',
},
medbag_big = {
    name = 'medbag_big',
    label = 'Big Med Bag',
    weight = 3500,
    type = 'item',
    image = 'medbag_big.png',
    unique = true,
    useable = true,
    shouldClose = false,
    combinable = nil,
    description = 'Large EMS bag. 16 slots / 25kg.',
},
```

{% endtab %}

{% tab title="ESX" %}
Run against your database, or insert items manually with the same names.

```sql
INSERT INTO `items` (`name`, `label`, `weight`) VALUES
    ('medkit', 'Medkit', 8),
    ('painkillers', 'Painkillers', 1),
    ('bandage', 'Bandage', 1),
    ('quickclot', 'QuickClot', 1),
    ('tourniquet', 'Tourniquet', 2),
    ('suturekit', 'Suture Kit', 2),
    ('splint', 'Splint', 3),
    ('saline', 'Saline Solution', 2),
    ('bagvalvemask', 'Bag Valve Mask', 4),
    ('ems_gps', 'EMS GPS', 1),
    ('ems_stretcher', 'EMS Stretcher', 5),
    ('ems_wheelchair', 'Wheelchair', 8),
    ('body_bag_empty', 'Empty Body Bag', 2),
    ('body_bag', 'Body Bag', 5),
    ('dna_sample', 'DNA Sample', 1),
    ('dna_print_data', 'DNA Data to print', 1),
    ('death_certificate', 'Death Certificate', 1),
    ('incident_report', 'Incident Report', 1),
    ('call_report', 'Call Report', 1),
    ('insurance_certificate', 'Insurance Certificate', 1),
    ('medbag_small', 'Small Med Bag', 2),
    ('medbag_big', 'Big Med Bag', 4)
ON DUPLICATE KEY UPDATE `label` = VALUES(`label`);
```

Usable items are registered by the resource via `Fr.RegisterUsableItem`.
{% endtab %}
{% endtabs %}

{% hint style="warning" %}
Stretcher and wheelchair models require `op-ambulancejob-props` started before `op-ambulancejob`.
{% endhint %}


# Exports


# Client Side

Client Side list of exports/events.

{% hint style="warning" %}
Missing an export? Contact us on our Discord Server.
{% endhint %}

***

#### GetMedicalState / IsDowned / IsDead

```lua
local medical = exports['op-ambulancejob']:GetMedicalState()
local downed = exports['op-ambulancejob']:IsDowned()
local dead = exports['op-ambulancejob']:IsDead()
```

***

#### IsOnDuty

```lua
local onDuty = exports['op-ambulancejob']:IsOnDuty()
```

***

#### ExaminePatient

```lua
exports['op-ambulancejob']:ExaminePatient(serverId)
```

***

#### IsTreating

```lua
local busy = exports['op-ambulancejob']:IsTreating()
```

***

#### Item exports (inventory)

Used from inventory item definitions:

```lua
exports['op-ambulancejob']:useBandage()
exports['op-ambulancejob']:useMedkit()
exports['op-ambulancejob']:usePainkillers()
exports['op-ambulancejob']:useEmsGps()
exports['op-ambulancejob']:useEmsStretcher()
exports['op-ambulancejob']:useEmsWheelchair()
exports['op-ambulancejob']:usePlaceable(data, slot)
```


# Server Side

List of server side exports/events.

{% hint style="warning" %}
Missing an export? Contact us on our Discord Server.
{% endhint %}

***

#### GetMedicalState

```lua
local medical = exports['op-ambulancejob']:GetMedicalState(src)
```

Returns the full medical table for the player.

***

#### SetMedicalState

```lua
exports['op-ambulancejob']:SetMedicalState(src, medicalTable)
```

***

#### GetMedicalStateName

```lua
local state = exports['op-ambulancejob']:GetMedicalStateName(src)
-- 'alive' | 'injured' | 'knockout' | 'laststand' | 'dead'
```

***

#### IsDead / IsDowned / IsKnockout

```lua
local dead = exports['op-ambulancejob']:IsDead(src)
local downed = exports['op-ambulancejob']:IsDowned(src) -- dead / laststand / knockout
local ko = exports['op-ambulancejob']:IsKnockout(src)
```

***

#### AddInjury

```lua
exports['op-ambulancejob']:AddInjury(src, {
    type = 'gunshot',
    region = 'chest',
    severity = 3,
})
```

***

#### RevivePlayer

```lua
exports['op-ambulancejob']:RevivePlayer(src, {
    -- optional opts table
})
```

***

#### Duty

```lua
exports['op-ambulancejob']:SetDuty(src, true)
exports['op-ambulancejob']:SetDuty(src, true, true) -- fromMdt = true (skip MDT loop)
exports['op-ambulancejob']:ToggleDuty(src)
local onDuty = exports['op-ambulancejob']:IsOnDuty(src)
```

***

#### Insurance / CK

```lua
local status = exports['op-ambulancejob']:getPlayerInsuranceStatus(charId)
local isCk = exports['op-ambulancejob']:IsCharacterCk(identifier)
```


# Integrations

List of compatible scripts with OP Ambulance Job.

### List of Integrations

Auto-detected when the resource is started (unless forced in `MainConfig.lua`). Your script is not listed? Open a ticket on Discord.

#### Framework

| Resource     | Config value |
| ------------ | ------------ |
| es\_extended | `esx`        |
| qb-core      | `qb`         |
| qbx\_core    | `qbox`       |

#### Inventory

| Resource          | Config value                        |
| ----------------- | ----------------------------------- |
| ox\_inventory     | `ox_inventory`                      |
| qb-inventory      | `qb-inventory`                      |
| old qb-inventory  | `old-qb-inventory` (force manually) |
| codem-inventory   | `codem-inventory`                   |
| tgiann-inventory  | `tgiann_inventory`                  |
| origen\_inventory | `origen_inventory`                  |
| one\_inventory    | `one_inventory`                     |

#### Garage (job fleet)

| Resource           | Config value                  |
| ------------------ | ----------------------------- |
| op-garages         | `op-garages`                  |
| jg-advancedgarages | `jg-advancedgarages`          |
| none               | built-in hospital garage menu |

#### Dispatch

| Resource        | Config value                   |
| --------------- | ------------------------------ |
| op-ambulancemdt | `op-ambulancemdt`              |
| none            | EMS notify + map blip fallback |

Set jobs that receive distress alerts in `Config.Dispatch.jobs`.

#### Target / TextUI / Notify

| Type     | Supported                                                |
| -------- | -------------------------------------------------------- |
| Target   | ox\_target, qb-target                                    |
| TextUI   | ox\_lib, jg-textui, okokTextUI                           |
| Notify   | op-hud, okokNotify, vms\_notify, ox\_lib, brutal\_notify |
| Progress | ox\_lib                                                  |

#### Clothing (wardrobe)

| Resource               | Config value |
| ---------------------- | ------------ |
| op-clothing            | --           |
| illenium-appearance    | --           |
| fivem-appearance       | --           |
| rcore\_clothing        | --           |
| skinchanger            | --           |
| crm-appearance         | --           |
| 17mov\_CharacterSystem | --           |
| 0r-clothingv2          | --           |
| none                   | --           |

#### Compatibility bridges

Resource provides `esx_ambulancejob` and `qb-ambulancejob`. Legacy revive / heal / death-status events are handled when enabled in `config/CompatConfig.lua`.


# OP Mechanic V2

Mechanic job system with workshop tuning, work orders, tablet, maintenance, nitro, anti-lag, RGB underglow, dyno, fleet and shop admin. Built for ESX, QBCore and Qbox.

### Features

* In-game **Mechanic Admin** to create shops (job, zones, blips, tablet apps, stashes, commission)
* Tuning menu with free preview, cart, tax / grade discount, work-order install flow
* Mechanic tablet: finances, parts order, fleet, maintenance, garage, wardrobe, dispatch
* Vehicle maintenance with mileage tracking and part wear
* Nitro, anti-lag, wax, RGB controller, tuning kits, flatbed, placeables, dyno
* Garage-ready vehicle properties exports


# Installation

Before you start, make sure you're logged into the CFX portal. This ensures you can download the asset and receive future updates.

### Downloading Dependencies

Dependencies are required before you start `op-mechanic`.

{% stepper %}
{% step %}

#### ox\_lib

{% embed url="<https://github.com/overextended/ox_lib>" %}
{% endstep %}

{% step %}

### fivem-freecam

{% embed url="<https://github.com/Deltanic/fivem-freecam>" %}
{% endstep %}

{% step %}

### flatbed (optional)

remove /flatbed.lua after installation!!!!!

{% embed url="<https://github.com/glitchdetector/fivem-functional-flatbed>" %}
{% endstep %}
{% endstepper %}

***

### Server.cfg

Start dependencies before our script. Framework and inventory must also be running.

```
ensure oxmysql
ensure ox_lib
ensure fivem-freecam
ensure fivem-functional-flatbed
ensure op-mechanic-props
ensure op-mechanic
```

***

### Database

On first start the script creates its own tables automatically (`ServerConfig.DisableAutoSql = false`).

{% hint style="info" %}
Set `ServerConfig.DisableAutoSql = true` in `config/ServerConfig.lua` only if you manage schema yourself.
{% endhint %}

***

### First Setup

{% stepper %}
{% step %}

#### Create items

{% content-ref url="/pages/7ab5HiN00BEt9PvNvSSW" %}
[Create Items](/scripts/op-mechanic-v2/create-items)
{% endcontent-ref %}
{% endstep %}

{% step %}

#### Grant Mechanic Admin

Follow How to add player to Mechanic Admin, then open `/mechanicadmin`.

{% content-ref url="/pages/evCQxsHbiLhEoqpNn66g" %}
[How to add player to Mechanic Admin](/scripts/op-mechanic-v2/tutorials/how-to-add-player-to-mechanic-admin)
{% endcontent-ref %}
{% endstep %}

{% step %}

#### Create a shop

Follow How to setup the first shop. Link a real framework job (for example `mechanic`).

{% content-ref url="/pages/Bf3W4uUaKXqq2HNNW6Qb" %}
[How to setup the first shop](/scripts/op-mechanic-v2/tutorials/how-to-setup-the-first-shop)
{% endcontent-ref %}
{% endstep %}

{% step %}

#### Configure locale / currency

Edit `config/MainConfig.lua` (`Config.Locale`, `Config.Currency`) and check other files listed in Config Files.
{% endstep %}
{% endstepper %}


# Create Items

## Create Items

{% hint style="warning" %}
If inventory which you use is not on this list - Create Items regarding to other items in your inventory script.
{% endhint %}

{% hint style="info" %}
Items Icons are shipped with script - check \[itemicons\[ folder in op-mechanic resource.
{% endhint %}

#### Tools & install parts

| Item                 | Purpose                                                         |
| -------------------- | --------------------------------------------------------------- |
| `mechanic_tablet`    | Opens the shop tablet (**usable**, not consumed)                |
| `mechanic_tools`     | Required for work-order installs (not consumed)                 |
| `wheel_parts`        | Wheels / rims                                                   |
| `tire_parts`         | Tires, smoke, bulletproof                                       |
| `paint_supplies`     | Paint / finish / pearlescent                                    |
| `engine_parts`       | Engine / turbo                                                  |
| `brake_parts`        | Brakes                                                          |
| `transmission_parts` | Transmission                                                    |
| `suspension_parts`   | Suspension / stance                                             |
| `body_parts`         | Body mods / armor / extras                                      |
| `exhaust_parts`      | Exhaust                                                         |
| `lighting_parts`     | Lights / neon / xenon                                           |
| `rgb_controller`     | Underglow RGB controller - install part and remote (**usable**) |
| `interior_parts`     | Interior / dashboard colours                                    |
| `repair_kit`         | repair kit for players (optional)                               |

#### Maintenance

| Item                 | Purpose                           |
| -------------------- | --------------------------------- |
| `engine_oil`         | Engine oil service                |
| `clutch_kit`         | Clutch replacement                |
| `air_filter`         | Intake filter                     |
| `spark_plugs`        | Spark plugs (usually qty 4)       |
| `brake_pads`         | Brake pad set                     |
| `service_suspension` | Suspension service kit            |
| `tyre_set`           | Tyre set (usually qty 4)          |
| `alternator`         | Alternator (hard-fault / replace) |
| `ev_motor`           | EV drive motor                    |
| `ev_battery`         | EV traction battery               |
| `ev_coolant`         | EV battery coolant                |

#### Nitro

| Item                   | Purpose                                            |
| ---------------------- | -------------------------------------------------- |
| `nitrous_install_kit`  | Install NOS kit on vehicle                         |
| `nitrous_bottle`       | Full bottle - refill at vehicle front (**usable**) |
| `empty_nitrous_bottle` | Empty bottle after deplete / replace               |

#### Field kits & care (**usable**)

| Item           | Purpose                                       |
| -------------- | --------------------------------------------- |
| `engine_kit`   | F6 / inventory - repair engine (consumed)     |
| `body_kit`     | F6 / inventory - repair body (consumed)       |
| `cleaning_kit` | F6 / inventory - wash vehicle (consumed)      |
| `car_wax`      | Inventory - wax coat; slower dirt for a while |

#### Tuning kits

| Item                 | Purpose                    |
| -------------------- | -------------------------- |
| `engine_swap_i4`     | Twin-scroll I4 engine swap |
| `engine_swap_v6`     | Street V6 engine swap      |
| `engine_swap_v8`     | Forged V8 engine swap      |
| `engine_swap_v12`    | Motorsport V12 engine swap |
| `tyre_track_slick`   | Track slick tires          |
| `tyre_street_semi`   | Street semi-slick tires    |
| `tyre_trail_mud`     | Trail / mud tires          |
| `brake_race_ceramic` | Race ceramic brakes        |
| `brake_endurance`    | Endurance brake package    |
| `drivetrain_awd`     | AWD conversion             |
| `drivetrain_rwd`     | RWD conversion             |
| `drivetrain_fwd`     | FWD conversion             |

{% tabs %}
{% tab title="ox\_inventory" %}
**File:** `ox_inventory/data/items.lua`\
**Images:** `ox_inventory/web/images/`

```lua
-- Tools & install parts
['repair_kit'] = {
    label = 'Repair Kit',
    weight = 1000,
    stack = true,
    close = true,
    consume = 0,
    client = { export = 'op-mechanic.useRepairKit' },
},
['mechanic_tablet'] = {
    label = 'Mechanic Tablet',
    weight = 900,
    stack = false,
    close = true,
    description = 'Shop tablet - work orders, invoices, parts, fleet and finances.',
    client = {
        export = 'op-mechanic.useMechanicTablet',
    },
},
['mechanic_tools'] = {
    label = 'Mechanic Tools',
    weight = 1500,
    stack = false,
    close = true,
    description = 'Toolkit required to install workshop modifications.',
},
['wheel_parts'] = {
    label = 'Wheel Parts',
    weight = 500,
    stack = true,
    close = true,
    description = 'Parts used for wheel and rim installations.',
},
['tire_parts'] = {
    label = 'Tire Parts',
    weight = 400,
    stack = true,
    close = true,
    description = 'Parts used for tires, smoke and related work.',
},
['paint_supplies'] = {
    label = 'Paint Supplies',
    weight = 350,
    stack = true,
    close = true,
    description = 'Paint, finish and pearlescent supplies.',
},
['engine_parts'] = {
    label = 'Engine Parts',
    weight = 800,
    stack = true,
    close = true,
    description = 'Parts used for engine and turbo upgrades.',
},
['brake_parts'] = {
    label = 'Brake Parts',
    weight = 450,
    stack = true,
    close = true,
    description = 'Parts used for brake upgrades.',
},
['transmission_parts'] = {
    label = 'Transmission Parts',
    weight = 700,
    stack = true,
    close = true,
    description = 'Parts used for transmission upgrades.',
},
['suspension_parts'] = {
    label = 'Suspension Parts',
    weight = 550,
    stack = true,
    close = true,
    description = 'Parts used for suspension and stance work.',
},
['body_parts'] = {
    label = 'Body Parts',
    weight = 600,
    stack = true,
    close = true,
    description = 'Body panels, armor and general bodywork parts.',
},
['exhaust_parts'] = {
    label = 'Exhaust Parts',
    weight = 500,
    stack = true,
    close = true,
    description = 'Parts used for exhaust installations.',
},
['lighting_parts'] = {
    label = 'Lighting Parts',
    weight = 300,
    stack = true,
    close = true,
    description = 'Parts used for lights, neon and xenon.',
},
['rgb_controller'] = {
    label = 'RGB Controller',
    weight = 400,
    stack = true,
    close = true,
    description = 'Controls fitted underglow strips - colour, RGB fade, flash and per-side.',
    client = {
        export = 'op-mechanic.useRgbController',
    },
},
['interior_parts'] = {
    label = 'Interior Parts',
    weight = 350,
    stack = true,
    close = true,
    description = 'Parts used for interior and dashboard colours.',
},

-- Maintenance
['engine_oil'] = {
    label = 'Engine Oil',
    weight = 250,
    stack = true,
    close = true,
    description = 'Service oil for combustion engines.',
},
['clutch_kit'] = {
    label = 'Clutch Kit',
    weight = 900,
    stack = true,
    close = true,
    description = 'Clutch assembly for workshop maintenance.',
},
['air_filter'] = {
    label = 'Air Filter',
    weight = 120,
    stack = true,
    close = true,
    description = 'Intake air filter element.',
},
['spark_plugs'] = {
    label = 'Spark Plugs',
    weight = 80,
    stack = true,
    close = true,
    description = 'Ignition plugs (usually used in sets of 4).',
},
['brake_pads'] = {
    label = 'Brake Pads',
    weight = 400,
    stack = true,
    close = true,
    description = 'Friction pads for a full axle set.',
},
['service_suspension'] = {
    label = 'Suspension Service Kit',
    weight = 700,
    stack = true,
    close = true,
    description = 'Bushings and dampers for suspension service.',
},
['tyre_set'] = {
    label = 'Tyre Set',
    weight = 1200,
    stack = true,
    close = true,
    description = 'Set of road tyres for maintenance swaps.',
},
['alternator'] = {
    label = 'Alternator',
    weight = 850,
    stack = true,
    close = true,
    description = 'Charging system alternator for ICE vehicles.',
},
['ev_motor'] = {
    label = 'EV Drive Motor',
    weight = 2500,
    stack = true,
    close = true,
    description = 'Electric drive unit service part.',
},
['ev_battery'] = {
    label = 'EV Battery Pack',
    weight = 3000,
    stack = true,
    close = true,
    description = 'Traction battery module for EV service.',
},
['ev_coolant'] = {
    label = 'EV Coolant',
    weight = 200,
    stack = true,
    close = true,
    description = 'Coolant for EV battery thermal systems.',
},

-- Nitro
['nitrous_install_kit'] = {
    label = 'Nitrous Install Kit',
    weight = 1200,
    stack = true,
    close = true,
    description = 'Workshop kit that enables NOS bottles on a vehicle.',
},
['nitrous_bottle'] = {
    label = 'Nitrous Bottle',
    weight = 2000,
    stack = true,
    close = true,
    description = 'Full NOS bottle. Use near the front of a kit-equipped vehicle to refill.',
    client = {
        event = 'opm:client:nitro:useBottle',
    },
},
['empty_nitrous_bottle'] = {
    label = 'Empty Nitrous Bottle',
    weight = 800,
    stack = true,
    close = true,
    description = 'Spent NOS bottle.',
},

-- Field kits & care (usable)
['engine_kit'] = {
    label = 'Engine Kit',
    weight = 2000,
    stack = true,
    close = true,
    description = 'Field kit to repair a vehicle engine (F6 or use item).',
    client = {
        export = 'op-mechanic.useEngineKit',
    },
},
['body_kit'] = {
    label = 'Body Kit',
    weight = 1800,
    stack = true,
    close = true,
    description = 'Field kit to repair vehicle bodywork (F6 or use item).',
    client = {
        export = 'op-mechanic.useBodyKit',
    },
},
['cleaning_kit'] = {
    label = 'Cleaning Kit',
    weight = 500,
    stack = true,
    close = true,
    description = 'Sponge and cleaner to wash a vehicle (F6 or use item).',
    client = {
        export = 'op-mechanic.useCleaningKit',
    },
},
['car_wax'] = {
    label = 'Car Wax',
    weight = 200,
    stack = true,
    close = true,
    description = 'Protective wax - vehicle gets dirty slower for a while.',
    client = {
        export = 'op-mechanic.useCarWax',
    },
},

-- Tuning kits
['engine_swap_i4'] = {
    label = 'I4 Engine Swap',
    weight = 3500,
    stack = true,
    close = true,
    description = 'Twin-scroll I4 crate engine for workshop swaps.',
},
['engine_swap_v6'] = {
    label = 'V6 Engine Swap',
    weight = 4200,
    stack = true,
    close = true,
    description = 'Street V6 crate engine for workshop swaps.',
},
['engine_swap_v8'] = {
    label = 'V8 Engine Swap',
    weight = 5000,
    stack = true,
    close = true,
    description = 'Forged V8 crate engine for workshop swaps.',
},
['engine_swap_v12'] = {
    label = 'V12 Engine Swap',
    weight = 5800,
    stack = true,
    close = true,
    description = 'Motorsport V12 crate engine for workshop swaps.',
},
['tyre_track_slick'] = {
    label = 'Track Slick Tires',
    weight = 1600,
    stack = true,
    close = true,
    description = 'Dry-track slick compound set.',
},
['tyre_street_semi'] = {
    label = 'Street Semi-Slick Tires',
    weight = 1400,
    stack = true,
    close = true,
    description = 'Sticky road-legal semi-slick set.',
},
['tyre_trail_mud'] = {
    label = 'Trail Mud Tires',
    weight = 1500,
    stack = true,
    close = true,
    description = 'Soft-surface trail / mud tire set.',
},
['brake_race_ceramic'] = {
    label = 'Race Ceramic Brakes',
    weight = 900,
    stack = true,
    close = true,
    description = 'High-friction ceramic brake package.',
},
['brake_endurance'] = {
    label = 'Endurance Brake Kit',
    weight = 850,
    stack = true,
    close = true,
    description = 'Fade-resistant endurance brake package.',
},
['drivetrain_awd'] = {
    label = 'AWD Drivetrain Kit',
    weight = 2200,
    stack = true,
    close = true,
    description = 'All-wheel-drive conversion hardware.',
},
['drivetrain_rwd'] = {
    label = 'RWD Drivetrain Kit',
    weight = 1800,
    stack = true,
    close = true,
    description = 'Rear-wheel-drive conversion hardware.',
},
['drivetrain_fwd'] = {
    label = 'FWD Drivetrain Kit',
    weight = 1800,
    stack = true,
    close = true,
    description = 'Front-wheel-drive conversion hardware.',
},
```

{% endtab %}

{% tab title="qb-inventory" %}
**File:** `qb-core/shared/items.lua`\
**Images:** `qb-inventory/html/images/`

```lua
-- Tools & install parts
repair_kit = {
    name = "repair_kit",
    label = 'Repair Kit',
    weight = 1000,
    type = 'item',
    unique = true,
    useable = true,
    shouldClose = true,
    client = { export = 'op-mechanic.useRepairKit' },
},
mechanic_tablet = {
    name = 'mechanic_tablet',
    label = 'Mechanic Tablet',
    weight = 900,
    type = 'item',
    image = 'mechanic_tablet.png',
    unique = true,
    useable = true,
    shouldClose = true,
    description = 'Shop tablet - work orders, invoices, parts, fleet and finances.',
},
mechanic_tools = {
    name = 'mechanic_tools',
    label = 'Mechanic Tools',
    weight = 1500,
    type = 'item',
    image = 'mechanic_tools.png',
    unique = true,
    useable = false,
    shouldClose = true,
    description = 'Toolkit required to install workshop modifications.',
},
wheel_parts = {
    name = 'wheel_parts',
    label = 'Wheel Parts',
    weight = 500,
    type = 'item',
    image = 'wheel_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Parts used for wheel and rim installations.',
},
tire_parts = {
    name = 'tire_parts',
    label = 'Tire Parts',
    weight = 400,
    type = 'item',
    image = 'tire_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Parts used for tires, smoke and related work.',
},
paint_supplies = {
    name = 'paint_supplies',
    label = 'Paint Supplies',
    weight = 350,
    type = 'item',
    image = 'paint_supplies.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Paint, finish and pearlescent supplies.',
},
engine_parts = {
    name = 'engine_parts',
    label = 'Engine Parts',
    weight = 800,
    type = 'item',
    image = 'engine_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Parts used for engine and turbo upgrades.',
},
brake_parts = {
    name = 'brake_parts',
    label = 'Brake Parts',
    weight = 450,
    type = 'item',
    image = 'brake_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Parts used for brake upgrades.',
},
transmission_parts = {
    name = 'transmission_parts',
    label = 'Transmission Parts',
    weight = 700,
    type = 'item',
    image = 'transmission_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Parts used for transmission upgrades.',
},
suspension_parts = {
    name = 'suspension_parts',
    label = 'Suspension Parts',
    weight = 550,
    type = 'item',
    image = 'suspension_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Parts used for suspension and stance work.',
},
body_parts = {
    name = 'body_parts',
    label = 'Body Parts',
    weight = 600,
    type = 'item',
    image = 'body_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Body panels, armor and general bodywork parts.',
},
exhaust_parts = {
    name = 'exhaust_parts',
    label = 'Exhaust Parts',
    weight = 500,
    type = 'item',
    image = 'exhaust_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Parts used for exhaust installations.',
},
lighting_parts = {
    name = 'lighting_parts',
    label = 'Lighting Parts',
    weight = 300,
    type = 'item',
    image = 'lighting_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Parts used for lights, neon and xenon.',
},
rgb_controller = {
    name = 'rgb_controller',
    label = 'RGB Controller',
    weight = 400,
    type = 'item',
    image = 'rgb_controller.png',
    unique = false,
    useable = true,
    shouldClose = true,
    description = 'Controls fitted underglow strips - colour, RGB fade, flash and per-side.',
},
interior_parts = {
    name = 'interior_parts',
    label = 'Interior Parts',
    weight = 350,
    type = 'item',
    image = 'interior_parts.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Parts used for interior and dashboard colours.',
},

-- Maintenance
engine_oil = {
    name = 'engine_oil',
    label = 'Engine Oil',
    weight = 250,
    type = 'item',
    image = 'engine_oil.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Service oil for combustion engines.',
},
clutch_kit = {
    name = 'clutch_kit',
    label = 'Clutch Kit',
    weight = 900,
    type = 'item',
    image = 'clutch_kit.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Clutch assembly for workshop maintenance.',
},
air_filter = {
    name = 'air_filter',
    label = 'Air Filter',
    weight = 120,
    type = 'item',
    image = 'air_filter.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Intake air filter element.',
},
spark_plugs = {
    name = 'spark_plugs',
    label = 'Spark Plugs',
    weight = 80,
    type = 'item',
    image = 'spark_plugs.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Ignition plugs (usually used in sets of 4).',
},
brake_pads = {
    name = 'brake_pads',
    label = 'Brake Pads',
    weight = 400,
    type = 'item',
    image = 'brake_pads.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Friction pads for a full axle set.',
},
service_suspension = {
    name = 'service_suspension',
    label = 'Suspension Service Kit',
    weight = 700,
    type = 'item',
    image = 'service_suspension.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Bushings and dampers for suspension service.',
},
tyre_set = {
    name = 'tyre_set',
    label = 'Tyre Set',
    weight = 1200,
    type = 'item',
    image = 'tyre_set.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Set of road tyres for maintenance swaps.',
},
alternator = {
    name = 'alternator',
    label = 'Alternator',
    weight = 850,
    type = 'item',
    image = 'alternator.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Charging system alternator for ICE vehicles.',
},
ev_motor = {
    name = 'ev_motor',
    label = 'EV Drive Motor',
    weight = 2500,
    type = 'item',
    image = 'ev_motor.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Electric drive unit service part.',
},
ev_battery = {
    name = 'ev_battery',
    label = 'EV Battery Pack',
    weight = 3000,
    type = 'item',
    image = 'ev_battery.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Traction battery module for EV service.',
},
ev_coolant = {
    name = 'ev_coolant',
    label = 'EV Coolant',
    weight = 200,
    type = 'item',
    image = 'ev_coolant.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Coolant for EV battery thermal systems.',
},

-- Nitro
nitrous_install_kit = {
    name = 'nitrous_install_kit',
    label = 'Nitrous Install Kit',
    weight = 1200,
    type = 'item',
    image = 'nitrous_install_kit.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Workshop kit that enables NOS bottles on a vehicle.',
},
nitrous_bottle = {
    name = 'nitrous_bottle',
    label = 'Nitrous Bottle',
    weight = 2000,
    type = 'item',
    image = 'nitrous_bottle.png',
    unique = false,
    useable = true,
    shouldClose = true,
    description = 'Full NOS bottle. Use near the front of a kit-equipped vehicle to refill.',
},
empty_nitrous_bottle = {
    name = 'empty_nitrous_bottle',
    label = 'Empty Nitrous Bottle',
    weight = 800,
    type = 'item',
    image = 'empty_nitrous_bottle.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Spent NOS bottle.',
},

-- Field kits & care (usable)
engine_kit = {
    name = 'engine_kit',
    label = 'Engine Kit',
    weight = 2000,
    type = 'item',
    image = 'engine_kit.png',
    unique = false,
    useable = true,
    shouldClose = true,
    description = 'Field kit to repair a vehicle engine (F6 or use item).',
},
body_kit = {
    name = 'body_kit',
    label = 'Body Kit',
    weight = 1800,
    type = 'item',
    image = 'body_kit.png',
    unique = false,
    useable = true,
    shouldClose = true,
    description = 'Field kit to repair vehicle bodywork (F6 or use item).',
},
cleaning_kit = {
    name = 'cleaning_kit',
    label = 'Cleaning Kit',
    weight = 500,
    type = 'item',
    image = 'cleaning_kit.png',
    unique = false,
    useable = true,
    shouldClose = true,
    description = 'Sponge and cleaner to wash a vehicle (F6 or use item).',
},
car_wax = {
    name = 'car_wax',
    label = 'Car Wax',
    weight = 200,
    type = 'item',
    image = 'car_wax.png',
    unique = false,
    useable = true,
    shouldClose = true,
    description = 'Protective wax - vehicle gets dirty slower for a while.',
},

-- Tuning kits
engine_swap_i4 = {
    name = 'engine_swap_i4',
    label = 'I4 Engine Swap',
    weight = 3500,
    type = 'item',
    image = 'engine_swap_i4.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Twin-scroll I4 crate engine for workshop swaps.',
},
engine_swap_v6 = {
    name = 'engine_swap_v6',
    label = 'V6 Engine Swap',
    weight = 4200,
    type = 'item',
    image = 'engine_swap_v6.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Street V6 crate engine for workshop swaps.',
},
engine_swap_v8 = {
    name = 'engine_swap_v8',
    label = 'V8 Engine Swap',
    weight = 5000,
    type = 'item',
    image = 'engine_swap_v8.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Forged V8 crate engine for workshop swaps.',
},
engine_swap_v12 = {
    name = 'engine_swap_v12',
    label = 'V12 Engine Swap',
    weight = 5800,
    type = 'item',
    image = 'engine_swap_v12.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Motorsport V12 crate engine for workshop swaps.',
},
tyre_track_slick = {
    name = 'tyre_track_slick',
    label = 'Track Slick Tires',
    weight = 1600,
    type = 'item',
    image = 'tyre_track_slick.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Dry-track slick compound set.',
},
tyre_street_semi = {
    name = 'tyre_street_semi',
    label = 'Street Semi-Slick Tires',
    weight = 1400,
    type = 'item',
    image = 'tyre_street_semi.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Sticky road-legal semi-slick set.',
},
tyre_trail_mud = {
    name = 'tyre_trail_mud',
    label = 'Trail Mud Tires',
    weight = 1500,
    type = 'item',
    image = 'tyre_trail_mud.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Soft-surface trail / mud tire set.',
},
brake_race_ceramic = {
    name = 'brake_race_ceramic',
    label = 'Race Ceramic Brakes',
    weight = 900,
    type = 'item',
    image = 'brake_race_ceramic.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'High-friction ceramic brake package.',
},
brake_endurance = {
    name = 'brake_endurance',
    label = 'Endurance Brake Kit',
    weight = 850,
    type = 'item',
    image = 'brake_endurance.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Fade-resistant endurance brake package.',
},
drivetrain_awd = {
    name = 'drivetrain_awd',
    label = 'AWD Drivetrain Kit',
    weight = 2200,
    type = 'item',
    image = 'drivetrain_awd.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'All-wheel-drive conversion hardware.',
},
drivetrain_rwd = {
    name = 'drivetrain_rwd',
    label = 'RWD Drivetrain Kit',
    weight = 1800,
    type = 'item',
    image = 'drivetrain_rwd.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Rear-wheel-drive conversion hardware.',
},
drivetrain_fwd = {
    name = 'drivetrain_fwd',
    label = 'FWD Drivetrain Kit',
    weight = 1800,
    type = 'item',
    image = 'drivetrain_fwd.png',
    unique = false,
    useable = false,
    shouldClose = true,
    description = 'Front-wheel-drive conversion hardware.',
},
```

{% endtab %}

{% tab title="Basic ESX" %}
MySQL insert:

```sql
INSERT INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
-- Tools & install parts
('repair_kit', 'Repair Kit', 900, 0, 1),
('mechanic_tablet', 'Mechanic Tablet', 900, 0, 1),
('mechanic_tools', 'Mechanic Tools', 1500, 0, 1),
('wheel_parts', 'Wheel Parts', 500, 0, 1),
('tire_parts', 'Tire Parts', 400, 0, 1),
('paint_supplies', 'Paint Supplies', 350, 0, 1),
('engine_parts', 'Engine Parts', 800, 0, 1),
('brake_parts', 'Brake Parts', 450, 0, 1),
('transmission_parts', 'Transmission Parts', 700, 0, 1),
('suspension_parts', 'Suspension Parts', 550, 0, 1),
('body_parts', 'Body Parts', 600, 0, 1),
('exhaust_parts', 'Exhaust Parts', 500, 0, 1),
('lighting_parts', 'Lighting Parts', 300, 0, 1),
('rgb_controller', 'RGB Controller', 400, 0, 1),
('interior_parts', 'Interior Parts', 350, 0, 1),
-- Maintenance
('engine_oil', 'Engine Oil', 250, 0, 1),
('clutch_kit', 'Clutch Kit', 900, 0, 1),
('air_filter', 'Air Filter', 120, 0, 1),
('spark_plugs', 'Spark Plugs', 80, 0, 1),
('brake_pads', 'Brake Pads', 400, 0, 1),
('service_suspension', 'Suspension Service Kit', 700, 0, 1),
('tyre_set', 'Tyre Set', 1200, 0, 1),
('alternator', 'Alternator', 850, 0, 1),
('ev_motor', 'EV Drive Motor', 2500, 0, 1),
('ev_battery', 'EV Battery Pack', 3000, 0, 1),
('ev_coolant', 'EV Coolant', 200, 0, 1),
-- Nitro
('nitrous_install_kit', 'Nitrous Install Kit', 1200, 0, 1),
('nitrous_bottle', 'Nitrous Bottle', 2000, 0, 1),
('empty_nitrous_bottle', 'Empty Nitrous Bottle', 800, 0, 1),
-- Field kits & care
('engine_kit', 'Engine Kit', 2000, 0, 1),
('body_kit', 'Body Kit', 1800, 0, 1),
('cleaning_kit', 'Cleaning Kit', 500, 0, 1),
('car_wax', 'Car Wax', 200, 0, 1),
-- Tuning kits
('engine_swap_i4', 'I4 Engine Swap', 3500, 0, 1),
('engine_swap_v6', 'V6 Engine Swap', 4200, 0, 1),
('engine_swap_v8', 'V8 Engine Swap', 5000, 0, 1),
('engine_swap_v12', 'V12 Engine Swap', 5800, 0, 1),
('tyre_track_slick', 'Track Slick Tires', 1600, 0, 1),
('tyre_street_semi', 'Street Semi-Slick Tires', 1400, 0, 1),
('tyre_trail_mud', 'Trail Mud Tires', 1500, 0, 1),
('brake_race_ceramic', 'Race Ceramic Brakes', 900, 0, 1),
('brake_endurance', 'Endurance Brake Kit', 850, 0, 1),
('drivetrain_awd', 'AWD Drivetrain Kit', 2200, 0, 1),
('drivetrain_rwd', 'RWD Drivetrain Kit', 1800, 0, 1),
('drivetrain_fwd', 'FWD Drivetrain Kit', 1800, 0, 1);
```

{% endtab %}
{% endtabs %}


# Tutorials


# How to add player to Mechanic Admin

/mechanicadmin is gated the same way as other OTHERPLANET admin panels: ACE -> identifier whitelist → framework rank.

### Option 1 - ACE (recommended)

In `server.cfg`:

```
add_ace group.admin opmechanic.admin allow
```

Restart server, then run `/mechanicadmin` in-game.

### Option 2 - Identifier whitelist

Edit `config/MainConfig.lua`:

```lua
Config.AdminPanelPlayers = {
    ['license:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'] = true,
    -- ['discord:000000000000000000'] = true,
}
```

Use license / discord / character / citizenid identifiers from txAdmin.

### Option 3 - Framework rank

```lua
Config.RanksPermissions = {
    AdminPanelPlayers = {
        ['esx'] = { 'superadmin', 'admin', 'mod' },
        ['qbcore'] = { 'god', 'admin' },
        ['qbox'] = { 'god', 'admin' },
    },
}
```

{% hint style="info" %}
Command name defaults to `mechanicadmin` (`config/AdminConfig.lua` → `Config.MechanicAdmin.command`).
{% endhint %}


# How to setup the first shop

Shops are stored in the database and managed from Mechanic Admin. There is no static Config.Jobs list - jobs sync from shops you create.

{% stepper %}
{% step %}

#### Open Mechanic Admin

After you have permission, run:

```
/mechanicadmin
```

{% endstep %}

{% step %}

#### Create shop

Set at least:

* **Label** - display name
* **Job** - must match a real framework job name (example: `mechanic`)
* **Society account** - optional; defaults to `society_<job>`
* **Min grade** - lowest grade that counts as employed at this shop
* **Blip** - optional map blip
* **Zones** - tuning / workshop areas used to open the menu and claim work
* **Tablet apps** - min grade per app (finances, parts, maintenance, garage, …)
* **Commission** - % of invoice total deposited into the society tablet bank
  {% endstep %}

{% step %}

#### Framework job

Create the job in your framework (ESX jobs / QB shared jobs) with the same name as the shop `job` field. Grades should match tablet app min grades.
{% endstep %}

{% step %}

#### Duty / access

Players need the shop job (and grade) to use tablet apps and, depending on `Config.Tuning.requireJob`, to open / confirm tuning.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
If the shop job name does not exist on the framework, tablet and dispatch routing will not work correctly.
{% endhint %}


# Require nitro & anti-lag install item

Sell nitro / anti-lag kits as inventory items (Tebex, coin shop, black market, mechanic parts order). Players must use the item on the vehicle before the tuning menu lets them mount the kit.

{% hint style="success" %}
**Tebex-friendly flow:** package delivers `nitrous_install_kit` / `antilag_install_kit` → player uses item near the car → mount unlocks in tuning → player still pays labour / opens a work order like any other mod.
{% endhint %}

{% hint style="info" %}
Using the item only **unlocks** install. It does **not** fit the kit by itself.
{% endhint %}

***

{% stepper %}
{% step %}
**Enable the lock in config**

**Nitro** - `config/NitroConfig.lua`

```lua
Config.Nitro = {
    -- ...
    requireInstallItem = true,          -- false = free mount from tuning (default)
    kitItem = 'nitrous_install_kit',    -- must match your inventory item name
    kitUnlockDurationMs = 4500,         -- progress bar; 0 = instant
    -- ...
}
```

**Anti-lag** - `config/AntiLagConfig.lua`

```lua
Config.AntiLag = {
    -- ...
    requireInstallItem = true,
    kitItem = 'antilag_install_kit',
    kitUnlockDurationMs = 4500,
    -- ...
}
```

Restart `op-mechanic` (or the server) after editing.
{% endstep %}

{% step %}
**Create the inventory items**

Add items if they are missing. See also [Create Items](https://docs.otherplanet.dev/scripts/op-mechanic-v2/create-items.md).

**ox\_inventory**

```lua
['nitrous_install_kit'] = {
    label = 'Nitrous Install Kit',
    weight = 1200,
    stack = true,
    close = true,
    description = 'Use on a vehicle to unlock nitro kit install in the tuning menu.',
    client = {
        export = 'op-mechanic.useNitroKitUnlock',
    },
},

['antilag_install_kit'] = {
    label = 'Anti-lag Install Kit',
    weight = 1000,
    stack = true,
    close = true,
    description = 'Use on a vehicle to unlock anti-lag install in the tuning menu.',
    client = {
        export = 'op-mechanic.useAntiLagKitUnlock',
    },
},
```

{% hint style="info" %}
When `requireInstallItem = true`, the resource also registers the items as framework usables (ESX / QB / Qbox). The ox `client.export` is the preferred path for ox\_inventory.
{% endhint %}

**qb-core / qbox (`qb-core/shared/items.lua`)**

```lua
nitrous_install_kit = {
    name = 'nitrous_install_kit',
    label = 'Nitrous Install Kit',
    weight = 1200,
    type = 'item',
    image = 'nitrous_install_kit.png',
    unique = false,
    useable = true,
    shouldClose = true,
    description = 'Use on a vehicle to unlock nitro kit install in the tuning menu.',
},
antilag_install_kit = {
    name = 'antilag_install_kit',
    label = 'Anti-lag Install Kit',
    weight = 1000,
    type = 'item',
    image = 'antilag_install_kit.png',
    unique = false,
    useable = true,
    shouldClose = true,
    description = 'Use on a vehicle to unlock anti-lag install in the tuning menu.',
},
```

**ESX (`items` SQL)**

```sql
INSERT INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
('nitrous_install_kit', 'Nitrous Install Kit', 1200, 0, 1),
('antilag_install_kit', 'Anti-lag Install Kit', 1000, 0, 1);
```

{% endstep %}

{% step %}
**Sell the items (Tebex / shops)**

Typical monetization:

| Channel                  | Idea                                                                                   |
| ------------------------ | -------------------------------------------------------------------------------------- |
| **Tebex**                | Package grants `nitrous_install_kit` / `antilag_install_kit` via your give-item bridge |
| **Coin / VIP shop**      | Same item names as above                                                               |
| **Mechanic parts order** | `nitrous_install_kit` and `antilag_install_kit` are in `Config.Parts.catalog`          |
| **Illegal craft**        | Craft → use on car → still need a mechanic (or self-service) to mount                  |

Bottles (`nitrous_bottle`) stay separate - they only refill an **already fitted** kit.
{% endstep %}

{% step %}
**Player flow in-game**

1. Stand near (or in) the vehicle.
2. **Use** `nitrous_install_kit` or `antilag_install_kit` from inventory.
3. Progress finishes → item is **consumed** → mount is **unlocked** on that vehicle.
4. Open the **tuning menu** at a shop.
5. Nitro kit / anti-lag style is no longer locked - add to cart, pay / work order as usual.

{% hint style="warning" %}
Uninstalling the kit from tuning **clears** the unlock. The player must use another kit item before mounting again.
{% endhint %}
{% endstep %}
{% endstepper %}

***

#### Disable again

Set both flags back to `false` if you want free mounting from the menu (stock behaviour):

```lua
Config.Nitro.requireInstallItem = false
Config.AntiLag.requireInstallItem = false
```

***

#### Related exports

```lua
-- ox_inventory / custom handlers
exports['op-mechanic']:useNitroKitUnlock()
exports['op-mechanic']:useAntiLagKitUnlock()
```

***

#### Troubleshooting

| Issue                                 | Fix                                                                                                     |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Option still locked after using item  | Confirm `requireInstallItem = true`, restart resource, stand closer to the vehicle, check notify errors |
| Item does nothing                     | ox: set `client.export`; QB/ESX: ensure usable registration + resource started                          |
| Item consumed but tuning still locked | Re-open the tuning session (or wait for mid-session lock sync);                                         |
| Want custom item names                | Change `kitItem` in config **and** inventory item name to match                                         |


# Config Files

The script is fully configurable. Always back up before editing. Wrong edits can break the resource.

### File list

| File                                 | Purpose                                                                                            |
| ------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `config/MainConfig.lua`              | Framework / inventory / banking / target / notify detection, admin ACL, currency, commands, tuning |
| `config/ServerConfig.lua`            | Auto SQL toggle, Discord webhook logs                                                              |
| `config/AdminConfig.lua`             | Mechanic Admin command, default blip / commission                                                  |
| `config/TabletConfig.lua`            | Tablet behaviour                                                                                   |
| `config/CategoriesConfig.lua`        | Tuning categories                                                                                  |
| `config/PricingConfig.lua`           | Parts / labor pricing                                                                              |
| `config/CameraConfig.lua`            | Tuning camera                                                                                      |
| `config/RestrictionsConfig.lua`      | Class / model restrictions                                                                         |
| `config/WorkOrdersConfig.lua`        | Work-order flow, progress / minigames                                                              |
| `config/InvoicesConfig.lua`          | Invoices                                                                                           |
| `config/PartsConfig.lua`             | Install part items + tools gate                                                                    |
| `config/PartsOrderConfig.lua`        | Parts catalog / orders                                                                             |
| `config/VehicleFleetConfig.lua`      | Job fleet purchase list                                                                            |
| `config/MaintenanceConfig.lua`       | Mileage, wear, service, EV / immune models                                                         |
| `config/NitroConfig.lua`             | Nitro                                                                                              |
| `config/AntiLagConfig.lua`           | Anti-lag / pops                                                                                    |
| `config/WaxConfig.lua`               | Car wax                                                                                            |
| `config/RgbConfig.lua`               | RGB underglow controller                                                                           |
| `config/TuningKitsConfig.lua`        | Engine / tyre / brake / drivetrain kits                                                            |
| `config/WorkshopEquipmentConfig.lua` | Workshop props / equipment                                                                         |
| `config/InteractionPointsConfig.lua` | Bone / offset install points on vehicles                                                           |
| `config/EngineHoistConfig.lua`       | Engine hoist                                                                                       |
| `config/ClothingConfig.lua`          | Wardrobe / clothing bridge                                                                         |
| `config/PlaceablesConfig.lua`        | Placeable props                                                                                    |
| `config/MechanicMenuConfig.lua`      | F6 / mechanic menu                                                                                 |
| `config/FlatbedConfig.lua`           | Flatbed tow                                                                                        |
| `locales/*.json`                     | UI strings (`Config.Locale`)                                                                       |

{% hint style="info" %}
Shops (jobs, zones, tablet apps) live in the database via Mechanic Admin - not in `MainConfig.lua`.
{% endhint %}


# Customization

### With the Editable files, you can:

* Add or adjust FRAMEWORK bridges (`framework/`)
* Modify notifications (`integrations/client/utils/sendNotify.lua`)
* Add inventory / banking adapters (`integrations/server/...`)
* Swap clothing systems (`integrations/client/clothing/...`)
* Change HUD toggle while tuning (`Config.SwitchHud` in `MainConfig.lua`)

### Editable zones

Escrow ignore (editable after purchase):

* `config/**`
* `framework/**`
* `integrations/**`
* `locales/**`

### Notifications

Edit `integrations/client/utils/sendNotify.lua`, or force a provider:

```lua
Config.Notify = 'ox_lib' -- op_hud | okokNotify | vms_notify | brutal_notify | ESX | QBCORE | QBOX | none
```

### Force detection

If auto-detect picks the wrong resource:

```lua
Config.Framework = 'qb'
Config.Inventory = { inventoryScript = 'ox_inventory' }
Config.Banking = 'qb-banking'
Config.GarageScript = 'op-garages'
Config.Target = 'ox-target'
Config.TextUI = 'ox_lib'
```

### Locales

Set `Config.Locale` and edit `locales/en.json` / `locales/pl.json` (or add your own JSON file).


# Exports

List of available Exports / Events for OP Mechanic


# Client Side

Client Side list of exports/events

{% hint style="warning" %}
Missing an export? Contact us on our [Discord Server](https://discord.gg/otherplanet).
{% endhint %}

***

### GetVehicleProperties

Capture a full props table for garage persistence (mods, paint, neon, health, …).

```lua
local props = exports['op-mechanic']:GetVehicleProperties(vehicle)
```

* `vehicle`: `number` (entity)
* returns: `table | nil`

***

### SetVehicleProperties

Apply a props table. Default uses a reliable multi-pass apply.

```lua
exports['op-mechanic']:SetVehicleProperties(vehicle, props)
exports['op-mechanic']:SetVehicleProperties(vehicle, props, false) -- single pass
```

* `vehicle`: `number`
* `props`: `table`
* `reliable`: `boolean?` (`true` default)
* returns: `boolean`

***

### OpenTuning / CloseTuning / IsTuningOpen

```lua
exports['op-mechanic']:OpenTuning(vehicle)   -- optional vehicle; otherwise closest
exports['op-mechanic']:CloseTuning(restore)  -- restore snapshot when true (default)
local open = exports['op-mechanic']:IsTuningOpen()
```

***

### OpenTablet / CloseTablet

```lua
exports['op-mechanic']:OpenTablet()
exports['op-mechanic']:CloseTablet()
```

Useful for radial menus or custom item handlers. Prefer inventory export `useMechanicTablet` when using `mechanic_tablet`.

***

### GetVehicleMileage

Live mileage for HUDs. Reads state bag.

```lua
local data = exports['op-mechanic']:GetVehicleMileage(vehicle)
-- { plate, mileageKm, odometer, odometerUnit, synced } | nil
```

```lua
CreateThread(function()
    while true do
        local ped = PlayerPedId()
        if IsPedInAnyVehicle(ped, false) then
            local veh = GetVehiclePedIsIn(ped, false)
            local m = exports['op-mechanic']:GetVehicleMileage(veh)
            if m then
                -- string.format('%.0f %s', m.odometer, m.odometerUnit)
            end
            Wait(200)
        else
            Wait(1000)
        end
    end
end)
```

***

### EnsureVehicleMileage

If the state bag is missing, loads DB mileage.

```lua
local data = exports['op-mechanic']:EnsureVehicleMileage(vehicle)
```

***

### Nitro Exports

Get nitro state and all required data to connect it to your hud.

```lua
exports['op-mechanic']:GetNitro(vehicle?)       
exports['op-mechanic']:GetNitroLevel(vehicle?)  
exports['op-mechanic']:IsNitroBoosting(vehicle?)
exports['op-mechanic']:IsNitroPurging(vehicle?)
exports['op-mechanic']:IsNitroHudEnabled()
```

### Item exports (inventory)

Used from inventory item definitions:

```lua
exports['op-mechanic']:useMechanicTablet()
exports['op-mechanic']:useRgbController()
exports['op-mechanic']:useEngineKit()
exports['op-mechanic']:useBodyKit()
exports['op-mechanic']:useCleaningKit()
exports['op-mechanic']:useCarWax()
exports['op-mechanic']:usePlaceable(data, slot)
```

### **Kit unlock items (Tebex / item shops)**

When `Config.Nitro.requireInstallItem` / `Config.AntiLag.requireInstallItem` is `true`, players **use** the kit on the vehicle to **unlock** mounting in the tuning menu. The item does **not** fit the kit by itself - install still happens in tuning (pay / work order).

```lua
exports['op-mechanic']:useNitroKitUnlock()     -- nitrous_install_kit
exports['op-mechanic']:useAntiLagKitUnlock()  -- antilag_install_kit
```

{% hint style="info" %}
Full setup: Require nitro & anti-lag install item.
{% endhint %}

**ox\_inventory example**

```lua
['nitrous_install_kit'] = {
    label = 'Nitrous Install Kit',
    weight = 1200,
    stack = true,
    close = true,
    description = 'Use on a vehicle to unlock nitro kit install in the tuning menu.',
    client = {
        export = 'op-mechanic.useNitroKitUnlock',
    },
},

['antilag_install_kit'] = {
    label = 'Anti-lag Install Kit',
    weight = 1000,
    stack = true,
    close = true,
    description = 'Use on a vehicle to unlock anti-lag install in the tuning menu.',
    client = {
        export = 'op-mechanic.useAntiLagKitUnlock',
    },
},
```

Framework usable registration is also handled automatically by the resource when `requireInstallItem = true` (ESX / QB / Qbox via `Fr.RegisterUsableItem`).


# Server Side

List of server side exports/events

{% hint style="warning" %}
Missing an export? Contact us on our [Discord Server](https://discord.gg/otherplanet).
{% endhint %}

***

### GetVehicleMileage

```lua
local data = exports['op-mechanic']:GetVehicleMileage(plate)
-- { plate, mileageKm, odometer, odometerUnit } | nil
```

```lua
local data = exports['op-mechanic']:GetVehicleMileageByNetId(netId)
```

***

### EnsureVehicleMileage

```lua
local data = exports['op-mechanic']:EnsureVehicleMileage(netId, modelName)
```

* `netId`: `number`
* `modelName`: `string?`
* returns: mileage table or `nil`

***

### SendDispatchAlert

Quick dispatch for other resources. **`job` is required** and must match a shop job key.

```lua
exports['op-mechanic']:SendDispatchAlert(
    'Flat tire',
    'Citizen reports a blown tire near Legion Square.',
    'orange', -- orange | red | green
    { x = 215.12, y = -810.45, z = 30.73 },
    'build',  -- optional icon
    'mechanic'
)
```

Returns the created call table, or `nil`.

***

### CreateDispatch

Full payload. Pass `job` or `src` so the call routes to that shop only.

```lua
exports['op-mechanic']:CreateDispatch({
    job = 'mechanic', -- required unless src is set
    -- src = 12,
    header = 'Engine smoke',
    desc = 'Smoke coming from the hood on Del Perro Freeway.',
    color = 'red',
    coords = { x = -1204.55, y = -778.21, z = 17.33 },
    street = 'Del Perro Fwy', -- optional
    icon = 'build',           -- optional
})
```

***

### Mechanic shops

```lua
local shops = exports['op-mechanic']:GetMechanicShops()
local shop = exports['op-mechanic']:GetMechanicShop(id)
local shop = exports['op-mechanic']:GetMechanicShopByJob(jobName)
```

***

#### Duty helpers

```lua
exports['op-mechanic']:SetDuty(src, true)
exports['op-mechanic']:ToggleDuty(src)
local onDuty = exports['op-mechanic']:IsOnDuty(src)
```

***

#### OnPlateChange

Call this after an external plate changer so history / mileage / persistence stay linked.

```lua
exports['op-mechanic']:OnPlateChange(oldPlate, newPlate, props?)
```

***

#### HasAdminPermission

```lua
local allowed = exports['op-mechanic']:HasAdminPermission(src)
```


# Integrations List

List of compatible scripts with OP Mechanic

## List of Integrations

Auto-detected when the resource is started (unless forced in `MainConfig.lua`). Your script is not listed? Open a ticket on Discord.

### Framework

| Resource     | Config value |
| ------------ | ------------ |
| es\_extended | `esx`        |
| qb-core      | `qb`         |
| qbx\_core    | `qbox`       |

### Inventory

| Resource          | Config value                        |
| ----------------- | ----------------------------------- |
| ox\_inventory     | `ox_inventory`                      |
| qb-inventory      | `qb-inventory`                      |
| old qb-inventory  | `old-qb-inventory` (force manually) |
| codem-inventory   | `codem-inventory`                   |
| tgiann-inventory  | `tgiann_inventory`                  |
| origen\_inventory | `origen_inventory`                  |
| one\_inventory    | `one_inventory`                     |

### Banking / society

| Resource          | Config value       |
| ----------------- | ------------------ |
| esx\_addonaccount | `esx_addonaccount` |
| qb-banking        | `qb-banking`       |
| fd\_banking       | `fd_banking`       |
| okokBanking       | `okokBanking`      |
| Renewed-Banking   | `Renewed-Banking`  |
| tgg-banking       | `tgg-banking`      |

### Garage (job fleet)

| Resource           | Config value         |
| ------------------ | -------------------- |
| op-garages         | `op-garages`         |
| jg-advancedgarages | `jg-advancedgarages` |

### Target / TextUI / Notify

| Type     | Supported                                                                  |
| -------- | -------------------------------------------------------------------------- |
| Target   | ox\_target, qb-target                                                      |
| TextUI   | ox\_lib, jg-textui, okokTextUI, brutal\_textui, 0r-textui                  |
| Notify   | op-hud, okokNotify, vms\_notify, ox\_lib, brutal\_notify, framework notify |
| Progress | ox\_lib                                                                    |

### Clothing (wardrobe)

| Resource               | Config value |
| ---------------------- | ------------ |
| op-clothing            | --           |
| illenium-appearance    | --           |
| fivem-appearance       | --           |
| rcore\_clothing        | --           |
| skinchanger            | --           |
| crm-appearance         | --           |
| 17mov\_charactersystem | --           |
| 0r-clothingv2          | --           |
| none                   | --           |


# OP Missions Creator

OP Mission Creator is the most advanced mission creator script ever released for FiveM. Build, edit and run unlimited custom missions in-game.

{% embed url="<https://youtu.be/FPgT9dXdjTA>" %}

{% embed url="<https://otherplanet.dev/product/missions-creator>" %}


# Installation

Installation guide for missions creator.

## Downloading Dependencies

Dependencies are very important — you need to install them before moving on to the installation of our script.

{% stepper %}
{% step %}

### ox\_lib

{% embed url="<https://github.com/overextended/ox_lib>" %}
{% endstep %}

{% step %}

### fivem-freecam

{% embed url="<https://github.com/Deltanic/fivem-freecam>" %}
{% endstep %}
{% endstepper %}

***

## Server.cfg

All dependencies you’ve installed must be started before our script. Make sure to follow the correct start-up order in your `server.cfg`.

```
# REQUIRED:

ensure fivem-freecam
ensure ox_lib
ensure op-missioncreator
```


# Customization

Our script is fully configurable, allowing you to customize it however you like. You can also modify many functions located in the so-called "Editable" zone!

## With the Editable files, you can:

* Add your own FRAMEWORK
* Modify functions, such as:
  * Add a new clothing system
  * Add a new inventory system
  * Customize the progress bar
  * Modify notifications
  * Change the targeting system
  * Customize fuel and vehicle key systems
  * Modify the revive function
  * Modify Dispatch Section
  * Modify Garage Integrations


# Exports


# Client Side

### Exports & Events

{% hint style="warning" %}
If you're looking for other data than listed here - feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

**op-missioncreator** has no public client-side exports.

Start / stop / lobby / queue flows must be driven from the **server** via:

* `exports['op-missioncreator']:StartMission(...)`
* `exports['op-missioncreator']:StartMissionWithLobby(...)`
* `exports['op-missioncreator']:OpCrimeStart(...)`
* `exports['op-missioncreator']:StopMission(...)`

See Server Side.


# Server Side

### Exports & Events

{% hint style="warning" %}
If you're looking for other data than listed here - feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

#### StartMission

Starts a mission for a player. Soft gates (cooldown / requirements) are skipped via `forced = true`, but `exclusive` / `maxPlayers` are still enforced.

**Returns:** `ok, reason`

```lua
local ok, reason = exports['op-missioncreator']:StartMission(src, missionId, opts)
```

```lua
-- Example
local ok, err = exports['op-missioncreator']:StartMission(source, 'my_heist', {
    startStepId = 'step_1', -- optional
})
```

***

#### StartMissionWithLobby

Creates a co-op lobby with a fixed host + member roster and starts the mission immediately (no invite UI). The host is always included - `members` is a list of additional player server ids.

**Returns:** `ok, lobbyId` on success, `false, reason` on failure.

`opts.forced` defaults to `true`. `opts.startStepId` is optional.

```lua
local ok, lobbyId = exports['op-missioncreator']:StartMissionWithLobby(host, missionId, members, opts)
```

```lua
-- Example
local ok, result = exports['op-missioncreator']:StartMissionWithLobby(
    1,                -- host server id
    'my_heist',
    { 2, 3 },         -- additional members
    { startStepId = 'step_1' }
)
```

***

#### StopMission

Stops the player's current mission and clears the server registry slot.

```lua
exports['op-missioncreator']:StopMission(src, reason)
```

```lua
-- Example
exports['op-missioncreator']:StopMission(source, 'admin')
```

***

#### CanStartMission

Dry-run of the start gate. Checks requirements / cooldown / exclusive / queue / etc. without consuming items or starting anything.

**Returns:** `ok, reason`

```lua
local ok, reason = exports['op-missioncreator']:CanStartMission(src, missionId)
```

***

#### GetMissionRegistry

Returns all registered mission definitions (`{ [missionId] = def, ... }`).

```lua
local missions = exports['op-missioncreator']:GetMissionRegistry()
```

***

#### BroadcastMissionEvent

Sends a named event into the player's running mission runtime (received as a mission event on the client).

```lua
exports['op-missioncreator']:BroadcastMissionEvent(src, name, data)
```

```lua
-- Example
exports['op-missioncreator']:BroadcastMissionEvent(source, 'policeAlert', {
    zone = 'docks',
})
```

***

#### OpCrimeStart

op-crime tablet entry point. Routes by mission settings:

* queue enabled -> queue join
* coop allowed -> client invite / lobby flow
* otherwise -> direct start (requirements consumed)

**Returns:** `ok, 'started'|'queued'|'invite_pending'` or `false, reason`

```lua
local ok, status = exports['op-missioncreator']:OpCrimeStart(src, missionId)
```

***

#### GetOpCrimeMissions

Returns missions marked as exposed to op-crime, shaped for op-crime `Config.Missions`.

```lua
local missions = exports['op-missioncreator']:GetOpCrimeMissions()
```

***

#### IsOpCrimeIntegrationActive

Returns a `boolean` - whether the op-crime integration is currently active.

```lua
local active = exports['op-missioncreator']:IsOpCrimeIntegrationActive()
```

### Server Events

#### op-missioncreator:server:missionStarted

Fired after a mission successfully starts for a player.

```lua
AddEventHandler('op-missioncreator:server:missionStarted', function(src, missionId, runId, opts)
    -- opts may include: lobbyHost, lobbyMember, lobbyId, forced, startStepId, runId, …
end)
```

***

#### op-missioncreator:server:missionEnded

Fired when a mission ends for a player.

`status` is one of: `'completed'` | `'failed'` | `'aborted'` | `'queue_left'` | `'lobby_closed'`

```lua
AddEventHandler('op-missioncreator:server:missionEnded', function(src, missionId, status, payload)
    -- Your Code
end)
```


# Integrations

Full List of Scripts Compatible with our Missions Creator System!

## Dispatch

{% hint style="warning" %}
You Can add Your Dispatch Script in `integrations/dispatch/`
{% endhint %}

<table><thead><tr><th width="182">Script Name</th><th width="258">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>cd_dispatch</td><td>✅ Supported</td><td>--</td></tr><tr><td>codem_dispatch</td><td>✅ Supported</td><td>--</td></tr><tr><td>lb-tablet</td><td>✅ Supported</td><td>--</td></tr><tr><td>ps-dispatch</td><td>✅ Supported</td><td>--</td></tr><tr><td>rcore_dispatch</td><td>✅ Supported</td><td>--</td></tr></tbody></table>

## Inventories

Inventory integrations is used only for items with metadata. If you dont want to assign items with metadata you can skip this.

{% hint style="warning" %}
You Can add Your Inventory Script in `integrations/inventory/`
{% endhint %}

<table><thead><tr><th width="173">Script Name</th><th width="188">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>ox_inventory</td><td>✅ Supported</td><td>--</td></tr><tr><td>qb-inventory</td><td>✅ Supported</td><td>--</td></tr><tr><td>codem-inventory</td><td>✅ Supported</td><td>--</td></tr><tr><td>tgiann_inventory</td><td>✅ Supported</td><td>--</td></tr><tr><td>none</td><td>✅ Supported</td><td>--</td></tr></tbody></table>

## Fuel&#x20;

{% hint style="warning" %}
You Can add Custom Fuel Script in `integrations/utils/vehicleFuel.lua`
{% endhint %}

<table><thead><tr><th width="156">Script Name</th><th width="166">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>cdn-fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "cdn-fuel"</td></tr><tr><td>ox-fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "ox-fuel"</td></tr><tr><td>LegacyFuel</td><td>✅ Supported</td><td>Config.FuelDependency = "LegacyFuel"</td></tr><tr><td>rcore-fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "rcore-fuel"</td></tr><tr><td>codem-xfuel</td><td>✅ Supported</td><td>Config.FuelDependency = "codem-xfuel"</td></tr><tr><td>lc_fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "lc_fuel"</td></tr><tr><td>stg-fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "stg-fuel"</td></tr></tbody></table>

## Vehicle Keys

{% hint style="warning" %}
You Can add Custom Vehicle Keys Script in `integrations/utils/vehicleKeys.lua`
{% endhint %}

<table><thead><tr><th width="186">Script Name</th><th width="150">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>brutal_keys</td><td>✅ Supported</td><td>Config.KeysDependency = "brutal_keys"</td></tr><tr><td>qb-keys</td><td>✅ Supported</td><td>Config.KeysDependency = "qb-keys"</td></tr><tr><td>qbx_vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "qbx_vehiclekeys"</td></tr><tr><td>wasabi_carlock</td><td>✅ Supported</td><td>Config.KeysDependency = "wasabi_carlock"</td></tr><tr><td>sna-vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "sna-vehiclekeys"</td></tr><tr><td>dusa_vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "dusa_vehiclekeys"</td></tr><tr><td>RenewedVehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "RenewedVehiclekeys"</td></tr><tr><td>tgiann-keys</td><td>✅ Supported</td><td>Config.KeysDependency = "tgiann-keys"</td></tr><tr><td>ak47_vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "ak47_vehiclekeys"</td></tr><tr><td>ak47_qb_vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "ak47_qb_vehiclekeys"</td></tr><tr><td>MrNewbVehicleKeys</td><td>✅ Supported</td><td>Config.KeysDependency = "MrNewbVehicleKeys"</td></tr><tr><td>mVehicle</td><td>✅ Supported</td><td>Config.KeysDependency = "mVehicle"</td></tr><tr><td>sy_carkeys</td><td>✅ Supported</td><td>Config.KeysDependency = "sy_carkeys"</td></tr><tr><td>p_carkeys</td><td>✅ Supported</td><td>Config.KeysDependency = "p_carkeys"</td></tr></tbody></table>

## Text UI

{% hint style="warning" %}
You Can add Custom Text UI Script in `integrations/utils/textUI.lua`
{% endhint %}

<table><thead><tr><th width="145">Script Name</th><th width="159">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>ox_lib</td><td>✅ Supported</td><td>Config.TextUI = "ox_lib"</td></tr><tr><td>jg-textui</td><td>✅ Supported</td><td>Config.TextUI = "jg-textui"</td></tr><tr><td>okokTextUI</td><td>✅ Supported</td><td>Config.TextUI = "okokTextUI"</td></tr><tr><td>brutal_textui</td><td>✅ Supported</td><td>Config.TextUI = "brutal_textui"</td></tr><tr><td>0r_textui</td><td>✅ Supported</td><td>Config.TextUI = "0r_textui"</td></tr></tbody></table>

## Targets

{% hint style="warning" %}
You Can add Custom Targeting Script in `integrations/utils/targets.lua`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="154">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>ox_target</td><td>✅ Supported</td><td>--</td></tr><tr><td>qb-target</td><td>✅ Supported</td><td>--</td></tr><tr><td>fivem native markers</td><td>✅ Supported</td><td>--</td></tr></tbody></table>

## Notify

{% hint style="warning" %}
You Can add Custom Notify Script in `integrations/utils/sendNotify.lua`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="149">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>op_hud</td><td>✅ Supported</td><td>Config.Misc.Notify = "op_hud"</td></tr><tr><td>okokNotify</td><td>✅ Supported</td><td>Config.Misc.Notify = "okokNotify"</td></tr><tr><td>vms_notify</td><td>✅ Supported</td><td>Config.Misc.Notify = "vms_notify"</td></tr><tr><td>brutal_notify</td><td>✅ Supported</td><td>Config.Misc.Notify = "brutal_notify"</td></tr><tr><td>ox_lib</td><td>✅ Supported</td><td>Config.Misc.Notify = "ox_lib"</td></tr><tr><td>ESX</td><td>✅ Supported</td><td>Config.Misc.Notify = "ESX"</td></tr><tr><td>QBCORE</td><td>✅ Supported</td><td>Config.Misc.Notify = "QBCORE"</td></tr><tr><td>QBOX</td><td>✅ Supported</td><td>Config.Misc.Notify = "QBOX"</td></tr></tbody></table>


# List of Integrations

See full list of available Integrations in Our Script!

{% content-ref url="/pages/bGBCrkttWNAKdY3laEB9" %}
[Integrations](/scripts/op-gangs/integrations)
{% endcontent-ref %}


# OP Gangs

Most Advanced Gangs Script with Turf Zones System and Sleek UI Design available on the Market!

## 🛒 Purchase Script:&#x20;

{% embed url="<https://otherplanet.dev/product/gangs>" %}

{% embed url="<https://youtu.be/YbPWD3R992w?si=-VgrWSIvN87qGvEi>" %}


# Installation

Before you start, make sure you're logged into the CFX portal. This ensures you can download the asset and receive future updates.

## Downloading Dependencies

Dependencies are very important — you need to install them before moving on to the installation of our script.

{% stepper %}
{% step %}

### Create Items

{% content-ref url="/pages/fpBMpT8joeI7FfnGyV9Q" %}
[Create Items](/scripts/op-gangs/create-items)
{% endcontent-ref %}
{% endstep %}

{% step %}

### PolyZone

{% embed url="<https://github.com/mkafrin/PolyZone>" %}
{% endstep %}

{% step %}

### ox\_lib

{% embed url="<https://github.com/overextended/ox_lib>" %}
{% endstep %}

{% step %}

### fivem-freecam

{% embed url="<https://github.com/Deltanic/fivem-freecam>" %}
{% endstep %}

{% step %}

### Rope Prop (Optional)

{% embed url="<https://forum.cfx.re/t/props-rope-cuffs-and-a-gag/5313909>" %}
{% endstep %}

{% step %}

### OP Drug Selling (Optional)

This is Drug Selling script which is completly free and have many functions!

{% embed url="<https://github.com/Bagietame/op-drugselling>" %}
{% endstep %}

{% step %}

### fivem-greenscreener (optional)

Install this dependency only on your Test Server if you want to complete Furnitures Images Screenshots!

{% embed url="<https://github.com/Bentix-cs/fivem-greenscreener>" %}
{% endstep %}

{% step %}

### screenshot-basic (optional)

Install this dependency only on your Test Server if you want to complete Furnitures Images Screenshots!

{% embed url="<https://github.com/citizenfx/screenshot-basic>" %}
{% endstep %}
{% endstepper %}

***

## Server.cfg

All dependencies you’ve installed must be started before our script. Make sure to follow the correct start-up order in your `server.cfg`.

```
# REQUIRED:

ensure PolyZone
ensure ox_lib
ensure op-crime

# OPTIONAL:

ensure screenshot-basic
ensure fivem-greenscreener
ensure bzzz_gag
ensure op-drugselling
```

***

## Permissions

It's important to grant your server administrators access to our Crime Admin. Don’t forget to add them to your `mainConfig.lua` as well!

```lua
Config.AdminPanelPlayers = {
    ['char1:7e0ec7b80d186fd8c29f6631e4377e75812fe8fd'] = true
}
```

***

## Database

The script will automatically create its own database on first start-up.

***

## Integration for Qbcore and Qbox

Complete additional integration dedicated for QbCore and QBox if you want to properly display gang name, ids etc. in other scripts.

{% content-ref url="/pages/Xive17QV8mDDgqoxGRZA" %}
[QB Core and QBox](/scripts/op-gangs/qb-core-and-qbox)
{% endcontent-ref %}


# Tutorials


# How to add player to Crime Admin

How do I grant admin panel permissions to a specific player?

* Navigate to `config/MainConfig.lua`.
* Find the section `Config.AdminPanelPlayers`.
* Open **TXAdmin** and select the player you want to grant permissions to.
* Click on the **"IDs"** option.
* Copy one of the available identifiers.
* Paste the identifier into `Config.AdminPanelPlayers` in the config file.
  * **Remember to add a comma (`,`) at the end of each line.**

```lua
Config.AdminPanelPlayers = {
    ['steam:110000118d2c0db'] = true,
    ['discord:571105311251890186'] = true,
}
```

<figure><img src="https://36113860-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1vEl4u37HzsUUsBP0oWM%2Fuploads%2FaT0pzsv33rRbn2Ml4OaS%2Fimage.png?alt=media&amp;token=b08db30a-b9a8-45e7-ad71-7faa7a15153a" alt=""><figcaption></figcaption></figure>


# Migration from V1

In V2 we changed way how the vehicles are saved in the database.

{% stepper %}
{% step %}

### Run Migration Command

Run `transferVehicles` in txAdmin Console

<figure><img src="https://36113860-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1vEl4u37HzsUUsBP0oWM%2Fuploads%2F6wUmOUCHfaHDyVOK3VB4%2Fimage.png?alt=media&amp;token=75e1ee47-7cc7-4023-a63e-7786459c0600" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### All Set!

If you haven't got any error, restart script and it's ready to use! (Please open ticket in Customer Category on our discord if you will get any error)
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
WARNING! If you want to fix error during the command, it's most likely issue with your owned\_vehicles/player\_vehicles database. Vehicle insert to the database function is available in `framework/server/s_framework.lua`
{% endhint %}


# I Can't withdraw/deposit Dirty Money

Depending on your server framework you have to configure how you would like to handle dirty money.

{% stepper %}
{% step %}

### MainConfig.lua

Go to the config/MainConfig.lua&#x20;
{% endstep %}

{% step %}

### Change Config.DirtyMoney

Find this part of Configuration file and change item name according to your framework configuration.

```lua
Config.DirtyMoney = {
    -- ON ESX IT WORKS WITH DEFAULT black_money account balance!
    -- This settings apply only for QB/QBOX!
    itemName = "dirty_money"
}
```

{% endstep %}
{% endstepper %}

{% hint style="warning" %}
If this does not help, feel free to contact us through our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}


# I want to disable some features

Informations regarding to the configuration of the Script.

Please read this before opening ticket in our Support Server. Our Script is made to fit every server, you can disable almost every feature of it. Season Pass, Garages, Illegal Medics, PVP Zones, Turf Zones, Rivalry Mode, Graffiti, Racketeering - And More. Please just read the configuration file carefully before Contacting us. Thanks!


# How to screenshot furnitures?

Quick tutorial on how to screenshot furnitures.

After configuring your available furniture, you can automatically generate screenshots of those objects using our script.

**✅ Requirements**

Make sure you have installed the following resources:

{% stepper %}
{% step %}

### fivem-greenscreener

{% embed url="<https://github.com/Bentix-cs/fivem-greenscreener>" %}
{% endstep %}

{% step %}

### screenshot-basic

{% embed url="<https://github.com/citizenfx/screenshot-basic>" %}
{% endstep %}
{% endstepper %}

***

**▶️ Commands**

After installing the required scripts, you can use the following commands:

* **All furniture:**

  ```
  /screenshotall
  ```
* **Specific furniture category:**

  ```
  /screenshotcategory "categoryname"
  ```
* **Single prop only:**

  ```
  /screenshotselected propname
  ```

***

**⚙️ How It Works**

After running a command, the script will:

1. Spawn the selected props in front of a green screen
2. Automatically take screenshots
3. Remove the green background from the images
4. Save the final images to files

Once the process is complete, **restart the script** to load and display the generated images.

***

**⚠️ Important Notes**

* This feature should be used **only on a test server**.\
  ❗ Do NOT run it on a live server with players.
* After finishing the process:
  * Copy the **`web`** folder
  * Paste it into your main server inside the **`op-crime`** resource

***

**💡 Additional Recommendation**

For best results:

* Remove all **shaders, mods, custom citizens, and plugins** from your game files
* Use **default GTA V settings**

This ensures the green screen is properly detected and removed from the images.


# Why is the Spy Detector not working?

Spy Detector is instantly hiding after using item.

## Why is the Spy Detector not working?

If your Spy Detector (Digiscanner) is not working, the most common reason is that your inventory system is **blocking or hiding the weapon**.

Many inventory systems (including **ox\_inventory**) automatically filter out certain weapons to prevent misuse, confusion, or clutter. Unfortunately, this can also affect the Digiscanner, since it may be treated as a restricted or unusual weapon.

***

### 🔧 For `ox_inventory` users

By default, **ox\_inventory blocks the Digiscanner**, which prevents it from functioning properly.

#### How to fix it:

1. Navigate to your `inventory.cfg` file
2. Add the Digiscanner as an ignored weapon:

```
setr inventory:ignoreweapons ["WEAPON_DIGISCANNER"]
```

This ensures the Digiscanner is **not filtered out** and can be used correctly in-game.

***

### 📦 Using a different inventory system?

Even if you are **not using ox\_inventory**, the issue may still occur.

Many inventory systems:

* Hide certain weapons automatically
* Flag uncommon items as illegal or restricted
* Prevent specific weapons from being equipped or used

#### What you should check:

* Make sure the Digiscanner is **not blacklisted**
* Ensure it is **allowed to be equipped**
* Disable any filters that treat it as an illegal or hidden weapon

👉 In short:\
If your inventory system interferes with weapons, you will likely need to **whitelist or explicitly allow the Digiscanner**.

***

### ✅ Summary

If the Spy Detector is not working:

* It’s usually **not a script issue**
* Your **inventory is blocking the Digiscanner**
* Fix it by **whitelisting or ignoring the weapon** in your inventory config

***

If you're unsure how to configure your specific inventory, check its documentation or look for options related to:

* weapon filtering
* ignored weapons
* blacklists / whitelists


# Yarn Failed to build

Errors like yarn failed, uv\_close, or missing modules when starting op-crime are usually caused by outdated artifacts. Updating to a stable artifact version often fixes the issue instantly.

## Build / Startup Error Fix

If you see errors like these while starting `op-crime`, the issue is usually caused by **outdated artifacts**, not by the resource itself.

### Example errors

#### Yarn / build error

```
Running build tasks on resource op-crime - it'll restart once completed.
Couldn't start resource op-crime.
[yarn] yarn install v1.22.5
[yarn] [1/4] Resolving packages...
[yarn] success Already up-to-date.
[yarn] Done in 0.06s.
[yarn] Error: Assertion failed: 0 (../deps/uv/src/unix/core.c: uv_close: 234)
Building resource op-crime failed.
Error data: yarn failed!
```

#### Missing module error

```
Error loading script server/server.js in resource op-crime: Error: Cannot find module 'image-js'
Require stack:
- ./QBox Passion Project/txData/qbox_7BFF69.base/resources/[gangs]/op-crime/dummy.js
```

***

### Primary fix

In most cases, the correct fix is to **update your server artifacts**.

`op-crime` may fail to build or start properly on older artifacts.\
Before troubleshooting Yarn, Node, or dependencies, make sure you are running a **recent artifact version**.

You can check the latest recommended artifacts here:

[**https://artifacts.jgscripts.com/**](https://artifacts.jgscripts.com/)

Use that page to see which artifacts are currently up to date and do not have reported issues.

***

## Why this happens

These errors often appear because the server is running on **older or problematic artifacts** that are not fully compatible with the resource build process.

This can result in issues such as:

* `yarn failed`
* `uv_close: 234`
* `Cannot find module ...`
* resource build failures
* startup crashes during dependency handling

Even if the resource files are correct, outdated artifacts can still cause the resource to fail.

***

## Recommended fix order

### 1. Update artifacts first

Before changing anything in the resource itself, update your server artifacts to a newer stable version.

#### What to do

1. Check your current artifact version
2. Visit [**artifacts.jgscripts.com**](https://artifacts.jgscripts.com/)
3. Compare your current version with the latest recommended ones
4. Update to a newer artifact that has no reported issues
5. Restart the server and test `op-crime` again

Do this before reinstalling packages or editing dependencies.

***

### 2. Restart the resource after updating

After updating artifacts, restart the whole server or run:

```
ensure op-crime
```

If the resource starts correctly after the artifact update, no further action is needed.

***

## If the issue still happens after updating artifacts

If you already updated artifacts and the issue continues, follow the steps below.

***

### Reinstall dependencies

Go to the resource folder (this is example, use real directory on your server):

```
resources/[gangs]/op-crime
```

Remove old install files:

```
rm -rf node_modules
rm -f yarn.lock
rm -f package-lock.json
```

Then reinstall:

```
yarn install
```

If needed, clean the Yarn cache first:

```
yarn cache clean
yarn install
```

***

### Fix `Cannot find module 'image-js'`

If you still get this error after updating artifacts, install the missing package manually:

```
yarn add image-js
```

Or with npm:

```
npm install image-js
```

Then restart the resource.


# Remove player\_vehicles Foreign Key Constraint

Learn how to remove the player\_vehicles\_ibfk\_1 foreign key constraint from your MySQL database to fix vehicle insert errors for gang, job, and shared vehicles in FiveM QBCore/QBOX servers.

## Overview

If your server throws this error:

```luau
Cannot add or update a child row: a foreign key constraint fails
(`otherplanet_server`.`player_vehicles`, CONSTRAINT `player_vehicles_ibfk_1`
FOREIGN KEY (`citizenid`) REFERENCES `players` (`citizenid`)
ON DELETE CASCADE ON UPDATE CASCADE)
```

It means the `player_vehicles` table has a **foreign key constraint** linked to the `players` table.

When a vehicle is inserted for a player that does **not exist** in `players`, MySQL blocks the insert.

***

## Goal

Remove the constraint:

```
player_vehicles_ibfk_1
```

So vehicles can be inserted without checking `players.citizenid`.

***

## Step 1 - Open Your Database

Use one of these:

* phpMyAdmin
* HeidiSQL
* DBeaver
* Navicat
* MySQL CLI

***

## Step 2 - Remove the Constraint

Run:

```
ALTER TABLE player_vehicles
DROP FOREIGN KEY player_vehicles_ibfk_1;
```


# QB Core and QBox

Read More about QB Core and QBox integration with default Gangs functions.

{% content-ref url="/pages/EbAgYzWcZHkyEqDtXQw0" %}
[Gangs: qb-core/qbox](/scripts/op-gangs/integrations/gangs-qb-core-qbox)
{% endcontent-ref %}


# Config Files

The script is fully configurable, allowing complete customization of its behavior and features. Below is a list of configuration files included in the resource.

## Useful Articles

{% embed url="<https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks>" %}

{% embed url="<https://steamcommunity.com/dev/apikey>" %}

<details>

<summary>MainConfig.lua</summary>

```lua
-- ──────────────────────────────────────────────────────────────────────────────
-- Dependency Check Helper                                                     
-- (Information) ► Returns the first matching started resource alias from provided table.
-- (Information) ► Used by Fuel/Keys/TextUI/Inventory/Target detection above.
-- ──────────────────────────────────────────────────────────────────────────────
function scriptCheck(data)            -- Do not modify unless you know what you're doing.
    for k, v in pairs(data) do
        if GetResourceState(k):find('started') ~= nil then
            return v
        end
    end
    return false
end

-- ──────────────────────────────────────────────────────────────────────────────
--  OTHERPLANET / OP Gangs 3.0 Main CONFIGURATION
-- ──────────────────────────────────────────────────────────────────────────────
--  This configuration file controls all customizable behaviour of OP Gangs 3.0.
--  Always make a backup before editing.
--  Wrong edits can break the resource.
-- ──────────────────────────────────────────────────────────────────────────────

Config = {}                           

-- ──────────────────────────────────────────────────────────────────────────────
-- Locale & Debug                                                             
-- (Information) ► Locale controls which language file from locales/* will be used.
-- (Information) ► Debug enables extra logging to help with issue tracking.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Locale = "en"                  -- Supported: PL / ES / LT / HU / EN / FR / IT / PT / SK / TW / HR / EL / CZ / SI / AR / TR / DE / SV / NL / NO
Config.Debug  = false                 -- true = verbose debug output in console.

-- ──────────────────────────────────────────────────────────────────────────────
-- TIMEZONE                                                         
-- (Information) ► Full list of timezones is available in AvailableTimeZones.json
-- ──────────────────────────────────────────────────────────────────────────────

Config.TimeZone = "Europe/Warsaw"

-- ──────────────────────────────────────────────────────────────────────────────
-- Fuel Dependency Detection                                                   
-- (Information) ► Auto-detects your active fuel resource.
-- (Information) ► To add support for another fuel script, extend the list below and 
-- (Information) ► handle logic in the integrations where fuel is used.
-- ──────────────────────────────────────────────────────────────────────────────
local fuelScripts = {                 
    ['cdn-fuel']        = "cdn-fuel",
    ['ox_fuel']         = "ox-fuel",
    ['LegacyFuel']      = "LegacyFuel",
    ['rcore_fuel']      = "rcore-fuel",
    ['codem-xfuel']     = "codem-xfuel",
    ['lc_fuel']         = "lc_fuel",
    ['stg-fuel']        = "stg-fuel",
}
Config.FuelDependency = scriptCheck(fuelScripts) or 'none'

-- ──────────────────────────────────────────────────────────────────────────────
-- Vehicle Keys Dependency Detection                                           
-- (Information) ► Auto-detects supported vehicle key systems.
-- (Information) ► If none is found, script will behave as if no key system is present.
-- ──────────────────────────────────────────────────────────────────────────────
local keyScripts = {                  
    ['brutal_keys']        = "brutal_keys",
    ['qbx_vehiclekeys'] = "qb-keys",
    ['qb-vehiclekeys']            = "qb-keys",
    ['wasabi_carlock']     = "wasabi_carlock",
    ['sna-vehiclekeys']    = "sna-vehiclekeys",
    ['dusa_vehiclekeys']   = "dusa_vehiclekeys",
    ['Renewed-Vehiclekeys']= "Renewed-Vehiclekeys",
    ['tgiann-hotwire']     = "tgiann-keys",
    ['ak47_vehiclekeys']   = "ak47_vehiclekeys",
    ['ak47_qb_vehiclekeys']= "ak47_qb_vehiclekeys",
    ['mVehicle']           = "mVehicle",
    ['sy_carkeys']         = "sy_carkeys",
    ['MrNewbVehicleKeys']  = "MrNewbVehicleKeys",
    ['vehicles_keys']      = "vehicles_keys",
}
Config.KeysDependency = scriptCheck(keyScripts) or 'none'

-- ──────────────────────────────────────────────────────────────────────────────
-- Text UI Dependency Detection                                                
-- (Information) ► Auto-detects supported 3D/2D Text UI libraries.
-- (Information) ► If none is found, some prompts may fallback to default behaviour.
-- ──────────────────────────────────────────────────────────────────────────────
local textUIScripts = {              
    ['ox_lib']        = "ox_lib",
    ['jg-textui']     = "jg-textui",
    ['okokTextUI']    = "okokTextUI",
    ['brutal_textui'] = "brutal_textui",
    ['0r-textui']     = "0r-textui",
}
Config.TextUI = scriptCheck(textUIScripts) or 'none' 

-- ──────────────────────────────────────────────────────────────────────────────
-- Garage Script                                                               
-- (Information) ► Manual selection of external garage script integration.
-- (Information) ► If set to 'none', OP-Crime will handle garage logic internally where applicable.
-- ──────────────────────────────────────────────────────────────────────────────
Config.GarageScript = 'none'          -- Options: 'none', 'op-garages', 'jg-advancedgarages'

-- ──────────────────────────────────────────────────────────────────────────────
-- Additional Script Integrations                                              
-- (Information) ► Toggle support for optional helper resources.
-- ──────────────────────────────────────────────────────────────────────────────
Config.AdditionalScripts = {         
    kq_shellcreator  = false,         -- (Information) ► true if you are using kq_shellcreator.
    advancedParking  = false          -- (Information) ► true if you use AdvancedParking.
}

local dispatchScripts = {            
    ['cd_dispatch']        = "cd_dispatch",
    ['codem_dispatch']     = "codem_dispatch",
    ['kartik-mdt']         = "kartik-mdt",
    ['lb-tablet']          = "lb-tablet",
    ['ps-dispatch']        = "ps-dispatch",
    ['rcore_dispatch']     = "rcore_dispatch",
}
Config.dispatchScript = scriptCheck(dispatchScripts) or 'none'

-- ──────────────────────────────────────────────────────────────────────────────
-- Inventory System Detection                                                  
-- (Information) ► Auto-detects currently started inventory system.
-- (Information) ► To support more inventories, extend the list and update inventory integration.
-- ──────────────────────────────────────────────────────────────────────────────
local inventoryScripts = {            
    ['ox_inventory']     = "ox_inventory",
    ['qb-inventory']     = "qb-inventory",
    ['codem-inventory']  = "codem-inventory",
    ['tgiann-inventory'] = "tgiann_inventory",
    ['origen_inventory'] = "origen_inventory",
}

-- If you're using QB Inventory and it's not working properly set below option to:
-- Config.Inventory = {                  
--     inventoryScript = 'old-qb-inventory'
-- }

Config.Inventory = {                  
    inventoryScript = scriptCheck(inventoryScripts) or 'none'
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Currency Formatting                                                         
-- (Information) ► Visual formatting of money values in the UI (JS Intl.NumberFormat).
-- (Information) ► This does NOT change internal game currency logic, only display.
-- ──────────────────────────────────────────────────────────────────────────────
Config.CurrencySettings = {           
    -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
    currency = "USD",                 -- 'USD','EUR','PLN', etc.
    style    = "currency",            -- 'currency','decimal','percent','unit'
    format   = "en-US"                -- Locale string for formatting.
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Dirty Money Handling                                                        
-- (Information) ► ESX: Uses default 'black_money' account automatically.
-- (Information) ► QB/QBOX: Uses configured item as dirty cash equivalent.
-- ──────────────────────────────────────────────────────────────────────────────
Config.DirtyMoney = {                 
    itemName = "dirty_money" -- Dirty money item name on QB/QBOX.
}

-- ──────────────────────────────────────────────────────────────────────────────
-- ⚒️ MISC CONFIGURATION ⚒️                                                   
-- (Information) ► General behaviour, markers, notifications, ranking limits, etc.
-- ──────────────────────────────────────────────────────────────────────────────

local targets = { -- Target libraries detection
    ['ox_target'] = "ox-target",
    ['qb-target'] = "qb-target"
}

local notifyScripts = { -- Notify libraries detection
    ['op-hud'] = "op_hud",
    ['okokNotify'] = "okokNotify",
    ['vms_notify'] = "vms_notify",
    ['ox_lib'] = "ox_lib",
    ['brutal_notify'] = "brutal_notify",
}

Config.Misc = {                       
    AccessMethod = scriptCheck(targets) or 'none', --  'ox-target' / 'qb-target' / 'none'
    zoneSize = 1.2,                   -- Marker radius for interaction zones.
    zoneColor = {                     -- Marker color (RGB).
        r = 219,
        g = 0,
        b = 0,
    },
    TowingTime = 5,                                -- Towing time in seconds.
    Notify = scriptCheck(notifyScripts) or 'none', -- Notify system: none / ESX / QBCORE / QBOX or auto detected from list 'notifyScripts'
    stashCapacityUpgradePer = 5,                   -- One stash upgrade = +X KG. Note: Changing this requires your changes in Locale file! UI description (will still display that upgrade adds 5kg until you adjust translation)
    ranksLimit   = 5,                              -- Max number of ranks per organisation.
    membersLimit = 20,                             -- Max number of members per organisation.
    limitBossMenu = false,                         -- true = only one player can access bossmenu at once. Note: You can limit bossmenu to one player at a time!
    disableGarage = false,                         -- true = completely disable garages & vehicle shop in bossmenu.
    disableDarkChat = false,                       -- true = disable darkchat in crime tablet.
    disableDarkChatNotificationAboveMap = false,   -- true = disable on-screen darkchat notifications.
    disableRanking = false,                        -- true = disable ranking page in tablet.
    defaultStashWeight = 2,                        -- Default Stash KG for new Gangs.
    defaultRanksSlotsAmount = 2,                   -- Default Ranks Slots for new Gangs.
    defaultRanksMembersAmount = 2,                 -- Default Member Slots for new Gangs.
    useDirtyMoneyInBossmenu = false,               -- Use Dirty money to purchase upgrades in bossmenu.
    disableGangBlip = false,                       -- Disables Gang Location Blip on gta Map
    disableStashes = false,                        -- Disable Stashes
} 

-- ──────────────────────────────────────────────────────────────────────────────
-- Admin Commands & Permissions                                                
-- (Information) ► Configure admin commands and who is allowed to use them.
-- ──────────────────────────────────────────────────────────────────────────────
Config.AdminPanelCommand   = "crimeadmin"   -- Command for Crime Admin Panel.
Config.SetJobCommand       = "setcrimejob"  -- Command to set selected player's crime job.
Config.AddCrimeVehicle     = "addcrimecar"  -- Command to add vehicle to selected organisation.
Config.FireCommand         = "firemember"   -- Command to fire member from current organisation.
Config.ResetGangsStats     = "resetgangstats" -- Wipes all gangs stats (zones, missions, etc.) used in ranking.

-- List of allowed identifiers to use admin Commands /setcrimejob and /addcrimecar
-- (Information) ► Use identifiers from txAdmin -> IDs (no hardware IDs!).
-- (Information) ► You can also use character identifiers or citizenid on QBOX/QB.
-- E.g.: char1:7e0ec7b80d186fd8c29f6631e4377e75812fe8fd
Config.AdminCommandsPlayers = {      
    ['license:94792cad7ae764305aae8f1372d3cd287848ae7c'] = true,
    ['discord:571105311251890186'] = true,
}

-- List of allowed identifiers to use /crimeadmin
-- Same rules as above (txAdmin IDs / character identifiers / citizenid).
Config.AdminPanelPlayers = {          
    ['license:94792cad7ae764305aae8f1372d3cd287848ae7c'] = true,
    ['discord:571105311251890186'] = true,
}

Config.SellVehiclePercentage = 20     -- Percentage of original vehicle price when selling in bossmenu.

-- ──────────────────────────────────────────────────────────────────────────────
-- 📱 TABLET CONFIGURATION 📱                                                 
-- (Information) ► Controls how crime tablet is accessed and how missions are handled.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Tablet = {                     
    tabletASItem = false,             -- true = tablet is usable item, false = command-based.
    commandName = "crimetablet",      -- Command used when tabletASItem is false.
    item = {
        name = "crime_tablet"         -- Required item name if tabletASItem is true.
    },
    MissionsPerRestart = 15,           -- How many missions per organisation per update.
    DisableSeasonPass = false,        -- true = disable season pass feature Completly (As well from Boss Menu and Admin Panel).
    DisableFrames = false,            -- true = hide tablet frame UI elements.
    Animation = { -- Tablet Animation
        enable = true,
        dict = "amb@code_human_in_bus_passenger_idles@female@tablet@idle_a",
        name = "idle_a",
        prop = { -- Replace prop table with false (prop = false) if you want to disable it!
            Prop = "prop_cs_tablet",
            PropBone = 28422,
            PropPlacement = {
                -0.05,
                0.0,
                0.0,
                0.0,
                -90.0,
                0.0
            }
        }
    }
}

Config.SeasonPass = {
    -- To disable/enable season pass check Config.Tablet.DisableSeasonPass
    -- Configure custom currency exports in `op-crime/integrations/server/utils/customCurrency.lua`
    enableCustomCurrency = false, 
    customCurrencyLabel = "GC", -- Label of coins, example: Purchase for 950GC
}

-- ──────────────────────────────────────────────────────────────────────────────
-- 🌍 BLIPS CONFIGURATION 🌍                                                  
-- (Information) ► Map blips for organisations, garages, zones and money laundry.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Blips = {                      
    BlipScale = 0.8,                  -- Global blip scale.
    ShowBlipsOnMap = true,            -- false = hide money laundry, garages and illegal medic blips.
    ZonesShowBlipsOnMap = true,       -- false = hide PVP/turf zone blips.
    Medic = { blipId = 51, blipColor = 7 },
    Organisation = { blipId = 437, blipColor = 1 },
    Zone = { blipId = 379, blipColor = 3 },
    Garage = { blipId = 357, blipColor = 2 },
    MoneyLaundry = { blipId = 318, blipColor = 25 },
    MoneyLaundryLocation = { blipId = 478, blipColor = 1 },
    LaundryLocationRadiusBlip = { 
        Color = 49,
        Alpha = 222,
        Radius = 60.0
    }
}

-- ──────────────────────────────────────────────────────────────────────────────
-- 🧼 MONEY LAUNDRY CONFIGURATION 🧼                                         
-- (Information) ► Settings for dirty money washing missions.
-- ──────────────────────────────────────────────────────────────────────────────
Config.MoneyLaundry = {               
    Disable = false,                  -- true = disable entire money laundry feature.
    laundryAmountPerOneStop = 20000,  -- Amount of dirty money cleaned per location.
    laundryPercentage = 15,           -- Tax percentage taken from laundered amount.
    Ped = {                           -- Set to false to disable ped (Ped = false,).
        model = "a_m_m_afriamer_01",
        gender = "male", -- options: male/female
    },
    laundryMisc = {
        location = vec4(78.8508, 112.5588, 80.1682, 161.7077),
        vehicleSpawnCoords = vec4(68.4445, 119.2293, 79.1232, 161.5234),
        vehicleModel = 'boxville4',
        -- LAUNDRY MISSION OUTFIT IS LOCATED NOW IN config/ClothingConfig.lua
    },
    laundryLocations = {
        {
            coords = vec4(237.7540, 22.6503, 82.6137, 341.4727)
        },
        {
            coords = vec4(-77.5555, -1200.6666, 26.6352, 92.4784)
        },
        {
            coords = vec4(232.4930, -1771.4315, 27.6610, 48.4330)
        },
        {
            coords = vec4(967.5204, -1823.1718, 30.0824, 229.1019)
        },
        {
            coords = vec4(947.5532, -1698.1992, 29.0851, 84.8497)
        },
    }
}

-- ──────────────────────────────────────────────────────────────────────────────
-- 🏥 MEDIC CONFIGURATION 🏥                                                 
-- (Information) ► Illegal medic / healing spot configuration.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Medic = {                      
    Disable = false,                  -- true = disable this feature.
    HealingTime = 10,                 -- Healing time in seconds.
    Ped = {
        model = "s_m_m_scientist_01",
        gender = "male",
        animation = {
            Dict = "missheistdockssetup1clipboard@base",
            Lib  = "base",
            Prop = {
                Prop = 'prop_notepad_01',
                PropBone = 18905,
                PropPlacement = {
                    0.1,
                    0.02,
                    0.05,
                    10.0,
                    0.0,
                    0.0
                }
            }
        }
    },
}

-- ──────────────────────────────────────────────────────────────────────────────
-- 📍 PVP ZONES CONFIGURATION 📍                                             
-- (Information) ► Non-turf PVP zones. For Turf Zones see: config/TurfConfig.lua
-- ──────────────────────────────────────────────────────────────────────────────

-- THIS IS SECTION FOR PVP ZONES (NO TURFZONES)
-- IF YOU WANT TO CONFIGURE TURF ZONES - GO TO config/TurfConfig.lua

Config.ZonesMisc = {                 
    PerOnePlayerInside = 0.3,           -- Time (in seconds) for 1% capture progress for one org.
    -- If there are 2 organisations in zone the zone capturing percentage will stop.
    ZonesCooldown   = 15,             -- Cooldown time (in minutes) after a capture.
    ZoneCaptureEXP  = 150,            -- EXP given per zone capture.
    Disable         = false,          -- true = disable PVP zones completely.
    ZoneTimers = {
        -- Time window when zones can be captured
        enable = false,
        -- WARNING: Make sure you configured correct Config.TimeZone in MainConfig.lua
        -- INFORMATION: Use 24 hours time format
        startTime = "02:00",
        endTime   = "11:00",
    }
}

Config.Zones = {                      -- Add custom PVP zones here.
    -- To create new zones use /pzcreate poly
    -- To add new point to created poly use /pzadd 
    -- More info: https://github.com/mkafrin/PolyZone
    {
        label = "Sandy Scrapy Yard",
        index = "sandyscrapy",
        coords = vec3(2404.2021, 3104.1765, 48.1648),
        rewards = {
            enable = false,
            list = {
                {
                    rewardType = "item",       ---@param rewardType "item" | "money" | "blackmoney"
                    itemName   = "spray_can",
                    amount     = 10,
                    -- EXAMPLE OF METADATA USAGE (only for ox_inv):
                    -- metadata   = {
                    --     testmeta = 69
                    -- }
                },
            }
        },
        Zone = function()
            return PolyZone:Create({
                vector2(2329.3020019532, 3053.681640625),
                vector2(2330.888671875, 3081.3254394532),
                vector2(2361.8330078125, 3087.1943359375),
                vector2(2379.0219726562, 3105.4108886718),
                vector2(2404.3395996094, 3163.1628417968),
                vector2(2437.2124023438, 3160.5307617188),
                vector2(2434.8149414062, 3024.4143066406),
                vector2(2329.9057617188, 3024.9143066406)
            }, {
                name = "SandyScrapyYard",
            })
        end,
    },
}

-- ──────────────────────────────────────────────────────────────────────────────
-- ROPE MENU                                                                 
-- (Information) ► Enables rope item / keybind usage with optional target integration.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Rope = {                       
    Enable = true, -- false = disable rope completly
    Item = {
        Enable   = true,
        ItemName = "rope",             -- Rope item name.
        RemoveItem = false            -- Remove item when tie/untie
    },
    Keybind = {
        Enable = true,
        Bind   = "F6"                 -- Keybind used for rope menu when enabled.
    },
    Target = {
        RequireItemInInventory = true, 
        RequiredItemName = "rope",
        Enable = true                 -- true = add target options to every player.
        -- When it's enabled - will add options to every player on the server.
    },
}

-- ──────────────────────────────────────────────────────────────────────────────
-- GANG WARS                                                               
-- (Information) ► All configuration for Gang Wars.
-- ──────────────────────────────────────────────────────────────────────────────
Config.GangWars = {
    enable = true, -- Enable/Disable Gangwars
    requiredLevel = 1, -- Required minimum level to use gangwars!
}

-- ──────────────────────────────────────────────────────────────────────────────
-- AIRDROPS                                                                  
-- (Information) ► Global configuration for Airdrop events, guards and rewards.
-- ──────────────────────────────────────────────────────────────────────────────
Config.AirDrop = {                    
    Guards = {
        enable = true,
        amount = 8, 
        weaponsList = {
            `weapon_SNSPISTOL`,
            `WEAPON_SNSPISTOL_MK2`,
            `WEAPON_VINTAGEPISTOL`,
            `WEAPON_PISTOL`
        },
        guardModels = { 
            "s_m_y_blackops_01", 
            "s_m_y_blackops_02", 
            "s_m_m_armoured_01", 
            "s_m_m_marine_01" 
        }
    },
    RequiredItems = {
        --["crowbar"] = {
        --    itemName = "crowbar",
        --    quanity = 1
        --},
    },
    SpawnTimeWindow = {
        -- Time window when Air Drops are auto started.
        enable = false,
        -- WARNING: Make sure you configured correct Config.TimeZone in MainConfig.lua
        -- INFORMATION: Use 24 hours time format
        startTime = "17:00",
        endTime   = "23:00",
    },
    StartCommand = {
        Enable = true,               -- Allow your admins to start AirDrop manually.
        CommandName = "startAirDrop" -- Command name to start Airdrop.
    },
    TimeToLand = 2,                  -- Time in minutes before airdrop starts going down.
    Enable = true,                   -- Master toggle for Airdrop system.
    Timer = 2,                       -- Interval in hours between automatic airdrops.
    DespawnTime = 25,                -- Minutes after which unopened airdrop will despawn.
    Locations = {
        vec4(470.7527, 2942.3884, 40.7600, 95.2011),
        vec4(1350.2504, 4354.7686, 42.7147, 315.9569),
        vec4(2034.9507, 4764.8198, 40.0590, 290.3700),
        vec4(3700.1226, 4533.2456, 22.2974, 193.3774),
        vec4(1518.1498, 6341.2002, 23.0057, 171.9430),
        vec4(-70.5034, 1910.8385, 195.1936, 196.7471),
    },
    Exp = 100,                       -- EXP given for opening Airdrop.
    Blip = {
        EnableRadiusBlip = true, 
        Blip      = 550,
        BlipColor = 3,
    },
    RewardsAmount = 3,               -- Number of random rewards per airdrop.
    Rewards = {

        {
            rewardType = "item",       ---@param rewardType "item" | "money" | "blackmoney"
            itemName   = "spray_can",
            amount     = 10,
            chance     = 50,
        },
        {
            rewardType = "item",       ---@param rewardType "item" | "money" | "blackmoney"
            itemName   = "spray_remover",
            amount     = 10,
            chance     = 50,
        },
        {
            rewardType = "blackmoney", ---@param rewardType "item" | "money" | "blackmoney"
            itemName   = "black_money",
            amount     = 100000,
            chance     = 50,
        },
        --[[
        EXAMPLE ITEM WITH METADATA:
        {
            rewardType = "item",       --@param rewardType "item" | "money" | "blackmoney"
            itemName   = "testitem",
            amount     = 15,
            chance     = 50,
            metadata   = {
                testmeta = 69
            }
        },--]]
        -- [Note]: Metadata currently works only for ox_inventory. 
        -- If you want metadata compatible with your inventory script:
        -- Files that need to be edited: `integrations/server/inventory/`
        -- and one of `framework/server` functions Fr.AddItem
    },
    ProgressTime = 10000,            -- Time in milliseconds for opening progress bar.
    SkillCheck = function()          -- Custom skill check function (uses ox_lib by default).
        return lib.skillCheck({'easy', 'easy', 'medium', 'easy', 'medium'}, {'w', 'a', 's', 'd'})
    end,
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Garage Disable Marker Helper                                               
-- (Information) ► Some external garage scripts manage their own markers/blips.
-- (Information) ► This helper allows OP-Crime to disable its own markers when needed.
-- ──────────────────────────────────────────────────────────────────────────────
-- Do not touch function below if you don't know what you're changing!
Config.GarageDisableMarker = function() 
    if Config.GarageScript == 'jg-advancedgarages' then 
        return true
    else 
        return false
    end
end
```

</details>

<details>

<summary>TurfConfig.lua</summary>

```lua
-- ──────────────────────────────────────────────────────────────────────────────
-- Turf Zones System Configuration                                             
--        racketeering, rivalry conflicts and drug-selling influence.
-- ──────────────────────────────────────────────────────────────────────────────

-- ──────────────────────────────────────────────────────────────────────────────
-- Turf Zones Base                                                             
-- (Information) ► Enables or disables the entire turf zone mechanic.
-- ──────────────────────────────────────────────────────────────────────────────
Config.DisableTurfZones = false              -- Disable all Turf Zone features
Config.DisableEnterNotifications = true      -- Disable enter/leave zone notifications

Config.TurfActivitiesTimers = {
    -- Time window when activities in Rivalries are allowed
    -- This is regarding when graffiti, drugs sales etc counts to reputation.
    enable = false,
    -- WARNING: Make sure you configured correct Config.TimeZone in MainConfig.lua
    -- INFORMATION: Use 24 hours time format
    startTime = "23:00",
    endTime   = "11:00",
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Rivalry System                                                              
-- (Information) ► A timed gang–vs–gang conflict inside a turf zone.
-- (Information) ► EXP and costs scale with your server economy.
-- ──────────────────────────────────────────────────────────────────────────────

Config.Rivalry = {
    Disable = false,             -- Disable/Enable rivalry system
    RivalryStartPrice = 5000,    -- Cost (dirty money). Set to 0 for free rivalry.
    RivalryDuration = 1,         -- Duration in HOURS
    RivalryWinEXP = 250,         -- EXP reward for winning rivalry
    RivarlyBonusMultiplier = 0.5 -- 0.5 - 50% will be added to rivalry after sale of drug 
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Graffiti System                                                             
-- (Information) ► Allows gangs to paint and remove graffiti inside turf zones.
-- (Information) ► Graffiti contributes to zone loyalty and gang EXP.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Graffiti = {
    Disable = false,             -- Disable/Enable graffiti system
    RenderDistance = 30.0,       -- Distance at which graffiti becomes visible
    CooldownTime = 1,            -- Minutes between spraying graffiti
    CooldownTimeRemover = 1,     -- Minutes between removing graffiti
    SprayingTime = 10,           -- Time in seconds for Spraying Progressbar
    RemovingTime = 10,           -- Time in seconds for removing spray Progressbar

    Dispatch = {
        enable = true,
        title = "Graffiti in Progress",
        message = "Individuals are marking territory with graffiti. Possible gang-related activity.",
        type = "Graffiti", -- Reffer to dispatch type of your Dispatch script.
        blip = {
            color = 1, 
            sprite = 1, 
        }
    },

    Items = {                    -- Required items
        graffitiSpray = "spray_can",
        graffitiRemover = "spray_remover"
    },

    loyality = {                 -- Loyalty mechanics
        LoyalityPerGraffiti = 75,     -- Loyalty gained when gang paints graffiti
        loyalityIncreaseOnRemove = 15, -- Loyalty for removing enemy graffiti
        loyalityDecreaseOnRemove = 15, -- Loyalty lost when enemy removes your graffiti
        loyalityDecreaseOnPaint = 5,   -- Loyalty lost when enemy paints in your zone
        -- Zone ownership changes when a gang reaches ≥51% loyalty.
    },

    exp = {
        SprayingEXPgain = 50,         -- EXP for successfully spraying
        RemovingGraffitiEXPgain = 50  -- EXP for removing enemy graffiti
        -- Note: Removing your own graffiti gives no EXP.
    },

    -- Turf zone graffiti blips (only visible to gang members)
    Blip = {
        Enable = true,
        Radius = 80.0,                -- Min 25. Larger radius = bigger zone highlight.
    },

    Settings = {
        MaxSize = 9.75,               -- Optimal max graffiti size
        MinSize = 1.75,               -- Optimal min graffiti size
    },

    CleanGraffitiInterval = 1,       -- Every X hours all graffiti is auto-cleaned
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Racketeering System                                                         
-- (Information) ► “Protection” spots generating EXP and rewards for controlling gangs.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Racketeering = {
    DisableBlips = false,   -- True = disables blips on map for racketeering
    Cooldown = 30,          -- Minutes between collect attempts
    Exp = 50,               -- EXP for claiming a racketeering point
    Blip = {
        BlipId = 358,
        BlipColor = 1,
    },
    Dispatch = {
        enable = true,
        title = "Extortion in Progress",
        message = "An individual is collecting protection money from a local store. Immediate attention may be required.",
        type = "Robbery", -- Reffer to dispatch type of your Dispatch script.
        blip = {
            color = 1, 
            sprite = 1, 
        }
    }
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Kills Inside Zone                                                        
-- (Information) ► Configure Influance from Killing other Gangs Members inside Turf
--                 and kills on NPC'S
-- ──────────────────────────────────────────────────────────────────────────────
Config.Kills = {
    enable = true,
    Gangs = {
        loyality = {                       -- Loyalty mechanics
            LoyalityPerKill = 15,          -- Loyalty gained when gang kills other gang member
            loyalityDecreasePerKill = 5,   -- Loyalty lost when enemy kills your gang member
            -- Zone ownership changes when a gang reaches ≥51% loyalty.
        },
        exp = {
            killEXPgain = 50, -- EXP for killing enemy gang
        },
    },
    NPCS = {
        LoyalityDecreasePerKill = 15, -- Loyalty decrease when killing NPC'S inside turf
    }
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Drug Selling Integration                                                    
-- (Information) ► Influence for selling drugs inside turf zones.
-- (Information) ► Supports custom drug scripts via integrations.
-- ──────────────────────────────────────────────────────────────────────────────

local drugSellingAvailable = {
    ['drugs_creator'] = "jaksam_drugs",
    ['envi-trap-phone'] = "envi-trap-phone",
}

Config.DrugSelling = {
    expOnDrugSell = 15,         -- EXP per drug transaction in zone

    loyality = {
        -- Ownership uses the ≥51% loyalty rule
        LoyalityPerTransaction = 50,  -- Loyalty gained when gang sells drugs in zone
        loyalityDecreaseOnOtherOrgs = 15 -- Loyalty lost when OTHER gang sells in your zone
    },

    -- Integration Drug Script (follow docs to integrate)
    -- (Information) ► This is regarding fetching data of sold drugs inside turf zones to add loyality etc.

    DrugScript = scriptCheck(drugSellingAvailable) or 'none',

    -- Full integration documentation:
    -- https://docs.otherplanet.dev/scripts/op-gangs/integrations
    --
    -- Fully supported (no config change needed):
    -- - op-drugselling (FREE)
    -- - nc-drugselling
    -- - visualz_selldrugs
    -- - tk_drugs
    -- - lunar_drugscreator
    -- - fs_trapphone
    -- - lation_selling
}
```

</details>

<details>

<summary>ClothingConfig.lua</summary>

```lua
-- ──────────────────────────────────────────────────────────────────────────────
-- Clothing System Detection                                                   
-- (Information) ► Detects your character appearance system automatically.
-- (Information) ► This is used for outfit switching during the laundry mission.
-- (Information) ► Add new integrations by expanding the list below and implementing
--        the logic inside your clothing adapter handlers.
-- ──────────────────────────────────────────────────────────────────────────────
local compatibleClothingList = { 
    ['skinchanger']           = "skinchanger",
    ['illenium-appearance']   = "illenium-appearance",
    ['fivem-appearance']      = "fivem-appearance",
    ['17mov_CharacterSystem'] = "17mov_CharacterSystem",
    ['rcore_clothing']        = "rcore_clothing",
    ['crm-appearance']        = "crm-appearance",
    ['tgiann-clothing']       = "tgiann_clothing",
    ['0r-clothingv2']         = "0r-clothingv2",
    ['qs-appearance']         = "qs-appearance"
}

Config.Clothing = { 
    enable = true, -- Disable/Enable gang locker.
    clothingScript = scriptCheck(compatibleClothingList) or 'none'
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Laundry Mission: Outfit Configuration                                       
-- (Information) ► Defines the outfit the player will wear during the Laundry Mission.
-- (Information) ► Each clothing script uses its own format (components, props, ids, etc.)
-- (Information) ► Use /getMyOutfit in-game to print your current outfit to the F8 console.
-- (Information) ► Do not mix formats between systems. Only edit the block of the system
--        you are using. Wrong formatting will break outfit application.
-- ──────────────────────────────────────────────────────────────────────────────
Config.LaundryClothing = {
    enable = false, -- Enable/disable outfit swapping during laundry missions.

    -- (Information) ► Outfit lists per clothing script.
    -- (Information) ► The script will automatically choose the correct block based on the
    -- detected clothing system (Config.Clothing.clothingScript).
    playerOutFit = {
        ['qs-appearance'] = {
            { component_id = 0, drawable = 0, texture = 0 },
            { component_id = 1, drawable = 10, texture = 0 },
            { component_id = 2, drawable = 0, texture = 0 },
            { component_id = 3, drawable = 0, texture = 0 },
            { component_id = 4, drawable = 0, texture = 0 },
            { component_id = 5, drawable = 0, texture = 0 },
            { component_id = 6, drawable = 0, texture = 0 },
            { component_id = 7, drawable = 0, texture = 0 },
            { component_id = 8, drawable = 0, texture = 0 },
            { component_id = 9, drawable = 0, texture = 0 },
            { component_id = 10, drawable = 0, texture = 0 },
            { component_id = 11, drawable = 10, texture = 0 }
        },
        ['crm-appearance'] = { {
            crm_texture = 0,
            crm_style = 0
            }, {
            crm_texture = 0,
            crm_id = 0,
            crm_style = 0
            }, {
            crm_texture = 0,
            crm_id = 1,
            crm_style = 0
            }, {
            crm_texture = 0,
            crm_id = 3,
            crm_style = 4
            }, {
            crm_texture = 5,
            crm_id = 4,
            crm_style = 0
            }, {
            crm_texture = 0,
            crm_id = 5,
            crm_style = 0
            }, {
            crm_texture = 0,
            crm_id = 6,
            crm_style = 3
            }, {
            crm_texture = 0,
            crm_id = 7,
            crm_style = 0
            }, {
            crm_texture = 0,
            crm_id = 8,
            crm_style = 15
            }, {
            crm_texture = 0,
            crm_id = 9,
            crm_style = 0
            }, {
            crm_texture = 0,
            crm_id = 10,
            crm_style = 0
            }, {
            crm_texture = 0,
            crm_id = 11,
            crm_style = 12
            } },
        ['rcore_clothing'] = {
            components = {
                ["3"] = "nondlcgta5--3--0--0",
                ["8"] = "8_15_0",
                ["11"] = "nondlcgta5--11--9--14"
            },
            props = {}
        },
        ['skinchanger'] = {
            chain_2 = 0,
            mask_1 = 0,
            pants_2 = 1,
            torso_1 = 14,
            bproof_1 = 0,
            shoes_1 = 57,
            bproof_2 = 0,
            glasses_2 = 0,
            glasses_1 = 0,
            helmet_2 = 1,
            tshirt_1 = 15,
            bags_1 = 0,
            decals_1 = 0,
            mask_2 = 0,
            arms_2 = 0,
            tshirt_2 = 0,
            torso_2 = 7,
            bags_2 = 0,
            shoes_2 = 10,
            chain_1 = 0,
            pants_1 = 6,
            arms = 0,
            helmet_1 = 45,
            decals_2 = 0
        },
        ['illenium-appearance'] = {
            { component_id = 0, drawable = 0, texture = 0 },
            { component_id = 1, drawable = 10, texture = 0 },
            { component_id = 2, drawable = 0, texture = 0 },
            { component_id = 3, drawable = 0, texture = 0 },
            { component_id = 4, drawable = 0, texture = 0 },
            { component_id = 5, drawable = 0, texture = 0 },
            { component_id = 6, drawable = 0, texture = 0 },
            { component_id = 7, drawable = 0, texture = 0 },
            { component_id = 8, drawable = 0, texture = 0 },
            { component_id = 9, drawable = 0, texture = 0 },
            { component_id = 10, drawable = 0, texture = 0 },
            { component_id = 11, drawable = 10, texture = 0 }
        },
        ['fivem-appearance'] = {
            { component_id = 0, drawable = 0, texture = 0 },
            { component_id = 1, drawable = 10, texture = 0 },
            { component_id = 2, drawable = 0, texture = 0 },
            { component_id = 3, drawable = 0, texture = 0 },
            { component_id = 4, drawable = 0, texture = 0 },
            { component_id = 5, drawable = 0, texture = 0 },
            { component_id = 6, drawable = 0, texture = 0 },
            { component_id = 7, drawable = 0, texture = 0 },
            { component_id = 8, drawable = 0, texture = 0 },
            { component_id = 9, drawable = 0, texture = 0 },
            { component_id = 10, drawable = 0, texture = 0 },
            { component_id = 11, drawable = 10, texture = 0 }
        },
        ['17mov_CharacterSystem'] = { {
            component_id = 1,
            texture = 0,
            drawable = 0
            }, {
            component_id = 3,
            texture = 0,
            drawable = 0
            }, {
            component_id = 4,
            texture = 2,
            drawable = 15
            }, {
            component_id = 5,
            texture = 0,
            drawable = 0
            }, {
            component_id = 6,
            texture = 0,
            drawable = 114
            }, {
            component_id = 7,
            texture = 0,
            drawable = 0
            }, {
            component_id = 8,
            texture = 0,
            drawable = 15
            }, {
            component_id = 9,
            texture = 0,
            drawable = 0
            }, {
            component_id = 10,
            texture = 0,
            drawable = 0
            }, {
            component_id = 11,
            texture = 0,
            drawable = 36
        }},
    }
}
```

</details>

<details>

<summary>FurnituresConfig.lua</summary>

```lua
-- ──────────────────────────────────────────────────────────────────────────────
-- 🪑 FURNITURE SYSTEM CONFIGURATION
-- ──────────────────────────────────────────────────────────────────────────────
-- Description:
-- This configuration controls the furniture system used inside gang hideouts.
-- Players can place, manage, and sell furniture objects such as decor,
-- storage units, and functional objects (boss menu, storage, etc.).
-- ──────────────────────────────────────────────────────────────────────────────

-- 🔧 GENERAL SETTINGS
Config.EnableFurnitures = true -- Enable / Disable furniture system

Config.FurnituresCommand = "furnitures" 
-- Command used to open the furniture menu inside gang hideout

Config.FurnitureSellPercentage = 90 
-- Percentage of original price returned when selling furniture
-- Example: 500$ item → player gets 450$ (10% loss)

-- 🏠 SHELLS (INTERIORS)
-- Defines available interiors where furniture can be placed

-- Shells in this config (FREE): https://k4mb1maps.com/product/5015840

Config.Shells = {
    ['k4_house3_shell'] = {
        name = "K4 Small House",
        spawn = "k4_house3_shell", -- Shell model
        coords = vec3(-1035.2122, -851.6562, -12.7472), -- Interior coordinates
        entranceDoor = vec4(-1032.7672, -855.3149, -11.5169, 81.7287) -- Entrance position
    },
    ['k4_warehouse1_shell'] = {
        name = "K4 Warehouse",
        spawn = "k4_warehouse1_shell",
        coords = vec3(-1020.3682, -815.6011, -11.7018),
        entranceDoor = vec4(-1028.8514, -815.3627, -10.3859, 259.8542)
    }
}


-- 📂 FURNITURE CATEGORIES
-- Used to organize furniture in the UI
Config.FurnituresCategories = {
    {name = "Living Room"},
    {name = "Bedroom"},
    {name = "Kitchen"},
    {name = "Bathroom"},
    {name = "Decor"},

    {name = "Boss Office - Desks"},
    {name = "Boss Office - Electronics"},
    {name = "Boss Office - Seating"},

    {name = "Storage"},
    {name = "Warehouse"}
}


-- 🪑 FURNITURE LIST
-- Each furniture item includes:
-- model     -> GTA prop model
-- category  -> UI category
-- price     -> purchase price
-- label     -> display name
-- usable    -> (optional) interaction type
-- helpText  -> (optional) description shown to player

Config.Furnitures = {

    -- LIVING ROOM (8)
    ["apa_mp_h_stn_sofacorn_01"] = { model = "apa_mp_h_stn_sofacorn_01", category = "Living Room", price = 2500, label = "Corner Sofa" },
    ["apa_mp_h_stn_sofa2seat_02"] = { model = "apa_mp_h_stn_sofa2seat_02", category = "Living Room", price = 1800, label = "Two Seat Sofa" },
    ["apa_mp_h_yacht_coffee_table_01"] = { model = "apa_mp_h_yacht_coffee_table_01", category = "Living Room", price = 900, label = "Coffee Table" },
    ["apa_mp_h_yacht_side_table_01"] = { model = "apa_mp_h_yacht_side_table_01", category = "Living Room", price = 600, label = "Side Table" },
    ["apa_mp_h_str_avunitl_01_b"] = { model = "apa_mp_h_str_avunitl_01_b", category = "Living Room", price = 1200, label = "TV Cabinet" },
    ["apa_mp_h_str_avunits_04"] = { model = "apa_mp_h_str_avunits_04", category = "Living Room", price = 1500, label = "Modern TV Unit" },
    ["prop_tv_flat_01"] = { model = "prop_tv_flat_01", category = "Living Room", price = 1300, label = "Flat TV" },
    ["apa_mp_h_stn_sofacorn_05"] = { model = "apa_mp_h_stn_sofacorn_05", category = "Living Room", price = 2600, label = "Modern Corner Sofa" },
    ["apa_mp_h_stn_sofa_daybed_01"] = { model = "apa_mp_h_stn_sofa_daybed_01", category = "Living Room", price = 2200, label = "Daybed Sofa" },
    ["prop_t_coffe_table"] = { model = "prop_t_coffe_table", category = "Living Room", price = 700, label = "Wood Coffee Table" },
    ["prop_tv_flat_02"] = { model = "prop_tv_flat_02", category = "Living Room", price = 1400, label = "Large Flat TV" },
    ["prop_cs_remote_01"] = { model = "prop_cs_remote_01", category = "Living Room", price = 100, label = "TV Remote" },

    -- BEDROOM (8)
    ["apa_mp_h_bed_double_08"] = { model = "apa_mp_h_bed_double_08", category = "Bedroom", price = 3000, label = "Double Bed" },
    ["apa_mp_h_bed_with_table_02"] = { model = "apa_mp_h_bed_with_table_02", category = "Bedroom", price = 3200, label = "Luxury Bed" },
    ["apa_mp_h_din_chair_04"] = { model = "apa_mp_h_din_chair_04", category = "Bedroom", price = 400, label = "Bedroom Chair" },
    ["apa_mp_h_bed_chestdrawer_02"] = { model = "apa_mp_h_bed_chestdrawer_02", category = "Bedroom", price = 1400, label = "Chest Drawer", usable = {type = "locker"}, helpText = "Locker for clothing" },
    ["apa_mp_h_str_shelffloorm_02"] = { model = "apa_mp_h_str_shelffloorm_02", category = "Bedroom", price = 1100, label = "Shelf Unit" },
    ["apa_mp_h_bed_double_09"] = { model = "apa_mp_h_bed_double_09", category = "Bedroom", price = 3100, label = "Modern Bed" },
    ["prop_clothes_rail_01"] = { model = "prop_clothes_rail_01", category = "Bedroom", price = 800, label = "Clothes Rail" },

    -- KITCHEN (8)
    ["apa_mp_h_din_table_06"] = { model = "apa_mp_h_din_table_06", category = "Kitchen", price = 2000, label = "Dining Table" },
    ["apa_mp_h_din_chair_08"] = { model = "apa_mp_h_din_chair_08", category = "Kitchen", price = 450, label = "Dining Chair" },
    ["prop_kitch_juicer"] = { model = "prop_kitch_juicer", category = "Kitchen", price = 300, label = "Juicer" },
    ["prop_micro_01"] = { model = "prop_micro_01", category = "Kitchen", price = 500, label = "Microwave" },
    ["prop_fridge_01"] = { model = "prop_fridge_01", category = "Kitchen", price = 1500, label = "Fridge" },
    ["prop_coffee_mac_02"] = { model = "prop_coffee_mac_02", category = "Kitchen", price = 450, label = "Coffee Machine" },
    ["prop_washer_01"] = { model = "prop_washer_01", category = "Kitchen", price = 1400, label = "Washer" },
    ["prop_cooker_03"] = { model = "prop_cooker_03", category = "Kitchen", price = 1100, label = "Cooker" },

    -- BATHROOM (8)
    ["prop_toilet_01"] = { model = "prop_toilet_01", category = "Bathroom", price = 400, label = "Toilet" },
    ["prop_sink_02"] = { model = "prop_sink_02", category = "Bathroom", price = 350, label = "Sink" },
    ["prop_shower_rack_01"] = { model = "prop_shower_rack_01", category = "Bathroom", price = 300, label = "Shower Rack" },
    ["prop_towel_rail_01"] = { model = "prop_towel_rail_01", category = "Bathroom", price = 200, label = "Towel Rail" },
    ["prop_mirror_01"] = { model = "prop_mirror_01", category = "Bathroom", price = 250, label = "Mirror" },
    ["prop_bin_05a"] = { model = "prop_bin_05a", category = "Bathroom", price = 150, label = "Trash Bin" },
    ["prop_toilet_brush_01"] = { model = "prop_toilet_brush_01", category = "Bathroom", price = 100, label = "Toilet Brush" },
    ["prop_table_05"] = { model = "prop_table_05", category = "Boss Office - Desks", price = 1000, label = "Wide Table" },
    ["prop_fbi3_coffee_table"] = { model = "prop_fbi3_coffee_table", category = "Boss Office - Desks", price = 1200, label = "Office Coffee Table" },

    -- DECOR (8)
    ["apa_mp_h_acc_vase_01"] = { model = "apa_mp_h_acc_vase_01", category = "Decor", price = 300, label = "Vase" },
    ["apa_mp_h_acc_plant_palm_01"] = { model = "apa_mp_h_acc_plant_palm_01", category = "Decor", price = 700, label = "Palm Plant" },
    ["apa_mp_h_acc_artwalll_02"] = { model = "apa_mp_h_acc_artwalll_02", category = "Decor", price = 800, label = "Wall Art" },
    ["apa_mp_h_acc_rugwooll_03"] = { model = "apa_mp_h_acc_rugwooll_03", category = "Decor", price = 450, label = "Wool Rug" },
    ["prop_glass_stack_01"] = { model = "prop_glass_stack_01", category = "Decor", price = 150, label = "Glass Stack" },
    ["prop_pot_plant_01a"] = { model = "prop_pot_plant_01a", category = "Decor", price = 350, label = "Small Plant" },
    

    -- BOSS OFFICE - DESKS (8)
    ["apa_mp_h_office_desk_02"] = { model = "apa_mp_h_office_desk_02", category = "Boss Office - Desks", price = 3500, label = "Modern Desk" },
    ["apa_mp_h_yacht_table_01"] = { model = "apa_mp_h_yacht_table_01", category = "Boss Office - Desks", price = 2800, label = "Meeting Table" },
    ["prop_table_02"] = { model = "prop_table_02", category = "Boss Office - Desks", price = 900, label = "Small Table" },
    ["prop_office_desk_01"] = { model = "prop_office_desk_01", category = "Boss Office - Desks", price = 2000, label = "Office Desk" },
    ["prop_table_03"] = { model = "prop_table_03", category = "Boss Office - Desks", price = 850, label = "Wood Table" },
    ["prop_table_04"] = { model = "prop_table_04", category = "Boss Office - Desks", price = 950, label = "Metal Desk" },
    ["prop_office_desk_02"] = { model = "prop_office_desk_02", category = "Boss Office - Desks", price = 2200, label = "Office Desk Large" },
    ["prop_table_05"] = { model = "prop_table_05", category = "Boss Office - Desks", price = 1000, label = "Wide Table" },
    ["prop_fbi3_coffee_table"] = { model = "prop_fbi3_coffee_table", category = "Boss Office - Desks", price = 1200, label = "Office Coffee Table" },

    -- BOSS OFFICE - ELECTRONICS (8)
    ["prop_laptop_01a"] = { model = "prop_laptop_01a", category = "Boss Office - Electronics", price = 1500, label = "Laptop", usable = {type = "bossmenu"} },
    ["prop_monitor_01a"] = { model = "prop_monitor_01a", category = "Boss Office - Electronics", price = 800, label = "Monitor", usable = {type = "bossmenu"} },
    ["prop_pc_01a"] = { model = "prop_pc_01a", category = "Boss Office - Electronics", price = 1200, label = "PC Tower" },
    ["prop_printer_01"] = { model = "prop_printer_01", category = "Boss Office - Electronics", price = 700, label = "Printer" },
    ["prop_office_phone_tnt"] = { model = "prop_office_phone_tnt", category = "Boss Office - Electronics", price = 500, label = "Office Phone" },
    ["hei_prop_hei_bank_mon"] = { model = "hei_prop_hei_bank_mon", category = "Boss Office - Electronics", price = 2500, label = "Multi Monitor Setup" },
    ["prop_cctv_cam_01a"] = { model = "prop_cctv_cam_01a", category = "Boss Office - Electronics", price = 800, label = "CCTV Camera" },

    -- BOSS OFFICE - SEATING (8)
    ["prop_off_chair_01"] = { model = "prop_off_chair_01", category = "Boss Office - Seating", price = 700, label = "Desk Chair" },
    ["v_corp_offchair"] = { model = "v_corp_offchair", category = "Boss Office - Seating", price = 1000, label = "Corporate Chair" },
    ["prop_chair_01a"] = { model = "prop_chair_01a", category = "Boss Office - Seating", price = 300, label = "Basic Chair" },
    ["prop_armchair_01"] = { model = "prop_armchair_01", category = "Boss Office - Seating", price = 850, label = "Armchair" },
    ["prop_chair_04a"] = { model = "prop_chair_04a", category = "Boss Office - Seating", price = 350, label = "Meeting Chair" },
    ["prop_bar_stool_01"] = { model = "prop_bar_stool_01", category = "Boss Office - Seating", price = 250, label = "Bar Stool" },
    ["prop_chair_02"] = { model = "prop_chair_02", category = "Boss Office - Seating", price = 350, label = "Simple Chair" },
    ["prop_chair_06"] = { model = "prop_chair_06", category = "Boss Office - Seating", price = 400, label = "Plastic Chair" },

    -- STORAGE (8) 
    ["prop_boxpile_06b"] = { model = "prop_boxpile_06b", category = "Storage", price = 500, label = "Box Pile", usable = {type = "storage", capacity = 500000, slots = 50}, helpText = "This object allows you to store items up to 50kg capacity and 50 slots." },
    ["prop_box_wood02a_pu"] = { model = "prop_box_wood02a_pu", category = "Storage", price = 300, label = "Wood Box", usable = {type = "storage", capacity = 350000, slots = 30}, helpText = "This object allows you to store items up to 35kg capacity and 30 slots." },
    ["prop_crate_11e"] = { model = "prop_crate_11e", category = "Storage", price = 350, label = "Large Crate", usable = {type = "storage", capacity = 200000, slots = 20}, helpText = "This object allows you to store items up to 20kg capacity and 20 slots." },
    ["prop_ld_int_safe_01"] = { model = "prop_ld_int_safe_01", category = "Storage", price = 2500, label = "Safe", usable = {type = "storage", capacity = 5000, slots = 10}, helpText = "This object allows you to store items up to 5kg capacity and 10 slots." },
    ["prop_mil_crate_01"] = { model = "prop_mil_crate_01", category = "Storage", price = 600, label = "Military Crate", usable = {type = "storage", capacity = 20000, slots = 15}, helpText = "This object allows you to store items up to 20kg capacity and 15 slots." },
    ["prop_cabinet_02b"] = { model = "prop_cabinet_02b", category = "Storage", price = 900, label = "Storage Cabinet", usable = {type = "storage", capacity = 350000, slots = 50}, helpText = "This object allows you to store items up to 35kg capacity and 50 slots." },
    ["prop_boxpile_02c"] = { model = "prop_boxpile_02c", category = "Storage", price = 400, label = "Small Box Pile" },
    ["prop_crate_03a"] = { model = "prop_crate_03a", category = "Storage", price = 300, label = "Wood Crate" },
    ["prop_toolchest_01"] = { model = "prop_toolchest_01", category = "Storage", price = 800, label = "Tool Chest" },

    -- WAREHOUSE (8)
    ["prop_ind_mech_01c"] = { model = "prop_ind_mech_01c", category = "Warehouse", price = 3000, label = "Industrial Machine" },
    ["prop_generator_01a"] = { model = "prop_generator_01a", category = "Warehouse", price = 2800, label = "Generator" },
    ["prop_barrel_02a"] = { model = "prop_barrel_02a", category = "Warehouse", price = 250, label = "Barrel" },
    ["prop_container_05a"] = { model = "prop_container_05a", category = "Warehouse", price = 4000, label = "Shipping Container" },
    ["prop_gas_tank_01a"] = { model = "prop_gas_tank_01a", category = "Warehouse", price = 2200, label = "Gas Tank" },
    ["prop_worklight_03b"] = { model = "prop_worklight_03b", category = "Warehouse", price = 450, label = "Work Light" },
    ["prop_tool_bench02"] = { model = "prop_tool_bench02", category = "Warehouse", price = 1200, label = "Workbench" },
    ["prop_pallet_02a"] = { model = "prop_pallet_02a", category = "Warehouse", price = 300, label = "Pallet" },
    ["prop_barrel_03a"] = { model = "prop_barrel_03a", category = "Warehouse", price = 300, label = "Rusty Barrel" },

}

-- ⚙️ USABLE FURNITURE FUNCTIONS
-- Defines interactions for specific furniture types

Config.UsableFunctions = {
    ['storage'] = {
        label = "Open Storage",
        icon = "fa-solid fa-box", 
        onSelect = function(furnitureId, capacity, slots, label)
            openFurnitureStorage("furniture_storage_" .. furnitureId, capacity, slots, label)
        end,
    },
    ['bossmenu'] = {
        label = "Open Bossmenu",
        icon = "fa-solid fa-skull-crossbones",

        onSelect = function()
            openBossMenu(false, playerOrganisationData.jobId)
        end,
    },
    ['locker'] = {
        label = "Open Locker",
        icon = "fa-solid fa-shirt",

        onSelect = function()
            openCloakRoom()
        end,
    }
}
```

</details>

<details>

<summary>MarketConfig.lua</summary>

```lua
-- ──────────────────────────────────────────────────────────────────────────────
-- 🧩 CRIME TABLET - MARKET CONFIGURATION
-- ──────────────────────────────────────────────────────────────────────────────
-- Description:
-- This config controls the Market tab inside the Crime Tablet.
-- Players can list and purchase illegal items such as weapons, drugs,
-- and special equipment through a marketplace system.
-- ──────────────────────────────────────────────────────────────────────────────

-- ⚙️ GENERAL MARKET SETTINGS
Config.MarketMisc = {
    enable = true, -- Enable / Disable the Market tab
    requiredGangLevel = 1, -- Minimum gang level required to access the Market
    useDirtyMoney = false, -- If true, transactions use dirty money instead of regular cash
    advertiseFee = 5000, -- Cost to create a new market listing
    advertiseTime = 96, -- Listing duration (in hours)
}

-- 📦 ALLOWED MARKET ITEMS
-- Only items listed here can be sold through the Market

-- 🎨 ITEM CLASSES (itemclass)
-- blue   -> common items
-- purple -> uncommon 
-- red    -> illegal 
-- gold   -> rare 

-- THOSE ITEMS ARE EXAMPLES!
-- To display images properly place them inside: `web/build/items` with the same name as item and in .png extension.

Config.MarketItems = {
    ['spy_binoculars'] = {
        label = "Tactical Binoculars",
        itemclass = "purple",
    },
    ['spy_gps'] = {
        label = "GPS Tracker",
        itemclass = "purple",
    },
    ['spy_cam'] = {
        label = "Hidden Camera",
        itemclass = "red",
    },
    ['spy_motionsensor'] = {
        label = "Motion Sensor",
        itemclass = "blue",
    },
    ['spy_cam_terminal'] = {
        label = "Camera Terminal",
        itemclass = "red",
    },
    ['spy_detector'] = {
        label = "Signal Detector",
        itemclass = "purple",
    },
    ['WEAPON_VINTAGEPISTOL'] = {
        label = "Vintage Pistol",
        itemclass = "red",
    },
    ['WEAPON_SNSPISTOL_MK2'] = {
        label = "SNS Pistol MK2",
        itemclass = "red",
    },
    ['cocaine'] = {
        label = "Cocaine",
        itemclass = "purple",
    },
    ['meth'] = {
        label = "Meth",
        itemclass = "purple",
    },
    ['weed'] = {
        label = "Weed",
        itemclass = "purple",
    },
    ['rope'] = {
        label = "Rope",
        itemclass = "red",
    },
    ['spray_can'] = {
        label = "Spray Can",
        itemclass = "blue",
    },
    ['spray_remover'] = {
        label = "Spray Remover",
        itemclass = "purple",
    },

    ['ammo-9'] = {
        label = "AMMO 9MM",
        itemclass = "purple",
    },
}
```

</details>

<details>

<summary>RaidsConfig.lua</summary>

```lua
-- ──────────────────────────────────────────────────────────────────────────────
-- Police Raids Configuration
-- (Information) ► Configure Jobs that are allowed to raid gangs
-- ──────────────────────────────────────────────────────────────────────────────

Config.Raids = {

    -- Enable or disable the entire raid system
    enable = true,
    commandName = "startraid",

    -- Jobs that are allowed to start raids
    allowedJobs = {
        ['police'] = {
            minimumGradeToStart = 2, -- Minimum job grade required to start a raid
        },
    },

    -- Cooldown settings
    raidCooldownPerJob  = 2,   -- Cooldown (in hours) per job after starting a raid
    raidCooldownPerGang = 24,  -- Cooldown (in hours) before the same gang can be raided again

    -- Time window when raids are allowed
    raidTime = {

        -- WARNING: Make sure you configured correct Config.TimeZone in MainConfig.lua
        -- INFORMATION: Use 24 hours time format

        startTime = "00:00", -- Raids can start from this hour
        endTime   = "11:00", -- Raids can start until this hour
    },

    -- Display blips for raiding officers
    -- Set to false for more serious RP (no automatic gang location)
    displayBlips = true,

    -- Elements that can be seized during a raid
    raidableElements = {
        stash       = true, -- Gang stash
        money       = true, -- Clean money
        dirty_money = true, -- Dirty money
    },

    -- Admin approval mode
    -- When enabled, an admin must approve each raid manually
    adminMode = {
        enable = false,              -- Enable admin approval requirement
        commandToApprove = "approveraid", -- Command used by admins to approve a raid
    },

    -- Minimum Gang members online
    minimumGangPlayersOnline = 0,
    -- Minimum Job members online
    minimumJobPlayersOnline = 1,

    -- Raid duration
    raidDuration = 30, -- Duration of the raid in minutes

    -- Inform Online Gang Members about Raid.
    informGang = true
}
```

</details>

<details>

<summary>SpySystem.lua</summary>

```lua
-- ──────────────────────────────────────────────────────────────────────────────
-- 🕵️ SPY SYSTEM CONFIGURATION
-- ──────────────────────────────────────────────────────────────────────────────
-- Description:
-- This configuration controls all spy-related items and mechanics.
-- It includes GPS trackers, motion sensors, spy cameras, binoculars,
-- and detection tools used for surveillance and tracking players.
-- ──────────────────────────────────────────────────────────────────────────────

Config.SpySystem = {
    -- 🚗 CAR GPS TRACKER
    -- Allows players to place a GPS tracker on vehicles
    CarGps = {
        enable = true, -- Enable / Disable this feature
        itemName = "spy_gps", -- Item required to place GPS
        refreshRate = 0.5, -- How often GPS position updates (in seconds)
        gpsMode = "exact", 
        -- "exact"  → shows exact vehicle position
        -- "radius" → shows approximate area (circle)
        blip = {
            radius = 20.0, -- Used only in "radius" mode
            blipId = 724, -- Blip sprite ID
            blipColor = 1, -- Blip color
            blipSize = 0.7 -- Blip scale
        }
    },
    -- 📡 MOTION SENSOR
    -- Detects movement in a specific area
    MotionSensor = {
        enable = true, -- Enable / Disable
        itemName = "spy_motionsensor", -- Required item
        radius = 15.0, -- Detection radius
        prop = {
            enable = true, -- Spawn prop in world
            propName = "prop_spycam" -- Prop model
        },
        blip = {
            blipId = 875,
            blipColor = 3,
            blipSize = 0.7  
        }
    },
    -- 📷 SPY CAMERA
    -- Placeable cameras with remote viewing
    SpyCam = {
        enable = true, -- Enable / Disable
        itemName = "spy_cam", -- Item to place camera
        maxDistance = 250.0, 
        -- Maximum viewing distance (OneSync limitation)
        prop = {
            enable = true, 
            propName = "prop_spycam"
        },
        blip = {
            blipId = 873,
            blipColor = 2,
            blipSize = 0.7  
        },
        terminalItemName = "spy_cam_terminal"
        -- Item used to access camera list and live feed
    },
    -- 🔭 SPY BINOCULARS
    -- Allows long-distance observation of players
    SpyBinoculars = {
        enable = true, -- Enable / Disable
        itemName = "spy_binoculars", -- Required item
        maxDistance = 50.0, -- Maximum usable distance
        enableVoice = true, 
        -- If enabled, you can hear players you are aiming at
    },
    -- 📡 SPY DETECTOR
    -- Detects nearby spy devices (GPS, cameras, sensors)
    SpyDetector = {
        enable = true, -- Enable / Disable
        itemName = "spy_detector", -- Required item
    }
}
```

</details>

<details>

<summary>ServerConfig.lua</summary>

```lua
-- ──────────────────────────────────────────────────────────────────────────────
-- Server-Side Configuration                                                   
-- (Information) ► Core server behaviour, external APIs, and integration options.
-- (Information) ► These settings affect backend operations such as logging, Steam avatar
--        fetching, and optional compatibility modes.
-- ──────────────────────────────────────────────────────────────────────────────
ServerConfig = {}

-- ──────────────────────────────────────────────────────────────────────────────
-- QB Gangs Integration                                                        
-- (Information) ► Enables compatibility mode for qb-gangs data structures.
-- (Information) ► Requires modified versions of qb-core or qbx_core.
-- (Information) ► Refer to documentation before enabling:
--        https://docs.otherplanet.dev/scripts/op-gangs/integrations/gangs-qb-core-qbox
-- ──────────────────────────────────────────────────────────────────────────────
ServerConfig.EnableQBgangsIntegrations = false

-- ──────────────────────────────────────────────────────────────────────────────
-- Steam API Key                                                              
-- (Information) ► Used to fetch player Steam avatars for UI and logs.
-- (Information) ► Optional — leave empty to disable avatar fetching.
-- ──────────────────────────────────────────────────────────────────────────────
ServerConfig.SteamApiKey = "" -- e.g. "YOUR_STEAM_API_KEY"

-- ──────────────────────────────────────────────────────────────────────────────
-- Discord Webhooks                                                            
-- (Information) ► Webhook URLs for sending server logs and admin actions.
-- (Information) ► Leave blank to disable Discord logging.
-- ──────────────────────────────────────────────────────────────────────────────
ServerConfig.DiscordWebHook = ''       -- General logs (boss menu actions, missions, etc.)
ServerConfig.DiscordWebHookAdmin = ''  -- Admin panel logs (bans, wipes, resets)
```

</details>


# Customization

Our script is fully configurable, allowing you to customize it however you like. You can also modify many functions located in the so-called "Editable" zone!

## With the Editable files, you can:

* Add your own FRAMEWORK
* Modify functions, such as:
  * Add a new clothing system
  * Add a new inventory system
  * Customize the progress bar
  * Modify notifications
  * Change the targeting system
  * Customize fuel and vehicle key systems
  * Modify the revive function
  * Modify Dispatch Section
  * Modify Garage Integrations


# Create Items

If you want to use items for graffiti, rope etc. you need to create them based on the inventory script you're using.

{% hint style="warning" %}
If inventory which you use is not on this list - Create Items regarding to other items in your inventory script.
{% endhint %}

{% tabs %}
{% tab title="ox\_inventory" %}
File Location: ox\_inventory/data/items.lua

Image Should Be placed in: ox\_inventory/web/images

```lua
['crime_tablet'] = {
    label = 'Crime Tablet',
    weight = 1,
    type = 'item',
    image = 'tablet.png', -- Optional, if you want an image for it
    useable = true,
    description = 'A tablet for various crime-related tasks.',
    client = {
	event = 'op-crime:openCrimeTablet'
    }
},
['rope'] = {
    label = "Rope",
    weight = 250,
    client = {
      event = "op-crime:openHandcuffsMenu",
    }
},
['spray_remover'] = {
    label = "Spray Remover",
    weight = 250,
    client = {
      event = "op-crime:useRemover",
    }
},
['spray_can'] = {
    label = "Gang Spray Can",
    weight = 1500,
    client = {
      event = "op-crime:useSpray",
    }
},
['spy_binoculars'] = {
	label = "Tactical Binoculars",
	weight = 1500,
	client = {
		event = "op-crime:spybinoculars:client:toggle",
	}
},
['spy_gps']= {
	label = "GPS Tracker",
	weight = 1500,
	client = {
		event = "op-crime:spy-gps:placeGps",
	}
},
['spy_cam']= {
	label = "Hidden Camera",
	weight = 1500,
	client = {
		event = "op-crime:addSpyItem:SpyCam",
	}
},
['spy_motionsensor']= {
	label = "Motion Sensor",
	weight = 1500,
	client = {
		event = "op-crime:addSpyItem:MotionSensor",
	}
},
['spy_cam_terminal']= {
	label = "Camera Terminal",
	weight = 1500,
	client = {
		event = "op-crime:openSpyCamTerminal",
	}
},
['spy_detector']= {
	label = "Signal Detector",
	weight = 1500,
	client = {
		event = "op-crime:detectorItem",
	}
},
['cocaine'] = {
    label = "Cocaine 1g",
    weight = 1,
},
['meth'] = {
    label = "Crystal Meth 1g",
    weight = 1,
},
['weed'] = {
    label = "Weed 1g",
    weight = 1,
},
```

{% endtab %}

{% tab title="qb-inventory" %}
File Location: qb-core/shared/items.lua

Image Should Be placed in: qb-inventory/html/images

```lua
crime_tablet = { 
    name = 'crime_tablet', 
    label = 'Crime Tablet', 
    weight = 1, 
    type = 'item', 
    image = 'tablet.png', -- Optional, if you want an image for it
    useable = true, 
    shouldClose = true, 
    description = 'A tablet for various crime-related tasks.' ,
    client = {
        event = "op-crime:openCrimeTablet"
    }
},
rope = {
    name = 'rope',
    label = 'Rope',
    weight = 250,
    type = 'item',
    image = 'rope.png',
    useable = true,
    shouldClose = true,
    description = 'A rope that can be used for restraining or other tasks.',
    client = {
        event = "op-crime:openHandcuffsMenu"
    }
},
spray_remover = {
    name = 'spray_remover',
    label = 'Spray Remover',
    weight = 250,
    type = 'item',
    image = 'spray_remover.png',
    useable = true,
    shouldClose = true,
    description = 'Removes gang graffiti from walls.',
    client = {
        event = "op-crime:useRemover"
    }
},
spray_can = {
    name = 'spray_can',
    label = 'Gang Spray Can',
    weight = 1500,
    type = 'item',
    image = 'spray_can.png',
    useable = true,
    shouldClose = true,
    description = 'Used to spray graffiti in gang territories.',
    client = {
        event = "op-crime:useSpray"
    }
},
spy_binoculars = {
    name = 'spy_binoculars',
    label = 'Tactical Binoculars',
    weight = 1500,
    type = 'item',
    image = 'spy_binoculars.png',
    useable = true,
    shouldClose = true,
    description = 'Tactical binoculars for long-range surveillance.',
    client = {
        event = "op-crime:spybinoculars:client:toggle"
    }
},
spy_gps = {
    name = 'spy_gps',
    label = 'GPS Tracker',
    weight = 1500,
    type = 'item',
    image = 'spy_gps.png',
    useable = true,
    shouldClose = true,
    description = 'A GPS tracker used to monitor targets.',
    client = {
        event = "op-crime:spy-gps:placeGps"
    }
},
spy_cam = {
    name = 'spy_cam',
    label = 'Hidden Camera',
    weight = 1500,
    type = 'item',
    image = 'spy_cam.png',
    useable = true,
    shouldClose = true,
    description = 'A hidden camera for surveillance.',
    client = {
        event = "op-crime:addSpyItem:SpyCam"
    }
},
spy_motionsensor = {
    name = 'spy_motionsensor',
    label = 'Motion Sensor',
    weight = 1500,
    type = 'item',
    image = 'spy_motionsensor.png',
    useable = true,
    shouldClose = true,
    description = 'Detects movement in a specific area.',
    client = {
        event = "op-crime:addSpyItem:MotionSensor"
    }
},
spy_cam_terminal = {
    name = 'spy_cam_terminal',
    label = 'Camera Terminal',
    weight = 1500,
    type = 'item',
    image = 'spy_cam_terminal.png',
    useable = true,
    shouldClose = true,
    description = 'A terminal used to access camera feeds.',
    client = {
        event = "op-crime:openSpyCamTerminal"
    }
},
spy_detector = {
    name = 'spy_detector',
    label = 'Signal Detector',
    weight = 1500,
    type = 'item',
    image = 'spy_detector.png',
    useable = true,
    shouldClose = true,
    description = 'Detects electronic signals nearby.',
    client = {
        event = "op-crime:detectorItem"
    }
},
cocaine = {
    name = 'cocaine',
    label = 'Cocaine 1g',
    weight = 1,
    type = 'item',
    image = 'cocaine.png',
    useable = true,
    shouldClose = true,
    description = 'A gram of cocaine.'
},
meth = {
    name = 'meth',
    label = 'Crystal Meth 1g',
    weight = 1,
    type = 'item',
    image = 'meth.png',
    useable = true,
    shouldClose = true,
    description = 'A gram of crystal meth.'
},
weed = {
    name = 'weed',
    label = 'Weed 1g',
    weight = 1,
    type = 'item',
    image = 'weed.png',
    useable = true,
    shouldClose = true,
    description = 'A gram of weed.'
},

```

{% endtab %}

{% tab title="Basic ESX" %}

## This is MySql insert.

```sql
INSERT INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
("rope", "Rope", 250, 0, 1),
("spray_remover", "Spray Remover", 250, 0, 1),
("spray_can", "Gang Spray Can", 1500, 0, 1),
("cocaine", "Cocaine 1g", 1, 0, 1),
("meth", "Crystal Meth 1g", 1, 0, 1),
("weed", "Weed 1g", 1, 0, 1),
("crime_tablet", "Crime Tablet", 1, 0, 1),
("spy_binoculars", "Tactical Binoculars", 1500, 0, 1),
("spy_gps", "GPS Tracker", 1500, 0, 1),
("spy_cam", "Hidden Camera", 1500, 0, 1),
("spy_motionsensor", "Motion Sensor", 1500, 0, 1),
("spy_cam_terminal", "Camera Terminal", 1500, 0, 1),
("spy_detector", "Signal Detector", 1500, 0, 1);
```

{% endtab %}
{% endtabs %}


# Exports

Useful exports List:

{% content-ref url="/pages/B601ltmk7aeulohMlaTT" %}
[Server Side](/scripts/op-gangs/exports/server-side)
{% endcontent-ref %}


# Useful Tips

Useful tips for Developers to help understand Our script.

## Get Player Gang Name, Rank and jobId

Our script save all data about it in Player States

```lua
Player(src).state.gangName
Player(src).state.gangId
Player(src).state.gangRankName
```

Or

```lua
LocalPlayer.state.gangName
LocalPlayer.state.gangId
LocalPlayer.state.gangRankName
```

## Crime Job Changed Handler

If you want to fetch information when crime job is changed or player is Fired you can listen to this Client-Sided Event.

```lua
AddEventHandler("op-crime:jobChanged", function(jobId, jobLabel, rankName)
  -- Your Code
end)
```


# Client Side

Client Side List of Exports.

{% hint style="warning" %}
If you're looking for Other Data then listed Here and in Server-Side Exports - Feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

## isInTurfZone

return Turf Zone `index` when inside or `nil`

```lua
exports['op-crime']:isInTurfZone()
```

***

## getPlayerOrganisation

Table:

* id: `number`
* label: `string`
* entranceCoords: `vec4`

```lua
exports['op-crime']:getPlayerOrganisation()
```

***

## playerGaragePermission

return `boolean` value in callback.

```lua
exports['op-crime']:playerGaragePermission(function(hasPerms)
    print(hasPerms)
end)
```

***

## isInsideFurnituresMenu

return `boolean` value.

```lua
exports['op-crime']:isInsideFurnituresMenu()
```

## Furnitures menu toggle handler

If you want to fetch information when player enters/leave furnitures menu

```lua
RegisterNetEvent("op-crime:userEnteredFurnituresMenu", function()
  -- Your Code
end)

RegisterNetEvent("op-crime:userLeftFurnituresMenu", function()
  -- Your Code
end)
```


# Server Side

There’s no matching export for you? Contact us through our Discord server, and we will do our best to assist you!

{% hint style="warning" %}
If you're looking for Other Data then listed Here and in Client-Side Exports - Feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

## addMapBlip

Add blip to all gang members into Crime Tablet

```lua
exports['op-crime']:addMapBlip(blipUniqueName, data)
```

#### Parameters

* `blipUniqueName: string`
* `data: table`

Example usage:

```lua
local ok, err = exports['op-crime']:addMapBlip("my_blip", {
    label       = "Drug Dealer",
    description = "Sells everything from coke to fentanyl",
    coords      = vec3(123.4, 567.8, 30.0),
    icon        = "skull",          -- Google Icons
    color       = "#ff4b3a",        -- Blip Color
})
```

## removeMapBlip

Removes blip from Crime Tablet by unique id

```lua
exports['op-crime']:removeMapBlip(blipUniqueName)
```

#### Parameters

* `blipUniqueName: string`

Example usage:

```lua
exports['op-crime']:removeMapBlip("my_blip")
```

## CreateGang

Create new organisation/gang and return created organisation index.

```lua
exports['op-crime']:CreateGang(data, cb)
```

#### Parameters

* `data: table`
* `cb: function`

***

### Data Object

* `orgLabel: string`
* `rankName: string`
* `color: string`
* `graffitiUrl: string`
* `shellValue: string|number`
* `isInteriorMode: shell | ipl | mlo`
* `garageCoords: table`
  * `x: number`
  * `y: number`
  * `z: number`
  * `w: number`
* `entranceCoords: table`
  * `x: number`
  * `y: number`
  * `z: number`
  * `w: number`
* `bossmenuCoords: table`
  * `x: number`
  * `y: number`
  * `z: number`
* `stashCoords: table`
  * `x: number`
  * `y: number`
  * `z: number`
* `cloakRoomCoords: table`
  * `x: number`
  * `y: number`
  * `z: number`
* `newOwner: table`
  * `identificator: string`

***

### Callback Response

* `status: boolean`
* `jobIndex: number`
* `message: string?`

***

### Example Usage

```lua
exports['op-crime']:CreateGang({
    orgLabel = "Ballas",
    rankName = "Boss",
    color = "#7d3cff",
    graffitiUrl = "https://your-image-url.com/graffiti.png",
    shellValue = "shell_highend",
    isInteriorMode = "shell", -- shell / ipl / mlo
    garageCoords = {
        x = 124.25,
        y = -1942.11,
        z = 20.75,
        w = 180.0
    },

    entranceCoords = {
        x = 118.91,
        y = -1961.22,
        z = 20.75,
        w = 92.0
    },

    bossmenuCoords = {
        x = 112.40,
        y = -1955.10,
        z = 20.75
    },

    stashCoords = {
        x = 110.20,
        y = -1953.30,
        z = 20.75
    },

    cloakRoomCoords = {
        x = 114.70,
        y = -1951.60,
        z = 20.75
    },

    newOwner = {
        identificator = "license:123456789abcdef"
    }
}, function(response)
    if response.status then
        print("Organisation created with index:", response.jobIndex)
    else
        print("Failed to create organisation:", response.message or "unknown_error")
    end
end)
```

***

### Notes

* Export returns `jobIndex` immediately after organisation creation.
* Owner assignment and additional sync actions are processed in background.
* This export can be safely used by external resources.

### EditGang

***

Edit existing organisation/gang and return edited organisation index.

* data: `table`
* cb: `function`

Data Object:

* editOrgIndex: `number|string`
* orgLabel: `string`
* rankName: `string`
* color: `string`
* graffitiUrl: `string`
* shellValue: `string|number`
* isInteriorMode: `string` — shell / ipl / mlo
* garageCoords: `table`
* entranceCoords: `table`
* bossmenuCoords: `table`
* stashCoords: `table`
* cloakRoomCoords: `table`
* oldOwner: `table` optional
* newOwner: `table` optional

Callback Response:

* status: `boolean`
* jobIndex: `number`
* message: `string` optional

Example usage:

```lua
exports['op-crime']:EditGang({
    editOrgIndex = 1,
    orgLabel = "Ballas",
    rankName = "Boss",
    color = "#7d3cff",
    graffitiUrl = "https://your-image-url.com/graffiti.png",
    shellValue = "shell_highend",
    isInteriorMode = "shell",

    garageCoords = {
        x = 124.25,
        y = -1942.11,
        z = 20.75,
        w = 180.0
    },
    entranceCoords = {
        x = 118.91,
        y = -1961.22,
        z = 20.75,
        w = 92.0
    },
    bossmenuCoords = {
        x = 112.40,
        y = -1955.10,
        z = 20.75
    },
    stashCoords = {
        x = 110.20,
        y = -1953.30,
        z = 20.75
    },
    cloakRoomCoords = {
        x = 114.70,
        y = -1951.60,
        z = 20.75
    },
    oldOwner = {
        identificator = "license:oldowneridentifier"
    },
    newOwner = {
        identificator = "license:newowneridentifier"
    }
}, function(response)
    if response.status then
        print("Organisation edited with index:", response.jobIndex)
    else
        print("Failed to edit organisation:", response.message or "unknown_error")
    end
end)
```

Notes:

* If `oldOwner` and `newOwner` are provided and their identifiers are different, the owner will be changed automatically.
* If `oldOwner` or `newOwner` is not provided, owner change will be skipped.
* Online members of the edited organisation will be synced automatically.
* This export can be safely used by external resources.

## getOrganisationsList

***

Return object

* identifier: `number`
* label: `string`

```lua
exports['op-crime']:getOrganisationsList()
```

## isTurfZoneInRivalry

***

Return false or true.

```lua
exports['op-crime']:isTurfZoneInRivalry(turfId)
```

## isPlayerTurfOwner

***

Return false or true.

```lua
exports['op-crime']:isPlayerTurfOwner(playerId, turfId)
```

## getPlayerTurfZone

***

Return index of player turf zone or nil. Useful for drugs selling script integrations.

```lua
exports['op-crime']:getPlayerTurfZone(source)
```

## getPlayerOrganisation

***

Return player organisation data object.

```lua
exports['op-crime']:getPlayerOrganisation(identifier)
```

* identifier: `string`<br>

Data Object:

* orgIndex: `number`
* player: `table`
  * identifier: `string`
  * totalTime: `string`
  * rank: `string`
  * status: `string`
* orgData: `table`

## getOrganisation

***

Return organisation object.

```lua
exports['op-crime']:getOrganisation(id)
```

* id: `number`

***

## Server Side Events:

Add Organisation EXP

```lua
op-crime:addOrganisationEXP
```

Data:

* `orgId`: number;
* `amount`: number;

Example usage:

```lua
TriggerEvent('op-crime:addOrganisationEXP', 5, 250)
```

***

Add Organisation Money

```
op-crime:addOrganisationMoney
```

Data:

* `orgId`: number;
* `amount`: number;
* `account`: string; (balance/dirtymoney)

Example usage:

```lua
TriggerEvent('op-crime:addOrganisationMoney', 5, 100000, "dirtymoney")
```

***

Remove Organisation Money

```
op-crime:removeOrganisationMoney
```

Data:

* `orgId`: number;
* `amount`: number;
* `account`: string; (balance/dirtymoney)

Example usage:

```lua
TriggerEvent('op-crime:removeOrganisationMoney', 5, 50000, "balance")
```

***

Add Organisation Missions Done

```
op-crime:addOrgMissionsDone
```

Data:

* `orgId`: number;
* `amount`: number;

Example usage:

```lua
TriggerEvent('op-crime:addOrgMissionsDone', 5, 15)
```

***

Add Organisation Zones Captured

```
op-crime:addZonesCaptured
```

Data:

* `orgId`: number;
* `amount`: number;

Example usage:

```lua
TriggerEvent('op-crime:addZonesCaptured', 5, 8)
```

***

## Zone Captured Event

`AddEventHandler('op-crime:zoneCaptured', newOrgId, oldOrgId, ZoneConfig)`

This is server-side event which is called when PVP zone is properly captured.

## decreaseSelected

***

Decrease Selected gang loyality in selected turf zone

```lua
exports['op-crime']:decreaseSelected(jobId, decreaseAmount, turfIndex)
```

* jobId: `string`
* decreaseAmount: `number`
* turfIndex: `string`<br>

## increaseSelected

***

Increase Selected gang loyality in selected turf zone

```lua
exports['op-crime']:increaseSelected(jobId, amount, turfIndex)
```

* jobId: `string`
* amount: `number`
* turfIndex: `string`<br>

## increaseZoneLoyality

***

Increase Selected gang loyality in selected turf zone and decrease every other gang loyality in this zone

```lua
exports['op-crime']:increaseZoneLoyality(jobId, amount, turfIndex, decreaseAmount)
```

* jobId: `string`
* amount: `number`
* turfIndex: `string`
* decreaseAmount: `number`<br>

## getTurfZones

***

Returns a list of all turf zones with name, id, polygon points, loyality list and current owner.

```lua
exports['op-crime']:getTurfZones()
```

#### Return Object (array)

Each entry:

* `id`: `number`
* `name`: `string`
* `points`: `table` - polygon points (`{x, y, z}` / vector list)
* `loyalityList`: `table`
  * `jobId`: `number`
  * `gangName`: `string`
  * `influencePoints`: `number`
* `owner`: `table|nil` - `nil` when nobody owns the turf (≥51% influence)
  * `jobId`: `number`
  * `label`: `string`
  * `percent`: `number`

#### Example Usage

```lua
local zones = exports['op-crime']:getTurfZones()

for _, zone in ipairs(zones) do
    print(zone.id, zone.name)

    if zone.owner then
        print("Owner:", zone.owner.label, zone.owner.percent .. "%")
    else
        print("No owner")
    end
end
```

## getTurfZone

***

Returns a single turf zone by id, or `false` if not found.

```lua
exports['op-crime']:getTurfZone(turfId)
```

#### Parameters

* `turfId`: `number|string`

#### Return Object

Same shape as one entry from `getTurfZones()`, or `false`.

#### Example Usage

```lua
local zone = exports['op-crime']:getTurfZone(12)

if zone then
    print(zone.name, #zone.points)
else
    print("Turf not found")
end
```

***

## Turf Zone Updated Event

Server-side event fired whenever a turf zone is updated (loyality change, create, edit, delete, admin reset).

```lua
AddEventHandler('op-crime:turfZoneUpdated', function(turfData)
end)
```

#### Data Object

Same as `getTurfZone` / `getTurfZones` entry, plus:

* `reason`: `string` - one of:
  * `loyality`
  * `created`
  * `edited`
  * `deleted`
  * `reset`
  * `updated`

#### Example Usage

```lua
AddEventHandler('op-crime:turfZoneUpdated', function(turfData)
    print(("[op-crime] Turf %s (%s) updated: %s"):format(
        tostring(turfData.id),
        tostring(turfData.name),
        tostring(turfData.reason)
    ))

    if turfData.owner then
        print("Current owner:", turfData.owner.label, turfData.owner.percent)
    end
end)
```

#### Notes

* This is a **server-side** event (`AddEventHandler` / `TriggerEvent`).
* Fired after loyality changes (`increaseZoneLoyality`, `increaseSelected`, `decreaseSelected`, etc.), turf create/edit/delete, and gang stats resets that clear turf loyality.


# Custom Drug Script

How to make your custom Drug Script compatible?

You need to call This server-side event when player successfully sold drug.

```lua
TriggerEvent("op-crime:drugSold", playerId, turfId, finalPrice)
```

{% hint style="warning" %}
This Event Can Be called only from Server-Side to prevent cheaters!
{% endhint %}

## How To Get Player Turf Id?

## getPlayerTurfZone

***

Return index of player turf zone or nil. Useful for drugs selling script integrations.

```lua
exports['op-crime']:getPlayerTurfZone(source)
```


# Integrations

Full List of Scripts Compatible with our Gangs System!

## Drug Sell Scripts

{% hint style="warning" %}
You Can add Your Dispatch Script in `integrations/drugSelling/`
{% endhint %}

<table><thead><tr><th width="167">Script Name</th><th width="136">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>op-drugselling</td><td>✅ Supported</td><td>Our own Free Drug Selling script <a href="https://github.com/Bagietame/op-drugselling">op-drugselling github</a></td></tr><tr><td>jaksam_drugs</td><td>✅ Supported</td><td>Config.DrugSelling.CustomDrugScript = "jaksam_drugs"</td></tr><tr><td>nc-drugselling</td><td>✅ Supported</td><td>Need download <a href="/scripts/op-gangs/integrations/nc-drugselling">nc-drugselling integration</a></td></tr><tr><td>visualz_selldrugs</td><td>✅ Supported</td><td>Need download <a href="/scripts/op-gangs/integrations/visualz_selldrugs">visualz_selldrugs integration</a></td></tr><tr><td>tk_drugs</td><td>✅ Supported</td><td>Need additonal changes <a href="/scripts/op-gangs/integrations/tk_drugs">tk_drugs integration</a></td></tr><tr><td>lunar_drugscreator</td><td>✅ Supported</td><td>Need additional changes <a href="/scripts/op-gangs/integrations/lunar_drugscreator">lunar_drugscreator integration</a></td></tr><tr><td>fs_trapphone</td><td>✅ Supported</td><td>Need additional changes <a href="/scripts/op-gangs/integrations/fs_trapphone">fs_trapphone integration</a></td></tr><tr><td>lation_selling</td><td>✅ Supported</td><td>Need additional changes <a href="/scripts/op-gangs/integrations/lation_selling">lation_selling integration</a></td></tr><tr><td>envi-traphone</td><td>✅ Supported</td><td>Need additional changes <a href="/scripts/op-gangs/integrations/envi-traphone">envi-traphone integration</a></td></tr></tbody></table>

## Dispatch

{% hint style="warning" %}
You Can add Your Dispatch Script in `integrations/dispatch/`
{% endhint %}

<table><thead><tr><th width="182">Script Name</th><th width="258">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>cd_dispatch</td><td>✅ Supported</td><td>--</td></tr><tr><td>codem_dispatch</td><td>✅ Supported</td><td>--</td></tr><tr><td>lb-tablet</td><td>✅ Supported</td><td>--</td></tr><tr><td>ps-dispatch</td><td>✅ Supported</td><td>--</td></tr><tr><td>rcore_dispatch</td><td>✅ Supported</td><td>--</td></tr></tbody></table>

## Clothing

{% hint style="warning" %}
You Can add Your Clothing Script in `integrations/clothing/`
{% endhint %}

<table><thead><tr><th width="252">Script Name</th><th width="258">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>skinchanger</td><td>✅ Supported</td><td>--</td></tr><tr><td>illenium-appearance</td><td>✅ Supported</td><td>--</td></tr><tr><td>fivem-appearance</td><td>✅ Supported</td><td>--</td></tr><tr><td>17mov_CharacterSystem</td><td>✅ Supported</td><td>--</td></tr><tr><td>rcore_clothing</td><td>✅ Supported</td><td>--</td></tr><tr><td>crm-appearance</td><td>✅ Supported</td><td>--</td></tr><tr><td>0r-clothingv2</td><td>✅ Supported</td><td>--</td></tr></tbody></table>

## Inventories

{% hint style="warning" %}
You Can add Your Inventory Script in `integrations/inventory/`
{% endhint %}

<table><thead><tr><th width="173">Script Name</th><th width="188">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>ox_inventory</td><td>✅ Supported</td><td>--</td></tr><tr><td>qb-inventory</td><td>✅ Supported</td><td>--</td></tr><tr><td>old-qb-inventory</td><td>✅ Supported</td><td>Supports Old Inventory Versions.</td></tr><tr><td>codem-inventory</td><td>✅ Supported</td><td>--</td></tr><tr><td>tgiann_inventory</td><td>✅ Supported</td><td>--</td></tr><tr><td>none</td><td>✅ Supported</td><td>You can use default ox_lib baed Menu.</td></tr></tbody></table>

## Fuel&#x20;

{% hint style="warning" %}
You Can add Custom Fuel Script in `integrations/utils/vehicleFuel.lua`
{% endhint %}

<table><thead><tr><th width="156">Script Name</th><th width="166">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>cdn-fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "cdn-fuel"</td></tr><tr><td>ox-fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "ox-fuel"</td></tr><tr><td>LegacyFuel</td><td>✅ Supported</td><td>Config.FuelDependency = "LegacyFuel"</td></tr><tr><td>rcore-fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "rcore-fuel"</td></tr><tr><td>codem-xfuel</td><td>✅ Supported</td><td>Config.FuelDependency = "codem-xfuel"</td></tr><tr><td>lc_fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "lc_fuel"</td></tr><tr><td>stg-fuel</td><td>✅ Supported</td><td>Config.FuelDependency = "stg-fuel"</td></tr></tbody></table>

## Vehicle Keys

{% hint style="warning" %}
You Can add Custom Vehicle Keys Script in `integrations/utils/vehicleKeys.lua`
{% endhint %}

<table><thead><tr><th width="186">Script Name</th><th width="150">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>brutal_keys</td><td>✅ Supported</td><td>Config.KeysDependency = "brutal_keys"</td></tr><tr><td>qb-keys</td><td>✅ Supported</td><td>Config.KeysDependency = "qb-keys"</td></tr><tr><td>qbx_vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "qbx_vehiclekeys"</td></tr><tr><td>wasabi_carlock</td><td>✅ Supported</td><td>Config.KeysDependency = "wasabi_carlock"</td></tr><tr><td>sna-vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "sna-vehiclekeys"</td></tr><tr><td>dusa_vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "dusa_vehiclekeys"</td></tr><tr><td>RenewedVehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "RenewedVehiclekeys"</td></tr><tr><td>tgiann-keys</td><td>✅ Supported</td><td>Config.KeysDependency = "tgiann-keys"</td></tr><tr><td>ak47_vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "ak47_vehiclekeys"</td></tr><tr><td>ak47_qb_vehiclekeys</td><td>✅ Supported</td><td>Config.KeysDependency = "ak47_qb_vehiclekeys"</td></tr><tr><td>MrNewbVehicleKeys</td><td>✅ Supported</td><td>Config.KeysDependency = "MrNewbVehicleKeys"</td></tr><tr><td>mVehicle</td><td>✅ Supported</td><td>Config.KeysDependency = "mVehicle"</td></tr><tr><td>sy_carkeys</td><td>✅ Supported</td><td>Config.KeysDependency = "sy_carkeys"</td></tr><tr><td>p_carkeys</td><td>✅ Supported</td><td>Config.KeysDependency = "p_carkeys"</td></tr></tbody></table>

## Text UI

{% hint style="warning" %}
You Can add Custom Text UI Script in `integrations/utils/textUI.lua`
{% endhint %}

<table><thead><tr><th width="145">Script Name</th><th width="159">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>ox_lib</td><td>✅ Supported</td><td>Config.TextUI = "ox_lib"</td></tr><tr><td>jg-textui</td><td>✅ Supported</td><td>Config.TextUI = "jg-textui"</td></tr><tr><td>okokTextUI</td><td>✅ Supported</td><td>Config.TextUI = "okokTextUI"</td></tr><tr><td>brutal_textui</td><td>✅ Supported</td><td>Config.TextUI = "brutal_textui"</td></tr><tr><td>0r_textui</td><td>✅ Supported</td><td>Config.TextUI = "0r_textui"</td></tr></tbody></table>

## Targets

{% hint style="warning" %}
You Can add Custom Targeting Script in `integrations/utils/targets.lua`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="154">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>ox_target</td><td>✅ Supported</td><td>Config.Misc.AccessMethod = "ox-target"</td></tr><tr><td>qb-target</td><td>✅ Supported</td><td>Config.Misc.AccessMethod = "qb-target"</td></tr><tr><td>fivem native markers</td><td>✅ Supported</td><td>Config.Misc.AccessMethod = "none"</td></tr></tbody></table>

## Notify

{% hint style="warning" %}
You Can add Custom Notify Script in `integrations/utils/sendNotify.lua`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="149">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>op_hud</td><td>✅ Supported</td><td>Config.Misc.Notify = "op_hud"</td></tr><tr><td>okokNotify</td><td>✅ Supported</td><td>Config.Misc.Notify = "okokNotify"</td></tr><tr><td>vms_notify</td><td>✅ Supported</td><td>Config.Misc.Notify = "vms_notify"</td></tr><tr><td>brutal_notify</td><td>✅ Supported</td><td>Config.Misc.Notify = "brutal_notify"</td></tr><tr><td>ox_lib</td><td>✅ Supported</td><td>Config.Misc.Notify = "ox_lib"</td></tr><tr><td>ESX</td><td>✅ Supported</td><td>Config.Misc.Notify = "ESX"</td></tr><tr><td>QBCORE</td><td>✅ Supported</td><td>Config.Misc.Notify = "QBCORE"</td></tr><tr><td>QBOX</td><td>✅ Supported</td><td>Config.Misc.Notify = "QBOX"</td></tr></tbody></table>

## Garages

{% hint style="warning" %}
You Can add your Garage Script in `integrations/garage/`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="201">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>op-garages</td><td>✅ Supported</td><td>Above Version 2.5.4</td></tr><tr><td>Default Menu</td><td>✅ Supported</td><td>Basic Garage Menu Included in our script</td></tr><tr><td>jg-advancedgarages</td><td>✅ Supported</td><td>Need additional Download and changes. <a href="/scripts/op-gangs/integrations/jg-advancedgarages">(jg-advancedgarages)</a></td></tr><tr><td>cd_garage</td><td>⚒️ Work In Progress</td><td></td></tr><tr><td>RxGarages</td><td>⚒️ Work In Progress</td><td></td></tr><tr><td>okokGarage</td><td>⚒️ Work In Progress</td><td></td></tr></tbody></table>

## Optional Addons Scripts

<table><thead><tr><th width="196">Script Name</th><th width="147">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>OX Doorlock</td><td>✅ Supported</td><td>Need Additional Download <a href="/scripts/op-gangs/integrations/ox_doorlock-v1.18.0">(ox_doorlock)</a></td></tr><tr><td>kq_shellcreator</td><td>✅ Supported</td><td>Config.AdditionalScripts.kq_shellcreator = true</td></tr><tr><td>nextgenfivem_crafting</td><td>✅ Supported</td><td>Need Additional Download <a href="/scripts/op-gangs/integrations/nextgenfivem_crafting">(nextgen_crafting)</a></td></tr><tr><td>p_policejob (handsup function)</td><td>✅ Supported</td><td>Need Additional Download (<a href="/scripts/op-gangs/integrations/p_policejob">p_policejob</a>)</td></tr><tr><td>rcore_spray</td><td>✅ Supported</td><td>Need Additional Download (<a href="/scripts/op-gangs/integrations/rcore_spray">rcore_spray</a>)</td></tr><tr><td>jg-hud</td><td>✅ Supported</td><td>Need Additional Download (<a href="/scripts/op-gangs/integrations/jg-hud">jg-hud</a>)</td></tr><tr><td>vigi_spray</td><td>✅ Supported</td><td>Config.OPGangs.enabled = true</td></tr></tbody></table>


# List of Integrations

See full list of available Integrations in Our Script!

{% content-ref url="/pages/bGBCrkttWNAKdY3laEB9" %}
[Integrations](/scripts/op-gangs/integrations)
{% endcontent-ref %}


# op-drugselling

Integration with our Free Drug Selling script!

## Download Script:

{% embed url="<https://github.com/Bagietame/op-drugselling>" %}

## Enable OP Gangs integration in config

`Config.AdditionalScripts.op_Gangs = true`


# ox\_doorlock (v1.18.0)

How to make ox\_doorlock work with our organisations system:

{% stepper %}
{% step %}

### Downlaod framework files

Download edited by us framework files of ox\_doorlock (v1.18.0)

{% embed url="<https://data.otherplanet.dev/download/integrations/ox_doorlock/framework.rar>" %}
{% endstep %}

{% step %}

### Navigate to your ox\_doorlock files.

Replace downloaded files with files in folder: `ox_doorlock/server/framework`
{% endstep %}

{% step %}

### Create First Doorlock

{% embed url="<https://youtu.be/wss7j6l1YCI>" %}
{% endstep %}
{% endstepper %}


# nc-drugselling

Integration of our Turf Zone system with nc-drugselling script!

{% stepper %}
{% step %}

### Find Server-Side Folder

Go to `nc-drugselling/server/main.lua`
{% endstep %}

{% step %}

### Add Code inside Drug Dealing Event

Find event called:

```lua
nc-drugselling:server:makeDeal
```

{% endstep %}

{% step %}

### Add Our Code Snippet

Please make sure to paste code in correct place.

<figure><img src="https://36113860-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1vEl4u37HzsUUsBP0oWM%2Fuploads%2FiwEBLA8YH68sfoIGgYZ9%2Fimage.png?alt=media&amp;token=80283869-6973-42f2-ae50-5e5800b372c6" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Copy Snippet:

```lua
local turfId = exports['op-crime']:getPlayerTurfZone(src)
if turfId then 
    TriggerEvent('op-crime:drugSold', src, turfId, dealData.price)
end
```

{% endstep %}
{% endstepper %}


# visualz\_selldrugs

Integration of our Turf Zone system with visualz\_selldrugs script!

{% stepper %}
{% step %}

### Find Server-Side folder

Go to `visualz_selldrugs/server/functions.lua`
{% endstep %}

{% step %}

### Add Code Snippet

You have to replace function `SellDrugEvent` with our:

```lua
function SellDrugEvent(source, drug, price, amount, zone)
  --local xPlayer = ESX.GetPlayerFromId(source)
  --exports["visualz_zones"]:AddPoints(xPlayer, zone, price * amount, drug)
  local turfId = exports['op-crime']:getPlayerTurfZone(source)
  if turfId then 
      TriggerEvent('op-crime:drugSold', source, turfId, price * amount)
  end
end
```

{% endstep %}

{% step %}

### File After Editing

File should look like this:\
![](https://36113860-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1vEl4u37HzsUUsBP0oWM%2Fuploads%2Fe7iPmFV8qTYt5A3kEi11%2Fimage.png?alt=media\&token=6764086d-9046-43d0-b3f9-45173e781a5b)
{% endstep %}
{% endstepper %}


# tk\_drugs

Integration with tk\_drugs for our Turf Zones Loylity System.

{% stepper %}
{% step %}

### Find Folder in tk\_drugs

Find folder: `tk_drugs/server/main_editable.lua`
{% endstep %}

{% step %}

### Replace function

Replace Function:

```lua
function SoldDrugs(xPlayer, drugName, amount, price)
    AddItem(xPlayer, Config.Selling.moneyType, price)
end
```

With this function:

```lua
function SoldDrugs(xPlayer, drugName, amount, price)
    AddItem(xPlayer, Config.Selling.moneyType, price)
    
    local id = xPlayer.source or xPlayer.PlayerData.source
    local turfId = exports['op-crime']:getPlayerTurfZone(id)
    if turfId then 
        TriggerEvent('op-crime:drugSold', id, turfId, price)
    end
end
```

{% endstep %}
{% endstepper %}


# lunar\_drugscreator

Integration with lunar\_drugscreator for our Turf Zones Loylity System. This integration has been created by our Customer: Awkward (206743186570346496)

{% stepper %}
{% step %}

### Find Folder

Find foler: `lunar_drugscreator/config/sv_edit.lua`
{% endstep %}

{% step %}

### Find Function

Find function called `Editable.onRetailSale` and replace it with this code:

```lua
function Editable.onRetailSale(player, itemName, amount, price)
    local total = amount * price
    local turfId = exports['op-crime']:getPlayerTurfZone(player.source)

    if turfId then
        TriggerEvent('op-crime:drugSold', player.source, turfId, total)
    end
end
```

{% endstep %}
{% endstepper %}


# fs\_trapphone

Integration with fs\_trapphone for our Turf Zones Loylity System.

{% stepper %}
{% step %}

### Find Folder

Go to `FS_TRAPPHONE/server/unlocked.lua`
{% endstep %}

{% step %}

### Find Event

Find event `fs_trapphone:onDrugSell`
{% endstep %}

{% step %}

### Replace This Event with:

```lua
AddEventHandler('fs_trapphone:onDrugSell',function (serverId,itemname,quantity)
  local foundDrugCfg = nil

  for k, v in pairs(Config.Drugs) do 
    if v.name == itemname then
      foundDrugCfg = v
      break;
    end
  end

  if foundDrugCfg then 
    local turfId = exports['op-crime']:getPlayerTurfZone(serverId)
    if turfId then 
        local price = foundDrugCfg.price.min * quantity
        TriggerEvent('op-crime:drugSold', serverId, turfId, price)
    end
  end
end)
```

{% endstep %}
{% endstepper %}


# lation\_selling

Integration with lation\_selling for our Turf Zones Loylity System.

{% stepper %}
{% step %}

### Find Directory

Find folder `lation_selling/server/functions.lua`
{% endstep %}

{% step %}

### Find Function

Find function named `DrugSold`
{% endstep %}

{% step %}

### Replace function

Replace existing function `DrugSold` with this:

```lua
DrugSold = function(source, zoneId, item, quantity, value)
    local turfId = exports['op-crime']:getPlayerTurfZone(source)
    if turfId then 
        TriggerEvent('op-crime:drugSold', source, turfId, value)
    end
end

```

{% endstep %}
{% endstepper %}


# envi-traphone

Integration with Turf Zones to envi-traphone

{% hint style="warning" %}
Only above version 2.0.8!
{% endhint %}

All you need to do is set `Config.DrugSelling.DrugScript` to `envi-trapphone`

This value can be changed in `op-crime/config/TurfConifg.lua`


# nextgenfivem\_crafting

Integration with our script to restrict Crafting Tables only for selected gangs.

{% stepper %}
{% step %}

### Download Edited Files

Download Framework edited files by us from this URL:

{% embed url="<https://data.otherplanet.dev/download/integrations/nextgenfivem_crafting/frameworks.rar>" %}
{% endstep %}

{% step %}

### Replace Files

Replace files into `nextgenfivem_crafting`
{% endstep %}
{% endstepper %}


# jg-advancedgarages

Integration with jg-advancedgarages

{% hint style="warning" %}
This integration works **only with OP Gangs v2.1.4 or newer**.&#x20;

Support for this integration will be available **out of the box** in the **latest version of JG Garages** very soon.
{% endhint %}

{% stepper %}
{% step %}

### Download Edited Files

Download the prepared integration files from the link below:

{% embed url="<https://data.otherplanet.dev/download/integrations/jg-garages/jg-garagesint.rar>" %}
{% endstep %}

{% step %}

### Replace Files in JG Garages

1. Navigate to the following directory: `jg-advancedgarages/framework/`
2. Unzip the downloaded archive.
3. **Replace the existing files** in this directory with the extracted ones.
   {% endstep %}

{% step %}

### Configure Garage System in OP Gangs

Open your **op-crime config** and set the garage system to JG Garages:

`Config.GarageScript = "jg-advancedgarages"`
{% endstep %}

{% step %}

### Only For ESX Framework

Open your **jg-advancedgarages config** and set ESX Integration to true:

`Config.GangEnableCustomESXIntegration = true`
{% endstep %}
{% endstepper %}


# jg-hud

Integration with jg-hud (displaying gang name)

{% hint style="warning" %}
This integration works **only with OP Gangs v2.1.4 or newer**.&#x20;

Support for this integration will be available **out of the box** in the **latest version of JG Hud** very soon.
{% endhint %}

{% stepper %}
{% step %}

### Download Edited Files

Download the prepared integration files from the link below:

{% embed url="<https://data.otherplanet.dev/download/integrations/jg-hud/cl-functions-jghud.rar>" %}
{% endstep %}

{% step %}

### Replace Files in JG Hud

1. Navigate to the following directory: `jg-hud/framework/`
2. Unzip the downloaded archive.
3. **Replace the existing files** in this directory with the extracted ones.
   {% endstep %}
   {% endstepper %}


# rcore\_spray

Integration with rcore\_spray

{% hint style="warning" %}
This integration works **only with OP Gangs v2.1.4 or newer**.&#x20;
{% endhint %}

{% stepper %}
{% step %}

### Download Edited Files

Download the prepared integration files from the link below:

{% embed url="<https://data.otherplanet.dev/download/integrations/rcore_spray/server-rcorespray.rar>" %}
{% endstep %}

{% step %}

### Replace Files in RCore Spray

1. Navigate to the following directory: `rcore_spay/server/`
2. Unzip the downloaded archive.
3. **Replace the existing files** in this directory with the extracted ones.
   {% endstep %}
   {% endstepper %}


# p\_policejob

Want to make handsup function work with our Crime Rope and p\_policejob? Follow this guide.

{% stepper %}
{% step %}

### Replace Files In p\_policejob

find directory `p_policejob/modules/client/handsup.lua`

Replace code inside with this:

```lua
local handsUp = false

local function exportHandler(exportName, func)
    AddEventHandler(('__cfx_export_qb-smallresources%s'):format(exportName), function(setCB)
        setCB(func)
    end)
end

RegisterCommand('p_handsup', function()
	if exports['op-crime']:isRopeActive() or LocalPlayer.state.isDead or exports['p_policejob']:isInCuffProcess() or LocalPlayer.state.PhoneOpen then
		return
	end
	
	if handsUp then
		ClearPedSecondaryTask(cache.ped)
		handsUp = false
		return
	end

	if LocalPlayer.state.isCuffed or LocalPlayer.state.PhoneOpen then
		return
	end

	local animDict = lib.requestAnimDict("random@mugging3")
	TaskPlayAnim(cache.ped, animDict, "handsup_standing_base", 2.0, 2.0, -1, 49, true)
	RemoveAnimDict(animDict)
	handsUp = true
	Citizen.CreateThread(function()
		while handsUp do
			Wait(1)
			DisableControlAction(2, 24, true)
			DisableControlAction(2, 257, true)
			DisableControlAction(2, 25, true)
			DisableControlAction(2, 263, true)
			DisableControlAction(2, 45, true)
			DisableControlAction(2, 37, true)
			DisableControlAction(2, 23, true)
			DisableControlAction(0, 47, true)
			DisableControlAction(0, 264, true)
			DisableControlAction(0, 257, true)
			DisableControlAction(0, 140, true)
			DisableControlAction(0, 141, true)
			DisableControlAction(0, 142, true)
			DisableControlAction(0, 143, true)
			if (IsEntityPlayingAnim(cache.ped, "random@mugging3", "handsup_standing_base", 3) ~= 1) then
				handsUp = false
			end
		end
	end)
end)

RegisterKeyMapping('p_handsup', locale('hands_up'), 'keyboard', 'X') -- X

-- compatible with qb-smallresources
exportHandler('getHandsup', function()
	return handsUp
end)
```

{% endstep %}

{% step %}

### Replace files in op-crime

find directory `op-crime/integrations/client/utils/handsup.lua`

Replace code inside with this:

```lua
handsupDictonary = "random@mugging3"
handsupAnimation = "handsup_standing_base"
handsup = false

function hasHandsUp()
    return handsup
end

function isCuffed(id)
    return LocalPlayer.state.isCuffed
end

function isPedDead(Ped)
    return IsPedDeadOrDying(Ped, true)
end

function isRopeActive()
    return isHandcuffed
end
exports("isRopeActive", isRopeActive)

function ToggleHands()
    
end
```

{% endstep %}
{% endstepper %}


# Gangs: qb-core/qbox

OP-Gangs v2.0 now fully integrates with QBCore/QBox gang API - native functions like GetPlayerGang() and PlayerData.gang work out of the box, ensuring seamless compatibility with third-party scripts.

{% hint style="warning" %}
Functions such as `Player.SetGang` or the `/setgang` command will **not** work. Instead, use the `/setcrimejob` command, which is built into our script.
{% endhint %}

{% stepper %}
{% step %}

### Download Edited Core

This integration needs additional download of edited `qb-core` or `qbx_core` by our Team.

Edited **qb-core**:

{% embed url="<https://github.com/Bagietame/qb-core/commit/1fa46b4ba697d22f848f49be5cc6d3aba2f82ad3>" %}

Edited **qbx\_core**:

{% embed url="<https://github.com/Bagietame/qbx_core/commit/e87aeb89c8b21cebdd510b00f9bdf85b32508182>" %}
{% endstep %}

{% step %}

### Enable Integration in op-crime

Navigate to `op-crime/config/ServerConfig.lua` and enable this option:

```lua
ServerConfig.EnableQBgangsIntegrations = true
```

{% endstep %}

{% step %}

### Restart Your Server

All Done! Now gang name, rank etc will be displayed in all of your other resources.
{% endstep %}
{% endstepper %}


# OP Ambulance MDT

Most Advanced Fivem Ambulance MDT & Dispatch Script! Available now on otherplanet.dev!

## 🛒 Purchase Script:&#x20;

{% embed url="<https://otherplanet.dev/product/ambulance-mdt>" %}

{% embed url="<https://youtu.be/hfLoQaoDDzE>" %}


# Installation

Before you start, make sure you're logged into the CFX portal. This ensures you can download the asset and receive future updates.

## Downloading Dependencies

Dependencies are very important — you need to install them before moving on to the installation of our script.

{% stepper %}
{% step %}

### OX\_LIB

{% embed url="<https://github.com/overextended/ox_lib>" %}
{% endstep %}
{% endstepper %}

***

## Server.cfg

All dependencies you’ve installed must be started before our script. Make sure to follow the correct start-up order in your `server.cfg`.

```
ensure ox_lib
ensure op-ambulancemdt
```

***

## First Usage

Configure your additional scripts in config/MainConfig.lua

After configuring access Job to the Tablet you must run this command to assign Permissions To View all tabs to selected rank.&#x20;

> **IMPORTANT:** Run this command in txAdmin Console!

```
/setallpermsrank <jobName> <gradeNumber>

Example:
/setallpermsrank ambulance 4
```


# Config File

The script is fully configurable, allowing complete customization of its behavior and features. Below is a list of configuration files included in the resource.

```lua
-- ──────────────────────────────────────────────────────────────────────────────
-- Dependency Check Helper                                                     
-- (Information) ► Returns the first matching started resource alias from provided table.
-- (Information) ► Used by Fuel/Keys/TextUI/Inventory/Target detection above.
-- ──────────────────────────────────────────────────────────────────────────────
function scriptCheck(data)            -- Do not modify unless you know what you're doing.
    for k, v in pairs(data) do
        if GetResourceState(k):find('started') ~= nil then
            return v
        end
    end
    return false
end

-- ──────────────────────────────────────────────────────────────────────────────
--  OTHERPLANET / OP Ambulance MDT / Main CONFIGURATION
-- ──────────────────────────────────────────────────────────────────────────────
--  This configuration file controls all customizable behaviour of OP Ambulance MDT
--  Always make a backup before editing.
--  Wrong edits can break the resource.
-- ──────────────────────────────────────────────────────────────────────────────

Config = {}

-- ──────────────────────────────────────────────────────────────────────────────
-- Locale & Debug                                                             
-- (Information) ► Locale controls which language file from locales/* will be used.
-- (Information) ► Debug enables extra logging to help with issue tracking.
-- ──────────────────────────────────────────────────────────────────────────────

Config.Locale = "en"                  -- Supported: EN, DE, EL, SK, SV
Config.Debug  = false                 -- true = verbose debug output in console.

-- ──────────────────────────────────────────────────────────────────────────────
-- Currency Formatting                                                         
-- (Information) ► Visual formatting of money values in the UI (JS Intl.NumberFormat).
-- (Information) ► This does NOT change internal game currency logic, only display.
-- ──────────────────────────────────────────────────────────────────────────────

Config.CurrencySettings = {
    -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
    currency = "USD",
    style = "currency",
    format = "en-US"
}

-- ──────────────────────────────────────────────────────────────────────────────
-- MISC CONFIGURATION                                                   
-- (Information) ► Notifications and Target
-- ──────────────────────────────────────────────────────────────────────────────

local targets = { -- Target libraries detection
    ['ox_target'] = "ox-target",
    ['qb-target'] = "qb-target"
}

local notifyScripts = { -- Notify libraries detection
    ['op-hud'] = "op_hud",
    ['okokNotify'] = "okokNotify",
    ['vms_notify'] = "vms_notify",
    ['ox_lib'] = "ox_lib",
    ['brutal_notify'] = "brutal_notify",
}

Config.Target = scriptCheck(targets) or 'none' -- Supported: ox-target / qb-target / none
Config.Notify = scriptCheck(notifyScripts) or 'none'

-- ──────────────────────────────────────────────────────────────────────────────
-- Text UI Dependency Detection                                                
-- (Information) ► Auto-detects supported 3D/2D Text UI libraries.
-- (Information) ► If none is found, some prompts may fallback to default behaviour.
-- ──────────────────────────────────────────────────────────────────────────────
local textUIScripts = {              
    ['ox_lib']        = "ox_lib",
    ['jg-textui']     = "jg-textui",
    ['okokTextUI']    = "okokTextUI",
    ['brutal_textui'] = "brutal_textui",
    ['0r-textui']     = "0r-textui",
}
Config.TextUI = scriptCheck(textUIScripts) or 'none' 

-- ──────────────────────────────────────────────────────────────────────────────
-- Banking SCRIPT Detection                                           
-- (Information) ► Auto-detects supported banking scripts
-- (Information) ► If none is found, some prompts may fallback to default behaviour.
-- ──────────────────────────────────────────────────────────────────────────────

local bankingScripts = {              
    ['esx_addonaccount'] = "esx_addonaccount",
    ['qb-banking']       = "qb-banking",
    ['fd_banking']       = "fd_banking",
    ['okokBanking']      = "okokBanking",
    ['Renewed-Banking']  = "Renewed-Banking",
    ['tgg-banking']      = "tgg-banking"
}
Config.Banking = scriptCheck(bankingScripts) or 'none' 

-- ──────────────────────────────────────────────────────────────────────────────
-- Garage Script                                                               
-- (Information) ► Manual selection of external garage script integration.
-- ──────────────────────────────────────────────────────────────────────────────

Config.GarageScript = "op-garages" -- op-garages / jg-advancedgarages 

-- ──────────────────────────────────────────────────────────────────────────────
-- Jobs Configuration                                                          
-- (Information) ► Defines which jobs can access the MDT.
-- (Information) ► Each job has its own separated tablet and data.
-- (Information) ► This is NOT a multi-job tablet – each job must be defined separately.
-- (Information) ► Key = job name | Value = job display label.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Jobs = {
    ["ambulance"] = "Emergency Medical Services",
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Commands Configuration                                                       
-- (Information) ► Enables or disables available MDT-related commands.
-- (Information) ► Commands are optional and can be fully disabled.
-- (Information) ► Command names must be unique and not conflict with other scripts.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Commands = {
    openMDT = {
        enable = true, 
        command = "ambulancemdt"
    },
    adjustdispatch = {
        enable = true, 
        command = "adjustdispatch"
    },
    toggleDispatch = {
        enable = true, 
        command = "toggledispatch"
    }
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Vehicle Shop Configuration                                                   
-- (Information) ► Defines vehicles available for purchase via MDT.
-- (Information) ► Vehicles are assigned per job.
-- (Information) ► Prices, resale value and preview images are configurable.
-- (Information) ► Vehicle models must exist in the server files.
-- ──────────────────────────────────────────────────────────────────────────────
Config.VehicleShop = {
    ["ambulance"] = {
        {
            label = "Ambulance",
            model = "ambulance",
            price = 95000,
            image = "https://docs.fivem.net/vehicles/ambulance.webp",
            resellPrice = 8500,
        },
        {
            label = "Lifeguard",
            model = "lguard",
            price = 120000,
            image = "https://docs.fivem.net/vehicles/lguard.webp",
            resellPrice = 100000,
        },
    }
}

-- ──────────────────────────────────────────────────────────────────────────────
-- Insurance System Configuration                                               
-- (Information) ► Controls the insurance system used by MDT.
-- (Information) ► Players can extend insurance for a selected time period.
-- (Information) ► Insurance time is stored in UTC ISO format.
-- ──────────────────────────────────────────────────────────────────────────────
Config.Insurance = {

    -- ──────────────────────────────────────────────────────────────────────────
    -- Insurance Extend Options                                                   
    -- (Information) ► Available insurance extension plans.
    -- (Information) ► Key does not matter, value.value defines duration.
    -- (Information) ► Time value is defined in HOURS.
    -- (Information) ► Price is charged from selected payment method.
    -- ──────────────────────────────────────────────────────────────────────────
    ExtendOptions = {
        [24] = {
            value = 24, -- Time in hours
            price = 5500
        },
        [72] = {
            value = 72, -- Time in hours
            price = 9500
        },
        [144] = {
            value = 144, -- Time in hours
            price = 14500
        },
    },

    -- ──────────────────────────────────────────────────────────────────────────
    -- Insurance Ped Configuration                                                
    -- (Information) ► Defines NPC used to access insurance features.
    -- (Information) ► NPC can be interacted with via target or marker.
    -- (Information) ► Ped animation and prop are fully configurable.
    -- ──────────────────────────────────────────────────────────────────────────
    InsurancePed = {
        ped_coords = vec4(340.8385, -1398.8954, 31.5093, 71.7369),
        ped_model = "s_m_m_paramedic_01",
        ped_gender = "male",

        ped_animation = false,
        --ped_animation = {
        --    Dict = "missheistdockssetup1clipboard@base",
        --    Lib  = "base",
        --    Prop = {
        --        Prop = 'prop_notepad_01',
        --        PropBone = 18905,
        --        PropPlacement = {
        --            0.1,
        --            0.02,
        --            0.05,
        --            10.0,
        --            0.0,
        --            0.0
        --        }
        --    }
        --},

        enable = true,

        -- Marker settings (used only when target system is disabled)
        markerColor = {
            r = 219,
            g = 0,
            b = 0,
        },

        -- Map blip configuration
        ped_blip = {
            BlipScale = 0.8, 
            blipId = 51,
            blipColor = 7
        }
    },

    -- ──────────────────────────────────────────────────────────────────────────
    -- Insurance Command Configuration                                            
    -- (Information) ► Optional command to open insurance menu.
    -- (Information) ► Useful when NPC is disabled or for quick access.
    -- ──────────────────────────────────────────────────────────────────────────
    InsuranceCommand = {
        enable = false,
        command = "myinsurance"
    }
}

Config.TabletAnimation = {
    enable = true,
    dict = "amb@code_human_in_bus_passenger_idles@female@tablet@idle_a",
    name = "idle_a",
    prop = { -- Replace prop table with false (prop = false) if you want to disable it!
        Prop = "prop_cs_tablet",
        PropBone = 28422,
        PropPlacement = {
            -0.05,
            0.0,
            0.0,
            0.0,
            -90.0,
            0.0
        }
    }
}
```


# Exports


# Client Side

{% hint style="warning" %}
If you're looking for Other Data then listed Here and in Server-Side Exports - Feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

## Send Dispatch Alert

```lua
TriggerServerEvent('op-ambulancemdt:SendDispatchAlert', jobName, header, desc, color, icon, coords)
```

* jobName: `string`
* header: `string`
* desc: `string`
* color: `red | yellow | green`   &#x20;
* icon: `google icon (`[`https://fonts.google.com/icons`](https://fonts.google.com/icons)`)`
* coords: `vec4`

***

## Open MDT

Open's MDT, useful for radial menu or item configuration

```lua
TriggerEvent('op-ambulancemdt:openMDT')
```

***


# Server Side

{% hint style="warning" %}
If you're looking for Other Data then listed Here and in Client-Side Exports - Feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

## SendDispatchAlert

***

* jobName: `string`
* header: `string`
* desc: `string`
* color: `red | yellow | green`   &#x20;
* icon: `font awesome icon`&#x20;
* coords: `vec4`

```lua
exports['op-ambulancemdt']:SendDispatchAlert(jobName, header, desc, color, icon, coords)
```

## changePlayerDuty

* playerId: `number`
* newStatus: `offduty | active | busy`

***

```lua
exports['op-ambulancemdt']:changePlayerDuty(playerId, newStatus)
```


# Integrations

Full List of Scripts Compatible with our MDT System!

## Banking

{% hint style="warning" %}
You Can add Custom Targeting Script in `integrations/server/banking.lua`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="154">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>esx_addonaccount</td><td>✅ Supported</td><td></td></tr><tr><td>qb-banking</td><td>✅ Supported</td><td></td></tr><tr><td>fd_banking</td><td>✅ Supported</td><td></td></tr><tr><td>okokBanking</td><td>✅ Supported</td><td></td></tr><tr><td>Renewed-Banking</td><td>✅ Supported</td><td></td></tr><tr><td>tgg-banking</td><td>✅ Supported</td><td></td></tr></tbody></table>

## Text UI

{% hint style="warning" %}
You Can add Custom Text UI Script in `integrations/utils/textUI.lua`
{% endhint %}

<table><thead><tr><th width="145">Script Name</th><th width="159">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>ox_lib</td><td>✅ Supported</td><td>Config.TextUI = "ox_lib"</td></tr><tr><td>jg-textui</td><td>✅ Supported</td><td>Config.TextUI = "jg-textui"</td></tr><tr><td>okokTextUI</td><td>✅ Supported</td><td>Config.TextUI = "okokTextUI"</td></tr><tr><td>brutal_textui</td><td>✅ Supported</td><td>Config.TextUI = "brutal_textui"</td></tr><tr><td>0r_textui</td><td>✅ Supported</td><td>Config.TextUI = "0r_textui"</td></tr></tbody></table>

## Targets

{% hint style="warning" %}
You Can add Custom Targeting Script in `integrations/utils/targets.lua`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="154">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>ox_target</td><td>✅ Supported</td><td>Config.Target  = "ox-target"</td></tr><tr><td>qb-target</td><td>✅ Supported</td><td>Config.Target  = "qb-target"</td></tr><tr><td>fivem native markers</td><td>✅ Supported</td><td>Config.Target = "none"</td></tr></tbody></table>

## Notify

{% hint style="warning" %}
You Can add Custom Notify Script in `integrations/utils/sendNotify.lua`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="149">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>op_hud</td><td>✅ Supported</td><td>Config.Notify = "op_hud"</td></tr><tr><td>okokNotify</td><td>✅ Supported</td><td>Config.Notify = "okokNotify"</td></tr><tr><td>vms_notify</td><td>✅ Supported</td><td>Config.Notify = "vms_notify"</td></tr><tr><td>brutal_notify</td><td>✅ Supported</td><td>Config.Notify = "brutal_notify"</td></tr><tr><td>ox_lib</td><td>✅ Supported</td><td>Config.Notify = "ox_lib"</td></tr><tr><td>ESX</td><td>✅ Supported</td><td>Config.Notify = "ESX"</td></tr><tr><td>QBCORE</td><td>✅ Supported</td><td>Config.Notify = "QBCORE"</td></tr><tr><td>QBOX</td><td>✅ Supported</td><td>Config.Notify = "QBOX"</td></tr></tbody></table>

## Garages

{% hint style="warning" %}
You Can add your Garage Script in `integrations/garage/`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="201">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>op-garages</td><td>✅ Supported</td><td>--</td></tr><tr><td>jg-advancedgarages</td><td>✅ Supported</td><td>--</td></tr><tr><td>cd_garage</td><td>⚒️ Work In Progress</td><td></td></tr><tr><td>RxGarages</td><td>⚒️ Work In Progress</td><td></td></tr><tr><td>okokGarage</td><td>⚒️ Work In Progress</td><td></td></tr></tbody></table>

## Ambulance Jobs

{% hint style="warning" %}
Every Ambulance job is supported. This MDT is only an add-on for your Medic players! However, some ambulance jobs already include options to send dispatches and open the EMS MDT through their own menus. If you want to integrate a specific ambulance job script, feel free to open a ticket on our Discord server.

Additionally, please note that some creators may be restricted due to a blacklist policy. It’s strongly recommended to contact us before purchasing the script to avoid any issues.
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="201">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>p_ambulancejob</td><td>✅ Supported</td><td>Need Config changes (<a href="/scripts/op-ambulance-mdt/integrations/p_ambulancejob">read this</a>)</td></tr></tbody></table>


# p\_ambulancejob

{% stepper %}
{% step %}

### Find Directory

Go to p\_ambulancejob/shared/config.lua

Find alert function and replace it with this:

```lua
alert = function()
  if GetResourceState('piotreq_gmt') == 'started' or GetResourceState('pp-ems-mdt') == 'started' then
      TriggerServerEvent('p_ambulancejob/server/editable/alert')
      return
  end

  local coords = lib.callback.await('p_ambulancejob/server/editable/getPlyCoords', false)
  if GetResourceState('redutzu-ems') == 'started' then
      TriggerEvent('redutzu-ems:client:addDispatchToEMS', {
          code = '911',
          title = 'Alert 911',
          street = GetStreetNameFromHashKey(GetStreetNameAtCoord(coords.x, coords.y, coords.z)),
          gender = GetEntityModel(cache.ped) == `mp_f_freemode_01` and 'female' or 'male',
          duration = (5 * 60 * 60),
          coords = {x = coords.x, y = coords.y, z = coords.z}
      })
  end

  if GetResourceState('op-ambulancemdt') == 'started' then
      TriggerServerEvent('op-ambulancemdt:SendDispatchAlert', 
          "ambulance", 
          'Alert 911', 
          "A person collapsed on the sidewalk and is not responding", 
          "red", 
          "fa-solid fa-truck-medical", 
          coords
      )
  end

  if Bridge.Dispatch and Bridge.Dispatch.SendAlert then
      Bridge.Dispatch.SendAlert({
          job = {'ambulance'},
          code = '911',
          title = 'Alert',
          coords = coords,
          time = 60,
          priority = 'normal',
          icon = 'fa solid fa-skull',
          blip = {
              sprite = 153,
              color = 1,
              length = 1,
              scale = 1.1,
              name = 'Medical Emergency',
          }
      })
  else
      TriggerServerEvent('p_ambulancejob/server/alerts/new', {
          code = '911',
          title = 'Alert',
          message = 'I need help!',
          coords = coords,
          expire = 60, -- 60 seconds
          blip = {
              sprite = 153,
              color = 1,
              scale = 1.1,
              pulse = true
          }
      })
  end
end
```

{% endstep %}

{% step %}

### EMS Tablet function (optional)

If you're using radial, find Config.Radial and set emsTablet item to this:

```lua
{
    id = 'emsTablet',
    icon = 'tablet',
    label = locale('tablet'),
    onSelect = function()
        if GetResourceState('piotreq_gmt') == 'started' then
            TriggerEvent('piotreq_gmt:OpenGMT')
        elseif GetResourceState('tk_mdt') == 'started' then
            exports['tk_mdt']:openUI('ambulance')
        elseif GetResourceState('lb-tablet') == 'started' then
            TriggerEvent('tablet:toggleOpen')
        elseif GetResourceState('kartik-mdt') == 'started' then
            exports['kartik-mdt']:openMDT()
        elseif GetResourceState('qf_mdt') == 'started' then
            exports['qf_mdt']:OpenMDT()
        elseif GetResourceState('op-ambulancemdt') == 'started' then
            TriggerEvent('op-ambulancemdt:openMDT')
        else
            lib.print.error('You are not using supported MDT! Add it in config.lua')
        end
    end
},
```

{% endstep %}
{% endstepper %}


# OP Multicharacter

Most Advanced Multicharacter System for FiveM servers. Create Unique and seasonable scenes for your players, identity creator, support for multiple scripts and Frameworks!

{% embed url="<https://otherplanet.dev/product/multicharacter>" %}

{% embed url="<https://www.youtube.com/watch?v=Jt4dVY2_E28>" %}


# Installation

Before you start, make sure you're logged into the CFX portal. This ensures you can download the asset and receive future updates.

## Downloading Dependencies

Dependencies are very important — you need to install them before moving on to the installation of our script.

{% stepper %}
{% step %}

### ox\_lib

{% embed url="<https://github.com/overextended/ox_lib>" %}
{% endstep %}
{% endstepper %}

***

## Server.cfg

All dependencies you’ve installed must be started before our script. Make sure to follow the correct start-up order in your `server.cfg`.

```
ensure ox_lib
ensure op-multicharacter
```

***

## Additional Steps (Important):

{% tabs %}
{% tab title="ESX" %}

1. Go to your `es_extended`
2. Find config file located in `shared/config/main.lua`
3. Change line:

* From: `Config.Multichar = GetResourceState("esx_multicharacter") ~= "missing"`  &#x20;
* To: `Config.Multichar = true`&#x20;
  {% endtab %}

{% tab title="QBOX" %}

1. Go to your qbx\_core
2. Find config located in config/client.lua

* Change `useExternalCharacters` to true&#x20;

Before:&#x20;

<figure><img src="https://36113860-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1vEl4u37HzsUUsBP0oWM%2Fuploads%2Fv6LrNGwMSJZHO181hDDB%2Fimage.png?alt=media&amp;token=32bf13be-404d-4d99-9905-5790b6284e69" alt=""><figcaption></figcaption></figure>

After:

<figure><img src="https://36113860-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1vEl4u37HzsUUsBP0oWM%2Fuploads%2FjtHMaFccdfV70ZpddaE3%2Fimage.png?alt=media&amp;token=559ed9e0-0d08-4de8-b196-4ca3303dec63" alt=""><figcaption></figcaption></figure>

3. Find resource:  `qbx_properties`
4. Go to `qbx_properties/server/apartmentselect.lua`
5. Find event `QBCore:Server:OnPlayerLoaded`

* Replace this:

  ```lua
  RegisterNetEvent('QBCore:Server:OnPlayerLoaded', function()
      local playerSource = source --[[@as number]]
      local player = exports.qbx_core:GetPlayer(playerSource)
      local hasApartment = MySQL.single.await('SELECT * FROM properties WHERE owner = ?', {player.PlayerData.citizenid})
      if not hasApartment then
          TriggerClientEvent('apartments:client:setupSpawnUI', playerSource)
      end
  end)
  ```
* With This:

  ```lua
  RegisterNetEvent('QBCore:Server:OnPlayerLoaded', function()
      local playerSource = source --[[@as number]]
      local player = exports.qbx_core:GetPlayer(playerSource)
      local hasApartment = MySQL.single.await('SELECT * FROM properties WHERE owner = ?', {player.PlayerData.citizenid})
      if not hasApartment then
          --TriggerClientEvent('apartments:client:setupSpawnUI', playerSource)
      end
  end)
  ```

{% endtab %}

{% tab title="QBCore" %}
Only If you're using qb-clothing:

{% embed url="<https://docs.otherplanet.dev/scripts/op-multicharacter/integrations/qb-clothing>" %}
{% endtab %}
{% endtabs %}

***

## Final Step

Make sure you deleted other Multicharacter scripts from your server, like esx\_multicharacter or qb-multicharacter!


# Common Issues


# CreatePed F8 Error

Explanation and fix for the CreatePed F8 error caused by too many streamed EUP / clothing resources.

F8 Console Error Code:

```lua
^1SCRIPT ERROR: native d49f9b0955c367de: exception at address
FiveM_GTAProcess.exe+7C6A38^7
^3> CreatePed^7
(^5CreatePed.lua^7:15)
^3> fn^7 (^5@op-multicharacter/framework/client/shared.lua^7:171)
```

### ❓ What does this error mean?

If you are getting this **CreatePed F8 error**, it usually means that **your server is streaming too many separate clothing resource directories**.

FiveM has an internal limit on how many streamed clothing folders can be loaded at once.\
When this limit is exceeded, the game may crash or throw a `CreatePed` exception during character creation or spawning.

This issue is **not caused by OP Multicharacter itself**, but by the **amount of individual EUP / clothing packs** loaded on the server.

### ✅ How to fix this issue

To resolve this problem, you should **merge your clothing packs into consolidated resources**.

#### Recommended solution:

Use the [**Durty Cloth Tool**](https://gta.clothing/) to merge multiple separate clothing packs into fewer combined ones.

#### Why this works:

* Reduces the number of streamed directories
* Prevents FiveM from hitting the streaming limit

After merging the clothing packs and restarting the server, the **CreatePed error should no longer occur**, and the **multicharacter system will work normally again**.

### 📝 Important notes

* This issue can appear on **any server** with a large amount of EUP / clothing packs
* The fix applies globally and is **not script-specific**
* Most servers experiencing this issue resolve it **immediately** after merging clothing resources


# Exports

List of available exports. Client/Server side


# Server Side

Server-side list of available exports.

{% hint style="warning" %}
If you're looking for Other Data then listed Here and in Client-Side Exports - Feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

## setSlots <a href="#getorganisationslist" id="getorganisationslist"></a>

***

Allows setting a specific number of slots for a selected player. Works only when the Discord Ranks option is disabled.

```lua
exports['op-multicharacter']:setSlots(identifier, amount, byWho)
```

## isPlayerInsideMulticharacter

***

Check if player is inside Multicharacter. Returns `false/true`

```lua
exports['op-multicharacter']:isPlayerInsideMulticharacter(playerId)
```


# Client Side

Client-side list of available exports.

{% hint style="warning" %}
If you're looking for Other Data then listed Here and in Server-Side Exports - Feel free to contact us on our [Discord Server](https://discord.gg/otherplanet)!
{% endhint %}

## isPlayerInsideMulticharacter

***

Check if player is inside Multicharacter. Returns `false/true`

```lua
exports['op-multicharacter']:isPlayerInsideMulticharacter()
```


# Config Files

The script is fully configurable, allowing complete customization of its behavior and features. Below is a list of configuration files included in the resource.

{% tabs %}
{% tab title="MainConfig.lua" %}
op-multicharacter/config/MainConfig.lua

```lua
-- ─────────────────────────────────────────────────────────────────────────
-- OTHERPLANET / OP Multicharacter - Main Configuration File
-- (Information) ► This file controls language, clothing integration, currency,
--                 spawn selector, starter items, notifications and scenes.
-- (Information) ► Do not rename fields or change structure unless you know
--                 exactly how the script reads this config.
-- ─────────────────────────────────────────────────────────────────────────

Config = {}

-- ─────────────────────────────────────────────────────────────────────────
-- Locale & Debug                                                             
-- (Information) ► Locale controls which translation file is loaded.
-- (Information) ► Debug enables extra prints/logs for troubleshooting.
-- ─────────────────────────────────────────────────────────────────────────
Config.Locale = "en" -- Supported: EN / FR / HR / HU / LT / SK / SL / CZ / SV / TR / DE / EL / ES / PL
Config.Debug = false
Config.DisableBlackScreens = false
Config.DisableBugHunter = false
Config.EnableNativeBlackScreens = false -- Enabled DoScreenFadeIn and DoScreenFadeOut

-- IMPORTANT: DisableBlackScreens will disable all fade in/out animations, additional loading screens etc!

-- ─────────────────────────────────────────────────────────────────────────
-- Clothing & Tattoo System Detection                                             
-- (Information) ► Following clothing scripts will not start cutscene/spawn selector after character created: tgiann-clothing
-- ─────────────────────────────────────────────────────────────────────────

-- SUPPORTED CLOTHING SCRIPTS
-- skinchanger
-- crm-appearance
-- fivem-appearance
-- illenium-appearance
-- qb-clothing
-- rcore_clothing
-- tgiann-clothing
-- qs-appearance
-- qf_skinmenu
-- p_appearance
-- sn_appearance
-- origen_clothing

Config.Clothing = "none"

-- SUPPORTED TATTOOS SCRIPTS
-- rcore_tattoos

Config.Tattoos = "none" -- none / rcore_tattoos

-- ─────────────────────────────────────────────────────────────────────────
-- Housing System Detection                                                  
-- (Information) ► Set your housing script to give started house for player
-- ─────────────────────────────────────────────────────────────────────────

-- SUPPORTED HOUSING SCRIPTS
-- nolag_properties
-- vms_housing
-- rtx_housing

Config.Housing = "none" 

-- ─────────────────────────────────────────────────────────────────────────
-- Notify System Detection                                                  
-- (Information) ► Set your notification script
-- ─────────────────────────────────────────────────────────────────────────

-- Notify scripts:
-- okokNotify
-- vms_notify
-- ox_lib
-- brutal_notify
-- op_hud
-- ESX
-- QBCORE
-- QBOX

Config.Notify = "none"

-- ─────────────────────────────────────────────────────────────────────────
-- Currency Formatting                                                       
-- (Information) ► Visual currency format used in UI (JS Intl.NumberFormat).
-- (Information) ► Does not change actual economy, only how numbers are shown.
-- ─────────────────────────────────────────────────────────────────────────
Config.CurrencySettings = {
    -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
    currency = "USD",
    style    = "currency",
    format   = "en-US"
}

-- ─────────────────────────────────────────────────────────────────────────
-- Start Cutscene                                                            
-- (Information) ► Enables or disables the initial intro cutscene for players.
-- ─────────────────────────────────────────────────────────────────────────
Config.EnableStartCutScene = true
Config.CutSceneData = {
    welcomeTextHeader = "OTHERPLANET",
    welcomeTextDesc = "Welcome on the best server!"
}

-- ─────────────────────────────────────────────────────────────────────────
-- Player Switch Function                                                           
-- (Information) ► Enables or disables the player switch (Map zoom and zoom-out).
-- (Information) ► If you disabled Spawn Selector you can also adjust start
-- coordinates here!
-- ─────────────────────────────────────────────────────────────────────────
Config.SwitchPlayer = {
    enable = true, -- This will work only if spawn selector is disabled!
    startCoords = vec4(-1037.6252, -2737.7544, 19.1693, 326.5721), -- This will work only if spawn selector is disabled!
    disableSwitchOnNewCharacter = true, -- Disables switch on new character
}

-- ─────────────────────────────────────────────────────────────────────────
-- Spawn Selector                                                             
-- (Information) ► Controls the spawn menu that appears after character select.
-- (Information) ► "isLastLocation = true" will use last saved coordinates.
-- (Information) ► "spawnCoords = vec4(...)" defines static spawn locations.
-- ─────────────────────────────────────────────────────────────────────────
Config.SpawnSelector = {
    enable = true, 
    enableOnlyForNewCharacters = false,
    cameraDuration = 3000,
    locations = {
        ["1"] = {
            isLastLocation = true,
            spawnCoords    = false,
            label          = "Last Location",
            image          = "https://img.gta5-mods.com/q95/images/felix-s-movie-like-reshade/3d87bc-GTA52019-11-0912-35-04_Easy-Resize.com.jpg"
        },
        ["2"] = {
            isLastLocation = false,
            spawnCoords    = vec4(-268.8336, -956.3636, 30.2231, 205.1087),
            label          = "Los Santos",
            image          = "https://cdn.mos.cms.futurecdn.net/9QfCTPwV3DkPuUgBHrorom-1200-80.jpg"
        },
        ["3"] = {
            isLastLocation = false,
            spawnCoords    = vec4(-19.8265, 6497.2964, 30.5491, 51.6798),
            label          = "Paleto Bay",
            image          = "https://c4.wallpaperflare.com/wallpaper/143/671/687/grand-theft-auto-online-grand-theft-auto-v-mountain-chiliad-chiliad-mountain-state-wilderness-state-wilderness-paleto-bay-los-santos-wallpaper-preview.jpg"
        },
        ["4"] = {
            isLastLocation = false,
            spawnCoords    = vec4(1527.3490, 3774.9458, 33.5113, 193.8923),
            label          = "Sandy Shores",
            image          = "https://img.gta5-mods.com/q75/images/sandy-shores-remaster-ymap/c276b2-SandyShores1-min.png"
        },
    }
}

-- ─────────────────────────────────────────────────────────────────────────
-- Server Branding (UI Text & Logos)                                          
-- (Information) ► Controls server name, description and logo in the UI.
-- ─────────────────────────────────────────────────────────────────────────
Config.ServerDetails = {
    serverName        = "OTHERPLANET",
    serverCreatorDesc = "Create your new character and start journey on the best FiveM server.",
    serverLogo        = "https://data.otherplanet.dev/img/op-white.png"
}

-- ─────────────────────────────────────────────────────────────────────────
-- Starter Items                                                              
-- (Information) ► Optional items/money granted to new characters.
-- (Information) ► Works separately from itemShop in Config.Misc.
-- ─────────────────────────────────────────────────────────────────────────
Config.StarterItems = {
    enable = false,
    list = {
        {
            type      = "item",
            nameSpawn = "bread",
            quanity   = 3
        },
        {
            type      = "money", -- money, bank
            nameSpawn = "",      -- leave blank
            quanity   = 5000
        }
    }
}

-- ─────────────────────────────────────────────────────────────────────────
-- Miscellaneous Settings                                                     
-- (Information) ► General behaviour: notifications, character slots and
--                 optional item shop available on character creation.
-- ─────────────────────────────────────────────────────────────────────────
Config.Misc = {
    DefaultCharsAmount = 3,
    AllowCharacterDelete = true, 
    itemShop = {
        enable = true,
        money  = 6500, -- Starting budget for shop purchases
        items  = {
            {
                type      = "item",
                nameSpawn = "bread",
                label     = "Sandwich",
                price     = 500,
                maxQuanity = 5,
                image     = "https://data.otherplanet.dev/fivemicons/%5bfood%5d/hornsandwich.png",
            },
            {
                type      = "item",
                nameSpawn = "water",
                label     = "Water",
                price     = 250,
                maxQuanity = 5,
                image     = "https://data.otherplanet.dev/fivemicons/%5bfood%5d/water_bottle.png",
            },
            {
                type      = "item",
                nameSpawn = "phone",
                label     = "Phone",
                price     = 1500,
                maxQuanity = 1,
                image     = "https://data.otherplanet.dev/fivemicons/%5belectronics%5d/ifruit_blue.png",
            },
            {
                type      = "item",
                nameSpawn = "energydrink",
                label     = "Energy Drink",
                price     = 750,
                maxQuanity = 3,
                image     = "https://data.otherplanet.dev/fivemicons/%5bfood%5d/sprunk.png",
            },
            {
                type      = "car",
                nameSpawn = "blista",
                label     = "Blista",
                price     = 2000,
                maxQuanity = 1,
                image     = "https://cdn.majestic-files.com/public/master/static/img/vehicles/blista.png",
            },
            {
                type      = "car",
                nameSpawn = "bmx",
                label     = "BMX",
                price     = 800,
                maxQuanity = 1,
                image     = "https://cdn.majestic-files.com/public/master/static/img/vehicles/bmx.png",
            },
        }
    }
}

-- ─────────────────────────────────────────────────────────────────────────
-- Scenes Configuration                                                       
-- (Information) ► Defines cinematic scenes used for character selection.
-- (Information) ► Includes camera paths, characters placements, props,
--                 vehicles and weather presets per scene.
-- ─────────────────────────────────────────────────────────────────────────
Config.Scenes = {
    ["scene_1"] = {
        SceneCoords = vec4(49.2859, 7184.5527, 2.4002, 64.6467), -- Player will be teleported to this coords! This is really important.
        InitialCamTime          = 7.0,
        InitialCamCoords        = vec4(34.9546, 7199.9526, 10.2047, 248.3370),
        InitialCamLookAtCoords  = vec4(66.0174, 7188.3491, 1.4332, 59.7735),

        charactersPlacement = {
            {
                coords = vec4(67.7456, 7194.3882, 1.5501, 42.4638),
                animation = {
                    Dict = "missdocksshowoffcar@idle_a",
                    Lib  = "idle_b_5"
                },
                prop = false,
                weapon = false,
                camera = {
                    toCoords = vec4(64.6069, 7200.0630, 2.8786, 227.4351),
                    zoom     = 20.0,
                    duration = 2.0,
                },
                insideVehicle     = false,
                insideVehicleSeat = 0,
            },
            {
                coords    = vec4(66.0174, 7188.3491, 1.4332, 59.7735),
                animation = false,
                prop = false,
                weapon = false,
                camera = {
                    toCoords = vec4(61.9422, 7192.1289, 2.8725, 227.2279),
                    zoom     = 20.0,
                    duration = 3.0,
                },
                insideVehicle     = false, -- unique identifier of vehicle which you add in vehicles section.
                insideVehicleSeat = 1,       -- 1-4 Seat in vehicle
            },
            {
                coords = vec4(59.7110, 7183.2471, 1.6228, 322.9862),
                animation = {
                    Dict = "amb@world_human_drinking@coffee@male@idle_a",
                    Lib  = "idle_c"
                },
                prop = {
                    Prop = "ba_prop_battle_whiskey_bottle_2_s",
                    PropBone = 28422,
                    PropPlacement = {
                        0.0,
                        0.0,
                        0.05,
                        0.0,
                        0.0,
                        0.0
                    }
                },
                weapon = false,
                camera = {
                    toCoords = vec4(60.7500, 7188.7920, 2.8235, 170.8597),
                    zoom     = 20.0,
                    duration = 3.0,
                },
                insideVehicle     = false,
                insideVehicleSeat = 0,
            },
        },

        newCharacter = {
            -- Coords where you will create new character
            coords = vec4(-763.3376, 329.4693, 198.4861, 183.4883)
        },

        vehicles = {},

        customProps = {
            {
                name  = "xm_prop_base_tripod_lampb",
                coords = vec4(69.9330, 7188.8125, 1.1861, 274.8404),
            },
            {
                name  = "xm_prop_base_tripod_lampb",
                coords = vec4(67.5469, 7186.2432, 1.2403, 179.5323),
            },
        },
        weather = {
            hour    = 23,
            minute  = 0,
            weather = "SMOG", 
            -- EXTRASUNNY
            -- CLEAR
            -- CLOUDS
            -- SMOG
            -- FOGGY
            -- OVERCAST
            -- RAIN
            -- THUNDER
            -- CLEARING
            -- NEUTRAL
            -- SNOW
            -- BLIZZARD
            -- SNOWLIGHT
            -- XMAS
            -- HALLOWEEN
        }
    },
    --[[["cayo"] = {
        SceneCoords = vec4(4893.3608, -4903.8926, 3.4867, 174.4742), -- Player will be teleported to this coords! This is really important.
        InitialCamTime          = 7.0,
        InitialCamCoords        = vec4(4858.5122, -4922.6084, 11.4874, 220.0356),
        InitialCamLookAtCoords  = vec4(4881.5386, -4951.0298, 2.5879, 29.2787),

        charactersPlacement = {
            {
                coords = vec4(4881.5386, -4951.0298, 2.5879, 29.2787),
                animation = {
                    Dict = "anim_heist@arcade_combined@",
                    Lib  = "ped_female@_stand@_03b@_base_base"
                },
                prop = false,
                weapon = false,
                camera = {
                    toCoords = vec4(4880.2852, -4946.5171, 3.4353, 201.5674),
                    zoom     = 30.0,
                    duration = 3.0,
                },
                insideVehicle     = false,
                insideVehicleSeat = 0,
            },
            {
                coords    = vec4(4874.4858, -4952.9634, 3.2330, 359.0229),
                animation = {
                    Dict = "mrwitt@chin_support_on_floor",
                    Lib = "mrwitt"
                },
                prop = false,
                weapon = false,
                camera = {
                    toCoords = vec4(4876.6748, -4946.3887, 3.5261, 149.4190),
                    zoom     = 20.0,
                    duration = 3.0,
                },
                insideVehicle     = false, -- unique identifier of vehicle which you add in vehicles section.
                insideVehicleSeat = 0,       -- 1-4 Seat in vehicle
            }
        },
        newCharacter = {
            -- Coords where you will create new character
            coords = vec4(-763.3376, 329.4693, 198.4861, 183.4883)
        },
        vehicles = {},
        customProps = {},
        weather = {
            hour    = 12,
            minute  = 0,
            weather = "EXTRASUNNY", 
            -- EXTRASUNNY
            -- CLEAR
            -- CLOUDS
            -- SMOG
            -- FOGGY
            -- OVERCAST
            -- RAIN
            -- THUNDER
            -- CLEARING
            -- NEUTRAL
            -- SNOW
            -- BLIZZARD
            -- SNOWLIGHT
            -- XMAS
            -- HALLOWEEN
        }
    },
    --[[["xmas"] = {
        SceneCoords = vec4(173.6660, -912.0629, 31.3267, 258.4043), -- Player will be teleported to this coords! This is really important.
        InitialCamTime          = 7.0,
        InitialCamCoords        = vec4(190.3594, -941.9056, 35.9057, 47.8419),
        InitialCamLookAtCoords  = vec4(165.9147, -919.8946, 32.8227, 47.9994),

        charactersPlacement = {
            {
                coords    = vec4(163.3140, -926.8589, 30.6239, 218.1640),
                animation = {
                    Dict = "anim@scripted@player@fix_astu_ig8_weed_smoke_v1@male@",
                    Lib = "male_pos_a_p2_base"
                },
                prop = false,
                weapon = false,
                camera = {
                    toCoords = vec4(169.6110, -926.8345, 31.3267, 75.3578),
                    zoom     = 20.0,
                    duration = 3.0,
                },
                insideVehicle     = false, -- unique identifier of vehicle which you add in vehicles section.
                insideVehicleSeat = 0,       -- 1-4 Seat in vehicle
            },
            {
                coords = vec4(165.9431, -918.2198, 30.3717, 192.0681),
                animation = {
                    Dict = "anim@scripted@freemode@ig7_office_cell_floor@heeled@",
                    Lib  = "base_pose_01"
                },
                prop = false,
                weapon = false,
                camera = {
                    toCoords = vec4(168.6161, -925.0262, 31.3221, 11.7331),
                    zoom     = 50.0,
                    duration = 3.0,
                },
                insideVehicle     = false,
                insideVehicleSeat = 0,
            },
            {
                coords = vec4(173.1998, -917.3516, 30.3267, 151.3287),
                animation = {
                    Dict = "amb@world_human_aa_coffee@base",
                    Lib  = "base"
                },
                prop = {
                    Prop = "pata_christmasfood1",
                    PropBone = 28422,
                    PropPlacement = {
                        0.0100,
                        -0.1100,
                        -0.1300,
                        0.0,
                        0.0,
                        0.0
                    }
                },
                weapon = false,
                camera = {
                    toCoords = vec4(165.6446, -922.1199, 32.7531, 302.8799),
                    zoom     = 40.0,
                    duration = 3.0,
                },
                insideVehicle     = false,
                insideVehicleSeat = 0,
            },
        },
        newCharacter = {
            -- Coords where you will create new character
            coords = vec4(-763.3376, 329.4693, 198.4861, 183.4883)
        },
        vehicles = {},
        customProps = {},
        weather = {
            hour    = 21,
            minute  = 0,
            weather = "XMAS", 
            -- EXTRASUNNY
            -- CLEAR
            -- CLOUDS
            -- SMOG
            -- FOGGY
            -- OVERCAST
            -- RAIN
            -- THUNDER
            -- CLEARING
            -- NEUTRAL
            -- SNOW
            -- BLIZZARD
            -- SNOWLIGHT
            -- XMAS
            -- HALLOWEEN
        }
    },--]]
}

-- ──────────────────────────────────────────────────────────────────────────────
-- HUD Switch                                                    
-- (Information) ► Function will toggle hud when needed to provide best user experience!
-- ──────────────────────────────────────────────────────────────────────────────

Config.SwitchHud = function(toggle)
    if toggle then
        DisplayRadar(true)
        --TriggerEvent('op-hud:toggleHud', true)
        -- Display HUD
    else
        Citizen.CreateThread(function()
            Wait(1000)
            DisplayRadar(false)
            --TriggerEvent('op-hud:toggleHud', false)
            -- Hide HUD
        end)   
    end
end
```

{% endtab %}

{% tab title="ServerConfig.lua" %}
op-multicharacter/config/ServerConfig.lua

````lua
ServerConfig = {}
ServerConfig.LogsWebhook = "https://discord.com/api/webhooks/" -- Discord webhook.

ServerConfig.DiscordRanks = {
    enable = false,
    botToken = "", -- Discord Bot Token.
    serverId = "988478565790122095",
    ranks = {
        -- SCHEMA: ["role_discord_id"] = amount of available slots
        ["1438914499506733310"] = 3,
        ["1438914514279071834"] = 2,
        ["1438914530141798400"] = 5,
    }
}

ServerConfig.DisableAutoSql = false -- Enable/Disable auto sql injection for slots data!

ServerConfig.Commands = {
    logout = {
        enable = true, 
        command = "logout",
        allowed = "user"
    },
    setslots = {
        enable = true, 
        command = "setslots",
        allowed = "admin"
    },
}

ServerConfig.LogsData = {
    ['character_loaded'] = {
        color = 706333,
        header = "Player Connected",
        desc = "**Character Name:** `%s`\n**Character ID:** `%s`\n**Player Identifiers:** ```%s```"
    },
    ['character_created'] = {
        color = 706333,
        header = "Character Created",
        desc = "**Name:** `%s`\n**ID:** `%s`\n**Nationality:** `%s`\n**Birthday:** `%s`\n**Gender:** `%s`\n**Height:** `%s`\n**Player Identifiers:** ```%s```\n**Starting Items:** ```%s```"
    },
    ['character_unloaded'] = {
        color = 13044234,
        header = "Character Log Out",
        desc = "**Character Name:** `%s`\n**Character ID:** `%s`\n**Player Identifiers:** ```%s```"
    },
    ['slotsadded'] = {
        color = 706333,
        header = "Slots Added",
        desc = "**Admin:** `%s`\n**License:** `%s`\n**Amount:** `%s`"
    },
}

-- ──────────────────────────────────────────────────────────────────────────────
-- (Information) ► Formats webhook message based on LogsData entry and sends it.
-- (Information) ► Usage example:
--                 ServerConfig.formatWebHook("character_created", arg1, arg2, ...)
-- ──────────────────────────────────────────────────────────────────────────────
---@param logType string   -- key in ServerConfig.LogsData
---@param ... any          -- formatting params for desc
---@return table           -- { title = "", color = 0, message = "" }
ServerConfig.formatWebHook = function(logType, ...)
    local log = ServerConfig.LogsData[logType]
    if not log then
        print("^1[OP-MULTICHARACTER] Invalid logType: " .. tostring(logType))
        return false
    end

    local formattedDesc = string.format(log.desc, ...)

    local embed = {{
        ["color"] = log.color,
        ["title"] = log.header,
        ["description"] = formattedDesc,
        ["footer"] = { text = os.date("%c") .. " (Server Time)." }
    }}

    PerformHttpRequest(ServerConfig.LogsWebhook,
        function(err, text, headers) end,
        "POST",
        json.encode({ username = "OP Multicharacter", embeds = embed }),
        { ["Content-Type"] = "application/json" }
    )
end
````

{% endtab %}
{% endtabs %}

## Useful Articles

{% embed url="<https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks>" %}
How to create discord webhooks for Logs
{% endembed %}


# Integrations

Full List of Scripts Compatible with our Multicharacter System!

## Clothing

{% hint style="warning" %}
You Can add Your Clothing Script in `integrations/clothing/`
{% endhint %}

<table><thead><tr><th width="252">Script Name</th><th width="258">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>skinchanger</td><td>✅ Supported</td><td>Need Changes: Read <a href="/scripts/op-multicharacter/integrations/skinchanger">skinchanger integration</a></td></tr><tr><td>qb-clothing</td><td>✅ Supported</td><td>Need Changes: Read <a href="/scripts/op-multicharacter/integrations/qb-clothing">qb-clothing integration</a></td></tr><tr><td>illenium-appearance</td><td>✅ Supported</td><td>--</td></tr><tr><td>fivem-appearance</td><td>✅ Supported</td><td>--</td></tr><tr><td>17mov_CharacterSystem</td><td>✅ Supported</td><td>--</td></tr><tr><td>rcore_clothing</td><td>✅ Supported</td><td>--</td></tr><tr><td>crm-appearance</td><td>✅ Supported</td><td>--</td></tr><tr><td>tgiann-clothing</td><td>✅ Supported</td><td>--</td></tr><tr><td>p_appearance</td><td>✅ Supported</td><td>Need changes: Read <a href="/scripts/op-multicharacter/integrations/p_appearance">p_appearance integration</a></td></tr><tr><td>qf_skinmenu</td><td>✅ Supported</td><td>--</td></tr><tr><td>qs-apperance</td><td>✅ Supported</td><td>--</td></tr><tr><td>origen_clothing</td><td>✅ Supported</td><td>--</td></tr></tbody></table>

## Housing

{% hint style="warning" %}
You Can add Your Clothing Script in `integrations/server/housing/`
{% endhint %}

<table><thead><tr><th width="252">Script Name</th><th width="258">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>nolag_properties</td><td>✅ Supported</td><td>Config.Housing</td></tr><tr><td>vms_housing</td><td>✅ Supported</td><td>Config.Housing</td></tr><tr><td>rtx_housing</td><td>✅ Supported</td><td>Config.Housing</td></tr></tbody></table>

## Notify

{% hint style="warning" %}
You Can add Custom Notify Script in `integrations/client/sendNotify.lua`
{% endhint %}

<table><thead><tr><th width="208">Script Name</th><th width="149">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>op_hud</td><td>✅ Supported</td><td>--</td></tr><tr><td>okokNotify</td><td>✅ Supported</td><td>--</td></tr><tr><td>vms_notify</td><td>✅ Supported</td><td>--</td></tr><tr><td>brutal_notify</td><td>✅ Supported</td><td>--</td></tr><tr><td>ox_lib</td><td>✅ Supported</td><td>--</td></tr><tr><td>ESX</td><td>✅ Supported</td><td>--</td></tr><tr><td>QBCORE</td><td>✅ Supported</td><td>--</td></tr><tr><td>QBOX</td><td>✅ Supported</td><td>--</td></tr></tbody></table>

## Tattoos

{% hint style="warning" %}
You Can add Your Clothing Script in `integrations/tattoos/`
{% endhint %}

<table><thead><tr><th width="252">Script Name</th><th width="258">Current Status</th><th>Additional Notes</th></tr></thead><tbody><tr><td>rcore_tattoos</td><td>✅ Supported</td><td>Config.Tattoos</td></tr></tbody></table>




---

[Next Page](/llms-full.txt/1)

