# Information

Redutzu's Scripts creates high quality scripts with beautiful and easy to use interfaces!

{% hint style="success" %}
All scripts are encrypted through the **Cfx.re escrow system** for the safety of every customer!
{% endhint %}

## Compatibility

{% hint style="info" %}
All scripts work perfectly with the latest versions of **QBCore / ESX Legacy** and are compatible with **Oxmysql / Mysql-Async**
{% endhint %}

## **Useful links**

Discord:[ ](http://scripts.redutzu.works/)<https://discord.gg/kJkNSYM9pt>\
Shop: <https://store.redutzu.com/>\
Github: <https://github.com/redutzu>


# Redutzu MDT

Redutzu-MDT is the most advanced and cutting-edge MDT system available. What are you waiting for? Elevate your productivity with Redutzu-MDT today!

{% embed url="<https://www.youtube.com/watch?v=NbZihO86RwE>" %}
*Purchase at*[ *store.redutzu.com*](https://store.redutzu.com)
{% endembed %}


# Installation

Welcome to the Redutzu-MDT installation guide. Here, you will learn how to fully install our asset to ensure a smooth and trouble-free setup for your FiveM server. By carefully following each step in this guide, you will achieve a clean and efficient installation.

{% hint style="info" %}
If you encounter any issues during the installation, please do not hesitate to reach out for assistance. Open a ticket in our Discord server, and our dedicated support team will be ready to help you resolve any problems. We're committed to ensuring that your setup process is as smooth and trouble-free as possible, so feel free to contact us with any questions or concerns you may have.
{% endhint %}

***

## Download the asset

After purchasing the script from our store at [**Redutzu's Scripts Store**](https://store.redutzu.com), head over to [**Keymaster**](https://keymaster.fivem.net/asset-grants). Here, you will find the assets you have acquired. Download the scripts named **"Redutzu MDT"** and **"Redutzu MDT (Prop)"** to your environment.

{% hint style="danger" %}
The script will not work if the asset is not purchased and present on your Keymaster account. Additionally, please be aware that if you transfer these assets, you will not be able to receive them back, and the script will cease to function.
{% endhint %}

***

## Download the dependencies

To make sure the MDT works as it should, there are a few scripts you must download. These extra scripts are key for the MDT system to run well and fit into your FiveM server. Be sure to get all the needed dependencies listed in the documentation to ensure a smooth and fully working setup.

<table><thead><tr><th width="224">Dependency</th><th>Link</th></tr></thead><tbody><tr><td>screenshot-basic</td><td><a href="https://github.com/citizenfx/screenshot-basic">https://github.com/citizenfx/screenshot-basic</a></td></tr><tr><td>oxmysql / mysql-async</td><td><a href="https://github.com/overextended/oxmysql/releases">OxMySQL</a> or <a href="https://github.com/brouznouf/fivem-mysql-async/releases">MySQL-Async</a></td></tr></tbody></table>

{% hint style="info" %}
For optimal performance and smooth operation of the script, we highly recommend having one of the latest recommended artifacts installed.
{% endhint %}

***

## Start the resources

To get Redutzu-MDT running smoothly on your FiveM server, it's important to start the scripts in the right order. This ensures everything loads correctly, avoiding problems and making sure the MDT system works well.

```systemd
# The first hing you want to start is your database wrapper
ensure oxmysql / mysql-async

# Then start your core
ensure es_extended / qb-core / qbx_core

# Before you start the MDT, make sure to start screenshot-basic
ensure screenshot-basic

# Make sure you start the prop before
ensure redutzu-mdt-prop
ensure redutzu-mdt
```

{% hint style="danger" %}
Make sure the license for your server matches the account where you bought the script. Using different licenses will cause errors, making the script to not work.
{% endhint %}

***

## Insert the SQL

This step is crucial, so pay close attention. Inserting the SQL is a vital part of setting up the MDT on your server. Be sure to follow each step carefully and with full attention to detail. This ensures that the database is properly configured and ready to support the functionality of the MDT system without any issues.

{% hint style="warning" %}
There are different SQL files available for each framework, so make sure to choose the one that suits your server.

ALWAYS insert the Default one
{% endhint %}

{% tabs %}
{% tab title="Default" %}
You must insert this code, no matter which framework you're using. This is the main SQL required for the script to function properly.

```sql
DROP TABLE IF EXISTS `mdt_incidents`;
DROP TABLE IF EXISTS `mdt_evidences`;
DROP TABLE IF EXISTS `mdt_warrants`;
DROP TABLE IF EXISTS `mdt_bolos`;
DROP TABLE IF EXISTS `mdt_gallery`;
DROP TABLE IF EXISTS `mdt_weapons`;
DROP TABLE IF EXISTS `mdt_charges`;
DROP TABLE IF EXISTS `mdt_tags`;
DROP TABLE IF EXISTS `mdt_activity`;
DROP TABLE IF EXISTS `mdt_announcements`;
DROP TABLE IF EXISTS `mdt_codes`;


CREATE TABLE IF NOT EXISTS `mdt_incidents` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `description` TEXT DEFAULT NULL,
  `players` TEXT DEFAULT NULL,
  `victims` TEXT DEFAULT NULL,
  `cops` TEXT DEFAULT NULL,
  `vehicles` TEXT DEFAULT NULL,
  `evidences` TEXT DEFAULT NULL,
  `charges` TEXT DEFAULT NULL,
  `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
) CHARACTER SET utf8mb4 AUTO_INCREMENT=0;

CREATE TABLE IF NOT EXISTS `mdt_evidences` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `description` TEXT DEFAULT NULL,
  `players` TEXT DEFAULT NULL,
  `cops` TEXT DEFAULT NULL,
  `vehicles` TEXT DEFAULT NULL,
  `weapons` TEXT DEFAULT NULL,
  `images` TEXT DEFAULT NULL,
  `archive` TEXT DEFAULT NULL,
  `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
) CHARACTER SET utf8mb4 AUTO_INCREMENT=0;

CREATE TABLE IF NOT EXISTS `mdt_warrants` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `reason` TEXT NOT NULL,
  `players` TEXT NOT NULL DEFAULT '[]',
  `house` TEXT DEFAULT NULL,
  `tag` varchar(64) DEFAULT NULL,
  `date` varchar(64) NOT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
) CHARACTER SET utf8mb4 AUTO_INCREMENT=0;

CREATE TABLE IF NOT EXISTS `mdt_bolos` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `description` TEXT DEFAULT NULL,
  `player` varchar(255) DEFAULT NULL,
  `vehicle` varchar(255) DEFAULT NULL,
  `tag` varchar(64) DEFAULT NULL,
  `date` varchar(64) NOT NULL,
  `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
) CHARACTER SET utf8mb4 AUTO_INCREMENT=0;

CREATE TABLE IF NOT EXISTS `mdt_gallery` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `identifier` VARCHAR(64) NOT NULL,
    `type` VARCHAR(10) NOT NULL,
    `value` TEXT NOT NULL,
    `description` TEXT DEFAULT NULL,
    PRIMARY KEY (`id`)
) CHARACTER SET utf8mb4 AUTO_INCREMENT=0;

CREATE TABLE IF NOT EXISTS `mdt_weapons` (
    `label` VARCHAR(64) NOT NULL,
    `name` VARCHAR(64) NOT NULL,
    `serial` VARCHAR(20) NOT NULL,
    `identifier` VARCHAR(64) DEFAULT NULL,
    `notes` TEXT DEFAULT NULL,
    PRIMARY KEY (`serial`)
) CHARACTER SET utf8mb4;

CREATE TABLE IF NOT EXISTS `mdt_charges` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NOT NULL,
    `description` TEXT NOT NULL DEFAULT '',
    `jail` int(11) DEFAULT 0,
    `fine` FLOAT DEFAULT 0,
    `tag` VARCHAR(64) DEFAULT NULL,
    `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(),
    PRIMARY KEY (`id`)
) CHARACTER SET utf8mb4 AUTO_INCREMENT=0;

CREATE TABLE IF NOT EXISTS `mdt_tags` (
    `identifier` VARCHAR(64) NOT NULL,
    `type` VARCHAR(64) NOT NULL,
    `name` VARCHAR(255) NOT NULL,
    `description` TEXT DEFAULT NULL,
    `color` VARCHAR(64) NOT NULL,
    PRIMARY KEY (`identifier`)
) CHARACTER SET utf8mb4;

CREATE TABLE IF NOT EXISTS `mdt_activity` (
    `identifier` VARCHAR(64) NOT NULL,
    `amount` FLOAT NOT NULL,
    `clockIn` VARCHAR(128) NOT NULL,
    `clockOut` TIMESTAMP NOT NULL DEFAULT current_timestamp()
) CHARACTER SET utf8mb4;

CREATE TABLE IF NOT EXISTS `mdt_announcements` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(255) NOT NULL,
  `content` TEXT NOT NULL,
  `author` varchar(255) NOT NULL,
  `pinned` BOOLEAN DEFAULT FALSE,
  `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
) CHARACTER SET utf8mb4 AUTO_INCREMENT=0;

CREATE TABLE IF NOT EXISTS `mdt_codes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `description` TEXT DEFAULT NULL,
  `code` varchar(32) NOT NULL UNIQUE,
  `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
) CHARACTER SET utf8mb4 AUTO_INCREMENT=0;

-- Delete triggers (for avoiding attached unexisting records)

DELIMITER //
  CREATE TRIGGER delete_evidence AFTER DELETE ON mdt_evidences FOR EACH ROW BEGIN
      UPDATE mdt_incidents SET
        evidences = JSON_REMOVE(mdt_incidents.evidences, JSON_UNQUOTE(
          JSON_SEARCH(mdt_incidents.evidences, 'one', OLD.id)
        ))
      WHERE
        JSON_CONTAINS(mdt_incidents.evidences, OLD.id);
  END;
// DELIMITER ;
```

{% endtab %}

{% tab title="ESX" %}
You'll need to insert the following code into your database:

```sql
ALTER TABLE `users`
ADD IF NOT EXISTS `mdt_image` TEXT DEFAULT NULL,
ADD IF NOT EXISTS `mdt_notes` TEXT DEFAULT NULL;

ALTER TABLE `owned_vehicles`
ADD IF NOT EXISTS `mdt_image` TEXT DEFAULT NULL,
ADD IF NOT EXISTS `mdt_notes` TEXT DEFAULT NULL;
```

{% endtab %}

{% tab title="QBCore" %}
You'll need to insert the following code into your database:

```sql
ALTER TABLE `players`
ADD IF NOT EXISTS `mdt_image` TEXT DEFAULT NULL,
ADD IF NOT EXISTS `mdt_notes` TEXT DEFAULT NULL;

ALTER TABLE `player_vehicles`
ADD IF NOT EXISTS `mdt_image` TEXT DEFAULT NULL,
ADD IF NOT EXISTS `mdt_notes` TEXT DEFAULT NULL;
```

{% endtab %}

{% tab title="QBox" %}
You'll need to insert the following code into your database:

```sql
ALTER TABLE `players`
ADD IF NOT EXISTS `mdt_image` TEXT DEFAULT NULL,
ADD IF NOT EXISTS `mdt_notes` TEXT DEFAULT NULL;

ALTER TABLE `player_vehicles`
ADD IF NOT EXISTS `mdt_image` TEXT DEFAULT NULL,
ADD IF NOT EXISTS `mdt_notes` TEXT DEFAULT NULL;
```

{% endtab %}

{% tab title="Standalone" %}
You'll need to insert the following code into your database:

```sql
CREATE TABLE IF NOT EXISTS `mdt_citizens` (
    `identifier` VARCHAR(64) NOT NULL UNIQUE,
    `firstname` VARCHAR(20) NOT NULL,
    `lastname` VARCHAR(20) NOT NULL,
    `gender` VARCHAR(10) NOT NULL,
    `birthdate` DATE NOT NULL,
    `job` VARCHAR(128) DEFAULT '{}',
    `licenses` TEXT DEFAULT '[]',
    `fingerprint` VARCHAR(64) DEFAULT NULL,
    `notes` TEXT DEFAULT NULL,
    `image` TEXT DEFAULT NULL,
    PRIMARY KEY (`identifier`)
) CHARACTER SET utf8mb4;

