# Welcome

<div align="center"><figure><img src="/files/xsuZtxX6BsiBhnMNNyRM" alt="" width="563"><figcaption></figcaption></figure></div>

## Welcome on VMS\* Documentation!

We warmly welcome you to VMS\* Store, the place where your ideas for FiveM servers come to life. We take pride in being the creators of exclusive scripts that will enhance the gaming experience on your server.

### What We Offer:

**Exclusive FiveM Scripts:** Our offering focuses on unique and professional scripts that will give your server a distinctive character. We provide both paid, advanced scripts and occasionally offer free ones to support the development of the FiveM community.

**Secure Transactions:** Shopping at VMS\* Store is safe and easy, thanks to our authorized site on tebex.io. When you buy from us, you can be sure that you are receiving high-quality products fully compliant with the principles of FiveM.

**Discord Support:** Our friendly community extends beyond the store. If you encounter any difficulties or have questions about our scripts, our experienced support team is at your disposal on our [Discord](https://discord.gg/7aRuZQxnzK).

### Why Choose VMS Store?

**Experience:** As experienced FiveM developers, we understand the needs of the community and strive to deliver products that meet the expectations of even the most demanding servers.

**Transaction Security:** All purchases are conducted safely and in accordance with tebex.io regulations. Your comfort and safety are our priorities.

{% embed url="<https://www.vames-store.com/>" fullWidth="false" %}


# Basic Server Knowledge


# How to import SQL to database?

Some scripts come with `.sql` files. These are important - they create the necessary database tables for the resource to work properly.

Here's how to import them into your server’s database:

***

#### 🧭 **Step-by-step guide**

> In this example, we'll use [**HeidiSQL**](https://www.heidisql.com/download.php), one of the most common database tools.\
> (You can also use **phpMyAdmin** or any other database tool of your choice.)

#### 💡 Tips

> * Don't import the same file twice - if you're unsure, check if the table already exists.

***

## 1. Open your database tool

Launch **HeidiSQL**, select your saved connection (you should already have it set up), and click **Open** to connect to your MySQL server.

## 2. Select your database

On the left side, you'll see a list of databases.\
Click the one your server uses (commonly named something like `fivem`, `esx_legacy`).

## 3. Open the **Query** tab

When you go to your database, the button <kbd>**▶️ Query**</kbd> is available on the header.\
A blank editor will open where you can paste SQL code.

## 4. Upload the .sql file

Open the `.sql` file (e.g. with Notepad or any code editor), **copy everything**, and **paste it into the SQL tab in HeidiSQL**.

{% hint style="info" %}
If the resource comes with multiple files (like `items.sql`, `jobs.sql`, etc.), repeat this process for each one.
{% endhint %}

## 5. Click **Execute**

Click the **Execute SQL** button at the top (▶️ play icon), or press **F9**.\
HeidiSQL will run the code and show a success message at the bottom if everything went fine.

<figure><img src="/files/63QRaCv6sEi2KXV8L6oU" alt=""><figcaption></figcaption></figure>

✅ **Done! Your database is now ready.**


# How to update server artifacts?

### Windows

{% embed url="<https://www.youtube.com/watch?ab_channel=HighwayTrooper&v=UZXnK8IjT20>" %}
Tutorial for updating artifacts on a Windows-based server
{% endembed %}

***

### Linux


# General Issues

<details>

<summary>No such export Notification in resource vms_notify</summary>

1. Open config file of the script.
2. Search for `Config.Notification`.
3. Remove `exports['vms_notify']:Notification(...)`.
4. Add trigger/export from your notification system. (If you are using a custom notification system, go to the documentation of the creator of these notifications and implement the appropriate event or export)

```lua
Config.Notification = function(message, time, type)
    if type == "success" then
        -- exports['vms_notify']:Notification("MARKETPLACE", message, time, "#36f230", "fa-solid fa-store")
        
        -- DEFAULT ESX Notification:
        TriggerEvent('esx:showNotification', message)
        
        -- DEFAULT QB-Core Notification:
        TriggerEvent('QBCore:Notify', message, "success", time)
    elseif type == "error" then
        -- exports['vms_notify']:Notification("MARKETPLACE", message, time, "#f23030", "fa-solid fa-store")
        
        -- DEFAULT ESX Notification:
        TriggerEvent('esx:showNotification', message)
	
	-- DEFAULT QB-Core Notification:
	TriggerEvent('QBCore:Notify', message, "error", time)
    elseif type == "info" then
        -- exports['vms_notify']:Notification("MARKETPLACE", message, time, "#4287f5", "fa-solid fa-store")
        
        -- DEFAULT ESX Notification:
        TriggerEvent('esx:showNotification', message)
        
        -- DEFAULT QB-Core Notification:
        TriggerEvent('QBCore:Notify', message, "primary", time)
    end
end
```

</details>

<details>

<summary>You lack the required entitlement</summary>

If you are displaying this error, it means that you do not have access to the script, if freshly after purchase you added the script to the server without restarting the server, you need to restart it because your server license key must refresh the scripts available to the server.

If the error still occurs, verify that your license key from the server matches the one on which you have purchased the scripts.

Make sure you haven't deleted the .fxap file - if there is one, download it again from keymaster and update this file

</details>

<details>

<summary>syntax error near '&#x3C;\1>'</summary>

1. Make sure you use a program other than FileZilla when installing escrow version resources - it destroys files that are closed by FiveM Escrow system, instead it use another program such as [WinSCP](https://winscp.net/eng/download.php) or [Bitvise](https://www.bitvise.com/download-area).
2. Check if your artifacts are not too old, to find out type **`version`** in the server console you will receive feedback with the version number, what you need to know is that artifacts for servers are created by the community, so some versions may turn out to be faulty, we recommend using LATEST RECOMMENDED artifacts which you can easily download using the blue button at the top of the page.\
   \
   FiveM artifacts: [**Linux**](https://runtime.fivem.net/artifacts/fivem/build_proot_linux/master/), [**Windows**](https://runtime.fivem.net/artifacts/fivem/build_server_windows/master/)

</details>

<details>

<summary>Changed key does not change</summary>

We use RegisterKeyMapping for performance purposes to dispense with the 0 tick loop so when you entered the server with the previous key, when you change the key it will not change automatically because it has already been saved to your client files, for new players it will be the key you set as the latest one.

To change the key at your game to the newly set one, you can do it in two ways:

### Changing the key in the game settings

1. Go to game settings <kbd>ESC</kbd>
2. Then enter the Key Bindings section and the FiveM
3. Find the key by resource name and change it.

### Remove the key in the game files

1. Exit FiveM
2. Navigate to the fivem.cfg file on the path `C:\Users\vames\AppData\Roaming\CitizenFX\fivem.cfg`
3. Find the lines relevant to the resource name and delete them.

</details>

<details>

<summary>When trying to open the Management Menu / Clerk Menu, I get a ban from qb-management</summary>

1. Go to your qb-management/server/sv\_boss.lua
2. Find these [marked lines](https://github.com/qbcore-framework/qb-management/blob/main/server/sv_boss.lua#L22-L25)

```lua
if not Player.PlayerData.job.isboss then
    ExploitBan(src, 'GetEmployees Exploiting')
    return
end
```

3. Remove them.

</details>

<details>

<summary>I cannot open Management Menu / Clerk Menu on qbx-core</summary>

This is normal, because qbx\_management does not have a callback by default to get a list of employees with the exact parameters we use from qb-management on qb-core.

To do this, you must register the prepared callback in your qbx\_management.

1. Go to **qbx\_management/server/main.lua**
2. Add this code in the file:

```lua
local QBCore = exports['qb-core']:GetCoreObject()
QBCore.Functions.CreateCallback('qbx_management:server:GetEmployeesForVMS', function(source, cb, jobname) 
    local employees = {}
    local players = MySQL.query.await("SELECT * FROM `players` WHERE `job` LIKE '%" .. jobname .. "%'", {})

    if players[1] ~= nil then
        for _, value in pairs(players) do
            local isOnline = QBCore.Functions.GetPlayerByCitizenId(value.citizenid)

            if isOnline and isOnline.PlayerData.job.name == jobname then
                employees[#employees + 1] = {
                    empSource = isOnline.PlayerData.citizenid,
                    grade = isOnline.PlayerData.job.grade,
                    isboss = isOnline.PlayerData.job.isboss,
                    name = '🟢 ' .. isOnline.PlayerData.charinfo.firstname .. ' ' .. isOnline.PlayerData.charinfo.lastname
                }
            elseif value.job.name == jobname then
                employees[#employees + 1] = {
                    empSource = value.citizenid,
                    grade = value.job.grade,
                    isboss = value.job.isboss,
                    name = '🔴 ' .. value.charinfo.firstname .. ' ' .. value.charinfo.lastname
                }
            end
        end
        table.sort(employees, function(a, b)
            return a.grade.level > b.grade.level
        end)
    end
    cb(employees)
end)
```

3. Save the file as well as close it
4. Go to **VMS\_RESOURCE/config/config.client.lua**
5. Find `CL.GetEmployees` function
6. Replace it with the one below:

```lua
CL.GetEmployees = function(cb, jobName)
    QBCore.Functions.TriggerCallback('qbx_management:server:GetEmployeesForVMS', function(employees)
        cb(employees)
    end, jobName)
end
```

7. Save the file as well as close it
8. Restart server

</details>


# Prepared Resources

## Compatible SkinManager's

*Recommended use of one of these resources when using the vms\_barber, vms\_tattooshop.*

<table><thead><tr><th width="183.6363525390625" align="center">Framework</th><th width="295.8182373046875">Resource Name</th><th data-type="files">Download</th></tr></thead><tbody><tr><td align="center"><strong>ESX</strong> <strong>(1.1 - 1.8.X)</strong></td><td>esx_skin &#x26; skinmanager</td><td><a href="/files/tp83IRKtRsYWERggmabH">/files/tp83IRKtRsYWERggmabH</a></td></tr><tr><td align="center"><strong>ESX (1.9.X)</strong></td><td>esx_skin &#x26; skinmanager</td><td><a href="/files/2qWfPoWs8tm2J5kU4aXc">/files/2qWfPoWs8tm2J5kU4aXc</a></td></tr><tr><td align="center"><strong>ESX (any)</strong></td><td>fivem-appearance</td><td><a href="/files/1C651QfdxhsIyQ9xervy">/files/1C651QfdxhsIyQ9xervy</a></td></tr><tr><td align="center"><strong>ESX (any)</strong></td><td>illenium-appearance</td><td><a href="/files/RJVzCD8RuE9RQXDmaIo0">/files/RJVzCD8RuE9RQXDmaIo0</a></td></tr><tr><td align="center"><strong>QB-Core (any)</strong></td><td> qb-clothing</td><td><a href="/files/dIB1EAfF2y9ozt02O6e7">/files/dIB1EAfF2y9ozt02O6e7</a></td></tr><tr><td align="center"><strong>QB-Core (any)</strong></td><td> illenium-appearance</td><td><a href="/files/4JRtZxSP5NYTpOGWCSfr">/files/4JRtZxSP5NYTpOGWCSfr</a></td></tr></tbody></table>

***

## Compatible Status

*Recommended use these resources when using the vms\_hud.*

<table><thead><tr><th width="183.54547119140625" align="center">Framework</th><th width="296.5455322265625">Resource Name</th><th data-type="files">Download</th></tr></thead><tbody><tr><td align="center"><strong>ESX (any)</strong></td><td>esx_status</td><td><a href="/files/6YBCaZ0Jfb2NHLhfbmhu">/files/6YBCaZ0Jfb2NHLhfbmhu</a></td></tr><tr><td align="center"><strong>ESX (any)</strong></td><td>esx_basicneeds</td><td><a href="/files/O8FRf4XMjtj1wS7o8a2Y">/files/O8FRf4XMjtj1wS7o8a2Y</a></td></tr></tbody></table>

***

## Compatible Society

*Recommended to use our esx\_society for vms\_vehicleshopV2/vms\_tuning if you do not use the built-in balance and if you use vms\_hud on ESX, also in is prepared to update the display of the company account.*

<table><thead><tr><th width="184.45458984375" align="center">Framework</th><th width="296.727294921875">Framework &#x26; Resource Name</th><th data-type="files">Download</th></tr></thead><tbody><tr><td align="center"><strong>ESX (any)</strong></td><td>esx_society</td><td><a href="/files/nh9Eb8TNwm7UxdDWqzqt">/files/nh9Eb8TNwm7UxdDWqzqt</a></td></tr></tbody></table>


# vms\_needs

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


# Installation

## 1. Download Resource

Download the purchased resource from [**CFX Portal**](https://portal.cfx.re/assets/granted-assets) - the official site of FiveM with purchased resources.

***

## 2. Import Database Tables

This is a very important step - without it, the script will not work properly.\
Depending on the framework you are using (ESX or QB-Core), select the appropriate section below and **paste the SQL code into your database**.

{% hint style="warning" %}

## Not sure how to do it?

No worries - we've prepared a short guide that shows you step by step how to import an SQL file into your database:\
👉 [**Click here to view the tutorial.**](/helpful/basic-server-knowledge/how-to-import-sql-to-database)
{% endhint %}

<details>

<summary>Database for ESX</summary>

```sql
ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `status` longtext DEFAULT NULL;

CREATE TABLE IF NOT EXISTS `needs` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `item` varchar(50) NOT NULL,
    `model` varchar(50) NOT NULL,
    `position` longtext NOT NULL,
    `bucket` int(11) DEFAULT 0,
    `metadata` longtext DEFAULT NULL,
    `createdAt` bigint(20) DEFAULT NULL,
    `createdBy` varchar(100) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
```

</details>

<details>

<summary>Database for QB-Core</summary>

```sql
ALTER TABLE `players` ADD COLUMN IF NOT EXISTS `status` longtext DEFAULT NULL;

CREATE TABLE IF NOT EXISTS `needs` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `item` varchar(50) NOT NULL,
    `model` varchar(50) NOT NULL,
    `position` longtext NOT NULL,
    `bucket` int(11) DEFAULT 0,
    `metadata` longtext DEFAULT NULL,
    `createdAt` bigint(20) DEFAULT NULL,
    `createdBy` varchar(100) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
```

</details>

***

## 3. Add Items

The script uses its own items.\
Depending on what inventory you are using, select the appropriate section and add these items to either your item file or database.

<details>

<summary>Items for ox_inventory</summary>

{% code expandable="true" %}

```lua
['sandwich'] = {
    label = "Sandwich",
    weight = 80,
    stack = false,
    allowArmed = false,
    degrade = 720,
},
['burger'] = {
    label = 'Burger',
    weight = 120,
    stack = false,
    allowArmed = false,
    degrade = 360,
},
['hotdog'] = {
    label = "Hot-Dog",
    weight = 100,
    stack = false,
    allowArmed = false,
    degrade = 360,
},
['apple'] = {
    label = "Apple",
    weight = 60,
    stack = false,
    allowArmed = false,
    degrade = 720,
},
['water'] = {
    label = 'Water',
    weight = 500,
    stack = false,
    allowArmed = false,
    degrade = 4320,
},
['sprunk_can'] = {
    label = "Sprunk Can",
    weight = 330,
    stack = false,
    allowArmed = false,
    degrade = 1080,
},
['ecola_can'] = {
    label = "e-Cola Can",
    weight = 330,
    stack = false,
    allowArmed = false,
    degrade = 1080,
},
['junk_can'] = {
    label = "Junk Can",
    weight = 500,
    stack = false,
    allowArmed = false,
    degrade = 1080,
},
['patriot_beer'] = {
    label = "Patriot Beer",
    weight = 500,
    stack = false,
    allowArmed = false,
},
['logger_beer'] = {
    label = "Logger Beer",
    weight = 500,
    stack = false,
    allowArmed = false,
},
['piswasser_beer'] = {
    label = "Piswasser Beer",
    weight = 500,
    stack = false,
    allowArmed = false,
},
['themount_whiskey'] = {
    label = "The Mount Whiskey",
    weight = 750,
    stack = false,
    allowArmed = false,
},
['bleuterd_champagne'] = {
    label = "Champagne",
    weight = 750,
    stack = false,
    allowArmed = true
},
['bleuterd_champagne2'] = {
    label = "Champagne",
    weight = 750,
    stack = false,
    allowArmed = true
},
['vodka'] = {
    label = "Vodka",
    weight = 700,
    stack = false,
    allowArmed = true
},
['glass'] = {
    label = "Glass",
    weight = 300,
    stack = false,
    allowArmed = false,
},
['whisky_glass'] = {
    label = "Whisky Glass",
    weight = 400,
    stack = false,
    allowArmed = false,
},
['shot_glass'] = {
    label = "Shot Glass",
    weight = 180,
    stack = false,
    allowArmed = false,
},
['champ_flute'] = {
    label = "Champagne Flute",
    weight = 280,
    stack = false,
    allowArmed = false,
},
['lighter'] = {
    label = "Lighter",
    weight = 20,
    stack = true,
    allowArmed = false
},
['redwood_pack'] = {
    label = "Redwood Pack",
    weight = 100,
    stack = false,
    allowArmed = false
},
['cigarette'] = {
    label = "Cigarette",
    weight = 5,
    stack = true,
    allowArmed = false
},
['codeine'] = {
    label = "Codeine",
    weight = 100,
    stack = false,
    allowArmed = false
},
['joint_sd'] = {
    label = "Sour Diesel Joint",
    weight = 5,
    stack = true,
    allowArmed = false
},
['joint_nl'] = {
    label = "Northern Lights Joint",
    weight = 5,
    stack = true,
    allowArmed = false
},
['cocaine'] = {
    label = "Cocaine Bag",
    weight = 10,
    stack = true,
    allowArmed = false
},
['amphetamine'] = {
    label = "Amphetamine Bag",
    weight = 10,
    stack = true,
    allowArmed = false
},
['meth'] = {
    label = "Methamphetamine Bag",
    weight = 10,
    stack = true,
    allowArmed = false
},
['heroin'] = {
    label = "Heroin",
    weight = 10,
    stack = true,
    allowArmed = false
},
['heroin_syringe'] = {
    label = "Heroin Syringe",
    weight = 10,
    stack = false,
    allowArmed = false
},
['lsd'] = {
    label = "LSD",
    weight = 5,
    stack = false,
    allowArmed = false
},
['breathalyzer'] = {
    label = "Breathalyzer",
    weight = 420,
    stack = true,
    allowArmed = false
},
['drugtest_thc'] = {
    label = "Drug Test (THC)",
    weight = 150,
    stack = false,
    allowArmed = false
},
['drugtest_lsd'] = {
    label = "Drug Test (LSD)",
    weight = 150,
    stack = false,
    allowArmed = false
},
['drugtest_multi'] = {
    label = "Drug Test Multi",
    weight = 200,
    stack = false,
    allowArmed = false
},
```

{% endcode %}

</details>

<details>

<summary>Items for qb-inventory / origen_inventory</summary>

{% code expandable="true" %}

```lua
['sandwich'] = {
    name = 'sandwich',
    label = "Sandwich",
    description = "",
    weight = 80,
    type = "item",
    image = "sandwich.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['burger'] = {
    name = 'burger',
    label = "Burger",
    description = "",
    weight = 120,
    type = "item",
    image = "burger.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['hotdog'] = {
    name = 'hotdog',
    label = "Hot-Dog",
    description = "",
    weight = 100,
    type = "item",
    image = "hotdog.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['apple'] = {
    name = 'apple',
    label = "Apple",
    description = "",
    weight = 60,
    type = "item",
    image = "apple.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['water'] = {
    name = 'water',
    label = "Water",
    description = "",
    weight = 500,
    type = "item",
    image = "water.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['sprunk_can'] = {
    name = 'sprunk_can',
    label = "Sprunk Can",
    description = "",
    weight = 330,
    type = "item",
    image = "sprunk_can.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['ecola_can'] = {
    name = 'ecola_can',
    label = "e-Cola Can",
    description = "",
    weight = 330,
    type = "item",
    image = "ecola_can.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['junk_can'] = {
    name = 'junk_can',
    label = "Junk Can",
    description = "",
    weight = 500,
    type = "item",
    image = "junk_can.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['patriot_beer'] = {
    name = 'patriot_beer',
    label = "Patriot Beer",
    description = "",
    weight = 500,
    type = "item",
    image = "patriot_beer.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['logger_beer'] = {
    name = 'logger_beer',
    label = "Logger Beer",
    description = "",
    weight = 500,
    type = "item",
    image = "logger_beer.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['piswasser_beer'] = {
    name = 'piswasser_beer',
    label = "Piswasser Beer",
    description = "",
    weight = 500,
    type = "item",
    image = "piswasser_beer.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['themount_whiskey'] = {
    name = 'themount_whiskey',
    label = "The Mount Whiskey",
    description = "",
    weight = 750,
    type = "item",
    image = "themount_whiskey.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['bleuterd_champagne'] = {
    name = 'bleuterd_champagne',
    label = "Champagne",
    description = "",
    weight = 750,
    type = "item",
    image = "bleuterd_champagne.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['bleuterd_champagne2'] = {
    name = 'bleuterd_champagne2',
    label = "Champagne",
    description = "",
    weight = 750,
    type = "item",
    image = "bleuterd_champagne2.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['vodka'] = {
    name = 'vodka',
    label = "Vodka",
    description = "",
    weight = 700,
    type = "item",
    image = "vodka.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['glass'] = {
    name = 'glass',
    label = "Glass",
    description = "",
    weight = 300,
    type = "item",
    image = "glass.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['whisky_glass'] = {
    name = 'whisky_glass',
    label = "Whisky Glass",
    description = "",
    weight = 400,
    type = "item",
    image = "whisky_glass.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['shot_glass'] = {
    name = 'shot_glass',
    label = "Shot Glass",
    description = "",
    weight = 180,
    type = "item",
    image = "shot_glass.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['champ_flute'] = {
    name = 'champ_flute',
    label = "Champagne Flute",
    description = "",
    weight = 280,
    type = "item",
    image = "champ_flute.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['lighter'] = {
    name = 'lighter',
    label = "Lighter",
    description = "",
    weight = 20,
    type = "item",
    image = "lighter.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['redwood_pack'] = {
    name = 'redwood_pack',
    label = "Redwood Pack",
    description = "",
    weight = 100,
    type = "item",
    image = "redwood_pack.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['cigarette'] = {
    name = 'cigarette',
    label = "Cigarette",
    description = "",
    weight = 5,
    type = "item",
    image = "cigarette.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['codeine'] = {
    name = 'codeine',
    label = "Codeine",
    description = "",
    weight = 100,
    type = "item",
    image = "codeine.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['joint_sd'] = {
    name = 'joint_sd',
    label = "Sour Diesel Joint",
    description = "",
    weight = 5,
    type = "item",
    image = "joint_sd.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['joint_nl'] = {
    name = 'joint_nl',
    label = "Northern Lights Joint",
    description = "",
    weight = 5,
    type = "item",
    image = "joint_nl.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['cocaine'] = {
    name = 'cocaine',
    label = "Cocaine Bag",
    description = "",
    weight = 10,
    type = "item",
    image = "cocaine.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['amphetamine'] = {
    name = 'amphetamine',
    label = "Amphetamine Bag",
    description = "",
    weight = 10,
    type = "item",
    image = "amphetamine.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['meth'] = {
    name = 'meth',
    label = "Methamphetamine Bag",
    description = "",
    weight = 10,
    type = "item",
    image = "meth.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['heroin'] = {
    name = 'heroin',
    label = "Heroin",
    description = "",
    weight = 10,
    type = "item",
    image = "heroin.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['heroin_syringe'] = {
    name = 'heroin_syringe',
    label = "Heroin Syringe",
    description = "",
    weight = 10,
    type = "item",
    image = "heroin_syringe.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['lsd'] = {
    name = 'lsd',
    label = "LSD",
    description = "",
    weight = 5,
    type = "item",
    image = "lsd.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['breathalyzer'] = {
    name = 'breathalyzer',
    label = "Breathalyzer",
    description = "",
    weight = 420,
    type = "item",
    image = "breathalyzer.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['drugtest_thc'] = {
    name = 'drugtest_thc',
    label = "Drug Test (THC)",
    description = "",
    weight = 150,
    type = "item",
    image = "drugtest_thc.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['drugtest_lsd'] = {
    name = 'drugtest_lsd',
    label = "Drug Test (LSD)",
    description = "",
    weight = 150,
    type = "item",
    image = "drugtest_lsd.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
['drugtest_multi'] = {
    name = 'drugtest_multi',
    label = "Drug Test Multi",
    description = "",
    weight = 200,
    type = "item",
    image = "drugtest_multi.png",
    unique = false,
    useable = true,
    shouldClose = true,
    combinable = nil,
},
```

{% endcode %}

</details>

***

## 4. Remove Conflicting Resources

Before starting **VMS Needs**, make sure to remove or disable any resources that provide the same functionality.

Running multiple needs/status systems at the same time may cause conflicts, duplicated effects, or unexpected behavior.

**Common resources that should be removed:**

* `esx_status`
* `esx_basicneeds`
* `qb-smallresources` *(*&#x63;onsumables.lua fil&#x65;*)*
* Any custom hunger / thirst / stress / needs system
* Any standalone consumables resource that modifies player needs

<details>

<summary><strong>⚠️  Additional Step for qb-core (Required)</strong></summary>

You must remove the built-in hunger/thirst handling from `qb-core` to prevent conflicts with VMS Needs.

Remove the following code from: `qb-core/client/loops.lua`&#x20;

```lua
CreateThread(function()
    while true do
        if LocalPlayer.state.isLoggedIn then
            if (QBCore.PlayerData.metadata['hunger'] <= 0 or QBCore.PlayerData.metadata['thirst'] <= 0) and not (QBCore.PlayerData.metadata['isdead'] or QBCore.PlayerData.metadata['inlaststand']) then
                local ped = PlayerPedId()
                local currentHealth = GetEntityHealth(ped)
                local decreaseThreshold = math.random(5, 10)
                SetEntityHealth(ped, currentHealth - decreaseThreshold)
            end
        end
        Wait(QBCore.Config.StatusInterval)
    end
end)
```

</details>

<details>

<summary><strong>⚠️ Additional Step for qbx_core (Required)</strong></summary>

You must remove the built-in hunger/thirst handling from `qbx_core` to prevent conflicts with VMS Needs.

Remove the following code from: `qbx_core/client/loops.lua`&#x20;

```lua
CreateThread(function()
    local timeout = 1000 * statusInterval
    while true do
        Wait(timeout)

        if QBX.IsLoggedIn then
            if ((playerState.hunger or 0) <= 0 or (playerState.thirst or 0) <= 0) and not playerState.isDead then
                local currentHealth = GetEntityHealth(cache.ped)
                local decreaseThreshold = math.random(5, 10)
                SetEntityHealth(cache.ped, currentHealth - decreaseThreshold)
            end
        end
    end
end)
```

Remove the following code from: `qbx_core/server/loops.lua`&#x20;

```lua
local function removeHungerAndThirst(src, player)
    local playerState = Player(src).state
    if not playerState.isLoggedIn then return end
    local newHunger = playerState.hunger - config.player.hungerRate
    local newThirst = playerState.thirst - config.player.thirstRate

    player.Functions.SetMetaData('thirst', newThirst)
    player.Functions.SetMetaData('hunger', newHunger)

    player.Functions.Save()
end

CreateThread(function()
    local interval = 60000 * config.updateInterval
    while true do
        Wait(interval)
        forEachPlayerStaggered(removeHungerAndThirst)
    end
end)
```

</details>

{% hint style="warning" %}
**Important:**\
VMS Needs is designed to fully replace these systems. Running them together is **not supported**.
{% endhint %}

***

## 6. Start Resource

To start a resource in your `server.cfg`, ensure that it begins after your framework has been initiated. For instance, if you are using a framework like `es_extended`, you should start resource after it, like so:

```python
start [core] # For example, here is your framework (esx/qb-core)

# Here you run your other resources like esx_policejob etc.

# VMS Resources
start vms_needs
```

Ensure there are no syntax errors or incorrect paths in your `server.cfg`.


# Compatibility

<table data-view="cards"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td>✅ <strong>Officially Supported</strong></td><td>Veified compatibility and built in both resources.</td><td></td></tr><tr><td>✅ <strong>Supported</strong></td><td>Veified compatibility but possible code modifications.</td><td></td></tr><tr><td>⚠️ <strong>Supported</strong></td><td>Compatibility according to code from the resource, but has not been tested.</td><td></td></tr><tr><td>🚫 <strong>Not Supported</strong></td><td>Unsupported resource, you cannot use this resource compatibility.</td><td></td></tr><tr><td><strong>🕛 Soon...</strong></td><td>We plan to introduce compatibility soon.</td><td></td></tr></tbody></table>

## Migration:

{% hint style="success" %}

### Custom Migration

If you need migration from a different status script that is not listed, please open a ticket on our Discord and let us know.

We'll need the SQL structure of your current status system in order to evaluate and prepare a custom migration solution.
{% endhint %}

{% hint style="info" %}
**This migration converts your existing player status data to the format used by VMS Needs.**

It transfers supported statuses (such as hunger and thirst) from supported resources into the VMS Needs structure, allowing players to keep their current values after installation.

Unsupported resources or custom status formats are not migrated and may require manual adjustments.
{% endhint %}

<table><thead><tr><th width="128.4908447265625">Resource</th><th width="223.1636962890625">Is compatible</th><th>Notes</th></tr></thead><tbody><tr><td><strong>esx_status</strong></td><td>✅  <strong>Officially Supported</strong></td><td><p>Converts the <code>status</code> column to the format used by <code>vms_needs</code>, preserving <code>hunger</code> and <code>thirst</code> values.</p><p>If you need to migrate additional statuses, you can customize the migration files.</p></td></tr><tr><td><strong>qb-core</strong></td><td>✅  <strong>Officially Supported</strong></td><td>Converts player metadata into the <code>status</code> column format used by <code>vms_needs</code>, preserving <code>hunger</code> and <code>thirst</code>.</td></tr><tr><td><strong>qbx_core</strong></td><td>✅  <strong>Officially Supported</strong></td><td>Converts player metadata into the <code>status</code> column format used by <code>vms_needs</code>, preserving <code>hunger</code> and <code>thirst</code>.</td></tr></tbody></table>

## Weather:

{% hint style="info" %}

### Custom Integration

If you don't find your weather system in the list below, don't worry!

You can manually enter events that update the time and weather in your weather system - [you'll find the events here](/assets/vms_needs/developer-api/server-events).
{% endhint %}

{% hint style="success" %}
Weather integration is used by the VMS Needs temperature system to calculate the player's body temperature based on current weather conditions.
{% endhint %}

<table><thead><tr><th width="318.0909423828125">Resource</th><th width="193.6363525390625">Is compatible</th><th align="center">Tutorial</th></tr></thead><tbody><tr><td><strong>vSync</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/5a8uwCALG0JL38glXYmz">/pages/5a8uwCALG0JL38glXYmz</a></td></tr><tr><td><strong>qb-weathersync</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/AWgHGVcvvvIXNP3nV6HD">/pages/AWgHGVcvvvIXNP3nV6HD</a></td></tr><tr><td><strong>qbx_weathersync</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/AWgHGVcvvvIXNP3nV6HD">/pages/AWgHGVcvvvIXNP3nV6HD</a></td></tr><tr><td><strong>cd_easytime</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/EWlTpa6DQGFxGU45ofAo">/pages/EWlTpa6DQGFxGU45ofAo</a></td></tr><tr><td><strong>Renewed-Weathersync</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/zsD3Lccy9ug06xrhNCIn">/pages/zsD3Lccy9ug06xrhNCIn</a></td></tr></tbody></table>

## Inventory:

{% hint style="info" %}

### Custom Integration

If you don't find your inventory in the list below, don't worry!

In the **./integration/\[inventory]** you can customize the exports.
{% endhint %}

<table><thead><tr><th width="365">Resource</th><th>Is compatible</th></tr></thead><tbody><tr><td><strong>ox_inventory</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>qb-inventory</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>ps-inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>tgiann-inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>codem-inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>origen_inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>core_inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>jaksam_inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>one_inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr></tbody></table>

## Menu:

{% hint style="info" %}

### Custom Integration

If you don't find your menu in the list below, don't worry!

In the **./integration/\[menu]** you can customize the exports.
{% endhint %}

<table><thead><tr><th width="365">Resource</th><th>Is compatible</th></tr></thead><tbody><tr><td><strong>esx_context</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>ox_lib</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>lation_ui</strong></td><td>✅ <strong>Officially Supported</strong></td></tr></tbody></table>

## Phones:

{% hint style="info" %}

#### Integration

The phone integrations are solely intended to display the correct temperature on the phone.
{% endhint %}

<table><thead><tr><th width="243.54541015625">Resource</th><th width="287.4544677734375">Temperature</th><th align="center">Tutorial</th></tr></thead><tbody><tr><td><a href="https://store.lbphone.com/"><strong>lb-phone</strong></a></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/suAKcibCx8OYOPIQIk7A">/pages/suAKcibCx8OYOPIQIk7A</a></td></tr></tbody></table>

## Ambulance Systems:

<table><thead><tr><th width="243.54541015625">Resource</th><th width="237.0545654296875">Temperature</th><th align="center">Tutorial</th></tr></thead><tbody><tr><td><strong>esx_ambulancejob</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/Uh6HIWmjn8OYwDSTpMzO">/pages/Uh6HIWmjn8OYwDSTpMzO</a></td></tr><tr><td><strong>qb-ambulancejob</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/k1wKzX8w9QIbX8dwDUMU">/pages/k1wKzX8w9QIbX8dwDUMU</a></td></tr><tr><td><strong>qbx_medical</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/rYRMDHQVhWm9IQ91PiEl">/pages/rYRMDHQVhWm9IQ91PiEl</a></td></tr><tr><td><strong>wasabi_ambulance</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/zdsybUhcjQxo6QRZtdUk">/pages/zdsybUhcjQxo6QRZtdUk</a></td></tr><tr><td><strong>wasabi_ambulance_v2</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/5tYm2bLWwfIJbAn5dEie">/pages/5tYm2bLWwfIJbAn5dEie</a></td></tr><tr><td><strong>ars_ambulancejob</strong></td><td>⚠️ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/K38yCmRMXYt9h71mMupF">/pages/K38yCmRMXYt9h71mMupF</a></td></tr><tr><td><strong>tk_ambulancejob</strong></td><td>⚠️ <strong>Supported</strong></td><td align="center">-</td></tr></tbody></table>

## Huds:

<table><thead><tr><th width="243.54541015625">Resource</th><th width="287.4544677734375">Temperature</th><th align="center">Tutorial</th></tr></thead><tbody><tr><td><strong>vms_hud</strong></td><td>✅ <strong>Officially Supported</strong></td><td align="center">-</td></tr><tr><td><strong>esx_hud</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/8g23RA3mTbsAkp6vhd85">/pages/8g23RA3mTbsAkp6vhd85</a></td></tr><tr><td><strong>qb-hud</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/GFF7qAVpvmBzyFze6v12">/pages/GFF7qAVpvmBzyFze6v12</a></td></tr><tr><td><strong>qbx_hud</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/93gX0uxXCLExdVa6ebxO">/pages/93gX0uxXCLExdVa6ebxO</a></td></tr><tr><td><strong>jg-hud</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/uteg7iNRj9zAipw8WFsq">/pages/uteg7iNRj9zAipw8WFsq</a></td></tr><tr><td><strong>tuff-hud</strong></td><td>⚠️ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/N4Of6XsgFtPTIaLsqnBD">/pages/N4Of6XsgFtPTIaLsqnBD</a></td></tr></tbody></table>


# Weather


# vSync

{% hint style="warning" %}
Only add the highlighted lines. Do not replace the existing vSync code.
{% endhint %}

***

#### 1. Open the file

Open the following file: **vSync/vs\_server.lua**

#### 2. Synchronize weather and time

Find the `vSync:requestSync` event and add the highlighted lines inside it.

<pre class="language-lua"><code class="lang-lua">RegisterServerEvent('vSync:requestSync')
AddEventHandler('vSync:requestSync', function()
    -- Your existing code...

    -- ===== VMS Needs =====
<strong>    local hour = math.floor(((baseTime+timeOffset)/60)%24)
</strong><strong>    local minute = math.floor((baseTime+timeOffset)%60)
</strong><strong>    TriggerEvent('vms_needs:temperature:update', CurrentWeather, hour, minute)
</strong>    -- =====================
end)
</code></pre>

#### 3. Synchronize time updates

Find the thread responsible for updating the in-game time and add the highlighted lines inside it.

<pre class="language-lua"><code class="lang-lua">Citizen.CreateThread(function()
    while true do
        Citizen.Wait(5000)
        TriggerClientEvent('vSync:updateTime', -1, baseTime, timeOffset, freezeTime)
        
        -- ===== VMS Needs =====
<strong>        local hour = math.floor(((baseTime+timeOffset)/60)%24)
</strong><strong>        local minute = math.floor((baseTime+timeOffset)%60)
</strong><strong>        TriggerEvent('vms_needs:temperature:updateTime', hour, minute)
</strong>        -- =====================
    end
end)
</code></pre>

#### 4. Synchronize weather updates

Find the thread responsible for updating the weather and add the highlighted line inside it.

<pre class="language-lua"><code class="lang-lua">Citizen.CreateThread(function()
    while true do
        Citizen.Wait(300000)
        TriggerClientEvent('vSync:updateWeather', -1, CurrentWeather, blackout)

        -- ===== VMS Needs =====
<strong>        TriggerEvent('vms_needs:temperature:updateWeather', CurrentWeather)
</strong>        -- =====================
    end
end)
</code></pre>

#### 5. Initiate the first synchronization after the server starts up

Add the following thread at the end of the file.

```lua
Citizen.CreateThread(function()
    Citizen.Wait(10000)
    
    local hour = math.floor(((baseTime+timeOffset)/60)%24)
    local minute = math.floor((baseTime+timeOffset)%60)
    TriggerEvent('vms_needs:temperature:update', CurrentWeather, hour, minute)
end)
```


# qb/qbx weathersync

{% hint style="warning" %}
Only add the highlighted lines. Do not replace the existing qb-weathersync code.
{% endhint %}

***

#### 1. Open the file

Open the following file: **qb-weathersync/server.lua**

#### 2. Synchronize weather and time

Find the `qb-weathersync:server:RequestStateSync` event and add the highlighted lines inside it.

<pre class="language-lua"><code class="lang-lua">RegisterNetEvent('qb-weathersync:server:RequestStateSync', function()
    -- Your existing code...

    -- ===== VMS Needs =====
<strong>    local hour = math.floor(((baseTime+timeOffset)/60)%24)
</strong><strong>    local minute = math.floor((baseTime+timeOffset)%60)
</strong><strong>    TriggerEvent('vms_needs:temperature:update', CurrentWeather, hour, minute)
</strong>    -- =====================
end)
</code></pre>

#### 3. Synchronize time updates

Find the thread responsible for updating the in-game time and add the highlighted lines inside it.

<pre class="language-lua"><code class="lang-lua">CreateThread(function()
    while true do
        Wait(2000)
        TriggerClientEvent('qb-weathersync:client:SyncTime', -1, baseTime, timeOffset, freezeTime)
        
        -- ===== VMS Needs =====
<strong>        local hour = math.floor(((baseTime+timeOffset)/60)%24)
</strong><strong>        local minute = math.floor((baseTime+timeOffset)%60)
</strong><strong>        TriggerEvent('vms_needs:temperature:updateTime', hour, minute)
</strong>        -- =====================
    end
end)
</code></pre>

#### 4. Synchronize weather updates

Find the thread responsible for updating the weather and add the highlighted line inside it.

<pre class="language-lua"><code class="lang-lua">CreateThread(function()
    while true do
        Wait(300000)
        TriggerClientEvent('qb-weathersync:client:SyncWeather', -1, CurrentWeather, blackout)
        
        -- ===== VMS Needs =====
<strong>        TriggerEvent('vms_needs:temperature:updateWeather', CurrentWeather)
</strong>        -- =====================
    end
end)
</code></pre>

#### 5. Initiate the first synchronization after the server starts up

Add the following thread at the end of the file.

```lua
Citizen.CreateThread(function()
    Citizen.Wait(10000)
    
    local hour = math.floor(((baseTime+timeOffset)/60)%24)
    local minute = math.floor((baseTime+timeOffset)%60)
    TriggerEvent('vms_needs:temperature:update', CurrentWeather, hour, minute)
end)
```


# cd\_easytime

***

#### 1. Open the file

Open the following file: **cd\_easytime/server/server.lua**

#### 2. Synchronize weather and time

Add the following thread at the end of the file.

```lua
CreateThread(function()
    local lastWeather
    local lastHour
    local lastMinute

    Wait(10000)
    
    while true do
        Wait(5000)

        local weather = self.weather
        local hour = self.hours
        local minute = self.mins

        if weather ~= lastWeather or hour ~= lastHour or minute ~= lastMinute then
            lastWeather = weather
            lastHour = hour
            lastMinute = minute

            TriggerEvent('vms_needs:temperature:update', weather, hour, minute)
        end
    end
end)
```


# Renewed-Weathersync

***

#### 1. Open the file

Open the following file: **Renewed-Weathersync/server/weather.lua**

#### 2. Synchronize weather and time

Add the following thread at the end of the file.

```lua
CreateThread(function()
    local lastWeather
    local lastHour
    local lastMinute

    Wait(10000)
    
    while true do
        Wait(5000)

        local weather = GlobalState.weather
        local time = GlobalState.currentTime

        if weather.weather ~= lastWeather or time.hour ~= lastHour or time.minute ~= lastMinute then
            lastWeather = weather.weather
            lastHour = time.hour
            lastMinute = time.minute

            TriggerEvent('vms_needs:temperature:update', weather.weather, time.hour, time.minute)
        end
    end
end)
```


# Phones


# lb-phone

{% hint style="info" %}
This replaces the built-in random temperature generation with the real ambient and feels-like temperatures provided by VMS Needs.
{% endhint %}

{% hint style="warning" %}
Only replace the highlighted code. Leave the rest of the callback unchanged.
{% endhint %}

***

#### 1. Open the file

Open the following file: **lb-phone/client/apps/default/weather.lua**

#### 2. Replace the Temperature Calculation

Find the following code:

```lua
local temperature = math.random(weatherData.temperature[1], weatherData.temperature[2])
weatherData.temperature = temperature

if windSpeed > 4 and temperature < 50 then
    -- calculation from https://www.weather.gov/media/epz/wxcalc/windChill.pdf
    local windSpeedMph = windSpeed * 2.236936
    local feelsLike = (35.74 + (0.6215 * temperature) - (35.75 * windSpeedMph ^ 0.16) + (0.4275 * temperature * windSpeedMph ^ 0.16))

    weatherData.feelsLike = math.min(math.floor(feelsLike), temperature)
end
```

Replace it with:

```lua
local worldTemp = exports['vms_needs']:GetServerTemperature('fahrenheit')
local feelsLike = exports['vms_needs']:GetServerFeelsLikeTemperature('fahrenheit')

weatherData.temperature = worldTemp
weatherData.feelsLike = feelsLike
```


# Ambulance Job


# esx\_ambulancejob

{% hint style="warning" %}
Only add the highlighted lines. Do not replace the existing esx\_ambulancejob code.
{% endhint %}

***

#### 1. Open the file

Open the following file: **esx\_ambulancejob/client/main.lua**

#### 2. Restore Visual Effects After Revive

Find the `esx_ambulancejob:revive` event and add the highlighted lines inside it.

{% hint style="info" %}
This restores the active VMS Needs visual effect after the ambulance job removes or replaces the current screen effect. Player statuses, alcohol, and substance effects are not reset.
{% endhint %}

<pre class="language-lua"><code class="lang-lua">RegisterNetEvent('esx_ambulancejob:revive')
AddEventHandler('esx_ambulancejob:revive', function()
    -- Your existing code...

    -- ===== VMS Needs =====
<strong>    if GetResourceState('vms_needs') == 'started' then
</strong><strong>        exports['vms_needs']:ReloadVisualEffect()
</strong><strong>    end
</strong>    -- =====================
end)
</code></pre>

#### 3. Restore Player Statuses After Hospital Respawn

Find the `RemoveItemsAfterRPDeath` function and add the highlighted lines inside it.

{% hint style="info" %}
This restores hunger and thirst to at least 50% after a hospital respawn and clears the configured alcohol and substance statuses.
{% endhint %}

<pre class="language-lua"><code class="lang-lua">function RemoveItemsAfterRPDeath()
    TriggerServerEvent('esx_ambulancejob:setDeathStatus', false)

    CreateThread(function()
        ESX.TriggerServerCallback('esx_ambulancejob:removeItemsAfterRPDeath', function()
            -- Your existing code...
    
            -- ===== VMS Needs =====
<strong>            if GetResourceState('vms_needs') == 'started' then
</strong><strong>                local hunger = exports['vms_needs']:GetPercentageStatus('hunger')
</strong><strong>                if hunger &#x3C; 50.0 then
</strong><strong>                    exports['vms_needs']:SetStatus('hunger', 250.0) -- 250.0 = half
</strong><strong>                end
</strong><strong>                
</strong><strong>                local thirst = exports['vms_needs']:GetPercentageStatus('thirst')
</strong><strong>                if thirst &#x3C; 50.0 then
</strong><strong>                    exports['vms_needs']:SetStatus('thirst', 250.0) -- 250.0 = half
</strong><strong>                end
</strong><strong>                
</strong><strong>                exports['vms_needs']:SetStatus('stress', 0.0)
</strong><strong>                
</strong><strong>                exports['vms_needs']:CleanupOptionalStatuses()
</strong><strong>            end
</strong>            -- =====================
        end)
    end)
end
</code></pre>


# qb-ambulancejob

{% hint style="warning" %}
Only add the highlighted lines. Do not replace the existing qb-ambulancejob code.
{% endhint %}

***

#### 1. Open the file

Open the following file: **qb-ambulancejob/client/main.lua**

#### 2. Restore Visual Effects After Heal

Find the `ResetPartial` function and add the highlighted lines inside it.

{% hint style="info" %}
This restores the active VMS Needs visual effect after the ambulance job removes or replaces the current screen effect. Player statuses, alcohol, and substance effects are not reset.
{% endhint %}

<pre class="language-lua"><code class="lang-lua">function ResetPartial()
    -- Your existing code...

    -- ===== VMS Needs =====
<strong>    if GetResourceState('vms_needs') == 'started' then
</strong><strong>        exports['vms_needs']:ReloadVisualEffect()
</strong><strong>    end
</strong>    -- =====================
end
</code></pre>

#### 3. Restore Player Statuses After Revive

Find the `ResetAll` function and add the highlighted lines inside it.

{% hint style="info" %}
This restores hunger and thirst to at least 50% after a hospital respawn and clears the configured alcohol and substance statuses.
{% endhint %}

<pre class="language-lua"><code class="lang-lua">local function ResetAll()
    -- Your existing code...
    
    -- ===== VMS Needs =====
<strong>    if GetResourceState('vms_needs') == 'started' then
</strong><strong>        local hunger = exports['vms_needs']:GetPercentageStatus('hunger')
</strong><strong>        if hunger &#x3C; 50.0 then
</strong><strong>            exports['vms_needs']:SetStatus('hunger', 250.0) -- 250.0 = half
</strong><strong>        end
</strong><strong>        
</strong><strong>        local thirst = exports['vms_needs']:GetPercentageStatus('thirst')
</strong><strong>        if thirst &#x3C; 50.0 then
</strong><strong>            exports['vms_needs']:SetStatus('thirst', 250.0) -- 250.0 = half
</strong><strong>        end
</strong><strong>        
</strong><strong>        exports['vms_needs']:SetStatus('stress', 0.0)
</strong><strong>                
</strong><strong>        exports['vms_needs']:CleanupOptionalStatuses()
</strong><strong>    end
</strong>    -- =====================
end
</code></pre>


# qbx\_medical

{% hint style="warning" %}
Only add the highlighted lines. Do not replace the existing qbx\_medical code.
{% endhint %}

***

#### 1. Open the file

Open the following file: **qbx\_medical/client/main.lua**

#### 2. Restore Visual Effects After Heal

Find the `resetMinorInjuries` function and add the highlighted lines inside it.

{% hint style="info" %}
This restores the active VMS Needs visual effect after the ambulance job removes or replaces the current screen effect. Player statuses, alcohol, and substance effects are not reset.
{% endhint %}

<pre class="language-lua"><code class="lang-lua">local function resetMinorInjuries()
    -- Your existing code...

    -- ===== VMS Needs =====
<strong>    if GetResourceState('vms_needs') == 'started' then
</strong><strong>        exports['vms_needs']:ReloadVisualEffect()
</strong><strong>    end
</strong>    -- =====================
end
</code></pre>

#### 3. Restore Player Statuses After Revive

Find the `resetAllInjuries` function and add the highlighted lines inside it.

{% hint style="info" %}
This restores hunger and thirst to at least 50% after a hospital respawn and clears the configured alcohol and substance statuses.
{% endhint %}

<pre class="language-lua"><code class="lang-lua">local function resetAllInjuries()
    -- Your existing code...
    
    -- ===== VMS Needs =====
<strong>    if GetResourceState('vms_needs') == 'started' then
</strong><strong>        local hunger = exports['vms_needs']:GetPercentageStatus('hunger')
</strong><strong>        if hunger &#x3C; 50.0 then
</strong><strong>            exports['vms_needs']:SetStatus('hunger', 250.0) -- 250.0 = half
</strong><strong>        end
</strong><strong>        
</strong><strong>        local thirst = exports['vms_needs']:GetPercentageStatus('thirst')
</strong><strong>        if thirst &#x3C; 50.0 then
</strong><strong>            exports['vms_needs']:SetStatus('thirst', 250.0) -- 250.0 = half
</strong><strong>        end
</strong><strong>        
</strong><strong>        exports['vms_needs']:SetStatus('stress', 0.0)
</strong><strong>                
</strong><strong>        exports['vms_needs']:CleanupOptionalStatuses()
</strong><strong>    end
</strong>    -- =====================
end
</code></pre>


# wasabi\_ambulance

***

#### 1. Open the file

Open the following file: **wasabi\_ambulance/game/client/cl\_customize.lua**

Find `wasabi_ambulance:resetStatus` event and replace it.

{% hint style="info" %}
This restores hunger and thirst to at least 50%.
{% endhint %}

```lua
RegisterNetEvent('wasabi_ambulance:resetStatus', function() -- Set your own custom hunger/thirst reset logic?
    if GetResourceState('vms_needs') == 'started' then
        local hunger = exports['vms_needs']:GetPercentageStatus('hunger')
        if hunger < 50.0 then
            exports['vms_needs']:SetStatus('hunger', 250.0)
        end
        
        local thirst = exports['vms_needs']:GetPercentageStatus('thirst')
        if thirst < 50.0 then
            exports['vms_needs']:SetStatus('thirst', 250.0)
        end
        
        exports['vms_needs']:SetStatus('stress', 0.0)
    end
end)
```


# wasabi\_ambulance\_v2

***

#### 1. Open the file

Open the following file: **wasabi\_ambulance\_v2/bridge/framework/*****YOUR\_FRAMEWORK*****/server.lua**

Find `bridge.resetPlayerNeeds` function and replace it.

{% hint style="info" %}
This restores hunger and thirst to at least 50%.
{% endhint %}

```lua
function bridge.resetPlayerNeeds(source)
    -- ===== VMS Needs =====
    if GetResourceState('vms_needs') == 'started' then
        local hunger = exports['vms_needs']:GetPercentageStatus(source, 'hunger')
        if hunger < 50.0 then
            exports['vms_needs']:SetStatus(source, 'hunger', 250.0)
        end
        
        local thirst = exports['vms_needs']:GetPercentageStatus(source, 'thirst')
        if thirst < 50.0 then
            exports['vms_needs']:SetStatus(source, 'thirst', 250.0)
        end
        
        exports['vms_needs']:SetStatus(source, 'stress', 0.0)
    end
    -- =====================
end
```


# ars\_ambulancejob

***

#### 1. Open the file

Open the following file: **ars\_ambulancejob/modules/compatibility/frameworks/*****YOUR\_FRAMEWORK*****/client.lua**

#### 2. Restore Player Statuses After Revive

Find the `Framework.healStatus` function and add the highlighted lines inside it.

{% hint style="info" %}
This restores hunger and thirst to at least 50% after a hospital respawn and clears the configured alcohol and substance statuses.
{% endhint %}

<pre class="language-lua"><code class="lang-lua">function Framework.healStatus()
    -- ===== VMS Needs =====
<strong>    if GetResourceState('vms_needs') == 'started' then
</strong><strong>        local hunger = exports['vms_needs']:GetPercentageStatus('hunger')
</strong><strong>        if hunger &#x3C; 50.0 then
</strong><strong>            exports['vms_needs']:SetStatus('hunger', 250.0) -- 250.0 = half
</strong><strong>        end
</strong><strong>                
</strong><strong>        local thirst = exports['vms_needs']:GetPercentageStatus('thirst')
</strong><strong>        if thirst &#x3C; 50.0 then
</strong><strong>            exports['vms_needs']:SetStatus('thirst', 250.0) -- 250.0 = half
</strong><strong>        end
</strong><strong>        
</strong><strong>        exports['vms_needs']:SetStatus('stress', 0.0)
</strong><strong>                
</strong><strong>        exports['vms_needs']:CleanupOptionalStatuses()
</strong><strong>    end
</strong>    -- =====================
end
</code></pre>


# Hud


# esx\_hud

{% hint style="info" %}
No changes are required. Simply set `Config.ESXStatusOnTick = true` in the vms\_needs  `config.lua`.

However, for the best performance and immediate status updates, we recommend applying the changes below to your `esx_hud`.
{% endhint %}

***

#### 1. Open the file

Open the following file: **esx\_hud/client/player/status.lua**

#### 2. Remove unnecessary code

Find the `esx_status:onTick` event and delete the entire block `AddEventHandler`.

#### 2. Add status change listeners

Register both `AddStateBagChangeHandler` instances so that they can listen for status changes.

```lua
AddStateBagChangeHandler("hunger", ("player:%s"):format(GetPlayerServerId(ESX.playerId)), function(_, _, percentage)
    values.foodBar = percentage
end)

AddStateBagChangeHandler("thirst", ("player:%s"):format(GetPlayerServerId(ESX.playerId)), function(_, _, percentage)
    values.drinkBar = percentage
end)
```

#### 3. Enter the required code

Find the `HUD:StatusThread` function and add the highlighted lines inside it.

<pre class="language-lua"><code class="lang-lua">function HUD:StatusThread()
    values = {}
    CreateThread(function()
        while ESX.PlayerLoaded do
            local oxygen, stamina = 0, 0

            stamina = math.floor(100 - GetPlayerSprintStaminaRemaining(ESX.playerId))
            if stamina == 0 then
                stamina = 1
            end
            if stamina == 100 then
                stamina = 0
            end

            if IsPedSwimmingUnderWater(ESX.PlayerData.ped) then
                oxygen = math.floor(GetPlayerUnderwaterTimeRemaining(ESX.playerId) * 10)
            end
                
<strong>            values.healthBar = math.floor((GetEntityHealth(ESX.PlayerData.ped) - 100) / 100 * 100)
</strong><strong>
</strong><strong>            values.armorBar = GetPedArmour(ESX.PlayerData.ped)
</strong>
            values.oxygenBar = oxygen or 0
            values.staminaBar = stamina
            SendNUIMessage({ type = "STATUS_HUD", value = values })
            Wait(250)
        end
    end)
end
</code></pre>


# qb-hud

***

#### 1. Open the file

Open the following file: **qb-hud/client.lua**

#### 2. Remove unnecessary code

Find the `hud:client:UpdateNeeds` event and delete the entire block `RegisterNetEvent`.

Find the `hud:client:UpdateStress` event and delete the entire block `RegisterNetEvent`.

#### 3. Add status change listeners

Register both `AddStateBagChangeHandler` instances so that they can listen for status changes.

```lua
AddStateBagChangeHandler("hunger", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
    hunger = percentage
end)

AddStateBagChangeHandler("thirst", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
    thirst = percentage
end)

AddStateBagChangeHandler("stress", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
    stress = percentage
end)
```


# qbx\_hud

***

#### 1. Open the file

Open the following file: **qbx\_hud/client/main.lua**

#### 2. Remove unnecessary code

Find the `hud:client:UpdateNeeds` event and delete the entire block `RegisterNetEvent`.

Find the `hud:client:UpdateStress` event and delete the entire block `RegisterNetEvent`.

#### 3. Modify code

Find the `QBCore:Client:OnPlayerLoaded` event and modify the highlighted lines inside it.

<pre class="language-lua"><code class="lang-lua">RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
    Wait(2000)
    local hudSettings = GetResourceKvpString('hudSettings')
    if hudSettings then loadSettings(json.decode(hudSettings)) end
<strong>    stress = exports['vms_needs']:GetPercentageStatus('stress')
</strong><strong>    hunger = exports['vms_needs']:GetPercentageStatus('hunger')
</strong><strong>    thirst = exports['vms_needs']:GetPercentageStatus('thirst')
</strong>    hp = QBX.PlayerData.metadata.health
end)
</code></pre>

Find the `startWeaponStressThread` function and modify the highlighted line inside it.

<pre class="language-lua"><code class="lang-lua">local function startWeaponStressThread(weapon)
    if isWhitelistedWeaponStress(weapon) then return end
    hasWeapon = true

    CreateThread(function()
        while hasWeapon do
            if IsPedShooting(cache.ped) then
                if math.random() &#x3C;= config.stress.chance then
<strong>                    exports['vms_needs']:AddStatus('stress', math.random(1, 5))
</strong>                end
            end
            Wait(0)
        end
    end)
end
</code></pre>

Find the Speeding thread and modify the highlighted line inside it.

<pre class="language-lua"><code class="lang-lua">if config.stress.enableStress then
    CreateThread(function() -- Speeding
        while true do
            if LocalPlayer.state.isLoggedIn then
                if cache.vehicle then
                    local vehClass = GetVehicleClass(cache.vehicle)
                    local speed = GetEntitySpeed(cache.vehicle) * speedMultiplier

                    if vehClass ~= 13 and vehClass ~= 14 and vehClass ~= 15 and vehClass ~= 16 and vehClass ~= 21 then
                        local stressSpeed
                        if vehClass == 8 then
                            stressSpeed = config.stress.minForSpeeding
                        else
                            stressSpeed = LocalPlayer.state?.seatbelt and config.stress.minForSpeeding or config.stress.minForSpeedingUnbuckled
                        end
                        if speed >= stressSpeed then
<strong>                            exports['vms_needs']:AddStatus('stress', math.random(1, 3))
</strong>                        end
                    end
                end
            end
            Wait(10000)
        end
    end)
end
</code></pre>


# jg-hud

***

#### 1. Open the file

Open the following file: **jg-hud/framework/cl-functions.lua**

#### 2. Modify code

Find the `Framework.Client.GetPlayerHunger`, `Framework.Client.GetPlayerThirst`, `Framework.Client.GetPlayerStress` functions and replace the highlighted lines inside it.

<pre class="language-lua"><code class="lang-lua">function Framework.Client.GetPlayerHunger()
<strong>  return LocalPlayer.state.hunger or false
</strong>end

function Framework.Client.GetPlayerThirst()
<strong>  return LocalPlayer.state.thirst or false
</strong>end

function Framework.Client.GetPlayerStress()
<strong>  return LocalPlayer.state.stress or false
</strong>end
</code></pre>


# tuff-hud

***

#### 1. Open the file

Open the following file: **tuff-hud/client/bridge/framework/*****YOUR\_FRAMEWORK*****/client.lua**

#### 2. Remove unnecessary code

{% tabs %}
{% tab title="qb-core" %}
Find the `QBCoreBridge:ApplyPlayerData` function and delete the entire section on hunger, thirst, and stress.

```lua
if PlayerData.metadata then
    if PlayerData.metadata.hunger ~= nil then Client.PlayerData.Hunger = math.floor(PlayerData.metadata.hunger) end
    if PlayerData.metadata.thirst ~= nil then Client.PlayerData.Thrist = math.floor(PlayerData.metadata.thirst) end
    if not Settings.DisableStress and Settings.FrameworkSystems and PlayerData.metadata.stress ~= nil then
        Client.PlayerData.Stress = math.floor(PlayerData.metadata.stress)
    end
end
```

{% endtab %}

{% tab title="qbx\_core" %}
Find the `QboxBridge:ApplyPlayerData` function and delete the entire section on hunger, thirst, and stress.

```lua
if PlayerData.metadata then
    if PlayerData.metadata.hunger ~= nil then
        Client.PlayerData.Hunger = math.floor(PlayerData.metadata.hunger)
    end
    
    if PlayerData.metadata.thirst ~= nil then
        Client.PlayerData.Thrist = math.floor(PlayerData.metadata.thirst)
    end
    
    if not Settings.DisableStress and Settings.FrameworkSystems and PlayerData.metadata.stress ~= nil then
        Client.PlayerData.Stress = math.floor(PlayerData.metadata.stress)
    end
end
```

{% endtab %}
{% endtabs %}

#### 3. Modify code

{% tabs %}
{% tab title="esx" %}
Find the `ESXBridge:StartStatusListener` function and replace with the following:

```lua
function ESXBridge:StartStatusListener(Client, Settings, refreshPlayerData)
    AddStateBagChangeHandler("hunger", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
        Client.PlayerData.Hunger = percentage
    end)

    AddStateBagChangeHandler("thirst", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
        Client.PlayerData.Thrist = percentage
    end)

    AddStateBagChangeHandler("stress", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
        Client.PlayerData.Stress = percentage
    end)
end
```

{% endtab %}

{% tab title="qb-core" %}
Find the `QBCoreBridge:StartStatusListener` function and replace with the following:

```lua
function QBCoreBridge:StartStatusListener(Client, Settings, refreshPlayerData)
    AddStateBagChangeHandler("hunger", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
        Client.PlayerData.Hunger = percentage
    end)

    AddStateBagChangeHandler("thirst", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
        Client.PlayerData.Thrist = percentage
    end)

    AddStateBagChangeHandler("stress", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
        Client.PlayerData.Stress = percentage
    end)
end
```

{% endtab %}

{% tab title="qbx\_core" %}
Find the `QboxBridge:StartStatusListener` function and replace with the following:

```lua
function QboxBridge:StartStatusListener(Client, Settings, refreshPlayerData)
    AddStateBagChangeHandler("hunger", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
        Client.PlayerData.Hunger = percentage
    end)

    AddStateBagChangeHandler("thirst", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
        Client.PlayerData.Thrist = percentage
    end)

    AddStateBagChangeHandler("stress", ("player:%s"):format(GetPlayerServerId(PlayerId())), function(_, _, percentage)
        Client.PlayerData.Stress = percentage
    end)
end
```

{% endtab %}
{% endtabs %}


# FAQ

Below you’ll find answers to the most frequently asked questions related to setup, usage, and integration. We recommend checking this section before opening a support ticket.

***

<details>

<summary>Does VMS Needs require a weather sync resource?</summary>

Yes. The temperature system requires synchronized weather and in-game time. Ready-to-use integrations are available in the documentation.

</details>

<details>

<summary>Can I use VMS Needs without the temperature system?</summary>

Yes. You can disable the temperature system in the configuration if you don't need it.

</details>

<details>

<summary>Does VMS Needs use esx_status?</summary>

No. VMS Needs includes its own built-in status system.

However, for backward compatibility, it supports the events commonly used by `esx_status`.&#x20;

We still recommend migrating to the native VMS Needs status system to ensure full compatibility and avoid potential issues.

</details>

<details>

<summary>Can I create my own statuses?</summary>

Yes. You can create unlimited custom statuses in `Config.Statuses`.

</details>

<details>

<summary>Can I create custom drugs?</summary>

Yes. Every effect, status, and drug test category can be configured independently.

</details>

<details>

<summary>Can I create my own food and drinks?</summary>

Yes. All usable items are fully configurable through `Config.Items`.

</details>

<details>

<summary>Why is my body temperature not changing immediately?</summary>

Body temperature changes gradually based on the ambient temperature, clothing insulation, and weather conditions.

</details>

<details>

<summary>Why isn't the temperature system working?</summary>

Make sure your weather resource is integrated correctly. The temperature system requires synchronized weather and in-game time. Ready-to-use integrations are available in the documentation.

</details>

<details>

<summary>Can multiple effects be active at the same time?</summary>

Yes. Effects are blended together based on the current active statuses.

</details>

<details>

<summary>Can I migrate from another needs system?</summary>

Yes. You can synchronize your existing hunger and thirst statuses using the provided exports.

</details>

<details>

<summary>Can I use only selected features?</summary>

Yes. VMS Needs is fully modular. You can enable only the systems you need, such as temperature, alcohol, cigarettes, drugs, or food, without using the entire feature set.

</details>


# Common Errors

Solutions to frequently encountered errors

In this section, you will find answers to commonly encountered issues. Remember that most errors stem from incorrect configuration, lack of required resources, or insufficient modification.

Bear in mind that troubleshooting requires patience and precision. Try to carefully analyze the errors and systematically review possible solutions.

If the issue persists after verifying the configuration and available resources, please contact the support on the VMS Discord.

***

<details>

<summary>Why don't I see the visual effects of alcohol or drugs after a revive?</summary>

</details>


# Guides

On the sub-pages of the Guides section, you will find a variety of guides to help you better understand and use the available features and tools.


# Creating a Custom Item

This guide explains how to create your own usable items and configure their behavior.

***

### 1. Create a new item

Every usable item must be registered inside `Config.Items`.\
The table key must match the item name registered in your inventory.

```lua
['my_item'] = {

}
```

### 2. Choose an Item Type

The `type` option defines the item category used by VMS Needs and other configurable systems, such as item accidents.

```lua
type = 'drink',
```

VMS Needs includes several default types, such as `food`, `drink`, `glass`, `drug`, `cig`, `cigarette_pack`, `breathalyzer`, and `drugtest`.

These values are not restricted - you can create your own item types when implementing custom behavior.

### 3. Choose a Behavior (optional)

The `behavior` option assigns custom interaction logic to the item.

```lua
behavior = 'smoking',
```

Simple items such as standard food and drinks usually do not require a behavior. Advanced items, including cigarettes, powder drugs, syringes, breathalyzers, and drug tests, use behavior files to control their interaction flow.

Built-in behaviors are located in: **shared/behaviors/**

Behavior files are open and can be modified or used as templates for completely custom item interactions. The selected behavior determines which model, attachment, animation, and particle keys are required.

### 4. Configure the Item Properties

<details>

<summary>Size</summary>

Defines the total quantity or capacity stored by the item.

```lua
size = 500,
```

The unit depends on the item:

* drinks and glasses usually use milliliters,
* food can use grams or a custom portion size,
* cigarettes and joints commonly use a durability value,
* single-use drugs may use `1`.

</details>

<details>

<summary>Smell Profile (Optional)</summary>

Assigns a smell profile to the item.

```lua
smellProfile = 'alcohol',
```

The value must match a profile defined in `Config.SmellProfiles`. When liquids are mixed, smell detection is calculated using their contribution to the total composition.

</details>

<details>

<summary>Required Items (Optional)</summary>

Defines additional inventory items required to use or prepare the item.

```lua
requiredItems = {
    {name = 'lighter', quantity = 1, remove = false},
}
```

<table><thead><tr><th width="180.3636474609375">Option</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td>Inventory item name.</td></tr><tr><td><code>quantity</code></td><td>Required amount.</td></tr><tr><td><code>remove</code></td><td>Whether the required item is removed when used.</td></tr></tbody></table>

Requirements are validated before the interaction begins.

</details>

<details>

<summary>Player Restrictions</summary>

Defines which player actions are disabled while the item is active.

```lua
disableRun = true,
disableJump = true,
disableMelee = true,
disableAsDriver = true,
disableInVehicle = true,
```

<table><thead><tr><th width="180.3636474609375">Option</th><th>Description</th></tr></thead><tbody><tr><td><code>disableRun</code></td><td>Prevents sprinting.</td></tr><tr><td><code>disableJump</code></td><td>Prevents jumping.</td></tr><tr><td><code>disableMelee</code></td><td>Prevents melee attacks.</td></tr><tr><td><code>disableAsDriver</code></td><td>Prevents use while driving.</td></tr><tr><td><code>disableInVehicle</code></td><td>Prevents use inside any vehicle.</td></tr></tbody></table>

</details>

<details>

<summary>Models</summary>

Defines the GTA models used during the interaction.

```lua
models = {
    default = 'prop_ld_flow_bottle'
},
```

The required keys depend on the selected behavior. A simple item may only use `default`, while advanced behaviors can require entries such as `lighter`, `powder`, `line`, `card`, `syringe`, or custom nested model states.

```lua
models = {
    [0] = 'prop_cs_shot_glass',
    [5] = 'ba_prop_battle_shot_glass_01',
}
```

Numeric keys represent the minimum fill or usage percentage at which a model becomes active.

```lua
lit = {
    [0] = 'vms_cigarette_1',
    [30] = 'vms_cigarette_2',
    [60] = 'vms_cigarette_3',
    [80] = 'prop_cs_ciggy_01b'
}
```

This allows the visible model to change as the item is consumed.

</details>

<details>

<summary>Attachments</summary>

Defines how a model is attached to a player bone.

```lua
attaches = {
    hand = {
        bone = 57005,
        offset = vector3(0.124, 0.024, -0.023),
        rotation = vector3(-87.04, 0.0, -10.24),
    },
},
```

<table><thead><tr><th width="180.3636474609375">Option</th><th>Description</th></tr></thead><tbody><tr><td><code>bone</code></td><td>GTA ped bone ID.</td></tr><tr><td><code>offset</code></td><td>Position offset relative to the bone.</td></tr><tr><td><code>rotation</code></td><td>Rotation relative to the bone.</td></tr></tbody></table>

Attachment names are referenced by behaviors and can be freely extended in custom behavior files.

</details>

<details>

<summary>Animations</summary>

Defines animations used at different stages of the interaction.

```lua
animation = {
    idle = {
        dictionary = 'amb@code_human_wander_drinking@male@base',
        name = 'static',
        duration = -1,
        flag = 51
    },
}
```

<table><thead><tr><th width="180.3636474609375">Option</th><th>Description</th></tr></thead><tbody><tr><td><code>dictionary</code></td><td>Animation dictionary.</td></tr><tr><td><code>name</code></td><td>Animation clip name.</td></tr><tr><td><code>duration</code></td><td>Duration in milliseconds. Use <code>-1</code> for looping animations.</td></tr><tr><td><code>flag</code></td><td>GTA animation flag.</td></tr></tbody></table>

</details>

<details>

<summary>Particles (Optional)</summary>

Defines named particle effects available to the behavior.

```lua
particles = {
    fire = {
        dictionary = 'scr_fm_mp_missioncreator',
        name = 'scr_sh_lighter_flame',
        rotation = vector3(0.0, 0.0, 0.0),
        scale = 1.5,
        time = 1500
    },
}
```

<table><thead><tr><th width="180.3636474609375">Option</th><th>Description</th></tr></thead><tbody><tr><td><code>dictionary</code></td><td>Particle asset dictionary.</td></tr><tr><td><code>name</code></td><td>Particle effect name.</td></tr><tr><td><code>boneIndex</code></td><td>Optional ped bone used for attachment.</td></tr><tr><td><code>rotation</code></td><td>Local particle rotation.</td></tr><tr><td><code>scale</code></td><td>Particle size.</td></tr><tr><td><code>time</code></td><td>Duration in milliseconds.</td></tr></tbody></table>

Particle entry names are referenced by the behavior. Do not rename built-in entries unless the behavior is updated accordingly.

</details>

<details>

<summary>Consumption</summary>

Defines how much of the item is consumed during each use and how player statuses are modified.

#### Bite size

```lua
consume = {
    bite = {
        minimum = 40,
        maximum = 90
    },
}
```

Each use consumes a random amount between `minimum` and `maximum`, limited by the item's remaining size.

#### Adding statuses

```lua
add = {
    thirst = {18, 45},
    drunk = {0.6, 1.2},
}
```

#### Removing statuses

```lua
remove = {
    stress = {1.6, 3.5},
}
```

Numeric values apply a fixed status change.\
A `{minimum, maximum}` range is scaled according to the consumed amount relative to `consume.bite.maximum`.

```lua
thirst = 45
```

Always adds 45.

```lua
thirst = {18, 45}
```

Produces a proportional value between 18 and 45, depending on the amount consumed.

</details>

<details>

<summary>Allowed Drinks (Glass Items Only)</summary>

Defines which configured drinks can be poured into the glass.

```lua
allowedDrinks = {
    water = true,
    sprunk_can = true,
    patriot_beer = true,
}
```

Each key must match an item name in `Config.Items`.

</details>

<details>

<summary>Give Item (Pack Items)</summary>

Defines the item received when taking a unit from a pack.

```lua
giveItem = 'cigarette',
```

The pack’s `size` determines how many units remain.

</details>

<details>

<summary>Drug Test Category</summary>

Assigns a test category to a single-category drug test.

```lua
testCategory = 'LSD',
```

The value must match a category defined in `Config.DrugTests.Categories`.

</details>

### 5. Behavior-Specific Properties

{% hint style="info" %}
**Important:** Some properties inside `models`, `attaches`, `animation`, and `particles` are behavior-specific. Always use an existing item with the same behavior as your starting template. When creating a custom behavior, you decide which keys are required and how they are used.
{% endhint %}

### 6. Complete Example

{% code expandable="true" %}

```lua
['my_energy_drink'] = {
    type = 'drink',
    smellProfile = 'sweet',

    size = 330,

    disableRun = true,
    disableJump = true,
    disableMelee = true,
    disableAsDriver = true,
    disableInVehicle = true,

    models = {
        default = 'prop_ecola_can'
    },

    attaches = {
        hand = {
            bone = 57005,
            offset = vector3(0.12, 0.02, -0.02),
            rotation = vector3(-87.0, 0.0, -10.0),
        },
    },

    animation = {
        idle = {
            dictionary = 'amb@code_human_wander_drinking@male@base',
            name = 'static',
            duration = -1,
            flag = 51
        },
        bite = {
            dictionary = 'amb@code_human_wander_drinking_fat@male@idle_a',
            name = 'idle_c',
            duration = 2000,
            flag = 51
        },
    },

    consume = {
        bite = {
            minimum = 30,
            maximum = 60
        },
        add = {
            thirst = {12, 28},
        },
        remove = {
            stress = {0, 1},
        },
    }
}
```

{% endcode %}


# Creating a Custom Item Behavior

Item behaviors allow you to create completely custom interaction logic for items registered in `Config.Items`.

A behavior can define available controls, spawned objects, placement support, placed-object actions, primary interactions, custom actions, and cancellation logic.

Behavior files are located in: **shared/behaviors/**

After creating a behavior, register it using `RegisterBehavior` and assign its name to the item’s `behavior` option.

***

### 1. Create the Behavior

```lua
local CustomBehavior = {}
```

A behavior is a Lua table containing the methods used by VMS Needs during the item interaction lifecycle.

<table><thead><tr><th width="195.39990234375">Method</th><th width="106.60009765625">Required</th><th>Purpose</th></tr></thead><tbody><tr><td><code>GetControls</code></td><td>No</td><td>Defines the available interaction controls and item description.</td></tr><tr><td><code>PrimaryAction</code></td><td>No</td><td>Handles the primary <code>use</code> action.</td></tr><tr><td><code>HandleAction</code></td><td>No</td><td>Handles additional named controls.</td></tr><tr><td><code>GetObjects</code></td><td>No</td><td>Defines which objects should be spawned and attached.</td></tr><tr><td><code>CanPlace</code></td><td>No</td><td>Determines whether the item can currently be placed.</td></tr><tr><td><code>PlacedActions</code></td><td>No</td><td>Adds actions to an item after it has been placed.</td></tr><tr><td><code>CancelAction</code></td><td>No</td><td>Defines what happens when the player cancels item usage.</td></tr></tbody></table>

Only implement the methods required by your custom interaction.

### 2. GetControls

```lua
function CustomBehavior:GetControls()
    local controls = {
        {
            key = 'E',
            label = 'Primary Action',
            name = 'use',
        },
        {
            key = 'G',
            label = 'Custom Action',
            name = 'example_control',
        },
    }

    local description = 'Example description'

    return controls, description
end
```

<details>

<summary>Returned values</summary>

<table><thead><tr><th width="135">Value</th><th>Description</th></tr></thead><tbody><tr><td><code>controls</code></td><td>List of controls available for the current item.</td></tr><tr><td><code>description</code></td><td>Optional text displayed alongside the controls.</td></tr></tbody></table>

</details>

<details>

<summary>Control fields</summary>

<table><thead><tr><th width="134.9998779296875">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>key</code></td><td>Keyboard key displayed and listened for by the controls system.</td></tr><tr><td><code>label</code></td><td>Text displayed to the player. Use <code>TRANSLATE(...)</code> for localized labels.</td></tr><tr><td><code>name</code></td><td>Internal action identifier passed to the behavior.</td></tr></tbody></table>

</details>

The action named `use` calls `PrimaryAction`. Other action names are passed to `HandleAction`.

### 3. PrimaryAction

`PrimaryAction` is called when the player activates the control with the action name `use`.

```lua
function CustomBehavior:PrimaryAction()
    print('Player used the primary action')
end
```

For consumable items, you can call the internal `ConsumeItem` function to use the configured animations and status changes.

{% code expandable="true" %}

```lua
function CustomBehavior:PrimaryAction()
    ConsumeItem(function(action)
        if action == 'started' then
            print('Consumption started')
        elseif action == 'finished' then
            print('Consumption finished')
        end
    end)
end
```

{% endcode %}

### 4. HandleAction

`HandleAction` receives all named controls other than the primary `use` action.

```lua
function CustomBehavior:HandleAction(action)
    if action == 'example_control' then
        print('Player used the custom action')
    end
end
```

The `action` value matches the `name` configured in `GetControls`.

### 5. GetObjects

`GetObjects` defines the objects spawned while the item is being used.

{% code expandable="true" %}

```lua
function CustomBehavior:GetObjects(data)
    local item = Config.Items[data.name]

    return {
        {
            model = item.models.default,
            attach = item.attaches.hand,
        },
    }
end
```

{% endcode %}

<details>

<summary>Parameters</summary>

<table><thead><tr><th width="184.5999755859375">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>data.name</code></td><td>Current item name.</td></tr><tr><td><code>data.metadata</code></td><td>Current item metadata.</td></tr><tr><td><code>data.visualState</code></td><td>Optional visual state requested by the behavior.</td></tr></tbody></table>

</details>

<details>

<summary>Returned object fields</summary>

<table><thead><tr><th width="184.5999755859375">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>model</code></td><td>Model name or hash to spawn.</td></tr><tr><td><code>attach</code></td><td>Attachment configuration used for the object.</td></tr></tbody></table>

</details>

<details>

<summary>Advanced Example - Dynamic Model</summary>

{% code expandable="true" %}

```lua
function CustomBehavior:GetObjects(data)
    local item = Config.Items[data.name]
    local visualState = data.visualState or 'hand'

    local model

    if data.metadata.isLit then
        model = Object.GetVisualModel(
            item.models.joint.lit,
            data.metadata.value,
            item.size
        )
    else
        model = item.models.joint.unlit
    end

    return {
        {
            model = model,
            attach = item.attaches[visualState],
        },
    }
end
```

{% endcode %}

This example changes the model according to item metadata and remaining item size.

</details>

### 6. CanPlace

`CanPlace` determines whether the currently used item can be placed in the world.

```lua
function CustomBehavior:CanPlace()
    return true
end
```

Return `true` to allow placement or `false` to prevent it.

### 7. PlacedActions

`PlacedActions` defines the interactions available after the item has been placed in the world.

```lua
function CustomBehavior:PlacedActions(item)
    return {
        allowDefault = {
            take = false,
            pour = false,
        },

        options = {
            {
                name = 'custom_action',
                icon = 'fa-solid fa-xmark',
                label = 'sweep_away',
                actionControl = 'H',
                distance = 2.0,

                action = function()
                    print('Player used the placed-item action')
                end,

                canInteract = function()
                    return not currentItem.name
                end,
            },
        },
    }
end
```

<details>

<summary>allowDefault</summary>

<table><thead><tr><th width="144.60009765625">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>take</code></td><td>Enables the built-in action for picking up the placed item.</td></tr><tr><td><code>pour</code></td><td>Enables the built-in pouring action when supported.</td></tr></tbody></table>

</details>

<details>

<summary>Custom option fields</summary>

<table><thead><tr><th width="144.5999755859375">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td>Unique action identifier.</td></tr><tr><td><code>icon</code></td><td>Target-system icon.</td></tr><tr><td><code>label</code></td><td>Translation suffix used by the target and 3D Text modules.</td></tr><tr><td><code>actionControl</code></td><td>Key used for the 3D Text interaction.</td></tr><tr><td><code>distance</code></td><td>Maximum interaction distance.</td></tr><tr><td><code>action</code></td><td>Function executed after selecting the action.</td></tr><tr><td><code>canInteract</code></td><td>Optional function determining whether the action is currently available.</td></tr></tbody></table>

</details>

{% hint style="info" %}
For `label = 'sweep_away'`, the system uses `target.sweep_away` or `3dtext.sweep_away`, depending on the active interaction system.
{% endhint %}

### 8. PackClientMetadata

`PackClientMetadata` allows a behavior to send only the metadata required on the client after a placed item is synchronized.

By default, VMS Needs does **not** send the complete metadata of placed items to clients in order to minimize the amount of data transferred during the initial synchronization. If your behavior requires specific metadata on the client (for example, to determine available interactions in `PlacedActions`), return only the required values from this function.

```lua
function CustomBehavior:PackClientMetadata(metadata)
    if metadata and metadata.isLine then
        return {
            isLine = true
        }
    end

    return nil
end
```

Return a table containing only the metadata required by the client, or `nil` if no metadata needs to be synchronized.

### 9. CancelAction

`CancelAction` defines what happens when the player closes or cancels the current item interaction.

{% code expandable="true" %}

```lua
function CustomBehavior:CancelAction()
    print('Player canceled item usage')

    CancelUsage()
end
```

{% endcode %}

`CancelUsage()` hides or returns the item according to its current state and configured item behavior.

{% code expandable="true" %}

```lua
function CustomBehavior:CancelAction()
    ThrowItem()
end
```

{% endcode %}

Use `ThrowItem()` when the item should be discarded instead of returned to the inventory.

### 10. Register the Behavior

{% code expandable="true" %}

```lua
RegisterBehavior('custom_behavior', CustomBehavior)
```

{% endcode %}

The registration name must match the `behavior` option configured for the item.

{% code expandable="true" %}

```lua
['my_item'] = {
    behavior = 'custom_behavior',

    -- Other item configuration...
}
```

{% endcode %}

### 11. Complete Example

{% code expandable="true" %}

```lua
local CustomBehavior = {}

function CustomBehavior:GetControls()
    return {
        {
            key = 'E',
            label = 'Use',
            name = 'use',
        },
        {
            key = 'G',
            label = 'Custom Action',
            name = 'custom_action',
        },
        {
            key = Config.Controls.MAIN.CANCEL,
            label = 'Put Away',
            name = 'cancel',
        },
    }, 'Custom item'
end

function CustomBehavior:GetObjects(data)
    local item = Config.Items[data.name]

    return {
        {
            model = item.models.default,
            attach = item.attaches.hand,
        },
    }
end

function CustomBehavior:PrimaryAction()
    print('Primary action used')
end

function CustomBehavior:HandleAction(action)
    if action == 'custom_action' then
        print('Custom action used')
    end
end

function CustomBehavior:CanPlace()
    return true
end

function CustomBehavior:CancelAction()
    CancelUsage()
end

RegisterBehavior('custom_behavior', CustomBehavior)
```

{% endcode %}

**Behavior-specific configuration**

A custom behavior decides which entries are required inside `models`, `attaches`, `animation`, and `particles`. When creating a new behavior, you are responsible for reading and using those configuration entries inside the behavior file.

We recommend copying the built-in behavior closest to your intended interaction and using it as a starting point.


# Developer API


# Client Exports

### GetStatus

Returns a status controller for the specified player and status.

```lua
---@param {statusName: string}: Status name defined in Config.Statuses
---@return {status: table}: Status controller with Set, GetPercentage, Add and Remove functions
local status = exports['vms_needs']:GetStatus(statusName)
```

<details>

<summary><strong>Example Usage</strong></summary>

```lua
local hunger = exports['vms_needs']:GetStatus('hunger')

hunger.Set(300)

print(hunger.GetPercentage())

hunger.Add(50)
hunger.Remove(25)
```

</details>

<details>

<summary><strong>Returned Functions</strong></summary>

```lua
{
    Set = function(value)
        -- Sets the raw status value.
    end,

    GetPercentage = function()
        -- Returns the current status value as a percentage from 0 to 100.
    end,

    Add = function(value)
        -- Adds the specified value to the status.
    end,

    Remove = function(value)
        -- Removes the specified value from the status.
    end,
}
```

</details>

***

### GetStatuses

Returns a list of all registered player statuses.

```lua
---@return {statuses: table}: Status data indexed by status name
local statuses = exports['vms_needs']:GetStatuses()
```

<details>

<summary><strong>Example Returned Data</strong></summary>

```lua
{
    ["hunger"] = {
        max = 500,
        value = 154.0,
        percentage = 30.8,
        name = "hunger",
    },
    ["thirst"] = {
        max = 500,
        value = 106.0,
        percentage = 21.2,
        name = "thirst",
    },
    ["stress"] = {
        max = 500,
        value = 0.0,
        percentage = 0.0,
        name = "stress",
    },
}
```

</details>

***

### GetPercentageStatus

Returns the current status value as a percentage (0-100).

```lua
---@param {statusName: string}: Status name defined in Config.Statuses
---@return {statusPercentage: number}: Current status value as a percentage (0-100)
local statusPercentage = exports['vms_needs']:GetPercentageStatus(statusName)
```

***

### SetStatus

Sets the raw value of the status.

```lua
---@param {statusName: string}: Status name defined in Config.Statuses
---@param {value: number}: New raw status value
exports['vms_needs']:SetStatus(statusName, value)
```

***

### AddStatus

Adds a raw value to the status.

```lua
---@param {statusName: string}: Status name defined in Config.Statuses
---@param {value: number}: Raw status value to add
exports['vms_needs']:AddStatus(statusName, value)
```

***

### RemoveStatus

Removes a raw value from the status.

```lua
---@param {statusName: string}: Status name defined in Config.Statuses
---@param {value: number}: Raw status value to remove
exports['vms_needs']:RemoveStatus(statusName, value)
```

***

### CleanupOptionalStatuses

Clear all statuses if they are marked as `isOptional` in `Config.Statuses`.

```lua
exports['vms_needs']:CleanupOptionalStatuses()
```

***

### GetServerTemperature

Returns the current ambient server temperature.

```lua
---@param {unit: string | nil}: Temperature unit ("fahrenheit"). Defaults to Celsius if nil.
---@return {temperature: number}: Current ambient temperature
local temperature = exports['vms_needs']:GetServerTemperature(unit)
```

***

### GetServerFeelsLikeTemperature

Returns the current perceived temperature based on weather conditions.

```lua
---@param {unit: string | nil}: Temperature unit ("fahrenheit"). Defaults to Celsius if nil.
---@return {temperature: number}: Current perceived temperature
local temperature = exports['vms_needs']:GetServerFeelsLikeTemperature(unit)
```

***

### GetPlayerBodyTemperature

Returns the player's current body temperature and thermal state.

```lua
---@param {unit: string | nil}: Temperature unit ("fahrenheit"). Defaults to Celsius if nil.
---@return {temperature: number}: Current player body temperature
---@return {state: string}: Current thermal state (NORMAL, WARM, HOT, OVERHEATED, CHILLED, COLD or HYPOTHERMIA)
local temperature, state = exports['vms_needs']:GetPlayerBodyTemperature(unit)
```

<details>

<summary>Example</summary>

```lua
local temperature, state = exports['vms_needs']:GetPlayerBodyTemperature()

print(temperature) -- 36.8
print(state)       -- NORMAL
```

</details>

<details>

<summary>Available States</summary>

The following thermal states may be returned by this export.

<table><thead><tr><th width="206.6363525390625">State</th><th width="302.636474609375">Description</th><th>Temperature Range</th></tr></thead><tbody><tr><td>NORMAL</td><td>Normal body temperature</td><td>36.0 - 37.5 °C</td></tr><tr><td>WARM</td><td>Slightly elevated body temperature</td><td>37.5 - 38.0 °C</td></tr><tr><td>HOT</td><td>Elevated body temperature</td><td>38.0 - 39.0 °C</td></tr><tr><td>OVERHEATED</td><td>Dangerously high body temperature</td><td>> 39.0 °C</td></tr><tr><td>CHILLED</td><td>Slightly reduced body temperature</td><td>35.2 - 36.0 °C</td></tr><tr><td>COLD</td><td>Low body temperature</td><td>34.0 - 35.2 °C</td></tr><tr><td>HYPOTHERMIA</td><td>Dangerously low body temperature</td><td>&#x3C; 34.0 °C</td></tr></tbody></table>

</details>

***

### OverwriteTemperature

Overrides the ambient temperature for the local player until disabled. Useful for interiors or controlled environments where the perceived temperature should differ from the outside world.

The player's body temperature will not change instantly. It will gradually adjust toward the overridden ambient temperature according to the configured temperature system.

```lua
---@param {toggle: boolean}: Enables or disables the local ambient temperature override.
---@param {temperature: number | nil}: Ambient temperature to use while the override is enabled. Ignored when toggle is false.
exports['vms_needs']:OverwriteTemperature(toggle, temperature)
```

<details>

<summary>Example</summary>

```lua
-- Entering a heated interior
exports['vms_needs']:OverwriteTemperature(true, 40.0)

-- Entering a comfortable interior
exports['vms_needs']:OverwriteTemperature(true, 20.0)

-- Leaving the interior
exports['vms_needs']:OverwriteTemperature(false)
```

</details>

***

### GetPlayerCold

Returns the player's current cold factor.

{% hint style="info" %}
The returned value is normalized between 0.0 and 1.0 and can be used to scale custom gameplay effects.
{% endhint %}

```lua
---@return {cold: number}: Cold intensity (0.0-1.0)
local cold = exports['vms_needs']:GetPlayerCold()
```

***

### GetPlayerHeat

Returns the player's current heat factor.

{% hint style="info" %}
The returned value is normalized between 0.0 and 1.0 and can be used to scale custom gameplay effects.
{% endhint %}

```lua
---@return {heat: number}: Heat intensity (0.0-1.0)
local heat = exports['vms_needs']:GetPlayerHeat()
```

***

### GetUsingItem

Returns the currently active item, including its name, metadata and configured type.

```lua
---@return {name: string | nil}: Current item name
---@return {metadata: table | nil}: Current item metadata
---@return {type: string | nil}: Item type defined in Config.Items
local name, metadata, type = exports['vms_needs']:GetUsingItem()
```

<details>

<summary>Example</summary>

```lua
local name, metadata, type = exports['vms_needs']:GetUsingItem()

if name then
    print("Name:", name)
    print("Metadata:", json.encode(metadata))
    print("Type:", type)
end
```

</details>

***

### CancelItemUsage

Cancels the current item usage by either hiding or dropping the item, depending on the specified action and item behavior.

{% hint style="info" %}
**Note:** Not all items support both actions. For example, lit cigarettes cannot be returned to the inventory, while some items (such as a breathalyzer) cannot be dropped. In these cases, the item's configured behavior is applied automatically.
{% endhint %}

```lua
---@param {action: string | nil}: "drop" to drop the current item, or "cancel"/nil to hide it if possible.
exports['vms_needs']:CancelItemUsage(action)
```

<details>

<summary>Example</summary>

```lua
-- Hide the current item (default behavior)
exports['vms_needs']:CancelItemUsage()

-- Explicitly hide the current item
exports['vms_needs']:CancelItemUsage('cancel')

-- Attempt to drop the current item
exports['vms_needs']:CancelItemUsage('drop')
```

</details>

***

### GetBreathalyzerReading

Calculates and returns the current breathalyzer reading using the player's drunk status and the configured alcohol settings.

{% hint style="info" %}
**Note:** The returned values are calculated using the current `drunk` status and the settings defined in `Config.Alcohol`.
{% endhint %}

```lua
---@return {data: table}: Current breathalyzer reading
local data = exports['vms_needs']:GetBreathalyzerReading()
```

<details>

<summary>Example Returned Data</summary>

```lua
{
    drunkPercent = 42.5,
    permille = 1.28,
    mgL = 0.61
}
```

</details>

<details>

<summary>Returned Fields</summary>

<table><thead><tr><th width="234.8182373046875">Field</th><th>Description</th></tr></thead><tbody><tr><td>drunkPercent</td><td>Current <code>drunk</code> status as a percentage (0–100).</td></tr><tr><td>permille</td><td>Calculated blood alcohol concentration (‰).</td></tr><tr><td>mgL</td><td>Calculated breath alcohol concentration (mg/L).</td></tr></tbody></table>

</details>

***

### ReloadVisualEffect

Reapplies the active VMS Needs visual effect after another resource overrides or clears the current timecycle modifier.

Use this export after reviving a player or whenever another resource temporarily applies its own screen effect.

```lua
exports['vms_needs']:ReloadVisualEffect()
```

***

### GetConfig

```lua
local config = exports['vms_needs']:GetConfig()
```


# Client State Bags

### Getting Current Status

Returns the player's current status directly from the client state bag.\
These values are automatically synchronized by VMS Needs and are available at any time on the client.

```lua
LocalPlayer.state.STATUS_NAME
```

<details>

<summary><strong>Example Usage</strong></summary>

```lua
local hunger = LocalPlayer.state.hunger
print('My hunger level is ' .. hunger .. '%')

local thirst = LocalPlayer.state.thirst
print('My thirst level is ' .. thirst .. '%')

local stress = LocalPlayer.state.stress
print('My stress level is ' .. stress .. '%')
```

</details>

***

### Listening for Status Changes

Registers a callback that is automatically executed whenever the selected state bag value changes.\
Recommended for HUDs, UI elements, and any system that needs to react to status changes in real time.

```lua
local serverId = GetPlayerServerId(PlayerId())
AddStateBagChangeHandler(STATUS_NAME, ("player:%s"):format(serverId), function(_, _, percentage)
    print('Updated status to ' .. percentage .. '%')
end)
```

<details>

<summary><strong>Example Usage</strong></summary>

```lua
local serverId = GetPlayerServerId(PlayerId())

AddStateBagChangeHandler("hunger", ("player:%s"):format(serverId), function(_, _, percentage)
     -- Update HUD
end)

AddStateBagChangeHandler("thirst", ("player:%s"):format(serverId), function(_, _, percentage)
    -- Update HUD
end)

AddStateBagChangeHandler("stress", ("player:%s"):format(serverId), function(_, _, percentage)
    -- Update HUD
end)
```

</details>


# Client Events

### Refresh the player's visual effects

Refreshes the current visual effects derived from substances, e.g., if the effects have been overwritten by the death effect.&#x20;

```lua
TriggerEvent('vms_status:cl:reloadVisualEffect')
```


# Server Exports

### GetStatus

Returns a status controller for the specified player and status.

```lua
---@param {source: number}: Player Server ID
---@param {statusName: string}: Status name defined in Config.Statuses
---@return {status: table}: Status controller with Set, GetPercentage, Add and Remove functions
local status = exports['vms_needs']:GetStatus(source, statusName)
```

<details>

<summary><strong>Example Usage</strong></summary>

```lua
local hunger = exports['vms_needs']:GetStatus(1, 'hunger')

hunger.Set(300)

print(hunger.GetPercentage())

hunger.Add(50)
hunger.Remove(25)
```

</details>

<details>

<summary><strong>Returned Functions</strong></summary>

```lua
{
    Set = function(value)
        -- Sets the raw status value.
    end,

    GetPercentage = function()
        -- Returns the current status value as a percentage from 0 to 100.
    end,

    Add = function(value)
        -- Adds the specified value to the status.
    end,

    Remove = function(value)
        -- Removes the specified value from the status.
    end,
}
```

</details>

***

### GetPercentageStatus

Returns the current status value as a percentage (0-100).

```lua
---@param {source: number}: Player Server ID
---@param {statusName: string}: Status name defined in Config.Statuses
---@return {statusPercentage: number}: Current status value as a percentage (0-100)
local statusPercentage = exports['vms_needs']:GetPercentageStatus(source, statusName)
```

***

### SetStatus

Sets the raw value of the specified player status.

```lua
---@param {source: number}: Player Server ID
---@param {statusName: string}: Status name defined in Config.Statuses
---@param {value: number}: New raw status value
exports['vms_needs']:SetStatus(source, statusName, value)
```

***

### AddStatus

Adds a raw value to the specified player status.

```lua
---@param {source: number}: Player Server ID
---@param {statusName: string}: Status name defined in Config.Statuses
---@param {value: number}: Raw status value to add
exports['vms_needs']:AddStatus(source, statusName, value)
```

***

### RemoveStatus

Removes a raw value from the specified player status.

```lua
---@param {source: number}: Player Server ID
---@param {statusName: string}: Status name defined in Config.Statuses
---@param {value: number}: Raw status value to remove
exports['vms_needs']:RemoveStatus(source, statusName, value)
```

***

### CleanupOptionalStatuses

Clear all statuses if they are marked as `isOptional` in `Config.Statuses`.

```lua
---@param {source: number}: Player Server ID
exports['vms_needs']:CleanupOptionalStatuses(source)
```

***

### SaveStatus

Saves the status data for the specified player or all online players.

```lua
---@param {source: number | nil}: Player Server ID or nil to save all online players.
exports['vms_needs']:SaveStatus(source)
```

***

### GetServerTemperature

Returns the current server temperature.

```lua
local tempreature = exports['vms_needs']:GetServerTemperature()
```


# Server Events

### Update Weather & Time for Temperature System

Updates both the active weather type and the current in-game time used by the temperature system.

{% hint style="warning" %}
**These events are server-side only**. Trigger them using TriggerEvent, not TriggerServerEvent.
{% endhint %}

```lua
---@param {weatherType: string}: Current GTA weather type
---@param {hour: number}: Current in-game hour (0-23)
---@param {minute: number}: Current in-game minute (0-59)
TriggerEvent('vms_needs:temperature:update', weatherType, hour, minute)
```

<details>

<summary>Example</summary>

```lua
TriggerEvent('vms_needs:temperature:update', 'CLEAR', 14, 30)
```

</details>

***

### Update Weather for Temperature System

Updates only the active weather type used by the temperature system.

{% hint style="warning" %}
**These events are server-side only**. Trigger them using TriggerEvent, not TriggerServerEvent.
{% endhint %}

```lua
---@param {weatherType: string}: Current GTA weather type
TriggerEvent('vms_needs:temperature:updateWeather', weatherType)
```

<details>

<summary>Example</summary>

```lua
TriggerEvent('vms_needs:temperature:updateWeather', 'RAIN')
```

</details>

***

### Update Time for Temperature System

Updates only the current in-game time used by the temperature system.

{% hint style="warning" %}
**These events are server-side only**. Trigger them using TriggerEvent, not TriggerServerEvent.
{% endhint %}

```lua
---@param {hour: number}: Current in-game hour (0-23)
---@param {minute: number}: Current in-game minute (0-59)
TriggerEvent('vms_needs:temperature:updateTime', hour, minute)
```

<details>

<summary>Example</summary>

```lua
TriggerEvent('vms_needs:temperature:updateTime', 22, 15)
```

</details>


# Configuration Files


# config.lua

Preview File Updated: v1.0.2 - 04/08/2026

{% code fullWidth="true" %}

```lua
Config = Config or {}


-- ███████╗██████╗  █████╗ ███╗   ███╗███████╗██╗    ██╗ ██████╗ ██████╗ ██╗  ██╗
-- ██╔════╝██╔══██╗██╔══██╗████╗ ████║██╔════╝██║    ██║██╔═══██╗██╔══██╗██║ ██╔╝
-- █████╗  ██████╔╝███████║██╔████╔██║█████╗  ██║ █╗ ██║██║   ██║██████╔╝█████╔╝ 
-- ██╔══╝  ██╔══██╗██╔══██║██║╚██╔╝██║██╔══╝  ██║███╗██║██║   ██║██╔══██╗██╔═██╗ 
-- ██║     ██║  ██║██║  ██║██║ ╚═╝ ██║███████╗╚███╔███╔╝╚██████╔╝██║  ██║██║  ██╗
-- ╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝ ╚══╝╚══╝  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
local DetectActiveFramework = function()
    if GetResourceState('es_extended') == 'started' then
        return "ESX"
    elseif GetResourceState('qb-core') == 'started' then
        return "QB-Core"
    end
end

Config.Core = DetectActiveFramework()
Config.CoreExport = function()
    if Config.Core == "ESX" then
        return exports['es_extended']:getSharedObject()
    elseif Config.Core == "QB-Core" then
        return exports['qb-core']:GetCoreObject()
    end
end

---@field PlayerLoaded string: ESX: "esx:playerLoaded" / QB-Core: "QBCore:Client:OnPlayerLoaded"
Config.PlayerLoaded = Config.Core == "ESX" and "esx:playerLoaded" or "QBCore:Client:OnPlayerLoaded"

---@field PlayerLogoutServer string: ESX: "esx:playerDropped" / QB-Core: "QBCore:Server:OnPlayerUnload"
Config.PlayerLogoutServer = Config.Core == "ESX" and "esx:playerDropped" or "QBCore:Server:OnPlayerUnload"

---@field PlayerSetJob string: ESX: "esx:setJob" / QB-Core: "QBCore:Client:OnJobUpdate"
Config.PlayerSetJob = Config.Core == "ESX" and "esx:setJob" or "QBCore:Client:OnJobUpdate"



-- ███╗   ███╗ █████╗ ██╗███╗   ██╗    ███████╗███████╗████████╗████████╗██╗███╗   ██╗ ██████╗ ███████╗
-- ████╗ ████║██╔══██╗██║████╗  ██║    ██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗  ██║██╔════╝ ██╔════╝
-- ██╔████╔██║███████║██║██╔██╗ ██║    ███████╗█████╗     ██║      ██║   ██║██╔██╗ ██║██║  ███╗███████╗
-- ██║╚██╔╝██║██╔══██║██║██║╚██╗██║    ╚════██║██╔══╝     ██║      ██║   ██║██║╚██╗██║██║   ██║╚════██║
-- ██║ ╚═╝ ██║██║  ██║██║██║ ╚████║    ███████║███████╗   ██║      ██║   ██║██║ ╚████║╚██████╔╝███████║
-- ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝╚═╝  ╚═══╝    ╚══════╝╚══════╝   ╚═╝      ╚═╝   ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝
---@class AutoExecuteQuery<boolean>
-- Automatically creates required SQL tables on resource start (recommended for first install).
Config.AutoExecuteQuery = true

---@class Debug<boolean>
-- By running debugging, you will receive prints at various activities, in case of any problems, this will be able to help you find the cause of the misconfiguration.
Config.Debug = true -- Enables debug prints for various system events.

---@class UseTarget<boolean>
Config.UseTarget = true

---@class UseDrawText<boolean>
-- If you are not using the target system, you can render 3D Text.
Config.UseDrawText = true

---@class UseDrawTextBox<boolean>
-- Using 3D Text system, you can render dark box under the text (using this option will increase CPU consumption by ~0.04ms)
Config.UseDrawTextBox = false

---@class DrawTextDistanceBetween<number>
-- Vertical spacing between 3D interaction texts.
Config.DrawTextDistanceBetween = 0.1

---@class UseBuiltInRequestMenu<boolean>
-- Use the built-in request menu instead of the custom CL.QuestionMenu callback.
Config.UseBuiltInRequestMenu = true



-- ███████╗███████╗████████╗████████╗██╗███╗   ██╗ ██████╗ ███████╗
-- ██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗  ██║██╔════╝ ██╔════╝
-- ███████╗█████╗     ██║      ██║   ██║██╔██╗ ██║██║  ███╗███████╗
-- ╚════██║██╔══╝     ██║      ██║   ██║██║╚██╗██║██║   ██║╚════██║
-- ███████║███████╗   ██║      ██║   ██║██║ ╚████║╚██████╔╝███████║
-- ╚══════╝╚══════╝   ╚═╝      ╚═╝   ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝
Config.Controls = { -- client/modules/keybinds.lua
    ['MAIN'] = {
        ['USE']     = 'E',
        ['SMELL']   = 'L',
        ['PLACE']   = 'H',
        ['GIVE']    = 'G',
        ['CANCEL']  = 'X',
    },
    ['GIVING'] = {
        ['ACCEPT']  = 'E',
        ['BACK']    = 'X',
    },
    ['INTERACTING'] = {
        ['ACCEPT']  = 'E',
        ['BACK']    = 'X',
    },
    ['PLACING'] = {
        ['ACCEPT']  = 'E',
        ['SWITCH_MODE'] = 'G',
        ['ROTATE']  = 'SCROLL', -- This key cannot be changed.
        ['BACK']    = 'X',
    },
    ['PLACED'] = { -- Keys for non-target interactions after placing an object (e.g., when using 3D Text instead of a target)
        ['TAKE']    = 'E',
        ['POUR']    = 'K',
    },
    ['GIZMO'] = {
        ['SNAP_TO_GROUND'] = 'ALT',
        ['TOGGLE_CURSOR'] = 'RMB',
        ['RELATIVE'] = 'V',
        ['TRANSLATION'] = 'X',
        ['ROTATION'] = 'C',
        ['BACK'] = 'BACKSPACE',
    },
    ['REQUEST_MENU'] = {
        ['ACCEPT'] = 'Y',
        ['DECLINE'] = 'N',
    },
}

Config.PlacingSettings = {
    AllowGizmo = true,
    RotateSpeed = 5.0,          -- Default rotation speed
    RotateSpeedSlow = 1.0,      -- Slower rotation speed (when holding CTRL)
}



--  ██████╗ ██████╗ ██╗     ██████╗     ███████╗███╗   ██╗███████╗███████╗███████╗███████╗
-- ██╔════╝██╔═══██╗██║     ██╔══██╗    ██╔════╝████╗  ██║██╔════╝██╔════╝╚══███╔╝██╔════╝
-- ██║     ██║   ██║██║     ██║  ██║    ███████╗██╔██╗ ██║█████╗  █████╗    ███╔╝ █████╗  
-- ██║     ██║   ██║██║     ██║  ██║    ╚════██║██║╚██╗██║██╔══╝  ██╔══╝   ███╔╝  ██╔══╝  
-- ╚██████╗╚██████╔╝███████╗██████╔╝    ███████║██║ ╚████║███████╗███████╗███████╗███████╗
--  ╚═════╝ ╚═════╝ ╚══════╝╚═════╝     ╚══════╝╚═╝  ╚═══╝╚══════╝╚══════╝╚══════╝╚══════╝
Config.SneezeEffects = {
    ['male'] = {
        { fileName = 'male_sneeze_1', volume = 0.12, cooldown = 1200 },
        { fileName = 'male_sneeze_2', volume = 0.12, cooldown = 1200 },
    }, 
    ['female'] = {
        { fileName = 'female_sneeze_1', volume = 0.12, cooldown = 1200 },
        { fileName = 'female_sneeze_2', volume = 0.12, cooldown = 1200 },
    },
}



-- ███████╗████████╗ █████╗ ████████╗██╗   ██╗███████╗
-- ██╔════╝╚══██╔══╝██╔══██╗╚══██╔══╝██║   ██║██╔════╝
-- ███████╗   ██║   ███████║   ██║   ██║   ██║███████╗
-- ╚════██║   ██║   ██╔══██║   ██║   ██║   ██║╚════██║
-- ███████║   ██║   ██║  ██║   ██║   ╚██████╔╝███████║
-- ╚══════╝   ╚═╝   ╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚══════╝
---@class Statuses<table>
-- Defines all statuses used by VMS Needs, including their maximum values, update intervals and custom tick behavior.
-- Add, remove or modify statuses here to match your server's gameplay. Make sure every status used by configured items is also defined in this table.
Config.Statuses = {
    {
        name = 'hunger',
        max = 500,
        default = 500, -- Initial value available only when isOptional = false
        isOptional = false,
        tickInterval = 10000,
        onTick = function(status)
            local baseRemove = 1.2
            local multiplier = 1.0

            local indica = exports['vms_needs']:GetPercentageStatus('weed_indica')
            local sativa = exports['vms_needs']:GetPercentageStatus('weed_sativa')
            local cocaine = exports['vms_needs']:GetPercentageStatus('cocaine')
            local amphetamine = exports['vms_needs']:GetPercentageStatus('amphetamine')
            local methamphetamine = exports['vms_needs']:GetPercentageStatus('methamphetamine')

            local weed = library.Clamp(indica + sativa, 0.0, 100.0)

            multiplier = multiplier
                + ((weed / 100.0) * 2.0)
                - ((cocaine / 100.0) * 0.60)
                - ((amphetamine / 100.0) * 0.75)
                - ((methamphetamine / 100.0) * 0.90)

            multiplier = library.Clamp(multiplier, 0.20, 3.0)

            status.Remove(baseRemove * multiplier)
        end
    },
    {
        name = 'thirst',
        max = 500,
        default = 500, -- Initial value available only when isOptional = false
        isOptional = false,
        tickInterval = 10000,
        onTick = function(status)
            local baseRemove = 1.8
            local multiplier = 1.0

            local heat = exports['vms_needs']:GetPlayerHeat()

            local drunk = exports['vms_needs']:GetPercentageStatus('drunk')
            local weedIndica = exports['vms_needs']:GetPercentageStatus('weed_indica')
            local weedSativa = exports['vms_needs']:GetPercentageStatus('weed_sativa')
            local cocaine = exports['vms_needs']:GetPercentageStatus('cocaine')
            local amphetamine = exports['vms_needs']:GetPercentageStatus('amphetamine')
            local methamphetamine = exports['vms_needs']:GetPercentageStatus('methamphetamine')

            local weed = library.Clamp(weedIndica + weedSativa, 0.0, 100.0)

            multiplier = multiplier
                + (heat * 1.5)
                + ((drunk / 100.0) * 0.50)
                + ((weed / 100.0) * 0.25)
                + ((cocaine / 100.0) * 0.45)
                + ((amphetamine / 100.0) * 0.60)
                + ((methamphetamine / 100.0) * 0.80)

            multiplier = library.Clamp(multiplier, 1.0, 4.0)

            status.Remove(baseRemove * multiplier)
        end
    },
    {
        name = 'stress',
        max = 500,
        default = 0, -- Initial value available only when isOptional = false
        isOptional = false,
        tickInterval = 15000,
        onTick = function(status)
            local baseRemove = 0.5
            local multiplier = 1.0

            local drunk = exports['vms_needs']:GetPercentageStatus('drunk')
            local indica = exports['vms_needs']:GetPercentageStatus('weed_indica')
            local sativa = exports['vms_needs']:GetPercentageStatus('weed_sativa')
            local codeine = exports['vms_needs']:GetPercentageStatus('codeine')
            local heroin = exports['vms_needs']:GetPercentageStatus('heroin')
            local cocaine = exports['vms_needs']:GetPercentageStatus('cocaine')
            local amphetamine = exports['vms_needs']:GetPercentageStatus('amphetamine')
            local methamphetamine = exports['vms_needs']:GetPercentageStatus('methamphetamine')

            multiplier = multiplier
                + ((drunk / 100.0) * 0.75)
                + ((indica / 100.0) * 2.0)
                + ((sativa / 100.0) * 1.0)
                + ((codeine / 100.0) * 1.25)
                + ((heroin / 100.0) * 2.5)
                - ((cocaine / 100.0) * 0.50)
                - ((amphetamine / 100.0) * 0.65)
                - ((methamphetamine / 100.0) * 0.80)

            multiplier = library.Clamp(multiplier, 0.10, 4.0)

            status.Remove(baseRemove * multiplier)
        end
    },
    {
        name = 'drunk',
        max = 100,
        isOptional = true,
        tickInterval = 10000, -- ~32 min from 100% to 0%
        onTick = function(status)
            status.Remove(0.5)
        end
    },
    {
        name = 'weed_indica',
        max = 100,
        isOptional = true,
        tickInterval = 10000, -- ~16 min from 100% to 0%
        onTick = function(status)
            status.Remove(1.0)
        end
    },
    {
        name = 'weed_sativa',
        max = 100,
        isOptional = true,
        tickInterval = 10000, -- ~14 min from 100% to 0%
        onTick = function(status)
            status.Remove(1.2)
        end
    },
    {
        name = 'cocaine',
        max = 100,
        isOptional = true,
        tickInterval = 8000, -- ~5 min from 100% to 0%
        onTick = function(status)
            status.Remove(2.5)
        end
    },
    {
        name = 'amphetamine',
        max = 100,
        isOptional = true,
        tickInterval = 10000, -- ~11 min from 100% to 0%
        onTick = function(status)
            status.Remove(1.5)
        end
    },
    {
        name = 'methamphetamine',
        max = 100,
        isOptional = true,
        tickInterval = 10000, -- ~11 min from 100% to 0%
        onTick = function(status)
            status.Remove(1.5)
        end
    },
    {
        name = 'lsd',
        max = 100,
        isOptional = true,
        tickInterval = 12000, -- ~13 min from 100% to 0%
        onTick = function(status)
            status.Remove(1.5)
        end
    },
    {
        name = 'heroin',
        max = 100,
        isOptional = true,
        tickInterval = 10000, -- ~9.5 min from 100% to 0%
        onTick = function(status)
            status.Remove(1.75)
        end
    },
    {
        name = 'codeine',
        max = 100,
        isOptional = true,
        tickInterval = 10000, -- ~8 min from 100% to 0%
        onTick = function(status)
            status.Remove(2)
        end
    },
}

---@class DefaultTickInterval<number>
-- Interval (in milliseconds) for updating player statuses.
Config.DefaultTickInterval = 10000

---@class StatusSyncInterval<number>
-- Interval (in milliseconds) for synchronizing player statuses with the server.
Config.StatusSyncInterval = 30000

---@class StatusSaveInterval<number>
-- Interval (in milliseconds) for saving player statuses to database.
Config.StatusSaveInterval = 30000

---@class ESXStatusOnTick<boolean>
-- Enables compatibility mode for `esx_status` and most ESX HUDs by periodically triggering the `esx_status:onTick` event.
-- Disable this if your HUD uses Client State Bags for better performance.
Config.ESXStatusOnTick = Config.Core == "ESX"

---@class ESXStatusOnTickTime<number>
-- Interval (in milliseconds), we recommend not setting it to less than 1000
Config.ESXStatusOnTickTime = 2000



-- ███████╗███████╗███████╗███████╗ ██████╗████████╗███████╗
-- ██╔════╝██╔════╝██╔════╝██╔════╝██╔════╝╚══██╔══╝██╔════╝
-- █████╗  █████╗  █████╗  █████╗  ██║        ██║   ███████╗
-- ██╔══╝  ██╔══╝  ██╔══╝  ██╔══╝  ██║        ██║   ╚════██║
-- ███████╗██║     ██║     ███████╗╚██████╗   ██║   ███████║
-- ╚══════╝╚═╝     ╚═╝     ╚══════╝ ╚═════╝   ╚═╝   ╚══════╝
---@class Alcohol<table>
-- Settings used to calculate breathalyzer results based on the player's drunk status.
Config.Alcohol = {
    -- Maximum breath alcohol concentration (‰) at 100% drunk status.
    MaxPermille = 3.00,

    -- Maximum breath alcohol concentration (mg/L) at 100% drunk status.
    -- If set to nil, the value will be calculated automatically using MaxPermille and RatioPermillePerMgL.
    MaxMgL = nil,

    -- Conversion ratio used when calculating mg/L from ‰.
    RatioPermillePerMgL = 2.10,

    -- Number of decimal places displayed for ‰ results.
    PermilleDecimals = 2,

    -- Number of decimal places displayed for mg/L results.
    MgLDecimals = 2,

    -- Minimum ‰ value required for the breathalyzer to return a positive result.
    PositivePermilleThreshold = 0.20,

    -- Minimum allowed breathalyzer value.
    ClampMin = 0.0,

    -- Maximum multiplier applied to the calculated value (1.0 = never exceeds the configured maximum).
    ClampMaxMultiplier = 1.0,
}

---@class ItemAccidents<table>
-- Defines accidental item interactions caused by various events, such as getting shot or falling down drunk.
-- Configure which events can trigger an accident and how each item type should react.
-- Critical events such as death, heavy falls, vehicle hits and tasers are handled separately by CL.GetCancelUsageAction and always interrupt the current item usage.
Config.ItemAccidents = {
    Enabled = true,

    Reasons = {
        bullet = {
            chance = 100,
            cooldown = 1000,
            action = 'drop',
        },
        melee = {
            chance = 60,
            cooldown = 1000,
            useItemTypeActions = true,
        },
        waterCannon = {
            chance = 100,
            cooldown = 5000,
            action = 'drop',
        },
        drunkRagdoll = {
            chance = 35,
            cooldown = 5000,
            useItemTypeActions = true,
        },
        vehicleCrash = {
            chance = 35,
            cooldown = 5000,
            useItemTypeActions = true,
        },
    },

    ItemTypes = {
        food = {
            actions = {
                {type = 'drop', chance = 100},
            }
        },

        drink = {
            actions = {
                {type = 'spill', chance = 70, amount = {min = 10, max = 50}},
                {type = 'drop', chance = 30},
            }
        },

        glass = {
            actions = {
                {type = 'spill', chance = 45, amount = {min = 40, max = 120}},
                {type = 'drop', chance = 40},
                {type = 'break', chance = 15},
            }
        },
    }
}

---@class Effects<table>
-- Defines all gameplay effects applied by player statuses.
-- Configure how each status behaves at different percentage ranges by assigning movement, visual and gameplay effects.
Config.Effects = {
    ['drunk'] = {
        Levels = { -- Effects applied within the specified status percentage range.
            {
                range = {3, 15},
                sprintModifier = 0.0,
                walkStyle = 'move_m@drunk@slightlydrunk',
                cameraShake = {
                    name = 'DRUNK_SHAKE',
                    intensity = 0.2
                },
                timecycle = {
                    effect = 'drunk', -- Config.TimecycleEffects key
                    level = 1
                },
                vehicleSteerBias = false,
            },
            {
                range = {15, 25},
                sprintModifier = 0.0,
                walkStyle = 'move_m@drunk@slightlydrunk',
                cameraShake = {
                    name = 'DRUNK_SHAKE',
                    intensity = 0.55
                },
                timecycle = {
                    effect = 'drunk', -- Config.TimecycleEffects key
                    level = 2
                },
                vehicleSteerBias = {
                    chance = 65, -- 80%
                    multiplier = 40,
                    cooldown = 800,
                },
            },
            {
                range = {25, 50},
                sprintModifier = 0.0,
                walkStyle = 'move_m@drunk@slightlydrunk',
                cameraShake = {
                    name = 'DRUNK_SHAKE',
                    intensity = 0.85
                },
                timecycle = {
                    effect = 'drunk', -- Config.TimecycleEffects key
                    level = 3
                },
                vehicleSteerBias = {
                    chance = 75, -- 80%
                    multiplier = 60,
                    cooldown = 250,
                },
            },
            {
                range = {50, 70},
                sprintModifier = 0.0,
                walkStyle = 'move_m@drunk@slightlydrunk',
                cameraShake = {
                    name = 'DRUNK_SHAKE',
                    intensity = 1.2
                },
                timecycle = {
                    effect = 'drunk', -- Config.TimecycleEffects key
                    level = 4
                },
                ragdoll = {
                    chance = 10,
                    time = 2000,
                    cooldown = 8000,
                    minSpeed = 4.0,
                },
                vehicleSteerBias = {
                    chance = 80, -- 80%
                    multiplier = 75,
                    cooldown = 250,
                },
            },
            {
                range = {70, 85},
                sprintModifier = 0.0,
                walkStyle = 'move_m@drunk@slightlydrunk',
                cameraShake = {
                    name = 'DRUNK_SHAKE',
                    intensity = 1.2
                },
                timecycle = {
                    effect = 'drunk', -- Config.TimecycleEffects key
                    level = 5
                },
                ragdoll = {
                    chance = 30,
                    time = 2000,
                    cooldown = 10000,
                    minSpeed = 1.7,
                },
                vehicleSteerBias = {
                    chance = 80, -- 80%
                    multiplier = 75,
                    cooldown = 250,
                },
            },
            {
                range = {85, 100},
                sprintModifier = 0.0,
                walkStyle = 'move_m@drunk@verydrunk',
                cameraShake = {
                    name = 'DRUNK_SHAKE',
                    intensity = 1.2
                },
                timecycle = {
                    effect = 'drunk', -- Config.TimecycleEffects key
                    level = 6
                },
                ragdoll = {
                    chance = 45,
                    time = 3000,
                    cooldown = 10000,
                    minSpeed = 1.41,
                },
                vehicleSteerBias = {
                    chance = 80, -- 80%
                    multiplier = 75,
                    cooldown = 250,
                },
            },
        },
    },

    ['codeine'] = { ... },

    ['weed_indica'] = { ... },

    ['weed_sativa'] = { ... },

    ['cocaine'] = { ... },

    ['amphetamine'] = { ... },
    
    ['methamphetamine'] = { ... },

    ['lsd'] = { ... },
    
    ['heroin'] = { ... },
}

---@class TimecycleEffects<table>
-- Defines custom timecycle effects used by player statuses.
-- Each effect consists of a base configuration and optional levels that override selected values.
-- Timecycle effects are referenced by Config.Effects using the `effect` and `level` fields.
Config.TimecycleEffects = {
    ['drunk'] = {
        base = { -- Base values applied to every level.
            hh_minIntensity = {0.500, 0.500},
            hh_maxIntensity = {30.000, 0.000},
            hh_displacementU = {20.000, 0.000},
            hh_displacementV = {20.000, 0.000},
            hh_tex1UScale = {0.500, 0.000, 'instant'},
            hh_tex1VScale = {0.500, 0.000, 'instant'},
            hh_tex1UOffset = {0.000, 0.000, 'instant'},
            hh_tex2UScale = {1.000, 0.000, 'instant'},
            hh_tex2VScale = {1.000, 0.000, 'instant'},
            hh_tex1VScrollingSpeed = {5.000, 0.000, 'instant'},
            hh_tex2VScrollingSpeed = {5.000, 0.000, 'instant'},
        },

        levels = { -- Overrides applied for a specific effect level.
            [1] = {},
            [2] = {
                hh_displacementU = {35.000, 0.000},
                hh_displacementV = {35.000, 0.000},
            },
            [3] = {
                hh_displacementU = {65.000, 0.000},
                hh_displacementV = {65.000, 0.000},
                hh_tex1VScrollingSpeed = {3.500, 0.000, 'instant'},
                hh_tex2VScrollingSpeed = {3.500, 0.000, 'instant'},
            },
            [4] = {
                hh_displacementU = {95.000, 0.000},
                hh_displacementV = {95.000, 0.000},
                hh_tex1UScale = {0.000, 0.000, 'instant'},
                hh_tex1VScale = {0.100, 0.000, 'instant'},
                hh_tex1VScrollingSpeed = {6.500, 0.000, 'instant'},
                hh_tex2VScrollingSpeed = {6.500, 0.000, 'instant'},
            },
            [5] = {
                hh_minIntensity = {0.600, 0.500},
                hh_displacementU = {195.000, 0.000},
                hh_displacementV = {195.000, 0.000},
                hh_tex1UScale = {0.000, 0.000, 'instant'},
                hh_tex1VScale = {0.300, 0.000, 'instant'},
                hh_tex1VScrollingSpeed = {3.500, 0.000, 'instant'},
                hh_tex2VScrollingSpeed = {2.500, 0.000, 'instant'},
            },
            [6] = {
                hh_minIntensity = {0.900, 0.500},
                hh_displacementU = {180.000, 0.000},
                hh_displacementV = {180.000, 0.000},
                hh_tex1UScale = {0.000, 0.000, 'instant'},
                hh_tex1VScale = {0.600, 0.000, 'instant'},
                hh_tex2UScale = {1.500, 0.000, 'instant'},
                hh_tex2VScale = {1.500, 0.000, 'instant'},
                hh_tex1VScrollingSpeed = {9.500, 0.000, 'instant'},
                hh_tex2VScrollingSpeed = {9.500, 0.000, 'instant'},
            },
        }
    },
    
    ['codeine'] = { ... },

    ['weed_indica'] = { ... },

    ['weed_sativa'] = { ... },

    ['cocaine'] = { ... },

    ['amphetamine'] = { ... },

    ['methamphetamine'] = { ... },

    ['lsd'] = { ... },

    ['heroin'] = { ... },
}



-- ██████╗ ██████╗ ██╗   ██╗ ██████╗     ████████╗███████╗███████╗████████╗███████╗
-- ██╔══██╗██╔══██╗██║   ██║██╔════╝     ╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝██╔════╝
-- ██║  ██║██████╔╝██║   ██║██║  ███╗       ██║   █████╗  ███████╗   ██║   ███████╗
-- ██║  ██║██╔══██╗██║   ██║██║   ██║       ██║   ██╔══╝  ╚════██║   ██║   ╚════██║
-- ██████╔╝██║  ██║╚██████╔╝╚██████╔╝       ██║   ███████╗███████║   ██║   ███████║
-- ╚═════╝ ╚═╝  ╚═╝ ╚═════╝  ╚═════╝        ╚═╝   ╚══════╝╚══════╝   ╚═╝   ╚══════╝
---@class DrugTests<table>
-- Configure the chance of an invalid result and assign statuses to each test category.
Config.DrugTests = {
    ErrorChance = 5, -- Chance (%) for the test to return an invalid result.

    Categories = {
        -- Status names defined in Config.Statuses.
        MDMA = {},
        AMP = {'amphetamine'},
        COC = {'cocaine'},
        MET = {'methamphetamine'},
        OPI = {'heroin', 'codeine'},
        THC = {'weed_indica', 'weed_sativa'},
        LSD = {'lsd'},
    }
}



-- ███████╗███╗   ███╗███████╗██╗     ██╗     
-- ██╔════╝████╗ ████║██╔════╝██║     ██║     
-- ███████╗██╔████╔██║█████╗  ██║     ██║     
-- ╚════██║██║╚██╔╝██║██╔══╝  ██║     ██║     
-- ███████║██║ ╚═╝ ██║███████╗███████╗███████╗
-- ╚══════╝╚═╝     ╚═╝╚══════╝╚══════╝╚══════╝
---@class MinSmellPercent<number>
-- Minimum percentage of the total smell composition required for a smell profile to be detected.
Config.MinSmellPercent = 0.15

---@class MaxDetectedSmells<number>
-- Maximum number of smell profiles that can be detected at the same time.
-- Smells are displayed from the highest to the lowest percentage.
Config.MaxDetectedSmells = 3

---@class SmellProfiles<table>
-- Defines available smell profiles and their descriptions for different intensity levels.
-- Profile names are referenced by the `smellProfile` option in Config.Items.
Config.SmellProfiles = {
    alcohol = {
        labels = {
            weak = 'slightly alcoholic',
            medium = 'alcoholic',
            strong = 'strongly alcoholic'
        }
    },

    chemical = {
        messages = {
            weak = 'slightly chemical',
            medium = 'chemical',
            strong = 'strongly chemical'
        }
    },

    medicinal = {
        labels = {
            weak = 'slightly medicinal',
            medium = 'medicinal',
            strong = 'strongly medicinal'
        }
    },

    sweet = {
        labels = {
            weak = 'slightly sweet',
            medium = 'sweet',
            strong = 'very sweet'
        }
    },

    fruity = {
        messages = {
            weak = 'slightly fruity',
            medium = 'fruity',
            strong = 'strongly fruity'
        }
    },

    coffee = {
        messages = {
            weak = 'slightly coffee-like',
            medium = 'coffee-like',
            strong = 'strongly coffee-like'
        }
    },

    unusual = {
        messages = {
            weak = 'It smells unusual.',
            medium = 'It has an unusual smell.',
            strong = 'It has a very unusual smell.'
        }
    }
}
```

{% endcode %}


# config.client.lua

Preview File Updated: v1.0.2 - 04/08/2026

{% code fullWidth="true" %}

```lua
CL = {}

-- ███╗   ██╗ ██████╗ ████████╗██╗███████╗██╗ ██████╗ █████╗ ████████╗██╗ ██████╗ ███╗   ██╗███████╗
-- ████╗  ██║██╔═══██╗╚══██╔══╝██║██╔════╝██║██╔════╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗  ██║██╔════╝
-- ██╔██╗ ██║██║   ██║   ██║   ██║█████╗  ██║██║     ███████║   ██║   ██║██║   ██║██╔██╗ ██║███████╗
-- ██║╚██╗██║██║   ██║   ██║   ██║██╔══╝  ██║██║     ██╔══██║   ██║   ██║██║   ██║██║╚██╗██║╚════██║
-- ██║ ╚████║╚██████╔╝   ██║   ██║██║     ██║╚██████╗██║  ██║   ██║   ██║╚██████╔╝██║ ╚████║███████║
-- ╚═╝  ╚═══╝ ╚═════╝    ╚═╝   ╚═╝╚═╝     ╚═╝ ╚═════╝╚═╝  ╚═╝   ╚═╝   ╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝
CL.Notification = function(data)
    local color = 
        data.type == "success" and '#36f230' or
        data.type == "error"   and '#f23030' or
        data.type == "info"    and '#4287f5'
    
    if GetResourceState("vms_notify") == 'started' then
        exports['vms_notify']:Notification("", data.message, data.time, color, data.icon)
    elseif GetResourceState("lation_ui") == 'started' then
        exports['lation_ui']:notify({
            message = data.message,
            type = data.type,
            duration = data.time,
        })
    else
        TriggerEvent('esx:showNotification', data.message)
        TriggerEvent('QBCore:Notify', data.message, 'primary', data.time)
    end
end


CL.QuestionMenu = function(data, cb)
    if GetResourceState("vms_notifyv2") == 'started' then
        local question = exports['vms_notifyv2']:Question({
            title = data.title,
            description = data.description,
            color = "#673feb",
            icon = "fa-solid fa-circle-question",
        }, {
            {
                displayKey = "Y",
                description = TRANSLATE("control.request:accept"),
                control = 246
            },
            {
                displayKey = "N",
                description = TRANSLATE("control.request:decline"),
                control = 306
            }
        })

        Citizen.Await(question) -- IT MUST BE HERE

        if question == "246" then -- export returns clicked control as string!
            cb(true)
        else
            cb(false)
        end
    elseif GetResourceState('lation_ui') == 'started' then
        local result = exports.lation_ui:alert({
            header = data.title,
            content = data.description,
            icon = 'fa-solid fa-circle-question',
            iconColor = '#673feb',
            type = 'default',
            labels = {
                cancel = TRANSLATE("control.request:decline"),
                confirm = TRANSLATE("control.request:accept"),
            }
        })

        if result == 'confirm' then
            cb(true)
        else
            cb(false)
        end
    end
end


-- ████████╗ █████╗ ██████╗  ██████╗ ███████╗████████╗
-- ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝
--    ██║   ███████║██████╔╝██║  ███╗█████╗     ██║   
--    ██║   ██╔══██║██╔══██╗██║   ██║██╔══╝     ██║   
--    ██║   ██║  ██║██║  ██║╚██████╔╝███████╗   ██║   
--    ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝ ╚══════╝   ╚═╝   
CL.Target = function(type, data)
    if type == 'entity' then
        if Config.TargetResource == 'ox_target' then
            local options = {}
            
            if data.options and next(data.options) then
                for k, v in pairs(data.options) do
                    local icon = v.icon or 'fa-solid fa-bars' -- Default Icon
                    
                    options[#options + 1] = {
                        name = v.name,
                        icon = icon,
                        label = v.label,
                        distance = v.distance or 1.2,
                        items = v.requiredItem,
                        groups = v.jobs,
                        onSelect = v.action,
                        canInteract = v.canInteract,
                    }
                end
            end

            exports['ox_target']:addLocalEntity(data.entity, options)
        elseif Config.TargetResource == 'qb-target' then
            local options = {}
            
            if data.options and next(data.options) then
                for k, v in pairs(data.options) do
                    local icon = v.icon or 'fa-solid fa-bars' -- Default Icon
                    
                    options[#options + 1] = {
                        num = #options + 1,
                        icon = icon,
                        label = v.label,
                        distance = v.distance or 1.2,
                        item = v.requiredItem,
                        job = v.jobs,
                        action = v.action,
                        canInteract = v.canInteract,
                    }
                end
            end
            
            exports['qb-target']:AddTargetEntity(data.entity, {
                options = options,
                distance = 2.0
            })
        else
            warn('You need to prepare CL.Target for the target system')
        end
        
    elseif type == 'remove-entity' then
        if Config.TargetResource == 'ox_target' then
            exports['ox_target']:removeLocalEntity(data)
        elseif Config.TargetResource == 'qb-target' then
            exports['qb-target']:RemoveTargetEntity(data)
        else
            warn('You need to prepare CL.Target for the target system')
        end

    end
end


-- ██████╗ ██████╗     ████████╗███████╗██╗  ██╗████████╗
-- ╚════██╗██╔══██╗    ╚══██╔══╝██╔════╝╚██╗██╔╝╚══██╔══╝
--  █████╔╝██║  ██║       ██║   █████╗   ╚███╔╝    ██║   
--  ╚═══██╗██║  ██║       ██║   ██╔══╝   ██╔██╗    ██║   
-- ██████╔╝██████╔╝       ██║   ███████╗██╔╝ ██╗   ██║   
-- ╚═════╝ ╚═════╝        ╚═╝   ╚══════╝╚═╝  ╚═╝   ╚═╝   
CL.DrawText3D = function(coords, text)
    if not Config.UseDrawText then return end
    
    local textScale = 0.50
    local camCoords = GetFinalRenderedCamCoord()
    local distance = #(coords.xyz - camCoords)
    local scale = (textScale / distance) * 2
    local fov = (1 / GetGameplayCamFov()) * 100
    scale = scale * fov
    
    SetTextScale(0.0 * scale, 0.50 * scale)
    SetTextFont(4)
    SetTextDropshadow(0, 0, 0, 0, 255)
    SetTextProportional(1)
    SetTextColour(255, 255, 255, 205)
    SetTextDropShadow()
    SetTextCentre(true)

    if Config.UseDrawTextBox then
        BeginTextCommandWidth("STRING")
        AddTextComponentString(text)
        local textWidth = EndTextCommandGetWidth(true) + (0.006 * scale * 2)
        
        SetDrawOrigin(coords.x, coords.y, coords.z, 0)
        DrawRect(0.0, 0.018 * scale, textWidth, 0.045 * scale, 11, 4, 20, 120)
    else
        SetDrawOrigin(coords.x, coords.y, coords.z, 0)
    end

    SetTextEntry("STRING")
    AddTextComponentString(text)
    DrawText(0.0, 0.0)

    ClearDrawOrigin()
end


-- ███████╗██████╗  █████╗ ███╗   ███╗███████╗██╗    ██╗ ██████╗ ██████╗ ██╗  ██╗
-- ██╔════╝██╔══██╗██╔══██╗████╗ ████║██╔════╝██║    ██║██╔═══██╗██╔══██╗██║ ██╔╝
-- █████╗  ██████╔╝███████║██╔████╔██║█████╗  ██║ █╗ ██║██║   ██║██████╔╝█████╔╝ 
-- ██╔══╝  ██╔══██╗██╔══██║██║╚██╔╝██║██╔══╝  ██║███╗██║██║   ██║██╔══██╗██╔═██╗ 
-- ██║     ██║  ██║██║  ██║██║ ╚═╝ ██║███████╗╚███╔███╔╝╚██████╔╝██║  ██║██║  ██╗
-- ╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝ ╚══╝╚══╝  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
CL.IsPlayerLoaded = function(type)
    if Config.Core == "ESX" then
        return ESX.IsPlayerLoaded()
    elseif Config.Core == "QB-Core" then
        return CL.GetPlayerData() and CL.GetPlayerData().job ~= nil
    end
end

CL.GetPlayerData = function(type)
    if Config.Core == "ESX" then
        return ESX.GetPlayerData()
    elseif Config.Core == "QB-Core" then
        return QBCore.Functions.GetPlayerData()
    end
end

CL.GetItemLabel = function(name)
    if GetItemLabel then
        return GetItemLabel(name)
    else
        if Config.Core == "QB-Core" and QBCore?.Shared?.Items then
            local item = QBCore.Shared.Items[name]
            if item.label then
                return item.label
            end
        end

        return name
    end
end

CL.GetCancelUsageAction = function(ped)
    -- Death Check
    if GetEntityHealth(ped) <= 0 or IsEntityDead(ped) or LocalPlayer.state.isDead then
        return 'drop'
    end

    -- Stun Check
    if IsPedBeingStunned(ped) then
        return 'drop'
    end

    -- Ragdoll
    local ragdollReason = DetectRagdollReason(ped)
    if ragdollReason == 'fall' or ragdollReason == 'vehicleHit' then
        return 'drop'
    end
    
    -- qb-smallresources handsup.lua
    -- if exports['qb-smallresources']:getHandsup() then
    --     return 'cancel'
    -- end

    -- Example: Handcuffed
    -- if exports['your_cuffs']:IsHandcuffed() then
    --     return 'cancel'
    -- end

    return false
end

CL.GetPlayerGender = function(ped)
    local model = GetEntityModel(ped)

    if model == GetHashKey("mp_m_freemode_01") then
        return "male"
    elseif model == GetHashKey("mp_f_freemode_01") then
        return "female"
    end

    return "unknown"
end


-- ███████╗████████╗ █████╗ ████████╗██╗   ██╗███████╗
-- ██╔════╝╚══██╔══╝██╔══██╗╚══██╔══╝██║   ██║██╔════╝
-- ███████╗   ██║   ███████║   ██║   ██║   ██║███████╗
-- ╚════██║   ██║   ██╔══██║   ██║   ██║   ██║╚════██║
-- ███████║   ██║   ██║  ██║   ██║   ╚██████╔╝███████║
-- ╚══════╝   ╚═╝   ╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚══════╝
CL.OnConsume = function(itemName, biteSize)
    local isGymStarted = GetResourceState('vms_gym') == 'started'
    if isGymStarted then
        local value = ({
            -- Here you can configure the items that will negatively affect the player's stamina.
            ['cigarette'] = math.random(1, 8),  -- 0.1 - 0.8%
            ['joint_sd']  = math.random(0, 5),  -- 0.0 - 0.5%
            ['joint_nl']  = math.random(0, 5),  -- 0.0 - 0.5%
        })[itemName]

        if value and value >= 1 then
            value = value / 10
            exports["vms_gym"]:removeSkill('condition', value)
        end
    end
end

CreateThread(function()
    while WaitingForLoad do
        Citizen.Wait(100)
    end

    local resourceName = GetCurrentResourceName()
    while true do
        Wait(2000)

        if CL.IsPlayerLoaded() then
            local removeHealth = 0
            
            local hunger = exports[resourceName]:GetPercentageStatus('hunger')
            local thirst = exports[resourceName]:GetPercentageStatus('thirst')
    
            if hunger <= 5 then
                removeHealth = removeHealth + math.random(0, 2)
            end
    
            if thirst <= 5 then
                removeHealth = removeHealth + math.random(0, 3)
            end
    
            if removeHealth > 0 then
                local myPed = PlayerPedId()
                local health = GetEntityHealth(myPed)
    
                SetEntityHealth(myPed, health - removeHealth)
            end
        end
    end
end)


-- ██╗███╗   ██╗██╗████████╗██╗ █████╗ ████████╗██╗ ██████╗ ███╗   ██╗
-- ██║████╗  ██║██║╚══██╔══╝██║██╔══██╗╚══██╔══╝██║██╔═══██╗████╗  ██║
-- ██║██╔██╗ ██║██║   ██║   ██║███████║   ██║   ██║██║   ██║██╔██╗ ██║
-- ██║██║╚██╗██║██║   ██║   ██║██╔══██║   ██║   ██║██║   ██║██║╚██╗██║
-- ██║██║ ╚████║██║   ██║   ██║██║  ██║   ██║   ██║╚██████╔╝██║ ╚████║
-- ╚═╝╚═╝  ╚═══╝╚═╝   ╚═╝   ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝ ╚═════╝ ╚═╝  ╚═══╝
function CL.InitiateFunctions()
    -- Ambulance Integration
    local function RestoreDefaultStatuses()
        local hunger = exports['vms_needs']:GetPercentageStatus('hunger')
        if hunger < 50.0 then
            exports['vms_needs']:SetStatus('hunger', 250.0) -- 250.0 = half
        end
            
        local thirst = exports['vms_needs']:GetPercentageStatus('thirst')
        if thirst < 50.0 then
            exports['vms_needs']:SetStatus('thirst', 250.0) -- 250.0 = half
        end
    end

    local foundIntegration = false
    if GetResourceState('tk_ambulancejob') == 'started' then
        foundIntegration = 'tk_ambulancejob'

        RegisterNetEvent('tk_ambulancejob:onPlayerRevive', function()
            RestoreDefaultStatuses()
            exports['vms_needs']:CleanupOptionalStatuses()
        end)

    elseif GetResourceState('wasabi_ambulance') == 'started' then
        foundIntegration = 'wasabi_ambulance'
        
        RegisterNetEvent('wasabi_ambulance:revive', function()
            Citizen.Wait(500) -- Wait for the player to be fully revived
            exports['vms_needs']:ReloadVisualEffect()
        end)

        RegisterNetEvent('wasabi_ambulance:revivePlayer', function()
            Citizen.Wait(500) -- Wait for the player to be fully revived
            exports['vms_needs']:ReloadVisualEffect()
        end)

    elseif GetResourceState('wasabi_ambulance_v2') == 'started' then
        foundIntegration = 'wasabi_ambulance_v2'

        RegisterNetEvent('wasabi_ambulance:Client:OnRevive', function()
            Citizen.Wait(500) -- Wait for the player to be fully revived
            exports['vms_needs']:ReloadVisualEffect()
        end)
        
        RegisterNetEvent('wasabi_ambulance:Client:Listeners:OnRespawn', function()
            exports['vms_needs']:CleanupOptionalStatuses()
        end)

        RegisterNetEvent('wasabi_ambulance:Client:Listeners:OnFacilityHeal', function()
            exports['vms_needs']:CleanupOptionalStatuses()
        end)

    end

    if foundIntegration then
        library.Debug(('^2[Integration]^7 Found Ambulance Integration: ^3%s^7'):format(foundIntegration))
    end
end

```

{% endcode %}


# config.server.lua

Preview File Updated: v1.0.2 - 04/08/2026

{% code fullWidth="true" %}

```lua
SV = {}

-- ██████╗  █████╗ ████████╗ █████╗ ██████╗  █████╗ ███████╗███████╗
-- ██╔══██╗██╔══██╗╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝
-- ██║  ██║███████║   ██║   ███████║██████╔╝███████║███████╗█████╗  
-- ██║  ██║██╔══██║   ██║   ██╔══██║██╔══██╗██╔══██║╚════██║██╔══╝  
-- ██████╔╝██║  ██║   ██║   ██║  ██║██████╔╝██║  ██║███████║███████╗
-- ╚═════╝ ╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝╚═════╝ ╚═╝  ╚═╝╚══════╝╚══════╝
SV.Database = {
    ['ESX'] = {
        player = {
            table = 'users',
            columns = {
                identifier = 'identifier',
            }
        },
    },

    ['QB-Core'] = {
        player = {
            table = 'players',
            columns = {
                identifier = 'citizenid',
            }
        },
    }
}


-- ███████╗██████╗  █████╗ ███╗   ███╗███████╗██╗    ██╗ ██████╗ ██████╗ ██╗  ██╗
-- ██╔════╝██╔══██╗██╔══██╗████╗ ████║██╔════╝██║    ██║██╔═══██╗██╔══██╗██║ ██╔╝
-- █████╗  ██████╔╝███████║██╔████╔██║█████╗  ██║ █╗ ██║██║   ██║██████╔╝█████╔╝ 
-- ██╔══╝  ██╔══██╗██╔══██║██║╚██╔╝██║██╔══╝  ██║███╗██║██║   ██║██╔══██╗██╔═██╗ 
-- ██║     ██║  ██║██║  ██║██║ ╚═╝ ██║███████╗╚███╔███╔╝╚██████╔╝██║  ██║██║  ██╗
-- ╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝ ╚══╝╚══╝  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
SV.GetPlayer = function(src)
    if Config.Core == "ESX" then
        return Core.GetPlayerFromId(src)
    elseif Config.Core == "QB-Core" then
        return Core.Functions.GetPlayer(src)
    end
end

SV.GetIdentifier = function(xPlayer)
    if Config.Core == "ESX" then
        return xPlayer.identifier
    elseif Config.Core == "QB-Core" then
        return xPlayer.PlayerData.citizenid
    end
end

SV.RegisterCommand = function(command, groups, cb, help, suggestions)
    if Config.Core == "ESX" then
        local arguments = {}

        if suggestions then
            for k, v in pairs(suggestions) do
                table.insert(arguments, {
                    name = v.name,
                    help = v.label,
                    type = 'any'
                })
            end
        end
        
        Core.RegisterCommand(command, groups, function(xPlayer, args, showError)
            local src = xPlayer.source
            cb(src, args)
        end, false, {
            help = help,
            arguments = arguments
        })
    elseif Config.Core == "QB-Core" then
        local arguments = {}

        if suggestions then
            for k, v in pairs(suggestions) do
                table.insert(arguments, {
                    name = v.name,
                    help = v.label
                })
            end
        end
        
        Core.Commands.Add(command, help, arguments, false, function(source, args)
            cb(source, args)
        end, groups)
    end
end


-- ██╗████████╗███████╗███╗   ███╗███████╗
-- ██║╚══██╔══╝██╔════╝████╗ ████║██╔════╝
-- ██║   ██║   █████╗  ██╔████╔██║███████╗
-- ██║   ██║   ██╔══╝  ██║╚██╔╝██║╚════██║
-- ██║   ██║   ███████╗██║ ╚═╝ ██║███████║
-- ╚═╝   ╚═╝   ╚══════╝╚═╝     ╚═╝╚══════╝
SV.RegisterUsableItem = function(name, cb)
    if RegisterUsableItem then -- The script looks for functions in ./integration/[inventory]/*
        RegisterUsableItem(name, cb)
    else
        if Config.Core == "ESX" then
            Core.RegisterUsableItem(name, function(src, itemName, itemData)
                cb(src, itemName, {metadata = itemData.metadata})
            end)
        elseif Config.Core == "QB-Core" then
            Core.Functions.CreateUseableItem(name, function(src, item)
                cb(src, item.name, {metadata = item.info})
            end)
        end
    end
end

SV.GetItemCount = function(xPlayer, name)
    if Config.Core == "ESX" then
        return xPlayer.getInventoryItem(name).count
    elseif Config.Core == "QB-Core" then
        return xPlayer.Functions.GetItemByName(name) and xPlayer.Functions.GetItemByName(name).amount or 0
    end
end

SV.AddItem = function(src, xPlayer, name, count, metadata)
    if AddItem then -- The script looks for functions in ./integration/[inventory]/*
        AddItem(src, xPlayer, name, count, metadata)
    else
        if Config.Core == "ESX" then
            xPlayer.addInventoryItem(name, count)
        elseif Config.Core == "QB-Core" then
            xPlayer.Functions.AddItem(name, count)
        end
    end
end

SV.RemoveItem = function(src, xPlayer, name, count, slot)
    if RemoveItem then -- The script looks for functions in ./integration/[inventory]/*
        RemoveItem(src, xPlayer, name, count, slot)
    else
        if Config.Core == "ESX" then
            xPlayer.removeInventoryItem(name, count)
        elseif Config.Core == "QB-Core" then
            xPlayer.Functions.RemoveItem(name, count)
        end
    end
end
```

{% endcode %}


# config.items.lua

Preview File Updated: v1.0.2 - 04/08/2026

{% code fullWidth="true" %}

```lua
Config = Config or {}

Config.Items = {
    -- █▀ ▄▀▄ ▄▀▄ █▀▄
    -- █▀ ▀▄▀ ▀▄▀ █▄▀
    ['sandwich'] = { ... },
    ['burger'] = { ... },
    ['hotdog'] = {
        type = 'food',

        size = 100,
        
        models = {
            [0] = 'prop_cs_hotdog_02',
            [50] = 'prop_cs_hotdog_01',
        },

        attaches = {
            hand = {
                bone = 18905,
                offset = vector3(0.131000, 0.017000, 0.024000),
                rotation = vector3(149.100067, -32.099941, -4.800000),
            },
        },
        
        animation = {
            ['bite'] = {
                dictionary = "mp_player_inteat@burger",
                name = "mp_player_int_eat_burger_fp",
                duration = 1200,
                flag = 51
            },
        },
        
        consume = {
            ['bite'] = {
                minimum = 12,
                maximum = 20
            },
            ['add'] = {
                hunger = {40, 90},
                thirst = 0,
                drunk = 0
            },
            ['remove'] = {
                stress = 0,
                weed_indica = {0, 2},
                weed_sativa = {0, 2},
            },
        }
    },
    ['apple'] = { ... },

    
    -- █▀▄ █▀▄ █ █▄ █ █▄▀
    -- █▄▀ █▀▄ █ █ ▀█ █ █
    ['water'] = { ... },
    ['sprunk_can'] = {
        type = 'drink',
        smellProfile = 'sweet',

        size = 330,

        disableRun = true,
        disableJump = true,
        disableMelee = true,
        disableAsDriver = true,
        disableInVehicle = true,

        models = {
            default = 'prop_ld_can_01b'
        },

        attaches = {
            hand = {
                bone = 57005,
                offset = vector3(0.139000, 0.018000, -0.027000),
                rotation = vector3(-101.920815, -117.601173, -6.399996),
            },
        },
        
        animation = {
            ['idle'] = {
                dictionary = "amb@code_human_wander_drinking@male@base",
                name = "static",
                duration = -1,
                flag = 51
            },
            ['bite'] = {
                dictionary = "amb@code_human_wander_drinking_fat@male@idle_a",
                name = "idle_c",
                duration = 2000,
                flag = 51
            },
        },

        consume = {
            ['bite'] = {
                minimum = 35,
                maximum = 70
            },
            ['add'] = {
                thirst = {14, 32}
            },
            ['remove'] = {
                stress = 0
            },
        }
    },
    ['ecola_can'] = { ... },
    ['junk_can'] = { ... },


    -- ▄▀▄ █   ▄▀▀ ▄▀▄ █▄█ ▄▀▄ █  
    -- █▀█ █▄▄ ▀▄▄ ▀▄▀ █ █ ▀▄▀ █▄▄
    ['patriot_beer'] = {
        type = 'drink',
        smellProfile = 'alcohol',

        size = 500,

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = true,
        disableInVehicle = false,
        
        models = {
            default = 'prop_beer_patriot'
        },

        attaches = {
            hand = {
                bone = 57005,
                offset = vector3(0.109000, -0.130000, -0.054000),
                rotation = vector3(-88.800514, -223.843597, 12.799995),
            },
        },

        animation = {
            ['idle'] = {
                dictionary = "amb@code_human_wander_drinking@male@base",
                name = "static",
                duration = -1,
                flag = 51
            },
            ['bite'] = {
                dictionary = "amb@code_human_wander_drinking_fat@male@idle_a",
                name = "idle_c",
                duration = 2000,
                flag = 51
            },
        },
        
        consume = {
            ['bite'] = {
                minimum = 10,
                maximum = 20
            },
            ['add'] = {
                thirst = {0.8, 2.0},
                drunk = {0.8, 1.7}
            },
            ['remove'] = {
                stress = 0
            },
        }
    },
    ['logger_beer'] = { ... },
    ['piswasser_beer'] = { ... },
    ['themount_whiskey'] = { ... },
    ['bleuterd_champagne'] = { ... },
    ['bleuterd_champagne2'] = { ... },
    ['vodka'] = { ... },


    -- ▄▀  █   ▄▀▄ ▄▀▀ ▄▀▀
    -- ▀▄█ █▄▄ █▀█ ▄██ ▄██
    ['glass'] = {
        type = 'glass',

        size = 300,

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = true,
        disableInVehicle = false,
        
        allowedDrinks = {
            ['water'] = true,
            ['ecola_can'] = true,
            ['sprunk_can'] = true,
            ['junk_can'] = true,
            ['patriot_beer'] = true,
            ['logger_beer'] = true,
            ['piswasser_beer'] = true,
            ['themount_whiskey'] = true,
            ['bleuterd_champagne'] = true,
            ['bleuterd_champagne2'] = true,
            ['codeine'] = true,
        },

        models = {
            [0] = 'prop_wheat_grass_empty',
            [5] = 'vms_glass_half_b',  -- If there is no model available for a given liquid, this one is used.
            [50] = 'vms_glass_full_b',  -- If there is no model available for a given liquid, this one is used.

            liquids = {
                water = {
                    [5] = 'vms_glass_half_b', -- 5% - 49%
                    [50] = 'vms_glass_full_b' -- 50% - 100%
                },
                ecola_can = {
                    [5] = 'vms_glass_half_e',
                    [50] = 'vms_glass_full_e'
                },
                sprunk_can = {
                    [5] = 'vms_glass_half_d',
                    [50] = 'vms_glass_full_d'
                },
                junk_can = {
                    [5] = 'vms_glass_half_f',
                    [50] = 'vms_glass_full_f'
                },
                -- coffee = {
                --     [5] = 'vms_glass_half_h',
                --     [50] = 'vms_glass_full_h'
                -- },
                patriot_beer = {
                    [5] = 'vms_glass_half_c',
                    [50] = 'vms_glass_full_c'
                },
                logger_beer = {
                    [5] = 'vms_glass_half_c',
                    [50] = 'vms_glass_full_c'
                },
                piswasser_beer = {
                    [5] = 'vms_glass_half_c',
                    [50] = 'vms_glass_full_c'
                },
                themount_whiskey = {
                    [5] = 'vms_glass_half_g',
                    [50] = 'vms_glass_full_g'
                },
                codeine = {
                    [5] = 'vms_glass_half_a',
                    [50] = 'vms_glass_full_a'
                },
            }
        },

        attaches = {
            hand = {
                bone = 57005,
                offset = vector3(0.124000, -0.131000, -0.027000),
                rotation = vector3(-82.240364, -217.123444, 7.360001),
            },
        },

        animation = {
            ['idle'] = {
                dictionary = "amb@code_human_wander_drinking@male@base",
                name = "static",
                duration = -1,
                flag = 51
            },
            ['bite'] = {
                dictionary = "amb@code_human_wander_drinking_fat@male@idle_a",
                name = "idle_c",
                duration = 2000,
                flag = 51
            },
            ['smell'] = {
                dictionary = "amb@code_human_wander_drinking@male@idle_a",
                name = "idle_b",
                duration = 3000,
                flag = 51
            },
        },

        consume = {
            ['bite'] = {
                minimum = 25,
                maximum = 50
            },
        }
    },
    ['whisky_glass'] = { ... },
    ['shot_glass'] = { ... },
    ['champ_flute'] = { ... },


    -- ▄▀▀ █▄ ▄█ ▄▀▄ █▄▀ ██▀
    -- ▄██ █ ▀ █ ▀▄▀ █ █ █▄▄
    ['redwood_pack'] = {
        type = 'cigarette_pack',
        behavior = 'cigarette_pack',

        size = 20,

        giveItem = 'cigarette',

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = true,
        disableInVehicle = false,

        models = {
            default = 'prop_fag_packet_01',
            cigarette = 'prop_cs_ciggy_01'
        },

        attaches = {
            hand = {
                bone = 18905,
                offset = vector3(0.104000, 0.041000, 0.018000),
                rotation = vector3(60.300232, -199.348022, -186.298553),
            },
            rightHand = {
                bone = 57005,
                offset = vector3(0.112000, 0.049000, -0.048000),
                rotation = vector3(-23.699961, -306.443665, -203.097870),
            },
        },

        animation = {
            ['take_out'] = {
                dictionary = "vms@needs@cig_pack",
                name = "take_out",
                duration = nil, -- ./shared/behaviors/CigarettePackBehavior.lua
                flag = 49,
            },
        },
    },
    ['cigarette'] = {
        type = 'cig',
        behavior = 'smoking',

        size = 100,

        requiredItems = {
            {name = 'lighter', quantity = 1, remove = false},
        },

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = true,
        disableInVehicle = false,

        models = {
            cigarette = {
                unlit = 'prop_cs_ciggy_01',
                lit = {
                    [0] = 'vms_cigarette_1',
                    [30] = 'vms_cigarette_2',
                    [60] = 'vms_cigarette_3',
                    [80] = 'prop_cs_ciggy_01b'
                },
            },
            lighter = 'p_cs_lighter_01'
        },

        particles = {
            ['fire'] = {
                dictionary = 'scr_fm_mp_missioncreator',
                name = 'scr_sh_lighter_flame',
                rotation = vector3(0.0, 0.0, 0.0),
                scale = 1.5,
                time = 1500
            },
            ['start_smoke'] = {
                dictionary = 'core',
                name = 'ent_anim_cig_exhale_nse_car',
                boneIndex = 31086,
                rotation = vector3(0.0, 0.0, 0.0),
                scale = 1.5,
                time = 1500
            },
            ['smoke'] = {
                dictionary = 'core',
                name = 'ent_anim_cig_smoke',
                rotation = vector3(0.0, 0.0, 0.0),
                scale = 1.0,
                time = 4400
            },
            ['exhale'] = {
                dictionary = 'cut_finale1',
                name = 'cs_cig_exhale_mouth_finale',
                boneIndex = 31086,
                rotation = vector3(0.0, 0.0, 0.0),
                scale = 1.5,
                time = 2000
            },
        },

        attaches = {
            hand = {
                bone = 57005,
                offset = vector3(0.125000, 0.022000, -0.057000),
                rotation = vector3(-8.250003, -4.050001, 106.350700),
            },

            leftHand = {
                bone = 18905,
                offset = vector3(0.110000, -0.008000, 0.026000),
                rotation = vector3(294.795197, 112.130989, 379.156525),
            },
        },

        animation = {
            ['idle'] = {
                dictionary = "vms@needs@smoking",
                name = "idle",
                duration = -1,
                flag = 49
            },

            ['bite'] = {
                dictionary = "vms@needs@smoking",
                name = "hit",
                duration = 4800,
                flag = 49,
            },

            ['light'] = {
                dictionary = "vms@needs@smoking",
                name = "light",
                duration = nil, -- ./shared/behaviors/SmokingBehavior.lua
                flag = 49,
            },
        },
        
        consume = {
            ['bite'] = {
                minimum = 8,
                maximum = 14
            },
            ['remove'] = {
                thirst = {1.6, 3.5},
                stress = {1.6, 3.5},
            },
        }
    },


    --  █▀▄ █▀▄ █ █ ▄▀  ▄▀▀
    --  █▄▀ █▀▄ ▀▄█ ▀▄█ ▄██
    ['codeine'] = {
        type = 'drink',
        smellProfile = 'medicinal',

        size = 100,

        disableRun = true,
        disableJump = true,
        disableMelee = true,
        disableAsDriver = true,
        disableInVehicle = true,
        
        models = {
            default = 'vms_codeine'
        },
        
        attaches = {
            hand = {
                bone = 57005,
                offset = vector3(0.131000, -0.060000, -0.028000),
                rotation = vector3(-92.700562, 7.500003, -1.950002),
            },
        },

        animation = {
            ['idle'] = {
                dictionary = "amb@code_human_wander_drinking@male@base",
                name = "static",
                duration = -1,
                flag = 51
            },
            ['bite'] = {
                dictionary = "amb@code_human_wander_drinking_fat@male@idle_a",
                name = "idle_c",
                duration = 2000,
                flag = 51
            },
        },

        consume = {
            ['bite'] = {
                minimum = 15,
                maximum = 40
            },
            ['add'] = {
                codeine = {10, 18},
            },
            ['remove'] = {
                stress = 0
            },
        }
    },
    ['joint_sd'] = {
        type = 'drug',
        behavior = 'joint',

        size = 100,

        requiredItems = {
            {name = 'lighter', quantity = 1, remove = false},
        },

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = false,
        disableInVehicle = false,

        models = {
            joint = {
                unlit = 'p_cs_joint_02',
                lit = {
                    [0] = 'vms_joint_1',
                    [30] = 'vms_joint_2',
                    [60] = 'vms_joint_3',
                    [80] = 'p_cs_joint_01'
                },
            },
            lighter = 'p_cs_lighter_01'
        },

        particles = {
            ['fire'] = {
                dictionary = 'scr_fm_mp_missioncreator',
                name = 'scr_sh_lighter_flame',
                rotation = vector3(0.0, 0.0, 0.0),
                scale = 1.5,
                time = 1500
            },
            ['start_smoke'] = {
                dictionary = 'core',
                name = 'ent_anim_cig_exhale_nse_car',
                boneIndex = 31086,
                rotation = vector3(0.0, 0.0, 0.0),
                scale = 1.5,
                time = 1500
            },
            ['smoke'] = {
                dictionary = 'core',
                name = 'ent_anim_cig_smoke',
                rotation = vector3(0.0, 0.0, 0.0),
                scale = 1.0,
                time = 4400
            },
            ['exhale'] = {
                dictionary = 'cut_finale1',
                name = 'cs_cig_exhale_mouth_finale',
                boneIndex = 31086,
                rotation = vector3(0.0, 0.0, 0.0),
                scale = 1.5,
                time = 2000
            },
        },

        attaches = {
            hand = {
                bone = 57005,
                offset = vector3(0.125000, 0.022000, -0.057000),
                rotation = vector3(-8.250003, -4.050001, 106.350700),
            },

            leftHand = {
                bone = 18905,
                offset = vector3(0.110000, -0.008000, 0.026000),
                rotation = vector3(294.795197, 112.130989, 379.156525),
            },

            mouth = {
                bone = 31086,
                offset = vector3(-0.009000, 0.123000, -0.018000),
                rotation = vector3(-175.498993, 72.300354, -106.200684),
            },

            ear = {
                bone = 31086,
                offset = vector3(0.040000, -0.025000, -0.076000),
                rotation = vector3(-177.448914, 8.850001, -136.200577),
            }
        },

        animation = {
            ['idle'] = {
                dictionary = "vms@needs@smoking",
                name = "idle",
                duration = -1,
                flag = 49
            },

            ['bite'] = {
                dictionary = "vms@needs@smoking",
                name = "hit",
                duration = 4800,
                flag = 49,
            },

            ['light'] = {
                dictionary = "vms@needs@smoking",
                name = "light",
                duration = nil, -- ./shared/behaviors/SmokingBehavior.lua
                flag = 49,
            },
        },
        
        consume = {
            ['bite'] = {
                minimum = 6,
                maximum = 10
            },
            ['add'] = {
                weed_sativa = {1.9, 3.2}
            },
            ['remove'] = {
                thirst = {1.6, 3.5},
                stress = {1.6, 3.5},
            },
        }
    },
    ['joint_nl'] = { ... },
    ['cocaine'] = {
        type = 'drug',
        behavior = 'cocaine',
        smellProfile = 'chemical',

        size = 1,

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = false,
        disableInVehicle = false,
        
        models = {
            bag = 'vms_cocaine_bag',
            spatula = 'bkr_prop_coke_spatula_02',
            powder = 'vms_cocaine_pile',
            line = 'vms_cocaine_line',
            card = 'prop_cs_business_card',
            banknote = 'tr_prop_tr_note_rolled_01a',
        },

        attaches = {
            hand = {
                bone = 18905,
                offset = vector3(0.114000, 0.054000, 0.057000),
                rotation = vector3(-100.200638, -68.700317, 10.500000),
            },
            card = {
                bone = 57005,
                offset = vector3(0.138000, 0.030000, -0.075000),
                rotation = vector3(0.000000, 0.000000, -93.300568),
            },
            banknote = {
                bone = 57005,
                offset = vector3(0.121000, 0.032000, -0.060000),
                rotation = vector3(31.039976, 0.000000, -40.959965),
            },
            spatula = {
                bone = 57005,
                offset = vector3(0.100000, 0.011000, -0.096000),
                rotation = vector3(-302.646210, -21.259981, -35.197865),
            },
        },
        
        animation = {
            ['idle'] = {
                dictionary = "vms@needs@drugs",
                name = "idle_bag",
                duration = -1,
                flag = 51
            },
            ['place'] = {
                dictionary = "vms@needs@drugs",
                variants = {
                    high = "spill_high",
                    mid = "spill_mid",
                    low = "spill_low",
                },
                duration = 3300,
                flag = 1,
            },
            ['bite'] = {
                dictionary = "vms@needs@drugs",
                name = "snort_spatula",
                duration = 5900,
                flag = 51
            },
            ['prepare_line'] = {
                dictionary = "vms@needs@drugs",
                variants = {
                    high = "prepare_line_high",
                    mid = "prepare_line_mid",
                    low = "prepare_line_low",
                },
                duration = 6300,
                flag = 1
            },
            ['snort_line'] = {
                dictionary = "vms@needs@drugs",
                variants = {
                    high = "snort_line_high",
                    mid = "snort_line_mid",
                    low = "snort_line_low",
                },
                duration = 5650,
                flag = 1
            },
            ['sweep_away'] = {
                dictionary = "vms@needs@drugs",
                variants = {
                    high = "prepare_line_high",
                    mid = "prepare_line_mid",
                    low = "prepare_line_low",
                },
                duration = 1000,
                flag = 51
            },
        },

        consume = {
            ['bite'] = {
                minimum = 1,
                maximum = 1
            },
            ['add'] = {
                cocaine = 45,
            },
            ['remove'] = {
                hunger = {1, 10},
                alcohol = {2, 8},
            },
        }
    },
    ['amphetamine'] = {
        type = 'drug',
        behavior = 'snortable',
        smellProfile = 'chemical',

        size = 1,

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = false,
        disableInVehicle = false,
        
        models = {
            bag = 'vms_amphetamine_bag',
            powder = 'vms_amphetamine_pile',
            line = 'vms_amphetamine_line',
            card = 'prop_cs_business_card',
            banknote = 'tr_prop_tr_note_rolled_01a',
        },

        attaches = {
            hand = {
                bone = 18905,
                offset = vector3(0.114000, 0.054000, 0.057000),
                rotation = vector3(-100.200638, -68.700317, 10.500000),
            },
            card = {
                bone = 57005,
                offset = vector3(0.138000, 0.030000, -0.075000),
                rotation = vector3(0.000000, 0.000000, -93.300568),
            },
            banknote = {
                bone = 57005,
                offset = vector3(0.121000, 0.032000, -0.060000),
                rotation = vector3(31.039976, 0.000000, -40.959965),
            },
        },
        
        animation = {
            ['idle'] = {
                dictionary = "vms@needs@drugs",
                name = "idle_bag",
                duration = -1,
                flag = 51
            },
            ['place'] = {
                dictionary = "vms@needs@drugs",
                variants = {
                    high = "spill_high",
                    mid = "spill_mid",
                    low = "spill_low",
                },
                duration = 3300,
                flag = 1,
            },
            ['bite'] = {
                dictionary = "vms@needs@drugs",
                name = "snort_hand",
                duration = 5500,
                flag = 49
            },
            ['prepare_line'] = {
                dictionary = "vms@needs@drugs",
                variants = {
                    high = "prepare_line_high",
                    mid = "prepare_line_mid",
                    low = "prepare_line_low",
                },
                duration = 6300,
                flag = 1
            },
            ['snort_line'] = {
                dictionary = "vms@needs@drugs",
                variants = {
                    high = "snort_line_high",
                    mid = "snort_line_mid",
                    low = "snort_line_low",
                },
                duration = 5650,
                flag = 1
            },
            ['sweep_away'] = {
                dictionary = "vms@needs@drugs",
                variants = {
                    high = "prepare_line_high",
                    mid = "prepare_line_mid",
                    low = "prepare_line_low",
                },
                duration = 1000,
                flag = 51
            },
        },

        consume = {
            ['bite'] = {
                minimum = 1,
                maximum = 1
            },
            ['add'] = {
                amphetamine = 40,
            },
            ['remove'] = {
                hunger = {5, 10},
                alcohol = {2, 8},
            },
        }
    },
    ['meth'] = { ... },
    ['lsd'] = { ... },
    ['heroin_syringe'] = { ... },


    --  ▀█▀ ██▀ ▄▀▀ ▀█▀ █ █▄ █ ▄▀ 
    --   █  █▄▄ ▄██  █  █ █ ▀█ ▀▄█
    ['breathalyzer'] = {
        type = 'breathalyzer',
        behavior = 'breathalyzer',

        size = 1,

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = true,
        disableInVehicle = false,

        models = {
            default = 'vms_breathalyzer',
            positive = 'vms_breathalyzer_red',
            negative = 'vms_breathalyzer_green'
        },
        
        attaches = {
            hand = {
                bone = 18905,
                offset = vector3(0.098000, 0.068000, 0.030000),
                rotation = vector3(-10.649997, -78.150414, -4.350001),
            },
        },

        animation = {
            ['idle'] = {
                dictionary = "vms@needs@breathalyzer",
                name = "idle",
                duration = -1,
                flag = 49
            },
        },
    },
    ['drugtest_thc'] = {
        type = 'drugtest',
        behavior = 'drugtest',

        size = 1,

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = true,
        disableInVehicle = false,

        testCategory = 'THC', -- Config.DrugTests.Categories.THC

        models = {
            closed = 'vms_drugtest',
            opened = 'vms_drugtest_open',
        },

        attaches = {
            hand = {
                bone = 57005,
                offset = vector3(0.147000, 0.057000, -0.025000),
                rotation = vector3(30.299948, 50.700134, 16.799982),
            },
        },
    },
    ['drugtest_lsd'] = { ... },
    ['drugtest_multi'] = {
        type = 'drugtest',
        behavior = 'drugtest_multi',

        size = 1,

        disableRun = false,
        disableJump = false,
        disableMelee = true,
        disableAsDriver = true,
        disableInVehicle = false,

        models = {
            default = 'vms_drugtest_multi',
        },
        
        attaches = {
            hand = {
                bone = 57005,
                offset = vector3(0.150000, 0.077000, -0.005000),
                rotation = vector3(-88.350517, -41.850044, -40.800034),
            },
        },
    },
}
```

{% endcode %}


# config.clothing.lua

Preview File Updated: v1.0.2 - 04/08/2026

{% code fullWidth="true" %}

```lua
-- ████████╗███████╗███╗   ███╗██████╗ ███████╗██████╗  █████╗ ████████╗██╗   ██╗██████╗ ███████╗
-- ╚══██╔══╝██╔════╝████╗ ████║██╔══██╗██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██║   ██║██╔══██╗██╔════╝
--    ██║   █████╗  ██╔████╔██║██████╔╝█████╗  ██████╔╝███████║   ██║   ██║   ██║██████╔╝█████╗  
--    ██║   ██╔══╝  ██║╚██╔╝██║██╔═══╝ ██╔══╝  ██╔══██╗██╔══██║   ██║   ██║   ██║██╔══██╗██╔══╝  
--    ██║   ███████╗██║ ╚═╝ ██║██║     ███████╗██║  ██║██║  ██║   ██║   ╚██████╔╝██║  ██║███████╗
--    ╚═╝   ╚══════╝╚═╝     ╚═╝╚═╝     ╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝╚══════╝
Config = Config or {}
Config.Temperature = Config.Temperature or {}

--[[
    TEMPERATURE CLOTHING CONFIG

    This file contains all clothing-related temperature settings.

    IMPORTANT:
    - Most servers will only need to edit DrawableMap.
    - ClothingCategoryInsulation defines how effective each clothing category is.
    - BodyWeights defines which body parts have the biggest impact on temperature.
    - If a drawable is not mapped, DefaultClothingCategory will be used.

    Recommended categories:

    NONE
    - Bare chest
    - Underwear
    - Bikini tops
    - Barefoot

    VERY_LIGHT
    - Tank tops
    - Crop tops
    - Shorts
    - Flip-flops
    - Baseball caps

    LIGHT
    - T-shirts
    - Polo shirts
    - Shirts
    - Jeans
    - Sneakers

    MEDIUM
    - Hoodies
    - Sweaters
    - Long sleeves
    - Cargo pants

    HEAVY
    - Leather jackets
    - Bomber jackets
    - Thick pants
    - Work boots

    WINTER
    - Winter jackets
    - Parkas
    - Ski clothing
    - Thermal clothing
    - Winter boots
]]


---@class MenuCommand<table>
Config.Temperature.MenuCommand = {
    Enabled = true,
    Name = 'insulationmenu',
    Help = 'Open Insulation Menu',
    Groups = 'admin',
    Suggestions = {}
}


---@class ClothingCategoryInsulation<table>
-- cold = protection against cold temperatures
-- heat = heat retention (higher values increase overheating risk)
-- sun  = protection against direct sunlight
Config.Temperature.ClothingCategoryInsulation = {
    NONE        = { cold = 0.0, heat = 0.0, sun = 0.0 },        -- No clothing or almost no insulation, provides almost no protection from cold or heat
    VERY_LIGHT  = { cold = 0.05, heat = 0.05, sun = 0.05 },     -- Very light clothing - suitable for hot weather
    LIGHT       = { cold = 0.15, heat = 0.15, sun = 0.10 },     -- Standard everyday clothing
    MEDIUM      = { cold = 0.35, heat = 0.435, sun = 0.15 },    -- Warm everyday clothing - can become uncomfortable during hot weather
    HEAVY       = { cold = 0.60, heat = 0.60, sun = 0.20 },     -- Heavy clothing - good protection from cold but may cause overheating
    WINTER      = { cold = 1.00, heat = 1.00, sun = 0.25 },     -- Maximum insulation, designed for winter conditions
}


---@class DefaultClothingCategory<table>
-- Fallback clothing category used when a drawable is not mapped.
-- If a player wears a custom clothing pack and the drawable does not exist inside DrawableMap, these categories will be used.
Config.Temperature.DefaultClothingCategory = {
    torso = "LIGHT",
    legs = "LIGHT",
    shoes = "LIGHT",
    hats = "NONE",
    arms = "NONE",
}

---@class DrawableMap<table>
-- Maps clothing drawables to insulation categories.
-- If a drawable is not mapped, DefaultClothingCategory is used.
-- NOTE: Custom clothing packs require manual drawable mapping.
-- Supported categories: NONE, VERY_LIGHT, LIGHT, MEDIUM, HEAVY, WINTER
Config.Temperature.DrawableMap = {
    torso = {
        [joaat('mp_m_freemode_01')] = {
            [2] = "VERY_LIGHT",
            [3] = "MEDIUM",
            [4] = "MEDIUM",
            [5] = "VERY_LIGHT",
            [6] = "HEAVY",
            ...
        },
        [joaat('mp_f_freemode_01')] = {
            [1] = "HEAVY",
            [3] = "MEDIUM",
            [4] = "VERY_LIGHT",
            [5] = "VERY_LIGHT",
            [6] = "MEDIUM",
            ...
        }
    },

    legs = {
        [joaat('mp_m_freemode_01')] = {
            ...
        },
        [joaat('mp_f_freemode_01')] = {
            ...
        }
    },

    shoes = {
        [joaat('mp_m_freemode_01')] = {
            ...
        },
        [joaat('mp_f_freemode_01')] = {
            ...
        }
    },

    hats = {
        [joaat('mp_m_freemode_01')] = {
            ...
        },
        [joaat('mp_f_freemode_01')] = {
            ...
        }
    },

    arms = {
        [joaat('mp_m_freemode_01')] = {
            ...
        },
        [joaat('mp_f_freemode_01')] = {
            ...
        }
    }   
}

---@class BodyWeights<table>
-- Body part influence on total insulation.
-- Higher values = bigger impact on temperature calculations.
-- Recommended: torso > legs > shoes > arms > hats
-- Example: A WINTER torso affects temperature much more than a WINTER hat.
Config.Temperature.BodyWeights = {
    torso = 1.0,
    legs  = 0.8,
    shoes = 0.5,
    hats = 0.3,
    arms = 0.4,
}
```

{% endcode %}


# config.temperature.lua

Preview File Updated: v1.0.2 - 04/08/2026

{% code fullWidth="true" %}

```lua
-- ████████╗███████╗███╗   ███╗██████╗ ███████╗██████╗  █████╗ ████████╗██╗   ██╗██████╗ ███████╗
-- ╚══██╔══╝██╔════╝████╗ ████║██╔══██╗██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██║   ██║██╔══██╗██╔════╝
--    ██║   █████╗  ██╔████╔██║██████╔╝█████╗  ██████╔╝███████║   ██║   ██║   ██║██████╔╝█████╗  
--    ██║   ██╔══╝  ██║╚██╔╝██║██╔═══╝ ██╔══╝  ██╔══██╗██╔══██║   ██║   ██║   ██║██╔══██╗██╔══╝  
--    ██║   ███████╗██║ ╚═╝ ██║██║     ███████╗██║  ██║██║  ██║   ██║   ╚██████╔╝██║  ██║███████╗
--    ╚═╝   ╚══════╝╚═╝     ╚═╝╚═╝     ╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝╚══════╝
Config = Config or {}

Config.Temperature = {
    -- Enable or disable the entire temperature system.
    Enabled = true,
    
    -- Difficulty: EASY | NORMAL | HARD | REALISTIC
    Difficulty = 'NORMAL',
    
    -- If enabled, temperature is calculated using only day and night values instead of HourCurve.
    UseDayNightOnly = false,

    -- Hour when daytime starts.
    DayStartHour = 7,

    -- Hour when nighttime starts.
    NightStartHour = 20,

    -- Daily temperature progression curve.
    -- Range: 0.0 - 1.0
    -- Lower values = colder hours
    -- Higher values = warmer hours
    HourCurve = {
        [0]=0.15,[1]=0.12,[2]=0.10,[3]=0.08,[4]=0.07,[5]=0.08,
        [6]=0.12,[7]=0.22,[8]=0.35,[9]=0.50,[10]=0.62,[11]=0.72,
        [12]=0.80,[13]=0.86,[14]=0.90,[15]=0.88,[16]=0.82,[17]=0.70,
        [18]=0.55,[19]=0.40,[20]=0.28,[21]=0.22,[22]=0.18,[23]=0.16,
    },

    -- Fallback weather values used when the current weather type is not configured.
    Default = {
        DayMin = 18,
        DayMax = 28,
        NightMin = 12,
        NightMax = 18,
        Offset = 0
    },

    -- Temperature presets for each weather type.
    -- 
    -- DayMin/DayMax:
    -- Temperature range during daytime.
    -- 
    -- NightMin/NightMax:
    -- Temperature range during nighttime.
    -- 
    -- Offset:
    -- Additional temperature adjustment.
    Weather = {
        ['CLEAR']       = { DayMin = 20, DayMax = 30, NightMin = 14, NightMax = 20 },
        ['EXTRASUNNY']  = { DayMin = 24, DayMax = 36, NightMin = 16, NightMax = 24, Offset=1 },
        ['CLOUDS']      = { DayMin = 18, DayMax = 26, NightMin = 12, NightMax = 18 },
        ['OVERCAST']    = { DayMin = 16, DayMax = 22, NightMin = 10, NightMax = 16, Offset=-1 },
        ['RAIN']        = { DayMin = 14, DayMax = 20, NightMin = 8,  NightMax = 14, Offset=-1 },
        ['THUNDER']     = { DayMin = 13, DayMax = 19, NightMin = 7,  NightMax = 13, Offset=-2 },
        ['FOGGY']       = { DayMin = 10, DayMax = 16, NightMin = 6,  NightMax = 12, Offset=-1 },
        ['SMOG']        = { DayMin = 18, DayMax = 25, NightMin = 12, NightMax = 18 },
        ['SNOW']        = { DayMin = -4, DayMax = 2,  NightMin = -10, NightMax = -4 },
        ['XMAS']        = { DayMin = -4, DayMax = 2,  NightMin = -10, NightMax = -4 },
        ['BLIZZARD']    = { DayMin = -8, DayMax = -2, NightMin = -14, NightMax = -8 },
    },

    -- Additional weather effects.
    -- 
    -- Rain:
    -- Can reduce effective temperature.
    -- 
    -- Wind:
    -- Creates wind chill effect in cold weather and slight cooling in hot weather.
    Modifiers = {
        Rain = {
            MinEffect = 0.2,       -- Minimum rain intensity before rain starts affecting temperature.
            MaxColdEffect = -6.0,  -- Maximum cooling effect in cold weather.
            MaxHeatEffect = -2.0,  -- Maximum cooling effect in hot weather.
        },
        Wind = {
            Threshold = 3.0,       -- Minimum wind speed required for wind chill calculations.
            MaxWind = 50.0,        -- Maximum wind speed used in calculations.
            MaxColdEffect = -8.0,  -- Maximum cooling effect in cold weather.
            MaxHeatEffect = -3.0,  -- Maximum cooling effect in hot weather.
        }
    },

    -- Comfortable effective temperature range.
    -- 
    -- Below Min:
    -- Player starts feeling cold.
    -- 
    -- Above Max:
    -- Player starts feeling hot.
    ComfortZone = {
        Min = 8.0,     -- Below this temperature one begins to feel cold
        Max = 38.0,    -- Above this temperature one begins to feel hot
    },

    -- Vehicles with open roofs or convertible support.
    -- 
    -- true:
    -- Vehicle is always considered open.
    -- 
    -- extra:
    -- Vehicle roof state is determined using a vehicle extra.
    VehiclesRoofs = {
        [joaat('sm722')] = true,
        [joaat('ruston')] = true,
        [joaat('raptor')] = true,
        [joaat('veto')] = true,
        [joaat('veto2')] = true,
        [joaat('luiva')] = true,
        [joaat('zorrusso')] = true,
        [joaat('bifta')] = true,
        [joaat('bodhi2')] = true,
        [joaat('kalahari')] = true,
        [joaat('outlaw')] = true,
        [joaat('ratel')] = true,
        [joaat('vagrant')] = true,
        [joaat('winky')] = true,
        [joaat('mesa')] = {extra = 1, enabled = false},
        [joaat('buccaneer2')] = {extra = 4, enabled = false},
        [joaat('chino2')] = {extra = 3, enabled = false},
        [joaat('coquette2')] = {extra = 2, enabled = false},
        [joaat('coquette3')] = {extra = 2, enabled = false},
        [joaat('manana')] = {extra = 2, enabled = false},
        [joaat('manana2')] = {extra = 2, enabled = false},
        [joaat('stalion')] = {extra = 3, enabled = false},
        [joaat('casco')] = {extra = 2, enabled = false},
        [joaat('mamba')] = {extra = 3, enabled = false},
        [joaat('peyote')] = {extra = 2, enabled = false},
        [joaat('stinger')] = {extra = 2, enabled = false},
        [joaat('banshee')] = {extra = 2, enabled = false},
        [joaat('banshee2')] = {extra = 2, enabled = false},
        [joaat('coquette')] = {extra = 2, enabled = false},
        [joaat('coquette4')] = {extra = 1, enabled = false},
        [joaat('pfister811')] = {extra = 1, enabled = false},
        [joaat('kamacho')] = {extra = 1, enabled = false},
    }
}
```

{% endcode %}


# vms\_housing

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

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


# Installation

## 1. Download Resource

Download the purchased resource from [**CFX Portal**](https://portal.cfx.re/assets/granted-assets) - the official site of FiveM with purchased resources.

***

## 2. Install Required Dependencies

This script needs a few extra resources to work properly.\
Below you will find a list of things to download - click the link, download and upload to your server just like other resources.

<table data-full-width="false"><thead><tr><th width="251">Resource</th><th data-type="content-ref">Download Link</th></tr></thead><tbody><tr><td><strong>t3_lockpick (optional)</strong></td><td><a href="https://github.com/T3development/t3_lockpick">https://github.com/T3development/t3_lockpick</a></td></tr><tr><td><strong>tgiann-skillbar (optional)</strong></td><td><a href="https://github.com/TGIANN/fivem-tgiann-skillbar">https://github.com/TGIANN/fivem-tgiann-skillbar</a></td></tr><tr><td><strong>bl_ui (optional)</strong></td><td><a href="https://github.com/Byte-Labs-Studio/bl_ui">https://github.com/Byte-Labs-Studio/bl_ui</a></td></tr></tbody></table>

***

## 3. Import Database Tables

This is a very important step - without it, the script will not work properly.\
Depending on the framework you are using (ESX or QB-Core), select the appropriate section below and **paste the SQL code into your database**.

{% hint style="warning" %}

## Not sure how to do it?

No worries - we've prepared a short guide that shows you step by step how to import an SQL file into your database:\
👉 [**Click here to view the tutorial.**](/helpful/basic-server-knowledge/how-to-import-sql-to-database)
{% endhint %}

<details>

<summary>Database for ESX</summary>

```sql
ALTER TABLE `users` ADD COLUMN `last_property` VARCHAR(255) DEFAULT NULL;

CREATE TABLE IF NOT EXISTS `houses` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(50) DEFAULT NULL,
  `object_id` int(11) DEFAULT NULL,
  `owner` varchar(120) DEFAULT NULL,
  `owner_name` varchar(80) DEFAULT NULL,
  `renter` varchar(120) DEFAULT NULL,
  `renter_name` varchar(80) DEFAULT NULL,
  `name` longtext DEFAULT '',
  `description` longtext DEFAULT NULL,
  `region` varchar(70) DEFAULT NULL,
  `address` varchar(50) DEFAULT NULL,
  `keys` longtext NOT NULL DEFAULT '[]',
  `permissions` longtext DEFAULT '[]',
  `metadata` longtext NOT NULL DEFAULT '[]',
  `grass` longtext DEFAULT NULL,
  `designs` longtext DEFAULT '[]',
  `sale` longtext DEFAULT '[]',
  `rental` longtext DEFAULT '[]',
  `last_enter` int(11) DEFAULT NULL,
  `creator` varchar(120) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_last_enter` (`last_enter`),
  KEY `idx_cleanup` (`type`,`object_id`,`last_enter`,`owner`,`renter`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `houses_bills` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `house_id` int(11) NOT NULL,
  `period` varchar(50) NOT NULL DEFAULT '',
  `type` varchar(50) DEFAULT NULL,
  `total` float DEFAULT 0,
  `paid` tinyint(1) DEFAULT 0,
  `details` longtext NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_period_per_house_type` (`house_id`,`period`,`type`),
  KEY `idx_house_id` (`house_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `houses_designs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `company` varchar(50) DEFAULT NULL,
  `author` varchar(80) DEFAULT NULL,
  `author_name` varchar(120) DEFAULT NULL,
  `type` varchar(20) DEFAULT NULL,
  `model` varchar(50) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `price` int(11) DEFAULT NULL,
  `furniture` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`furniture`)),
  `images` longtext DEFAULT NULL,
  `is_listed` int(11) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `house_idea_orders` (
  `id` varchar(50) DEFAULT NULL,
  `property` int(11) DEFAULT NULL,
  `furniture` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`furniture`)),
  `date` int(11) DEFAULT NULL,
  `type` varchar(50) DEFAULT NULL,
  `size` varchar(50) DEFAULT NULL,
  `price` int(11) DEFAULT NULL,
  `deliveryPrice` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `houses_furniture` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `house_id` int(11) NOT NULL,
  `position` longtext DEFAULT NULL,
  `model` varchar(70) DEFAULT NULL,
  `stored` int(11) DEFAULT 0,
  `metadata` longtext DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_house_id` (`house_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `houses_furniture_list` (
  `model` varchar(80) NOT NULL,
  `label` varchar(80) DEFAULT NULL,
  `price` int(11) DEFAULT 0,
  `deliverySize` int(11) DEFAULT 1,
  `tag` varchar(50) DEFAULT NULL,
  `isOutdoor` int(11) DEFAULT 1,
  `isIndoor` int(11) DEFAULT 1,
  `interactableName` varchar(50) DEFAULT NULL,
  `metadata` longtext DEFAULT NULL,
  PRIMARY KEY (`model`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
```

</details>

<details>

<summary>Database for QB-Core</summary>

```sql
ALTER TABLE `players` ADD COLUMN `last_property` VARCHAR(255) DEFAULT NULL;

CREATE TABLE IF NOT EXISTS `houses` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(50) DEFAULT NULL,
  `object_id` int(11) DEFAULT NULL,
  `owner` varchar(120) DEFAULT NULL,
  `owner_name` varchar(80) DEFAULT NULL,
  `renter` varchar(120) DEFAULT NULL,
  `renter_name` varchar(80) DEFAULT NULL,
  `name` longtext DEFAULT '',
  `description` longtext DEFAULT NULL,
  `region` varchar(70) DEFAULT NULL,
  `address` varchar(50) DEFAULT NULL,
  `keys` longtext NOT NULL DEFAULT '[]',
  `permissions` longtext DEFAULT '[]',
  `metadata` longtext NOT NULL DEFAULT '[]',
  `grass` longtext DEFAULT NULL,
  `designs` longtext DEFAULT '[]',
  `sale` longtext DEFAULT '[]',
  `rental` longtext DEFAULT '[]',
  `last_enter` int(11) DEFAULT NULL,
  `creator` varchar(120) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_last_enter` (`last_enter`),
  KEY `idx_cleanup` (`type`,`object_id`,`last_enter`,`owner`,`renter`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `houses_bills` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `house_id` int(11) NOT NULL,
  `period` varchar(50) NOT NULL DEFAULT '',
  `type` varchar(50) DEFAULT NULL,
  `total` float DEFAULT 0,
  `paid` tinyint(1) DEFAULT 0,
  `details` longtext NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_period_per_house_type` (`house_id`,`period`,`type`),
  KEY `idx_house_id` (`house_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `houses_designs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `company` varchar(50) DEFAULT NULL,
  `author` varchar(80) DEFAULT NULL,
  `author_name` varchar(120) DEFAULT NULL,
  `type` varchar(20) DEFAULT NULL,
  `model` varchar(50) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `price` int(11) DEFAULT NULL,
  `furniture` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`furniture`)),
  `images` longtext DEFAULT NULL,
  `is_listed` int(11) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `house_idea_orders` (
  `id` varchar(50) DEFAULT NULL,
  `property` int(11) DEFAULT NULL,
  `furniture` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`furniture`)),
  `date` int(11) DEFAULT NULL,
  `type` varchar(50) DEFAULT NULL,
  `size` varchar(50) DEFAULT NULL,
  `price` int(11) DEFAULT NULL,
  `deliveryPrice` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `houses_furniture` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `house_id` int(11) NOT NULL,
  `position` longtext DEFAULT NULL,
  `model` varchar(70) DEFAULT NULL,
  `stored` int(11) DEFAULT 0,
  `metadata` longtext DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_house_id` (`house_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE IF NOT EXISTS `houses_furniture_list` (
  `model` varchar(80) NOT NULL,
  `label` varchar(80) DEFAULT NULL,
  `price` int(11) DEFAULT 0,
  `deliverySize` int(11) DEFAULT 1,
  `tag` varchar(50) DEFAULT NULL,
  `isOutdoor` int(11) DEFAULT 1,
  `isIndoor` int(11) DEFAULT 1,
  `interactableName` varchar(50) DEFAULT NULL,
  `metadata` longtext DEFAULT NULL,
  PRIMARY KEY (`model`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
```

</details>

***

## 4. Import Furniture

<details>

<summary>Furniture</summary>

```sql
INSERT INTO `houses_furniture_list` (`model`, `label`, `price`, `deliverySize`, `tag`, `isOutdoor`, `isIndoor`, `interactableName`, `metadata`) VALUES
	('apa_mp_h_acc_artwalll_03', 'Painting (Blue)', 12300, 3, 'decorations', 0, 1, NULL, NULL),
	('apa_mp_h_acc_artwallm_04', 'Painting (Red)', 14800, 2, 'decorations', 0, 1, NULL, NULL),
	('apa_mp_h_acc_rugwooll_03', 'Wool Rug', 450, 2, 'decorations', 0, 1, NULL, NULL),
	('apa_mp_h_acc_rugwoolm_01', 'Rug (White & Orange)', 1000, 1, 'decorations', 0, 1, NULL, NULL),
	('apa_mp_h_acc_rugwoolm_02', 'Rug (Turquoise & Black)', 2000, 2, 'decorations', 0, 1, NULL, NULL),
	('apa_mp_h_acc_rugwools_03', 'Rug (Blue & Cream)', 1600, 2, 'decorations', 0, 1, NULL, NULL),
	('apa_mp_h_bed_chestdrawer_02', 'Modern Chest of Drawers (Dark)', 7800, 3, 'storage', 0, 1, 'storage', '{"slots":10,"weight":10000}'),
	('apa_mp_h_bed_double_09', 'Double Bed (Black Upholstered)', 3800, 3, 'bed', 0, 1, NULL, NULL),
	('apa_mp_h_bed_wide_05', 'Wide Bed (Red Frame)', 2800, 3, 'bed', 0, 1, NULL, NULL),
	('apa_mp_h_bed_with_table_02', 'Double Bed with Built-in Table', 4500, 3, 'bed', 0, 1, NULL, NULL),
	('apa_mp_h_din_table_01', 'Glass Dining Table', 7000, 3, 'table', 1, 1, NULL, NULL),
	('apa_mp_h_din_table_04', 'Glass Dinning Table', 11000, 3, 'table', 1, 1, NULL, NULL),
	('apa_mp_h_din_table_06', 'White Rounded Dinning Table', 9200, 3, 'table', 0, 1, NULL, NULL),
	('apa_mp_h_din_table_11', 'Glass Dinning Table', 11000, 3, 'table', 1, 1, NULL, NULL),
	('apa_mp_h_lampbulb_multiple_a', 'Chandelier', 1000, 2, 'light', 1, 1, NULL, NULL),
	('apa_mp_h_lit_floorlampnight_14', 'Floor Lamp Night', 900, 2, 'light', 1, 1, NULL, NULL),
	('apa_mp_h_lit_floorlamp_05', 'Floor Lamp', 700, 2, 'light', 1, 1, NULL, NULL),
	('apa_mp_h_stn_chairarm_01', 'Modern Armchair (Mustard)', 2000, 2, 'chair', 0, 1, NULL, NULL),
	('apa_mp_h_stn_chairarm_23', 'Modern Arm Chair (Burgundy)', 2000, 2, 'chair', 1, 1, NULL, NULL),
	('apa_mp_h_stn_chairstrip_03', 'Armchair (Red Fabric)', 2000, 3, 'chair', 0, 1, NULL, NULL),
	('apa_mp_h_stn_chairstrip_06', 'Strip Chair (Red)', 2200, 1, 'chair', 1, 1, NULL, NULL),
	('apa_mp_h_stn_chairstrip_07', 'Strip Chair (Black)', 2300, 2, 'chair', 1, 1, NULL, NULL),
	('apa_mp_h_stn_chairstrip_08', 'Armchair (Cyan Fabric)', 2000, 3, 'chair', 0, 1, NULL, NULL),
	('apa_mp_h_stn_sofacorn_01', 'Corner Sofa (L-Shaped, Cream)', 4950, 3, 'sofa', 1, 1, NULL, NULL),
	('apa_mp_h_stn_sofacorn_08', 'Corner Sofa (Beige, Cushioned)', 4400, 3, 'sofa', 0, 1, NULL, NULL),
	('apa_mp_h_stn_sofacorn_09', 'Corner Sofa (Dark, Modern)', 5500, 3, 'sofa', 0, 1, NULL, NULL),
	('apa_mp_h_str_shelffloorm_02', 'Floor Standing Shelf (Wood & Black)', 8000, 3, 'storage', 0, 1, 'storage', '{"slots":10,"weight":10000}'),
	('apa_mp_h_str_sideboardl_06', 'Modern Sideboard (Rounded)', 11500, 3, 'storage', 0, 1, 'storage', '{"slots":10,"weight":10000}'),
	('apa_mp_h_str_sideboardl_09', 'Low Sideboard (Beige)', 14000, 3, 'storage', 1, 1, 'storage', '{"slots":10,"weight":10000}'),
	('apa_mp_h_str_sideboardl_11', 'Low Sideboard (Dark Wood)', 10000, 3, 'storage', 0, 1, 'storage', '{"slots":10,"weight":10000}'),
	('apa_mp_h_str_sideboardl_13', 'Low Sideboard (White & Gray)', 12000, 3, 'storage', 0, 1, 'storage', '{"slots":10,"weight":10000}'),
	('apa_mp_h_str_sideboardl_14', 'Low Sideboard (White & Wood)', 11500, 3, 'storage', 0, 1, 'storage', '{"slots":10,"weight":10000}'),
	('apa_mp_h_str_sideboards_01', 'Modern Color Sideboard (Rounded)', 11000, 3, 'storage', 1, 1, 'storage', '{"slots":10,"weight":10000}'),
	('apa_mp_h_tab_coffee_07', 'Glass Small Table (Rounded)', 3750, 2, 'table', 1, 1, NULL, NULL),
	('apa_mp_h_tab_sidelrg_01', 'Glass Small Table (Shapely)', 3800, 2, 'table', 1, 1, NULL, NULL),
	('apa_mp_h_tab_sidelrg_02', 'Modern Curved Chaise Lounger', 3500, 2, 'table', 1, 1, NULL, NULL),
	('apa_mp_h_tab_sidelrg_07', 'Glass Small Table (Square)', 3300, 2, 'table', 1, 1, NULL, NULL),
	('bkr_prop_biker_chairstrip_01', 'Leather Club Armchair (Brown)', 650, 3, 'chair', 1, 1, NULL, NULL),
	('bkr_prop_biker_garage_locker_01', 'Open Biker Locker', 4200, 3, 'storage', 1, 1, 'wardrobe', NULL),
	('bkr_prop_gunlocker_01a', 'Gun Locker Cabinet', 9200, 3, 'storage', 1, 1, 'storage', '{"slots":10,"weight":10000}'),
	('ch_prop_ch_corridor_door_derelict', 'Wooden Door', 2300, 2, 'door', 1, 1, 'door', NULL),
	('ch_prop_vault_painting_01a', 'Painting (#1)', 9200, 1, 'decorations', 1, 1, NULL, NULL),
	('ch_prop_whiteboard_04', 'Whiteboard', 1500, 1, 'decorations', 1, 1, NULL, NULL),
	('ex_mp_h_acc_rugwoolm_04', 'Rug (White)', 1900, 2, 'decorations', 1, 1, NULL, NULL),
	('ex_mp_h_acc_vase_flowers_01', 'Flowers Vase', 600, 1, 'plant', 1, 1, NULL, NULL),
	('ex_mp_h_acc_vase_flowers_03', 'Flowers Vase', 500, 1, 'plant', 1, 1, NULL, NULL),
	('ex_mp_h_acc_vase_flowers_04', 'Flowers Vase', 480, 1, 'plant', 1, 1, NULL, NULL),
	('ex_mp_h_off_sofa_003', 'Executive Sofa (Charcoal)', 3200, 3, 'sofa', 0, 1, NULL, NULL),
	('ex_mp_h_off_sofa_02', 'Executive Sofa (Gray)', 3200, 3, 'sofa', 1, 1, NULL, NULL),
	('ex_prop_exec_bed_01', 'Executive Bed (Beige Upholstery)', 1100, 3, 'bed', 0, 1, NULL, NULL),
	('ex_p_mp_door_apart_door', 'Wooden Door', 2800, 2, 'door', 1, 1, 'door', NULL),
	('ex_p_mp_door_apart_doorbrown_s', 'Apartment Door (Brown)', 3000, 2, 'door', 1, 1, 'door', NULL),
	('ex_p_mp_door_apart_doorwhite01_s', 'Apartment Door (White)', 3000, 2, 'door', 1, 1, 'door', NULL),
	('ex_p_mp_door_apart_door_black_s', 'Apartment Door (Black)', 3000, 2, 'door', 1, 1, 'door', NULL),
	('ex_p_mp_door_office_door01_s', 'Office Door', 2800, 2, 'door', 1, 1, 'door', NULL),
	('gr_prop_gr_rsply_crate04a', 'Green Crate', 7200, 3, 'storage', 1, 1, 'storage', '{"weight":15000,"slots":8}'),
	('h4_mp_h_yacht_floor_lamp_01', 'Yacht Floor Lamp', 580, 1, 'light', 1, 1, NULL, NULL),
	('h4_mp_h_yacht_sofa_01', 'Yacht Sofa (White & Brown)', 7500, 3, 'sofa', 0, 1, NULL, NULL),
	('h4_mp_h_yacht_strip_chair_01', 'Yacht Lounge Chair (White)', 1400, 3, 'chair', 1, 1, NULL, NULL),
	('h4_prop_h4_glass_disp_01a', 'Glass Display Pedestal', 1000, 2, 'decorations', 1, 1, NULL, NULL),
	('h4_prop_h4_painting_01d', 'Painting (#2)', 30000, 1, 'decorations', 1, 1, NULL, NULL),
	('hei_heist_apart2_door', 'Apartment Wooden Door 2', 3000, 2, 'door', 1, 1, 'door', NULL),
	('hei_heist_bed_double_08', 'Modern Double Bed (Dark Gray)', 3700, 3, 'bed', 0, 1, NULL, NULL),
	('hei_heist_din_chair_02', 'Dining Chair (Red Plastic)', 350, 2, 'chair', 1, 1, NULL, NULL),
	('hei_heist_stn_sofa3seat_01', 'Sofa (Cream)', 3400, 3, 'sofa', 1, 1, NULL, NULL),
	('hei_heist_stn_sofa3seat_02', 'Classic Sofa', 2500, 3, 'sofa', 1, 1, NULL, NULL),
	('hei_heist_stn_sofa3seat_06', 'Elegant Sofa (Grey)', 4850, 3, 'sofa', 1, 1, NULL, NULL),
	('hei_heist_stn_sofacorn_05', 'Corner Sofa (Dark Blue)', 3900, 3, 'sofa', 0, 1, NULL, NULL),
	('hei_heist_stn_sofacorn_06', 'Corner Sofa (Green Cushions)', 3700, 3, 'sofa', 0, 1, NULL, NULL),
	('hei_prop_heist_cutscene_doorb', 'Gate Door', 1850, 2, 'door', 1, 1, 'door', NULL),
	('hei_prop_yah_lounger', 'Rattan Lounger (Dark Frame)', 600, 3, 'chair', 1, 1, NULL, NULL),
	('hei_prop_yah_seat_01', 'Rattan Chair (Single, Narrow)', 650, 2, 'chair', 1, 1, NULL, NULL),
	('hei_prop_yah_seat_02', 'Rattan Chair (Wide Seat)', 800, 2, 'chair', 1, 1, NULL, NULL),
	('hei_prop_yah_seat_03', 'Rattan Chair (Armrests)', 950, 3, 'chair', 1, 1, NULL, NULL),
	('hei_prop_yah_table_01', 'Yacht Dining Table (Wooden)', 500, 1, 'table', 1, 1, NULL, NULL),
	('hei_prop_yah_table_02', 'Yacht Table (With Cloth)', 550, 2, 'table', 1, 1, NULL, NULL),
	('hei_prop_yah_table_03', 'Yacht Table (Blue Legs)', 1000, 3, 'table', 1, 1, NULL, NULL),
	('hei_v_ilev_fh_heistdoor1', 'Apartment Wooden Door', 2800, 2, 'door', 1, 1, 'door', NULL),
	('m23_1_prop_iaa_base_door_01', 'Fire Door', 1300, 2, 'door', 1, 1, 'door', NULL),
	('m23_2_prop_m32_bucket_mop_01b', 'Mop', 200, 1, 'recreation', 0, 1, 'mop', NULL),
	('m23_2_prop_m32_weaponcrate_01a', 'Weapon Wooden Crate', 6800, 2, 'storage', 1, 1, 'storage', '{"weight":8000,"slots":7}'),
	('m24_1_prop_m41_rug_01a', 'Round Rug (Red)', 2000, 1, 'decorations', 1, 1, NULL, NULL),
	('prop_airhockey_01', 'Air Hockey Table', 8000, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_amp_01', 'Guitar Amplifier', 1200, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_armchair_01', 'Upholstered Armchair (Brown Fabric)', 650, 2, 'chair', 0, 1, NULL, NULL),
	('prop_barbell_01', 'Dumbbell 30kg', 180, 1, 'recreation', 1, 1, NULL, NULL),
	('prop_barbell_02', 'Barbell 110kg', 770, 2, 'recreation', 1, 1, NULL, NULL),
	('prop_barbell_100kg', 'Barbell 100kg', 700, 2, 'recreation', 1, 1, NULL, NULL),
	('prop_barbell_10kg', 'Barbell 10kg', 200, 1, 'recreation', 1, 1, NULL, NULL),
	('prop_barbell_20kg', 'Barbell 20kg', 260, 1, 'recreation', 1, 1, NULL, NULL),
	('prop_barbell_30kg', 'Barbell 30kg', 330, 1, 'recreation', 1, 1, NULL, NULL),
	('prop_barbell_40kg', 'Barbell 40kg', 390, 2, 'recreation', 1, 1, NULL, NULL),
	('prop_barbell_50kg', 'Barbell 50kg', 440, 1, 'recreation', 1, 1, NULL, NULL),
	('prop_barbell_60kg', 'Barbell 60kg', 510, 2, 'recreation', 1, 1, NULL, NULL),
	('prop_barbell_80kg', 'Barbell 80kg', 600, 2, 'recreation', 1, 1, NULL, NULL),
	('prop_barebulb_01', 'Light bulb', 40, 1, 'light', 1, 1, NULL, NULL),
	('prop_basketball_net', 'Basketball Hoop', 1850, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_bbq_1', 'BBQ', 4800, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bbq_3', 'Brick BBQ', 5500, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bbq_4', 'BBQ', 900, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bbq_5', 'BBQ', 4500, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_beach_fire', 'Beach Campfire', 300, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_beach_lilo_02', 'Inflatable Pool Mattress', 240, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_beach_parasol_05', 'Beach Parasol (Striped)', 400, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_beach_ring_01', 'Inflatable Swim Ring', 200, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_beach_sandcas_04', 'Sandcastle', 100, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_01a', 'Metal Bench (Classic)', 440, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_01b', 'Metal Bench (Classic Blue)', 460, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_01c', 'Metal Bench (Green)', 420, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_02', 'Metal Park Bench', 400, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_03', 'Industrial Metal Bench', 550, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_04', 'Wooden Bench', 850, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_06', 'Garden Bench (Slatted)', 600, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_07', 'Minimalist Park Bench', 500, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_08', 'Wooden Bench (Concrete legs)', 950, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_10', 'Bus Stop Bench', 400, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bench_11', 'Heavy Duty Bench', 500, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_beware_dog_sign', 'Dog Sign', 130, 1, 'decorations', 1, 0, NULL, NULL),
	('prop_bin_01a', 'Standard Trash Can', 180, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_02a', 'Trash Can (Blue)', 120, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_03a', 'Black Garbage Bin', 10, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_04a', 'Commercial Trash Bin (Closed)', 300, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_05a', 'Dumpster Bin (Blue)', 280, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_06a', 'Industrial Garbage Bin', 180, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_07a', 'Metal Trash Can (Empty)', 300, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_07b', 'Metal Trash Can (Closed)', 300, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_07c', 'Trash Can (Half-closed)', 300, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_07d', 'Graffiti Trash Can', 200, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_08a', 'Tall Public Bin', 300, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_08open', 'Tall Public Bin (Open)', 300, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_09a', 'Tall Trash Can (With Lid)', 80, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_10a', 'Bin (Dark Blue)', 200, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_10b', 'Bin (Red)', 200, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_11a', 'Street Bin (Yellow)', 160, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_11b', 'Street Bin (Green)', 160, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_12a', 'City Trash Can (Metal Base)', 400, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_delpiero', 'Del Perro Promenade Trash Bin', 500, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_bin_delpiero_b', 'Del Perro Trash Bin (Decorative)', 400, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_cabinet_01', 'White Cabinet (Tall, Drawers)', 7000, 2, 'storage', 1, 1, 'storage', '{"slots":10,"weight":10000}'),
	('prop_cabinet_02b', 'Filing Cabinet (3 Drawers, Beige)', 6000, 2, 'storage', 0, 1, 'storage', '{"slots":10,"weight":10000}'),
	('prop_cctv_cam_04c', 'CCTV Camera (Ceiling Dome)', 12000, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_cctv_cam_05a', 'CCTV Camera (Ceiling Tube)', 13000, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_cctv_cam_06a', 'CCTV Camera (Wall Box)', 14000, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_chair_01a', 'Metal Chair (Blue Frame)', 250, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_01b', 'Plastic Chair (Worn Red)', 280, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_02', 'Wicker Chair (Dark)', 380, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_03', 'Antique Dining Chair (Round Back)', 220, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_04a', 'Modern Chair (Striped Back)', 740, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_04b', 'Modern Chair (Flat Back)', 700, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_05', 'Classic Wicker Chair', 470, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_06', 'Dining Chair (Dark Red)', 290, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_07', 'Vintage Wooden Chair', 220, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_08', 'Plastic Chair (White Legs)', 200, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_09', 'Rattan Chair (Green Weave)', 300, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chair_10', 'Ornate Armchair (Vintage)', 300, 2, 'chair', 1, 1, NULL, NULL),
	('prop_chall_lamp_02', 'Chall Lamp', 1150, 2, 'light', 1, 1, NULL, NULL),
	('prop_chateau_chair_01', 'Antique Chateau Armchair', 290, 2, 'chair', 1, 1, NULL, NULL),
	('prop_clown_chair', 'Circus-Style Wooden Chair', 190, 1, 'chair', 1, 1, NULL, NULL),
	('prop_copier_01', 'Office Copier (Open Tray)', 700, 3, 'electronic', 0, 1, NULL, NULL),
	('prop_couch_01', 'Two-Seat Sofa (Pillows)', 1500, 3, 'sofa', 1, 0, NULL, NULL),
	('prop_couch_03', 'Rustic Couch (Beige Cushions)', 1300, 3, 'sofa', 1, 0, NULL, NULL),
	('prop_couch_lg_02', 'Modern Couch (Wood Trim)', 2500, 3, 'sofa', 1, 1, NULL, NULL),
	('prop_couch_lg_05', 'Fabric Couch (Gray/Brown)', 2700, 3, 'sofa', 1, 0, NULL, NULL),
	('prop_couch_lg_06', 'Vintage Couch (Red/Brown)', 1700, 3, 'sofa', 1, 0, NULL, NULL),
	('prop_couch_lg_07', 'Couch with Pillows (Gray/Orange)', 2900, 3, 'sofa', 0, 1, NULL, NULL),
	('prop_couch_lg_08', 'Modern Couch (Brown/Gray)', 4100, 3, 'sofa', 0, 1, NULL, NULL),
	('prop_couch_sm1_07', 'L-Shaped Sofa (Left Side)', 490, 2, 'sofa', 0, 1, NULL, NULL),
	('prop_couch_sm2_07', 'L-Shaped Sofa (Right Side)', 490, 2, 'sofa', 0, 1, NULL, NULL),
	('prop_couch_sm_02', 'Compact Couch (Brown Fabric)', 720, 2, 'sofa', 1, 1, NULL, NULL),
	('prop_couch_sm_05', 'Compact Couch (Beige Fabric)', 900, 3, 'sofa', 1, 1, NULL, NULL),
	('prop_couch_sm_06', 'Compact Sofa (Gray)', 700, 3, 'sofa', 1, 1, NULL, NULL),
	('prop_couch_sm_07', 'Vintage Leather Sofa', 2000, 3, 'sofa', 1, 1, NULL, NULL),
	('prop_cs_keyboard_01', 'Keyboard (Black)', 480, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_cs_tv_stand', 'TV Stand with Screen', 2000, 3, 'electronic', 1, 1, NULL, NULL),
	('prop_dart_bd_01', 'Dartboard (Wood Backing)', 400, 1, 'recreation', 1, 1, NULL, NULL),
	('prop_dart_bd_cab_01', 'Dartboard with Cabinet', 550, 2, 'recreation', 1, 1, NULL, NULL),
	('prop_doghouse_01', 'Wooden Doghouse', 750, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_dyn_pc', 'Desktop Computer (Tower)', 2000, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_exer_bike_01', 'Exercise Bike (Stationary)', 1600, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_fan_01', 'Oscillating Fan (Floor)', 400, 2, 'electronic', 1, 1, NULL, NULL),
	('prop_fax_01', 'Office Fax Machine', 400, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_fbi3_coffee_table', 'Modern Coffee Table (White Frame)', 1200, 1, 'table', 1, 1, NULL, NULL),
	('prop_ff_shelves_01', 'Warehouse Shelving Unit (Metal)', 10000, 3, 'storage', 1, 1, 'storage', '{"slots":10,"weight":10000}'),
	('prop_fib_ashtray_01', 'FIB Ashtray (Wall-Mounted)', 25, 1, 'decorations', 1, 1, NULL, NULL),
	('prop_flamingo', 'Pink Flamingo (Lawn Ornament)', 400, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_folder_02', 'Magazine Holder (Black)', 40, 1, 'decorations', 1, 1, NULL, NULL),
	('prop_foodprocess_01', 'Blender / Food Processor', 170, 1, 'kitchen', 0, 1, NULL, NULL),
	('prop_fountain2', 'Stone Fountain (Round)', 8500, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_fridge_03', 'Tall Fridge (Closed)', 8500, 3, 'kitchen', 0, 1, 'storage', '{"weight":8000,"slots":8}'),
	('prop_game_clock_01', 'Wall Clock (White Face)', 100, 1, 'decorations', 0, 1, NULL, NULL),
	('prop_game_clock_02', 'Wall Clock (Black Face)', 120, 1, 'decorations', 0, 1, NULL, NULL),
	('prop_gazebo_01', 'Gazebo Tent (Open Sides)', 3800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_gazebo_02', 'Gazebo Tent (Side Walls)', 3800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_gazebo_03', 'Gazebo Tent (No Walls)', 3800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_gc_chair02', 'Basic Chair (Plastic Seat)', 195, 2, 'chair', 1, 1, NULL, NULL),
	('prop_ghettoblast_01', 'Boombox Radio (Retro)', 1500, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_gnome1', 'Garden Gnome (Smiling)', 300, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_gnome2', 'Garden Gnome (Lantern)', 300, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_gnome3', 'Garden Gnome (Digging)', 300, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_gravestones_10a', 'Gravestone Statue (Angel Wings)', 6000, 3, 'decorations', 1, 0, NULL, NULL),
	('prop_handdry_01', 'Wall Cabinet (White, Small)', 60, 1, 'decorations', 1, 1, NULL, NULL),
	('prop_hottub2', 'Wooden Hot Tub (Lid Closed)', 9000, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_inout_tray_02', 'Stacked Document Trays (3x)', 80, 1, 'decorations', 1, 1, NULL, NULL),
	('prop_jewel_02a', 'Watch Display Stand', 11500, 1, 'decorations', 1, 1, NULL, NULL),
	('prop_kayak_01b', 'Inflatable Kayak (Yellow)', 400, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_kettle', 'Electric Kettle (Silver)', 140, 1, 'kitchen', 0, 1, NULL, NULL),
	('prop_keyboard_01a', 'Keyboard (White)', 480, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_kitch_juicer', 'Electric Juicer', 200, 1, 'kitchen', 0, 1, NULL, NULL),
	('prop_kitch_pot_fry', 'Frying Pan (Shallow)', 60, 1, 'kitchen', 1, 1, NULL, NULL),
	('prop_kitch_pot_lrg2', 'Cooking Pot (Large)', 80, 1, 'kitchen', 1, 1, NULL, NULL),
	('prop_knife', 'Kitchen Knife (Used)', 50, 1, 'kitchen', 1, 1, NULL, NULL),
	('prop_laptop_01a', 'Laptop (Open, Green Wallpaper)', 3200, 1, 'electronic', 1, 1, 'device', NULL),
	('prop_lawnmower_01', 'Lawn Mower', 600, 2, 'recreation', 1, 0, 'mower', NULL),
	('prop_ld_bankdoors_02', 'Bank Door', 2600, 2, 'door', 1, 1, 'door', NULL),
	('prop_ld_farm_table01', 'Low Farmhouse Table (Dark Wood)', 1500, 3, 'table', 1, 1, NULL, NULL),
	('prop_ld_farm_table02', 'Low Farm Table (Dark Wood)', 1400, 2, 'table', 1, 1, NULL, NULL),
	('prop_lime_jar', 'Jar of Lime', 35, 1, 'kitchen', 1, 1, NULL, NULL),
	('prop_micro_01', 'Microwave (Basic, White)', 850, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_micro_02', 'Microwave (Digital, White)', 950, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_micro_04', 'Microwave (Wooden Casing)', 800, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_monitor_01c', 'Monitor (Windows XP Style)', 2000, 1, 'electronic', 0, 1, 'device', NULL),
	('prop_monitor_01d', 'Monitor (Gameshow Display)', 2200, 1, 'electronic', 0, 1, 'device', NULL),
	('prop_monitor_li', 'Monitor (Lifeinvader UI)', 2200, 1, 'electronic', 0, 1, 'device', NULL),
	('prop_monitor_w_large', 'Wide Office Monitor', 3100, 1, 'electronic', 0, 1, 'device', NULL),
	('prop_mouse_02', 'Mouse (Standard, Right)', 90, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_muscle_bench_01', 'Workout Bench (Flat)', 600, 2, 'recreation', 1, 1, NULL, NULL),
	('prop_muscle_bench_03', 'Workout Bench (Inclined)', 1200, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_muscle_bench_05', 'Workout Machine (Arms & Pull)', 1500, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_office_desk_01', 'Office Desk (Drawers, Gray)', 1850, 2, 'table', 1, 1, NULL, NULL),
	('prop_office_phone_tnt', 'Office Telephone (TNT Model)', 200, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_off_chair_01', 'Office Chair (High Back)', 650, 2, 'chair', 1, 1, NULL, NULL),
	('prop_off_chair_03', 'Office Chair (Padded, Gray)', 300, 2, 'chair', 1, 1, NULL, NULL),
	('prop_off_chair_04', 'Office Chair (Worn Fabric)', 300, 2, 'chair', 1, 1, NULL, NULL),
	('prop_off_chair_04b', 'Office Chair (Low Back, Gray)', 350, 2, 'chair', 1, 1, NULL, NULL),
	('prop_off_chair_05', 'Office Chair (Mesh Armrests)', 450, 2, 'chair', 1, 1, NULL, NULL),
	('prop_old_deck_chair', 'Folding Lounge Chair (Blue)', 160, 2, 'chair', 1, 1, NULL, NULL),
	('prop_old_wood_chair', 'Wooden Armchair (Gray, Aged)', 160, 2, 'chair', 1, 1, NULL, NULL),
	('prop_old_wood_chair_lod', 'Old Wooden Chair (LOD)', 180, 2, 'chair', 1, 1, NULL, NULL),
	('prop_palm_med_01a', 'Palm Tree (Tall, Full)', 3250, 3, 'plant', 1, 0, NULL, NULL),
	('prop_palm_med_01b', 'Palm Tree (Tall, Curved)', 3100, 3, 'plant', 1, 0, NULL, NULL),
	('prop_palm_med_01c', 'Palm Tree (Medium Height, Wide)', 3000, 3, 'plant', 1, 0, NULL, NULL),
	('prop_palm_med_01d', 'Palm Tree (Leaning Right)', 2300, 3, 'plant', 1, 0, NULL, NULL),
	('prop_palm_sm_01a', 'Palm Tree (Small Decorative)', 3000, 3, 'plant', 1, 0, NULL, NULL),
	('prop_palm_sm_01d', 'Palm Tree (Small, Dense)', 2400, 3, 'plant', 1, 0, NULL, NULL),
	('prop_palm_sm_01e', 'Palm Tree (Small, Thin)', 3000, 3, 'plant', 1, 0, NULL, NULL),
	('prop_palm_sm_01f', 'Palm Tree (Small, Thick)', 2800, 3, 'plant', 1, 0, NULL, NULL),
	('prop_parasol_01', 'Parasol (Light Gray Fabric)', 800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_01b_lod', 'Parasol (Dark Base, Lod Style)', 800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_01_b', 'Parasol (Dark Blue Fabric)', 800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_01_c', 'Parasol (Brown, Weathered)', 600, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_01_lod', 'Parasol (Light Gray, Lod Style)', 850, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_02', 'Parasol (White, Tilted)', 800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_02_b', 'Parasol (Green Fabric)', 800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_02_c', 'Parasol (Dark Blue Fabric)', 800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_03', 'Parasol (Blue, Wide)', 600, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_03_b', 'Parasol (Yellow with Branding)', 600, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_03_c', 'Parasol (Green with Branding)', 600, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_04c', 'Parasol (Red/White Sprunk Branding)', 600, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_parasol_04d', 'Parasol (Green/White Sprunk Branding)', 600, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_patio_lounger1', 'Patio Lounger (Beige Cushion)', 900, 3, 'chair', 1, 1, NULL, NULL),
	('prop_patio_lounger1b', 'Patio Lounger (No Cushion)', 800, 2, 'chair', 1, 1, NULL, NULL),
	('prop_patio_lounger1_table', 'Patio Table (Wooden)', 900, 1, 'table', 1, 1, NULL, NULL),
	('prop_patio_lounger_2', 'Green Patio Lounger (Foldable)', 800, 3, 'chair', 1, 1, NULL, NULL),
	('prop_patio_lounger_3', 'Patio Lounger (White)', 500, 3, 'chair', 1, 1, NULL, NULL),
	('prop_pc_02a', 'Old PC Tower (Beige)', 1600, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_picnictable_01', 'Wooden Picnic Table (Standard)', 680, 3, 'table', 1, 1, NULL, NULL),
	('prop_picnictable_01_lod', 'Wooden Picnic Table (LOD)', 500, 3, 'table', 1, 1, NULL, NULL),
	('prop_picnictable_02', 'Wooden Picnic Table (Red Accents)', 900, 3, 'table', 1, 1, NULL, NULL),
	('prop_plant_interior_05a', 'Rectangular Interior Planter', 600, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_01a', 'Indoor Plant (Short, Round Pot)', 360, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_01b', 'Indoor Plant (Tall, Brown Pot)', 340, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_02a', 'Topiary Ball (Square Pot)', 420, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_02b', 'Topiary Ball (Round Pot)', 460, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_03a', 'Indoor Fern (Stone Pot)', 330, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_03b', 'Indoor Plant (Tall, White Pot)', 285, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_03c', 'Indoor Plant (Pointed, Tall)', 250, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_04a', 'Wide Indoor Plant (Low Pot)', 310, 2, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_04b', 'Indoor Bush (Flat Base)', 340, 2, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_04c', 'Leafy Plant (Round Pot)', 120, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_05a', 'Wicker Planter with Flowers', 590, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_05b', 'Basket Plant (Rectangular)', 400, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_06a', 'Rectangular Planter (Tall Grass)', 550, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_06b', 'Planter with Dense Shrub', 700, 3, 'plant', 1, 1, NULL, NULL),
	('prop_plant_int_06c', 'Boxwood Hedge Planter', 500, 3, 'plant', 1, 1, NULL, NULL),
	('prop_police_door_r', 'Police Doors', 1250, 1, 'door', 1, 1, 'door', NULL),
	('prop_pooltable_02', 'Modern Pool Table (Gray Trim)', 3300, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_pooltable_3b', 'Pool Table (Purple)', 3600, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_portable_hifi_01', 'Portable Hi-Fi (Boombox)', 330, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_pot_03', 'Cooking Pot (Medium Size)', 60, 1, 'kitchen', 1, 1, NULL, NULL),
	('prop_pot_plant_01a', 'Indoor Plant (White Pot, Small)', 200, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_01b', 'Indoor Plant (Dark Pot, Spiked Leaves)', 280, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_01c', 'Indoor Plant (Tall, Curved Leaves)', 290, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_01d', 'Fan Palm (Short, Black Pot)', 340, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_01e', 'Fan Palm (Small, Gray Pot)', 300, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_02a', 'Round Pot with Shrub (Stone)', 390, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_02b', 'Planter with Flowers (Round, Tall)', 450, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_02c', 'Planter with Pink Flowers (Round)', 450, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_02d', 'Small Shrub in Round Pot', 350, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_03a', 'Cone Topiary (White Pot)', 550, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_03b', 'Shrub in Terracotta Pot', 480, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_03c', 'Topiary (Pedestal Pot)', 430, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_04a', 'Window Box Planter (Shrubs)', 365, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_04b', 'Ornamental Plant (Yellow Pot)', 620, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_04c', 'Ornamental Palm (Wide Pot)', 500, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_05a', 'Long Thin Plant (White Base)', 220, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_05b', 'Tall Cone Topiary (Gray Pot)', 530, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_05c', 'Rectangular Marble Planter', 850, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_05d', 'Round Stone Planter (Decorative)', 800, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_05d_l1', 'Stone Planter with Flowers', 900, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_6a', 'Hanging Fern Basket', 600, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_bh1', 'Small Bush in Ceramic Pot', 330, 3, 'plant', 1, 1, NULL, NULL),
	('prop_pot_plant_inter_03a', 'Indoor Tall Plant (Minimalist)', 360, 3, 'plant', 1, 1, NULL, NULL),
	('prop_printer_01', 'Office Printer (Closed Lid)', 450, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_proxy_chateau_table', 'Wooden Round Table (Chateau Style)', 800, 3, 'table', 1, 1, NULL, NULL),
	('prop_punch_bag_l', 'Punching Bag (Hanging)', 1800, 2, 'recreation', 1, 1, NULL, NULL),
	('prop_recyclebin_01a', 'Recycle Bin (Blue)', 300, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_recyclebin_02a', 'Recycle Bin (Red – Mixed Plastic)', 300, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_recyclebin_02b', 'Recycle Bin (Yellow – Cans Only)', 300, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_recyclebin_03_a', 'Classic Bin', 200, 2, 'outdoor', 1, 0, NULL, NULL),
	('prop_rub_binbag_01', 'Black Garbage Bag (Full)', 10, 2, 'decorations', 1, 1, NULL, NULL),
	('prop_rub_cabinet02', 'Wooden Shelf (Open, Aged)', 6100, 3, 'storage', 1, 1, 'storage', '{"slots":10,"weight":10000}'),
	('prop_rub_matress_01', 'Mattress (Old, Dirty)', 750, 2, 'bed', 1, 1, NULL, NULL),
	('prop_rub_table_01', 'Old Wooden Table (Worn)', 350, 2, 'table', 1, 1, NULL, NULL),
	('prop_rub_table_02', 'Rustic Wooden Table (Damaged)', 310, 3, 'table', 1, 1, NULL, NULL),
	('prop_rus_olive', 'Bushy Olive Shrub', 430, 3, 'plant', 1, 1, NULL, NULL),
	('prop_rus_olive_l2', 'Olive Tree (Large)', 800, 3, 'plant', 1, 0, NULL, NULL),
	('prop_rus_olive_wint', 'Winter Olive Tree', 2200, 3, 'plant', 1, 0, NULL, NULL),
	('prop_sapling_break_01', 'Young Sapling Tree (Thin)', 2100, 3, 'plant', 1, 0, NULL, NULL),
	('prop_sapling_break_02', 'Tall Sapling (Pointed)', 2400, 2, 'plant', 1, 1, NULL, NULL),
	('prop_shower_rack_01', 'Shower Rack (3 Shelves)', 80, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_shrub_rake', 'Garden Rake (Green)', 90, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_sink_02', 'Metal Sink (Wall Mounted)', 300, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_sink_05', 'Bathroom Sink (Rounded)', 280, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_sink_06', 'Bathroom Sink (Tall Pedestal)', 250, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_skid_chair_01', 'Camping Chair (Dark Green)', 430, 2, 'chair', 1, 1, NULL, NULL),
	('prop_skid_chair_02', 'Camping Chair (Blue Fabric)', 430, 2, 'chair', 1, 1, NULL, NULL),
	('prop_skid_chair_03', 'Camping Chair (Plaid)', 430, 2, 'chair', 1, 1, NULL, NULL),
	('prop_skid_tent_01', 'Camping Tent (Folded)', 800, 3, 'outdoor', 1, 0, NULL, NULL),
	('prop_soap_disp_01', 'Wall Soap Dispenser', 220, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_sol_chair', 'Classic Armchair (Leather, Worn)', 1650, 3, 'chair', 1, 1, NULL, NULL),
	('prop_speaker_01', 'Speaker Panel (Vertical, LED)', 1450, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_speaker_02', 'Tall Wooden Speaker (Hi-Fi)', 1200, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_speaker_06', 'Large Black Speaker', 2000, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_speaker_07', 'Party Speaker (Dual)', 550, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_speedball_01', 'Speedball Lamp (Modern)', 1400, 2, 'recreation', 1, 1, NULL, NULL),
	('prop_sponge_01', 'Cleaning Sponge (Yellow)', 20, 1, 'decorations', 1, 1, NULL, NULL),
	('prop_sports_clock_01', 'Wall Clock (Sport Design)', 130, 1, 'decorations', 0, 1, NULL, NULL),
	('prop_stickbfly', 'Butterfly Decoration (Purple)', 90, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_stickhbird', 'Bird Decoration (Small, Brown)', 90, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_stoneshroom1', 'Mushroom Decoration (Purple Glow)', 120, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_stoneshroom2', 'Mushroom Decoration (Blue Glow)', 120, 1, 'outdoor', 1, 0, NULL, NULL),
	('prop_stool_01', 'Bar Stool (Metal)', 280, 1, 'chair', 1, 1, NULL, NULL),
	('prop_suitcase_01', 'Suitcase (Black)', 5500, 1, 'storage', 1, 1, 'storage', '{"weight":10000,"slots":5}'),
	('prop_suitcase_01b', 'Suitcase (Brown)', 5500, 1, 'storage', 1, 1, 'storage', '{"weight":10000,"slots":5}'),
	('prop_suitcase_01c', 'Suitcase (Red)', 5500, 1, 'storage', 1, 1, 'storage', '{"weight":10000,"slots":5}'),
	('prop_suitcase_01d', 'Suitcase (Dark Gray)', 5500, 1, 'storage', 1, 1, 'storage', '{"weight":10000,"slots":5}'),
	('prop_suitcase_02', 'Double Suitcase (Stacked)', 3500, 1, 'storage', 1, 1, 'storage', '{"weight":10000,"slots":5}'),
	('prop_suitcase_03', 'Double Suitcase (Red + Black)', 3500, 1, 'storage', 1, 1, 'storage', '{"weight":10000,"slots":3}'),
	('prop_suitcase_03b', 'Double Suitcase (Black)', 3500, 1, 'storage', 1, 1, 'storage', '{"weight":10000,"slots":3}'),
	('prop_tablesmall_01', 'Low Wooden Table (Small)', 2000, 2, 'table', 1, 1, NULL, NULL),
	('prop_table_01', 'Coffee Table (Wooden)', 3400, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_01_chr_a', 'Wooden Chair (With Armrests)', 350, 2, 'chair', 1, 1, NULL, NULL),
	('prop_table_01_chr_b', 'Wooden Chair (No Armrests)', 320, 2, 'chair', 1, 1, NULL, NULL),
	('prop_table_02', 'Round Table (Wood + Iron Base)', 1200, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_02_chr', 'Wooden Chair (Dark Wood)', 300, 2, 'chair', 1, 1, NULL, NULL),
	('prop_table_03', 'Plastic Table (White)', 950, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_03b', 'Plastic Table (Square, White)', 600, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_03b_chr', 'Plastic Chair (Wide Back, White)', 220, 2, 'chair', 1, 1, NULL, NULL),
	('prop_table_03b_cs', 'Plastic Table (Square, White)', 600, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_03_chr', 'Plastic Chair (Armrest, White)', 220, 2, 'chair', 1, 1, NULL, NULL),
	('prop_table_04', 'Dining Bench (Dark Frame)', 3000, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_04_chr', 'Dining Chair (Metal Frame)', 380, 2, 'chair', 1, 1, NULL, NULL),
	('prop_table_05', 'Round Dining Table (Wicker)', 720, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_05_chr', 'Wicker Chair (Dining)', 330, 2, 'chair', 1, 1, NULL, NULL),
	('prop_table_06', 'Modern Dining Table (Metal Legs)', 1400, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_06_chr', 'Chair (Thin Metal Frame)', 650, 2, 'chair', 1, 1, NULL, NULL),
	('prop_table_07', 'Round Table with Pole', 620, 2, 'table', 1, 1, NULL, NULL),
	('prop_table_08', 'Old Wooden Table with Cover', 500, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_para_comb_01', 'Parasol Table (Wood, Red Canopy)', 1200, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_para_comb_03', 'Parasol Table (Blue Canopy)', 1200, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_para_comb_04', 'Parasol Table (Gray Fabric)', 1300, 3, 'table', 1, 1, NULL, NULL),
	('prop_table_tennis', 'Table Tennis Table (Foldable)', 2100, 3, 'table', 1, 1, NULL, NULL),
	('prop_toaster_01', 'Toaster (Stainless)', 400, 1, 'kitchen', 0, 1, NULL, NULL),
	('prop_toaster_02', 'Toaster (White)', 330, 1, 'kitchen', 0, 1, NULL, NULL),
	('prop_toilet_01', 'Toilet (Standard)', 350, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_toilet_02', 'Toilet (Modern)', 500, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_toilet_brush_01', 'Toilet Brush (Holder)', 25, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_toilet_soap_02', 'Toilet Soap (With Dish)', 30, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_toilet_soap_04', 'Soap Dispenser (Pink)', 50, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_toothpaste_01', 'Toothpaste Tube', 20, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_towel_01', 'Towel Stack (White)', 40, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_trev_tv_01', 'CRT TV (Wooden Frame)', 140, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_tv_01', 'TV (Black Screen)', 680, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_tv_02', 'TV', 550, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_tv_03', 'CRT TV (Dark Gray)', 150, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_tv_04', 'CRT TV (Retro, Gray)', 120, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_tv_05', 'Vintage TV (Antenna)', 700, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_tv_06', 'CRT TV (Angled Edges)', 200, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_tv_07', 'CRT TV (Beveled Frame)', 180, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_tv_cabinet_03', 'TV Cabinet (Open Shelves)', 6900, 2, 'storage', 1, 1, 'storage', '{"weight":10000,"slots":10}'),
	('prop_tv_cabinet_04', 'TV Cabinet (Bookshelf Style)', 6400, 2, 'storage', 1, 1, 'storage', '{"weight":10000,"slots":10}'),
	('prop_tv_cabinet_05', 'TV Cabinet (Curved Front)', 6200, 2, 'storage', 1, 1, 'storage', '{"slots":10,"weight":10000}'),
	('prop_tv_flat_01', 'Flat TV (Modern, On Stand)', 3700, 2, 'electronic', 0, 1, NULL, NULL),
	('prop_tv_flat_02', 'Flat TV (Modern, Wide)', 3000, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_tv_flat_02b', 'Flat TV (Wide, Black Frame)', 1300, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_tv_flat_03', 'Flat TV (Modern Stand)', 1500, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_tv_flat_03b', 'Flat TV (Stand, No Logo)', 4000, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_tv_flat_michael', 'Flat TV (Michael\'s Room)', 3800, 1, 'electronic', 0, 1, NULL, NULL),
	('prop_tv_stand_01', 'TV Stand (Trust Poster)', 2200, 3, 'electronic', 1, 1, NULL, NULL),
	('prop_tv_test', 'Test TV (Boxy, Placeholder)', 900, 1, 'electronic', 1, 1, NULL, NULL),
	('prop_t_sofa_02', 'Double Bed (White Frame)', 2100, 3, 'bed', 1, 1, NULL, NULL),
	('prop_ven_market_table1', 'Market Table (Foldable)', 1300, 2, 'table', 1, 1, NULL, NULL),
	('prop_waiting_seat_01', 'Waiting Seat (Brown Leather)', 500, 2, 'chair', 1, 1, NULL, NULL),
	('prop_wall_light_06a', 'Ceiling Light (Glass Dome)', 500, 1, 'light', 1, 1, NULL, NULL),
	('prop_washer_02', 'Washing Machine (Modern)', 2400, 2, 'electronic', 0, 1, NULL, NULL),
	('prop_washer_03', 'Washing Machine (Vintage)', 2000, 2, 'electronic', 0, 1, NULL, NULL),
	('prop_watercooler', 'Water Cooler (Plastic Jug)', 1100, 1, 'kitchen', 1, 1, NULL, NULL),
	('prop_weight_bench_02', 'Weight Bench (Barbell)', 1900, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_weight_rack_02', 'Weight Rack (Dumbbells)', 2400, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_weight_squat', 'Squat Rack (Weights)', 1100, 3, 'recreation', 1, 1, NULL, NULL),
	('prop_w_fountain_01', 'Public Sink (Stainless, Wide)', 180, 1, 'bathroom', 0, 1, NULL, NULL),
	('prop_yacht_table_01', 'Yacht Table (Dark Wicker)', 640, 1, 'table', 1, 1, NULL, NULL),
	('prop_yacht_table_02', 'Yacht Table (Gray Wicker)', 550, 2, 'table', 1, 1, NULL, NULL),
	('prop_yacht_table_03', 'Yacht Table (Black Wicker)', 1000, 3, 'table', 1, 1, NULL, NULL),
	('prop_yaught_chair_01', 'Yacht Chair (Single, Black)', 850, 2, 'chair', 1, 1, NULL, NULL),
	('prop_yaught_sofa_01', 'Yacht Sofa (2-Seat, Black)', 1900, 3, 'sofa', 1, 1, NULL, NULL),
	('p_dinechair_01_s', 'Ornate Wooden Chair (Carved)', 400, 2, 'chair', 1, 1, NULL, NULL),
	('p_lestersbed_s', 'Worn Wooden Bed (Blanket)', 1500, 3, 'bed', 0, 1, NULL, NULL),
	('p_new_j_counter_03', 'Store Counter', 9000, 2, 'storage', 1, 1, 'storage', '{"slots":10,"weight":10000}'),
	('p_v_43_safe_s', 'Vault Safe (Industrial)', 40000, 3, 'storage', 1, 1, 'safe', '{"weight":30000,"slots":12}'),
	('sf_mp_h_yacht_armchair_03', 'Yacht Armchair (White)', 1100, 2, 'chair', 1, 1, NULL, NULL),
	('sf_prop_sf_bed_dog_01a', 'Dog Bed (Grey)', 340, 2, 'decorations', 0, 1, NULL, NULL),
	('sf_prop_sf_bed_dog_01b', 'Dog Bed (Brown)', 330, 2, 'decorations', 0, 1, NULL, NULL),
	('sf_prop_sf_door_com_r_06a', 'Door with Glass', 2300, 2, 'door', 1, 1, 'door', NULL),
	('sf_prop_sf_door_rec_01a', 'Office Door with Glass', 2600, 2, 'door', 1, 1, 'door', NULL),
	('sum_mp_h_yacht_bed_01', 'Yacht Bed (Single, Brown)', 3600, 3, 'bed', 0, 1, NULL, NULL),
	('sum_mp_h_yacht_bed_02', 'Yacht Bed (Double, Gray)', 4000, 3, 'bed', 0, 1, NULL, NULL),
	('sum_p_mp_yacht_door_02', 'Door (Yacht)', 2650, 2, 'door', 1, 1, 'door', NULL),
	('tr_prop_tr_door2', 'Door (White)', 3300, 2, 'door', 1, 1, 'door', NULL),
	('tr_prop_tr_door3', 'Door (Grey)', 3300, 2, 'door', 1, 1, 'door', NULL),
	('vw_prop_art_wall_segment_02a', 'Stand Art (#1)', 3500, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_art_wall_segment_02b', 'Stand Art (#2)', 4000, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_casino_art_car_04a', 'Adder Model 1:43', 500, 1, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_casino_art_car_09a', 'Jester Model 1:43', 500, 1, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_casino_art_car_11a', 'X80 Proto Model 1:43', 500, 1, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_casino_art_gun_01a', 'Glass Display Stand (Revolver)', 2500, 1, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_casino_art_gun_02a', 'Glass Display Stand (Rifles)', 4000, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_casino_art_head_01c', 'Stone Head', 8900, 1, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_casino_art_miniature_05b', 'Statue of Liberty', 4900, 1, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_plaque_02a', 'Framed T-Shirt (Boars Baseball Club)', 4000, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_plaque_02b', 'Framed T-Shirt (LS Panic Basketball Team)', 4000, 1, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_101a', 'Painting (#3)', 12000, 1, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_131a', 'Wall Art (BennyS cars)', 1100, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_153a', 'Painting (#4)', 10000, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_170a', 'Painting (#6)', 8100, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_22a', 'Painting (#5)', 9800, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_26a', 'Painting (#7)', 14500, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_41a', 'Painting (#8)', 19000, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_42a', 'Painting (#9)', 14500, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_58a', 'Painting (#10)', 4800, 1, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_59a', 'Wall Art (Benefactor)', 6600, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_61a', 'Painting (#11)', 7800, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_81a', 'Wall Art (Abstraction)', 5500, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_86a', 'Wall Art (Knuckle)', 5000, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_94a', 'Wall Art (Logotypes)', 4900, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_wallart_96a', 'Wall Art (Le Chien)', 4700, 2, 'decorations', 1, 1, NULL, NULL),
	('vw_prop_vw_watch_case_01b', 'Watch Display Case (Tray)', 43000, 1, 'decorations', 1, 1, NULL, NULL),
	('v_club_officechair', 'Office Chair (Boss Style)', 950, 2, 'chair', 1, 1, NULL, NULL),
	('v_club_roc_eq1', 'Club Speaker (Wall Mount 1)', 300, 1, 'electronic', 1, 1, NULL, NULL),
	('v_club_roc_eq2', 'Club Speaker (Wall Mount 2)', 450, 1, 'electronic', 1, 1, NULL, NULL),
	('v_corp_bk_chair3', 'Desk Chair (Gray, No Arms)', 200, 2, 'chair', 1, 1, NULL, NULL),
	('v_corp_cd_chair', 'Desk Chair (Red)', 240, 1, 'chair', 1, 1, NULL, NULL),
	('v_corp_offchair', 'Office Chair (Slim Back)', 800, 2, 'chair', 1, 1, NULL, NULL),
	('v_ilev_bk_door2', 'Office Door with Glass 2', 2000, 2, 'door', 1, 1, 'door', NULL),
	('v_ilev_dev_door', 'Door (Black)', 3300, 2, 'door', 1, 1, 'door', NULL),
	('v_ilev_fa_roomdoor', 'Room Door', 1950, 2, 'door', 1, 1, 'door', NULL),
	('v_ilev_fh_door5', 'Dirty Door', 800, 2, 'door', 1, 1, 'door', NULL),
	('v_ilev_fh_frontdoor', 'Front Door', 2450, 2, 'door', 1, 1, 'door', NULL),
	('v_ilev_fh_lampa_on', 'Small Table Lamp', 740, 1, 'light', 1, 1, NULL, NULL),
	('v_ilev_liconftable_sml', 'Coffee Table (Modern, Black)', 8000, 3, 'table', 1, 1, NULL, NULL),
	('v_ilev_m_sofa', 'L-Shaped Sofa (Modern)', 4500, 3, 'sofa', 1, 1, NULL, NULL),
	('v_ilev_ra_door2', 'Wooden Door', 2300, 2, 'door', 1, 1, 'door', NULL),
	('v_ilev_roc_door3', 'Shop Door', 900, 2, 'door', 1, 1, 'door', NULL),
	('v_ilev_tort_door', 'Torture Door', 500, 2, 'door', 1, 1, 'door', NULL),
	('v_ilev_trev_doorbath', 'Bathroom Door', 1800, 2, 'door', 1, 1, 'door', NULL),
	('v_ind_rc_lowtable', 'Wooden Coffee Table (Low)', 800, 1, 'table', 1, 1, NULL, NULL),
	('v_med_fabricchair1', 'Fabric Armchair (Green)', 590, 2, 'chair', 1, 1, NULL, NULL),
	('v_res_cakedome', 'Decorative Cake Dome (Glass)', 75, 1, 'kitchen', 1, 1, NULL, NULL),
	('v_res_d_bed', 'Iron Bed (Red Floral)', 1300, 3, 'bed', 0, 1, NULL, NULL),
	('v_res_fh_bedsideclock', 'Alarm Clock (Black/White)', 100, 1, 'decorations', 0, 1, NULL, NULL),
	('v_res_foodjara', 'Food Jar (Chilli)', 45, 1, 'kitchen', 1, 1, NULL, NULL),
	('v_res_foodjarb', 'Food Jar (Rice)', 40, 1, 'kitchen', 1, 1, NULL, NULL),
	('v_res_fridgemodsml', 'Fridge (Double Door, Water Dispenser)', 9900, 3, 'kitchen', 0, 1, 'storage', '{"weight":8000,"slots":8}'),
	('v_res_mbath', 'Bathtub (Modern Oval)', 600, 3, 'bathroom', 0, 1, NULL, NULL),
	('v_res_mbbed', 'Double Bed (Red Blanket)', 1800, 3, 'bed', 0, 1, NULL, NULL),
	('v_res_mbdresser', 'Dresser (Ornate Wood)', 3100, 3, 'table', 1, 1, NULL, NULL),
	('v_res_mbsink', 'Bathroom Sink (Wall Mount)', 400, 1, 'bathroom', 0, 1, NULL, NULL),
	('v_res_mconsolemod', 'TV Console (Ornate Wood)', 5500, 3, 'storage', 1, 1, 'storage', '{"weight":20000,"slots":15}'),
	('v_res_mdbed', 'Double Bed (Red Blanket)', 2300, 3, 'bed', 0, 1, NULL, NULL),
	('v_res_mflowers', 'Flowers in Vase (White & Pink)', 520, 2, 'plant', 1, 1, NULL, NULL),
	('v_res_mknifeblock', 'Knife Block (Wood)', 110, 1, 'kitchen', 1, 1, NULL, NULL),
	('v_res_mmug', 'Mug (Stoneware)', 20, 1, 'kitchen', 1, 1, NULL, NULL),
	('v_res_monitorsquare', 'Monitor (Square)', 2000, 1, 'electronic', 0, 1, 'device', NULL),
	('v_res_monitorwidelarge', 'Monitor (Wide)', 3000, 1, 'electronic', 0, 1, 'device', NULL),
	('v_res_mousemat', 'Mouse with Mat', 110, 1, 'electronic', 0, 1, NULL, NULL),
	('v_res_mplatesml', 'Dinner Plate (Blue Border)', 20, 1, 'kitchen', 1, 1, NULL, NULL),
	('v_res_msonbed', 'Single Bed (Dark, Unmade)', 1700, 3, 'bed', 0, 1, NULL, NULL),
	('v_res_m_armoire', 'Armoire (Classic, Closed)', 7000, 3, 'storage', 1, 1, 'wardrobe', NULL),
	('v_res_m_armoirmove', 'Armoire (Single, Moveable)', 6200, 3, 'storage', 1, 1, 'wardrobe', NULL),
	('v_res_m_l_chair1', 'Armchair (Vintage, Orange)', 640, 2, 'chair', 1, 1, NULL, NULL),
	('v_res_paperfolders', 'Stack of Folders', 45, 1, 'decorations', 1, 1, NULL, NULL),
	('v_res_pcheadset', 'PC Headset (Lying)', 190, 1, 'electronic', 0, 1, NULL, NULL),
	('v_res_pcspeaker', 'Tower Speaker (Standing)', 300, 1, 'electronic', 0, 1, NULL, NULL),
	('v_res_pctower', 'PC Tower (Black)', 2000, 1, 'electronic', 0, 1, NULL, NULL),
	('v_res_pestle', 'Mortar and Pestle (Stone)', 50, 1, 'kitchen', 1, 1, NULL, NULL),
	('v_res_printer', 'Printer (Multifunction)', 550, 1, 'electronic', 0, 1, NULL, NULL),
	('v_res_r_perfume', 'Perfume Bottle (Green)', 300, 1, 'decorations', 1, 1, NULL, NULL),
	('v_res_tre_bed1', 'Double Bed (White Frame)', 2500, 3, 'bed', 0, 1, NULL, NULL),
	('v_res_tre_bed1_messy', 'Double Bed (Messy Blanket)', 2200, 3, 'bed', 0, 1, NULL, NULL),
	('v_res_tre_bedsidetable', 'Nightstand (Single Drawer)', 5500, 1, 'storage', 1, 1, 'storage', '{"slots":10,"weight":10000}'),
	('v_res_tre_chair', 'Chair (White Wooden)', 400, 1, 'chair', 1, 1, NULL, NULL),
	('v_res_tre_fridge', 'Fridge (Retro)', 7800, 2, 'kitchen', 0, 1, 'storage', '{"weight":8000,"slots":8}'),
	('v_res_tre_lightfan', 'Ceiling Fan (Wooden)', 800, 2, 'light', 1, 1, NULL, NULL),
	('v_res_tt_bed', 'Bed (Simple, Plaid)', 1200, 3, 'bed', 0, 1, NULL, NULL),
	('v_res_tt_bowlpile02', 'Stack of Plates', 40, 1, 'kitchen', 1, 1, NULL, NULL),
	('v_res_vacuum', 'Vacuum Cleaner (Upright)', 1250, 1, 'electronic', 0, 1, NULL, NULL),
	('v_ret_gc_chair03', 'Office Chair (Black Leather)', 800, 2, 'chair', 1, 1, NULL, NULL),
	('v_ret_gc_shred', 'Shredder (Office)', 480, 1, 'electronic', 0, 1, NULL, NULL),
	('v_ret_mirror', 'Wall Mirror (Gold Frame)', 300, 1, 'decorations', 0, 1, NULL, NULL),
	('v_ret_ta_paproll', 'Toilet Paper Roll (Dark Texture)', 15, 1, 'decorations', 1, 1, NULL, NULL),
	('xm_lab_chairarm_03', 'Office Chair (Wood Frame)', 550, 2, 'chair', 1, 1, NULL, NULL),
	('xm_prop_lab_desk_02', 'Modern Desk (Black&Wooden)', 3600, 3, 'table', 1, 1, NULL, NULL),
	('xm_prop_x17_l_door_frame_01', 'Glass Door', 3100, 2, 'door', 1, 1, 'door', NULL),
	('zprop_bin_01a_old', 'Classic Bin (Mesh)', 280, 2, 'outdoor', 1, 0, NULL, NULL);
```

</details>

***

## 5. Add Required Items

The script uses its own items.\
Depending on what inventory you are using, select the appropriate section and add these items to either your item file or database.

<details>

<summary>Items for esx inventory</summary>

```sql
INSERT INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
    ("house_key", "Housing Key", 10, 0, 1),
    ("lockpick", "Lockpick", 8000, 0, 1),
    ("police_barrier", "Police Barrier", 3000, 0, 1),
    ("police_stormram", "Police Storm Ram", 5000, 0, 1),
    ("pliers", "Pliers", 500, 0, 1),
    ("insulating_tape", "Insulating Tape", 200, 0, 1)
;
```

</details>

<details>

<summary>Items for ox_inventory</summary>

```lua
['house_key'] = {
    label = 'Housing Key',
    weight = 10,
    stack = false,
    close = true,
},
['lockpick'] = {
    label = 'Lockpick',
    weight = 800,
    stack = true,
    close = true,
},
['police_barrier'] = {
    label = 'Police Barrier',
    weight = 300,
    stack = true,
    close = true,
},
['police_stormram'] = {
    label = 'Police Storm Ram',
    weight = 5000,
    stack = true,
    close = true,
},
['pliers'] = {
    label = 'Pliers',
    weight = 500,
    stack = true,
    close = true,
},
['insulating_tape'] = {
    label = 'Insulating Tape',
    weight = 200,
    stack = true,
    close = true,
},
```

</details>

<details>

<summary>Items for qb-inventory / qs-inventory / origen_inventory</summary>

```lua
['house_key'] = {
    ["name"] = 'house_key',
    ["label"] = "Housing Key",
    ["description"] = "",
    ["weight"] = 10,
    ["type"] = "item",
    ["image"] = "house_key.png",
    ["unique"] = true,
    ["useable"] = true,
    ["shouldClose"] = true,
    ["combinable"] = nil,
},
['lockpick'] = {
    ["name"] = 'lockpick',
    ["label"] = "Lockpick",
    ["description"] = "",
    ["weight"] = 800,
    ["type"] = "item",
    ["image"] = "lockpick.png",
    ["unique"] = false,
    ["useable"] = true,
    ["shouldClose"] = false,
    ["combinable"] = nil,
},
['police_barrier'] = {
    ["name"] = 'police_barrier',
    ["label"] = "Police Barrier",
    ["description"] = "",
    ["weight"] = 300,
    ["type"] = "item",
    ["image"] = "police_barrier.png",
    ["unique"] = false,
    ["useable"] = true,
    ["shouldClose"] = false,
    ["combinable"] = nil,
},
['police_stormram'] = {
    ["name"] = 'police_stormram',
    ["label"] = "Police Storm Ram",
    ["description"] = "",
    ["weight"] = 5000,
    ["type"] = "item",
    ["image"] = "police_stormram.png",
    ["unique"] = false,
    ["useable"] = true,
    ["shouldClose"] = false,
    ["combinable"] = nil,
},
['pliers'] = {
    ["name"] = 'pliers',
    ["label"] = "Pliers",
    ["description"] = "",
    ["weight"] = 500,
    ["type"] = "item",
    ["image"] = "pliers.png",
    ["unique"] = false,
    ["useable"] = false,
    ["shouldClose"] = false,
    ["combinable"] = nil,
},
['insulating_tape'] = {
    ["name"] = 'insulating_tape',
    ["label"] = "Insulating Tape",
    ["description"] = "",
    ["weight"] = 200,
    ["type"] = "item",
    ["image"] = "insulating_tape.png",
    ["unique"] = false,
    ["useable"] = false,
    ["shouldClose"] = false,
    ["combinable"] = nil,
},
```

</details>

***

## 6. Register Jobs

Some functions work only if the player has a proper job.\
In this section, you will find ready-made job that you need to add in your framework (ESX/QB).

<details>

<summary>Jobs for ESX</summary>

```sql
INSERT INTO `jobs` (name, label) VALUES
    ('realestate', 'Real Estate'),
    ('dynasty8', 'Dynasty 8'),
    ('idealproperty', 'Ideal Property'),
    ('idea', 'IDEA');

INSERT INTO `job_grades` (job_name, grade, name, label, salary, skin_male, skin_female) VALUES
    ('realestate', 0, 'employee', 'Employee', 150, '{}', '{}'),
    ('dynasty8', 0, 'employee', 'Employee', 150, '{}', '{}'),
    ('dynasty8', 1, 'manager', 'Manager', 150, '{}', '{}'),
    ('dynasty8', 2, 'boss', 'Boss', 150, '{}', '{}'),
    ('idealproperty', 0, 'employee', 'Employee', 150, '{}', '{}'),
    ('idealproperty', 1, 'manager', 'Manager', 150, '{}', '{}'),
    ('idealproperty', 2, 'boss', 'Boss', 150, '{}', '{}'),
    ('idea', 0, 'employee', 'Employee', 150, '{}', '{}'),
    ('idea', 1, 'boss', 'Boss', 150, '{}', '{}');
```

</details>

<details>

<summary>Jobs for QB-Core</summary>

```lua
['realestate'] = {
    label = 'Real Estate',
    defaultDuty = true,
    offDutyPay = false,
    grades = {
        ['0'] = {
            name = 'Employee',
            payment = 150
        },
    },
},
['dynasty8'] = {
    label = 'Dynasty 8',
    defaultDuty = true,
    offDutyPay = false,
    grades = {
        ['0'] = {
            name = 'Employee',
            payment = 150
        },
        ['1'] = {
            name = 'Manager',
            payment = 150
        },
        ['2'] = {
            name = 'Boss',
            payment = 150
        },
    },
},
['idealproperty'] = {
    label = 'Ideal Property',
    defaultDuty = true,
    offDutyPay = false,
    grades = {
        ['0'] = {
            name = 'Employee',
            payment = 150
        },
        ['1'] = {
            name = 'Manager',
            payment = 150
        },
        ['2'] = {
            name = 'Boss',
            payment = 150
        },
    },
},
['idea'] = {
    label = 'IDEA',
    defaultDuty = true,
    offDutyPay = false,
    grades = {
        ['0'] = {
            name = 'Employee',
            payment = 150
        },
        ['1'] = {
            name = 'Manager',
            payment = 150
        },
    },
},
```

</details>

***

## 7. Environment Requirements

{% stepper %}
{% step %}

### Introduction of door functionality

Go to your **server.cfg** and add:

```nginx
setr game_enableDynamicDoorCreation "true"
```

{% endstep %}
{% endstepper %}

***

## 8. Start Resource

To start a resource in your `server.cfg`, ensure that it begins after your framework has been initiated. For instance, if you are using a framework like `es_extended`, you should start resource after it, like so:

```python
# Here you should start your shells

start [core] # For example, here is your framework (esx/qb-core)

# Here you run your other resources like esx_policejob etc.

# Dependencies
start t3_lockpick        # Minigame for lockpicking
start tgiann-skillbar    # Minigame for raid
start bl_ui              # Minigames for power box

# VMS Resources
start vms_housing
start vms_housing_idea   # IDEA Furniture store MLO
```

Ensure there are no syntax errors or incorrect paths in your `server.cfg`.


# Compatibility

<table data-view="cards"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td>✅ <strong>Officially Supported</strong></td><td>Veified compatibility and built in both resources.</td><td></td></tr><tr><td>✅ <strong>Supported</strong></td><td>Veified compatibility but possible code modifications.</td><td></td></tr><tr><td>⚠️ <strong>Supported</strong></td><td>Compatibility according to code from the resource, but has not been tested.</td><td></td></tr><tr><td>🚫 <strong>Not Supported</strong></td><td>Unsupported resource, you cannot use this resource compatibility.</td><td></td></tr><tr><td><strong>🕛 Soon...</strong></td><td>We plan to introduce compatibility soon.</td><td></td></tr></tbody></table>

## Migration:

{% hint style="success" %}

### Custom Migration

If you need migration from a different housing script that is not listed, please open a ticket on our Discord and let us know.

We'll need the SQL structure of your current housing system in order to evaluate and prepare a custom migration solution.
{% endhint %}

{% hint style="info" %}
**The migration is intended solely to simplify the configuration of properties on your server.**\
**It does not transfer furniture, property owners, or any other data associated with the houses.**

Its purpose is **only to preserve registered properties**, helping you reduce the time needed to reconfigure hundreds of existing locations.

Some options may require manual adjustments in the in-game configurator, as not every housing script uses the same settings or data structure as vms\_housing.
{% endhint %}

<table><thead><tr><th width="159.39990234375">Resource</th><th width="206.800048828125">Is compatible</th><th>Information</th></tr></thead><tbody><tr><td><strong>qs-housing</strong></td><td>✅  <strong>Officially Supported</strong></td><td>The migration transfers all essential data from Shell and IPL-based properties.<br>MLO migration are not supported.</td></tr><tr><td><strong>nolag_properties</strong></td><td>✅  <strong>Officially Supported</strong></td><td>The migration transfers all essential data from Shell and IPL-based properties.<br>MLO migration are not supported.</td></tr><tr><td><strong>ps-housing</strong></td><td>✅  <strong>Officially Supported</strong></td><td>The migration transfers all essential data from Shell properties.<br>MLO migration are not supported.</td></tr><tr><td><strong>esx_property</strong></td><td>🚫 <strong>Not Supported</strong></td><td>No PolyZone data required in our script.</td></tr><tr><td><strong>qb-houses</strong></td><td>🚫 <strong>Not Supported</strong></td><td>No PolyZone data required in our script.</td></tr><tr><td><strong>qbx_houses</strong></td><td>🚫 <strong>Not Supported</strong></td><td>No PolyZone data required in our script.</td></tr><tr><td><strong>loaf_housing</strong></td><td>🚫 <strong>Not Supported</strong></td><td>No PolyZone data required in our script.</td></tr></tbody></table>

## Garages:

{% hint style="info" %}

### Custom Integration

If you don't find your garages in the list below, don't worry!

In the **./integration/\[garages]** you can customize the exports.
{% endhint %}

<table><thead><tr><th width="241.800048828125">Resource</th><th width="357.5999755859375">Is compatible</th><th align="center">Tutorial</th></tr></thead><tbody><tr><td><strong>vms_garagesV2</strong></td><td>✅  <strong>Officially Supported</strong></td><td align="center">-</td></tr><tr><td><strong>esx_garage</strong></td><td>✅ <strong>Officially Supported</strong></td><td align="center">-</td></tr><tr><td><strong>cd_garage</strong></td><td>✅ <strong>Officially Supported</strong></td><td align="center">-</td></tr><tr><td><strong>okokGarage</strong></td><td>✅ <strong>Officially Supported</strong></td><td align="center">-</td></tr><tr><td><strong>ZSX_Garages</strong></td><td>✅ <strong>Officially Supported</strong></td><td align="center">-</td></tr><tr><td><strong>qbx_garages</strong></td><td>✅ <strong>Officially Supported</strong></td><td align="center">-</td></tr><tr><td><strong>qb-garages</strong></td><td>✅ <strong>Supported</strong></td><td align="center"><a data-mention href="/pages/2ME7zCNuDVafE9dh27Jz">/pages/2ME7zCNuDVafE9dh27Jz</a></td></tr><tr><td><strong>rcore_garages</strong></td><td>⚠️ <strong>Supported</strong></td><td align="center">-</td></tr><tr><td><strong>loaf_garage</strong></td><td>⚠️ <strong>Supported</strong></td><td align="center">-</td></tr><tr><td><strong>jg-advancedgarages</strong></td><td>⚠️ <strong>Supported</strong></td><td align="center">-</td></tr><tr><td><strong>RxGarages</strong></td><td>⚠️ <strong>Supported</strong></td><td align="center">-</td></tr></tbody></table>

## Inventory:

{% hint style="info" %}

### Custom Integration

If you don't find your inventory in the list below, don't worry!

In the **./integration/\[inventory]** you can customize the exports.
{% endhint %}

<table><thead><tr><th width="365">Resource</th><th>Is compatible</th></tr></thead><tbody><tr><td><strong>ox_inventory</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>qb-inventory</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>qs-inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>ps-inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>tgiann-inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>codem-inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>origen_inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>core_inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>ak47_inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>Cheeza Inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>jaksam_inventory</strong></td><td>⚠️ <strong>Supported</strong></td></tr></tbody></table>

## Dispatch:

{% hint style="info" %}

### Custom Integration

If you don't find your dispatch in the list below, don't worry!

The script sends notifications to the police by default in case of a break-in, and if you have your custom dispatch, in the **./integration/\[dispatch]** you can customize the exports.
{% endhint %}

<table><thead><tr><th width="365">Resource</th><th>Is compatible</th></tr></thead><tbody><tr><td><strong>lb-tablet</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>cd_dispatch</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>core_dispatch</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>ps-dispatch</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>rcore_dispatch</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>qs-dispatch</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>tk_dispatch</strong></td><td>⚠️ <strong>Supported</strong></td></tr></tbody></table>

## Clothing:

{% hint style="info" %}

### Custom Integration

If you don't find your clothing system in the list below, don't worry!

In the **./integration/\[clothing]** you can customize the exports.
{% endhint %}

<table><thead><tr><th width="365">Resource</th><th>Is compatible</th></tr></thead><tbody><tr><td><strong>vms_clothestore</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>qb-clothing</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>illenium-appearance</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>fivem-appearance</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>rcore_clothing</strong></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://17movement.net/products/complete-character-system"><strong>17mov_CharacterSystem</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><strong>raid_clothes</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>qs-appearance</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>codem-appearance</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>bl_appearance</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>ak47_clothing</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>tgiann-clothing</strong></td><td>⚠️ <strong>Supported</strong></td></tr><tr><td><strong>crm-appearance</strong></td><td>⚠️ <strong>Supported</strong></td></tr></tbody></table>

## Shells:

{% hint style="info" %}

### Custom Integration

If you don't find your shells in the list below, don't worry!

In the **./integration/\[shells]** you can add new shells.
{% endhint %}

<table><thead><tr><th width="461.36358642578125">Resource</th><th>Is compatible</th></tr></thead><tbody><tr><td><a href="https://k4mb1maps.com/product/5015840"><strong>K4MB1-StarterShells</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://k4mb1maps.com/product/7166895"><strong>K4MB1-ExclusiveHousingPack</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://k4mb1maps.com/product/7166890"><strong>K4MB1-FurnishedHousingPack</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://k4mb1maps.com/product/7166893"><strong>K4MB1-GaragesPack</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://k4mb1maps.com/product/7166896"><strong>K4MB1-IllegalOperationsPack</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://k4mb1maps.com/product/4673316"><strong>K4MB1-MainHousingPack</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://k4mb1maps.com/product/7166894"><strong>K4MB1-MiscPack</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://k4mb1maps.com/product/7166889"><strong>K4MB1-PremiumHousingPack</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://k4mb1maps.com/product/5107241-1"><strong>K4MB1-AllShellsSubPack</strong> &#x26; <strong>K4MB1-AllShellsSubPack2</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://www.dccustomz.com/"><strong>DC Customz</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://forum.cfx.re/t/free-envi-shells-interior-shells-for-housing-scripts"><strong>ENVI</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://kuzquality.com/package/shell-creator"><strong>KuzQuality Shells Creator</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://jamaring-maps.tebex.io/package/6095279"><strong>JamaringMaps_Shells V1</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://jamaring-maps.tebex.io/package/6274927"><strong>JamaringMaps_Shells V2</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://jamaring-maps.tebex.io/package/6377855"><strong>JamaringMaps_Shells V3</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://jamaring-maps.tebex.io/package/6559637"><strong>JamaringMaps_Shells V4</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://jamaring-maps.tebex.io/package/6715104"><strong>JamaringMaps_Shells V5</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://maxcreationsstore.tebex.io/package/6801554"><strong>Max Creations "2 Classic Shells"</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr><tr><td><a href="https://maxcreationsstore.tebex.io/package/6177840"><strong>Max Creations "25 Shells Pack"</strong></a></td><td>✅ <strong>Officially Supported</strong></td></tr></tbody></table>

## Furniture:

{% hint style="info" %}

### Custom Integration

If you don't find your furniture pack in the list below, don't worry!

You can add an **unlimited number of custom furniture items** using the VMS Housing menu!
{% endhint %}

<table><thead><tr><th width="261.5999755859375">Resource</th><th width="312">Is compatible</th><th align="center">Tutorial</th></tr></thead><tbody><tr><td><a href="https://atenea.tebex.io/package/7019619"><strong>Atenea Store (Bedroom 1)</strong></a></td><td>✅ <strong>Supported</strong></td><td align="center"><a href="https://atenea.gitbook.io/atenea-store-r/props/furniture-or-bedroom-1/vms-housing"><strong>SQL File</strong></a></td></tr><tr><td><a href="https://atenea.tebex.io/package/7043470"><strong>Atenea Store (Dining 1)</strong></a></td><td>✅ <strong>Supported</strong></td><td align="center"><a href="https://atenea.gitbook.io/atenea-store-r/props/furniture-or-dining-1/vms-housing"><strong>SQL File</strong></a></td></tr><tr><td><a href="https://atenea.tebex.io/package/6969523"><strong>Atenea Store (Gaming)</strong></a></td><td>✅ <strong>Supported</strong></td><td align="center"><a href="https://atenea.gitbook.io/atenea-store-r/props/furniture-or-gaming/vms-housing"><strong>SQL File</strong></a></td></tr><tr><td><a href="https://atenea.tebex.io/package/7072698"><strong>Atenea Store (Garden 1)</strong></a></td><td>✅ <strong>Supported</strong></td><td align="center"><a href="https://atenea.gitbook.io/atenea-store-r/props/furniture-or-garden-1/vms-housing"><strong>SQL File</strong></a></td></tr><tr><td><a href="https://atenea.tebex.io/package/7058602"><strong>Atenea Store (Hall 1)</strong></a></td><td>✅ <strong>Supported</strong></td><td align="center"><a href="https://atenea.gitbook.io/atenea-store-r/props/furniture-or-hall-1/vms-housing"><strong>SQL File</strong></a></td></tr><tr><td><a href="https://atenea.tebex.io/package/7055631"><strong>Atenea Store</strong> (<strong>Kitchen 1)</strong></a></td><td>✅ <strong>Supported</strong></td><td align="center"><a href="https://atenea.gitbook.io/atenea-store-r/props/furniture-or-kitchen-1/vms-housing"><strong>SQL File</strong></a></td></tr><tr><td><a href="https://atenea.tebex.io/package/7030626"><strong>Atenea Store (Lounge 1)</strong></a></td><td>✅ <strong>Supported</strong></td><td align="center"><a href="https://atenea.gitbook.io/atenea-store-r/props/furniture-or-lounge-1/vms-housing"><strong>SQL File</strong></a></td></tr></tbody></table>

## Phones:

<table><thead><tr><th width="259">Resource</th><th width="240">SMS Alerts</th><th>Phone "Home" App</th></tr></thead><tbody><tr><td><a href="https://store.lbphone.com/"><strong>lb-phone</strong></a></td><td>✅  <strong>Officially Supported</strong></td><td>✅  <strong>Officially Supported</strong></td></tr><tr><td><a href="https://17movement.net/products/17mov_phone"><strong>17mov_Phone</strong></a></td><td>✅ <strong>Officially Supported</strong></td><td>✅  <strong>Officially Supported</strong></td></tr><tr><td><a href="https://store.teamsgg.com/category/phone"><strong>yflip-phone</strong></a></td><td>✅  <strong>Officially Supported</strong></td><td><strong>🕛 Soon...</strong></td></tr><tr><td><strong>okokPhone</strong></td><td>✅  <strong>Officially Supported</strong></td><td>🚫 <strong>Not Supported</strong></td></tr><tr><td><strong>GKSPhone 1.0</strong></td><td>✅  <strong>Officially Supported</strong></td><td>🚫 <strong>Not Supported</strong></td></tr><tr><td><strong>GKSPhone 2.0</strong></td><td>✅  <strong>Officially Supported</strong></td><td>✅  <strong>Officially Supported</strong></td></tr><tr><td><strong>qs-smartphone-pro</strong></td><td>🚫 <strong>Not Supported</strong></td><td>🚫 <strong>Not Supported</strong></td></tr></tbody></table>


# Phones


# lb-phone

1. Navigate to the **lb-phone/config/config.lua**
2. Find the `Config.HouseScript` option and customize:

```lua
Config.HouseScript = "vms_housing"
```

2. Navigate to the **lb-phone/client/apps/framework/home**
3. Create a new lua file named `vms_housing.lua`
4. Paste all of the following code into the `vms_housing.lua` file

```lua
if Config.HouseScript ~= "vms_housing" then
    return
end

local function FormatKeyHolders(keys)
    local formatted = {}

    if keys then
        local keys = json.decode(keys)
        if exports['vms_housing']:GetConfiguration('UseKeysOnItem') then        
            for _, keySerialNumber in pairs(keys) do
                table.insert(formatted, {
                    identifier = nil,
                    name = keySerialNumber
                })
            end
        else
            for identifier, characterName in pairs(keys) do
                table.insert(formatted, {
                    identifier = identifier,
                    name = characterName
                })
            end
        end
    end

    return formatted
end

local function FormatHouses(houses)
    local formatted = {}
    for i = 1, #houses do
        local houseData = houses[i]

        local coords = nil
        local building = nil
        if houseData.object_id then
            local object = exports['vms_housing']:GetProperty(houseData.object_id)
            if not object then
                return
            end
        
            if object.type == 'building' then
                building = object
            end
        end
        coords = building and building.metadata.enter or houseData.metadata.enter or houseData.metadata.menu

        formatted[i] = {
            label = houseData.name,
            id = houseData.id,
            uniqueId = houseData.id,
            coords = coords,
            locked = nil,
            keyholders = FormatKeyHolders(houseData.keys)
        }
    end

    return formatted
end

RegisterNUICallback("Home", function(data, cb)
    local action, houseData = data.action, data.houseData
    debugprint("vms_housing - Home:" .. (action or ""))

    if action == "getHomes" then
        cb(FormatHouses(exports['vms_housing']:GetPlayerProperties()))

    elseif action == "removeKeyholder" then
        -- To make the Remove Keyholder option work, you have to use `Config.UseKeysOnItem = false` in VMS Housing
        if not exports['vms_housing']:GetConfiguration('UseKeysOnItem') then
            TriggerServerEvent('vms_housing:sv:removeKey', houseData.uniqueId, data.identifier)
            Wait(500)
            cb(FormatKeyHolders(exports['vms_housing']:GetProperty(houseData.uniqueId)?.keys))
        end

    elseif action == "addKeyholder" then
        TriggerServerEvent('vms_housing:sv:buyKey', houseData.uniqueId, tonumber(data.source))
        TriggerServerEvent('vms_housing:sv:giveKey', houseData.uniqueId, tonumber(data.source))
        Wait(500)
        cb(FormatKeyHolders(exports['vms_housing']:GetProperty(houseData.uniqueId)?.keys))
        
    elseif action == "setWaypoint" then
        if houseData.coords then
            SetNewWaypoint(houseData.coords.x, houseData.coords.y)
        end

        cb(true)
    end
end)
```


# Garages


# qb-garages

1. Navigate to the **qb-garages/client/main.lua**
2. Find the `qb-garages:client:setHouseGarage` event and replace:

```lua
RegisterNetEvent('qb-garages:client:setHouseGarage', function(house) -- event sent periodically from housing
    if not house then return end
    local formattedHouseName = string.gsub(string.lower(house), ' ', '')
    local zoneName = 'house_' .. formattedHouseName
    local hasKey = exports['vms_housing']:HasPermissions(house, 'garage')
    
    if Config.Garages[formattedHouseName] then
        if hasKey and not ZoneExists(zoneName) then
            CreateHouseZone(formattedHouseName, Config.Garages[formattedHouseName], 'house')
        elseif not hasKey and ZoneExists(zoneName) then
            RemoveHouseZone(zoneName)
        end
    else
        if not hasKey then
            return
        end
        local property = exports['vms_housing']:GetProperty(house)
        if not property or not property.metadata or not property.metadata.garage then return end
        local garageCoords = property.metadata.garage
        Config.Garages[formattedHouseName] = {
            houseName = house,
            takeVehicle = vector3(garageCoords.x, garageCoords.y, garageCoords.z),
            spawnPoint = {
                vector4(garageCoords.x, garageCoords.y, garageCoords.z, garageCoords.w)
            },
            label = property.name,
            type = 'house',
            category = Config.VehicleClass['all']
        }
        CreateHouseZone(formattedHouseName, Config.Garages[formattedHouseName], 'house')
    end
end)
```

2. Navigate to the **qb-garages/server/main.lua**
3. Find the `qb-garages:server:getHouseGarage` callback and replace:

```lua
QBCore.Functions.CreateCallback('qb-garages:server:getHouseGarage', function(_, cb, house)
    local property = exports['vms_housing']:GetProperty(house)
    cb(property)
end)
```

4. Find the `qb-garages:server:GetGarageVehicles` callback and replace:

```lua
QBCore.Functions.CreateCallback('qb-garages:server:GetGarageVehicles', function(source, cb, garage, type, category)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return end
    local citizenId = Player.PlayerData.citizenid

    local vehicles
    if type == 'depot' then
        vehicles = MySQL.rawExecute.await('SELECT * FROM player_vehicles WHERE citizenid = ? AND depotprice > 0', { citizenId })
    elseif type == 'house' then
        vehicles = MySQL.rawExecute.await('SELECT * FROM player_vehicles WHERE garage = ?', { garage })
    elseif Config.SharedGarages then
        vehicles = MySQL.rawExecute.await('SELECT * FROM player_vehicles WHERE citizenid = ?', { citizenId })
    else
        vehicles = MySQL.rawExecute.await('SELECT * FROM player_vehicles WHERE citizenid = ? AND garage = ?', { citizenId, garage })
    end
    if #vehicles == 0 then
        cb(nil)
        return
    end
    if Config.ClassSystem then
        local filteredVehicles = filterVehiclesByCategory(vehicles, category)
        cb(filteredVehicles)
    else
        cb(vehicles)
    end
end)
```

5. Find the `qb-garages:server:canDeposit` callback and replace:

```lua
QBCore.Functions.CreateCallback('qb-garages:server:canDeposit', function(source, cb, plate, type, garage, state, propertyId)
    local Player = QBCore.Functions.GetPlayer(source)
    local isOwned = MySQL.scalar.await('SELECT citizenid FROM player_vehicles WHERE plate = ? LIMIT 1', { plate })
    if isOwned ~= Player.PlayerData.citizenid and type ~= 'house' then
        cb(false)
        return
    end
    if state == 1 then
        MySQL.update('UPDATE player_vehicles SET state = ?, garage = ? WHERE plate = ?', { state, garage, plate })
        cb(true)
    else
        cb(false)
    end
end)
```


# Tablet


# lb-tablet

1. Navigate to the **lb-tablet/config/config.lua**
2. Find the `Config.HousingScript` option and customize:

```lua
CConfig.HousingScript = "vms_housing"
```

2. Navigate to the **lb-tablet/server/custom/housing**
3. Create a new lua file named `vms_housing.lua`
4. Paste all of the following code into the `vms_housing.lua` file

```lua
if Config.HousingScript ~= "vms_housing" then
    return
end

local resourceName = "vms_housing"

while GetResourceState(resourceName) ~= "started" do
    debugprint("Waiting for housing script to start...")
    Wait(1000)
end

local selectPropertyQuery = [[
    SELECT
        p.id,
        COALESCE(NULLIF(p.owner, ''), NULLIF(p.renter, '')) AS owner,
        CASE
            WHEN NULLIF(p.owner, '') IS NOT NULL THEN p.owner_name
            ELSE p.renter_name
        END AS `name`
    FROM houses p
]]

local searchPropertiesQuery = selectPropertyQuery .. [[
    WHERE
        COALESCE(NULLIF(p.owner, ''), NULLIF(p.renter, '')) IS NOT NULL

        AND (
            CASE
                WHEN NULLIF(p.owner, '') IS NOT NULL THEN p.owner_name
                ELSE p.renter_name
            END LIKE ?
            OR CAST(p.id AS CHAR) LIKE ?
        )

        {WHERE_FILTER}
    LIMIT ?, ?
]]


local function EncodePropertyId(owner, id)
    return "owner:" .. owner .. ",id:" .. id
end

local function DecodePropertyId(id)
    local owner, propertyId = string.match(id, "owner:(.+),id:([^,]+)$")
    return owner, propertyId and tonumber(propertyId)
end

local function FormatPropery(property)
    local propertyData = exports['vms_housing']:GetProperty(property.id)

    property.label = propertyData.name
    property.id = EncodePropertyId(property.owner, property.id)

    property.owner = {
        name = property.name,
        identifier = property.owner
    }

    property.name = nil
    property.propertyid = nil
    property.address = propertyData.address

    if propertyData.type == 'mlo' then
        property.location = {x = propertyData.metadata.menu.x, y = propertyData.metadata.menu.y}
    else
        if propertyData.object_id then
            local object = exports['vms_housing']:GetProperty(propertyData.object_id)
            if object.type == 'building' then
                property.location = {x = object.metadata.enter.x, y = object.metadata.enter.y}
                goto skip
            end
        end
        property.location = {x = propertyData.metadata.enter.x, y = propertyData.metadata.enter.y}
        ::skip::
    end

    return property
end

function SearchProperties(query, page, filter)
    local params = { "%" .. query .. "%", "%" .. query .. "%" }
    local where = ""
    local searchQuery = searchPropertiesQuery

    if filter.tags then
        for i = 1, #filter.tags do
            where = where .. " AND EXISTS (SELECT 1 FROM lbtablet_police_profile_tags ppt WHERE ppt.id COLLATE utf8mb4_unicode_ci = (CONCAT('house:owner:', p.owner, ',id:', p.id) COLLATE utf8mb4_unicode_ci) AND ppt.tag_id = ?)"
            params[#params+1] = filter.tags[i]
        end
    end

    searchQuery = searchQuery:gsub("{WHERE_FILTER}", where)

    params[#params+1] = (page or 0) * 10
    params[#params+1] = 10

    local properties = MySQL.query.await(
        searchQuery,
        params
    )

    for i = 1, #properties do
        properties[i] = FormatPropery(properties[i])
    end

    return properties
end

function GetProperty(identifier)
    local owner, id = DecodePropertyId(identifier)
    
    local property = exports['vms_housing']:GetProperty(id)

    if not property then
        debugprint("Failed to decode property id", id)
        return
    end

    return FormatPropery({
        id = id,
        owner = property.owner or property.renter,
        name = property.owner_name or property.renter_name,
    })
end

function GetPlayerProperties(identifier)
    local propertiesList = exports['vms_housing']:GetPlayerProperties(identifier)
    local properties = {}

    for k, v in pairs(propertiesList) do
        table.insert(properties, {
            id = v.id,
            name = v.name
        })
    end

    return properties
end
```


# FAQ

Below you’ll find answers to the most frequently asked questions related to setup, usage, and integration. We recommend checking this section before opening a support ticket.

***

<details>

<summary>Is the Taget system required?</summary>

No, you can use both Target systems such as ox\_target or qb-target, as well as the 3D Text system and menus such as esx\_context, qb-menu, ox\_lib.

</details>

<details>

<summary>Can I use Tebex to sell custom properties?</summary>

Yes. See the [**Tebex Integration**](/assets/vms_housing/guides/tebex-integration) guide. All you need is the property ID from the database.

</details>

<details>

<summary>Can I disable the furniture system or prevent furniture in specific properties?</summary>

Yes! You can customize the option in the menu for creating and managing properties: **Can Furniture** - **Inside**, **Outside**

</details>

<details>

<summary>Do I have to use the key as an item?</summary>

No - it's entirely optional.

You can disable physical keys in `config.lua` with `Config.UseKeysOnItem` if you prefer a simpler system.

However, we highly **recommend enabling it** to enhance immersion and roleplay potential.\
It adds consequences for losing or having your key stolen, and allows for more complex access scenarios.

</details>

<details>

<summary>Do I have to use the delivery / furniture ordering system?</summary>

Not at all - the furniture delivery system is **fully optional and configurable**.

You can disable it entirely or choose the mode that best suits your server's style.

**Available furnishing modes:**

* `Config.DeliveryType = 1`\
  Players receive furniture instantly after ordering - it appears right away in their `/furniture` menu.
* `Config.DeliveryType = 2`\
  Players must wait for the delivery timer to finish, after which the item is automatically added to `/furniture` (no package required).
* `Config.DeliveryType = 3`\
  Players wait for the delivery, then must **physically pick up a package** at their property to receive the item.
* `Config.RequirePurchaseFurniture = true`\
  If you don't want to use any ordering system at all, you can disable it.\
  Players will then see all furniture directly in the `/furniture` menu, choose an item, pay for it, and place it instantly - with no waiting or delivery required.

</details>

<details>

<summary>Can I create custom Interactable Furniture?</summary>

Absolutely!

You can easily create your own custom interactive furniture.

1. Add your interaction name in the `config.js` file.
2. Then, define what it does inside `config.client.lua`, in the `CL.InteractableFurniture` function - here you decide what happens: open a UI, play an animation, trigger an event, etc.

</details>

<details>

<summary>Is it possible to create an apartment building with MLO apartments?</summary>

Yes, you can create a residential building so that each apartment is owned by a different player.

You must register the building as a Motel, and then create MLOs associated with the motel you have created.

</details>


# Common Errors

Solutions to frequently encountered errors

In this section, you will find answers to commonly encountered issues. Remember that most errors stem from incorrect configuration, lack of required resources, or insufficient modification.

Bear in mind that troubleshooting requires patience and precision. Try to carefully analyze the errors and systematically review possible solutions.

If the issue persists after verifying the configuration and available resources, please contact the support on the VMS Discord.

***

<details>

<summary>My LMB does not work when creating a house.</summary>

This usually means that an external resource is blocking the Left Mouse Button input.

To fix this, open your `config.lua`, find the option `Config.HousingCreatorControls` and try replacing `SELECT` with one of the alternative control indexes for the Left Mouse Button:

```lua
['SELECT'] = {controlIndex = 92},
['SELECT'] = {controlIndex = 106},
['SELECT'] = {controlIndex = 122},
['SELECT'] = {controlIndex = 135},
['SELECT'] = {controlIndex = 142},
['SELECT'] = {controlIndex = 223},
['SELECT'] = {controlIndex = 229},
['SELECT'] = {controlIndex = 237},
['SELECT'] = {controlIndex = 257},
['SELECT'] = {controlIndex = 329},
['SELECT'] = {controlIndex = 346},
```

</details>

<details>

<summary>K4MB1 Shells have a buggy starting position inside.</summary>

You need to remove the **qb-interior** resource, and download Starter Shells from the [**official K4MB1 website**](https://k4mb1maps.com/product/5015840)**.**

</details>

<details>

<summary>Rain/snow inside the shell for a while with qb-weathersync</summary>

Find the equivalents in your current **qb-weathersync/client/client.lua** and replace with the code below.

```lua
RegisterNetEvent('qb-weathersync:client:DisableSync', function()
    disable = true
end)
```

{% code expandable="true" %}

```lua
CreateThread(function()
    while true do
        if not disable then
            if lastWeather ~= CurrentWeather then
                lastWeather = CurrentWeather
                SetWeatherTypeOverTime(CurrentWeather, 15.0)
                Wait(15000)
            end
            Wait(100) -- Wait 0 seconds to prevent crashing.
            if not disable then
                SetArtificialLightsState(blackout)
                SetArtificialLightsStateAffectsVehicles(blackoutVehicle)
                ClearOverrideWeather()
                ClearWeatherTypePersist()
                SetWeatherTypePersist(lastWeather)
                SetWeatherTypeNow(lastWeather)
                SetWeatherTypeNowPersist(lastWeather)
                if lastWeather == 'XMAS' then
                    SetForceVehicleTrails(true)
                    SetForcePedFootstepsTracks(true)
                else
                    SetForceVehicleTrails(false)
                    SetForcePedFootstepsTracks(false)
                end
                if lastWeather == 'RAIN' then
                    SetRainLevel(0.3)
                elseif lastWeather == 'THUNDER' then
                    SetRainLevel(0.5)
                else
                    SetRainLevel(0.0)
                end
            end
        else
            Wait(1000)
        end
    end
end)
```

{% endcode %}

</details>

<details>

<summary>Rain/snow inside the shell for a while with qbx_weathersync</summary>

Find the equivalents in your current **qbx\_weathersync/client/client.lua** and replace with the code below.

```lua
RegisterNetEvent('qb-weathersync:client:DisableSync', function()
    disable = true
end)
```

{% code expandable="true" %}

```lua
CreateThread(function()
    while true do
        if not disable then
            if lastWeather ~= currentWeather then
                lastWeather = currentWeather
                SetWeatherTypeOverTime(currentWeather, 15.0)
                Wait(15000)
            end
            Wait(100) -- Wait 0 seconds to prevent crashing.
            if not disable then
                print(lastWeather)
                SetArtificialLightsState(blackout)
                SetArtificialLightsStateAffectsVehicles(blackoutVehicle)
                ClearOverrideWeather()
                ClearWeatherTypePersist()
                SetWeatherTypePersist(lastWeather)
                SetWeatherTypeNow(lastWeather)
                SetWeatherTypeNowPersist(lastWeather)
                if lastWeather == 'XMAS' then
                    SetForceVehicleTrails(true)
                    SetForcePedFootstepsTracks(true)
                else
                    SetForceVehicleTrails(false)
                    SetForcePedFootstepsTracks(false)
                end
                if lastWeather == 'RAIN' then
                    SetRainLevel(0.3)
                elseif lastWeather == 'THUNDER' then
                    SetRainLevel(0.5)
                else
                    SetRainLevel(0.0)
                end
            end
        else
            Wait(1000)
        end
    end
end)
```

{% endcode %}

</details>

<details>

<summary>No time sync inside the shell with vSync</summary>

By default, [vSync](https://github.com/DevTestingPizza/vSync) does not have the ability to stop and resume time or weather, but in the following file we have prepared such an option.

The marked lines of code have been added:

<pre class="language-lua" data-expandable="true"><code class="lang-lua">CurrentWeather = 'EXTRASUNNY'

<strong>local isWeatherSynced = true
</strong>local lastWeather = CurrentWeather
local baseTime = 0
local timeOffset = 0
local timer = 0
local freezeTime = false
local blackout = false

<strong>RegisterNetEvent('vSync:toggle')
</strong><strong>AddEventHandler('vSync:toggle', function(toggle)
</strong><strong>    isWeatherSynced = toggle
</strong><strong>end)
</strong>
RegisterNetEvent('vSync:updateWeather')
AddEventHandler('vSync:updateWeather', function(NewWeather, newblackout)
    CurrentWeather = NewWeather
    blackout = newblackout
end)

Citizen.CreateThread(function()
    while true do
<strong>        if isWeatherSynced then
</strong>            if lastWeather ~= CurrentWeather then
                lastWeather = CurrentWeather
                SetWeatherTypeOverTime(CurrentWeather, 15.0)
                Citizen.Wait(15000)
            end
            Citizen.Wait(100) -- Wait 0 seconds to prevent crashing.
            SetBlackout(blackout)
            ClearOverrideWeather()
            ClearWeatherTypePersist()
            SetWeatherTypePersist(lastWeather)
            SetWeatherTypeNow(lastWeather)
            SetWeatherTypeNowPersist(lastWeather)
            if lastWeather == 'XMAS' then
                SetForceVehicleTrails(true)
                SetForcePedFootstepsTracks(true)
            else
                SetForceVehicleTrails(false)
                SetForcePedFootstepsTracks(false)
            end
<strong>        end
</strong><strong>        Citizen.Wait(100)
</strong>    end
end)

RegisterNetEvent('vSync:updateTime')
AddEventHandler('vSync:updateTime', function(base, offset, freeze)
    freezeTime = freeze
    timeOffset = offset
    baseTime = base
end)

Citizen.CreateThread(function()
    local hour = 0
    local minute = 0
    while true do
        Citizen.Wait(0)
<strong>        if isWeatherSynced then
</strong>            local newBaseTime = baseTime
            if GetGameTimer() - 500  > timer then
                newBaseTime = newBaseTime + 0.25
                timer = GetGameTimer()
            end
            if freezeTime then
                timeOffset = timeOffset + baseTime - newBaseTime			
            end
            baseTime = newBaseTime
            hour = math.floor(((baseTime+timeOffset)/60)%24)
            minute = math.floor((baseTime+timeOffset)%60)
            NetworkOverrideClockTime(hour, minute, 0)
<strong>        end
</strong>    end
end)

AddEventHandler('playerSpawned', function()
    TriggerServerEvent('vSync:requestSync')
end)

Citizen.CreateThread(function()
    TriggerEvent('chat:addSuggestion', '/weather', 'Change the weather.', {{ name="weatherType", help="Available types: extrasunny, clear, neutral, smog, foggy, overcast, clouds, clearing, rain, thunder, snow, blizzard, snowlight, xmas &#x26; halloween"}})
    TriggerEvent('chat:addSuggestion', '/time', 'Change the time.', {{ name="hours", help="A number between 0 - 23"}, { name="minutes", help="A number between 0 - 59"}})
    TriggerEvent('chat:addSuggestion', '/freezetime', 'Freeze / unfreeze time.')
    TriggerEvent('chat:addSuggestion', '/freezeweather', 'Enable/disable dynamic weather changes.')
    TriggerEvent('chat:addSuggestion', '/morning', 'Set the time to 09:00')
    TriggerEvent('chat:addSuggestion', '/noon', 'Set the time to 12:00')
    TriggerEvent('chat:addSuggestion', '/evening', 'Set the time to 18:00')
    TriggerEvent('chat:addSuggestion', '/night', 'Set the time to 23:00')
    TriggerEvent('chat:addSuggestion', '/blackout', 'Toggle blackout mode.')
end)

-- Display a notification above the minimap.
function ShowNotification(text, blink)
    if blink == nil then blink = false end
    SetNotificationTextEntry("STRING")
    AddTextComponentSubstringPlayerName(text)
    DrawNotification(blink, false)
end

RegisterNetEvent('vSync:notify')
AddEventHandler('vSync:notify', function(message, blink)
    ShowNotification(message, blink)
end)

</code></pre>

</details>


# Guides

On the sub-pages of the Guides section, you will find a variety of guides to help you better understand and use the available features and tools.


# How to add new Shell

***

#### 1. Add the Shell to Your Resource

Make sure the shell model resource is properly streamed by your server.

* Place the files in a folder under `resources/`.
* Ensure it's started in your `server.cfg`.

#### 2. Register the Shell

Go to `vms_housing/integration/[shells]` and create a new file for your shell. You can name it for example `custom_shells.lua`

Inside that file, define your shell using the following structure:

```lua
local shells = {
    ['envi_shell_01_empty'] = { -- Unique Name
        label = 'Apartment 1', -- Name displayed in menus

        tags = {'empty', 'envi'}, -- Useful for filtering and categorization

        rooms = 5, -- Visual info about how many rooms the interior has

        model = 'envi_shell_01_empty', -- The actual YDR model of the shell that needs to be streamed

        doors = { -- Position and heading of the shell's entrance, used for teleporting in/out
            x = 0.62,
            y = 0.6,
            z = 498.86,
            heading = 1.26
        },

        interactable = { -- Define functional items inside the shell (e.g. sinks, appliances, etc.)
            {
                target = vector4(-1.15, 9.77, 499.48, 88.59), -- Position and rotation of the interactable target box
                targetSize = vector3(0.9, 0.8, 2.0), -- Size of the target interaction box
                options = {
                    {
                        timeUsage = 7000, -- Time in milliseconds to perform the action
                        targetIcon = "fa-solid fa-sink", -- FontAwesome icon for UI
                        type = 'sink', -- Used to determine the type of interaction
                        waterUsage = 5000, -- Water usage in milliliters
                        billType = 'water', -- Type of utility to bill
                        coords = {
                            particles = vector3(-1.175646, 9.782219, 499.701), -- Particle spawn position
                            player = vector4(-0.57, 9.84, 498.86, 92.5), -- Where the player stands during the action
                        },
                    },
                    {
                        timeUsage = 5000,
                        targetIcon = "fa-solid fa-glass-water-droplet",
                        type = 'sink_drink',
                        waterUsage = 300,
                        billType = 'water',
                        coords = {
                            particles = vector3(-1.175646, 9.782219, 499.701),
                            player = vector4(-0.57, 9.84, 498.86, 92.5),
                        },
                    },
                }
            },
        },

        images = { -- Screenshots used in the housing menu when browsing interiors
            'shell/envi_shell_01_empty-1.png',
            'shell/envi_shell_01_empty-2.png',
            'shell/envi_shell_01_empty-3.png',
            'shell/envi_shell_01_empty-4.png',
            'shell/envi_shell_01_empty-5.png',
            'shell/envi_shell_01_empty-6.png',
        },
    },
}

addShells(shells) -- Register your shell(s)
```

#### 3. Configure Shell Door Position

To set the proper entrance position for your shell:

1. First, insert your shell into the list with a temporary `doors` coordinate like:

```lua
doors = {
    x = 0.0,
    y = 0.0,
    z = 500.0,
    heading = 0.0
},
```

2. Start your server and open the housing menu using `/housing`
3. Navigate to **Create Property** - **Shell**, find your new one and press the **Preview** button
4. You'll be teleported to the 0.0, 0.0, 500.0 coordinates and the shell will be placed
5. Walk inside the shell to the door you want to use as the entrance
6. Use a tool like `/copycoords` or any coord grabber to get the accurate position + heading (be your back to the door)
7. Paste those coordinates into the `doors` section of your shell config

This ensures the entrance is perfectly aligned for teleporting in/out.

#### 4. Done!

Your shell will now appear in the list when creating properties through the admin menu.


# How to add new Furniture

***

{% hint style="warning" %}
Remember to change **Post FX** to **Normal** in your Grand Theft Auto settings.

Otherwise, photos may be blurred and the subject may receive a green glow.
{% endhint %}

#### 1. Configure screenshot path

Go to `config.lua`, then find the configuration option `Config.FurnitureScreenshotsPath`.\
Adjust this path according to your preference.

{% hint style="info" %}
We recommend leaving it as `'html/images/furniture/'`, as all generated furniture screenshots will then be automatically saved in the folder used by the script to load furniture images.
{% endhint %}

#### 2. Find the Object Model

Go to <https://forge.plebmasters.de/objects> and search for the object you want to add. You can also paste the name of a custom model if you're using your own.

#### 3. Open the Housing Menu

Open the Housing Menu in-game using the `/housing` command (default).

#### 4. Enter the "All Furniture" section

Navigate to the **All Furniture** tab and click the **`+` button** in the top-right corner.

<figure><img src="/files/ZPbzLsfyXFNS4gIiNcdM" alt=""><figcaption></figcaption></figure>

#### 5. Enable Duplicate Check (Optional)

If you want to avoid adding objects that already exist in the list, check the **"Check Existing Furniture"** option.

#### 6. Add the Model(s)

Paste the model names of the objects you want to add. Then click the **SAVE** button.

This will launch the **screenshot mode**. Position the object on screen so that it fills the majority of the frame or is clearly visible.

> *Adjust object position on screen so it's clearly visible before capturing. Smaller objects = smaller file size, but also lower image quality.*

<div><figure><img src="/files/OPQkE08U9kTS0FKU63xE" alt=""><figcaption></figcaption></figure> <figure><img src="/files/yyya0BFxsRbwQQf1A4G0" alt=""><figcaption></figcaption></figure></div>

#### 7. Final Configuration

Open the Housing menu again and go to **All Furniture**. Search for your object using the search bar.

{% hint style="info" %}
To refresh the images, you must restart the resource.
{% endhint %}

{% hint style="warning" %}
Do not restart the resource on a live server with active players. Restarting the resource will reload the housing system and may temporarily interrupt gameplay.
{% endhint %}

> *Finalize your object with labels, pricing, tags and environment settings.*

Set up your object preferences:

* **Label**
* **Price**
* **Tags**
* **Environment Type**
* **Delivery Size**
* **Interactable Metadata**

You're done! The object is now fully available in your furniture list.

<figure><img src="/files/veFOB0AVFlzEue3rNQd7" alt=""><figcaption></figcaption></figure>


# How to use Starter Apartments

***

{% hint style="info" %}
**Info:** Starter Apartments let you automatically give new players a simple property when they first join your server or create a character.
{% endhint %}

#### 1. Prepare the Property

Enter the server, go to the `/housing` admin menu and create a Building. Then:

1. Save the property
2. Go to your database and find the `id` of the new property in the `houses` table
3. Copy that ID and paste it into the `Object` field in the config below

#### 2. Configure Starter Apartments

Go to the `config.lua` file and find this code:

```lua
Config.StarterApartments = {
    Object = 114, -- You need to have a property building created and here is to find the id of this building from the database.
    Name = 'Starting Apartment #%s', -- Name of the starter apartment, which will be displayed in the menu.
    Type = 'shell', -- 'shell' / 'ipl'

    Shell = 'standardmotel_shell',
    Ipl = 'apa_v_mp_h_01_a',

    DefaultPurchasePrice = 0, -- Default purchase price of the starter apartment. (By setting 0, the player will not be able to sell the property to make money)
    DefaultRentPrice = 0, -- Default rent price of the starter apartment.

    AllowFurnitureInside = true, -- true : Allow furniture inside the starter apartment
    AllowChangeTheme = true, -- true : Allow changing the theme of the starter apartment IPL
    DefaultThemeIpl = 'modern',

    Delivery = {
        Enabled = true,
        Coords = vector4(2.4304, -2.1917, 498.4416, 79.99999237060547), -- Coords of the storage in the starter apartment
    },

    Storage = {
        Enabled = true,
        Coords = vector3(1.5388, -3.0803, 499.7162), -- Coords of the storage in the starter apartment
        Slots = 20, -- Slots of the storage in the starter apartment
        Weight = 25000, -- Weight of the storage in the starter apartment
    },

    Wardrobe = {
        Enabled = true,
        Coords = vector3(1.3227, 2.8945, 500.0726), -- Coords of the wardrobe in the starter apartment
    },
}
```

#### 3. Assign Apartment Automatically

From your multicharacter, identity, or framework, call this server-side export when a player registers for the first time:

```lua
---@param {identifier: string}: Player Identifier (e.g. 'char1:1100113jadckz')
exports['vms_housing']:AddStarterApartment(identifier)
```

{% tabs %}
{% tab title="es\_extended" %}

1. Go to your `es_extended/server/main.lua`
2. Find your function `loadESXPlayer`
3. Go to the end of the function and add the highlighted code in the appropriate place:

<pre class="language-lua"><code class="lang-lua">    TriggerEvent("esx:playerLoaded", playerId, xPlayer, isNew)
    userData.money = xPlayer.getMoney()
    userData.maxWeight = xPlayer.getMaxWeight()
    xPlayer.triggerEvent("esx:playerLoaded", userData, isNew, userData.skin)

    if setPlayerInventory then
        setPlayerInventory(playerId, xPlayer, userData.inventory, isNew)
    end

<strong>    if isNew then
</strong><strong>        exports['vms_housing']:AddStarterApartment(identifier)
</strong><strong>    end
</strong>
    xPlayer.triggerEvent("esx:registerSuggestions", Core.RegisteredCommands)
    print(('[^2INFO^0] Player ^5"%s"^0 has connected to the server. ID: ^5%s^7'):format(xPlayer.getName(), playerId))
end
</code></pre>

{% endtab %}

{% tab title="qb-multicharacter" %}

1. Go to your `qb-multicharacter/server/main.lua`
2. Find your function `GiveStarterItems`
3. Add the highlighted code in the appropriate place:

<pre class="language-lua"><code class="lang-lua">local function GiveStarterItems(source)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    for _, v in pairs(QBCore.Shared.StarterItems) do
        local info = {}
        if v.item == 'id_card' then
            info.citizenid = Player.PlayerData.citizenid
            info.firstname = Player.PlayerData.charinfo.firstname
            info.lastname = Player.PlayerData.charinfo.lastname
            info.birthdate = Player.PlayerData.charinfo.birthdate
            info.gender = Player.PlayerData.charinfo.gender
            info.nationality = Player.PlayerData.charinfo.nationality
        elseif v.item == 'driver_license' then
            info.firstname = Player.PlayerData.charinfo.firstname
            info.lastname = Player.PlayerData.charinfo.lastname
            info.birthdate = Player.PlayerData.charinfo.birthdate
            info.type = 'Class C Driver License'
        end
        exports['qb-inventory']:AddItem(src, v.item, v.amount, false, info, 'qb-multicharacter:GiveStarterItems')
    end
<strong>    exports["vms_housing"]:AddStarterApartment(Player.PlayerData.citizenid)
</strong>end
</code></pre>

{% endtab %}
{% endtabs %}

#### 4. Additional Notes

* Set `DefaultPurchasePrice` and `DefaultRentPrice` to `0` to prevent players from selling the apartment.
* This system is perfect for giving new players a default place to live without extra steps.
* You can fully customize the locations for delivery, wardrobe, and storage.

Once configured, your players will receive a starter apartment automatically when joining the server or creating their character.

#### 5. Teleport player to Starter Apartment

{% hint style="info" %}
Teleporting a player to a starter apartment is not mandatory - **it's an optional use**.
{% endhint %}

{% tabs %}
{% tab title="esx\_multicharacter" %}

1. Go to your `esx_multicharacter/client/modules/multicharacter.lua`
2. Find your function `Multicharacter:LoadSkinCreator`
3. Replace function:

```lua
function Multicharacter:LoadSkinCreator(skin)
    TriggerEvent("skinchanger:loadSkin", skin, function()
        exports['vms_housing']:TeleportToStartingApartment()
        Citizen.Wait(2500)
        DoScreenFadeIn(600)
        SetPedAoBlobRendering(self.playerPed, true)
        ResetEntityAlpha(self.playerPed)

        TriggerEvent("esx_skin:openSaveableMenu", function()
            Multicharacter.finishedCreation = true
        end, function()
            Multicharacter.finishedCreation = true
        end)
    end)
end
```

{% endtab %}

{% tab title="qb-multicharacter" %}

1. Go to your `esx_multicharacter/client/modules/multicharacter.lua`
2. Find your event `Multicharacter:LoadSkinCreator`
3. Replace function:

```lua
RegisterNetEvent('qb-multicharacter:client:closeNUIdefault', function()
    DeleteEntity(charPed)
    SetNuiFocus(false, false)
    DoScreenFadeOut(500)
    TriggerServerEvent('QBCore:Server:OnPlayerLoaded')
    TriggerEvent('QBCore:Client:OnPlayerLoaded')
    Wait(1000)
    exports['vms_housing']:TeleportToStartingApartment()
    Wait(2500)
    openCharMenu()
    SetEntityVisible(PlayerPedId(), true)
    Wait(800)
    TriggerEvent('qb-clothes:client:CreateFirstCharacter')
end)
```

{% endtab %}
{% endtabs %}


# Tebex Integration

***

{% hint style="info" %}
**Info:** You can sell properties through Tebex and assign them to players automatically after purchase.
{% endhint %}

#### 1. Tebex Resource

Download and configure the official [tebex resource](https://github.com/tebexio/Tebex-FiveM) to integrate product sales with your FiveM server.

#### 2. Create a Property

Use the `/housing` admin menu to create a new property you want to sell. Once saved:

1. Open your database and copy the `id` of that property from the `houses` table
2. This `id` will be used as the property identifier in the Tebex command

#### 3. Create Package on your Tebex

In your Tebex package, set up a command

```asciidoc
tebexProperty {sid} 152
```

* `{sid}` is automatically replaced by Tebex with the player’s server ID
* `152` is the property ID from your database

<figure><img src="/files/3YSzXOAHdsYLBl9XLBUF" alt=""><figcaption></figcaption></figure>

#### 4. Done!

Once someone purchases your package, Tebex will trigger the command and the player will receive their property.


# Ambulance Integration


# esx\_ambulancejob

{% hint style="info" %}
**Notification to EMS**

The following code detects the current property, and when a player sends a death message inside the property, it transmits the coordinates of the house entrance.
{% endhint %}

<pre class="language-lua" data-title="esx_ambulancejob/server/main.lua"><code class="lang-lua">RegisterNetEvent('esx_ambulancejob:onPlayerDistress')
AddEventHandler('esx_ambulancejob:onPlayerDistress', function()
    local source = source
    local injuredPed = GetPlayerPed(source)
    local injuredCoords = GetEntityCoords(injuredPed)
    
<strong>    local playerProperty = exports['vms_housing']:GetPlayerCurrentProperty(source)
</strong><strong>    if playerProperty then
</strong><strong>        local property = exports['vms_housing']:GetProperty(playerProperty)
</strong><strong>        if property.object_id then
</strong><strong>            local building = exports['vms_housing']:GetProperty(property.object_id)
</strong><strong>            if building.type == 'building' then
</strong><strong>                property = building
</strong><strong>            end
</strong><strong>        end
</strong><strong>        if property.metadata?.exit then
</strong><strong>            injuredCoords = property.metadata.exit
</strong><strong>        end
</strong><strong>    end
</strong>    
    if deadPlayers[source] then
        deadPlayers[source] = 'distress'
        local Ambulance = ESX.GetExtendedPlayers("job", "ambulance")
        for _, xPlayer in pairs(Ambulance) do
            xPlayer.triggerEvent('esx_ambulancejob:PlayerDistressed', source, injuredCoords)
        end
    end
end)
</code></pre>

{% hint style="info" %}
**Kicking a player out of the property after respawning**
{% endhint %}

<pre class="language-lua" data-title="esx_ambulancejob/server/main.lua"><code class="lang-lua">ESX.RegisterServerCallback('esx_ambulancejob:removeItemsAfterRPDeath', function(source, cb)
    local xPlayer = ESX.GetPlayerFromId(source)
    
<strong>    exports['vms_housing']:DropPlayerFromProperty(source)
</strong>
    -- Rest of the code
end)
</code></pre>


# qb-ambulancejob

{% hint style="info" %}
**Notification to EMS**

The following code detects the current property, and when a player sends a death message inside the property, it transmits the coordinates of the house entrance.
{% endhint %}

<pre class="language-lua" data-title="qb-ambulancejob/server/main.lua"><code class="lang-lua">RegisterNetEvent('hospital:server:ambulanceAlert', function(text)
    local src = source
    local ped = GetPlayerPed(src)
    local coords = GetEntityCoords(ped)
    local players = QBCore.Functions.GetQBPlayers()
    
<strong>    local playerProperty = exports['vms_housing']:GetPlayerCurrentProperty(source)
</strong><strong>    if playerProperty then
</strong><strong>        local property = exports['vms_housing']:GetProperty(playerProperty)
</strong><strong>        if property.object_id then
</strong><strong>            local building = exports['vms_housing']:GetProperty(property.object_id)
</strong><strong>            if building.type == 'building' then
</strong><strong>                property = building
</strong><strong>            end
</strong><strong>        end
</strong><strong>        if property.metadata?.exit then
</strong><strong>            coords = property.metadata.exit
</strong><strong>        end
</strong><strong>    end
</strong>    
    for _, v in pairs(players) do
        if v.PlayerData.job.name == 'ambulance' and v.PlayerData.job.onduty then
            TriggerClientEvent('hospital:client:ambulanceAlert', v.PlayerData.source, coords, text)
        end
    end
end)
</code></pre>

{% hint style="info" %}
**Kicking a player out of the property after respawning**
{% endhint %}

<pre class="language-lua" data-title="qb-ambulancejob/server/main.lua"><code class="lang-lua">RegisterNetEvent('hospital:server:RespawnAtHospital', function(hospitalIndex)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)

<strong>    exports['vms_housing']:DropPlayerFromProperty(src)
</strong>
    -- Rest of the code
end)
</code></pre>


# qbx\_ambulancejob

{% hint style="info" %}
**Notification to EMS**

The following code detects the current property, and when a player sends a death message inside the property, it transmits the coordinates of the house entrance.
{% endhint %}

<pre class="language-lua" data-title="qbx_ambulancejob/server/main.lua"><code class="lang-lua">local function alertAmbulance(src, text)
    local ped = GetPlayerPed(src)
    local coords = GetEntityCoords(ped)
    local players = exports.qbx_core:GetQBPlayers()
    
<strong>    local playerProperty = exports['vms_housing']:GetPlayerCurrentProperty(source)
</strong><strong>    if playerProperty then
</strong><strong>        local property = exports['vms_housing']:GetProperty(playerProperty)
</strong><strong>        if property.object_id then
</strong><strong>            local building = exports['vms_housing']:GetProperty(property.object_id)
</strong><strong>            if building.type == 'building' then
</strong><strong>                property = building
</strong><strong>            end
</strong><strong>        end
</strong><strong>        if property.metadata?.exit then
</strong><strong>            coords = property.metadata.exit
</strong><strong>        end
</strong><strong>    end
</strong>    
    for _, v in pairs(players) do
        if v.PlayerData.job.type == 'ems' and v.PlayerData.job.onduty then
            TriggerClientEvent('hospital:client:ambulanceAlert', v.PlayerData.source, coords, text)
        end
    end
end
</code></pre>

{% hint style="info" %}
**Kicking a player out of the property after respawning**
{% endhint %}

<pre class="language-lua" data-title="qbx_ambulancejob/server/hospital.lua"><code class="lang-lua">local function respawn(src)
    local player = exports.qbx_core:GetPlayer(src)

<strong>    exports['vms_housing']:DropPlayerFromProperty(src)
</strong>
    -- Rest of the code
end
</code></pre>


# Developer API


# Client Exports

### GetProperty

Returns full data of the specified property.

```lua
---@return {propertyId: string | nil}
---@return {propertyData: table}
local propertyData = exports['vms_housing']:GetProperty(propertyId)
```

***

### GetPlayerProperties

Returns a list of all properties owned by the current player.

```lua
---@return {properties: table}: Data list of all properties
local properties = exports['vms_housing']:GetPlayerProperties()
```

<details>

<summary><strong>Example</strong></summary>

```lua
local properties = exports['vms_housing']:GetPlayerProperties()

print(json.encode(properties, {indent=true}))
--[[
    {
        "id": 138,
        "name": "Paleto House",
        "description": "",
        "type": "shell",
        "owner_name": "Tony Blunt"
        "owner": "char3:11000014c8ce27b",
        "region": "Paleto Bay",
        "address": "Procopio Dr",
        "last_enter": 1751993299,
        "unpaidBills": 0,
        "bills": [
            {
                "details": "{\"electricityUsage\":0,\"rateInfo\":{\"electricity\":0.0115,\"internet\":80.0,\"water\":0.5},\"internet\":80,\"electricity\":0,\"waterUsage\":0,\"water\":0.0}",
                "type": "services",
                "paid": false,
                "period": "07:2025",
                "id": 334,
                "house_id": 138,
                "total": 80
            }
        ],
        "permissions": [],
        "keys": "[\"138-9008CEA257\",\"138-9361DPW426\"]",
        "sale": {
            "active": false,
            "defaultActive": true,
            "defaultPrice": 1500000,
            "price": 1500000
        },
        "rental": {
            "active": false,
            "defaultActive": false,
            "defaultPrice": 0,
            "price": 0
        },
        "metadata": {
            "exit": {
                "w": 47.32084655761719,
                "x": -213.5472,
                "y": 6396.1655,
                "z": 32.1852
            },
            "lightState": false,
            "enter": {
                "x": -213.4049,
                "y": 6395.9585,
                "z": 33.7235
            },
            "allowFurnitureInside": true,
            "deliveryType": "outside",
            "lastCadastralPeriod": "07:2025",
            "zone": {
                "maxZ": 41.33707237243652,
                "area": 1148,
                "minZ": 25.33707237243652,
                "points": [
                    {
                        "y": 6362.8569,
                        "x": -192.8159
                    },
                    {
                        "y": 6378.2285,
                        "x": -177.2231
                    },
                    {
                        "y": 6388.0571,
                        "x": -186.8981
                    },
                    {
                        "y": 6389.7969,
                        "x": -185.0591
                    },
                    {
                        "y": 6408.2832,
                        "x": -204.0579
                    },
                    {
                        "y": 6415.0508,
                        "x": -207.3751
                    },
                    {
                        "y": 6415.5547,
                        "x": -207.3833
                    },
                    {
                        "y": 6398.7544,
                        "x": -227.5731
                    }
                ]
            },
            "upgrades": {
                "furnitureLimit": "1"
            },
            "locked": false,
            "allowFurnitureOutside": true,
            "garage": {
                "w": 46.99999618530273,
                "x": -197.7123,
                "y": 6397.5713,
                "z": 30.8626
            },
            "delivery": {
                "w": 130.0,
                "x": -216.7046,
                "y": 6394.7998,
                "z": 32.0852
            },
            "shell": "envi_shell_02_empty"
        },
        "creator": "char3:11000014c8ce27b",
        "furniture": [
            {
                "position": {
                    "pitch": 0.0,
                    "y": -3.47876214981079,
                    "z": 502.9772644042969,
                    "yaw": 0.0,
                    "x": -2.18221616744995,
                    "roll": -0.0,
                    "environment": "inside"
                },
                "stored": 0,
                "model": "prop_wall_light_06a",
                "id": 640,
                "metadata": [],
                "house_id": 138
            },
        ],
    },
]]
```

</details>

***

### IsPlayerOnPropertyZone

Returns `true` if the player is standing in the property's yard/zone (outside).

```lua
---@return {inAnyZone: boolean}
---@return {propertyId: string}
local inAnyZone, propertyId = exports['vms_housing']:IsPlayerOnPropertyZone()
```

***

### IsPlayerInsideProperty

Returns `true` if the player is currently inside any property interior or inside MLO.

```lua
---@return {propertyId: string | nil}
---@return {propertyData : table | nil}
local propertyId, propertyData = exports['vms_housing']:IsPlayerInsideProperty()
```

***

### GetCurrentRegion

Returns the current region by coords.

```lua
---@param {coords: vector3}
---@return {region: string | nil}
local region = exports['vms_housing']:GetCurrentRegion(coords)
```

***

### HasKeys

Returns `true` if the player has keys to the specified property.

```lua
---@param {propertyId: number | string}
---@return {hasKeys: boolean}
local hasKeys = exports['vms_housing']:HasKeys(propertyId)
```

***

### HasPermissions

Returns `true` if the player has the given permission for a property.

```lua
---@param {propertyId: number | string}
---@param {permission: string}
---@return {hasPermission: boolean}
local hasPermission = exports['vms_housing']:HasPermissions(propertyId, permission)
```

<details>

<summary>Explanation</summary>

List of available permissions:

* garage
* furniture
* billPayments
* keysManage
* upgradesManage
* marketplaceManage
* sell
* automaticSell
* rent
* rentersManage

</details>

***

### HasAnyPermission

Returns `true` if the player has **any** permissions set for the property.

```lua
---@param {propertyId: number | string}
---@return {hasAnyPermission: boolean}
local hasAnyPermission = exports['vms_housing']:HasAnyPermission(propertyId)
```

***

### IsHaveAnyApartment

Returns `true` if the player owns any apartment in the specific building.

```lua
---@param {buildingId: number | string}
---@return {isHave: boolean}
local isHave = exports['vms_housing']:IsHaveAnyApartment(buildingId)
```

***

### TeleportToStartingApartment

Teleport the player directly to the property.

{% hint style="info" %}
It works only once, after the Starting Apartment has previously been granted.\
[**Example Integration**](/assets/vms_housing/guides/how-to-use-starter-apartments)
{% endhint %}

```lua
exports['vms_housing']:TeleportToStartingApartment()
```

***

### CreateDirtUnderPlayer

Creating dirt for the house under player.

You can use this in your eating / drinking system when a player eats, they may leave a stain or crumbs.

```lua
---@param {model: string}: Dirt object model from VMS Housing files
exports['vms_housing']:CreateDirtAtCoords(model)
```

<details>

<summary>Dirt Models</summary>

<kbd>vms\_blood1</kbd>\ <kbd>vms\_blood1small</kbd>\ <kbd>vms\_blood2</kbd>\ <kbd>vms\_blood2small</kbd>\ <kbd>vms\_dirtfootsteps</kbd>\ <kbd>vms\_coffeestain1</kbd>\ <kbd>vms\_coffeestain2</kbd>\ <kbd>vms\_crumbs1</kbd>\ <kbd>vms\_crumbs2</kbd>\ <kbd>vms\_dirtmud1</kbd>\ <kbd>vms\_dirtmud2</kbd>\ <kbd>vms\_dirtmud3</kbd>\ <kbd>vms\_dirtmud4</kbd>\ <kbd>vms\_ketchupspill</kbd>\ <kbd>vms\_liquidspill1</kbd>\ <kbd>vms\_liquidspill2</kbd>\ <kbd>vms\_liquidspill3</kbd>\ <kbd>vms\_liquidspill4</kbd>\ <kbd>vms\_mustardspill</kbd>\ <kbd>vms\_oilspill1</kbd>\ <kbd>vms\_oilspill2</kbd>\ <kbd>vms\_sodaspill1</kbd>\ <kbd>vms\_winestain1</kbd>

</details>

***

### OpenMarketplace

Opens the Marketplace UI to buy or rent properties.

```lua
exports['vms_housing']:OpenMarketplace()
```

***

### OpenManageMenu

Opens the management menu for a property (if has any permissions).

```lua
exports['vms_housing']:OpenManageMenu()
```

***

### OpenFurnitureMenu

Opens the Furniture Menu for a given property (if allowed).

```lua
exports['vms_housing']:OpenFurnitureMenu()
```

***

### OpenRealEstate

Opens the Real Estate Agent Menu.

```lua
exports['vms_housing']:OpenRealEstate()
```


# Server Exports

### GetProperty

Returns full data of a property by its ID.

```lua
---@param {propertyId: number | string}: Property ID
---@return {propertyData: table | nil}
local propertyData = exports['vms_housing']:GetProperty(propertyId)
```

***

### GetAllProperties

Returns full list of a properties.

```lua
local properties = exports['vms_housing']:GetAllProperties()
```

***

### GetPlayerProperties

Returns a list of all properties owned / rented by the specified player.

```lua
---@param {identifier: number | string}: Player Server ID or Player Identifier
---@return {properties: table}: Data list of all properties
local properties = exports['vms_housing']:GetPlayerProperties(identifier)
```

<details>

<summary><strong>Example</strong></summary>

```lua
local properties = exports['vms_housing']:GetPlayerProperties(1)

print(json.encode(properties, {indent=true}))
--[[
    {
        "id": 138,
        "name": "Paleto House",
        "description": "",
        "type": "shell",
        "owner_name": "Tony Blunt"
        "owner": "char3:11000014c8ce27b",
        "region": "Paleto Bay",
        "address": "Procopio Dr",
        "last_enter": 1751993299,
        "unpaidBills": 0,
        "bills": [
            {
                "details": "{\"electricityUsage\":0,\"rateInfo\":{\"electricity\":0.0115,\"internet\":80.0,\"water\":0.5},\"internet\":80,\"electricity\":0,\"waterUsage\":0,\"water\":0.0}",
                "type": "services",
                "paid": false,
                "period": "07:2025",
                "id": 334,
                "house_id": 138,
                "total": 80
            }
        ],
        "permissions": [],
        "keys": "[\"138-9008CEA257\",\"138-9361DPW426\"]",
        "sale": {
            "active": false,
            "defaultActive": true,
            "defaultPrice": 1500000,
            "price": 1500000
        },
        "rental": {
            "active": false,
            "defaultActive": false,
            "defaultPrice": 0,
            "price": 0
        },
        "metadata": {
            "exit": {
                "w": 47.32084655761719,
                "x": -213.5472,
                "y": 6396.1655,
                "z": 32.1852
            },
            "lightState": false,
            "enter": {
                "x": -213.4049,
                "y": 6395.9585,
                "z": 33.7235
            },
            "allowFurnitureInside": true,
            "deliveryType": "outside",
            "lastCadastralPeriod": "07:2025",
            "zone": {
                "maxZ": 41.33707237243652,
                "area": 1148,
                "minZ": 25.33707237243652,
                "points": [
                    {
                        "y": 6362.8569,
                        "x": -192.8159
                    },
                    {
                        "y": 6378.2285,
                        "x": -177.2231
                    },
                    {
                        "y": 6388.0571,
                        "x": -186.8981
                    },
                    {
                        "y": 6389.7969,
                        "x": -185.0591
                    },
                    {
                        "y": 6408.2832,
                        "x": -204.0579
                    },
                    {
                        "y": 6415.0508,
                        "x": -207.3751
                    },
                    {
                        "y": 6415.5547,
                        "x": -207.3833
                    },
                    {
                        "y": 6398.7544,
                        "x": -227.5731
                    }
                ]
            },
            "upgrades": {
                "furnitureLimit": "1"
            },
            "locked": false,
            "allowFurnitureOutside": true,
            "garage": {
                "w": 46.99999618530273,
                "x": -197.7123,
                "y": 6397.5713,
                "z": 30.8626
            },
            "delivery": {
                "w": 130.0,
                "x": -216.7046,
                "y": 6394.7998,
                "z": 32.0852
            },
            "shell": "envi_shell_02_empty"
        },
        "creator": "char3:11000014c8ce27b",
        "furniture": [
            {
                "position": {
                    "pitch": 0.0,
                    "y": -3.47876214981079,
                    "z": 502.9772644042969,
                    "yaw": 0.0,
                    "x": -2.18221616744995,
                    "roll": -0.0,
                    "environment": "inside"
                },
                "stored": 0,
                "model": "prop_wall_light_06a",
                "id": 640,
                "metadata": [],
                "house_id": 138
            },
        ],
    },
]]
```

</details>

***

### GetPlayerCurrentProperty

Returns the ID of the property the player is currently inside (or `nil`).

```lua
---@param {src: number}: Player Server ID
---@return {propertyId: string}
local propertyId = exports['vms_housing']:GetPlayerCurrentProperty(src)
```

***

### IsPlayerInProperty

Returns `true` if the player is inside any property.

```lua
---@param {src: number}: Player Server ID
---@param {propertyId: number | string}
---@return {isInProperty: boolean}
local isInProperty = exports['vms_housing']:IsPlayerInProperty(src, propertyId)
```

***

### HasKeys

Returns `true` if the player has keys to the specified property.

```lua
---@param {src: number}: Player Server ID
---@param {identifier: string}: Player Identifier (e.g. 'char1:1100113jadckz')
---@param {propertyId: number | string}
---@param {keyId: string} (Optional)
---@return {hasKeys: boolean}
local hasKeys = exports['vms_housing']:HasKeys(src, identifier, propertyId, keyId)
```

***

### GenerateKeySerialNumber

Generates a unique serial number for a key for the given property.

```lua
---@param {propertyId: number | string}
---@return {serialNumber: boolean}
local serialNumber = exports['vms_housing']:GenerateKeySerialNumber(propertyId)
```

***

### HasPermissions

Returns `true` if the player has the specified permission for a property.

```lua
---@param {propertyId: number | string}
---@param {identifier: string}: Player Identifier (e.g. 'char1:1100113jadckz')
---@param {permission: string}
---@return {hasPermissions: boolean}
local hasPermissions = exports['vms_housing']:HasPermissions(propertyId, identifier, permission)
```

<details>

<summary>Explanation</summary>

List of available permissions:

* garage
* furniture
* billPayments
* keysManage
* upgradesManage
* marketplaceManage
* sell
* automaticSell
* rent
* rentersManage

</details>

***

### HasAnyPermission

Returns `true` if the player has any kind of permission for that property.

```lua
---@param {propertyId: number | string}
---@param {identifier: string}: Player Identifier (e.g. 'char1:1100113jadckz')
---@return {hasAnyPermissions: boolean}
local hasAnyPermissions = exports['vms_housing']:HasAnyPermission(propertyId, identifier)
```

***

### EnterProperty

Enter the property (used for admin tools or dev cleanup).

```lua
---@param {src: number}: Player Server ID
---@param {propertyId: number | string}
exports['vms_housing']:EnterProperty(src, propertyId)
```

***

### GiveProperty

Give the property to a player or company

```lua
---@param {identifier: number | string}: Player Server ID / Identifier or CitizenID / Agency Name
---@param {propertyId: number | string}: Property ID
exports['vms_housing']:GiveProperty(identifier, propertyId)
```

***

### DeleteProperty

Deletes the property (used for admin tools or dev cleanup).

```lua
---@param {propertyId: number | string}: Property ID
exports['vms_housing']:DeleteProperty(propertyId)
```

***

### CreateDirtAtCoords

Creating dirt for the house.

You can use this in your eating / drinking system when a player eats, they may leave a stain or crumbs.

```lua
---@param {propertyId: number | string}: Property ID
---@param {model: string}: Dirt object model from VMS Housing files
---@param {position: vector3}
---@param {heading: number}
exports['vms_housing']:CreateDirtAtCoords(propertyId, model, position, heading)
```

<details>

<summary>Dirt Models</summary>

<kbd>vms\_blood1</kbd>\ <kbd>vms\_blood1small</kbd>\ <kbd>vms\_blood2</kbd>\ <kbd>vms\_blood2small</kbd>\ <kbd>vms\_dirtfootsteps</kbd>\ <kbd>vms\_coffeestain1</kbd>\ <kbd>vms\_coffeestain2</kbd>\ <kbd>vms\_crumbs1</kbd>\ <kbd>vms\_crumbs2</kbd>\ <kbd>vms\_dirtmud1</kbd>\ <kbd>vms\_dirtmud2</kbd>\ <kbd>vms\_dirtmud3</kbd>\ <kbd>vms\_dirtmud4</kbd>\ <kbd>vms\_ketchupspill</kbd>\ <kbd>vms\_liquidspill1</kbd>\ <kbd>vms\_liquidspill2</kbd>\ <kbd>vms\_liquidspill3</kbd>\ <kbd>vms\_liquidspill4</kbd>\ <kbd>vms\_mustardspill</kbd>\ <kbd>vms\_oilspill1</kbd>\ <kbd>vms\_oilspill2</kbd>\ <kbd>vms\_sodaspill1</kbd>\ <kbd>vms\_winestain1</kbd>

</details>

<details>

<summary><strong>Example</strong></summary>

```lua
local myPed = GetPlayerPed(src)
local myCoords = GetEntityCoords(myPed)
local heading = GetEntityHeading(myPed)

local propertyId = exports['vms_housing']:GetPlayerCurrentProperty(src)
if not propertyId then
    return
end

local model = 'vms_crumbs1'
local position = vector3(myCoords.x, myCoords.y, myCoords.z - 0.99) -- Setting foot coordinates

exports['vms_housing']:CreateDirtAtCoords(propertyId, model, position, heading)
```

</details>

***

### UpdatePropertyState

Update the property data state

```lua
---@param {propertyId: number | string}: Property ID
---@param {state: string}
---@param {value: any}
exports['vms_housing']:UpdatePropertyState(propertyId, state, value)
```

<details>

<summary>States</summary>

#### Light State:

state: `light`\
value: `true` or `false`

</details>

<details>

<summary><strong>Example</strong></summary>

```lua
exports['vms_housing']:UpdatePropertyState(123, 'light', true) -- Enable light
exports['vms_housing']:UpdatePropertyState(123, 'light', false) -- Disable light
```

</details>

***

### DropPlayerFromProperty

Kick a player out of the property immediately without screen fade-in/fade-out

```lua
---@param {src: number}: Player Server ID
exports['vms_housing']:DropPlayerFromProperty(src)
```

***

### WipeProperty

Resets or partially clears a property depending on ownership state.

```lua
---@param {propertyId: number | string}: Property ID
---@param {removeOwnership: boolean}: If true, owner/renter may be removed depending on logic
---@param {identifier: string | nil}: Identifier / CitizenID / Agency Name
exports['vms_housing']:WipeProperty(propertyId, removeOwnership, identifier)
```

<details>

<summary>Explanation</summary>

This function may perform either a **partial reset** or a **full reset** depending on the ownership relationship.

* If `identifier` is **nil**, full reset is allowed (admin/system usage).
* If `identifier` belongs to the **owner** and there is no renter = full reset.
* If `identifier` belongs to the **owner** and renter exists = only owner is removed.
* If `identifier` belongs to the **renter** and no owner exists = full reset.
* If `identifier` belongs to the **renter** and owner exists = only renter is removed.

**Full Reset**

A full reset restores the property to its **default configuration defined in Housing Creator before being purchased by a player**.

Full reset includes:

* All furniture removed (database + memory)
* All property bills removed
* Unpaid bills counters cleared
* Keys cleared
* Permissions cleared
* Grass state cleared
* Metadata reset (locked state, lights, upgrades, dirt, alarm phone number, doors locked)
* Last enter timestamp removed
* Sale price restored to default value
* Rental price restored to default value
* Sale/rental active state restored to default (property returns to market if configured so)

</details>

***

### ResetPlayerProperties

Resets all properties associated with a given identifier.

{% hint style="info" %}
This function is primarily intended for character lifecycle management, such as Character Kill (CK), permanent character deletion, or player bans with asset removal.

It ensures that all properties linked to the character are safely restored to their default Housing Creator configuration.
{% endhint %}

```lua
---@param {identifier: string}: Identifier / CitizenID / Agency Name
exports['vms_housing']:ResetPlayerProperties(identifier)
```

<details>

<summary>Explanation</summary>

* Finds all properties owned or rented by the provided identifier.
* Internally calls `WipeProperty` for each property.
* Ownership logic is handled automatically.

</details>

***

### AddStarterApartment

Gives a player a predefined starter apartment (e.g. at character creation).

```lua
---@param {identifier: string}: Player Identifier (e.g. 'char1:1100113jadckz')
exports['vms_housing']:AddStarterApartment(identifier)
```

<details>

<summary>Explanation</summary>

Read the [How to use Starter Apartments](/assets/vms_housing/guides/how-to-use-starter-apartments) guide if you want to know how to properly implement this feature on your server.

</details>

***

### TeleportToStartingApartment

Teleport the player directly to the property.

{% hint style="info" %}
It works only once, after the Starting Apartment has previously been granted.\
[**Example Integration**](/assets/vms_housing/guides/how-to-use-starter-apartments)
{% endhint %}

```lua
---@param {src: number}: Player Server ID
exports['vms_housing']:TeleportToStartingApartment(src)
```

***

### RegisterHook

```lua
--- ### Available Hook Names:
--- OnPropertyCreate - Fired when a new property is created.
--- OnPropertyEdit - Fired when a property is edited.
--- OnPropertyDelete - Fired when a property is deleted.
--- OnPermissionUpdate - Fired when property permissions are updated.
--- OnOwnerChange - Fired when the owner of the property changes.
--- OnRenterChange - Fired when the renter of the property changes.

---@param name string The hook name (see list above)
---@param cb fun(data: table) Callback function triggered when the hook fires
exports['vms_housing']:RegisterHook(name, function(data)
    print(json.encode(data, {indent = true}))
end)
```


# Client Events

### Listener for Freecam

Triggered when freecam mode is activated.\
Use this event to temporarily disable or adjust anti-cheat checks (e.g., freecam detection) to avoid false bans.

```lua
AddEventHandler('vms_housing:cl:enteringFreecam', function()
    -- Your bypass to anticheat
end)
```

Triggered when exiting freecam mode.\
Use this event to re-enable normal anti-cheat checking.

```lua
AddEventHandler('vms_housing:cl:exitingFreecam', function()
    -- Restore anti-freecam checking
end)
```

***

### Listener for Teleport

Triggered when player is teleported and has been teleported.\
Use this event to temporarily disable or adjust anti-cheat checks (e.g., anti-teleport detection) to avoid false bans.

```lua
AddEventHandler('vms_housing:cl:teleport', function(status)
    if status == 'start' then
        -- Your bypass to anticheat
    elseif status == 'end' then
        -- Restore anti-freecam checking
    end
end)
```

***

### Listener for Property Zone

Called when a player enters a property zone.

```lua
AddEventHandler('vms_housing:cl:enteredPropertyZone', function(propertyId, propertyData)
    -- Your custom logic here
end)
```

Called when a player leaves the property zone.

```lua
AddEventHandler('vms_housing:cl:leftPropertyZone', function(propertyId)
    -- Your custom logic here
end)
```

***

### Listener for Property

Called when the owner of the property is changed (purchase, sale, transfer, agency).

```lua
AddEventHandler('vms_housing:cl:updatedOwner', function(propertyId)
    -- Your custom logic here
end)
```

Called when the tenant of the property changes (rental, termination of rental).

```lua
AddEventHandler('vms_housing:cl:updatedRenter', function(propertyId)
    -- Your custom logic here
end)
```

Called when a player's permissions for a property change.

```lua
AddEventHandler('vms_housing:cl:updatedPermissions', function(propertyId)
    -- Your custom logic here
end)
```


# Server Events

### Generating dirt

Creating dirt for the house.

You can use this in your eating / drinking system when a player eats, they may leave a stain or crumbs.

```lua
---@param {propertyId: number | string}
---@param {model: string}: Dirt object model from VMS Housing files
---@param {position: vector3}
---@param {heading: number}
TriggerServerEvent('vms_housing:sv:spawnDirt', propertyId, model, position, heading)
```

<details>

<summary>Dirt Models</summary>

<kbd>vms\_blood1</kbd>\ <kbd>vms\_blood1small</kbd>\ <kbd>vms\_blood2</kbd>\ <kbd>vms\_blood2small</kbd>\ <kbd>vms\_dirtfootsteps</kbd>\ <kbd>vms\_coffeestain1</kbd>\ <kbd>vms\_coffeestain2</kbd>\ <kbd>vms\_crumbs1</kbd>\ <kbd>vms\_crumbs2</kbd>\ <kbd>vms\_dirtmud1</kbd>\ <kbd>vms\_dirtmud2</kbd>\ <kbd>vms\_dirtmud3</kbd>\ <kbd>vms\_dirtmud4</kbd>\ <kbd>vms\_ketchupspill</kbd>\ <kbd>vms\_liquidspill1</kbd>\ <kbd>vms\_liquidspill2</kbd>\ <kbd>vms\_liquidspill3</kbd>\ <kbd>vms\_liquidspill4</kbd>\ <kbd>vms\_mustardspill</kbd>\ <kbd>vms\_oilspill1</kbd>\ <kbd>vms\_oilspill2</kbd>\ <kbd>vms\_sodaspill1</kbd>\ <kbd>vms\_winestain1</kbd>

</details>

<details>

<summary><strong>Example</strong></summary>

```lua
local myPed = PlayerPedId()
local myCoords = GetEntityCoords(myPed)
local heading = GetEntityHeading(myPed)

local propertyId, propertyData = exports['vms_housing']:IsPlayerInsideProperty()
if not propertyId then
    return
end

local model = 'vms_crumbs1'
local position = vector3(myCoords.x, myCoords.y, myCoords.z - 0.99) -- Setting foot coordinates

TriggerServerEvent('vms_housing:sv:spawnDirt', propertyId, model, position, heading)
```

</details>

***


# Commands

<table data-full-width="false"><thead><tr><th width="149.54541015625">Command</th><th width="301.09088134765625">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><strong>housing</strong></td><td>Command for admins</td><td>Launches menu for creating, modifying properties and furniture</td></tr><tr><td><strong>giveproperty</strong></td><td>Command for admins</td><td>Allows you to add a property to a player or real estate agency</td></tr><tr><td><strong>realestate</strong></td><td>Command for Real Estate Agencies</td><td>Launches real estate agent menu</td></tr><tr><td><strong>housemanage</strong></td><td>Owner / Renter / Permissions</td><td>Launches the property management menu</td></tr><tr><td><strong>furniture</strong></td><td>Owner / Renter / Permissions</td><td>Launches furniture menu</td></tr><tr><td><strong>migratehouses</strong></td><td>Only Server-Side</td><td>Migration of property from your previous housing system</td></tr><tr><td><strong>TebexProperty</strong></td><td>Only Server-Side</td><td>Property assign via tebex.io</td></tr><tr><td><strong>getRegion</strong></td><td>Only with <code>Config.DebugRegionsZone</code></td><td></td></tr></tbody></table>


# Configuration Files


# config.lua

Preview File Updated: v1.4.4 - 06/06/2026

{% code fullWidth="true" %}

```lua
Config = Config or {}

-- ███████╗██████╗  █████╗ ███╗   ███╗███████╗██╗    ██╗ ██████╗ ██████╗ ██╗  ██╗
-- ██╔════╝██╔══██╗██╔══██╗████╗ ████║██╔════╝██║    ██║██╔═══██╗██╔══██╗██║ ██╔╝
-- █████╗  ██████╔╝███████║██╔████╔██║█████╗  ██║ █╗ ██║██║   ██║██████╔╝█████╔╝ 
-- ██╔══╝  ██╔══██╗██╔══██║██║╚██╔╝██║██╔══╝  ██║███╗██║██║   ██║██╔══██╗██╔═██╗ 
-- ██║     ██║  ██║██║  ██║██║ ╚═╝ ██║███████╗╚███╔███╔╝╚██████╔╝██║  ██║██║  ██╗
-- ╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝ ╚══╝╚══╝  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
local DetectActiveFramework = function()
    if GetResourceState('es_extended') == 'started' then
        return "ESX"
    elseif GetResourceState('qb-core') == 'started' then
        return "QB-Core"
    end
end

Config.Core = DetectActiveFramework()
Config.CoreExport = function()
    if Config.Core == "ESX" then
        return exports['es_extended']:getSharedObject()
    elseif Config.Core == "QB-Core" then
        return exports['qb-core']:GetCoreObject()
    end
end

---@field PlayerLoaded string: ESX: "esx:playerLoaded" / QB-Core: "QBCore:Client:OnPlayerLoaded"
Config.PlayerLoaded = Config.Core == "ESX" and "esx:playerLoaded" or "QBCore:Client:OnPlayerLoaded"

---@field PlayerLogoutClient string: ESX: "esx:onPlayerLogout" / QB-Core: "QBCore:Client:OnPlayerUnload"
Config.PlayerLogoutClient = Config.Core == "ESX" and "esx:onPlayerLogout" or "QBCore:Client:OnPlayerUnload"

---@field PlayerLogoutServer string: ESX: "esx:playerDropped" / QB-Core: "QBCore:Server:OnPlayerUnload"
Config.PlayerLogoutServer = Config.Core == "ESX" and "esx:playerDropped" or "QBCore:Server:OnPlayerUnload"

---@field PlayerSetJob string: ESX: "esx:setJob" / QB-Core: "QBCore:Client:OnJobUpdate"
Config.PlayerSetJob = Config.Core == "ESX" and "esx:setJob" or "QBCore:Client:OnJobUpdate"



-- ███╗   ███╗ █████╗ ██╗███╗   ██╗    ███████╗███████╗████████╗████████╗██╗███╗   ██╗ ██████╗ ███████╗
-- ████╗ ████║██╔══██╗██║████╗  ██║    ██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗  ██║██╔════╝ ██╔════╝
-- ██╔████╔██║███████║██║██╔██╗ ██║    ███████╗█████╗     ██║      ██║   ██║██╔██╗ ██║██║  ███╗███████╗
-- ██║╚██╔╝██║██╔══██║██║██║╚██╗██║    ╚════██║██╔══╝     ██║      ██║   ██║██║╚██╗██║██║   ██║╚════██║
-- ██║ ╚═╝ ██║██║  ██║██║██║ ╚████║    ███████║███████╗   ██║      ██║   ██║██║ ╚████║╚██████╔╝███████║
-- ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝╚═╝  ╚═══╝    ╚══════╝╚══════╝   ╚═╝      ╚═╝   ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝
---@class AutoExecuteQuery<boolean>
-- Automatically creates required SQL tables on resource start (recommended for first install).
Config.AutoExecuteQuery = true

---@class Logs<string>
-- Log method: 'discord' / 'fivemanage'
Config.Logs = 'discord'

---@class Debug<boolean>
-- By running debugging, you will receive prints at various activities, in case of any problems, this will be able to help you find the cause of the misconfiguration.
Config.Debug = true                 -- Enables debug prints for various system events.
Config.DebugTarget = false          -- Enables debug related to target zones.
Config.DebugPolyZone = false        -- Enables debug related to PolyZone entries/exits.
Config.DebugRegionsZone = false     -- Enables debug related to Regions PolyZone.

---@class UseTarget<boolean>
Config.UseTarget = false

---@class UseTextUI<boolean>
Config.UseTextUI = false

---@class UseDrawText<boolean>
-- If you are not using the target system, you can render 3D Text.
Config.UseDrawText = true

---@class UseDrawTextBox<boolean>
-- Using 3D Text system, you can render dark box under the text (using this option will increase CPU consumption by ~0.04ms)
Config.UseDrawTextBox = true

---@class FurnitureScreenshotsPath<string>
-- Path where generated furniture screenshots will be saved.
--
-- Default: "html/images/furniture/"
-- Screenshots saved here will be automatically used by the furniture menu.
--
-- You can change this path if you prefer to store screenshots in a separate
-- folder first (for example to review or organize them before moving them
-- to the final furniture images directory).
--
-- Example: "screenshots/"
Config.FurnitureScreenshotsPath = 'html/images/furniture/'

---@class AutoLoadKuzQualityShells<boolean>
-- Automatic addition of shells created with "KuzQuality Shell Creator"
Config.AutoLoadKuzQualityShells = true



--  ██████╗ ███████╗███╗   ██╗███████╗██████╗  █████╗ ██╗         ███████╗███████╗████████╗████████╗██╗███╗   ██╗ ██████╗ ███████╗
-- ██╔════╝ ██╔════╝████╗  ██║██╔════╝██╔══██╗██╔══██╗██║         ██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗  ██║██╔════╝ ██╔════╝
-- ██║  ███╗█████╗  ██╔██╗ ██║█████╗  ██████╔╝███████║██║         ███████╗█████╗     ██║      ██║   ██║██╔██╗ ██║██║  ███╗███████╗
-- ██║   ██║██╔══╝  ██║╚██╗██║██╔══╝  ██╔══██╗██╔══██║██║         ╚════██║██╔══╝     ██║      ██║   ██║██║╚██╗██║██║   ██║╚════██║
-- ╚██████╔╝███████╗██║ ╚████║███████╗██║  ██║██║  ██║███████╗    ███████║███████╗   ██║      ██║   ██║██║ ╚████║╚██████╔╝███████║
--  ╚═════╝ ╚══════╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝    ╚══════╝╚══════╝   ╚═╝      ╚═╝   ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝
Config.Blips = {
    ['Motel'] = {
        enabled = true,
        sprite = 475,
        display = 2,
        scale = 0.9,
        color = 0,
        name = TRANSLATE('blip.motel'),
    },
    ['Building'] = {
        enabled = true,
        sprite = 475,
        display = 2,
        scale = 0.9,
        color = 0,
        name = TRANSLATE('blip.building'),
    },
    ['HouseAgency'] = {
        enabled = true,
        sprite = 40,
        display = 2,
        scale = 0.8,
        color = 11,
        blipCategory = 11,
        name = TRANSLATE('blip.house_agency'),
    },
    ['HouseOwner'] = {
        enabled = true,
        sprite = 40,
        display = 2,
        scale = 0.8,
        color = 0,
        blipCategory = 11,
        name = TRANSLATE('blip.house_owner'),
    },
    ['HouseRenter'] = {
        enabled = true,
        sprite = 40,
        display = 2,
        scale = 0.8,
        color = 0,
        blipCategory = 11,
        name = TRANSLATE('blip.house_renter'),
    },
    ['HouseKeyHolder'] = {
        enabled = true,
        sprite = 811,
        display = 2,
        scale = 0.9,
        color = 26,
        blipCategory = 11,
        name = TRANSLATE('blip.house_key_holder'),
    },
    ['HouseForSale'] = {
        enabled = true,
        sprite = 350,
        display = 2,
        scale = 0.6,
        color = 0,
        blipCategory = 10,
        name = TRANSLATE('blip.house_for_sale'),
    },
    ['Marketplace'] = {
        enabled = true,
        sprite = 476,
        display = 2,
        scale = 1.0,
        color = 2,
        name = TRANSLATE('blip.marketplace'),
    },
    ['Idea'] = {
        enabled = true,
        sprite = 568,
        display = 2,
        scale = 1.0,
        color = 5,
        name = TRANSLATE('blip.idea'),
    },
    ['IdeaDelivery'] = {
        sprite = 478,
        display = 2,
        scale = 1.0,
        color = 3,
        name = TRANSLATE('blip.idea_delivery'),
    },
    ['IdeaDeliveryBack'] = {
        sprite = 569,
        display = 2,
        scale = 1.0,
        color = 3,
        name = TRANSLATE('blip.idea_warehouse'),
    },
}

Config.GivePropertyCommand = {
    Enabled = true,
    Name = 'giveproperty',
    Help = 'Give Property',
    Groups = 'admin',
    Suggestions = {
        {name = 'identifier', label = 'Player ID / Identifier or CitizenID / Agency name'},
        {name = 'propertyId', label = 'Property ID'},
    },
}

---@class AreaUnit<string>
-- Unit used for displaying property area ("m2" for meters, "ft2" for feet)
Config.AreaUnit = "m2"

---@class TeleportToLastPropertyOnJoin<boolean>
-- Automatically teleport player to last property after joining the server when he left the server while in the interiors.
-- If you are using a custom spawnselector, make sure you cancel running it when the player has data in the `last_property` column.
Config.TeleportToLastPropertyOnJoin = true

---@class FurnitureInteractionAccess<number>
-- 1 : Every player who is inside or on the property has access to the interaction
-- 2 : Only players who have any permission in the property have access to furniture interaction
-- 3 : Only players who have the furniture permission in the property have access to furniture interaction
Config.FurnitureInteractionAccess = 2

---@class StaticInteractionAccess<number>
-- 1 : Every player who is inside or on the property has access to the interaction
-- 2 : Only players who have any permission in the property have access to furniture interaction
-- 3 : Only players who have the furniture permission in the property have access to furniture interaction
Config.StaticInteractionAccess = 1

---@class AllowInteractionUnderRaid<number>
-- The police and players will be able to interact with furniture and static interactions during the police raid.
Config.AllowInteractionUnderRaid = true

---@class AllowChangeStoragePosition<boolean>
-- Allows the player to reposition the default storage unit inside the property.
-- Only applies if the house was created with a pre-set storage location (e.g., via housing creator).
-- Useful for letting the owner place it elsewhere inside the house after purchase.
Config.AllowChangeStoragePosition = true

---@class AllowChangeWardrobePosition<boolean>
-- Allows the player to reposition the default wardrobe inside the property.
-- Only applies if the house was created with a pre-set wardrobe location (e.g., via housing creator).
-- Gives flexibility to organize the interior layout more freely.
Config.AllowChangeWardrobePosition = true

---@class MaxPropertiesPerPlayer<number | false>
-- You can set a limit on the maximum number of properties owned by one player, if you do not want to use the limit, set false
Config.MaxPropertiesPerPlayer = false

---@class AllowExitOnLocked<boolean>
-- You will allow players to leave the property even when the door is closed, so the player will not have to close the door behind them
Config.AllowExitOnLocked = true

---@class InactivePropertyCleanup<table>
-- Automatically handles inactive properties based on last activity time.
--
-- Behavior:
-- Starter apartments: the property is permanently deleted from the database.
-- Houses / Apartments / Motel rooms: the property is reset (ownership, furniture, bills, etc. are cleared).
Config.InactivePropertyCleanup = {
    Enabled = true, -- Enables or disables the cleanup system entirely.
    
    Days = {
        -- Defines how many days a property can remain inactive before cleanup is triggered.
        -- Set to 0 to disable cleanup for a specific property type.
        -- Recommended to use lower values for high-turnover properties (e.g. starter apartments).

        houses = 120,        -- Player-owned houses (Shell / IPL only)
        apartments = 45,    -- Apartments in buildings
        motel = 0,          -- Motel rooms (Shell / IPL only)
        starter = 14,       -- Starter Apartments (recommended: low value)
    },
}

---@class MaxVisitingTime<number | false>
-- Maximum allowed visiting time before purchase.
-- number: Time limit in seconds (player will be automatically ejected after this time)
-- false: No time limit
Config.MaxVisitingTime = 20

---@class InteriorBoundaryCheck<boolean>
-- Ensures the player stays within the interior boundaries and automatically corrects invalid positions
Config.InteriorBoundaryCheck = true

---@class SaleSignColor<string>
-- Defines the primary color of the FOR SALE / FOR RENT sign.
-- This color affects the visual appearance of the sale sign in-game
Config.SaleSignColor = '#20763f'

---@class RentalCycles<table>
Config.RentalCycles = {
    ['weekly'] = true,
    ['monthly'] = true,
}

---@class WeeklyRentPriceCalculation<number>
-- 1: Year Scale - Rent is calculated using a full-year (52 weeks) distribution for maximum accuracy
-- 2: Monthly Split - Rent is simplified and divided into 4-week periods (less precise, but easier to manage)
Config.WeeklyRentPriceCalculation = 2

---@class HideOccupiedMotelRooms<boolean>
-- If enabled, occupied motel rooms will be hidden in the reception menu.
-- Only available (free) rooms will be displayed to players when interacting with the motel receptionist.
Config.HideOccupiedMotelRooms = false

---@class MaxDesignsPerProperty<boolean>
-- Maximum number of saved designs per property.
-- Setting this limit helps prevent database overload from excessive design saves, while still allowing players to create and manage multiple interior layouts for their properties.
Config.MaxDesignsPerProperty = 2



-- ███████╗████████╗ █████╗ ██████╗ ████████╗███████╗██████╗      █████╗ ██████╗  █████╗ ██████╗ ████████╗███╗   ███╗███████╗███╗   ██╗████████╗███████╗
-- ██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔══██╗    ██╔══██╗██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝████╗ ████║██╔════╝████╗  ██║╚══██╔══╝██╔════╝
-- ███████╗   ██║   ███████║██████╔╝   ██║   █████╗  ██████╔╝    ███████║██████╔╝███████║██████╔╝   ██║   ██╔████╔██║█████╗  ██╔██╗ ██║   ██║   ███████╗
-- ╚════██║   ██║   ██╔══██║██╔══██╗   ██║   ██╔══╝  ██╔══██╗    ██╔══██║██╔═══╝ ██╔══██║██╔══██╗   ██║   ██║╚██╔╝██║██╔══╝  ██║╚██╗██║   ██║   ╚════██║
-- ███████║   ██║   ██║  ██║██║  ██║   ██║   ███████╗██║  ██║    ██║  ██║██║     ██║  ██║██║  ██║   ██║   ██║ ╚═╝ ██║███████╗██║ ╚████║   ██║   ███████║
-- ╚══════╝   ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝╚═╝  ╚═╝    ╚═╝  ╚═╝╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝     ╚═╝╚══════╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝
---@class StarterApartments<table>
-- Configuration of automatically assigned starter apartments for new players
-- https://docs.vames-store.com/assets/vms_housing/guides/how-to-use-starter-apartments
Config.StarterApartments = {
    Object = 1627, -- You need to have a property building created and here is to find the id of this building from the database.
    Name = 'Starting Apartment #%s', -- Name of the starter apartment, which will be displayed in the menu.
    Type = 'shell', -- 'shell' / 'ipl'

    Shell = 'standardmotel_shell',
    Ipl = 'apa_v_mp_h_01_a',

    DefaultPurchasePrice = 0, -- Default purchase price of the starter apartment. (By setting 0, the player will not be able to sell the property to make money)
    DefaultRentPrice = 0, -- Default rent price of the starter apartment.

    AllowFurnitureInside = true, -- true : Allow furniture inside the starter apartment
    AllowChangeTheme = true, -- true : Allow changing the theme of the starter apartment IPL
    DefaultThemeIpl = 'modern',

    AllowSell = false,
    AllowTransfer = false,
    AllowRent = false,

    Delivery = {
        Enabled = true,
        Coords = vector4(2.4304, -2.1917, 498.4416, 79.99999237060547), -- Coords of the storage in the starter apartment
    },

    Storage = {
        Enabled = true,
        Coords = vector3(1.5388, -3.0803, 499.7162), -- Coords of the storage in the starter apartment
        Slots = 20, -- Slots of the storage in the starter apartment
        Weight = 25000, -- Weight of the storage in the starter apartment
    },

    Wardrobe = {
        Enabled = true,
        Coords = vector3(1.3227, 2.8945, 500.0726), -- Coords of the wardrobe in the starter apartment
    },
}



-- ██╗  ██╗ ██████╗ ██╗   ██╗███████╗██╗███╗   ██╗ ██████╗     ███╗   ███╗ █████╗ ███╗   ██╗ █████╗  ██████╗ ███████╗███╗   ███╗███████╗███╗   ██╗████████╗
-- ██║  ██║██╔═══██╗██║   ██║██╔════╝██║████╗  ██║██╔════╝     ████╗ ████║██╔══██╗████╗  ██║██╔══██╗██╔════╝ ██╔════╝████╗ ████║██╔════╝████╗  ██║╚══██╔══╝
-- ███████║██║   ██║██║   ██║███████╗██║██╔██╗ ██║██║  ███╗    ██╔████╔██║███████║██╔██╗ ██║███████║██║  ███╗█████╗  ██╔████╔██║█████╗  ██╔██╗ ██║   ██║   
-- ██╔══██║██║   ██║██║   ██║╚════██║██║██║╚██╗██║██║   ██║    ██║╚██╔╝██║██╔══██║██║╚██╗██║██╔══██║██║   ██║██╔══╝  ██║╚██╔╝██║██╔══╝  ██║╚██╗██║   ██║   
-- ██║  ██║╚██████╔╝╚██████╔╝███████║██║██║ ╚████║╚██████╔╝    ██║ ╚═╝ ██║██║  ██║██║ ╚████║██║  ██║╚██████╔╝███████╗██║ ╚═╝ ██║███████╗██║ ╚████║   ██║   
-- ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚══════╝╚═╝╚═╝  ╚═══╝ ╚═════╝     ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝  ╚═╝ ╚═════╝ ╚══════╝╚═╝     ╚═╝╚══════╝╚═╝  ╚═══╝   ╚═╝   
Config.HousingManagement = {
    Command = 'housemanage',                    -- Command to open the property management menu
    Key = 'F6',                                 -- Keyboard shortcut to open the management menu
    Description = 'Property management menu',   -- Description shown in keybind hint
}

---@class AutomaticSell<number>
-- Percentage (%) refunded to player when selling their property via menu
Config.AutomaticSell = 50



-- ██╗  ██╗███████╗██╗   ██╗███████╗    ███████╗██╗   ██╗███████╗████████╗███████╗███╗   ███╗
-- ██║ ██╔╝██╔════╝╚██╗ ██╔╝██╔════╝    ██╔════╝╚██╗ ██╔╝██╔════╝╚══██╔══╝██╔════╝████╗ ████║
-- █████╔╝ █████╗   ╚████╔╝ ███████╗    ███████╗ ╚████╔╝ ███████╗   ██║   █████╗  ██╔████╔██║
-- ██╔═██╗ ██╔══╝    ╚██╔╝  ╚════██║    ╚════██║  ╚██╔╝  ╚════██║   ██║   ██╔══╝  ██║╚██╔╝██║
-- ██║  ██╗███████╗   ██║   ███████║    ███████║   ██║   ███████║   ██║   ███████╗██║ ╚═╝ ██║
-- ╚═╝  ╚═╝╚══════╝   ╚═╝   ╚══════╝    ╚══════╝   ╚═╝   ╚══════╝   ╚═╝   ╚══════╝╚═╝     ╚═╝
---@class UseKeysOnItem<boolean>
-- true = keys are given as inventory items (e.g. qb-inventory / ox_inventory)
-- false = key ownership stored internally (no item given)
--
-- When using Keys as Item, opening and closing doors will only be possible by using the item in your inventory.
-- The "Toggle Lock" option will not be available as a text UI or in the target menu.
Config.UseKeysOnItem = true

---@class KeysLimit<number>
-- Max number of physical keys a player can generate for a property
Config.KeysLimit = 8

---@class KeyPrice<number>
-- Price ($) to generate an additional key
Config.KeyPrice = 300

---@class LockReplacementPrice<number>
-- Price ($) to replace the lock and invalidate old keys
Config.LockReplacementPrice = 1000

---@class RemoveKeysOnRentEnd<boolean>
-- Remove all keys from tenants when the rent ends
Config.RemoveKeysOnRentEnd = true

---@class AutomaticGiveKeyForProperty<boolean>
-- Do you want the player to receive the key item immediately after buying/renting the property?
Config.AutomaticGiveKeyForProperty = true

---@class RequireKeysInsideInterior<boolean>
-- true = Requires keys to lock/unlock inside interior doors
-- false = Allows locking/unlocking interior doors from inside without keys (realistic behavior)
Config.RequireKeysInsideInterior = false



-- ██████╗  ██████╗  ██████╗ ██████╗     ███████╗████████╗ █████╗ ████████╗███████╗
-- ██╔══██╗██╔═══██╗██╔═══██╗██╔══██╗    ██╔════╝╚══██╔══╝██╔══██╗╚══██╔══╝██╔════╝
-- ██║  ██║██║   ██║██║   ██║██████╔╝    ███████╗   ██║   ███████║   ██║   █████╗  
-- ██║  ██║██║   ██║██║   ██║██╔══██╗    ╚════██║   ██║   ██╔══██║   ██║   ██╔══╝  
-- ██████╔╝╚██████╔╝╚██████╔╝██║  ██║    ███████║   ██║   ██║  ██║   ██║   ███████╗
-- ╚═════╝  ╚═════╝  ╚═════╝ ╚═╝  ╚═╝    ╚══════╝   ╚═╝   ╚═╝  ╚═╝   ╚═╝   ╚══════╝
---@class SaveDoorsState<boolean>
-- true = door state (open/closed) is saved and restored after a server restart
Config.SaveDoorsState = true

---@class DefaultDoorsLocked<boolean>
-- Default door state if SaveDoorsState is false
-- true = doors locked
-- false = doors unlocked
Config.DefaultDoorsLocked = true

---@class DoorsSpeed<table>
Config.DoorsSpeed = {
    Single = 10.0,  -- Speed for single doors
    Double = 10.0,  -- Speed for double doors
    Gate = 5.0,     -- Speed for gates
}



-- ██████╗ ███████╗██████╗ ███╗   ███╗██╗███████╗███████╗██╗ ██████╗ ███╗   ██╗███████╗
-- ██╔══██╗██╔════╝██╔══██╗████╗ ████║██║██╔════╝██╔════╝██║██╔═══██╗████╗  ██║██╔════╝
-- ██████╔╝█████╗  ██████╔╝██╔████╔██║██║███████╗███████╗██║██║   ██║██╔██╗ ██║███████╗
-- ██╔═══╝ ██╔══╝  ██╔══██╗██║╚██╔╝██║██║╚════██║╚════██║██║██║   ██║██║╚██╗██║╚════██║
-- ██║     ███████╗██║  ██║██║ ╚═╝ ██║██║███████║███████║██║╚██████╔╝██║ ╚████║███████║
-- ╚═╝     ╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝╚═╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝
---@class DefaultPermissionsForRenter<table>
-- Default permissions granted to a renter when added to a property
Config.DefaultPermissionsForRenter = {
    'garage',           -- Access to garage
    'furniture',        -- Can manage furniture
    'billPayments',     -- Can pay bills
    'keysManage',       -- Can manage keys
    'upgradesManage',   -- Can manage house upgrades
}

---@class PermissionsLimit<number>
-- Max number of players with permissions for a property
Config.PermissionsLimit = 10



-- ████████╗ █████╗ ██╗  ██╗███████╗███████╗
-- ╚══██╔══╝██╔══██╗╚██╗██╔╝██╔════╝██╔════╝
--    ██║   ███████║ ╚███╔╝ █████╗  ███████╗
--    ██║   ██╔══██║ ██╔██╗ ██╔══╝  ╚════██║
--    ██║   ██║  ██║██╔╝ ██╗███████╗███████║
--    ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝╚══════╝
---@class CityHallTaxes<table>
-- City tax configuration for property-related transactions (Only for VMS City Hall Users)
Config.CityHallTaxes = {
    PropertyPurchase = {
        Enabled = true,
        Type = 'percentage', -- 'percentage' / 'static'
        Value = 3, -- 3% for percentage Type or 3$ for static Type
        Title = 'Property Purchase - %s %s',
    },
    PropertyResale = {
        Enabled = true,
        Type = 'percentage', -- 'percentage' / 'static'
        Value = 12, -- 12% for percentage Type or 12$ for static Type
        Title = 'Property Resale - %s %s',
    },
    RentalIncome = {
        Enabled = true,
        Type = 'percentage', -- 'percentage' / 'static'
        Value = 15, -- 15% for percentage Type or 15$ for static Type
        Title = 'Property Rental - %s %s',
    },
    PropertyCadastralTax = {
        Enabled = true,
        Type = 'percentage', -- 'percentage' / 'static'
        Value = 1, -- 1% for percentage Type or 1$ for static Type
        Title = 'Property Cadastral Tax - %s %s',
    },
    AgencyRentalIncome = {
        Enabled = true,
        Type = 'percentage', -- 'percentage' / 'static'
        Value = 5, -- 15% for percentage Type or 15$ for static Type
        Title = 'Property Rental - %s %s',
    },
    AgencyPropertyPurchase = {
        Enabled = true,
        Type = 'percentage', -- 'percentage' / 'static'
        Value = 3, -- 3% for percentage Type or 3$ for static Type
        Title = 'Property Purchase - %s %s',
    },
    AgencyPropertyResale = {
        Enabled = true,
        Type = 'percentage', -- 'percentage' / 'static'
        Value = 12, -- 12% for percentage Type or 12$ for static Type
        Title = 'Property Resale - %s %s',
    },
    AuthorProjectSale = {
        Enabled = false,
        Type = 'percentage', -- 'percentage' / 'static'
        Value = 5, -- 5% for percentage Type or 5$ for static Type
        Title = 'Project Sold - %s %s',
    },
    StudioProjectSale = {
        Enabled = true,
        Type = 'percentage', -- 'percentage' / 'static'
        Value = 10, -- 12% for percentage Type or 12$ for static Type
        Title = 'Project Sold - %s %s',
    },
}



-- ██████╗ ██╗██╗     ██╗     ███████╗
-- ██╔══██╗██║██║     ██║     ██╔════╝
-- ██████╔╝██║██║     ██║     ███████╗
-- ██╔══██╗██║██║     ██║     ╚════██║
-- ██████╔╝██║███████╗███████╗███████║
-- ╚═════╝ ╚═╝╚══════╝╚══════╝╚══════╝
---@class UseBills<boolean>
-- Enable utility bills system (electricity, water, internet)
Config.UseServiceBills = true

---@class StarterApartmentsBills<boolean>
-- Enables or disables utility bills (electricity, water, internet) for starter apartments.
-- Recommended for large servers with high player turnover.
-- Disabling this option prevents excessive database growth caused by generating monthly bills
-- for inactive or unused starter properties.
Config.StarterApartmentsBills = false

---@class MaxUnpaidServiceBills<number>
-- Maximum number of unpaid utility bills.
-- If this limit is reached, new bills will not be generated to prevent database overload.
Config.MaxUnpaidServiceBills = 4

---@class AllowedUnpaidBills<number>
-- Max number of unpaid utility bills before services are disabled
Config.AllowedUnpaidBills = 2

---@class AllowedUnpaidRentBills<number>
-- Max number of unpaid rent bills before tenant is evicted
-- Do not set to 1 to avoid instant eviction!
Config.AllowedUnpaidRentBills = 2

---@class RemoveBillsOnRentEnd<boolean>
-- Delete all unpaid bills assigned to the tenant when the rent ends
Config.RemoveBillsOnRentEnd = true

---@class OptimizeBillsTable<boolean>
-- true = Removes paid bills from the database at server startup
-- false = Doesn't remove bills from the database
Config.OptimizeBillsTable = false



-- ███████╗███████╗ ██████╗██╗   ██╗██████╗ ██╗████████╗██╗   ██╗
-- ██╔════╝██╔════╝██╔════╝██║   ██║██╔══██╗██║╚══██╔══╝╚██╗ ██╔╝
-- ███████╗█████╗  ██║     ██║   ██║██████╔╝██║   ██║    ╚████╔╝ 
-- ╚════██║██╔══╝  ██║     ██║   ██║██╔══██╗██║   ██║     ╚██╔╝  
-- ███████║███████╗╚██████╗╚██████╔╝██║  ██║██║   ██║      ██║   
-- ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝  ╚═╝╚═╝   ╚═╝      ╚═╝   
---@class Lockpick<table>
-- Configuration related to the possibility of breaking into a property
Config.Lockpick = {
    Enable = true, -- Enable or disable lockpicking for properties

    PoliceJobs = { 'police', 'sheriff' }, -- List of jobs considered as police for lockpicking checks
    RequiredPolice = 1, -- Minimum number of police officers required online to allow lockpicking

    RequiredOwnerOnline = true, -- Require the property owner / renter to be online for lockpicking
    
    Item = {
        Required = true,           -- true = item is required to lockpick
        Name = 'lockpick',         -- Name of the item used for lockpicking
        Count = 1,                 -- Amount required
        RemoveOnUse = true,        -- true = item will be removed after use
    },
}

Config.Alarm = {
    -- Police Alerts: (./integration/[dispatch]/*)
    AlertPoliceOnlyWithUpgrade = true, -- Only alert the police if the property has the alarm upgrade installed

    AlarmJobs = { 'police', 'sheriff' },

    AlertPoliceOnLockpickStart = true,     -- Notify police when a lockpick attempt starts
    AlertPoliceOnLockpickSuccess = false,  -- Notify police only if the break-in is successful
    AlertPoliceOnLockpickFail = false,     -- Notify police only if the break-in attempt fails

    -- SMS Alerts: (./integration/[phone]/*)
    AllowPhoneAlerts = true,

    VerificationCodeExpiryTime = 180, -- Time in seconds before the alarm verification code expires
    PhoneNumberMaxLength = 15, -- Maximum length of the phone number for alarm verification

    AlertOwnerOnLockpickStart = true,     -- Notify player when a lockpick attempt starts
    AlertOwnerOnLockpickSuccess = false,  -- Notify player only if the break-in is successful
    AlertOwnerOnLockpickFail = false,     -- Notify player only if the break-in attempt fails
}

Config.PropertyLockdown = {
    Enable = true, -- Enable the property lockdown system (e.g., police tape to restrict access)

    ObjectModel = 'prop_barrier_work05', -- Model used as the physical lockdown object (e.g., barrier or tape)

    Jobs = { 'police' }, -- List of jobs allowed to initiate property lockdown

    Item = {
        Required = true,            -- true = item is required to lockdown
        Name = 'police_barrier',    -- Name of the required item
        Count = 1,                  -- Amount required
        RemoveOnUse = true,         -- true = item will be removed after use
    },
}

Config.PropertyRaids = {
    Enable = true, -- Enable the ability to raid properties by specific jobs

    Jobs = { 'police' }, -- List of jobs allowed to initiate raids

    InteractiveBuildInMinigame = {
        Use = true,

        Hits = {
            Normal = 5,
            AntiBurglaryDoors = {8, 12},
        },

        Outline = true,
        OutlineColors = {
            Default = {9, 181, 15, 255},
            Hover = {181, 9, 9, 255},
        },
    },

    AntiBurglaryDoors = {
        AllowRaid = true, -- Allow raiding even if the property has Anti-Burglary Doors installed
        RaidChance = 10, -- % chance that the raid will succeed if Anti-Burglary Doors are present

        ---@field BreakOnRam<boolean>
        -- true = Raiding will remove the Anti-Burglary Doors upgrade and require repurchase
        -- false = Raiding will not remove the Anti-Burglary Doors upgrade
        BreakOnRam = true
    }, 

    Item = {
        Required = true,            -- true = item is required to raid
        Name = 'police_stormram',   -- Name of the required item
        Count = 1,                  -- Amount required
        RemoveOnUse = true,         -- true = item will be removed after use
    },
}



-- ██████╗  ██████╗ ██╗    ██╗███████╗██████╗     ██████╗  ██████╗ ██╗  ██╗
-- ██╔══██╗██╔═══██╗██║    ██║██╔════╝██╔══██╗    ██╔══██╗██╔═══██╗╚██╗██╔╝
-- ██████╔╝██║   ██║██║ █╗ ██║█████╗  ██████╔╝    ██████╔╝██║   ██║ ╚███╔╝ 
-- ██╔═══╝ ██║   ██║██║███╗██║██╔══╝  ██╔══██╗    ██╔══██╗██║   ██║ ██╔██╗ 
-- ██║     ╚██████╔╝╚███╔███╔╝███████╗██║  ██║    ██████╔╝╚██████╔╝██╔╝ ██╗
-- ╚═╝      ╚═════╝  ╚══╝╚══╝ ╚══════╝╚═╝  ╚═╝    ╚═════╝  ╚═════╝ ╚═╝  ╚═╝
Config.PowerBox = {
    ObjectModel = {
        Opened = 'm24_1_prop_m41_circuitbox_open_01a',
        Closed = 'm24_1_prop_m41_circuitbox_01a',
    },

    Actions = {
        ['TurnOn'] = {
            Item = {
                Required = false,          -- true = item is required
                Name = nil,                -- Name of the item used
                Count = 1,                 -- Amount required
                RemoveOnUse = true,        -- true = item will be removed after use
                ShowOnlyWithItem = true,   -- true = The option will not be visible if the player does not have the required item
            },

            Explode = {
                AllowOnFail = false, -- Allow the power box to explode if the player fails to turn it on
                Chance = 0, -- % chance of explosion on failure
            },

            Electrocution = {
                AllowOnFail = true, -- Allow the player to be electrocuted if they fail to turn on the power box
                Chance = 40, -- % chance of electrocution on failure
                Duration = {5000, 10000}, -- Duration of electrocution effect in milliseconds

                Knockback = {
                    Allow = true, -- Allow knockback effect to be applied to the player during electrocution
                    Chance = 50, -- % chance of knockback effect
                    Force = {5, 10}, -- Knockback force applied to the player
                },

                Damage = {
                    Allow = true, -- Allow damage to be applied to the player during electrocution
                    Chance = 50, -- % chance of damage
                    Value = {5, 15}, -- Damage value applied to the player
                },

                Death = {
                    Allow = false, -- Allow the player to die if they fail to turn on the power box
                    Chance = 0, -- % chance of death on failure
                }
            },
        },
        ['Repair'] = {
            Item = {
                Required = true,           -- true = item is required
                Name = 'insulating_tape',  -- Name of the item used
                Count = 1,                 -- Amount required
                RemoveOnUse = true,        -- true = item will be removed after use
                ShowOnlyWithItem = true,   -- true = The option will not be visible if the player does not have the required item
            },

            Explode = {
                AllowOnFail = true, -- Allow the power box to explode if the player fails to turn it on
                Chance = 10, -- % chance of explosion on failure
            },

            Electrocution = {
                AllowOnFail = true, -- Allow the player to be electrocuted if they fail to turn on the power box
                Chance = 70, -- % chance of electrocution on failure
                Duration = {20000, 30000}, -- Duration of electrocution effect in milliseconds

                Knockback = {
                    Allow = true, -- Allow knockback effect to be applied to the player during electrocution
                    Chance = 50, -- % chance of knockback effect
                    Force = {10, 18}, -- Knockback force applied to the player
                },

                Damage = {
                    Allow = true, -- Allow damage to be applied to the player during electrocution
                    Chance = 75, -- % chance of damage
                    Value = {10, 20}, -- Damage value applied to the player
                },

                Death = {
                    Allow = true, -- Allow the player to die if they fail to turn on the power box
                    Chance = 10, -- % chance of death on failure
                }
            },
        },
        ['TurnOff'] = {
            Item = {
                Required = false,          -- true = item is required
                Name = nil,                -- Name of the item used
                Count = 1,                 -- Amount required
                RemoveOnUse = true,        -- true = item will be removed after use
                ShowOnlyWithItem = true,   -- true = The option will not be visible if the player does not have the required item
            },

            Explode = {
                AllowOnFail = false, -- Allow the power box to explode if the player fails to turn it on
                Chance = 10, -- % chance of explosion on failure
            },

            Electrocution = {
                AllowOnFail = true, -- Allow the player to be electrocuted if they fail to turn on the power box
                Chance = 90, -- % chance of electrocution on failure
                Duration = {20000, 30000}, -- Duration of electrocution effect in milliseconds

                Knockback = {
                    Allow = true, -- Allow knockback effect to be applied to the player during electrocution
                    Chance = 60, -- % chance of knockback effect
                    Force = 10, -- Knockback force applied to the player
                },

                Damage = {
                    Allow = true, -- Allow damage to be applied to the player during electrocution
                    Chance = 80, -- % chance of damage
                    Value = {8, 16}, -- Damage value applied to the player
                },

                Death = {
                    Allow = false, -- Allow the player to die if they fail to turn on the power box
                    Chance = 0, -- % chance of death on failure
                }
            },
        },
        ['Destroy'] = {
            Item = {
                Required = true,           -- true = item is required
                Name = 'pliers',           -- Name of the item used
                Count = 1,                 -- Amount required
                RemoveOnUse = true,        -- true = item will be removed after use
                ShowOnlyWithItem = true,   -- true = The option will not be visible if the player does not have the required item
            },
            
            Explode = {
                AllowOnFail = true, -- Allow the power box to explode if the player fails to turn it on
                Chance = 35, -- % chance of explosion on failure
            },

            Electrocution = {
                AllowOnFail = true, -- Allow the player to be electrocuted if they fail to turn on the power box
                Chance = 80, -- % chance of electrocution on failure
                Duration = {20000, 30000}, -- Duration of electrocution effect in milliseconds

                Knockback = {
                    Allow = true, -- Allow knockback effect to be applied to the player during electrocution
                    Chance = 100, -- % chance of knockback effect
                    Force = {10, 20}, -- Knockback force applied to the player
                },

                Damage = {
                    Allow = true, -- Allow damage to be applied to the player during electrocution
                    Chance = 80, -- % chance of damage
                    Value = {10, 25}, -- Damage value applied to the player
                },

                Death = {
                    Allow = true, -- Allow the player to die if they fail to turn on the power box
                    Chance = 30, -- % chance of death on failure
                }
            },
        },
    },
}



-- ██╗  ██╗ ██████╗ ██╗   ██╗███████╗██╗███╗   ██╗ ██████╗      ██████╗██████╗ ███████╗ █████╗ ████████╗ ██████╗ ██████╗ 
-- ██║  ██║██╔═══██╗██║   ██║██╔════╝██║████╗  ██║██╔════╝     ██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗
-- ███████║██║   ██║██║   ██║███████╗██║██╔██╗ ██║██║  ███╗    ██║     ██████╔╝█████╗  ███████║   ██║   ██║   ██║██████╔╝
-- ██╔══██║██║   ██║██║   ██║╚════██║██║██║╚██╗██║██║   ██║    ██║     ██╔══██╗██╔══╝  ██╔══██║   ██║   ██║   ██║██╔══██╗
-- ██║  ██║╚██████╔╝╚██████╔╝███████║██║██║ ╚████║╚██████╔╝    ╚██████╗██║  ██║███████╗██║  ██║   ██║   ╚██████╔╝██║  ██║
-- ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚══════╝╚═╝╚═╝  ╚═══╝ ╚═════╝      ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝
Config.HousingCreator = {
    Command = 'housing',                    -- Command to open the housing creator menu
    Key = 'F7',                             -- Keyboard shortcut to open the housing creator menu
    Description = 'Open Housing Creator',   -- Description shown in keybind hint
    Access = {
        Job = {
            -- ['realestate'] = { -- job name
            --     [0] = { -- job grade
            --         ['Property:Create'] = true,
            --         ['Property:RemoveOwner'] = true,
            --         ['Property:RemoveRenter'] = true,
            --         ['Property:Teleport'] = true,
            --         ['Property:Edit'] = true,
            --         ['Property:Delete'] = true,
            --         ['Furniture:Create'] = true,
            --         ['Furniture:Edit'] = true,
            --         ['Furniture:Delete'] = true,
            --         ['CreateNewShell'] = true,
            --     },
            -- }
        },
        Group = {
            --[[
                If you are using QB-Core or QBX_Core, don't forget to customize the group names in SV.GetPlayerGroup (config.server.lua)
            ]]
            ['god'] = {
                ['Property:Create'] = true,
                ['Property:RemoveOwner'] = true,
                ['Property:RemoveRenter'] = true,
                ['Property:Teleport'] = true,
                ['Property:Edit'] = true,
                ['Property:Delete'] = true,
                ['Furniture:Create'] = true,
                ['Furniture:Edit'] = true,
                ['Furniture:Delete'] = true,
                ['CreateNewShell'] = true,
            },
            ['admin'] = {
                ['Property:Create'] = true,
                ['Property:RemoveOwner'] = true,
                ['Property:RemoveRenter'] = true,
                ['Property:Teleport'] = true,
                ['Property:Edit'] = true,
                ['Property:Delete'] = true,
                ['Furniture:Create'] = true,
                ['Furniture:Edit'] = true,
                ['Furniture:Delete'] = true,
                ['CreateNewShell'] = true,
            },
        },
    }
}

Config.HousingCreatorControls = {
    ['SELECT'] = {controlIndex = 24},                       -- Default: Mouse Left
    ['BACK'] = {controlIndex = 70},                         -- Default: Mouse Right
    ['SCROLL_DOWN'] = {controlIndex = 180},                 -- Default: Scroll Down
    ['SCROLL_UP'] = {controlIndex = 181},                   -- Default: Scroll UP
    ['LEFT_CTRL'] = {controlIndex = 210},                   -- Default: Left CTRL
    ['ENTER'] = {controlIndex = 191},                       -- Default: Enter
    ['CANCEL'] = {controlIndex = 202},                      -- Default: BACKSPACE / ESC
    
    ['ARROW_UP'] = {controlIndex = 172},                    -- Default: Arrow Up
    ['ARROW_DOWN'] = {controlIndex = 173},                  -- Default: Arrow Down
    ['ARROW_LEFT'] = {controlIndex = 174},                  -- Default: Arrow Left
    ['ARROW_RIGHT'] = {controlIndex = 175},                 -- Default: Arrow Right

    ['EXIT_PREVIEW'] = {controlIndex = 73, control = 'X'},  -- Defalut: X
}



-- ██████╗ ███████╗ █████╗ ██╗         ███████╗███████╗████████╗ █████╗ ████████╗███████╗
-- ██╔══██╗██╔════╝██╔══██╗██║         ██╔════╝██╔════╝╚══██╔══╝██╔══██╗╚══██╔══╝██╔════╝
-- ██████╔╝█████╗  ███████║██║         █████╗  ███████╗   ██║   ███████║   ██║   █████╗  
-- ██╔══██╗██╔══╝  ██╔══██║██║         ██╔══╝  ╚════██║   ██║   ██╔══██║   ██║   ██╔══╝  
-- ██║  ██║███████╗██║  ██║███████╗    ███████╗███████║   ██║   ██║  ██║   ██║   ███████╗
-- ╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝╚══════╝    ╚══════╝╚══════╝   ╚═╝   ╚═╝  ╚═╝   ╚═╝   ╚══════╝
Config.RealEstate = {
    Command = 'realestate',
    Description = 'Open Real Estate Menu',
    Key = nil,
    
    PercentageForAgent = 10,  -- 10% of the amount goes to the real estate agent
    PercentageForAgency = 90, -- 90% of the amount goes to the real estate agency

    Jobs = {
        ['dynasty8'] = {
            Label = 'Dynasty 8',
            SocietyName = 'dynasty8', -- This name, will be used for transactions with the company's bank account.

            AllowSellProperties = true,
            AllowRentProperties = true,

            Blip = {
                enabled = true,
                sprite = 476,
                display = 2,
                scale = 1.0,
                color = 2,
                name = 'Dynasty 8',
            },
            BlipCoords = vector3(-720.11, -382.26, 34.8), -- Location of the blip on the map
            
            Showroom = {
                PlayersCanPurchaseProperties = false,
                PlayersCanRentProperties = true,
                TargetCoords = vector4(-720.11, -382.26, 34.8, 159.53), -- interaction coordinates
                TargetSize = vec3(2.0, 1.6, 1.8), -- Target zone dimensions (length, width, height)
            },

            Grades = {
                [0] = {
                    perms = {
                        ['BossMenuAccess'] = false,
                        ['PropertyAccess'] = true,
                        ['PurchaseProperties'] = false,
                        ['SellProperties'] = false,
                        ['RentProperties'] = false,
                        ['TerminateRental'] = true,
                        ['UpdatePropertyPrice'] = false,
                        ['ListProperty'] = false,
                    },
                },
                [1] = {
                    perms = {
                        ['BossMenuAccess'] = false,
                        ['PropertyAccess'] = true,
                        ['PurchaseProperties'] = true,
                        ['SellProperties'] = true,
                        ['RentProperties'] = true,
                        ['TerminateRental'] = true,
                        ['UpdatePropertyPrice'] = true,
                        ['ListProperty'] = true,
                    },
                },
                [2] = {
                    perms = {
                        ['BossMenuAccess'] = true, -- Set false if you do not have bossmenu integration
                        ['PropertyAccess'] = true,
                        ['PurchaseProperties'] = true,
                        ['SellProperties'] = true,
                        ['RentProperties'] = true,
                        ['TerminateRental'] = true,
                        ['UpdatePropertyPrice'] = true,
                        ['ListProperty'] = true,
                    },
                },
            }
        },
    }
}

---@class MaxPropertiesPerAgency<number | false>
-- You can set a limit on the maximum number of properties owned by real estate agency, if you do not want to use the limit, set false
Config.MaxPropertiesPerAgency = false



-- ██╗███╗   ██╗████████╗███████╗██████╗ ██╗ ██████╗ ██████╗     ██████╗ ███████╗███████╗██╗ ██████╗ ███╗   ██╗███████╗██████╗ 
-- ██║████╗  ██║╚══██╔══╝██╔════╝██╔══██╗██║██╔═══██╗██╔══██╗    ██╔══██╗██╔════╝██╔════╝██║██╔════╝ ████╗  ██║██╔════╝██╔══██╗
-- ██║██╔██╗ ██║   ██║   █████╗  ██████╔╝██║██║   ██║██████╔╝    ██║  ██║█████╗  ███████╗██║██║  ███╗██╔██╗ ██║█████╗  ██████╔╝
-- ██║██║╚██╗██║   ██║   ██╔══╝  ██╔══██╗██║██║   ██║██╔══██╗    ██║  ██║██╔══╝  ╚════██║██║██║   ██║██║╚██╗██║██╔══╝  ██╔══██╗
-- ██║██║ ╚████║   ██║   ███████╗██║  ██║██║╚██████╔╝██║  ██║    ██████╔╝███████╗███████║██║╚██████╔╝██║ ╚████║███████╗██║  ██║
-- ╚═╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝╚═╝  ╚═╝╚═╝ ╚═════╝ ╚═╝  ╚═╝    ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝
Config.InteriorDesigner = {
    Command = 'interiordesigner',
    Description = 'Open Interior Designer Menu',
    Key = nil,
    
    PercentageForDesigner = 30,  -- 30% of the amount goes to the author of project
    PercentageForStudio = 70, -- 70% of the amount goes to the design studio
    AllowDeleteOwnProjects = true,
    MaxFurnitureCount = 60,

    Jobs = {
        ['interiordesigner'] = {
            Label = 'Interior Designer',
            SocietyName = 'interiordesigner', -- This name, will be used for transactions with the company's bank account.

            Blip = {
                enabled = true,
                sprite = 476,
                display = 2,
                scale = 1.0,
                color = 2,
                name = 'Interior Designer',
            },
            BlipCoords = vector3(-1038.71, -759.52, 18.84), -- Location of the blip on the map
            
            Showroom = {
                TargetCoords = vector4(-1038.53, -759.4, 19.84, 273.44), -- interaction coordinates
                TargetSize = vec3(2.0, 1.6, 1.8), -- Target zone dimensions (length, width, height)
            },

            -- If you want to allow all shells, remove the entire `AllowedShell` class
            -- AllowedShell = {
            --     'dc_apartmentfive',
            --     'dc_apartment6',
            --     'dc_house9',
            --     'dc_house10',
            -- },

            Grades = {
                [0] = {
                    perms = {
                        ['BossMenuAccess'] = false,
                        ['CreateProject'] = true,
                        ['DeleteProject'] = false,
                        ['ModifyProject'] = false,
                        ['ListProject'] = false,
                    },
                },
                [1] = {
                    perms = {
                        ['BossMenuAccess'] = true,
                        ['CreateProject'] = true,
                        ['DeleteProject'] = true,
                        ['ModifyProject'] = true,
                        ['ListProject'] = true,
                    },
                },
            }
        },
    }
}

Config.InteriorDesignerControls = {
    ['SAVE_PROJECT'] = {controlIndex = 73},                 -- Default: X
    ['FURNITURE_MENU'] = {controlIndex = 23},               -- Default: F
}



-- ███╗   ███╗ █████╗ ██████╗ ██╗  ██╗███████╗████████╗██████╗ ██╗      █████╗  ██████╗███████╗
-- ████╗ ████║██╔══██╗██╔══██╗██║ ██╔╝██╔════╝╚══██╔══╝██╔══██╗██║     ██╔══██╗██╔════╝██╔════╝
-- ██╔████╔██║███████║██████╔╝█████╔╝ █████╗     ██║   ██████╔╝██║     ███████║██║     █████╗  
-- ██║╚██╔╝██║██╔══██║██╔══██╗██╔═██╗ ██╔══╝     ██║   ██╔═══╝ ██║     ██╔══██║██║     ██╔══╝  
-- ██║ ╚═╝ ██║██║  ██║██║  ██║██║  ██╗███████╗   ██║   ██║     ███████╗██║  ██║╚██████╗███████╗
-- ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝   ╚═╝   ╚═╝     ╚══════╝╚═╝  ╚═╝ ╚═════╝╚══════╝
Config.Marketplace = {
    Enabled = true, -- Enables or disables the marketplace system
    AllowTransactionFromMenu = true, -- Allow transactions directly from the property management menu

    ShowSecondaryMarketOnly = false, -- Show only the secondary market (properties for sale by players)

    IncludeMotelRooms = false, -- Include motel rooms in the marketplace listings
    IncludeBuldingApartments = false, -- Include building apartments in the marketplace listings

    Blip = Config.Blips['Marketplace'], -- Blip settings (see Config.Blips section)
    BlipCoords = vector3(-1082.89, -249.45, 43.98), -- Location of the blip on the map

    TargetCoords = vector4(-1083.08, -249.66, 44.02, 296.5), -- Target interaction coordinates (for targeting systems like ox_target)
    TargetSize = vec3(2.0, 1.6, 1.8), -- Target zone dimensions (length, width, height)

    Ped = { -- NPC configuration for visual immersion
        Model = 'ig_drfriedlander', -- Model of the NPC ped
        Coords = vector4(-1082.72, -249.5, 43.02, 296.32), -- Position and heading of the ped
        Animation = {'switch@michael@sitting', 'idle'} -- Animation dictionary and name used by the ped
    },
}



-- ███████╗██╗   ██╗██████╗ ███╗   ██╗██╗████████╗██╗   ██╗██████╗ ███████╗
-- ██╔════╝██║   ██║██╔══██╗████╗  ██║██║╚══██╔══╝██║   ██║██╔══██╗██╔════╝
-- █████╗  ██║   ██║██████╔╝██╔██╗ ██║██║   ██║   ██║   ██║██████╔╝█████╗  
-- ██╔══╝  ██║   ██║██╔══██╗██║╚██╗██║██║   ██║   ██║   ██║██╔══██╗██╔══╝  
-- ██║     ╚██████╔╝██║  ██║██║ ╚████║██║   ██║   ╚██████╔╝██║  ██║███████╗
-- ╚═╝      ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝╚══════╝
Config.HousingFurniture = {
    Command = 'furniture',                     -- Command to open the property furniture menu
    Key = nil,                                 -- Keyboard shortcut to open the furniture menu
    Description = 'Property furniture menu',   -- Description shown in keybind hint
}

---@class RequirePurchaseFurniture<boolean>
-- true: Players must order furniture before placement
-- false: Furniture can be placed without ordering
Config.RequirePurchaseFurniture = true

---@class AllowOrderingFurnitureInProperty<boolean>
-- true: Players can order furniture directly from the property management menu
-- false: Players must go to IDEA to order furniture
Config.AllowOrderingFurnitureInProperty = true

---@class FurnitureSellPercentage<number>
-- Percentage (%) refunded when selling furniture through the menu
Config.FurnitureSellPercentage = 80

---@class FurnitureLimit<number>
-- Default maximum number of placed furniture per property without upgrades
Config.FurnitureLimit = 10

---@class FurnitureRequireLineOfSight<boolean>
-- Enables line-of-sight check for furniture interactions (non-target mode only).
-- Prevents interacting with furniture through walls or obstacles.
-- Note: may slightly increase resmon when many interactable furniture objects are nearby.
Config.FurnitureRequireLineOfSight = false

---@class RemoveFurnitureOnRentEnd<boolean>
-- Remove all furniture placed by tenant when the rent ends
Config.RemoveFurnitureOnRentEnd = true

---@class FurnitureLimitPerProperty<table>
-- Limit the maximum number of furniture by model name.
Config.FurnitureLimitPerProperty = {
    ['p_v_43_safe_s'] = 3,  -- Maximum of 3 safes per house
}

Config.FurnitureControls = {
    ['CLOSE'] = {controlIndex = 202},           -- Default: BACKSPACE / ESC

    ['ACCEPT'] = {controlIndex = 201},          -- Default: Mouse Left
    ['UP'] = {controlIndex = 172},              -- Default: Arrow Up
    ['DOWN'] = {controlIndex = 173},            -- Default: Arrow Down
    ['ROTATE_LEFT'] = {controlIndex = 181},     -- Default: Scroll UP
    ['ROTATE_RIGHT'] = {controlIndex = 180},    -- Default: Scroll Down
    ['SPEED_DOWN'] = {controlIndex = 210},      -- Default: Left CTRL

    ['DELETE'] = {controlIndex = 178},          -- Default: DEL

    ['SNAP_TO_GROUND'] = {controlIndex = 19},   -- Default: Left ALT
    ['ENABLE_CURSOR'] = {controlIndex = 238},   -- Default: PPM
    ['CHANGE_MODE'] = {controlIndex = 58},      -- Default: G

    ['CLEAN_MOP'] = {controlIndex = 58},        -- Default: G
    ['MOWER_EXIT'] = {controlIndex = 73},       -- Default: X
    
    ['GIZMO_RELATIVE'] = {control = 'V'},
    ['GIZMO_TRANSLATION'] = {control = 'X'},
    ['GIZMO_ROTATION'] = {control = 'C'},
}

Config.FurnitureSettings = {
    HeightSpeed = 0.01,         -- Default height movement speed
    HeightSpeedSlow = 0.005,    -- Slower height movement speed (when holding CTRL)
    RotateSpeed = 5.0,          -- Default rotation speed
    RotateSpeedSlow = 1.0,      -- Slower rotation speed (when holding CTRL)

    CancelOutsideZone = true,   -- Cancel furniture placement if gizmo camera is outside the property zone, shell or ipl
    GizmoMaxDistanceIPL = 50.0, -- Maximum distance from player position to gizmo camera
    GizmoMaxMarginShell = 2.5,  -- Maximum margin from shell size to gizmo camera position
}

---@class Cameras<table>
-- List of object models recognized as security cameras, allowing interaction or placement
Config.Cameras = {
    ['prop_cctv_cam_04c'] = true,
    ['prop_cctv_cam_05a'] = true,
    ['prop_cctv_cam_06a'] = true,
}



-- ██████╗ ██╗██████╗ ████████╗     █████╗ ███╗   ██╗██████╗      ██████╗██╗     ███████╗ █████╗ ███╗   ██╗██╗███╗   ██╗ ██████╗ 
-- ██╔══██╗██║██╔══██╗╚══██╔══╝    ██╔══██╗████╗  ██║██╔══██╗    ██╔════╝██║     ██╔════╝██╔══██╗████╗  ██║██║████╗  ██║██╔════╝ 
-- ██║  ██║██║██████╔╝   ██║       ███████║██╔██╗ ██║██║  ██║    ██║     ██║     █████╗  ███████║██╔██╗ ██║██║██╔██╗ ██║██║  ███╗
-- ██║  ██║██║██╔══██╗   ██║       ██╔══██║██║╚██╗██║██║  ██║    ██║     ██║     ██╔══╝  ██╔══██║██║╚██╗██║██║██║╚██╗██║██║   ██║
-- ██████╔╝██║██║  ██║   ██║       ██║  ██║██║ ╚████║██████╔╝    ╚██████╗███████╗███████╗██║  ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
-- ╚═════╝ ╚═╝╚═╝  ╚═╝   ╚═╝       ╚═╝  ╚═╝╚═╝  ╚═══╝╚═════╝      ╚═════╝╚══════╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝╚═╝  ╚═══╝ ╚═════╝ 
---@class DirtSystem<boolean>
-- Set to `false` to completely disable the generation and operation of the dirt system.
Config.DirtSystem = true

---@class DirtSlipping<boolean>
-- Determines whether the player can slip on dirt.
-- When set to `true`, there is a chance of slipping (ragdoll) when entering a dirt area.
Config.DirtSlipping = true

---@class DirtSlippingChance<number>
-- Probability of slipping on dirt (in percent).
Config.DirtSlippingChance = 60 -- % chance of slipping (0 – 100)

---@class MaxDirtPerProperty<number>
-- The maximum number of stains/dirt that can exist on a single property at the same time.
-- Once the limit is reached, new stains will not be generated until the old ones are removed.
Config.MaxDirtPerProperty = 8

---@class DirtSpawnChance<number>
-- The chance (in percent) of a new dirt spot appearing during the generation cycle.
-- A value of 80 means that in 80% of cases, the system will attempt to create a new dirt object.
Config.DirtSpawnChance = 80

---@class DirtSpawnTimeout<number>
-- The time interval (in milliseconds) between successive attempts to generate noise.
-- The system randomly selects a value from the specified range — e.g., from 30s to 80s.
Config.DirtSpawnTimeout = {30000, 80000}

---@class DirtObjects<table>
-- List of available dirt models and their spawn status.
-- When spawn = true, the model can be randomly generated by the system.
Config.DirtObjects = {
    --[[
        💡 Tip: for greater realism, we recommend leaving only the vms_dirtfootsteps, vms_dirtmud1, vms_dirtmud2, vms_dirtmud3, and vms_dirtmud4 models active,
        and generating additional effects (e.g., spilled drinks) manually via export or event from the documentation in systems such as food and drink.
    ]]
    {model = 'vms_dirtfootsteps',   spawn = true},
    {model = 'vms_coffeestain1',    spawn = false},
    {model = 'vms_coffeestain2',    spawn = false},
    {model = 'vms_crumbs1',         spawn = false},
    {model = 'vms_crumbs2',         spawn = false},
    {model = 'vms_dirtmud1',        spawn = true},
    {model = 'vms_dirtmud2',        spawn = true},
    {model = 'vms_dirtmud3',        spawn = true},
    {model = 'vms_dirtmud4',        spawn = true},
    {model = 'vms_ketchupspill',    spawn = false},
    {model = 'vms_liquidspill1',    spawn = false},
    {model = 'vms_liquidspill2',    spawn = false},
    {model = 'vms_liquidspill3',    spawn = false},
    {model = 'vms_liquidspill4',    spawn = false},
    {model = 'vms_mustardspill',    spawn = false},
    {model = 'vms_oilspill1',       spawn = false},
    {model = 'vms_oilspill2',       spawn = false},
    {model = 'vms_sodaspill1',      spawn = false},
    {model = 'vms_winestain1',      spawn = false},
}



--  ██████╗ ██████╗  █████╗ ███████╗███████╗    ███╗   ███╗ ██████╗ ██╗    ██╗██╗███╗   ██╗ ██████╗ 
-- ██╔════╝ ██╔══██╗██╔══██╗██╔════╝██╔════╝    ████╗ ████║██╔═══██╗██║    ██║██║████╗  ██║██╔════╝ 
-- ██║  ███╗██████╔╝███████║███████╗███████╗    ██╔████╔██║██║   ██║██║ █╗ ██║██║██╔██╗ ██║██║  ███╗
-- ██║   ██║██╔══██╗██╔══██║╚════██║╚════██║    ██║╚██╔╝██║██║   ██║██║███╗██║██║██║╚██╗██║██║   ██║
-- ╚██████╔╝██║  ██║██║  ██║███████║███████║    ██║ ╚═╝ ██║╚██████╔╝╚███╔███╔╝██║██║ ╚████║╚██████╔╝
--  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝╚══════╝    ╚═╝     ╚═╝ ╚═════╝  ╚══╝╚══╝ ╚═╝╚═╝  ╚═══╝ ╚═════╝ 
---@class GrassSystem<boolean>
-- Enables or disables the entire grass system.
-- Set to `false` to completely disable grass generation, growth logic and related interactions.
Config.GrassSystem = true

---@class GrassObjects<table>
-- Defines available grass prop sets used by the grass system.
-- Each index represents a visual grass variation.
-- Each variation contains multiple growth stages (levels).
Config.GrassObjects = {
    [1] = {
        'vms_grass_a_1', -- LVL 1
        'vms_grass_a_2', -- LVL 2
        'vms_grass_a_3', -- LVL 3
        'vms_grass_a_4', -- LVL 4
        'vms_grass_a_5', -- LVL 5
    },
    [2] = {
        'vms_grass_b_1', -- LVL 1
        'vms_grass_b_2', -- LVL 2
        'vms_grass_b_3', -- LVL 3
        'vms_grass_b_4', -- LVL 4
        'vms_grass_b_5', -- LVL 5
    }
}


---@class GrassLimit<number>
-- Maximum amount of grass objects allowed per property.
--
-- We do NOT recommend setting a value higher than 50.
-- Higher values may cause performance issues on large properties due to increased object count and grass growth checks.
--
-- * Increasing this limit is done at your own risk.
Config.GrassLimit = 50

---@class GrassLevels<table>
-- Defines grass growth stages and the time required to advance to the next level.
-- All times are defined in SECONDS.
--
-- The system is timestamp-based:
-- - Grass growth continues even if the server is offline.
-- - Missed time is automatically calculated when the property is loaded.
--
-- With the default configuration, grass will fully grow
-- from level 0 to level 5 in approximately 8 real-life days.
Config.GrassLevels = {
    [0] = { growthTime = 12 * 60 * 60 },    -- Level 0 -> Level 1  (12 Hours)
    [1] = { growthTime = 24 * 60 * 60 },    -- Level 1 -> Level 2  (24 Hours)
    [2] = { growthTime = 36 * 60 * 60 },    -- Level 2 -> Level 3  (36 Hours)
    [3] = { growthTime = 48 * 60 * 60 },    -- Level 3 -> Level 4  (48 Hours)
    [4] = { growthTime = 72 * 60 * 60 },    -- Level 4 -> Level 5  (72 Hours)
    [5] = { growthTime = nil },             -- Level 5 -> Maximum growth (no further progression)
}

---@class GrassLawnMower<table>
-- Controls player movement restrictions while using the lawn mower furniture.
Config.GrassLawnMower = {
    DisableJump = true, -- Prevents jumping while mowing
    DisableRun  = true, -- Prevents sprinting while mowing
}

---@class GrassMowerVehicles<table>
-- Vehicle models that can also be used to mow grass by simply driving over the property terrain.
Config.GrassMowerVehicles = {
    [joaat('mower')] = true,
}



-- ██████╗ ███████╗██╗     ██╗██╗   ██╗███████╗██████╗ ██╗███████╗███████╗
-- ██╔══██╗██╔════╝██║     ██║██║   ██║██╔════╝██╔══██╗██║██╔════╝██╔════╝
-- ██║  ██║█████╗  ██║     ██║██║   ██║█████╗  ██████╔╝██║█████╗  ███████╗
-- ██║  ██║██╔══╝  ██║     ██║╚██╗ ██╔╝██╔══╝  ██╔══██╗██║██╔══╝  ╚════██║
-- ██████╔╝███████╗███████╗██║ ╚████╔╝ ███████╗██║  ██║██║███████╗███████║
-- ╚═════╝ ╚══════╝╚══════╝╚═╝  ╚═══╝  ╚══════╝╚═╝  ╚═╝╚═╝╚══════╝╚══════╝
--[[ 
🔧 DELIVERY CONFIGURATION (QUICK SETUP)

Choose how furniture delivery works:

✔ Instant delivery
- Enable only "instant"
- Set Config.Delivery.Object = false

✔ Automatic delivery (no boxes)
- Enable "standard" or "express"
- Set Config.Delivery.Object = false

✔ Realistic delivery (boxes to unpack)
- Enable "standard" or "express"
- Set Config.Delivery.Object to a prop (e.g. 'prop_boxpile_01a')

⚠️ You don't need to change anything else for basic setups.
]]

Config.Delivery = {}

---@class Delivery.Type<number>
-- 1: Script Delivery - Furniture arrives after a set delay as boxes on the property.
-- 2: Player Delivery - IDEA store employees must deliver the ordered furniture to the players homes.
Config.Delivery.Type = 1

---@class Delivery.Object<string | boolean>
-- If you don't want to use realistic home delivery boxes, set this to false,
-- then the furniture will go straight into the menu without having to be unpacked
Config.Delivery.Object = 'prop_boxpile_01a'


Config.Delivery.SizeThresholds = {
    { max = 5,  size = '1' },
    { max = 10, size = '2' },
    { max = math.huge, size = '3' },
}

Config.Delivery.Sizes = {
    ['1'] = {
        price = 2000,
        time = 30 * 60 * 1000
    },
    ['2'] = {
        price = 2000,
        time = 60 * 60 * 1000
    },
    ['3'] = {
        price = 2000,
        time = 180 * 60 * 1000
    },
}

Config.Delivery.Options = {
    ['system'] = {  -- with Config.Delivery.Type = 1
        ["instant"] = {
            enabled = true,
            label = 'Instant',
            mode = 'instant',
            priceMultiplier = 2.5,
            allowedSizes = {'1', '2'}       -- Allowed sizes from Config.Delivery.Sizes
        },
        ["standard"] = {
            enabled = true,
            label = 'Standard',
            mode = 'auto',
            priceMultiplier = 1.0,
            timeMultiplier = 1.0,
            allowedSizes = {'1', '2', '3'}  -- Allowed sizes from Config.Delivery.Sizes
        },
        ["express"] = {
            enabled = true,
            label = 'Express',
            mode = 'auto',
            priceMultiplier = 1.75,
            timeMultiplier = 0.5,
            allowedSizes = {'1', '2', '3'}  -- Allowed sizes from Config.Delivery.Sizes
        },
    },
    ['job'] = {     -- with Config.Delivery.Type = 2
        ["standard"] = {
            enabled = true,
            label = 'Standard',
            priceMultiplier = 0.8,
            allowedSizes = {'1', '2', '3'}  -- Allowed sizes from Config.Delivery.Sizes
        },
        ["priority"] = {
            enabled = true,
            label = 'Priority',
            priceMultiplier = 1.2,
            allowedSizes = {'1', '2', '3'}  -- Allowed sizes from Config.Delivery.Sizes
        }
    },
}



-- ██╗   ██╗██████╗  ██████╗ ██████╗  █████╗ ██████╗ ███████╗███████╗
-- ██║   ██║██╔══██╗██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝
-- ██║   ██║██████╔╝██║  ███╗██████╔╝███████║██║  ██║█████╗  ███████╗
-- ██║   ██║██╔═══╝ ██║   ██║██╔══██╗██╔══██║██║  ██║██╔══╝  ╚════██║
-- ╚██████╔╝██║     ╚██████╔╝██║  ██║██║  ██║██████╔╝███████╗███████║
--  ╚═════╝ ╚═╝      ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚═════╝ ╚══════╝╚══════╝
---@class HousingUpgrades<table>
-- Property upgrades available for players to purchase through the management menu.
-- Each upgrade enhances property features like security or furniture capacity.
Config.HousingUpgrades = {
    ['alarm'] = {
        metadata = 'alarm',
        price = 60000,

        icon = 'fa-solid fa-car-on',
        label = "Alarm",

        description = [[
            An advanced alarm system that immediately triggers an alert when unauthorized entry is detected.
            Highly effective against break-ins, especially when you're offline.
        ]],
    },

    ['anti_burglary_doors'] = {
        metadata = 'antiBurglaryDoors',
        price = 80000,

        icon = 'fa-solid fa-door-closed',
        label = "Anti-Burglary Doors",

        description = [[
            Reinforced doors designed to resist forced entry attempts.
            Adds an extra layer of security and delays break-in time significantly.
        ]],
    },

    ['smart_peephole'] = {
        metadata = 'smartPeephole',
        price = 80000,

        icon = 'fa-solid fa-door-closed',
        label = "Smart Peephole",

        description = [[
            Replace your regular peephole with an electronic one and gain a wider field of view and greater control over what is happening outside your door.
        ]],
    },

    ['furniture_limit'] = {
        metadata = 'furnitureLimit',

        icon = 'fa-solid fa-couch',
        label = "Furniture Limit",

        description = ([[
            Upgrade to be able to have more furniture in your property.
            Your current furniture limit is %s - after this upgrade, it will increase to 30.
        ]]):format(Config.FurnitureLimit),

        levels = {
            ['1'] = {
                limit = 30,     -- Furniture Limit with LVL 1
                price = 60000,
                description = [[
                    Increase your furniture limit from 30 to 50.
                    Useful if you're running out of space in your current setup.
                ]],
            },
            ['2'] = {
                limit = 50,
                price = 80000,
                description = [[
                    Increase your furniture limit from 50 to 100.
                    Great for heavily customized interiors.
                ]],
            },
            ['3'] = {
                limit = 100,
                price = 100000,
                description = [[
                    Increase your furniture limit from 100 to 150.
                    Perfect for large homes with detailed furnishing needs.
                ]],
            },
            ['4'] = {
                limit = 150,
                price = 120000,
                description = [[
                    You've reached the maximum furniture level.
                    No further upgrades are available.
                ]],
            },
        },
    },

    ['doorbell'] = {
        metadata = 'doorbell',

        icon = 'fa-solid fa-bell',
        label = "Doorbell Sound",

        description = [[
            Customize the sound of your doorbell to make it uniquely yours.
            Choose from a variety of tones to suit your style.
        ]],

        types = {
            ['default'] = { -- For the default one, do not rename key!
                label = 'Default Bell',
                price = 0,
                audioFile = 'doorbell', -- Supported only .ogg
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
            ['cartoon'] = {
                label = 'Cartoon Melodic Bell',
                price = 3000,
                audioFile = 'doorbell-cartoon',
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
            ['christmas'] = {
                label = 'Christmas Bell',
                price = 3000,
                audioFile = 'doorbell-christmas',
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
            ['futuristic'] = {
                label = 'Futuristic Bell',
                price = 3000,
                audioFile = 'doorbell-futuristic',
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
            ['modern'] = {
                label = 'Modern Bell',
                price = 3000,
                audioFile = 'doorbell-modern',
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
            ['discrete'] = {
                label = 'Discrete Bell',
                price = 3000,
                audioFile = 'doorbell-discrete',
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
            ['melodic'] = {
                label = 'Melodic Bell',
                price = 3000,
                audioFile = 'doorbell-melodic',
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
            ['buzzer'] = {
                label = 'Buzzer Bell',
                price = 3000,
                audioFile = 'doorbell-buzzer',
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
            ['classic'] = {
                label = 'Classic Bell',
                price = 3000,
                audioFile = 'doorbell-classic',
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
            ['classic2'] = {
                label = 'Classic Bell 2',
                price = 3000,
                audioFile = 'doorbell-classic2',
                audioVolume = {
                    inside = 0.1,       -- Volume level (0.0 to 1.0)
                    outside = 0.1,      -- Volume level (0.0 to 1.0)
                },
            },
        }
    }
}
```

{% endcode %}


# config.client.lua

Preview File Updated: v1.4.4 - 06/06/2026

{% code fullWidth="true" %}

```lua
CL = {}

-- ███╗   ██╗ ██████╗ ████████╗██╗███████╗██╗ ██████╗ █████╗ ████████╗██╗ ██████╗ ███╗   ██╗███████╗
-- ████╗  ██║██╔═══██╗╚══██╔══╝██║██╔════╝██║██╔════╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗  ██║██╔════╝
-- ██╔██╗ ██║██║   ██║   ██║   ██║█████╗  ██║██║     ███████║   ██║   ██║██║   ██║██╔██╗ ██║███████╗
-- ██║╚██╗██║██║   ██║   ██║   ██║██╔══╝  ██║██║     ██╔══██║   ██║   ██║██║   ██║██║╚██╗██║╚════██║
-- ██║ ╚████║╚██████╔╝   ██║   ██║██║     ██║╚██████╗██║  ██║   ██║   ██║╚██████╔╝██║ ╚████║███████║
-- ╚═╝  ╚═══╝ ╚═════╝    ╚═╝   ╚═╝╚═╝     ╚═╝ ╚═════╝╚═╝  ╚═╝   ╚═╝   ╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝
CL.Notification = function(message, time, type)
    if type == "success" then
        if GetResourceState("vms_notify") == 'started' then
            exports['vms_notify']:Notification("", message, time, "#36f230", "fa-solid fa-house")
        else
            TriggerEvent('esx:showNotification', message)
            TriggerEvent('QBCore:Notify', message, 'success', time)
        end
    elseif type == "error" then
        if GetResourceState("vms_notify") == 'started' then
            exports['vms_notify']:Notification("", message, time, "#f23030", "fa-solid fa-house")
        else
            TriggerEvent('esx:showNotification', message)
            TriggerEvent('QBCore:Notify', message, 'error', time)
        end
    elseif type == "info" then
        if GetResourceState("vms_notify") == 'started' then
            exports['vms_notify']:Notification("", message, time, "#4287f5", "fa-solid fa-house")
        else
            TriggerEvent('esx:showNotification', message)
            TriggerEvent('QBCore:Notify', message, 'primary', time)
        end
    elseif type == "dispatch" then
        if GetResourceState("vms_notify") == 'started' then
            exports['vms_notify']:Notification("", message, time, "#429aff", "fa-solid fa-person-through-window")
        else
            TriggerEvent('esx:showNotification', message)
            TriggerEvent('QBCore:Notify', message, 'primary', time)
        end
    end
end



-- ██╗  ██╗██╗   ██╗██████╗ 
-- ██║  ██║██║   ██║██╔══██╗
-- ███████║██║   ██║██║  ██║
-- ██╔══██║██║   ██║██║  ██║
-- ██║  ██║╚██████╔╝██████╔╝
-- ╚═╝  ╚═╝ ╚═════╝ ╚═════╝ 
CL.Hud = {
    Enable = function()
        if GetResourceState('vms_hud') ~= 'missing' then
            exports['vms_hud']:Display(true)
        end
    end,
    Disable = function()
        if GetResourceState('vms_hud') ~= 'missing' then
            exports['vms_hud']:Display(false)
        end
    end
}



-- ████████╗ █████╗ ██████╗  ██████╗ ███████╗████████╗
-- ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝
--    ██║   ███████║██████╔╝██║  ███╗█████╗     ██║   
--    ██║   ██╔══██║██╔══██╗██║   ██║██╔══╝     ██║   
--    ██║   ██║  ██║██║  ██║╚██████╔╝███████╗   ██║   
--    ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝ ╚══════╝   ╚═╝   
CL.Target = function(action, data)
    if action == 'zone' then
        if Config.TargetResource == 'ox_target' then
            local options = {}
            
            if data.options and next(data.options) then
                for k, v in pairs(data.options) do
                    options[#options + 1] = {
                        name = v.name,
                        icon = v.icon,
                        label = v.label,
                        onSelect = v.action,
                        canInteract = v.canInteract,
                        distance = v.distance or 1.2,
                        items = v.requiredItem,
                        groups = v.jobs,
                    }
                end
            end

            return exports['ox_target']:addBoxZone({
                coords = vec(data.coords.x, data.coords.y, data.coords.z-0.5),
                size = data.size,
                debug = Config.DebugTarget,
                useZ = true,
                rotation = data.rotation or 0.0,
                options = options
            })
        elseif Config.TargetResource == 'qb-target' then
            local uniqueName = 'vms_housing-'..math.random(1000000, 9999999999)
            local options = {}

            if data.options and next(data.options) then
                for k, v in pairs(data.options) do
                    local id = #options + 1
                    options[id] = {
                        num = id,
                        icon = v.icon,
                        label = v.label,
                        action = v.action,
                        canInteract = v.canInteract,
                        distance = v.distance or 1.2,
                        item = v.requiredItem,
                        job = v.jobs,
                    }
                    if v.jobs and type(v.jobs) == 'table' then
                        options[id].job = {}
                        for _, job in ipairs(v.jobs) do
                            options[id].job[job] = 0
                        end
                    end
                end
            end

            exports['qb-target']:AddBoxZone(
                uniqueName,
                vec(data.coords.x, data.coords.y, data.coords.z),
                data.size and data.size.x or 1.2,
                data.size and data.size.y or 1.2,
                {
                    name = uniqueName,
                    heading = data.rotation and data.rotation - 90.0 or 0.0,
                    debugPoly = false,
                    minZ = data.coords.z - (data.size and data.size.y or 1.2),
                    maxZ = data.coords.z + (data.size and data.size.y or 1.2),
                },
                {
                    options = options,
                    distance = 3.5,
                }
            )

            return uniqueName
        else
            warn('You need to prepare CL.Target for the target system')
        end
        
    elseif action == 'remove-zone' then
        if Config.TargetResource == 'ox_target' then
            exports['ox_target']:removeZone(data)
        elseif Config.TargetResource == 'qb-target' then
            exports['qb-target']:RemoveZone(data)
        else
            warn('You need to prepare CL.Target for the target system')
        end
        
    elseif action == 'entity' then
        if Config.TargetResource == 'ox_target' then
            local options = {}
            
            if data.options and next(data.options) then
                for k, v in pairs(data.options) do
                    local icon = v.icon or 'fa-solid fa-bars' -- Default Icon

                    if v.interactableName then
                        if v.interactableName == 'delivery' then
                            icon = 'fa-solid fa-box-open'
                        elseif v.interactableName == 'storage' then
                            icon = 'fa-solid fa-box'
                        elseif v.interactableName == 'wardrobe' then
                            icon = 'fa-solid fa-shirt'
                        elseif v.interactableName == 'device' then
                            icon = 'fa-solid fa-laptop'
                        elseif v.interactableName == 'door' then
                            icon = 'fa-solid fa-door-closed'
                        elseif v.interactableName == 'mop' then
                            icon = 'fa-solid fa-broom'
                        elseif v.interactableName == 'mower' then
                            icon = 'fa-solid fa-seedling'
                        elseif v.interactableName == 'power-box' then
                            if v.name == 'furniture-power-box-open' then
                                icon = 'fa-solid fa-bolt-lightning'
                            elseif v.name == 'furniture-power-box-close' then
                                icon = 'fa-solid fa-bolt-lightning'
                            elseif v.name == 'furniture-power-box-turn-off' then
                                icon = 'fa-solid fa-plug-circle-exclamation'
                            elseif v.name == 'furniture-power-box-destroy' then
                                icon = 'fa-solid fa-hammer'
                            elseif v.name == 'furniture-power-box-turn-on' then
                                icon = 'fa-solid fa-plug'
                            elseif v.name == 'furniture-power-box-repair' then
                                icon = 'fa-solid fa-wrench'
                            end
                        elseif v.interactableName == 'check-idea-offer' then
                            icon = 'fa-solid fa-eye'
                        end
                    end
                    
                    options[#options + 1] = {
                        name = v.name,
                        icon = icon,
                        label = v.label,
                        distance = v.distance or 1.2,
                        items = v.requiredItem,
                        groups = v.jobs,
                        onSelect = v.action,
                        canInteract = v.canInteract,
                    }
                end
            end

            exports['ox_target']:addLocalEntity(data.entity, options)
        elseif Config.TargetResource == 'qb-target' then
            local options = {}
            
            if data.options and next(data.options) then
                for k, v in pairs(data.options) do
                    local icon = v.icon or 'fa-solid fa-bars' -- Default Icon

                    if v.interactableName then
                        if v.interactableName == 'delivery' then
                            icon = 'fa-solid fa-box-open'
                        elseif v.interactableName == 'storage' then
                            icon = 'fa-solid fa-box'
                        elseif v.interactableName == 'wardrobe' then
                            icon = 'fa-solid fa-shirt'
                        elseif v.interactableName == 'device' then
                            icon = 'fa-solid fa-laptop'
                        elseif v.interactableName == 'door' then
                            icon = 'fa-solid fa-door-closed'
                        elseif v.interactableName == 'mop' then
                            icon = 'fa-solid fa-broom'
                        elseif v.interactableName == 'mower' then
                            icon = 'fa-solid fa-seedling'
                        elseif v.interactableName == 'power-box' then
                            if v.name == 'furniture-power-box-open' then
                                icon = 'fa-solid fa-bolt-lightning'
                            elseif v.name == 'furniture-power-box-close' then
                                icon = 'fa-solid fa-bolt-lightning'
                            elseif v.name == 'furniture-power-box-turn-off' then
                                icon = 'fa-solid fa-plug-circle-exclamation'
                            elseif v.name == 'furniture-power-box-destroy' then
                                icon = 'fa-solid fa-hammer'
                            elseif v.name == 'furniture-power-box-turn-on' then
                                icon = 'fa-solid fa-plug'
                            elseif v.name == 'furniture-power-box-repair' then
                                icon = 'fa-solid fa-wrench'
                            end
                        elseif v.interactableName == 'check-idea-offer' then
                            icon = 'fa-solid fa-eye'
                        elseif v.interactableName == 'checkout-idea' then
                            icon = 'fa-solid fa-basket-shopping'
                        elseif v.interactableName == 'orders-idea' then
                            icon = 'fa-solid fa-list'
                        end
                    end
                    
                    local id = #options + 1
                    options[id] = {
                        num = id,
                        icon = icon,
                        label = v.label,
                        distance = v.distance or 1.2,
                        item = v.requiredItem,
                        job = v.jobs,
                        action = v.action,
                        canInteract = v.canInteract,
                    }
                    if v.jobs and type(v.jobs) == 'table' then
                        options[id].job = {}
                        for _, job in ipairs(v.jobs) do
                            options[id].job[job] = 0
                        end
                    end
                end
            end
            
            exports['qb-target']:AddTargetEntity(data.entity, {
                options = options,
                distance = 1.2
            })
        else
            warn('You need to prepare CL.Target for the target system')
        end
        
    elseif action == 'remove-entity' then
        if Config.TargetResource == 'ox_target' then
            exports['ox_target']:removeLocalEntity(data)
        elseif Config.TargetResource == 'qb-target' then
            exports['qb-target']:RemoveTargetEntity(data)
        else
            warn('You need to prepare CL.Target for the target system')
        end

    end
end



-- ██████╗ ██████╗     ████████╗███████╗██╗  ██╗████████╗
-- ╚════██╗██╔══██╗    ╚══██╔══╝██╔════╝╚██╗██╔╝╚══██╔══╝
--  █████╔╝██║  ██║       ██║   █████╗   ╚███╔╝    ██║   
--  ╚═══██╗██║  ██║       ██║   ██╔══╝   ██╔██╗    ██║   
-- ██████╔╝██████╔╝       ██║   ███████╗██╔╝ ██╗   ██║   
-- ╚═════╝ ╚═════╝        ╚═╝   ╚══════╝╚═╝  ╚═╝   ╚═╝   
CL.DrawText3D = function(coords, text)
    if not Config.UseDrawText then return end
    
    local textScale = 0.55
    local camCoords = GetFinalRenderedCamCoord()
    local distance = #(coords.xyz - camCoords)
    local scale = (textScale / distance) * 2
    local fov = (1 / GetGameplayCamFov()) * 100
    scale = scale * fov
    
    SetTextScale(0.0 * scale, 0.55 * scale)
    SetTextFont(4)
    SetTextDropShadow()
    SetTextProportional(1)
    SetTextColour(255, 255, 255, 205)
    SetTextCentre(true)

    if Config.UseDrawTextBox then
        BeginTextCommandWidth("STRING")
        AddTextComponentString(text)
        local textWidth = EndTextCommandGetWidth(true) + (0.006 * scale * 2)
        
        SetDrawOrigin(coords.x, coords.y, coords.z, 0)
        DrawRect(0.0, 0.018 * scale, textWidth, 0.045 * scale, 11, 4, 20, 120)
    else
        SetDrawOrigin(coords.x, coords.y, coords.z, 0)
    end

    SetTextEntry("STRING")
    AddTextComponentString(text)
    DrawText(0.0, 0.0)

    ClearDrawOrigin()
end



-- ████████╗███████╗██╗  ██╗████████╗██╗   ██╗██╗
-- ╚══██╔══╝██╔════╝╚██╗██╔╝╚══██╔══╝██║   ██║██║
--    ██║   █████╗   ╚███╔╝    ██║   ██║   ██║██║
--    ██║   ██╔══╝   ██╔██╗    ██║   ██║   ██║██║
--    ██║   ███████╗██╔╝ ██╗   ██║   ╚██████╔╝██║
--    ╚═╝   ╚══════╝╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚═╝
CL.TextUI = {
    Open = function(text)
        if Config.TextUI == 'vms_notifyv2' then
            text = '~INPUT_CONTEXT~ ' .. text
            exports['vms_notifyv2']:ShowTextUI(text)
            exports['vms_notifyv2']:UpdateTextUI(text)
        elseif Config.TextUI == 'lation_ui' then
            exports.lation_ui:showText({
                description = text,
                keybind = 'E',
            })
        elseif Config.TextUI == 'wasabi_uikit' then
            exports.wasabi_uikit:OpenTextUI(text, 'E', 'right', 'normal')
        elseif Config.TextUI == 'ox_lib' then
            text = '[E] ' .. text
            exports.ox_lib:showTextUI(text)
        end
    end,
    Close = function()
        if Config.TextUI == 'vms_notifyv2' then
            exports['vms_notifyv2']:HideTextUI()
        elseif Config.TextUI == 'lation_ui' then
            exports.lation_ui:hideText()
        elseif Config.TextUI == 'wasabi_uikit' then
            exports.wasabi_uikit:CloseTextUI()
        elseif Config.TextUI == 'ox_lib' then
            exports.ox_lib:hideTextUI()
        end
    end
}



-- ███╗   ███╗██╗███╗   ██╗██╗ ██████╗  █████╗ ███╗   ███╗███████╗███████╗
-- ████╗ ████║██║████╗  ██║██║██╔════╝ ██╔══██╗████╗ ████║██╔════╝██╔════╝
-- ██╔████╔██║██║██╔██╗ ██║██║██║  ███╗███████║██╔████╔██║█████╗  ███████╗
-- ██║╚██╔╝██║██║██║╚██╗██║██║██║   ██║██╔══██║██║╚██╔╝██║██╔══╝  ╚════██║
-- ██║ ╚═╝ ██║██║██║ ╚████║██║╚██████╔╝██║  ██║██║ ╚═╝ ██║███████╗███████║
-- ╚═╝     ╚═╝╚═╝╚═╝  ╚═══╝╚═╝ ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝╚══════╝
CL.Minigame = function(type, cb, data)
    isPlayingMinigame = true
    if type == 'lockpick' then
        if GetResourceState('t3_lockpick') ~= 'started' then
            warn('The required resource ^1t3_lockpick^7 was not found!')
            return cb(false)
        end

        local difficulty = {
            strength = 0.55,
            difficulty = 2,
            pins = 5
        }
        if data.antiBurglaryDoors then
            difficulty = {
                strength = 0.2,
                difficulty = 1,
                pins = 6
            }
        end

        local result = exports['t3_lockpick']:startLockpick(difficulty.strength, difficulty.difficulty, difficulty.pins)
        cb(result)

    elseif type == 'police_raid' then
        if GetResourceState('tgiann-skillbar') ~= 'started' then
            warn('The required resource ^1tgiann-skillbar^7 was not found!')
            return cb(false)
        end

        local finished = exports["tgiann-skillbar"]:taskBar(3000)
        cb(finished)

    elseif type == 'power_box_open' then
        library.PlayAnimation(PlayerPedId(), 'anim@veh@truck@squaddie@rds@enter_exit', 'd_open_out', 8.0, 8.0, 900, 1)
        Citizen.Wait(900)
        cb(true)

    elseif type == 'power_box_close' then
        library.PlayAnimation(PlayerPedId(), 'anim@scripted@freemode@ig18_wall_safe@right@female@', 'close', 8.0, 8.0, 1200, 1)
        Citizen.Wait(1200)
        cb(true)

    elseif type == 'power_box_turn_off' then
        if GetResourceState('bl_ui') ~= 'started' then
            warn('The required resource ^1bl_ui^7 was not found!')
            return cb(false)
        end
        
        library.PlayAnimation(PlayerPedId(), 'missmechanic', 'work_base', 8.0, 8.0, -1, 1)
        
        -- https://forum.cfx.re/t/bl-ui-v2/5273959
        local success = exports.bl_ui:RapidLines(1, 60, 10)

        Citizen.Wait(200)
        library.StopAnimation(PlayerPedId())
        cb(success)

    elseif type == 'power_box_destroy' then
        if GetResourceState('bl_ui') ~= 'started' then
            warn('The required resource ^1bl_ui^7 was not found!')
            return cb(false)
        end
        library.PlayAnimation(PlayerPedId(), 'missmechanic', 'work2_base', 8.0, 8.0, -1, 1)

        -- https://forum.cfx.re/t/bl-ui-v2/5273959
        local success = exports.bl_ui:MineSweeper(3, {
            grid = 6,               -- grid 6x6
            duration = 7500,        -- 7.5 sec to fail
            target = 8,             -- target you need to remember
            previewDuration = 2000  -- preview duration (time for red mines preview to hide)
        })

        Citizen.Wait(200)
        library.StopAnimation(PlayerPedId())
        cb(success)

    elseif type == 'power_box_turn_on' then
        if GetResourceState('bl_ui') ~= 'started' then
            warn('The required resource ^1bl_ui^7 was not found!')
            return cb(false)
        end
        library.PlayAnimation(PlayerPedId(), 'missmechanic', 'work_base', 8.0, 8.0, -1, 1)

        -- https://forum.cfx.re/t/bl-ui-v2/5273959
        local success = exports.bl_ui:RapidLines(1, 60, 10)
        
        Citizen.Wait(200)
        library.StopAnimation(PlayerPedId())
        cb(success)

    elseif type == 'power_box_repair' then
        if GetResourceState('bl_ui') ~= 'started' then
            warn('The required resource ^1bl_ui^7 was not found!')
            return cb(false)
        end
        library.PlayAnimation(PlayerPedId(), 'missmechanic', 'work2_base', 8.0, 8.0, -1, 1)

        -- https://forum.cfx.re/t/bl-ui-v2/5273959
        local success = exports.bl_ui:MineSweeper(3, {
            grid = 6,               -- grid 6x6
            duration = 7500,        -- 7.5 sec to fail
            target = 8,             -- target you need to remember
            previewDuration = 2000  -- preview duration (time for red mines preview to hide)
        })

        Citizen.Wait(200)
        library.StopAnimation(PlayerPedId())
        cb(success)

    end

    isPlayingMinigame = false
end



-- ███████╗██████╗  █████╗ ███╗   ███╗███████╗██╗    ██╗ ██████╗ ██████╗ ██╗  ██╗
-- ██╔════╝██╔══██╗██╔══██╗████╗ ████║██╔════╝██║    ██║██╔═══██╗██╔══██╗██║ ██╔╝
-- █████╗  ██████╔╝███████║██╔████╔██║█████╗  ██║ █╗ ██║██║   ██║██████╔╝█████╔╝ 
-- ██╔══╝  ██╔══██╗██╔══██║██║╚██╔╝██║██╔══╝  ██║███╗██║██║   ██║██╔══██╗██╔═██╗ 
-- ██║     ██║  ██║██║  ██║██║ ╚═╝ ██║███████╗╚███╔███╔╝╚██████╔╝██║  ██║██║  ██╗
-- ╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝ ╚══╝╚══╝  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
CL.IsPlayerLoaded = function(type)
    if Config.Core == "ESX" then
        return ESX.IsPlayerLoaded()
    elseif Config.Core == "QB-Core" then
        return CL.GetPlayerData() and CL.GetPlayerData().job ~= nil
    end
end

CL.GetPlayerData = function(type)
    if Config.Core == "ESX" then
        return ESX.GetPlayerData()
    elseif Config.Core == "QB-Core" then
        return QBCore.Functions.GetPlayerData()
    end
end

CL.GetPlayerIdentifier = function()
    if Config.Core == "ESX" then
        return PlayerData.identifier
    elseif Config.Core == "QB-Core" then
        return PlayerData.citizenid
    end
end

CL.GetPlayerCharacterName = function()
    if Config.Core == "ESX" then
        return PlayerData.firstName .. ' ' .. PlayerData.lastName
    elseif Config.Core == "QB-Core" then
        return PlayerData.charinfo.firstname .. ' ' .. PlayerData.charinfo.lastname
    end
end

CL.GetPlayerJob = function(type)
    if Config.Core == "ESX" and PlayerData.job then
        if type == "table" then
            return PlayerData.job
        end
        if type == "name" then
            return PlayerData.job.name
        end
        if type == "label" then
            return PlayerData.job.label
        end
        if type == "grade" then
            return PlayerData.job.grade
        end
        if type == "grade_name" then
            return PlayerData.job.grade_name
        end
    elseif Config.Core == "QB-Core" and PlayerData.job then
        if type == "table" then
            return PlayerData.job
        end
        if type == "name" then
            return PlayerData.job.name
        end
        if type == "label" then
            return PlayerData.job.label
        end
        if type == "grade" then
            return PlayerData.job.grade.level
        end
        if type == "grade_name" then
            return PlayerData.job.grade.name
        end
    end
    return nil
end

CL.GetPlayerGroup = function()
    if Config.Core == "ESX" then
        return PlayerData.group
    elseif Config.Core == "QB-Core" then
        return Group
    end
end

CL.GetClosestPlayers = function()
    if Config.Core == "ESX" then
        return ESX.Game.GetPlayersInArea(GetEntityCoords(PlayerPedId()), 5.0)
    elseif Config.Core == "QB-Core" then
        return QBCore.Functions.GetPlayersFromCoords(GetEntityCoords(PlayerPedId()), 5.0)
    end
end

CL.GetInventoryItem = function(name, count)
    if Config.Core == 'ESX' then
        local inventory = CL.GetPlayerData().inventory
        if inventory then
            for k, v in pairs(inventory) do
                if v and v.name == name then
                    return v.count >= count
                end
            end
        end
    elseif Config.Core == "QB-Core" then
        return QBCore.Functions.HasItem(name, count)
    end

    return false
end



-- ██╗   ██╗ █████╗ ██████╗ ██╗ █████╗ ██████╗ ██╗     ███████╗███████╗
-- ██║   ██║██╔══██╗██╔══██╗██║██╔══██╗██╔══██╗██║     ██╔════╝██╔════╝
-- ██║   ██║███████║██████╔╝██║███████║██████╔╝██║     █████╗  ███████╗
-- ╚██╗ ██╔╝██╔══██║██╔══██╗██║██╔══██║██╔══██╗██║     ██╔══╝  ╚════██║
--  ╚████╔╝ ██║  ██║██║  ██║██║██║  ██║██████╔╝███████╗███████╗███████║
--   ╚═══╝  ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝╚═╝  ╚═╝╚═════╝ ╚══════╝╚══════╝╚══════╝
CL.HandleAction = function(action)
    if action == 'enterInteriorPreview' then -- enterInteriorPreview: is called when the player enters the interior preview in the bid using the "Check Inside" button
        if Config.Inventory == 'ox_inventory' then
            LocalPlayer.state.invBusy = true -- Lock player inventory during preview
            LocalPlayer.state.canUseWeapons = false
        end
        if Config.Inventory == 'qb-inventory' then
            LocalPlayer.state:set('inv_busy', true, true) -- Lock player inventory during preview
        end
        if Config.Inventory == 'qs-inventory' then
            exports['qs-inventory']:setInventoryDisabled(true) -- Lock player inventory during preview
        end

    elseif action == 'exitInteriorPreview' then -- exitInteriorPreview: is called when the player exits the interior preview
        if Config.Inventory == 'ox_inventory' then
            LocalPlayer.state.invBusy = false -- Unlock the player's inventory after previewing
            LocalPlayer.state.canUseWeapons = true -- Unlock the player's inventory after previewing
        end
        if Config.Inventory == 'qb-inventory' then
            LocalPlayer.state:set('inv_busy', true, true) -- Unlock the player's inventory after previewing
        end
        if Config.Inventory == 'qs-inventory' then
            exports['qs-inventory']:setInventoryDisabled(false) -- Unlock the player's inventory after previewing
        end
        
    end
end

CL.CanEnterHouse = function()
    -- This function is triggered when a player tries to enter the property.
    -- You can return false to block access, for example if they are handcuffed or anything else

    if IsEntityDead(PlayerPedId()) then
        return false
    end

    return true
end

CL.CanExitHouse = function()
    -- This function is triggered when a player tries to exit the property.
    -- You can return false to block access, for example if they are handcuffed or anything else

    return true
end

CL.InteractableFurniture = function(model, type, furnitureId, metadata)
    if type == 'wardrobe' then
        OpenWardrobe()

    elseif type == 'storage' then
        OpenStorage(metadata)
        
    elseif type == 'safe' then
        OpenSafe(furnitureId, metadata)

    elseif type == 'device' then
        openManageMenu(nil, true)

    elseif type == 'mop' then
        Property:Mop(furnitureId)

    elseif type == 'mower' then
        Property:Mower(furnitureId)

    elseif type == 'sink' then
        local timeUsage = metadata.option.timeUsage or 7000
        local myPed = PlayerPedId()

        local playerCoords = library.CalcShellPosition(CurrentPropertyData.metadata.shellPosition, metadata.option.coords.player)
        SetEntityCoords(myPed, playerCoords.xyz)
        SetEntityHeading(myPed, metadata.option.coords.player.w)
        FreezeEntityPosition(myPed, true)
        Citizen.Wait(100)

        local particleCoords = library.CalcShellPosition(CurrentPropertyData.metadata.shellPosition, metadata.option.coords.particles)
        local particle = library.StartParticles('core', 'water_cannon_jet', particleCoords, vector3(-70.0, 180.0, 0.0), 0.5)
        library.PlayAnimation(myPed, 'missheist_agency3aig_23', 'urinal_sink_loop', 8.0, 8.0, timeUsage, 1)
        Citizen.Wait(timeUsage + 200)
        FreezeEntityPosition(myPed, false)
        library.StopParticles(particle)

    elseif type == 'sink_drink' then
        local timeUsage = metadata.option.timeUsage or 5000
        local myPed = PlayerPedId()

        local playerCoords = library.CalcShellPosition(CurrentPropertyData.metadata.shellPosition, metadata.option.coords.player)
        SetEntityCoords(myPed, playerCoords.xyz)
        SetEntityHeading(myPed, metadata.option.coords.player.w)
        FreezeEntityPosition(myPed, true)
        library.PlayAnimation(myPed, 'anim@scripted@player@fix_drink_juice@heeled@', 'drink_wheatgrass_stage1', 8.0, 4.0, timeUsage, 1, {
            'lts_prop_tumbler_01_s2', nil, false, {
                attachTo = myPed,
                boneIndex = GetPedBoneIndex(myPed, 57005),
                placement = {0.124000, 0.024000, -0.064000, -86.069855, 215.931122, -369.073578}
            }, true, false
        })
        Citizen.Wait(1250)

        local particleCoords = library.CalcShellPosition(CurrentPropertyData.metadata.shellPosition, metadata.option.coords.particles)
        local particle = library.StartParticles('core', 'water_cannon_jet', particleCoords, vector3(-70.0, 180.0, 0.0), 0.5)
        Citizen.Wait(2000)
        library.StopParticles(particle)
        Citizen.Wait(timeUsage - 3250)
        FreezeEntityPosition(myPed, false)
        library.StopAnimation(myPed)

    elseif type == 'shower' then
        local timeUsage = metadata.option.timeUsage or 10000
        local myPed = PlayerPedId()
        FreezeEntityPosition(myPed, true)

        local playerCoords = library.CalcShellPosition(CurrentPropertyData.metadata.shellPosition, metadata.option.coords.player)
        SetEntityCoords(myPed, playerCoords)
        SetEntityHeading(myPed, metadata.option.coords.player.w)
        Citizen.Wait(100)

        local particleCoords = library.CalcShellPosition(CurrentPropertyData.metadata.shellPosition, metadata.option.coords.particles)
        local particle = library.StartParticles('core', 'water_cannon_jet', particleCoords, vector3(-80.0, 180.0, 0.0), 0.7)
        library.PlayAnimation(myPed, 'mp_safehouseshower@female@', 'shower_idle_a', 8.0, 8.0, timeUsage, 1)
        Citizen.Wait(timeUsage + 200)
        FreezeEntityPosition(myPed, false)
        library.StopParticles(particle)

    end
end



--  ██████╗ ██╗  ██╗    ██╗███╗   ██╗██╗   ██╗███████╗███╗   ██╗████████╗ ██████╗ ██████╗ ██╗   ██╗    ███╗   ███╗███████╗████████╗ █████╗ ██████╗  █████╗ ████████╗ █████╗ 
-- ██╔═══██╗╚██╗██╔╝    ██║████╗  ██║██║   ██║██╔════╝████╗  ██║╚══██╔══╝██╔═══██╗██╔══██╗╚██╗ ██╔╝    ████╗ ████║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔══██╗
-- ██║   ██║ ╚███╔╝     ██║██╔██╗ ██║██║   ██║█████╗  ██╔██╗ ██║   ██║   ██║   ██║██████╔╝ ╚████╔╝     ██╔████╔██║█████╗     ██║   ███████║██║  ██║███████║   ██║   ███████║
-- ██║   ██║ ██╔██╗     ██║██║╚██╗██║╚██╗ ██╔╝██╔══╝  ██║╚██╗██║   ██║   ██║   ██║██╔══██╗  ╚██╔╝      ██║╚██╔╝██║██╔══╝     ██║   ██╔══██║██║  ██║██╔══██║   ██║   ██╔══██║
-- ╚██████╔╝██╔╝ ██╗    ██║██║ ╚████║ ╚████╔╝ ███████╗██║ ╚████║   ██║   ╚██████╔╝██║  ██║   ██║       ██║ ╚═╝ ██║███████╗   ██║   ██║  ██║██████╔╝██║  ██║   ██║   ██║  ██║
--  ╚═════╝ ╚═╝  ╚═╝    ╚═╝╚═╝  ╚═══╝  ╚═══╝  ╚══════╝╚═╝  ╚═══╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝   ╚═╝       ╚═╝     ╚═╝╚══════╝   ╚═╝   ╚═╝  ╚═╝╚═════╝ ╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝
Citizen.CreateThread(function()
    Citizen.Wait(500)
    if GetResourceState('ox_inventory') ~= 'missing' then
        exports.ox_inventory:displayMetadata({
            propertyId = 'Property',
            keySerialNumber = 'Serial',
        });
    end
end)
```

{% endcode %}


# config.server.lua

Preview File Updated: v1.4.4 - 06/06/2026

{% code fullWidth="true" %}

```lua
SV = {}

-- ██╗    ██╗███████╗██████╗ ██╗  ██╗ ██████╗  ██████╗ ██╗  ██╗███████╗
-- ██║    ██║██╔════╝██╔══██╗██║  ██║██╔═══██╗██╔═══██╗██║ ██╔╝██╔════╝
-- ██║ █╗ ██║█████╗  ██████╔╝███████║██║   ██║██║   ██║█████╔╝ ███████╗
-- ██║███╗██║██╔══╝  ██╔══██╗██╔══██║██║   ██║██║   ██║██╔═██╗ ╚════██║
-- ╚███╔███╔╝███████╗██████╔╝██║  ██║╚██████╔╝╚██████╔╝██║  ██╗███████║
--  ╚══╝╚══╝ ╚══════╝╚═════╝ ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚═╝  ╚═╝╚══════╝
SV.Webhook = function(data)
    if Config.Logs == 'fivemanage' then
        exports.fmsdk:LogMessage(
            "info",
            '[VMS Housing] ' .. data.title,
            data.metadata
        )
    else
        local token = Webhooks[data.id]
        if not token or token == "" then
            return
        end
        local embeds = {{
            ["title"] = data.title,
            ["type"] = "rich",
            ["description"] = data.description,
            ["color"] = 0,
            ["footer"] = {
                ["text"] = data.footer .. ' - ' .. os.date(),
            },
        }}
        PerformHttpRequest(token, function(err, text, headers) end, 'POST', json.encode({embeds = embeds}), {['Content-Type'] = 'application/json'})
    end
end



-- ███████╗██████╗  █████╗ ███╗   ███╗███████╗██╗    ██╗ ██████╗ ██████╗ ██╗  ██╗
-- ██╔════╝██╔══██╗██╔══██╗████╗ ████║██╔════╝██║    ██║██╔═══██╗██╔══██╗██║ ██╔╝
-- █████╗  ██████╔╝███████║██╔████╔██║█████╗  ██║ █╗ ██║██║   ██║██████╔╝█████╔╝ 
-- ██╔══╝  ██╔══██╗██╔══██║██║╚██╔╝██║██╔══╝  ██║███╗██║██║   ██║██╔══██╗██╔═██╗ 
-- ██║     ██║  ██║██║  ██║██║ ╚═╝ ██║███████╗╚███╔███╔╝╚██████╔╝██║  ██║██║  ██╗
-- ╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝ ╚══╝╚══╝  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
SV.GetPlayer = function(src)
    if Config.Core == "ESX" then
        return Core.GetPlayerFromId(src)
    elseif Config.Core == "QB-Core" then
        return Core.Functions.GetPlayer(src)
    end
end

SV.GetSource = function(xPlayer)
    if Config.Core == "ESX" then
        return xPlayer.source
    elseif Config.Core == "QB-Core" then
        return xPlayer.PlayerData.source
    end
end

SV.GetIdentifier = function(xPlayer)
    if Config.Core == "ESX" then
        return xPlayer.identifier
    elseif Config.Core == "QB-Core" then
        return xPlayer.PlayerData.citizenid
    end
end

SV.GetPlayerByIdentifier = function(identifier)
    if Config.Core == "ESX" then
        return Core.GetPlayerFromIdentifier(identifier)
    elseif Config.Core == "QB-Core" then
        return Core.Functions.GetPlayerByCitizenId(identifier)
    end
end

SV.GetCharacterName = function(xPlayer, offline)
    if Config.Core == "ESX" then
        if offline then
            local response = MySQL.query.await('SELECT `firstname`, `lastname` FROM `users` WHERE `identifier` = ?', {
                xPlayer.identifier
            })
    
            if response then
                local data = response[1]
                return data.firstname .. ' ' .. data.lastname
            end
        else
            return xPlayer.getName()
        end

    elseif Config.Core == "QB-Core" then
        if offline then
            local response = MySQL.query.await('SELECT `charinfo` FROM `players` WHERE `citizenid` = ?', {
                xPlayer.identifier
            })
    
            if response then
                local data = response[1]
                local charinfo = json.decode(data.charinfo)
                return charinfo.firstname .. ' ' .. charinfo.lastname
            end
        else
            return xPlayer.PlayerData.charinfo.firstname .. ' ' .. xPlayer.PlayerData.charinfo.lastname
        end

    end
end

SV.GetPlayerGroup = function(xPlayer)
    if Config.Core == "ESX" then
        return xPlayer.group
    elseif Config.Core == "QB-Core" then
        if GetResourceState('qbx_core') == 'started' then
            -- Sort your permissions from highest to lowest so that when it detects the highest role, the function returns the highest one!
            if IsPlayerAceAllowed(xPlayer.PlayerData.source, 'god') then
                return 'god'
            elseif IsPlayerAceAllowed(xPlayer.PlayerData.source, 'admin') then
                return 'admin'
            elseif IsPlayerAceAllowed(xPlayer.PlayerData.source, 'mod') then
                return 'mod'
            elseif IsPlayerAceAllowed(xPlayer.PlayerData.source, 'support') then
                return 'support'
            end
        else
            local permissions = Core.Functions.GetPermission(xPlayer.PlayerData.source)
    
            -- Sort your permissions from highest to lowest so that when it detects the highest role, the function returns the highest one!
            if permissions['god'] then
                return 'god'
            elseif permissions['admin'] then
                return 'admin'
            elseif permissions['mod'] then
                return 'mod'
            end
        end

        return nil
    end
end

SV.GetPlayerJob = function(xPlayer, type)
    if Config.Core == "ESX" and xPlayer.job then
        if type == "table" then
            return xPlayer.job
        end
        if type == "name" then
            return xPlayer.job.name
        end
        if type == "label" then
            return xPlayer.job.label
        end
        if type == "grade" then
            return xPlayer.job.grade
        end
        if type == "grade_name" then
            return xPlayer.job.grade_name
        end
    elseif Config.Core == "QB-Core" and xPlayer.PlayerData.job then
        if type == "table" then
            return xPlayer.PlayerData.job
        end
        if type == "name" then
            return xPlayer.PlayerData.job.name
        end
        if type == "label" then
            return xPlayer.PlayerData.job.label
        end
        if type == "grade" then
            return xPlayer.PlayerData.job.grade.level
        end
        if type == "grade_name" then
            return xPlayer.PlayerData.job.grade.name
        end
    end
    return nil
end

SV.GetJobsOnline = function(jobName)
    if Config.Core == "ESX" then
        return #Core.GetExtendedPlayers('job', jobName)

    elseif Config.Core == "QB-Core" then
        local players, count = Core.Functions.GetPlayersOnDuty(jobName)
        return count
        
    end
end

SV.RegisterCommand = function(command, groups, cb, help, suggestions)
    if Config.Core == "ESX" then
        local arguments = {}

        if suggestions then
            for k, v in pairs(suggestions) do
                table.insert(arguments, {
                    name = v.name,
                    help = v.label,
                    type = 'any'
                })
            end
        end
        
        Core.RegisterCommand(command, groups, function(xPlayer, args, showError)
            local src = SV.GetSource(xPlayer)
            cb(src, args)
        end, false, {
            help = help,
            arguments = arguments
        })
    elseif Config.Core == "QB-Core" then
        local arguments = {}

        if suggestions then
            for k, v in pairs(suggestions) do
                table.insert(arguments, {
                    name = v.name,
                    help = v.label
                })
            end
        end
        
        Core.Commands.Add(command, help, arguments, false, function(source, args)
            cb(source, args)
        end, groups)
    end
end


-- ███╗   ███╗ ██████╗ ███╗   ██╗███████╗██╗   ██╗
-- ████╗ ████║██╔═══██╗████╗  ██║██╔════╝╚██╗ ██╔╝
-- ██╔████╔██║██║   ██║██╔██╗ ██║█████╗   ╚████╔╝ 
-- ██║╚██╔╝██║██║   ██║██║╚██╗██║██╔══╝    ╚██╔╝  
-- ██║ ╚═╝ ██║╚██████╔╝██║ ╚████║███████╗   ██║   
-- ╚═╝     ╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝   ╚═╝   
SV.GetMoney = function(xPlayer, moneyType)
    if Config.Core == "ESX" then
        local moneyType = moneyType == 'cash' and 'money' or moneyType
        return xPlayer.getAccount(moneyType).money
    elseif Config.Core == "QB-Core" then
        return xPlayer.Functions.GetMoney(moneyType)
    end
end

---@class AddMoney
---@param reason string
---| 'automaticSale'
---| 'sellFurniture'
---| 'rentalBillPayment'
---| 'rentalContract'
---| 'salesContract'
---| 'salesMarketplace'
---| 'rentalMarketplace'
---| 'deliveredFurniture'
SV.AddMoney = function(xPlayer, moneyType, count, reason)
    if Config.Core == "ESX" then
        local moneyType = moneyType == 'cash' and 'money' or moneyType == 'dirty' and 'black_money' or moneyType
        xPlayer.addAccountMoney(moneyType, count)
    elseif Config.Core == "QB-Core" then
        xPlayer.Functions.AddMoney(moneyType, count)
    end
end

---@class RemoveMoney
---@param reason string
---| 'furniturePurchase'
---| 'purchaseProperty'
---| 'rentalProperty'
---| 'purchaseKey'
---| 'lockReplacement'
---| 'upgradeProperty'
---| 'serviceBillPayment'
---| 'rentalBillPayment'
---| 'rentalContract'
---| 'salesContract'
---| 'salesMarketplace'
---| 'rentalMarketplace'
SV.RemoveMoney = function(xPlayer, moneyType, count, reason)
    if Config.Core == "ESX" then
        local moneyType = moneyType == 'cash' and 'money' or moneyType
        xPlayer.removeAccountMoney(moneyType, count)
    elseif Config.Core == "QB-Core" then
        xPlayer.Functions.RemoveMoney(moneyType, count)
    end
end

---@class AddMoneyOffline
---@param reason string
---| 'rentalBillPayment'
---| 'salesMarketplace'
---| 'rentalMarketplace'
SV.AddMoneyOffline = function(identifier, moneyType, count, reason)
    if Config.Core == "ESX" then
        local moneyType = moneyType == 'cash' and 'money' or moneyType == 'dirty' and 'black_money' or moneyType

        local data = MySQL.query.await("SELECT accounts FROM `users` WHERE `identifier` = @identifier", {
            ['@identifier'] = identifier
        })
        if not data or not data[1] then return end

        local accounts = json.decode(data[1].accounts)
        if not accounts or not accounts[moneyType] then return end

        accounts[moneyType] = tonumber(accounts[moneyType]) + tonumber(count)
        local updatedAccountsJson = json.encode(accounts)
        MySQL.Async.execute('UPDATE `users` SET accounts = @accounts WHERE `identifier` = @identifier', {
            ['@accounts'] = updatedAccountsJson,
            ['@identifier'] = identifier
        })

    elseif Config.Core == "QB-Core" then
        local data = MySQL.query.await("SELECT money FROM `players` WHERE `citizenid` = @citizenid", {
            ['@citizenid'] = identifier
        })
        if not data or not data[1] then return end

        local accounts = json.decode(data[1].money)
        if not accounts or not accounts[moneyType] then return end

        accounts[moneyType] = tonumber(accounts[moneyType]) + tonumber(count)
        local updatedAccountsJson = json.encode(accounts)
        MySQL.Async.execute('UPDATE `players` SET money = @money WHERE `citizenid` = @citizenid', {
            ['@money'] = updatedAccountsJson,
            ['@citizenid'] = identifier
        })
    end
end



-- ██╗████████╗███████╗███╗   ███╗███████╗
-- ██║╚══██╔══╝██╔════╝████╗ ████║██╔════╝
-- ██║   ██║   █████╗  ██╔████╔██║███████╗
-- ██║   ██║   ██╔══╝  ██║╚██╔╝██║╚════██║
-- ██║   ██║   ███████╗██║ ╚═╝ ██║███████║
-- ╚═╝   ╚═╝   ╚══════╝╚═╝     ╚═╝╚══════╝
SV.RegisterUsableItem = function(name, cb)
    if RegisterUsableItem then -- The script looks for functions in ./integration/[inventory]/*
        RegisterUsableItem(name, cb)
    else
        if Config.Core == "ESX" then
            Core.RegisterUsableItem(name, function(src, itemName, itemData)
                cb(src, itemName, {metadata = itemData.metadata})
            end)
        elseif Config.Core == "QB-Core" then
            Core.Functions.CreateUseableItem(name, function(src, item)
                cb(src, item.name, {metadata = item.info})
            end)
        end
    end
end

SV.GetItem = function(src, xPlayer, name, data, search)
    if search == 'key' then
        if GetItem then -- The script looks for functions in ./integration/[inventory]/*
            return GetItem(src, xPlayer, 'house_key', data, search)
        end
    end
end

SV.GetItemCount = function(xPlayer, name)
    if Config.Core == "ESX" then
        return xPlayer.getInventoryItem(name).count
    elseif Config.Core == "QB-Core" then
        return xPlayer.Functions.GetItemByName(name) and xPlayer.Functions.GetItemByName(name).amount or 0
    end
end

SV.AddItem = function(src, xPlayer, name, count, metadata)
    if AddItem then -- The script looks for functions in ./integration/[inventory]/*
        AddItem(src, xPlayer, name, count, metadata)
    else
        if Config.Core == "ESX" then
            xPlayer.addInventoryItem(name, count)
        elseif Config.Core == "QB-Core" then
            xPlayer.Functions.AddItem(name, count)
        end
    end
end

SV.RemoveItem = function(src, xPlayer, name, count)
    if RemoveItem then -- The script looks for functions in ./integration/[inventory]/*
        RemoveItem(src, xPlayer, name, count)
    else
        if Config.Core == "ESX" then
            xPlayer.removeInventoryItem(name, count)
        elseif Config.Core == "QB-Core" then
            xPlayer.Functions.RemoveItem(name, count)
        end
    end
end



--  ██████╗██╗████████╗██╗   ██╗██╗  ██╗ █████╗ ██╗     ██╗     
-- ██╔════╝██║╚══██╔══╝╚██╗ ██╔╝██║  ██║██╔══██╗██║     ██║     
-- ██║     ██║   ██║    ╚████╔╝ ███████║███████║██║     ██║     
-- ██║     ██║   ██║     ╚██╔╝  ██╔══██║██╔══██║██║     ██║     
-- ╚██████╗██║   ██║      ██║   ██║  ██║██║  ██║███████╗███████╗
--  ╚═════╝╚═╝   ╚═╝      ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝╚══════╝
SV.AddTax = function(identifier, characterName, propertyName, propertyAddress, type, fromAmount)
    local isCityHall = GetResourceState('vms_cityhall') == 'started'
    if not isCityHall then return false end
    if not Config.CityHallTaxes then return false end

    local amount = 0
    local percentage = 0

    if type == 'property_purchase_tax' then
        if Config.CityHallTaxes.PropertyPurchase.Type == 'percentage' then
            amount = fromAmount
            percentage = Config.CityHallTaxes.PropertyPurchase.Value
        else
            amount = Config.CityHallTaxes.PropertyPurchase.Value
            percentage = 100
        end
        
        if amount >= 1 and percentage >= 1 then
            exports['vms_cityhall']:addPlayerCustomTaxToPay({
                identifier = identifier,
                characterName = characterName,
            }, amount, percentage, (Config.CityHallTaxes.PropertyPurchase.Title):format(propertyName, propertyAddress))
        end

    elseif type == 'property_resale_tax' then
        if Config.CityHallTaxes.PropertyResale.Type == 'percentage' then
            amount = fromAmount
            percentage = Config.CityHallTaxes.PropertyResale.Value
        else
            amount = Config.CityHallTaxes.PropertyResale.Value
            percentage = 100
        end

        if amount >= 1 and percentage >= 1 then
            exports['vms_cityhall']:addPlayerCustomTaxToPay({
                identifier = identifier,
                characterName = characterName,
            }, amount, percentage, (Config.CityHallTaxes.PropertyResale.Title):format(propertyName, propertyAddress))
        end

    elseif type == 'rental_income_tax' then
        if Config.CityHallTaxes.RentalIncome.Type == 'percentage' then
            amount = fromAmount
            percentage = Config.CityHallTaxes.RentalIncome.Value
        else
            amount = Config.CityHallTaxes.RentalIncome.Value
            percentage = 100
        end
        
        if amount >= 1 and percentage >= 1 then
            exports['vms_cityhall']:addPlayerCustomTaxToPay({
                identifier = identifier,
                characterName = characterName,
            }, amount, percentage, (Config.CityHallTaxes.RentalIncome.Title):format(propertyName, propertyAddress))
        end
        
    elseif type == 'property_cadastral_tax' then
        if Config.CityHallTaxes.PropertyCadastralTax.Type == 'percentage' then
            amount = fromAmount
            percentage = Config.CityHallTaxes.PropertyCadastralTax.Value
        else
            amount = Config.CityHallTaxes.PropertyCadastralTax.Value
            percentage = 100
        end
        
        if amount >= 1 and percentage >= 1 then
            exports['vms_cityhall']:addPlayerCustomTaxToPay({
                identifier = identifier,
                characterName = characterName,
            }, amount, percentage, (Config.CityHallTaxes.PropertyCadastralTax.Title):format(propertyName, propertyAddress))
        end

    elseif type == 'agency_rental_income_tax' then
        if Config.CityHallTaxes.AgencyRentalIncome.Type == 'percentage' then
            amount = fromAmount
            percentage = Config.CityHallTaxes.AgencyRentalIncome.Value
        else
            amount = Config.CityHallTaxes.AgencyRentalIncome.Value
            percentage = 0
        end
        
        if percentage == 0 then
            exports['vms_cityhall']:addCompanyFlatTaxAmount(identifier, amount)
        else
            exports['vms_cityhall']:addCompanyCustomTaxAmount(identifier, amount, percentage)
        end

    elseif type == 'agency_purchase_tax' then
        if Config.CityHallTaxes.AgencyPropertyPurchase.Type == 'percentage' then
            amount = fromAmount
            percentage = Config.CityHallTaxes.AgencyPropertyPurchase.Value
        else
            amount = Config.CityHallTaxes.AgencyPropertyPurchase.Value
            percentage = 0
        end
        
        if percentage == 0 then
            exports['vms_cityhall']:addCompanyFlatTaxAmount(identifier, amount)
        else
            exports['vms_cityhall']:addCompanyCustomTaxAmount(identifier, amount, percentage)
        end

    elseif type == 'agency_resale_tax' then
        if Config.CityHallTaxes.AgencyPropertyResale.Type == 'percentage' then
            amount = fromAmount
            percentage = Config.CityHallTaxes.AgencyPropertyResale.Value
        else
            amount = Config.CityHallTaxes.AgencyPropertyResale.Value
            percentage = 0
        end
        
        if percentage == 0 then
            exports['vms_cityhall']:addCompanyFlatTaxAmount(identifier, amount)
        else
            exports['vms_cityhall']:addCompanyCustomTaxAmount(identifier, amount, percentage)
        end
        
    elseif type == 'project_sale_tax' then
        if Config.CityHallTaxes.AuthorProjectSale.Type == 'percentage' then
            amount = fromAmount
            percentage = Config.CityHallTaxes.AuthorProjectSale.Value
        else
            amount = Config.CityHallTaxes.AuthorProjectSale.Value
            percentage = 100
        end

        if amount >= 1 and percentage >= 1 then
            exports['vms_cityhall']:addPlayerCustomTaxToPay({
                identifier = identifier,
                characterName = characterName,
            }, amount, percentage, (Config.CityHallTaxes.AuthorProjectSale.Title):format(propertyName))
        end

    elseif type == 'studio_project_sale_tax' then
        if Config.CityHallTaxes.StudioProjectSale.Type == 'percentage' then
            amount = fromAmount
            percentage = Config.CityHallTaxes.StudioProjectSale.Value
        else
            amount = Config.CityHallTaxes.StudioProjectSale.Value
            percentage = 100
        end

        if percentage == 0 then
            exports['vms_cityhall']:addCompanyFlatTaxAmount(identifier, amount)
        else
            exports['vms_cityhall']:addCompanyCustomTaxAmount(identifier, amount, percentage)
        end
        
    end

    return true -- DO NOT REMOVE!
end



-- ██╗   ██╗ █████╗ ██████╗ ██╗ █████╗ ██████╗ ██╗     ███████╗███████╗
-- ██║   ██║██╔══██╗██╔══██╗██║██╔══██╗██╔══██╗██║     ██╔════╝██╔════╝
-- ██║   ██║███████║██████╔╝██║███████║██████╔╝██║     █████╗  ███████╗
-- ╚██╗ ██╔╝██╔══██║██╔══██╗██║██╔══██║██╔══██╗██║     ██╔══╝  ╚════██║
--  ╚████╔╝ ██║  ██║██║  ██║██║██║  ██║██████╔╝███████╗███████╗███████║
--   ╚═══╝  ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝╚═╝  ╚═╝╚═════╝ ╚══════╝╚══════╝╚══════╝
SV.CanEnterHouse = function(src, xPlayer)
    -- This function is triggered when a player tries to enter the property.
    -- You can return false to block access, for example if they are handcuffed or anything else
    return true
end

SV.CanExitHouse = function(src, xPlayer)
    -- This function is triggered when a player tries to exit the property.
    -- You can return false to block access, for example if they are handcuffed or anything else
    return true
end
```

{% endcode %}


# config.idea.lua

Preview File Updated: v1.4.4 - 06/06/2026

{% code fullWidth="true" %}

```lua
-- ██╗██████╗ ███████╗ █████╗ 
-- ██║██╔══██╗██╔════╝██╔══██╗
-- ██║██║  ██║█████╗  ███████║
-- ██║██║  ██║██╔══╝  ██╔══██║
-- ██║██████╔╝███████╗██║  ██║
-- ╚═╝╚═════╝ ╚══════╝╚═╝  ╚═╝
Config.Idea = {
    Enabled = true,

    Job = 'idea',
    CompanyAccount = 'society_idea',
    
    DriverPercentage = 70, -- Percentage of the `deliveryPrice` should the courier receive
    CompanyPercentage = 80, -- Percentage of the order's `price` should the company receive

    BlipCoords = vector3(75.95, -1777.33, 28.62),

    Zone = {
        points = {
            vector2(89.344, -1850.5814),
            vector2(147.873, -1780.785),
            vector2(44.1947, -1698.5505),
            vector2(-45.1952, -1733.5248)
        },
        minZ = 21.4,
        maxZ = 45.2
    },

    Checkout = {
        {
            coords = vector4(74.53, -1735.55, 29.2, 320.0), -- Location of the point
            size = vec(3.6, 1.7, 2.8), -- Target size
            radius = 2.0, -- Interaction radius
            ped = {
                enabled = true,
                model = 'CSB_Hugh', -- Model of the NPC ped
                coords = vector4(74.82, -1734.01, 28.61, 139.4), -- Position and heading of the ped
                animation = {'anim@amb@nightclub@lazlow@ig1_vip@', 'clubvip_base_laz'} -- Animation dictionary and name used by the ped
            },
        },
        {
            coords = vector4(78.76, -1739.09, 29.2, 320.0), -- Location of the point
            size = vec(3.6, 1.7, 2.8), -- Target size
            radius = 2.0, -- Interaction radius
            ped = {
                enabled = true,
                model = 'HC_Hacker', -- Model of the NPC ped
                coords = vector4(80.23, -1738.58, 28.61, 145.79), -- Position and heading of the ped
                animation = {'missah_2_ext_altleadinout', 'hack_loop'} -- Animation dictionary and name used by the ped
            },
        },
    },

    Warehouse = {
        OrdersMenu = {
            coords = vector4(89.39, -1805.94, 29.63, 150.47),
            radius = 2.5
        },

        Vehicle = {
            model = 'benson_vms',
            spawnCoords = {
                [1] = vector4(103.6, -1794.25, 26.05, 138.45),
                [2] = vector4(94.07, -1786.92, 26.05, 139.71)
            },
            returnCoords = vector3(108.95, -1783.98, 26.07)
        },

        PalletTruck = {
            model = 'prop_idea_pallettruck_01',
            defaultPosition = {
                coords = vec3(114.91000366211, -1783.6999511719, 26.072986602783),
                rotation = vec3(0.0, 0.0, -37.179977416992)
            },
        },
        
        Boxes = {
            ['1'] = { -- Delivery Size 1
                deliveryModel = 'prop_rub_boxpile_07',
                bundleSpawn = vector4(114.71, -1783.9, 26.32, 322.82),
                bundleAttach = {
                    coords = vector3(0.0, 0.0, 0.55),
                },

                pickupModel = 'prop_hat_box_05',
                pickupAttach = {
                    boneId = 57005,
                    coords = vector3(0.482997, 0.497997, -0.213000),
                    rotation = vector3(38.399982, -108.960976, -349.398071),
                },

                requiredPickups = 2,
                pickupPoints = {
                    [1] = {
                        coords = vec3(114.49, -1783.63, 26.32),
                        rotation = vec3(0.0, 0.0, -45.35)
                    },
                    [2] = {
                        coords = vec3(114.84, -1783.95, 26.32),
                        rotation = vec3(0.0, 0.0, -39.69)
                    }
                },
            },
            ['2'] = { -- Delivery Size 2
                deliveryModel = 'prop_rub_boxpile_03',
                bundleSpawn = vector4(114.71, -1783.9, 26.55, 322.82),
                bundleAttach = {
                    coords = vector3(0.0, 0.0, 0.45),
                },
                
                pickupModel = 'prop_hat_box_06',
                pickupAttach = {
                    boneId = 57005,
                    coords = vector3(0.424998, 0.355999, -0.187000),
                    rotation = vector3(67.840042, -43.199970, -410.519470),
                },
                
                requiredPickups = 5,
                pickupPoints = {
                    [1] = {
                        coords = vec3(114.12000274658, -1783.7299804688, 26.319999694824),
                        rotation = vec3(0.0, 0.0, 0.0)
                    },
                    [2] = {
                        coords = vec3(114.51999664307, -1783.3199462891, 26.319999694824),
                        rotation = vec3(0.0, 0.0, -36.849990844727)
                    },
                    [3] = {
                        coords = vec3(114.59999847412, -1783.8199462891, 26.62),
                        rotation = vec3(-90.0, -0.0, -49.999992370605)
                    },

                    [4] = {
                        coords = vec3(115.01000213623, -1783.99, 26.49),
                        rotation = vec3(0.0, 90.0, 0.0)
                    },
                    [5] = {
                        coords = vec3(114.43000030518, -1784.4000244141, 26.540000915527),
                        rotation = vec3(90.0, -1.017777442186e-13, 60.0)
                    }
                },
            },
            ['3'] = { -- Delivery Size 3
                deliveryModel = 'prop_boxpile_04a',
                bundleSpawn = vector4(114.71, -1783.9, 26.32, 322.82),
                bundleAttach = {
                    coords = vector3(0.0, -0.3, 0.83),
                },

                pickupModel = 'prop_cardbordbox_03a',
                pickupAttach = {
                    boneId = 57005,
                    coords = vector3(0.342999, 0.366999, -0.471997),
                    rotation = vector3(-8.799988, 331.686066, -415.319580),
                },
                
                requiredPickups = 8,
                pickupPoints = {
                    [1] = {
                        coords = vec3(115.33, -1783.94, 26.31),
                        rotation = vec3(0.0, 0.0, -127.56)
                    },
                    [2] = {
                        coords = vec3(114.75, -1784.59, 26.31),
                        rotation = vec3(0.0, 0.0, -127.56)
                    },
                    [3] = {
                        coords = vec3(114.55, -1783.39, 26.31),
                        rotation = vec3(0.0, 0.0, 51.02)
                    },
                    [4] = {
                        coords = vec3(114.04, -1783.95, 26.31),
                        rotation = vec3(0.0, 0.0, 48.19)
                    },

                    [5] = {
                        coords = vec3(115.37, -1783.90, 26.8),
                        rotation = vec3(0.0, 0.0, -121.24)
                    },
                    [6] = {
                        coords = vec3(114.83, -1784.61, 26.8),
                        rotation = vec3(0.0, 0.0, -132.12)
                    },
                    [7] = {
                        coords = vec3(114.61, -1783.4, 26.8),
                        rotation = vec3(0.0, 0.0, 53.12)
                    },
                    [8] = {
                        coords = vec3(114.07, -1783.88, 26.8),
                        rotation = vec3(0.0, 0.0, 45.36)
                    },
                }
            },
        },
        
        Products = {
            vector4(102.83, -1775.08, 26.07, 55.21),
            vector4(111.89, -1767.59, 26.76, 51.9),
            vector4(112.67, -1769.49, 26.07, 228.75),
            vector4(116.11, -1772.51, 26.07, 47.07),
            vector4(117.77, -1778.42, 27.39, 322.89),
            vector4(124.28, -1778.33, 26.07, 48.45),
            vector4(126.12, -1777.04, 26.07, 43.57),
            vector4(129.35, -1775.29, 26.07, 51.16),
            vector4(139.21, -1784.75, 26.07, 234.83),
            vector4(138.41, -1788.43, 26.07, 221.12),
            vector4(128.79, -1801.37, 26.07, 316.88),
            vector4(125.65, -1801.35, 26.07, 144.34),
            vector4(123.1, -1807.62, 26.07, 147.19),
            vector4(114.22, -1796.93, 26.07, 156.09),
        }
    },

    Displays = {
        --[[
            Display Types:
            - door
            - art
            - shelf
            - square
            - rectangle
            - tv
        ]]

        -- Living Room section
        { type = 'square', coords = vector4(76.44432067871094, -1811.000732421875, 28.61, -130.0) }, -- Speaker Display
        { type = 'rectangle', coords = vector4(74.11013793945313, -1813.7764892578126, 28.61, -130.0) }, -- TV Display
        { type = 'tv', coords = vector4(68.87403106689453, -1815.6512451171876, 28.61, 140.0) }, -- TV Display
        { type = 'square', coords = vector4(77.0, -1800.0, 28.61, -40.0) }, -- Plant Display
        
        { type = 'art', coords = vector4(69.1052017211914, -1792.251220703125, 28.65480041503906, 50.0) }, -- Art Display

        -- Bathroom section
        { type = 'rectangle', coords = vector4(86.61372375488281, -1761.2701416015626, 28.61, -40.0) }, -- 

        -- Kitchen section
        { type = 'rectangle', coords = vector4(75.28, -1783.96, 28.61, 272.28) }, -- Speaker Display

        -- Office section
        { type = 'square', coords = vector4(61.93104553222656, -1774.3531494140626, 28.65, -40.0) }, -- Monitor Display
        { type = 'square', coords = vector4(60.04923248291015, -1776.6019287109376, 28.65, -40.0) }, -- PC Display
        { type = 'square', coords = vector4(54.35869598388672, -1771.8414306640626, 28.65, -40.0) }, -- Documents Display
        { type = 'square', coords = vector4(56.25644302368164, -1769.5931396484376, 28.65, -40.0) }, -- Accessories Display

        -- Fun section
        { type = 'shelf', coords = vector4(85.76, -1757.72, 29.6, 50.0) }, -- Barbell Display

        -- Garden section
        { type = 'square', coords = vector4(55.05920791625976, -1766.9393310546876, 28.65, -40.0) }, -- Accessories Display
        { type = 'square', coords = vector4(53.13338851928711, -1769.2247314453126, 28.65, -40.0) }, -- Accessories Display

        -- Decor section
        { type = 'door', coords = vector4(57.79071044921875, -1743.296875, 28.61000061035156, 52.0) }, -- Door Display
        { type = 'square', coords = vector4(62.739067077637, -1737.1203613281, 28.61, 230.0) },
        { type = 'square', coords = vector4(71.05659484863281, -1744.0928955078126, 28.61, 230.0) },
        { type = 'square', coords = vector4(68.86103057861328, -1746.7142333984376, 28.61, 230.0) },
        { type = 'square', coords = vector4(66.64784240722656, -1749.3369140625, 28.61, 230.0) },
    },

    Presets = {
        -- Living Room section
        {
            label = 'Living Room #1',
            coords = vector4(28.58, -1777.87, 29.6, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(35.350689, -1776.381348, 31.214828),
                fov = 47.0
            },

            targetZone = {
                coords = vector4(28.58, -1777.87, 29.6, 140.0),
                size = vec(6.5, 6.5, 6.0),
            },
            list = {
                {model = 'hei_heist_stn_sofa3seat_06', x = 30.42183303833, y = -1779.4769287109, z = 28.614799499512, w = -129.99998474121},
                {model = 'm24_1_prop_m41_rug_01a', x = 28.280342102051, y = -1777.7250976562, z = 28.619798660278, w = 0.0},
                {model = 'apa_mp_h_tab_sidelrg_07', x = 28.843456268311, y = -1778.1472167969, z = 28.624797821045, w = 49.999977111816},
                {model = 'prop_pot_plant_05a', x = 25.294174194336, y = -1778.4205322266, z = 28.614799499512, w = 0.0},
                {model = 'apa_mp_h_str_sideboardl_06', x = 26.108268737793, y = -1775.8198242188, z = 28.614799499512, w = 49.999938964844},
                {model = 'prop_tv_flat_01', x = 26.213907241821, y = -1775.8990478516, z = 30.101800918579, w = 49.999973297119},
                {model = 'vw_prop_casino_art_gun_01a', x = 25.537017822266, y = -1777.0008544922, z = 29.6107006073, w = 59.999969482422},
                {model = 'vw_prop_vw_wallart_58a', x = 27.504535675049, y = -1774.1566162109, z = 30.416116714478, w = 49.999973297119},
                {model = 'apa_mp_h_acc_artwallm_04', x = 26.1204662323, y = -1780.8217773438, z = 30.473257064819, w = 139.99984741211}
            }
        },
        {
            label = 'Living Room #2',
            coords = vector4(37.0, -1785.41, 29.07, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(34.628746, -1778.580566, 31.214828),
                fov = 47.0
            },

            targetZone = {
                coords = vector4(37.0, -1785.41, 29.07, 140.0),
                size = vec(6.5, 6.5, 6.0),
            },
            list = {
                {model = 'apa_mp_h_stn_sofacorn_01', x = 39.039123535156, y = -1784.7391357422, z = 28.614799499512, w = -40.000011444092},
                {model = 'apa_mp_h_tab_sidelrg_01', x = 37.02950668335, y = -1784.9038085938, z = 28.620355606079, w = 49.999980926514},
                {model = 'vw_prop_vw_wallart_61a', x = 39.491355895996, y = -1787.5960693359, z = 30.617776870728, w = -129.99998474121},
                {model = 'prop_fan_01', x = 37.146286010742, y = -1789.3077392578, z = 28.614799499512, w = 179.99975585938},
                {model = 'prop_tv_flat_01', x = 34.586410522461, y = -1787.7332763672, z = 29.945117950439, w = 139.99984741211},
                {model = 'apa_mp_h_str_sideboardl_14', x = 34.517166137695, y = -1787.8266601562, z = 28.614799499512, w = 140.1374206543},
                {model = 'vw_prop_casino_art_gun_02a', x = 33.040985107422, y = -1786.1990966797, z = 28.614799499512, w = 134.9997253418},
            }
        },
        {
            label = 'Living Room #3',
            coords = vector4(38.51, -1769.3, 29.46, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(43.257736, -1776.810425, 33.274876),
                fov = 47.0
            },

            targetZone = {
                coords = vector4(38.51, -1769.3, 29.46, 140.0),
                size = vec(9.0, 6.5, 6.0),
            },
            list = {
                {model = 'apa_mp_h_acc_artwalll_03', x = 34.739204406738, y = -1765.5343017578, z = 30.600749969482, w = 49.999927520752},
                {model = 'prop_pot_plant_05b', x = 38.908309936523, y = -1765.7586669922, z = 28.543172836304, w = -5.0000157356262},
                {model = 'prop_speaker_06', x = 39.706317901611, y = -1766.0766601562, z = 29.692136764526, w = -30.00008392334},
                {model = 'apa_mp_h_str_sideboardl_09', x = 40.819152832031, y = -1766.7225341797, z = 28.543172836304, w = -40.000011444092},
                {model = 'prop_speaker_06', x = 41.681941986084, y = -1767.7374267578, z = 29.697849273682, w = -50.000026702881},
                {model = 'vw_prop_casino_art_car_09a', x = 41.356597900391, y = -1767.4833984375, z = 29.424831390381, w = -30.000032424927},
                {model = 'prop_tv_flat_01', x = 40.827869415283, y = -1766.7510986328, z = 29.924335479736, w = -40.000011444092},
                {model = 'prop_game_clock_02', x = 40.78861618042, y = -1766.7196044922, z = 31.430149078369, w = -40.000030517578},
                {model = 'prop_speaker_07', x = 42.03536605835, y = -1768.103515625, z = 28.974756240845, w = -40.000011444092},
                {model = 'apa_mp_h_din_table_01', x = 38.611000061035, y = -1769.4405517578, z = 28.654808044434, w = -40.000011444092},
                {model = 'h4_mp_h_yacht_strip_chair_01', x = 40.737903594971, y = -1773.0236816406, z = 28.546514511108, w = -169.99989318848},
                {model = 'h4_mp_h_yacht_sofa_01', x = 37.134281158447, y = -1771.2091064453, z = 28.535158157349, w = 139.99984741211},
                {model = 'h4_mp_h_yacht_strip_chair_01', x = 34.763313293457, y = -1768.0581054688, z = 28.543172836304, w = 99.999862670898},
                {model = 'apa_mp_h_lit_floorlampnight_14', x = 34.436676025391, y = -1769.3492431641, z = 28.614810943604, w = 0.0},
                {model = 'ex_mp_h_acc_rugwoolm_04', x = 38.665916442871, y = -1769.3465576172, z = 28.614799499512, w = 49.999969482422},
            }
        },
        {
            label = 'Living Room #4',
            coords = vector4(45.03, -1775.47, 29.0, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(37.910641, -1777.509644, 33.274876),
                fov = 47.0
            },

            targetZone = {
                coords = vector4(45.03, -1775.47, 29.0, 140.0),
                size = vec(6.5, 6.5, 6.5),
            },
            list = {
                {model = 'apa_mp_h_lit_floorlamp_05', x = 45.773258209229, y = -1772.6765136719, z = 28.543172836304, w = -109.99997711182},
                {model = 'vw_prop_vw_wallart_41a', x = 47.821250915527, y = -1772.5979003906, z = 30.563943862915, w = -40.000011444092},
                {model = 'apa_mp_h_stn_sofacorn_09', x = 44.489776611328, y = -1773.869140625, z = 28.543172836304, w = 50.208385467529},
                {model = 'prop_laptop_01a', x = 44.604118347168, y = -1775.6184082031, z = 28.992433547974, w = -145.00012207031},
                {model = 'apa_mp_h_tab_coffee_07', x = 44.767471313477, y = -1775.8875732422, z = 28.543172836304, w = -35.000003814697},
                {model = 'sf_prop_sf_bed_dog_01a', x = 49.209659576416, y = -1774.8502197266, z = 28.543172836304, w = -54.999862670898},
                {model = 'prop_pot_plant_05a', x = 48.59001159668, y = -1776.1527099609, z = 28.543172836304, w = 49.999973297119},
                {model = 'apa_mp_h_str_sideboardl_13', x = 47.397346496582, y = -1778.18359375, z = 28.623857498169, w = -129.99998474121},
                {model = 'vw_prop_casino_art_head_01c', x = 46.386432647705, y = -1778.8713378906, z = 29.416179656982, w = -135.00022888184},
                {model = 'prop_tv_flat_01', x = 47.384738922119, y = -1778.1346435547, z = 30.195245742798, w = -129.99998474121},
                {model = 'prop_plant_int_01a', x = 45.626525878906, y = -1779.9420166016, z = 28.543172836304, w = 4.9999980926514},
            }
        },

        -- Bedroom section
        {
            label = 'Bedroom #1',
            coords = vector4(42.94, -1790.45, 28.57, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(47.939266, -1794.692017, 31.474834),
                fov = 47.0
            },

            targetZone = {
                coords = vector4(42.94, -1790.45, 28.57, 140.0),
                size = vec(6.0, 6.0, 6.0),
            },
            list = {
                {model = 'ch_prop_vault_painting_01a', x = 44.701736450195, y = -1788.1853027344, z = 30.014949798584, w = -40.49658203125},
                {model = 'apa_mp_h_bed_double_09', x = 44.739570617676, y = -1788.1673583984, z = 28.614990234375, w = -38.640022277832},
                {model = 'apa_mp_h_str_sideboardl_13', x = 40.845973968506, y = -1793.1795654297, z = 28.614990234375, w = 139.99975585938},
                {model = 'prop_tv_flat_02', x = 41.10111618042, y = -1792.8088378906, z = 29.633373260498, w = 140.99980163574},
            }
        },
        {
            label = 'Bedroom #2',
            coords = vector4(48.55, -1795.37, 28.57, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(42.507851, -1791.812134, 31.474834),
                fov = 47.0
            },

            targetZone = {
                coords = vector4(48.55, -1795.37, 28.57, 140.0),
                size = vec(6.0, 6.0, 6.0),
            },
            list = {
                {model = 'prop_tv_flat_01', x = 46.443355560303, y = -1797.6802978516, z = 30.126438140869, w = 139.99998474121},
                {model = 'prop_jewel_02a', x = 46.20809173584, y = -1797.2629394531, z = 29.558418273926, w = 139.9998626709},
                {model = 'v_res_paperfolders', x = 45.74361038208, y = -1796.9415283203, z = 29.6032371521, w = 134.9998626709},
                {model = 'apa_mp_h_str_sideboardl_11', x = 46.399715423584, y = -1797.8277587891, z = 28.614990234375, w = 139.99998474121},
                {model = 'vw_prop_casino_art_car_04a', x = 47.404716491699, y = -1798.3941650391, z = 29.496650695801, w = -60.0},
                {model = 'prop_exer_bike_01', x = 50.12585067749, y = -1797.9344482422, z = 28.726732254028, w = 119.99993133545},
                {model = 'hei_heist_bed_double_08', x = 50.403308868408, y = -1792.9458007812, z = 28.94197845459, w = -40.000011444092},
                {model = 'vw_prop_vw_wallart_41a', x = 50.401222229004, y = -1792.9676513672, z = 30.373798370361, w = -40.838996887207},
            }
        },
        {
            label = 'Bedroom #3',
            coords = vector4(51.67, -1779.92, 28.57, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(57.581688, -1781.015869, 32.194851),
                fov = 45.0
            },

            targetZone = {
                coords = vector4(51.67, -1779.92, 28.57, 140.0),
                size = vec(6.0, 6.0, 6.0),
            },
            list = {
                {model = 'vw_prop_plaque_02b', x = 48.622268676758, y = -1781.509765625, z = 30.446783065796, w = 139.99984741211},
                {model = 'apa_mp_h_str_sideboardl_06', x = 49.61555480957, y = -1782.3531494141, z = 28.614990234375, w = 139.99983215332},
                {model = 'vw_prop_casino_art_car_04a', x = 50.583751678467, y = -1782.8461914062, z = 29.610891342163, w = -55.000026702881},
                {model = 'prop_tv_flat_02', x = 49.805820465088, y = -1782.1313476562, z = 29.836954116821, w = 139.99996948242},
                {model = 'sum_mp_h_yacht_bed_02', x = 53.677375793457, y = -1777.5153808594, z = 28.614799499512, w = -40.000011444092},
                {model = 'sf_prop_sf_bed_dog_01a', x = 50.992851257324, y = -1776.3149414062, z = 28.614799499512, w = -20.000040054321},
            }
        },
        {
            label = 'Bedroom #4',
            coords = vector4(57.33, -1784.66, 28.57, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(54.696987, -1779.176392, 30.374809),
                fov = 45.0
            },

            targetZone = {
                coords = vector4(57.33, -1784.66, 28.57, 140.0),
                size = vec(6.0, 6.0, 6.0),
            },
            list = {
                {model = 'apa_mp_h_str_sideboardl_11', x = 55.275394439697, y = -1787.1070556641, z = 28.614799499512, w = 139.99981689453},
                {model = 'ex_mp_h_acc_vase_flowers_01', x = 56.399784088135, y = -1787.6574707031, z = 29.49640083313, w = -40.000011444092},
                {model = 'ex_mp_h_acc_vase_flowers_01', x = 54.542942047119, y = -1786.0356445312, z = 29.49640083313, w = 139.9998626709},
                {model = 'prop_tv_flat_michael', x = 55.321807861328, y = -1787.0815429688, z = 30.428567886353, w = 139.99984741211},
                {model = 'apa_mp_h_bed_with_table_02', x = 59.341529846191, y = -1782.2701416016, z = 28.61457824707, w = -40.000244140625},
                {model = 'v_ilev_fh_lampa_on', x = 60.492942810059, y = -1783.7794189453, z = 29.263698577881, w = 49.999977111816},
                {model = 'v_ilev_fh_lampa_on', x = 57.691570281982, y = -1781.3975830078, z = 29.263698577881, w = -129.99998474121},
            }
        },

        -- Kitchen section
        {
            label = 'Kitchen',
            coords = vector4(55.2, -1799.79, 30.0, 50.0), -- Location of the point
            radius = 3.5, -- Interaction radius

            camera = {
                coords = vec3(55.938644, -1793.831909, 30.814819),
                fov = 44.0
            },

            targetZone = {
                coords = vector4(55.2, -1799.79, 30.0, 140.0),
                size = vec(8.0, 8.0, 6.0),
            },
            list = {
                {model = 'prop_chair_04b', x = 56.093856811523, y = -1797.3034667969, z = 28.618831634521, w = -40.000011444092},
                {model = 'prop_chair_04b', x = 56.826263427734, y = -1797.9259033203, z = 28.618831634521, w = -40.000011444092},
                {model = 'prop_chair_04b', x = 57.54776763916, y = -1798.5140380859, z = 28.618831634521, w = -40.000011444092},
                {model = 'prop_chair_04b', x = 56.675094604492, y = -1799.5462646484, z = 28.614990234375, w = 140.0},
                {model = 'prop_chair_04b', x = 55.963180541992, y = -1798.955078125, z = 28.614990234375, w = 140.0},
                {model = 'prop_chair_04b', x = 55.234615325928, y = -1798.3304443359, z = 28.614990234375, w = 140.00003051758},
                {model = 'apa_mp_h_din_table_11', x = 56.352165222168, y = -1798.4158935547, z = 28.614990234375, w = -40.000011444092},
                {model = 'v_res_cakedome', x = 53.515277862549, y = -1799.1322021484, z = 29.560203552246, w = 0.0},
                {model = 'prop_kitch_pot_fry', x = 54.040367126465, y = -1800.7850341797, z = 29.596879959106, w = -175.00021362305},
                {model = 'prop_kitch_pot_lrg2', x = 54.439037322998, y = -1801.1882324219, z = 29.678546905518, w = -65.000007629395},
                {model = 'v_res_pestle', x = 55.375408172607, y = -1801.4471435547, z = 29.560203552246, w = -40.000011444092},
                {model = 'prop_foodprocess_01', x = 53.664413452148, y = -1803.5725097656, z = 29.504819869995, w = 139.99981689453},
                {model = 'v_res_foodjara', x = 52.871921539307, y = -1802.9835205078, z = 29.505519866943, w = 49.999973297119},
                {model = 'prop_lime_jar', x = 52.680824279785, y = -1802.8511962891, z = 29.716245651245, w = 0.0},
                {model = 'prop_micro_02', x = 51.652030944824, y = -1801.7624511719, z = 29.652437210083, w = 129.99987792969},
                {model = 'v_res_fridgemodsml', x = 54.65789794921875, y = -1804.252197265625, z = 28.71738815307617, w = 139.99986267089845},
            }
        },

        -- Bathroom section
        {
            label = 'Bathroom',
            coords = vector4(63.03, -1790.41, 30.0, 50.0), -- Location of the point
            radius = 3.5, -- Interaction radius

            camera = {
                coords = vec3(60.327129, -1796.747192, 31.794842),
                fov = 44.0
            },

            targetZone = {
                coords = vector4(63.03, -1790.41, 30.0, 140.0),
                size = vec(8.0, 8.0, 6.0),
            },
            list = {
                {model = 'v_res_mbath', x = 59.470127105713, y = -1790.7650146484, z = 28.656324386597, w = 49.999969482422},
                {model = 'prop_toilet_01', x = 60.972442626953, y = -1787.6207275391, z = 28.615612030029, w = 49.999969482422},
                {model = 'prop_toilet_brush_01', x = 61.12903213501, y = -1787.2810058594, z = 28.614990234375, w = 0.0},
                {model = 'prop_toilet_soap_02', x = 63.82653427124, y = -1786.3549804688, z = 29.597757339478, w = 49.999973297119},
                {model = 'prop_toilet_soap_04', x = 64.52172088623, y = -1786.8240966797, z = 29.656639099121, w = 0.0},
                {model = 'prop_toothpaste_01', x = 65.082992553711, y = -1787.2379150391, z = 29.57981300354, w = 54.999973297119},
                {model = 'prop_towel_01', x = 65.683517456055, y = -1792.5993652344, z = 29.859523773193, w = 94.999938964844},
                {model = 'prop_shower_rack_01', x = 68.177879333496, y = -1789.6926269531, z = 29.94980430603, w = -40.000011444092},
            }
        },
    },

    Points = {
        -- Living Room section
        {
            label = 'Corner sofas',
            coords = vector4(61.78, -1803.74, 28.54, 50.0), -- Location of the point
            default = 'apa_mp_h_stn_sofacorn_01',
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vector3(63.53, -1809.5, 30.6),
                pointZ = 0.4,
                fov = 50.0,
            },
            
            placeOnGround = true,

            list = {
                {model = 'apa_mp_h_stn_sofacorn_01'},
                {model = 'apa_mp_h_stn_sofacorn_08'},
                {model = 'apa_mp_h_stn_sofacorn_09'},
                {model = 'hei_heist_stn_sofacorn_05',   x = 61.89942169189453, y = -1805.142822265625},
                {model = 'v_ilev_m_sofa',               x = 62.43185424804687, y = -1804.3349609375, w = -40.0},
            }
        },
        {
            label = 'Sofas',
            coords = vector4(65.331, -1811.316, 28.543, 140.0), -- Location of the point
            default = 'h4_mp_h_yacht_sofa_01',
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vector3(65.54, -1806.58, 30.1),
                pointZ = 0.0,
                fov = 45.0,
            },

            placeOnGround = true,

            list = {
                {model = 'h4_mp_h_yacht_sofa_01'},
                {model = 'ex_mp_h_off_sofa_003'},
                {model = 'ex_mp_h_off_sofa_02'},
                {model = 'prop_couch_01'},
                {model = 'prop_couch_03'},
                {model = 'prop_couch_lg_02'},
                {model = 'prop_couch_lg_05'},
                {model = 'prop_couch_lg_06'},
                {model = 'prop_couch_lg_07'},
                {model = 'prop_couch_lg_08'},
                {model = 'prop_couch_sm_06'},
                {model = 'hei_heist_stn_sofa3seat_01'},
                {model = 'hei_heist_stn_sofa3seat_02'},
                {model = 'hei_heist_stn_sofa3seat_06'},
                {model = 'prop_couch_sm1_07'},
                {model = 'prop_couch_sm2_07'},
            }
        },
        {
            label = 'Chairs',
            coords = vector4(67.835, -1808.313, 28.6, -40.0), -- Location of the point
            default = 'h4_mp_h_yacht_strip_chair_01',
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vector3(63.61, -1809.89, 30.5),
                pointZ = 0.0,
                fov = 40.0,
            },

            placeOnGround = true,

            list = {
                {model = 'h4_mp_h_yacht_strip_chair_01'},
                {model = 'apa_mp_h_stn_chairarm_01'},
                {model = 'apa_mp_h_stn_chairarm_23'},
                {model = 'apa_mp_h_stn_chairstrip_03'},
                {model = 'apa_mp_h_stn_chairstrip_06'},
                {model = 'apa_mp_h_stn_chairstrip_07'},
                {model = 'apa_mp_h_stn_chairstrip_08'},
                {model = 'bkr_prop_biker_chairstrip_01'},
                {model = 'prop_couch_sm_02',                x = 67.32190704345703, y = -1808.141845703125},
                {model = 'prop_couch_sm_05',                x = 67.41608428955078, y = -1808.0125732421876},

                -- {model = 'prop_couch_sm_07',                x = 67.45124053955078, y = -1808.021484375},
                {model = 'prop_couch_sm_07'},
                
                {model = 'prop_armchair_01'},
                {model = 'prop_chair_05'},
                {model = 'xm_lab_chairarm_03'},
            }
        },
        {
            label = 'Tables',
            coords = vector4(62.13, -1806.4, 28.61, 50.0), -- Location of the point
            default = 'apa_mp_h_tab_coffee_07',
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vector3(64.19, -1808.77, 30.07),
                pointZ = 0.5,
                fov = 36.0,
            },

            list = {
                {model = 'apa_mp_h_tab_coffee_07'},
                {model = 'apa_mp_h_tab_sidelrg_01'},
                {model = 'apa_mp_h_tab_sidelrg_02'},
                {model = 'apa_mp_h_tab_sidelrg_07'},
                {model = 'prop_fbi3_coffee_table'},
                {model = 'prop_tablesmall_01'},
                {model = 'prop_ld_farm_table01'},
                {model = 'prop_ld_farm_table02'},
                {model = 'v_ilev_liconftable_sml'},
                {model = 'v_ind_rc_lowtable'},
            }
        },
        {
            label = 'Standing TV\'s',
            coords = vector4(74.11013793945313, -1813.7764892578126, 29.68, 230.0), -- Location of the point
            default = 'prop_tv_flat_02',
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(69.041618, -1812.299072, 30.5),
                pointZ = 0.5,
                fov = 40.0,
            },

            placeOnGround = true,

            list = {
                {model = 'prop_tv_flat_02'},
                {model = 'prop_tv_flat_02b'},
                {model = 'prop_trev_tv_01'},
                {model = 'prop_tv_01'},
                {model = 'prop_tv_02'},
                {model = 'prop_tv_03'},
                {model = 'prop_tv_04'},
                {model = 'prop_tv_05'},
                {model = 'prop_tv_06'},
                {model = 'prop_tv_07'},
                {model = 'prop_tv_flat_03'},
                {model = 'prop_tv_stand_01'},
                {model = 'prop_tv_test'},
                {model = 'prop_cs_tv_stand'},
            }
        },
        {
            label = 'Speaker\'s',
            coords = vector4(76.44432067871094, -1811.000732421875, 29.92, -130.0), -- Location of the point
            default = 'prop_speaker_01',
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(71.026207, -1807.144409, 30.235022),
                pointZ = 0.5,
                fov = 29.0,
            },

            placeOnGround = true,
            
            list = {
                {model = 'prop_speaker_01'},
                {model = 'prop_speaker_02'},
                {model = 'prop_speaker_06'},
                {model = 'prop_speaker_07'},
                {model = 'prop_amp_01'},
                {model = 'prop_ghettoblast_01'},
                {model = 'prop_portable_hifi_01'},
            }
        },
        {
            label = 'Wall-mounted TV\'s',
            coords = vector4(69.14306640625, -1815.3594970703126, 30.16, 140.0), -- Location of the point
            radius = 3.5, -- Interaction radius

            camera = {
                coords = vec3(74.632980, -1804.605957, 29.821775),
                pointZ = 0.0,
                fov = 38.0,
            },

            default = 'prop_tv_flat_01',
            list = {
                {model = 'prop_tv_flat_01'},
                {model = 'prop_tv_flat_03b'},
                {model = 'prop_tv_flat_michael'},
            }
        },
        {
            label = 'Lamp',
            coords = vector4(70.19, -1809.6, 28.61, 55.07), -- Location of the point
            default = 'apa_mp_h_lit_floorlamp_05',
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vector3(65.77, -1810.86, 30.19),
                pointZ = 1.0,
                fov = 60.0,
            },

            list = {
                {model = 'apa_mp_h_lit_floorlamp_05',       w = 186.63},
                {model = 'apa_mp_h_lit_floorlampnight_14'},
                {model = 'h4_mp_h_yacht_floor_lamp_01'},
                {model = 'apa_mp_h_lampbulb_multiple_a',    z = 32.0},
                {model = 'prop_wall_light_06a',    z = 32.0},
            }
        },
        {
            label = 'TV stands',
            coords = vector4(66.41159057617188, -1795.0184326171876, 28.54, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(72.967400, -1798.847290, 30.161783),
                pointZ = 0.6,
                fov = 30.0,
            },

            default = 'apa_mp_h_str_sideboardl_09',
            list = {
                {model = 'apa_mp_h_str_sideboardl_09'},
                {model = 'apa_mp_h_str_sideboardl_06'},
                {model = 'apa_mp_h_str_sideboards_01'},
                {model = 'apa_mp_h_str_sideboardl_11'},
                {model = 'apa_mp_h_str_sideboardl_13'},
                {model = 'apa_mp_h_str_sideboardl_14'},
                {model = 'prop_tv_cabinet_03'},
                {model = 'prop_tv_cabinet_04'},
                {model = 'prop_tv_cabinet_05'},
            }
        },
        {
            label = 'Wall decorations',
            coords = vector4(69.21345520019531, -1792.346923828125, 30.15361976623535, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(74.739517, -1794.777344, 30.091782),
                pointZ = 0.0,
                fov = 47.0,
            },

            default = 'apa_mp_h_acc_artwalll_03',
            list = {
                {model = 'apa_mp_h_acc_artwalll_03'},
                {model = 'apa_mp_h_acc_artwallm_04'},
                {model = 'ch_prop_vault_painting_01a'},
                {model = 'ch_prop_whiteboard_04'},
                {model = 'h4_prop_h4_painting_01d'},
                {model = 'prop_game_clock_01'},
                {model = 'prop_game_clock_02'},
                {model = 'prop_sports_clock_01'},
                {model = 'vw_prop_plaque_02a'},
                {model = 'vw_prop_plaque_02b'},
                {model = 'vw_prop_vw_wallart_22a'},
                {model = 'vw_prop_vw_wallart_26a'},
                {model = 'vw_prop_vw_wallart_41a'},
                {model = 'vw_prop_vw_wallart_42a'},
                {model = 'vw_prop_vw_wallart_58a'},
                {model = 'vw_prop_vw_wallart_59a'},
                {model = 'vw_prop_vw_wallart_61a'},
                {model = 'vw_prop_vw_wallart_81a'},
                {model = 'vw_prop_vw_wallart_86a'},
                {model = 'vw_prop_vw_wallart_94a'},
                {model = 'vw_prop_vw_wallart_96a'},
                {model = 'vw_prop_vw_wallart_101a'},
                {model = 'vw_prop_vw_wallart_131a'},
                {model = 'vw_prop_vw_wallart_153a'},
                {model = 'vw_prop_vw_wallart_170a'},
            }
        },
        {
            label = 'Plants',
            coords = vector4(77.0, -1800.0, 29.49, 325.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(70.717987, -1802.508789, 30.315035),
                pointZ = 0.6,
                fov = 38.0,
            },

            default = 'prop_pot_plant_01c',
            list = {
                {model = 'prop_pot_plant_01c'},
                {model = 'prop_pot_plant_01a'},
                {model = 'prop_pot_plant_01b'},
                {model = 'prop_pot_plant_01d'},
                {model = 'prop_pot_plant_01e'},
                {model = 'prop_pot_plant_05b'},
                {model = 'prop_plant_int_01a'},
                {model = 'prop_plant_int_01b'},
                {model = 'prop_plant_int_02a'},
                {model = 'prop_plant_int_02b'},
                {model = 'prop_plant_int_03a'},
                {model = 'prop_plant_int_03b'},
                {model = 'prop_plant_int_03c'},
                {model = 'prop_plant_int_04a'},
                {model = 'prop_plant_int_04b'},
                {model = 'prop_plant_int_04c'},
                {model = 'ex_mp_h_acc_vase_flowers_01'},
                {model = 'ex_mp_h_acc_vase_flowers_03'},
                {model = 'ex_mp_h_acc_vase_flowers_04'},
                {model = 'prop_pot_plant_bh1'},
                {model = 'prop_pot_plant_inter_03a'},
                {model = 'prop_pot_plant_05a'},
            }
        },
        {
            label = 'Rugs',
            coords = vector4(70.11, -1795.8, 28.62, 321.54), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vector3(70.79, -1804.25, 32.3),
                pointZ = 0.0,
                fov = 38.0,
            },
            
            placeOnGround = true,
            default = 'ex_mp_h_acc_rugwoolm_04',
            list = {
                {model = 'ex_mp_h_acc_rugwoolm_04',     w = 321.54},
                {model = 'apa_mp_h_acc_rugwoolm_01',    w = 321.54},
                {model = 'apa_mp_h_acc_rugwoolm_02',    w = 321.54},
                {model = 'apa_mp_h_acc_rugwooll_03',    w = 321.54},
                {model = 'apa_mp_h_acc_rugwools_03',    w = 321.54},
                {model = 'm24_1_prop_m41_rug_01a',      w = 321.54},
            }
        },
        
        -- Bedroom section
        {
            label = 'Bed',
            coords = vector4(90.65750885009766, -1769.3450927734376, 28.61457824707031, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(96.160698, -1775.977783, 31.715057),
                pointZ = 0.0,
                fov = 42.0,
            },
            
            placeOnGround = true,

            default = 'apa_mp_h_bed_double_09',
            list = {
                {model = 'apa_mp_h_bed_double_09'},
                {model = 'hei_heist_bed_double_08'},
                {model = 'apa_mp_h_bed_with_table_02'},
                {model = 'apa_mp_h_bed_wide_05'},
                {model = 'sum_mp_h_yacht_bed_01'},
                {model = 'sum_mp_h_yacht_bed_02'},
                {model = 'prop_t_sofa_02'},
                {model = 'p_lestersbed_s',               x = 91.68550872802735, y = -1770.2314453125},
                {model = 'ex_prop_exec_bed_01',          x = 92.1283950805664, y = -1770.576416015625, w = 140.0},
                {model = 'prop_rub_matress_01',          x = 91.56686401367188, y = -1770.1112060546876, w = 140.0},
            }
        },
        {
            label = 'Shelf',
            coords = vector4(87.93309783935547, -1773.69873046875, 28.62086105346679, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(94.650352, -1776.289429, 31.275045),
                pointZ = 1.0,
                fov = 42.0,
            },
            
            placeOnGround = true,

            default = 'apa_mp_h_str_shelffloorm_02',
            list = {
                {model = 'apa_mp_h_str_shelffloorm_02'},
                {model = 'apa_mp_h_bed_chestdrawer_02'},
                {model = 'prop_rub_cabinet02'},
            }
        },

        -- Bathroom section
        {
            label = 'Bathtub',
            coords = vector4(84.36888885498047, -1772.497314453125, 28.54317283630371, -130.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(82.572998, -1769.291870, 30.295027),
                pointZ = 0.65,
                fov = 50.0
            },
            
            placeOnGround = true,

            default = 'v_res_mbath',
            list = {
                {model = 'v_res_mbath'},
            }
        },
        {
            label = 'Toilets',
            coords = vector4(87.09173583984375, -1770.631103515625, 28.54317283630371, -125.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(83.958763, -1770.018677, 30.295027),
                pointZ = 0.5,
                fov = 50.0
            },
            
            placeOnGround = true,
            
            default = 'prop_toilet_01',
            list = {
                {model = 'prop_toilet_01'},
                {model = 'prop_toilet_02'},
            }
        },
        {
            label = 'Sinks',
            coords = vector4(89.34, -1768.07, 28.54317283630371, -130.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(86.736259, -1768.220337, 30.295027),
                pointZ = 0.8,
                fov = 50.0
            },

            placeOnGround = true,

            default = 'prop_sink_06',
            list = {
                {model = 'prop_sink_06'},
                {model = 'prop_sink_02'},
                {model = 'prop_sink_05'},
                {model = 'v_res_mbsink'},
                {model = 'prop_w_fountain_01'},
            }
        },
        {
            label = 'AGD\'s',
            coords = vector4(89.58, -1763.8, 28.54317283630371, -40.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(87.401039, -1765.774902, 30.045021),
                pointZ = 0.65,
                fov = 50.0
            },
            
            placeOnGround = true,
            
            default = 'prop_washer_03',
            list = {
                {model = 'prop_washer_03'},
                {model = 'prop_washer_02'},
            }
        },
        {
            label = 'Accessories',
            coords = vector4(86.61372375488281, -1761.2701416015626, 29.5594253540039, -130.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(85.235695, -1764.410278, 31.175047),
                pointZ = 0.0,
                fov = 43.0
            },
            
            placeOnGround = true,

            default = 'prop_towel_01',
            list = {
                {model = 'prop_towel_01'},
                {model = 'prop_shower_rack_01'},
                {model = 'prop_soap_disp_01'},
                {model = 'prop_toilet_brush_01'},
                {model = 'prop_toilet_soap_02'},
                {model = 'prop_toilet_soap_04'},
                {model = 'prop_toothpaste_01'},
            }
        },
        
        -- Kitchen section
        {
            label = 'Fridge',
            coords = vector4(65.08, -1775.32, 28.61, 10.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(67.849937, -1780.250610, 30.495029),
                pointZ = 1.0,
                fov = 40.0,
            },
            
            placeOnGround = true,

            default = 'prop_fridge_03',
            list = {
                {model = 'prop_fridge_03'},
                {model = 'v_res_fridgemodsml'},
                {model = 'v_res_tre_fridge'},
            }
        },
        {
            label = 'Chair',
            coords = vector4(69.17, -1786.45, 28.62, 140.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(70.385345, -1781.230347, 30.825037),
                pointZ = 0.8,
                fov = 30.0,
            },
            
            placeOnGround = true,

            default = 'prop_chair_04b',
            list = {
                {model = 'prop_chair_04b'},
                {model = 'hei_heist_din_chair_02'},
                {model = 'prop_chair_03'},
                {model = 'prop_chair_04a'},
                {model = 'prop_chair_06'},
                {model = 'prop_chair_07'},
                {model = 'prop_chateau_chair_01'},
                {model = 'prop_clown_chair'},
                {model = 'prop_stool_01'},
            }
        },
        {
            label = 'Table',
            coords = vector4(65.34463500976563, -1783.63330078125, 28.54317283630371, 140.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(69.815033, -1780.268921, 31.0),
                pointZ = 0.7,
                fov = 36.0,
            },

            placeOnGround = true,

            default = 'apa_mp_h_din_table_01',
            list = {
                {model = 'apa_mp_h_din_table_01'},
                {model = 'apa_mp_h_din_table_04'},
                {model = 'apa_mp_h_din_table_11'},
                {model = 'apa_mp_h_din_table_06'},
            }
        },
        {
            label = 'Accessories',
            coords = vector4(75.28, -1783.96, 29.47165298461914, -85.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(70.481949, -1786.004272, 30.365026),
                pointZ = 0.4,
                fov = 30.0,
            },
            
            placeOnGround = true,

            default = 'prop_micro_04',
            list = {
                {model = 'prop_micro_04'},
                {model = 'prop_foodprocess_01'},
                {model = 'prop_kettle'},
                {model = 'prop_kitch_juicer'},
                {model = 'prop_kitch_pot_fry'},
                {model = 'prop_kitch_pot_lrg2'},
                {model = 'prop_knife'},
                {model = 'prop_lime_jar'},
                {model = 'prop_micro_01'},
                {model = 'prop_micro_02'},
                {model = 'prop_pot_03'},
                {model = 'prop_toaster_01'},
                {model = 'prop_toaster_02'},
                {model = 'v_res_cakedome'},
                {model = 'v_res_foodjara'},
                {model = 'v_res_foodjarb'},
                {model = 'v_res_mknifeblock'},
                {model = 'v_res_mmug'},
                {model = 'v_res_mplatesml'},
                {model = 'v_res_pestle'},
                {model = 'v_res_tt_bowlpile02'},
                {model = 'v_ret_ta_paproll'},
            }
        },
        
        -- Office section
        {
            label = 'Monitor\'s',
            coords = vector4(61.93104553222656, -1774.3531494140626, 29.82024383544922, 230.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(59.806461, -1772.432861, 30.12),
                pointZ = 0.0,
                fov = 37.0,
            },
            
            placeOnGround = true,

            default = 'prop_monitor_w_large',
            list = {
                {model = 'prop_monitor_w_large'},
                {model = 'v_res_monitorwidelarge'},
                {model = 'prop_monitor_01c'},
                {model = 'prop_monitor_01d'},
                {model = 'prop_monitor_li'},
                {model = 'v_res_monitorsquare'},
            }
        },
        {
            label = 'PC',
            coords = vector4(60.04923248291015, -1776.6019287109376, 29.48231315612793, 230.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(57.820213, -1774.797241, 30.5),
                pointZ = 0.3,
                fov = 31.0,
            },
            
            placeOnGround = true,

            default = 'prop_laptop_01a',
            list = {
                {model = 'prop_laptop_01a'},
                {model = 'v_res_pctower'},
                {model = 'prop_dyn_pc'},
                {model = 'prop_pc_02a'},
            }
        },
        {
            label = 'Documents',
            coords = vector4(54.35869598388672, -1771.8414306640626, 29.55541229248047, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            camera = {
                coords = vec3(57.368683, -1773.018433, 30.485085),
                pointZ = 0.0,
                fov = 48.0,
            },
            
            placeOnGround = true,

            default = 'prop_copier_01',
            list = {
                {model = 'prop_copier_01',      x = 54.163620, y = -1772.200317},
                {model = 'prop_printer_01'},
                {model = 'v_res_printer'},
                {model = 'v_ret_gc_shred'},
                {model = 'prop_inout_tray_02'},
                {model = 'prop_folder_02'},
                {model = 'v_res_paperfolders'},
            }
        },
        {
            label = 'Accessories',
            coords = vector4(56.25644302368164, -1769.5931396484376, 29.47469520568847, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            camera = {
                coords = vec3(57.899521, -1771.785278, 31.25),
                pointZ = 0.0,
                fov = 30.0,
            },

            placeOnGround = true,

            default = 'prop_cs_keyboard_01',
            list = {
                {model = 'prop_cs_keyboard_01'},
                {model = 'prop_keyboard_01a'},
                {model = 'prop_fax_01'},
                {model = 'prop_mouse_01b'},
                {model = 'prop_mouse_02'},
                {model = 'prop_office_phone_tnt'},
                {model = 'v_club_roc_eq1'},
                {model = 'v_club_roc_eq2'},
                {model = 'v_res_mousemat'},
                {model = 'v_res_pcheadset'},
                {model = 'v_res_pcspeaker'},
                {model = 'prop_cctv_cam_04c'},
                {model = 'prop_cctv_cam_05a'},
                {model = 'prop_cctv_cam_06a'},
            }
        },
        {
            label = 'Desk',
            coords = vector4(56.56888198852539, -1777.0008544921876, 28.90201759338379, 139.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(57.783463, -1773.139038, 30.195078),
                pointZ = 0.3,
                fov = 40.0,
            },
            
            placeOnGround = true,
            default = 'prop_office_desk_01',
            list = {
                {model = 'prop_office_desk_01', w = 139.0},
                {model = 'xm_prop_lab_desk_02', w = 139.0},
                -- {model = 'v_res_mbdresser',     w = 139.0},
            }
        },
        {
            label = 'Chairs',
            coords = vector4(54.7042007446289, -1775.1326904296876, 29.23001861572265, 139.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(57.586937, -1772.734741, 30.195078),
                pointZ = 0.0,
                fov = 48.0,
            },
            
            placeOnGround = true,
            default = 'v_ret_gc_chair03',
            list = {
                {model = 'v_ret_gc_chair03',    w = 139.0},
                {model = 'prop_off_chair_01',   w = 139.0},
                {model = 'prop_off_chair_03',   w = 139.0},
                {model = 'prop_off_chair_04',   w = 139.0},
                {model = 'prop_off_chair_04b',  w = 139.0},
                {model = 'prop_off_chair_05',   w = 139.0},
                {model = 'prop_sol_chair',      w = 139.0},
                {model = 'v_club_officechair',  w = 139.0},
                {model = 'v_corp_bk_chair3',    w = 139.0},
                {model = 'v_corp_cd_chair',     w = 139.0},
                {model = 'v_corp_offchair',     w = 139.0},
            }
        },
        
        -- Fun section
        {
            label = 'Accessories',
            coords = vector4(85.41, -1757.45, 29.8, 232.53), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vector3(83.67, -1754.78, 30.48),
                pointZ = 0.2,
                fov = 40.0,
            },
            
            placeOnGround = true,
            default = 'prop_barbell_02',
            list = {
                {model = 'prop_barbell_02',    w = 232.53},
                {model = 'prop_barbell_100kg', w = 232.53},
                {model = 'prop_barbell_80kg',  w = 232.53},
                {model = 'prop_barbell_60kg',  w = 232.53},
                {model = 'prop_barbell_50kg',  w = 232.53},
                {model = 'prop_barbell_40kg',  w = 232.53},
                {model = 'prop_barbell_30kg',  w = 232.53},
                {model = 'prop_barbell_20kg',  w = 232.53},
                {model = 'prop_barbell_10kg',  w = 232.53},
                {model = 'prop_barbell_01',    w = 232.53},
            }
        },
        {
            label = 'Game Tables',
            coords = vector4(77.74, -1754.91, 28.61, 138.54), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vector3(83.16, -1754.69, 30.57),
                pointZ = 0.75,
                fov = 35.0,
            },
            
            placeOnGround = true,
            default = 'prop_pooltable_02',
            list = {
                {model = 'prop_pooltable_02'},
                {model = 'prop_pooltable_3b'},
                {model = 'prop_table_tennis'},
                {model = 'prop_airhockey_01'},
            }
        },
        {
            label = 'Sports Equipment',
            coords = vector4(85.82150268554688, -1746.5985107421876, 28.54317283630371, 5.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vector3(83.16, -1754.69, 30.57),
                pointZ = 1.2,
                fov = 30.0,
            },
            
            placeOnGround = true,
            default = 'prop_weight_bench_02',
            list = {
                {model = 'prop_weight_bench_02',    w = 5.0},
                {model = 'prop_muscle_bench_03',    w = 5.0},
                {model = 'prop_muscle_bench_05',    w = 5.0},
                {model = 'prop_muscle_bench_01',    w = 5.0},
                {model = 'prop_weight_rack_02',     w = 5.0},
                {model = 'prop_weight_squat',       w = 5.0},
                {model = 'prop_exer_bike_01',       w = 5.0},
            }
        },
        {
            label = 'Sport',
            coords = vector4(90.08366394042969, -1750.176025390625, 28.6, -85.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vector3(83.16, -1754.69, 30.57),
                pointZ = 1.5,
                fov = 40.0,
            },
            
            default = 'prop_basketball_net',
            list = {
                {model = 'prop_basketball_net',     w = -85.0},
                {model = 'prop_punch_bag_l',        z = 31.3, w = -85.0},
                {model = 'prop_speedball_01',       x = 91.12104034423828, y = -1748.952880859375, z = 30.23772621154785, w = -40.0},
                {model = 'prop_dart_bd_01',         x = 91.12104034423828, y = -1748.952880859375, z = 30.23772621154785, w = -40.0},
                {model = 'prop_dart_bd_cab_01',     x = 91.12104034423828, y = -1748.952880859375, z = 30.23772621154785, w = -40.0},
            }
        },

        -- Garden section
        {
            label = 'Gazebo',
            coords = vector4(62.13, -1755.91, 28.61, -40.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(50.536980, -1762.054443, 30.725063),
                pointZ = 1.0,
                fov = 28.0
            },
            
            placeOnGround = true,
            default = 'prop_gazebo_03',
            list = {
                {model = 'prop_gazebo_03',           w = -40.0},
                {model = 'prop_gazebo_01',           w = -40.0},
                {model = 'prop_gazebo_02',           w = -40.0},
                {model = 'prop_parasol_01',          w = -40.0},
                {model = 'prop_parasol_01_b',        w = -40.0},
                {model = 'prop_parasol_01_c',        w = -40.0},
                {model = 'prop_parasol_01_lod',      w = -40.0},
                {model = 'prop_parasol_01b_lod',     w = -40.0},
                {model = 'prop_parasol_02',          w = -40.0},
                {model = 'prop_parasol_02_b',        w = -40.0},
                {model = 'prop_parasol_02_c',        w = -40.0},
                {model = 'prop_parasol_03',          w = -40.0},
                {model = 'prop_parasol_03_b',        w = -40.0},
                {model = 'prop_parasol_03_c',        w = -40.0},
                {model = 'prop_parasol_04c',         w = -40.0},
                {model = 'prop_parasol_04d',         w = -40.0},
                {model = 'prop_table_para_comb_01',  w = -40.0},
                {model = 'prop_table_para_comb_03',  w = -40.0},
                {model = 'prop_table_para_comb_04',  w = -40.0},
                {model = 'prop_skid_tent_01',        w = -40.0},
            }
        },
        {
            label = 'Plants',
            coords = vector4(53.38700485229492, -1747.686767578125, 28.61, -180.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(57.476341, -1755.774902, 31.805088),
                pointZ = 0.9,
                fov = 39.0
            },
            
            placeOnGround = false,
            default = 'prop_plant_int_06b',
            list = {
                {model = 'prop_plant_int_06b',            w = 50.0},
                {model = 'prop_pot_plant_02a',            w = 50.0},
                {model = 'prop_pot_plant_02b',            w = 50.0},
                {model = 'prop_pot_plant_02c',            w = 50.0},
                {model = 'prop_pot_plant_02d',            w = 50.0},
                {model = 'prop_pot_plant_03a',            w = 50.0},
                {model = 'prop_pot_plant_03b',            w = 50.0},
                {model = 'prop_pot_plant_03c',            w = 50.0},
                {model = 'prop_pot_plant_04a',            w = 50.0},
                {model = 'prop_pot_plant_04b',            w = 50.0},
                {model = 'prop_pot_plant_04c',            w = 50.0},
                {model = 'prop_pot_plant_05c',            w = 50.0},
                {model = 'prop_pot_plant_05d',            w = 50.0},
                {model = 'prop_pot_plant_05d_l1',         w = 50.0},
                {model = 'prop_pot_plant_6a',             x = 51.3, y = -1746.19, z = 32.1, w = 50.0},
                {model = 'prop_plant_int_05a',            w = 50.0},
                {model = 'prop_plant_int_05b',            w = 50.0},
                {model = 'prop_plant_int_06a',            w = 50.0},
                {model = 'prop_plant_interior_05a',       w = 50.0},
            }
        },
        {
            label = 'Decorations',
            coords = vector4(55.05920791625976, -1766.9393310546876, 29.47967529296875, -155.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(51.863308, -1762.427368, 30.634981),
                pointZ = 0.0,
                fov = 40.0
            },
            
            placeOnGround = true,
            default = 'prop_doghouse_01',
            list = {
                -- html/images/furniture/prop_beach_lilo_02.webp
                -- html/images/furniture/prop_beach_parasol_05.webp
                -- html/images/furniture/prop_beach_ring_01.webp
                -- html/images/furniture/prop_kayak_01b.webp
                {model = 'prop_doghouse_01',        x = 55.19010543823242, y = -1767.084960937, w = -47.87562},
                {model = 'prop_gnome1',             w = -155.0},
                {model = 'prop_gnome2',             w = -155.0},
                {model = 'prop_gnome3',             w = -155.0},
                {model = 'prop_stoneshroom1',       w = -155.0},
                {model = 'prop_stoneshroom2',       w = -155.0},
                {model = 'prop_beach_sandcas_04',   x = 54.916031, y = -1766.709473, w = 228.99},
                {model = 'prop_beware_dog_sign',    w = -155.0},
                {model = 'prop_flamingo',           w = -155.0},
                {model = 'prop_shrub_rake',         w = -155.0},
                {model = 'prop_stickbfly',          w = -155.0},
                {model = 'prop_stickhbird',         w = -155.0},
            }
        },
        {
            label = 'Bins',
            coords = vector4(53.13338851928711, -1769.2247314453126, 29.47967529296875, 80.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(49.547081, -1763.294678, 31.4),
                pointZ = 0.5,
                fov = 30.0
            },
            
            placeOnGround = true,
            default = 'prop_bin_delpiero',
            list = {
                {model = 'prop_bin_delpiero',       w = 80.0},
                {model = 'prop_bin_delpiero_b',     w = 80.0},
                {model = 'prop_bin_01a',            w = 80.0},
                {model = 'prop_bin_02a',            w = 80.0},
                {model = 'prop_bin_03a',            w = 80.0},
                {model = 'prop_bin_04a',            w = 80.0},
                {model = 'prop_bin_05a',            w = 80.0},
                {model = 'prop_bin_06a',            w = 80.0},
                {model = 'prop_bin_07a',            w = 80.0},
                {model = 'prop_bin_07b',            w = 80.0},
                {model = 'prop_bin_07c',            w = 80.0},
                {model = 'prop_bin_07d',            w = 80.0},
                {model = 'prop_bin_08a',            w = 183.19},
                {model = 'prop_bin_08open',         w = 183.19},
                {model = 'prop_bin_09a',            w = 183.19},
                {model = 'prop_bin_10a',            w = 183.19},
                {model = 'prop_bin_10b',            w = 183.19},
                {model = 'prop_bin_11a',            w = 183.19},
                {model = 'prop_bin_11b',            w = 183.19},
                {model = 'prop_bin_12a',            w = 183.19},
                {model = 'prop_recyclebin_01a',     w = 183.19},
                {model = 'prop_recyclebin_02a',     w = 183.19},
                {model = 'prop_recyclebin_02b',     w = 183.19},
                {model = 'prop_recyclebin_03_a',    w = 183.19},
                {model = 'prop_rub_binbag_01',      w = 183.19},
                {model = 'zprop_bin_01a_old',       w = 183.19},
            }
        },
        {
            label = 'BBQ\'s',
            coords = vector4(48.4316177368164, -1769.6492919921876, 28.62, 140.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(53.321602, -1764.827393, 30.525059),
                pointZ = 0.5,
                fov = 39.0
            },
            
            placeOnGround = false,
            default = 'prop_bbq_5',
            list = {
                {model = 'prop_bbq_5',      w = 140.0},
                {model = 'prop_bbq_1',      w = 140.0},
                {model = 'prop_bbq_3',      w = 140.0},
                {model = 'prop_bbq_4',      w = 140.0},
                {model = 'prop_beach_fire', z = 28.8, w = 140.0},
            }
        },
        {
            label = 'Bench',
            coords = vector4(41.31587600708008, -1762.2496337890626, 28.54317283630371, 90.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(48.305859, -1760.034546, 30.9),
                pointZ = 0.6,
                fov = 36.0
            },
            
            placeOnGround = true,
            default = 'prop_bench_08',
            list = {
                {model = 'prop_bench_01a',          w = 90.0},
                {model = 'prop_bench_01b',          w = 90.0},
                {model = 'prop_bench_01c',          w = 90.0},
                {model = 'prop_bench_02',           w = 90.0},
                {model = 'prop_bench_03',           w = 90.0},
                {model = 'prop_bench_04',           w = 90.0},
                {model = 'prop_bench_06',           w = 90.0},
                {model = 'prop_bench_07',           w = 90.0},
                {model = 'prop_bench_08',           w = 90.0},
                {model = 'prop_bench_10',           w = 90.0},
                {model = 'prop_bench_11',           w = 90.0},
                {model = 'prop_picnictable_01',     w = 90.0},
                {model = 'prop_picnictable_01_lod', w = 90.0},
                {model = 'prop_picnictable_02',     w = 90.0},
            }
        },
        {
            label = 'Beach chairs',
            coords = vector4(45.94565963745117, -1756.6829833984376, 28.54317283630371, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(51.742634, -1758.120850, 30.525059),
                pointZ = 0.6,
                fov = 39.0
            },
            
            placeOnGround = true,
            default = 'prop_patio_lounger_3',
            list = {
                {model = 'prop_patio_lounger_3', w = 50.0},
                {model = 'prop_patio_lounger_2', w = 50.0},
                {model = 'hei_prop_yah_lounger', w = 50.0},
                {model = 'prop_old_deck_chair',  w = 50.0},
                {model = 'prop_patio_lounger1',  w = 50.0},
                {model = 'prop_patio_lounger1b', w = 50.0},
            }
        },
        {
            label = 'Water',
            coords = vector4(45.61604690551758, -1766.2547607421876, 28.54317283630371, 140.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(51.620907, -1762.474976, 31.495081),
                pointZ = 0.5,
                fov = 39.0
            },
            
            placeOnGround = true,
            default = 'prop_hottub2',
            list = {
                {model = 'prop_hottub2',    w = 140.0},
                {model = 'prop_fountain2',  w = 140.0},
            }
        },
        {
            label = 'Tables',
            coords = vector4(51.74920272827148, -1753.9019775390626, 28.94257354736328, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(51.047764, -1759.031006, 30.945068),
                pointZ = 0.0,
                fov = 44.0
            },
            
            placeOnGround = true,
            default = 'prop_table_05',
            list = {
                {model = 'prop_table_05',             w = 50.0},
                {model = 'prop_patio_lounger1_table', w = 50.0},
                {model = 'prop_proxy_chateau_table',  w = 50.0},
                {model = 'prop_rub_table_01',         w = 50.0},
                {model = 'prop_rub_table_02',         w = 50.0},
                {model = 'prop_table_01',             w = 50.0},
                {model = 'prop_table_02',             w = 50.0},
                {model = 'prop_table_03',             w = 50.0},
                {model = 'prop_table_03b',            w = 50.0},
                {model = 'prop_table_03b_cs',         w = 50.0},
                {model = 'prop_table_04',             w = 50.0},
                {model = 'prop_table_06',             w = 50.0},
                {model = 'prop_table_07',             w = 50.0},
                {model = 'prop_table_08',             w = 50.0},
                {model = 'prop_yacht_table_01',       w = 50.0},
                {model = 'prop_yacht_table_02',       w = 50.0},
                {model = 'prop_yacht_table_03',       w = 50.0},
                {model = 'hei_prop_yah_table_01',     w = 50.0},
                {model = 'hei_prop_yah_table_02',     w = 50.0},
                {model = 'hei_prop_yah_table_03',     w = 50.0},
            }
        },
        {
            label = 'Chairs',
            coords = vector4(49.16395950317383, -1752.0145263671876, 28.5422306060791, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(51.645802, -1757.254517, 30.945068),
                pointZ = 0.6,
                fov = 36.0
            },
            
            placeOnGround = true,
            default = 'prop_table_05_chr',
            list = {
                {model = 'prop_table_05_chr',           w = 50.0},
                {model = 'prop_chair_01a',              w = 50.0},
                {model = 'prop_chair_01b',              w = 50.0},
                {model = 'prop_chair_02',               w = 50.0},
                {model = 'prop_chair_08',               w = 50.0},
                {model = 'prop_chair_09',               w = 50.0},
                {model = 'prop_chair_10',               w = 50.0},
                {model = 'prop_gc_chair02',             w = 50.0},
                {model = 'prop_old_wood_chair',         w = 50.0},
                {model = 'prop_old_wood_chair_lod',     w = 50.0},
                {model = 'prop_skid_chair_01',          w = 50.0},
                {model = 'prop_skid_chair_02',          w = 50.0},
                {model = 'prop_skid_chair_03',          w = 50.0},
                {model = 'prop_table_01_chr_a',         w = 50.0},
                {model = 'prop_table_01_chr_b',         w = 50.0},
                {model = 'prop_table_02_chr',           w = 50.0},
                {model = 'prop_table_03_chr',           w = 50.0},
                {model = 'prop_table_03b_chr',          w = 50.0},
                {model = 'prop_table_04_chr',           w = 50.0},
                {model = 'prop_table_06_chr',           w = 50.0},
                {model = 'hei_prop_yah_seat_01',        w = 50.0},
                {model = 'hei_prop_yah_seat_02',        w = 50.0},
                {model = 'hei_prop_yah_seat_03',        w = 50.0},
                {model = 'prop_yaught_chair_01',        w = 50.0},
                {model = 'prop_yaught_sofa_01',         w = 50.0},
                {model = 'sf_mp_h_yacht_armchair_03',   w = 50.0},
            }
        },
        
        -- Storage section
        {
            label = 'Storage',
            coords = vector4(66.933837890625, -1757.2171630859, 29.364799499512, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(72.683105, -1759.589111, 30.534967),
                pointZ = 0.4,
                fov = 31.0
            },

            placeOnGround = true,
            default = 'p_v_43_safe_s',
            list = {
                {model = 'p_v_43_safe_s',                   w = 50.0},
                {model = 'gr_prop_gr_rsply_crate04a',       w = 50.0},
                {model = 'm23_2_prop_m32_weaponcrate_01a',  w = 50.0},
                {model = 'm23_2_prop_m32_cabinet_02a',      w = 50.0},
            }
        },
        {
            label = 'Storage',
            coords = vector4(65.017517089844, -1759.4603271484, 28.614545822144, 50.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(68.57, -1760.7, 30.534967),
                pointZ = 1.2,
                fov = 46.0
            },

            placeOnGround = true,
            default = 'prop_ff_shelves_01',
            list = {
                {model = 'prop_ff_shelves_01',                  w = 50.0},
                {model = 'bkr_prop_biker_garage_locker_01',     w = 50.0},
                {model = 'bkr_prop_gunlocker_01a',              w = 50.0},
                {model = 'prop_cabinet_01',                     w = 50.0},
                {model = 'prop_cabinet_02b',                    w = 50.0},
            }
        },
        
        -- Decor section
        {
            label = 'Doors',
            coords = vector4(57.97758483886719, -1743.043701171875, 29.7699909210205, -129.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(65.716522, -1743.910645, 30.215036),
                pointZ = 0.0,
                fov = 43.0
            },
            
            placeOnGround = true,
            default = 'tr_prop_tr_door2',
            list = {
                {model = 'tr_prop_tr_door2',                    isDoor = true},
                {model = 'tr_prop_tr_door3',                    isDoor = true},
                {model = 'ex_p_mp_door_apart_door',             isDoor = true},
                {model = 'ex_p_mp_door_apart_door_black_s',     isDoor = true},
                {model = 'ex_p_mp_door_apart_doorbrown_s',      isDoor = true},
                {model = 'ex_p_mp_door_apart_doorwhite01_s',    isDoor = true},
                {model = 'ex_p_mp_door_office_door01_s',        isDoor = true},
                {model = 'hei_heist_apart2_door',               isDoor = true},
                {model = 'm23_1_prop_iaa_base_door_01',         isDoor = true,    w = 52.0},
                {model = 'prop_police_door_r',                  isDoor = true},
                {model = 'sf_prop_sf_door_com_r_06a',           isDoor = true},
                {model = 'sf_prop_sf_door_rec_01a',             isDoor = true},
                {model = 'sum_p_mp_yacht_door_02',              isDoor = true},
                {model = 'v_ilev_dev_door',                     isDoor = true},
                {model = 'v_ilev_tort_door',                    isDoor = true},
                {model = 'xm_prop_x17_l_door_frame_01',         isDoor = true,    w = 52.0},
                {model = 'hei_prop_heist_cutscene_doorb',       isDoor = true,    w = 52.0},
                {model = 'prop_sm1_11_doorr',                   isDoor = true},
                {model = 'v_ilev_bk_door2',                     isDoor = true},
                {model = 'v_ilev_fh_door5',                     isDoor = true},
                {model = 'v_ilev_ra_door2',                     isDoor = true,    w = 52.0},
                {model = 'v_ilev_roc_door3',                    isDoor = true,    w = 52.0},
                {model = 'v_ilev_trev_doorbath',                isDoor = true},
                {model = 'ch_prop_ch_corridor_door_derelict',   isDoor = true,    w = 52.0},
            }
        },
        {
            label = 'Interactable',
            coords = vector4(62.739067077637, -1737.1203613281, 29.55, -130.0), -- Location of the point
            radius = 2.0, -- Interaction radius
            
            camera = {
                coords = vec3(65.112389, -1741.158813, 31.35),
                pointZ = 0.3,
                fov = 40.0
            },
            
            placeOnGround = true,
            default = 'prop_lawnmower_01',
            list = {
                {model = 'prop_lawnmower_01',               w = 1.0663990974426},
                {model = 'm23_2_prop_m32_bucket_mop_01b',   w = 190.66},
            }
        },
        {
            label = 'Decorations',
            coords = vector4(71.05659484863281, -1744.0928955078126, 29.55, 190.66), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(70.949028, -1740.408325, 31.35),
                pointZ = 1.0,
                fov = 50.0
            },
            
            placeOnGround = true,
            default = 'v_res_vacuum',
            list = {
                {model = 'v_res_vacuum',                    w = 190.66},
                {model = 'prop_fan_01',                     w = 190.66},
                {model = 'prop_watercooler',                w = 190.66},
                {model = 'sf_prop_sf_bed_dog_01a',          w = 230.09},
                {model = 'sf_prop_sf_bed_dog_01b',          w = 230.09},
            }
        },
        {
            label = 'Decorations',
            coords = vector4(68.86103057861328, -1746.7142333984376, 29.55, 230.0), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(65.644524, -1744.964478, 31.35),
                pointZ = 0.2,
                fov = 33.0
            },
            
            placeOnGround = true,
            default = 'vw_prop_casino_art_gun_01a',
            list = {
                {model = 'vw_prop_casino_art_gun_01a',        w = 230.0},
                {model = 'vw_prop_casino_art_gun_02a',        w = 230.0},
                {model = 'vw_prop_casino_art_car_04a',        w = 230.0},
                {model = 'vw_prop_casino_art_car_09a',        w = 230.0},
                {model = 'vw_prop_casino_art_car_11a',        w = 230.0},
                {model = 'vw_prop_casino_art_miniature_05b',  w = 230.0},
                {model = 'vw_prop_casino_art_head_01c',       w = 230.0},
                {model = 'prop_jewel_02a',                    w = 230.0},
                {model = 'vw_prop_vw_watch_case_01b',         w = 230.0},
                {model = 'prop_sponge_01',                    w = 230.0},
                {model = 'v_res_r_perfume',                   w = 230.0},
                {model = 'prop_fib_ashtray_01',               w = 230.0},
            }
        },
        {
            label = 'Decorations',
            coords = vector4(66.64784240722656, -1749.3369140625, 29.55, 266.16), -- Location of the point
            radius = 2.0, -- Interaction radius

            camera = {
                coords = vec3(62.350880, -1748.825806, 31.1),
                pointZ = 0.3,
                fov = 30.0
            },
            
            placeOnGround = true,
            default = 'prop_suitcase_01',
            list = {
                {model = 'prop_suitcase_01',   w = 266.16},
                {model = 'prop_suitcase_01b',  w = 266.16},
                {model = 'prop_suitcase_01c',  w = 266.16},
                {model = 'prop_suitcase_01d',  w = 266.16},
                {model = 'prop_suitcase_02',   w = 266.16},
                {model = 'prop_suitcase_03',   w = 266.16},
                {model = 'prop_suitcase_03b',  w = 266.16},
            }
        },



        -- h4_prop_h4_glass_disp_01a
        -- p_new_j_counter_03
        -- prop_chall_lamp_02

        -- prop_doghouse_01
    }
}
```

{% endcode %}


# config.regions.lua

Preview File Updated: v1.4.4 - 06/06/2026

{% code fullWidth="true" %}

```lua
Config.NoRegion = {
    electricity = {
        rate = 0.01, -- per kWh
        unit = "kWh"
    },
    water = {
        rate = 0.55, -- per 1L
        unit = "1L"
    },
    internet = {
        flatRate = 80
    },
}

Config.Regions = {
    ['Mirror Park'] = {
        debugColor = vec(200, 100, 120), -- r, g, b

        electricity = {
            rate = 0.0135, -- per kWh
            unit = "kWh"
        },
        water = {
            rate = 0.6, -- per 1L
            unit = "1L"
        },
        internet = {
            flatRate = 130
        },

        zone = {
            {x = 1123.9431152344, y = -277.8190612793},
            {x = 1069.5903320312, y = -336.39959716797},
            {x = 976.44384765625, y = -416.92517089844},
            {x = 881.31329345703, y = -451.77828979492},
            {x = 802.57055664062, y = -505.89730834961},
            {x = 800.03051757812, y = -612.28265380859},
            {x = 892.60247802734, y = -678.92999267578},
            {x = 944.78491210938, y = -713.77856445312},
            {x = 998.83117675781, y = -776.65563964844},
            {x = 1046.3355712891, y = -819.97204589844},
            {x = 1110.6312255859, y = -826.97100830078},
            {x = 1160.6580810547, y = -818.47253417969},
            {x = 1190.0313720703, y = -781.72180175781},
            {x = 1227.1156005859, y = -767.60327148438},
            {x = 1259.8072509766, y = -761.01031494141},
            {x = 1347.5787353516, y = -783.70031738281},
            {x = 1398.1555175781, y = -799.03857421875},
            {x = 1436.841796875, y = -769.32440185547},
            {x = 1437.1605224609, y = -661.86456298828},
            {x = 1453.1373291016, y = -568.69812011719},
            {x = 1399.1130371094, y = -506.35372924805},
            {x = 1333.2176513672, y = -385.39117431641},
            {x = 1273.9291992188, y = -314.56201171875},
            {x = 1203.5946044922, y = -265.16271972656},
        }
    },
    ['Paleto Bay'] = { ... },
    ['Sandy Shores'] = { ... },
    ['Richman'] = { ... },
    ['West Vinewood'] = { ... },
    ['Harmony'] = { ... },
    ['Rockford Hills'] = { ... },
    ['Burton'] = { ... },
    ['Downtown Vinewood'] = { ... },
    ['East Vinewood'] = { ... },
    ['Vinewood Hills'] = { ... },
    ['Alta'] = { ... },
    ['Pillbox Hill'] = { ... },
    ['Little Seoul'] = { ... },
    ['Vespucci Canals'] = { ... },
    ['Del Perro Beach'] = { ... },
    ['Del Perro'] = { ... },
    ['Vespucci Beach'] = { ... },
    ['Pacific Bluffs'] = { ... },
    ['Strawberry'] = { ... },
    ['Davis'] = { ... },
    ['Rancho'] = { ... },
    ['El Burro Heights'] = { ... },
}
```

{% endcode %}


# config.js

Preview File Updated: v1.4.4 - 06/06/2026

{% code fullWidth="true" %}

```javascript
const config = {}

config.timeZone = 'pl-PL'

config.filterFurnitureAlphabetically = false

config.shellsTags = {
    // Required, do not modify 'default' key value!
    'default': 'Default',
    
    // Shell state:
    'empty': 'Empty',
    'furnished': 'Furnished',

    // Creators:
    'k4mb1': 'K4MB1',
    'envi': 'ENVI',
    'dccustomz': 'DC Customz',
    'kuzquality': 'KuzQuality',
    'jamaring': 'Jamaring Maps',
    'ateneastore': 'Atenea',
    'maxcreations': 'Max Creations',
}

config.iplsTags = {
    // Required, do not modify 'default' key value!
    'default': 'Default',
    
    // Shell state:
    'furnished': 'Furnished',
    'apartment': 'Apartment',
    'office': 'Office',
}

config.furnitureTags = {
    "bed": "Beds",
    "sofa": "Sofas",
    "chair": "Chairs",
    "bathroom": "Bathroom",
    "kitchen": "Kitchen",
    "electronic": "Electronics",
    "light": "Lights",
    "plant": "Plants",
    "storage": "Storages",
    "table": "Tables",
    "decorations": "Decorations",
    "outdoor": "Outdoor",
    "recreation": "Recreation",
    "door": "Doors",

    "theme": "Themes",
}

config.interactableInfo = {
    wardrobe: {
        label: "Wardrobe",      // Label
        description: `You can dress up.`,
    },
    storage: {
        label: "Storage",       // Label
        description: `You can store your items there.`,
        weight: 'Max Weight',   // Metadata weight
        slots: 'Slots',         // Metadata slots
    },
    safe: {
        label: "Safe",          // Label
        description: `You can store your items there.`,
        weight: 'Max Weight',   // Metadata weight
        slots: 'Slots',         // Metadata slots
    },
    device: {
        label: "Device",        // Label
        description: `You can use the device to make orders from IKEA.`,
    },
    door: {
        label: "Door",          // Label
        description: ``,
    },
    mop: {
        label: "Mop",           // Label
        description: `You can clean dirt in your property using a mop.`,
    },
    mower: {
        label: "Mower",           // Label
        description: `You can mow your lawn using a mower.`,
    },
}

config.configuratorFurnitureInteractables = [
    "wardrobe",
    "storage",
    "safe",
    "device",
    "door",
    "mop",
    "mower",
]

config.configuratorFurnitureMetadata = {
    safe: [
        {key: "slots", allowedValue: "number", defaultValue: 10},
        {key: "weight", allowedValue: "number", defaultValue: 1500},
    ],
    storage: [
        {key: "slots", allowedValue: "number", defaultValue: 10},
        {key: "weight", allowedValue: "number", defaultValue: 1500},
    ],
}

config.charactersLimit = {
    name: 45,
    description: 400,
}

config.controlsList = {
    'Property:mop': [
        { control: 'G',             label: 'Clean' },
    ],
    'Property:photomode_off': [
        { control: 'E',             label: 'Enter Photo Mode' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'Property:photomode_on': [
        { control: 'ENTER',         label: 'Save' },
        { control: 'E',             label: 'Exit Photo Mode' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'Property:peephole': [
        { control: 'BACKSPACE',     label: 'Exit' },
    ],
    'Property:camera': [
        { control: 'ARROW LEFT',    label: 'Previous' },
        { control: 'ARROW RIGHT',   label: 'Next' },
        { control: 'BACKSPACE',     label: 'Exit' },
    ],
    'Property:ipl_theme': [
        { control: 'ENTER',         label: 'Accept' },
        { control: 'BACKSPACE',     label: 'Exit' },
    ],
    'Property:visiting_time': [
        { info: 'Time remaining',   label: '<span id="remaining_time"></span>s.' },
    ],
    'Furniture:gizmo': [
        { control: 'ENTER',         label: 'Place' },
        { control: 'DEL',           label: 'Take to Storage' },
        { control: 'RMB',           label: 'Toggle Cursor' },
        { control: 'G',             label: 'Change Mode' },
        { control: 'V',             label: 'Change Relative' },
        { control: 'X',             label: 'Translation' },
        { control: 'C',             label: 'Rotation' },
        { control: 'LEFT ALT',      label: 'Snap to Ground' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'Furniture:walkmode': [
        { control: 'ENTER',         label: 'Place' },
        { control: 'DEL',           label: 'Take to Storage' },
        { control: 'SCROLL',        label: 'Rotate' },
        { control: 'G',             label: 'Change Mode' },
        { control: 'ARROW UP',      label: '+Z Axis' },
        { control: 'ARROW DOWN',    label: '-Z Axis' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'Furniture:gizmo_interior_designer': [
        { control: 'ENTER',         label: 'Place' },
        { control: 'DEL',           label: 'Delete' },
        { control: 'RMB',           label: 'Toggle Cursor' },
        { control: 'G',             label: 'Change Mode' },
        { control: 'X',             label: 'Translation' },
        { control: 'C',             label: 'Rotation' },
        { control: 'LEFT ALT',      label: 'Snap to Ground' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'Furniture:walkmode_interior_designer': [
        { control: 'ENTER',         label: 'Place' },
        { control: 'DEL',           label: 'Delete' },
        { control: 'SCROLL',        label: 'Rotate' },
        { control: 'G',             label: 'Change Mode' },
        { control: 'ARROW UP',      label: '+Z Axis' },
        { control: 'ARROW DOWN',    label: '-Z Axis' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'HousingCreator:shell_position': [
        { control: 'LMB',           label: 'Save' },
        { control: 'SCROLL',        label: 'Height' },
        { control: 'BACKSPACE',     label: 'Cancel' },
        { control: 'ARROW UP',      label: 'Forward' },
        { control: 'ARROW DOWN',    label: 'Backward' },
        { control: 'ARROW LEFT',    label: 'Left' },
        { control: 'ARROW RIGHT',   label: 'Right' },
    ],
    'HousingCreator:polyzone': [
        { control: 'LMB',           label: 'Add Point' },
        { control: 'RMB',           label: 'Remove Previous Point' },
        { control: 'SCROLL',        label: 'Height' },
        { control: 'ENTER',         label: 'Save' },
        { control: 'BACKSPACE',     label: 'Cancel' },
        { control: 'LEFT CTRL',     label: 'Hold to change the lower zone' },
    ],
    'HousingCreator:doors': [
        { control: 'LMB',           label: 'Select door' },
        { control: 'ENTER',         label: 'Save' },
        { control: 'BACKSPACE',     label: 'Cancel' },
        { control: 'SCROLL',        label: 'Change Distance' },
        { info: 'Access Distance:', label: '<span id="doors_distance_access"></span>m.' },
    ],
    'HousingCreator:sign': [
        { control: 'LMB',           label: 'Select position' },
        { control: 'SCROLL',        label: 'Rotate' },
        { control: 'LEFT CTRL',     label: 'Slow down' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'HousingCreator:grass': [
        { control: 'LMB',           label: 'Select position' },
        { control: 'SCROLL',        label: 'Rotate' },
        { control: 'LEFT CTRL',     label: 'Slow down' },
        { control: 'ARROW LEFT',    label: 'Previous Model' },
        { control: 'ARROW RIGHT',   label: 'Next Model' },
        { control: 'BACKSPACE',     label: 'Cancel' },
        { info: 'Grass Count:',     label: '<span id="grass_count"></span>' },
    ],
    'HousingCreator:garage': [
        { control: 'LMB',           label: 'Select position' },
        { control: 'SCROLL',        label: 'Rotate' },
        { control: 'LEFT CTRL',     label: 'Slow down' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'HousingCreator:enter_garage': [
        { control: 'LMB',           label: 'Select position' },
        { control: 'SCROLL',        label: 'Rotate' },
        { control: 'LEFT CTRL',     label: 'Slow down' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'HousingCreator:parking': [
        { control: 'LMB',           label: 'Select position' },
        { control: 'RMB',           label: 'Remove closest' },
        { control: 'SCROLL',        label: 'Rotate' },
        { control: 'LEFT CTRL',     label: 'Slow down' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'HousingCreator:delivery': [
        { control: 'LMB',           label: 'Select position' },
        { control: 'SCROLL',        label: 'Rotate' },
        { control: 'LEFT CTRL',     label: 'Slow down' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'HousingCreator:interaction': [
        { control: 'LMB',           label: 'Accept' },
        { control: 'SCROLL',        label: 'Rotate' },
        { control: 'CTRL + SCROLL', label: 'Coord-Z' },

        { control: 'ARROW LEFT',    label: '+X' },
        { control: 'CTRL + ARROW LEFT', label: '-X' },

        { control: 'ARROW RIGHT', label: '+Y' },
        { control: 'CTRL + ARROW RIGHT', label: '-Y' },

        { control: 'ARROW UP',      label: '+Height' },
        { control: 'ARROW DOWN',    label: '-Height' },
        
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'HousingCreator:interaction_option': [
        { control: 'LMB',           label: 'Select position' },
        { control: 'ARROWS',        label: 'Position' },
        { control: 'SCROLL',        label: 'Position Z' },
        { control: 'LEFT CTRL',     label: 'Slow down' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'HousingCreator:default': [
        { control: 'LMB',           label: 'Select position' },
        { control: 'BACKSPACE',     label: 'Cancel' },
    ],
    'InteriorDesigner:default': [
        { control: 'F',             label: 'Furniture Menu' },
        { control: 'X',             label: 'Manage Project' },
    ],
}
```

{% endcode %}


# Integration Files


# Clothing


# Client-Side

{% tabs %}
{% tab title="vms\_clothestore" %}

```lua
if Config.Clothing ~= 'vms_clothestore' then
    return
end

function OpenWardrobe()
    exports['vms_clothestore']:OpenWardrobe()
end
```

{% endtab %}

{% tab title="qb-clothing" %}

```lua
if Config.Clothing ~= 'qb-clothing' then
    return
end

function OpenWardrobe()
    TriggerEvent('qb-clothing:client:openOutfitMenu')
end
```

{% endtab %}
{% endtabs %}


# Inventory


# Client-Side

{% tabs %}
{% tab title="ox\_inventory" %}

```lua
if Config.Inventory ~= 'ox_inventory' then
    return
end

function OpenStorage(metadata)
    exports.ox_inventory:openInventory('stash', {id = metadata.id})
end
```

{% endtab %}

{% tab title="qb-inventory" %}

```lua
if Config.Inventory ~= 'qb-inventory' then
    return
end

function OpenStorage(metadata)
    TriggerServerEvent('vms_housing:qb-inventory:openStorage', metadata.id, {
        maxweight = metadata.weight,
        slots = metadata.slots
    })
end
```

{% endtab %}
{% endtabs %}


# Server-Side

{% tabs %}
{% tab title="ox\_inventory" %}
{% code expandable="true" %}

```lua
if Config.Inventory ~= 'ox_inventory' then
    return
end

function RegisterStorage(metadata)
    local slots = tonumber(metadata.slots)
    local weight = tonumber(metadata.weight)
    
    if not slots or not weight then
        return
    end

    exports['ox_inventory']:RegisterStash(metadata.id, 'Storage', slots, weight)
end

function RegisterUsableItem(name, cb)
    if Config.Core == "ESX" then
        Core.RegisterUsableItem(name, function(src, itemName, itemData)
            cb(src, itemName, { metadata = itemData.metadata })
        end)
    elseif Config.Core == "QB-Core" then
        Core.Functions.CreateUseableItem(name, function(src, item)
            cb(src, item.name, { metadata = item.metadata })
        end)
    end
end

function GetItem(src, xPlayer, name, data, search)
    if search == 'key' then
        return exports['ox_inventory']:GetItem(src, name, {keySerialNumber = data.keySerialNumber}, true) >= 1
    end
end

function AddItem(src, xPlayer, name, count, metadata)
    exports['ox_inventory']:AddItem(src, name, count, metadata)
end

function RemoveItem(src, xPlayer, name, count)
    exports['ox_inventory']:RemoveItem(src, name, count)
end
```

{% endcode %}
{% endtab %}

{% tab title="qb-inventory" %}
{% code expandable="true" %}

```lua
if Config.Inventory ~= 'qb-inventory' then
    return
end

function RegisterStorage(metadata)
    local slots = tonumber(metadata.slots)
    local weight = tonumber(metadata.weight)
    
    if not slots or not weight then
        return
    end

    exports['qb-inventory']:CreateInventory(metadata.id, {
        label = 'Storage',
        slots = slots,
        weight = weight,
    })
end

RegisterNetEvent('vms_housing:qb-inventory:openStorage', function(id, data)
    local src = source

    if not id or not string.find(id, "house_storage") then
        return
    end

    exports['qb-inventory']:OpenInventory(src, id, {
        maxweight = data.maxweight,
        slots = data.slots,
    })
end)

function RegisterUsableItem(name, cb)
    Core.Functions.CreateUseableItem(name, function(src, item)
        cb(src, item.name, {metadata = item.info})
    end)
end

function GetItem(src, xPlayer, name, data, search)
    if search == 'key' then
        local items = exports['qb-inventory']:GetItemsByName(src, name)
        for k, v in pairs(items) do
            if v.info and v.info.keySerialNumber == data.keySerialNumber then
                return true
            end
        end
    end
end

function AddItem(src, xPlayer, name, count, metadata)
    exports['qb-inventory']:AddItem(src, name, count, false, metadata)
end

function RemoveItem(src, xPlayer, name, count)
    exports['qb-inventory']:RemoveItem(src, name, count)
end
```

{% endcode %}
{% endtab %}
{% endtabs %}


# Garage


# Client-Side

{% tabs %}
{% tab title="vms\_garagesV2" %}

```lua
if Config.Garages ~= 'vms_garagesv2' then
    return
end

function OpenGarage(propertyId, propertyData)
    exports['vms_garagesv2']:enterHouseGarage()
end
```

{% endtab %}

{% tab title="rcore\_garage" %}
{% code expandable="true" %}

```lua
if Config.Garages ~= 'rcore_garage' then
    return
end

function OpenGarage(propertyId, propertyData)
    local myPed = PlayerPedId()
    local myVehicle = GetVehiclePedIsIn(myPed, false)
    local isInVehicle = myVehicle and myVehicle ~= 0 and GetPedInVehicleSeat(myVehicle, -1) == myPed
    if isInVehicle then
        TriggerEvent("rcore_garage:StoreMyVehicle", "car")
    else
        TriggerEvent("rcore_garage:OpenGarageOnSpot", "car", "civ")
    end
end
```

{% endcode %}
{% endtab %}
{% endtabs %}


# Server-Side

{% tabs %}
{% tab title="vms\_garagesV2" %}

```lua
if Config.Garages ~= 'vms_garagesv2' then
    return
end

---@param isNew - If the property is newly created, you will get true, if the property is edited, it will be false
function RegisterGarage(propertyId, propertyData, isNew)
    exports['vms_garagesv2']:registerHousingGarage(
        'vms_housing.garage.' .. propertyId,                                                                                                        --[[ File name - Garage ID will always be as "HouseGarage:vms_housing.garage-ID" ]]
        propertyData.name,                                                                                                                          --[[ Garage Label ]]
        propertyId,                                                                                                                                 --[[ Housing ID ]]
        vector4(propertyData.metadata.garage.x, propertyData.metadata.garage.y, propertyData.metadata.garage.z, propertyData.metadata.garage.w),    --[[ Coordinates ]]
        1                                                                                                                                           --[[ Garage Interior ID (Config.ParkingCreator.HouseGarageInteriors) ]]
    )
end
```

{% endtab %}
{% endtabs %}


# Phone


# Client-Side

{% tabs %}
{% tab title="lb-phone" %}
{% code expandable="true" %}

```lua
CreateThread(function()
    Wait(1000)
    
    if Config.Phone ~= 'lb-phone' then
        return
    end
    
    function SendSMS(data)
        exports["lb-phone"]:SendMessage(TRANSLATE('sms.alarm:sender'), data.phoneNumber, data.message)
    
        if data.coords then
            exports["lb-phone"]:SendCoords(TRANSLATE('sms.alarm:sender'), data.phoneNumber, data.coords.xy)
        end
    end
end)
```

{% endcode %}
{% endtab %}
{% endtabs %}


# Dispatch


# Client-Side

{% tabs %}
{% tab title="Default" %}
{% code expandable="true" %}

```lua
if Config.Dispatch then
    return
end

function DispatchAlertClient(property, type)
    -- Nothing
end

RegisterNetEvent('vms_housing:cl:dispatchAlert', function(propertyId, coords, alarmType)
    local hasJob = false
    
    local property = Properties[propertyId]

    if Config.Alarm.AlarmJobs then
        local myJob = CL.GetPlayerJob('name')

        if type(Config.Alarm.AlarmJobs) == 'table' then
            for _, jobName in ipairs(Config.Alarm.AlarmJobs) do
                if myJob == jobName then
                    hasJob = true
                    break
                end
            end
        else
            if myJob == Config.Alarm.AlarmJobs then
                hasJob = true
            end
        end
    end

    if hasJob then
        CL.Notification(TRANSLATE('notify.lockpick:alarm_' .. alarmType), 12000, 'dispatch')

        local blipId = #AlarmBlips + 1

        AlarmBlips[blipId] = {
            alpha = 220,
            blip = AddBlipForCoord(coords),
            radius = AddBlipForRadius(coords, 25.0),
        }

        SetBlipSprite(AlarmBlips[blipId].blip, 480)
        SetBlipDisplay(AlarmBlips[blipId].blip, 4)
        SetBlipScale(AlarmBlips[blipId].blip, 1.0)
        SetBlipColour(AlarmBlips[blipId].blip, 1)
        SetBlipAsShortRange(AlarmBlips[blipId].blip, false)

        BeginTextCommandSetBlipName("STRING")
        AddTextComponentString(TRANSLATE('blip.alarm'))
        EndTextCommandSetBlipName(AlarmBlips[blipId].blip)

        SetBlipHighDetail(AlarmBlips[blipId].radius, true)
        SetBlipColour(AlarmBlips[blipId].radius, 1)
        SetBlipAlpha(AlarmBlips[blipId].radius, 90)
        SetBlipAsShortRange(AlarmBlips[blipId].radius, true)

        Citizen.CreateThread(function()
            Citizen.Wait(30000)
            while AlarmBlips[blipId].alpha ~= 0 do
                AlarmBlips[blipId].alpha = AlarmBlips[blipId].alpha - 1
                SetBlipAlpha(AlarmBlips[blipId].radius, AlarmBlips[blipId].alpha)

                if AlarmBlips[blipId].alpha <= 0 then
                    RemoveBlip(AlarmBlips[blipId].radius)
                    RemoveBlip(AlarmBlips[blipId].blip)
                    AlarmBlips[blipId] = nil
                    break
                end

                Citizen.Wait(200)
            end
        end)
    end
end)
```

{% endcode %}
{% endtab %}

{% tab title="qs-dispatch" %}
{% code expandable="true" %}

```lua
if Config.Dispatch ~= 'qs-dispatch' then
    return
end

function DispatchAlertClient(property, type)
    local coords = nil
    if property.type == 'mlo' then
        coords = vector3(property.metadata.menu.x, property.metadata.menu.y, property.metadata.menu.z)
    else
        coords = vector3(property.metadata.enter.x, property.metadata.enter.y, property.metadata.enter.z)
    end

    if not coords then
        return
    end

    TriggerServerEvent('qs-dispatch:server:CreateDispatchCall', {
        job = Config.Alarm.AlarmJobs,
        callLocation = coords,
        callCode = {code = '459', snippet = 'Burglary'},
        message = TRANSLATE('notify.lockpick:alarm_' .. type),
        flashes = false,
        blip = {
            sprite = 480,
            scale = 1.0,
            colour = 1,
            flashes = true,
            text = 'Burglary',
            time = (20 * 1000),
        },
    })
end
```

{% endcode %}
{% endtab %}
{% endtabs %}


# Server-Side

{% tabs %}
{% tab title="Default" %}
{% code expandable="true" %}

```lua
if Config.Dispatch then
    return
end

function DispatchAlertServer(src, xPlayer, property, type)
    -- type:
    --   'start'
    --   'success'
    --   'failed'
    
    if not property then
        return
    end
    
    local coords = nil
    if property.type == 'mlo' then
        coords = vector3(property.metadata.menu.x, property.metadata.menu.y, property.metadata.menu.z)
    else
        coords = vector3(property.metadata.enter.x, property.metadata.enter.y, property.metadata.enter.z)
    end

    if not coords then
        return
    end

    TriggerClientEvent('vms_housing:cl:dispatchAlert', -1, tostring(property.id), coords, type)
end
```

{% endcode %}
{% endtab %}
{% endtabs %}


# Banking


# Server-Side

{% tabs %}
{% tab title="vms\_bossmenu" %}
{% code expandable="true" %}

```lua
if Config.Banking ~= 'vms_bossmenu' then
    return
end

function GetSocietyMoney(societyName)
    local money = exports['vms_bossmenu']:getMoney(societyName)
    return money
end

---@class AddSocietyMoney
---@param reason string
---| 'salesContract'
function AddSocietyMoney(societyName, amount, reason)
    exports['vms_bossmenu']:addMoney(societyName, amount)
end

---@class RemoveSocietyMoney
---@param reason string
---| 'purchaseProperty'
function RemoveSocietyMoney(societyName, amount, reason)
    exports['vms_bossmenu']:removeMoney(societyName, amount)
end


---@class RegisterSocietyTransaction

---@class data
---@ propertyName
---@ propertyAddress
---@ propertyRegion
---@ agentName
---@ agentIdentifier
---@ buyerName
---@ buyerIdentifier
---@ sellerName
---@ sellerIdentifier
function RegisterSocietyTransaction(src, societyName, type, data)
    if type == 'propertyPurchase' then
        local title = ('Purchased Property %s %s'):format(data.propertyAddress, data.propertyRegion)
        exports['vms_bossmenu']:addTransaction(src, societyName, nil, 'transfer_outgoing', data.amount, title)
        
    elseif type == 'propertySale' then
        local title = ('Sold %s %s for %s'):format(data.propertyAddress, data.propertyRegion, data.buyerName)
        exports['vms_bossmenu']:addTransaction(src, societyName, nil, 'transfer_incoming', data.amount, title)

    end
end
```

{% endcode %}
{% endtab %}

{% tab title="okokBanking" %}
{% code expandable="true" %}

```lua
if Config.Banking ~= 'okokBanking' then
    return
end

function GetSocietyMoney(societyName)
    local money = exports['okokBanking']:GetAccount(societyName)
    return money
end

---@class AddSocietyMoney
---@param reason string
---| 'salesContract'
function AddSocietyMoney(societyName, amount, reason)
    exports['okokBanking']:AddMoney(societyName, amount)
end

---@class RemoveSocietyMoney
---@param reason string
---| 'purchaseProperty'
function RemoveSocietyMoney(societyName, amount, reason)
    exports['okokBanking']:RemoveMoney(societyName, amount)
end


---@class RegisterSocietyTransaction

---@class data
---@ propertyName
---@ propertyAddress
---@ propertyRegion
---@ agentName
---@ agentIdentifier
---@ buyerName
---@ buyerIdentifier
---@ sellerName
---@ sellerIdentifier
function RegisterSocietyTransaction(src, societyName, type, data)

end
```

{% endcode %}
{% endtab %}
{% endtabs %}


# Boss Menu


# Client-Side

{% tabs %}
{% tab title="vms\_bossmenu" %}
{% code expandable="true" %}

```lua
if Config.BossMenu ~= 'vms_bossmenu' then
    return
end

function OpenBossMenu(societyName)
    exports['vms_bossmenu']:openBossMenu(societyName, 'job')
end
```

{% endcode %}
{% endtab %}

{% tab title="esx\_society" %}

```lua
if Config.BossMenu ~= 'esx_society' then
    return
end

function OpenBossMenu(societyName)
    exports['esx_society']:OpenBossMenu(societyName)
end
```

{% endtab %}
{% endtabs %}


# Weather


# Client-Side

{% tabs %}
{% tab title="vSync" %}
{% code expandable="true" %}

```lua
if Config.Weather ~= 'vSync' then
    return
end

function ToggleWeather(toggle, isIPL)
    if toggle then
        -- When the player enters the house
        TriggerEvent('vSync:toggle', false)
        TriggerEvent('vSync:updateWeather', 'EXTRASUNNY', false) 
        
        if not isIPL then
            Citizen.Wait(100)
            NetworkOverrideClockTime(23, 30, 0)
            ClearOverrideWeather()
            ClearWeatherTypePersist()
            SetWeatherTypePersist('EXTRASUNNY')
            SetWeatherTypeNow('EXTRASUNNY')
            SetWeatherTypeNowPersist('EXTRASUNNY')
        end
    else
        -- When the player leaves the house
        TriggerEvent('vSync:toggle', true)
        TriggerServerEvent('vSync:requestSync')
        SetArtificialLightsState(false)
    end
end
```

{% endcode %}
{% endtab %}

{% tab title="qb-weathersync" %}
{% code expandable="true" %}

```lua
if Config.Weather ~= 'qb-weathersync' and Config.Weather ~= 'qbx_weathersync' then
    return
end

function ToggleWeather(toggle, isIPL)
    if toggle then
        -- When the player enters the house
        if not isIPL then
            TriggerEvent('qb-weathersync:client:DisableSync')

            Citizen.Wait(100)
            
            SetRainLevel(0.0)
            NetworkOverrideClockTime(23, 30, 0)
            ClearOverrideWeather()
            ClearWeatherTypePersist()
            SetWeatherTypePersist('EXTRASUNNY')
            SetWeatherTypeNow('EXTRASUNNY')
            SetWeatherTypeNowPersist('EXTRASUNNY')
        end
    else
        -- When the player leaves the house
        TriggerEvent('qb-weathersync:client:EnableSync')
        SetArtificialLightsState(false)
    end
end
```

{% endcode %}
{% endtab %}
{% endtabs %}




---

[Next Page](/llms-full.txt/1)