CREATE TABLE IF NOT EXISTS `mdt_vehicles` (
    `plate` VARCHAR(32) NOT NULL UNIQUE,
    `model` VARCHAR(32) NOT NULL,
    `owner` VARCHAR(64) NOT NULL,
    `notes` TEXT DEFAULT NULL,
    `image` TEXT DEFAULT NULL,
    PRIMARY KEY (`plate`)
) CHARACTER SET utf8mb4;
```

{% endtab %}
{% endtabs %}

***

## Start your server

You can now start your server and enjoy the script. Additionally, you can configure the script further to match your preferences. For more information, refer to the configuration section.


# Guides

The complete guide to Redutzu-MDT features gives you all the details you need about what the software can do. Inside, you'll find clear information about each feature and easy-to-follow steps on how to set them up to suit your needs.


# Frameworks

```lua
Config.Framework = 'auto' -- auto, esx, qb-core, qbox, standalone
```

{% code title="server/custom/framework/standalone.lua" fullWidth="false" %}

```lua
if Config.Framework ~= 'standalone' then
    return
end

Framework = {}

debugPrint('Successfully loaded standalone')

function Framework.GetPlayerIdentifier(source)
    local identifiers, license = GetPlayerIdentifiers(source)

    for key, value in pairs(identifiers) do
        if string.match(value, 'license:') then
            license = value
            break
        end
    end

    return license
end

function Framework.GetSourceFromIdentifier(identifier)
    return 0
end

function Framework.GetCharacterName(source)
    return 'firstName', 'lastName'
end

function Framework.GetPlayerJob(source)
    return {
        name = 'user',
        label = 'Citizen',
        grade = 0,
        grade_label = 'Unemployed'
    }
end

function Framework.SetPlayerJob(source, job, grade)

end

function Framework.GetJobPlayers(job)
    return {}
end

function Framework.GetJobs()
    return {
        ['police'] = {
            label = 'Law Enforcement',
            -- continous data
        }
    }
end

function Framework.GetJobData(job)
    return {
        name = 'Job name',
        grades = {
            { name = 'Job grade', level = 0 }
        }
    }
end

function Framework.GetDefaultPoliceJob(online)
    return { name = 'default_police_job', grade = 0 }
end

function Framework.GetUnemployedJob(online)
    return { name = 'default_unemployed_job', grade = 0 }
end

function Framework.GetWeapons()
    return {
        { name = 'WEAPON_NAME', label = 'Weapon Label' }
    }
end

function Framework.RegisterCommand(name, description, callback)
    RegisterCommand(name, function(source)
        if source > 0 then
            callback(source)
        end
    end, false)

    TriggerEvent('chat:addSuggestion', string.format('/%s', name), description)
end

function Framework.Notify(source, message)
    TriggerClientEvent('chat:addMessage', source, {
        args = { '[Redutzu-MDT]', message },
        color = { 255, 255, 255 },
        multiline = true
    })
end

function Framework.RegisterCallback(name, callback)
    RegisterServerEvent('redutzu-mdt:server-callback:' .. name, function(...)
        local player = source

        callback(player, function(...)
            TriggerClientEvent('redutzu-mdt:client-callback:' .. name, player, ...)            
        end, ...)
    end)
end

local function Callback(name, source, callback, ...)
    TriggerClientEvent('redutzu-mdt:server-client-callback:' .. name, source, ...)

    return RegisterNetEvent('redutzu-mdt:client-server-callback:' .. name, function(...)
        callback(...)
    end)
end

function Framework.TriggerClientCallback(name, source, callback, ...)
    local event = false

    local cb = function(...)
        if event ~= false then
            RemoveEventHandler(event)
        end

        callback(...)
    end

    event = Callback(name, source, cb, ...)

    return event
end

if Config.Command.Enabled then
    Framework.RegisterCommand(Config.Command.Name, Config.Command.Description, function(source)
        TriggerClientEvent('redutzu-mdt:client:openMDT', source)
    end)
end
```

{% endcode %}


# Items

**ox\_inventory**

```lua
['mdt'] = {
    label = 'Mobile Data Terminal',
    weight = 500,
    stack = false,
    close = true,
    allowArmed = false,
    consume = 0,
    client = { event = 'redutzu-mdt:client:openMDT', image = 'redutzu_mdt.png' },
    description = 'Take roleplay to another level with the most advanced MDT on FiveM'
},

['bodycam'] = {
        label = 'Bodycam',
        weight = 300,
        stack = false,
        close = true,
        allowArmed = true,
        consume = 0,
        client = { event = 'redutzu-mdt:client:toggle-bodycam-state', image = 'bodycam.png' },
        description = 'Let other players see your body with the most advanced bodycam on FiveM'
} 
```

**qb-inventory/others**

```lua
['mdt'] = {
    ['name'] = 'mdt', 			                
    ['label'] = 'Mobile Data Terminal', 	
    ['weight'] = 500, 		
    ['type'] = 'item', 		
    ['image'] = 'redutzu_mdt.png', 		    
    ['unique'] = false, 	
    ['useable'] = true, 	
    ['shouldClose'] = true,	   
    ['combinable'] = nil,   
    ['description'] = 'Take roleplay to another level with the most advanced MDT on FiveM'
},

['bodycam'] = {
    ['name'] = 'Body Cam', 			                
    ['label'] = 'Body Camera', 	
    ['weight'] = 500, 		
    ['type'] = 'item', 		
    ['image'] = 'bodycam.png', 		    
    ['unique'] = false, 	
    ['useable'] = true, 	
    ['shouldClose'] = true,	   
    ['combinable'] = nil,   
    ['description'] = 'Let other players see your body with the most advanced bodycam on FiveM'
}
```


# Localization

{% code overflow="wrap" %}

```lua
Config.Locales = { 'en-US', 'ro-RO', 'fr-FR', 'de-DE', 'pt-PT', 'hu-HU', 'nl-NL', 'cs-CZ', 'el-GR', 'da-DK' }
```

{% endcode %}

<pre class="language-json" data-title="nui/dist/locales/[LANGUAGE]/translation.json"><code class="lang-json">{
    "navigation": {
        "dashboard": "Dashboard",
        "incidents": "Incidents",
        "evidences": "Evidences",
        "warrants": "Warrants",
        "officers": "Officers",
        "bolos": "Bolos",
        "dispatch": "Dispatch",
        "cameras": "Cameras",
        "citizens": "Citizens",
        "vehicles": "Vehicles",
        "houses": "Houses",
        "weapons": "Weapons",
        "codes": "Codes",
        "charges": "Charges",
        "announcements": "Announcements",
        "config": "Configuration",
        "administration": "Administration",
        "exit": "Exit"

<strong>// the rest of the json file...
</strong></code></pre>


# Images

upload.lua

## Upload Configuration Explanation

The `Upload` configuration allows you to set up image uploading for your MDT system. You can choose between different upload methods: Discord, Imgur, FiveManage, or a custom solution.

### Main Configuration

```lua
Upload = {}
Upload.Method = 'fivemanage'  -- Options: 'discord', 'imgur', 'fivemanage', 'custom'
```

`Upload.Method` specifies which upload service to use. Set this to one of the available options.

### Upload Methods Configuration

Each upload method has its own configuration:

```lua
Upload.Methods = {
    ['discord'] = { ... },
    ['imgur'] = { ... },
    ['fivemanage'] = { ... },
    ['custom'] = { ... }
}
```

#### Discord Upload

```lua
['discord'] = {
    link = 'https://discord.com/api/webhooks/',  -- Your webhook link
    field = 'files[]',
    path = 'attachments.1.url',
    options = {
        encoding = 'webp'  -- Options: 'webp', 'png', 'jpg'
    }
}
```

To set up Discord uploading:

1. Create a webhook in your Discord server.
2. Replace `'https://discord.com/api/webhooks/'` with your full webhook URL.
3. Choose an encoding format ('webp' for smaller file sizes, or 'png'/'jpg' for different formats).

Example:

```lua
['discord'] = {
    link = 'https://discord.com/api/webhooks/123456789/abcdefghijklmnop',
    field = 'files[]',
    path = 'attachments.1.url',
    options = {
        encoding = 'png'
    }
}
```

#### Imgur Upload

```lua
['imgur'] = {
    link = 'https://api.imgur.com/3/upload',
    field = 'image',
    path = 'data.link',
    options = {
        headers = {
            ['Authorization'] = 'Client-ID YOUR_KEY_HERE'  -- Add your client id
        }
    }
}
```

To set up Imgur uploading:

1. Create an Imgur account and register an application to get a Client ID.
2. Replace `'YOUR_KEY_HERE'` with your Imgur Client ID.

Example:

```lua
['imgur'] = {
    link = 'https://api.imgur.com/3/upload',
    field = 'image',
    path = 'data.link',
    options = {
        headers = {
            ['Authorization'] = 'Client-ID a1b2c3d4e5f6g7h'
        }
    }
}
```

#### FiveManage Upload

```lua
['fivemanage'] = {
    link = 'https://api.fivemanage.com/api/image',
    field = 'image',
    path = 'url',
    options = {
        encoding = 'png',
        headers = {
            ['Authorization'] = 'YOUR_KEY_HERE'
        }
    }
}
```

To set up FiveManage uploading:

1. Obtain an API key from FiveManage.
2. Replace `'YOUR_KEY_HERE'` with your FiveManage API key.
3. Optionally, change the `encoding` if needed.

Example:

```lua
['fivemanage'] = {
    link = 'https://api.fivemanage.com/api/image',
    field = 'image',
    path = 'url',
    options = {
        encoding = 'webp',
        headers = {
            ['Authorization'] = 'fm_api_key_123456789abcdef'
        }
    }
}
```

#### Custom Upload

```lua
['custom'] = {
    link = 'https://api.your_website.com/api',  -- Your API link
    field = 'file',
    path = 'link',
    options = {
        headers = {
            ['Authorization'] = 'Key YOUR_KEY_HERE'
        }
    }
}
```

To set up a custom upload solution:

1. Replace `'https://api.your_website.com/api'` with your custom API endpoint.
2. Adjust the `field` and `path` values to match your API's requirements.
3. Add any necessary headers, including authorization if required.

Example:

```lua
['custom'] = {
    link = 'https://imageupload.myserver.com/upload',
    field = 'image_data',
    path = 'response.image_url',
    options = {
        headers = {
            ['Authorization'] = 'Key myserver_secret_key_123',
            ['Content-Type'] = 'application/json'
        }
    }
}
```

Remember to choose your preferred upload method by setting `Upload.Method` to the corresponding key ('discord', 'imgur', 'fivemanage', or 'custom') and properly configure that method's settings.


# Logs

```lua
Config.UseLogs = true
```

{% code title="server/custom/functions/logs.lua" %}

````lua
local LOGS_WEBHOOK <const> = 'https://discord.com/api/webhooks/'
-- local WEBHOOKS <const> = {
--     ['incident'] = 'https://discord.com/api/webhooks/',
--     ...
-- }

local EMBED_SETTINGS <const> = {
    ['colors'] = {
        ['delete'] = 16720402,
        ['create'] = 515594,
        ['update'] = 16757025
    }
}

--- Sends the log to discord (you can change this)
---@param action string (create, update, delete)
---@param type string (incident, evidence, warrant, etc.)
---@param data object (example: { name: 'updated name', description: 'updated description' })
---@param source number | string (creator of the log)
function CreateLog(action, recordType, data, source)
    local fields = {}

    if type(data) == 'table' then
        for key, value in pairs(data) do
            local val = type(value) ~= 'string' and '```js\n' .. json.encode(value) .. '```' or value

            fields[#fields + 1] = {
                name = key,
                value = val,
                inline = true
            }
        end
    else
        fields[#fields + 1] = { name = 'id', value = tostring(data) }
    end

    PerformHttpRequest(
        LOGS_WEBHOOK, -- if you want to use a channel for all logs
        -- WEBHOOKS[recordType], -- if you want to use a different channel for each category
        function(err, text, headers)
            if err == 400 then
                error('Caught an error while sending the log to discord')
                return
            end
        end, 'POST',
        json.encode({
            embeds = {{
                ['color'] = EMBED_SETTINGS['colors'][action],
                ['title'] = ('New action (%s - %s)'):format(action, recordType),
                ['fields'] = fields,
                ['timestamp'] = os.date('!%Y-%m-%dT%H:%M:%S'),
                ['footer'] = {
                    ['text'] = 'Made with ♥ by Redutzu\'s Scripts'
                }
            }}
        }), {
            ['Content-Type'] = 'application/json'
        }
    )
end

````

{% endcode %}


# Permissions

{% code title="config/permissions.json" %}

```json
{
    "list": [
        "dashboard.view",
        "incidents.view",
        "evidences.view",
        "warrants.view",
        "officers.view",
        // rest of permissions...
    ],
    "ranks": {
        "police": {
            "0": [
                "dashboard.view",
                "incidents.view",
                "evidences.view"
            ],
            "1": [
                // this includes the previous permissions
                "incidents.delete",
                "evidences.delete"
            ],
            "2": [
                // this includes the previous permissions
                "warrants.delete",
                "warrants.create"
            ],
            "3": [
                // this includes the previous permissions
                "cameras.bodycam.view",
                "codes.delete"
            ],
            "4": [
                // this includes the previous permissions
                "announcements.create",
                "announcements.delete"
            ]
        },
        // "job_name": {
        //    "grade": {
        //        ...permissions
        //    }
        // }
    }
}
```

{% endcode %}


# Bodycam

The bodycam feature allows players to activate a bodycam that other officers can view in real-time.

## Adding the bodycam item to your inventory

To add the bodycam item to your inventory, follow the steps for your specific inventory system:

#### ox\_inventory (@ox\_inventory/data/items.lua)

```lua
['bodycam'] = {
    label = 'Bodycam',
    weight = 300,
    stack = false,
    close = true,
    allowArmed = true,
    consume = 0,
    client = { event = 'redutzu-mdt:client:toggle-bodycam-state', image = 'bodycam.png' },
    description = 'Let other players see your body with the most advanced bodycam on FiveM'
}
```

#### qb-inventory (@qb-core/shared/items.lua)

```lua
bodycam = {
    name = 'bodycam',
    label = 'Bodycam',
    weight = 300,
    type = 'item',
    image = 'bodycam.png',
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = 'Let other players see your body with the most advanced bodycam on FiveM'
}
```

#### qs-inventory (@qs-inventory/shared/items.lua)

```lua
['bodycam'] = {
    ['name'] = 'bodycam',
    ['label'] = 'Bodycam',
    ['weight'] = 300,
    ['type'] = 'item',
    ['image'] = 'bodycam.png',
    ['unique'] = true,
    ['useable'] = true,
    ['shouldClose'] = true,
    ['combinable'] = nil,
    ['description'] = 'Let other players see your body with the most advanced bodycam on FiveM'
}
```

## Modifying Bodycam Functionality

The functionality of the bodycam can be modified in the `server/custom/bodycam/default.lua` file. This file contains the default implementation of the bodycam feature, and you can customize it to fit your needs.

The image of the bodycam item is located in the same directory as the `default.lua` file.

## Server-Side Export

There is also a server-side export to check if a player has the bodycam enabled:

```lua
exports['redutzu-mdt']:isBodycamEnabled(source)
```

To toggle the bodycam, the player must have the bodycam item in their inventory and use it. The UI will appear, indicating that the bodycam is enabled, and any officer can then view the player's real-time footage.


# Exports/Events

Redutzu-MDT provides various export functions for easy integration with other scripts on your server. These functions enable you to enhance and customize your server experience effortlessly. Explore the options available and utilize Redutzu-MDT exports to elevate your server's capabilities.


# Server Events


# addDispatchToMDT

{% hint style="info" %}
Server side -- Example code: (Needs your own edits to get coords, street etc.)
{% endhint %}

```lua
    TriggerEvent('redutzu-mdt:server:addDispatchToMDT', {
        code = '10-14',
        title = 'Carjacking',
        street = 'Street name',
        weapon = 'Assault Rifle (AK47)',
        gender = 'Male',
        vehicle = {
            data.name = 'Infernus',
            data.plate = 'ABC123',
            data.doors = 'four door',
            data.color = 'Dark Blue (Metalic)',
            data.class = 'Sports'
        },
        duration = Config.Dispatch.DefaultAlertDuration, -- in miliseconds
        coords = {
            x = 0.0,
            y = 0.0,
            z = 0.0
        }
    })
```

{% hint style="info" %}
Client side --  -- Example code: (Needs your own edits to get coords, street etc.)
{% endhint %}

```lua
function onDriveBy(ped)
    local coords = GetEntityCoords(ped)
    local street = GetStreetNameFromCoords(coords)
    local weapon = GetWeaponName()
    local gender = Framework.GetPlayerGender()
    local vehicle = GetVehiclePedIsIn(ped, false)
    local data = GetVehicleInfo(vehicle)

    TriggerServerEvent('redutzu-mdt:server:sendDispatchMessage', {
        code = 'driveby',
        coords = coords,
        street = street,
        weapon = weapon,
        gender = gender,
        vehicle = data
    })
end
```


# Server Exports

Here, you'll discover all the handy exports for this asset. Please take the time to read through each step and example carefully to grasp how they function. We advise against using these exports if you're not an experienced developer.

***

## open

This export opens the MDT interface for a specific player.

```lua
exports['redutzu-mdt']:open(source)
```

Here's how you can utilize this export:

```lua
RegisterCommand('open_mdt', function(source)
    exports['redutzu-mdt']:open(source)
end, false)
```

***

## isAllowed

This export verifies if a player has the job necessary to open the MDT.

```lua
exports['redutzu-mdt']:isAllowed(source)
```

Here's how you can utilize this export:

{% code overflow="wrap" %}

```lua
RegisterCommand('example', function(source)
    local allowed = exports['redutzu-mdt']:isAllowed(source)

    if allowed then
        -- Do something if the player is allowed to open the MDT
    end
end, false)
```

{% endcode %}

***

## isJobWhitelisted

This export checks if at least one job is whitelisted.

```lua
exports['redutzu-mdt']:isJobWhitelisted(jobs) -- table or string
```

Here's how you can utilize this export:

```lua
RegisterNetEvent('dispatchEvent', function(data)
    local isWhitelisted = exports['redutzu-mdt']:isJobWhitelisted(data.jobs)

    if isWhitelisted then
        -- data.jobs includes one of the whitelisted jobs from Config.WhitelistedJobs
    end
end)
```

***

## hasPermission

This export verifies if a player has a specific permission.

```lua
exports['redutzu-mdt']:hasPermission(source, permission)
```

Here's how you can utilize this export:

```lua
RegisterCommand('pin_announcement', function(source)
    local hasPerm = exports['redutzu-mdt']:hasPermission(source, 'announcements.pin')
    
    if hasPerm then
        print('The player can pin announcements')    
    end
end, false)
```

***

## GetOfficerCallsign

This export retrieves the callsign of a player.

```lua
exports['redutzu-mdt']:GetOfficerCallsign(source)
```

Here's how you can utilize this export:

```lua
RegisterCommand('callsign', function(source)
    local callsign = exports['redutzu-mdt']:GetOfficerCallsign(source)
    
    if callsign then
        print('Callsign: ' .. callsign)    
    else
        print('No callsign')
    end
end, false)
```

***

## SetOfficerCallsign

This export modifies the callsign of a player.

```lua
exports['redutzu-mdt']:SetOfficerCallsign(source, callsign)
```

Here's how you can utilize this export:

```lua
RegisterCommand('change_callsign', function(source)
    exports['redutzu-mdt']:SetOfficerCallsign(source, 'ABC123')
end, false)
```

***

## GetOfficerStatus

This export retrieves the status of a player.

```lua
exports['redutzu-mdt']:GetOfficerStatus(source)
```

Here's how you can utilize this export:

```lua
RegisterCommand('status', function(source)
    local status = exports['redutzu-mdt']:GetOfficerStatus(source) -- 0, 1, 2, 3
    -- if you are using this code inside the MDT you can do:
    local statusName = Config.Statuses[status]?.label
    print('Current status: ' .. statusName )
end, false)
```

***

## SetOfficerStatus

This export updates the status of a player.

```lua
exports['redutzu-mdt']:SetOfficerStatus(source, status) -- status: keyof Config.Statuses
```

Here's how you can utilize this export:

```lua
RegisterCommand('change_status', function(source, args)
    local status = args[1] -- must be 0, 1, 2 or 3
    exports['redutzu-mdt']:SetOfficerStatus(source, status)
end, false)
```

***

## IsDuty

This export indicates whether the player is on duty or not.

```lua
exports['redutzu-mdt']:IsDuty(source)
```

Here's how you can utilize this export:

```lua
RegisterCommand('duty', function(source)
    local isDuty = exports['redutzu-mdt']:IsDuty(source)
    print('Player is ' .. isDuty and 'on duty' or 'off duty')
end, false)
```

***

## SetDuty

This export modifies the duty state of a player.

```lua
exports['redutzu-mdt']:SetDuty(source, state)
```

Here's how you can utilize this export:

```lua
RegisterCommand('toggleDuty', function(source)
    local isDuty = exports['redutzu-mdt']:IsDuty(source)
    exports['redutzu-mdt']:SetDuty(source, not isDuty)
end, false)
```


# Incidents

Here, you'll find key exports for managing incidents. These exports allow you to create, update, and delete incidents as needed.

***

## Type

<pre class="language-typescript"><code class="lang-typescript"><strong>type Incident = {
</strong>    id: number,
    name: string,
    description: string, // stringified JSON
    vehicles: string[],
    evidences: number[],
    players: {
        identifier: string,
        name: string
    }[],
    victims: {
        identifier: string,
        name: string
    }[],
    cops: {
        identifier: string,
        name: string
    }[],
    charges: {
        list: { id: number, name: string }[],
        reduction: {
            fine: 50,
            jail: 70
        },
        amount: {
            fine: 15000,
            jail: 60
        }
    },
    createdAt: string
}
</code></pre>

***

## Exports

### Search for an incident

<pre class="language-lua"><code class="lang-lua"><strong>-- It returns the incident data
</strong><strong>exports['redutzu-mdt']:SearchIncident(id)
</strong></code></pre>

```lua
local incident = exports['redutzu-mdt']:SearchIncident(1)
print(incident.name)
```

***

### Create an incident

```lua
-- It returns the id of the created incident
exports['redutzu-mdt']:CreateIncident(data, sender)
```

```lua
RegisterCommand('createIncident', function(source)
    exports['redutzu-mdt']:CreateIncident({
        name = 'Incident Name',
        description = '[]',
        players = { 'identifier' },
        victims = { 'identifier' },
        cops = { 'identifier' },
        vehicles = { 'plate' },
        evidences = { 1, 5 },
        charges = {
            list = { 2, 4, 8 },
            amount = { fine = 15000, jail = 50 },
            reduction = { fine = 35, jail = 80 }
        }
    }, source)
end, false)
```

***

### Update an incident

<pre class="language-lua"><code class="lang-lua"><strong>-- It returns a boolean (if it was successfully updated)
</strong>exports['redutzu-mdt']:UpdateIncident(id, data)
</code></pre>

```lua
local success = exports['redutzu-mdt']:UpdateIncident(1, {
    name = 'Updated Incident',
    vehicles = { 'XYZ987' }
})

if not success then
    print('There was an error updating the incident')
    return
end

print('Incident updated')
```

***

### Delete an incident

```lua
-- It returns a boolean (if it was successfully deleted)
exports['redutzu-mdt']:DeleteIncident(id)
```

```lua
local success = exports['redutzu-mdt']:DeleteIncident(1)

if not success then
    print('There was an error deleting the incident')
    return
end

print('Incident deleted')
```


# Evidences

Here, you'll find key exports for managing evidences. These exports allow you to create, update, and delete evidences as needed.

***

## Type

```typescript
type Evidence = {
    id: number,
    name: string,
    description: string,
    vehicles?: string[],
    archive?: number[],
    weapons?: {
        serial: string,
        label: string
    }[],
    images: {
        id: number,
        value: string,
        description: string
    }[],
    players: {
        identifier: string,
        name: string
    }[],
    cops: {
        identifier: string,
        name: string
    }[],
    createdAt: string
}
```

***

## Exports

### Search for evidence

<pre class="language-lua"><code class="lang-lua"><strong>-- It returns the incident data
</strong><strong>exports['redutzu-mdt']:SearchEvidence(id)
</strong></code></pre>

```lua
local evidence = exports['redutzu-mdt']:SearchEvidence(1)
print(evidence.name)
```

### Create new evidence

```lua
exports['redutzu-mdt']:CreateEvidence({
  name = 'Evidence name',
  description = 'Evidence description',
  players = { 'license:1234' },  -- array of identifiers/citizenids
  cops = { 'license:1234', 'license:4321' }, -- array of identifiers/citizenids
  vehicles = { 'ABC123' }, -- array of plate numbers
  weapons = { 'serialNumber' }, -- array of serial numbers
  images = { 1, 8 } -- array of gallery image ids
}) -- number (id)
```

### Update evidence

```lua
-- It returns a boolean (if it was successfully updated)
exports['redutzu-mdt']:UpdateEvidence(id, {
  name = 'New evidence name',
  players = { 'license:4321' }
}) -- boolean
```

```lua
local success = exports['redutzu-mdt']:UpdateEvidence(1, {
    name = 'Evidence Example',
    players = { 'license:4321' }
})

if not success then
    print('There was an error updating the evidence')
    return
end

print('Evidence updated')
```

### Delete evidence

```lua
-- It returns a boolean (if it was successfully deleted)
exports['redutzu-mdt']:DeleteEvidence(id: number) // boolean
```

```lua
local success = exports['redutzu-mdt']:DeleteEvidence(1)

if not success then
    print('There was an error deleting the evidence')
    return
end

print('Evidence deleted')
```


# Warrants

Here, you'll find key exports for managing warrants. These exports allow you to create, update, and delete warrants as needed.

***

## Type

```typescript
type Warrant = {
    id: number,
    reason: string,
    house: string | number, // depends on your housing script
    date: number,
    active: boolean,
    createdAt: string,
    tag?: {
        identifier: string,
        label: string,
        color: string
    },
    players: {
        identifier: string,
        name: string
    }[]
}
```

## Exports

### SearchWarrant

```lua
exports['redutzu-mdt']:SearchWarrant(id: number) // WarrantType | null
```

### CreateWarrant

```lua
exports['redutzu-mdt']:CreateWarrant({
  reason = 'Warrant reason',
  players = { 'license:1234', 'license:4321' }, -- array of identifiers/citizenids
  house = 1, -- depends on your housing script (usually the id)
  tag = 'tag identifier',
  date = 1716935068 -- javascript timestamp (this is the starting date)
}) -- number (id)
```

### UpdateWarrant

```lua
-- It returns a boolean (if it was successfully updated)
exports['redutzu-mdt']:UpdateWarrant(id, {
  reason = 'New reason',
  players = { 'license:0000' }
}) -- boolean
```

```lua
local success = exports['redutzu-mdt']:UpdateWarrant(1, {
    reason = 'New reason',
    players = { 'license:0000' }
})

if not success then
    print('There was an error updating the warrant')
    return
end

print('Warrant updated')
```

### DeleteWarrant

```lua
-- It returns a boolean (if it was successfully deleted)
exports['redutzu-mdt']:DeleteWarrant(id: number) // boolean
```

```lua
local success = exports['redutzu-mdt']:DeleteWarrant(1)

if not success then
    print('There was an error deleting the warrant')
    return
end

print('Warrant deleted')
```


# Bolos

Here, you'll find key exports for managing bolos. These exports allow you to create, update, and delete bolos as needed.

***

## Type

```typescript
type BOLO = {
    id: number,
    name: string,
    description: string,
    vehicle: string,
    date: number,
    createdAt: string,
    tag: {
        identifier: string,
        label: string,
        color: string
    },
    player: {
        identifier: string,
        name: string
    }
}
```

## Exports

### Search a bolo

```lua
exports['redutzu-mdt']:SearchBolo(id: number) // BoloType | null
```

### Create a new Bolo

```lua
exports['redutzu-mdt']:CreateBolo({
  name = 'BOLO name',
  description = 'BOLO description',
  player = 'license:1234567890', -- player identifier (QBCore: citizenid, ESX/Standalone: identifier)
  vehicle = 'ABC123', -- plate
  tag = 'identifier', -- tag identifier
  date = 1716935068 -- javascript timestamp (this is the expiry date)
}) -- number (id)
```

### Update a Bolo

```lua
-- It returns a boolean (if it was successfully updated)
exports['redutzu-mdt']:UpdateBolo(id, {
  player = 'new identifier',
  tag = 'new tag identifier',
  vehicle = {
    plate = 'new plate'
  }
}) -- boolean
```

```lua
local success = exports['redutzu-mdt']:UpdateBolo(1, {
  player = 'new identifier',
  tag = 'new tag identifier',
  vehicle = {
    plate = 'new plate'
  }
})

if not success then
    print('There was an error updating the incident')
    return
end

print('Incident updated')
```

### Delete an Bolo

```lua
-- It returns a boolean (if it was successfully deleted)
exports['redutzu-mdt']:DeleteBolo(id: number) // boolean
```

```lua
local success = exports['redutzu-mdt']:DeleteBolo(1)

if not success then
    print('There was an error deleting the bolo')
    return
end

print('Bolo deleted')
```


# Citizens

Here, you'll find key exports for managing citizens. These exports allow you to register and update citizens as needed.

***

## Type

```typescript
type Citizen = {
    identifier: string,
    firstname: string,
    lastname: string,
    gender: string,
    image?: string,
    notes?: string,
    birthdate: string,
    job: string,
    job_grade: string,
    vehicles: string[],
    images: {
        id: number,
        value: string,
        description: string
    }[],
    incidents: {
        id: number,
        createdAt: string
    }[],
    evidences: {
        id: number,
        createdAt: string
    }[],
    warrants: {
        id: number,
        createdAt: string
    }[],
    bolos: {
        id: number,
        createdAt: string
    }[],
    weapons: {
        serial: string,
        label: string
    }[]
}
```

## Exports

The exports can contain data from the type field.

### SearchCitizen

```lua
exports['redutzu-mdt']:SearchCitizen(identifier: string) // CitizenType | null

```

### RegisterCitizen

```lua
exports['redutzu-mdt']:RegisterCitizen({
  identifier = 'license:1234',
  firstname = 'Firstname',
  lastname = 'Lastname',
  gender = 'm',
  birthdate = '10/05/2000',
  job = { name = 'police', label = 'Law Enforcement', grade = 'Chief' },
  fingerprint = 'abc123',
  notes = 'Citizen notes',
  image = 'https://yourwebsite.com/'
}) -- boolean
```

### UpdateCitizen

```lua
-- It returns a boolean (if it was successfully updated)
exports['redutzu-mdt']:UpdateCitizen(identifier, {
  notes = 'New notes',
  incidents = { 1, 7, 10 }
}) -- boolean
```

```lua
local success = exports['redutzu-mdt']:UpdateCitizen('license:1234', {
    image = 'https://i.pinimg.com/736x/98/1d/6b/981d6b2e0ccb5e968a0618c8d47671da.jpg',
    vehicles = { 'XYZ987' }
})

if not success then
    print('There was an error updating the citizen')
    return
end

print('Citizen updated')
```


# Vehicles

Here, you'll find key exports for managing vehicles. These exports allow you to register and update vehicles as needed.

***

## Type

```typescript
type Vehicle = {
    plate: string,
    hash: string, // depends on your framework
    notes?: string,
    image?: string,
    owner: {
       identifier: string,
       name: string
    },
    gallery: {
       id: number,
       value: string,
       description: string
    }[],
    incidents: {
       id: number,
       createdAt: string
    }[],
    bolos: {
       id: number,
       createdAt: string
    }[]
}
```

## Exports

### RegisterVehicle

```lua
exports['redutzu-mdt']:RegisterVehicle({
  plate = 'ABC1234',
  owner = 'license:1234',
  model = 'hash/model',
  notes = 'Vehicle notes',
  image = 'https://yourwebsite.com/'
}) -- boolean
```

### SearchVehicle

```lua
exports['redutzu-mdt']:SearchVehicle(plate: string) // VehicleType | null
```

### UpdateVehicle

```lua
-- It returns a boolean (if it was successfully updated)
exports['redutzu-mdt']:UpdateVehicle(plate, {
  image = 'https://yourwebsite.com/'
}) -- boolean
```

```lua
local success = exports['redutzu-mdt']:UpdateVehicle('ABC1234', {
    image = 'https://yourwebsite.com/'
})

if not success then
    print('There was an error updating the vehicle')
    return
end

print('Vehicle updated')
```


# Codes

Here, you'll find key exports for managing codes. These exports allow you to create, update and delete codes as needed.

***

## Type

```typescript
type Code = {
    id: number,
    name: string,
    description: string,
    code: string,
    createdAt: string
}
```

## Exports

### Search for a code

```lua
exports['redutzu-mdt']:SearchCode(id: number) // CodeType | null
```

### Create a new code

```lua
exports['redutzu-mdt']:CreateCode({
  name = 'Code name',
  description = 'Code description',
  code = '1-101'
}) -- number (id)
```

### Update a code

```lua
-- It returns a boolean (if it was successfully updated)
exports['redutzu-mdt']:UpdateCode(id, { name = 'New code name' }) -- boolean
```

### Delete a code

```lua
-- It returns a boolean (if it was successfully deleted)
exports['redutzu-mdt']:DeleteCode(id: number) // boolean
```


# Charges

Here, you'll find key exports for managing charges. These exports allow you to create, update and delete charges as needed.

***

## Type

```typescript
type Charge = {
    id: number,
    name: string,
    description: string,
    jail?: number,
    fine?: number,
    createdAt: string,
    tag: {
        identifier: string,
        label: string,
        color: string
    }
}
```

## Exports

### Search for a charge

```lua
exports['redutzu-mdt']:SearchCharge(id: number) // ChargeType | null
```

### Create a new charge

```lua
exports['redutzu-mdt']:CreateCharge({
  name = 'Charge name',
  description = 'Charge description',
  jail = 5, -- this is optional
  fine = 1000, -- this is optional
  tag = 'tag identifier'
}) -- number (id)
```

### Update a charge

```lua
-- It returns a boolean (if it was successfully updated)
exports['redutzu-mdt']:UpdateCharge(id, { name = 'New name', jail = 10 }) -- boolean
```

### Delete a charge

<pre class="language-lua"><code class="lang-lua">-- It returns a boolean (if it was successfully deleted)
<strong>exports['redutzu-mdt']:DeleteCharge(id: number) // boolean
</strong></code></pre>


# Weapons

Here, you'll find key exports for managing weapons. These exports allow you to register and update weapons as needed.

***

## Type

<pre class="language-typescript"><code class="lang-typescript"><strong>type Weapon = {
</strong>    name: string,
    label: string, 
    serial: string, 
    notes: string, 
    evidences: {
        id: number,
        createdAt: string
    }[]
}
</code></pre>

## Exports

### Search for a weapon

```lua
exports['redutzu-mdt']:SearchWeapon(serial: string) // WeaponType | null
```

### Register a new weapon

```lua
local serialNumber = exports['redutzu-mdt']:GenerateWeaponSerial()

exports['redutzu-mdt']:RegisterWeapon({
  label = 'Pistol',
  name = 'weapon_pistol', -- this must be the same as in your GetWeapons function
  serial = serialNumber, -- you can generate your own serial number but it must be unique
  identifier = 'license:1234567890', -- owner identifier (QBCore: citizenid, ESX/Standalone: Identifier)
  notes = 'Weapon notes'
}) -- string (serial number)
```

### Generate a weapon Serial

<pre class="language-lua"><code class="lang-lua">-- It returns a string
<strong>exports['redutzu-mdt']:GenerateWeaponSerial() // string (generates an UNIQUE serial number)
</strong></code></pre>

### Update a weapons info

```lua
-- It returns a boolean (if it was successfully updated)
exports['redutzu-mdt']:UpdateWeapon('serialNumber', { notes = 'Updated notes' }) -- boolean
```


# Announcements&#x20;

Here, you'll find key exports for managing weapons. These exports allow you to register and update weapons as needed.

***

## Type

```typescript
type Announcement = {
    id: number,
    title: string,
    content: string, // stringified JSON
    pinned: boolean,
    author: {
        identifier: string,
        name: string
    }
}
```

## Exports

### Search for an Announcement

```lua
exports['redutzu-mdt']:SearchAnnouncement(id: number) // AnnouncementType | null
```

### Create a new Announcement

```lua
exports['redutzu-mdt']:CreateAnnouncement({ title = 'Title', content = '[{..}]' }, authorSource) -- number (id)
```

### Update a Announcement

```lua
-- It returns a boolean (if it was successfully updated)
exports['redutzu-mdt']:UpdateAnnouncement(id, { title = 'New title' }) -- boolean
```

### Delete an Announcement&#x20;

```lua
-- It returns a boolean (if it was successfully deleted)
exports['redutzu-mdt']:DeleteAnnouncement(id: number) // boolean
```


# Tags

Here, you'll find more information about the tags

***

## Type

```typescript
type Tag = {
    identifier: string,
    color: string,
    label: string
}
```

## Exports

### SearchTags

```lua
exports['redutzu-mdt']:SearchTags(query: string, type: string) // Tag[] | null
```

### CreateTag

```lua
exports['redutzu-mdt']:CreateTag({
  name = 'Dangerous',
  type = 'warrant',
  description = 'A short description about the tag', -- optional
  color = '#FFFFFF' -- hex, rgba, hsl
}) -- number (id)
```


# Client Events


# Open/Close MDT

To open the mdt using an event use this trigger:

&#x20;redutzu-mdt:client:openMDT

To Close the mdt you can use:

redutzu-mdt:client:closeMDT


# Client Exports

Here, you'll find useful client-side exports for this asset. Take your time to understand each step and example to see how they work. We recommend these exports for experienced developers.

***

## isOpened

This export returns whether the player has the MDT opened.

```lua
exports['redutzu-mdt']:isOpened()
```

Here's how you can utilize this export:

```lua
CreateThread(function()
    while true do
        if IsControlJustReleased(0, 38) then
            local opened = exports['redutzu-mdt']:isOpened()

            if opened then
                -- do something if the player pressed "E" and he is using the MDT
            end
        end

        Wait(5)
    end
end)
```


# Common Issues

## List of common issue

<details>

<summary>Illegal mix of collations</summary>

Ensure that the collations of the mdt table match those of your players/users. The error message at the bottom will indicate the correct collation for the mdt tables. To fix the issue, run the following sql: (***make sure the COLLATE below is the same as youre users COLLATE***)

\*utf8mb4 COLLATE utf8mb4\_unicode\_ci;\* must match what you have on you're players/users table check that first that can be aything even \`utf8mb4\_turkish\_ci\`  or anything else.

it show in the error message in server console.\
ther will be 2 displayed.

#### collation updates:

```sql
ALTER TABLE mdt_gallery CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_incidents CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_evidences CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_warrants CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_bolos CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_weapons CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_tags CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_charges CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_activity CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_announcements CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_codes CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_citizens CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE mdt_vehicles CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

</details>

<details>

<summary>Unkown column 'mdt_table.column' in 'field list'</summary>

If you encounter this error message, it means that the structure of the mdt table does not match what the application expects. To fix this issue, you'll need to delete the existing table and reinsert it from the provided SQL file. (`redutzu-mdt/sql/database.sql`)

</details>

<details>

<summary>attempt to index a nil value (global 'QBX')</summary>

If you encounter this error message, it means that the `QBX` global variable is not being properly initialized. This is likely due to an issue with the configuration in the `fxmanifest.lua` file.

To resolve this error, you'll need to uncomment the following lines in the `fxmanifest.lua` file:

1. Uncomment the line `'@ox_lib/init.lua'` from the `shared_scripts` section.
2. Uncomment the line `'@qbx_core/modules/playerdata.lua'` from the `client_scripts` section.

After making these changes, save the `fxmanifest.lua` file and restart the resource.

</details>


# Redutzu EMS

Redutzu-EMS is a powerful and user-friendly tool that allows you to manage and track emergency medical incidents on your server.

{% embed url="<https://www.youtube.com/watch?v=g_-htwlJwpc>" %}
Purchase now at <https://store.redutzu.com/>
{% endembed %}


# Installation


# Guides


# Exports


# Redutzu Documents

Redutzu Documents is a script used for legitimizing people who belong to certain jobs

{% embed url="<https://www.youtube.com/watch?v=Dk4N6vhkp7I>" %}
Purchase now at <https://store.redutzu.com/>
{% endembed %}


# Installation

## Installing the script on your FiveM server is a simple process that can be completed in a few easy steps.

1. Purchase the script from Tebex.
2. Download the script from your Cfx.re Assets page and unzip the file.
3. Drag the "redutzu-documents" folder from the unzipped file into your server's resources folder.
4. Open your server's server.cfg file and add the following line at the bottom of your resources: "ensure redutzu-documents"
5. Once the server has started, you can find three different configuration files located in the "redutzu-documents" resource. You can configure the script to your liking by editing these files. You can find more config details on the configuration section of the script's documentation.


# Guides

Here you will find all the options from the configuration explained with some examples.

{% hint style="success" %}
The script is divided into 3 config files to have the highest data security!
{% endhint %}

## 1. Client Config (client/config.lua)

```lua
Config.Notify = function(message, type)
    exports['esx_notify']:Notify(type, 5000, message)
end
```

## 2. Shared Config (config.lua)

<pre class="language-lua"><code class="lang-lua"><strong>Config.Messages = {
</strong>    ['NOT_ALLOWED'] = 'You are not allowed to do that!',
    ['CANT_NOW'] = 'You can\'t do that now!'
}

Config.Jobs = {
    ['police'] = {
        colors = {
            background = '#1243e3',
            header = '#648df3'
        },
        command = {
            enabled = true,
            name = '+police_document',
            description = 'Display your document to nearby players'
        },
        item = {
            enabled = true,
            name = 'police-card'
        },
        prop = {
            name = 'prop_fib_badge'
        },
        animation = {
            dict = 'paper_1_rcm_alt1-9',
            anim = 'player_one_dual-9',
            bone_index = 28422
        },
        information = {
            { label = 'Job', value = 'job.label' },
            { label = 'Grade', value = 'job.grade_label' },
            { label = 'Date of birth', value = 'variables.dateofbirth' }
        },
        removeBackground = false,
        range = 5.0,
        time = 4.0
    },
    ['ambulance'] = {
        colors = {
            background = '#fc4457',
            header = '#fa6e7c'
        },
        command = {
            enabled = true,
            name = '+ambulance_document',
            description = 'Display your document to nearby players'
        },
        item = {
            enabled = true,
            name = 'ambulance-card'
        },
        prop = {
            name = 'prop_fib_badge'
        },
        animation = {
            dict = 'paper_1_rcm_alt1-9',
            anim = 'player_one_dual-9',
            bone_index = 28422
        },
        information = {
            { label = 'Job', value = 'job.label' },
            { label = 'Grade', value = 'job.grade_label' },
            { label = 'Date of birth', value = 'variables.dateofbirth' }
        },
        removeBackground = false,
        range = 5.0,
        time = 4.0
    }
}
</code></pre>

## 3. Server Config (server/config.lua)

```lua
-- Custom Functions
Config.Notify = function(source, message, type)
    TriggerClientEvent('esx:showNotification', source, message, type)
end

-- Commands
Config.UseESXCommands = true -- If you want to use ESX commands, set this to true
Config.RegisterCommand = function(name, description, callback)
    if Config.UseESXCommands then
        ESX.RegisterCommand(name, 'user', function(player, args, error)
            callback(player)
        end, false, { help = description })    
    else
        RegisterCommand(name, function(source, args, raw)
            local player = ESX.GetPlayerFromId(source)
            callback(player)
        end, false)
    end
end

-- Items
Config.RegisterItem = function(name, callback)
    ESX.RegisterUsableItem(name, function(source)
        local player = ESX.GetPlayerFromId(source)
        callback(player)
    end)
end
```


# Exports

## 1. AddJobDocument (Server-Side)

{% code lineNumbers="true" %}

```lua
CreateThread(function()
	exports['redutzu-documents-esx']:AddJobDocument('mechanic', {
		colors = {
			background = 'rgb(255, 255, 255, .9)',
			header = 'rgb(255, 255, 255, .7)'
		},
		command = {
			enabled = true,
			name = '+mechanic_document',
			description = 'Display your document to nearby players'
		},
		item = {
			enabled = true,
			name = 'mechanic-card'
		},
		prop = {
			name = 'prop_fib_badge'
		},
		animation = {
			dict = 'paper_1_rcm_alt1-9',
			anim = 'player_one_dual-9',
			bone_index = 28422
		},
		information = {
			{ label = 'Job', value = 'job.label' },
			{ label = 'Grade', value = 'job.grade_label' },
			{ label = 'Date of birth', value = 'variables.dateofbirth' }
		},
		range = 5.0,
		time = 4.0
	})
end)
```

{% endcode %}

## 2. CreateCustomDocument (Server-Side)

{% code lineNumbers="true" %}

```lua
RegisterCommand('custom-document', function(source, args, raw)
   exports['redutzu-documents-esx']:CreateCustomDocument(source, {
       information = {
          { label = 'Job', value = 'job.label' },
          { label = 'Rank', value = 'job.grade_label' },
          { label = 'Date of birth', value = 'variables.dateofbirth' }
       },
       removeBackground = true
   })
end, false)
```

{% endcode %}

<figure><img src="/files/eDPlMlJwPz0B0CXgBaQG" alt=""><figcaption><p>This is how the document created using the code above will look</p></figcaption></figure>


# Gang Activities


# Installation

Welcome to the Gang-Activities installation guide. Here, you will learn how to fully install our asset to ensure a smooth and trouble-free setup for your FiveM server. By carefully following each step in this guide, you will achieve a clean and efficient installation.

{% hint style="info" %}
If you encounter any issues during the installation, please do not hesitate to reach out for assistance. Open a ticket in our Discord server, and our dedicated support team will be ready to help you resolve any problems. We're committed to ensuring that your setup process is as smooth and trouble-free as possible, so feel free to contact us with any questions or concerns you may have.
{% endhint %}

***

### Download the asset

After purchasing the script from our store at [**Redutzu's Scripts Store**](https://store.redutzu.com), head over to [**Keymaster**](https://keymaster.fivem.net/asset-grants). Here, you will find the assets you have acquired. Download the scripts named **"Gang Activities"**  to your environment.

{% hint style="danger" %}
The script will not work if the asset is not purchased and present on your Keymaster account. Additionally, please be aware that if you transfer these assets, you will not be able to receive them back, and the script will cease to function.
{% endhint %}

***

### Download the dependencies

To make sure the MDT works as it should, there are a few scripts you must download. These extra scripts are key for the MDT system to run well and fit into your FiveM server. Be sure to get all the needed dependencies listed in the documentation to ensure a smooth and fully working setup.

<table><thead><tr><th width="224">Dependency</th><th>Link</th></tr></thead><tbody><tr><td>oxmysql / mysql-async</td><td><a href="https://github.com/overextended/oxmysql/releases">OxMySQL</a> or <a href="https://github.com/brouznouf/fivem-mysql-async/releases">MySQL-Async</a></td></tr></tbody></table>

{% hint style="info" %}
For optimal performance and smooth operation of the script, we highly recommend having one of the latest recommended artifacts installed.
{% endhint %}

***

### Start the resources

To get Gang Activities running smoothly on your FiveM server, it's important to start the scripts in the right order. This ensures everything loads correctly, avoiding problems and making sure the MDT system works well.

```systemd
# The first hing you want to start is your database wrapper
ensure oxmysql / mysql-async

# Then start your core
ensure es_extended / qb-core / qbx_core / vrp

ensure gang-activities
```

{% hint style="danger" %}
Make sure the license for your server matches the account where you bought the script. Using different licenses will cause errors, making the script to not work.
{% endhint %}

***

### Enable onesync

To ensure the script runs optimally and missions function correctly, it is strongly recommended — and in fact required — to enable **OneSync** and **OneSync Infinity**, along with several additional settings that improve overall performance.

```systemd
onesync on
onesync_enableInfinity 1
onesync_distanceCullVehicles true
onesync_forceMigration true
onesync_population false
onesync_distanceCulling false
```

### Insert the SQL

This step is crucial, so pay close attention. Inserting the SQL is a vital part of setting up the MDT on your server. Be sure to follow each step carefully and with full attention to detail. This ensures that the database is properly configured and ready to support the functionality of the MDT system without any issues.

{% hint style="warning" %}
The SQL file is working for every type of framework, you do not need a special one for your server.
{% endhint %}

{% tabs %}
{% tab title="Default" %}
You must insert this code, no matter which framework you're using. This is the main SQL required for the script to function properly.

```sql
CREATE TABLE `gang_activities` (
  `id` int(255) NOT NULL AUTO_INCREMENT,
  `identifier` varchar(20) NOT NULL,
  `avatar` varchar(255) DEFAULT 'default',
  `level` int(20) DEFAULT 1,
  `xp` int(255) DEFAULT 0,
  `statistics` LONGTEXT,
  `missions` LONGTEXT,
  PRIMARY KEY (`id`)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

{% endtab %}
{% endtabs %}

***

### Start your server

You can now start your server and enjoy the script. Additionally, you can configure the script further to match your preferences. For more information, refer to the configuration section.


# Guides


# Frameworks

### server/custom/framework

{% tabs %}
{% tab title="QB-CORE" %}

```lua
Config.Framework = 'qb-core' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'qb-core' then
    return
end

Framework = {}

local QBCore = exports['qb-core']:GetCoreObject()

function Framework.GetPlayerIdentifier(source)
   return QBCore.Functions.GetPlayer(source)?.PlayerData?.citizenid
end

function Framework.GetSourceFromIdentifier(identifier)
    return QBCore.Functions.GetPlayerByCitizenId(identifier)?.PlayerData?.source
end

function Framework.AddMoney(identifier, amount, reason)
    local source = Framework.GetSourceFromIdentifier(identifier)
    local PlayerData = QBCore.Functions.GetPlayer(source)
    PlayerData.Functions.AddMoney('bank', amount, reason)
end

function Framework.RegisterCommand(name, description, callback, group)
    QBCore.Commands.Add(name, description, {}, false, callback, group)
end
```

{% endtab %}

{% tab title="QBOX" %}

```lua
Config.Framework = 'qbox' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'qbox' then
    return
end

Framework = {}

function Framework.GetPlayerIdentifier(source)
    return exports['qbx_core']:GetPlayer(source)?.PlayerData?.citizenid
end

function Framework.GetSourceFromIdentifier(identifier)
    return exports['qbx_core']:GetPlayerByCitizenId(identifier)?.PlayerData?.source
end

function Framework.AddMoney(identifier, amount, reason)
    local source = Framework.GetSourceFromIdentifier(identifier)
    local player = exports['qbx_core']:GetPlayer(source)
    return player.Functions.AddMoney('bank', amount, reason)
end

function Framework.RegisterCommand(name, description, callback, group)
    lib.addCommand(name, {
        help = description,
        restricted = group and ('group.%s'):format(group) or nil
    }, callback)
end
```

{% endtab %}

{% tab title="ESX" %}

```lua
Config.Framework = 'esx' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'esx' then
    return
end

Framework = {}

local success, ESX = pcall(function()
    return exports['es_extended']:getSharedObject()
end)

if not success then
    TriggerEvent('esx:getSharedObject', function(object)
        ESX = object
    end)
end

function Framework.GetPlayerIdentifier(source)
   return ESX.GetPlayerFromId(source)?.identifier
end

function Framework.GetSourceFromIdentifier(identifier)
   return ESX.GetPlayerFromIdentifier(identifier)?.source
end

function Framework.AddMoney(identifier, amount, reason)
    local source = Framework.GetSourceFromIdentifier(identifier)
    local player = ESX.GetPlayerFromId(source)
    return player.addMoney(amount)
end

function Framework.RegisterCommand(name, description, callback, group)
    ESX.RegisterCommand(name, group, function(player)
        return callback(player.source)
    end, true, {
        help = description,
        arguments = {}
    })
end
```

{% endtab %}

{% tab title="VRP" %}

```lua
Config.Framework = 'vrp' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'vrp' then
    return
end

local Proxy = module('vrp', 'lib/Proxy')
local vRP = Proxy.getInterface('vRP')

Framework = {}

function Framework.GetPlayerIdentifier(source)
    local id = vRP.getUserId { source }
    return tostring(id)
end

function Framework.GetSourceFromIdentifier(identifier)
    identifier = tonumber(identifier)
    return vRP.getUserSource { identifier }
end

function Framework.AddMoney(identifier, amount, reason)
    identifier = tonumber(identifier)
    vRP.giveMoney({ identifier, amount })
end

function Framework.RegisterCommand(name, description, callback, group)
    RegisterCommand(name, function(source)
        local identifier = Framework.GetPlayerIdentifier(source)
        local level = vRP.getUserAdminLevel({ identifier })

        if group == 'admin' and level < 5 then
            return
        end

        return callback(source)
    end, false)
end
```

{% endtab %}

{% tab title="STANDALONE" %}

```lua
Config.Framework = 'standalone' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'standalone' then
    return
end

Framework = {}

function Framework.GetPlayerIdentifier(source)
    local identifiers, license = GetPlayerIdentifiers(source), ''

    for key, value in pairs(identifiers) do
        if string.match(value, 'license:') then
            license = value
            break
        end
    end

    return license
end

function Framework.GetSourceFromIdentifier(identifier)
    -- implement your code here
    return 1
end

function Framework.AddMoney(identifier, amount, reason)
    -- implement your code here
end

function Framework.RegisterCommand(name, description, callback, group)
    RegisterCommand(name, callback, false)
endConfig.Framework = 'auto'              -- qb-core/qbox/esx/vrp/custom
```

{% endtab %}
{% endtabs %}

### client/custom/framework

{% tabs %}
{% tab title="QB-CORE" %}

```lua
Config.Framework = 'qb-core' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'qb-core' then
    return
end

Framework = {}

QBCore = exports['qb-core']:GetCoreObject()

function Framework.ProgressBar(message, time)
    QBCore.Functions.Progressbar('gang_activities_progress', message, time, false, true, {
        disableMovement = false,
        disableCarMovement = false,
        disableMouse = false,
        disableCombat = true,
    }, {}, {}, {})
    Wait(time)
end

function Framework.SetVehicleExtras(entity)
    SetVehicleFuelLevel(entity, 100.0);
    SetVehicleNumberPlateText(entity, 'LS' .. math.random(111111, 999999))
end

function Framework.setVehicleDoorsOpen(netId, entity, plate)
       TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys',plate)
end

function Framework.HideInterfaceForScene()
    -- your events to hide server interfaces for cutscenes
end

function Framework.ShowInterfaceAfterScene()
    -- your events to show again server interfaces for cutscenes
end

function Framework.DrawText3D(x, y, z, text)
    QBCore.Functions.DrawText3D(x, y, z, text)
end
```

{% endtab %}

{% tab title="QBOX" %}

```lua
Config.Framework = 'qbox' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'qbox' then
    return
end

Framework = {}

function Framework.ProgressBar(message, time)
    lib.progressBar({
        duration = time,
        label = message
    })
end

function Framework.SetVehicleExtras(entity)
    SetVehicleFuelLevel(entity, 100.0);
    SetVehicleNumberPlateText(entity, 'LS' .. math.random(111111, 999999))
end


function Framework.setVehicleDoorsOpen(netId, entity, plate)
    --    TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys',plate)
end


function Framework.HideInterfaceForScene()
    -- your events to hide server interfaces for cutscenes
end

function Framework.ShowInterfaceAfterScene()
    -- your events to show again server interfaces for cutscenes
end

function Framework.DrawText3D(x, y, z, text)
    SetTextScale(0.30, 0.30)
    SetTextFont(0)
    SetTextProportional(1)
    SetTextColour(255, 255, 255, 215)
    SetTextEntry("STRING")
    SetTextCentre(true)
    AddTextComponentString(text)
    SetDrawOrigin(x, y, z, 0)
    DrawText(0.0, 0.0)
    local factor = (string.len(text)) / 250
    DrawRect(0.0, 0.0 + 0.0125, 0.017 + factor, 0.03, 0, 0, 0, 75)
    ClearDrawOrigin()
end
```

{% endtab %}

{% tab title="ESX" %}

```lua
Config.Framework = 'esx' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'esx' then
    return
end

Framework = {}

success, ESX = pcall(function()
    return exports['es_extended']:getSharedObject()
end)

if not success then
    while not ESX do
        TriggerEvent('esx:getSharedObject', function(object)
            ESX = object
        end)

        Wait(500)
    end
end

function Framework.ProgressBar(message, time)
    ESX.Progressbar(message, time, { FreezePlayer = false })
    Wait(time)
end

function Framework.SetVehicleExtras(entity)
    SetVehicleFuelLevel(entity, 100.0);
    SetVehicleNumberPlateText(entity, 'LS' .. math.random(111111, 999999))
end

function Framework.setVehicleDoorsOpen(netId, entity, plate)
       TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys',plate)
end

function Framework.HideInterfaceForScene()
    -- your events to hide server interfaces for cutscenes
end

function Framework.ShowInterfaceAfterScene()
    -- your events to show again server interfaces for cutscenes
end

function Framework.DrawText3D(x, y, z, text)
    ESX.Game.Utils.DrawText3D(vector3(x, y, z), text)
end
```

{% endtab %}

{% tab title="VRP" %}

```lua
Config.Framework = 'vrp' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'vrp' then
    return
end

Framework = {}

function Framework.ProgressBar(message, time)
    exports['rprogress']:Start(message, time)
    -- set Wait(time) if you don't have sync for progressBar
    -- Wait(time)
end

function Framework.SetVehicleExtras(entity)
    SetVehicleFuelLevel(entity, 100.0);
    SetVehicleNumberPlateText(entity, 'LS' .. math.random(111111, 999999))
end

function Framework.setVehicleDoorsOpen(netId, entity, plate)
       TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys',plate)
end

function Framework.HideInterfaceForScene()
    -- your events to hide server interfaces for cutscenes
end

function Framework.ShowInterfaceAfterScene()
    -- your events to show again server interfaces for cutscenes
end

function Framework.DrawText3D(x, y, z, text)
    SetTextScale(0.30, 0.30)
    SetTextFont(0)
    SetTextProportional(1)
    SetTextColour(255, 255, 255, 215)
    SetTextEntry("STRING")
    SetTextCentre(true)
    AddTextComponentString(text)
    SetDrawOrigin(x, y, z, 0)
    DrawText(0.0, 0.0)
    local factor = (string.len(text)) / 250
    DrawRect(0.0, 0.0 + 0.0125, 0.017 + factor, 0.03, 0, 0, 0, 75)
    ClearDrawOrigin()
end
```

{% endtab %}

{% tab title="STANDALONE" %}

```lua
Config.Framework = 'standalone' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Framework ~= 'standalone' then
    return
end

Framework = {}

function Framework.ProgressBar(message, time)
    -- your progressbar
end

function Framework.SetVehicleExtras(entity)
    SetVehicleFuelLevel(entity, 100.0);
    SetVehicleNumberPlateText(entity, 'LS' .. math.random(111111, 999999))
end

function Framework.setVehicleDoorsOpen(netId, entity, plate)
    --    TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys',plate)
end


function Framework.HideInterfaceForScene()
    -- your events to hide server interfaces for cutscenes
end

function Framework.ShowInterfaceAfterScene()
    -- your events to show again server interfaces for cutscenes
end

function Framework.DrawText3D(x, y, z, text)
    SetTextScale(0.30, 0.30)
    SetTextFont(0)
    SetTextProportional(1)
    SetTextColour(255, 255, 255, 215)
    SetTextEntry("STRING")
    SetTextCentre(true)
    AddTextComponentString(text)
    SetDrawOrigin(x, y, z, 0)
    DrawText(0.0, 0.0)
    local factor = (string.len(text)) / 250
    DrawRect(0.0, 0.0 + 0.0125, 0.017 + factor, 0.03, 0, 0, 0, 75)
    ClearDrawOrigin()
end
```

{% endtab %}
{% endtabs %}


# Notify

### client/custom/notify

{% tabs %}
{% tab title="default" %}

```lua
Config.Notify = 'default' -- default, custom
```

```lua
if Config.Notify ~= 'default' then
    return
end

local vRP
if Config.Framework == 'vrp' then
    vRP = Proxy.getInterface('vRP')
end

function Notify(message, type)
    if Config.Framework == 'qb-core' then
        TriggerEvent('QBCore:Notify', message, type)
    elseif Config.Framework == 'qbox' then
        exports['qbx_core']:Notify(message, type)
    elseif Config.Framework == 'esx' then
        TriggerEvent('esx:showNotification', message, type)
    elseif Config.Framework == 'vrp' then
        vRP.notify({ message, type })
    end
end
```

{% endtab %}

{% tab title="custom" %}

```lua
Config.Notify = 'custom' -- default, custom
```

```lua
if Config.Notify ~= 'custom' then
    return
end

---@param message string
---@param type string
function Notify(message, type)
    -- implement your logic here (this is just an example)
    TriggerEvent('chat:addMessage', {
        args = { '[Gang-Activities]', message },
        color = { 255, 255, 255 },
        multiline = true
    })
end
```

{% endtab %}
{% endtabs %}

### server/custom/notify

{% tabs %}
{% tab title="default" %}

```lua
Config.Notify = 'default' -- default, custom
```

```lua
if Config.Notify ~= 'default' then
    return
end

local vRPClient
if Config.Framework == 'vrp' then
    local Tunnel = module('vrp', 'lib/Tunnel')
    vRPClient = Tunnel.getInterface('vRP', 'gang-activities')
end

function Notify(source, message, type)
    if Config.Framework == 'qb-core' then
        TriggerClientEvent('QBCore:Notify', source, message, type)
    elseif Config.Framework == 'qbox' then
        exports['qbx_core']:Notify(source, message, type)
    elseif Config.Framework == 'esx' then
        TriggerClientEvent('esx:showNotification', source, message, type)
    elseif Config.Framework == 'vrp' then
        vRPClient.notify(source, { message, type })
    end
end

```

{% endtab %}

{% tab title="custom" %}

```lua
Config.Notify = 'custom' -- default, custom
```

```lua
if Config.Notify ~= 'custom' then
    return
end

---@param source number
---@param message string
---@param type string
function Notify(source, message, type)
    -- implement your logic here (this is just an example)
    TriggerClientEvent('chat:addMessage', source, {
        args = { '[Gang-Activities]', message },
        color = { 255, 255, 255 },
        multiline = true
    })
end

```

{% endtab %}
{% endtabs %}


# Inventory

### server/custom/inventory

{% tabs %}
{% tab title="Default" %}

```lua
Config.Inventory = 'default'              -- default, ox_inventory, axr_inventory, qs-inventory or custom
```

```lua
if Config.Inventory ~= 'default' then
    return
end

local ESX, QBCore
if Config.Framework == 'qb-core' then
    QBCore = exports['qb-core']:GetCoreObject()

    exports['qb-core']:AddItem(Config.Item.Name, {
        name = Config.Item.Name,
        label = Config.Item.Label,
        weight = 10,
        type = 'item',
        image = 'dark_tablet.png',
        unique = false,
        useable = true,
        shouldClose = true,
        combinable = nil,
        description = Config.Item.Description
    })
elseif Config.Framework == 'esx' then
    ESX = exports['es_extended']:getSharedObject()
end

function CreateItem(name, callback)
    if Config.Framework == 'qb-core' then
        QBCore.Functions.CreateUseableItem(name, function(source)
            local identifier = Framework.GetPlayerIdentifier(source)
            callback(identifier)
        end)
    elseif Config.Framework == 'esx' then
        ESX.RegisterUsableItem(name, callback)
    elseif Config.Framework == 'vrp' then
        vRP.defInventoryItem({ name, 'Dark Tablet', 'Dark tablet for illegal activities', function()
            local choices = {}

            choices['Use'] = function(player, choice)
                local user_id = vRP.getUserId({ player })

                if user_id then
                    callback(user_id)
                end
            end

            return choices
        end, 0.01 })
    end
end

function HasItem(identifier, item, amount)
    amount = tonumber(amount) or 1

    if Config.Framework == 'qb-core' then
        local source = QBCore.Functions.GetPlayerByCitizenId(identifier).PlayerData.source
        local items = exports['qb-inventory']:GetItemsByName(source, item)
        return #items >= amount
    elseif Config.Framework == 'esx' then
        local player = ESX.GetPlayerFromIdentifier(identifier)
        local items = player.getInventoryItem(item).count
        return items >= amount
    elseif Config.Framework == 'vrp' then
        local items = vRP.getInventoryItemAmount({ identifier, item })
        return items >= amount
    end
end

function GiveItem(identifier, item, amount)
    if Config.Framework == 'qb-core' then
        local source = QBCore.Functions.GetPlayerByCitizenId(identifier).PlayerData.source
        exports['qb-inventory']:AddItem(source, item, amount)
    elseif Config.Framework == 'esx' then
        local player = ESX.GetPlayerFromIdentifier(identifier)
        player.addInventoryItem(item, amount)
    elseif Config.Framework == 'vrp' then
        vRP.giveInventoryItem({ identifier, item, amount, true })
    end
end
```

{% endtab %}

{% tab title="ox\_inventory" %}

```lua
Config.Framework = 'ox_inventory' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Inventory ~= 'ox_inventory' then
    return
end

-- Add this in @ox_inventory/data/items.lua
-- ['dark_tablet'] = {
--     label = 'Mobile Data Terminal',
--     weight = 100,
--     stack = false,
--     close = true,
--     allowArmed = false,
--     consume = 0,
--     client = { event = 'gang-activities:openMenu', image = 'dark_tablet.png' },
--     description = 'Dark tablet for illegal activities'
-- }

function CreateItem(name, callback)
    -- This inventory doesn't require this function
end

function HasItem(identifier, item, amount)
    amount = tonumber(amount) or 1
    local source = Framework.GetSourceFromIdentifier(identifier)
    local data = exports['ox_inventory']:GetItem(source, item, nil, true)
    return data.count >= amount
end

function GiveItem(identifier, item, amount)
    local source = Framework.GetSourceFromIdentifier(identifier)
    exports['ox_inventory']:AddItem(source, item, amount)
end
```

{% endtab %}

{% tab title="qs-inventory" %}

```lua
Config.Framework = 'qs-inventory' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Inventory ~= 'qs-inventory' then
    return
end

function CreateItem(name, callback)
    exports['qs-inventory']:CreateUsableItem(name, function(source, item)
        local identifier = Framework.GetPlayerIdentifier(source)
        callback(identifier)
    end)
end

function HasItem(identifier, item, amount)
    amount = tonumber(amount) or 1

    local source = Framework.GetSourceFromIdentifier(identifier)
    local totalAmount = exports['qs-inventory']:GetItemTotalAmount(source, item)

    return totalAmount >= amount
end

function GiveItem(identifier, item, amount)
    local source = Framework.GetSourceFromIdentifier(identifier)
    exports['qs-inventory']:AddItem(source, item, amount)
end
```

{% endtab %}

{% tab title="axr\_inventory" %}

```lua
Config.Framework = 'axr_inventory' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Inventory ~= 'axr_inventory' then
    return
end

-- This inventory is a custom script made by our developer
-- https://axero.tebex.io/package/6459587

function CreateItem(name, callback)
    exports['axr_inventory']:createItem(name, 'Dark Tablet', 'Dark tablet for illegal activities', 0.01, 'all', function(identifier)
        callback(identifier)
    end)
end

function HasItem(identifier, item, amount)
    amount = tonumber(amount) or 1
    local items = exports['axr_inventory']:getPlayerItemAmount(identifier, item)
    return items >= amount
end

function GiveItem(identifier, item, amount)
    exports['axr_inventory']:givePlayerItem(identifier, item, amount, true)
end
```

{% endtab %}

{% tab title="custom" %}

```lua
Config.Framework = 'custom' -- auto/qb-core/qbox/esx/vrp/standalone
```

```lua
if Config.Inventory ~= 'custom' then
    return
end

---Creates the item
---@param name string
---@param callback fun(identifier: string)
function CreateItem(name, callback)
    -- register the item as useable
end

---Has the item in inventory?
---@param identifier string|number Citizen id (qb-core, qbox), License (esx), numerical id (vrp)
---@param item string Item name
---@param amount number?
---@return boolean
function HasItem(identifier, item, amount)
    -- implement your logic here
    return true
end

---Gives an item to a player
---@param identifier string|number Citizen id (qb-core, qbox), License (esx), numerical id (vrp)
---@param item string Item name
---@param amount number How many items should be added?
function GiveItem(identifier, item, amount)
    -- add the item to the player
end
```

{% endtab %}
{% endtabs %}


# Functions

### client/custom/functions/alerts.lua

```lua
---Sends an alert to the police (usually used for dispatches)
---@param coords vector3|vector4
---@param data table
function SendAlertToPolice(coords, data)

end
```

### client/custom/functions/open.lua

```lua
function CanOpenMenu()
    local ped = PlayerPedId()

    -- your custom functions to open menu (coma status/handcuff/is in any vehicle etc)
    if IsPedInAnyVehicle(ped, true) then
        return false
    end

    return true
end
```

### server/custom/functions/open.lua

```lua
---Is the player allowed to open the menu?
---@param identifier string|number
---@return boolean allowed
function canOpenMenu(identifier)
    if identifier then
        -- you can implement your custom code here (such as coma status, handcuffs, etc.)
        return true
    end

    return false
end
```

### server/custom/functions/rewards.lua

```lua
---Gives the rewards to a player after succeeding a mission
---@param identifier string|number
---@param money number
function GiveMissionRewards(identifier, money)
    local source = Framework.GetSourceFromIdentifier(identifier)
    Framework.AddMoney(identifier, money, 'Mission-Reward')
    Notify(
        source,
        Lang.notify['receive_money_from_mission'].message:format(money),
        Lang.notify['receive_money_from_mission'].type
    )
end
```


# Custom missions

{% hint style="info" %}
To create a custom mission, whether single-player or multiplayer, you first need to understand which files are required and how to edit/use them properly.
{% endhint %}

{% stepper %}
{% step %}

### Create a basic config template

Locate the config/missions.lua file and create a basic template (example bellow)

{% tabs %}
{% tab title="Singleplayer" %}

```lua
    {
        label = 'Custom Mission Example',
        description = 'Your Custom Mission Example description for gang-activities.',
        icon = 'assets/avatars/male_5.png',
        reward = 100,
        xpReward = 50,
        difficulty = 'easy',
        required_level = 1,
        mission = 'custom_mission_exemple',
        endMissionRespawn = false,
        respawnRandomPos = {
            vector3(-57.509113311768, -1079.9354248047, 26.964902877808),
            vector3(377.14935302734, -1306.2001953125, 33.484718322754),
            vector3(-823.34063720703, -1094.5144042969, 11.145439147949),

        },
        blip = { -- set blip = nil so you don't activate the blip menu
            id = 119,
            color = 4,
            scale = 0.6,
            coords = true,

            name = 'Custom Mission Blip'
        },
        tasks = {
            [1] = {
                info = 'Custom singleplayer mission task 1',
                completeFunction = function(tasksArgs)
                    local arg1 = tasksArgs[1]


                    return false;
                end,
            },
        }
    },
```

{% endtab %}

{% tab title="Multiplayer" %}

```lua
    ['custom-multiplayer-mission'] = {
        label = 'Custom multiplayer Mission',
        description = 'Custom multiplayer Mission description',
        icon = 'assets/avatars/default.png',
        mission_color = 'green',
        mission_background = 'store-robbery', -- .png file name from assets/missions-bg/
        infos = {
            'Info 1',
            'Info 2',
            'Info 3',
        },
        endMissionRespawn = false,
        respawnRandomPos = {
            vector3(-57.509113311768, -1079.9354248047, 26.964902877808),
            vector3(377.14935302734, -1306.2001953125, 33.484718322754),
            vector3(-823.34063720703, -1094.5144042969, 11.145439147949),

        },

        blip = { -- set blip = nil so you don't activate the blip menu
            id = 59,
            color = 1,
            scale = 0.6,
            coords = nil,
            forEntity = true,
            setRoute = true,
            name = 'Custom Mission'
        },

        difficulty = 'easy',
        required_level = 1,
        xpReward = 60,
        reward = 35000,
        multiplayer = true, -- don't change
        setVirtualWorld = false,
        tasks = {
            [1] = {
                info = 'Custom Task 1',
                completeFunction = function(tasksArgs)
                    local task1 = tasksArgs[1];
                    return task1
                end,
            },
        }
    },
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Note: In order to create a high quality mission script you need to add more data in this mission table, such as random mission locations/ rewards / tasks etc.
{% endhint %}
{% endstep %}

{% step %}

### Create the server-side file for your mission.

Navigate to server/custom/missions/singleplayer/ or server/custom/missions/multiplayer/ and use the default template located there.&#x20;

{% tabs %}
{% tab title="Singleplayer" %}

```lua
---@type string unique and same as client side
local missionId <const> = 'custom_mission_exemple';

---comment
---@param playerSource integer
---@param missionData table
---@return table?nil, table?nil
local missionFunction = function(playerSource, missionData)
    if not missionData then return print('ERROR: MissionData is nil') end;

    local extraData = {};
    local entities = {};

    -- custom singleplayer missions for server side;
    -- insert all the entities in entities table with table.insert(entities, {entityData}), so after the mission is finished/failed or resource is restarted the entities will be deleted
    -- extraData need to contain all the entities informations created in server side such as: networkId, weaponName for npc enemy, extra mission data such as selected location/house etc
    -- even if you are not using extraData and entities make sure to let it an empty table, = {}
    -- make sure to set entities in virtual world if the missionData.setVirtualWorld = true;
    -- example:

    local npc; -- npc entity after you create it;

    local playerIdentifier = Framework.GetPlayerIdentifier(playerSource);

    if missionData.setVirtualWorld then
        local bucketId = 100 + playerSource;
        SetEntityRoutingBucket(npc, bucketId);
    end

    return extraData, entities;
end

-- be sure to add this code line if you want to crete the mission;

-- exports['axr_gang-activities']:insertSingleMission(missionId, missionFunction);

```

{% endtab %}

{% tab title="Multiplayer" %}

```lua
---@type string unique and same as client side
local missionId <const> = 'custom_multiplayer_mission_exemple';

---comment
---@param playerSource integer -- leader indentifier
---@param missionData table
---@param lobbyData table
---@return table?nil, table?nil
local missionFunction = function(playerSource, missionData, lobbyData)
    if not missionData then return print('ERROR: MissionData is nil') end;

    local extraData = {};
    local entities = {};

    -- custom multiplayer missions for server side;
    -- insert all the entities in entities table with table.insert(entities, {entityData}), so after the mission is finished/failed or resource is restarted the entities will be deleted
    -- extraData need to contain all the entities informations created in server side such as: networkId, weaponName for npc enemy, extra mission data such as selected location/house etc
    -- even if you are not using extraData and entities make sure to let it an empty table, = {}
    -- make sure to set entities in virtual world if the missionData.setVirtualWorld = true;
    -- example:

    local npcEntity;
    -- set entity in multiplayer mission virtual world;
    -- lobbyData.bucket is generated on lobby created;

    if missionData.setVirtualWorld then
        local bucketId = lobbyData.bucket;
        SetEntityRoutingBucket(npcEntity, bucketId);
    else
        SetEntityRoutingBucket(npcEntity, 0);
    end

    -- add this code lines to have all lobby memebers sources in client side;
    extraData.missionPlayers = {};

    for k, v in pairs(lobbyData.players) do
        local playerSource = Framework.GetSourceFromIdentifier(v.identifier);
        table.insert(extraData.missionPlayers, playerSource);
    end
    -- extraData.missionEvents is like the mission progress, for example extraData.missionEvents['event_1'] can be the task_1, task_2 or any condition to make progress in client side
    extraData.missionEvents = {
        ['event_1'] = false,
    }


    -- extraInformations:
    -- you can add custom RegisterServerEvents so you can sync missionEvents server side - client side for all players: example bellow
    return extraData, entities;
end

RegisterServerEvent('axr_gang-activities:trySyncCustomMissionEvent_1', function(params)
    local src = source;
    local identifier = Framework.GetPlayerIdentifier(src);
    
    if identifier then
        local lobbyIndex = exports['axr_gang-activities']:getPlayerLobby(identifier);
        if lobbyIndex and lobbies[lobbyIndex] then
            if not lobbies[lobbyIndex].missionEvents then
                lobbies[lobbyIndex].missionEvents = {};
            end
            
            if lobbies[lobbyIndex].missionEvents['event_1'] then
                return;
            end

            lobbies[lobbyIndex].missionEvents['event_1'] = true;
            TriggerClientEvent('axr_gang-activities:syncCustomMissionEvent_1', src); -- extra trigger event for client side

            -- code exemple to update missionEvents for every lobby memeber;
            for k, v in pairs(lobbies[lobbyIndex].players) do
                local playerSource = Framework.GetSourceFromIdentifier(v.identifier);
                TriggerClientEvent('axr_gang-activities:updateMissionEvents', playerSource, 'event_1', true);
            end
        end
    end
end)

-- be sure to add this code line if you want to crete the mission;

-- exports['axr_gang-activities']:insertMultiplayerMission(missionId, missionFunction);

```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Note: safe this file and rename it to a desire label. Also you need to create custom code lines in order to work, such as CreateVehicle, CreatePed, custom threads etc.
{% endhint %}
{% endstep %}

{% step %}

### Create the client-side file for your mission.

Navigate to client/custom/missions/singleplayer/ or client/custom/missions/multiplayer/ and use the default template located there.&#x20;

{% hint style="info" %}
Note: safe this file and rename it to a desire label. Also you need to create custom code lines in order to work, such as CreateVehicle, CreatePed, custom threads etc.
{% endhint %}

{% tabs %}
{% tab title="Singleplayer" %}

```lua
---@type string unique and same as server side
local missionId <const> = 'custom_mission_exemple';

---comment
---@param missionData table
---@param extraData table
local missionFunction = function(missionData, extraData)
    if not missionData then return end; 
    if not extraData then return end;

    -- custom singleplayer missions for client side;
    -- use missionData and extraData from server side to take control of entities/ set multiple blips and make your mission as you want

    inMissions = true -- need to set inMissions = true before creating mission overlay

    -- create blip with createMissionBlip function

    local blip;
    local blipSetter = vector3(1834.9813232422,-1922.0208740234,149.18962097168); -- blip setter need to be entity or vector3 coord, set forEntity = true or coords = true in config/missions.lua for your mission as you want;
    if missionData.blip then
        blip = createMissionBlip(missionData.blip, blipSetter)
    end


    -- create and show mission overlay code:

    local missionDataCopy = overlayFunctions.createMissionCopy(missionData);
    overlayFunctions.createMissionOverlay(missionDataCopy);
    overlayFunctions.createOverlaytoogleThread();



    Citizen.CreateThread(function()
        ---@type table
        local playerTempStats = {}
        local condition = true;
        local waiter = 1000;

        local failed = false;
        local failed_condition = false;
        local taskParameter1, taskParameter2;
        local condition_made_kill = false;
        while condition do
            Citizen.Wait(waiter);

            -- add verification for mission tasks,
            ---@taskParameter1 any value you want (the logic for completing the task is made in config/missions.lua)
            ---@taskParameter2 any value you want (the logic for completing the task is made in config/missions.lua)
            ---@params count = mission tasks count

            overlayFunctions.checkCompletedTasks(missionData, { taskParameter1, taskParameter2, });

            if condition_made_kill then -- example to update player kills statistics. For more information check our documentation
                playerTempStats['missions_kills'] = {
                    addValue = true,
                    value = 1
                }
            end

            ---@type boolean -- set failed_condition for your custom mission
            if failed_condition then
                failed = true;
            end

            -- break the mission and notify the player for failing it
            if failed then
                -- Notify(fail_message, notify_type)
                break;
            end
        end

        -- delete blips after mission failed/completed

        if blip and missionData.blip then
            RemoveBlip(blip)
        end

        inMissions = false -- be sure to set inMissions = false after mission failed/completed

        -- respawn random player after failed/completed, coords can be added/edited in config/missions.lua

        if missionData.endMissionRespawn then
            respawnPlayerFromMission(missionData.respawnRandomPos, missionData.success)
        end

        -- destroy mission overlay;
        overlayFunctions.destroyMissionOverlay();

        -- be sure to add this TriggerServerEvent to update player statistics and give reward if missionData.success = true;

        TriggerServerEvent('axr_gang-activities:finishMission', missionData, playerTempStats)
    end)
end

-- be sure to add this code line if you want to crete the mission;

-- exports['axr_gang-activities']:insertSingleMission(missionId, missionFunction);

```

{% endtab %}

{% tab title="Multiplayer" %}

```lua
---@type string unique and same as server side
local missionId <const> = 'custom_multiplayer_mission_exemple';

---comment
---@param missionData table
---@param extraData table
---@param isLeader boolean
local missionFunction = function(missionData, extraData, isLeader)
    if not missionData then return end;
    if not extraData then return end;

    -- custom singleplayer missions for client side;
    -- use missionData and extraData from server side to take control of entities/ set multiple blips and make your mission as you want

    -- use isLeader so you can take control of entities and edit them, example bellow

    local netId;
    local npcEntity;

    if not netId then
        return print("netId doesn't exist")
    end
    local timeout = GetGameTimer() + 5000

    repeat
        npcEntity = NetToPed(netId)
        Wait(1)
    until DoesEntityExist(npcEntity) or GetGameTimer() > timeout

    if isLeader then
        if not DoesEntityExist(npcEntity) then
            print("❌ Error not exist in client-side")
            return
        end

        SetPedCanRagdoll(npcEntity, true)

        NetworkRequestControlOfEntity(npcEntity)
        while not NetworkHasControlOfEntity(npcEntity) and GetGameTimer() < timeout do
            Wait(10)
        end

        if not NetworkHasControlOfEntity(npcEntity) then
            print("❌ Error getting entity control")
            return
        end
    end

    inMissions = true -- need to set inMissions = true before creating mission overlay

    -- create blip with createMissionBlip function

    local blip;
    local blipSetter = vector3(1834.9813232422, -1922.0208740234, 149.18962097168); -- blip setter need to be entity or vector3 coord, set forEntity = true or coords = true in config/missions.lua for your mission as you want;
    if missionData.blip then
        blip = createMissionBlip(missionData.blip, blipSetter)
    end


    -- create and show mission overlay code:

    local missionDataCopy = overlayFunctions.createMissionCopy(missionData);
    overlayFunctions.createMissionOverlay(missionDataCopy);
    overlayFunctions.createOverlaytoogleThread();



    Citizen.CreateThread(function()
        ---@type table
        local playerTempStats = {}
        local condition = true;
        local waiter = 1000;

        local failed = false;
        local failed_condition = false;
        local taskParameter1, taskParameter2;
        local condition_made_kill = false;
        while condition do
            Citizen.Wait(waiter);

            -- add verification for mission tasks,
            ---@taskParameter1 any value you want (the logic for completing the task is made in config/missions.lua)
            ---@taskParameter2 any value you want (the logic for completing the task is made in config/missions.lua)
            ---@params count = mission tasks count

            overlayFunctions.checkCompletedTasks(missionData, { taskParameter1, taskParameter2, });

            if condition_made_kill then -- example to update player kills statistics. For more information check our documentation
                playerTempStats['missions_kills'] = {
                    addValue = true,
                    value = 1
                }
            end

            ---@type boolean -- set failed_condition for your custom mission
            if failed_condition then
                failed = true;
            end

            -- break the mission and notify the player for failing it
            if failed then
                -- Notify(fail_message, notify_type)
                break;
            end
        end

        -- delete blips after mission failed/completed

        if blip and missionData.blip then
            RemoveBlip(blip)
        end

        inMissions = false -- be sure to set inMissions = false after mission failed/completed

        -- respawn random player after failed/completed, coords can be added/edited in config/missions.lua

        if missionData.endMissionRespawn then
            respawnPlayerFromMission(missionData.respawnRandomPos, missionData.success)
        end

        -- destroy mission overlay;
        overlayFunctions.destroyMissionOverlay();

        -- be sure to add this TriggerServerEvent to update player statistics and give reward if missionData.success = true;

        TriggerServerEvent('axr_gang-activities:finishMission', missionData, playerTempStats)
    end)
end

-- be sure to add this code line if you want to crete the mission;

-- exports['axr_gang-activities']:insertMultiplayerMission(missionId, missionFunction);

```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Sync the logic between these files.

Now that you have created all three files, you need to implement the logic that will make the code behave like a mission.

**Recommendation:**

* Store all mission data such as coordinates, rewards, ped/vehicle/prop codes inside the mission’s config table.
* Create entities on the **server-side**.
* Add entities to the global `entities` table using `table.insert(entities, entity)`.
* Return all necessary mission information inside the `extraData` table so it can be used later on the client-side.
* On the client-side, ensure that entity control is assigned to a single client.
* Use boolean parameters for tasks, or apply simple checks inside the task’s `completeFunction`.
* Follow the existing templates to avoid issues with blip creation, mission overlay handling (start/stop), etc.
* Use logical expressions to detect whether the mission has failed or not.
* At the end of the mission, make sure to remove temporary blips, destroy the mission overlay, and clean up client-side entities. (On the server-side, entities are automatically removed.)

{% hint style="danger" %}
There is no need to manually set **XP, level, rewards, or statistics** at the end of a mission.\
Instead, use the **default templates** provided within the script or documentation and follow the corresponding instructions.
{% endhint %}
{% endstep %}
{% endstepper %}


# Exports/Events

Gang-Activities provides various export functions for easy integration with other scripts on your server. These functions enable you to enhance and customize your server experience effortlessly. Explore the options available and utilize Gang-Activities exports to elevate your server's capabilities.


# Server Exports

### getAccessCode

```lua
-- it returns string
local pincode = exports['gang-activities']:getAccessCode();
```

### openMenu

```lua
---@params
---identifier string
exports['gang-activities']:openMenu(identifier);
```

### giveUserXp

```lua
---@params
---identifier string
---xp integer
exports['gang-activities']:giveUserXp(identifier, xp);

```


# Client Exports

### buildMissionOverlay

```lua
-- it returns string
-- missionData is used in client missions, you can see the templates in client/custom/missions/
exports['gang-activities']:buildMissionOverlay(missionData);
```

### destroyMissionOverlay

```lua
---@params
exports['gang-activities']:destroyMissionOverlay();
```

### toogleMissionOverlay

```lua
exports['gang-activities']:toogleMissionOverlay();
```


# Common Issues

### List of common issue

<details>

<summary>❌ Error not exist in client-side | ❌ Error getting entity control</summary>

If you encounter one of the following errors:

* ❌ *Error not exist in client-side*
* ❌ *Error getting entity control*

First, check if you are using **OneSync** and **OneSync Infinity**.

We also recommend adding the following settings to your `server.cfg`:

```
onesync on
onesync_enableInfinity 1
onesync_distanceCullVehicles true
onesync_forceMigration true
onesync_population false
onesync_distanceCulling false
```

If the issue still persists while running a custom mission you created, use the **debug method** to verify that the transmitted data (such as `networkId`, `entityId`, etc.) is valid.

</details>

<details>

<summary>Error: Please update your config/config.lua file for Config.Sql (sql/nonsql)</summary>

If you encounter this error message in the **server console**, go to `config/config.lua` and choose your database type: **SQL** or **NoSQL**.

{% hint style="danger" %}
The **NoSQL option is only intended for MongoDB**.\
In 99% of cases, you should leave the default option set to **SQL**.
{% endhint %}

</details>


